feature - added password change, email change, and email confirm forms
This commit is contained in:
parent
0e2b6e79e5
commit
66ccc4ee4f
|
@ -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}
|
||||
</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>
|
||||
);
|
||||
}
|
|
@ -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}
|
||||
</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}
|
||||
</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>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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"
|
||||
>
|
||||
|
|
|
@ -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 }) => {
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue