From 85dddc61fb4171d5a39133f411d71085b8f0a38b Mon Sep 17 00:00:00 2001 From: isra el Date: Mon, 6 Jan 2025 06:17:19 +0300 Subject: [PATCH] feat(web): implement email verification ui --- web/app/(app)/(auth)/verify-email/page.tsx | 202 +++++++++++++++++++++ web/config/api.ts | 3 + 2 files changed, 205 insertions(+) create mode 100644 web/app/(app)/(auth)/verify-email/page.tsx diff --git a/web/app/(app)/(auth)/verify-email/page.tsx b/web/app/(app)/(auth)/verify-email/page.tsx new file mode 100644 index 0000000..c1ba3eb --- /dev/null +++ b/web/app/(app)/(auth)/verify-email/page.tsx @@ -0,0 +1,202 @@ +'use client' + +import { useState, useEffect } from 'react' +import { useSearchParams } from 'next/navigation' +import Link from 'next/link' +import { Button } from '@/components/ui/button' +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from '@/components/ui/card' +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' +import { Loader2, CheckCircle, XCircle, Mail, ArrowRight } from 'lucide-react' +import { useMutation } from '@tanstack/react-query' +import httpBrowserClient from '@/lib/httpBrowserClient' +import { ApiEndpoints } from '@/config/api' + +const ErrorAlert = ({ message }: { message: string }) => ( + + + Error + {message} + +) + +const SendVerificationButton = ({ + onClick, + isLoading, + hasVerificationCode, +}: { + onClick: () => void + isLoading: boolean + hasVerificationCode: boolean +}) => ( + +) + +const SendVerificationEmail = () => { + const [successMessage, setSuccessMessage] = useState('') + const [error, setError] = useState('') + + const { + mutate: sendVerificationEmailMutation, + isPending: isSendingVerificationEmail, + } = useMutation({ + mutationFn: () => + httpBrowserClient.post( + ApiEndpoints.auth.sendEmailVerificationEmail(), + {} + ), + onSuccess: () => { + setSuccessMessage('Verification email has been sent to your inbox') + }, + onError: (error: any) => { + setError(error.message || 'Failed to send verification email') + }, + }) + + const renderContent = () => { + if (successMessage) + return ( + + + Email Sent + {successMessage} + + ) + if (error) return + return null + } + + return ( + + + Email Verification + + Send a verification email to verify your account + + + {renderContent()} + + sendVerificationEmailMutation()} + isLoading={isSendingVerificationEmail} + hasVerificationCode={false} + /> + + + ) +} + +const VerifyEmail = ({ + userId, + verificationCode, +}: { + userId: string + verificationCode: string +}) => { + const [successMessage, setSuccessMessage] = useState('') + const [error, setError] = useState('') + const [isVerified, setIsVerified] = useState(false) + + const { mutate: verifyEmailMutation, isPending: isVerifyingEmail } = + useMutation({ + mutationFn: () => + httpBrowserClient.post('/auth/verify-email', { + userId, + verificationCode, + }), + onSuccess: () => { + setIsVerified(true) + setSuccessMessage('Your email has been successfully verified') + }, + onError: (error: any) => { + setError(error.message || 'Failed to verify email') + }, + }) + + useEffect(() => { + verifyEmailMutation() + }, [verifyEmailMutation]) + + const renderContent = () => { + if (isVerifyingEmail) + return ( +
+ +
+ ) + if (isVerified) + return ( + + + Success + {successMessage} + + ) + if (error) return + return null + } + + return ( + + + Email Verification + Verifying your email address... + + {renderContent()} + + {isVerified && ( + + )} + + + ) +} + +export default function VerifyEmailPage() { + const searchParams = useSearchParams() + const userId = searchParams.get('userId') + const verificationCode = searchParams.get('verificationCode') + + return ( +
+ {userId && verificationCode ? ( + + ) : ( + + )} +
+ ) +} diff --git a/web/config/api.ts b/web/config/api.ts index d6a9c1b..4a5c9ba 100644 --- a/web/config/api.ts +++ b/web/config/api.ts @@ -8,6 +8,9 @@ export const ApiEndpoints = { whoAmI: () => '/auth/who-am-i', + sendEmailVerificationEmail: () => '/auth/send-email-verification-email', + verifyEmail: () => '/auth/verify-email', + requestPasswordReset: () => '/auth/request-password-reset', resetPassword: () => '/auth/reset-password',