When a new user logs in with Apple, they are prompted to add their name & pfp since Apple does not provide that.
This commit is contained in:
		
							
								
								
									
										13
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								package.json
									
									
									
									
									
								
							@@ -18,8 +18,15 @@
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "@auth/drizzle-adapter": "^1.4.2",
 | 
					    "@auth/drizzle-adapter": "^1.4.2",
 | 
				
			||||||
 | 
					    "@hookform/resolvers": "^3.9.0",
 | 
				
			||||||
 | 
					    "@radix-ui/react-alert-dialog": "^1.1.1",
 | 
				
			||||||
 | 
					    "@radix-ui/react-avatar": "^1.1.0",
 | 
				
			||||||
    "@radix-ui/react-dropdown-menu": "^2.1.1",
 | 
					    "@radix-ui/react-dropdown-menu": "^2.1.1",
 | 
				
			||||||
 | 
					    "@radix-ui/react-label": "^2.1.0",
 | 
				
			||||||
 | 
					    "@radix-ui/react-navigation-menu": "^1.2.0",
 | 
				
			||||||
 | 
					    "@radix-ui/react-popover": "^1.1.1",
 | 
				
			||||||
    "@radix-ui/react-slot": "^1.1.0",
 | 
					    "@radix-ui/react-slot": "^1.1.0",
 | 
				
			||||||
 | 
					    "@radix-ui/react-toast": "^1.2.1",
 | 
				
			||||||
    "@t3-oss/env-nextjs": "^0.10.1",
 | 
					    "@t3-oss/env-nextjs": "^0.10.1",
 | 
				
			||||||
    "@types/jsonwebtoken": "^9.0.6",
 | 
					    "@types/jsonwebtoken": "^9.0.6",
 | 
				
			||||||
    "class-variance-authority": "^0.7.0",
 | 
					    "class-variance-authority": "^0.7.0",
 | 
				
			||||||
@@ -35,6 +42,8 @@
 | 
				
			|||||||
    "postgres": "^3.4.4",
 | 
					    "postgres": "^3.4.4",
 | 
				
			||||||
    "react": "^18.3.1",
 | 
					    "react": "^18.3.1",
 | 
				
			||||||
    "react-dom": "^18.3.1",
 | 
					    "react-dom": "^18.3.1",
 | 
				
			||||||
 | 
					    "react-hook-form": "^7.52.2",
 | 
				
			||||||
 | 
					    "server-only": "^0.0.1",
 | 
				
			||||||
    "tailwind-merge": "^2.4.0",
 | 
					    "tailwind-merge": "^2.4.0",
 | 
				
			||||||
    "tailwindcss-animate": "^1.0.7",
 | 
					    "tailwindcss-animate": "^1.0.7",
 | 
				
			||||||
    "zod": "^3.23.8"
 | 
					    "zod": "^3.23.8"
 | 
				
			||||||
@@ -53,9 +62,9 @@
 | 
				
			|||||||
    "postcss": "^8.4.41",
 | 
					    "postcss": "^8.4.41",
 | 
				
			||||||
    "prettier": "^3.3.3",
 | 
					    "prettier": "^3.3.3",
 | 
				
			||||||
    "prettier-plugin-tailwindcss": "^0.6.5",
 | 
					    "prettier-plugin-tailwindcss": "^0.6.5",
 | 
				
			||||||
    "tailwindcss": "^3.4.8",
 | 
					    "tailwindcss": "^3.4.9",
 | 
				
			||||||
    "ts-node": "^10.9.2",
 | 
					    "ts-node": "^10.9.2",
 | 
				
			||||||
    "tsx": "^4.16.5",
 | 
					    "tsx": "^4.17.0",
 | 
				
			||||||
    "typescript": "^5.5.4"
 | 
					    "typescript": "^5.5.4"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "ct3aMetadata": {
 | 
					  "ct3aMetadata": {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										711
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										711
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										27
									
								
								src/app/api/users/set_username_by_email/route.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/app/api/users/set_username_by_email/route.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					"use server"
 | 
				
			||||||
 | 
					import { NextResponse } from "next/server"
 | 
				
			||||||
 | 
					import type { NextRequest } from "next/server"
 | 
				
			||||||
 | 
					import { set_users_name_by_email } from "~/server/functions"
 | 
				
			||||||
 | 
					import { auth } from "~/auth"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type updateNameData = {
 | 
				
			||||||
 | 
					  users_name: string;
 | 
				
			||||||
 | 
					  users_email: string;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const POST = async (req: NextRequest) => {
 | 
				
			||||||
 | 
					  const session = await auth();
 | 
				
			||||||
 | 
					  if (!session) return NextResponse.json(
 | 
				
			||||||
 | 
					    { error: "Not authenticated" },
 | 
				
			||||||
 | 
					    { status: 401 }
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  const { users_name, users_email } = await req.json() as updateNameData;
 | 
				
			||||||
 | 
					  console.log('API received users_name:', users_name, 'users_id:', users_email); // Log received data
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    await set_users_name_by_email(users_name, users_email);
 | 
				
			||||||
 | 
					    return NextResponse.json({ message: "Username updated successfully", users_name }, { status: 200 });
 | 
				
			||||||
 | 
					  } catch (error) {
 | 
				
			||||||
 | 
					    console.error('Error in API route:', error);
 | 
				
			||||||
 | 
					    return NextResponse.json({ error: "Error updating username" }, { status: 500 });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -2,12 +2,13 @@
 | 
				
			|||||||
import Theme_Toggle from "~/components/theme/theme_toggle"
 | 
					import Theme_Toggle from "~/components/theme/theme_toggle"
 | 
				
			||||||
import { auth } from "~/auth"
 | 
					import { auth } from "~/auth"
 | 
				
			||||||
import Sign_In_Apple_Button from "~/components/auth/server/SignInAppleButton"
 | 
					import Sign_In_Apple_Button from "~/components/auth/server/SignInAppleButton"
 | 
				
			||||||
import Sign_Out_Button from "~/components/auth/server/SignOutButton"
 | 
					 | 
				
			||||||
import Title from "~/components/home/Title"
 | 
					import Title from "~/components/home/Title"
 | 
				
			||||||
 | 
					import Avatar_Popover from "~/components/auth/AvatarPopover"
 | 
				
			||||||
 | 
					import First_Sign_In_Form from "~/components/auth/FirstSignInForm"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default async function HomePage() {
 | 
					export default async function HomePage() {
 | 
				
			||||||
  const session = await auth();
 | 
					  const session = await auth();
 | 
				
			||||||
  if (!session) {
 | 
					  if (!session?.user) {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <main className="min-h-screen">
 | 
					      <main className="min-h-screen">
 | 
				
			||||||
        <div className="w-full justify-end items-end p-3 flex flex-col">
 | 
					        <div className="w-full justify-end items-end p-3 flex flex-col">
 | 
				
			||||||
@@ -20,14 +21,23 @@ export default async function HomePage() {
 | 
				
			|||||||
      </main>
 | 
					      </main>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    const email = session?.user?.email;
 | 
					    const users_email = session.user.email ?? "";
 | 
				
			||||||
 | 
					    const users_name = session.user.name ?? "New User";
 | 
				
			||||||
 | 
					    console.log("session:", session);
 | 
				
			||||||
 | 
					    console.log("users_email:", users_email);
 | 
				
			||||||
 | 
					    console.log("users_name:", users_name);
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <main className="min-h-screen">
 | 
					      <main className="min-h-screen">
 | 
				
			||||||
        <div className="w-full justify-end items-end p-3 flex flex-col">
 | 
					        <div className="w-full justify-end items-end p-3 flex flex-col">
 | 
				
			||||||
          <Theme_Toggle />
 | 
					          <div className="my-auto flex flex-row">
 | 
				
			||||||
 | 
					            <div className="px-4">
 | 
				
			||||||
 | 
					              <Avatar_Popover />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <Theme_Toggle />
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
          <div className="w-full flex flex-col justify-center items-center">
 | 
					          <div className="w-full flex flex-col justify-center items-center">
 | 
				
			||||||
            <h1>Welcome, {email}</h1>
 | 
					            <h1>Welcome, {users_name.split(" ")[0]}</h1>
 | 
				
			||||||
            <Sign_Out_Button />
 | 
					            <First_Sign_In_Form users_name={users_name} users_email={users_email} />
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </main>
 | 
					      </main>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										47
									
								
								src/components/auth/AvatarPopover.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/components/auth/AvatarPopover.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
				
			|||||||
 | 
					"use server"
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  DropdownMenu,
 | 
				
			||||||
 | 
					  DropdownMenuContent,
 | 
				
			||||||
 | 
					  DropdownMenuLabel,
 | 
				
			||||||
 | 
					  DropdownMenuItem,
 | 
				
			||||||
 | 
					  DropdownMenuSeparator,
 | 
				
			||||||
 | 
					  DropdownMenuTrigger,
 | 
				
			||||||
 | 
					} from "~/components/ui/dropdown-menu"
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  Avatar,
 | 
				
			||||||
 | 
					  AvatarFallback,
 | 
				
			||||||
 | 
					  AvatarImage,
 | 
				
			||||||
 | 
					} from "~/components/ui/avatar"
 | 
				
			||||||
 | 
					import { auth } from "~/auth"
 | 
				
			||||||
 | 
					import Sign_Out_Button from "~/components/auth/server/SignOutButton"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default async function Avatar_Popover() {
 | 
				
			||||||
 | 
					  const session = await auth();
 | 
				
			||||||
 | 
					  const pfp = session?.user?.image ?? "https://github.com/shadcn.png";
 | 
				
			||||||
 | 
					  const users_name = session?.user?.name ?? "shadcn";
 | 
				
			||||||
 | 
					  const initials = users_name.split(" ").map((name) => name[0]).join("");
 | 
				
			||||||
 | 
					  console.log(pfp);
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <DropdownMenu>
 | 
				
			||||||
 | 
					      <DropdownMenuTrigger>
 | 
				
			||||||
 | 
					        <Avatar>
 | 
				
			||||||
 | 
					          <AvatarImage src={pfp} alt="@shadcn" />
 | 
				
			||||||
 | 
					          <AvatarFallback>{initials}</AvatarFallback>
 | 
				
			||||||
 | 
					        </Avatar>
 | 
				
			||||||
 | 
					      </DropdownMenuTrigger>
 | 
				
			||||||
 | 
					      <DropdownMenuContent>
 | 
				
			||||||
 | 
					        <DropdownMenuLabel>{users_name}</DropdownMenuLabel>
 | 
				
			||||||
 | 
					        <DropdownMenuSeparator />
 | 
				
			||||||
 | 
					        <DropdownMenuItem>
 | 
				
			||||||
 | 
					          <div className="w-full flex flex-row
 | 
				
			||||||
 | 
					            justify-center items-center">
 | 
				
			||||||
 | 
					            Settings
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </DropdownMenuItem>
 | 
				
			||||||
 | 
					        <DropdownMenuItem> 
 | 
				
			||||||
 | 
					          <Sign_Out_Button />
 | 
				
			||||||
 | 
					        </DropdownMenuItem>
 | 
				
			||||||
 | 
					      </DropdownMenuContent>
 | 
				
			||||||
 | 
					    </DropdownMenu>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										125
									
								
								src/components/auth/FirstSignInForm.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								src/components/auth/FirstSignInForm.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,125 @@
 | 
				
			|||||||
 | 
					"use client"
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  AlertDialog,
 | 
				
			||||||
 | 
					  AlertDialogContent,
 | 
				
			||||||
 | 
					  AlertDialogDescription,
 | 
				
			||||||
 | 
					  AlertDialogHeader,
 | 
				
			||||||
 | 
					  AlertDialogTitle,
 | 
				
			||||||
 | 
					} from "~/components/ui/alert-dialog"
 | 
				
			||||||
 | 
					import { Button } from "~/components/ui/button"
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  Form,
 | 
				
			||||||
 | 
					  FormControl,
 | 
				
			||||||
 | 
					  FormField,
 | 
				
			||||||
 | 
					  FormItem,
 | 
				
			||||||
 | 
					  FormLabel,
 | 
				
			||||||
 | 
					} from "~/components/ui/form"
 | 
				
			||||||
 | 
					import { Input } from "~/components/ui/input"
 | 
				
			||||||
 | 
					import { z } from "zod"
 | 
				
			||||||
 | 
					import { zodResolver } from "@hookform/resolvers/zod"
 | 
				
			||||||
 | 
					import { useForm } from "react-hook-form"
 | 
				
			||||||
 | 
					import { useEffect, useState } from "react"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const formSchema = z.object({
 | 
				
			||||||
 | 
					  users_name: z.string().min(2, {
 | 
				
			||||||
 | 
					    message: "Really? Your name is one letter? You expect me to believe that?",
 | 
				
			||||||
 | 
					  }),
 | 
				
			||||||
 | 
					  users_profile_image: z.string().url().optional(),
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function First_Sign_In_Form({ users_name, users_email }: { users_name: string, users_email: string }) {
 | 
				
			||||||
 | 
					  const [isOpen, setIsOpen] = useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const form = useForm<z.infer<typeof formSchema>>({
 | 
				
			||||||
 | 
					    resolver: zodResolver(formSchema),
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const update_users_name = async (users_name: string, users_email: string) => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const res = await fetch("/api/users/set_username_by_email", {
 | 
				
			||||||
 | 
					        method: "POST",
 | 
				
			||||||
 | 
					        headers: {
 | 
				
			||||||
 | 
					          "Content-Type": "application/json",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        body: JSON.stringify({
 | 
				
			||||||
 | 
					          users_name: users_name,
 | 
				
			||||||
 | 
					          users_email: users_email,
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      if (!res.ok) {
 | 
				
			||||||
 | 
					        throw new Error("Failed to update user's name");
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      console.error("Could not update user's name", error);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const onSubmit = async (data: z.infer<typeof formSchema>) => {
 | 
				
			||||||
 | 
					    await update_users_name(data.users_name, users_email);
 | 
				
			||||||
 | 
					    setIsOpen(false);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    if (users_name === "New User") {
 | 
				
			||||||
 | 
					      setIsOpen(true);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }, [users_name]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (users_name === "New User") {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <AlertDialog open={isOpen} onOpenChange={setIsOpen}>
 | 
				
			||||||
 | 
					        <AlertDialogContent>
 | 
				
			||||||
 | 
					          <AlertDialogHeader>
 | 
				
			||||||
 | 
					            <AlertDialogTitle>Welcome to the Tenant Portal</AlertDialogTitle>
 | 
				
			||||||
 | 
					            <AlertDialogDescription>
 | 
				
			||||||
 | 
					              Please fill out the form to complete your account setup.
 | 
				
			||||||
 | 
					            </AlertDialogDescription>
 | 
				
			||||||
 | 
					            <Form {...form}>
 | 
				
			||||||
 | 
					              <form onSubmit={form.handleSubmit(onSubmit)}>
 | 
				
			||||||
 | 
					                <FormField
 | 
				
			||||||
 | 
					                  control={form.control}
 | 
				
			||||||
 | 
					                  name="users_name"
 | 
				
			||||||
 | 
					                  render={({ field }) => (
 | 
				
			||||||
 | 
					                    <FormItem>
 | 
				
			||||||
 | 
					                      <FormLabel>Enter your name</FormLabel>
 | 
				
			||||||
 | 
					                      <FormControl>
 | 
				
			||||||
 | 
					                        <Input placeholder="First & Last Name" {...field} />
 | 
				
			||||||
 | 
					                      </FormControl>
 | 
				
			||||||
 | 
					                    </FormItem>
 | 
				
			||||||
 | 
					                  )}
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                </FormField>
 | 
				
			||||||
 | 
					                <FormField
 | 
				
			||||||
 | 
					                  control={form.control}
 | 
				
			||||||
 | 
					                  name="users_profile_image"
 | 
				
			||||||
 | 
					                  render={({ field }) => (
 | 
				
			||||||
 | 
					                    <FormItem>
 | 
				
			||||||
 | 
					                      <FormLabel>Enter a URL path to your profile picture</FormLabel>
 | 
				
			||||||
 | 
					                      <FormControl>
 | 
				
			||||||
 | 
					                        <Input placeholder="https://example.com/profile.jpg" {...field} />
 | 
				
			||||||
 | 
					                      </FormControl>
 | 
				
			||||||
 | 
					                    </FormItem>
 | 
				
			||||||
 | 
					                  )}
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                </FormField>
 | 
				
			||||||
 | 
					                <div className="w-full flex justify-end">
 | 
				
			||||||
 | 
					                  <Button type="submit" variant="secondary"
 | 
				
			||||||
 | 
					                    className="mt-2"
 | 
				
			||||||
 | 
					                  >
 | 
				
			||||||
 | 
					                    Submit
 | 
				
			||||||
 | 
					                  </Button>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					              </form>
 | 
				
			||||||
 | 
					            </Form>
 | 
				
			||||||
 | 
					          </AlertDialogHeader>
 | 
				
			||||||
 | 
					        </AlertDialogContent>
 | 
				
			||||||
 | 
					      </AlertDialog>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <div/>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -3,12 +3,13 @@ import { signOut } from "~/auth"
 | 
				
			|||||||
export default function Sign_Out() {
 | 
					export default function Sign_Out() {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <form
 | 
					    <form
 | 
				
			||||||
 | 
					      className="w-full"
 | 
				
			||||||
      action={async () => {
 | 
					      action={async () => {
 | 
				
			||||||
        "use server"
 | 
					        "use server"
 | 
				
			||||||
        await signOut()
 | 
					        await signOut()
 | 
				
			||||||
      }}
 | 
					      }}
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      <button type="submit">Sign Out</button>
 | 
					      <button type="submit" className="w-full">Sign Out</button>
 | 
				
			||||||
    </form>
 | 
					    </form>
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										141
									
								
								src/components/ui/alert-dialog.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								src/components/ui/alert-dialog.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,141 @@
 | 
				
			|||||||
 | 
					"use client"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import * as React from "react"
 | 
				
			||||||
 | 
					import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { cn } from "~/lib/utils"
 | 
				
			||||||
 | 
					import { buttonVariants } from "~/components/ui/button"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const AlertDialog = AlertDialogPrimitive.Root
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const AlertDialogTrigger = AlertDialogPrimitive.Trigger
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const AlertDialogPortal = AlertDialogPrimitive.Portal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const AlertDialogOverlay = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <AlertDialogPrimitive.Overlay
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "fixed inset-0 z-50 bg-black/80  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}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const AlertDialogContent = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof AlertDialogPrimitive.Content>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <AlertDialogPortal>
 | 
				
			||||||
 | 
					    <AlertDialogOverlay />
 | 
				
			||||||
 | 
					    <AlertDialogPrimitive.Content
 | 
				
			||||||
 | 
					      ref={ref}
 | 
				
			||||||
 | 
					      className={cn(
 | 
				
			||||||
 | 
					        "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
 | 
				
			||||||
 | 
					        className
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  </AlertDialogPortal>
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const AlertDialogHeader = ({
 | 
				
			||||||
 | 
					  className,
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.HTMLAttributes<HTMLDivElement>) => (
 | 
				
			||||||
 | 
					  <div
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "flex flex-col space-y-2 text-center sm:text-left",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					AlertDialogHeader.displayName = "AlertDialogHeader"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const AlertDialogFooter = ({
 | 
				
			||||||
 | 
					  className,
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.HTMLAttributes<HTMLDivElement>) => (
 | 
				
			||||||
 | 
					  <div
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					AlertDialogFooter.displayName = "AlertDialogFooter"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const AlertDialogTitle = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof AlertDialogPrimitive.Title>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <AlertDialogPrimitive.Title
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn("text-lg font-semibold", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const AlertDialogDescription = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof AlertDialogPrimitive.Description>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <AlertDialogPrimitive.Description
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn("text-sm text-muted-foreground", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					AlertDialogDescription.displayName =
 | 
				
			||||||
 | 
					  AlertDialogPrimitive.Description.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const AlertDialogAction = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof AlertDialogPrimitive.Action>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <AlertDialogPrimitive.Action
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(buttonVariants(), className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const AlertDialogCancel = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <AlertDialogPrimitive.Cancel
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      buttonVariants({ variant: "outline" }),
 | 
				
			||||||
 | 
					      "mt-2 sm:mt-0",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export {
 | 
				
			||||||
 | 
					  AlertDialog,
 | 
				
			||||||
 | 
					  AlertDialogPortal,
 | 
				
			||||||
 | 
					  AlertDialogOverlay,
 | 
				
			||||||
 | 
					  AlertDialogTrigger,
 | 
				
			||||||
 | 
					  AlertDialogContent,
 | 
				
			||||||
 | 
					  AlertDialogHeader,
 | 
				
			||||||
 | 
					  AlertDialogFooter,
 | 
				
			||||||
 | 
					  AlertDialogTitle,
 | 
				
			||||||
 | 
					  AlertDialogDescription,
 | 
				
			||||||
 | 
					  AlertDialogAction,
 | 
				
			||||||
 | 
					  AlertDialogCancel,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										50
									
								
								src/components/ui/avatar.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/components/ui/avatar.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
				
			|||||||
 | 
					"use client"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import * as React from "react"
 | 
				
			||||||
 | 
					import * as AvatarPrimitive from "@radix-ui/react-avatar"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { cn } from "~/lib/utils"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Avatar = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof AvatarPrimitive.Root>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <AvatarPrimitive.Root
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					Avatar.displayName = AvatarPrimitive.Root.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const AvatarImage = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof AvatarPrimitive.Image>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <AvatarPrimitive.Image
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn("aspect-square h-full w-full", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					AvatarImage.displayName = AvatarPrimitive.Image.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const AvatarFallback = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof AvatarPrimitive.Fallback>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <AvatarPrimitive.Fallback
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "flex h-full w-full items-center justify-center rounded-full bg-muted",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { Avatar, AvatarImage, AvatarFallback }
 | 
				
			||||||
							
								
								
									
										178
									
								
								src/components/ui/form.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								src/components/ui/form.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,178 @@
 | 
				
			|||||||
 | 
					"use client"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import * as React from "react"
 | 
				
			||||||
 | 
					import * as LabelPrimitive from "@radix-ui/react-label"
 | 
				
			||||||
 | 
					import { Slot } from "@radix-ui/react-slot"
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  Controller,
 | 
				
			||||||
 | 
					  ControllerProps,
 | 
				
			||||||
 | 
					  FieldPath,
 | 
				
			||||||
 | 
					  FieldValues,
 | 
				
			||||||
 | 
					  FormProvider,
 | 
				
			||||||
 | 
					  useFormContext,
 | 
				
			||||||
 | 
					} from "react-hook-form"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { cn } from "~/lib/utils"
 | 
				
			||||||
 | 
					import { Label } from "~/components/ui/label"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Form = FormProvider
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type FormFieldContextValue<
 | 
				
			||||||
 | 
					  TFieldValues extends FieldValues = FieldValues,
 | 
				
			||||||
 | 
					  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
 | 
				
			||||||
 | 
					> = {
 | 
				
			||||||
 | 
					  name: TName
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const FormFieldContext = React.createContext<FormFieldContextValue>(
 | 
				
			||||||
 | 
					  {} as FormFieldContextValue
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const FormField = <
 | 
				
			||||||
 | 
					  TFieldValues extends FieldValues = FieldValues,
 | 
				
			||||||
 | 
					  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
 | 
				
			||||||
 | 
					>({
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: ControllerProps<TFieldValues, TName>) => {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <FormFieldContext.Provider value={{ name: props.name }}>
 | 
				
			||||||
 | 
					      <Controller {...props} />
 | 
				
			||||||
 | 
					    </FormFieldContext.Provider>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const useFormField = () => {
 | 
				
			||||||
 | 
					  const fieldContext = React.useContext(FormFieldContext)
 | 
				
			||||||
 | 
					  const itemContext = React.useContext(FormItemContext)
 | 
				
			||||||
 | 
					  const { getFieldState, formState } = useFormContext()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const fieldState = getFieldState(fieldContext.name, formState)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!fieldContext) {
 | 
				
			||||||
 | 
					    throw new Error("useFormField should be used within <FormField>")
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { id } = itemContext
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    id,
 | 
				
			||||||
 | 
					    name: fieldContext.name,
 | 
				
			||||||
 | 
					    formItemId: `${id}-form-item`,
 | 
				
			||||||
 | 
					    formDescriptionId: `${id}-form-item-description`,
 | 
				
			||||||
 | 
					    formMessageId: `${id}-form-item-message`,
 | 
				
			||||||
 | 
					    ...fieldState,
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type FormItemContextValue = {
 | 
				
			||||||
 | 
					  id: string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const FormItemContext = React.createContext<FormItemContextValue>(
 | 
				
			||||||
 | 
					  {} as FormItemContextValue
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const FormItem = React.forwardRef<
 | 
				
			||||||
 | 
					  HTMLDivElement,
 | 
				
			||||||
 | 
					  React.HTMLAttributes<HTMLDivElement>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => {
 | 
				
			||||||
 | 
					  const id = React.useId()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <FormItemContext.Provider value={{ id }}>
 | 
				
			||||||
 | 
					      <div ref={ref} className={cn("space-y-2", className)} {...props} />
 | 
				
			||||||
 | 
					    </FormItemContext.Provider>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					FormItem.displayName = "FormItem"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const FormLabel = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof LabelPrimitive.Root>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => {
 | 
				
			||||||
 | 
					  const { error, formItemId } = useFormField()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Label
 | 
				
			||||||
 | 
					      ref={ref}
 | 
				
			||||||
 | 
					      className={cn(error && "text-destructive", className)}
 | 
				
			||||||
 | 
					      htmlFor={formItemId}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					FormLabel.displayName = "FormLabel"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const FormControl = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof Slot>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof Slot>
 | 
				
			||||||
 | 
					>(({ ...props }, ref) => {
 | 
				
			||||||
 | 
					  const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Slot
 | 
				
			||||||
 | 
					      ref={ref}
 | 
				
			||||||
 | 
					      id={formItemId}
 | 
				
			||||||
 | 
					      aria-describedby={
 | 
				
			||||||
 | 
					        !error
 | 
				
			||||||
 | 
					          ? `${formDescriptionId}`
 | 
				
			||||||
 | 
					          : `${formDescriptionId} ${formMessageId}`
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      aria-invalid={!!error}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					FormControl.displayName = "FormControl"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const FormDescription = React.forwardRef<
 | 
				
			||||||
 | 
					  HTMLParagraphElement,
 | 
				
			||||||
 | 
					  React.HTMLAttributes<HTMLParagraphElement>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => {
 | 
				
			||||||
 | 
					  const { formDescriptionId } = useFormField()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <p
 | 
				
			||||||
 | 
					      ref={ref}
 | 
				
			||||||
 | 
					      id={formDescriptionId}
 | 
				
			||||||
 | 
					      className={cn("text-sm text-muted-foreground", className)}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					FormDescription.displayName = "FormDescription"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const FormMessage = React.forwardRef<
 | 
				
			||||||
 | 
					  HTMLParagraphElement,
 | 
				
			||||||
 | 
					  React.HTMLAttributes<HTMLParagraphElement>
 | 
				
			||||||
 | 
					>(({ className, children, ...props }, ref) => {
 | 
				
			||||||
 | 
					  const { error, formMessageId } = useFormField()
 | 
				
			||||||
 | 
					  const body = error ? String(error?.message) : children
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!body) {
 | 
				
			||||||
 | 
					    return null
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <p
 | 
				
			||||||
 | 
					      ref={ref}
 | 
				
			||||||
 | 
					      id={formMessageId}
 | 
				
			||||||
 | 
					      className={cn("text-sm font-medium text-destructive", className)}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      {body}
 | 
				
			||||||
 | 
					    </p>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					FormMessage.displayName = "FormMessage"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export {
 | 
				
			||||||
 | 
					  useFormField,
 | 
				
			||||||
 | 
					  Form,
 | 
				
			||||||
 | 
					  FormItem,
 | 
				
			||||||
 | 
					  FormLabel,
 | 
				
			||||||
 | 
					  FormControl,
 | 
				
			||||||
 | 
					  FormDescription,
 | 
				
			||||||
 | 
					  FormMessage,
 | 
				
			||||||
 | 
					  FormField,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										25
									
								
								src/components/ui/input.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/components/ui/input.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					import * as React from "react"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { cn } from "~/lib/utils"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface InputProps
 | 
				
			||||||
 | 
					  extends React.InputHTMLAttributes<HTMLInputElement> {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Input = React.forwardRef<HTMLInputElement, InputProps>(
 | 
				
			||||||
 | 
					  ({ className, type, ...props }, ref) => {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <input
 | 
				
			||||||
 | 
					        type={type}
 | 
				
			||||||
 | 
					        className={cn(
 | 
				
			||||||
 | 
					          "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
 | 
				
			||||||
 | 
					          className
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					        ref={ref}
 | 
				
			||||||
 | 
					        {...props}
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					Input.displayName = "Input"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { Input }
 | 
				
			||||||
							
								
								
									
										26
									
								
								src/components/ui/label.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/components/ui/label.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					"use client"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import * as React from "react"
 | 
				
			||||||
 | 
					import * as LabelPrimitive from "@radix-ui/react-label"
 | 
				
			||||||
 | 
					import { cva, type VariantProps } from "class-variance-authority"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { cn } from "~/lib/utils"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const labelVariants = cva(
 | 
				
			||||||
 | 
					  "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Label = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof LabelPrimitive.Root>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
 | 
				
			||||||
 | 
					    VariantProps<typeof labelVariants>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <LabelPrimitive.Root
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(labelVariants(), className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					Label.displayName = LabelPrimitive.Root.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { Label }
 | 
				
			||||||
							
								
								
									
										128
									
								
								src/components/ui/navigation-menu.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								src/components/ui/navigation-menu.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,128 @@
 | 
				
			|||||||
 | 
					import * as React from "react"
 | 
				
			||||||
 | 
					import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
 | 
				
			||||||
 | 
					import { cva } from "class-variance-authority"
 | 
				
			||||||
 | 
					import { ChevronDown } from "lucide-react"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { cn } from "~/lib/utils"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NavigationMenu = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof NavigationMenuPrimitive.Root>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
 | 
				
			||||||
 | 
					>(({ className, children, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <NavigationMenuPrimitive.Root
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "relative z-10 flex max-w-max flex-1 items-center justify-center",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    {children}
 | 
				
			||||||
 | 
					    <NavigationMenuViewport />
 | 
				
			||||||
 | 
					  </NavigationMenuPrimitive.Root>
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NavigationMenuList = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof NavigationMenuPrimitive.List>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <NavigationMenuPrimitive.List
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "group flex flex-1 list-none items-center justify-center space-x-1",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NavigationMenuItem = NavigationMenuPrimitive.Item
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const navigationMenuTriggerStyle = cva(
 | 
				
			||||||
 | 
					  "group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NavigationMenuTrigger = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
 | 
				
			||||||
 | 
					>(({ className, children, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <NavigationMenuPrimitive.Trigger
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(navigationMenuTriggerStyle(), "group", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    {children}{" "}
 | 
				
			||||||
 | 
					    <ChevronDown
 | 
				
			||||||
 | 
					      className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180"
 | 
				
			||||||
 | 
					      aria-hidden="true"
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  </NavigationMenuPrimitive.Trigger>
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NavigationMenuContent = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof NavigationMenuPrimitive.Content>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <NavigationMenuPrimitive.Content
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NavigationMenuLink = NavigationMenuPrimitive.Link
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NavigationMenuViewport = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <div className={cn("absolute left-0 top-full flex justify-center")}>
 | 
				
			||||||
 | 
					    <NavigationMenuPrimitive.Viewport
 | 
				
			||||||
 | 
					      className={cn(
 | 
				
			||||||
 | 
					        "origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
 | 
				
			||||||
 | 
					        className
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					      ref={ref}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					NavigationMenuViewport.displayName =
 | 
				
			||||||
 | 
					  NavigationMenuPrimitive.Viewport.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NavigationMenuIndicator = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <NavigationMenuPrimitive.Indicator
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    <div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
 | 
				
			||||||
 | 
					  </NavigationMenuPrimitive.Indicator>
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					NavigationMenuIndicator.displayName =
 | 
				
			||||||
 | 
					  NavigationMenuPrimitive.Indicator.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export {
 | 
				
			||||||
 | 
					  navigationMenuTriggerStyle,
 | 
				
			||||||
 | 
					  NavigationMenu,
 | 
				
			||||||
 | 
					  NavigationMenuList,
 | 
				
			||||||
 | 
					  NavigationMenuItem,
 | 
				
			||||||
 | 
					  NavigationMenuContent,
 | 
				
			||||||
 | 
					  NavigationMenuTrigger,
 | 
				
			||||||
 | 
					  NavigationMenuLink,
 | 
				
			||||||
 | 
					  NavigationMenuIndicator,
 | 
				
			||||||
 | 
					  NavigationMenuViewport,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										31
									
								
								src/components/ui/popover.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/components/ui/popover.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					"use client"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import * as React from "react"
 | 
				
			||||||
 | 
					import * as PopoverPrimitive from "@radix-ui/react-popover"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { cn } from "~/lib/utils"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Popover = PopoverPrimitive.Root
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const PopoverTrigger = PopoverPrimitive.Trigger
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const PopoverContent = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof PopoverPrimitive.Content>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
 | 
				
			||||||
 | 
					>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <PopoverPrimitive.Portal>
 | 
				
			||||||
 | 
					    <PopoverPrimitive.Content
 | 
				
			||||||
 | 
					      ref={ref}
 | 
				
			||||||
 | 
					      align={align}
 | 
				
			||||||
 | 
					      sideOffset={sideOffset}
 | 
				
			||||||
 | 
					      className={cn(
 | 
				
			||||||
 | 
					        "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
 | 
				
			||||||
 | 
					        className
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  </PopoverPrimitive.Portal>
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					PopoverContent.displayName = PopoverPrimitive.Content.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { Popover, PopoverTrigger, PopoverContent }
 | 
				
			||||||
							
								
								
									
										117
									
								
								src/components/ui/table.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								src/components/ui/table.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,117 @@
 | 
				
			|||||||
 | 
					import * as React from "react"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { cn } from "~/lib/utils"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Table = React.forwardRef<
 | 
				
			||||||
 | 
					  HTMLTableElement,
 | 
				
			||||||
 | 
					  React.HTMLAttributes<HTMLTableElement>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <div className="relative w-full overflow-auto">
 | 
				
			||||||
 | 
					    <table
 | 
				
			||||||
 | 
					      ref={ref}
 | 
				
			||||||
 | 
					      className={cn("w-full caption-bottom text-sm", className)}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					Table.displayName = "Table"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const TableHeader = React.forwardRef<
 | 
				
			||||||
 | 
					  HTMLTableSectionElement,
 | 
				
			||||||
 | 
					  React.HTMLAttributes<HTMLTableSectionElement>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					TableHeader.displayName = "TableHeader"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const TableBody = React.forwardRef<
 | 
				
			||||||
 | 
					  HTMLTableSectionElement,
 | 
				
			||||||
 | 
					  React.HTMLAttributes<HTMLTableSectionElement>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <tbody
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn("[&_tr:last-child]:border-0", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					TableBody.displayName = "TableBody"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const TableFooter = React.forwardRef<
 | 
				
			||||||
 | 
					  HTMLTableSectionElement,
 | 
				
			||||||
 | 
					  React.HTMLAttributes<HTMLTableSectionElement>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <tfoot
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					TableFooter.displayName = "TableFooter"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const TableRow = React.forwardRef<
 | 
				
			||||||
 | 
					  HTMLTableRowElement,
 | 
				
			||||||
 | 
					  React.HTMLAttributes<HTMLTableRowElement>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <tr
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					TableRow.displayName = "TableRow"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const TableHead = React.forwardRef<
 | 
				
			||||||
 | 
					  HTMLTableCellElement,
 | 
				
			||||||
 | 
					  React.ThHTMLAttributes<HTMLTableCellElement>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <th
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					TableHead.displayName = "TableHead"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const TableCell = React.forwardRef<
 | 
				
			||||||
 | 
					  HTMLTableCellElement,
 | 
				
			||||||
 | 
					  React.TdHTMLAttributes<HTMLTableCellElement>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <td
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					TableCell.displayName = "TableCell"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const TableCaption = React.forwardRef<
 | 
				
			||||||
 | 
					  HTMLTableCaptionElement,
 | 
				
			||||||
 | 
					  React.HTMLAttributes<HTMLTableCaptionElement>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <caption
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn("mt-4 text-sm text-muted-foreground", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					TableCaption.displayName = "TableCaption"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export {
 | 
				
			||||||
 | 
					  Table,
 | 
				
			||||||
 | 
					  TableHeader,
 | 
				
			||||||
 | 
					  TableBody,
 | 
				
			||||||
 | 
					  TableFooter,
 | 
				
			||||||
 | 
					  TableHead,
 | 
				
			||||||
 | 
					  TableRow,
 | 
				
			||||||
 | 
					  TableCell,
 | 
				
			||||||
 | 
					  TableCaption,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										129
									
								
								src/components/ui/toast.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								src/components/ui/toast.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,129 @@
 | 
				
			|||||||
 | 
					"use client"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 { cn } from "~/lib/utils"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ToastProvider = ToastPrimitives.Provider
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ToastViewport = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof ToastPrimitives.Viewport>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <ToastPrimitives.Viewport
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					ToastViewport.displayName = ToastPrimitives.Viewport.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const toastVariants = cva(
 | 
				
			||||||
 | 
					  "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    variants: {
 | 
				
			||||||
 | 
					      variant: {
 | 
				
			||||||
 | 
					        default: "border bg-background text-foreground",
 | 
				
			||||||
 | 
					        destructive:
 | 
				
			||||||
 | 
					          "destructive group border-destructive bg-destructive text-destructive-foreground",
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    defaultVariants: {
 | 
				
			||||||
 | 
					      variant: "default",
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Toast = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof ToastPrimitives.Root>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
 | 
				
			||||||
 | 
					    VariantProps<typeof toastVariants>
 | 
				
			||||||
 | 
					>(({ className, variant, ...props }, ref) => {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <ToastPrimitives.Root
 | 
				
			||||||
 | 
					      ref={ref}
 | 
				
			||||||
 | 
					      className={cn(toastVariants({ variant }), className)}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					Toast.displayName = ToastPrimitives.Root.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ToastAction = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof ToastPrimitives.Action>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <ToastPrimitives.Action
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					ToastAction.displayName = ToastPrimitives.Action.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ToastClose = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof ToastPrimitives.Close>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <ToastPrimitives.Close
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    toast-close=""
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    <X className="h-4 w-4" />
 | 
				
			||||||
 | 
					  </ToastPrimitives.Close>
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					ToastClose.displayName = ToastPrimitives.Close.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ToastTitle = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof ToastPrimitives.Title>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <ToastPrimitives.Title
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn("text-sm font-semibold", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					ToastTitle.displayName = ToastPrimitives.Title.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ToastDescription = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof ToastPrimitives.Description>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <ToastPrimitives.Description
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn("text-sm opacity-90", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					ToastDescription.displayName = ToastPrimitives.Description.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ToastActionElement = React.ReactElement<typeof ToastAction>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export {
 | 
				
			||||||
 | 
					  type ToastProps,
 | 
				
			||||||
 | 
					  type ToastActionElement,
 | 
				
			||||||
 | 
					  ToastProvider,
 | 
				
			||||||
 | 
					  ToastViewport,
 | 
				
			||||||
 | 
					  Toast,
 | 
				
			||||||
 | 
					  ToastTitle,
 | 
				
			||||||
 | 
					  ToastDescription,
 | 
				
			||||||
 | 
					  ToastClose,
 | 
				
			||||||
 | 
					  ToastAction,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										35
									
								
								src/components/ui/toaster.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/components/ui/toaster.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					"use client"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  Toast,
 | 
				
			||||||
 | 
					  ToastClose,
 | 
				
			||||||
 | 
					  ToastDescription,
 | 
				
			||||||
 | 
					  ToastProvider,
 | 
				
			||||||
 | 
					  ToastTitle,
 | 
				
			||||||
 | 
					  ToastViewport,
 | 
				
			||||||
 | 
					} from "~/components/ui/toast"
 | 
				
			||||||
 | 
					import { useToast } from "~/components/ui/use-toast"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function Toaster() {
 | 
				
			||||||
 | 
					  const { toasts } = useToast()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <ToastProvider>
 | 
				
			||||||
 | 
					      {toasts.map(function ({ id, title, description, action, ...props }) {
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					          <Toast key={id} {...props}>
 | 
				
			||||||
 | 
					            <div className="grid gap-1">
 | 
				
			||||||
 | 
					              {title && <ToastTitle>{title}</ToastTitle>}
 | 
				
			||||||
 | 
					              {description && (
 | 
				
			||||||
 | 
					                <ToastDescription>{description}</ToastDescription>
 | 
				
			||||||
 | 
					              )}
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            {action}
 | 
				
			||||||
 | 
					            <ToastClose />
 | 
				
			||||||
 | 
					          </Toast>
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					      })}
 | 
				
			||||||
 | 
					      <ToastViewport />
 | 
				
			||||||
 | 
					    </ToastProvider>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										194
									
								
								src/components/ui/use-toast.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										194
									
								
								src/components/ui/use-toast.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,194 @@
 | 
				
			|||||||
 | 
					"use client"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Inspired by react-hot-toast library
 | 
				
			||||||
 | 
					import * as React from "react"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import type {
 | 
				
			||||||
 | 
					  ToastActionElement,
 | 
				
			||||||
 | 
					  ToastProps,
 | 
				
			||||||
 | 
					} from "~/components/ui/toast"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const TOAST_LIMIT = 1
 | 
				
			||||||
 | 
					const TOAST_REMOVE_DELAY = 1000000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ToasterToast = ToastProps & {
 | 
				
			||||||
 | 
					  id: string
 | 
				
			||||||
 | 
					  title?: React.ReactNode
 | 
				
			||||||
 | 
					  description?: React.ReactNode
 | 
				
			||||||
 | 
					  action?: ToastActionElement
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const actionTypes = {
 | 
				
			||||||
 | 
					  ADD_TOAST: "ADD_TOAST",
 | 
				
			||||||
 | 
					  UPDATE_TOAST: "UPDATE_TOAST",
 | 
				
			||||||
 | 
					  DISMISS_TOAST: "DISMISS_TOAST",
 | 
				
			||||||
 | 
					  REMOVE_TOAST: "REMOVE_TOAST",
 | 
				
			||||||
 | 
					} as const
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let count = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function genId() {
 | 
				
			||||||
 | 
					  count = (count + 1) % Number.MAX_SAFE_INTEGER
 | 
				
			||||||
 | 
					  return count.toString()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ActionType = typeof actionTypes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Action =
 | 
				
			||||||
 | 
					  | {
 | 
				
			||||||
 | 
					      type: ActionType["ADD_TOAST"]
 | 
				
			||||||
 | 
					      toast: ToasterToast
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  | {
 | 
				
			||||||
 | 
					      type: ActionType["UPDATE_TOAST"]
 | 
				
			||||||
 | 
					      toast: Partial<ToasterToast>
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  | {
 | 
				
			||||||
 | 
					      type: ActionType["DISMISS_TOAST"]
 | 
				
			||||||
 | 
					      toastId?: ToasterToast["id"]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  | {
 | 
				
			||||||
 | 
					      type: ActionType["REMOVE_TOAST"]
 | 
				
			||||||
 | 
					      toastId?: ToasterToast["id"]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface State {
 | 
				
			||||||
 | 
					  toasts: ToasterToast[]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const addToRemoveQueue = (toastId: string) => {
 | 
				
			||||||
 | 
					  if (toastTimeouts.has(toastId)) {
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const timeout = setTimeout(() => {
 | 
				
			||||||
 | 
					    toastTimeouts.delete(toastId)
 | 
				
			||||||
 | 
					    dispatch({
 | 
				
			||||||
 | 
					      type: "REMOVE_TOAST",
 | 
				
			||||||
 | 
					      toastId: toastId,
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  }, TOAST_REMOVE_DELAY)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  toastTimeouts.set(toastId, timeout)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const reducer = (state: State, action: Action): State => {
 | 
				
			||||||
 | 
					  switch (action.type) {
 | 
				
			||||||
 | 
					    case "ADD_TOAST":
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        ...state,
 | 
				
			||||||
 | 
					        toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case "UPDATE_TOAST":
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        ...state,
 | 
				
			||||||
 | 
					        toasts: state.toasts.map((t) =>
 | 
				
			||||||
 | 
					          t.id === action.toast.id ? { ...t, ...action.toast } : t
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case "DISMISS_TOAST": {
 | 
				
			||||||
 | 
					      const { toastId } = action
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // ! Side effects ! - This could be extracted into a dismissToast() action,
 | 
				
			||||||
 | 
					      // but I'll keep it here for simplicity
 | 
				
			||||||
 | 
					      if (toastId) {
 | 
				
			||||||
 | 
					        addToRemoveQueue(toastId)
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        state.toasts.forEach((toast) => {
 | 
				
			||||||
 | 
					          addToRemoveQueue(toast.id)
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        ...state,
 | 
				
			||||||
 | 
					        toasts: state.toasts.map((t) =>
 | 
				
			||||||
 | 
					          t.id === toastId || toastId === undefined
 | 
				
			||||||
 | 
					            ? {
 | 
				
			||||||
 | 
					                ...t,
 | 
				
			||||||
 | 
					                open: false,
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            : t
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case "REMOVE_TOAST":
 | 
				
			||||||
 | 
					      if (action.toastId === undefined) {
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					          ...state,
 | 
				
			||||||
 | 
					          toasts: [],
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        ...state,
 | 
				
			||||||
 | 
					        toasts: state.toasts.filter((t) => t.id !== action.toastId),
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const listeners: Array<(state: State) => void> = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let memoryState: State = { toasts: [] }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function dispatch(action: Action) {
 | 
				
			||||||
 | 
					  memoryState = reducer(memoryState, action)
 | 
				
			||||||
 | 
					  listeners.forEach((listener) => {
 | 
				
			||||||
 | 
					    listener(memoryState)
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Toast = Omit<ToasterToast, "id">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function toast({ ...props }: Toast) {
 | 
				
			||||||
 | 
					  const id = genId()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const update = (props: ToasterToast) =>
 | 
				
			||||||
 | 
					    dispatch({
 | 
				
			||||||
 | 
					      type: "UPDATE_TOAST",
 | 
				
			||||||
 | 
					      toast: { ...props, id },
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  dispatch({
 | 
				
			||||||
 | 
					    type: "ADD_TOAST",
 | 
				
			||||||
 | 
					    toast: {
 | 
				
			||||||
 | 
					      ...props,
 | 
				
			||||||
 | 
					      id,
 | 
				
			||||||
 | 
					      open: true,
 | 
				
			||||||
 | 
					      onOpenChange: (open) => {
 | 
				
			||||||
 | 
					        if (!open) dismiss()
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    id: id,
 | 
				
			||||||
 | 
					    dismiss,
 | 
				
			||||||
 | 
					    update,
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function useToast() {
 | 
				
			||||||
 | 
					  const [state, setState] = React.useState<State>(memoryState)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  React.useEffect(() => {
 | 
				
			||||||
 | 
					    listeners.push(setState)
 | 
				
			||||||
 | 
					    return () => {
 | 
				
			||||||
 | 
					      const index = listeners.indexOf(setState)
 | 
				
			||||||
 | 
					      if (index > -1) {
 | 
				
			||||||
 | 
					        listeners.splice(index, 1)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }, [state])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    ...state,
 | 
				
			||||||
 | 
					    toast,
 | 
				
			||||||
 | 
					    dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { useToast, toast }
 | 
				
			||||||
							
								
								
									
										25
									
								
								src/server/functions.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/server/functions.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					import "server-only"
 | 
				
			||||||
 | 
					import { db } from "~/server/db"
 | 
				
			||||||
 | 
					import * as schema from "~/server/db/schema"
 | 
				
			||||||
 | 
					import { eq } from "drizzle-orm"
 | 
				
			||||||
 | 
					//import { sql } from "drizzle-orm"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const set_users_name_by_email = async (users_name: string, users_email: string) => {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    console.log('Updating user:', users_email, 'with name:', users_name); // Log input
 | 
				
			||||||
 | 
					    await db.update(schema.users)
 | 
				
			||||||
 | 
					      .set({ name: users_name })
 | 
				
			||||||
 | 
					      .where(eq(schema.users.email, users_email))
 | 
				
			||||||
 | 
					  } catch (error) {
 | 
				
			||||||
 | 
					    console.error('Error updating user name:', error);
 | 
				
			||||||
 | 
					    throw error;  // Ensure we rethrow to be caught by the calling function
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const get_users_name_by_id = async (users_id: string) => {
 | 
				
			||||||
 | 
					  const result = await db.select({
 | 
				
			||||||
 | 
					    users_name: schema.users.name,
 | 
				
			||||||
 | 
					  }).from(schema.users)
 | 
				
			||||||
 | 
					  .where(eq(schema.users.id, users_id))
 | 
				
			||||||
 | 
					  return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user