From 333d1610fad10e70759545f223959503288a02c6 Mon Sep 17 00:00:00 2001 From: Mohamed Bassem Date: Thu, 10 Jul 2025 19:34:31 +0000 Subject: feat: Add invite user support --- apps/web/components/invite/InviteAcceptForm.tsx | 311 ++++++++++++++++++++++++ 1 file changed, 311 insertions(+) create mode 100644 apps/web/components/invite/InviteAcceptForm.tsx (limited to 'apps/web/components/invite/InviteAcceptForm.tsx') diff --git a/apps/web/components/invite/InviteAcceptForm.tsx b/apps/web/components/invite/InviteAcceptForm.tsx new file mode 100644 index 00000000..dcebed73 --- /dev/null +++ b/apps/web/components/invite/InviteAcceptForm.tsx @@ -0,0 +1,311 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useRouter } from "next/navigation"; +import { ActionButton } from "@/components/ui/action-button"; +import { Alert, AlertDescription } from "@/components/ui/alert"; +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { api } from "@/lib/trpc"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { TRPCClientError } from "@trpc/client"; +import { AlertCircle, Clock, Loader2, Mail, UserPlus } from "lucide-react"; +import { signIn } from "next-auth/react"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; + +const inviteAcceptSchema = z + .object({ + name: z.string().min(1, "Name is required"), + password: z.string().min(8, "Password must be at least 8 characters"), + confirmPassword: z + .string() + .min(8, "Password must be at least 8 characters"), + }) + .refine((data) => data.password === data.confirmPassword, { + message: "Passwords don't match", + path: ["confirmPassword"], + }); + +interface InviteAcceptFormProps { + token: string; +} + +export default function InviteAcceptForm({ token }: InviteAcceptFormProps) { + const router = useRouter(); + + const form = useForm>({ + resolver: zodResolver(inviteAcceptSchema), + }); + + const [errorMessage, setErrorMessage] = useState(null); + + const { + isPending: loading, + data: inviteData, + error, + } = api.invites.get.useQuery({ token }); + + useEffect(() => { + if (error) { + setErrorMessage(error.message); + } + }, [error]); + + const acceptInviteMutation = api.invites.accept.useMutation(); + + const handleBackToSignIn = () => { + router.push("/signin"); + }; + + if (loading) { + return ( + + + + Loading Invitation + + + Please wait while we verify your invitation... + + + +
+ +
+
+
+ ); + } + + if (!inviteData) { + return ( + + + + Invalid Invitation + + + This invitation link is not valid or has been removed. + + + +
+ +
+ + {errorMessage && ( + + + {errorMessage} + + )} + + +
+
+ ); + } + + if (inviteData.expired) { + return ( + + + + Invitation Expired + + + This invitation link has expired and is no longer valid. + + + +
+ +
+ +
+

+ Please contact an administrator to request a new invitation. +

+
+ + +
+
+ ); + } + + return ( + + + + Accept Your Invitation + + + Complete your account setup to join Karakeep + + + +
+ +
+ +
+
+ +

Invited email:

+
+

{inviteData.email}

+
+ +
+ { + try { + await acceptInviteMutation.mutateAsync({ + token, + name: value.name, + password: value.password, + }); + + // Sign in the user after successful account creation + const resp = await signIn("credentials", { + redirect: false, + email: inviteData.email, + password: value.password, + }); + + if (!resp || !resp.ok || resp.error) { + setErrorMessage( + resp?.error ?? + "Account created but sign in failed. Please try signing in manually.", + ); + return; + } + + router.replace("/"); + } catch (e) { + if (e instanceof TRPCClientError) { + setErrorMessage(e.message); + } else { + setErrorMessage("An unexpected error occurred"); + } + } + })} + className="space-y-4" + > + {errorMessage && ( + + + {errorMessage} + + )} + + ( + + Full Name + + + + + + )} + /> + + ( + + Password + + + + + + )} + /> + + ( + + Confirm Password + + + + + + )} + /> + + + {form.formState.isSubmitting || acceptInviteMutation.isPending ? ( + <> + + Creating Account... + + ) : ( + "Create Account & Sign In" + )} + + + + + +
+
+ ); +} -- cgit v1.2.3-70-g09d2