Made it so users have to verify their e-mail starting to use the application
This commit is contained in:
@@ -16,9 +16,9 @@ export default function useUser({redirectTo = "", redirectIfFound = false} = {})
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
// If redirectTo is set, redirect if the user was not found.
|
// If redirectTo is set, redirect if the user was not found.
|
||||||
(redirectTo && !redirectIfFound && !user) ||
|
(redirectTo && !redirectIfFound && (!user || (user && !user.isVerified))) ||
|
||||||
// If redirectIfFound is also set, redirect if the user was found
|
// If redirectIfFound is also set, redirect if the user was found
|
||||||
(redirectIfFound && user)
|
(redirectIfFound && user && user.isVerified)
|
||||||
) {
|
) {
|
||||||
Router.push(redirectTo);
|
Router.push(redirectTo);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export interface User {
|
|||||||
desiredLevels: {[key in Module]: number};
|
desiredLevels: {[key in Module]: number};
|
||||||
type: Type;
|
type: Type;
|
||||||
bio: string;
|
bio: string;
|
||||||
|
isVerified: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Stat {
|
export interface Stat {
|
||||||
|
|||||||
153
src/pages/action.tsx
Normal file
153
src/pages/action.tsx
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
/* eslint-disable @next/next/no-img-element */
|
||||||
|
import {User} from "@/interfaces/user";
|
||||||
|
import {toast, ToastContainer} from "react-toastify";
|
||||||
|
import axios from "axios";
|
||||||
|
import {FormEvent, useEffect, useState} from "react";
|
||||||
|
import Head from "next/head";
|
||||||
|
import useUser from "@/hooks/useUser";
|
||||||
|
import {Divider} from "primereact/divider";
|
||||||
|
import Button from "@/components/Low/Button";
|
||||||
|
import {BsArrowRepeat, BsCheck} from "react-icons/bs";
|
||||||
|
import Link from "next/link";
|
||||||
|
import Input from "@/components/Low/Input";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import {useRouter} from "next/router";
|
||||||
|
|
||||||
|
export function getServerSideProps({query, res}: {query: {oobCode: string; mode: string; apiKey?: string; continueUrl?: string}; res: any}) {
|
||||||
|
if (!query || !query.oobCode || !query.mode) {
|
||||||
|
res.setHeader("location", "/login");
|
||||||
|
res.statusCode = 302;
|
||||||
|
res.end();
|
||||||
|
return {
|
||||||
|
props: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
code: query.oobCode,
|
||||||
|
mode: query.mode,
|
||||||
|
apiKey: query.apiKey,
|
||||||
|
continueUrl: query.continueUrl,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Reset({code, mode, apiKey, continueUrl}: {code: string; mode: string; apiKey?: string; continueUrl?: string}) {
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
useUser({
|
||||||
|
redirectTo: "/",
|
||||||
|
redirectIfFound: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (mode === "signIn") {
|
||||||
|
axios
|
||||||
|
.post<{ok: boolean}>("/api/reset/verify", {
|
||||||
|
link: `https://encoach.com/action?apiKey=${apiKey}&mode=${mode}&oobCode=${code}&continueUrl=${continueUrl}`,
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.data.ok) {
|
||||||
|
toast.success("Your account has been verified!", {toastId: "verify-successful"});
|
||||||
|
setTimeout(() => {
|
||||||
|
router.push("/");
|
||||||
|
}, 2000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.error("Something went wrong! Please make sure to click the link in your e-mail again!", {toastId: "verify-error"});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
toast.error("Something went wrong! Please make sure to click the link in your e-mail again!", {toastId: "verify-error"});
|
||||||
|
})
|
||||||
|
.finally(() => setIsLoading(false));
|
||||||
|
}
|
||||||
|
}, [apiKey, code, continueUrl, mode, router]);
|
||||||
|
|
||||||
|
const login = (e: FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
setIsLoading(true);
|
||||||
|
axios
|
||||||
|
.post<{ok: boolean}>("/api/reset/confirm", {code, password})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.data.ok) {
|
||||||
|
toast.success("Your password has been reset!", {toastId: "reset-successful"});
|
||||||
|
setTimeout(() => {
|
||||||
|
router.push("/login");
|
||||||
|
}, 2000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.error("Something went wrong! Please make sure to click the link in your e-mail again!", {toastId: "reset-error"});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
toast.error("Something went wrong! Please make sure to click the link in your e-mail again!", {toastId: "reset-error"});
|
||||||
|
})
|
||||||
|
.finally(() => setIsLoading(false));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>Reset | EnCoach</title>
|
||||||
|
<meta name="description" content="Generated by create next app" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
</Head>
|
||||||
|
<main className="w-full h-[100vh] flex bg-white text-black">
|
||||||
|
<ToastContainer />
|
||||||
|
<section className="h-full w-fit min-w-fit relative hidden lg:flex">
|
||||||
|
<div className="absolute h-full w-full bg-mti-rose-light z-10 bg-opacity-50" />
|
||||||
|
<img src="/people-talking-tablet.png" alt="People smiling looking at a tablet" className="h-full aspect-auto" />
|
||||||
|
</section>
|
||||||
|
{mode === "reset" && (
|
||||||
|
<section className="h-full w-full flex flex-col items-center justify-center gap-2">
|
||||||
|
<div className="flex flex-col gap-2 items-center relative">
|
||||||
|
<img src="/logo_title.png" alt="EnCoach's Logo" className="w-36 lg:w-64 absolute -top-36 lg:-top-64" />
|
||||||
|
<h1 className="font-bold text-2xl lg:text-4xl">Reset your password</h1>
|
||||||
|
<p className="self-start text-sm lg:text-base font-normal text-mti-gray-cool">to your registered Email Address</p>
|
||||||
|
</div>
|
||||||
|
<Divider className="max-w-xs lg:max-w-md" />
|
||||||
|
<form className="flex flex-col items-center gap-6 w-full -lg:px-8 lg:w-1/2" onSubmit={login}>
|
||||||
|
<Input type="password" name="password" onChange={(e) => setPassword(e)} placeholder="Password" />
|
||||||
|
|
||||||
|
<Button className="mt-8 w-full" color="purple" disabled={isLoading}>
|
||||||
|
{!isLoading && "Reset"}
|
||||||
|
{isLoading && (
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<BsArrowRepeat className="text-white animate-spin" size={25} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
<span className="text-mti-gray-cool text-sm font-normal mt-8">
|
||||||
|
Don't have an account?{" "}
|
||||||
|
<Link className="text-mti-purple-light" href="/register">
|
||||||
|
Sign up
|
||||||
|
</Link>
|
||||||
|
</span>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
{mode === "signIn" && (
|
||||||
|
<section className="h-full w-full flex flex-col items-center justify-center gap-2">
|
||||||
|
<div className="flex flex-col gap-2 items-center relative">
|
||||||
|
<img src="/logo_title.png" alt="EnCoach's Logo" className="w-36 lg:w-64 absolute -top-36 lg:-top-64" />
|
||||||
|
<h1 className="font-bold text-2xl lg:text-4xl">Confirm your account</h1>
|
||||||
|
<p className="self-start text-sm lg:text-base font-normal text-mti-gray-cool">to your registered Email Address</p>
|
||||||
|
</div>
|
||||||
|
<Divider className="max-w-xs lg:max-w-md" />
|
||||||
|
<form className="flex flex-col items-center gap-6 w-full -lg:px-8 lg:w-1/2" onSubmit={login}>
|
||||||
|
Your e-mail is currently being verified, please wait a second. <br /> <br />
|
||||||
|
Once it has been verified, you will be redirected to the home page.
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
</main>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import {NextApiRequest, NextApiResponse} from "next";
|
import {NextApiRequest, NextApiResponse} from "next";
|
||||||
import {getAuth, sendPasswordResetEmail, confirmPasswordReset} from "firebase/auth";
|
import {getAuth, confirmPasswordReset} from "firebase/auth";
|
||||||
import {app} from "@/firebase";
|
import {app} from "@/firebase";
|
||||||
import {sessionOptions} from "@/lib/session";
|
import {sessionOptions} from "@/lib/session";
|
||||||
import {withIronSessionApiRoute} from "iron-session/next";
|
import {withIronSessionApiRoute} from "iron-session/next";
|
||||||
|
|||||||
24
src/pages/api/reset/sendVerification.ts
Normal file
24
src/pages/api/reset/sendVerification.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import {NextApiRequest, NextApiResponse} from "next";
|
||||||
|
import {getAuth, sendSignInLinkToEmail, User} from "firebase/auth";
|
||||||
|
import {app} from "@/firebase";
|
||||||
|
import {sessionOptions} from "@/lib/session";
|
||||||
|
import {withIronSessionApiRoute} from "iron-session/next";
|
||||||
|
|
||||||
|
const auth = getAuth(app);
|
||||||
|
|
||||||
|
export default withIronSessionApiRoute(sendVerification, sessionOptions);
|
||||||
|
|
||||||
|
async function sendVerification(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
console.log(auth.currentUser);
|
||||||
|
if (req.session.user) {
|
||||||
|
sendSignInLinkToEmail(auth, req.session.user.email, {
|
||||||
|
url: "https://encoach.com/",
|
||||||
|
handleCodeInApp: true,
|
||||||
|
})
|
||||||
|
.then(() => res.status(200).json({ok: true}))
|
||||||
|
.catch((e) => {
|
||||||
|
console.log(e);
|
||||||
|
res.status(404).json({ok: false});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/pages/api/reset/verify.ts
Normal file
29
src/pages/api/reset/verify.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import {NextApiRequest, NextApiResponse} from "next";
|
||||||
|
import {getAuth, signInWithEmailLink} from "firebase/auth";
|
||||||
|
import {app} from "@/firebase";
|
||||||
|
import {sessionOptions} from "@/lib/session";
|
||||||
|
import {withIronSessionApiRoute} from "iron-session/next";
|
||||||
|
import {doc, getFirestore, setDoc} from "firebase/firestore";
|
||||||
|
|
||||||
|
const auth = getAuth(app);
|
||||||
|
const db = getFirestore(app);
|
||||||
|
|
||||||
|
export default withIronSessionApiRoute(verify, sessionOptions);
|
||||||
|
|
||||||
|
async function verify(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
const {link} = req.body as {link: string};
|
||||||
|
|
||||||
|
if (req.session.user) {
|
||||||
|
signInWithEmailLink(auth, req.session.user.email, link)
|
||||||
|
.then(async () => {
|
||||||
|
const userRef = doc(db, "users", req.session.user!.id);
|
||||||
|
await setDoc(userRef, {isVerified: true}, {merge: true});
|
||||||
|
|
||||||
|
req.session.user = {...req.session.user!, isVerified: true};
|
||||||
|
await req.session.save();
|
||||||
|
|
||||||
|
res.status(200).json({ok: true});
|
||||||
|
})
|
||||||
|
.catch(() => res.status(404).json({ok: false}));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,7 +35,7 @@ import AbandonPopup from "@/components/AbandonPopup";
|
|||||||
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
||||||
const user = req.session.user;
|
const user = req.session.user;
|
||||||
|
|
||||||
if (!user) {
|
if (!user || !user.isVerified) {
|
||||||
res.setHeader("location", "/login");
|
res.setHeader("location", "/login");
|
||||||
res.statusCode = 302;
|
res.statusCode = 302;
|
||||||
res.end();
|
res.end();
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import AbandonPopup from "@/components/AbandonPopup";
|
|||||||
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
||||||
const user = req.session.user;
|
const user = req.session.user;
|
||||||
|
|
||||||
if (!user) {
|
if (!user || !user.isVerified) {
|
||||||
res.setHeader("location", "/login");
|
res.setHeader("location", "/login");
|
||||||
res.statusCode = 302;
|
res.statusCode = 302;
|
||||||
res.end();
|
res.end();
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import axios from "axios";
|
|||||||
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
||||||
const user = req.session.user;
|
const user = req.session.user;
|
||||||
|
|
||||||
if (!user) {
|
if (!user || !user.isVerified) {
|
||||||
res.setHeader("location", "/login");
|
res.setHeader("location", "/login");
|
||||||
res.statusCode = 302;
|
res.statusCode = 302;
|
||||||
res.end();
|
res.end();
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {BsArrowRepeat, BsCheck} from "react-icons/bs";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Input from "@/components/Low/Input";
|
import Input from "@/components/Low/Input";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
import {useRouter} from "next/router";
|
||||||
|
|
||||||
const EMAIL_REGEX = new RegExp(/^[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*@[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*$/g);
|
const EMAIL_REGEX = new RegExp(/^[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*@[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*$/g);
|
||||||
|
|
||||||
@@ -20,7 +21,9 @@ export default function Login() {
|
|||||||
const [rememberPassword, setRememberPassword] = useState(false);
|
const [rememberPassword, setRememberPassword] = useState(false);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
const {mutateUser} = useUser({
|
const router = useRouter();
|
||||||
|
|
||||||
|
const {user, mutateUser} = useUser({
|
||||||
redirectTo: "/",
|
redirectTo: "/",
|
||||||
redirectIfFound: true,
|
redirectIfFound: true,
|
||||||
});
|
});
|
||||||
@@ -64,6 +67,23 @@ export default function Login() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sendEmailVerification = () => {
|
||||||
|
setIsLoading(true);
|
||||||
|
axios
|
||||||
|
.post<{ok: boolean}>("/api/reset/sendVerification", {})
|
||||||
|
.catch((e) => {
|
||||||
|
console.log(e);
|
||||||
|
toast.error("Something went wrong, please logout and re-login.", {toastId: "send-verify-error"});
|
||||||
|
})
|
||||||
|
.finally(() => setIsLoading(false));
|
||||||
|
};
|
||||||
|
|
||||||
|
const logout = async () => {
|
||||||
|
axios.post("/api/logout").finally(() => {
|
||||||
|
setTimeout(() => router.reload(), 500);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
@@ -85,11 +105,15 @@ export default function Login() {
|
|||||||
<p className="self-start text-sm lg:text-base font-normal text-mti-gray-cool">with your registered Email Address</p>
|
<p className="self-start text-sm lg:text-base font-normal text-mti-gray-cool">with your registered Email Address</p>
|
||||||
</div>
|
</div>
|
||||||
<Divider className="max-w-xs lg:max-w-md" />
|
<Divider className="max-w-xs lg:max-w-md" />
|
||||||
|
{!user && (
|
||||||
|
<>
|
||||||
<form className="flex flex-col items-center gap-6 w-full -lg:px-8 lg:w-1/2" onSubmit={login}>
|
<form className="flex flex-col items-center gap-6 w-full -lg:px-8 lg:w-1/2" onSubmit={login}>
|
||||||
<Input type="email" name="email" onChange={(e) => setEmail(e)} placeholder="Enter email address" />
|
<Input type="email" name="email" onChange={(e) => setEmail(e)} placeholder="Enter email address" />
|
||||||
<Input type="password" name="password" onChange={(e) => setPassword(e)} placeholder="Password" />
|
<Input type="password" name="password" onChange={(e) => setPassword(e)} placeholder="Password" />
|
||||||
<div className="flex justify-between w-full px-4">
|
<div className="flex justify-between w-full px-4">
|
||||||
<div className="flex gap-3 text-mti-gray-dim text-xs cursor-pointer" onClick={() => setRememberPassword((prev) => !prev)}>
|
<div
|
||||||
|
className="flex gap-3 text-mti-gray-dim text-xs cursor-pointer"
|
||||||
|
onClick={() => setRememberPassword((prev) => !prev)}>
|
||||||
<input type="checkbox" className="hidden" />
|
<input type="checkbox" className="hidden" />
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
@@ -120,6 +144,29 @@ export default function Login() {
|
|||||||
Sign up
|
Sign up
|
||||||
</Link>
|
</Link>
|
||||||
</span>
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{user && (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-col items-center gap-6 w-full -lg:px-8 lg:w-1/2 relative">
|
||||||
|
<h4 className="font-semibold text-2xl text-mti-purple-light">Please confirm your account!</h4>
|
||||||
|
<span className="text-center">
|
||||||
|
An e-mail has been sent to <span className="italic text-mti-purple-light">{user.email}</span>, please click the
|
||||||
|
link in it to confirm your account to be able to use the application. <br /> <br />
|
||||||
|
Please refresh this page once it has been verified.
|
||||||
|
</span>
|
||||||
|
<Button className="mt-8 w-full" color="purple" disabled={isLoading} onClick={sendEmailVerification}>
|
||||||
|
Resend e-mail
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Divider className="max-w-xs lg:max-w-md" />
|
||||||
|
<span className="text-mti-gray-cool text-sm font-normal">
|
||||||
|
<button className="text-mti-purple-light" onClick={logout}>
|
||||||
|
Log out instead
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import {ErrorMessage} from "@/constants/errors";
|
|||||||
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
||||||
const user = req.session.user;
|
const user = req.session.user;
|
||||||
|
|
||||||
if (!user) {
|
if (!user || !user.isVerified) {
|
||||||
res.setHeader("location", "/login");
|
res.setHeader("location", "/login");
|
||||||
res.statusCode = 302;
|
res.statusCode = 302;
|
||||||
res.end();
|
res.end();
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import {BsBook, BsHeadphones, BsMegaphone, BsPen} from "react-icons/bs";
|
|||||||
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
||||||
const user = req.session.user;
|
const user = req.session.user;
|
||||||
|
|
||||||
if (!user) {
|
if (!user || !user.isVerified) {
|
||||||
res.setHeader("location", "/login");
|
res.setHeader("location", "/login");
|
||||||
res.statusCode = 302;
|
res.statusCode = 302;
|
||||||
res.end();
|
res.end();
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ import {BsArrowRepeat} from "react-icons/bs";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Input from "@/components/Low/Input";
|
import Input from "@/components/Low/Input";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import {Divider} from "primereact/divider";
|
||||||
|
import {useRouter} from "next/router";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
export default function Register() {
|
export default function Register() {
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
@@ -16,7 +19,9 @@ export default function Register() {
|
|||||||
const [confirmPassword, setConfirmPassword] = useState("");
|
const [confirmPassword, setConfirmPassword] = useState("");
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
const {mutateUser} = useUser({
|
const router = useRouter();
|
||||||
|
|
||||||
|
const {user, mutateUser} = useUser({
|
||||||
redirectTo: "/",
|
redirectTo: "/",
|
||||||
redirectIfFound: true,
|
redirectIfFound: true,
|
||||||
});
|
});
|
||||||
@@ -32,7 +37,9 @@ export default function Register() {
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
axios
|
axios
|
||||||
.post("/api/register", {name, email, password, profilePicture: "/defaultAvatar.png"})
|
.post("/api/register", {name, email, password, profilePicture: "/defaultAvatar.png"})
|
||||||
.then((response) => mutateUser(response.data.user))
|
.then((response) => {
|
||||||
|
mutateUser(response.data.user).then(sendEmailVerification);
|
||||||
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.log(error.response.data);
|
console.log(error.response.data);
|
||||||
|
|
||||||
@@ -45,6 +52,23 @@ export default function Register() {
|
|||||||
.finally(() => setIsLoading(false));
|
.finally(() => setIsLoading(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sendEmailVerification = () => {
|
||||||
|
setIsLoading(true);
|
||||||
|
axios
|
||||||
|
.post<{ok: boolean}>("/api/reset/sendVerification", {})
|
||||||
|
.catch((e) => {
|
||||||
|
console.log(e);
|
||||||
|
toast.error("Something went wrong, please logout and re-login.", {toastId: "send-verify-error"});
|
||||||
|
})
|
||||||
|
.finally(() => setIsLoading(false));
|
||||||
|
};
|
||||||
|
|
||||||
|
const logout = async () => {
|
||||||
|
axios.post("/api/logout").finally(() => {
|
||||||
|
setTimeout(() => router.reload(), 500);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
@@ -60,10 +84,12 @@ export default function Register() {
|
|||||||
<img src="/people-talking-tablet.png" alt="People smiling looking at a tablet" className="h-full aspect-auto" />
|
<img src="/people-talking-tablet.png" alt="People smiling looking at a tablet" className="h-full aspect-auto" />
|
||||||
</section>
|
</section>
|
||||||
<section className="h-full w-full flex flex-col items-center justify-center gap-4">
|
<section className="h-full w-full flex flex-col items-center justify-center gap-4">
|
||||||
<div className="flex flex-col gap-2 items-center relative mb-4">
|
<div className={clsx("flex flex-col gap-2 items-center relative", !user && "mb-4")}>
|
||||||
<img src="/logo_title.png" alt="EnCoach's Logo" className="w-36 lg:w-64 absolute -top-36 lg:-top-64" />
|
<img src="/logo_title.png" alt="EnCoach's Logo" className="w-36 lg:w-64 absolute -top-36 lg:-top-64" />
|
||||||
<h1 className="font-bold text-2xl lg:text-4xl">Create new account</h1>
|
<h1 className="font-bold text-2xl lg:text-4xl">Create new account</h1>
|
||||||
</div>
|
</div>
|
||||||
|
{!user && (
|
||||||
|
<>
|
||||||
<form className="flex flex-col items-center gap-6 w-full -lg:px-8 lg:w-1/2" onSubmit={register}>
|
<form className="flex flex-col items-center gap-6 w-full -lg:px-8 lg:w-1/2" onSubmit={register}>
|
||||||
<Input type="text" name="name" onChange={(e) => setName(e)} placeholder="Enter your name" required />
|
<Input type="text" name="name" onChange={(e) => setName(e)} placeholder="Enter your name" required />
|
||||||
<Input type="email" name="email" onChange={(e) => setEmail(e)} placeholder="Enter email address" required />
|
<Input type="email" name="email" onChange={(e) => setEmail(e)} placeholder="Enter email address" required />
|
||||||
@@ -87,6 +113,30 @@ export default function Register() {
|
|||||||
<Link className="text-mti-purple-light text-sm font-normal" href="/login">
|
<Link className="text-mti-purple-light text-sm font-normal" href="/login">
|
||||||
Sign in instead
|
Sign in instead
|
||||||
</Link>
|
</Link>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{user && (
|
||||||
|
<>
|
||||||
|
<Divider className="max-w-xs lg:max-w-md" />
|
||||||
|
<div className="flex flex-col items-center gap-6 w-full -lg:px-8 lg:w-1/2 relative">
|
||||||
|
<h4 className="font-semibold text-2xl text-mti-purple-light">Please confirm your account!</h4>
|
||||||
|
<span className="text-center">
|
||||||
|
An e-mail has been sent to <span className="italic text-mti-purple-light">{user.email}</span>, please click the
|
||||||
|
link in it to confirm your account to be able to use the application. <br /> <br />
|
||||||
|
Please refresh this page once it has been verified.
|
||||||
|
</span>
|
||||||
|
<Button className="mt-8 w-full" color="purple" disabled={isLoading} onClick={sendEmailVerification}>
|
||||||
|
Resend e-mail
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Divider className="max-w-xs lg:max-w-md" />
|
||||||
|
<span className="text-mti-gray-cool text-sm font-normal">
|
||||||
|
<button className="text-mti-purple-light" onClick={logout}>
|
||||||
|
Log out instead
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,110 +0,0 @@
|
|||||||
/* eslint-disable @next/next/no-img-element */
|
|
||||||
import {User} from "@/interfaces/user";
|
|
||||||
import {toast, ToastContainer} from "react-toastify";
|
|
||||||
import axios from "axios";
|
|
||||||
import {FormEvent, useState} from "react";
|
|
||||||
import Head from "next/head";
|
|
||||||
import useUser from "@/hooks/useUser";
|
|
||||||
import {Divider} from "primereact/divider";
|
|
||||||
import Button from "@/components/Low/Button";
|
|
||||||
import {BsArrowRepeat, BsCheck} from "react-icons/bs";
|
|
||||||
import Link from "next/link";
|
|
||||||
import Input from "@/components/Low/Input";
|
|
||||||
import clsx from "clsx";
|
|
||||||
import {useRouter} from "next/router";
|
|
||||||
|
|
||||||
export function getServerSideProps({query, res}: {query: {oobCode: string}; res: any}) {
|
|
||||||
if (!query || !query.oobCode) {
|
|
||||||
res.setHeader("location", "/login");
|
|
||||||
res.statusCode = 302;
|
|
||||||
res.end();
|
|
||||||
return {
|
|
||||||
props: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
code: query.oobCode,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Reset({code}: {code: string}) {
|
|
||||||
const [password, setPassword] = useState("");
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
useUser({
|
|
||||||
redirectTo: "/",
|
|
||||||
redirectIfFound: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const login = (e: FormEvent<HTMLFormElement>) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
setIsLoading(true);
|
|
||||||
axios
|
|
||||||
.post<{ok: boolean}>("/api/reset/confirm", {code, password})
|
|
||||||
.then((response) => {
|
|
||||||
if (response.data.ok) {
|
|
||||||
toast.success("Your password has been reset!", {toastId: "reset-successful"});
|
|
||||||
setTimeout(() => {
|
|
||||||
router.push("/login");
|
|
||||||
}, 2000);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
toast.error("Something went wrong! Please make sure to click the link in your e-mail again!", {toastId: "reset-error"});
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
toast.error("Something went wrong! Please make sure to click the link in your e-mail again!", {toastId: "reset-error"});
|
|
||||||
})
|
|
||||||
.finally(() => setIsLoading(false));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Head>
|
|
||||||
<title>Reset | EnCoach</title>
|
|
||||||
<meta name="description" content="Generated by create next app" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<link rel="icon" href="/favicon.ico" />
|
|
||||||
</Head>
|
|
||||||
<main className="w-full h-[100vh] flex bg-white text-black">
|
|
||||||
<ToastContainer />
|
|
||||||
<section className="h-full w-fit min-w-fit relative hidden lg:flex">
|
|
||||||
<div className="absolute h-full w-full bg-mti-rose-light z-10 bg-opacity-50" />
|
|
||||||
<img src="/people-talking-tablet.png" alt="People smiling looking at a tablet" className="h-full aspect-auto" />
|
|
||||||
</section>
|
|
||||||
<section className="h-full w-full flex flex-col items-center justify-center gap-2">
|
|
||||||
<div className="flex flex-col gap-2 items-center relative">
|
|
||||||
<img src="/logo_title.png" alt="EnCoach's Logo" className="w-36 lg:w-64 absolute -top-36 lg:-top-64" />
|
|
||||||
<h1 className="font-bold text-2xl lg:text-4xl">Reset your password</h1>
|
|
||||||
<p className="self-start text-sm lg:text-base font-normal text-mti-gray-cool">to your registered Email Address</p>
|
|
||||||
</div>
|
|
||||||
<Divider className="max-w-xs lg:max-w-md" />
|
|
||||||
<form className="flex flex-col items-center gap-6 w-full -lg:px-8 lg:w-1/2" onSubmit={login}>
|
|
||||||
<Input type="password" name="password" onChange={(e) => setPassword(e)} placeholder="Password" />
|
|
||||||
|
|
||||||
<Button className="mt-8 w-full" color="purple" disabled={isLoading}>
|
|
||||||
{!isLoading && "Reset"}
|
|
||||||
{isLoading && (
|
|
||||||
<div className="flex items-center justify-center">
|
|
||||||
<BsArrowRepeat className="text-white animate-spin" size={25} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
<span className="text-mti-gray-cool text-sm font-normal mt-8">
|
|
||||||
Don't have an account?{" "}
|
|
||||||
<Link className="text-mti-purple-light" href="/register">
|
|
||||||
Sign up
|
|
||||||
</Link>
|
|
||||||
</span>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -35,7 +35,7 @@ const COLORS = ["#1EB3FF", "#FF790A", "#3D9F11", "#EF5DA8"];
|
|||||||
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
||||||
const user = req.session.user;
|
const user = req.session.user;
|
||||||
|
|
||||||
if (!user) {
|
if (!user || !user.isVerified) {
|
||||||
res.setHeader("location", "/login");
|
res.setHeader("location", "/login");
|
||||||
res.statusCode = 302;
|
res.statusCode = 302;
|
||||||
res.end();
|
res.end();
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const ReactMediaRecorder = dynamic(() => import("react-media-recorder").then((mo
|
|||||||
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
||||||
const user = req.session.user;
|
const user = req.session.user;
|
||||||
|
|
||||||
if (!user) {
|
if (!user || !user.isVerified) {
|
||||||
res.setHeader("location", "/login");
|
res.setHeader("location", "/login");
|
||||||
res.statusCode = 302;
|
res.statusCode = 302;
|
||||||
res.end();
|
res.end();
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {Dropdown} from "primereact/dropdown";
|
|||||||
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
||||||
const user = req.session.user;
|
const user = req.session.user;
|
||||||
|
|
||||||
if (!user) {
|
if (!user || !user.isVerified) {
|
||||||
res.setHeader("location", "/login");
|
res.setHeader("location", "/login");
|
||||||
res.statusCode = 302;
|
res.statusCode = 302;
|
||||||
res.end();
|
res.end();
|
||||||
|
|||||||
Reference in New Issue
Block a user