Skip to content

Commit

Permalink
Merge pull request #38 from cs3216-a3-group-4/seeleng/add-password-re…
Browse files Browse the repository at this point in the history
…set-frontend

feat(frontend): add password reset
  • Loading branch information
seelengxd authored Sep 24, 2024
2 parents 3f6ecc9 + f0f3bf7 commit ff637f7
Show file tree
Hide file tree
Showing 18 changed files with 1,466 additions and 793 deletions.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ type LoginForm = z.infer<typeof loginFormSchema>;

function LoginPage() {
const router = useRouter();
const isLoggedIn = useUserStore((state) => state.isLoggedIn);
const setLoggedIn = useUserStore((state) => state.setLoggedIn);
const [isError, setIsError] = useState<boolean>(false);

Expand All @@ -63,10 +62,6 @@ function LoginPage() {
}
};

if (isLoggedIn) {
router.push("/");
}

return (
<Box className="flex flex-col m-auto w-full justify-center items-center gap-y-6">
<Card className="flex flex-col border-0 md:border px-6 sm:px-12 sm:py-3 md:max-w-lg">
Expand Down Expand Up @@ -145,6 +140,11 @@ function LoginPage() {
Create an account
</Link>
</div>
<div className="flex gap-x-2 w-full justify-center">
<Link href="/reset-password" size="sm">
Forgot your password?
</Link>
</div>
</Box>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import { Alert, AlertDescription } from "@/components/ui/alert";
import { Box } from "@/components/ui/box";
import { Button } from "@/components/ui/button";
import { Form } from "@/components/ui/form";
import { useUserStore } from "@/store/user/user-store-provider";

const registerFormSchema = z.object({
email: z.string().email("Invalid email address"),
Expand All @@ -32,7 +31,6 @@ type RegisterForm = z.infer<typeof registerFormSchema>;

function RegisterPage() {
const router = useRouter();
const isLoggedIn = useUserStore((state) => state.isLoggedIn);
const [isError, setIsError] = useState<boolean>(false);
const form = useForm<RegisterForm>({
resolver: zodResolver(registerFormSchema),
Expand All @@ -53,10 +51,6 @@ function RegisterPage() {
}
};

if (isLoggedIn) {
router.push("/");
}

return (
<Box className="flex w-full gap-x-24">
<Box className="flex justify-center items-center bg-card text-card-foreground px-12 md:px-20 w-full lg:w-6/12">
Expand Down
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>
</>
);
}
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>
);
}
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&apos;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>
</>
);
}
Loading

0 comments on commit ff637f7

Please sign in to comment.