diff --git a/app/(content)/(community)/communities/page.tsx b/app/(content)/(community)/communities/page.tsx deleted file mode 100644 index 7a1e2cfa04c7c5585326afa281e80d9b89cc765d..0000000000000000000000000000000000000000 --- a/app/(content)/(community)/communities/page.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export default function CommunitiesPage() { - return ( - <div> - <h1>Community WIP</h1> - </div> - ) -} \ No newline at end of file diff --git a/app/(content)/(search)/search/page.tsx b/app/(content)/(search)/search/page.tsx new file mode 100644 index 0000000000000000000000000000000000000000..cb8d68edd738d54202a2ea8066adcaf25b04772a --- /dev/null +++ b/app/(content)/(search)/search/page.tsx @@ -0,0 +1,19 @@ +import { GlobalLayout } from "@/components/global-layout" + +export default async function SearchPage() { + return ( + <GlobalLayout + mainContent={ + <> + Search Page + </> + } + + sideContent={ + <> + + </> + } + /> + ) +} \ No newline at end of file diff --git a/app/(content)/(user)/[userid]/(profile)/followers/page.tsx b/app/(content)/(user)/[userid]/(profile)/followers/page.tsx deleted file mode 100644 index 4c4cc4597bb4c954f24bb86c4cb6a19a8f08dd35..0000000000000000000000000000000000000000 --- a/app/(content)/(user)/[userid]/(profile)/followers/page.tsx +++ /dev/null @@ -1,62 +0,0 @@ - -import { GlobalLayout } from "@/components/global-layout" -import { UserAvatar } from "@/components/user-avatar" -import { db } from "@/lib/db" -import { User } from "@prisma/client" -import Link from "next/link" - -export default async function UserFollowers({ params }: { params: { userid: string } }) { - - const fullUser = await db.user.findFirst({ - where: { - username: params.userid - }, - include: { - following: true, - followers: true - } - }) - - const followers = await db.user.findMany({ - where: { - following: { - every: { - followerId: { not: undefined } - } - }, - followers: { - every: { - followingId: fullUser?.id - } - } - } - }) - - return ( - <GlobalLayout - mainContent={ - <div className="flex flex-col w-full"> - {followers?.map((follower: User) => ( - <div className="flex items-center space-y-6" key={follower.id}> - <Link href={`/${follower.username}`} className="flex flex-row"> - <UserAvatar - user={{ username: follower.username, image: follower.image }} - className="h-10 w-10" - /> - - <div className="flex flex-col ml-3"> - <span className="font-bold">{follower.name}</span> - <span className="text-sky-500 text-sm">@{follower.username}</span> - </div> - </Link> - - <div className="ml-auto"> - {/* Followbutton */} - </div> - </div> - ))} - </div> - } - /> - ) -} \ No newline at end of file diff --git a/app/(content)/(user)/[userid]/(profile)/likes/page.tsx b/app/(content)/(user)/[userid]/(profile)/likes/page.tsx deleted file mode 100644 index d1050ff370eab7ae89abaf9012a3c9d499129093..0000000000000000000000000000000000000000 --- a/app/(content)/(user)/[userid]/(profile)/likes/page.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export default async function Likes() { - return ( - <div> - <h1>Likes Page WIP</h1> - </div> - ) -} \ No newline at end of file diff --git a/app/(content)/(user)/[userid]/(profile)/page.tsx b/app/(content)/(user)/[userid]/(profile)/page.tsx deleted file mode 100644 index d60b12fe5162b14c03e21f932c9902ad9170be22..0000000000000000000000000000000000000000 --- a/app/(content)/(user)/[userid]/(profile)/page.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import FollowButton from "@/components/following-button" -import GameItem from "@/components/game-item" -import { GlobalLayout } from "@/components/global-layout" -import { AspectRatio } from "@/components/ui/aspect-ratio" -import { Card } from "@/components/ui/card" -import { Skeleton } from "@/components/ui/skeleton" -import { UserAvatar } from "@/components/user-avatar" -import { db } from "@/lib/db" -import { getFavoriteGames } from "@/lib/igdb" -import { getCurrentUser } from "@/lib/session" -import { IGame } from "@/types/igdb-types" -import { redirect } from "next/navigation" - - -export default async function User({ params }: { params: { userid: string } }) { - const user = await getCurrentUser() - - const sessionUser = await db.user.findFirst({ - where: { - id: user?.id - }, - include: { - following: true, - followers: true - } - }) - - const fullUser = await db.user.findFirst({ - where: { - username: params.userid - }, - include: { - following: true, - followers: true - } - }) - - if (!fullUser) { - redirect('/home') - } - - let favoritegames = undefined - let playingGames = undefined - let finishedGames = undefined - let planningGames = undefined - - if (fullUser?.favGameList?.length !== 0 && fullUser?.favGameList?.length != undefined) { - favoritegames = await getFavoriteGames(fullUser?.favGameList!) - } - if (fullUser?.playingGameList?.length !== 0) { - playingGames = await getFavoriteGames(fullUser?.playingGameList!) - } - if (fullUser?.finishedGameList?.length !== 0) { - finishedGames = await getFavoriteGames(fullUser?.finishedGameList!) - } - if (fullUser?.planningGameList?.length !== 0) { - planningGames = await getFavoriteGames(fullUser?.planningGameList!) - } - - return ( - <GlobalLayout - mainContent={ - <div className="space-y-6 w-full"> - <Card className="overflow-hidden"> - <div className="h-64 overflow-hidden"> - <AspectRatio ratio={889 / 500} className="bg-slate-600 dark:bg-slate-400"> - {/* profile banner */} - {/* <Image - src={ } - alt={ } - fill - priority - className="object-center" /> */} - </AspectRatio> - </div> - <div className="p-6 md:p-12 ss:flex"> - <UserAvatar - user={{ username: fullUser.username, image: fullUser.image || null }} - className="h-52 w-52 -mt-36" - /> - <div className="ml-6 md:ml-12 space-y-3"> - <h1 className="text-2xl font-bold">{fullUser.name}</h1> - <h1 className="text-md text-sky-500">@{fullUser.username}</h1> - {/* <h1 className="pt-3">{fullUser.bio}</h1> */} - </div> - <div className="flex justify-start ml-6 md:ml-12 space-y-3"> - <FollowButton user={sessionUser!} followingId={fullUser?.id!} /> - </div> - </div> - - <div className="px-6 md:px-12"> - {/* <div className="border-b border-gray-400 dark:border-gray-200" /> */} - {/* gweets */} - </div> - </Card > - - <Card className="overflow-hidden p-6 md:p-12" > - <h1 className="text-2xl font-bold pb-3">Favorite Games</h1> - <div className="grid grid-cols-1 ss:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-4 lg:gap-8 items-center"> - {favoritegames ? favoritegames.map((game: IGame) => ( - <GameItem id={game.id} name={game.name} cover={game.cover} key={game.id} /> - )) - : - <p>No favorites currently...</p>} - </div> - </Card> - - <Card className="overflow-hidden p-6 md:p-12" > - <h1 className="text-2xl font-bold pb-3">Currently playing</h1> - <div className="grid grid-cols-1 ss:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-4 lg:gap-8 items-center"> - {playingGames ? playingGames.map((game: IGame) => ( - <GameItem id={game.id} name={game.name} cover={game.cover} key={game.id} /> - )) - : - <p>Currently not playing any games...</p>} - </div> - </Card> - - <Card className="overflow-hidden p-6 md:p-12" > - <h1 className="text-2xl font-bold pb-3">Planning to play</h1> - <div className="grid grid-cols-1 ss:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-4 lg:gap-8 items-center"> - {planningGames ? planningGames.map((game: IGame) => ( - <GameItem id={game.id} name={game.name} cover={game.cover} key={game.id} /> - )) - : - <p>Currently not planning to play any games...</p>} - </div> - </Card> - - <Card className="overflow-hidden p-6 md:p-12" > - <h1 className="text-2xl font-bold pb-3">Finished Games</h1> - <div className="grid grid-cols-1 ss:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-4 lg:gap-8 items-center"> - {finishedGames ? finishedGames.map((game: IGame) => ( - <GameItem id={game.id} name={game.name} cover={game.cover} key={game.id} /> - )) - : - <p>No finished games...</p>} - </div> - </Card> - </div> - } - - sideContent={ - <Card className="p-6 grid items-start gap-2 bg-secondary"> - <h1>Media</h1> - <div className="grid grid-cols-1 gap-4"> - {Array.from({ length: 2 }, (_, i) => i + 1).map((i) => { - return ( - <Skeleton key={i} className="aspect-[264/374] bg-gray-300" /> - ) - })} - </div> - </Card> - } - /> - ) -} \ No newline at end of file diff --git a/app/(content)/(user)/[username]/(profile)/games/page.tsx b/app/(content)/(user)/[username]/(profile)/games/page.tsx new file mode 100644 index 0000000000000000000000000000000000000000..802d5a5df55780ae74012fc3bb61b173eb9fe3e6 --- /dev/null +++ b/app/(content)/(user)/[username]/(profile)/games/page.tsx @@ -0,0 +1,10 @@ +import { UserGames } from "@/components/profile/components/user-games" +import { TabsContent } from "@/components/ui/tabs" + +export default async function Games({ params }: { params: { username: string } }) { + return ( + <TabsContent value="games"> + <UserGames username={params.username} /> + </TabsContent> + ) +} \ No newline at end of file diff --git a/app/(content)/(user)/[username]/(profile)/layout.tsx b/app/(content)/(user)/[username]/(profile)/layout.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f79ac51af8414f111f180c3ba913c54d01be8dbe --- /dev/null +++ b/app/(content)/(user)/[username]/(profile)/layout.tsx @@ -0,0 +1,40 @@ +import { GlobalLayout } from "@/components/global-layout" +import { ProfileNavbar } from "@/components/profile/components/profile-navbar" +import { ProfileSideContent } from "@/components/profile/components/profile-side-content" +import { ProfileUserInfo } from "@/components/profile/components/profile-user-info" +import { Card } from "@/components/ui/card" +import { UserNotFound } from "@/components/user-not-found" +import getURL from "@/lib/utils" + +// export const dynamic = 'force-dynamic' +// export const fetchCache = 'force-no-store' + +export default async function ProfileLayout({ + params, + children, +}: { + params: { username: string } + children: React.ReactNode +}) { + const user = await fetch(getURL(`/api/users/${params.username}`)).then((result) => result.json()) + + return ( + <GlobalLayout + mainContent={ + <Card className="overflow-hidden h-full w-full"> + {!user ? + <UserNotFound /> + : + <> + <ProfileUserInfo user={user} /> + <ProfileNavbar param={params.username}> + {children} + </ProfileNavbar> + </> + } + </Card> + } + sideContent={<ProfileSideContent />} + /> + ) +} \ No newline at end of file diff --git a/app/(content)/(user)/[username]/(profile)/likes/page.tsx b/app/(content)/(user)/[username]/(profile)/likes/page.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4d506758e5217df9ca8db3d7605187210576891e --- /dev/null +++ b/app/(content)/(user)/[username]/(profile)/likes/page.tsx @@ -0,0 +1,13 @@ +import { UserGweets } from "@/components/profile/components/user-gweets" +import { TabsContent } from "@/components/ui/tabs" +import { getCurrentUser } from "@/lib/session" + +export default async function Likes({ params }: { params: { username: string } }) { + const session = await getCurrentUser() + + return ( + <TabsContent value="likes"> + <UserGweets username={params.username} sessionname={session?.username as string} /> + </TabsContent> + ) +} \ No newline at end of file diff --git a/app/(content)/(user)/[username]/(profile)/media/page.tsx b/app/(content)/(user)/[username]/(profile)/media/page.tsx new file mode 100644 index 0000000000000000000000000000000000000000..05724bd0ad11312382de9dd368b7fa59e7b61c81 --- /dev/null +++ b/app/(content)/(user)/[username]/(profile)/media/page.tsx @@ -0,0 +1,13 @@ +import { UserGweets } from "@/components/profile/components/user-gweets" +import { TabsContent } from "@/components/ui/tabs" +import { getCurrentUser } from "@/lib/session" + +export default async function Media({ params }: { params: { username: string } }) { + const session = await getCurrentUser() + + return ( + <TabsContent value="media"> + <UserGweets username={params.username} sessionname={session?.username as string} /> + </TabsContent> + ) +} \ No newline at end of file diff --git a/app/(content)/(user)/[username]/(profile)/page.tsx b/app/(content)/(user)/[username]/(profile)/page.tsx new file mode 100644 index 0000000000000000000000000000000000000000..35111b1f5f4208ad3223b38a8fce56d510054e8f --- /dev/null +++ b/app/(content)/(user)/[username]/(profile)/page.tsx @@ -0,0 +1,13 @@ +import { UserGweets } from "@/components/profile/components/user-gweets" +import { TabsContent } from "@/components/ui/tabs" +import { getCurrentUser } from "@/lib/session" + +export default async function Gweets({ params }: { params: { username: string } }) { + const session = await getCurrentUser() + + return ( + <TabsContent value="gweets"> + <UserGweets username={params.username} sessionname={session?.username as string} /> + </TabsContent> + ) +} \ No newline at end of file diff --git a/app/(content)/(user)/[userid]/(profile)/following/page.tsx b/app/(content)/(user)/[username]/followers/page.tsx similarity index 100% rename from app/(content)/(user)/[userid]/(profile)/following/page.tsx rename to app/(content)/(user)/[username]/followers/page.tsx diff --git a/app/(content)/(user)/[username]/following/page.tsx b/app/(content)/(user)/[username]/following/page.tsx new file mode 100644 index 0000000000000000000000000000000000000000..5ea8f4daa5c5fef29b079a07c60284b12ab27cba --- /dev/null +++ b/app/(content)/(user)/[username]/following/page.tsx @@ -0,0 +1,7 @@ +export default async function Following() { + return ( + <div> + <h1>Following Page WIP</h1> + </div> + ) +} \ No newline at end of file diff --git a/app/(content)/(user)/[userid]/status/[id]/page.tsx b/app/(content)/(user)/[username]/status/[id]/page.tsx similarity index 100% rename from app/(content)/(user)/[userid]/status/[id]/page.tsx rename to app/(content)/(user)/[username]/status/[id]/page.tsx diff --git a/app/(content)/layout.tsx b/app/(content)/layout.tsx index 9f3fedee59a59c10d952f8fde755d0f6d05d2e98..a868a706afdb2837acc0b38966bae28a5ec8f559 100644 --- a/app/(content)/layout.tsx +++ b/app/(content)/layout.tsx @@ -3,12 +3,11 @@ import { Sidebar } from "@/components/nav-sidebar" import { dashboardConfig } from "@/lib/config/dashboard" import { getCurrentUser } from "@/lib/session" -interface DashboardLayoutProps { - children?: React.ReactNode -} export default async function ContentLayout({ children, -}: DashboardLayoutProps) { +}: { + children: React.ReactNode +}) { const user = await getCurrentUser() return ( diff --git a/app/api/followers/route.ts b/app/api/followers/route.ts deleted file mode 100644 index cc327201fc2833606e01beb869bff9515f15ddb7..0000000000000000000000000000000000000000 --- a/app/api/followers/route.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { db } from "@/lib/db" -import { getCurrentUser } from "@/lib/session" -import { revalidatePath } from "next/cache" -import { NextRequest, NextResponse } from "next/server" - -export async function GET(req: NextRequest) { - const sessionUser = await getCurrentUser() - - if (!sessionUser) { - return NextResponse.json({ status: 401, message: 'Unauthorized' }) - } - let follows = undefined - - try { - follows = await db.follows.findMany({ - where: { - followerId: sessionUser.id - } - }) - } catch (error) { - console.log("error", error) - } - - return NextResponse.json({ status: 200, message: "fetched follows", follows }) -} - -export async function PUT(req: NextRequest) { - const sessionUser = await getCurrentUser() - - const data = await req.json() - - if (!sessionUser) { - return NextResponse.json({ status: 401, message: 'Unauthorized' }) - } - - try { - const dbUser = await db.user.findFirst({ - where: { - id: sessionUser.id - } - }) - - const follow = await db.follows.findFirst({ - where: { - followerId: sessionUser.id, - followingId: data.followingId - } - }) - console.log("follow", follow) - - if (follow) { - // User is already following, so unfollow - console.log("delete follow") - const delfollow = await db.follows.delete({ - where: { - followerId_followingId: { - followerId: sessionUser.id, - followingId: data.followingId - } - - } - }) - console.log("del follow:", delfollow) - } else { - // User is not following, so follow - console.log("create follow") - await db.follows.create({ - data: { - followerId: sessionUser.id, - followingId: data.followingId - }, - }) - - } - } catch (error) { - console.log("err", error) - } - const path = req.nextUrl.searchParams.get('path') || '/' - revalidatePath(path) - - return NextResponse.json({ status: 200, message: 'Follow handled' }) -} \ No newline at end of file diff --git a/app/api/games/route.ts b/app/api/games/route.ts index 04d0ee92cb77c31e5331e1d5a346d4e8d440e681..8af8dfb2cf1203f6092531762fdc32620b6532aa 100644 --- a/app/api/games/route.ts +++ b/app/api/games/route.ts @@ -37,7 +37,7 @@ export async function GET(req: NextRequest) { sortby ? sortby : undefined, order ? order : undefined ) - return NextResponse.json(games) + return NextResponse.json(games, { status: 200 }) } catch (error) { return NextResponse.json(error, { status: 500 }) } diff --git a/app/api/gweets/[id]/route.ts b/app/api/gweets/[id]/route.ts index a9c706539844ac3becd0dc6549fb977d1e65391d..13408317200201894c5654d6d433ed0068268005 100644 --- a/app/api/gweets/[id]/route.ts +++ b/app/api/gweets/[id]/route.ts @@ -3,7 +3,6 @@ import { z } from "zod" import { db } from "@/lib/db" -// get a single gweet export async function GET(request: Request, { params }: { params: { id: string } }) { const { id } = params @@ -12,12 +11,10 @@ export async function GET(request: Request, { params }: { params: { id: string } const zod = gweetIdSchema.safeParse(id) if (!zod.success) { - return NextResponse.json( - { - message: "Invalid request body", - error: zod.error.formErrors, - }, { status: 400 }, - ) + return NextResponse.json({ + message: "Invalid request body", + error: zod.error.formErrors, + }, { status: 400 }) } try { @@ -96,20 +93,16 @@ export async function GET(request: Request, { params }: { params: { id: string } }) if (!gweet) { - return NextResponse.json( - { - message: "Gweet not found", - }, { status: 404 }, - ) + return NextResponse.json({ + message: "Gweet not found", + }, { status: 404 }) } return NextResponse.json(gweet, { status: 200 }) } catch (error) { - return NextResponse.json( - { - message: "Something went wrong", - error, - }, { status: 500 }, - ) + return NextResponse.json({ + message: "Something went wrong", + error, + }, { status: 500 }) } } \ No newline at end of file diff --git a/app/api/gweets/likes/route.ts b/app/api/gweets/likes/route.ts index 6c298fed796a99885baa5826724286a7097b2550..f560fe5e44116dc9eea9244939c704be0534ef07 100644 --- a/app/api/gweets/likes/route.ts +++ b/app/api/gweets/likes/route.ts @@ -3,7 +3,6 @@ import { z } from "zod" import { db } from "@/lib/db" -// get likes from user export async function GET(request: Request) { const { searchParams } = new URL(request.url) const user_id = searchParams.get("user_id") || undefined @@ -41,16 +40,13 @@ export async function GET(request: Request) { return NextResponse.json(gweets, { status: 200 }) } catch (error: any) { - return NextResponse.json( - { - message: "Something went wrong", - error: error.message, - }, { status: error.errorCode || 500 }, - ) + return NextResponse.json({ + message: "Something went wrong", + error: error.message, + }, { status: error.errorCode || 500 }) } } -// like and dislike export async function POST(request: Request) { const { gweet_id, user_id } = await request.json() @@ -64,12 +60,10 @@ export async function POST(request: Request) { const zod = likeSchema.safeParse({ gweet_id, user_id }) if (!zod.success) { - return NextResponse.json( - { - message: "Invalid request body", - error: zod.error.formErrors, - }, { status: 400 }, - ) + return NextResponse.json({ + message: "Invalid request body", + error: zod.error.formErrors, + }, { status: 400 }) } try { @@ -87,7 +81,7 @@ export async function POST(request: Request) { }, }) - return NextResponse.json({ message: "Gweet unliked" }) + return NextResponse.json({ message: "Gweet unliked" }, { status: 200 }) } else { await db.like.create({ data: { @@ -96,12 +90,12 @@ export async function POST(request: Request) { }, }) - return NextResponse.json({ message: "Gweet liked" }) + return NextResponse.json({ message: "Gweet liked" }, { status: 201 }) } } catch (error: any) { return NextResponse.json({ message: "Something went wrong", error: error.message, - }) + }, { status: 500 }) } } \ No newline at end of file diff --git a/app/api/gweets/regweets/route.ts b/app/api/gweets/regweets/route.ts index 1cc824193d6af5d418bb82692d660f6a01f709f8..00a446ef38a0c9d8c1c309c4bc75fc2027e96783 100644 --- a/app/api/gweets/regweets/route.ts +++ b/app/api/gweets/regweets/route.ts @@ -16,12 +16,10 @@ export async function POST(request: Request) { const zod = regweetSchema.safeParse({ gweet_id, user_id }) if (!zod.success) { - return NextResponse.json( - { - message: "Invalid request body", - error: zod.error.formErrors, - }, { status: 400 }, - ) + return NextResponse.json({ + message: "Invalid request body", + error: zod.error.formErrors, + }, { status: 400 }) } try { @@ -39,7 +37,7 @@ export async function POST(request: Request) { }, }) - return NextResponse.json({ message: "Deleted gweet regweet" }) + return NextResponse.json({ message: "Deleted gweet regweet" }, { status: 200 }) } else { await db.regweet.create({ data: { @@ -48,7 +46,7 @@ export async function POST(request: Request) { }, }) - return NextResponse.json({ message: "Gweet regweeted" }) + return NextResponse.json({ message: "Gweet regweeted" }, { status: 201 }) } } catch (error: any) { return NextResponse.json({ error: error.message }, { status: 500 }) diff --git a/app/api/gweets/route.ts b/app/api/gweets/route.ts index cf5ae7257c9c264516e04c3eb90afe5d57ace20a..4f5903d9bf64f21bf738dc71399fe9f920160cc0 100644 --- a/app/api/gweets/route.ts +++ b/app/api/gweets/route.ts @@ -4,7 +4,6 @@ import { z } from "zod" import { db } from "@/lib/db" import { utapi } from "uploadthing/server" -// get gweets export async function GET(request: Request) { const { searchParams } = new URL(request.url) @@ -30,9 +29,9 @@ export async function GET(request: Request) { // if (gweet && gweet.replyToGweetId) thread = await fetchThread(gweet.replyToGweetId) // } - // // logic correct TODO + // // logic correct TODO get all gweets above comment // const prevId = thread.length < 4 ? undefined : thread[thread.length - 1].id - // return NextResponse.json({ gweets: thread, prevId }) + // return NextResponse.json({ gweets: thread, prevId }, { status: 200 }) // } const gweets = await db.gweet.findMany({ @@ -53,20 +52,35 @@ export async function GET(request: Request) { }), ...(type === "user_gweets" && { - authorId: id, + author: { + username: id, + }, }), ...(type === "user_replies" && { - authorId: id, + author: { + username: id, + }, NOT: { replyToGweetId: null, }, }), + ...(type === "user_media" && { + author: { + username: id, + }, + media: { + some: {}, + }, + }), + ...(type === "user_likes" && { likes: { some: { - userId: id, + user: { + username: id, + }, }, }, }), @@ -113,13 +127,12 @@ export async function GET(request: Request) { const nextId = gweets.length < take ? undefined : gweets[gweets.length - 1].id - return NextResponse.json({ gweets, nextId }) + return NextResponse.json({ gweets, nextId }, { status: 200 }) } catch (error) { - return NextResponse.error() + return NextResponse.json(error, { status: 500 }) } } -// create gweet export async function POST(request: Request) { const { gweet, fileprops } = await request.json() @@ -144,12 +157,10 @@ export async function POST(request: Request) { ) if (!zodGweet.success) { - return NextResponse.json( - { - message: "Invalid request body", - error: zodGweet.error.formErrors, - }, { status: 400 }, - ) + return NextResponse.json({ + message: "Invalid request body", + error: zodGweet.error.formErrors, + }, { status: 400 }) } try { @@ -174,12 +185,10 @@ export async function POST(request: Request) { const zodMedia = mediaSchema.safeParse(mediaArray) if (!zodMedia.success) { - return NextResponse.json( - { - message: "Invalid media body", - error: zodMedia.error.formErrors, - }, { status: 400 }, - ) + return NextResponse.json({ + message: "Invalid media body", + error: zodMedia.error.formErrors, + }, { status: 400 }) } await db.media.createMany({ @@ -187,18 +196,15 @@ export async function POST(request: Request) { }) } - return NextResponse.json(created_gweet, { status: 200 }) + return NextResponse.json(created_gweet, { status: 201 }) } catch (error: any) { - return NextResponse.json( - { - message: "Something went wrong", - error: error.message, - }, { status: error.errorCode || 500 }, - ) + return NextResponse.json({ + message: "Something went wrong", + error: error.message, + }, { status: error.errorCode || 500 }) } } -// delete gweet export async function DELETE(request: Request) { const { searchParams } = new URL(request.url) const id = searchParams.get("id") as string @@ -232,14 +238,12 @@ export async function DELETE(request: Request) { }, }) - return NextResponse.json({ message: "Gweet deleted successfully", }) + return NextResponse.json({ message: "Gweet deleted" }, { status: 200 }) } catch (error: any) { - return NextResponse.json( - { - message: "Something went wrong", - error: error.message, - }, { status: error.errorCode || 500 }, - ) + return NextResponse.json({ + message: "Something went wrong", + error: error.message, + }, { status: error.errorCode || 500 }) } } diff --git a/app/api/hashtags/route.ts b/app/api/hashtags/route.ts index b4ffb8ef97ebee57128b889e83ab2264d6c5aefe..58ee86ebdecd344bc86dd8d6a5d028eb0c72438b 100644 --- a/app/api/hashtags/route.ts +++ b/app/api/hashtags/route.ts @@ -57,18 +57,8 @@ export async function POST(request: Request) { } } - return NextResponse.json( - { - message: "Hashtag(s) created", - }, - { status: 200 }, - ) + return NextResponse.json({ message: "Hashtag(s) created", }, { status: 201 }) } catch (error: any) { - return NextResponse.json( - { - error: error.message, - }, - { status: 500 }, - ) + return NextResponse.json({ error: error.message, }, { status: 500 },) } } \ No newline at end of file diff --git a/app/api/route.ts b/app/api/route.ts index ae6ece829b02260a63fbd9add4bcd171f9e5a215..8672d2778c10343280dc3f6d89d28c530136c270 100644 --- a/app/api/route.ts +++ b/app/api/route.ts @@ -9,5 +9,5 @@ export async function GET() { return new NextResponse(JSON.stringify({ error: 'unauthorized' }), { status: 401 }) } - return NextResponse.json({ authenticated: !!session }) + return NextResponse.json({ authenticated: !!session }, { status: 200 }) } \ No newline at end of file diff --git a/app/api/signup/route.ts b/app/api/signup/route.ts index ffb9dbaa646337cc621f18ecf6cd6a80ce49d71b..d754d92b38ce52c4fbd73c70a34a6baa07b9d891 100644 --- a/app/api/signup/route.ts +++ b/app/api/signup/route.ts @@ -19,7 +19,7 @@ export async function POST(req: Request) { }) if (existingUser) { - throw new Error('email already exists') + return NextResponse.json({ error: 'Email already exists' }, { status: 422 }) } let isUnique = false @@ -46,21 +46,11 @@ export async function POST(req: Request) { } }) + return NextResponse.json({ usernameOrEmail: user.email }) + } catch (error) { return NextResponse.json({ - usernameOrEmail: user.email - }) - } catch (err: any) { - if (err.message === 'email already exists') { - return new NextResponse(JSON.stringify({ - error: err.message - }), { status: 422 } - ) - } - - return new NextResponse( - JSON.stringify({ - error: err.message - }), { status: 500 } - ) + message: "Something went wrong", + error, + }, { status: 500 }) } } \ No newline at end of file diff --git a/app/api/uploadthing/core.ts b/app/api/uploadthing/core.ts index 3652b13a059642ebb2aca3d32ea7e3ccf7d15291..fbb5b7f38f093a56544208c9935e4c0a846011ec 100644 --- a/app/api/uploadthing/core.ts +++ b/app/api/uploadthing/core.ts @@ -4,7 +4,27 @@ import { createUploadthing, type FileRouter } from "uploadthing/next" const f = createUploadthing() export const ourFileRouter = { - imageUploader: f({ image: { maxFileSize: "4MB", maxFileCount: 4 } }) + mediaUploader: f({ image: { maxFileSize: "4MB", maxFileCount: 4 } }) + .middleware(async ({ req }) => { + const user = await getCurrentUser() + + if (!user) throw new Error("Unauthorized") + + return { userId: user.id } + }) + .onUploadComplete(async ({ metadata, file }) => { }), + + imageUploader: f({ image: { maxFileSize: "4MB", maxFileCount: 1 } }) + .middleware(async ({ req }) => { + const user = await getCurrentUser() + + if (!user) throw new Error("Unauthorized") + + return { userId: user.id } + }) + .onUploadComplete(async ({ metadata, file }) => { }), + + bannerUploader: f({ image: { maxFileSize: "4MB", maxFileCount: 1 } }) .middleware(async ({ req }) => { const user = await getCurrentUser() diff --git a/app/api/users/[username]/route.ts b/app/api/users/[username]/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..e089b9b7276365c1e9cc1d0131039894fc72fe11 --- /dev/null +++ b/app/api/users/[username]/route.ts @@ -0,0 +1,107 @@ +import { db } from "@/lib/db" +import { NextResponse } from "next/server" +import { z } from "zod" + +export async function GET(request: Request, context: { params: { username: string } }) { + const { username } = context.params + + const idSchema = z.string() + const zod = idSchema.safeParse(username) + + if (!zod.success) { + return NextResponse.json(zod.error, { status: 400 }) + } + + try { + const user = await db.user.findUnique({ + where: { + username, + }, + + select: { + id: true, + name: true, + username: true, + email: true, + image: true, + banner: true, + + createdAt: true, + bio: true, + location: true, + website: true, + followers: true, + following: true, + + _count: { + select: { + followers: true, + following: true, + }, + }, + }, + }) + + return NextResponse.json(user, { status: 200 }) + } catch (error: any) { + return NextResponse.json(error.message, { status: 500 }) + } +} + +export async function PUT(request: Request) { + const { + userId, + name, + bio, + location, + website, + banner, + image, + } = await request.json() + + const userSchema = z + .object({ + userId: z.string().cuid(), + name: z.string().min(1).max(50), + bio: z.string().max(160).optional(), + location: z.string().max(30).optional(), + website: z.string().max(100).optional(), + banner: z.string().optional(), + image: z.string().optional(), + }) + .strict() + + const zod = userSchema.safeParse({ + userId, + name, + bio, + location, + website, + banner, + image, + }) + + if (!zod.success) { + return NextResponse.json(zod.error, { status: 400 }) + } + + try { + const user = await db.user.update({ + where: { + id: userId, + }, + data: { + name, + bio, + location, + website, + banner, + image, + }, + }) + + return NextResponse.json(user, { status: 200 }) + } catch (error: any) { + return NextResponse.json(error.message, { status: 500 }) + } +} \ No newline at end of file diff --git a/app/api/users/favgameslist/route.ts b/app/api/users/favgameslist/route.ts index 7b06bead77749d765ee284b985b0063922027025..67bd79f2c75a37bc649488a92df2e41cefd8d826 100644 --- a/app/api/users/favgameslist/route.ts +++ b/app/api/users/favgameslist/route.ts @@ -4,13 +4,13 @@ import { revalidatePath } from "next/cache" import { NextRequest, NextResponse } from "next/server" export async function PUT(req: NextRequest) { - const user = await getCurrentUser() + const session = await getCurrentUser() - if (!user) { + if (!session) { return NextResponse.json({ status: 401, message: 'Unauthorized' }) } - const userId = user.id + const userId = session.id const data = await req.json() data.gameId = parseInt(data.gameId) @@ -51,9 +51,9 @@ export async function PUT(req: NextRequest) { const path = req.nextUrl.searchParams.get('path') || '/' revalidatePath(path) - return NextResponse.json({ status: 201, message: 'Game Hinzugefügt' }) + return NextResponse.json({ message: 'Game added' }, { status: 201 }) } catch (error: any) { - return NextResponse.json({ status: 500, message: error.message }) + return NextResponse.json({ message: error.message }, { status: 500 }) } } \ No newline at end of file diff --git a/app/api/users/follow/route.ts b/app/api/users/follow/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..7e36eb5c10dac51dd32462b4c50a6f88623a9e27 --- /dev/null +++ b/app/api/users/follow/route.ts @@ -0,0 +1,127 @@ +import { db } from "@/lib/db" +import { getCurrentUser } from "@/lib/session" +import { NextResponse } from "next/server" +import { z } from "zod" + +// get following or followers information +export async function GET(request: Request) { + const { searchParams } = new URL(request.url) + const userId = searchParams.get("userId") || undefined + const type = searchParams.get("type") || undefined + + const userIdSchema = z.string().cuid().optional() + const zod = userIdSchema.safeParse(userId) + + if (!zod.success) { + return NextResponse.json(zod.error, { status: 400 }) + } + + try { + if (type === "followers") { + const followers = await db.user + .findUnique({ + where: { + id: userId, + }, + }) + .followers() + return NextResponse.json(followers, { status: 200 }) + } else if (type === "following") { + const following = await db.user + .findUnique({ + where: { + id: userId, + }, + }) + .following() + + return NextResponse.json(following, { status: 200 }) + } + } catch (error: any) { + return NextResponse.json(error.message, { status: 500 }) + } +} + +// follow a user +export async function PUT(request: Request) { + const session = await getCurrentUser() + const { userId } = await request.json() + + const followerIdSchema = z + .object({ + userId: z.string().cuid(), + }) + .strict() + + const zod = followerIdSchema.safeParse({ userId }) + + if (!zod.success) { + return NextResponse.json(zod.error, { status: 400 }) + } + + try { + await db.user.update({ + where: { + id: userId, + }, + + data: { + followers: { + connect: { + id: session?.id, + }, + }, + }, + }) + + return NextResponse.json( + { + message: "followed", + }, { status: 200 } + ) + } catch (error: any) { + return NextResponse.json(error.message, { status: 500 }) + } +} + +// unfollow a user +export async function DELETE(request: Request) { + const session = await getCurrentUser() + const { userId } = await request.json() + + const followerIdSchema = z + .object({ + userId: z.string().cuid(), + }) + .strict() + + const zod = followerIdSchema.safeParse({ userId }) + + if (!zod.success) { + return NextResponse.json(zod.error, { status: 400 }) + } + + try { + await db.user.update({ + where: { + id: userId, + }, + + data: { + followers: { + disconnect: { + id: session?.id, + }, + }, + }, + }) + + return NextResponse.json( + { + message: "unfollowed", + }, { status: 200 }, + ) + } catch (error: any) { + return NextResponse.json(error.message, { status: 500 }) + } +} \ No newline at end of file diff --git a/app/api/gamelists/route.ts b/app/api/users/gamelists/route.ts similarity index 79% rename from app/api/gamelists/route.ts rename to app/api/users/gamelists/route.ts index 7c578b3ef8e25ba5431237bbfe24b459b46b5dd6..629f995d73d5ada83642ee9c5079cb9e421aed31 100644 --- a/app/api/gamelists/route.ts +++ b/app/api/users/gamelists/route.ts @@ -8,11 +8,11 @@ export async function PUT(req: NextRequest) { const sessionUser = await getCurrentUser() const data: User = await req.json() - console.log("userid", sessionUser!.id, "formdataid", data.id) + if (!sessionUser || sessionUser.id != data.id) { - return NextResponse.json({ status: 401, message: 'Unauthorized' }) + return NextResponse.json({ message: 'Unauthorized' }, { status: 401 }) } - console.log("put list") + try { const dbUser = await db.user.findFirst({ where: { @@ -41,11 +41,12 @@ export async function PUT(req: NextRequest) { } }) } - } catch (error) { - } - const path = req.nextUrl.searchParams.get('path') || '/' - revalidatePath(path) + const path = req.nextUrl.searchParams.get('path') || '/' + revalidatePath(path) - return NextResponse.json({ status: 201, message: 'Game Hinzugefügt' }) + return NextResponse.json({ message: 'Game added' }, { status: 201 }) + } catch (error: any) { + return NextResponse.json({ message: error.message }, { status: 500 }) + } } \ No newline at end of file diff --git a/app/api/users/route.ts b/app/api/users/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..7f3814a20547512e139f8877290c2d958691fed8 --- /dev/null +++ b/app/api/users/route.ts @@ -0,0 +1,43 @@ +import { db } from "@/lib/db" +import { NextResponse } from "next/server" +import { z } from "zod" + +export async function GET(request: Request) { + const { searchParams } = new URL(request.url) + const id = searchParams.get("id") || undefined + const idSchema = z.string().cuid().optional() + + const zod = idSchema.safeParse(id) + + if (!zod.success) { + return NextResponse.json(zod.error, { status: 400 }) + } + + try { + const users = await db.user.findMany({ + where: { + NOT: { + id, + }, + }, + + orderBy: { + createdAt: "desc", + }, + + select: { + id: true, + name: true, + username: true, + email: true, + image: true, + following: true, + followers: true, + }, + }) + + return NextResponse.json(users, { status: 200 }) + } catch (error: any) { + return NextResponse.json(error.message, { status: 500 }) + } +} \ No newline at end of file diff --git a/components/add-game-to-finished-list.tsx b/components/add-game-to-finished-list.tsx index bc37572a92fb214c02fa29e9ac094296782b856e..6f8ec4fec6592b7d0ef72a7b9c7fb5e990d99822 100644 --- a/components/add-game-to-finished-list.tsx +++ b/components/add-game-to-finished-list.tsx @@ -33,7 +33,7 @@ export default function AddGameToFinishedList(props: { gameId: string, user: Use formData.id = user.id formData.finishedGameList = props.user.finishedGameList.filter((id) => id !== gameId) console.log(formData.finishedGameList) - const response = await fetch('/api/gamelists', { + const response = await fetch('/api/users/gamelists', { method: 'PUT', body: JSON.stringify(formData) }) @@ -52,7 +52,7 @@ export default function AddGameToFinishedList(props: { gameId: string, user: Use formData.id = user.id props.user.finishedGameList.push(gameId) formData.finishedGameList = props.user.finishedGameList - const response = await fetch('/api/gamelists', { + const response = await fetch('/api/users/gamelists', { method: 'PUT', body: JSON.stringify(formData) }) diff --git a/components/add-game-to-plan-list.tsx b/components/add-game-to-plan-list.tsx index 61f62e85fa0c4ad352a0b7b680769904b34c91d4..9de2099cfc9a791adeeb582fa51f2951246130ea 100644 --- a/components/add-game-to-plan-list.tsx +++ b/components/add-game-to-plan-list.tsx @@ -33,7 +33,7 @@ export default function AddGameToPlanList(props: { gameId: string, user: User }) formData.id = user.id formData.planningGameList = props.user.planningGameList.filter((id) => id !== gameId) console.log(formData.planningGameList) - const response = await fetch('/api/gamelists', { + const response = await fetch('/api/users/gamelists', { method: 'PUT', body: JSON.stringify(formData) }) @@ -52,7 +52,7 @@ export default function AddGameToPlanList(props: { gameId: string, user: User }) formData.id = user.id props.user.planningGameList.push(gameId) formData.planningGameList = props.user.planningGameList - const response = await fetch('/api/gamelists', { + const response = await fetch('/api/users/gamelists', { method: 'PUT', body: JSON.stringify(formData) }) diff --git a/components/add-game-to-playing-list.tsx b/components/add-game-to-playing-list.tsx index 73e8067728687051a7b63e67640a5a5c014238d7..81232c6e72774bc9e86d07f90d7970f1fc070fd0 100644 --- a/components/add-game-to-playing-list.tsx +++ b/components/add-game-to-playing-list.tsx @@ -33,7 +33,7 @@ export default function AddGameToPlayingList(props: { gameId: string, user: User formData.id = user.id formData.playingGameList = props.user.playingGameList.filter((id) => id !== gameId) console.log(formData.playingGameList) - const response = await fetch('/api/gamelists', { + const response = await fetch('/api/users/gamelists', { method: 'PUT', body: JSON.stringify(formData) }) @@ -52,7 +52,7 @@ export default function AddGameToPlayingList(props: { gameId: string, user: User formData.id = user.id props.user.playingGameList.push(gameId) formData.playingGameList = props.user.playingGameList - const response = await fetch('/api/gamelists', { + const response = await fetch('/api/users/gamelists', { method: 'PUT', body: JSON.stringify(formData) }) diff --git a/components/create-gweet/api/post-gweet.ts b/components/create-gweet/api/post-gweet.ts index 76c02d36fcbeb61b585fa643da4bcb8bf334dc7e..9f10a8dce0a6907e68aa6d33c80f4b31b607b70c 100644 --- a/components/create-gweet/api/post-gweet.ts +++ b/components/create-gweet/api/post-gweet.ts @@ -25,7 +25,7 @@ export const postGweet = async ({ try { let fileprops: { fileUrl: string; fileKey: string }[] = [] if (files.length > 0) { - fileprops = await uploadFiles({ files, endpoint: 'imageUploader' }) + fileprops = await uploadFiles({ files, endpoint: 'mediaUploader' }) } const data = await fetch('/api/gweets', { diff --git a/components/follow-button.tsx b/components/follow-button.tsx new file mode 100644 index 0000000000000000000000000000000000000000..294bf2700fc7194870e5a28903a44378897ee6e3 --- /dev/null +++ b/components/follow-button.tsx @@ -0,0 +1,78 @@ +"use client" + +import { useRouter } from "next/navigation" +import { useEffect, useState } from "react" +import { Icons } from "./icons" +import { useFollow } from "./profile/hooks/use-follow" +import { Button } from "./ui/button" +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "./ui/dialog" + +export const FollowButton = ({ + userId, + username, + isFollowing = false, +}: { + userId: string + username: string + isFollowing?: boolean +}) => { + const followMutation = useFollow("follow") + const unfollowMutation = useFollow("unfollow") + + const [text, setText] = useState<"Following" | "Unfollow">("Following") + const [isHovered, setIsHovered] = useState(false) + const [open, setOpen] = useState(false) + + const router = useRouter() + + useEffect(() => { + if (followMutation.isSuccess) { + router.refresh() + } + if (unfollowMutation.isSuccess) { + setOpen(false) + router.refresh() + } + }, [router, followMutation.isSuccess, unfollowMutation.isSuccess]) + + return ( + <Dialog open={open} onOpenChange={setOpen}> + {isFollowing ? ( + <DialogTrigger asChild> + <Button variant={`${isHovered ? "destructive" : "outline"}`} size="lg" + className="w-24" + onMouseEnter={() => { + setText("Unfollow") + setIsHovered(true) + }} + onMouseOut={() => { + setText("Following") + setIsHovered(false) + }}> + {text} + </Button> + </DialogTrigger> + ) : ( + <Button size="lg" onClick={() => followMutation.mutate({ userId })}> + Follow + </Button> + )} + <DialogContent> + <DialogHeader> + <DialogTitle>Unfollow @{username}?</DialogTitle> + <DialogDescription> + You can still view their profile, unless their Gweets are protected. + </DialogDescription> + </DialogHeader> + <DialogFooter> + <Button type="submit" onClick={() => unfollowMutation.mutate({ userId })} disabled={unfollowMutation.isLoading}> + {unfollowMutation.isLoading && ( + <Icons.spinner className="mr-2 h-4 w-4 animate-spin" /> + )} + Unfollow + </Button> + </DialogFooter> + </DialogContent> + </Dialog> + ) +} \ No newline at end of file diff --git a/components/following-button.tsx b/components/following-button.tsx deleted file mode 100644 index b5eb3f4e51b564dad1cd30b38b77fecce04e4dd7..0000000000000000000000000000000000000000 --- a/components/following-button.tsx +++ /dev/null @@ -1,75 +0,0 @@ -"use client" - -import { Follows, Prisma } from '@prisma/client' -import { useSession } from 'next-auth/react' -import { usePathname } from "next/navigation" -import { useEffect, useState } from 'react' -import { Button } from './ui/button' - -// 1: Define a type that includes the relation to `Post` -const userWithFollows = Prisma.validator<Prisma.UserArgs>()({ - include: { followers: true, following: true }, -}) - -// 2: Define a type that only contains a subset of the scalar fields -const userPersonalData = Prisma.validator<Prisma.UserArgs>()({ - select: { email: true, name: true }, -}) - -// 3: This type will include a user and all their posts -type UserWithFollows = Prisma.UserGetPayload<typeof userWithFollows> - - -export default function FollowButton({ user, followingId }: { user: UserWithFollows; followingId: string }) { - const [isFollowing, setIsFollowing] = useState(false) - const [follows, setFollows] = useState([]) - const [buttonPressed, setButtonPress] = useState(false) - - const pathname = usePathname() - const { data: session } = useSession() - - async function fetchFollows() { - - try { - const response = await fetch('/api/followers') - - const data = await response.json() - setFollows(data.follows) - setIsFollowing(false) - data.follows?.forEach((f: Follows) => { - - if (f.followerId == user.id && f.followingId == followingId) { - setIsFollowing(true) - return - } - }) - } catch (error) { - - } - } - useEffect(() => { - fetchFollows() - // TODO: fix this warning - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [buttonPressed]) - - async function handleFollow(e: any) { - e.preventDefault() - - const response = await fetch('/api/followers', { - method: 'PUT', - body: JSON.stringify({ user, followingId }) - }) - - setButtonPress(!buttonPressed) - } - - return ( - <> - {pathname !== `/${session?.user.username}` && - <Button onClick={handleFollow}> - {isFollowing ? 'Unfollow' : 'Follow'} - </Button>} - </> - ) -} \ No newline at end of file diff --git a/components/gweets/components/infinite-gweets.tsx b/components/gweets/components/infinite-gweets.tsx index 2e8f73dd51094bb4dec000cd790196a93c6f4341..8952722cfe4c7ae68dcd00d6fd62dc4fb3d69d0c 100644 --- a/components/gweets/components/infinite-gweets.tsx +++ b/components/gweets/components/infinite-gweets.tsx @@ -49,9 +49,11 @@ export const InfiniteGweets = ({ key={gweet.id} > <Gweet gweet={gweet} /> - <div className="px-6"> - <Separator className="my-3" /> - </div> + {index !== page.gweets.length - 1 && ( + <div className="px-6"> + <Separator className="my-3" /> + </div> + )} </div> )) })} diff --git a/components/icons.tsx b/components/icons.tsx index 52f4a7e8c3ba0bc159f46798508a44b08580c2be..4c26e62a29788efefa0d39f372ab75225caaf343 100644 --- a/components/icons.tsx +++ b/components/icons.tsx @@ -4,6 +4,7 @@ import { ArrowRight, ArrowUpToLine, BellRing, + CalendarRange, Check, ChevronLeft, ChevronRight, @@ -18,8 +19,10 @@ import { Image, Laptop, Link, + Link2, Loader2, LucideProps, + MapPin, MessageCircle, Moon, MoreHorizontal, @@ -29,6 +32,7 @@ import { Repeat2, Settings, SunMedium, + SwitchCamera, Trash, User, Users, @@ -87,6 +91,10 @@ export const Icons: IconsType = { 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 post: FileText, page: File, media: Image, diff --git a/components/nav-sidebar.tsx b/components/nav-sidebar.tsx index 7d5808ab9c5a591ec5d9da89f0b49989c0c9d604..b8f579a029ec8e3811f75d96b540622a023f136e 100644 --- a/components/nav-sidebar.tsx +++ b/components/nav-sidebar.tsx @@ -31,7 +31,7 @@ export const Sidebar = ({ items, user }: { items: SidebarNavItem[], user: User | <span className={cn( "group flex items-center rounded-md px-3 py-2 font-medium hover:bg-accent hover:text-accent-foreground", - path === item.href ? "bg-accent" : "transparent", + path.startsWith(item.href) ? "bg-accent" : "transparent", item.disabled && "cursor-not-allowed opacity-80" )} > diff --git a/components/profile/api/follow-user.ts b/components/profile/api/follow-user.ts new file mode 100644 index 0000000000000000000000000000000000000000..bfa5bf2593a412d570c745dcbf6e2ac3f5027886 --- /dev/null +++ b/components/profile/api/follow-user.ts @@ -0,0 +1,12 @@ +export const followUser = async (userId: string) => { + try { + const data = await fetch("/api/users/follow", { + method: "PUT", + body: JSON.stringify({ userId }), + }).then((result) => result.json()) + + return data + } catch (error: any) { + return error.message + } +} \ No newline at end of file diff --git a/components/profile/api/get-follows.ts b/components/profile/api/get-follows.ts new file mode 100644 index 0000000000000000000000000000000000000000..dc83afb8de3fc7b2dc5c40f80186464b3f0ad418 --- /dev/null +++ b/components/profile/api/get-follows.ts @@ -0,0 +1,10 @@ +export const getFollows = async (id: string | undefined, type: string | undefined) => { + try { + const data = await fetch(`/api/users/follow?type=${type}&user_id=${id}`) + .then((result) => result.json()) + + return data + } catch (error: any) { + return error.message + } +} \ No newline at end of file diff --git a/components/profile/api/get-user-likes.ts b/components/profile/api/get-user-likes.ts new file mode 100644 index 0000000000000000000000000000000000000000..1a1ebd4dd3a14dfa0d5bf18823a772543771c8b4 --- /dev/null +++ b/components/profile/api/get-user-likes.ts @@ -0,0 +1,10 @@ +export const getUserLikes = async (id: string | undefined) => { + try { + const data = await fetch(`/api/tweets/likes?user_id=${id}`) + .then((result) => result.json()) + + return data + } catch (error: any) { + return error.message + } +} \ No newline at end of file diff --git a/components/profile/api/get-user.ts b/components/profile/api/get-user.ts new file mode 100644 index 0000000000000000000000000000000000000000..7942e09c24179c36a5b0f26146d34552920e20b7 --- /dev/null +++ b/components/profile/api/get-user.ts @@ -0,0 +1,25 @@ +export const getUser = async (username: string | undefined) => { + try { + const user = await fetch(`/api/users/${username}`) + .then((result) => result.json()) + + return user + } catch (error: any) { + if (error.response) { + // The request was made and the server responded with a status code + // that falls out of the range of 2xx + console.log(error.response.data) + console.log(error.response.status) + console.log(error.response.headers) + } else if (error.request) { + // The request was made but no response was received + // `error.request` is an instance of XMLHttpRequest in the browser and an instance of + // http.ClientRequest in node.js + console.log(error.request) + } else { + // Something happened in setting up the request that triggered an Error + console.log("Error", error.message) + } + console.log(error.config) + } +} \ No newline at end of file diff --git a/components/profile/api/get-users.ts b/components/profile/api/get-users.ts new file mode 100644 index 0000000000000000000000000000000000000000..5456b72d31a767f8a14246df0635d68ad20fdc21 --- /dev/null +++ b/components/profile/api/get-users.ts @@ -0,0 +1,10 @@ +export const getUsers = async (id?: string) => { + try { + const data = await fetch(`/api/users${id ? `?id=${id}` : ""}`) + .then((result) => result.json()) + + return data + } catch (error: any) { + return error.response.data + } +} \ No newline at end of file diff --git a/components/profile/api/unfollow-user.ts b/components/profile/api/unfollow-user.ts new file mode 100644 index 0000000000000000000000000000000000000000..1d23d10d70f67484e60bdaf1f8ef8f59f48bf422 --- /dev/null +++ b/components/profile/api/unfollow-user.ts @@ -0,0 +1,12 @@ +export const unfollowUser = async (userId: string) => { + try { + const data = await fetch(`/api/users/follow`, { + method: "DELETE", + body: JSON.stringify({ userId }), + }).then((result) => result.json()) + + return data + } catch (error: any) { + return error.message + } +} \ No newline at end of file diff --git a/components/profile/api/update-profile.ts b/components/profile/api/update-profile.ts new file mode 100644 index 0000000000000000000000000000000000000000..861e2c8125f87632faf323aee9d929a97e6f4ecb --- /dev/null +++ b/components/profile/api/update-profile.ts @@ -0,0 +1,42 @@ +import { uploadFiles } from "@/lib/uploadthing" +import { IProfile } from "./../types/index" + +export const updateProfile = async ({ + profile, + userId, +}: { + profile: IProfile + userId: string +}) => { + if (!profile) return + + try { + let bannerfileprops: { fileUrl: string; fileKey: string } = { fileUrl: '', fileKey: '' } + let imagefileprops: { fileUrl: string; fileKey: string } = { fileUrl: '', fileKey: '' } + + if (profile.banner.file) { + [bannerfileprops] = await uploadFiles({ files: [profile.banner.file], endpoint: 'mediaUploader' }) + } + + if (profile.image.file) { + [imagefileprops] = await uploadFiles({ files: [profile.image.file], endpoint: 'imageUploader' }) + } + console.log('bannerfileprops', profile.banner.removed) + const data = await fetch(`/api/users/${userId}`, { + method: 'PUT', + body: JSON.stringify({ + userId, + name: profile.name, + bio: profile.bio, + location: profile.location, + website: profile.website, + banner: profile.banner.removed ? "" : bannerfileprops.fileUrl !== "" ? bannerfileprops.fileUrl : undefined, + image: imagefileprops.fileUrl !== "" ? imagefileprops.fileUrl : undefined, + }) + }).then((result) => result.json()) + + return data + } catch (error: any) { + return error.Message + } +} \ No newline at end of file diff --git a/components/profile/components/edit-profile-modal.tsx b/components/profile/components/edit-profile-modal.tsx new file mode 100644 index 0000000000000000000000000000000000000000..bf4dbc6b54919ed0a4591bcea1404d6151da6591 --- /dev/null +++ b/components/profile/components/edit-profile-modal.tsx @@ -0,0 +1,359 @@ +"use client" + +import { Icons } from "@/components/icons" +import { AspectRatio } from "@/components/ui/aspect-ratio" +import { Button } from "@/components/ui/button" +import { Card } from "@/components/ui/card" +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog" +import { Form, FormControl, FormField, FormItem, FormMessage } from "@/components/ui/form" +import { Input } from "@/components/ui/input" +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 { zodResolver } from "@hookform/resolvers/zod" +import Image from "next/image" +import { useRouter } from "next/navigation" +import { use, useEffect, useRef, useState } from "react" +import { useForm } from "react-hook-form" +import { z } from "zod" +import { useUpdateProfile } from "../hooks/use-update-profile" +import { IProfile, IUser } from "../types" + +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(), +}) + +const ImagesSchema = z.custom<File>().refine((file) => file instanceof File, { message: "Expected a file" }) + .refine((file) => file?.size < 4000000, { message: "Images must be less than 4MB" }) + .optional() + +export const EditProfileModal = ({ user }: { user: IUser }) => { + const { isLoading, isSuccess, mutate } = useUpdateProfile() + + const form = useForm<z.infer<typeof FormSchema>>({ + resolver: zodResolver(FormSchema), + defaultValues: { + name: user.name, + bio: user.bio || "", + location: user.location || "", + website: user.website || "", + }, + }) + + const [open, setOpen] = useState(false) + const [imageErrorMessage, setImageErrorMessage] = useState("") + const [chosenBanner, setChosenBanner] = useState<Partial<IProfile>>({ + banner: { removed: false, url: user?.banner || "", file: undefined }, + }) + const [chosenImages, setChosenImages] = useState<Partial<IProfile>>({ + image: { url: user?.image || "", file: undefined }, + }) + + const bannerInputRef = useRef<HTMLInputElement>(null) + const imageInputRef = useRef<HTMLInputElement>(null) + + const router = useRouter() + + async function onSave(formData: z.infer<typeof FormSchema>) { + if (!user) return null + + const dirty = form.formState.dirtyFields + const profile: IProfile = { + name: formData.name, + bio: dirty.bio ? formData.bio : undefined, + location: dirty.location ? formData.location : undefined, + website: dirty.website ? formData.website : undefined, + banner: { removed: chosenBanner.banner?.removed, url: user?.banner || "", file: chosenBanner.banner?.file }, + image: { url: user?.image || "", file: chosenImages.image?.file }, + } + + mutate({ + profile, + userId: user.id, + }) + } + + let disable = true + if (!isLoading) { + if (chosenBanner.banner?.file !== undefined || chosenImages.image?.file !== undefined || form.formState.isDirty) { + disable = false + } + } + + useEffect(() => { + if (!open) { + form.reset() + form.setValue("name", user?.name) + form.setValue("bio", user?.bio || "") + form.setValue("location", user?.location || "") + form.setValue("website", user?.website || "") + + // wait 150ms for dialog to close before resetting image + setTimeout(() => { + setImageErrorMessage("") + setChosenBanner({ banner: { removed: false, url: user?.banner || "", file: undefined } }) + setChosenImages({ image: { url: user?.image || "", file: undefined } }) + }, 150) + } + }, [form, open, user]) + + useEffect(() => { + if (isSuccess) { + toast({ + description: "Successfully updated profile.", + }) + setOpen(false) + router.refresh() + } + }, [isSuccess, router]) + + const chooseImage = async (event: any, type: string) => { + const file = event.target.files[0] + if (!file) return + try { + ImagesSchema.parse(file) + setImageErrorMessage("") + } catch (error: any) { + const err = error as z.ZodError + setImageErrorMessage(err.issues[0].message) + return + } + + const reader = new FileReader() + + if (type === "banner" && bannerInputRef.current) { + bannerInputRef.current.value = "" + reader.onloadend = () => { + setChosenBanner({ + ["banner"]: { removed: false, url: reader.result as string, file }, + }) + } + } + + if (type === "image" && imageInputRef.current) { + imageInputRef.current.value = "" + reader.onloadend = () => { + setChosenImages({ + ["image"]: { url: reader.result as string, file }, + }) + } + } + + reader.readAsDataURL(file) + } + + if (!user) return null + + return ( + <Dialog open={open} onOpenChange={setOpen}> + <DialogTrigger asChild> + <Button variant="outline">Edit Profile</Button> + </DialogTrigger> + <DialogContent className="sm:max-w-[600px]"> + <DialogHeader> + <DialogTitle>Edit profile</DialogTitle> + <DialogDescription> + Make changes to your profile here. Click save when you are done. + </DialogDescription> + </DialogHeader> + <Form {...form}> + <form onSubmit={form.handleSubmit(onSave)}> + <div className="grid gap-3 py-3"> + <div className="space-y-2"> + <div className="grid grid-cols-4 items-center gap-4"> + <div className="col-span-1 relative w-full overflow-hidden items-center"> + <div className="object-center object-cover w-full h-full"> + {!chosenImages.image?.url ? + <UserAvatar + user={{ username: user.username, image: null }} + className="object-center object-cover w-full h-full aspect-square" + /> + : + <AspectRatio ratio={1 / 1} className="overflow-hidden rounded-full"> + <Image + src={chosenImages.image?.url} + alt={"user image"} + fill + priority + className="object-center object-cover w-full h-full" + /> + </AspectRatio> + } + </div> + <input + className="hidden resize-none" + type="file" + accept="image/*" + ref={imageInputRef} + onChange={(e) => chooseImage(e, "image")} + disabled={isLoading} + /> + <div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 flex space-x-3"> + <Button + type="button" + variant="outline" + size="icon" + className="bg-opacity-50 dark:bg-opacity-50" + onClick={() => imageInputRef?.current?.click()} + disabled={isLoading} + > + <Icons.camera /> + </Button> + </div> + </div> + + <Card className="col-span-3 relative w-full overflow-hidden border-none"> + + <AspectRatio ratio={3 / 1} className="bg-muted overflow-hidden"> + {chosenBanner.banner?.url && + <Image + src={chosenBanner.banner?.url} + alt={"user banner image"} + fill + priority + className="object-center object-cover w-full h-full" + /> + } + </AspectRatio> + <input + className="hidden w-full resize-none" + type="file" + accept="image/*" + ref={bannerInputRef} + onChange={(e) => chooseImage(e, "banner")} + disabled={isLoading} + /> + <div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 flex space-x-3"> + <Button + type="button" + variant="outline" + size="icon" + className="bg-opacity-50 dark:bg-opacity-50" + onClick={() => bannerInputRef.current?.click()} + disabled={isLoading} + > + <Icons.camera /> + </Button> + {chosenBanner.banner?.url && ( + <Button + type="button" + variant="outline" + size="icon" + className="bg-opacity-50 dark:bg-opacity-50" + onClick={() => setChosenBanner({ banner: { removed: true, url: "", file: undefined } })} + disabled={isLoading} + > + <Icons.close /> + </Button> + )} + </div> + </Card> + </div> + {imageErrorMessage && + <div className="grid grid-cols-4 items-center gap-4"> + <div className="col-span-1"></div> + <div className="col-span-3"> + <p className="text-sm font-medium text-destructive">{imageErrorMessage}</p> + </div> + </div> + } + </div> + + <div className="grid grid-cols-4 items-center gap-4"> + <Label htmlFor="name" className="text-right"> + Name + </Label> + <FormField + control={form.control} + name="name" + render={({ field }) => ( + <FormItem className="col-span-3"> + <FormControl> + <Input id="name" disabled={isLoading} {...field} /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + </div> + + <div className="grid grid-cols-4 items-center gap-4"> + <Label htmlFor="bio" className="text-right"> + Bio + </Label> + <FormField + control={form.control} + name="bio" + render={({ field }) => ( + <FormItem className="col-span-3"> + <FormControl> + <Textarea id="bio" className="resize-none" disabled={isLoading} {...field} /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + </div> + + <div className="grid grid-cols-4 items-center gap-4"> + <Label htmlFor="location" className="text-right"> + Location + </Label> + <FormField + control={form.control} + name="location" + render={({ field }) => ( + <FormItem className="col-span-3"> + <FormControl> + <Input id="location" disabled={isLoading} {...field} /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + </div> + + <div className="grid grid-cols-4 items-center gap-4"> + <Label htmlFor="website" className="text-right"> + Website + </Label> + <FormField + control={form.control} + name="website" + render={({ field }) => ( + <FormItem className="col-span-3"> + <FormControl> + <Input id="website" disabled={isLoading} {...field} /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + </div> + </div> + <DialogFooter> + <Button type="submit" disabled={disable}> + {isLoading && ( + <Icons.spinner className="mr-2 h-4 w-4 animate-spin" /> + )} + Save changes + </Button> + </DialogFooter> + </form> + </Form> + </DialogContent> + </Dialog> + ) +} \ No newline at end of file diff --git a/components/profile/components/profile-navbar.tsx b/components/profile/components/profile-navbar.tsx new file mode 100644 index 0000000000000000000000000000000000000000..94a436a3597f9ad8d705427a24c2c06a87b95454 --- /dev/null +++ b/components/profile/components/profile-navbar.tsx @@ -0,0 +1,40 @@ +"use client" + +import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs" +import Link from "next/link" +import { usePathname } from "next/navigation" + +export const ProfileNavbar = ({ children, param }: { children: React.ReactNode, param: string }) => { + const pathValue = usePathname().split("/")[2] || "gweets" + + return ( + <Tabs defaultValue={"gweets"} value={pathValue} className="w-full h-full p-6 md:p-12 "> + <TabsList className="grid w-full grid-cols-4"> + <Link href={`/${param}`} scroll={false} className="w-full inline-flex items-center justify-center"> + <TabsTrigger value="gweets" className="w-full"> + Gweets + </TabsTrigger> + </Link> + + <Link href={`/${param}/games`} scroll={false} className="w-full inline-flex items-center justify-center"> + <TabsTrigger value="games" className="w-full"> + Games + </TabsTrigger> + </Link> + + <Link href={`/${param}/media`} scroll={false} className="w-full inline-flex items-center justify-center"> + <TabsTrigger value="media" className="w-full"> + Media + </TabsTrigger> + </Link> + + <Link href={`/${param}/likes`} scroll={false} className="w-full inline-flex items-center justify-center"> + <TabsTrigger value="likes" className="w-full"> + Likes + </TabsTrigger> + </Link> + </TabsList> + {children} + </Tabs> + ) +} \ No newline at end of file diff --git a/components/profile/components/profile-side-content.tsx b/components/profile/components/profile-side-content.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2027f4072f97dbb22b883d1f277845bc8d3ea753 --- /dev/null +++ b/components/profile/components/profile-side-content.tsx @@ -0,0 +1,17 @@ +import { Card } from "@/components/ui/card" +import { Skeleton } from "@/components/ui/skeleton" + +export const ProfileSideContent = () => { + return ( + <Card className="p-6 grid items-start gap-2 bg-secondary"> + <h1>Media</h1> + <div className="grid grid-cols-1 gap-4"> + {Array.from({ length: 2 }, (_, i) => i + 1).map((i) => { + return ( + <Skeleton key={i} className="aspect-[264/374] bg-gray-300" /> + ) + })} + </div> + </Card> + ) +} \ No newline at end of file diff --git a/components/profile/components/profile-user-info.tsx b/components/profile/components/profile-user-info.tsx new file mode 100644 index 0000000000000000000000000000000000000000..914b6fdbf9a35359366fb785fefc7f95a9f2e0a1 --- /dev/null +++ b/components/profile/components/profile-user-info.tsx @@ -0,0 +1,95 @@ +import { FollowButton } from "@/components/follow-button" +import { Icons } from "@/components/icons" +import { AspectRatio } from "@/components/ui/aspect-ratio" +import { UserAvatar } from "@/components/user-avatar" +import { getCurrentUser } from "@/lib/session" +import Image from "next/image" +import Link from "next/link" +import { IUser } from "../types" +import { following } from "../utils/following" +import { EditProfileModal } from "./edit-profile-modal" +import { UserJoinDate } from "./user-join-date" + +export const ProfileUserInfo = async ({ user }: { user: IUser }) => { + const session = await getCurrentUser() + + const isFollowing = following({ + user, + sessionUserId: session ? session.id : "", + }) + + return ( + <> + <AspectRatio ratio={3 / 1} className="bg-muted w-full overflow-hidden"> + {user.banner && + <Image + src={user.banner} + alt={"user banner image"} + fill + priority + className="object-center object-cover w-full h-full" + /> + } + </AspectRatio> + <div className="relative"> + <div className="flex items-end justify-between p-6 md:px-12"> + <div> + <div className="absolute bottom-6 md:bottom-3"> + <UserAvatar + user={{ username: user.username, image: user.image || null }} + className="h-20 md:h-40 w-20 md:w-40" + /> + </div> + </div> + <div> + {session?.id === user.id ? ( + <EditProfileModal user={user} /> + ) : ( + <FollowButton + userId={user.id} + username={user.username ? user.username : ""} + isFollowing={isFollowing} + /> + )} + </div> + </div> + </div> + + <div className="px-6 md:px-12 flex flex-col space-y-3 w-full"> + <div className="pb-3 whitespace-nowrap items-center"> + <h1 className="text-2xl font-bold">{user.name}</h1> + <h1 className="text-md text-sky-500">@{user.username}</h1> + </div> + {user.bio && <h1 className="break-words">{user.bio}</h1>} + + <div className="flex whitespace-nowrap items-center space-x-6 text-muted-foreground"> + {user.location && + <div className="flex items-center"> + <Icons.location className="mr-1" /> + <div>{user.location}</div> + </div> + } + {user.website && + <div className="flex items-center"> + <Icons.website className="mr-1" /> + <div>{user.website}</div> + </div> + } + {user.createdAt && <UserJoinDate date={user.createdAt} />} + </div> + + <div className="flex whitespace-nowrap items-center space-x-6"> + <Link href={`/${user.username}/following`}> + <span className="font-bold">{user._count?.following}</span> + <span className="text-muted-foreground"> Following</span> + </Link> + + <Link href={`/${user.username}/followers`}> + <span className="font-bold">{user._count?.followers}</span> + <span className="text-muted-foreground"> Followers</span> + </Link> + </div> + </div> + </ > + ) +} \ No newline at end of file diff --git a/components/profile/components/user-games.tsx b/components/profile/components/user-games.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4f9d414fa9519d54cd1be6c2e6fca86d458f36cd --- /dev/null +++ b/components/profile/components/user-games.tsx @@ -0,0 +1,100 @@ +import GameItem from "@/components/game-item" +import { db } from "@/lib/db" +import { getFavoriteGames } from "@/lib/igdb" +import { getCurrentUser } from "@/lib/session" +import { IGame } from "@/types/igdb-types" +import { redirect } from "next/navigation" + +export const UserGames = async ({ username }: { username: string }) => { + const user = await getCurrentUser() + + const sessionUser = await db.user.findFirst({ + where: { + id: user?.id + }, + include: { + following: true, + followers: true + } + }) + + const fullUser = await db.user.findFirst({ + where: { + username: username + }, + include: { + following: true, + followers: true + } + }) + + if (!fullUser) { + redirect('/home') + } + + let favoritegames = undefined + let playingGames = undefined + let finishedGames = undefined + let planningGames = undefined + + if (fullUser?.favGameList?.length !== 0 && fullUser?.favGameList?.length != undefined) { + favoritegames = await getFavoriteGames(fullUser?.favGameList!) + } + if (fullUser?.playingGameList?.length !== 0) { + playingGames = await getFavoriteGames(fullUser?.playingGameList!) + } + if (fullUser?.finishedGameList?.length !== 0) { + finishedGames = await getFavoriteGames(fullUser?.finishedGameList!) + } + if (fullUser?.planningGameList?.length !== 0) { + planningGames = await getFavoriteGames(fullUser?.planningGameList!) + } + + return ( + <div className="p-3 space-y-12"> + <div> + <h1 className="text-2xl font-bold pb-3">Favorite Games</h1> + <div className="grid grid-cols-1 ss:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-4 lg:gap-8 items-center"> + {favoritegames ? favoritegames.map((game: IGame) => ( + <GameItem id={game.id} name={game.name} cover={game.cover} key={game.id} /> + )) + : + <p>No favorites currently...</p>} + </div> + </div> + + <div> + <h1 className="text-2xl font-bold pb-3">Currently playing</h1> + <div className="grid grid-cols-1 ss:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-4 lg:gap-8 items-center"> + {playingGames ? playingGames.map((game: IGame) => ( + <GameItem id={game.id} name={game.name} cover={game.cover} key={game.id} /> + )) + : + <p>Currently not playing any games...</p>} + </div> + </div> + + <div> + <h1 className="text-2xl font-bold pb-3">Planning to play</h1> + <div className="grid grid-cols-1 ss:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-4 lg:gap-8 items-center"> + {planningGames ? planningGames.map((game: IGame) => ( + <GameItem id={game.id} name={game.name} cover={game.cover} key={game.id} /> + )) + : + <p>Currently not planning to play any games...</p>} + </div> + </div> + + <div> + <h1 className="text-2xl font-bold pb-3">Finished Games</h1> + <div className="grid grid-cols-1 ss:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-4 lg:gap-8 items-center"> + {finishedGames ? finishedGames.map((game: IGame) => ( + <GameItem id={game.id} name={game.name} cover={game.cover} key={game.id} /> + )) + : + <p>No finished games...</p>} + </div> + </div> + </div > + ) +} \ No newline at end of file diff --git a/components/profile/components/user-gweets.tsx b/components/profile/components/user-gweets.tsx new file mode 100644 index 0000000000000000000000000000000000000000..6f53a744151d74c4f4b02673f97e9f91f44c0e6f --- /dev/null +++ b/components/profile/components/user-gweets.tsx @@ -0,0 +1,69 @@ +"use client" + +import { InfiniteGweets } from "@/components/gweets/components/infinite-gweets" +import { useGweets } from "@/components/gweets/hooks/use-gweets" +import LoadingItem from "@/components/loading-item" +import { TryAgain } from "@/components/try-again" +import { usePathname } from "next/navigation" + +export const UserGweets = ({ username, sessionname }: { username: string, sessionname: string }) => { + const pathValue = usePathname().split("/")[2] || "gweets" + + const { + data: gweets, + isLoading, + isError, + isSuccess, + isFetchingNextPage, + fetchNextPage, + hasNextPage, + } = useGweets({ + queryKey: ["gweets", username, pathValue], + type: "user_" + pathValue, + id: username, + }) + + if (isLoading) { + return ( + <LoadingItem /> + ) + } + + if (isError) { + return ( + <TryAgain /> + ) + } + + if (gweets?.pages[0].gweets.length === 0) { + if (sessionname === username) { + return ( + <div className="m-6 flex justify-center"> + <div className="font-bold"> + <h1>You haven't gweeted anything yet.</h1> + <p>When you do, it'll show up here.</p> + </div> + </div> + ) + } else { + return ( + <div className="m-6 flex justify-center"> + <div className="font-bold"> + <h1><span className="text-sky-500">@{username}</span> hasn't gweeted anything yet.</h1> + <p>When they do, it'll show up here.</p> + </div> + </div> + ) + } + } + + return ( + <InfiniteGweets + gweets={gweets} + hasNextPage={hasNextPage} + fetchNextPage={fetchNextPage} + isFetchingNextPage={isFetchingNextPage} + isSuccess={isSuccess} + /> + ) +} \ No newline at end of file diff --git a/components/profile/components/user-join-date.tsx b/components/profile/components/user-join-date.tsx new file mode 100644 index 0000000000000000000000000000000000000000..16706d98df60d7c109652d6122f9b5f435c1dd50 --- /dev/null +++ b/components/profile/components/user-join-date.tsx @@ -0,0 +1,19 @@ +import { Icons } from "@/components/icons" +import dayjs from "dayjs" + +export const UserJoinDate = ({ + date, + showIcon = true, +}: { + date: Date | undefined + showIcon?: boolean +}) => { + return ( + <div className="flex items-center"> + {showIcon && (<Icons.calendar className="mr-1" />)} + <div> + Joined {dayjs(date).format("MMMM YYYY")} + </div> + </div> + ) +} \ No newline at end of file diff --git a/components/profile/hooks/use-follow.ts b/components/profile/hooks/use-follow.ts new file mode 100644 index 0000000000000000000000000000000000000000..0d9073a9931857ffba1152d497d7af1f7ee52ff0 --- /dev/null +++ b/components/profile/hooks/use-follow.ts @@ -0,0 +1,35 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query" + +import { followUser } from "../api/follow-user" +import { unfollowUser } from "../api/unfollow-user" + +export const useFollow = (type: "follow" | "unfollow") => { + const queryClient = useQueryClient() + + return useMutation( + ({ + userId, + }: { + userId: string + }) => { + return type === "follow" + ? followUser(userId) + : unfollowUser(userId) + }, + + { + onSuccess: () => { + console.log("success") + }, + + onError: () => { + console.log("error") + }, + + onSettled: ({ userId }) => { + queryClient.invalidateQueries(["users", userId]) + queryClient.invalidateQueries(["tweets"]) + }, + }, + ) +} \ No newline at end of file diff --git a/components/profile/hooks/use-get-follows.ts b/components/profile/hooks/use-get-follows.ts new file mode 100644 index 0000000000000000000000000000000000000000..4fe0b3eb91b61c5565b1014d49e67ac3b6e7a0dd --- /dev/null +++ b/components/profile/hooks/use-get-follows.ts @@ -0,0 +1,22 @@ +import { useQuery } from "@tanstack/react-query" + +import { getFollows } from "../api/get-follows" +import { IUser } from "../types" + +export const useGetFollows = ({ + id, + type, +}: { + id: string | undefined + type: string | undefined +}) => { + return useQuery<IUser[]>( + ["users", id, type], + async () => { + return getFollows(id, type) + }, + { + refetchOnWindowFocus: false, + }, + ) +} \ No newline at end of file diff --git a/components/profile/hooks/use-update-profile.ts b/components/profile/hooks/use-update-profile.ts new file mode 100644 index 0000000000000000000000000000000000000000..4f3804edd55decd57cb9183571aaabf9415633eb --- /dev/null +++ b/components/profile/hooks/use-update-profile.ts @@ -0,0 +1,34 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query" + +import { updateProfile } from "../api/update-profile" +import { IProfile } from "../types" + +export const useUpdateProfile = () => { + const queryClient = useQueryClient() + + return useMutation( + ({ + profile, + userId, + }: { + profile: IProfile + userId: string + }) => { + return updateProfile({ profile, userId }) + }, + + { + onSuccess: () => { + console.log("success") + }, + + onError: (error) => { + console.log(error) + }, + + onSettled: ({ userId }) => { + queryClient.invalidateQueries(["users", userId]) + }, + }, + ) +} \ No newline at end of file diff --git a/components/profile/hooks/use-user-likes.ts b/components/profile/hooks/use-user-likes.ts new file mode 100644 index 0000000000000000000000000000000000000000..e78735258031065988b70a002975cbed0129bb0b --- /dev/null +++ b/components/profile/hooks/use-user-likes.ts @@ -0,0 +1,17 @@ +import { useQuery } from "@tanstack/react-query" + +import { ILike } from "@/components/gweets/types" + +import { getUserLikes } from "../api/get-user-likes" + +export const useUserLikes = (id: string | undefined) => { + return useQuery<ILike[]>( + ["likes", { userId: id }], + async () => { + return getUserLikes(id) + }, + { + refetchOnWindowFocus: false, + }, + ) +} \ No newline at end of file diff --git a/components/profile/hooks/use-user.ts b/components/profile/hooks/use-user.ts new file mode 100644 index 0000000000000000000000000000000000000000..79760e1f4496e2accedc93a6bc5f5761a333b912 --- /dev/null +++ b/components/profile/hooks/use-user.ts @@ -0,0 +1,20 @@ +import { useQuery, useQueryClient } from "@tanstack/react-query" + +import { getUser } from "../api/get-user" +import { IUser } from "../types" + +export const useUser = (username: string | undefined) => { + const queryClient = useQueryClient() + return useQuery<IUser>( + ["users", username], + async () => { + return getUser(username) + }, + { + refetchOnWindowFocus: false, + onSuccess: (data) => { + queryClient.setQueryData(["users", username], data) + }, + }, + ) +} \ No newline at end of file diff --git a/components/profile/hooks/use-users.ts b/components/profile/hooks/use-users.ts new file mode 100644 index 0000000000000000000000000000000000000000..e2e70b6ab95fb0c541991e32d6b6dc07cfb6909e --- /dev/null +++ b/components/profile/hooks/use-users.ts @@ -0,0 +1,23 @@ +import { useQuery, useQueryClient } from "@tanstack/react-query" +import { useSession } from "next-auth/react" + +import { getUsers } from "../api/get-users" +import { IUser } from "../types" + +export const useUsers = () => { + const { data: session } = useSession() + + const queryClient = useQueryClient() + return useQuery<IUser[]>( + ["users"], + async () => { + return getUsers(session?.user?.id) + }, + { + refetchOnWindowFocus: false, + onSuccess: (data) => { + queryClient.setQueryData(["users"], data) + }, + }, + ) +} \ No newline at end of file diff --git a/components/profile/types/index.ts b/components/profile/types/index.ts index fb812e3c3c2b0522d6bbf2f1ac5fe6c7f425e971..ae11567b9c5fd4034c6ed8ffa6f9672b5157e7e9 100644 --- a/components/profile/types/index.ts +++ b/components/profile/types/index.ts @@ -1,11 +1,11 @@ -import { Follows, Like, User } from "@prisma/client" +import { Like, User } from "@prisma/client" import { IGweet } from "@/components/gweets/types" export interface IUser extends User { gweets: IGweet[] - followers: IFollow[] - following: IFollow[] + followers: User[] + following: User[] likes: ILike[] _count?: { followers?: number @@ -16,19 +16,19 @@ export interface IUser extends User { export interface IProfile { name: string bio: string | undefined + location: string | undefined + website: string | undefined banner: { + removed: boolean | undefined url: string | undefined + file: File | undefined } - avatar: { + image: { url: string | undefined + file: File | undefined } } -export interface IFollow extends Follows { - follower: IUser - following: IUser -} - export interface ILike extends Like { user: IUser gweet: IGweet diff --git a/components/profile/utils/following.ts b/components/profile/utils/following.ts new file mode 100644 index 0000000000000000000000000000000000000000..4c6c83ca24a661d916e2f9ee6fa697a4a4de4867 --- /dev/null +++ b/components/profile/utils/following.ts @@ -0,0 +1,11 @@ +import { IUser } from "../types" + +export const following = ({ + user, + sessionUserId, +}: { + user: IUser + sessionUserId: string +}): boolean => { + return user?.followers?.some((follower) => follower.id === sessionUserId) +} \ No newline at end of file diff --git a/components/user-auth-form.tsx b/components/user-auth-form.tsx index 21a0d035f98ffca9d1a140cb8837476d7fd1fb98..9ce00468bcd0ab0f760f86e1a5ae7a477e35e16a 100644 --- a/components/user-auth-form.tsx +++ b/components/user-auth-form.tsx @@ -55,6 +55,8 @@ export function UserAuthForm({ type, className, ...props }: UserAuthFormProps) { if (!res.ok) { if (res.status === 422) { setError('email', { type: 'manual', message: 'This email is already in use. Please choose another one.' }) + setIsLoading(false) + return } setIsLoading(false) diff --git a/components/user-not-found.tsx b/components/user-not-found.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f08a2ba9871320952c6853da4309ac6caeecd734 --- /dev/null +++ b/components/user-not-found.tsx @@ -0,0 +1,8 @@ +export const UserNotFound = () => { + return ( + <div className=""> + <h1>This account does not exist.</h1> + <p>Try searching for another.</p> + </div> + ) +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 53f161a7a9f5b13a5384ba2c661c6c751d7ce495..f9117fa243b149547f7e6f5c84df6df6ba9c811b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,8 +31,8 @@ "class-variance-authority": "^0.6.1", "clsx": "^1.2.1", "dayjs": "^1.11.9", - "lucide-react": "^0.258.0", - "next": "^13.4.8", + "lucide-react": "^0.259.0", + "next": "^13.4.9", "next-auth": "^4.22.1", "next-themes": "^0.2.1", "normalize-diacritics": "^4.0.0", @@ -53,15 +53,15 @@ "@testing-library/react": "^14.0.0", "@types/bcrypt": "^5.0.0", "@types/jest": "^29.5.2", - "@types/node": "^20.3.3", + "@types/node": "^20.4.0", "@types/react": "^18.2.14", "@types/react-dom": "^18.2.6", "autoprefixer": "10.4.14", "eslint": "^8.44.0", - "eslint-config-next": "^13.4.8", - "jest": "^29.6.0", - "jest-environment-jsdom": "^29.6.0", - "postcss": "8.4.24", + "eslint-config-next": "^13.4.9", + "jest": "^29.6.1", + "jest-environment-jsdom": "^29.6.1", + "postcss": "8.4.25", "prisma": "^4.16.2", "tailwindcss": "3.3.2", "typescript": "^5.1.6" @@ -160,20 +160,20 @@ } }, "node_modules/@babel/core": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.6.tgz", - "integrity": "sha512-HPIyDa6n+HKw5dEuway3vVAhBboYCtREBMp+IWeseZy6TFtzn6MHkCH2KKYUOC/vKKwgSMHQW4htBOrmuRPXfw==", + "version": "7.22.8", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.8.tgz", + "integrity": "sha512-75+KxFB4CZqYRXjx4NlR4J7yGvKumBuZTmV4NV6v09dVXXkuYVYLT68N6HCzLvfJ+fWCxQsntNzKwwIXL4bHnw==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.22.5", - "@babel/generator": "^7.22.5", + "@babel/generator": "^7.22.7", "@babel/helper-compilation-targets": "^7.22.6", "@babel/helper-module-transforms": "^7.22.5", "@babel/helpers": "^7.22.6", - "@babel/parser": "^7.22.6", + "@babel/parser": "^7.22.7", "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.6", + "@babel/traverse": "^7.22.8", "@babel/types": "^7.22.5", "@nicolo-ribaudo/semver-v6": "^6.3.3", "convert-source-map": "^1.7.0", @@ -196,9 +196,9 @@ "dev": true }, "node_modules/@babel/generator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz", - "integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==", + "version": "7.22.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.7.tgz", + "integrity": "sha512-p+jPjMG+SI8yvIaxGgeW24u7q9+5+TGpZh8/CuB7RhBKd7RCy8FayNEFNNKrNK/eUcY/4ExQqLmyrvBXKsIcwQ==", "dev": true, "dependencies": { "@babel/types": "^7.22.5", @@ -454,9 +454,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.6.tgz", - "integrity": "sha512-EIQu22vNkceq3LbjAq7knDf/UmtI2qbcNI8GRBlijez6TpQLvSodJPYfydQmNA5buwkxxxa/PVI44jjYZ+/cLw==", + "version": "7.22.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz", + "integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -668,18 +668,18 @@ } }, "node_modules/@babel/traverse": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.6.tgz", - "integrity": "sha512-53CijMvKlLIDlOTrdWiHileRddlIiwUIyCKqYa7lYnnPldXCG5dUSN38uT0cA6i7rHWNKJLH0VU/Kxdr1GzB3w==", + "version": "7.22.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.8.tgz", + "integrity": "sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw==", "dev": true, "dependencies": { "@babel/code-frame": "^7.22.5", - "@babel/generator": "^7.22.5", + "@babel/generator": "^7.22.7", "@babel/helper-environment-visitor": "^7.22.5", "@babel/helper-function-name": "^7.22.5", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.22.6", + "@babel/parser": "^7.22.7", "@babel/types": "^7.22.5", "debug": "^4.1.0", "globals": "^11.1.0" @@ -948,16 +948,16 @@ } }, "node_modules/@jest/console": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.6.0.tgz", - "integrity": "sha512-anb6L1yg7uPQpytNVA5skRaXy3BmrsU8icRhTVNbWdjYWDDfy8M1Kq5HIVRpYoABdbpqsc5Dr+jtu4+qWRQBiQ==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.6.1.tgz", + "integrity": "sha512-Aj772AYgwTSr5w8qnyoJ0eDYvN6bMsH3ORH1ivMotrInHLKdUz6BDlaEXHdM6kODaBIkNIyQGzsMvRdOv7VG7Q==", "dev": true, "dependencies": { - "@jest/types": "^29.6.0", + "@jest/types": "^29.6.1", "@types/node": "*", "chalk": "^4.0.0", - "jest-message-util": "^29.6.0", - "jest-util": "^29.6.0", + "jest-message-util": "^29.6.1", + "jest-util": "^29.6.1", "slash": "^3.0.0" }, "engines": { @@ -981,16 +981,16 @@ } }, "node_modules/@jest/core": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.6.0.tgz", - "integrity": "sha512-5dbMHfY/5R9m8NbgmB3JlxQqooZ/ooPSOiwEQZZ+HODwJTbIu37seVcZNBK29aMdXtjvTRB3f6LCvkKq+r8uQA==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.6.1.tgz", + "integrity": "sha512-CcowHypRSm5oYQ1obz1wfvkjZZ2qoQlrKKvlfPwh5jUXVU12TWr2qMeH8chLMuTFzHh5a1g2yaqlqDICbr+ukQ==", "dev": true, "dependencies": { - "@jest/console": "^29.6.0", - "@jest/reporters": "^29.6.0", - "@jest/test-result": "^29.6.0", - "@jest/transform": "^29.6.0", - "@jest/types": "^29.6.0", + "@jest/console": "^29.6.1", + "@jest/reporters": "^29.6.1", + "@jest/test-result": "^29.6.1", + "@jest/transform": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", @@ -998,20 +998,20 @@ "exit": "^0.1.2", "graceful-fs": "^4.2.9", "jest-changed-files": "^29.5.0", - "jest-config": "^29.6.0", - "jest-haste-map": "^29.6.0", - "jest-message-util": "^29.6.0", + "jest-config": "^29.6.1", + "jest-haste-map": "^29.6.1", + "jest-message-util": "^29.6.1", "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.6.0", - "jest-resolve-dependencies": "^29.6.0", - "jest-runner": "^29.6.0", - "jest-runtime": "^29.6.0", - "jest-snapshot": "^29.6.0", - "jest-util": "^29.6.0", - "jest-validate": "^29.6.0", - "jest-watcher": "^29.6.0", + "jest-resolve": "^29.6.1", + "jest-resolve-dependencies": "^29.6.1", + "jest-runner": "^29.6.1", + "jest-runtime": "^29.6.1", + "jest-snapshot": "^29.6.1", + "jest-util": "^29.6.1", + "jest-validate": "^29.6.1", + "jest-watcher": "^29.6.1", "micromatch": "^4.0.4", - "pretty-format": "^29.6.0", + "pretty-format": "^29.6.1", "slash": "^3.0.0", "strip-ansi": "^6.0.0" }, @@ -1044,9 +1044,9 @@ } }, "node_modules/@jest/core/node_modules/pretty-format": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.0.tgz", - "integrity": "sha512-XH+D4n7Ey0iSR6PdAnBs99cWMZdGsdKrR33iUHQNr79w1szKTCIZDVdXuccAsHVwDBp0XeWPfNEoaxP9EZgRmQ==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", "dev": true, "dependencies": { "@jest/schemas": "^29.6.0", @@ -1076,37 +1076,37 @@ "dev": true }, "node_modules/@jest/environment": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.6.0.tgz", - "integrity": "sha512-bUZLYUxYlUIsslBbxII0fq0kr1+friI3Gty+cRLmocGB1jdcAHs7FS8QdCDqedE8q4DZE1g/AJHH6OJZBLGGsg==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.6.1.tgz", + "integrity": "sha512-RMMXx4ws+Gbvw3DfLSuo2cfQlK7IwGbpuEWXCqyYDcqYTI+9Ju3a5hDnXaxjNsa6uKh9PQF2v+qg+RLe63tz5A==", "dev": true, "dependencies": { - "@jest/fake-timers": "^29.6.0", - "@jest/types": "^29.6.0", + "@jest/fake-timers": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", - "jest-mock": "^29.6.0" + "jest-mock": "^29.6.1" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/expect": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.6.0.tgz", - "integrity": "sha512-a7pISPW28Q3c0/pLwz4mQ6tbAI+hc8/0CJp9ix6e9U4dQ6TiHQX82CT5DV5BMWaw8bFH4E6zsfZxXdn6Ka23Bw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.6.1.tgz", + "integrity": "sha512-N5xlPrAYaRNyFgVf2s9Uyyvr795jnB6rObuPx4QFvNJz8aAjpZUDfO4bh5G/xuplMID8PrnuF1+SfSyDxhsgYg==", "dev": true, "dependencies": { - "expect": "^29.6.0", - "jest-snapshot": "^29.6.0" + "expect": "^29.6.1", + "jest-snapshot": "^29.6.1" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.6.0.tgz", - "integrity": "sha512-LLSQQN7oypMSETKoPWpsWYVKJd9LQWmSDDAc4hUQ4JocVC7LAMy9R3ZMhlnLwbcFvQORZnZR7HM893Px6cJhvA==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.6.1.tgz", + "integrity": "sha512-o319vIf5pEMx0LmzSxxkYYxo4wrRLKHq9dP1yJU7FoPTB0LfAKSz8SWD6D/6U3v/O52t9cF5t+MeJiRsfk7zMw==", "dev": true, "dependencies": { "jest-get-type": "^29.4.3" @@ -1116,48 +1116,48 @@ } }, "node_modules/@jest/fake-timers": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.6.0.tgz", - "integrity": "sha512-nuCU46AsZoskthWSDS2Aj6LARgyNcp5Fjx2qxsO/fPl1Wp1CJ+dBDqs0OkEcJK8FBeV/MbjH5efe79M2sHcV+A==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.6.1.tgz", + "integrity": "sha512-RdgHgbXyosCDMVYmj7lLpUwXA4c69vcNzhrt69dJJdf8azUrpRh3ckFCaTPNjsEeRi27Cig0oKDGxy5j7hOgHg==", "dev": true, "dependencies": { - "@jest/types": "^29.6.0", + "@jest/types": "^29.6.1", "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", - "jest-message-util": "^29.6.0", - "jest-mock": "^29.6.0", - "jest-util": "^29.6.0" + "jest-message-util": "^29.6.1", + "jest-mock": "^29.6.1", + "jest-util": "^29.6.1" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/globals": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.6.0.tgz", - "integrity": "sha512-IQQ3hZ2D/hwEwXSMv5GbfhzdH0nTQR3KPYxnuW6gYWbd6+7/zgMz7Okn6EgBbNtJNONq03k5EKA6HqGyzRbpeg==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.6.1.tgz", + "integrity": "sha512-2VjpaGy78JY9n9370H8zGRCFbYVWwjY6RdDMhoJHa1sYfwe6XM/azGN0SjY8kk7BOZApIejQ1BFPyH7FPG0w3A==", "dev": true, "dependencies": { - "@jest/environment": "^29.6.0", - "@jest/expect": "^29.6.0", - "@jest/types": "^29.6.0", - "jest-mock": "^29.6.0" + "@jest/environment": "^29.6.1", + "@jest/expect": "^29.6.1", + "@jest/types": "^29.6.1", + "jest-mock": "^29.6.1" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/reporters": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.6.0.tgz", - "integrity": "sha512-dWEq4HI0VvHcAD6XTtyBKKARLytyyWPIy1SvGOcU91106MfvHPdxZgupFwVHd8TFpZPpA3SebYjtwS5BUS76Rw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.6.1.tgz", + "integrity": "sha512-9zuaI9QKr9JnoZtFQlw4GREQbxgmNYXU6QuWtmuODvk5nvPUeBYapVR/VYMyi2WSx3jXTLJTJji8rN6+Cm4+FA==", "dev": true, "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.6.0", - "@jest/test-result": "^29.6.0", - "@jest/transform": "^29.6.0", - "@jest/types": "^29.6.0", + "@jest/console": "^29.6.1", + "@jest/test-result": "^29.6.1", + "@jest/transform": "^29.6.1", + "@jest/types": "^29.6.1", "@jridgewell/trace-mapping": "^0.3.18", "@types/node": "*", "chalk": "^4.0.0", @@ -1170,9 +1170,9 @@ "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.6.0", - "jest-util": "^29.6.0", - "jest-worker": "^29.6.0", + "jest-message-util": "^29.6.1", + "jest-util": "^29.6.1", + "jest-worker": "^29.6.1", "slash": "^3.0.0", "string-length": "^4.0.1", "strip-ansi": "^6.0.0", @@ -1233,13 +1233,13 @@ } }, "node_modules/@jest/test-result": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.6.0.tgz", - "integrity": "sha512-9qLb7xITeyWhM4yatn2muqfomuoCTOhv0QV9i7XiIyYi3QLfnvPv5NeJp5u0PZeutAOROMLKakOkmoAisOr3YQ==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.6.1.tgz", + "integrity": "sha512-Ynr13ZRcpX6INak0TPUukU8GWRfm/vAytE3JbJNGAvINySWYdfE7dGZMbk36oVuK4CigpbhMn8eg1dixZ7ZJOw==", "dev": true, "dependencies": { - "@jest/console": "^29.6.0", - "@jest/types": "^29.6.0", + "@jest/console": "^29.6.1", + "@jest/types": "^29.6.1", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" }, @@ -1248,14 +1248,14 @@ } }, "node_modules/@jest/test-sequencer": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.6.0.tgz", - "integrity": "sha512-HYCS3LKRQotKWj2mnA3AN13PPevYZu8MJKm12lzYojpJNnn6kI/3PWmr1At/e3tUu+FHQDiOyaDVuR4EV3ezBw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.6.1.tgz", + "integrity": "sha512-oBkC36PCDf/wb6dWeQIhaviU0l5u6VCsXa119yqdUosYAt7/FbQU2M2UoziO3igj/HBDEgp57ONQ3fm0v9uyyg==", "dev": true, "dependencies": { - "@jest/test-result": "^29.6.0", + "@jest/test-result": "^29.6.1", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.6.0", + "jest-haste-map": "^29.6.1", "slash": "^3.0.0" }, "engines": { @@ -1263,22 +1263,22 @@ } }, "node_modules/@jest/transform": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.6.0.tgz", - "integrity": "sha512-bhP/KxPo3e322FJ0nKAcb6WVK76ZYyQd1lWygJzoSqP8SYMSLdxHqP4wnPTI4WvbB8PKPDV30y5y7Tya4RHOBA==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.6.1.tgz", + "integrity": "sha512-URnTneIU3ZjRSaf906cvf6Hpox3hIeJXRnz3VDSw5/X93gR8ycdfSIEy19FlVx8NFmpN7fe3Gb1xF+NjXaQLWg==", "dev": true, "dependencies": { "@babel/core": "^7.11.6", - "@jest/types": "^29.6.0", + "@jest/types": "^29.6.1", "@jridgewell/trace-mapping": "^0.3.18", "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.6.0", + "jest-haste-map": "^29.6.1", "jest-regex-util": "^29.4.3", - "jest-util": "^29.6.0", + "jest-util": "^29.6.1", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", @@ -1305,9 +1305,9 @@ } }, "node_modules/@jest/types": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.0.tgz", - "integrity": "sha512-8XCgL9JhqbJTFnMRjEAO+TuW251+MoMd5BSzLiE3vvzpQ8RlBxy8NoyNkDhs3K3OL3HeVinlOl9or5p7GTeOLg==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.1.tgz", + "integrity": "sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw==", "dev": true, "dependencies": { "@jest/schemas": "^29.6.0", @@ -1405,23 +1405,23 @@ } }, "node_modules/@next/env": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.8.tgz", - "integrity": "sha512-twuSf1klb3k9wXI7IZhbZGtFCWvGD4wXTY2rmvzIgVhXhs7ISThrbNyutBx3jWIL8Y/Hk9+woytFz5QsgtcRKQ==" + "version": "13.4.9", + "resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.9.tgz", + "integrity": "sha512-vuDRK05BOKfmoBYLNi2cujG2jrYbEod/ubSSyqgmEx9n/W3eZaJQdRNhTfumO+qmq/QTzLurW487n/PM/fHOkw==" }, "node_modules/@next/eslint-plugin-next": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-13.4.8.tgz", - "integrity": "sha512-cmfVHpxWjjcETFt2WHnoFU6EmY69QcPJRlRNAooQlNe53Ke90vg1Ci/dkPffryJZaxxiRziP9bQrV8lDVCn3Fw==", + "version": "13.4.9", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-13.4.9.tgz", + "integrity": "sha512-nDtGpa992tNyAkT/KmSMy7QkHfNZmGCBYhHtafU97DubqxzNdvLsqRtliQ4FU04CysRCtvP2hg8rRC1sAKUTUA==", "dev": true, "dependencies": { "glob": "7.1.7" } }, "node_modules/@next/swc-darwin-arm64": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.8.tgz", - "integrity": "sha512-MSFplVM4dTWOuKAUv0XR9gY7AWtMSBu9os9f+kp+s5rWhM1I2CdR3obFttd6366nS/W/VZxbPM5oEIdlIa46zA==", + "version": "13.4.9", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.9.tgz", + "integrity": "sha512-TVzGHpZoVBk3iDsTOQA/R6MGmFp0+17SWXMEWd6zG30AfuELmSSMe2SdPqxwXU0gbpWkJL1KgfLzy5ReN0crqQ==", "cpu": [ "arm64" ], @@ -1434,9 +1434,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.8.tgz", - "integrity": "sha512-Reox+UXgonon9P0WNDE6w85DGtyBqGitl/ryznOvn6TvfxEaZIpTgeu3ZrJLU9dHSMhiK7YAM793mE/Zii2/Qw==", + "version": "13.4.9", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.9.tgz", + "integrity": "sha512-aSfF1fhv28N2e7vrDZ6zOQ+IIthocfaxuMWGReB5GDriF0caTqtHttAvzOMgJgXQtQx6XhyaJMozLTSEXeNN+A==", "cpu": [ "x64" ], @@ -1449,9 +1449,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.8.tgz", - "integrity": "sha512-kdyzYvAYtqQVgzIKNN7e1rLU8aZv86FDSRqPlOkKZlvqudvTO0iohuTPmnEEDlECeBM6qRPShNffotDcU/R2KA==", + "version": "13.4.9", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.9.tgz", + "integrity": "sha512-JhKoX5ECzYoTVyIy/7KykeO4Z2lVKq7HGQqvAH+Ip9UFn1MOJkOnkPRB7v4nmzqAoY+Je05Aj5wNABR1N18DMg==", "cpu": [ "arm64" ], @@ -1464,9 +1464,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.8.tgz", - "integrity": "sha512-oWxx4yRkUGcR81XwbI+T0zhZ3bDF6V1aVLpG+C7hSG50ULpV8gC39UxVO22/bv93ZlcfMY4zl8xkz9Klct6dpQ==", + "version": "13.4.9", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.9.tgz", + "integrity": "sha512-OOn6zZBIVkm/4j5gkPdGn4yqQt+gmXaLaSjRSO434WplV8vo2YaBNbSHaTM9wJpZTHVDYyjzuIYVEzy9/5RVZw==", "cpu": [ "arm64" ], @@ -1479,9 +1479,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.8.tgz", - "integrity": "sha512-anhtvuO6eE9YRhYnaEGTfbpH3L5gT/9qPFcNoi6xS432r/4DAtpJY8kNktqkTVevVIC/pVumqO8tV59PR3zbNg==", + "version": "13.4.9", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.9.tgz", + "integrity": "sha512-iA+fJXFPpW0SwGmx/pivVU+2t4zQHNOOAr5T378PfxPHY6JtjV6/0s1vlAJUdIHeVpX98CLp9k5VuKgxiRHUpg==", "cpu": [ "x64" ], @@ -1494,9 +1494,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.8.tgz", - "integrity": "sha512-aR+J4wWfNgH1DwCCBNjan7Iumx0lLtn+2/rEYuhIrYLY4vnxqSVGz9u3fXcgUwo6Q9LT8NFkaqK1vPprdq+BXg==", + "version": "13.4.9", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.9.tgz", + "integrity": "sha512-rlNf2WUtMM+GAQrZ9gMNdSapkVi3koSW3a+dmBVp42lfugWVvnyzca/xJlN48/7AGx8qu62WyO0ya1ikgOxh6A==", "cpu": [ "x64" ], @@ -1509,9 +1509,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.8.tgz", - "integrity": "sha512-OWBKIrJwQBTqrat0xhxEB/jcsjJR3+diD9nc/Y8F1mRdQzsn4bPsomgJyuqPVZs6Lz3K18qdIkvywmfSq75SsQ==", + "version": "13.4.9", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.9.tgz", + "integrity": "sha512-5T9ybSugXP77nw03vlgKZxD99AFTHaX8eT1ayKYYnGO9nmYhJjRPxcjU5FyYI+TdkQgEpIcH7p/guPLPR0EbKA==", "cpu": [ "arm64" ], @@ -1524,9 +1524,9 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.8.tgz", - "integrity": "sha512-agiPWGjUndXGTOn4ChbKipQXRA6/UPkywAWIkx7BhgGv48TiJfHTK6MGfBoL9tS6B4mtW39++uy0wFPnfD0JWg==", + "version": "13.4.9", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.9.tgz", + "integrity": "sha512-ojZTCt1lP2ucgpoiFgrFj07uq4CZsq4crVXpLGgQfoFq00jPKRPgesuGPaz8lg1yLfvafkU3Jd1i8snKwYR3LA==", "cpu": [ "ia32" ], @@ -1539,9 +1539,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.8.tgz", - "integrity": "sha512-UIRKoByVKbuR6SnFG4JM8EMFlJrfEGuUQ1ihxzEleWcNwRMMiVaCj1KyqfTOW8VTQhJ0u8P1Ngg6q1RwnIBTtw==", + "version": "13.4.9", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.9.tgz", + "integrity": "sha512-QbT03FXRNdpuL+e9pLnu+XajZdm/TtIXVYY4lA9t+9l0fLZbHXDYEKitAqxrOj37o3Vx5ufxiRAniaIebYDCgw==", "cpu": [ "x64" ], @@ -2905,9 +2905,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.3.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.3.tgz", - "integrity": "sha512-wheIYdr4NYML61AjC8MKj/2jrR/kDQri/CIpVoZwldwhnIrD/j9jIU5bJ8yBKuB2VhpFV7Ab6G2XkBjv9r9Zzw==", + "version": "20.4.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.0.tgz", + "integrity": "sha512-jfT7iTf/4kOQ9S7CHV9BIyRaQqHu67mOjsIQBC3BKZvzvUB6zLxEwJ6sBE3ozcvP8kF6Uk5PXN0Q+c0dfhGX0g==", "dev": true }, "node_modules/@types/prettier": { @@ -3484,12 +3484,12 @@ } }, "node_modules/babel-jest": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.6.0.tgz", - "integrity": "sha512-Jj8Bq2yKsk11XLk06Nm8SdvYkAcecH+GuhxB8DnK5SncjHnJ88TQjSnGgE7jpajpnSvz9DZ6X8hXrDkD/6/TPQ==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.6.1.tgz", + "integrity": "sha512-qu+3bdPEQC6KZSPz+4Fyjbga5OODNcp49j6GKzG1EKbkfyJBxEYGVUmVGpwCSeGouG52R4EgYMLb6p9YeEEQ4A==", "dev": true, "dependencies": { - "@jest/transform": "^29.6.0", + "@jest/transform": "^29.6.1", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", "babel-preset-jest": "^29.5.0", @@ -3925,9 +3925,9 @@ } }, "node_modules/collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", "dev": true }, "node_modules/color-convert": { @@ -4702,12 +4702,12 @@ } }, "node_modules/eslint-config-next": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-13.4.8.tgz", - "integrity": "sha512-2hE0b6lHuhtHBX8VgEXi8v4G8PVrPUBMOSLCTq8qtcQ2qQOX7+uBOLK2kU4FD2qDZzyXNlhmuH+WLT5ptY4XLA==", + "version": "13.4.9", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-13.4.9.tgz", + "integrity": "sha512-0fLtKRR268NArpqeXXwnLgMXPvF64YESQvptVg+RMLCaijKm3FICN9Y7Jc1p2o+yrWwE4DufJXDM/Vo53D1L7g==", "dev": true, "dependencies": { - "@next/eslint-plugin-next": "13.4.8", + "@next/eslint-plugin-next": "13.4.9", "@rushstack/eslint-patch": "^1.1.3", "@typescript-eslint/parser": "^5.42.0", "eslint-import-resolver-node": "^0.3.6", @@ -4715,7 +4715,7 @@ "eslint-plugin-import": "^2.26.0", "eslint-plugin-jsx-a11y": "^6.5.1", "eslint-plugin-react": "^7.31.7", - "eslint-plugin-react-hooks": "^4.5.0" + "eslint-plugin-react-hooks": "5.0.0-canary-7118f5dd7-20230705" }, "peerDependencies": { "eslint": "^7.23.0 || ^8.0.0", @@ -4940,9 +4940,9 @@ } }, "node_modules/eslint-plugin-react-hooks": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", - "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "version": "5.0.0-canary-7118f5dd7-20230705", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.0.0-canary-7118f5dd7-20230705.tgz", + "integrity": "sha512-AZYbMo/NW9chdL7vk6HQzQhT+PvTAEVqWk9ziruUoW2kAOcN5qNyelv70e0F1VNQAbvutOC9oc+xfWycI9FxDw==", "dev": true, "engines": { "node": ">=10" @@ -5129,17 +5129,17 @@ } }, "node_modules/expect": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.6.0.tgz", - "integrity": "sha512-AV+HaBtnDJ2YEUhPPo25HyUHBLaetM+y/Dq6pEC8VPQyt1dK+k8MfGkMy46djy2bddcqESc1kl4/K1uLWSfk9g==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.6.1.tgz", + "integrity": "sha512-XEdDLonERCU1n9uR56/Stx9OqojaLAQtZf9PrCHH9Hl8YXiEIka3H4NXJ3NOIBmQJTg7+j7buh34PMHfJujc8g==", "dev": true, "dependencies": { - "@jest/expect-utils": "^29.6.0", + "@jest/expect-utils": "^29.6.1", "@types/node": "*", "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.6.0", - "jest-message-util": "^29.6.0", - "jest-util": "^29.6.0" + "jest-matcher-utils": "^29.6.1", + "jest-message-util": "^29.6.1", + "jest-util": "^29.6.1" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -6332,15 +6332,15 @@ } }, "node_modules/jest": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.6.0.tgz", - "integrity": "sha512-do1J9gGrQ68E4UfMz/4OM71p9qCqQxu32N/9ZfeYFSSlx0uUOuxeyZxtJZNaUTW12ZA11ERhmBjBhy1Ho96R4g==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.6.1.tgz", + "integrity": "sha512-Nirw5B4nn69rVUZtemCQhwxOBhm0nsp3hmtF4rzCeWD7BkjAXRIji7xWQfnTNbz9g0aVsBX6aZK3n+23LM6uDw==", "dev": true, "dependencies": { - "@jest/core": "^29.6.0", - "@jest/types": "^29.6.0", + "@jest/core": "^29.6.1", + "@jest/types": "^29.6.1", "import-local": "^3.0.2", - "jest-cli": "^29.6.0" + "jest-cli": "^29.6.1" }, "bin": { "jest": "bin/jest.js" @@ -6371,28 +6371,28 @@ } }, "node_modules/jest-circus": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.6.0.tgz", - "integrity": "sha512-LtG45qEKhse2Ws5zNR4DnZATReLGQXzBZGZnJ0DU37p6d4wDhu41vvczCQ3Ou+llR6CRYDBshsubV7H4jZvIkw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.6.1.tgz", + "integrity": "sha512-tPbYLEiBU4MYAL2XoZme/bgfUeotpDBd81lgHLCbDZZFaGmECk0b+/xejPFtmiBP87GgP/y4jplcRpbH+fgCzQ==", "dev": true, "dependencies": { - "@jest/environment": "^29.6.0", - "@jest/expect": "^29.6.0", - "@jest/test-result": "^29.6.0", - "@jest/types": "^29.6.0", + "@jest/environment": "^29.6.1", + "@jest/expect": "^29.6.1", + "@jest/test-result": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", "dedent": "^0.7.0", "is-generator-fn": "^2.0.0", - "jest-each": "^29.6.0", - "jest-matcher-utils": "^29.6.0", - "jest-message-util": "^29.6.0", - "jest-runtime": "^29.6.0", - "jest-snapshot": "^29.6.0", - "jest-util": "^29.6.0", + "jest-each": "^29.6.1", + "jest-matcher-utils": "^29.6.1", + "jest-message-util": "^29.6.1", + "jest-runtime": "^29.6.1", + "jest-snapshot": "^29.6.1", + "jest-util": "^29.6.1", "p-limit": "^3.1.0", - "pretty-format": "^29.6.0", + "pretty-format": "^29.6.1", "pure-rand": "^6.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" @@ -6418,9 +6418,9 @@ } }, "node_modules/jest-circus/node_modules/pretty-format": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.0.tgz", - "integrity": "sha512-XH+D4n7Ey0iSR6PdAnBs99cWMZdGsdKrR33iUHQNr79w1szKTCIZDVdXuccAsHVwDBp0XeWPfNEoaxP9EZgRmQ==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", "dev": true, "dependencies": { "@jest/schemas": "^29.6.0", @@ -6450,21 +6450,21 @@ "dev": true }, "node_modules/jest-cli": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.6.0.tgz", - "integrity": "sha512-WvZIaanK/abkw6s01924DQ2QLwM5Q4Y4iPbSDb9Zg6smyXGqqcPQ7ft9X8D7B0jICz312eSzM6UlQNxuZJBrMw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.6.1.tgz", + "integrity": "sha512-607dSgTA4ODIN6go9w6xY3EYkyPFGicx51a69H7yfvt7lN53xNswEVLovq+E77VsTRi5fWprLH0yl4DJgE8Ing==", "dev": true, "dependencies": { - "@jest/core": "^29.6.0", - "@jest/test-result": "^29.6.0", - "@jest/types": "^29.6.0", + "@jest/core": "^29.6.1", + "@jest/test-result": "^29.6.1", + "@jest/types": "^29.6.1", "chalk": "^4.0.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", "import-local": "^3.0.2", - "jest-config": "^29.6.0", - "jest-util": "^29.6.0", - "jest-validate": "^29.6.0", + "jest-config": "^29.6.1", + "jest-util": "^29.6.1", + "jest-validate": "^29.6.1", "prompts": "^2.0.1", "yargs": "^17.3.1" }, @@ -6500,31 +6500,31 @@ } }, "node_modules/jest-config": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.6.0.tgz", - "integrity": "sha512-fKA4jM91PDqWVkMpb1FVKxIuhg3hC6hgaen57cr1rRZkR96dCatvJZsk3ik7/GNu9ERj9wgAspOmyvkFoGsZhA==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.6.1.tgz", + "integrity": "sha512-XdjYV2fy2xYixUiV2Wc54t3Z4oxYPAELUzWnV6+mcbq0rh742X2p52pii5A3oeRzYjLnQxCsZmp0qpI6klE2cQ==", "dev": true, "dependencies": { "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.6.0", - "@jest/types": "^29.6.0", - "babel-jest": "^29.6.0", + "@jest/test-sequencer": "^29.6.1", + "@jest/types": "^29.6.1", + "babel-jest": "^29.6.1", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-circus": "^29.6.0", - "jest-environment-node": "^29.6.0", + "jest-circus": "^29.6.1", + "jest-environment-node": "^29.6.1", "jest-get-type": "^29.4.3", "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.6.0", - "jest-runner": "^29.6.0", - "jest-util": "^29.6.0", - "jest-validate": "^29.6.0", + "jest-resolve": "^29.6.1", + "jest-runner": "^29.6.1", + "jest-util": "^29.6.1", + "jest-validate": "^29.6.1", "micromatch": "^4.0.4", "parse-json": "^5.2.0", - "pretty-format": "^29.6.0", + "pretty-format": "^29.6.1", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, @@ -6561,9 +6561,9 @@ } }, "node_modules/jest-config/node_modules/pretty-format": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.0.tgz", - "integrity": "sha512-XH+D4n7Ey0iSR6PdAnBs99cWMZdGsdKrR33iUHQNr79w1szKTCIZDVdXuccAsHVwDBp0XeWPfNEoaxP9EZgRmQ==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", "dev": true, "dependencies": { "@jest/schemas": "^29.6.0", @@ -6593,15 +6593,15 @@ "dev": true }, "node_modules/jest-diff": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.6.0.tgz", - "integrity": "sha512-ZRm7cd2m9YyZ0N3iMyuo1iUiprxQ/MFpYWXzEEj7hjzL3WnDffKW8192XBDcrAI8j7hnrM1wed3bL/oEnYF/8w==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.6.1.tgz", + "integrity": "sha512-FsNCvinvl8oVxpNLttNQX7FAq7vR+gMDGj90tiP7siWw1UdakWUGqrylpsYrpvj908IYckm5Y0Q7azNAozU1Kg==", "dev": true, "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.4.3", "jest-get-type": "^29.4.3", - "pretty-format": "^29.6.0" + "pretty-format": "^29.6.1" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -6624,9 +6624,9 @@ } }, "node_modules/jest-diff/node_modules/pretty-format": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.0.tgz", - "integrity": "sha512-XH+D4n7Ey0iSR6PdAnBs99cWMZdGsdKrR33iUHQNr79w1szKTCIZDVdXuccAsHVwDBp0XeWPfNEoaxP9EZgRmQ==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", "dev": true, "dependencies": { "@jest/schemas": "^29.6.0", @@ -6668,16 +6668,16 @@ } }, "node_modules/jest-each": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.6.0.tgz", - "integrity": "sha512-d0Jem4RBAlFUyV6JSXPSHVUpNo5RleSj+iJEy1G3+ZCrzHDjWs/1jUfrbnJKHdJdAx5BCEce/Ju379WqHhQk4w==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.6.1.tgz", + "integrity": "sha512-n5eoj5eiTHpKQCAVcNTT7DRqeUmJ01hsAL0Q1SMiBHcBcvTKDELixQOGMCpqhbIuTcfC4kMfSnpmDqRgRJcLNQ==", "dev": true, "dependencies": { - "@jest/types": "^29.6.0", + "@jest/types": "^29.6.1", "chalk": "^4.0.0", "jest-get-type": "^29.4.3", - "jest-util": "^29.6.0", - "pretty-format": "^29.6.0" + "jest-util": "^29.6.1", + "pretty-format": "^29.6.1" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -6700,9 +6700,9 @@ } }, "node_modules/jest-each/node_modules/pretty-format": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.0.tgz", - "integrity": "sha512-XH+D4n7Ey0iSR6PdAnBs99cWMZdGsdKrR33iUHQNr79w1szKTCIZDVdXuccAsHVwDBp0XeWPfNEoaxP9EZgRmQ==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", "dev": true, "dependencies": { "@jest/schemas": "^29.6.0", @@ -6732,18 +6732,18 @@ "dev": true }, "node_modules/jest-environment-jsdom": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.6.0.tgz", - "integrity": "sha512-/cOhoyv+uMbOh4nQPyqtkPas/uUxr5AbK6TPqMMFyj1qEJURY78RhqgBjOFIX02+Lvu5V0RWLq2qKY1dHubFOQ==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.6.1.tgz", + "integrity": "sha512-PoY+yLaHzVRhVEjcVKSfJ7wXmJW4UqPYNhR05h7u/TK0ouf6DmRNZFBL/Z00zgQMyWGMBXn69/FmOvhEJu8cIw==", "dev": true, "dependencies": { - "@jest/environment": "^29.6.0", - "@jest/fake-timers": "^29.6.0", - "@jest/types": "^29.6.0", + "@jest/environment": "^29.6.1", + "@jest/fake-timers": "^29.6.1", + "@jest/types": "^29.6.1", "@types/jsdom": "^20.0.0", "@types/node": "*", - "jest-mock": "^29.6.0", - "jest-util": "^29.6.0", + "jest-mock": "^29.6.1", + "jest-util": "^29.6.1", "jsdom": "^20.0.0" }, "engines": { @@ -6759,17 +6759,17 @@ } }, "node_modules/jest-environment-node": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.6.0.tgz", - "integrity": "sha512-BOf5Q2/nFCdBOnyBM5c5/6DbdQYgc+0gyUQ8l8qhUAB8O7pM+4QJXIXJsRZJaxd5SHV6y5VArTVhOfogoqcP8Q==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.6.1.tgz", + "integrity": "sha512-ZNIfAiE+foBog24W+2caIldl4Irh8Lx1PUhg/GZ0odM1d/h2qORAsejiFc7zb+SEmYPn1yDZzEDSU5PmDkmVLQ==", "dev": true, "dependencies": { - "@jest/environment": "^29.6.0", - "@jest/fake-timers": "^29.6.0", - "@jest/types": "^29.6.0", + "@jest/environment": "^29.6.1", + "@jest/fake-timers": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", - "jest-mock": "^29.6.0", - "jest-util": "^29.6.0" + "jest-mock": "^29.6.1", + "jest-util": "^29.6.1" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -6785,20 +6785,20 @@ } }, "node_modules/jest-haste-map": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.6.0.tgz", - "integrity": "sha512-dY1DKufptj7hcJSuhpqlYPGcnN3XjlOy/g0jinpRTMsbb40ivZHiuIPzeminOZkrek8C+oDxC54ILGO3vMLojg==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.6.1.tgz", + "integrity": "sha512-0m7f9PZXxOCk1gRACiVgX85knUKPKLPg4oRCjLoqIm9brTHXaorMA0JpmtmVkQiT8nmXyIVoZd/nnH1cfC33ig==", "dev": true, "dependencies": { - "@jest/types": "^29.6.0", + "@jest/types": "^29.6.1", "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "graceful-fs": "^4.2.9", "jest-regex-util": "^29.4.3", - "jest-util": "^29.6.0", - "jest-worker": "^29.6.0", + "jest-util": "^29.6.1", + "jest-worker": "^29.6.1", "micromatch": "^4.0.4", "walker": "^1.0.8" }, @@ -6810,13 +6810,13 @@ } }, "node_modules/jest-leak-detector": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.6.0.tgz", - "integrity": "sha512-JdV6EZOPxHR1gd6ccxjNowuROkT2jtGU5G/g58RcJX1xe5mrtLj0g6/ZkyMoXF4cs+tTkHMFX6pcIrB1QPQwCw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.6.1.tgz", + "integrity": "sha512-OrxMNyZirpOEwkF3UHnIkAiZbtkBWiye+hhBweCHkVbCgyEy71Mwbb5zgeTNYWJBi1qgDVfPC1IwO9dVEeTLwQ==", "dev": true, "dependencies": { "jest-get-type": "^29.4.3", - "pretty-format": "^29.6.0" + "pretty-format": "^29.6.1" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -6835,9 +6835,9 @@ } }, "node_modules/jest-leak-detector/node_modules/pretty-format": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.0.tgz", - "integrity": "sha512-XH+D4n7Ey0iSR6PdAnBs99cWMZdGsdKrR33iUHQNr79w1szKTCIZDVdXuccAsHVwDBp0XeWPfNEoaxP9EZgRmQ==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", "dev": true, "dependencies": { "@jest/schemas": "^29.6.0", @@ -6855,15 +6855,15 @@ "dev": true }, "node_modules/jest-matcher-utils": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.6.0.tgz", - "integrity": "sha512-oSlqfGN+sbkB2Q5um/zL7z80w84FEAcLKzXBZIPyRk2F2Srg1ubhrHVKW68JCvb2+xKzAeGw35b+6gciS24PHw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.6.1.tgz", + "integrity": "sha512-SLaztw9d2mfQQKHmJXKM0HCbl2PPVld/t9Xa6P9sgiExijviSp7TnZZpw2Fpt+OI3nwUO/slJbOfzfUMKKC5QA==", "dev": true, "dependencies": { "chalk": "^4.0.0", - "jest-diff": "^29.6.0", + "jest-diff": "^29.6.1", "jest-get-type": "^29.4.3", - "pretty-format": "^29.6.0" + "pretty-format": "^29.6.1" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -6886,9 +6886,9 @@ } }, "node_modules/jest-matcher-utils/node_modules/pretty-format": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.0.tgz", - "integrity": "sha512-XH+D4n7Ey0iSR6PdAnBs99cWMZdGsdKrR33iUHQNr79w1szKTCIZDVdXuccAsHVwDBp0XeWPfNEoaxP9EZgRmQ==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", "dev": true, "dependencies": { "@jest/schemas": "^29.6.0", @@ -6918,18 +6918,18 @@ "dev": true }, "node_modules/jest-message-util": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.6.0.tgz", - "integrity": "sha512-mkCp56cETbpoNtsaeWVy6SKzk228mMi9FPHSObaRIhbR2Ujw9PqjW/yqVHD2tN1bHbC8ol6h3UEo7dOPmIYwIA==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.6.1.tgz", + "integrity": "sha512-KoAW2zAmNSd3Gk88uJ56qXUWbFk787QKmjjJVOjtGFmmGSZgDBrlIL4AfQw1xyMYPNVD7dNInfIbur9B2rd/wQ==", "dev": true, "dependencies": { "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.0", + "@jest/types": "^29.6.1", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", - "pretty-format": "^29.6.0", + "pretty-format": "^29.6.1", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, @@ -6954,9 +6954,9 @@ } }, "node_modules/jest-message-util/node_modules/pretty-format": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.0.tgz", - "integrity": "sha512-XH+D4n7Ey0iSR6PdAnBs99cWMZdGsdKrR33iUHQNr79w1szKTCIZDVdXuccAsHVwDBp0XeWPfNEoaxP9EZgRmQ==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", "dev": true, "dependencies": { "@jest/schemas": "^29.6.0", @@ -6986,14 +6986,14 @@ "dev": true }, "node_modules/jest-mock": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.0.tgz", - "integrity": "sha512-2Pb7R2w24Q0aUVn+2/vdRDL6CqGqpheDZy7zrXav8FotOpSGw/4bS2hyVoKHMEx4xzOn6EyCAGwc5czWxXeN7w==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.1.tgz", + "integrity": "sha512-brovyV9HBkjXAEdRooaTQK42n8usKoSRR3gihzUpYeV/vwqgSoNfrksO7UfSACnPmxasO/8TmHM3w9Hp3G1dgw==", "dev": true, "dependencies": { - "@jest/types": "^29.6.0", + "@jest/types": "^29.6.1", "@types/node": "*", - "jest-util": "^29.6.0" + "jest-util": "^29.6.1" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -7026,17 +7026,17 @@ } }, "node_modules/jest-resolve": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.6.0.tgz", - "integrity": "sha512-+hrpY4LzAONoZA/rvB6rnZLkOSA6UgJLpdCWrOZNSgGxWMumzRLu7dLUSCabAHzoHIDQ9qXfr3th1zYNJ0E8sQ==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.6.1.tgz", + "integrity": "sha512-AeRkyS8g37UyJiP9w3mmI/VXU/q8l/IH52vj/cDAyScDcemRbSBhfX/NMYIGilQgSVwsjxrCHf3XJu4f+lxCMg==", "dev": true, "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.6.0", + "jest-haste-map": "^29.6.1", "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.6.0", - "jest-validate": "^29.6.0", + "jest-util": "^29.6.1", + "jest-validate": "^29.6.1", "resolve": "^1.20.0", "resolve.exports": "^2.0.0", "slash": "^3.0.0" @@ -7046,13 +7046,13 @@ } }, "node_modules/jest-resolve-dependencies": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.0.tgz", - "integrity": "sha512-eOfPog9K3hJdJk/3i6O6bQhXS+3uXhMDkLJGX+xmMPp7T1d/zdcFofbDnHgNoEkhD/mSimC5IagLEP7lpLLu/A==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.1.tgz", + "integrity": "sha512-BbFvxLXtcldaFOhNMXmHRWx1nXQO5LoXiKSGQcA1LxxirYceZT6ch8KTE1bK3X31TNG/JbkI7OkS/ABexVahiw==", "dev": true, "dependencies": { "jest-regex-util": "^29.4.3", - "jest-snapshot": "^29.6.0" + "jest-snapshot": "^29.6.1" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -7075,30 +7075,30 @@ } }, "node_modules/jest-runner": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.6.0.tgz", - "integrity": "sha512-4fZuGV2lOxS2BiqEG9/AI8E6O+jo+QZjMVcgi1x5E6aDql0Gd/EFIbUQ0pSS09y8cya1vJB/qC2xsE468jqtSg==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.6.1.tgz", + "integrity": "sha512-tw0wb2Q9yhjAQ2w8rHRDxteryyIck7gIzQE4Reu3JuOBpGp96xWgF0nY8MDdejzrLCZKDcp8JlZrBN/EtkQvPQ==", "dev": true, "dependencies": { - "@jest/console": "^29.6.0", - "@jest/environment": "^29.6.0", - "@jest/test-result": "^29.6.0", - "@jest/transform": "^29.6.0", - "@jest/types": "^29.6.0", + "@jest/console": "^29.6.1", + "@jest/environment": "^29.6.1", + "@jest/test-result": "^29.6.1", + "@jest/transform": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", "chalk": "^4.0.0", "emittery": "^0.13.1", "graceful-fs": "^4.2.9", "jest-docblock": "^29.4.3", - "jest-environment-node": "^29.6.0", - "jest-haste-map": "^29.6.0", - "jest-leak-detector": "^29.6.0", - "jest-message-util": "^29.6.0", - "jest-resolve": "^29.6.0", - "jest-runtime": "^29.6.0", - "jest-util": "^29.6.0", - "jest-watcher": "^29.6.0", - "jest-worker": "^29.6.0", + "jest-environment-node": "^29.6.1", + "jest-haste-map": "^29.6.1", + "jest-leak-detector": "^29.6.1", + "jest-message-util": "^29.6.1", + "jest-resolve": "^29.6.1", + "jest-runtime": "^29.6.1", + "jest-util": "^29.6.1", + "jest-watcher": "^29.6.1", + "jest-worker": "^29.6.1", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, @@ -7123,31 +7123,31 @@ } }, "node_modules/jest-runtime": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.6.0.tgz", - "integrity": "sha512-5FavYo3EeXLHIvnJf+r7Cj0buePAbe4mzRB9oeVxDS0uVmouSBjWeGgyRjZkw7ArxOoZI8gO6f8SGMJ2HFlwwg==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.6.1.tgz", + "integrity": "sha512-D6/AYOA+Lhs5e5il8+5pSLemjtJezUr+8zx+Sn8xlmOux3XOqx4d8l/2udBea8CRPqqrzhsKUsN/gBDE/IcaPQ==", "dev": true, "dependencies": { - "@jest/environment": "^29.6.0", - "@jest/fake-timers": "^29.6.0", - "@jest/globals": "^29.6.0", + "@jest/environment": "^29.6.1", + "@jest/fake-timers": "^29.6.1", + "@jest/globals": "^29.6.1", "@jest/source-map": "^29.6.0", - "@jest/test-result": "^29.6.0", - "@jest/transform": "^29.6.0", - "@jest/types": "^29.6.0", + "@jest/test-result": "^29.6.1", + "@jest/transform": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", "chalk": "^4.0.0", "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.6.0", - "jest-message-util": "^29.6.0", - "jest-mock": "^29.6.0", + "jest-haste-map": "^29.6.1", + "jest-message-util": "^29.6.1", + "jest-mock": "^29.6.1", "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.6.0", - "jest-snapshot": "^29.6.0", - "jest-util": "^29.6.0", + "jest-resolve": "^29.6.1", + "jest-snapshot": "^29.6.1", + "jest-util": "^29.6.1", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -7172,9 +7172,9 @@ } }, "node_modules/jest-snapshot": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.6.0.tgz", - "integrity": "sha512-H3kUE9NwWDEDoutcOSS921IqdlkdjgnMdj1oMyxAHNflscdLc9dB8OudZHV6kj4OHJxbMxL8CdI5DlwYrs4wQg==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.6.1.tgz", + "integrity": "sha512-G4UQE1QQ6OaCgfY+A0uR1W2AY0tGXUPQpoUClhWHq1Xdnx1H6JOrC2nH5lqnOEqaDgbHFgIwZ7bNq24HpB180A==", "dev": true, "dependencies": { "@babel/core": "^7.11.6", @@ -7182,21 +7182,21 @@ "@babel/plugin-syntax-jsx": "^7.7.2", "@babel/plugin-syntax-typescript": "^7.7.2", "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.6.0", - "@jest/transform": "^29.6.0", - "@jest/types": "^29.6.0", + "@jest/expect-utils": "^29.6.1", + "@jest/transform": "^29.6.1", + "@jest/types": "^29.6.1", "@types/prettier": "^2.1.5", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", - "expect": "^29.6.0", + "expect": "^29.6.1", "graceful-fs": "^4.2.9", - "jest-diff": "^29.6.0", + "jest-diff": "^29.6.1", "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.6.0", - "jest-message-util": "^29.6.0", - "jest-util": "^29.6.0", + "jest-matcher-utils": "^29.6.1", + "jest-message-util": "^29.6.1", + "jest-util": "^29.6.1", "natural-compare": "^1.4.0", - "pretty-format": "^29.6.0", + "pretty-format": "^29.6.1", "semver": "^7.5.3" }, "engines": { @@ -7220,9 +7220,9 @@ } }, "node_modules/jest-snapshot/node_modules/pretty-format": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.0.tgz", - "integrity": "sha512-XH+D4n7Ey0iSR6PdAnBs99cWMZdGsdKrR33iUHQNr79w1szKTCIZDVdXuccAsHVwDBp0XeWPfNEoaxP9EZgRmQ==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", "dev": true, "dependencies": { "@jest/schemas": "^29.6.0", @@ -7252,12 +7252,12 @@ "dev": true }, "node_modules/jest-util": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.0.tgz", - "integrity": "sha512-S0USx9YwcvEm4pQ5suisVm/RVxBmi0GFR7ocJhIeaCuW5AXnAnffXbaVKvIFodyZNOc9ygzVtTxmBf40HsHXaA==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", "dev": true, "dependencies": { - "@jest/types": "^29.6.0", + "@jest/types": "^29.6.1", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -7285,17 +7285,17 @@ } }, "node_modules/jest-validate": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.6.0.tgz", - "integrity": "sha512-MLTrAJsb1+W7svbeZ+A7pAnyXMaQrjvPDKCy7OlfsfB6TMVc69v7WjUWfiR6r3snULFWZASiKgvNVDuATta1dg==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.6.1.tgz", + "integrity": "sha512-r3Ds69/0KCN4vx4sYAbGL1EVpZ7MSS0vLmd3gV78O+NAx3PDQQukRU5hNHPXlyqCgFY8XUk7EuTMLugh0KzahA==", "dev": true, "dependencies": { - "@jest/types": "^29.6.0", + "@jest/types": "^29.6.1", "camelcase": "^6.2.0", "chalk": "^4.0.0", "jest-get-type": "^29.4.3", "leven": "^3.1.0", - "pretty-format": "^29.6.0" + "pretty-format": "^29.6.1" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -7330,9 +7330,9 @@ } }, "node_modules/jest-validate/node_modules/pretty-format": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.0.tgz", - "integrity": "sha512-XH+D4n7Ey0iSR6PdAnBs99cWMZdGsdKrR33iUHQNr79w1szKTCIZDVdXuccAsHVwDBp0XeWPfNEoaxP9EZgRmQ==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", "dev": true, "dependencies": { "@jest/schemas": "^29.6.0", @@ -7362,18 +7362,18 @@ "dev": true }, "node_modules/jest-watcher": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.6.0.tgz", - "integrity": "sha512-LdsQqFNX60mRdRRe+zsELnYRH1yX6KL+ukbh+u6WSQeTheZZe1TlLJNKRQiZ7e0VbvMkywmMWL/KV35noOJCcw==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.6.1.tgz", + "integrity": "sha512-d4wpjWTS7HEZPaaj8m36QiaP856JthRZkrgcIY/7ISoUWPIillrXM23WPboZVLbiwZBt4/qn2Jke84Sla6JhFA==", "dev": true, "dependencies": { - "@jest/test-result": "^29.6.0", - "@jest/types": "^29.6.0", + "@jest/test-result": "^29.6.1", + "@jest/types": "^29.6.1", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "emittery": "^0.13.1", - "jest-util": "^29.6.0", + "jest-util": "^29.6.1", "string-length": "^4.0.1" }, "engines": { @@ -7397,13 +7397,13 @@ } }, "node_modules/jest-worker": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.6.0.tgz", - "integrity": "sha512-oiQHH1SnKmZIwwPnpOrXTq4kHBk3lKGY/07DpnH0sAu+x7J8rXlbLDROZsU6vy9GwB0hPiZeZpu6YlJ48QoKcA==", + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.6.1.tgz", + "integrity": "sha512-U+Wrbca7S8ZAxAe9L6nb6g8kPdia5hj32Puu5iOqBCMTMWFHXuK6dOV2IFrpedbTV8fjMFLdWNttQTBL6u2MRA==", "dev": true, "dependencies": { "@types/node": "*", - "jest-util": "^29.6.0", + "jest-util": "^29.6.1", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, @@ -7668,9 +7668,9 @@ } }, "node_modules/lucide-react": { - "version": "0.258.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.258.0.tgz", - "integrity": "sha512-3evnpKadBrjLr2HHJ66eDZ1y0vPS6pm8NiNDaLqhddUUyJGnA+lfDPZfbVkuAFq7Xaa1TEy7Sg17sM7mHpMKrA==", + "version": "0.259.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.259.0.tgz", + "integrity": "sha512-dFBLc6jRDfcpD9NQ7NyFVa+YR3RHX6+bs+f/UiotvNPho+kd4WyeXWMCCchUf7i/pq3BAaHkbmtkbx/GxxHVUw==", "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0" } @@ -7878,11 +7878,11 @@ "dev": true }, "node_modules/next": { - "version": "13.4.8", - "resolved": "https://registry.npmjs.org/next/-/next-13.4.8.tgz", - "integrity": "sha512-lxUjndYKjZHGK3CWeN2RI+/6ni6EUvjiqGWXAYPxUfGIdFGQ5XoisrqAJ/dF74aP27buAfs8MKIbIMMdxjqSBg==", + "version": "13.4.9", + "resolved": "https://registry.npmjs.org/next/-/next-13.4.9.tgz", + "integrity": "sha512-vtefFm/BWIi/eWOqf1GsmKG3cjKw1k3LjuefKRcL3iiLl3zWzFdPG3as6xtxrGO6gwTzzaO1ktL4oiHt/uvTjA==", "dependencies": { - "@next/env": "13.4.8", + "@next/env": "13.4.9", "@swc/helpers": "0.5.1", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001406", @@ -7898,15 +7898,15 @@ "node": ">=16.8.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "13.4.8", - "@next/swc-darwin-x64": "13.4.8", - "@next/swc-linux-arm64-gnu": "13.4.8", - "@next/swc-linux-arm64-musl": "13.4.8", - "@next/swc-linux-x64-gnu": "13.4.8", - "@next/swc-linux-x64-musl": "13.4.8", - "@next/swc-win32-arm64-msvc": "13.4.8", - "@next/swc-win32-ia32-msvc": "13.4.8", - "@next/swc-win32-x64-msvc": "13.4.8" + "@next/swc-darwin-arm64": "13.4.9", + "@next/swc-darwin-x64": "13.4.9", + "@next/swc-linux-arm64-gnu": "13.4.9", + "@next/swc-linux-arm64-musl": "13.4.9", + "@next/swc-linux-x64-gnu": "13.4.9", + "@next/swc-linux-x64-musl": "13.4.9", + "@next/swc-win32-arm64-msvc": "13.4.9", + "@next/swc-win32-ia32-msvc": "13.4.9", + "@next/swc-win32-x64-msvc": "13.4.9" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", @@ -8573,9 +8573,9 @@ } }, "node_modules/postcss": { - "version": "8.4.24", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz", - "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==", + "version": "8.4.25", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.25.tgz", + "integrity": "sha512-7taJ/8t2av0Z+sQEvNzCkpDynl0tX3uJMCODi6nT3PfASC7dYCWV9aQ+uiCf+KBD4SEFcu+GvJdGdwzQ6OSjCw==", "funding": [ { "type": "opencollective", diff --git a/package.json b/package.json index 446bedae67af6c569c577e18ec5b20a8bb805b20..ca890177e6a903c8b707709ac43c720700549855 100644 --- a/package.json +++ b/package.json @@ -37,8 +37,8 @@ "class-variance-authority": "^0.6.1", "clsx": "^1.2.1", "dayjs": "^1.11.9", - "lucide-react": "^0.258.0", - "next": "^13.4.8", + "lucide-react": "^0.259.0", + "next": "^13.4.9", "next-auth": "^4.22.1", "next-themes": "^0.2.1", "normalize-diacritics": "^4.0.0", @@ -59,15 +59,15 @@ "@testing-library/react": "^14.0.0", "@types/bcrypt": "^5.0.0", "@types/jest": "^29.5.2", - "@types/node": "^20.3.3", + "@types/node": "^20.4.0", "@types/react": "^18.2.14", "@types/react-dom": "^18.2.6", "autoprefixer": "10.4.14", "eslint": "^8.44.0", - "eslint-config-next": "^13.4.8", - "jest": "^29.6.0", - "jest-environment-jsdom": "^29.6.0", - "postcss": "8.4.24", + "eslint-config-next": "^13.4.9", + "jest": "^29.6.1", + "jest-environment-jsdom": "^29.6.1", + "postcss": "8.4.25", "prisma": "^4.16.2", "tailwindcss": "3.3.2", "typescript": "^5.1.6" diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 9deaae8759c1e6d52d9ef69b7caa4e9be0174f0d..d6b3ef1cdc15ec826b97a8e27954672bc783f996 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -81,24 +81,12 @@ model User { regweets Regweet[] likes Like[] - following Follows[] @relation("following") - followers Follows[] @relation("follower") + following User[] @relation("followers") + followers User[] @relation("followers") @@map("users") } -model Follows { - followerId String - followingId String - createdAt DateTime @default(now()) @map("created_at") - - follower User @relation("following", fields: [followerId], references: [id]) - following User @relation("follower", fields: [followingId], references: [id]) - - @@id([followerId, followingId]) - @@map("follows") -} - model Gweet { id String @id @default(cuid()) authorId String @map("user_id")