finalized password reset + code refactoring
This commit is contained in:
parent
73dda21573
commit
329019b34e
|
@ -29,4 +29,6 @@ export default function useInitialData() {
|
||||||
// setLinks();
|
// setLinks();
|
||||||
}
|
}
|
||||||
}, [account]);
|
}, [account]);
|
||||||
|
|
||||||
|
return status;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import { ReactNode } from "react";
|
import { ReactNode, useEffect, useState } from "react";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
import Loader from "../components/Loader";
|
import Loader from "../components/Loader";
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import useInitialData from "@/hooks/useInitialData";
|
import useInitialData from "@/hooks/useInitialData";
|
||||||
import useAccountStore from "@/store/account";
|
import useAccountStore from "@/store/account";
|
||||||
|
|
||||||
|
@ -10,62 +9,68 @@ interface Props {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const emailEnabled = process.env.NEXT_PUBLIC_EMAIL_PROVIDER === "true";
|
||||||
|
const stripeEnabled = process.env.NEXT_PUBLIC_STRIPE === "true";
|
||||||
|
|
||||||
export default function AuthRedirect({ children }: Props) {
|
export default function AuthRedirect({ children }: Props) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { status, data } = useSession();
|
const { status } = useSession();
|
||||||
const [redirect, setRedirect] = useState(true);
|
const [shouldRenderChildren, setShouldRenderChildren] = useState(false);
|
||||||
const { account } = useAccountStore();
|
const { account } = useAccountStore();
|
||||||
|
|
||||||
const emailEnabled = process.env.NEXT_PUBLIC_EMAIL_PROVIDER === "true";
|
|
||||||
const stripeEnabled = process.env.NEXT_PUBLIC_STRIPE === "true";
|
|
||||||
|
|
||||||
useInitialData();
|
useInitialData();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!router.pathname.startsWith("/public")) {
|
const isLoggedIn = status === "authenticated";
|
||||||
if (
|
const isUnauthenticated = status === "unauthenticated";
|
||||||
status === "authenticated" &&
|
const isPublicPage = router.pathname.startsWith("/public");
|
||||||
account.id &&
|
const hasInactiveSubscription =
|
||||||
!account.subscription?.active &&
|
account.id && !account.subscription?.active && stripeEnabled;
|
||||||
stripeEnabled
|
const routes = [
|
||||||
) {
|
{ path: "/login", isProtected: false },
|
||||||
router.push("/subscribe").then(() => {
|
{ path: "/register", isProtected: false },
|
||||||
setRedirect(false);
|
{ path: "/confirmation", isProtected: false },
|
||||||
});
|
{ path: "/forgot", isProtected: false },
|
||||||
|
{ path: "/auth/reset-password", isProtected: false },
|
||||||
|
{ path: "/", isProtected: false },
|
||||||
|
{ path: "/subscribe", isProtected: true },
|
||||||
|
{ path: "/dashboard", isProtected: true },
|
||||||
|
{ path: "/settings", isProtected: true },
|
||||||
|
{ path: "/collections", isProtected: true },
|
||||||
|
{ path: "/links", isProtected: true },
|
||||||
|
{ path: "/tags", isProtected: true },
|
||||||
|
];
|
||||||
|
|
||||||
|
if (isPublicPage) {
|
||||||
|
setShouldRenderChildren(true);
|
||||||
|
} else {
|
||||||
|
if (isLoggedIn && hasInactiveSubscription) {
|
||||||
|
redirectTo("/subscribe");
|
||||||
} else if (
|
} else if (
|
||||||
status === "authenticated" &&
|
isLoggedIn &&
|
||||||
account.id &&
|
!routes.some((e) => router.pathname.startsWith(e.path) && e.isProtected)
|
||||||
(router.pathname === "/login" ||
|
|
||||||
router.pathname === "/register" ||
|
|
||||||
router.pathname === "/confirmation" ||
|
|
||||||
router.pathname === "/subscribe" ||
|
|
||||||
router.pathname === "/choose-username" ||
|
|
||||||
router.pathname === "/forgot" ||
|
|
||||||
router.pathname === "/")
|
|
||||||
) {
|
) {
|
||||||
router.push("/dashboard").then(() => {
|
redirectTo("/dashboard");
|
||||||
setRedirect(false);
|
|
||||||
});
|
|
||||||
} else if (
|
} else if (
|
||||||
status === "unauthenticated" &&
|
isUnauthenticated &&
|
||||||
!(
|
!routes.some(
|
||||||
router.pathname === "/login" ||
|
(e) => router.pathname.startsWith(e.path) && !e.isProtected
|
||||||
router.pathname === "/register" ||
|
|
||||||
router.pathname === "/confirmation" ||
|
|
||||||
router.pathname === "/forgot"
|
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
router.push("/login").then(() => {
|
redirectTo("/login");
|
||||||
setRedirect(false);
|
} else {
|
||||||
});
|
setShouldRenderChildren(true);
|
||||||
} else if (status === "loading") setRedirect(true);
|
}
|
||||||
else setRedirect(false);
|
|
||||||
} else {
|
|
||||||
setRedirect(false);
|
|
||||||
}
|
}
|
||||||
}, [status, account, router.pathname]);
|
}, [status, account, router.pathname]);
|
||||||
|
|
||||||
if (status !== "loading" && !redirect) return <>{children}</>;
|
function redirectTo(destination: string) {
|
||||||
else return <></>;
|
router.push(destination).then(() => setShouldRenderChildren(true));
|
||||||
// return <>{children}</>;
|
}
|
||||||
|
|
||||||
|
if (status !== "loading" && shouldRenderChildren) {
|
||||||
|
return <>{children}</>;
|
||||||
|
} else {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,11 +34,11 @@ export default async function sendPasswordResetRequest(
|
||||||
address: process.env.EMAIL_FROM as string,
|
address: process.env.EMAIL_FROM as string,
|
||||||
},
|
},
|
||||||
to: email,
|
to: email,
|
||||||
subject: "Verify your new Linkwarden email address",
|
subject: "Linkwarden: Reset password instructions",
|
||||||
html: emailTemplate({
|
html: emailTemplate({
|
||||||
user,
|
user,
|
||||||
baseUrl: process.env.BASE_URL,
|
baseUrl: process.env.BASE_URL,
|
||||||
url: `${process.env.BASE_URL}/auth/password-reset?token=${token}`,
|
url: `${process.env.BASE_URL}/auth/reset-password?token=${token}`,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,21 @@
|
||||||
import { readFileSync } from "fs";
|
import { readFileSync } from "fs";
|
||||||
import { SendVerificationRequestParams } from "next-auth/providers";
|
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import Handlebars from "handlebars";
|
import Handlebars from "handlebars";
|
||||||
import transporter from "./transporter";
|
import transporter from "./transporter";
|
||||||
|
|
||||||
export default async function sendVerificationRequest(
|
type Params = {
|
||||||
params: SendVerificationRequestParams
|
identifier: string;
|
||||||
) {
|
url: string;
|
||||||
|
from: string;
|
||||||
|
token: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function sendVerificationRequest({
|
||||||
|
identifier,
|
||||||
|
url,
|
||||||
|
from,
|
||||||
|
token,
|
||||||
|
}: Params) {
|
||||||
const emailsDir = path.resolve(process.cwd(), "templates");
|
const emailsDir = path.resolve(process.cwd(), "templates");
|
||||||
|
|
||||||
const templateFile = readFileSync(
|
const templateFile = readFileSync(
|
||||||
|
@ -16,13 +25,12 @@ export default async function sendVerificationRequest(
|
||||||
|
|
||||||
const emailTemplate = Handlebars.compile(templateFile);
|
const emailTemplate = Handlebars.compile(templateFile);
|
||||||
|
|
||||||
const { identifier, url, provider, token } = params;
|
|
||||||
const { host } = new URL(url);
|
const { host } = new URL(url);
|
||||||
const result = await transporter.sendMail({
|
const result = await transporter.sendMail({
|
||||||
to: identifier,
|
to: identifier,
|
||||||
from: {
|
from: {
|
||||||
name: "Linkwarden",
|
name: "Linkwarden",
|
||||||
address: provider.from as string,
|
address: from as string,
|
||||||
},
|
},
|
||||||
subject: `Please verify your email address`,
|
subject: `Please verify your email address`,
|
||||||
text: text({ url, host }),
|
text: text({ url, host }),
|
||||||
|
|
|
@ -9,6 +9,7 @@ import toast from "react-hot-toast";
|
||||||
import { Toaster, ToastBar } from "react-hot-toast";
|
import { Toaster, ToastBar } from "react-hot-toast";
|
||||||
import { Session } from "next-auth";
|
import { Session } from "next-auth";
|
||||||
import { isPWA } from "@/lib/client/utils";
|
import { isPWA } from "@/lib/client/utils";
|
||||||
|
import useInitialData from "@/hooks/useInitialData";
|
||||||
|
|
||||||
export default function App({
|
export default function App({
|
||||||
Component,
|
Component,
|
||||||
|
@ -55,6 +56,7 @@ export default function App({
|
||||||
<link rel="manifest" href="/site.webmanifest" />
|
<link rel="manifest" href="/site.webmanifest" />
|
||||||
</Head>
|
</Head>
|
||||||
<AuthRedirect>
|
<AuthRedirect>
|
||||||
|
{/* <GetData> */}
|
||||||
<Toaster
|
<Toaster
|
||||||
position="top-center"
|
position="top-center"
|
||||||
reverseOrder={false}
|
reverseOrder={false}
|
||||||
|
@ -88,7 +90,17 @@ export default function App({
|
||||||
)}
|
)}
|
||||||
</Toaster>
|
</Toaster>
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
|
{/* </GetData> */}
|
||||||
</AuthRedirect>
|
</AuthRedirect>
|
||||||
</SessionProvider>
|
</SessionProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// function GetData({ children }: { children: React.ReactNode }) {
|
||||||
|
// const status = useInitialData();
|
||||||
|
// return typeof window !== "undefined" && status !== "loading" ? (
|
||||||
|
// children
|
||||||
|
// ) : (
|
||||||
|
// <></>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
|
@ -65,6 +65,7 @@ import ZohoProvider from "next-auth/providers/zoho";
|
||||||
import ZoomProvider from "next-auth/providers/zoom";
|
import ZoomProvider from "next-auth/providers/zoom";
|
||||||
import * as process from "process";
|
import * as process from "process";
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
import { randomBytes } from "crypto";
|
||||||
|
|
||||||
const emailEnabled =
|
const emailEnabled =
|
||||||
process.env.EMAIL_FROM && process.env.EMAIL_SERVER ? true : false;
|
process.env.EMAIL_FROM && process.env.EMAIL_SERVER ? true : false;
|
||||||
|
@ -105,13 +106,54 @@ if (
|
||||||
email: username?.toLowerCase(),
|
email: username?.toLowerCase(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
emailVerified: { not: null },
|
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
username: username.toLowerCase(),
|
username: username.toLowerCase(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!user) throw Error("Invalid credentials.");
|
||||||
|
else if (!user?.emailVerified && emailEnabled) {
|
||||||
|
const identifier = user?.email as string;
|
||||||
|
const token = randomBytes(32).toString("hex");
|
||||||
|
const url = `${
|
||||||
|
process.env.NEXTAUTH_URL
|
||||||
|
}/callback/email?token=${token}&email=${encodeURIComponent(
|
||||||
|
identifier
|
||||||
|
)}`;
|
||||||
|
const from = process.env.EMAIL_FROM as string;
|
||||||
|
|
||||||
|
const recentVerificationRequestsCount =
|
||||||
|
await prisma.verificationToken.count({
|
||||||
|
where: {
|
||||||
|
identifier,
|
||||||
|
createdAt: {
|
||||||
|
gt: new Date(new Date().getTime() - 1000 * 60 * 5), // 5 minutes
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (recentVerificationRequestsCount >= 4)
|
||||||
|
throw Error("Too many requests. Please try again later.");
|
||||||
|
|
||||||
|
sendVerificationRequest({
|
||||||
|
identifier,
|
||||||
|
url,
|
||||||
|
from,
|
||||||
|
token,
|
||||||
|
});
|
||||||
|
|
||||||
|
await prisma.verificationToken.create({
|
||||||
|
data: {
|
||||||
|
identifier,
|
||||||
|
token,
|
||||||
|
expires: new Date(Date.now() + 24 * 3600 * 1000), // 1 day
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
throw Error("Email not verified. Verification email sent.");
|
||||||
|
}
|
||||||
|
|
||||||
let passwordMatches: boolean = false;
|
let passwordMatches: boolean = false;
|
||||||
|
|
||||||
if (user?.password) {
|
if (user?.password) {
|
||||||
|
@ -120,7 +162,7 @@ if (
|
||||||
|
|
||||||
if (passwordMatches && user?.password) {
|
if (passwordMatches && user?.password) {
|
||||||
return { id: user?.id };
|
return { id: user?.id };
|
||||||
} else return null as any;
|
} else throw Error("Invalid credentials.");
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -132,8 +174,26 @@ if (emailEnabled) {
|
||||||
server: process.env.EMAIL_SERVER,
|
server: process.env.EMAIL_SERVER,
|
||||||
from: process.env.EMAIL_FROM,
|
from: process.env.EMAIL_FROM,
|
||||||
maxAge: 1200,
|
maxAge: 1200,
|
||||||
sendVerificationRequest(params) {
|
async sendVerificationRequest({ identifier, url, provider, token }) {
|
||||||
sendVerificationRequest(params);
|
const recentVerificationRequestsCount =
|
||||||
|
await prisma.verificationToken.count({
|
||||||
|
where: {
|
||||||
|
identifier,
|
||||||
|
createdAt: {
|
||||||
|
gt: new Date(new Date().getTime() - 1000 * 60 * 5), // 5 minutes
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (recentVerificationRequestsCount >= 4)
|
||||||
|
throw Error("Too many requests. Please try again later.");
|
||||||
|
|
||||||
|
sendVerificationRequest({
|
||||||
|
identifier,
|
||||||
|
url,
|
||||||
|
from: provider.from as string,
|
||||||
|
token,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
|
@ -74,7 +74,7 @@ export default async function resetPassword(
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
response: "Password reset successfully.",
|
response: "Password has been reset successfully.",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,52 +2,56 @@ import AccentSubmitButton from "@/components/AccentSubmitButton";
|
||||||
import TextInput from "@/components/TextInput";
|
import TextInput from "@/components/TextInput";
|
||||||
import CenteredForm from "@/layouts/CenteredForm";
|
import CenteredForm from "@/layouts/CenteredForm";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
import { FormEvent, useState } from "react";
|
import { FormEvent, useState } from "react";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
|
|
||||||
interface FormData {
|
interface FormData {
|
||||||
password: string;
|
password: string;
|
||||||
passwordConfirmation: string;
|
token: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ResetPassword() {
|
export default function ResetPassword() {
|
||||||
const [submitLoader, setSubmitLoader] = useState(false);
|
const [submitLoader, setSubmitLoader] = useState(false);
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const [form, setForm] = useState<FormData>({
|
const [form, setForm] = useState<FormData>({
|
||||||
password: "",
|
password: "",
|
||||||
passwordConfirmation: "",
|
token: router.query.token as string,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [isEmailSent, setIsEmailSent] = useState(false);
|
const [requestSent, setRequestSent] = useState(false);
|
||||||
|
|
||||||
async function submitRequest() {
|
async function submit(event: FormEvent<HTMLFormElement>) {
|
||||||
const response = await fetch("/api/v1/auth/forgot-password", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(form),
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
toast.success(data.response);
|
|
||||||
setIsEmailSent(true);
|
|
||||||
} else {
|
|
||||||
toast.error(data.response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sendConfirmation(event: FormEvent<HTMLFormElement>) {
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
if (form.password !== "") {
|
if (
|
||||||
|
form.password !== "" &&
|
||||||
|
form.token !== "" &&
|
||||||
|
!requestSent &&
|
||||||
|
!submitLoader
|
||||||
|
) {
|
||||||
setSubmitLoader(true);
|
setSubmitLoader(true);
|
||||||
|
|
||||||
const load = toast.loading("Sending password recovery link...");
|
const load = toast.loading("Sending password recovery link...");
|
||||||
|
|
||||||
await submitRequest();
|
const response = await fetch("/api/v1/auth/reset-password", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(form),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
toast.success(data.response);
|
||||||
|
setRequestSent(true);
|
||||||
|
} else {
|
||||||
|
toast.error(data.response);
|
||||||
|
}
|
||||||
|
|
||||||
toast.dismiss(load);
|
toast.dismiss(load);
|
||||||
|
|
||||||
|
@ -59,15 +63,15 @@ export default function ResetPassword() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CenteredForm>
|
<CenteredForm>
|
||||||
<form onSubmit={sendConfirmation}>
|
<form onSubmit={submit}>
|
||||||
<div className="p-4 mx-auto flex flex-col gap-3 justify-between max-w-[30rem] min-w-80 w-full bg-base-200 rounded-2xl shadow-md border border-neutral-content">
|
<div className="p-4 mx-auto flex flex-col gap-3 justify-between max-w-[30rem] min-w-80 w-full bg-base-200 rounded-2xl shadow-md border border-neutral-content">
|
||||||
<p className="text-3xl text-center font-extralight">
|
<p className="text-3xl text-center font-extralight">
|
||||||
{isEmailSent ? "Email Sent!" : "Forgot Password?"}
|
{requestSent ? "Password Updated!" : "Reset Password"}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="divider my-0"></div>
|
<div className="divider my-0"></div>
|
||||||
|
|
||||||
{!isEmailSent ? (
|
{!requestSent ? (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
|
@ -76,12 +80,12 @@ export default function ResetPassword() {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm w-fit font-semibold mb-1">Email</p>
|
<p className="text-sm w-fit font-semibold mb-1">New Password</p>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
autoFocus
|
autoFocus
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="johnny@example.com"
|
placeholder="••••••••••••••"
|
||||||
value={form.password}
|
value={form.password}
|
||||||
className="bg-base-100"
|
className="bg-base-100"
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
|
@ -92,23 +96,22 @@ export default function ResetPassword() {
|
||||||
|
|
||||||
<AccentSubmitButton
|
<AccentSubmitButton
|
||||||
type="submit"
|
type="submit"
|
||||||
label="Send Login Link"
|
label="Update Password"
|
||||||
className="mt-2 w-full"
|
className="mt-2 w-full"
|
||||||
loading={submitLoader}
|
loading={submitLoader}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-center">
|
<>
|
||||||
Check your email for a link to reset your password. If it doesn’t
|
<p>Your password has been successfully updated.</p>
|
||||||
appear within a few minutes, check your spam folder.
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="flex items-baseline gap-1 justify-center">
|
<div className="mx-auto w-fit mt-3">
|
||||||
<Link href={"/login"} className="block font-bold">
|
<Link className="font-semibold" href="/login">
|
||||||
Go back
|
Back to Login
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</CenteredForm>
|
</CenteredForm>
|
||||||
|
|
|
@ -1,8 +1,33 @@
|
||||||
import CenteredForm from "@/layouts/CenteredForm";
|
import CenteredForm from "@/layouts/CenteredForm";
|
||||||
|
import { signIn } from "next-auth/react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import React from "react";
|
import { useRouter } from "next/router";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
export default function EmailConfirmaion() {
|
export default function EmailConfirmaion() {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const [submitLoader, setSubmitLoader] = useState(false);
|
||||||
|
|
||||||
|
const resend = async () => {
|
||||||
|
setSubmitLoader(true);
|
||||||
|
|
||||||
|
const load = toast.loading("Authenticating...");
|
||||||
|
|
||||||
|
const res = await signIn("email", {
|
||||||
|
email: decodeURIComponent(router.query.email as string),
|
||||||
|
callbackUrl: "/",
|
||||||
|
redirect: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
toast.dismiss(load);
|
||||||
|
|
||||||
|
setSubmitLoader(false);
|
||||||
|
|
||||||
|
toast.success("Verification email sent.");
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CenteredForm>
|
<CenteredForm>
|
||||||
<div className="p-4 max-w-[30rem] min-w-80 w-full rounded-2xl shadow-md mx-auto border border-neutral-content bg-base-200">
|
<div className="p-4 max-w-[30rem] min-w-80 w-full rounded-2xl shadow-md mx-auto border border-neutral-content bg-base-200">
|
||||||
|
@ -12,15 +37,16 @@ export default function EmailConfirmaion() {
|
||||||
|
|
||||||
<div className="divider my-3"></div>
|
<div className="divider my-3"></div>
|
||||||
|
|
||||||
<p>A sign in link has been sent to your email address.</p>
|
<p>
|
||||||
|
A sign in link has been sent to your email address. If you don't see
|
||||||
<p className="mt-3">
|
the email, check your spam folder.
|
||||||
Didn't see the email? Check your spam folder or visit the{" "}
|
|
||||||
<Link href="/forgot" className="font-bold underline">
|
|
||||||
Password Recovery
|
|
||||||
</Link>{" "}
|
|
||||||
page to resend the link.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<div className="mx-auto w-fit mt-3">
|
||||||
|
<div className="btn btn-ghost btn-sm" onClick={resend}>
|
||||||
|
Resend Email
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CenteredForm>
|
</CenteredForm>
|
||||||
);
|
);
|
||||||
|
|
|
@ -94,15 +94,15 @@ export default function Forgot() {
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-center">
|
<p>
|
||||||
Check your email for a link to reset your password. If it doesn’t
|
Check your email for a link to reset your password. If it doesn’t
|
||||||
appear within a few minutes, check your spam folder.
|
appear within a few minutes, check your spam folder.
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex items-baseline gap-1 justify-center">
|
<div className="mx-auto w-fit mt-2">
|
||||||
<Link href={"/login"} className="block font-bold">
|
<Link className="font-semibold" href="/login">
|
||||||
Go back
|
Back to Login
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -47,7 +47,7 @@ export default function Login({
|
||||||
setSubmitLoader(false);
|
setSubmitLoader(false);
|
||||||
|
|
||||||
if (!res?.ok) {
|
if (!res?.ok) {
|
||||||
toast.error("Invalid login.");
|
toast.error(res?.error || "Invalid credentials.");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
toast.error("Please fill out all the fields.");
|
toast.error("Please fill out all the fields.");
|
||||||
|
@ -108,7 +108,7 @@ export default function Login({
|
||||||
<div className="w-fit ml-auto mt-1">
|
<div className="w-fit ml-auto mt-1">
|
||||||
<Link
|
<Link
|
||||||
href={"/forgot"}
|
href={"/forgot"}
|
||||||
className="text-gray-500 dark:text-gray-400 font-semibold"
|
className="text-neutral font-semibold"
|
||||||
data-testid="forgot-password-link"
|
data-testid="forgot-password-link"
|
||||||
>
|
>
|
||||||
Forgot Password?
|
Forgot Password?
|
||||||
|
@ -158,7 +158,7 @@ export default function Login({
|
||||||
<p className="w-fit text-gray-500 dark:text-gray-400">New here?</p>
|
<p className="w-fit text-gray-500 dark:text-gray-400">New here?</p>
|
||||||
<Link
|
<Link
|
||||||
href={"/register"}
|
href={"/register"}
|
||||||
className="block text-black dark:text-white font-semibold"
|
className="font-semibold"
|
||||||
data-testid="register-link"
|
data-testid="register-link"
|
||||||
>
|
>
|
||||||
Sign Up
|
Sign Up
|
||||||
|
|
|
@ -76,12 +76,17 @@ export default function Register() {
|
||||||
setSubmitLoader(false);
|
setSubmitLoader(false);
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
if (form.email && emailEnabled)
|
if (form.email && emailEnabled) {
|
||||||
await signIn("email", {
|
await signIn("email", {
|
||||||
email: form.email,
|
email: form.email,
|
||||||
callbackUrl: "/",
|
callbackUrl: "/",
|
||||||
|
redirect: false,
|
||||||
});
|
});
|
||||||
else if (!emailEnabled) router.push("/login");
|
|
||||||
|
router.push(
|
||||||
|
"/confirmation?email=" + encodeURIComponent(form.email)
|
||||||
|
);
|
||||||
|
} else if (!emailEnabled) router.push("/login");
|
||||||
|
|
||||||
toast.success("User Created!");
|
toast.success("User Created!");
|
||||||
} else {
|
} else {
|
||||||
|
|
Ŝarĝante…
Reference in New Issue