feature - added password change, email change, and email confirm forms

This commit is contained in:
James Wyndham 2024-06-05 14:55:51 +08:00
parent 0e2b6e79e5
commit 66ccc4ee4f
10 changed files with 305 additions and 25 deletions

View File

@ -0,0 +1,89 @@
"use client"
import React from "react";
import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import { changeEmailValidationSchema } from "@/utils/form";
import { toast } from "react-toastify";
import PocketBase from 'pocketbase';
import PageWrapper from "@/components/Utilities/PageWrapper";
import { usePathname, useRouter } from 'next/navigation';
import Background from "@/components/Utilities/Background";
import PageHeader from "@/sections/PageHeader";
const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL as string);
export default function ConfirmEmailChangePage() {
const pathName = usePathname();
const token = pathName.split('/').at(-1);
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
reset
} = useForm({
resolver: yupResolver(changeEmailValidationSchema),
});
const onSubmit = async (data: any) => {
try {
await pb.collection('user').confirmEmailChange(
token ?? "",
data.password,
);
reset();
document.getElementById("sign-in-modal")?.click();
} catch (error) {
if (error instanceof Error) {
toast.error("There was a problem. Please try change your email again", {
position: "bottom-left",
autoClose: 5000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "colored",
});
}
}
};
return (
<PageWrapper>
<Background>
<div className="h-screen w-screen flex items-center flex-col">
<PageHeader
title={"Enter Your Password To Change Your Email"}
subtitle={<></>}
/>
<form onSubmit={handleSubmit(onSubmit)} className="w-full max-w-xl px-4">
<div className="relative mt-6">
<input
type="password"
className="py-3 px-4 block w-full bg-base-200 text-base-content border-primary/40 rounded-lg text-sm focus:border-secondary focus:ring-secondary disabled:opacity-50 disabled:pointer-events-none "
placeholder="Password…"
aria-label="Password…"
autoComplete="on"
{...register("password")}
/>
<div className="text-start text-sm italic text-error-content">
{errors.password?.message}&nbsp;
</div>
</div>
<div className="flex flex-row w-full justify-between">
<button
disabled={isSubmitting}
type="submit"
className={isSubmitting ? "btn btn-gray": "btn btn-primary"}
>
Change Email
{isSubmitting && <div className="loading"></div>}
</button>
</div>
</form>
</div>
</Background>
</PageWrapper>
);
}

View File

@ -0,0 +1,103 @@
"use client"
import React from "react";
import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import { passwordValidationSchema } from "@/utils/form";
import { toast } from "react-toastify";
import PocketBase from 'pocketbase';
import PageWrapper from "@/components/Utilities/PageWrapper";
import { usePathname, useRouter } from 'next/navigation';
import Background from "@/components/Utilities/Background";
import PageHeader from "@/sections/PageHeader";
const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL as string);
export default function ConfirmPasswordResetPage() {
const pathName = usePathname();
const token = pathName.split('/').at(-1);
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
reset
} = useForm({
resolver: yupResolver(passwordValidationSchema),
});
const onSubmit = async (data: any) => {
try {
await pb.collection('user').confirmPasswordReset(
token ?? "",
data.newPassword,
data.newPasswordConfirm,
);
reset()
document.getElementById("sign-in-modal")?.click();
} catch (error) {
if (error instanceof Error) {
toast.error("There was a problem. Please try reset your password again", {
position: "bottom-left",
autoClose: 5000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "colored",
});
}
}
};
return (
<PageWrapper>
<Background>
<div className="h-screen w-screen flex items-center flex-col">
<PageHeader
title={"Enter Your New Password"}
subtitle={<></>}
/>
<form onSubmit={handleSubmit(onSubmit)} className="w-full max-w-xl px-4">
<div className="relative mt-6">
<input
type="password"
className="py-3 px-4 block w-full bg-base-200 text-base-content border-primary/40 rounded-lg text-sm focus:border-secondary focus:ring-secondary disabled:opacity-50 disabled:pointer-events-none "
placeholder="New password…"
aria-label="New password…"
autoComplete="on"
{...register("newPassword")}
/>
<div className="text-start text-sm italic text-error-content">
{errors.newPassword?.message}&nbsp;
</div>
</div>
<div className="relative mt-6">
<input
type="password"
className="py-3 px-4 block w-full bg-base-200 text-base-content border-primary/40 rounded-lg text-sm focus:border-secondary focus:ring-secondary disabled:opacity-50 disabled:pointer-events-none "
placeholder="Confirm new password…"
aria-label="Confirm new password…"
autoComplete="on"
{...register("newPasswordConfirm")}
/>
<div className="text-start text-sm italic text-error-content">
{errors.newPasswordConfirm?.message}&nbsp;
</div>
</div>
<div className="flex flex-row w-full justify-between">
<button
disabled={isSubmitting}
type="submit"
className={isSubmitting ? "btn btn-gray": "btn btn-primary"}
>
Reset Password
{isSubmitting && <div className="loading"></div>}
</button>
</div>
</form>
</div>
</Background>
</PageWrapper>
);
}

View File

@ -0,0 +1,60 @@
"use client"
import React, { useEffect } from "react";
import { toast } from "react-toastify";
import PocketBase from 'pocketbase';
import PageWrapper from "@/components/Utilities/PageWrapper";
import { usePathname, useRouter } from 'next/navigation';
import Background from "@/components/Utilities/Background";
import PageHeader from "@/sections/PageHeader";
const pb = new PocketBase(process.env.NEXT_PUBLIC_POCKETBASE_URL as string);
export default function ConfirmVerification() {
const pathName = usePathname();
const router = useRouter();
const token = pathName.split('/').at(-1);
useEffect(() => {
(async () => {
try {
await pb.collection('user').confirmVerification(
token ?? ""
);
} catch (error) {
if (error instanceof Error) {
toast.error("There was a problem. Please try confirm your email again", {
position: "bottom-left",
autoClose: 5000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "colored",
});
}
}
})()
}, [])
const handleSignInRedirect = () => {
document.getElementById("sign-in-modal")?.click();
};
return (
<PageWrapper>
<Background>
<div className="h-screen w-screen flex items-center flex-col">
<PageHeader
title={"Your Email Was Verified"}
subtitle={<></>}
/>
<button onClick={handleSignInRedirect} className="btn btn-primary mt-4">
Go to Sign In
</button>
</div>
</Background>
</PageWrapper>
);
}

View File

@ -46,7 +46,7 @@ export default async function RootLayout({
<GTagProvider />
<body className={`${arimo.className} bg-base-100 flex`}>
<ThemeProvider>
<Header isUserLoggedIn={isUserLoggedIn} />
<Header isUserLoggedIn={isUserLoggedIn} authString={cookies().get("pb_auth")?.value || ""} />
{children}
<ToastContainer
position="bottom-left"

View File

@ -1,6 +1,6 @@
"use client";
import React, { useEffect, useState } from "react";
import React, { useEffect, useState, useRef } from "react";
import Navigation from "@/components/Navigation";
import ModalSignUp from "@/components/Modals/ModalSignUp";
import ModalSignIn from "@/components/Modals/ModalSignIn";
@ -12,22 +12,27 @@ import { useTheme } from "next-themes";
import Link from "next/link";
import ModalPasswordReset from "./Modals/ModalPasswordReset";
import {Person24Filled, SignOut24Filled} from '@fluentui/react-icons'
import ModalEmailChange from "./Modals/ModalEmailChange";
import { usePathname } from "next/navigation";
interface HeaderProps {
isUserLoggedIn: boolean;
authString: string;
}
export default function Header({ isUserLoggedIn }: HeaderProps) {
export default function Header({ isUserLoggedIn, authString }: HeaderProps) {
//Advice: Remove this and replace it with proper state management like Redux, Recoil or Zustand
const [user, setUser] = useState<User | undefined>();
const [statefulUser, setStatefulUser] = useState<User | undefined>();
const pathName = usePathname();
useEffect(() => {
(async () => {
const user = await getUser();
setUser(user as User);
})();
}, [statefulUser]);
//End Advice
const { theme, setTheme } = useTheme();
return (
<header className="absolute w-full z-30">
@ -150,6 +155,7 @@ export default function Header({ isUserLoggedIn }: HeaderProps) {
</div>
<ModalSignIn />
<ModalPasswordReset />
<ModalEmailChange authString={authString} />
<ModalSignUp setUser={setStatefulUser} />
</header>
);

View File

@ -1,28 +1,30 @@
import React, { useRef } from "react";
import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import { signInValidationSchema } from "@/utils/form";
import { emailValidationSchema } from "@/utils/form";
import { toast } from "react-toastify";
import pb from "@/lib/pocketbase";
import {Dismiss20Filled} from "@fluentui/react-icons"
import { redirect } from "next/navigation";
function ModalEmailChange() {
function ModalEmailChange({authString}:{authString:string}) {
const {
register,
handleSubmit,
reset,
formState: { errors, isSubmitting },
} = useForm({
resolver: yupResolver(signInValidationSchema),
resolver: yupResolver(emailValidationSchema),
});
const onSubmit = async (data: any) => {
console.log(data);
pb.authStore.loadFromCookie(authString);
!pb.authStore.isValid && redirect("/");
try {
//login user
if (await pb.collection("user").requestPasswordReset(data.email)) {
if (await pb.collection("user").requestEmailChange(data.email)) {
reset();
document.getElementById("password-reset-modal")?.click();
document.getElementById("email-change-modal")?.click();
document.getElementById("sign-in-modal")?.click();
}
} catch (error) {
@ -44,17 +46,17 @@ function ModalEmailChange() {
<>
<input
type="checkbox"
id="password-reset-modal"
id="email-change-modal"
className="modal-toggle"
onClick={() => {
reset();
}}
/>
<label htmlFor="password-reset-modal" className="modal cursor-pointer">
<label htmlFor="email-change-modal" className="modal cursor-pointer">
<label className="modal-box relative bg-base-100 max-w-full md:max-w-[450px] py-4 px-3 md:p-6">
<div className="flex justify-end pb-2 select-none">
<label
htmlFor="password-reset-modal"
htmlFor="email-change-modal"
className="cursor-pointer text-base-content"
>
<Dismiss20Filled />
@ -63,9 +65,9 @@ function ModalEmailChange() {
<div>
<div className="w-[100%] bg-gradient-to-r from-primary to-secondary px-6 pt-2 mt-3 pb-6 rounded-lg text-primary-content">
<h3 className="pb-1 text-3xl font-bold md:text-3xl pt-6">
Reset Your Password
Change Your Email
</h3>
<p className="text-sm md:text-base">Enter in your email</p>
<p className="text-sm md:text-base">Enter in your new email</p>
</div>
<form onSubmit={handleSubmit(onSubmit)} className="w-full">
<div className="relative mt-6">
@ -86,9 +88,9 @@ function ModalEmailChange() {
<button
disabled={isSubmitting}
type="submit"
className="btn btn-primary"
className={isSubmitting ? "btn btn-gray": "btn btn-primary"}
>
Reset Password
Change Email
{isSubmitting && <div className="loading"></div>}
</button>
</div>

View File

@ -1,10 +1,11 @@
import React, { useRef } from "react";
import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import { signInValidationSchema } from "@/utils/form";
import { emailValidationSchema } from "@/utils/form";
import { toast } from "react-toastify";
import pb from "@/lib/pocketbase";
import {Dismiss20Filled} from '@fluentui/react-icons'
import { redirect } from "next/navigation";
function ModalPasswordReset() {
const {
@ -13,7 +14,7 @@ function ModalPasswordReset() {
reset,
formState: { errors, isSubmitting },
} = useForm({
resolver: yupResolver(signInValidationSchema),
resolver: yupResolver(emailValidationSchema),
});
const onSubmit = async (data: any) => {

View File

@ -173,7 +173,7 @@ function AccountContent({ user }: ManageSubscriptionProps) {
</button>
<button
onClick={() =>
document.getElementById("reset-password-modal")?.click()
document.getElementById("password-reset-modal")?.click()
}
className="btn btn-sm btn-secondary md:ml-auto text-primary-content capitalize border-none"
>
@ -181,7 +181,7 @@ function AccountContent({ user }: ManageSubscriptionProps) {
</button>
<button
onClick={() =>
document.getElementById("change-email-modal")?.click()
document.getElementById("email-change-modal")?.click()
}
className="btn btn-sm btn-secondary text-primary-content capitalize border-none"
>

View File

@ -1,7 +1,7 @@
"use client";
import pb from "@/lib/pocketbase";
import { newsletterValidationSchema } from "@/utils/form";
import { emailValidationSchema } from "@/utils/form";
import { yupResolver } from "@hookform/resolvers/yup";
import React from "react";
import { useForm } from "react-hook-form";
@ -13,7 +13,7 @@ function Newsletter() {
handleSubmit,
formState: { errors },
} = useForm({
resolver: yupResolver(newsletterValidationSchema),
resolver: yupResolver(emailValidationSchema),
});
const onSubmit = async (data: { email: string }) => {

View File

@ -51,6 +51,23 @@ const signUpValidationSchema = Yup.object().shape({
.required("Password is required.")
.min(8, "Password is too short - should be 8 characters minimum.")
});
const passwordValidationSchema = Yup.object().shape({
newPassword: Yup.string()
.password()
.required("Password is required.")
.min(8, "Password is too short - should be 8 characters minimum."),
newPasswordConfirm: Yup.string()
.password()
.required("Password is required.")
.min(8, "Password is too short - should be 8 characters minimum.")
});
const changeEmailValidationSchema = Yup.object().shape({
password: Yup.string()
.password()
.required("Password is required.")
.min(8, "Password is too short - should be 8 characters minimum."),
});
const waitinglistValidationSchema = Yup.object().shape({
firstName: Yup.string().required("First Name is required"),
lastName: Yup.string().required("Last Name is required"),
@ -66,7 +83,7 @@ const contactUsValidationSchema = Yup.object().shape({
email: Yup.string().email().required("E-mail is required"),
});
const newsletterValidationSchema = Yup.object().shape({
const emailValidationSchema = Yup.object().shape({
email: Yup.string().email().required("E-mail is required"),
});
@ -80,7 +97,9 @@ const signInValidationSchema = Yup.object().shape({
});
export {
newsletterValidationSchema,
emailValidationSchema,
changeEmailValidationSchema,
passwordValidationSchema,
signUpValidationSchema,
signInValidationSchema,
waitinglistValidationSchema,