-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #38 from cs3216-a3-group-4/seeleng/add-password-re…
…set-frontend feat(frontend): add password reset
- Loading branch information
Showing
18 changed files
with
1,466 additions
and
793 deletions.
There are no files selected for viewing
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
154 changes: 154 additions & 0 deletions
154
frontend/app/(unauthenticated)/reset-password/_components/reset-password-complete-form.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
"use client"; | ||
|
||
import { useState } from "react"; | ||
import { SubmitHandler, useForm } from "react-hook-form"; | ||
import { zodResolver } from "@hookform/resolvers/zod"; | ||
import { CircleAlert } from "lucide-react"; | ||
import { z } from "zod"; | ||
|
||
import { completePasswordResetAuthPasswordResetPut } from "@/client"; | ||
import { Alert, AlertDescription } from "@/components/ui/alert"; | ||
import { Box } from "@/components/ui/box"; | ||
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"; | ||
|
||
const resetPasswordCompleteFormSchema = z | ||
.object({ | ||
password: z | ||
.string() | ||
.min(6, "Password should be at least 6 characters long"), | ||
confirmPassword: z.string(), | ||
}) | ||
.refine(({ password, confirmPassword }) => password === confirmPassword, { | ||
message: "Passwords must match", | ||
path: ["confirmPassword"], | ||
}); | ||
|
||
const resetPasswordFormDefault = { | ||
password: "", | ||
confirmPassword: "", | ||
}; | ||
|
||
type ResetPasswordRequestForm = z.infer<typeof resetPasswordCompleteFormSchema>; | ||
|
||
export default function ResetPasswordCompleteForm({ | ||
code, | ||
onComplete, | ||
}: { | ||
code: string; | ||
onComplete: () => void; | ||
}) { | ||
const [isError, setIsError] = useState<boolean>(false); | ||
|
||
const form = useForm<ResetPasswordRequestForm>({ | ||
resolver: zodResolver(resetPasswordCompleteFormSchema), | ||
defaultValues: resetPasswordFormDefault, | ||
}); | ||
|
||
const onSubmit: SubmitHandler<ResetPasswordRequestForm> = async (data) => { | ||
const response = await completePasswordResetAuthPasswordResetPut({ | ||
body: { | ||
password: data.password, | ||
confirm_password: data.confirmPassword, | ||
}, | ||
withCredentials: true, | ||
query: { | ||
code: code, | ||
}, | ||
}); | ||
|
||
if (response.error) { | ||
setIsError(true); | ||
} else { | ||
setIsError(false); | ||
onComplete(); | ||
} | ||
}; | ||
|
||
return ( | ||
<> | ||
<Card className="flex flex-col border-0 md:border px-6 sm:px-12 sm:py-3 md:w-[32rem] md:max-w-lg"> | ||
<CardHeader className="space-y-3"> | ||
<CardTitle>Reset your password</CardTitle> | ||
<CardDescription>Enter your new password!</CardDescription> | ||
</CardHeader> | ||
|
||
<CardContent> | ||
<Box className="space-y-6"> | ||
{isError && ( | ||
<Alert variant="destructive"> | ||
<CircleAlert className="h-5 w-5" /> | ||
<AlertDescription> | ||
Your password needs to match. | ||
</AlertDescription> | ||
</Alert> | ||
)} | ||
<Form {...form}> | ||
<form | ||
className="space-y-10" | ||
onSubmit={form.handleSubmit(onSubmit)} | ||
> | ||
<div className="space-y-4"> | ||
<Box className="flex flex-col space-y-2.5"> | ||
<FormField | ||
control={form.control} | ||
name={"password"} | ||
render={({ field }) => ( | ||
<FormItem> | ||
<FormLabel className="!text-current"> | ||
New Password | ||
</FormLabel> | ||
<FormControl> | ||
<Input {...field} type="password" /> | ||
</FormControl> | ||
<FormMessage /> | ||
</FormItem> | ||
)} | ||
/> | ||
</Box> | ||
</div> | ||
<div className="space-y-4"> | ||
<Box className="flex flex-col space-y-2.5"> | ||
<FormField | ||
control={form.control} | ||
name={"confirmPassword"} | ||
render={({ field }) => ( | ||
<FormItem> | ||
<FormLabel className="!text-current"> | ||
Confirm Password | ||
</FormLabel> | ||
<FormControl> | ||
<Input {...field} type="password" /> | ||
</FormControl> | ||
<FormMessage /> | ||
</FormItem> | ||
)} | ||
/> | ||
</Box> | ||
</div> | ||
<Button className="w-full" type="submit"> | ||
Submit | ||
</Button> | ||
</form> | ||
</Form> | ||
</Box> | ||
</CardContent> | ||
</Card> | ||
</> | ||
); | ||
} |
32 changes: 32 additions & 0 deletions
32
frontend/app/(unauthenticated)/reset-password/_components/reset-password-complete-sent.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import Link from "next/link"; | ||
import { CheckCircleIcon } from "lucide-react"; | ||
|
||
import { Button } from "@/components/ui/button"; | ||
import { | ||
Card, | ||
CardContent, | ||
CardDescription, | ||
CardHeader, | ||
CardTitle, | ||
} from "@/components/ui/card"; | ||
|
||
export default function ResetPasswordCompleteSent() { | ||
return ( | ||
<Card className="flex flex-col border-0 md:border px-6 sm:px-12 sm:py-3 md:w-[32em] md:max-w-lg"> | ||
<CardHeader className="space-y-3"> | ||
<CardTitle>Check your email</CardTitle> | ||
<CardDescription> | ||
All done! Login with your new password. | ||
</CardDescription> | ||
</CardHeader> | ||
<CardContent> | ||
<CheckCircleIcon className="w-24 h-24 m-auto stroke-muted-foreground mt-4" /> | ||
<div className="mt-8 m-auto flex"> | ||
<Link className="w-full" href="/login"> | ||
<Button className="w-full">Proceed to Login</Button> | ||
</Link> | ||
</div> | ||
</CardContent> | ||
</Card> | ||
); | ||
} |
114 changes: 114 additions & 0 deletions
114
...d/app/(unauthenticated)/reset-password/_components/reset-password-create-request-form.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
"use client"; | ||
|
||
import { useState } from "react"; | ||
import { SubmitHandler, useForm } from "react-hook-form"; | ||
import { zodResolver } from "@hookform/resolvers/zod"; | ||
import { CircleAlert } from "lucide-react"; | ||
import { z } from "zod"; | ||
|
||
import { requestPasswordResetAuthPasswordResetPost } from "@/client"; | ||
import TextField from "@/components/form/fields/text-field"; | ||
import Link from "@/components/navigation/link"; | ||
import { Alert, AlertDescription } from "@/components/ui/alert"; | ||
import { Box } from "@/components/ui/box"; | ||
import { Button } from "@/components/ui/button"; | ||
import { | ||
Card, | ||
CardContent, | ||
CardDescription, | ||
CardHeader, | ||
CardTitle, | ||
} from "@/components/ui/card"; | ||
import { Form } from "@/components/ui/form"; | ||
|
||
import ResetPasswordRequestSent from "./reset-password-request-sent"; | ||
|
||
const resetPasswordRequestFormSchema = z.object({ | ||
email: z.string().email("Invalid email address"), | ||
}); | ||
|
||
const resetPasswordFormDefault = { | ||
email: "", | ||
password: "", | ||
}; | ||
|
||
type ResetPasswordRequestForm = z.infer<typeof resetPasswordRequestFormSchema>; | ||
|
||
export default function ResetPasswordCreateRequestForm() { | ||
const [isError, setIsError] = useState<boolean>(false); | ||
const [sent, setSent] = useState<boolean>(false); | ||
|
||
const form = useForm<ResetPasswordRequestForm>({ | ||
resolver: zodResolver(resetPasswordRequestFormSchema), | ||
defaultValues: resetPasswordFormDefault, | ||
}); | ||
|
||
const onSubmit: SubmitHandler<ResetPasswordRequestForm> = async (data) => { | ||
const response = await requestPasswordResetAuthPasswordResetPost({ | ||
body: { email: data.email }, | ||
withCredentials: true, | ||
}); | ||
|
||
if (response.error) { | ||
setIsError(true); | ||
} else { | ||
setIsError(false); | ||
setSent(true); | ||
} | ||
}; | ||
|
||
return sent ? ( | ||
<ResetPasswordRequestSent | ||
email={form.getValues().email} | ||
reset={() => setSent(false)} | ||
/> | ||
) : ( | ||
<> | ||
<Card className="flex flex-col border-0 md:border px-6 sm:px-12 sm:py-3 md:max-w-lg"> | ||
<CardHeader className="space-y-3"> | ||
<CardTitle>Reset your password</CardTitle> | ||
<CardDescription> | ||
Don't worry, enter the email associated with your Jippy account | ||
and we will send you a link to reset your password. | ||
</CardDescription> | ||
</CardHeader> | ||
|
||
<CardContent> | ||
<Box className="space-y-6"> | ||
{isError && ( | ||
<Alert variant="destructive"> | ||
<CircleAlert className="h-5 w-5" /> | ||
<AlertDescription> | ||
Your email or password is incorrect. Please try again, or{" "} | ||
<Link href="/user/password-reset" size="sm"> | ||
reset your password | ||
</Link> | ||
. | ||
</AlertDescription> | ||
</Alert> | ||
)} | ||
<Form {...form}> | ||
<form | ||
className="space-y-10" | ||
onSubmit={form.handleSubmit(onSubmit)} | ||
> | ||
<div className="space-y-4"> | ||
<TextField label="Email" name="email" /> | ||
</div> | ||
<Button className="w-full" type="submit"> | ||
Continue | ||
</Button> | ||
</form> | ||
</Form> | ||
</Box> | ||
</CardContent> | ||
</Card> | ||
<div className="flex gap-x-2 w-full justify-center"> | ||
<p className="text-sm text-muted-foreground">Not registered yet?</p> | ||
<Link href="/register" size="sm"> | ||
Create an account | ||
</Link> | ||
</div> | ||
</> | ||
); | ||
} |
Oops, something went wrong.