diff --git a/apps/web/src/app/(unauthenticated)/reset-password/page.tsx b/apps/web/src/app/(unauthenticated)/reset-password/page.tsx new file mode 100644 index 000000000..d782e5039 --- /dev/null +++ b/apps/web/src/app/(unauthenticated)/reset-password/page.tsx @@ -0,0 +1,36 @@ +import Image from 'next/image'; +import Link from 'next/link'; + +import backgroundPattern from '~/assets/background-pattern.png'; +import { ResetPasswordForm } from '~/components/forms/reset-password'; + +export default function ResetPasswordPage() { + return ( + + + + + + + + Reset Password + + Please choose your new password + + + + + Don't have an account?{' '} + + Sign up + + + + + + ); +} diff --git a/apps/web/src/components/forms/reset-password.tsx b/apps/web/src/components/forms/reset-password.tsx new file mode 100644 index 000000000..db8631571 --- /dev/null +++ b/apps/web/src/components/forms/reset-password.tsx @@ -0,0 +1,138 @@ +'use client'; + +import { useEffect } from 'react'; + +import { useRouter, useSearchParams } from 'next/navigation'; + +import { zodResolver } from '@hookform/resolvers/zod'; +import { Loader } from 'lucide-react'; +import { useForm } from 'react-hook-form'; +import { z } from 'zod'; + +import { ErrorCode, isErrorCode } from '@documenso/lib/next-auth/error-codes'; +import { cn } from '@documenso/ui/lib/utils'; +import { Button } from '@documenso/ui/primitives/button'; +import { Input } from '@documenso/ui/primitives/input'; +import { Label } from '@documenso/ui/primitives/label'; +import { useToast } from '@documenso/ui/primitives/use-toast'; + +const ERROR_MESSAGES = { + [ErrorCode.CREDENTIALS_NOT_FOUND]: 'No account found with that email address.', + [ErrorCode.INCORRECT_EMAIL_PASSWORD]: 'No account found with that email address.', + [ErrorCode.USER_MISSING_PASSWORD]: + 'This account appears to be using a social login method, please sign in using that method', +}; + +export const ZResetPasswordFormSchema = z + .object({ + password: z.string().min(6).max(72), + repeatedPassword: z.string().min(6).max(72), + }) + .refine((data) => data.password === data.repeatedPassword, { + path: ['repeatedPassword'], + message: "Password don't match", + }); + +export type TResetPasswordFormSchema = z.infer; + +export type ResetPasswordFormProps = { + className?: string; +}; + +export const ResetPasswordForm = ({ className }: ResetPasswordFormProps) => { + const searchParams = useSearchParams(); + const router = useRouter(); + + const { toast } = useToast(); + + const { + register, + handleSubmit, + formState: { errors, isSubmitting }, + } = useForm({ + values: { + password: '', + repeatedPassword: '', + }, + resolver: zodResolver(ZResetPasswordFormSchema), + }); + + const errorCode = searchParams?.get('error'); + + useEffect(() => { + let timeout: NodeJS.Timeout | null = null; + + if (isErrorCode(errorCode)) { + timeout = setTimeout(() => { + toast({ + variant: 'destructive', + description: ERROR_MESSAGES[errorCode] ?? 'An unknown error occurred', + }); + }, 0); + } + + return () => { + if (timeout) { + clearTimeout(timeout); + } + }; + }, [errorCode, toast]); + + const onFormSubmit = ({ password, repeatedPassword }: TResetPasswordFormSchema) => { + console.log(password, repeatedPassword); + + router.push('/signin'); + }; + + return ( + + + + Password + + + + + {errors.password && ( + {errors.password.message} + )} + + + + + Confirm Password + + + + + {errors.repeatedPassword && ( + {errors.repeatedPassword.message} + )} + + + + {isSubmitting && } + Reset Password + + + ); +};
Please choose your new password
+ Don't have an account?{' '} + + Sign up + +