diff --git a/.env.example b/.env.example index 673680755844bdbf3adbf3fab1e77a9f73bb9594..31a6eb736513e35d2f52eabc3c3dbc6c3fcdb6c4 100644 --- a/.env.example +++ b/.env.example @@ -6,6 +6,10 @@ NEXT_PUBLIC_APP_URL="http://localhost:3000" # Database for connecting to Prisma DATABASE_URL="file:./dev.db" +# Database for connecting to Supabase for media +NEXT_PUBLIC_SUPABASE_URL="YOUR_SUPABASE_URL" +NEXT_PUBLIC_SUPABASE_ANON_KEY="YOUR_SUPABASE_ANON_KEY" + # URLs TWITCH_AUTH_BASE_URL="https://id.twitch.tv/oauth2" IGDB_BASE_URL="https://api.igdb.com/v4" diff --git a/app/(content)/(gaming)/games/[gameid]/page.tsx b/app/(content)/(gaming)/games/[gameid]/page.tsx index 9decb17582e3cff2bd46fda773cd1937e10926b7..dacb61f9747d6eebf23a2e0fab82f4f7982b64e1 100644 --- a/app/(content)/(gaming)/games/[gameid]/page.tsx +++ b/app/(content)/(gaming)/games/[gameid]/page.tsx @@ -1,4 +1,6 @@ -import AddGameToList from "@/components/addGameToList"; +import AddGameDropdown from "@/components/add-game-dropdown"; +import AddGameToFavList from "@/components/addGameToFavList"; +import { BackHeader } from "@/components/back-header"; import { AspectRatio } from "@/components/ui/aspect-ratio"; import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; @@ -32,6 +34,12 @@ export default async function GameDetail({ params }: { params: { gameid: string return ( <div className="main-content h-full"> <Card className="w-full h-full overflow-hidden"> + <div className="p-3"> + <BackHeader> + <h1 className="font-bold">Game</h1> + </BackHeader> + </div> + <div className="h-64 overflow-hidden"> <AspectRatio ratio={889 / 500}> <Image @@ -52,7 +60,11 @@ export default async function GameDetail({ params }: { params: { gameid: string priority className="object-cover rounded-lg" /> </Card> + <div className="flex justify-start p-6"> + <AddGameDropdown fullUser={fullUser!} gameid={params.gameid} /> + </div> </div> + <div className="ml-6 md:ml-12 space-y-3"> <h1 className="text-2xl font-bold">{data[0].name}</h1> <h1>released on{' '} @@ -62,6 +74,7 @@ export default async function GameDetail({ params }: { params: { gameid: string <h1 className="pt-3">{data[0].summary}</h1> <div className="pt-6"> + <h1 className="mb-2">Genres</h1> <div className="flex flex-wrap gap-2"> {data[0].genres.map((genre, i) => { @@ -82,7 +95,7 @@ export default async function GameDetail({ params }: { params: { gameid: string <div className="px-6 md:px-12"> <div className="border-b border-gray-400 dark:border-gray-200" /> <div className="p-6 w-full flex justify-center"> - {user && <AddGameToList userGameList={fullUser?.favGameList!} gameId={params.gameid} />} + {user && <AddGameToFavList userGameList={fullUser?.favGameList!} gameId={params.gameid} />} </div> {/* comments */} </div> diff --git a/app/(content)/(gaming)/games/page.tsx b/app/(content)/(gaming)/games/page.tsx index f0af8fc9792fa1096349cf9cbe2bc843915f76d8..17586dbea5c3b48e9aa5f2184a355c231c5a2cf2 100644 --- a/app/(content)/(gaming)/games/page.tsx +++ b/app/(content)/(gaming)/games/page.tsx @@ -7,7 +7,7 @@ export default async function GamesPage() { return ( <main className="main-content"> <div className="flex justify-center"> - <div className="fixed top-30 z-50"> + <div className="fixed top-30 z-40"> <ScrollToTop /> </div> <InfiniteScrollGames /> diff --git a/app/(content)/(home)/home/[postid]/page.tsx b/app/(content)/(home)/home/[postid]/page.tsx deleted file mode 100644 index 07d1c2a3fdad506dfcd0902b04fc59bbec4fc2c7..0000000000000000000000000000000000000000 --- a/app/(content)/(home)/home/[postid]/page.tsx +++ /dev/null @@ -1,19 +0,0 @@ -// import { PostCommentForm } from "@/components/comment-gweets"; - -export default async function PostDetail({ params }: { params: { postid: string } }) { - const postid = params.postid - - return ( - <div className="main-content px-3 pb-3"> - <div className="lg:col-span-1"> - {/* <PostCommentForm postid={postid} /> */} - </div> - - <div className="side-content"> - <div className="flex-col"> - - </div> - </div> - </div> - ) -} \ No newline at end of file diff --git a/app/(content)/(home)/home/page.tsx b/app/(content)/(home)/home/page.tsx index dbffa82339cfcaf836d1c674bbf4c9795a4752c7..3152776a2d11dc773e98ee25cd1a5282e4f61e3c 100644 --- a/app/(content)/(home)/home/page.tsx +++ b/app/(content)/(home)/home/page.tsx @@ -1,10 +1,12 @@ -import { PostGweets } from "@/components/post-gweets"; +import { CreateGweet } from "@/components/create-gweet/components/create-gweet"; +import { Gweets } from "@/components/gweets/components/gweets"; export default async function HomePage() { return ( <div className="main-content px-3 pb-3"> <div className="lg:col-span-1"> - <PostGweets /> + <CreateGweet /> + <Gweets /> </div> <div className="side-content"> diff --git a/app/(content)/(user)/[userid]/followers/page.tsx b/app/(content)/(user)/[userid]/followers/page.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4f7528ff84cf76e95bdc0aa87b069c24d311a620 --- /dev/null +++ b/app/(content)/(user)/[userid]/followers/page.tsx @@ -0,0 +1,58 @@ + +import { UserAvatar } from "@/components/user-avatar"; +import { db } from "@/lib/db"; +import { User } from "@prisma/client"; +import { getServerSession } from "next-auth"; + +export default async function UserFollowers({ params }: { params: { userid: string } }) { + // const session = await getServerSession(authOptions); + + // if (!session) { + // return <div>Loading...</div>; + // } + + 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 ( + <div> + <h1>Followers Page WIP</h1> + {followers?.map((follower: User) => ( + <div key={follower.id}> {follower.id} <UserAvatar + user={{ username: follower.name || null, image: follower.image || null }} + className="h-20 w-20 -mt-50" /> + + <div className="ml-6 md:ml-12 space-y-3"> + <h1 className="text-2xl font-bold">{follower.name}</h1> + <h1 className="text-md text-sky-500">@{follower.username}</h1> + </div> + </div> + ))} + </div> + ) +} + + diff --git a/app/(content)/(user)/[userid]/following/page.tsx b/app/(content)/(user)/[userid]/following/page.tsx new file mode 100644 index 0000000000000000000000000000000000000000..5ea8f4daa5c5fef29b079a07c60284b12ab27cba --- /dev/null +++ b/app/(content)/(user)/[userid]/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]/likes/page.tsx b/app/(content)/(user)/[userid]/likes/page.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d1050ff370eab7ae89abaf9012a3c9d499129093 --- /dev/null +++ b/app/(content)/(user)/[userid]/likes/page.tsx @@ -0,0 +1,7 @@ +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]/loading.tsx b/app/(content)/(user)/[userid]/loading.tsx index c9e709d65763f4f0b9464eba42b820b28815297c..0cc1e86f343bf788b4be9f2bd33313ad64a70f00 100644 --- a/app/(content)/(user)/[userid]/loading.tsx +++ b/app/(content)/(user)/[userid]/loading.tsx @@ -18,7 +18,7 @@ export default function Loading() { </div> <div className="px-6 md:px-12"> {/* <div className="border-b border-gray-400 dark:border-gray-200" /> */} - {/* tweets */} + {/* gweets */} </div> </Card > <div className="side-content"> diff --git a/app/(content)/(user)/[userid]/page.tsx b/app/(content)/(user)/[userid]/page.tsx index 005a747b08e63eaae2389041057c9d8a6c306fdd..9d88a6834e9b493dc958f374a90791f2065b20c7 100644 --- a/app/(content)/(user)/[userid]/page.tsx +++ b/app/(content)/(user)/[userid]/page.tsx @@ -1,3 +1,4 @@ +import FollowButton from "@/components/following-button" import GameItem from "@/components/game-item" import { AspectRatio } from "@/components/ui/aspect-ratio" import { Card } from "@/components/ui/card" @@ -9,78 +10,147 @@ 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() - if (user?.username !== params.userid) { - redirect('/') - } - const fullUser = await db.user.findFirst({ + 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 - if (fullUser?.favGameList?.length !== 0) { + 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="main-content h-full"> - <Card className="w-full h-full overflow-hidden"> - <div className="h-64 overflow-hidden"> - <AspectRatio ratio={889 / 500} className="bg-slate-600 dark:bg-slate-400"> - {/* profile banner */} - {/* <Image + <> + <div className="main-content h-full"> + <Card className="w-full h-full 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={{ name: user.name || null, image: user.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">{user.name}</h1> - <h1 className="text-md text-sky-500">@{user.username}</h1> - {/* <h1 className="pt-3">{user.bio}</h1> */} + </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 > + <div className="side-content"> + <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> </div> - <div className="px-6 md:px-12"> - {/* <div className="border-b border-gray-400 dark:border-gray-200" /> */} - {/* tweets */} - </div> - </Card > - <div className="side-content"> - <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 > + <div className="main-content"> + <Card className="w-full h-full 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> - </div> - <Card className="w-full h-full overflow-hidden p-6 md:p-12" > - <h1 className="text-2xl font-bold pb-3">Your 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>You have no favorites currently</p>} - </div> - </Card> - </div > + <Card className="w-full h-full 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="w-full h-full 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="w-full h-full 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> + </> ) } \ No newline at end of file diff --git a/app/(content)/(user)/[userid]/status/[id]/page.tsx b/app/(content)/(user)/[userid]/status/[id]/page.tsx new file mode 100644 index 0000000000000000000000000000000000000000..cae286fc6eb5c7ddb13bbe5f6fac8a7e022d1a7b --- /dev/null +++ b/app/(content)/(user)/[userid]/status/[id]/page.tsx @@ -0,0 +1,15 @@ +import { BackHeader } from "@/components/back-header"; +import { GweetDetails } from "@/components/gweets/components/gweet-details"; +import { Card } from "@/components/ui/card"; + +export default async function GweetDetailPage() { + return ( + <Card className="w-full h-full p-3 xl:p-6 "> + <BackHeader> + <h1 className="font-bold">Gweet</h1> + </BackHeader> + + <GweetDetails /> + </Card> + ); +}; \ No newline at end of file diff --git a/app/(content)/(user)/followers/page.tsx b/app/(content)/(user)/followers/page.tsx deleted file mode 100644 index ec8175d6a0d434b6273910bba52f1e23901b27ff..0000000000000000000000000000000000000000 --- a/app/(content)/(user)/followers/page.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import FollowersList from "@/components/following-users"; -import { getServerSession } from "next-auth"; - -export default async function Followers() { - // const session = await getServerSession(authOptions); - - // if (!session) { - // return <div>Loading...</div>; - // } - - return ( - <div> - <h1>Followers Page WIP</h1> - {/* <FollowersList userId={parseFloat(session.user?.id)} /> */} - </div> - ) -} \ No newline at end of file diff --git a/app/(content)/layout.tsx b/app/(content)/layout.tsx index 3c53c0a1715f24a9235da32dc946be7385c556a8..6c0fcadf02de982555e9e8d141c80527b76eab18 100644 --- a/app/(content)/layout.tsx +++ b/app/(content)/layout.tsx @@ -16,7 +16,7 @@ export default async function ContentLayout({ return ( <div className="flex min-h-screen flex-col space-y-6"> - <header className="sticky top-0 z-40 border-b bg-background"> + <header className="sticky top-0 z-50 border-b bg-background"> <div className="container flex h-16 items-center justify-between py-4"> <MainNav /> <SearchInput className="p-3 md:w-2/3 2xl:w-1/3" /> diff --git a/app/api/comments/route.ts b/app/api/comments/route.ts deleted file mode 100644 index 13e15c2429e8fa579d31cb6983eb92ba242d73fd..0000000000000000000000000000000000000000 --- a/app/api/comments/route.ts +++ /dev/null @@ -1,66 +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 POST(req: NextRequest) { - const user = await getCurrentUser(); - - if (!user) { - return NextResponse.json({ status: 401, message: 'Unauthorized' }); - } - - const userId = user.id; - const content = await req.json() - - try { - await db.comment.create({ - data: { - message: content.gweet, - postId: content.postId, - userId: userId, - } - }) - const path = req.nextUrl.searchParams.get('path') || '/'; - revalidatePath(path); - - return NextResponse.json({ status: 201, message: 'Comment Created' }) - - } catch (error: any) { - return NextResponse.json({ status: 500, message: error.message }) - } -} - -export async function GET(req: NextRequest): Promise<NextResponse> { - const pa = req.nextUrl.searchParams; - - try { - const p = pa.get('postid') - - if (!p) { - return NextResponse.json({ status: 400, message: 'Bad Request' }) - } - - const message = await db.post.findUnique({ - where: { - id: p - }, - include: { - user: true, - }, - }) - - const comments = await db.comment.findMany({ - where: { - postId: p - }, - orderBy: { - createdAt: "desc" - } - }) - - return NextResponse.json(comments); - } catch (error) { - return NextResponse.json(error, { status: 500 }); - } -} \ No newline at end of file diff --git a/app/api/followers/route.ts b/app/api/followers/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..f2c8c585c0b7c1e72f3a9b97a266f6fdb41cbee2 --- /dev/null +++ b/app/api/followers/route.ts @@ -0,0 +1,85 @@ +import { db } from "@/lib/db"; +import { getCurrentUser } from "@/lib/session"; +import { User } from "@prisma/client"; +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/gamelists/route.ts b/app/api/gamelists/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..fa9640edae83082c5d78b918a304dbb37eae900d --- /dev/null +++ b/app/api/gamelists/route.ts @@ -0,0 +1,54 @@ +import { db } from "@/lib/db"; +import { getCurrentUser } from "@/lib/session"; +import { User } from "@prisma/client"; +import { revalidatePath } from "next/cache"; +import { NextRequest, NextResponse } from "next/server"; + + +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' }); + } + console.log("put list") + try { + const dbUser = await db.user.findFirst({ + where: { + id: sessionUser.id + }, + select: { + planningGameList: true, + playingGameList: true, + finishedGameList: true + }, + }); + + if (dbUser) { + if (!data.finishedGameList) data.finishedGameList = dbUser?.finishedGameList + if (!data.planningGameList) data.planningGameList = dbUser?.planningGameList + if (!data.playingGameList) data.playingGameList = dbUser?.playingGameList + + await db.user.update({ + where: { + id: sessionUser.id + }, + data: { + finishedGameList: data.finishedGameList, + planningGameList: data.planningGameList, + playingGameList: data.playingGameList + } + }) + } + } catch (error) { + + } + const path = req.nextUrl.searchParams.get('path') || '/'; + revalidatePath(path); + + return NextResponse.json({ status: 201, message: 'Game Hinzugefügt' }) + + +} \ No newline at end of file diff --git a/app/api/gweets/[id]/route.ts b/app/api/gweets/[id]/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..be74cbb2f647e78903eb32e75f36022734037b66 --- /dev/null +++ b/app/api/gweets/[id]/route.ts @@ -0,0 +1,98 @@ +import { NextResponse } from "next/server"; +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; + + const gweetIdSchema = z.string().cuid(); + + const zod = gweetIdSchema.safeParse(id); + + if (!zod.success) { + return NextResponse.json( + { + message: "Invalid request body", + error: zod.error.formErrors, + }, { status: 400 }, + ); + } + + try { + const gweet = await db.gweet.findUnique({ + where: { + id, + }, + include: { + author: true, + likes: { + include: { + user: { + include: { + followers: true, + }, + }, + }, + orderBy: { + createdAt: "desc", + }, + }, + media: true, + regweets: { + include: { + user: { + include: { + followers: true, + }, + }, + }, + orderBy: { + createdAt: "desc", + }, + }, + quote: { + include: { + author: true, + media: true, + }, + }, + allQuotes: { + include: { + likes: true, + regweets: true, + author: true, + quote: { + include: { + author: true, + }, + }, + }, + + orderBy: { + createdAt: "desc", + }, + }, + allComments: true, + }, + }); + + if (!gweet) { + 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 }, + ); + } +} diff --git a/app/api/gweets/likes/route.ts b/app/api/gweets/likes/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..029782dbf6b4d922157fee03ccca842afd945f5d --- /dev/null +++ b/app/api/gweets/likes/route.ts @@ -0,0 +1,107 @@ +import { NextResponse } from "next/server"; +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; + + const userIdSchema = z.string().cuid(); + const zod = userIdSchema.safeParse(user_id); + + if (!zod.success) { + return NextResponse.json( + { + message: "Invalid request body", + error: zod.error.formErrors, + }, { status: 400 }, + ); + } + + try { + const gweets = await db.gweet.findMany({ + where: { + likes: { + some: { + userId: user_id, + }, + }, + }, + + include: { + author: true, + media: true, + likes: true, + regweets: true, + allComments: true, + }, + }); + + return NextResponse.json(gweets, { status: 200 }); + } catch (error: any) { + 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(); + + const likeSchema = z + .object({ + gweet_id: z.string().cuid(), + user_id: z.string().cuid(), + }) + .strict(); + + const zod = likeSchema.safeParse({ gweet_id, user_id }); + + if (!zod.success) { + return NextResponse.json( + { + message: "Invalid request body", + error: zod.error.formErrors, + }, { status: 400 }, + ); + } + + try { + const like = await db.like.findFirst({ + where: { + gweetId: gweet_id, + userId: user_id, + }, + }); + + if (like) { + await db.like.delete({ + where: { + id: like.id, + }, + }); + + return NextResponse.json({ message: "Gweet unliked" }); + } else { + await db.like.create({ + data: { + gweetId: gweet_id, + userId: user_id, + }, + }); + + return NextResponse.json({ message: "Gweet liked" }); + } + } catch (error: any) { + return NextResponse.json({ + message: "Something went wrong", + error: error.message, + }); + } +} \ No newline at end of file diff --git a/app/api/gweets/regweets/route.ts b/app/api/gweets/regweets/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..a7672c140c6fce4b7ce1740495cd6fb8ebffcc84 --- /dev/null +++ b/app/api/gweets/regweets/route.ts @@ -0,0 +1,56 @@ +import { NextResponse } from "next/server"; +import { z } from "zod"; + +import { db } from "@/lib/db"; + +export async function POST(request: Request) { + const { gweet_id, user_id } = await request.json(); + + const regweetSchema = z + .object({ + gweet_id: z.string().cuid(), + user_id: z.string().cuid(), + }) + .strict(); + + const zod = regweetSchema.safeParse({ gweet_id, user_id }); + + if (!zod.success) { + return NextResponse.json( + { + message: "Invalid request body", + error: zod.error.formErrors, + }, { status: 400 }, + ); + } + + try { + const regweet = await db.regweet.findFirst({ + where: { + gweetId: gweet_id, + userId: user_id, + }, + }); + + if (regweet) { + await db.regweet.delete({ + where: { + id: regweet.id, + }, + }); + + return NextResponse.json({ message: "Deleted gweet regweet" }); + } else { + await db.regweet.create({ + data: { + gweetId: gweet_id, + userId: user_id, + }, + }); + + return NextResponse.json({ message: "Gweet regweeted" }); + } + } 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 new file mode 100644 index 0000000000000000000000000000000000000000..8178a2d05def1608c68afa3d4271ead59fc3218b --- /dev/null +++ b/app/api/gweets/route.ts @@ -0,0 +1,211 @@ +import { NextResponse } from "next/server"; +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); + + const type = searchParams.get("type") || undefined; + const id = searchParams.get("id") || undefined; + + const cursorQuery = searchParams.get("cursor") || undefined; + const take = Number(searchParams.get("limit")) || 20; + + const skip = cursorQuery ? 1 : 0; + const cursor = cursorQuery ? { id: cursorQuery } : undefined; + + try { + const gweets = await db.gweet.findMany({ + skip, + take, + cursor, + + where: { + ...(type === "comments" && { + replyToGweetId: id, + }), + + ...(type === "search" && { + text: { + contains: id, + mode: "insensitive", + }, + }), + + ...(type === "user_gweets" && { + authorId: id, + }), + + ...(type === "user_replies" && { + authorId: id, + NOT: { + replyToGweetId: null, + }, + }), + + ...(type === "user_likes" && { + likes: { + some: { + userId: id, + }, + }, + }), + }, + + include: { + author: true, + likes: true, + media: true, + regweets: true, + + quote: { + include: { + author: true, + media: true, + }, + }, + + allComments: true, + allQuotes: true, + }, + + orderBy: { + createdAt: "desc", + }, + }); + + const nextId = gweets.length < take ? undefined : gweets[gweets.length - 1].id; + + return NextResponse.json({ gweets, nextId }); + } catch (error) { + return NextResponse.error(); + } +} + +// create gweet +export async function POST(request: Request) { + const { gweet, fileprops } = await request.json(); + + const gweetSchema = z + .object({ + content: z.string().min(1).max(280), + authorId: z.string().cuid(), + replyToGweetId: z.string().cuid().optional(), + quoteGweetId: z.string().cuid().optional(), + }) + .strict(); + + const zodGweet = gweetSchema.safeParse(gweet); + + const mediaSchema = z.array( + z.object({ + gweetId: z.string().nullable().optional(), + url: z.string(), + key: z.string(), + type: z.string(), + }).strict() + ); + + if (!zodGweet.success) { + return NextResponse.json( + { + message: "Invalid request body", + error: zodGweet.error.formErrors, + }, { status: 400 }, + ); + } + + try { + const created_gweet = await db.gweet.create({ + data: { + ...gweet, + }, + }); + + if (fileprops.length > 0) { + const mediaArray = fileprops.map((fileprop: { fileUrl: string; fileKey: string; }) => { + const media = { + gweetId: created_gweet.id, + url: fileprop.fileUrl, + key: fileprop.fileKey, + type: "IMAGE", + } + + return media; + }); + + const zodMedia = mediaSchema.safeParse(mediaArray); + + if (!zodMedia.success) { + return NextResponse.json( + { + message: "Invalid media body", + error: zodMedia.error.formErrors, + }, { status: 400 }, + ); + } + + await db.media.createMany({ + data: mediaArray, + }); + } + + return NextResponse.json(created_gweet, { status: 200 }); + } catch (error: any) { + console.log(error); + 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; + + const idSchema = z.string().cuid(); + const zod = idSchema.safeParse(id); + + if (!zod.success) { + return NextResponse.json( + { + message: "Invalid request body", + error: zod.error.formErrors, + }, { status: 400 }, + ); + } + + try { + const checkMedia = await db.media.findMany({ + where: { + gweetId: id, + }, + }); + + if (checkMedia.length > 0) { + await utapi.deleteFiles(checkMedia.map((media) => media.key)); + } + + await db.gweet.delete({ + where: { + id, + }, + }); + + return NextResponse.json({ message: "Gweet deleted successfully", }); + } catch (error: any) { + 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 new file mode 100644 index 0000000000000000000000000000000000000000..e2ab437790cc948a9c344b27254d188740580af3 --- /dev/null +++ b/app/api/hashtags/route.ts @@ -0,0 +1,74 @@ +import { NextResponse } from "next/server"; +import { z } from "zod"; + +import { db } from "@/lib/db"; + +export async function GET() { + try { + const hashtags = await db.hashtag.findMany({ + take: 10, + orderBy: { + score: "desc", + }, + }); + return NextResponse.json(hashtags, { status: 200 }); + } catch (error: any) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} + +export async function POST(request: Request) { + const { hashtags } = await request.json(); + + const hashtagsSchema = z.array(z.string()); + + const zod = hashtagsSchema.safeParse(hashtags); + + if (!zod.success) { + return NextResponse.json({ error: zod.error }, { status: 400 }); + } + + try { + for (const hashtag of hashtags) { + const hashtagExists = await db.hashtag.findUnique({ + where: { + hashtag: hashtag.toLowerCase(), + }, + }); + + if (hashtagExists) { + await db.hashtag.update({ + where: { + hashtag: hashtag.toLowerCase(), + }, + data: { + score: { + increment: 1, + }, + }, + }); + } else { + await db.hashtag.create({ + data: { + text: hashtag, + hashtag: hashtag.toLowerCase(), + }, + }); + } + } + + return NextResponse.json( + { + message: "Hashtag(s) created", + }, + { status: 200 }, + ); + } catch (error: any) { + return NextResponse.json( + { + error: error.message, + }, + { status: 500 }, + ); + } +} diff --git a/app/api/likes/likeService.ts b/app/api/likes/likeService.ts deleted file mode 100644 index 18f98072e9ac76646a899f7f4caa5c5b25704ade..0000000000000000000000000000000000000000 --- a/app/api/likes/likeService.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { db } from "@/lib/db" -import { Prisma } from "@prisma/client" - -type likeType = Prisma.LikeUncheckedCreateInput - -/** - * Creates like if user has not liked this post. - * Deletes like if user has liked post already. - */ -export async function putLike(like: likeType): Promise<likeType | undefined> { - // check if like exists by this user and for this post - // if exists delete - // if not create - try { - const actualLike = await db.like.findFirst({ - where: { - // id: like.id, - postId: like.postId, - userId: like.userId - } - }) - console.log("found like: ", actualLike?.id) - if (actualLike == null) { - console.log("like is null", "postid:", like.postId, "so create it") - throw Error("Message was not liked by this user") - } - console.log("delete like", like.postId, "likeid: ", actualLike?.id) - await db.like.delete({ - where: { - id: actualLike.id - } - }) - - /* const msg = await db.post.update({ - where: { - id: like.postId - }, - data: { - likeCount: { increment: -1 } - } - }) */ - - return undefined; - - } catch { - - const createdLike = await db.like.create({ - data: { - postId: like.postId, - userId: like.userId - } - }) - - const updatedMessage = await db.post.update({ - where: { - id: like.postId - }, - data: { - likeCount: { increment: 1 } - } - }) - } -} - -export async function putLikeComment(like: likeType) { - // check if like exists by this user and for this post - // if exists delete - // if not create - try { - const actualLike = await db.like.findFirst({ - where: { - // id: like.id, - postId: like.postId, - commentId: like.commentId, - userId: like.userId - } - }) - console.log("found like: ", actualLike?.id) - if (actualLike == null) { - console.log("like is null", like.commentId, "so create it") - const createdLike = await db.like.create({ - data: { - postId: like.postId, - userId: like.userId, - commentId: like.commentId - } - }) - } else { - console.log("delete like", like.commentId, "postid:", like.postId, "likeid: ", actualLike?.id) - await db.like.delete({ - where: { - id: actualLike.id - } - }) - } - - /* const msg = await db.comment.update({ - where: { - id: like.postId - }, - data: { - likeCount: { increment: -1 } - } - }) */ - - } catch { - - /* const updatedMessage = await db.comment.update({ - where: { - id: like.postId - }, - data: { - likeCount: { increment: 1 } - } - }) */ - } -} \ No newline at end of file diff --git a/app/api/likes/route.ts b/app/api/likes/route.ts deleted file mode 100644 index 35619aaa6a76f2bcbc09d2a19aa2664b108146b7..0000000000000000000000000000000000000000 --- a/app/api/likes/route.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Prisma } from "@prisma/client"; -import { NextRequest, NextResponse } from "next/server"; -import { putLike, putLikeComment } from "./likeService"; -import { getServerSession } from "next-auth/next"; -import { authOptions } from "@/lib/auth"; -import { revalidatePath } from "next/cache"; - -type like = Prisma.LikeUncheckedCreateInput - -export async function PUT(req: NextRequest) { - const session = await getServerSession(authOptions); - - if (!session) { - return NextResponse.json({ status: 401 }); - } - - const userId = session.user.id - - const data: like = await req.json() - data.userId = userId; - - console.log("router data: " + data, "status:") - try { - if (data.commentId == undefined) { - const msg = await putLike(data) - } else { - putLikeComment(data) - } - const path = req.nextUrl.searchParams.get('path') || '/'; - revalidatePath(path); - return NextResponse.json({ status: 200, message: 'Like handled' }) - - } catch (error) { - console.log("fail" + error); - return NextResponse.json(error, { status: 500 }); - } -} \ No newline at end of file diff --git a/app/api/messages/route.ts b/app/api/messages/route.ts deleted file mode 100644 index 6eb3296d3a705d11704ef00d9fb3dd9fc7231caf..0000000000000000000000000000000000000000 --- a/app/api/messages/route.ts +++ /dev/null @@ -1,50 +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 POST(req: NextRequest) { - const user = await getCurrentUser(); - - if (!user) { - return NextResponse.json({ status: 401, message: 'Unauthorized' }); - } - - const userId = user.id; - const content = await req.json() - - try { - await db.post.create({ - data: { - content: content.gweet, - userId: userId, - } - }) - const path = req.nextUrl.searchParams.get('path') || '/'; - revalidatePath(path); - - return NextResponse.json({ status: 201, message: 'Message Created' }) - - } catch (error: any) { - return NextResponse.json({ status: 500, message: error.message }) - } -} - -export async function GET() { - try { - const messages = await db.post.findMany({ - orderBy: { - createdAt: "desc" - }, - include: { - user: true, - Comment: true, - Like: true - }, - }) - - return NextResponse.json(messages); - } catch (error) { - return NextResponse.json(error, { status: 500 }); - } -} \ No newline at end of file diff --git a/app/api/uploadthing/core.ts b/app/api/uploadthing/core.ts new file mode 100644 index 0000000000000000000000000000000000000000..310010683f2bbfca79961f9e0995e1a0a0b6722c --- /dev/null +++ b/app/api/uploadthing/core.ts @@ -0,0 +1,18 @@ +import { getCurrentUser } from "@/lib/session"; +import { createUploadthing, type FileRouter } from "uploadthing/next"; + +const f = createUploadthing(); + +export const ourFileRouter = { + imageUploader: 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 }) => { }), +} satisfies FileRouter; + +export type OurFileRouter = typeof ourFileRouter; \ No newline at end of file diff --git a/app/api/uploadthing/route.ts b/app/api/uploadthing/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..d092e55a4cc395f5c35b2078d4f42200d8029951 --- /dev/null +++ b/app/api/uploadthing/route.ts @@ -0,0 +1,7 @@ +import { createNextRouteHandler } from "uploadthing/next"; + +import { ourFileRouter } from "./core"; + +export const { GET, POST } = createNextRouteHandler({ + router: ourFileRouter, +}); \ No newline at end of file diff --git a/app/api/favgameslist/route.ts b/app/api/users/favgameslist/route.ts similarity index 100% rename from app/api/favgameslist/route.ts rename to app/api/users/favgameslist/route.ts diff --git a/app/layout.tsx b/app/layout.tsx index bd44fed94d1e9fed940a2a0aafff438de284e2d2..ffdae6cda40e86230a38f03acb9e79dacaab9592 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,9 +1,9 @@ import { Inter } from 'next/font/google' import './globals.css' -import Providers from '@/components/react-query/provider' import { ThemeProvider } from '@/components/ui/theme-provider' import { Toaster } from '@/components/ui/toaster' +import Providers from '@/lib/react-query/provider' const inter = Inter({ subsets: ['latin'] }) diff --git a/components.json b/components.json new file mode 100644 index 0000000000000000000000000000000000000000..819a98d85f53d2dcf9e73a8c74a37738e6e37d9b --- /dev/null +++ b/components.json @@ -0,0 +1,14 @@ +{ + "style": "default", + "rsc": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "app/globals.css", + "baseColor": "slate", + "cssVariables": true + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils" + } +} \ No newline at end of file diff --git a/components/add-game-dropdown.tsx b/components/add-game-dropdown.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e8fb0d44fb746aa0552f26828262011c031c39b7 --- /dev/null +++ b/components/add-game-dropdown.tsx @@ -0,0 +1,33 @@ +"use client" + +import { useState } from "react"; +import { Card } from "./ui/card"; +import GameItem from "./game-item"; +import { IGame } from "@/types/igdb-types"; +import { User } from "@prisma/client"; +import AddGameToPlanList from "./add-game-to-plan-list"; +import AddGameToPlayingList from "./add-game-to-playing-list"; +import AddGameToFinishedList from "./add-game-to-finished-list"; +import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "./ui/select"; + +export default function AddGameDropdown(props: { gameid: string, fullUser: User }) { + return ( + <> + <Select> + <SelectTrigger className={`bg-background border-full w-32 h-8}`}> + <SelectValue placeholder="Status" /> + </SelectTrigger> + <SelectContent> + <SelectGroup> + <SelectLabel>Status</SelectLabel> + <AddGameToPlanList user={props.fullUser!} gameId={props.gameid} /> + <AddGameToFinishedList user={props.fullUser!} gameId={props.gameid} /> + <AddGameToPlayingList user={props.fullUser!} gameId={props.gameid} /> + </SelectGroup> + </SelectContent> + </Select> + + </> + ) + +} diff --git a/components/add-game-to-finished-list.tsx b/components/add-game-to-finished-list.tsx new file mode 100644 index 0000000000000000000000000000000000000000..40b4e1dbf77977388d0a43295237314f5b921f33 --- /dev/null +++ b/components/add-game-to-finished-list.tsx @@ -0,0 +1,96 @@ +"use client" + +import { User } from "@prisma/client"; +import { useRouter } from "next/navigation"; +import { startTransition } from "react"; +import { Button } from "./ui/button"; + +export default function AddGameToFinishedList(props: { gameId: string, user: User }) { + + const router = useRouter(); + const gameId = parseFloat(props.gameId); + const user = props.user; + + let formData: { + id: String; + gameId: Number; + add: boolean; + planningGameList: number[] | undefined; + playingGameList: number[] | undefined; + finishedGameList: number[] | undefined; + } = { + id: "", + gameId: -1, + add: true, + planningGameList: undefined, + playingGameList: undefined, + finishedGameList: undefined + }; + + async function removeGame(e: any) { + e.preventDefault() + + formData.id = user.id; + formData.finishedGameList = props.user.finishedGameList.filter((id) => id !== gameId) + console.log(formData.finishedGameList) + const response = await fetch('/api/gamelists', { + method: 'PUT', + body: JSON.stringify(formData) + }) + + startTransition(() => { + // Refresh the current route and fetch new data from the server without + // losing client-side browser or React state. + router.refresh(); + }); + return await response.json() + } + + async function addGame(e: any) { + e.preventDefault() + + formData.id = user.id; + props.user.finishedGameList.push(gameId) + formData.finishedGameList = props.user.finishedGameList; + const response = await fetch('/api/gamelists', { + method: 'PUT', + body: JSON.stringify(formData) + }) + + startTransition(() => { + // Refresh the current route and fetch new data from the server without + // losing client-side browser or React state. + router.refresh(); + }); + return await response.json() + } + + + let button = <div></div>; + try { + if (!props.user.finishedGameList.includes(parseFloat(props.gameId))) { + button = ( + <form onSubmit={addGame}> + <Button type="submit" size="lg"> + Add Game To finished-playing-List + </Button> + </form> + ) + } else { + button = ( + <form onSubmit={removeGame}> + <Button type="submit" size="lg" variant={"secondary"}> + Remove Game From finished-playing-List + </Button> + </form> + ) + } + } catch (error) { + // throw new Error("Failed to check finished-playing-List"); + } + + return ( + button + ) +} + diff --git a/components/add-game-to-plan-list.tsx b/components/add-game-to-plan-list.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b2c1e45426adea91cc01f81df1f43307588ee6ca --- /dev/null +++ b/components/add-game-to-plan-list.tsx @@ -0,0 +1,96 @@ +"use client" + +import { User } from "@prisma/client"; +import { useRouter } from "next/navigation"; +import { startTransition } from "react"; +import { Button } from "./ui/button"; + +export default function AddGameToPlanList(props: { gameId: string, user: User }) { + + const router = useRouter(); + const gameId = parseFloat(props.gameId); + const user = props.user; + + let formData: { + id: String; + gameId: Number; + add: boolean; + planningGameList: number[] | undefined; + playingGameList: number[] | undefined; + finishedGameList: number[] | undefined; + } = { + id: "", + gameId: -1, + add: true, + planningGameList: undefined, + playingGameList: undefined, + finishedGameList: undefined + }; + + async function removeGame(e: any) { + e.preventDefault() + + formData.id = user.id; + formData.planningGameList = props.user.planningGameList.filter((id) => id !== gameId) + console.log(formData.planningGameList) + const response = await fetch('/api/gamelists', { + method: 'PUT', + body: JSON.stringify(formData) + }) + + startTransition(() => { + // Refresh the current route and fetch new data from the server without + // losing client-side browser or React state. + router.refresh(); + }); + return await response.json() + } + + async function addGame(e: any) { + e.preventDefault() + + formData.id = user.id; + props.user.planningGameList.push(gameId) + formData.planningGameList = props.user.planningGameList; + const response = await fetch('/api/gamelists', { + method: 'PUT', + body: JSON.stringify(formData) + }) + + startTransition(() => { + // Refresh the current route and fetch new data from the server without + // losing client-side browser or React state. + router.refresh(); + }); + return await response.json() + } + + + let button = <div></div>; + try { + if (!props.user.planningGameList.includes(parseFloat(props.gameId))) { + button = ( + <form onSubmit={addGame}> + <Button type="submit" size="lg"> + Add Game To Planning-to-play-List + </Button> + </form> + ) + } else { + button = ( + <form onSubmit={removeGame}> + <Button type="submit" size="lg" variant={"secondary"}> + Remove Game From Planning-to-play-List + </Button> + </form> + ) + } + } catch (error) { + // throw new Error("Failed to check Planning-to-play-List"); + } + + return ( + button + ) +} + diff --git a/components/add-game-to-playing-list.tsx b/components/add-game-to-playing-list.tsx new file mode 100644 index 0000000000000000000000000000000000000000..befa763ba9d55009cc9204bfc64e4588d92c42b8 --- /dev/null +++ b/components/add-game-to-playing-list.tsx @@ -0,0 +1,96 @@ +"use client" + +import { User } from "@prisma/client"; +import { useRouter } from "next/navigation"; +import { startTransition } from "react"; +import { Button } from "./ui/button"; + +export default function AddGameToPlayingList(props: { gameId: string, user: User }) { + + const router = useRouter(); + const gameId = parseFloat(props.gameId); + const user = props.user; + + let formData: { + id: String; + gameId: Number; + add: boolean; + planningGameList: number[] | undefined; + playingGameList: number[] | undefined; + finishedGameList: number[] | undefined; + } = { + id: "", + gameId: -1, + add: true, + planningGameList: undefined, + playingGameList: undefined, + finishedGameList: undefined + }; + + async function removeGame(e: any) { + e.preventDefault() + + formData.id = user.id; + formData.playingGameList = props.user.playingGameList.filter((id) => id !== gameId) + console.log(formData.playingGameList) + const response = await fetch('/api/gamelists', { + method: 'PUT', + body: JSON.stringify(formData) + }) + + startTransition(() => { + // Refresh the current route and fetch new data from the server without + // losing client-side browser or React state. + router.refresh(); + }); + return await response.json() + } + + async function addGame(e: any) { + e.preventDefault() + + formData.id = user.id; + props.user.playingGameList.push(gameId) + formData.playingGameList = props.user.playingGameList; + const response = await fetch('/api/gamelists', { + method: 'PUT', + body: JSON.stringify(formData) + }) + console.log("add game") + startTransition(() => { + // Refresh the current route and fetch new data from the server without + // losing client-side browser or React state. + router.refresh(); + }); + return await response.json() + } + + + let button = <div></div>; + try { + if (!props.user.playingGameList.includes(parseFloat(props.gameId))) { + button = ( + <form onSubmit={addGame}> + <Button type="submit" size="lg"> + Add Game To currently-playing-List + </Button> + </form> + ) + } else { + button = ( + <form onSubmit={removeGame}> + <Button type="submit" size="lg" variant={"secondary"}> + Remove Game From currently-playing-List + </Button> + </form> + ) + } + } catch (error) { + // throw new Error("Failed to check playing-to-play-List"); + } + + return ( + button + ) +} + diff --git a/components/addGameToList.tsx b/components/addGameToFavList.tsx similarity index 82% rename from components/addGameToList.tsx rename to components/addGameToFavList.tsx index ebb42adaeb2f8e8d982b9893d8f6cd3ff8d66d91..9500d4d422a4daeb2cc578db608cad4b5411126c 100644 --- a/components/addGameToList.tsx +++ b/components/addGameToFavList.tsx @@ -4,7 +4,7 @@ import { useRouter } from "next/navigation"; import { startTransition } from "react"; import { Button } from "./ui/button"; -export default function AddGameToList(props: { userGameList: Number[], gameId: string }) { +export default function AddGameToFavList(props: { userGameList: Number[], gameId: string }) { const router = useRouter(); const gameId = props.gameId @@ -15,7 +15,7 @@ export default function AddGameToList(props: { userGameList: Number[], gameId: s formData.gameId = gameId; formData.add = false; - const response = await fetch('/api/favgameslist', { + const response = await fetch('/api/users/favgameslist', { method: 'PUT', body: JSON.stringify(formData) }) @@ -33,7 +33,7 @@ export default function AddGameToList(props: { userGameList: Number[], gameId: s formData.gameId = gameId; formData.add = true; - const response = await fetch('/api/favgameslist', { + const response = await fetch('/api/users/favgameslist', { method: 'PUT', body: JSON.stringify(formData) }) @@ -53,7 +53,7 @@ export default function AddGameToList(props: { userGameList: Number[], gameId: s button = ( <form onSubmit={addGame}> <Button type="submit" size="lg"> - Add Game To List + Add Game To Favorite List </Button> </form> ) @@ -61,13 +61,13 @@ export default function AddGameToList(props: { userGameList: Number[], gameId: s button = ( <form onSubmit={removeGame}> <Button type="submit" size="lg" variant={"secondary"}> - Remove Game From List + Remove Game From Favorite List </Button> </form> ) } } catch (error) { - throw new Error("Failed to fetch comments"); + // throw new Error("Failed to check list"); } return ( diff --git a/components/back-button.tsx b/components/back-button.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a0be855c52dac018acabf1a7cb08fb27e8b8b7e5 --- /dev/null +++ b/components/back-button.tsx @@ -0,0 +1,20 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { Icons } from "./icons"; +import { Button } from "./ui/button"; + +export const BackButton = () => { + const router = useRouter(); + + return ( + <Button + variant="ghost" + size="icon" + onClick={() => router.back()} + title="Go Back" + > + <Icons.chevronLeft /> + </Button> + ); +}; \ No newline at end of file diff --git a/components/back-header.tsx b/components/back-header.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ac66b0e7b0cf7a40dd128b2b97cd51f990447788 --- /dev/null +++ b/components/back-header.tsx @@ -0,0 +1,10 @@ +import { BackButton } from "@/components/back-button"; + +export const BackHeader = ({ children }: { children: React.ReactNode }) => { + return ( + <div className="flex items-center space-x-3"> + <BackButton /> + {children} + </div> + ); +} \ No newline at end of file diff --git a/components/comment-gweets.tsx b/components/comment-gweets.tsx deleted file mode 100644 index 4ef3272389b2a9b969c2eb35c362c95ff1247dfb..0000000000000000000000000000000000000000 --- a/components/comment-gweets.tsx +++ /dev/null @@ -1,172 +0,0 @@ -// "use client" - -// import { zodResolver } from "@hookform/resolvers/zod"; -// import { useForm } from "react-hook-form"; -// import * as z from "zod"; - -// import { Button } from "@/components/ui/button"; -// import { -// Form, -// FormControl, -// FormDescription, -// FormField, -// FormItem, -// FormLabel, -// FormMessage, -// } from "@/components/ui/form"; - -// import { Textarea } from "@/components/ui/textarea"; -// import { toast } from "@/components/ui/use-toast"; -// import { FormEvent, Fragment, useEffect, useState } from "react"; - -// import CommentItem from "./comment-item"; -// import { Icons } from "./icons"; -// import { Card } from "./ui/card"; -// import { Separator } from "./ui/separator"; -// import { Skeleton } from "./ui/skeleton"; - -// const FormSchema = z.object({ -// gweet: z -// .string() -// .min(1, { message: "Come on post something...", }) -// .max(1000, { message: "Gweets cannot be more that 1000 characters.", }), -// }) - -// export function PostCommentForm(props: { postid: string }) { -// const [isGweetLoading, setIsGweetLoading] = useState<boolean>(false); -// const [isLoading, setIsLoading] = useState<boolean>(false); -// const [messages, setMessages] = useState<any[]>([]); - -// const form = useForm<z.infer<typeof FormSchema>>({ -// resolver: zodResolver(FormSchema), -// }) - -// async function onCommentGweet(data: z.infer<typeof FormSchema>) { -// setIsGweetLoading(true); - -// await fetch('/api/comments', { -// method: 'POST', -// body: JSON.stringify(props.postid) -// }) - -// toast({ -// title: "Your comment is being processed...", -// description: ( -// <pre className="mt-2 w-[340px] rounded-md bg-slate-600 p-4"> -// <code className="text-white">{JSON.stringify(data, null, 2)}</code> -// </pre> -// ), -// }) - -// setIsGweetLoading(false); -// form.setValue('gweet', ''); -// await fetchMessages(); -// } - -// async function fetchMessages() { -// setIsLoading(true); - -// try { -// const res = await fetch(`/api/comments?postid=${props.postid}`); - -// if (!res.ok) { -// throw new Error("Failed to fetch comments"); -// } - -// const data = await res.json(); - -// setMessages(data); -// } catch (error) { -// return toast({ -// variant: "destructive", -// title: "Uh oh! Something went wrong.", -// description: "Failed to fetch messages. Please try again.", -// }); -// } - -// setIsLoading(false); -// } - -// useEffect(() => { -// fetchMessages(); -// }, []); - -// async function onSubmit(event: FormEvent<HTMLFormElement>) { -// event.preventDefault(); -// await fetchMessages(); -// } - -// // console.log((messages[0] as IPost).user.image); -// return ( -// <div> -// {/* <PostItem msg={(messages[0])} /> */} -// <Form {...form}> -// <form onSubmit={form.handleSubmit(onCommentGweet)} className="space-y-6"> -// <FormField -// control={form.control} -// name="gweet" -// render={({ field }) => ( -// <FormItem> -// <FormLabel>Comment</FormLabel> -// <FormControl> -// <Textarea -// placeholder="What's on your mind?" -// className="resize-none" -// disabled={isGweetLoading} -// {...field} -// /> -// </FormControl> -// <FormDescription> -// Your comment will be public, and everyone can see them. -// </FormDescription> -// <FormMessage /> -// </FormItem> -// )} -// /> -// <Button type="submit" disabled={isGweetLoading}>Submit</Button> -// </form> -// </Form> -// <Card className="w-full h-full overflow-hidden p-6 md:p-12 mt-12"> -// <form onSubmit={onSubmit}> -// <Button disabled={isLoading} type="submit" className="w-full mb-6"> -// {isLoading && ( -// <Icons.spinner className="mr-2 h-4 w-4 animate-spin" /> -// )} -// Load More -// </Button> - -// {messages.length > 0 ? ( -// messages.map((message: any) => ( -// <Fragment key={message.id}> -// <CommentItem msg={message} /> -// <Separator className="mt-3 mb-6" /> -// </Fragment> -// )) -// ) : ( -// <> -// {Array.from({ length: 4 }, (_, i) => i + 1).map((i) => ( -// <> -// <div className="flex"> -// <Skeleton className="h-10 w-10 rounded-full" /> -// <div className="ml-4 flex flex-col flex-grow"> -// <div> -// <div className="flex items-center"> -// <div className="mx-auto w-full space-y-6"> -// <Skeleton className="h-[30px] w-1/4" /> -// <Skeleton className="h-[20px] w-2/3" /> -// <Skeleton className="h-[20px] w-full" /> -// </div> -// </div> -// </div> -// </div> -// </div> -// <Separator className="mt-3 mb-6" /> -// </> -// ))} -// </> -// )} -// </form> -// </Card> -// </div> -// ) -// } \ No newline at end of file diff --git a/components/comment-item.tsx b/components/comment-item.tsx deleted file mode 100644 index 0356db7a7e7e31bf3ed10dd84e26057ff562ffbc..0000000000000000000000000000000000000000 --- a/components/comment-item.tsx +++ /dev/null @@ -1,27 +0,0 @@ -// import { formatTimeElapsed } from "@/lib/utils"; -// import { UserAvatar } from "./user-avatar"; - -// export default function CommentItem({ msg }: { msg: any }) { -// return ( -// <div className="flex"> -// <UserAvatar -// user={{ name: msg.user.username || null, image: msg.user.image || null }} -// className="h-10 w-10" -// /> -// <div className="ml-4 flex flex-col flex-grow"> -// <div> -// <div className="flex items-center"> -// <h1 className="font-bold mr-2">{msg.user.name}</h1> -// <h1 className="text-sky-500 text-sm"> -// @{msg.user.username} -// </h1> -// <h1 className="text-gray-500 text-sm ml-auto"> -// {formatTimeElapsed(msg.createdAt)} -// </h1> -// </div> -// <h1>{msg.message}</h1> -// </div> -// </div> -// </div> -// ) -// } \ No newline at end of file diff --git a/components/create-gweet/api/post-gweet.ts b/components/create-gweet/api/post-gweet.ts new file mode 100644 index 0000000000000000000000000000000000000000..32d7f2e1c1549a139dd3d279a2ef5569b584cd6c --- /dev/null +++ b/components/create-gweet/api/post-gweet.ts @@ -0,0 +1,42 @@ +import { postHashtags, retrieveHashtagsFromGweet } from "@/components/trends"; +import { uploadFiles } from "@/lib/uploadthing"; + +export const postGweet = async ({ + content, + files, + authorId, + replyToGweetId, + quoteGweetId, +}: { + content: string; + files: File[]; + authorId: string; + replyToGweetId?: string | null; + quoteGweetId?: string | null; +}) => { + const gweet = { + content, + authorId, + ...(replyToGweetId && { replyToGweetId }), + ...(quoteGweetId && { quoteGweetId }), + }; + + try { + let fileprops: { fileUrl: string; fileKey: string; }[] = []; + if (files.length > 0) { + fileprops = await uploadFiles({ files, endpoint: 'imageUploader' }) + } + + const data = await fetch('/api/gweets', { + method: 'POST', + body: JSON.stringify({ gweet, fileprops }) + }).then((result) => result.json()) + + const hashtags = retrieveHashtagsFromGweet(content); + if (hashtags) await postHashtags(hashtags); + + return data; + } catch (error: any) { + return error.response.data; + } +}; \ No newline at end of file diff --git a/components/create-gweet/components/create-gweet-wrapper.tsx b/components/create-gweet/components/create-gweet-wrapper.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b0572279f1fb0f6375a14032dffbde4904fc81e9 --- /dev/null +++ b/components/create-gweet/components/create-gweet-wrapper.tsx @@ -0,0 +1,30 @@ +"use client"; + +import { useState } from "react"; + +import { CreateGweet } from "./create-gweet"; + +export const CreateGweetWrapper = ({ + replyToGweetId, +}: { + replyToGweetId: string | null; +}) => { + const [isComment, setIsComment] = useState(true); + + return ( + <div className="px-6"> + <CreateGweet + replyToGweetId={replyToGweetId} + placeholder="Gweet your reply..." + isComment={isComment} + /> + {isComment && ( + <button + onClick={() => { + setIsComment(false); + }} + /> + )} + </div> + ); +}; \ No newline at end of file diff --git a/components/create-gweet/components/create-gweet.tsx b/components/create-gweet/components/create-gweet.tsx new file mode 100644 index 0000000000000000000000000000000000000000..71bd6bd3657f626016cc4a5b6256aa7c0bf45e07 --- /dev/null +++ b/components/create-gweet/components/create-gweet.tsx @@ -0,0 +1,267 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import { useSession } from "next-auth/react"; +import Image from "next/image"; +import { useRef, useState } from "react"; +import { useForm } from "react-hook-form"; +import * as z from "zod"; + +import { Button } from "@/components/ui/button"; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormMessage, +} from "@/components/ui/form"; + +import { Textarea } from "@/components/ui/textarea"; +import { toast } from "@/components/ui/use-toast"; + +import { IGweet } from "@/components/gweets/types"; +import { UserAvatar } from "@/components/user-avatar"; + +import { Icons } from "@/components/icons"; +import { Card } from "@/components/ui/card"; +import { useCreateGweet } from "../hooks/use-create-gweet"; +import { IChosenImages } from "../types"; + +const FormSchema = z.object({ + gweet: z + .string() + .min(1, { message: "Come on post something..." }) + .max(240, { message: "Gweets cannot be more that 240 characters." }), +}) + +export const CreateGweet = ({ + parent_gweet, + quoted_gweet, + replyToGweetId, + placeholder, + isComment = false, +}: { + parent_gweet?: IGweet | null; + quoted_gweet?: IGweet | null; + replyToGweetId?: string | null; + placeholder?: string | null; + isComment?: boolean; +}) => { + const [chosenImages, setChosenImages] = useState<IChosenImages[]>([]); + const imageUploadRef = useRef<HTMLInputElement>(null); + + const { data: session } = useSession(); + const { isLoading, mutate, data } = useCreateGweet(); + + const form = useForm<z.infer<typeof FormSchema>>({ + resolver: zodResolver(FormSchema), + }) + + async function onGweet(formData: z.infer<typeof FormSchema>) { + if (!session) return null; + + mutate({ + content: formData.gweet, + authorId: session?.user?.id, + replyToGweetId, + files: chosenImages.map((image) => image.file), + quoteGweetId: quoted_gweet?.id || null, + }) + + toast({ + description: "Your gweet was send.", + }) + + form.setValue('gweet', ''); + setChosenImages([]); + } + + const chooseImages = async ( + event: React.ChangeEvent<HTMLInputElement>, + setChosenImages: (images: IChosenImages[]) => void, + ) => { + const files = event.target.files; + + if (files && files.length > 0) { + const newImages: IChosenImages[] = []; + const totalSelectedImages = chosenImages.length + files.length; + + if (totalSelectedImages > 4) { + return toast({ + variant: "destructive", + description: "You can only upload 4 images per gweet.", + }) + } + + for (let i = 0; i < files.length; i++) { + const filePath = files[i]; + + const reader = new FileReader(); + reader.readAsDataURL(filePath); + + reader.onload = () => { + newImages.push({ + url: reader.result, + file: filePath, + }); + + if (newImages.length === files.length) { + setChosenImages([...chosenImages, ...newImages]); + } + }; + } + } + }; + + if (!session) return null; + + return ( + <> + {/* TODO showing if is replying */} + {parent_gweet && ( + <div className="grid grid-cols-2 gap-11 p-4"> + + <div className="grid place-items-center grid-rows-2 gap-1"> + <UserAvatar + user={{ username: parent_gweet?.author?.username, image: parent_gweet?.author?.image || null }} + /> + <div className="bg-gray-300 h-full w-px"></div> + </div> + + <div className="flex flex-col"> + <> + <div className="flex gap-1"> + <span className="text-secondary text-sm font-medium truncate hover:underline"> + {parent_gweet?.author?.name} + </span> + <span className="text-tertiary text-sm truncate"> + @{parent_gweet?.author?.email?.split("@")[0]} + </span> + <span className="text-tertiary">·</span> + </div> + <div> + {parent_gweet?.content && ( + <div className="text">{parent_gweet?.content}</div> + )} + </div> + </> + + {!isComment && ( + <div className={`${!parent_gweet ? 'ml-16' : ''} flex items-center gap-1 cursor-pointer`}> + <span className="text-tertiary truncate">Replying to</span> + <span className="text-primary truncate"> + @{parent_gweet?.authorId} + </span> + </div> + )} + </div> + </div> + )} + + <div className="relative"> + <Form {...form}> + <form onSubmit={form.handleSubmit(onGweet)} className="space-y-6"> + <Card className="p-3 flex items-start relative"> + <UserAvatar + className="mr-3" + user={{ username: session?.user.username || null, image: session?.user?.image || null }} + /> + <Button + type="button" + variant="ghost" + size="icon" + className="absolute bottom-0 left-0 mb-3 ml-3" + onClick={() => imageUploadRef.current?.click()} + disabled={isLoading || !session.user} + > + <Icons.media className="text-muted-foreground" /> + </Button> + <div className="flex-grow"> + <FormField + control={form.control} + name="gweet" + render={({ field }) => ( + <FormItem> + <FormControl> + <Textarea + placeholder={placeholder || "What's on your mind?"} + className="resize-none min-h-[100px]" + disabled={isLoading || !session.user} + {...field} + /> + </FormControl> + {!isComment ? + <FormDescription> + Your gweets will be public, and everyone can see them. + </FormDescription> + : null + } + <FormMessage /> + </FormItem> + )} + /> + + <input + className="hidden w-full resize-none" + type="file" + multiple + onChange={(e) => chooseImages(e, setChosenImages)} + ref={imageUploadRef} + disabled={isLoading || !session.user} + /> + {chosenImages.length > 0 && ( + <div className={`grid object-cover h-[600px] pt-2 + ${chosenImages.length === 1 ? "grid-cols-1" + : chosenImages.length === 2 ? "grid-cols-2 gap-3" + : chosenImages.length === 3 || 4 ? "grid-cols-2 grid-rows-2 gap-3" + : "" + }`} + > + {chosenImages.map((image, i) => { + const isFirstImage = chosenImages.length === 3 && i === 0; + return ( + <Card key={i} className={`relative max-h-[600px] overflow-hidden ${isFirstImage ? "row-span-2" : ""}`}> + <Button + type="button" + size="icon" + variant="secondary" + className="rounded-full absolute top-1 right-1 z-40" + onClick={() => { + setChosenImages( + chosenImages.filter((img, j) => j !== i), + ); + }} + > + <Icons.close className="w-6 h-6" /> + </Button> + <Image + src={image.url as string} + alt="gweet image" + fill + className="object-cover rounded-lg" + /> + </Card> + ); + })} + </div> + )} + {/* {quoted_gweet && <QuotedGweet gweet={quoted_gweet} />} */} + </div> + </Card> + + <div className="flex justify-end"> + <Button type="submit" size="lg" className="w-20" disabled={isLoading || !session.user}> + {isLoading ? ( + <Icons.spinner className="h-4 w-4 animate-spin" /> + ) : ( + isComment ? 'Reply' : 'Gweet' + )} + </Button> + </div> + </form> + </Form> + </div> + </> + ); +}; diff --git a/components/create-gweet/hooks/use-create-gweet.ts b/components/create-gweet/hooks/use-create-gweet.ts new file mode 100644 index 0000000000000000000000000000000000000000..686d29a3ab113de076555c96fa58a398f46ccc8a --- /dev/null +++ b/components/create-gweet/hooks/use-create-gweet.ts @@ -0,0 +1,40 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; + +import { postGweet } from "../api/post-gweet"; + +export const useCreateGweet = () => { + const queryClient = useQueryClient(); + + return useMutation( + ({ + content, + files, + authorId, + replyToGweetId, + quoteGweetId, + }: { + content: string; + files: File[]; + authorId: string; + replyToGweetId?: string | null; + quoteGweetId?: string | null; + }) => { + return postGweet({ + content, + files, + authorId, + replyToGweetId, + quoteGweetId, + }); + }, + { + onSuccess: () => { + queryClient.invalidateQueries(["gweets"]); + queryClient.invalidateQueries(["hashtags"]); + }, + onError: (error) => { + console.log("error", error); + }, + }, + ); +}; \ No newline at end of file diff --git a/components/create-gweet/types/index.ts b/components/create-gweet/types/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..0e7b1e436493726df1fe7cb78a32864a7b748932 --- /dev/null +++ b/components/create-gweet/types/index.ts @@ -0,0 +1,8 @@ +export interface Post { + id: string; +} + +export interface IChosenImages { + url: string | ArrayBuffer | null; + file: File; +} \ No newline at end of file diff --git a/components/following-button.tsx b/components/following-button.tsx index 420ecdc016ea82d2f78da72783b66dee0ac07c28..68df591d0cdbd7c9ffcc104492f0afb3dc874f02 100644 --- a/components/following-button.tsx +++ b/components/following-button.tsx @@ -1,55 +1,75 @@ "use client" -// import { PrismaClient } from '@prisma/client'; -import { useState } from 'react'; +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'; -// Muss in die API route -// const prisma = new PrismaClient(); +// 1: Define a type that includes the relation to `Post` +const userWithFollows = Prisma.validator<Prisma.UserArgs>()({ + include: { followers: true, following: true }, +}) -// async function getFollower(userId: number, followerId: number) { -// const follower = await prisma.follows.findFirst({ -// where: { -// followerId: followerId, -// followingId: userId, -// }, -// }); +// 2: Define a type that only contains a subset of the scalar fields +const userPersonalData = Prisma.validator<Prisma.UserArgs>()({ + select: { email: true, name: true }, +}) -// return follower; -// } +// 3: This type will include a user and all their posts +type UserWithFollows = Prisma.UserGetPayload<typeof userWithFollows> -export default function FollowButton({ userId, followerId }: { userId: number; followerId: number }) { + +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 }) + }) - const handleFollow = async () => { - // const follower = await getFollower(userId, followerId); - - // if (follower) { - // // User is already following, so unfollow - // await prisma.follows.delete({ - // where: { - // followerId_followingId: { - // followerId: followerId, - // followingId: userId, - // }, - // }, - // }); - // setIsFollowing(false); - // } else { - // // User is not following, so follow - // await prisma.follows.create({ - // data: { - // followerId: followerId, - // followingId: userId, - // }, - // }); - // setIsFollowing(true); - // } - }; + setButtonPress(!buttonPressed) + } return ( - <Button onClick={handleFollow}> - {isFollowing ? 'Unfollow' : 'Follow'} - </Button> + <> + {pathname !== `/${session?.user.username}` && + <Button onClick={handleFollow}> + {isFollowing ? 'Unfollow' : 'Follow'} + </Button>} + </> ); } \ No newline at end of file diff --git a/components/following-users.tsx b/components/following-users.tsx deleted file mode 100644 index 44408c13acfcd0343a27854bd4a9b23de54c74ad..0000000000000000000000000000000000000000 --- a/components/following-users.tsx +++ /dev/null @@ -1,47 +0,0 @@ -"use client" - -// import { PrismaClient } from '@prisma/client'; -import { useEffect, useState } from 'react'; - -// Muss in die API route -// const prisma = new PrismaClient(); - -interface Follower { - id: number; - name: string; - email: string | null; -} - -export default function FollowersList({ userId }: { userId: number }) { - const [followers, setFollowers] = useState<Follower[]>([]); - - useEffect(() => { - async function fetchFollowers() { - // const followersList = await prisma.follows.findMany({ - // where: { - // followingId: userId, - // }, - // include: { - // follower: true, - // }, - // }); - - // const filteredFollowers = followersList.map((follow: any) => { - // const { id, name, email } = follow.follower; - // return { id, name: name ?? "", email }; - // }); - - // setFollowers(filteredFollowers); - } - - fetchFollowers(); - }, [userId]); - - return ( - <ul> - {followers.map((follower) => ( - <li key={follower.id}>{follower.name} ({follower.email})</li> - ))} - </ul> - ); -} \ No newline at end of file diff --git a/components/gweets/api/delete-gweet.ts b/components/gweets/api/delete-gweet.ts new file mode 100644 index 0000000000000000000000000000000000000000..0d9f6bda7af520ff250b74c9503db13710b6fda7 --- /dev/null +++ b/components/gweets/api/delete-gweet.ts @@ -0,0 +1,11 @@ +export const deleteGweet = async (gweetId: string) => { + try { + const data = await fetch(`/api/gweets?id=${gweetId}`, { + method: 'DELETE' + }).then((result) => result.json()); + + return data; + } catch (error: any) { + console.log(error); + } +}; \ No newline at end of file diff --git a/components/gweets/api/get-gweet.ts b/components/gweets/api/get-gweet.ts new file mode 100644 index 0000000000000000000000000000000000000000..342687ec8b45e830b17efa9c45721498bcd6f4e0 --- /dev/null +++ b/components/gweets/api/get-gweet.ts @@ -0,0 +1,9 @@ +export default async function getGweet(id: string | undefined) { + try { + const data = await fetch(`/api/gweets/${id}`).then((result) => result.json()); + + return data; + } catch (error: any) { + return error.response.data; + } +} \ No newline at end of file diff --git a/components/gweets/api/get-gweets.ts b/components/gweets/api/get-gweets.ts new file mode 100644 index 0000000000000000000000000000000000000000..56fa0eba8abb0e1019f80988b060c2e15dc8d3f9 --- /dev/null +++ b/components/gweets/api/get-gweets.ts @@ -0,0 +1,20 @@ +export const getGweets = async ({ + pageParam = "", + limit = 20, + type, + id, +}: { + pageParam?: string; + limit?: number; + type?: string; + id?: string; +}) => { + try { + const url = `/api/gweets?cursor=${pageParam}&limit=${limit}${type ? `&type=${type}` : ""}${id ? `&id=${id}` : ""}`; + const data = await fetch(url).then((result) => result.json()); + + return data; + } catch (error: any) { + return error.response.data; + } +}; \ No newline at end of file diff --git a/components/gweets/api/handle-regweet.ts b/components/gweets/api/handle-regweet.ts new file mode 100644 index 0000000000000000000000000000000000000000..0607ca83de69f0c490f915505af599c5cd1d9946 --- /dev/null +++ b/components/gweets/api/handle-regweet.ts @@ -0,0 +1,15 @@ +export const handleRegweet = async (gweetId: string, userId: string) => { + try { + const data = await fetch("/api/gweets/regweets", { + method: "POST", + body: JSON.stringify({ + gweet_id: gweetId, + user_id: userId, + }), + }).then((result) => result.json()); + + return data; + } catch (error: any) { + return error.response.data; + } +}; \ No newline at end of file diff --git a/components/gweets/api/toggle-like.ts b/components/gweets/api/toggle-like.ts new file mode 100644 index 0000000000000000000000000000000000000000..08b0ab7611162f0e4d2a11dd837d59d7b028d16c --- /dev/null +++ b/components/gweets/api/toggle-like.ts @@ -0,0 +1,24 @@ +export const toggleLike = async ({ + gweetId, + userId, +}: { + gweetId: string | undefined; + userId: string | undefined; +}) => { + try { + const data = await fetch("/api/gweets/likes", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + gweet_id: gweetId, + user_id: userId, + }), + }).then((result) => result.json()); + + return data; + } catch (error: any) { + return error.message; + } +}; \ No newline at end of file diff --git a/components/gweets/components/actions/comment-button.tsx b/components/gweets/components/actions/comment-button.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d5c53c415937da2a116fe61a8244b5c5602cb59a --- /dev/null +++ b/components/gweets/components/actions/comment-button.tsx @@ -0,0 +1,18 @@ +import { Button } from "@/components/ui/button"; +import { IGweet } from "../../types"; + +import { Icons } from "@/components/icons"; + +export const CommentButton = ({ gweet }: { gweet: IGweet }) => { + return ( + <div className="relative inline-flex items-center"> + <Button variant="ghost" size="icon" className="hover:bg-sky-800 z-40"> + <Icons.messagecircle className="w-6 h-6" /> + </Button> + + {gweet.allComments.length > 0 && ( + <span className="absolute pl-12">{gweet.allComments.length}</span> + )} + </div> + ); +}; \ No newline at end of file diff --git a/components/gweets/components/actions/like-button.tsx b/components/gweets/components/actions/like-button.tsx new file mode 100644 index 0000000000000000000000000000000000000000..46c17791be726f6372a22684432cec0b59f4aaf9 --- /dev/null +++ b/components/gweets/components/actions/like-button.tsx @@ -0,0 +1,41 @@ +import { useSession } from "next-auth/react"; + +import { Icons } from "@/components/icons"; +import { Button } from "@/components/ui/button"; +import { useLike } from "../../hooks/use-like"; +import { IGweet } from "../../types"; + +export const LikeButton = ({ gweet }: { gweet: IGweet }) => { + const { data: session } = useSession(); + const hasLiked = gweet.likes.some( + (like) => like.userId === session?.user.id, + ); + + const mutation = useLike({ + gweetAuthorId: gweet.author.id, + sessionOwnerId: session?.user.id, + }); + + return ( + <div className="inline-flex items-center"> + <Button + onClick={(e) => { + e.stopPropagation(); + if (session) { + mutation.mutate({ gweetId: gweet.id, userId: session.user.id }); + } + }} + variant="ghost" size="icon" className="hover:bg-red-800" + > + {hasLiked ? + <Icons.heart className="w-6 h-6 fill-red-600 text-red-600" /> + : <Icons.heart className="w-6 h-6" /> + } + </Button> + + {gweet.likes.length > 0 && ( + <span className="px-2">{gweet?.likes?.length}</span> + )} + </div> + ); +}; \ No newline at end of file diff --git a/components/gweets/components/actions/regweet-button.tsx b/components/gweets/components/actions/regweet-button.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f02a894927f64205a1f87b26b6d968ea68db09a8 --- /dev/null +++ b/components/gweets/components/actions/regweet-button.tsx @@ -0,0 +1,40 @@ +import { Icons } from "@/components/icons"; +import { Button } from "@/components/ui/button"; +import { useSession } from "next-auth/react"; +import { useRegweet } from "../../hooks/use-regweet"; +import { IGweet } from "../../types"; + +export const RegweetButton = ({ gweet }: { gweet: IGweet }) => { + const { data: session } = useSession(); + const hasRegweeted = gweet.regweets.some( + (regweet) => regweet.userId === session?.user?.id, + ); + + const mutation = useRegweet(); + + return ( + <div className="relative inline-flex items-center"> + <Button + onClick={(e) => { + e.stopPropagation(); + if (session) { + mutation.mutate({ + gweetId: gweet.id, + userId: session.user.id, + }); + } + }} + variant="ghost" size="icon" className="hover:bg-green-800 z-40" + > + {hasRegweeted ? + <Icons.regweet className="w-6 h-6 text-green-600" /> + : <Icons.regweet className="w-6 h-6" /> + } + </Button> + + {gweet.regweets.length > 0 && ( + <span className="absolute pl-12">{gweet.regweets.length}</span> + )} + </div> + ); +}; \ No newline at end of file diff --git a/components/gweets/components/comments.tsx b/components/gweets/components/comments.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f1f57424dcdbb48e7128fcd3c7a59d13f0f21634 --- /dev/null +++ b/components/gweets/components/comments.tsx @@ -0,0 +1,39 @@ +import { TryAgain } from "@/components/try-again"; + +import LoadingItem from "@/components/loading-item"; +import { useGweets } from "../hooks/use-gweets"; +import { InfiniteGweets } from "./infinite-gweets"; + +export const Comments = ({ gweetId }: { gweetId: string }) => { + const { + data: comments, + isLoading, + isError, + fetchNextPage, + hasNextPage, + isFetchingNextPage, + isSuccess, + } = useGweets({ + queryKey: ["gweets", gweetId, "comments"], + type: "comments", + id: gweetId, + }); + + if (isLoading) { + return <LoadingItem />; + } + + if (isError) { + return <TryAgain />; + } + + return ( + <InfiniteGweets + gweets={comments} + fetchNextPage={fetchNextPage} + hasNextPage={hasNextPage} + isFetchingNextPage={isFetchingNextPage} + isSuccess={isSuccess} + /> + ); +}; \ No newline at end of file diff --git a/components/gweets/components/delete-gweet-modal.tsx b/components/gweets/components/delete-gweet-modal.tsx new file mode 100644 index 0000000000000000000000000000000000000000..83ae165e34f33902f6677c813cba5370b4ba94f5 --- /dev/null +++ b/components/gweets/components/delete-gweet-modal.tsx @@ -0,0 +1,67 @@ +"use client"; + +import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; +import { useDeleteGweet } from "../hooks/use-delete-gweet"; +import { IGweet } from "../types"; + +import { Icons } from "@/components/icons"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { useRouter } from "next/navigation"; + +export const DeleteGweetModal = ({ gweet, props, forwardedRef }: { gweet: IGweet, props: any, forwardedRef: any }) => { + const { triggerChildren, onSelect, onOpenChange, ...itemProps } = props; + const { isLoading, mutate, isSuccess } = useDeleteGweet(); + + const router = useRouter(); + if (isSuccess) router.push("/home"); + + return ( + <Dialog onOpenChange={onOpenChange}> + <DialogTrigger asChild> + <DropdownMenuItem + {...itemProps} + ref={forwardedRef} + onSelect={(event) => { + event.preventDefault(); + onSelect && onSelect(); + }}> + {triggerChildren} + </DropdownMenuItem> + </DialogTrigger> + <DialogContent className="sm:max-w-[425px]"> + <DialogHeader> + <DialogTitle>Delete gweet?</DialogTitle> + <DialogDescription> + You are about to delete this gweet. This can't be undone and will be removed from your profile, + the timeline of any accounts that follow you, and from the search results. + </DialogDescription> + </DialogHeader> + <DialogFooter> + <Button + variant="destructive" + type="submit" + disabled={isLoading} + onClick={() => { + mutate({ + gweetId: gweet?.id, + }); + }}> + {isLoading && ( + <Icons.spinner className="mr-2 h-4 w-4 animate-spin" /> + )} + Delete + </Button> + </DialogFooter> + </DialogContent> + </Dialog> + ) +} \ No newline at end of file diff --git a/components/gweets/components/gweet-actions.tsx b/components/gweets/components/gweet-actions.tsx new file mode 100644 index 0000000000000000000000000000000000000000..9011202799ca952df0d97d03f67c50ac86aff017 --- /dev/null +++ b/components/gweets/components/gweet-actions.tsx @@ -0,0 +1,19 @@ +import { IGweet } from "../types"; + +import { CommentButton } from "./actions/comment-button"; +import { LikeButton } from "./actions/like-button"; +import { RegweetButton } from "./actions/regweet-button"; + +export const GweetActions = ({ + gweet, +}: { + gweet: IGweet; +}) => { + return ( + <div className="space-x-12 w-60"> + <CommentButton gweet={gweet} /> + <RegweetButton gweet={gweet} /> + <LikeButton gweet={gweet} /> + </div> + ); +}; \ No newline at end of file diff --git a/components/gweets/components/gweet-author.tsx b/components/gweets/components/gweet-author.tsx new file mode 100644 index 0000000000000000000000000000000000000000..524f4a2c80dedd8b767b5a4e1dadfce6299db691 --- /dev/null +++ b/components/gweets/components/gweet-author.tsx @@ -0,0 +1,25 @@ +import { UserAvatar } from "@/components/user-avatar"; +import Link from "next/link"; +import { IGweet } from "../types"; +import { GweetOptions } from "./gweet-options"; + +export const GweetAuthor = ({ gweet }: { gweet: IGweet }) => { + return ( + <div className="flex items-center"> + <Link href={`/${gweet.author.username}`}> + <UserAvatar + user={{ username: gweet.author.username, image: gweet.author.image }} + className="h-10 w-10" + /> + </Link> + <div className="flex flex-col ml-3"> + <span className="font-bold">{gweet.author.name}</span> + <span className="text-sky-500 text-sm">@{gweet.author.username}</span> + </div> + + <div className="ml-auto"> + <GweetOptions gweet={gweet} /> + </div> + </div> + ); +}; \ No newline at end of file diff --git a/components/gweets/components/gweet-creation-date.tsx b/components/gweets/components/gweet-creation-date.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e787feb0d2187ab2f62386633f298635d909aa6e --- /dev/null +++ b/components/gweets/components/gweet-creation-date.tsx @@ -0,0 +1,11 @@ +import dayjs from "dayjs"; + +export const GweetCreationDate = ({ date }: { date: Date }) => { + return ( + <div className="space-x-2"> + <span>{dayjs(date).format(`h:mm A`)}</span> + <span>·</span> + <span>{dayjs(date).format(`MMM D, YYYY`)}</span> + </div> + ); +}; \ No newline at end of file diff --git a/components/gweets/components/gweet-details.tsx b/components/gweets/components/gweet-details.tsx new file mode 100644 index 0000000000000000000000000000000000000000..20fd88c18dbcda0bda0180e9321ccb774a9ea0ae --- /dev/null +++ b/components/gweets/components/gweet-details.tsx @@ -0,0 +1,88 @@ +"use client" + +import { CreateGweetWrapper } from "@/components/create-gweet/components/create-gweet-wrapper"; +import LoadingItem from "@/components/loading-item"; +import { TryAgain } from "@/components/try-again"; +import { Card } from "@/components/ui/card"; +import { Separator } from "@/components/ui/separator"; +import Image from "next/image"; +import { usePathname } from "next/navigation"; +import { useGweet } from "../hooks/use-gweet"; +import { Comments } from "./comments"; +import { GweetActions } from "./gweet-actions"; +import { GweetAuthor } from "./gweet-author"; +import { GweetCreationDate } from "./gweet-creation-date"; + +export const GweetDetails = () => { + // TODO use params + const pathname = usePathname(); + const id = pathname?.split(`/`)[3] || ``; + + const { data: gweet, isLoading, isError } = useGweet(id); + + if (isLoading) { + return <LoadingItem />; + } + + if (isError) { + return <TryAgain />; + } + + if (!isLoading && !isError && !gweet) { + return <>Not Found!</> + } + + return ( + <> + <div className="flex flex-col space-y-3 px-3 pt-3"> + <GweetAuthor gweet={gweet} /> + + {/* TODO needs handling of all gweets above and under the gweet */} + + <div className="flex flex-col space-y-3"> + {gweet.content && <h1 className="break-words">{gweet.content}</h1>} + + {/* TODO make own component */} + {gweet.media.length > 0 && ( + <div className={`grid object-cover h-[600px] ${gweet.media.length === 1 ? "grid-cols-1" + : gweet.media.length === 2 ? "grid-cols-2 gap-3" + : gweet.media.length === 3 || 4 ? "grid-cols-2 grid-rows-2 gap-3" + : "" + }`} + > + {gweet.media.map((image, i) => { + const isFirstImage = gweet.media.length === 3 && i === 0; + return ( + <Card key={i} className={`relative max-h-[600px] overflow-hidden ${isFirstImage ? "row-span-2" : ""}`}> + <Image + src={image.url as string} + alt="gweet image" + fill + className="object-cover rounded-lg" + /> + </Card> + ); + })} + </div> + )} + + {/* TODO */} + {/* {gweet?.quotedGweet && <QuotedGweet gweet={gweet?.quotedGweet} />} */} + </div> + + <div className="flex flex-col items-center flex-wrap flex-grow sm:flex-row space-y-3 sm:space-y-0"> + <div className="flex-grow text-muted-foreground"> + <GweetCreationDate date={gweet.createdAt} /> + </div> + <div className="sm:items-end"> + <GweetActions gweet={gweet} /> + </div> + </div> + + <CreateGweetWrapper replyToGweetId={gweet?.id} /> + </div> + <Separator className="h-1 mb-3" /> + <Comments gweetId={gweet?.id} /> + </> + ); +}; \ No newline at end of file diff --git a/components/gweets/components/gweet-options.tsx b/components/gweets/components/gweet-options.tsx new file mode 100644 index 0000000000000000000000000000000000000000..df18b1e70a017b2fcc5545cec9f2c1ae54d79877 --- /dev/null +++ b/components/gweets/components/gweet-options.tsx @@ -0,0 +1,95 @@ +"use client"; + +import { Icons } from "@/components/icons"; +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { toast } from "@/components/ui/use-toast"; +import { env } from "@/env.mjs"; +import getURL from "@/lib/utils"; +import { useSession } from "next-auth/react"; +import { useRef, useState } from "react"; +import { IGweet } from "../types"; +import { DeleteGweetModal } from "./delete-gweet-modal"; + +export const GweetOptions = ({ gweet }: { gweet: IGweet }) => { + const { data: session } = useSession(); + + const [dropdownOpen, setDropdownOpen] = useState<boolean>(false); + const [hasOpenDialog, setHasOpenDialog] = useState<boolean>(false); + const dropdownTriggerRef = useRef<HTMLButtonElement | null>(null); + const focusRef = useRef<HTMLButtonElement | null>(null); + + function handleDialogItemSelect() { + focusRef.current = dropdownTriggerRef.current; + } + + function handleDialogItemOpenChange(open: boolean) { + setHasOpenDialog(open); + if (open === false) { + setDropdownOpen(false); + } + } + + const url = getURL(`/status/${gweet?.id}`); + + return ( + <DropdownMenu open={dropdownOpen} onOpenChange={setDropdownOpen}> + <DropdownMenuTrigger asChild> + <div className="h-5 flex items-center"> + <Button variant="ghost" size="option" ref={dropdownTriggerRef}> + <Icons.moreOptions /> + </Button> + </div> + </DropdownMenuTrigger> + <DropdownMenuContent + align="end" + className="font-bold cursor-pointer" + hidden={hasOpenDialog} + onClick={(event) => { + event.stopPropagation(); + }} + onCloseAutoFocus={(event) => { + if (focusRef.current) { + focusRef.current.focus(); + focusRef.current = null; + event.preventDefault(); + } + }}> + <DropdownMenuGroup> + {gweet.author.id === session?.user?.id && ( + <DeleteGweetModal + gweet={gweet} + props={{ + triggerChildren: ( + <div className="text-red-600 flex"> + <Icons.trash className="mr-2 h-4 w-4 " /> + <span>Delete</span> + </div> + ), + onSelect: handleDialogItemSelect, + onOpenChange: handleDialogItemOpenChange, + }} + forwardedRef={focusRef} + /> + )} + <DropdownMenuItem + onClick={() => { + navigator.clipboard.writeText(url); + return toast({ + description: "Copied link to gweet.", + }) + }}> + <Icons.link className="mr-2 h-4 w-4 " /> + <span>Copy link to gweet</span> + </DropdownMenuItem> + </DropdownMenuGroup> + </DropdownMenuContent> + </DropdownMenu> + ); +}; \ No newline at end of file diff --git a/components/gweets/components/gweet.tsx b/components/gweets/components/gweet.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ff0b42fdb32a3b3261c94df785e3ccfdbbeda767 --- /dev/null +++ b/components/gweets/components/gweet.tsx @@ -0,0 +1,99 @@ +import { buttonVariants } from "@/components/ui/button"; +import { Card } from "@/components/ui/card"; +import { UserAvatar } from "@/components/user-avatar"; +import { cn, formatTimeElapsed } from "@/lib/utils"; +import Image from "next/image"; +import { useRouter } from "next/navigation"; +import { IGweet } from "../types"; +import { GweetActions } from "./gweet-actions"; +import { GweetOptions } from "./gweet-options"; + +export const Gweet = ({ gweet }: { gweet: IGweet }) => { + const router = useRouter(); + + return ( + <div + tabIndex={0} + onClick={() => router.push(`/${gweet.author.username}/status/${gweet.id}`)} + className={cn(buttonVariants({ variant: "ghost" }), "flex flex-col flex-grow h-auto w-full text-left cursor-pointer items-start p-3 space-y-3")} + > + {/* TODO replyto */} + {/* <div className=""> + {gweet?.replyToGweetId && ( + <div className=""> + <span className="">Replying to</span> + <button + onClick={(e) => { + e.stopPropagation(); + router.push(`/${gweet?.author.username}`); + }} + className="" + > + @{gweet?.author.username} + </button> + </div> + )} + </div> */} + <div className="flex flex-row h-auto w-full"> + <UserAvatar + user={{ username: gweet.author.username, image: gweet.author.image }} + className="h-10 w-10" + /> + + <div className="flex flex-col flex-grow space-y-3 ml-3 w-1"> + <div className="flex items-start"> + + <div className="flex space-x-2 flex-grow"> + <h1 className="font-bold">{gweet.author.name}</h1> + <h1 className="text-sky-500 text-sm"> + @{gweet.author.username} + </h1> + <span>·</span> + <h1 className="text-gray-500 text-sm"> + {formatTimeElapsed(gweet.createdAt)} + </h1> + </div> + + <div className="ml-auto"> + <GweetOptions gweet={gweet} /> + </div> + </div> + + <div className="flex flex-col flex-grow space-y-3 w-full"> + {gweet.content && <h1 className="break-words">{gweet.content}</h1>} + + {gweet.media.length > 0 && ( + <div className={`grid object-cover h-[600px] ${gweet.media.length === 1 ? "grid-cols-1" + : gweet.media.length === 2 ? "grid-cols-2 gap-3" + : gweet.media.length === 3 || 4 ? "grid-cols-2 grid-rows-2 gap-3" + : "" + }`} + > + {gweet.media.map((image, i) => { + const isFirstImage = gweet.media.length === 3 && i === 0; + return ( + <Card key={i} className={`relative max-h-[600px] overflow-hidden ${isFirstImage ? "row-span-2" : ""}`}> + <Image + src={image.url as string} + alt="gweet image" + fill + className="object-cover rounded-lg" + /> + </Card> + ); + })} + </div> + )} + + {/* TODO */} + {/* {quoted_gweet && <QuotedGweet gweet={quoted_gweet} />} */} + </div> + </div> + </div> + + <div className="flex justify-end flex-grow w-full" > + <GweetActions gweet={gweet} /> + </div> + </div> + ); +}; \ No newline at end of file diff --git a/components/gweets/components/gweets.tsx b/components/gweets/components/gweets.tsx new file mode 100644 index 0000000000000000000000000000000000000000..81fa2ee73942f6d698bae8f28b1b039c2e167c42 --- /dev/null +++ b/components/gweets/components/gweets.tsx @@ -0,0 +1,39 @@ +"use client"; + +import LoadingItem from "@/components/loading-item"; +import { TryAgain } from "@/components/try-again"; +import { Card } from "@/components/ui/card"; +import { useGweets } from "../hooks/use-gweets"; +import { InfiniteGweets } from "./infinite-gweets"; + +export const Gweets = () => { + const { + data: gweets, + isLoading, + isError, + isSuccess, + isFetchingNextPage, + fetchNextPage, + hasNextPage, + } = useGweets({}); + + if (isLoading) { + return <LoadingItem />; + } + + if (isError) { + return <TryAgain />; + } + + return ( + <Card className="w-full h-full mt-6 p-2 xl:p-4 "> + <InfiniteGweets + gweets={gweets} + isSuccess={isSuccess} + isFetchingNextPage={isFetchingNextPage} + fetchNextPage={fetchNextPage} + hasNextPage={hasNextPage} + /> + </Card> + ); +}; diff --git a/components/gweets/components/infinite-gweets.tsx b/components/gweets/components/infinite-gweets.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c86d6ae6bbc419a9993c70cee579f82d44573205 --- /dev/null +++ b/components/gweets/components/infinite-gweets.tsx @@ -0,0 +1,51 @@ +"use client" + +import LoadingItem from "@/components/loading-item"; +import { Separator } from "@/components/ui/separator"; +import { useEffect } from "react"; +import { useInView } from "react-intersection-observer"; +import { IInfiniteGweets } from "../types"; +import { Gweet } from "./gweet"; + +export const InfiniteGweets = ({ + gweets, + isSuccess, + isFetchingNextPage, + fetchNextPage, + hasNextPage, +}: { + gweets: IInfiniteGweets; + isSuccess: boolean | undefined; + isFetchingNextPage: boolean | undefined; + fetchNextPage: () => Promise<any> | void; + hasNextPage: boolean | undefined; +}) => { + const { ref, inView } = useInView(); + + useEffect(() => { + if (inView && hasNextPage) { + fetchNextPage(); + } + }, [inView, hasNextPage, fetchNextPage]); + + return ( + <div> + {isSuccess && + gweets?.pages?.map((page) => { + return page?.gweets?.map((gweet, index) => ( + <div + ref={index === page.gweets.length - 4 ? ref : undefined} + key={gweet.id} + > + <Gweet gweet={gweet} /> + <div className="px-6"> + <Separator className="my-3" /> + </div> + </div> + )); + })} + + {isFetchingNextPage && <LoadingItem />} + </div> + ); +}; \ No newline at end of file diff --git a/components/gweets/hooks/use-delete-gweet.ts b/components/gweets/hooks/use-delete-gweet.ts new file mode 100644 index 0000000000000000000000000000000000000000..9fbfabc50bf0c921a9b8da521b9ea08e8686db4b --- /dev/null +++ b/components/gweets/hooks/use-delete-gweet.ts @@ -0,0 +1,18 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; + +import { deleteGweet } from "../api/delete-gweet"; + +export const useDeleteGweet = () => { + const queryClient = useQueryClient(); + + return useMutation(({ gweetId }: { gweetId: string }) => { + return deleteGweet(gweetId); + }, { + onSuccess: () => { + queryClient.invalidateQueries(["gweets"]); + }, + onError: (error) => { + console.log(error); + }, + }); +}; \ No newline at end of file diff --git a/components/gweets/hooks/use-gweet.ts b/components/gweets/hooks/use-gweet.ts new file mode 100644 index 0000000000000000000000000000000000000000..6ff302edc3d3d493fcc371698d54b8084e9daa22 --- /dev/null +++ b/components/gweets/hooks/use-gweet.ts @@ -0,0 +1,20 @@ +import { useQuery, useQueryClient } from "@tanstack/react-query"; + +import getGweet from "../api/get-gweet"; +import { IGweet } from "../types"; + +export const useGweet = (id: string | undefined) => { + const queryClient = useQueryClient(); + return useQuery<IGweet>( + ["gweets", id], + async () => { + return getGweet(id); + }, + { + refetchOnWindowFocus: false, + onSuccess: (data) => { + queryClient.setQueryData(["gweets", id], data); + }, + }, + ); +}; diff --git a/components/gweets/hooks/use-gweets.ts b/components/gweets/hooks/use-gweets.ts new file mode 100644 index 0000000000000000000000000000000000000000..ea23e8eef2cb69261401728e5470cbac643c9de2 --- /dev/null +++ b/components/gweets/hooks/use-gweets.ts @@ -0,0 +1,33 @@ +import { useInfiniteQuery } from "@tanstack/react-query"; + +import { getGweets } from "../api/get-gweets"; + +export const useGweets = ({ + queryKey, + type, + id, +}: { + queryKey?: string[]; + type?: string; + id?: string; +}) => { + const data = useInfiniteQuery( + queryKey ?? ["gweets"], + ({ pageParam = "" }) => + getGweets({ + pageParam, + limit: 20, + type, + id, + }), + + { + getNextPageParam: (lastPage) => { + return lastPage?.nextId ?? false; + }, + refetchOnWindowFocus: false, + }, + ); + + return data; +}; diff --git a/components/gweets/hooks/use-like.ts b/components/gweets/hooks/use-like.ts new file mode 100644 index 0000000000000000000000000000000000000000..7f6366ceffb3c3838887887d0c76e8986e747cbe --- /dev/null +++ b/components/gweets/hooks/use-like.ts @@ -0,0 +1,30 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; + +import { toggleLike } from "../api/toggle-like"; + +export const useLike = ({ + gweetAuthorId, + sessionOwnerId, +}: { + gweetAuthorId: string | undefined; + sessionOwnerId: string | undefined; +}) => { + const queryClient = useQueryClient(); + + return useMutation( + ({ gweetId, userId }: { gweetId: string | undefined; userId: string }) => { + return toggleLike({ gweetId, userId }); + }, + { + onSuccess: () => { + queryClient.invalidateQueries(["gweets"]); + queryClient.invalidateQueries(["users", sessionOwnerId]); + if (sessionOwnerId !== gweetAuthorId) + queryClient.invalidateQueries(["users", gweetAuthorId]); + }, + onError: () => { + console.log("error"); + }, + }, + ); +}; diff --git a/components/gweets/hooks/use-regweet.ts b/components/gweets/hooks/use-regweet.ts new file mode 100644 index 0000000000000000000000000000000000000000..f46cb4dc7724e26150ed67b84476b3f506f244fb --- /dev/null +++ b/components/gweets/hooks/use-regweet.ts @@ -0,0 +1,26 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; + +import { handleRegweet } from "../api/handle-regweet"; + +export const useRegweet = () => { + const QueryClient = useQueryClient(); + + return useMutation( + ({ gweetId, userId }: { gweetId: string; userId: string }) => { + return handleRegweet(gweetId, userId); + }, + + { + onSuccess: () => { + QueryClient.invalidateQueries(["gweets"]); + QueryClient.invalidateQueries(["users"]); + }, + + onError: (error: any) => { + console.log(error); + }, + + onSettled: () => { }, + }, + ); +}; \ No newline at end of file diff --git a/components/gweets/types/index.ts b/components/gweets/types/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..29e8cd6bd0d7110bdef7cc06d3d15eb348b6c152 --- /dev/null +++ b/components/gweets/types/index.ts @@ -0,0 +1,35 @@ +import { Gweet, Like, Media, Regweet } from "@prisma/client"; + +import { IUser } from "@/components/profile/types"; + +export interface IFeed { + id: number; +} + +export interface IGweet extends Gweet { + author: IUser; + quotedGweet: IGweet; + likes: ILike[]; + media: IMedia[]; + regweets: IRegweet[]; + allQuotes: IGweet[]; + allComments: IGweet[]; +} + +export interface ILike extends Like { + user: IUser; + gweet: IGweet; +} + +export interface IMedia extends Media { + gweet: IGweet; +} + +export interface IRegweet extends Regweet { + user: IUser; +} + +export interface IInfiniteGweets { + pages: { gweets: IGweet[]; nextId?: string | undefined }[]; + pageParams: any; +} diff --git a/components/header.tsx b/components/header.tsx index cdf1c8c5ad8f903e57ed434e5d933c18b21b2512..03a591e38b7fac0073aa46258c8cad00e5bc61d1 100644 --- a/components/header.tsx +++ b/components/header.tsx @@ -7,7 +7,7 @@ import { GameUnityLogo } from "./logo" export function MainNav() { return ( <div className="flex gap-6 md:gap-10"> - <Link href="/" className="items-center space-x-2 flex"> + <Link href="/home" className="items-center space-x-2 flex"> <GameUnityLogo className="h-8 w-8" /> </Link> </div> diff --git a/components/icons.tsx b/components/icons.tsx index cdacdf511e61cfebad9cebf775031ff81d2908ab..35f194bd986048c9d630a78c0890321c6616d137 100644 --- a/components/icons.tsx +++ b/components/icons.tsx @@ -17,13 +17,16 @@ import { Home, Image, Laptop, + Link, Loader2, LucideProps, MessageCircle, Moon, + MoreHorizontal, MoreVertical, Pizza, Plus, + Repeat2, Settings, SunMedium, Trash, @@ -78,9 +81,12 @@ export const Icons: IconsType = { chevronLeft: ChevronLeft, // Back Login Arrow spinner: Loader2, // Loading Spinner github: Github, // Github Icon - close: X, - chevronRight: ChevronRight, - trash: Trash, + close: X, // Close Button + moreOptions: MoreHorizontal, // More Options Button + chevronRight: ChevronRight, // dropdown chevron + trash: Trash, // Delete Button + link: Link, // Link Button + regweet: Repeat2, // Regweet Button post: FileText, page: File, media: Image, diff --git a/components/loading-item.tsx b/components/loading-item.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ccb16e180585ef6aa087fe0e4c15cb5d899a68f6 --- /dev/null +++ b/components/loading-item.tsx @@ -0,0 +1,9 @@ +import { Icons } from "./icons" + +export default function LoadingItem() { + return ( + <div className="w-full m-6 flex items-center justify-center"> + <Icons.spinner className="h-6 w-6 animate-spin" /> + </div> + ) +} \ No newline at end of file diff --git a/components/site-loading.tsx b/components/loading-site.tsx similarity index 100% rename from components/site-loading.tsx rename to components/loading-site.tsx diff --git a/components/post-comment-button.tsx b/components/post-comment-button.tsx deleted file mode 100644 index 369528dff647d90a55c894c051ad0a9232fb8c78..0000000000000000000000000000000000000000 --- a/components/post-comment-button.tsx +++ /dev/null @@ -1,17 +0,0 @@ -// import Link from "next/link"; -// import { Icons } from "./icons"; -// import { Button } from "./ui/button"; - -// export default function CommentButton(props: { data: any }) { -// const postid = props.data.id -// const replyCount = props.data.Comment.length - -// return ( -// <Link href={`/home/${postid}`}> -// <Button type="submit" variant="ghost" size="lg" className="px-6 py-3" > -// <span className="pr-1">{replyCount}</span> -// <Icons.messagecircle className="h-5 w-5" /> -// </Button> -// </Link> -// ) -// } \ No newline at end of file diff --git a/components/post-gweets.tsx b/components/post-gweets.tsx deleted file mode 100644 index 8dd8738d6e465aec9192f592ae066785e77ce735..0000000000000000000000000000000000000000 --- a/components/post-gweets.tsx +++ /dev/null @@ -1,178 +0,0 @@ -"use client" - -import { zodResolver } from "@hookform/resolvers/zod" -import { useForm } from "react-hook-form" -import * as z from "zod" - -import { Button } from "@/components/ui/button" -import { - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form" - -import { Textarea } from "@/components/ui/textarea" -import { toast } from "@/components/ui/use-toast" - -import { useSession } from "next-auth/react" -import { FormEvent, Fragment, useEffect, useState } from "react" -import { Icons } from "./icons" -import PostItem from "./post-item" -import { Card } from "./ui/card" -import { Separator } from "./ui/separator" -import { Skeleton } from "./ui/skeleton" - -const FormSchema = z.object({ - gweet: z - .string() - .min(1, { message: "Come on post something...", }) - .max(1000, { message: "Gweets cannot be more that 1000 characters.", }), -}) - -export function PostGweets() { - const [isGweetLoading, setIsGweetLoading] = useState<boolean>(false); - const [isLoading, setIsLoading] = useState<boolean>(false); - const [messages, setMessages] = useState<any[]>([]); - - const user = useSession(); - - useEffect(() => { - fetchMessages(); - }, []); - - const form = useForm<z.infer<typeof FormSchema>>({ - resolver: zodResolver(FormSchema), - }) - - async function onGweet(data: z.infer<typeof FormSchema>) { - setIsGweetLoading(true); - - await fetch('/api/messages', { - method: 'POST', - body: JSON.stringify(data), - next: { tags: ['collection'] } - }) - - toast({ - title: "Your gweet is being processed...", - description: ( - <pre className="mt-2 w-[340px] rounded-md bg-slate-600 p-4"> - <code className="text-white">{JSON.stringify(data, null, 2)}</code> - </pre> - ), - }) - - setIsGweetLoading(false); - form.setValue('gweet', ''); - await fetchMessages(); - } - - async function fetchMessages() { - setIsLoading(true); - - try { - const res = await fetch(`/api/messages`); - - if (!res.ok) { - throw new Error("Failed to fetch messages"); - } - - const data = await res.json(); - - setMessages(data); - } catch (error) { - return toast({ - variant: "destructive", - title: "Uh oh! Something went wrong.", - description: "Failed to fetch messages. Please try again.", - }); - } - - setIsLoading(false); - } - - async function onSubmit(event: FormEvent<HTMLFormElement>) { - event.preventDefault(); - await fetchMessages(); - } - - return ( - <div> - <Form {...form}> - <form onSubmit={form.handleSubmit(onGweet)} className="space-y-6"> - <FormField - control={form.control} - name="gweet" - render={({ field }) => ( - <FormItem> - <FormLabel>Gweet</FormLabel> - <FormControl> - <Textarea - placeholder="What's on your mind?" - className="resize-none" - disabled={isGweetLoading || !user.data?.user} - {...field} - /> - </FormControl> - <FormDescription> - Your gweets will be public, and everyone can see them. - </FormDescription> - <FormMessage /> - </FormItem> - )} - /> - <Button type="submit" disabled={isGweetLoading || !user.data?.user}>Submit</Button> - </form> - </Form> - <Card className="w-full h-full overflow-hidden p-6 md:p-12 mt-12"> - <form onSubmit={onSubmit}> - <Button disabled={isLoading} type="submit" className="w-full mb-6"> - {isLoading && ( - <Icons.spinner className="mr-2 h-4 w-4 animate-spin" /> - )} - Load More - </Button> - - {messages.length > 0 ? ( - messages.map((message: any) => ( - <Fragment key={message.id}> - <PostItem msg={message} /> - <Separator className="mt-3 mb-6" /> - </Fragment> - )) - ) : ( - <> - {Array.from({ length: 4 }, (_, i) => i + 1).map((i) => ( - <> - <div className="flex"> - <Skeleton className="h-10 w-10 rounded-full" /> - <div className="ml-4 flex flex-col flex-grow"> - <div> - <div className="flex items-center"> - <div className="mx-auto w-full space-y-6"> - <Skeleton className="h-[30px] w-1/4" /> - <Skeleton className="h-[20px] w-2/3" /> - <Skeleton className="h-[20px] w-full" /> - </div> - </div> - </div> - <div className="flex justify-end space-x-3 mt-3" > - <Skeleton key={i} className="h-10 w-20 rounded-full" /> - <Skeleton key={i} className="h-10 w-20 rounded-full" /> - </div> - </div> - </div> - <Separator className="mt-3 mb-6" /> - </> - ))} - </> - )} - </form> - </Card> - </div> - ) -} \ No newline at end of file diff --git a/components/post-item.tsx b/components/post-item.tsx deleted file mode 100644 index bd2b5482c5e18618a69652a345db181085faaaf4..0000000000000000000000000000000000000000 --- a/components/post-item.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { formatTimeElapsed } from "@/lib/utils"; -import { IPost } from "@/types/prisma-item"; -// import CommentButton from "./post-comment-button"; -// import LikeButton from "./post-like-button"; -import { UserAvatar } from "./user-avatar"; - -export default function PostItem({ msg }: { msg: IPost }) { - return ( - <div className="flex"> - <UserAvatar - user={{ name: msg.user.username || null, image: msg.user.image || null }} - className="h-10 w-10" - /> - <div className="ml-4 flex flex-col flex-grow"> - <div> - <div className="flex items-center"> - <h1 className="font-bold mr-2">{msg.user.name}</h1> - <h1 className="text-sky-500 text-sm"> - @{msg.user.username} - </h1> - <h1 className="text-gray-500 text-sm ml-auto"> - {formatTimeElapsed(msg.createdAt)} - </h1> - </div> - <h1>{msg.content}</h1> - </div> - {/* <div className="flex justify-end" > - <LikeButton data={msg} /> - <CommentButton data={msg} /> - </div> */} - </div> - </div> - ) -} \ No newline at end of file diff --git a/components/post-like-button.tsx b/components/post-like-button.tsx deleted file mode 100644 index 4a8d923bd28d8278d4647c392e44fa3dcc38705d..0000000000000000000000000000000000000000 --- a/components/post-like-button.tsx +++ /dev/null @@ -1,75 +0,0 @@ -// "use client" - -// import { Prisma } from "@prisma/client"; -// import { useRouter } from "next/navigation"; -// import { startTransition } from "react"; -// import { Icons } from "./icons"; -// import { Button } from "./ui/button"; - -// type likeType = Prisma.LikeUncheckedCreateInput -// type postType = Prisma.PostUncheckedCreateInput -// type commentType = Prisma.CommentUncheckedCreateInput -// // type commentWithLikes = Prisma.CommentGetPayload<typeof commentWithPosts> - -// export default function LikeButton(props: { data: any }) { -// const router = useRouter(); -// const likeCount = props.data.Like.length -// // const likeCount = countLikes(likeArray, props.data); - -// async function postLike(e: any) { -// e.preventDefault() -// const postLikeData = props.data; -// const likeData = {} as likeType - -// if (postLikeData.postId == undefined) { -// likeData.postId = postLikeData.id! -// } else { -// likeData.postId = postLikeData.postId -// likeData.commentId = postLikeData.id -// } -// likeData.userId = postLikeData.userId - -// const response = await fetch('/api/likes', { -// method: 'PUT', -// body: JSON.stringify(likeData) -// }) - -// startTransition(() => { -// // Refresh the current route and fetch new data from the server without -// // losing client-side browser or React state. -// router.refresh(); -// }); -// return await response.json() -// } - -// return ( -// <form onSubmit={postLike}> -// <Button type="submit" variant="ghost" size="lg" className="px-6 py-3" > -// <span className="pr-1">{likeCount}</span> -// <Icons.heart className="h-5 w-5" /> -// </Button> -// </form> -// ) -// } - -// function countLikes(likeArray: any, msg: any): number { -// let likeCount = 0; - -// if (msg.postId == undefined) { -// likeArray.forEach(function (like: any) { -// if (like.postId == undefined) { -// likeCount++; -// } -// }) -// } else { -// likeArray.forEach(function (like: any) { -// if (like.postId != undefined) { -// likeCount++; -// } -// }) -// } - - -// return likeCount; -// } - diff --git a/components/profile/types/index.ts b/components/profile/types/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..0e493c971e6051ff1dfdf4d6a895268dbc8b00cf --- /dev/null +++ b/components/profile/types/index.ts @@ -0,0 +1,35 @@ +import { Follows, Like, User } from "@prisma/client"; + +import { IGweet } from "@/components/gweets/types"; + +export interface IUser extends User { + gweets: IGweet[]; + followers: IFollow[]; + following: IFollow[]; + likes: ILike[]; + _count?: { + followers?: number; + following?: number; + }; +} + +export interface IProfile { + name: string; + bio: string | undefined; + banner: { + url: string | undefined; + }; + avatar: { + url: string | undefined; + }; +} + +export interface IFollow extends Follows { + follower: IUser; + following: IUser; +} + +export interface ILike extends Like { + user: IUser; + gweet: IGweet; +} diff --git a/components/trends/api/get-hashtags.ts b/components/trends/api/get-hashtags.ts new file mode 100644 index 0000000000000000000000000000000000000000..ddf26448905ea6bf16f0d56f0fc59e6ca8036e38 --- /dev/null +++ b/components/trends/api/get-hashtags.ts @@ -0,0 +1,8 @@ +export const getHashtags = async () => { + try { + const data = await fetch(`/api/hashtags`).then((result) => result.json()); + return data; + } catch (error: any) { + return error.response.data; + } +}; diff --git a/components/trends/api/post-hashtags.ts b/components/trends/api/post-hashtags.ts new file mode 100644 index 0000000000000000000000000000000000000000..9a281b980b4b52d6959ea437e15b64507acf320d --- /dev/null +++ b/components/trends/api/post-hashtags.ts @@ -0,0 +1,12 @@ +export const postHashtags = async (hashtags: string[]) => { + try { + const data = await fetch(`/api/hashtags`, { + method: 'POST', + body: JSON.stringify(hashtags) + }).then((result) => result.json()) + + return data; + } catch (error: any) { + return error.response.data; + } +}; diff --git a/components/trends/api/retrieve-hashtags-from-gweet.ts b/components/trends/api/retrieve-hashtags-from-gweet.ts new file mode 100644 index 0000000000000000000000000000000000000000..f73af883c704536948e65f4acc3449c2a7523886 --- /dev/null +++ b/components/trends/api/retrieve-hashtags-from-gweet.ts @@ -0,0 +1,4 @@ +export const retrieveHashtagsFromGweet = (text: string): string[] | null => { + const hashtags = text.match(/#\w+/gi); + return hashtags ? hashtags.map((hashtag) => hashtag.slice(1)) : null; +}; diff --git a/components/trends/components/trend.tsx b/components/trends/components/trend.tsx new file mode 100644 index 0000000000000000000000000000000000000000..30d35673e6c7bb1ea7a69d0b903c3497b57720cc --- /dev/null +++ b/components/trends/components/trend.tsx @@ -0,0 +1,26 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { iTrendProps } from "../types"; + +export const Trend = ({ ranking = 1, title, gweets = 1 }: iTrendProps) => { + const router = useRouter(); + + return ( + <div + onClick={() => { router.push(`/search?query=${title.toLowerCase()}`); }} + className="flex justify-between items-center p-1 cursor-pointer hover:bg-trends-hover active:bg-trends-active"> + <div className="p-2"> + <div className="flex items-center gap-2 text-tertiary"> + <span>{ranking}</span> + <span className="w-2 h-2 bg-tertiary rounded-full"></span> + <span>Trending</span> + </div> + <div className="text-secondary font-medium">{title}</div> + <div className="text-tertiary"> + {gweets} {gweets === 1 ? "gweet" : "gweets"} + </div> + </div> + </div> + ); +}; \ No newline at end of file diff --git a/components/trends/components/trends.tsx b/components/trends/components/trends.tsx new file mode 100644 index 0000000000000000000000000000000000000000..de4f29d3fc28aa39d45487211b62c8d9314f5f13 --- /dev/null +++ b/components/trends/components/trends.tsx @@ -0,0 +1,46 @@ +import Link from "next/link"; + +import { Icons } from "@/components/icons"; +import { TryAgain } from "@/components/try-again"; +import { useHashtags } from "../hooks/use-hashtags"; +import { Trend } from "./trend"; + +export const Trends = ({ title = "Trends" }: { title?: string }) => { + const { data: hashtags, isLoading, isError, isSuccess } = useHashtags(); + + if (hashtags && hashtags?.length <= 0) return null; + + return ( + <div className=""> + {isLoading ? ( + <div className=""> + <Icons.spinner className="h-4 w-4 animate-spin" /> + </div> + ) : isError ? ( + <div className=""> + <TryAgain /> + </div> + ) : ( + <> + <div className=""> + <h1 className="">{title}</h1> + {isSuccess && + hashtags?.map((hashtag, index) => { + return ( + <Trend + key={hashtag.id} + ranking={index + 1} + title={hashtag.text} + gweets={hashtag.score} + /> + ); + })} + </div> + <button className=""> + <Link href={`trends`}>Show more</Link> + </button> + </> + )} + </div> + ); +}; diff --git a/components/trends/hooks/use-hashtags.ts b/components/trends/hooks/use-hashtags.ts new file mode 100644 index 0000000000000000000000000000000000000000..fff14be116f4cb886e7d74f7898b0fb60b2e9b32 --- /dev/null +++ b/components/trends/hooks/use-hashtags.ts @@ -0,0 +1,20 @@ +import { useQuery, useQueryClient } from "@tanstack/react-query"; + +import { getHashtags } from "../api/get-hashtags"; +import { IHashtag } from "../types"; + +export const useHashtags = () => { + const queryClient = useQueryClient(); + return useQuery<IHashtag[]>( + ["hashtags"], + async () => { + return getHashtags(); + }, + { + refetchOnWindowFocus: false, + onSuccess: (data) => { + queryClient.setQueryData(["hashtags"], data); + }, + }, + ); +}; diff --git a/components/trends/index.ts b/components/trends/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..07693e1419793b0241452c49c20c3afb47659a56 --- /dev/null +++ b/components/trends/index.ts @@ -0,0 +1,7 @@ +export * from "./api/get-hashtags"; +export * from "./api/post-hashtags"; +export * from "./api/retrieve-hashtags-from-gweet"; +export * from "./components/trend"; +export * from "./components/trends"; +export * from "./hooks/use-hashtags"; +export * from "./types"; diff --git a/components/trends/types/index.ts b/components/trends/types/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..ddfc80511ad78e07069988365b6274b973723637 --- /dev/null +++ b/components/trends/types/index.ts @@ -0,0 +1,11 @@ +export interface IHashtag { + id: string; + text: string; + score: number; +} + +export interface iTrendProps { + ranking: number; + title: string; + gweets: number; +} diff --git a/components/try-again.tsx b/components/try-again.tsx new file mode 100644 index 0000000000000000000000000000000000000000..00cea11d78df89cbf0fc09691f61a31dd6239a01 --- /dev/null +++ b/components/try-again.tsx @@ -0,0 +1,14 @@ +import { useRouter } from "next/navigation"; + +export const TryAgain = () => { + const router = useRouter(); + + return ( + <div className="flex flex-col items-center gap-1.2 p-4"> + <h2 className="text-tertiary text-center">Something went wrong. Try reloading.</h2> + <button onClick={() => router.refresh} className="px-4 py-2 rounded-full bg-primary text-light font-medium flex items-center gap-1"> + <span>Retry</span> + </button> + </div> + ); +}; \ No newline at end of file diff --git a/components/ui/button.tsx b/components/ui/button.tsx index 7419a7f5404578220055d1aaa678470e77c72349..6c84d7da14bc64ba4160bdd447e650ac57f486eb 100644 --- a/components/ui/button.tsx +++ b/components/ui/button.tsx @@ -17,12 +17,15 @@ const buttonVariants = cva( secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", + hover: "hover:bg-accent hover:text-accent-foreground hover:shadow-md hover:ring-2 hover:ring-ring hover:ring-offset-2", link: "underline-offset-4 hover:underline text-primary", }, size: { default: "h-10 py-2 px-4", sm: "h-9 px-3 rounded-md", lg: "h-11 px-8 rounded-full", + icon: "h-10 w-10 rounded-full", + option: "h-8 w-8 rounded-full", }, }, defaultVariants: { diff --git a/components/ui/dialog.tsx b/components/ui/dialog.tsx new file mode 100644 index 0000000000000000000000000000000000000000..7ac0d44b75d2415c60b4ed9b53ebebaf9c2cd60d --- /dev/null +++ b/components/ui/dialog.tsx @@ -0,0 +1,128 @@ +"use client" + +import * as React from "react" +import * as DialogPrimitive from "@radix-ui/react-dialog" +import { X } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Dialog = DialogPrimitive.Root + +const DialogTrigger = DialogPrimitive.Trigger + +const DialogPortal = ({ + className, + children, + ...props +}: DialogPrimitive.DialogPortalProps) => ( + <DialogPrimitive.Portal className={cn(className)} {...props}> + <div className="fixed inset-0 z-50 flex items-start justify-center sm:items-center"> + {children} + </div> + </DialogPrimitive.Portal> +) +DialogPortal.displayName = DialogPrimitive.Portal.displayName + +const DialogOverlay = React.forwardRef< + React.ElementRef<typeof DialogPrimitive.Overlay>, + React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay> +>(({ className, ...props }, ref) => ( + <DialogPrimitive.Overlay + ref={ref} + className={cn( + "fixed inset-0 z-50 bg-background/80 backdrop-blur-sm transition-all duration-100 data-[state=closed]:animate-out data-[state=closed]:fade-out data-[state=open]:fade-in", + className + )} + {...props} + /> +)) +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName + +const DialogContent = React.forwardRef< + React.ElementRef<typeof DialogPrimitive.Content>, + React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> +>(({ className, children, ...props }, ref) => ( + <DialogPortal> + <DialogOverlay /> + <DialogPrimitive.Content + ref={ref} + className={cn( + "fixed z-50 grid w-full gap-4 rounded-b-lg border bg-background p-6 shadow-lg animate-in data-[state=open]:fade-in-90 data-[state=open]:slide-in-from-bottom-10 sm:max-w-lg sm:rounded-lg sm:zoom-in-90 data-[state=open]:sm:slide-in-from-bottom-0", + className + )} + {...props} + > + {children} + <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"> + <X className="h-4 w-4" /> + <span className="sr-only">Close</span> + </DialogPrimitive.Close> + </DialogPrimitive.Content> + </DialogPortal> +)) +DialogContent.displayName = DialogPrimitive.Content.displayName + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes<HTMLDivElement>) => ( + <div + className={cn( + "flex flex-col space-y-1.5 text-center sm:text-left", + className + )} + {...props} + /> +) +DialogHeader.displayName = "DialogHeader" + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes<HTMLDivElement>) => ( + <div + className={cn( + "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", + className + )} + {...props} + /> +) +DialogFooter.displayName = "DialogFooter" + +const DialogTitle = React.forwardRef< + React.ElementRef<typeof DialogPrimitive.Title>, + React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title> +>(({ className, ...props }, ref) => ( + <DialogPrimitive.Title + ref={ref} + className={cn( + "text-lg font-semibold leading-none tracking-tight", + className + )} + {...props} + /> +)) +DialogTitle.displayName = DialogPrimitive.Title.displayName + +const DialogDescription = React.forwardRef< + React.ElementRef<typeof DialogPrimitive.Description>, + React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description> +>(({ className, ...props }, ref) => ( + <DialogPrimitive.Description + ref={ref} + className={cn("text-sm text-muted-foreground", className)} + {...props} + /> +)) +DialogDescription.displayName = DialogPrimitive.Description.displayName + +export { + Dialog, + DialogTrigger, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +} diff --git a/components/ui/hover-card.tsx b/components/ui/hover-card.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8abff5acb3e1caa0e38e01a18042c8ef7bfcf6f2 --- /dev/null +++ b/components/ui/hover-card.tsx @@ -0,0 +1,29 @@ +"use client" + +import * as React from "react" +import * as HoverCardPrimitive from "@radix-ui/react-hover-card" + +import { cn } from "@/lib/utils" + +const HoverCard = HoverCardPrimitive.Root + +const HoverCardTrigger = HoverCardPrimitive.Trigger + +const HoverCardContent = React.forwardRef< + React.ElementRef<typeof HoverCardPrimitive.Content>, + React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content> +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + <HoverCardPrimitive.Content + ref={ref} + align={align} + sideOffset={sideOffset} + className={cn( + "z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none animate-in zoom-in-90", + className + )} + {...props} + /> +)) +HoverCardContent.displayName = HoverCardPrimitive.Content.displayName + +export { HoverCard, HoverCardTrigger, HoverCardContent } diff --git a/components/ui/sheet.tsx b/components/ui/sheet.tsx new file mode 100644 index 0000000000000000000000000000000000000000..98cdd40d2a04c51a5fb80501bdd359eb6f3bc91d --- /dev/null +++ b/components/ui/sheet.tsx @@ -0,0 +1,144 @@ +"use client" + +import * as React from "react" +import * as SheetPrimitive from "@radix-ui/react-dialog" +import { cva, type VariantProps } from "class-variance-authority" +import { X } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Sheet = SheetPrimitive.Root + +const SheetTrigger = SheetPrimitive.Trigger + +const SheetClose = SheetPrimitive.Close + +const SheetPortal = ({ + className, + ...props +}: SheetPrimitive.DialogPortalProps) => ( + <SheetPrimitive.Portal className={cn(className)} {...props} /> +) +SheetPortal.displayName = SheetPrimitive.Portal.displayName + +const SheetOverlay = React.forwardRef< + React.ElementRef<typeof SheetPrimitive.Overlay>, + React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay> +>(({ className, ...props }, ref) => ( + <SheetPrimitive.Overlay + className={cn( + "fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", + className + )} + {...props} + ref={ref} + /> +)) +SheetOverlay.displayName = SheetPrimitive.Overlay.displayName + +const sheetVariants = cva( + "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500", + { + variants: { + side: { + top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top", + bottom: + "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", + left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm", + right: + "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm", + }, + }, + defaultVariants: { + side: "right", + }, + } +) + +interface SheetContentProps + extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>, + VariantProps<typeof sheetVariants> {} + +const SheetContent = React.forwardRef< + React.ElementRef<typeof SheetPrimitive.Content>, + SheetContentProps +>(({ side = "right", className, children, ...props }, ref) => ( + <SheetPortal> + <SheetOverlay /> + <SheetPrimitive.Content + ref={ref} + className={cn(sheetVariants({ side }), className)} + {...props} + > + {children} + <SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary"> + <X className="h-4 w-4" /> + <span className="sr-only">Close</span> + </SheetPrimitive.Close> + </SheetPrimitive.Content> + </SheetPortal> +)) +SheetContent.displayName = SheetPrimitive.Content.displayName + +const SheetHeader = ({ + className, + ...props +}: React.HTMLAttributes<HTMLDivElement>) => ( + <div + className={cn( + "flex flex-col space-y-2 text-center sm:text-left", + className + )} + {...props} + /> +) +SheetHeader.displayName = "SheetHeader" + +const SheetFooter = ({ + className, + ...props +}: React.HTMLAttributes<HTMLDivElement>) => ( + <div + className={cn( + "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", + className + )} + {...props} + /> +) +SheetFooter.displayName = "SheetFooter" + +const SheetTitle = React.forwardRef< + React.ElementRef<typeof SheetPrimitive.Title>, + React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title> +>(({ className, ...props }, ref) => ( + <SheetPrimitive.Title + ref={ref} + className={cn("text-lg font-semibold text-foreground", className)} + {...props} + /> +)) +SheetTitle.displayName = SheetPrimitive.Title.displayName + +const SheetDescription = React.forwardRef< + React.ElementRef<typeof SheetPrimitive.Description>, + React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description> +>(({ className, ...props }, ref) => ( + <SheetPrimitive.Description + ref={ref} + className={cn("text-sm text-muted-foreground", className)} + {...props} + /> +)) +SheetDescription.displayName = SheetPrimitive.Description.displayName + +export { + Sheet, + SheetTrigger, + SheetClose, + SheetContent, + SheetHeader, + SheetFooter, + SheetTitle, + SheetDescription, +} diff --git a/components/ui/textarea.tsx b/components/ui/textarea.tsx index 4c5d6b5d3828cad6d423fb0140081ee0454ab7dc..dc548f3150601861c5d4b78428cb939abd06bc4c 100644 --- a/components/ui/textarea.tsx +++ b/components/ui/textarea.tsx @@ -3,14 +3,14 @@ import * as React from "react" import { cn } from "@/lib/utils" export interface TextareaProps - extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {} + extends React.TextareaHTMLAttributes<HTMLTextAreaElement> { } const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>( ({ className, ...props }, ref) => { return ( <textarea className={cn( - "flex min-h-[80px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", + "flex min-h-[80px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", className )} ref={ref} diff --git a/components/ui/toast.tsx b/components/ui/toast.tsx index f8205889e631bae2ddc6941056bc5f39c13ccb8b..32ffed0b1cbe08afdef2b2c8f7923590c7275db4 100644 --- a/components/ui/toast.tsx +++ b/components/ui/toast.tsx @@ -1,7 +1,7 @@ -import * as React from "react" import * as ToastPrimitives from "@radix-ui/react-toast" import { cva, type VariantProps } from "class-variance-authority" import { X } from "lucide-react" +import * as React from "react" import { cn } from "@/lib/utils" @@ -41,7 +41,7 @@ const toastVariants = cva( const Toast = React.forwardRef< React.ElementRef<typeof ToastPrimitives.Root>, React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> & - VariantProps<typeof toastVariants> + VariantProps<typeof toastVariants> >(({ className, variant, ...props }, ref) => { return ( <ToastPrimitives.Root @@ -115,13 +115,5 @@ type ToastProps = React.ComponentPropsWithoutRef<typeof Toast> type ToastActionElement = React.ReactElement<typeof ToastAction> export { - type ToastProps, - type ToastActionElement, - ToastProvider, - ToastViewport, - Toast, - ToastTitle, - ToastDescription, - ToastClose, - ToastAction, + Toast, ToastAction, ToastClose, ToastDescription, ToastProvider, ToastTitle, ToastViewport, type ToastActionElement, type ToastProps } diff --git a/components/ui/toaster.tsx b/components/ui/toaster.tsx index ac9370c3ca8b43935a190b0cc2df6fb1823a570a..4414b1cbbb7e44feafe6fb205bb61c9278524a37 100644 --- a/components/ui/toaster.tsx +++ b/components/ui/toaster.tsx @@ -1,12 +1,12 @@ "use client" import { - Toast, - ToastClose, - ToastDescription, - ToastProvider, - ToastTitle, - ToastViewport, + Toast, + ToastClose, + ToastDescription, + ToastProvider, + ToastTitle, + ToastViewport, } from "@/components/ui/toast" import { useToast } from "@/components/ui/use-toast" diff --git a/components/user-avatar.tsx b/components/user-avatar.tsx index c81c4c396de1664395d776e179c5fa37b0e1805b..a343aaed2e926761e81ebca05ce7d9f183c86fdb 100644 --- a/components/user-avatar.tsx +++ b/components/user-avatar.tsx @@ -5,7 +5,7 @@ import { Icons } from "@/components/icons" import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" interface UserAvatarProps extends AvatarProps { - user: Pick<User, "image" | "name"> + user: Pick<User, "image" | "username"> } export function UserAvatar({ user, ...props }: UserAvatarProps) { @@ -15,7 +15,7 @@ export function UserAvatar({ user, ...props }: UserAvatarProps) { <AvatarImage alt="Picture" src={user.image} /> ) : ( <AvatarFallback> - <span className="sr-only">{user.name}</span> + <span className="sr-only">{user.username}</span> <Icons.user className="h-3/5 w-3/5" /> </AvatarFallback> )} diff --git a/components/user-item.tsx b/components/user-item.tsx deleted file mode 100644 index 64c729dfc82eeec37114594519bd6117190d9081..0000000000000000000000000000000000000000 --- a/components/user-item.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import Image from "next/image"; -import Link from "next/link"; -import FollowButton from "./following-button"; - -// this is a single user helper-component, only for design purposes -export default function FollowUser({ id, followId, username, image }: { id: number, followId: number, username: string, image: { url: string } }) { - return ( - <div> - <Link href={`/user/${id}`}> - <div className=""> - <Image - src={image.url} - alt={username} - width={50} - height={50} - priority={true} /> - </div> - <p>{username}</p> - <FollowButton userId={id} followerId={followId} /> - </Link> - </div> - ) -} \ No newline at end of file diff --git a/components/user-nav.tsx b/components/user-nav.tsx index a6e1c0aadf35e6ae863669563cc8180847568336..df7dda55369b7b072fa254bcb4fbcd451fd7d799 100644 --- a/components/user-nav.tsx +++ b/components/user-nav.tsx @@ -23,7 +23,7 @@ export function UserAccountNav({ user }: UserAccountNavProps) { <DropdownMenu> <DropdownMenuTrigger className="rounded-full"> <UserAvatar - user={{ name: user.name || null, image: user.image || null }} + user={{ username: user.username ? user.username : "", image: user.image || null }} className="h-8 w-8" /> </DropdownMenuTrigger> diff --git a/env.mjs b/env.mjs index 0154d5d3c4256505541cf0c7bb370f29cdf98395..f2ba50c268786a06d88998fcb2749adc6c3ec88c 100644 --- a/env.mjs +++ b/env.mjs @@ -9,6 +9,8 @@ export const env = createEnv({ NEXTAUTH_URL: z.string().url().optional(), NEXTAUTH_SECRET: z.string().min(1), TWITCH_CLIENT_ID: z.string().min(1), + UPLOADTHING_SECRET: z.string().min(1), + UPLOADTHING_APP_ID: z.string().min(1), TWITCH_CLIENT_SECRET: z.string().min(1), TWITCH_AUTH_BASE_URL: z.string().url().optional(), IGDB_BASE_URL: z.string().url().optional(), @@ -24,6 +26,8 @@ export const env = createEnv({ NEXTAUTH_URL: process.env.NEXTAUTH_URL, NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET, NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL, + UPLOADTHING_SECRET: process.env.UPLOADTHING_SECRET, + UPLOADTHING_APP_ID: process.env.UPLOADTHING_APP_ID, TWITCH_CLIENT_ID: process.env.TWITCH_CLIENT_ID, TWITCH_CLIENT_SECRET: process.env.TWITCH_CLIENT_SECRET, TWITCH_AUTH_BASE_URL: process.env.TWITCH_AUTH_BASE_URL, diff --git a/components/react-query/getQueryClient.ts b/lib/react-query/getQueryClient.ts similarity index 100% rename from components/react-query/getQueryClient.ts rename to lib/react-query/getQueryClient.ts diff --git a/components/react-query/hydrate.client.tsx b/lib/react-query/hydrate.client.tsx similarity index 100% rename from components/react-query/hydrate.client.tsx rename to lib/react-query/hydrate.client.tsx diff --git a/components/react-query/provider.tsx b/lib/react-query/provider.tsx similarity index 100% rename from components/react-query/provider.tsx rename to lib/react-query/provider.tsx diff --git a/lib/uploadthing.ts b/lib/uploadthing.ts new file mode 100644 index 0000000000000000000000000000000000000000..24fa466446c0345b00345a3fd095b4c9e0b605e7 --- /dev/null +++ b/lib/uploadthing.ts @@ -0,0 +1,10 @@ +import { generateComponents } from "@uploadthing/react"; + +import { generateReactHelpers } from '@uploadthing/react/hooks'; + +import type { OurFileRouter } from "@/app/api/uploadthing/core"; + +export const { UploadButton, UploadDropzone, Uploader } = + generateComponents<OurFileRouter>(); + +export const { uploadFiles } = generateReactHelpers<OurFileRouter>() \ No newline at end of file diff --git a/lib/utils.ts b/lib/utils.ts index e467e9a31a883d5cc35e047e8c7a8c4e6b3237bc..90a0b61f35b8703b73cea6dbea05a0a58966fa2e 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -1,5 +1,6 @@ import { env } from "@/env.mjs" import { ClassValue, clsx } from "clsx" +import dayjs from "dayjs" import { twMerge } from "tailwind-merge" // tailwindcss classnames generator from shadcn @@ -27,16 +28,17 @@ export function formatDate(data: number) { }) } +// formats the time elapsed since creation export function formatTimeElapsed(createdAt: Date) { - const now = new Date(); - const timeDiff = Math.abs(now.getTime() - new Date(createdAt).getTime()); // Difference in milliseconds + const now = dayjs(); + const timeDiff = Math.abs(now.diff(dayjs(createdAt))); // Difference in milliseconds const seconds = Math.floor(timeDiff / 1000); // Convert to seconds const minutes = Math.floor(seconds / 60); // Convert to minutes const hours = Math.floor(minutes / 60); // Convert to hours const days = Math.floor(hours / 24); // Convert to days if (days > 0) { - return new Date(createdAt).toLocaleDateString(); // Show the date if days have passed + return dayjs(createdAt).format('L'); // Show the date if days have passed } else if (hours > 0) { return hours + 'h'; // Show hours if hours have passed } else if (minutes > 0) { @@ -45,3 +47,12 @@ export function formatTimeElapsed(createdAt: Date) { return seconds + 's'; // Show seconds if seconds have passed } } + +// gets the current url for server or client +const IS_SERVER = typeof window === "undefined"; +export default function getURL(path: string) { + const baseURL = IS_SERVER + ? env.NEXT_PUBLIC_APP_URL! + : window.location.origin; + return new URL(path, baseURL).toString(); +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 93e0e4d4942afdc09414c1758a32ccee91dd551c..cc3d3ed872bbd559a04d3c3b3b82ba4b6e87e4ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,32 +10,39 @@ "dependencies": { "@auth/prisma-adapter": "^1.0.0", "@hookform/resolvers": "^3.1.1", - "@prisma/client": "^4.15.0", + "@prisma/client": "^4.16.1", "@radix-ui/react-aspect-ratio": "^1.0.3", "@radix-ui/react-avatar": "^1.0.3", + "@radix-ui/react-dialog": "^1.0.4", "@radix-ui/react-dropdown-menu": "^2.0.5", + "@radix-ui/react-hover-card": "^1.0.6", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-scroll-area": "^1.0.4", "@radix-ui/react-select": "^1.2.2", "@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-toast": "^1.1.4", - "@t3-oss/env-nextjs": "^0.4.1", - "@tanstack/react-query": "^4.29.14", + "@t3-oss/env-nextjs": "^0.6.0", + "@tanstack/react-query": "^4.29.19", + "@uploadthing/react": "^5.1.0", "bcrypt": "^5.1.0", - "class-variance-authority": "^0.6.0", + "class-variance-authority": "^0.6.1", "clsx": "^1.2.1", - "lucide-react": "^0.246.0", - "next": "^13.4.6", + "dayjs": "^1.11.8", + "lucide-react": "^0.256.0", + "next": "^13.4.7", "next-auth": "^4.22.1", "next-themes": "^0.2.1", "normalize-diacritics": "^4.0.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-hook-form": "^7.45.0", + "react-dropzone": "^14.2.3", + "react-hook-form": "^7.45.1", "react-infinite-scroll-component": "^6.1.0", + "react-intersection-observer": "^9.5.2", "tailwind-merge": "^1.13.2", "tailwindcss-animate": "^1.0.6", + "uploadthing": "^5.1.0", "zod": "^3.21.4" }, "devDependencies": { @@ -44,18 +51,27 @@ "@testing-library/react": "^14.0.0", "@types/bcrypt": "^5.0.0", "@types/jest": "^29.5.2", - "@types/node": "^20.3.1", - "@types/react": "^18.2.13", + "@types/node": "^20.3.2", + "@types/react": "^18.2.14", "@types/react-dom": "^18.2.6", "autoprefixer": "10.4.14", "eslint": "^8.43.0", - "eslint-config-next": "^13.4.6", + "eslint-config-next": "^13.4.7", "jest": "^29.5.0", "jest-environment-jsdom": "^29.5.0", "postcss": "8.4.24", - "prisma": "^4.15.0", + "prisma": "^4.16.1", "tailwindcss": "3.3.2", - "typescript": "^5.1.3" + "typescript": "^5.1.6" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" } }, "node_modules/@adobe/css-tools": { @@ -109,26 +125,6 @@ } } }, - "node_modules/@auth/core/node_modules/preact": { - "version": "10.11.3", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz", - "integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" - } - }, - "node_modules/@auth/core/node_modules/preact-render-to-string": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.3.tgz", - "integrity": "sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==", - "dependencies": { - "pretty-format": "^3.8.0" - }, - "peerDependencies": { - "preact": ">=10" - } - }, "node_modules/@auth/prisma-adapter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@auth/prisma-adapter/-/prisma-adapter-1.0.0.tgz", @@ -197,18 +193,6 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "dev": true }, - "node_modules/@babel/core/node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -252,15 +236,6 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -270,12 +245,6 @@ "semver": "bin/semver.js" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, "node_modules/@babel/helper-environment-visitor": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", @@ -690,9 +659,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz", - "integrity": "sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz", + "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==", "dependencies": { "regenerator-runtime": "^0.13.11" }, @@ -821,24 +790,24 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.2.6.tgz", - "integrity": "sha512-EvYTiXet5XqweYGClEmpu3BoxmsQ4hkj3QaYA6qEnigCWffTP3vNRwBReTdrwDwo7OoJ3wM8Uoe9Uk4n+d4hfg==" + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.3.1.tgz", + "integrity": "sha512-Bu+AMaXNjrpjh41znzHqaz3r2Nr8hHuHZT6V2LBKMhyMl0FgKA62PNYbqnfgmzOhoWZj70Zecisbo4H1rotP5g==" }, "node_modules/@floating-ui/dom": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.2.8.tgz", - "integrity": "sha512-XLwhYV90MxiHDq6S0rzFZj00fnDM+A1R9jhSioZoMsa7G0Q0i+Q4x40ajR8FHSdYDE1bgjG45mIWe6jtv9UPmg==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.4.2.tgz", + "integrity": "sha512-VKmvHVatWnewmGGy+7Mdy4cTJX71Pli6v/Wjb5RQBuq5wjUYx+Ef+kRThi8qggZqDgD8CogCpqhRoVp3+yQk+g==", "dependencies": { - "@floating-ui/core": "^1.2.6" + "@floating-ui/core": "^1.3.1" } }, "node_modules/@floating-ui/react-dom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.0.tgz", - "integrity": "sha512-Ke0oU3SeuABC2C4OFu2mSAwHIP5WUiV98O9YWoHV4Q5aT6E9k06DV0Khi5uYspR8xmmBk08t8ZDcz3TR3ARkEg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.1.tgz", + "integrity": "sha512-rZtAmSht4Lry6gdhAJDrCp/6rKN7++JnL1/Anbr/DdeyYXQPxvg/ivrbYvJulbRf4vL8b212suwMM2lxbv+RQA==", "dependencies": { - "@floating-ui/dom": "^1.2.7" + "@floating-ui/dom": "^1.3.0" }, "peerDependencies": { "react": ">=16.8.0", @@ -1011,6 +980,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jest/console/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@jest/core": { "version": "29.5.0", "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.5.0.tgz", @@ -1058,16 +1043,20 @@ } } }, - "node_modules/@jest/core/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/@jest/core/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/@jest/core/node_modules/pretty-format": { @@ -1084,6 +1073,18 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jest/core/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/@jest/core/node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -1205,6 +1206,22 @@ } } }, + "node_modules/@jest/reporters/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@jest/schemas": { "version": "29.4.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", @@ -1287,6 +1304,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jest/transform/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@jest/types": { "version": "29.5.0", "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz", @@ -1304,6 +1337,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jest/types/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", @@ -1372,23 +1421,23 @@ } }, "node_modules/@next/env": { - "version": "13.4.6", - "resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.6.tgz", - "integrity": "sha512-nqUxEtvDqFhmV1/awSg0K2XHNwkftNaiUqCYO9e6+MYmqNObpKVl7OgMkGaQ2SZnFx5YqF0t60ZJTlyJIDAijg==" + "version": "13.4.7", + "resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.7.tgz", + "integrity": "sha512-ZlbiFulnwiFsW9UV1ku1OvX/oyIPLtMk9p/nnvDSwI0s7vSoZdRtxXNsaO+ZXrLv/pMbXVGq4lL8TbY9iuGmVw==" }, "node_modules/@next/eslint-plugin-next": { - "version": "13.4.6", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-13.4.6.tgz", - "integrity": "sha512-bPigeu0RI7bgy1ucBA2Yqcfg539y0Lzo38P2hIkrRB1GNvFSbYg6RTu8n6tGqPVrH3TTlPTNKLXG01wc+5NuwQ==", + "version": "13.4.7", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-13.4.7.tgz", + "integrity": "sha512-ANEPltxzXbyyG7CvqxdY4PmeM5+RyWdAJGufTHnU+LA/i3J6IDV2r8Z4onKwskwKEhwqzz5lMaSYGGXLyHX+mg==", "dev": true, "dependencies": { "glob": "7.1.7" } }, "node_modules/@next/swc-darwin-arm64": { - "version": "13.4.6", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.6.tgz", - "integrity": "sha512-ahi6VP98o4HV19rkOXPSUu+ovfHfUxbJQ7VVJ7gL2FnZRr7onEFC1oGQ6NQHpm8CxpIzSSBW79kumlFMOmZVjg==", + "version": "13.4.7", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.7.tgz", + "integrity": "sha512-VZTxPv1b59KGiv/pZHTO5Gbsdeoxcj2rU2cqJu03btMhHpn3vwzEK0gUSVC/XW96aeGO67X+cMahhwHzef24/w==", "cpu": [ "arm64" ], @@ -1401,9 +1450,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "13.4.6", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.6.tgz", - "integrity": "sha512-13cXxKFsPJIJKzUqrU5XB1mc0xbUgYsRcdH6/rB8c4NMEbWGdtD4QoK9ShN31TZdePpD4k416Ur7p+deMIxnnA==", + "version": "13.4.7", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.7.tgz", + "integrity": "sha512-gO2bw+2Ymmga+QYujjvDz9955xvYGrWofmxTq7m70b9pDPvl7aDFABJOZ2a8SRCuSNB5mXU8eTOmVVwyp/nAew==", "cpu": [ "x64" ], @@ -1416,9 +1465,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "13.4.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.6.tgz", - "integrity": "sha512-Ti+NMHEjTNktCVxNjeWbYgmZvA2AqMMI2AMlzkXsU7W4pXCMhrryAmAIoo+7YdJbsx01JQWYVxGe62G6DoCLaA==", + "version": "13.4.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.7.tgz", + "integrity": "sha512-6cqp3vf1eHxjIDhEOc7Mh/s8z1cwc/l5B6ZNkOofmZVyu1zsbEM5Hmx64s12Rd9AYgGoiCz4OJ4M/oRnkE16/Q==", "cpu": [ "arm64" ], @@ -1431,9 +1480,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "13.4.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.6.tgz", - "integrity": "sha512-OHoC6gO7XfjstgwR+z6UHKlvhqJfyMtNaJidjx3sEcfaDwS7R2lqR5AABi8PuilGgi0BO0O0sCXqLlpp3a0emQ==", + "version": "13.4.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.7.tgz", + "integrity": "sha512-T1kD2FWOEy5WPidOn1si0rYmWORNch4a/NR52Ghyp4q7KyxOCuiOfZzyhVC5tsLIBDH3+cNdB5DkD9afpNDaOw==", "cpu": [ "arm64" ], @@ -1446,9 +1495,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "13.4.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.6.tgz", - "integrity": "sha512-zHZxPGkUlpfNJCboUrFqwlwEX5vI9LSN70b8XEb0DYzzlrZyCyOi7hwDp/+3Urm9AB7YCAJkgR5Sp1XBVjHdfQ==", + "version": "13.4.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.7.tgz", + "integrity": "sha512-zaEC+iEiAHNdhl6fuwl0H0shnTzQoAoJiDYBUze8QTntE/GNPfTYpYboxF5LRYIjBwETUatvE0T64W6SKDipvg==", "cpu": [ "x64" ], @@ -1461,9 +1510,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "13.4.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.6.tgz", - "integrity": "sha512-K/Y8lYGTwTpv5ME8PSJxwxLolaDRdVy+lOd9yMRMiQE0BLUhtxtCWC9ypV42uh9WpLjoaD0joOsB9Q6mbrSGJg==", + "version": "13.4.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.7.tgz", + "integrity": "sha512-X6r12F8d8SKAtYJqLZBBMIwEqcTRvUdVm+xIq+l6pJqlgT2tNsLLf2i5Cl88xSsIytBICGsCNNHd+siD2fbWBA==", "cpu": [ "x64" ], @@ -1476,9 +1525,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "13.4.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.6.tgz", - "integrity": "sha512-U6LtxEUrjBL2tpW+Kr1nHCSJWNeIed7U7l5o7FiKGGwGgIlFi4UHDiLI6TQ2lxi20fAU33CsruV3U0GuzMlXIw==", + "version": "13.4.7", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.7.tgz", + "integrity": "sha512-NPnmnV+vEIxnu6SUvjnuaWRglZzw4ox5n/MQTxeUhb5iwVWFedolPFebMNwgrWu4AELwvTdGtWjqof53AiWHcw==", "cpu": [ "arm64" ], @@ -1491,9 +1540,9 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "13.4.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.6.tgz", - "integrity": "sha512-eEBeAqpCfhdPSlCZCayjCiyIllVqy4tcqvm1xmg3BgJG0G5ITiMM4Cw2WVeRSgWDJqQGRyyb+q8Y2ltzhXOWsQ==", + "version": "13.4.7", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.7.tgz", + "integrity": "sha512-6Hxijm6/a8XqLQpOOf/XuwWRhcuc/g4rBB2oxjgCMuV9Xlr2bLs5+lXyh8w9YbAUMYR3iC9mgOlXbHa79elmXw==", "cpu": [ "ia32" ], @@ -1506,9 +1555,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "13.4.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.6.tgz", - "integrity": "sha512-OrZs94AuO3ZS5tnqlyPRNgfWvboXaDQCi5aXGve3o3C+Sj0ctMUV9+Do+0zMvvLRumR8E0PTWKvtz9n5vzIsWw==", + "version": "13.4.7", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.7.tgz", + "integrity": "sha512-sW9Yt36Db1nXJL+mTr2Wo0y+VkPWeYhygvcHj1FF0srVtV+VoDjxleKtny21QHaG05zdeZnw2fCtf2+dEqgwqA==", "cpu": [ "x64" ], @@ -1561,9 +1610,9 @@ } }, "node_modules/@pkgr/utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.0.tgz", - "integrity": "sha512-2OCURAmRtdlL8iUDTypMrrxfwe8frXTeXaxGsVOaYtc/wrUyk8Z/0OBetM7cdlsy7ZFWlMX72VogKeh+A4Xcjw==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.1.tgz", + "integrity": "sha512-JOqwkgFEyi+OROIyq7l4Jy28h/WwhDnG/cPkXG2Z1iFbubB6jsHW1NDvmyOzTBxHr3yg68YGirmh1JUgMqa+9w==", "dev": true, "dependencies": { "cross-spawn": "^7.0.3", @@ -1581,12 +1630,12 @@ } }, "node_modules/@prisma/client": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.15.0.tgz", - "integrity": "sha512-xnROvyABcGiwqRNdrObHVZkD9EjkJYHOmVdlKy1yGgI+XOzvMzJ4tRg3dz1pUlsyhKxXGCnjIQjWW+2ur+YXuw==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.16.1.tgz", + "integrity": "sha512-CoDHu7Bt+NuDo40ijoeHP79EHtECsPBTy3yte5Yo3op8TqXt/kV0OT5OrsWewKvQGKFMHhYQ+ePed3zzjYdGAw==", "hasInstallScript": true, "dependencies": { - "@prisma/engines-version": "4.15.0-28.8fbc245156db7124f997f4cecdd8d1219e360944" + "@prisma/engines-version": "4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c" }, "engines": { "node": ">=14.17" @@ -1601,16 +1650,16 @@ } }, "node_modules/@prisma/engines": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.15.0.tgz", - "integrity": "sha512-FTaOCGs0LL0OW68juZlGxFtYviZa4xdQj/rQEdat2txw0s3Vu/saAPKjNVXfIgUsGXmQ72HPgNr6935/P8FNAA==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.16.1.tgz", + "integrity": "sha512-gpZG0kGGxfemgvK/LghHdBIz+crHkZjzszja94xp4oytpsXrgt/Ice82MvPsWMleVIniKuARrowtsIsim0PFJQ==", "devOptional": true, "hasInstallScript": true }, "node_modules/@prisma/engines-version": { - "version": "4.15.0-28.8fbc245156db7124f997f4cecdd8d1219e360944", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.15.0-28.8fbc245156db7124f997f4cecdd8d1219e360944.tgz", - "integrity": "sha512-sVOig4tjGxxlYaFcXgE71f/rtFhzyYrfyfNFUsxCIEJyVKU9rdOWIlIwQ2NQ7PntvGnn+x0XuFo4OC1jvPJKzg==" + "version": "4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c.tgz", + "integrity": "sha512-tMWAF/qF00fbUH1HB4Yjmz6bjh7fzkb7Y3NRoUfMlHu6V+O45MGvqwYxqwBjn1BIUXkl3r04W351D4qdJjrgvA==" }, "node_modules/@radix-ui/number": { "version": "1.0.1", @@ -1760,6 +1809,42 @@ } } }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.0.4.tgz", + "integrity": "sha512-hJtRy/jPULGQZceSAP2Re6/4NpKo8im6V8P2hUqZsdFiSL8l35kYsw3qbRI6Ay5mQd2+wlLqje770eq+RJ3yZg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-dismissable-layer": "1.0.4", + "@radix-ui/react-focus-guards": "1.0.1", + "@radix-ui/react-focus-scope": "1.0.3", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-portal": "1.0.3", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-slot": "1.0.2", + "@radix-ui/react-use-controllable-state": "1.0.1", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.5" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-direction": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.1.tgz", @@ -1875,6 +1960,37 @@ } } }, + "node_modules/@radix-ui/react-hover-card": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.0.6.tgz", + "integrity": "sha512-2K3ToJuMk9wjwBOa+jdg2oPma+AmLdcEyTNsG/iC4BDVG3E0/mGCjbY8PEDSLxJcUi+nJi2QII+ec/4kWd88DA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-dismissable-layer": "1.0.4", + "@radix-ui/react-popper": "1.1.2", + "@radix-ui/react-portal": "1.0.3", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-controllable-state": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-id": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.1.tgz", @@ -2393,9 +2509,9 @@ } }, "node_modules/@rushstack/eslint-patch": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.3.0.tgz", - "integrity": "sha512-IthPJsJR85GhOkp3Hvp8zFOPK5ynKn6STyHa/WZpioK7E1aYDiBzpqQPrngc14DszIUkIrdd3k9Iu0XSzlP/1w==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.3.2.tgz", + "integrity": "sha512-V+MvGwaHH03hYhY+k6Ef/xKd6RYlc4q8WBx+2ANmipHJcKuktNcI/NgEsJgdSUF6Lw32njT6OnrRsKYCdgHjYw==", "dev": true }, "node_modules/@sinclair/typebox": { @@ -2431,20 +2547,20 @@ } }, "node_modules/@t3-oss/env-core": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@t3-oss/env-core/-/env-core-0.4.1.tgz", - "integrity": "sha512-JZI8vxlHwtiZO7OYS3qSX9Ngt7UcdqsugLwhBwx7UVi5wp1PtRo2tzMyNoBEGbfHdmkd2QU9IbvYjqtaLUA7TQ==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@t3-oss/env-core/-/env-core-0.6.0.tgz", + "integrity": "sha512-3FkPAba069WRZVVab/sB1m3eSGn/rZeypx5k+sWEu1d+k0OQdRDnvFS+7MtxYgqVrwaRk3b7yVnX2dgSPVmWPQ==", "peerDependencies": { "typescript": ">=4.7.2", "zod": "^3.0.0" } }, "node_modules/@t3-oss/env-nextjs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@t3-oss/env-nextjs/-/env-nextjs-0.4.1.tgz", - "integrity": "sha512-lDbewJvZwOW7bFwHzdKtAH4+YcVTvGU7UEJfHwlb1RqVe9ejPrKn6ax2OGv/nFji1JXGKecKYHz3Xkpru6TFbw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@t3-oss/env-nextjs/-/env-nextjs-0.6.0.tgz", + "integrity": "sha512-SpzcGNIbUYcQw4zPPFeRJqCC1560zL7QmB0puIqOnuCsmykPkqHPX+n9CNZLXVQerboHzfvb7Kd+jAdouk72Vw==", "dependencies": { - "@t3-oss/env-core": "0.4.1" + "@t3-oss/env-core": "0.6.0" }, "peerDependencies": { "typescript": ">=4.7.2", @@ -2462,20 +2578,20 @@ } }, "node_modules/@tanstack/query-core": { - "version": "4.29.14", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.29.14.tgz", - "integrity": "sha512-ElEAahtLWA7Y7c2Haw10KdEf2tx+XZl/Z8dmyWxZehxWK3TPL5qtXtb7kUEhvt/8u2cSP62fLxgh2qqzMMGnDQ==", + "version": "4.29.19", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.29.19.tgz", + "integrity": "sha512-uPe1DukeIpIHpQi6UzIgBcXsjjsDaLnc7hF+zLBKnaUlh7jFE/A+P8t4cU4VzKPMFB/C970n/9SxtpO5hmIRgw==", "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" } }, "node_modules/@tanstack/react-query": { - "version": "4.29.14", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.29.14.tgz", - "integrity": "sha512-wh4bd/QIy85YgTDBtj/7/9ZkpRX41QdZuUL8xKoSzuMCukXvAE1/oJ4p0F15lqQq9W3g2pgcbr2Aa+CNvqABhg==", + "version": "4.29.19", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.29.19.tgz", + "integrity": "sha512-XiTIOHHQ5Cw1WUlHaD4fmVUMhoWjuNJlAeJGq7eM4BraI5z7y8WkZO+NR8PSuRnQGblpuVdjClQbDFtwxTtTUw==", "dependencies": { - "@tanstack/query-core": "4.29.14", + "@tanstack/query-core": "4.29.19", "use-sync-external-store": "^1.2.0" }, "funding": { @@ -2515,38 +2631,31 @@ "node": ">=14" } }, - "node_modules/@testing-library/dom/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/@testing-library/dom/node_modules/aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "dependencies": { + "deep-equal": "^2.0.5" } }, - "node_modules/@testing-library/dom/node_modules/pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "node_modules/@testing-library/dom/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "dependencies": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@testing-library/dom/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true - }, "node_modules/@testing-library/jest-dom": { "version": "5.16.5", "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz", @@ -2569,19 +2678,6 @@ "yarn": ">=1" } }, - "node_modules/@testing-library/jest-dom/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@testing-library/react": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.0.0.tgz", @@ -2758,9 +2854,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.3.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.1.tgz", - "integrity": "sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==", + "version": "20.3.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.2.tgz", + "integrity": "sha512-vOBLVQeCQfIcF/2Y7eKFTqrMnizK5lRNQ7ykML/5RuwVXVWxYkgwS7xbt4B6fKCUPgbSL5FSsjHQpaGQP/dQmw==", "dev": true }, "node_modules/@types/prettier": { @@ -2776,9 +2872,9 @@ "devOptional": true }, "node_modules/@types/react": { - "version": "18.2.13", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.13.tgz", - "integrity": "sha512-vJ+zElvi/Zn9cVXB5slX2xL8PZodPCwPRDpittQdw43JR2AJ5k3vKdgJJyneV/cYgIbLQUwXa9JVDvUZXGba+Q==", + "version": "18.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.14.tgz", + "integrity": "sha512-A0zjq+QN/O0Kpe30hA1GidzyFjatVvrpIvWLxD+xv67Vt91TWWgco9IvrJBkeyHm1trGaFS/FSGqPlhyeZRm0g==", "devOptional": true, "dependencies": { "@types/prop-types": "*", @@ -2838,14 +2934,14 @@ "dev": true }, "node_modules/@typescript-eslint/parser": { - "version": "5.59.7", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.7.tgz", - "integrity": "sha512-VhpsIEuq/8i5SF+mPg9jSdIwgMBBp0z9XqjiEay+81PYLJuroN+ET1hM5IhkiYMJd9MkTz8iJLt7aaGAgzWUbQ==", + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.60.1.tgz", + "integrity": "sha512-pHWlc3alg2oSMGwsU/Is8hbm3XFbcrb6P5wIxcQW9NsYBfnrubl/GhVVD/Jm/t8HXhA2WncoIRfBtnCgRGV96Q==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.59.7", - "@typescript-eslint/types": "5.59.7", - "@typescript-eslint/typescript-estree": "5.59.7", + "@typescript-eslint/scope-manager": "5.60.1", + "@typescript-eslint/types": "5.60.1", + "@typescript-eslint/typescript-estree": "5.60.1", "debug": "^4.3.4" }, "engines": { @@ -2865,13 +2961,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.59.7", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.7.tgz", - "integrity": "sha512-FL6hkYWK9zBGdxT2wWEd2W8ocXMu3K94i3gvMrjXpx+koFYdYV7KprKfirpgY34vTGzEPPuKoERpP8kD5h7vZQ==", + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.60.1.tgz", + "integrity": "sha512-Dn/LnN7fEoRD+KspEOV0xDMynEmR3iSHdgNsarlXNLGGtcUok8L4N71dxUgt3YvlO8si7E+BJ5Fe3wb5yUw7DQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.7", - "@typescript-eslint/visitor-keys": "5.59.7" + "@typescript-eslint/types": "5.60.1", + "@typescript-eslint/visitor-keys": "5.60.1" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2882,9 +2978,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.59.7", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.7.tgz", - "integrity": "sha512-UnVS2MRRg6p7xOSATscWkKjlf/NDKuqo5TdbWck6rIRZbmKpVNTLALzNvcjIfHBE7736kZOFc/4Z3VcZwuOM/A==", + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.60.1.tgz", + "integrity": "sha512-zDcDx5fccU8BA0IDZc71bAtYIcG9PowaOwaD8rjYbqwK7dpe/UMQl3inJ4UtUK42nOCT41jTSCwg76E62JpMcg==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2895,13 +2991,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.59.7", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.7.tgz", - "integrity": "sha512-4A1NtZ1I3wMN2UGDkU9HMBL+TIQfbrh4uS0WDMMpf3xMRursDbqEf1ahh6vAAe3mObt8k3ZATnezwG4pdtWuUQ==", + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.60.1.tgz", + "integrity": "sha512-hkX70J9+2M2ZT6fhti5Q2FoU9zb+GeZK2SLP1WZlvUDqdMbEKhexZODD1WodNRyO8eS+4nScvT0dts8IdaBzfw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.7", - "@typescript-eslint/visitor-keys": "5.59.7", + "@typescript-eslint/types": "5.60.1", + "@typescript-eslint/visitor-keys": "5.60.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -2922,12 +3018,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.59.7", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.7.tgz", - "integrity": "sha512-tyN+X2jvMslUszIiYbF0ZleP+RqQsFVpGrKI6e0Eet1w8WmhsAtmzaqm8oM8WJQ1ysLwhnsK/4hYHJjOgJVfQQ==", + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.60.1.tgz", + "integrity": "sha512-xEYIxKcultP6E/RMKqube11pGjXH1DCo60mQoWhVYyKfLkwbIVVjYxmOenNMxILx0TjCujPTjjnTIVzm09TXIw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.7", + "@typescript-eslint/types": "5.60.1", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -2938,11 +3034,43 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", - "dev": true + "node_modules/@uploadthing/mime-types": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@uploadthing/mime-types/-/mime-types-0.2.0.tgz", + "integrity": "sha512-nRcdyM622+GYT9SdaJIVLknqoc8i7krItinfuBPtvmPc+UNGDJ+pMkr4AatYp88cEc4iYtPlN8flJvZ3dNtgrg==" + }, + "node_modules/@uploadthing/react": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@uploadthing/react/-/react-5.1.0.tgz", + "integrity": "sha512-gBcw7A6I3/1APqSB2tRk81JNRYdopurt5cHrTdlrLQakH2XnCTs+1Ptl+Tx6MP/zVPajJHdW5B3JB8iQc4SHoQ==", + "dependencies": { + "@uploadthing/shared": "^5.0.1" + }, + "peerDependencies": { + "react": "^17.0.2 || ^18.0.0", + "react-dropzone": "^14.2.3", + "uploadthing": "^5.0.0" + } + }, + "node_modules/@uploadthing/shared": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@uploadthing/shared/-/shared-5.0.1.tgz", + "integrity": "sha512-SMIM9Dyxu/LuKZCM0w/yCXL/wrZedUHKiEK022y5LXhbG7uApVFBPChC2+CCY7ZRU3O9sCCNsDj6OjdygIOEQw==", + "peerDependencies": { + "@uploadthing/mime-types": "^0.2.0", + "zod": "^3.21.4" + }, + "peerDependenciesMeta": { + "@uploadthing/mime-types": { + "optional": true + } + } + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true }, "node_modules/abbrev": { "version": "1.1.1", @@ -2950,9 +3078,9 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz", + "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -3123,12 +3251,12 @@ } }, "node_modules/aria-query": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", - "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, "dependencies": { - "deep-equal": "^2.0.5" + "dequal": "^2.0.3" } }, "node_modules/array-buffer-byte-length": { @@ -3233,6 +3361,14 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, + "node_modules/attr-accept": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz", + "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==", + "engines": { + "node": ">=4" + } + }, "node_modules/autoprefixer": { "version": "10.4.14", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz", @@ -3279,21 +3415,21 @@ } }, "node_modules/axe-core": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.1.tgz", - "integrity": "sha512-sCXXUhA+cljomZ3ZAwb8i1p3oOlkABzPy08ZDAoGcYuvtBPlQ1Ytde129ArXyHWDhfeewq7rlx9F+cUx2SSlkg==", + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.2.tgz", + "integrity": "sha512-zIURGIS1E1Q4pcrMjp+nnEh+16G56eG/MUllJH8yEvw7asDo7Ac9uhC9KIH5jzpITueEZolfYglnCGIuSBz39g==", "dev": true, "engines": { "node": ">=4" } }, "node_modules/axobject-query": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", - "integrity": "sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", + "integrity": "sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==", "dev": true, "dependencies": { - "deep-equal": "^2.0.5" + "dequal": "^2.0.3" } }, "node_modules/babel-jest": { @@ -3317,6 +3453,22 @@ "@babel/core": "^7.8.0" } }, + "node_modules/babel-jest/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/babel-plugin-istanbul": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", @@ -3455,9 +3607,9 @@ } }, "node_modules/browserslist": { - "version": "4.21.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", - "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "version": "4.21.9", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz", + "integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==", "dev": true, "funding": [ { @@ -3467,13 +3619,17 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "caniuse-lite": "^1.0.30001449", - "electron-to-chromium": "^1.4.284", - "node-releases": "^2.0.8", - "update-browserslist-db": "^1.0.10" + "caniuse-lite": "^1.0.30001503", + "electron-to-chromium": "^1.4.431", + "node-releases": "^2.0.12", + "update-browserslist-db": "^1.0.11" }, "bin": { "browserslist": "cli.js" @@ -3563,9 +3719,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001489", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001489.tgz", - "integrity": "sha512-x1mgZEXK8jHIfAxm+xgdpHpk50IN3z3q3zP261/WS+uvePxW8izXuCu6AHz0lkuYTlATDehiZ/tNyYBdSQsOUQ==", + "version": "1.0.30001509", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001509.tgz", + "integrity": "sha512-2uDDk+TRiTX5hMcUYT/7CSyzMZxjfGu0vAUjS2g0LSD8UoXOv0LtpH4LxGMemsiPq6LCVIUjNwVM0erkOkGCDA==", "funding": [ { "type": "opencollective", @@ -3582,19 +3738,16 @@ ] }, "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=8" } }, "node_modules/char-regex": { @@ -3673,22 +3826,14 @@ "dev": true }, "node_modules/class-variance-authority": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.6.0.tgz", - "integrity": "sha512-qdRDgfjx3GRb9fpwpSvn+YaidnT7IUJNe4wt5/SWwM+PmUwJUhQRk/8zAyNro0PmVfmen2635UboTjIBXXxy5A==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.6.1.tgz", + "integrity": "sha512-eurOEGc7YVx3majOrOb099PNKgO3KnKSApOprXI4BTq6bcfbqbQXPN2u+rPPmIJ2di23bMwhk0SxCCthBmszEQ==", "dependencies": { "clsx": "1.2.1" }, "funding": { "url": "https://joebell.co.uk" - }, - "peerDependencies": { - "typescript": ">= 4.5.5 < 6" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } } }, "node_modules/client-only": { @@ -3885,39 +4030,10 @@ "node": ">=12" } }, - "node_modules/data-urls/node_modules/tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", - "dev": true, - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/data-urls/node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/data-urls/node_modules/whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", - "dev": true, - "dependencies": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } + "node_modules/dayjs": { + "version": "1.11.8", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.8.tgz", + "integrity": "sha512-LcgxzFoWMEPO7ggRv1Y2N31hUf2R0Vj7fuy/m+Bg1K8rr+KAs1AEy4y9jd5DXe8pbHgX+srkHNS7TH6Q6ZhYeQ==" }, "node_modules/debug": { "version": "4.3.4", @@ -4025,6 +4141,116 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/default-browser/node_modules/execa": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-7.1.1.tgz", + "integrity": "sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^4.3.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": "^14.18.0 || ^16.14.0 || >=18.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/default-browser/node_modules/human-signals": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", + "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", + "dev": true, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/default-browser/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser/node_modules/npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/define-lazy-prop": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", @@ -4067,6 +4293,15 @@ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/detect-libc": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", @@ -4150,19 +4385,10 @@ "node": ">=12" } }, - "node_modules/domexception/node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "dev": true, - "engines": { - "node": ">=12" - } - }, "node_modules/electron-to-chromium": { - "version": "1.4.404", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.404.tgz", - "integrity": "sha512-te57sWvQdpxmyd1GiswaodKdXdPgn9cN4ht8JlNa04QgtrfnUdWEo1261rY2vaC6TKaiHn0E7QerJWPKFCvMVw==", + "version": "1.4.444", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.444.tgz", + "integrity": "sha512-/AjkL4syRvOpowXWy3N4OHmVbFdWQpfSKHh0sCVYipDeEAtMce3rLjMJi/27Ia9jNIbw6P1JuPN32pSWybXXEQ==", "dev": true }, "node_modules/emittery": { @@ -4184,9 +4410,9 @@ "dev": true }, "node_modules/enhanced-resolve": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.14.0.tgz", - "integrity": "sha512-+DCows0XNwLDcUhbFJPdlQEVnT2zXlCv7hPxemTz86/O+B/hCQ+mb7ydkPKiflpVraqLPCAfu7lDy+hBXueojw==", + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", "dev": true, "dependencies": { "graceful-fs": "^4.2.4", @@ -4476,12 +4702,12 @@ } }, "node_modules/eslint-config-next": { - "version": "13.4.6", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-13.4.6.tgz", - "integrity": "sha512-nlv4FYish1RYYHILbQwM5/rD37cOvEqtMfDjtQCYbXdE2O3MggqHu2q6IDeLE2Z6u8ZJyNPgWOA6OimWcxj3qw==", + "version": "13.4.7", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-13.4.7.tgz", + "integrity": "sha512-+IRAyD0+J1MZaTi9RQMPUfr6Q+GCZ1wOkK6XM52Vokh7VI4R6YFGOFzdkEFHl4ZyIX4FKa5vcwUP2WscSFNjNQ==", "dev": true, "dependencies": { - "@next/eslint-plugin-next": "13.4.6", + "@next/eslint-plugin-next": "13.4.7", "@rushstack/eslint-patch": "^1.1.3", "@typescript-eslint/parser": "^5.42.0", "eslint-import-resolver-node": "^0.3.6", @@ -4548,9 +4774,9 @@ } }, "node_modules/eslint-import-resolver-typescript/node_modules/globby": { - "version": "13.1.4", - "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.4.tgz", - "integrity": "sha512-iui/IiiW+QrJ1X1hKH5qwlMQyv34wJAYwH1vrf8b9kBA4sNiif3gKsMHa+BrdnOpEudWjpotfa7LrTzB1ERS/g==", + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.0.tgz", + "integrity": "sha512-jWsQfayf13NvqKUIL3Ta+CIqMnvlaIDFveWE/dpOZ9+3AMEJozsxDvKA02zync9UuvOM8rOXzsD5GqKP4OnWPQ==", "dev": true, "dependencies": { "dir-glob": "^3.0.1", @@ -4809,6 +5035,22 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/espree": { "version": "9.5.2", "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", @@ -4882,23 +5124,23 @@ } }, "node_modules/execa": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-7.1.1.tgz", - "integrity": "sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, "dependencies": { "cross-spawn": "^7.0.3", - "get-stream": "^6.0.1", - "human-signals": "^4.3.0", - "is-stream": "^3.0.0", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^3.0.7", - "strip-final-newline": "^3.0.0" + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" }, "engines": { - "node": "^14.18.0 || ^16.14.0 || >=18.0.0" + "node": ">=10" }, "funding": { "url": "https://github.com/sindresorhus/execa?sponsor=1" @@ -5002,6 +5244,17 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/file-selector": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz", + "integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==", + "dependencies": { + "tslib": "^2.4.0" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -5106,6 +5359,11 @@ "node": ">=8" } }, + "node_modules/fs-minipass/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -5254,10 +5512,13 @@ } }, "node_modules/get-tsconfig": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.5.0.tgz", - "integrity": "sha512-MjhiaIWCJ1sAU4pIQ5i5OfOuHHxVo1oYeNsWTON7jxYkod8pHocXeh+SSbmu5OZZZK73B6cbJ2XADzXehLyovQ==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.6.2.tgz", + "integrity": "sha512-E5XrT4CbbXcXWy+1jChlZmrmCwd5KGx502kDCXJJ7y898TtWW9FwoG5HfOLVRKmlmDGkWN2HM9Ho+/Y8F0sJDg==", "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, "funding": { "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } @@ -5500,12 +5761,12 @@ } }, "node_modules/human-signals": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", - "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, "engines": { - "node": ">=14.18.0" + "node": ">=10.17.0" } }, "node_modules/iconv-lite": { @@ -5897,12 +6158,12 @@ } }, "node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -6144,95 +6405,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-changed-files/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/jest-changed-files/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/jest-changed-files/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-changed-files/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/jest-changed-files/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-changed-files/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-changed-files/node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/jest-circus": { "version": "29.5.0", "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.5.0.tgz", @@ -6264,16 +6436,20 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-circus/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/jest-circus/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/jest-circus/node_modules/pretty-format": { @@ -6290,6 +6466,18 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-circus/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/jest-circus/node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -6330,6 +6518,22 @@ } } }, + "node_modules/jest-cli/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-config": { "version": "29.5.0", "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.5.0.tgz", @@ -6375,16 +6579,20 @@ } } }, - "node_modules/jest-config/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/jest-config/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/jest-config/node_modules/pretty-format": { @@ -6401,6 +6609,18 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-config/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/jest-config/node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -6422,16 +6642,20 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-diff/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/jest-diff/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/jest-diff/node_modules/pretty-format": { @@ -6448,6 +6672,18 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-diff/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/jest-diff/node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -6482,16 +6718,20 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-each/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/jest-each/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/jest-each/node_modules/pretty-format": { @@ -6508,6 +6748,18 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-each/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/jest-each/node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -6652,16 +6904,20 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-matcher-utils/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/jest-matcher-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/jest-matcher-utils/node_modules/pretty-format": { @@ -6678,6 +6934,18 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-matcher-utils/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/jest-matcher-utils/node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -6704,16 +6972,20 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-message-util/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/jest-message-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/jest-message-util/node_modules/pretty-format": { @@ -6730,6 +7002,18 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-message-util/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/jest-message-util/node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -6809,6 +7093,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-resolve/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-runner": { "version": "29.5.0", "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.5.0.tgz", @@ -6841,6 +7141,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-runner/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-runtime": { "version": "29.5.0", "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.5.0.tgz", @@ -6874,13 +7190,20 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runtime/node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "node_modules/jest-runtime/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/jest-snapshot": { @@ -6917,16 +7240,20 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-snapshot/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/jest-snapshot/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/jest-snapshot/node_modules/pretty-format": { @@ -6943,6 +7270,18 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-snapshot/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/jest-snapshot/node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -6966,6 +7305,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-validate": { "version": "29.5.0", "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.5.0.tgz", @@ -6983,28 +7338,32 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-validate/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "node_modules/jest-validate/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/jest-validate/node_modules/pretty-format": { @@ -7021,6 +7380,18 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-validate/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/jest-validate/node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -7046,6 +7417,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-watcher/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-worker": { "version": "29.5.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz", @@ -7154,40 +7541,6 @@ } } }, - "node_modules/jsdom/node_modules/tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", - "dev": true, - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/jsdom/node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/jsdom/node_modules/whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", - "dev": true, - "dependencies": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -7219,25 +7572,27 @@ "dev": true }, "node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, "bin": { "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" } }, "node_modules/jsx-ast-utils": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz", - "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.4.tgz", + "integrity": "sha512-fX2TVdCViod6HwKEtSWGHs57oFhVfCMwieb9PuRDgjDPh5XeqJiHFFFJCHxU5cnTc3Bu/GRL+kPiFmw8XWOfKw==", "dev": true, "dependencies": { - "array-includes": "^3.1.5", - "object.assign": "^4.1.3" + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" }, "engines": { "node": ">=4.0" @@ -7341,20 +7696,18 @@ } }, "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" + "yallist": "^3.0.2" } }, "node_modules/lucide-react": { - "version": "0.246.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.246.0.tgz", - "integrity": "sha512-0OkIG28coMDnQfy9+vYr/+8BY42KpBO05WkslfIEJuDxi/qwcJfUXsG+nhONGMAUs/ADR5fE8upHBw76O/yMPg==", + "version": "0.256.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.256.0.tgz", + "integrity": "sha512-uqw4MijZS29zePSBtkEIdOAp2Mn2mE6lVgFxaUd/MpclVUOuwacpRF67s1y3ErEIcR9aSBO0F1e6G56Ci8VUKA==", "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0" } @@ -7447,15 +7800,12 @@ } }, "node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, "node_modules/min-indent": { @@ -7518,6 +7868,11 @@ "node": ">=8" } }, + "node_modules/minizlib/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -7568,11 +7923,11 @@ "dev": true }, "node_modules/next": { - "version": "13.4.6", - "resolved": "https://registry.npmjs.org/next/-/next-13.4.6.tgz", - "integrity": "sha512-sjVqjxU+U2aXZnYt4Ud6CTLNNwWjdSfMgemGpIQJcN3Z7Jni9xRWbR0ie5fQzCg87aLqQVhKA2ud2gPoqJ9lGw==", + "version": "13.4.7", + "resolved": "https://registry.npmjs.org/next/-/next-13.4.7.tgz", + "integrity": "sha512-M8z3k9VmG51SRT6v5uDKdJXcAqLzP3C+vaKfLIAM0Mhx1um1G7MDnO63+m52qPdZfrTFzMZNzfsgvm3ghuVHIQ==", "dependencies": { - "@next/env": "13.4.6", + "@next/env": "13.4.7", "@swc/helpers": "0.5.1", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001406", @@ -7588,15 +7943,15 @@ "node": ">=16.8.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "13.4.6", - "@next/swc-darwin-x64": "13.4.6", - "@next/swc-linux-arm64-gnu": "13.4.6", - "@next/swc-linux-arm64-musl": "13.4.6", - "@next/swc-linux-x64-gnu": "13.4.6", - "@next/swc-linux-x64-musl": "13.4.6", - "@next/swc-win32-arm64-msvc": "13.4.6", - "@next/swc-win32-ia32-msvc": "13.4.6", - "@next/swc-win32-x64-msvc": "13.4.6" + "@next/swc-darwin-arm64": "13.4.7", + "@next/swc-darwin-x64": "13.4.7", + "@next/swc-linux-arm64-gnu": "13.4.7", + "@next/swc-linux-arm64-musl": "13.4.7", + "@next/swc-linux-x64-gnu": "13.4.7", + "@next/swc-linux-x64-musl": "13.4.7", + "@next/swc-win32-arm64-msvc": "13.4.7", + "@next/swc-win32-ia32-msvc": "13.4.7", + "@next/swc-win32-x64-msvc": "13.4.7" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", @@ -7701,6 +8056,25 @@ } } }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -7708,9 +8082,9 @@ "dev": true }, "node_modules/node-releases": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.11.tgz", - "integrity": "sha512-+M0PwXeU80kRohZ3aT4J/OnR+l9/KD2nVLNNoRgFtnf+umQVFdGBAO2N8+nCnEi0xlh/Wk3zOGC+vNNx+uM79Q==", + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz", + "integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==", "dev": true }, "node_modules/nopt": { @@ -7760,30 +8134,15 @@ } }, "node_modules/npm-run-path": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", - "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "path-key": "^3.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, "node_modules/npmlog": { @@ -7825,9 +8184,9 @@ } }, "node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", "engines": { "node": ">= 6" } @@ -7962,15 +8321,15 @@ } }, "node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "dependencies": { - "mimic-fn": "^4.0.0" + "mimic-fn": "^2.1.0" }, "engines": { - "node": ">=12" + "node": ">=6" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -8008,26 +8367,34 @@ "url": "https://github.com/sponsors/panva" } }, - "node_modules/openid-client/node_modules/object-hash": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", - "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "node_modules/openid-client/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, "engines": { - "node": ">= 6" + "node": ">=10" } }, + "node_modules/openid-client/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dev": true, "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "type-check": "^0.4.0" }, "engines": { "node": ">= 0.8.0" @@ -8179,9 +8546,9 @@ } }, "node_modules/pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", "engines": { "node": ">= 6" } @@ -8375,18 +8742,18 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, "node_modules/preact": { - "version": "10.15.1", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.15.1.tgz", - "integrity": "sha512-qs2ansoQEwzNiV5eAcRT1p1EC/dmEzaATVDJNiB3g2sRDWdA7b7MurXdJjB2+/WQktGWZwxvDrnuRFbWuIr64g==", + "version": "10.11.3", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz", + "integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==", "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" } }, "node_modules/preact-render-to-string": { - "version": "5.2.6", - "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz", - "integrity": "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.3.tgz", + "integrity": "sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==", "dependencies": { "pretty-format": "^3.8.0" }, @@ -8394,6 +8761,11 @@ "preact": ">=10" } }, + "node_modules/preact-render-to-string/node_modules/pretty-format": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", + "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -8404,18 +8776,39 @@ } }, "node_modules/pretty-format": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", - "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==" + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } }, "node_modules/prisma": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-4.15.0.tgz", - "integrity": "sha512-iKZZpobPl48gTcSZVawLMQ3lEy6BnXwtoMj7hluoGFYu2kQ6F9LBuBrUyF95zRVnNo8/3KzLXJXJ5TEnLSJFiA==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-4.16.1.tgz", + "integrity": "sha512-C2Xm7yxHxjFjjscBEW4tmoraPHH/Vyu/A0XABdbaFtoiOZARsxvOM7rwc2iZ0qVxbh0bGBGBWZUSXO/52/nHBQ==", "devOptional": true, "hasInstallScript": true, "dependencies": { - "@prisma/engines": "4.15.0" + "@prisma/engines": "4.16.1" }, "bin": { "prisma": "build/index.js", @@ -8442,13 +8835,17 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -8528,10 +8925,26 @@ "react": "^18.2.0" } }, + "node_modules/react-dropzone": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.3.tgz", + "integrity": "sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==", + "dependencies": { + "attr-accept": "^2.2.2", + "file-selector": "^0.6.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "react": ">= 16.8 || 18.0.0" + } + }, "node_modules/react-hook-form": { - "version": "7.45.0", - "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.45.0.tgz", - "integrity": "sha512-AbHeZ4ad+0dEIknSW9dBgIwcvRDfZ1O97sgj75WaMdOX0eg8TBiUf9wxzVkIjZbk76BBIE9lmFOzyD4PN80ZQg==", + "version": "7.45.1", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.45.1.tgz", + "integrity": "sha512-6dWoFJwycbuFfw/iKMcl+RdAOAOHDiF11KWYhNDRN/OkUt+Di5qsZHwA0OwsVnu9y135gkHpTw9DJA+WzCeR9w==", "engines": { "node": ">=12.22.0" }, @@ -8554,10 +8967,18 @@ "react": ">=16.0.0" } }, + "node_modules/react-intersection-observer": { + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.5.2.tgz", + "integrity": "sha512-EmoV66/yvksJcGa1rdW0nDNc4I1RifDWkT50gXSFnPLYQ4xUptuDD4V7k+Rj1OgVAlww628KLGcxPXFlOkkU/Q==", + "peerDependencies": { + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true }, "node_modules/react-remove-scroll": { @@ -8755,6 +9176,15 @@ "node": ">=4" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/resolve.exports": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", @@ -8802,95 +9232,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/run-applescript/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/run-applescript/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/run-applescript/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/run-applescript/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/run-applescript/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/run-applescript/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/run-applescript/node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -8973,9 +9314,9 @@ } }, "node_modules/semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -8986,6 +9327,22 @@ "node": ">=10" } }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -9235,24 +9592,21 @@ } }, "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true, "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, "node_modules/strip-indent": { @@ -9440,6 +9794,14 @@ "tailwindcss": ">=3.0.0 || insiders" } }, + "node_modules/tailwindcss/node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "engines": { + "node": ">= 6" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -9465,6 +9827,11 @@ "node": ">=10" } }, + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -9566,9 +9933,16 @@ } }, "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } }, "node_modules/ts-interface-checker": { "version": "0.1.13", @@ -9587,10 +9961,31 @@ "strip-bom": "^3.0.0" } }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/tslib": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.2.tgz", - "integrity": "sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==" + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" }, "node_modules/tsutils": { "version": "3.21.0", @@ -9661,9 +10056,9 @@ } }, "node_modules/typescript": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz", - "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -9735,6 +10130,15 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/uploadthing": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/uploadthing/-/uploadthing-5.1.0.tgz", + "integrity": "sha512-adWd3D0/r25EspS1s+p2RLvSpDPLOQC+AM+mH1JomsTdEDiderjxMeo6juZUWnq96O/Dsumh97SeFJXDLWI+Qw==", + "dependencies": { + "@uploadthing/mime-types": "^0.2.0", + "@uploadthing/shared": "^5.0.1" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -9870,9 +10274,13 @@ } }, "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } }, "node_modules/whatwg-encoding": { "version": "2.0.0", @@ -9896,12 +10304,16 @@ } }, "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" } }, "node_modules/which": { @@ -10068,14 +10480,15 @@ } }, "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true }, "node_modules/yaml": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz", - "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", + "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", "engines": { "node": ">= 14" } diff --git a/package.json b/package.json index 9948a5e8e19ac18de2f84ab3dfca63c44df3b76d..f87a373b23b5b958e1661132ccd2ec3f874b9f63 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "build": "next build", "start": "next start", "lint": "next lint", - "preview": "next build && next start", + "preview": "prisma generate && next build && next start", "vercel-build": "prisma generate && prisma migrate deploy && next build", "test": "jest", "coverage": "jest --coverage" @@ -16,32 +16,39 @@ "dependencies": { "@auth/prisma-adapter": "^1.0.0", "@hookform/resolvers": "^3.1.1", - "@prisma/client": "^4.15.0", + "@prisma/client": "^4.16.1", "@radix-ui/react-aspect-ratio": "^1.0.3", "@radix-ui/react-avatar": "^1.0.3", + "@radix-ui/react-dialog": "^1.0.4", "@radix-ui/react-dropdown-menu": "^2.0.5", + "@radix-ui/react-hover-card": "^1.0.6", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-scroll-area": "^1.0.4", "@radix-ui/react-select": "^1.2.2", "@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-toast": "^1.1.4", - "@t3-oss/env-nextjs": "^0.4.1", - "@tanstack/react-query": "^4.29.14", + "@t3-oss/env-nextjs": "^0.6.0", + "@tanstack/react-query": "^4.29.19", + "@uploadthing/react": "^5.1.0", "bcrypt": "^5.1.0", - "class-variance-authority": "^0.6.0", + "class-variance-authority": "^0.6.1", "clsx": "^1.2.1", - "lucide-react": "^0.246.0", - "next": "^13.4.6", + "dayjs": "^1.11.8", + "lucide-react": "^0.256.0", + "next": "^13.4.7", "next-auth": "^4.22.1", "next-themes": "^0.2.1", "normalize-diacritics": "^4.0.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-hook-form": "^7.45.0", + "react-dropzone": "^14.2.3", + "react-hook-form": "^7.45.1", "react-infinite-scroll-component": "^6.1.0", + "react-intersection-observer": "^9.5.2", "tailwind-merge": "^1.13.2", "tailwindcss-animate": "^1.0.6", + "uploadthing": "^5.1.0", "zod": "^3.21.4" }, "devDependencies": { @@ -50,17 +57,17 @@ "@testing-library/react": "^14.0.0", "@types/bcrypt": "^5.0.0", "@types/jest": "^29.5.2", - "@types/node": "^20.3.1", - "@types/react": "^18.2.13", + "@types/node": "^20.3.2", + "@types/react": "^18.2.14", "@types/react-dom": "^18.2.6", "autoprefixer": "10.4.14", "eslint": "^8.43.0", - "eslint-config-next": "^13.4.6", + "eslint-config-next": "^13.4.7", "jest": "^29.5.0", "jest-environment-jsdom": "^29.5.0", "postcss": "8.4.24", - "prisma": "^4.15.0", + "prisma": "^4.16.1", "tailwindcss": "3.3.2", - "typescript": "^5.1.3" + "typescript": "^5.1.6" } -} +} \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 8ba0bb4518b5cb184587e80f8e88772e7c5404bd..aa9abd8775d257c5569035756c7a059e57ea4e6b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -9,22 +9,20 @@ datasource db { } model Account { - id String @id @default(cuid()) - userId String @unique - type String - provider String - providerAccountId String - refresh_token String? - access_token String? - expires_at Int? - token_type String? - scope String? - id_token String? - session_state String? - createdAt DateTime @default(now()) @map("created_at") - updatedAt DateTime @default(now()) @map("updated_at") - refresh_token_expires_in Int? - user User @relation(fields: [userId], references: [id], onDelete: Cascade) + id String @id @default(cuid()) + userId String @unique + type String + provider String + providerAccountId String + refresh_token String? @db.Text + access_token String? @db.Text + expires_at Int? + token_type String? + scope String? + id_token String? @db.Text + session_state String? + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([provider, providerAccountId]) @@map("accounts") @@ -35,91 +33,143 @@ model Session { sessionToken String @unique userId String @unique expires DateTime - user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@map("sessions") } +model VerificationToken { + identifier String + token String @unique + expires DateTime + + @@unique([identifier, token]) + @@map("verification_tokens") +} + +enum UserRole { + ADMIN + USER +} + model User { id String @id @default(cuid()) - name String? + name String username String? @unique email String? @unique - emailVerified DateTime? + emailVerified DateTime? @map("email_verified") password String? image String? + banner String? + bio String? + website String? + location String? + private Boolean @default(false) + role UserRole @default(USER) createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @default(now()) @map("updated_at") - favGameList Int[] - accounts Account? - Comment Comment[] - following Follows[] @relation("following") - followers Follows[] @relation("follower") - Like Like[] - Post Post[] - sessions Session? - @@map("users") -} + favGameList Int[] + planningGameList Int[] + playingGameList Int[] + finishedGameList Int[] -model VerificationToken { - identifier String - token String @unique - expires DateTime + accounts Account[] + sessions Session[] + gweets Gweet[] + regweets Regweet[] + likes Like[] - @@unique([identifier, token]) - @@map("verification_tokens") + following Follows[] @relation("following") + followers Follows[] @relation("follower") + + @@map("users") } model Follows { followerId String followingId String - createdAt DateTime @default(now()) - follower User @relation("following", fields: [followerId], references: [id]) - following User @relation("follower", fields: [followingId], references: [id]) + 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 Post { - id String @id @default(cuid()) - createdAt DateTime @default(now()) @map("created_at") - updatedAt DateTime @default(now()) @map("updated_at") - userId String +model Gweet { + id String @id @default(cuid()) + authorId String @map("user_id") content String - likeCount Int? @default(0) - published Boolean @default(false) - Comment Comment[] - Like Like[] - user User @relation(fields: [userId], references: [id], onDelete: Cascade) + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @default(now()) @map("updated_at") + + replyToGweetId String? @map("reply_to_gweet_id") + quoteGweetId String? @map("quote_gweet_id") + + regweets Regweet[] + media Media[] + likes Like[] + + author User @relation(fields: [authorId], references: [id], onDelete: Cascade) + comment Gweet? @relation("gweet_comment", fields: [replyToGweetId], references: [id]) + allComments Gweet[] @relation("gweet_comment") + quote Gweet? @relation("gweet_quote", fields: [quoteGweetId], references: [id]) + allQuotes Gweet[] @relation("gweet_quote") + + @@map("gweets") +} + +enum MediaTypes { + IMAGE + VIDEO + GIF +} + +model Media { + id String @id @default(cuid()) + url String + key String @unique + type MediaTypes @default(IMAGE) + gweetId String @map("gweet_id") - @@map("posts") + gweet Gweet? @relation(fields: [gweetId], references: [id], onDelete: Cascade) + + @@map("media") } model Like { id String @id @default(cuid()) - postId String - commentId String? - userId String - comment Comment? @relation(fields: [commentId], references: [id], onDelete: Cascade) - post Post @relation(fields: [postId], references: [id], onDelete: Cascade) - user User @relation(fields: [userId], references: [id], onDelete: Cascade) + gweetId String @map("gweet_id") + userId String @map("user_id") + createdAt DateTime @default(now()) @map("created_at") + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + gweet Gweet @relation(fields: [gweetId], references: [id], onDelete: Cascade) @@map("likes") } -model Comment { +model Regweet { id String @id @default(cuid()) + gweetId String @map("gweet_id") + userId String @map("user_id") createdAt DateTime @default(now()) @map("created_at") - updatedAt DateTime @default(now()) @map("updated_at") - message String - likeCount Int? @default(0) - postId String - userId String - post Post @relation(fields: [postId], references: [id], onDelete: Cascade) - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - Like Like[] - - @@map("comments") + + user User @relation(fields: [userId], references: [id]) + gweet Gweet @relation(fields: [gweetId], references: [id], onDelete: Cascade) + + @@map("regweets") +} + +model Hashtag { + id String @id @default(cuid()) + text String + hashtag String @unique + score Int @default(1) + createdAt DateTime @default(now()) @map("created_at") + + @@map("hashtags") }