Skip to content
Snippets Groups Projects
Commit 8786f076 authored by Yusuf Akgül's avatar Yusuf Akgül :hatching_chick:
Browse files

Merge branch 'mobile' into 'main'

Mobile

See merge request !52
parents d326e4bf 18c20828
No related branches found
No related tags found
1 merge request!52Mobile
Pipeline #40101 canceled
Showing
with 297 additions and 67 deletions
import Sort from "@/components/filter-sort-games"
import { PopoverSort } from "@/components/filter-sort-games-mobile"
import { GlobalLayout } from "@/components/global-layout"
import { InfiniteScrollGames } from "@/components/infinity-scroll"
import ScrollToTop from "@/components/scroll-to-top"
......@@ -10,12 +11,19 @@ export default async function GamesPage() {
mainContent={
<>
<ScrollToTop />
<InfiniteScrollGames />
<div className="md:hidden">
<PopoverSort />
</div>
</>
}
sideContent={
<Sort />
<div className="hidden md:block">
<Sort />
</div>
}
/>
)
......
......@@ -14,9 +14,9 @@ export default function AddGameDropdown(props: { gameid: string, fullUser: User
</SelectTrigger>
<SelectContent>
<SelectGroup className="space-y-3 w-full">
<AddGameToPlanList user={props.fullUser!} gameId={props.gameid} />
<AddGameToFinishedList user={props.fullUser!} gameId={props.gameid} />
<AddGameToPlayingList user={props.fullUser!} gameId={props.gameid} />
<AddGameToPlanList user={props.fullUser!} gameId={props.gameid} />
</SelectGroup>
</SelectContent>
</Select>
......
......@@ -47,7 +47,7 @@ export default function AddGameToFavList(props: { userGameList: Number[], gameId
}
let button = <div></div>
let button = <></>
try {
if (!props.userGameList.includes(parseFloat(props.gameId))) {
button = (
......
......@@ -221,7 +221,7 @@ export const CreateGweet = ({
render={({ field }) => (
<FormItem>
<FormControl>
<Textarea
<Textarea id="gweet"
placeholder={placeholder || isComment ? "Gweet your reply" : "What's on your mind?"}
className="resize-none min-h-[100px]"
disabled={isLoading || !session.user}
......
import { Button } from "@/components/ui/button"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
import Sort from "./filter-sort-games"
import { Icons } from "./icons"
export function PopoverSort() {
return (
<div className="fixed z-[60] bottom-0 right-0 py-20 px-6 md:hidden">
<Popover>
<div className="w-14 h-14 rounded-full bg-secondary">
<PopoverTrigger asChild className="flex justify-end">
<Button variant="secondary" className="w-14 h-14 rounded-full">
<Icons.filter />
</Button>
</PopoverTrigger>
</div>
<PopoverContent className="w-60 m-1">
<Sort />
</PopoverContent>
</Popover>
</div>
)
}
\ No newline at end of file
......@@ -8,17 +8,22 @@ import { Button } from "./ui/button"
import { Card } from "./ui/card"
export default function Sort() {
const [selectedCategory, setSelectedCategory] = useState('')
const [selectedGenre, setSelectedGenre] = useState('')
const [selectedPlatform, setSelectedPlatform] = useState('')
const [selectedSortMethod, setSelectedSortMethod] = useState('total_rating_count')
const [selectedSortOrder, setSelectedSortOrder] = useState('desc')
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()
const search = searchParams.get('search')
const cat = searchParams.get('category')
const gen = searchParams.get('genre')
const plat = searchParams.get('platform')
const sortm = searchParams.get('sortby')
const sorto = searchParams.get('order')
const [selectedCategory, setSelectedCategory] = useState(cat || '')
const [selectedGenre, setSelectedGenre] = useState(gen || '')
const [selectedPlatform, setSelectedPlatform] = useState(plat || '')
const [selectedSortMethod, setSelectedSortMethod] = useState(sortm || 'total_rating_count')
const [selectedSortOrder, setSelectedSortOrder] = useState(sorto || 'desc')
const urlParams = {
search: search ? `search=${search}` : '',
......@@ -36,11 +41,11 @@ export default function Sort() {
const url = `${pathname}${queryParamString ? `${queryParamString}` : ''}`
// useEffect(() => {
// if (queryParamString) {
router.replace(url)
// }
// }, [queryParamString, router, url]);
useEffect(() => {
if (url) {
router.push(url) // needs fix for after game details page
}
}, [router, url])
function toggleSortOrder() {
const newSortOrder = selectedSortOrder === 'desc' ? 'asc' : 'desc'
......@@ -80,7 +85,7 @@ export default function Sort() {
<SelectTrigger className={`bg-background border-none w-full ${selectedGenre[0] ? 'font-extrabold' : ''}`}>
<SelectValue placeholder="By genre..." />
</SelectTrigger>
<SelectContent>
<SelectContent className="max-h-52">
<SelectGroup>
<SelectLabel>Genre</SelectLabel>
<SelectItem value="fighting">Fighting</SelectItem>
......@@ -88,11 +93,9 @@ export default function Sort() {
<SelectItem value="music">Music</SelectItem>
<SelectItem value="platform">Platformer</SelectItem>
<SelectItem value="puzzle">Puzzle</SelectItem>
<SelectItem value="real-time-strategy-rts">Real Time Strategy</SelectItem>
<SelectItem value="role-playing-rpg">Role-playing</SelectItem>
<SelectItem value="simulator">Simulation</SelectItem>
<SelectItem value="sport">Sport</SelectItem>
<SelectItem value="turn-based-strategy-tbs">Turn-based strategy</SelectItem>
<SelectItem value="tactical">Tactical</SelectItem>
<SelectItem value="quiz-trivia">Quiz/Trivia</SelectItem>
<SelectItem value="hack-and-slash-beat-em-up">Hack and slash</SelectItem>
......@@ -100,7 +103,6 @@ export default function Sort() {
<SelectItem value="adventure">Adventure</SelectItem>
<SelectItem value="arcade">Arcade</SelectItem>
<SelectItem value="visual-novel">Visual Novel</SelectItem>
<SelectItem value="card-and-board-game">Card & Board Game</SelectItem>
<SelectItem value="adventure">Adventure</SelectItem>
<SelectItem value="moba">Moba</SelectItem>
<SelectItem value="point-and-click">Point-and-click</SelectItem>
......
......@@ -8,9 +8,7 @@ import {
Check,
ChevronLeft,
ChevronRight,
CreditCard,
File,
FileText,
Filter,
Gamepad2,
Github,
Heart,
......@@ -23,12 +21,10 @@ import {
Loader2,
LucideProps,
MapPin,
Menu,
MessageCircle,
Moon,
MoreHorizontal,
MoreVertical,
Pizza,
Plus,
Repeat2,
Settings,
SunMedium,
......@@ -88,20 +84,16 @@ export const Icons = {
trash: Trash, // Delete Button
link: Link, // Link Button
regweet: Repeat2, // Regweet Button
location: MapPin, // Location Button
website: Link2, // Website Button
calendar: CalendarRange, // Calendar Button
camera: SwitchCamera, // Change Image Button
terminal: TerminalIcon, // Terminal Icon
post: FileText,
page: File,
media: Image,
billing: CreditCard,
ellipsis: MoreVertical,
add: Plus,
location: MapPin, // Location on Profile
website: Link2, // Website on Profile
calendar: CalendarRange, // Joined Date on Profile
camera: SwitchCamera, // Change Profile Image Button
terminal: TerminalIcon, // Terminal Error Icon
menu: Menu, // Mobile Menu Icon
media: Image, // Media Gweet Icon
filter: Filter, // Filter Games Icon
warning: AlertTriangle,
arrowRight: ArrowRight,
pizza: Pizza,
laptop: Laptop,
check: Check,
}
\ No newline at end of file
"use client"
import { Icons } from "@/components/icons"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
......
import Link from "next/link"
import { cn } from "@/lib/utils"
import { User } from "next-auth"
import Link from "next/link"
import { PopoverSort } from "./filter-sort-games-mobile"
import { GameUnityLogo } from "./logo"
import { MobileSheet } from "./nav-mobile-sheet"
import SearchInput from "./search/components/search-input"
import { Button, buttonVariants } from "./ui/button"
import { Button } from "./ui/button"
import { UserAccountDropdown } from "./user-nav"
export const Header = ({ user }: { user: User | undefined }) => {
......@@ -21,8 +21,8 @@ export const Header = ({ user }: { user: User | undefined }) => {
<SearchInput className="p-3 w-3/6 2xl:w-2/6" />
{user ?
<div className={cn(buttonVariants({ variant: "ghost", size: "logo" }))}>
{user &&
<div className="hidden md:block">
<UserAccountDropdown
user={{
name: user.name,
......@@ -31,10 +31,13 @@ export const Header = ({ user }: { user: User | undefined }) => {
}}
/>
</div>
:
<span className="w-12" />
}
<div className="md:hidden">
<MobileSheet user={user} />
</div>
</div>
</header>
)
}
\ No newline at end of file
"use client"
import { Icons } from "@/components/icons"
import { Button, buttonVariants } from "@/components/ui/button"
import {
Sheet,
SheetClose,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/components/ui/sheet"
import { cn } from "@/lib/utils"
import { User } from "next-auth"
import { signOut } from "next-auth/react"
import Link from "next/link"
import { GameUnityLogo } from "./logo"
import { ModeToggle } from "./mode-toggle"
import { NavPromo } from "./nav-promo"
import { UserAvatar } from "./user-avatar"
export const MobileSheet = ({ user }: { user: User | undefined }) => {
return (
<Sheet>
<SheetTrigger asChild>
<div className={cn(buttonVariants({ variant: "ghost", size: "logo" }), "w-12 cursor-pointer")}>
{user ?
<UserAvatar
user={{ username: user.username ?? null, image: user.image ?? null }}
className="h-8 w-8"
/>
:
<Icons.menu />}
</div>
</SheetTrigger>
<SheetContent>
{!user ?
<div className="flex flex-col justify-center items-center h-full space-y-6">
<GameUnityLogo />
<NavPromo />
</div>
:
<div className="flex flex-col justify-center items-center h-full">
<SheetClose asChild>
<Link href={`${user.username}`} className="flex flex-col justify-center items-center">
<UserAvatar
user={{ username: user.username ?? null, image: user.image ?? null }}
className="h-16 w-16"
/>
<SheetHeader className="pt-6">
<SheetTitle>{user.name}</SheetTitle>
<SheetDescription className="text-sky-500 text-center">
@{user.username}
</SheetDescription>
</SheetHeader>
</Link>
</SheetClose>
<div className="flex flex-col items-center space-y-6 pt-12">
<SheetClose asChild>
<Link href="/settings" className={cn(buttonVariants({ variant: "outline", size: "lg" }), "w-full")}>
Settings
</Link>
</SheetClose>
<Button variant="outline" size="lg"
onClick={(event) => {
event.preventDefault()
signOut({
callbackUrl: `${window.location.origin}/login`,
})
}}
>
Sign out
</Button>
<ModeToggle />
</div>
</div>
}
</SheetContent>
</Sheet>
)
}
\ No newline at end of file
import { buttonVariants } from "@/components/ui/button"
import { cn } from "@/lib/utils"
import Link from "next/link"
export const NavPromo = () => {
return (
<div className="space-y-6 justify-center text-center">
<Link href="/login" className={cn(buttonVariants({ size: "lg" }), "w-full")}>
Log In
</Link>
<Link href="/signup" className={cn(buttonVariants({ size: "lg", variant: "outline" }), "w-full")}>
Sign Up
</Link>
<h4 className="text-sm">
Unlock endless possibilities - sign up or log in to unleash the full potential of our website.
</h4>
</div>
)
}
\ No newline at end of file
......@@ -8,6 +8,7 @@ import { User } from "next-auth"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { ModeToggle } from "./mode-toggle"
import { NavPromo } from "./nav-promo"
export const Sidebar = ({ items, user }: { items: SidebarNavItem[], user: User | undefined }) => {
const path = usePathname()
......@@ -43,19 +44,11 @@ export const Sidebar = ({ items, user }: { items: SidebarNavItem[], user: User |
)
})}
<ModeToggle />
{!user && (
<div className="mt-24 space-y-3 justify-center text-center">
<Link href="/login" className={cn(buttonVariants({ size: "lg" }), "w-full")}>
Log In
</Link>
<Link href="/signup" className={cn(buttonVariants({ size: "lg", variant: "outline" }), "w-full")}>
Sign Up
</Link>
<h4 className="text-sm">
Unlock endless possibilities - sign up or log in to unleash the full potential of our website.
</h4>
{!user &&
<div className="mt-24">
<NavPromo />
</div>
)}
}
</nav>
)
}
\ No newline at end of file
......@@ -19,6 +19,7 @@ import { Label } from "@/components/ui/label"
import { Textarea } from "@/components/ui/textarea"
import { toast } from "@/components/ui/use-toast"
import { UserAvatar } from "@/components/user-avatar"
import { checkURL } from "@/lib/utils"
import { zodResolver } from "@hookform/resolvers/zod"
import Image from "next/image"
import { useRouter } from "next/navigation"
......@@ -32,7 +33,9 @@ const FormSchema = z.object({
name: z.string().min(1, "Name can't be blank").max(50, "Name can't be more than 50 characters"),
bio: z.string().max(160, "Bio can't be more than 160 characters").optional(),
location: z.string().max(30, "Location can't be more than 30 characters").optional(),
website: z.string().max(100, "Website can't be more than 100 characters").optional(),
website: z.string().max(100, "Website can't be more than 100 characters")
.refine((value) => checkURL(value), "Website needs to be a valid URL.")
.optional().or(z.literal('')),
})
const ImagesSchema = z.custom<File>().refine((file) => file instanceof File, { message: "Expected a file" })
......@@ -69,6 +72,10 @@ export const EditProfileModal = ({ user }: { user: IUser }) => {
async function onSave(formData: z.infer<typeof FormSchema>) {
if (!user) return null
if (formData.website && (!formData.website.startsWith("http://") || !formData.website.startsWith("https://"))) {
formData.website = `http://${formData.website}`
}
const dirty = form.formState.dirtyFields
const profile: IProfile = {
name: formData.name,
......
......@@ -72,7 +72,9 @@ export const ProfileUserInfo = async ({ user }: { user: IUser }) => {
{user.website &&
<div className="flex items-center">
<Icons.website className="mr-1" />
<div>{user.website}</div>
<Link href={user.website} prefetch={false} target="_blank">
{user.website.replace("http://", "").replace("https://", "").replace("www.", "")}
</Link>
</div>
}
{user.createdAt && <UserJoinDate date={user.createdAt} />}
......
......@@ -35,7 +35,7 @@ export const UserGames = async ({ username }: { username: string }) => {
finishedGames = fetchedGames.filter(game => fullUser.finishedGameList.includes(game.id))
planningGames = fetchedGames.filter(game => fullUser.planningGameList.includes(game.id))
}
console.log(favoritegames)
// Old Implementation
// if (fullUser?.favGameList?.length !== 0 && fullUser?.favGameList?.length != undefined) {
// favoritegames = await getFavoriteGames(fullUser?.favGameList!)
......
......@@ -46,6 +46,7 @@ export default function SearchInput({ className, ...props }: DocsSearchProps) {
{...props}
>
<Input
id='search'
type="search"
placeholder="Search..."
className="rounded-full pr-12"
......
"use client"
import * as PopoverPrimitive from "@radix-ui/react-popover"
import * as React from "react"
import { cn } from "@/lib/utils"
const Popover = PopoverPrimitive.Root
const PopoverTrigger = PopoverPrimitive.Trigger
const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
</PopoverPrimitive.Portal>
))
PopoverContent.displayName = PopoverPrimitive.Content.displayName
export { Popover, PopoverContent, PopoverTrigger }
......@@ -11,6 +11,8 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { UserAvatar } from "@/components/user-avatar"
import { cn } from "@/lib/utils"
import { buttonVariants } from "./ui/button"
type UserUsername = string | null | undefined
......@@ -21,13 +23,15 @@ interface UserAccountNavProps extends React.HTMLAttributes<HTMLDivElement> {
export function UserAccountDropdown({ user }: UserAccountNavProps) {
return (
<DropdownMenu>
<DropdownMenuTrigger className="rounded-full">
<UserAvatar
user={{ username: user.username ? user.username : "", image: user.image || null }}
className="h-8 w-8 shadow-none focus:shadow-none"
/>
<DropdownMenuTrigger>
<div className={cn(buttonVariants({ variant: "ghost", size: "logo" }), "w-12")}>
<UserAvatar
user={{ username: user.username ? user.username : "", image: user.image || null }}
className="h-8 w-8 shadow-none focus:shadow-none"
/>
</div>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="mt-1">
<DropdownMenuContent align="end">
<div className="flex items-center justify-start gap-2 p-2">
<div className="flex flex-col space-y-1 leading-none">
{user.name && <p className="font-medium">{user.name}</p>}
......
......@@ -55,4 +55,23 @@ export default function getURL(path: string) {
? env.NEXT_PUBLIC_APP_URL!
: window.location.origin
return new URL(path, baseURL).toString()
}
export function checkURL(value: string) {
if (!value) return true
const pattern = new RegExp(
'^(https?:\\/\\/)?' + // protocol
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name
'((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path
'(\\?[;&a-z\\d%_.~+=-]*)?' + // query string
'(\\#[-a-z\\d_]*)?$', // fragment locator
'i'
)
try {
const url = new URL(`${(value.startsWith("http://") || value.startsWith("https://")) ? value : `http://${value}`}`)
return pattern.test(url.href)
} catch {
return false
}
}
\ No newline at end of file
......@@ -19,6 +19,7 @@
"@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-hover-card": "^1.0.6",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-popover": "^1.0.6",
"@radix-ui/react-scroll-area": "^1.0.4",
"@radix-ui/react-select": "^1.2.2",
"@radix-ui/react-separator": "^1.0.3",
......@@ -2162,6 +2163,43 @@
}
}
},
"node_modules/@radix-ui/react-popover": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.0.6.tgz",
"integrity": "sha512-cZ4defGpkZ0qTRtlIBzJLSzL6ht7ofhhW4i1+pkemjV1IKXm0wgCRnee154qlV6r9Ttunmh2TNZhMfV2bavUyA==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/primitive": "1.0.1",
"@radix-ui/react-compose-refs": "1.0.1",
"@radix-ui/react-context": "1.0.1",
"@radix-ui/react-dismissable-layer": "1.0.4",
"@radix-ui/react-focus-guards": "1.0.1",
"@radix-ui/react-focus-scope": "1.0.3",
"@radix-ui/react-id": "1.0.1",
"@radix-ui/react-popper": "1.1.2",
"@radix-ui/react-portal": "1.0.3",
"@radix-ui/react-presence": "1.0.1",
"@radix-ui/react-primitive": "1.0.3",
"@radix-ui/react-slot": "1.0.2",
"@radix-ui/react-use-controllable-state": "1.0.1",
"aria-hidden": "^1.1.1",
"react-remove-scroll": "2.5.5"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-popper": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.2.tgz",
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment