You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

287 lines
8.3 KiB

'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'
import { Routes } from '@/config/routes'
const ErrorAlert = ({ message }: { message: string }) => (
<Alert
variant='destructive'
className='bg-red-50 text-red-700 border-red-200'
>
<XCircle className='h-5 w-5 text-red-600' />
<AlertTitle className='text-lg font-semibold'>Error</AlertTitle>
<AlertDescription>{message}</AlertDescription>
</Alert>
)
const SendVerificationButton = ({
onClick,
isLoading,
hasVerificationCode,
}: {
onClick: () => void
isLoading: boolean
hasVerificationCode: boolean
}) => (
<Button
className='w-full text-lg py-6'
onClick={onClick}
disabled={isLoading}
>
{isLoading ? (
<Loader2 className='mr-2 h-5 w-5 animate-spin' />
) : (
<Mail className='mr-2 h-5 w-5' />
)}
{hasVerificationCode
? 'Resend Verification Email'
: 'Send Verification Email'}
</Button>
)
const SendVerificationEmail = () => {
const [successMessage, setSuccessMessage] = useState<string>('')
const [error, setError] = useState<string>('')
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 (
<Alert
variant='default'
className='bg-blue-50 text-blue-700 border-blue-200'
>
<Mail className='h-5 w-5 text-blue-600' />
<AlertTitle className='text-lg font-semibold'>Email Sent</AlertTitle>
<AlertDescription>{successMessage}</AlertDescription>
</Alert>
)
if (error) return <ErrorAlert message={error} />
return null
}
return (
<Card className='w-full max-w-md'>
<CardHeader>
<CardTitle className='text-2xl font-bold'>Email Verification</CardTitle>
<CardDescription>
Send a verification email to verify your account
</CardDescription>
</CardHeader>
<CardContent className='space-y-4'>{renderContent()}</CardContent>
<CardFooter>
<SendVerificationButton
onClick={() => sendVerificationEmailMutation()}
isLoading={isSendingVerificationEmail}
hasVerificationCode={false}
/>
</CardFooter>
</Card>
)
}
const VerifyEmail = ({
userId,
verificationCode,
}: {
userId: string
verificationCode: string
}) => {
const [successMessage, setSuccessMessage] = useState<string>('')
const [error, setError] = useState<string>('')
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 (
<div className='flex justify-center py-8'>
<Loader2 className='h-12 w-12 animate-spin text-primary' />
</div>
)
if (isVerified)
return (
<Alert
variant='default'
className='bg-green-50 text-green-700 border-green-200'
>
<CheckCircle className='h-5 w-5 text-green-600' />
<AlertTitle className='text-lg font-semibold'>Success</AlertTitle>
<AlertDescription>
<div className='flex flex-col gap-2'>
<div>{successMessage}</div>
<Link href={Routes.dashboard} className='font-medium underline'>
Go to Dashboard
</Link>
</div>
</AlertDescription>
</Alert>
)
if (error) return <ErrorAlert message={error} />
return null
}
return (
<Card className='w-full max-w-md'>
<CardHeader>
<CardTitle className='text-2xl font-bold'>Email Verification</CardTitle>
<CardDescription>Verifying your email address...</CardDescription>
</CardHeader>
<CardContent className='space-y-4'>{renderContent()}</CardContent>
<CardFooter className='flex flex-col gap-4'>
{isVerified && (
<Button className='w-full text-lg py-6' asChild>
<Link href='/dashboard'>
Go to Dashboard
<ArrowRight className='ml-2 h-5 w-5' />
</Link>
</Button>
)}
</CardFooter>
</Card>
)
}
const CheckEmailPrompt = () => {
const {
mutate: sendVerificationEmailMutation,
isPending,
isError,
isSuccess,
} = useMutation({
mutationFn: () =>
httpBrowserClient.post(
ApiEndpoints.auth.sendEmailVerificationEmail(),
{}
),
})
return (
<Card className='w-full max-w-md'>
<CardHeader>
<CardTitle className='text-2xl font-bold'>Check your email</CardTitle>
<CardDescription>
We've sent you a verification email. Please check your inbox and click
the link to verify your account.
</CardDescription>
</CardHeader>
<CardContent className='space-y-4'>
{isSuccess && (
<Alert className='bg-green-50 text-green-700 border-green-200'>
<CheckCircle className='h-5 w-5 text-green-600' />
<AlertTitle className='text-lg font-semibold'>
Email Sent
</AlertTitle>
<AlertDescription>
A new verification email has been sent to your inbox
</AlertDescription>
</Alert>
)}
{isError && (
<Alert
variant='destructive'
className='bg-red-50 text-red-700 border-red-200'
>
<XCircle className='h-5 w-5 text-red-600' />
<AlertTitle className='text-lg font-semibold'>Error</AlertTitle>
<AlertDescription>
Failed to resend verification email
</AlertDescription>
</Alert>
)}
</CardContent>
<CardFooter className='flex flex-col gap-4'>
<div className='flex items-center gap-2 justify-center w-full'>
<span className='text-sm text-gray-600'>
Didn't receive the email?
</span>
<Button
variant='link'
onClick={() => sendVerificationEmailMutation()}
disabled={isPending}
className='text-sm p-0 h-auto font-semibold'
>
{isPending ? (
<>
<Loader2 className='mr-2 h-4 w-4 animate-spin' />
Sending...
</>
) : (
'Click to resend'
)}
</Button>
</div>
</CardFooter>
</Card>
)
}
export default function VerifyEmailPage() {
const searchParams = useSearchParams()
const userId = searchParams.get('userId')
const verificationCode = searchParams.get('verificationCode')
const verificationEmailSent = searchParams.get('verificationEmailSent')
return (
<div className='flex min-h-screen items-center justify-center bg-gray-100 dark:bg-gray-900 p-4'>
{userId && verificationCode ? (
<VerifyEmail userId={userId} verificationCode={verificationCode} />
) : verificationEmailSent ? (
<CheckEmailPrompt />
) : (
<SendVerificationEmail />
)}
</div>
)
}