From f212cfcb92f6344adf2c37649b165a9a4b422145 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Yusuf=20Akg=C3=BCl?= <s86116@bht-berlin.de>
Date: Mon, 26 Jun 2023 04:50:53 +0200
Subject: [PATCH] added regweets and media, but with error / bugs

---
 .env.example                                  |   4 +
 .../(user)/[userid]/status/[id]/page.tsx      |   2 +-
 app/api/gweets/[id]/route.ts                  |  38 +-
 app/api/gweets/likes/route.ts                 |  41 +-
 app/api/gweets/regweets/route.ts              |  56 ++
 app/api/gweets/route.ts                       |  22 +-
 components.json                               |  14 +
 components/create-gweet/api/post-gweet.ts     |  15 +-
 components/create-gweet/api/post-media.ts     |  60 ++
 .../components/create-gweet-wrapper.tsx       |   5 +-
 .../create-gweet/components/create-gweet.tsx  |  89 ++-
 .../create-gweet/hooks/use-create-gweet.ts    |  10 +-
 components/create-gweet/index.ts              |   1 +
 components/create-gweet/types/index.ts        |   5 +
 components/following-button.tsx               |  55 --
 components/following-users.tsx                |  47 --
 components/gweets/api/delete-media.ts         |  29 +
 components/gweets/api/handle-regweet.ts       |  15 +
 .../gweets/components/actions/like-button.tsx |   2 +-
 .../components/actions/regweet-button.tsx     |  41 +
 components/gweets/components/comments.tsx     |   2 +-
 .../gweets/components/delete-gweet-modal.tsx  |  29 +-
 .../gweets/components/gweet-actions.tsx       |   7 +-
 components/gweets/components/gweet-author.tsx |  22 +
 .../gweets/components/gweet-creation-date.tsx |  11 +
 .../gweets/components/gweet-details.tsx       |  91 +--
 .../gweets/components/gweet-options.tsx       |  88 +++
 components/gweets/components/gweet.tsx        |  12 +-
 .../gweets/components/infinite-gweets.tsx     |   1 -
 components/gweets/hooks/use-regweet.ts        |  26 +
 components/gweets/index.ts                    |   4 +
 components/gweets/types/index.ts              |  16 +-
 components/icons.tsx                          |  12 +-
 components/ui/button.tsx                      |   1 +
 components/ui/sheet.tsx                       | 144 ++++
 components/ui/textarea.tsx                    |   1 -
 components/ui/toast.tsx                       |  14 +-
 components/ui/toaster.tsx                     |  12 +-
 components/user-item.tsx                      |  23 -
 env.mjs                                       |   4 +
 lib/db-media.ts                               |   9 +
 package-lock.json                             | 729 ++++++++++--------
 package.json                                  |  15 +-
 prisma/schema.prisma                          | 147 ++--
 44 files changed, 1293 insertions(+), 678 deletions(-)
 create mode 100644 app/api/gweets/regweets/route.ts
 create mode 100644 components.json
 create mode 100644 components/create-gweet/api/post-media.ts
 delete mode 100644 components/following-button.tsx
 delete mode 100644 components/following-users.tsx
 create mode 100644 components/gweets/api/delete-media.ts
 create mode 100644 components/gweets/api/handle-regweet.ts
 create mode 100644 components/gweets/components/actions/regweet-button.tsx
 create mode 100644 components/gweets/components/gweet-author.tsx
 create mode 100644 components/gweets/components/gweet-creation-date.tsx
 create mode 100644 components/gweets/components/gweet-options.tsx
 create mode 100644 components/gweets/hooks/use-regweet.ts
 create mode 100644 components/ui/sheet.tsx
 delete mode 100644 components/user-item.tsx
 create mode 100644 lib/db-media.ts

diff --git a/.env.example b/.env.example
index 6736807..31a6eb7 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)/(user)/[userid]/status/[id]/page.tsx b/app/(content)/(user)/[userid]/status/[id]/page.tsx
index 0bf54b9..bb63ac5 100644
--- a/app/(content)/(user)/[userid]/status/[id]/page.tsx
+++ b/app/(content)/(user)/[userid]/status/[id]/page.tsx
@@ -1,7 +1,7 @@
 import { GweetDetails } from "@/components/gweets";
 import { GweetHeader } from "@/components/layout";
 
-export default async function TweetClientPage() {
+export default async function GweetDetailPage() {
     return (
         <div>
             <GweetHeader />
diff --git a/app/api/gweets/[id]/route.ts b/app/api/gweets/[id]/route.ts
index 067de1e..d75022f 100644
--- a/app/api/gweets/[id]/route.ts
+++ b/app/api/gweets/[id]/route.ts
@@ -26,7 +26,7 @@ export async function GET(request: Request, { params }: { params: { id: string }
         id,
       },
       include: {
-        user: true,
+        author: true,
         likes: {
           include: {
             user: {
@@ -39,6 +39,42 @@ export async function GET(request: Request, { params }: { params: { id: string }
             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",
+          },
+        },
       },
     });
 
diff --git a/app/api/gweets/likes/route.ts b/app/api/gweets/likes/route.ts
index 0ea6d20..029782d 100644
--- a/app/api/gweets/likes/route.ts
+++ b/app/api/gweets/likes/route.ts
@@ -3,7 +3,7 @@ import { z } from "zod";
 
 import { db } from "@/lib/db";
 
-// get likes
+// get likes from user
 export async function GET(request: Request) {
   const { searchParams } = new URL(request.url);
   const user_id = searchParams.get("user_id") || undefined;
@@ -31,8 +31,10 @@ export async function GET(request: Request) {
       },
 
       include: {
-        user: true,
+        author: true,
+        media: true,
         likes: true,
+        regweets: true,
         allComments: true,
       },
     });
@@ -71,12 +73,6 @@ export async function POST(request: Request) {
   }
 
   try {
-    const gweet = await db.gweet.findUnique({
-      where: {
-        id: gweet_id,
-      },
-    });
-
     const like = await db.like.findFirst({
       where: {
         gweetId: gweet_id,
@@ -91,19 +87,6 @@ export async function POST(request: Request) {
         },
       });
 
-      if (gweet && gweet.likeCount > 0)
-        await db.gweet.update({
-          where: {
-            id: gweet_id,
-          },
-
-          data: {
-            likeCount: {
-              decrement: 1,
-            },
-          },
-        });
-
       return NextResponse.json({ message: "Gweet unliked" });
     } else {
       await db.like.create({
@@ -113,20 +96,6 @@ export async function POST(request: Request) {
         },
       });
 
-      if (gweet) {
-        await db.gweet.update({
-          where: {
-            id: gweet_id,
-          },
-
-          data: {
-            likeCount: {
-              increment: 1,
-            },
-          },
-        });
-      }
-
       return NextResponse.json({ message: "Gweet liked" });
     }
   } catch (error: any) {
@@ -135,4 +104,4 @@ export async function POST(request: Request) {
       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 0000000..a7672c1
--- /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
index 4f208f9..60143cc 100644
--- a/app/api/gweets/route.ts
+++ b/app/api/gweets/route.ts
@@ -35,14 +35,13 @@ export async function GET(request: Request) {
         }),
 
         ...(type === "user_gweets" && {
-          userId: id,
+          authorId: id,
         }),
 
         ...(type === "user_replies" && {
-          userId: id,
+          authorId: id,
           NOT: {
             replyToGweetId: null,
-            replyToUserId: null,
           },
         }),
 
@@ -56,9 +55,20 @@ export async function GET(request: Request) {
       },
 
       include: {
-        user: true,
+        author: true,
         likes: true,
+        media: true,
+        regweets: true,
+
+        quote: {
+          include: {
+            author: true,
+            media: true,
+          },
+        },
+
         allComments: true,
+        allQuotes: true,
       },
 
       orderBy: {
@@ -81,9 +91,9 @@ export async function POST(request: Request) {
   const gweetSchema = z
     .object({
       content: z.string().min(1).max(280),
-      userId: z.string().cuid(),
-      replyToUserId: z.string().optional(),
+      authorId: z.string().cuid(),
       replyToGweetId: z.string().cuid().optional(),
+      quoteGweetId: z.string().cuid().optional(),
     })
     .strict();
 
diff --git a/components.json b/components.json
new file mode 100644
index 0000000..819a98d
--- /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/create-gweet/api/post-gweet.ts b/components/create-gweet/api/post-gweet.ts
index 597630c..799dc8a 100644
--- a/components/create-gweet/api/post-gweet.ts
+++ b/components/create-gweet/api/post-gweet.ts
@@ -1,23 +1,24 @@
 import { postHashtags, retrieveHashtagsFromGweet } from "@/components/trends";
+import { postMedia } from "./post-media";
 
 export const postGweet = async ({
   content,
+  files,
   userId,
-  replyToUserId,
   replyToGweetId,
+  quoteGweetId,
 }: {
   content: string;
+  files: File[];
   userId: string;
-  replyToUserId?: string | null;
   replyToGweetId?: string | null;
+  quoteGweetId?: string | null;
 }) => {
   const gweet = {
     content,
     userId,
-    // if no replyToUserId, don't send it
-    ...(replyToUserId && { replyToUserId }),
-    // if no replyToGweetId, don't send it
     ...(replyToGweetId && { replyToGweetId }),
+    ...(quoteGweetId && { quoteGweetId }),
   };
 
   try {
@@ -26,6 +27,10 @@ export const postGweet = async ({
       body: JSON.stringify(gweet)
     }).then((result) => result.json())
 
+    if (files.length > 0) {
+      await postMedia({ files, gweet_id: data.id });
+    }
+
     const hashtags = retrieveHashtagsFromGweet(content);
     if (hashtags) await postHashtags(hashtags);
 
diff --git a/components/create-gweet/api/post-media.ts b/components/create-gweet/api/post-media.ts
new file mode 100644
index 0000000..7e6676b
--- /dev/null
+++ b/components/create-gweet/api/post-media.ts
@@ -0,0 +1,60 @@
+import dbmedia from "@/lib/db-media";
+import { createId } from '@paralleldrive/cuid2';
+
+export const postMedia = async ({
+  files,
+  gweet_id,
+}: {
+  files: File[];
+  gweet_id?: string;
+}) => {
+
+  try {
+    files.forEach(async (file) => {
+      const imagePath = createId();
+
+      const { error } = await dbmedia.storage
+        .from("images")
+        .upload(`image-${imagePath}`, file);
+
+      if (error) {
+        console.log("error", error);
+        throw new Error("Failed to upload image");
+      } else {
+        const { data: mediaUrl } = dbmedia.storage
+          .from("images")
+          .getPublicUrl(`image-${imagePath}`);
+
+        const media = {
+          ...(gweet_id && { gweet_id }),
+          url: mediaUrl?.publicUrl,
+          type: "image",
+        };
+
+        await fetch('/api/media', {
+          method: 'POST',
+          body: JSON.stringify(media)
+        })
+      }
+    });
+
+    return true;
+  } catch (error: any) {
+    if (error.response) {
+      // The request was made and the server responded with a status code
+      // that falls out of the range of 2xx
+      console.log(error.response.data);
+      console.log(error.response.status);
+      console.log(error.response.headers);
+    } else if (error.request) {
+      // The request was made but no response was received
+      // `error.request` is an instance of XMLHttpRequest in the browser and an instance of
+      // http.ClientRequest in node.js
+      console.log(error.request);
+    } else {
+      // Something happened in setting up the request that triggered an Error
+      console.log("Error", error.message);
+    }
+    console.log(error.config);
+  }
+};
diff --git a/components/create-gweet/components/create-gweet-wrapper.tsx b/components/create-gweet/components/create-gweet-wrapper.tsx
index 53d1323..2c36409 100644
--- a/components/create-gweet/components/create-gweet-wrapper.tsx
+++ b/components/create-gweet/components/create-gweet-wrapper.tsx
@@ -5,10 +5,8 @@ import { useState } from "react";
 import { CreateGweet } from "./create-gweet";
 
 export const CreateGweetWrapper = ({
-  replyToUserId,
   replyToGweetId,
 }: {
-  replyToUserId: string | undefined;
   replyToGweetId: string | null;
 }) => {
   const [isComment, setIsComment] = useState(true);
@@ -16,7 +14,6 @@ export const CreateGweetWrapper = ({
   return (
     <div className="relative border-b border-border">
       <CreateGweet
-        replyToUserId={replyToUserId}
         replyToGweetId={replyToGweetId}
         placeholder="Gweet your reply"
         isComment={isComment}
@@ -31,4 +28,4 @@ export const CreateGweetWrapper = ({
       )}
     </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
index 66cf54e..fc7b359 100644
--- a/components/create-gweet/components/create-gweet.tsx
+++ b/components/create-gweet/components/create-gweet.tsx
@@ -2,7 +2,8 @@
 
 import { zodResolver } from "@hookform/resolvers/zod";
 import { useSession } from "next-auth/react";
-import { useState } from "react";
+import Image from "next/image";
+import { useRef, useState } from "react";
 import { useForm } from "react-hook-form";
 import * as z from "zod";
 
@@ -25,6 +26,7 @@ import { UserAvatar } from "@/components/user-avatar";
 
 import { Card } from "@/components/ui/card";
 import { useCreateGweet } from "../hooks/use-create-gweet";
+import { IChosenImages } from "../types";
 
 const FormSchema = z.object({
   gweet: z
@@ -35,18 +37,20 @@ const FormSchema = z.object({
 
 export const CreateGweet = ({
   parent_gweet,
-  replyToUserId,
+  quoted_gweet,
   replyToGweetId,
   placeholder,
   isComment = false,
 }: {
   parent_gweet?: IGweet | null;
-  replyToUserId?: string | null;
+  quoted_gweet?: IGweet | null;
   replyToGweetId?: string | null;
   placeholder?: string | null;
   isComment?: boolean;
 }) => {
   const [isGweetLoading, setIsGweetLoading] = useState<boolean>(false);
+  const [chosenImages, setChosenImages] = useState<IChosenImages[]>([]);
+  const imageUploadRef = useRef<HTMLInputElement>(null);
 
   const { data: session } = useSession();
   const mutation = useCreateGweet();
@@ -62,9 +66,10 @@ export const CreateGweet = ({
 
     mutation.mutate({
       content: data.gweet,
+      files: [],
       userId: session?.user?.id,
-      replyToUserId,
       replyToGweetId,
+      quoteGweetId: quoted_gweet?.id || null,
     })
 
     toast({
@@ -75,6 +80,27 @@ export const CreateGweet = ({
     form.setValue('gweet', '');
   }
 
+  const chooseImage = async (
+    event: React.ChangeEvent<HTMLInputElement>,
+    setChosenImages: (images: IChosenImages[]) => void,
+  ) => {
+    const file = event?.target?.files?.[0];
+
+    if (file) {
+      const reader = new FileReader();
+      reader.readAsDataURL(file);
+      reader.onload = () => {
+        setChosenImages([
+          ...chosenImages,
+          {
+            url: reader.result,
+            file: file,
+          },
+        ]);
+      };
+    }
+  };
+
   if (!session) return null;
 
   return (
@@ -83,7 +109,7 @@ export const CreateGweet = ({
         {parent_gweet && (
           <div className="grid place-items-center grid-rows-2 gap-1">
             <UserAvatar
-              user={{ username: parent_gweet?.user?.username, image: parent_gweet?.user?.image || null }}
+              user={{ username: parent_gweet?.author?.username, image: parent_gweet?.author?.image || null }}
             />
             <div className="bg-gray-300 h-full w-px"></div>
           </div>
@@ -93,10 +119,10 @@ export const CreateGweet = ({
             <>
               <div className="flex gap-1">
                 <span className="text-secondary text-sm font-medium truncate hover:underline">
-                  {parent_gweet?.user?.name}
+                  {parent_gweet?.author?.name}
                 </span>
                 <span className="text-tertiary text-sm truncate">
-                  @{parent_gweet?.user?.email?.split("@")[0]}
+                  @{parent_gweet?.author?.email?.split("@")[0]}
                 </span>
                 <span className="text-tertiary">·</span>
               </div>
@@ -108,11 +134,11 @@ export const CreateGweet = ({
             </>
           )}
 
-          {replyToUserId && !isComment && (
+          {parent_gweet && !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">
-                @{replyToUserId}
+                @{parent_gweet?.authorId}
               </span>
             </div>
           )}
@@ -140,7 +166,45 @@ export const CreateGweet = ({
                           disabled={isGweetLoading || !session.user}
                           {...field}
                         />
+                        <input
+                          className="hidden"
+                          type="file"
+                          onChange={(e) => chooseImage(e, setChosenImages)}
+                          ref={imageUploadRef}
+                        />
                       </FormControl>
+                      <div className={`grid object-cover grid-cols-
+                        ${chosenImages.length === 1 ? "1"
+                          : chosenImages.length === 2 ? "2 space-x-3"
+                            : chosenImages.length === 3 ? "3 space-x-3 space-y-3"
+                              : chosenImages.length === 4 ? "4 space-x-3 space-y-3"
+                                : ""
+                        }`}
+                      >
+                        {chosenImages.map((image, i) => {
+                          return (
+                            <div key={i} className="relative max-h-[700px] overflow-hidden">
+                              <Button
+                                size="sm"
+                                onClick={() => {
+                                  setChosenImages(
+                                    chosenImages.filter((img, j) => j !== i),
+                                  );
+                                }}
+                              >
+                                <Icons.close />
+                              </Button>
+                              <Image
+                                src={image.url as string}
+                                alt="gweet image"
+                                width={1000}
+                                height={1000}
+                              />
+                            </div>
+                          );
+                        })}
+                        {/* {quoted_gweet && <QuotedGweet gweet={quoted_gweet} />} */}
+                      </div>
                       {!isComment ?
                         <FormDescription className="pt-3">
                           Your gweets will be public, and everyone can see them.
@@ -152,6 +216,13 @@ export const CreateGweet = ({
                   </FormItem>
                 )}
               />
+              <Button
+                variant="ghost"
+                size="sm"
+                className="absolute bottom-3 right-3"
+                onClick={() => imageUploadRef.current?.click()}
+                disabled={isGweetLoading || !session.user}
+              />
             </Card>
 
             <div className="flex justify-end">
diff --git a/components/create-gweet/hooks/use-create-gweet.ts b/components/create-gweet/hooks/use-create-gweet.ts
index e50883b..acec8cb 100644
--- a/components/create-gweet/hooks/use-create-gweet.ts
+++ b/components/create-gweet/hooks/use-create-gweet.ts
@@ -9,20 +9,24 @@ export const useCreateGweet = () => {
   return useMutation(
     ({
       content,
+      files,
       userId,
-      replyToUserId,
       replyToGweetId,
+      quoteGweetId,
+
     }: {
       content: string;
+      files: File[];
       userId: string;
-      replyToUserId?: string | null;
       replyToGweetId?: string | null;
+      quoteGweetId?: string | null;
     }) => {
       return postGweet({
         content,
+        files,
         userId,
-        replyToUserId,
         replyToGweetId,
+        quoteGweetId,
       });
     },
     {
diff --git a/components/create-gweet/index.ts b/components/create-gweet/index.ts
index 9a73987..4d334cc 100644
--- a/components/create-gweet/index.ts
+++ b/components/create-gweet/index.ts
@@ -1,3 +1,4 @@
+export * from "./api/post-media";
 export * from "./components/create-gweet";
 export * from "./components/create-gweet-wrapper";
 export * from "./types";
diff --git a/components/create-gweet/types/index.ts b/components/create-gweet/types/index.ts
index 1b40498..0e7b1e4 100644
--- a/components/create-gweet/types/index.ts
+++ b/components/create-gweet/types/index.ts
@@ -1,3 +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
deleted file mode 100644
index 420ecdc..0000000
--- a/components/following-button.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-"use client"
-
-// import { PrismaClient } from '@prisma/client';
-import { useState } from 'react';
-import { Button } from './ui/button';
-
-// Muss in die API route
-// const prisma = new PrismaClient();
-
-// async function getFollower(userId: number, followerId: number) {
-//   const follower = await prisma.follows.findFirst({
-//     where: {
-//       followerId: followerId,
-//       followingId: userId,
-//     },
-//   });
-
-//   return follower;
-// }
-
-export default function FollowButton({ userId, followerId }: { userId: number; followerId: number }) {
-  const [isFollowing, setIsFollowing] = useState(false);
-
-  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);
-    // }
-  };
-
-  return (
-    <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 44408c1..0000000
--- 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-media.ts b/components/gweets/api/delete-media.ts
new file mode 100644
index 0000000..20f2d3a
--- /dev/null
+++ b/components/gweets/api/delete-media.ts
@@ -0,0 +1,29 @@
+import dbmedia from "@/lib/db-media";
+
+export const deleteMedia = async (media: string[]) => {
+  try {
+    const { error } = await dbmedia.storage.from("images").remove(media);
+
+    if (error) {
+      throw new Error("Failed to delete media");
+    }
+
+  } catch (error: any) {
+    if (error.response) {
+      // The request was made and the server responded with a status code
+      // that falls out of the range of 2xx
+      console.log(error.response.data);
+      console.log(error.response.status);
+      console.log(error.response.headers);
+    } else if (error.request) {
+      // The request was made but no response was received
+      // `error.request` is an instance of XMLHttpRequest in the browser and an instance of
+      // http.ClientRequest in node.js
+      console.log(error.request);
+    } else {
+      // Something happened in setting up the request that triggered an Error
+      console.log("Error", error.message);
+    }
+    console.log(error.config);
+  }
+};
\ No newline at end of file
diff --git a/components/gweets/api/handle-regweet.ts b/components/gweets/api/handle-regweet.ts
new file mode 100644
index 0000000..0607ca8
--- /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/components/actions/like-button.tsx b/components/gweets/components/actions/like-button.tsx
index cdfa89e..2c0f192 100644
--- a/components/gweets/components/actions/like-button.tsx
+++ b/components/gweets/components/actions/like-button.tsx
@@ -19,7 +19,7 @@ export const LikeButton = ({
   );
 
   const mutation = useLike({
-    gweetAuthorId: gweet?.user?.id,
+    gweetAuthorId: gweet?.author?.id,
     sessionOwnerId: session?.user?.id,
   });
 
diff --git a/components/gweets/components/actions/regweet-button.tsx b/components/gweets/components/actions/regweet-button.tsx
new file mode 100644
index 0000000..d1abfd4
--- /dev/null
+++ b/components/gweets/components/actions/regweet-button.tsx
@@ -0,0 +1,41 @@
+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 (
+    <Button
+      onClick={(e) => {
+        e.stopPropagation();
+        if (!session) {
+          return; //  TODO: show login modal
+        }
+        mutation.mutate({
+          gweetId: gweet?.id,
+          userId: session?.user?.id,
+        });
+      }}
+      variant="ghost" size="lg" className="px-6 py-3 hover:bg-green-800"
+    >
+      {hasRegweeted ?
+        <Icons.regweet className="h-5 w-5 fill-green-600 text-green-600" />
+        : <Icons.regweet className="h-5 w-5" />
+      }
+
+      <span className="pl-2">
+        {gweet && gweet?.regweets?.length > 0 && (
+          <span className="">{gweet?.regweets?.length}</span>
+        )}
+      </span>
+    </Button>
+  );
+};
\ No newline at end of file
diff --git a/components/gweets/components/comments.tsx b/components/gweets/components/comments.tsx
index 5841d8b..8cbc59d 100644
--- a/components/gweets/components/comments.tsx
+++ b/components/gweets/components/comments.tsx
@@ -21,7 +21,7 @@ export const Comments = ({ gweetId }: { gweetId: string }) => {
   });
 
   if (isLoading) {
-    return <Icons.spinner className="mr-2 h-4 w-4 animate-spin" />;
+    return <Icons.spinner className="mt-3 h-4 w-4 animate-spin align-middle" />;
   }
 
   if (isError) {
diff --git a/components/gweets/components/delete-gweet-modal.tsx b/components/gweets/components/delete-gweet-modal.tsx
index 5cc6fdf..6488b66 100644
--- a/components/gweets/components/delete-gweet-modal.tsx
+++ b/components/gweets/components/delete-gweet-modal.tsx
@@ -1,3 +1,6 @@
+"use client";
+
+import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
 import { useDeleteGweet } from "../hooks/use-delete-gweet";
 import { IGweet } from "../types";
 
@@ -12,25 +15,29 @@ import {
   DialogTrigger,
 } from "@/components/ui/dialog";
 
-export const DeleteGweetModal = ({
-  gweet,
-}: {
-  gweet: IGweet;
-}) => {
+export const DeleteGweetModal = ({ gweet, props, forwardedRef }: { gweet: IGweet, props: any, forwardedRef: any }) => {
+  const { triggerChildren, onSelect, onOpenChange, ...itemProps } = props;
   const mutation = useDeleteGweet();
 
   return (
-    <Dialog>
+    <Dialog onOpenChange={onOpenChange}>
       <DialogTrigger asChild>
-        <Button variant="outline">Delete gweet?</Button>
+        <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>
-            This can&apos;t be undone and it will be removed from your profile, the
-            timeline of any accounts that follow you, and from Twitter search
-            results.
+            You are about to delete this gweet. This can&apos;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>
@@ -44,4 +51,4 @@ export const DeleteGweetModal = ({
       </DialogContent>
     </Dialog>
   )
-}
+}
\ No newline at end of file
diff --git a/components/gweets/components/gweet-actions.tsx b/components/gweets/components/gweet-actions.tsx
index b57c5d9..e8589cd 100644
--- a/components/gweets/components/gweet-actions.tsx
+++ b/components/gweets/components/gweet-actions.tsx
@@ -2,19 +2,20 @@ 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,
-
   showStats = false,
 }: {
   gweet: IGweet;
   showStats?: boolean | undefined;
 }) => {
   return (
-    <div className=" space-x-2">
+    <div className="space-x-2">
       <CommentButton gweet={gweet} showStats={showStats} />
+      <RegweetButton gweet={gweet} />
       <LikeButton gweet={gweet} smallIcons={false} showStats={showStats} />
     </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 0000000..fb00c45
--- /dev/null
+++ b/components/gweets/components/gweet-author.tsx
@@ -0,0 +1,22 @@
+import { GweetOptions, IGweet } from "@/components/gweets";
+import { UserAvatar } from "@/components/user-avatar";
+
+export const GweetAuthor = ({ gweet }: { gweet: IGweet }) => {
+    return (
+        <div className="flex items-center">
+            <UserAvatar
+                user={{ username: gweet.author.username, image: gweet.author.image }}
+                className="h-10 w-10"
+            />
+
+            <div>
+                <span className="font-bold mr-2">{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 0000000..9c59197
--- /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>
+      <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
index d3d998d..53d75fc 100644
--- a/components/gweets/components/gweet-details.tsx
+++ b/components/gweets/components/gweet-details.tsx
@@ -1,21 +1,19 @@
 "use client"
 
-import { usePathname, useRouter } from "next/navigation";
-
 import { CreateGweetWrapper } from "@/components/create-gweet";
-import { TryAgain } from "@/components/try-again";
-
-import { useGweet } from "../hooks/use-gweet";
-
 import { Icons } from "@/components/icons";
+import { TryAgain } from "@/components/try-again";
 import { Card } from "@/components/ui/card";
-import { UserAvatar } from "@/components/user-avatar";
-import { formatTimeElapsed } from "@/lib/utils";
+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 = () => {
-    const router = useRouter();
+    // TODO use params
     const pathname = usePathname();
     const id = pathname?.split(`/`)[3] || ``;
 
@@ -33,54 +31,49 @@ export const GweetDetails = () => {
         return <>Not Found!</>
     }
 
-    console.log(`gweet`, gweet);
-
     return (
         <Card className="w-full h-full mt-12 p-2 xl:p-4 ">
-            <div className="flex">
-                {/* make gweet-author like twitter */}
-                <UserAvatar
-                    user={{ username: gweet.user.username, image: gweet.user.image }}
-                    className="h-10 w-10"
-                />
-                <div className="flex items-center">
-                    <h1 className="font-bold mr-2">{gweet.user.name}</h1>
-                    <h1 className="text-sky-500 text-sm">
-                        @{gweet.user.username}
-                    </h1>
-                </div>
+            <div className="flex flex-col space-y-2">
+                <GweetAuthor gweet={gweet} />
 
-                {/* {gweet?.replyToGweetId && (
-                    <div className="">
-                        <span className="">Replying to</span>
-                        <button
-                            onClick={(e) => {
-                                e.stopPropagation();
-                                router.push(`/${gweet?.replyToUserId}`);
-                            }}
-                            className=""
-                        >
-                            @{gweet?.replyToUserId}
-                        </button>
-                    </div>
-                )} */}
+                {/* TODO needs handling of all gweets above and under the gweet */}
 
-                {gweet?.content && <h1>{gweet?.content}</h1>}
+                <div>
+                    {gweet.content && <h1>{gweet.content}</h1>}
 
-                {/* make gweet creation date like twitter */}
-                {formatTimeElapsed(gweet.createdAt)}
-                <div className="">
-                    <GweetActions gweet={gweet} />
+                    {gweet.media && gweet.media.length > 0 && (
+                        <div className={`grid object-cover grid-cols-
+                            ${gweet.media.length === 1 ? "1"
+                                : gweet.media.length === 2 ? "2 space-x-3"
+                                    : gweet.media.length === 3 ? "3 space-x-3 space-y-3"
+                                        : gweet.media.length === 4 ? "4 space-x-3 space-y-3"
+                                            : ""
+                            }`}
+                        >
+                            {gweet.media.slice(0, 4).map((media) => {
+                                return (
+                                    <Image
+                                        key={media.id}
+                                        src={media.url}
+                                        alt={"image-" + media.id}
+                                        width={1000}
+                                        height={1000}
+                                    />
+                                );
+                            })}
+                        </div>
+                    )}
+
+                    {/* TODO */}
+                    {/* {gweet?.quotedGweet && <QuotedGweet gweet={gweet?.quotedGweet} />} */}
                 </div>
-            </div>
 
-            <CreateGweetWrapper
-                replyToUserId={gweet?.user?.email?.split(`@`)[0]}
-                replyToGweetId={gweet?.id}
-            />
-            <div className="">
-                <Comments gweetId={gweet?.id} />
+                <GweetCreationDate date={gweet.createdAt} />
+                <GweetActions gweet={gweet} />
             </div>
+
+            <CreateGweetWrapper replyToGweetId={gweet?.id} />
+            <Comments gweetId={gweet?.id} />
         </Card>
     );
 };
\ 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 0000000..c975c34
--- /dev/null
+++ b/components/gweets/components/gweet-options.tsx
@@ -0,0 +1,88 @@
+"use client";
+
+import { IGweet } from "@/components/gweets";
+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 { useSession } from "next-auth/react";
+import { useRef, useState } from "react";
+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 = `${env.NEXT_PUBLIC_APP_URL}/status/${gweet?.id}`;
+
+    return (
+        <DropdownMenu open={dropdownOpen} onOpenChange={setDropdownOpen}>
+            <DropdownMenuTrigger asChild>
+                <Button variant="ghost" size="icon" ref={dropdownTriggerRef}>
+                    <Icons.moreOptions />
+                </Button>
+            </DropdownMenuTrigger>
+            <DropdownMenuContent
+                className="w-56 font-bold cursor-pointer"
+                hidden={hasOpenDialog}
+                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">
+                                        <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
index e4aad6a..6c538da 100644
--- a/components/gweets/components/gweet.tsx
+++ b/components/gweets/components/gweet.tsx
@@ -13,19 +13,19 @@ export const Gweet = ({ gweet }: { gweet: IGweet }) => {
     <Button
       variant="ghost"
       tabIndex={0}
-      onClick={() => router.push(`/${gweet.user.username}/status/${gweet.id}`)}
+      onClick={() => router.push(`/${gweet.author.username}/status/${gweet.id}`)}
       className="flex h-auto w-full text-left"
     >
       <UserAvatar
-        user={{ username: gweet.user.username, image: gweet.user.image }}
+        user={{ username: gweet.author.username, image: gweet.author.image }}
         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">{gweet.user.name}</h1>
+            <h1 className="font-bold mr-2">{gweet.author.name}</h1>
             <h1 className="text-sky-500 text-sm">
-              @{gweet.user.username}
+              @{gweet.author.username}
             </h1>
             <h1 className="text-gray-500 text-sm ml-auto">
               {formatTimeElapsed(gweet.createdAt)}
@@ -39,11 +39,11 @@ export const Gweet = ({ gweet }: { gweet: IGweet }) => {
                 <button
                   onClick={(e) => {
                     e.stopPropagation();
-                    router.push(`/${gweet?.replyToUserId}`);
+                    router.push(`/${gweet?.author.username}`);
                   }}
                   className=""
                 >
-                  @{gweet?.replyToUserId}
+                  @{gweet?.author.username}
                 </button>
               </div>
             )}
diff --git a/components/gweets/components/infinite-gweets.tsx b/components/gweets/components/infinite-gweets.tsx
index b96c79e..e923d87 100644
--- a/components/gweets/components/infinite-gweets.tsx
+++ b/components/gweets/components/infinite-gweets.tsx
@@ -35,7 +35,6 @@ export const InfiniteGweets = ({
       {isSuccess &&
         gweets?.pages?.map((page) => {
           return page?.gweets?.map((gweet, index) => (
-            console.log(gweet.userId),
             <div
               ref={index === page.gweets.length - 4 ? ref : undefined}
               key={gweet.id}
diff --git a/components/gweets/hooks/use-regweet.ts b/components/gweets/hooks/use-regweet.ts
new file mode 100644
index 0000000..f46cb4d
--- /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/index.ts b/components/gweets/index.ts
index 412a93c..fb59a41 100644
--- a/components/gweets/index.ts
+++ b/components/gweets/index.ts
@@ -1,6 +1,10 @@
 export * from "./components/gweet";
 export * from "./components/gweet-details";
+export * from "./components/gweet-options";
+// export * from "./components/gweet-quotes";
 export * from "./components/gweets";
 export * from "./components/infinite-gweets";
+// export * from "./components/inspect-gweet-image-modal";
+// export * from "./components/quoted-gweet";
 export * from "./hooks/use-gweets";
 export * from "./types";
diff --git a/components/gweets/types/index.ts b/components/gweets/types/index.ts
index a385f50..ca430e7 100644
--- a/components/gweets/types/index.ts
+++ b/components/gweets/types/index.ts
@@ -1,4 +1,4 @@
-import { Gweet, Like } from "@prisma/client";
+import { Gweet, Like, Media, Regweet } from "@prisma/client";
 
 import { IUser } from "@/components/profile";
 
@@ -7,8 +7,12 @@ export interface IFeed {
 }
 
 export interface IGweet extends Gweet {
-  user: IUser;
+  author: IUser;
+  quotedGweet: IGweet;
   likes: ILike[];
+  media: IMedia[];
+  regweets: IRegweet[];
+  quotes: IGweet[];
   comments: IGweet[];
 }
 
@@ -17,6 +21,14 @@ export interface ILike extends Like {
   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/icons.tsx b/components/icons.tsx
index cdacdf5..35f194b 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/ui/button.tsx b/components/ui/button.tsx
index 7419a7f..c898032 100644
--- a/components/ui/button.tsx
+++ b/components/ui/button.tsx
@@ -23,6 +23,7 @@ const buttonVariants = cva(
         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",
       },
     },
     defaultVariants: {
diff --git a/components/ui/sheet.tsx b/components/ui/sheet.tsx
new file mode 100644
index 0000000..98cdd40
--- /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 8e7672b..dc548f3 100644
--- a/components/ui/textarea.tsx
+++ b/components/ui/textarea.tsx
@@ -22,4 +22,3 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
 Textarea.displayName = "Textarea"
 
 export { Textarea }
-
diff --git a/components/ui/toast.tsx b/components/ui/toast.tsx
index f820588..32ffed0 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 ac9370c..4414b1c 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-item.tsx b/components/user-item.tsx
deleted file mode 100644
index 64c729d..0000000
--- 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/env.mjs b/env.mjs
index 0154d5d..2e2b1ee 100644
--- a/env.mjs
+++ b/env.mjs
@@ -16,6 +16,8 @@ export const env = createEnv({
     },
     client: {
         NEXT_PUBLIC_APP_URL: z.string().min(1),
+        NEXT_PUBLIC_SUPABASE_URL: z.string().min(1),
+        NEXT_PUBLIC_SUPABASE_ANON_KEY: z.string().min(1),
     },
     runtimeEnv: {
         DATABASE_URL: process.env.DATABASE_URL,
@@ -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,
+        NEXT_PUBLIC_SUPABASE_URL: process.env.NEXT_PUBLIC_SUPABASE_URL,
+        NEXT_PUBLIC_SUPABASE_ANON_KEY: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
         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/lib/db-media.ts b/lib/db-media.ts
new file mode 100644
index 0000000..d08c18c
--- /dev/null
+++ b/lib/db-media.ts
@@ -0,0 +1,9 @@
+import { createClient } from "@supabase/supabase-js";
+
+const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL as string;
+const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY as string;
+const supabase = createClient(supabaseUrl, supabaseKey)
+
+const dbmedia = supabase
+
+export default dbmedia
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 9a4e70b..84a56ec 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,7 +10,8 @@
             "dependencies": {
                 "@auth/prisma-adapter": "^1.0.0",
                 "@hookform/resolvers": "^3.1.1",
-                "@prisma/client": "^4.16.0",
+                "@paralleldrive/cuid2": "^2.2.1",
+                "@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",
@@ -22,12 +23,14 @@
                 "@radix-ui/react-separator": "^1.0.3",
                 "@radix-ui/react-slot": "^1.0.2",
                 "@radix-ui/react-toast": "^1.1.4",
+                "@supabase/supabase-js": "^2.26.0",
                 "@t3-oss/env-nextjs": "^0.4.1",
                 "@tanstack/react-query": "^4.29.15",
                 "bcrypt": "^5.1.0",
                 "class-variance-authority": "^0.6.0",
                 "clsx": "^1.2.1",
-                "lucide-react": "^0.248.0",
+                "dayjs": "^1.11.8",
+                "lucide-react": "^0.252.0",
                 "next": "^13.4.7",
                 "next-auth": "^4.22.1",
                 "next-themes": "^0.2.1",
@@ -45,13 +48,13 @@
                 "@tanstack/eslint-plugin-query": "^4.29.9",
                 "@types/bcrypt": "^5.0.0",
                 "@types/node": "^20.3.1",
-                "@types/react": "^18.2.13",
+                "@types/react": "^18.2.14",
                 "@types/react-dom": "^18.2.6",
                 "autoprefixer": "10.4.14",
                 "eslint": "^8.43.0",
                 "eslint-config-next": "^13.4.7",
                 "postcss": "8.4.24",
-                "prisma": "^4.16.0",
+                "prisma": "^4.16.1",
                 "tailwindcss": "3.3.2",
                 "typescript": "^5.1.3"
             }
@@ -88,26 +91,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",
@@ -120,9 +103,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"
             },
@@ -187,24 +170,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",
@@ -468,6 +451,17 @@
                 "node": ">= 10"
             }
         },
+        "node_modules/@noble/hashes": {
+            "version": "1.3.1",
+            "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz",
+            "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==",
+            "engines": {
+                "node": ">= 16"
+            },
+            "funding": {
+                "url": "https://paulmillr.com/funding/"
+            }
+        },
         "node_modules/@nodelib/fs.scandir": {
             "version": "2.1.5",
             "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -508,10 +502,18 @@
                 "url": "https://github.com/sponsors/panva"
             }
         },
+        "node_modules/@paralleldrive/cuid2": {
+            "version": "2.2.1",
+            "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.1.tgz",
+            "integrity": "sha512-GJhHYlMhyT2gWemDL7BGMWfTNhspJKkikLKh9wAy3z4GTTINvTYALkUd+eGQK7aLeVkVzPuSA0VCT3H5eEWbbw==",
+            "dependencies": {
+                "@noble/hashes": "^1.1.5"
+            }
+        },
         "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",
@@ -529,9 +531,9 @@
             }
         },
         "node_modules/@prisma/client": {
-            "version": "4.16.0",
-            "resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.16.0.tgz",
-            "integrity": "sha512-CBD+5IdZPiavhLkQokvsz1uz4r9ppixaqY/ajybWs4WXNnsDVMBKEqN3BiPzpSo79jiy22VKj/67pqt4VwIg9w==",
+            "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.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c"
@@ -549,9 +551,9 @@
             }
         },
         "node_modules/@prisma/engines": {
-            "version": "4.16.0",
-            "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.16.0.tgz",
-            "integrity": "sha512-M6XoMRXnqL0rqZGQS8ZpNiHYG4G1fKBdoqW/oBtHnr1in5UYgerZqal3CXchmd6OBD/770PE9dtjQuqcilZJUA==",
+            "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
         },
@@ -1408,11 +1410,66 @@
             }
         },
         "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/@supabase/functions-js": {
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.1.2.tgz",
+            "integrity": "sha512-QCR6pwJs9exCl37bmpMisUd6mf+0SUBJ6mUpiAjEkSJ/+xW8TCuO14bvkWHADd5hElJK9MxNlMQXxSA4DRz9nQ==",
+            "dependencies": {
+                "cross-fetch": "^3.1.5"
+            }
+        },
+        "node_modules/@supabase/gotrue-js": {
+            "version": "2.31.0",
+            "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.31.0.tgz",
+            "integrity": "sha512-YcwlbbNfedlue/HVIXtYBb4fuOrs29gNOTl6AmyxPp4zryRxzFvslVN9kmLDBRUAVU9fnPJh2bgOR3chRjJX5w==",
+            "dependencies": {
+                "cross-fetch": "^3.1.5"
+            }
+        },
+        "node_modules/@supabase/postgrest-js": {
+            "version": "1.7.1",
+            "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.7.1.tgz",
+            "integrity": "sha512-xPRYLaZrkLbXNlzmHW6Wtf9hmcBLjjI5xUz2zj8oE2hgXGaYoZBBkpN9bmW9i17Z1f6Ujxa942AqK439XOA36A==",
+            "dependencies": {
+                "cross-fetch": "^3.1.5"
+            }
+        },
+        "node_modules/@supabase/realtime-js": {
+            "version": "2.7.3",
+            "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.7.3.tgz",
+            "integrity": "sha512-c7TzL81sx2kqyxsxcDduJcHL9KJdCOoKimGP6lQSqiZKX42ATlBZpWbyy9KFGFBjAP4nyopMf5JhPi2ZH9jyNw==",
+            "dependencies": {
+                "@types/phoenix": "^1.5.4",
+                "@types/websocket": "^1.0.3",
+                "websocket": "^1.0.34"
+            }
+        },
+        "node_modules/@supabase/storage-js": {
+            "version": "2.5.1",
+            "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.5.1.tgz",
+            "integrity": "sha512-nkR0fQA9ScAtIKA3vNoPEqbZv1k5B5HVRYEvRWdlP6mUpFphM9TwPL2jZ/ztNGMTG5xT6SrHr+H7Ykz8qzbhjw==",
+            "dependencies": {
+                "cross-fetch": "^3.1.5"
+            }
+        },
+        "node_modules/@supabase/supabase-js": {
+            "version": "2.26.0",
+            "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.26.0.tgz",
+            "integrity": "sha512-RXmTPTobaYAwkSobadHZmEVLmzX3SGrtRZIGfLWnLv92VzBRrjuXn0a+bJqKl50GUzsyqPA+j5pod7EwMkcH5A==",
+            "dependencies": {
+                "@supabase/functions-js": "^2.1.0",
+                "@supabase/gotrue-js": "^2.31.0",
+                "@supabase/postgrest-js": "^1.7.0",
+                "@supabase/realtime-js": "^2.7.3",
+                "@supabase/storage-js": "^2.5.1",
+                "cross-fetch": "^3.1.5"
+            }
+        },
         "node_modules/@swc/helpers": {
             "version": "0.5.1",
             "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.1.tgz",
@@ -1453,20 +1510,21 @@
             }
         },
         "node_modules/@tanstack/query-core": {
-            "version": "4.29.15",
-            "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.29.15.tgz",
-            "integrity": "sha512-Recc1d5rjHesKhzlH3Aw66v+vQxtB9OHEXP/vxgEcEJ0DwEpfe3EQ4id20vuBJHY2XRjfgWGmUs6ZgK6PSsTXA==",
+            "version": "4.29.17",
+            "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.29.17.tgz",
+            "integrity": "sha512-iDbO8yZOpm1lqgq6L8mpxGbKaoiyZSjthxEB3WGU7mNPYss9q4H3Q67+e2xXGwkemEVmtEX/WwvtFitrvVU8TA==",
             "funding": {
                 "type": "github",
                 "url": "https://github.com/sponsors/tannerlinsley"
             }
         },
         "node_modules/@tanstack/react-query": {
-            "version": "4.29.15",
-            "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.29.15.tgz",
-            "integrity": "sha512-1zDkv95ljuJ623hhbYU8YIprPW2x6774kh3IQNEuZav62+S+Zr26uUOrE2zGRp9I1uO5Liw/0uYB3dWXQP5+3Q==",
+            "version": "4.29.17",
+            "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.29.17.tgz",
+            "integrity": "sha512-udOy/jgqiBHBAP93YPAU3QoVYO+Rtx9HT/10xGDQzC8iQU/wIxcIaT/usX+1NSzoUFYU5hUcPaNErPWZnR7XgA==",
             "dependencies": {
-                "@tanstack/query-core": "4.29.15",
+                "@tanstack/query-core": "4.29.17",
+                "client-only": "0.0.1",
                 "use-sync-external-store": "^1.2.0"
             },
             "funding": {
@@ -1505,8 +1563,12 @@
         "node_modules/@types/node": {
             "version": "20.3.1",
             "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.1.tgz",
-            "integrity": "sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==",
-            "dev": true
+            "integrity": "sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg=="
+        },
+        "node_modules/@types/phoenix": {
+            "version": "1.6.0",
+            "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.0.tgz",
+            "integrity": "sha512-qwfpsHmFuhAS/dVd4uBIraMxRd56vwBUYQGZ6GpXnFuM2XMRFJbIyruFKKlW2daQliuYZwe0qfn/UjFCDKic5g=="
         },
         "node_modules/@types/prop-types": {
             "version": "15.7.5",
@@ -1515,9 +1577,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": "*",
@@ -1540,15 +1602,23 @@
             "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==",
             "devOptional": true
         },
+        "node_modules/@types/websocket": {
+            "version": "1.0.5",
+            "resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.5.tgz",
+            "integrity": "sha512-NbsqiNX9CnEfC1Z0Vf4mE1SgAJ07JnRYcNex7AJ9zAVzmiGHmjKFEk7O4TJIsgv2B1sLEb6owKFZrACwdYngsQ==",
+            "dependencies": {
+                "@types/node": "*"
+            }
+        },
         "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.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.60.0.tgz",
+            "integrity": "sha512-jBONcBsDJ9UoTWrARkRRCgDz6wUggmH5RpQVlt7BimSwaTkTjwypGzKORXbR4/2Hqjk9hgwlon2rVQAjWNpkyQ==",
             "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.0",
+                "@typescript-eslint/types": "5.60.0",
+                "@typescript-eslint/typescript-estree": "5.60.0",
                 "debug": "^4.3.4"
             },
             "engines": {
@@ -1568,13 +1638,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.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.60.0.tgz",
+            "integrity": "sha512-hakuzcxPwXi2ihf9WQu1BbRj1e/Pd8ZZwVTG9kfbxAMZstKz8/9OoexIwnmLzShtsdap5U/CoQGRCWlSuPbYxQ==",
             "dev": true,
             "dependencies": {
-                "@typescript-eslint/types": "5.59.7",
-                "@typescript-eslint/visitor-keys": "5.59.7"
+                "@typescript-eslint/types": "5.60.0",
+                "@typescript-eslint/visitor-keys": "5.60.0"
             },
             "engines": {
                 "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -1585,9 +1655,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.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.60.0.tgz",
+            "integrity": "sha512-ascOuoCpNZBccFVNJRSC6rPq4EmJ2NkuoKnd6LDNyAQmdDnziAtxbCGWCbefG1CNzmDvd05zO36AmB7H8RzKPA==",
             "dev": true,
             "engines": {
                 "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -1598,13 +1668,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.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.60.0.tgz",
+            "integrity": "sha512-R43thAuwarC99SnvrBmh26tc7F6sPa2B3evkXp/8q954kYL6Ro56AwASYWtEEi+4j09GbiNAHqYwNNZuNlARGQ==",
             "dev": true,
             "dependencies": {
-                "@typescript-eslint/types": "5.59.7",
-                "@typescript-eslint/visitor-keys": "5.59.7",
+                "@typescript-eslint/types": "5.60.0",
+                "@typescript-eslint/visitor-keys": "5.60.0",
                 "debug": "^4.3.4",
                 "globby": "^11.1.0",
                 "is-glob": "^4.0.3",
@@ -1625,12 +1695,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.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.60.0.tgz",
+            "integrity": "sha512-wm9Uz71SbCyhUKgcaPRauBdTegUyY/ZWl8gLwD/i/ybJqscrrdVSFImpvUz16BLPChIeKBK5Fa9s6KDQjsjyWw==",
             "dev": true,
             "dependencies": {
-                "@typescript-eslint/types": "5.59.7",
+                "@typescript-eslint/types": "5.60.0",
                 "eslint-visitor-keys": "^3.3.0"
             },
             "engines": {
@@ -1647,9 +1717,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"
@@ -1774,12 +1844,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": {
@@ -1924,21 +1994,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/balanced-match": {
@@ -2009,9 +2079,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": [
                 {
@@ -2021,13 +2091,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"
@@ -2036,6 +2110,18 @@
                 "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
             }
         },
+        "node_modules/bufferutil": {
+            "version": "4.0.7",
+            "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz",
+            "integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==",
+            "hasInstallScript": true,
+            "dependencies": {
+                "node-gyp-build": "^4.3.0"
+            },
+            "engines": {
+                "node": ">=6.14.2"
+            }
+        },
         "node_modules/bundle-name": {
             "version": "3.0.0",
             "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz",
@@ -2093,9 +2179,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.30001508",
+            "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001508.tgz",
+            "integrity": "sha512-sdQZOJdmt3GJs1UMNpCCCyeuS2IEGLXnHyAo9yIO5JJDjbjoVRij4M1qep6P6gFpptD1PqIYgzM+gwJbOi92mw==",
             "funding": [
                 {
                     "type": "opencollective",
@@ -2256,6 +2342,14 @@
                 "node": ">= 0.6"
             }
         },
+        "node_modules/cross-fetch": {
+            "version": "3.1.6",
+            "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz",
+            "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==",
+            "dependencies": {
+                "node-fetch": "^2.6.11"
+            }
+        },
         "node_modules/cross-spawn": {
             "version": "7.0.3",
             "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -2287,12 +2381,26 @@
             "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==",
             "devOptional": true
         },
+        "node_modules/d": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
+            "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
+            "dependencies": {
+                "es5-ext": "^0.10.50",
+                "type": "^1.0.1"
+            }
+        },
         "node_modules/damerau-levenshtein": {
             "version": "1.0.8",
             "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
             "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
             "dev": true
         },
+        "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",
             "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -2309,35 +2417,6 @@
                 }
             }
         },
-        "node_modules/deep-equal": {
-            "version": "2.2.1",
-            "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.1.tgz",
-            "integrity": "sha512-lKdkdV6EOGoVn65XaOsPdH4rMxTZOnmFyuIkMjM1i5HHCbfjC97dawgTAy0deYNfuqUqW+Q5VrVaQYtUpSd6yQ==",
-            "dev": true,
-            "dependencies": {
-                "array-buffer-byte-length": "^1.0.0",
-                "call-bind": "^1.0.2",
-                "es-get-iterator": "^1.1.3",
-                "get-intrinsic": "^1.2.0",
-                "is-arguments": "^1.1.1",
-                "is-array-buffer": "^3.0.2",
-                "is-date-object": "^1.0.5",
-                "is-regex": "^1.1.4",
-                "is-shared-array-buffer": "^1.0.2",
-                "isarray": "^2.0.5",
-                "object-is": "^1.1.5",
-                "object-keys": "^1.1.1",
-                "object.assign": "^4.1.4",
-                "regexp.prototype.flags": "^1.5.0",
-                "side-channel": "^1.0.4",
-                "which-boxed-primitive": "^1.0.2",
-                "which-collection": "^1.0.1",
-                "which-typed-array": "^1.1.9"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/ljharb"
-            }
-        },
         "node_modules/deep-is": {
             "version": "0.1.4",
             "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -2411,6 +2490,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",
@@ -2459,9 +2547,9 @@
             }
         },
         "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.440",
+            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.440.tgz",
+            "integrity": "sha512-r6dCgNpRhPwiWlxbHzZQ/d9swfPaEJGi8ekqRBwQYaR3WmA5VkqQfBWSDDjuJU1ntO+W9tHx8OHV/96Q8e0dVw==",
             "dev": true
         },
         "node_modules/emoji-regex": {
@@ -2471,9 +2559,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",
@@ -2531,26 +2619,6 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
-        "node_modules/es-get-iterator": {
-            "version": "1.1.3",
-            "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz",
-            "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==",
-            "dev": true,
-            "dependencies": {
-                "call-bind": "^1.0.2",
-                "get-intrinsic": "^1.1.3",
-                "has-symbols": "^1.0.3",
-                "is-arguments": "^1.1.1",
-                "is-map": "^2.0.2",
-                "is-set": "^2.0.2",
-                "is-string": "^1.0.7",
-                "isarray": "^2.0.5",
-                "stop-iteration-iterator": "^1.0.0"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/ljharb"
-            }
-        },
         "node_modules/es-set-tostringtag": {
             "version": "2.0.1",
             "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz",
@@ -2591,6 +2659,39 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
+        "node_modules/es5-ext": {
+            "version": "0.10.62",
+            "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz",
+            "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==",
+            "hasInstallScript": true,
+            "dependencies": {
+                "es6-iterator": "^2.0.3",
+                "es6-symbol": "^3.1.3",
+                "next-tick": "^1.1.0"
+            },
+            "engines": {
+                "node": ">=0.10"
+            }
+        },
+        "node_modules/es6-iterator": {
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
+            "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==",
+            "dependencies": {
+                "d": "1",
+                "es5-ext": "^0.10.35",
+                "es6-symbol": "^3.1.1"
+            }
+        },
+        "node_modules/es6-symbol": {
+            "version": "3.1.3",
+            "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz",
+            "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==",
+            "dependencies": {
+                "d": "^1.0.1",
+                "ext": "^1.1.2"
+            }
+        },
         "node_modules/escalade": {
             "version": "3.1.1",
             "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@@ -2741,9 +2842,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",
@@ -3084,6 +3185,19 @@
                 "url": "https://github.com/sindresorhus/execa?sponsor=1"
             }
         },
+        "node_modules/ext": {
+            "version": "1.7.0",
+            "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz",
+            "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==",
+            "dependencies": {
+                "type": "^2.7.2"
+            }
+        },
+        "node_modules/ext/node_modules/type": {
+            "version": "2.7.2",
+            "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz",
+            "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw=="
+        },
         "node_modules/fast-deep-equal": {
             "version": "3.1.3",
             "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -3359,10 +3473,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.0",
+            "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.6.0.tgz",
+            "integrity": "sha512-lgbo68hHTQnFddybKbbs/RDRJnJT5YyGy2kQzVwbq+g67X73i+5MVTval34QxGkOe9X5Ujf1UYpCaphLyltjEg==",
             "dev": true,
+            "dependencies": {
+                "resolve-pkg-maps": "^1.0.0"
+            },
             "funding": {
                 "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
             }
@@ -3651,22 +3768,6 @@
                 "loose-envify": "^1.0.0"
             }
         },
-        "node_modules/is-arguments": {
-            "version": "1.1.1",
-            "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
-            "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==",
-            "dev": true,
-            "dependencies": {
-                "call-bind": "^1.0.2",
-                "has-tostringtag": "^1.0.0"
-            },
-            "engines": {
-                "node": ">= 0.4"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/ljharb"
-            }
-        },
         "node_modules/is-array-buffer": {
             "version": "3.0.2",
             "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz",
@@ -3818,15 +3919,6 @@
                 "url": "https://github.com/sponsors/sindresorhus"
             }
         },
-        "node_modules/is-map": {
-            "version": "2.0.2",
-            "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz",
-            "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==",
-            "dev": true,
-            "funding": {
-                "url": "https://github.com/sponsors/ljharb"
-            }
-        },
         "node_modules/is-negative-zero": {
             "version": "2.0.2",
             "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
@@ -3887,15 +3979,6 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
-        "node_modules/is-set": {
-            "version": "2.0.2",
-            "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz",
-            "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==",
-            "dev": true,
-            "funding": {
-                "url": "https://github.com/sponsors/ljharb"
-            }
-        },
         "node_modules/is-shared-array-buffer": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
@@ -3969,14 +4052,10 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
-        "node_modules/is-weakmap": {
-            "version": "2.0.1",
-            "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz",
-            "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==",
-            "dev": true,
-            "funding": {
-                "url": "https://github.com/sponsors/ljharb"
-            }
+        "node_modules/is-typedarray": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+            "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="
         },
         "node_modules/is-weakref": {
             "version": "1.0.2",
@@ -3990,19 +4069,6 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
-        "node_modules/is-weakset": {
-            "version": "2.0.2",
-            "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz",
-            "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==",
-            "dev": true,
-            "dependencies": {
-                "call-bind": "^1.0.2",
-                "get-intrinsic": "^1.1.1"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/ljharb"
-            }
-        },
         "node_modules/is-wsl": {
             "version": "2.2.0",
             "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
@@ -4030,12 +4096,6 @@
                 "url": "https://github.com/sponsors/sindresorhus"
             }
         },
-        "node_modules/isarray": {
-            "version": "2.0.5",
-            "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
-            "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
-            "dev": true
-        },
         "node_modules/isexe": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -4197,9 +4257,9 @@
             }
         },
         "node_modules/lucide-react": {
-            "version": "0.248.0",
-            "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.248.0.tgz",
-            "integrity": "sha512-zMqVvwsvhLV6ooekfM8HEngR/lLGkIgOwZ4X140K+CDdAPwfp9LARnmJhMF32wlDganAIVEgN7saIv3pcCLUVw==",
+            "version": "0.252.0",
+            "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.252.0.tgz",
+            "integrity": "sha512-98hUdm23F3YlC3UN4mzv1FAsWr81YYdxF31cYhm19c51FwOph4dn5B4NjKp45UXBiR1Xx+cKrdmSIZX0ldS8zw==",
             "peerDependencies": {
                 "react": "^16.5.1 || ^17.0.0 || ^18.0.0"
             }
@@ -4451,6 +4511,11 @@
                 "react-dom": "*"
             }
         },
+        "node_modules/next-tick": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
+            "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="
+        },
         "node_modules/next/node_modules/postcss": {
             "version": "8.4.14",
             "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
@@ -4498,10 +4563,20 @@
                 }
             }
         },
+        "node_modules/node-gyp-build": {
+            "version": "4.6.0",
+            "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz",
+            "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==",
+            "bin": {
+                "node-gyp-build": "bin.js",
+                "node-gyp-build-optional": "optional.js",
+                "node-gyp-build-test": "build-test.js"
+            }
+        },
         "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": {
@@ -4610,9 +4685,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"
             }
@@ -4626,22 +4701,6 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
-        "node_modules/object-is": {
-            "version": "1.1.5",
-            "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz",
-            "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==",
-            "dev": true,
-            "dependencies": {
-                "call-bind": "^1.0.2",
-                "define-properties": "^1.1.3"
-            },
-            "engines": {
-                "node": ">= 0.4"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/ljharb"
-            }
-        },
         "node_modules/object-keys": {
             "version": "1.1.1",
             "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
@@ -4793,14 +4852,6 @@
                 "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==",
-            "engines": {
-                "node": ">= 6"
-            }
-        },
         "node_modules/optionator": {
             "version": "0.9.1",
             "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
@@ -4925,9 +4976,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"
             }
@@ -5057,18 +5108,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"
             },
@@ -5091,13 +5142,13 @@
             "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew=="
         },
         "node_modules/prisma": {
-            "version": "4.16.0",
-            "resolved": "https://registry.npmjs.org/prisma/-/prisma-4.16.0.tgz",
-            "integrity": "sha512-kSCwbTm3LCephyGfZMJYqBXpPJXdJStg5xwfzeFmR5C05zfkOURK9pQpJF6uUQvFWm3lI9ZMSNkObmFkAPnB+g==",
+            "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.16.0"
+                "@prisma/engines": "4.16.1"
             },
             "bin": {
                 "prisma": "build/index.js",
@@ -5355,6 +5406,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/reusify": {
             "version": "1.0.4",
             "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
@@ -5546,9 +5606,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"
             },
@@ -5621,18 +5681,6 @@
                 "node": ">=0.10.0"
             }
         },
-        "node_modules/stop-iteration-iterator": {
-            "version": "1.0.0",
-            "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz",
-            "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==",
-            "dev": true,
-            "dependencies": {
-                "internal-slot": "^1.0.4"
-            },
-            "engines": {
-                "node": ">= 0.4"
-            }
-        },
         "node_modules/streamsearch": {
             "version": "1.1.0",
             "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
@@ -5930,6 +5978,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",
@@ -6034,9 +6090,9 @@
             }
         },
         "node_modules/tslib": {
-            "version": "2.5.2",
-            "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.2.tgz",
-            "integrity": "sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA=="
+            "version": "2.5.3",
+            "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz",
+            "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w=="
         },
         "node_modules/tsutils": {
             "version": "3.21.0",
@@ -6059,6 +6115,11 @@
             "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
             "dev": true
         },
+        "node_modules/type": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
+            "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg=="
+        },
         "node_modules/type-check": {
             "version": "0.4.0",
             "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -6097,6 +6158,14 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
+        "node_modules/typedarray-to-buffer": {
+            "version": "3.1.5",
+            "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
+            "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
+            "dependencies": {
+                "is-typedarray": "^1.0.0"
+            }
+        },
         "node_modules/typescript": {
             "version": "5.1.3",
             "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz",
@@ -6221,6 +6290,18 @@
                 "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
             }
         },
+        "node_modules/utf-8-validate": {
+            "version": "5.0.10",
+            "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
+            "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==",
+            "hasInstallScript": true,
+            "dependencies": {
+                "node-gyp-build": "^4.3.0"
+            },
+            "engines": {
+                "node": ">=6.14.2"
+            }
+        },
         "node_modules/util-deprecate": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -6251,6 +6332,35 @@
             "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
             "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
         },
+        "node_modules/websocket": {
+            "version": "1.0.34",
+            "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz",
+            "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==",
+            "dependencies": {
+                "bufferutil": "^4.0.1",
+                "debug": "^2.2.0",
+                "es5-ext": "^0.10.50",
+                "typedarray-to-buffer": "^3.1.5",
+                "utf-8-validate": "^5.0.2",
+                "yaeti": "^0.0.6"
+            },
+            "engines": {
+                "node": ">=4.0.0"
+            }
+        },
+        "node_modules/websocket/node_modules/debug": {
+            "version": "2.6.9",
+            "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+            "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+            "dependencies": {
+                "ms": "2.0.0"
+            }
+        },
+        "node_modules/websocket/node_modules/ms": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+            "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+        },
         "node_modules/whatwg-url": {
             "version": "5.0.0",
             "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
@@ -6291,21 +6401,6 @@
                 "url": "https://github.com/sponsors/ljharb"
             }
         },
-        "node_modules/which-collection": {
-            "version": "1.0.1",
-            "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz",
-            "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==",
-            "dev": true,
-            "dependencies": {
-                "is-map": "^2.0.1",
-                "is-set": "^2.0.1",
-                "is-weakmap": "^2.0.1",
-                "is-weakset": "^2.0.1"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/ljharb"
-            }
-        },
         "node_modules/which-typed-array": {
             "version": "1.1.9",
             "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz",
@@ -6348,15 +6443,23 @@
             "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
             "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
         },
+        "node_modules/yaeti": {
+            "version": "0.0.6",
+            "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz",
+            "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==",
+            "engines": {
+                "node": ">=0.10.32"
+            }
+        },
         "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/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 0353145..54fe4a7 100644
--- a/package.json
+++ b/package.json
@@ -8,13 +8,14 @@
         "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"
     },
     "dependencies": {
         "@auth/prisma-adapter": "^1.0.0",
         "@hookform/resolvers": "^3.1.1",
-        "@prisma/client": "^4.16.0",
+        "@paralleldrive/cuid2": "^2.2.1",
+        "@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",
@@ -26,12 +27,14 @@
         "@radix-ui/react-separator": "^1.0.3",
         "@radix-ui/react-slot": "^1.0.2",
         "@radix-ui/react-toast": "^1.1.4",
+        "@supabase/supabase-js": "^2.26.0",
         "@t3-oss/env-nextjs": "^0.4.1",
         "@tanstack/react-query": "^4.29.15",
         "bcrypt": "^5.1.0",
         "class-variance-authority": "^0.6.0",
         "clsx": "^1.2.1",
-        "lucide-react": "^0.248.0",
+        "dayjs": "^1.11.8",
+        "lucide-react": "^0.252.0",
         "next": "^13.4.7",
         "next-auth": "^4.22.1",
         "next-themes": "^0.2.1",
@@ -49,14 +52,14 @@
         "@tanstack/eslint-plugin-query": "^4.29.9",
         "@types/bcrypt": "^5.0.0",
         "@types/node": "^20.3.1",
-        "@types/react": "^18.2.13",
+        "@types/react": "^18.2.14",
         "@types/react-dom": "^18.2.6",
         "autoprefixer": "10.4.14",
         "eslint": "^8.43.0",
         "eslint-config-next": "^13.4.7",
         "postcss": "8.4.24",
-        "prisma": "^4.16.0",
+        "prisma": "^4.16.1",
         "tailwindcss": "3.3.2",
         "typescript": "^5.1.3"
     }
-}
+}
\ No newline at end of file
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 341c014..bfc5153 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -9,21 +9,18 @@ datasource db {
 }
 
 model Account {
-  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?
-  createdAt                DateTime @default(now()) @map("created_at")
-  updatedAt                DateTime @default(now()) @map("updated_at")
-  refresh_token_expires_in Int?
+  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)
 
@@ -51,6 +48,11 @@ model VerificationToken {
   @@map("verification_tokens")
 }
 
+enum UserRole {
+  ADMIN
+  USER
+}
+
 model User {
   id            String    @id @default(cuid())
   name          String
@@ -59,6 +61,12 @@ model User {
   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")
 
@@ -67,11 +75,11 @@ model User {
   playingGameList  Int[]
   finishedGameList Int[]
 
-  accounts Account?
-  sessions Session?
-
-  gweets Gweet[]
-  likes  Like[]
+  accounts Account[]
+  sessions Session[]
+  gweets   Gweet[]
+  regweets Regweet[]
+  likes    Like[]
 
   following Follows[] @relation("following")
   followers Follows[] @relation("follower")
@@ -93,23 +101,44 @@ model Follows {
 
 model Gweet {
   id        String   @id @default(cuid())
-  userId    String   @map("user_id")
+  authorId  String   @map("user_id")
   content   String
-  likeCount Int      @default(0) @map("like_count")
   createdAt DateTime @default(now()) @map("created_at")
   updatedAt DateTime @default(now()) @map("updated_at")
 
-  replyToUserId  String? @map("reply_to_user_id")
   replyToGweetId String? @map("reply_to_gweet_id")
+  quoteGweetId   String? @map("quote_gweet_id")
+
+  regweets Regweet[]
+  media    Media[]
+  likes    Like[]
 
-  user        User    @relation(fields: [userId], references: [id], onDelete: Cascade)
+  author      User    @relation(fields: [authorId], references: [id], onDelete: Cascade)
   comment     Gweet?  @relation("gweet_comment", fields: [replyToGweetId], references: [id])
   allComments Gweet[] @relation("gweet_comment")
-  likes       Like[]
+  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
+  type    MediaTypes @default(IMAGE)
+  gweetId String     @map("gweet_id")
+
+  gweet Gweet? @relation(fields: [gweetId], references: [id], onDelete: Cascade)
+
+  @@map("media")
+}
+
 model Like {
   id        String   @id @default(cuid())
   gweetId   String   @map("gweet_id")
@@ -122,54 +151,24 @@ model Like {
   @@map("likes")
 }
 
-model Hashtag {
-  id         String   @id @default(cuid())
-  text       String
-  hashtag    String   @unique
-  score      Int      @default(1)
-  created_at DateTime @default(now())
+model Regweet {
+  id        String   @id @default(cuid())
+  gweetId   String   @map("gweet_id")
+  userId    String   @map("user_id")
+  createdAt DateTime @default(now()) @map("created_at")
+
+  user  User  @relation(fields: [userId], references: [id])
+  gweet Gweet @relation(fields: [gweetId], references: [id], onDelete: Cascade)
+
+  @@map("regweets")
 }
 
-// model Post {
-//   id        String   @id @default(cuid())
-//   userId    String
-//   content   String
-//   likeCount Int?     @default(0)
-//   createdAt DateTime @default(now()) @map("created_at")
-//   updatedAt DateTime @default(now()) @map("updated_at")
-
-//   user    User      @relation(fields: [userId], references: [id], onDelete: Cascade)
-//   Comment Comment[]
-//   Like    Like[]
-
-//   @@map("posts")
-// }
-
-// model Comment {
-//   id        String   @id @default(cuid())
-//   createdAt DateTime @default(now()) @map("created_at")
-//   updatedAt DateTime @default(now()) @map("updated_at")
-//   message   String
-//   likeCount Int?     @default(0)
-//   postId    String
-//   userId    String
-
-//   user User   @relation(fields: [userId], references: [id], onDelete: Cascade)
-//   post Post   @relation(fields: [postId], references: [id], onDelete: Cascade)
-//   Like Like[]
-
-//   @@map("comments")
-// }
-
-// model Like {
-//   id        String  @id @default(cuid())
-//   postId    String
-//   commentId String?
-//   userId    String
-
-//   user    User     @relation(fields: [userId], references: [id], onDelete: Cascade)
-//   post    Post     @relation(fields: [postId], references: [id], onDelete: Cascade)
-//   comment Comment? @relation(fields: [commentId], references: [id], onDelete: Cascade)
-
-//   @@map("likes")
-// }
+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")
+}
-- 
GitLab