chore: refactor
This commit is contained in:
@@ -1,51 +1,8 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { useState } from 'react';
|
|
||||||
|
|
||||||
import { useSearchParams } from 'next/navigation';
|
|
||||||
|
|
||||||
import { Mails } from 'lucide-react';
|
import { Mails } from 'lucide-react';
|
||||||
|
|
||||||
import { ONE_SECOND } from '@documenso/lib/constants/time';
|
import { SendConfirmationEmailForm } from '~/components/forms/send-confirmation-email';
|
||||||
import { trpc } from '@documenso/trpc/react';
|
|
||||||
import { Button } from '@documenso/ui/primitives/button';
|
|
||||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
|
||||||
|
|
||||||
const RESEND_CONFIRMATION_EMAIL_TIMEOUT = 20 * ONE_SECOND;
|
|
||||||
|
|
||||||
export default function UnverifiedAccount() {
|
export default function UnverifiedAccount() {
|
||||||
const [isButtonDisabled, setIsButtonDisabled] = useState(false);
|
|
||||||
const searchParams = useSearchParams();
|
|
||||||
const { toast } = useToast();
|
|
||||||
|
|
||||||
const encryptedEmail = searchParams?.get('token') ?? '';
|
|
||||||
|
|
||||||
const { mutateAsync: sendConfirmationEmail } = trpc.profile.sendConfirmationEmail.useMutation();
|
|
||||||
|
|
||||||
const onResendConfirmationEmail = async () => {
|
|
||||||
try {
|
|
||||||
setIsButtonDisabled(true);
|
|
||||||
|
|
||||||
await sendConfirmationEmail({ encryptedEmail });
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: 'Success',
|
|
||||||
description: 'Verification email sent successfully.',
|
|
||||||
duration: 5000,
|
|
||||||
});
|
|
||||||
|
|
||||||
setTimeout(() => setIsButtonDisabled(false), RESEND_CONFIRMATION_EMAIL_TIMEOUT);
|
|
||||||
} catch (err) {
|
|
||||||
setIsButtonDisabled(false);
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: 'Error',
|
|
||||||
description: 'Something went wrong while sending the confirmation email.',
|
|
||||||
variant: 'destructive',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full items-start">
|
<div className="flex w-full items-start">
|
||||||
<div className="mr-4 mt-1 hidden md:block">
|
<div className="mr-4 mt-1 hidden md:block">
|
||||||
@@ -55,13 +12,11 @@ export default function UnverifiedAccount() {
|
|||||||
<h2 className="text-2xl font-bold md:text-4xl">Confirm email</h2>
|
<h2 className="text-2xl font-bold md:text-4xl">Confirm email</h2>
|
||||||
|
|
||||||
<p className="text-muted-foreground mt-4">
|
<p className="text-muted-foreground mt-4">
|
||||||
To gain full access to your account and unlock all its features, please confirm your email
|
To gain access to your account, please confirm your email address by clicking on the
|
||||||
address by clicking on the link sent to your email address.
|
confirmation link from your inbox.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<Button className="mt-4" disabled={isButtonDisabled} onClick={onResendConfirmationEmail}>
|
<SendConfirmationEmailForm />
|
||||||
Resend email
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import { FcGoogle } from 'react-icons/fc';
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { ErrorCode, isErrorCode } from '@documenso/lib/next-auth/error-codes';
|
import { ErrorCode, isErrorCode } from '@documenso/lib/next-auth/error-codes';
|
||||||
import { trpc } from '@documenso/trpc/react';
|
|
||||||
import { cn } from '@documenso/ui/lib/utils';
|
import { cn } from '@documenso/ui/lib/utils';
|
||||||
import { Button } from '@documenso/ui/primitives/button';
|
import { Button } from '@documenso/ui/primitives/button';
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@documenso/ui/primitives/dialog';
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@documenso/ui/primitives/dialog';
|
||||||
@@ -62,8 +61,6 @@ export const SignInForm = ({ className, isGoogleSSOEnabled }: SignInFormProps) =
|
|||||||
useState(false);
|
useState(false);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const { mutateAsync: encryptSecondaryData } = trpc.crypto.encryptSecondaryData.useMutation();
|
|
||||||
|
|
||||||
const [twoFactorAuthenticationMethod, setTwoFactorAuthenticationMethod] = useState<
|
const [twoFactorAuthenticationMethod, setTwoFactorAuthenticationMethod] = useState<
|
||||||
'totp' | 'backup'
|
'totp' | 'backup'
|
||||||
>('totp');
|
>('totp');
|
||||||
@@ -132,9 +129,12 @@ export const SignInForm = ({ className, isGoogleSSOEnabled }: SignInFormProps) =
|
|||||||
const errorMessage = ERROR_MESSAGES[result.error];
|
const errorMessage = ERROR_MESSAGES[result.error];
|
||||||
|
|
||||||
if (result.error === ErrorCode.UNVERIFIED_EMAIL) {
|
if (result.error === ErrorCode.UNVERIFIED_EMAIL) {
|
||||||
const encryptedEmail = await encryptSecondaryData({ data: email });
|
router.push(`/unverified-account`);
|
||||||
|
|
||||||
router.push(`/unverified-account?token=${encryptedEmail}`);
|
toast({
|
||||||
|
title: 'Unable to sign in',
|
||||||
|
description: errorMessage ?? 'An unknown error occurred',
|
||||||
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,19 +62,17 @@ export const SignUpForm = ({ className, isGoogleSSOEnabled }: SignUpFormProps) =
|
|||||||
const isSubmitting = form.formState.isSubmitting;
|
const isSubmitting = form.formState.isSubmitting;
|
||||||
|
|
||||||
const { mutateAsync: signup } = trpc.auth.signup.useMutation();
|
const { mutateAsync: signup } = trpc.auth.signup.useMutation();
|
||||||
const { mutateAsync: encryptSecondaryData } = trpc.crypto.encryptSecondaryData.useMutation();
|
|
||||||
|
|
||||||
const onFormSubmit = async ({ name, email, password, signature }: TSignUpFormSchema) => {
|
const onFormSubmit = async ({ name, email, password, signature }: TSignUpFormSchema) => {
|
||||||
try {
|
try {
|
||||||
await signup({ name, email, password, signature });
|
await signup({ name, email, password, signature });
|
||||||
|
|
||||||
const encryptedEmail = await encryptSecondaryData({ data: email });
|
router.push(`/unverified-account}`);
|
||||||
|
|
||||||
router.push(`/unverified-account?token=${encryptedEmail}`);
|
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: 'Registration Successful',
|
title: 'Registration Successful',
|
||||||
description: 'You have successfully registered. Please sign in to continue.',
|
description:
|
||||||
|
'You have successfully registered. Please verify your account by clicking on the link you received in the email.',
|
||||||
duration: 5000,
|
duration: 5000,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { IdentityProvider } from '@documenso/prisma/client';
|
|||||||
import { isTwoFactorAuthenticationEnabled } from '../server-only/2fa/is-2fa-availble';
|
import { isTwoFactorAuthenticationEnabled } from '../server-only/2fa/is-2fa-availble';
|
||||||
import { validateTwoFactorAuthentication } from '../server-only/2fa/validate-2fa';
|
import { validateTwoFactorAuthentication } from '../server-only/2fa/validate-2fa';
|
||||||
import { getUserByEmail } from '../server-only/user/get-user-by-email';
|
import { getUserByEmail } from '../server-only/user/get-user-by-email';
|
||||||
|
import { sendConfirmationToken } from '../server-only/user/send-confirmation-token';
|
||||||
import { ErrorCode } from './error-codes';
|
import { ErrorCode } from './error-codes';
|
||||||
|
|
||||||
export const NEXT_AUTH_OPTIONS: AuthOptions = {
|
export const NEXT_AUTH_OPTIONS: AuthOptions = {
|
||||||
@@ -71,6 +72,15 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!user.emailVerified) {
|
if (!user.emailVerified) {
|
||||||
|
const totalUserVerificationTokens = user.VerificationToken.length;
|
||||||
|
const lastUserVerificationToken = user.VerificationToken[totalUserVerificationTokens - 1];
|
||||||
|
const expiredToken =
|
||||||
|
DateTime.fromJSDate(lastUserVerificationToken.expires) <= DateTime.now();
|
||||||
|
|
||||||
|
if (totalUserVerificationTokens < 1 || expiredToken) {
|
||||||
|
await sendConfirmationToken({ email });
|
||||||
|
}
|
||||||
|
|
||||||
throw new Error(ErrorCode.UNVERIFIED_EMAIL);
|
throw new Error(ErrorCode.UNVERIFIED_EMAIL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,5 +9,8 @@ export const getUserByEmail = async ({ email }: GetUserByEmailOptions) => {
|
|||||||
where: {
|
where: {
|
||||||
email: email.toLowerCase(),
|
email: email.toLowerCase(),
|
||||||
},
|
},
|
||||||
|
include: {
|
||||||
|
VerificationToken: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ export const sendConfirmationToken = async ({ email }: { email: string }) => {
|
|||||||
throw new Error('User not found');
|
throw new Error('User not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (user.emailVerified) {
|
||||||
|
throw new Error('Email verified');
|
||||||
|
}
|
||||||
|
|
||||||
const createdToken = await prisma.verificationToken.create({
|
const createdToken = await prisma.verificationToken.create({
|
||||||
data: {
|
data: {
|
||||||
identifier: IDENTIFIER,
|
identifier: IDENTIFIER,
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { TRPCError } from '@trpc/server';
|
import { TRPCError } from '@trpc/server';
|
||||||
|
|
||||||
import { decryptSecondaryData } from '@documenso/lib/server-only/crypto/decrypt';
|
|
||||||
import { forgotPassword } from '@documenso/lib/server-only/user/forgot-password';
|
import { forgotPassword } from '@documenso/lib/server-only/user/forgot-password';
|
||||||
import { getUserById } from '@documenso/lib/server-only/user/get-user-by-id';
|
import { getUserById } from '@documenso/lib/server-only/user/get-user-by-id';
|
||||||
import { resetPassword } from '@documenso/lib/server-only/user/reset-password';
|
import { resetPassword } from '@documenso/lib/server-only/user/reset-password';
|
||||||
@@ -118,15 +117,9 @@ export const profileRouter = router({
|
|||||||
.input(ZConfirmEmailMutationSchema)
|
.input(ZConfirmEmailMutationSchema)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
try {
|
try {
|
||||||
const { encryptedEmail } = input;
|
const { email } = input;
|
||||||
|
|
||||||
const decryptedEmail = decryptSecondaryData(encryptedEmail);
|
return await sendConfirmationToken({ email });
|
||||||
|
|
||||||
if (!decryptedEmail) {
|
|
||||||
throw new Error('Email is required');
|
|
||||||
}
|
|
||||||
|
|
||||||
return await sendConfirmationToken({ email: decryptedEmail });
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
let message = 'We were unable to send a confirmation email. Please try again.';
|
let message = 'We were unable to send a confirmation email. Please try again.';
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const ZResetPasswordFormSchema = z.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const ZConfirmEmailMutationSchema = z.object({
|
export const ZConfirmEmailMutationSchema = z.object({
|
||||||
encryptedEmail: z.string().min(1),
|
email: z.string().email().min(1),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TRetrieveUserByIdQuerySchema = z.infer<typeof ZRetrieveUserByIdQuerySchema>;
|
export type TRetrieveUserByIdQuerySchema = z.infer<typeof ZRetrieveUserByIdQuerySchema>;
|
||||||
|
|||||||
Reference in New Issue
Block a user