mirror of
https://github.com/documenso/documenso.git
synced 2026-08-23 23:02:22 +10:00
feat: unify settings (#3128)
This commit is contained in:
@@ -64,7 +64,7 @@ export async function loader({ params }: Route.LoaderArgs) {
|
||||
},
|
||||
});
|
||||
|
||||
if (!verificationToken || verificationToken.expires < new Date()) {
|
||||
if (!verificationToken || verificationToken.completed || verificationToken.expires < new Date()) {
|
||||
throw data({
|
||||
type: 'invalid-token',
|
||||
});
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { isTokenExpired } from '@documenso/lib/utils/token-verification';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
import { Link } from 'react-router';
|
||||
|
||||
import type { Route } from './+types/team.verify.email.$token';
|
||||
@@ -19,8 +23,15 @@ export async function loader({ params }: Route.LoaderArgs) {
|
||||
where: {
|
||||
token,
|
||||
},
|
||||
include: {
|
||||
team: true,
|
||||
select: {
|
||||
email: true,
|
||||
completed: true,
|
||||
expiresAt: true,
|
||||
team: {
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -37,52 +48,11 @@ export async function loader({ params }: Route.LoaderArgs) {
|
||||
} as const;
|
||||
}
|
||||
|
||||
const { team } = teamEmailVerification;
|
||||
|
||||
let isTeamEmailVerificationError = false;
|
||||
|
||||
try {
|
||||
await prisma.$transaction([
|
||||
prisma.teamEmailVerification.updateMany({
|
||||
where: {
|
||||
teamId: team.id,
|
||||
email: teamEmailVerification.email,
|
||||
},
|
||||
data: {
|
||||
completed: true,
|
||||
},
|
||||
}),
|
||||
prisma.teamEmailVerification.deleteMany({
|
||||
where: {
|
||||
teamId: team.id,
|
||||
expiresAt: {
|
||||
lt: new Date(),
|
||||
},
|
||||
},
|
||||
}),
|
||||
prisma.teamEmail.create({
|
||||
data: {
|
||||
teamId: team.id,
|
||||
email: teamEmailVerification.email,
|
||||
name: teamEmailVerification.name,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
isTeamEmailVerificationError = true;
|
||||
}
|
||||
|
||||
if (isTeamEmailVerificationError) {
|
||||
return {
|
||||
state: 'VerificationError',
|
||||
teamName: team.name,
|
||||
} as const;
|
||||
}
|
||||
|
||||
return {
|
||||
state: 'Success',
|
||||
teamName: team.name,
|
||||
state: 'Pending',
|
||||
token,
|
||||
email: teamEmailVerification.email,
|
||||
teamName: teamEmailVerification.team.name,
|
||||
} as const;
|
||||
}
|
||||
|
||||
@@ -113,60 +83,117 @@ export default function VerifyTeamEmailPage({ loaderData }: Route.ComponentProps
|
||||
|
||||
if (data.state === 'AlreadyCompleted') {
|
||||
return (
|
||||
<div>
|
||||
<h1 className="font-semibold text-4xl">
|
||||
<Trans>Team email already verified!</Trans>
|
||||
</h1>
|
||||
<div className="w-screen max-w-lg px-4">
|
||||
<div className="w-full">
|
||||
<h1 className="font-semibold text-4xl">
|
||||
<Trans>Team email already verified!</Trans>
|
||||
</h1>
|
||||
|
||||
<p className="mt-2 mb-4 text-muted-foreground text-sm">
|
||||
<Trans>
|
||||
You have already verified your email address for <strong>{data.teamName}</strong>.
|
||||
</Trans>
|
||||
</p>
|
||||
<p className="mt-2 mb-4 text-muted-foreground text-sm">
|
||||
<Trans>
|
||||
You have already verified your email address for <strong>{data.teamName}</strong>.
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<Button asChild>
|
||||
<Link to="/">
|
||||
<Trans>Continue</Trans>
|
||||
</Link>
|
||||
</Button>
|
||||
<Button asChild>
|
||||
<Link to="/">
|
||||
<Trans>Continue</Trans>
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (data.state === 'VerificationError') {
|
||||
return (
|
||||
<div>
|
||||
<h1 className="font-semibold text-4xl">
|
||||
<Trans>Team email verification</Trans>
|
||||
</h1>
|
||||
return <PendingTeamEmailVerification token={data.token} email={data.email} teamName={data.teamName} />;
|
||||
}
|
||||
|
||||
<p className="mt-2 text-muted-foreground text-sm">
|
||||
<Trans>
|
||||
Something went wrong while attempting to verify your email address for <strong>{data.teamName}</strong>.
|
||||
Please try again later.
|
||||
</Trans>
|
||||
</p>
|
||||
type PendingTeamEmailVerificationProps = {
|
||||
token: string;
|
||||
email: string;
|
||||
teamName: string;
|
||||
};
|
||||
|
||||
const PendingTeamEmailVerification = ({ token, email, teamName }: PendingTeamEmailVerificationProps) => {
|
||||
const { t } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const [isVerified, setIsVerified] = useState(false);
|
||||
|
||||
const { mutateAsync: completeTeamEmailVerification, isPending } = trpc.team.email.verification.complete.useMutation({
|
||||
onSuccess: () => setIsVerified(true),
|
||||
onError: (err) => {
|
||||
const error = AppError.parseError(err);
|
||||
|
||||
if (error.code === AppErrorCode.ALREADY_EXISTS) {
|
||||
toast({
|
||||
title: t`Email already in use`,
|
||||
description: t`This email is already being used as a team email. Please contact your team for assistance.`,
|
||||
variant: 'destructive',
|
||||
duration: 10000,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
toast({
|
||||
title: t`Something went wrong`,
|
||||
description: t`We were unable to verify this email at this time. Please try again later.`,
|
||||
variant: 'destructive',
|
||||
duration: 10000,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
if (isVerified) {
|
||||
return (
|
||||
<div className="w-screen max-w-lg px-4">
|
||||
<div className="w-full">
|
||||
<h1 className="font-semibold text-4xl">
|
||||
<Trans>Team email verified!</Trans>
|
||||
</h1>
|
||||
|
||||
<p className="mt-2 mb-4 text-muted-foreground text-sm">
|
||||
<Trans>
|
||||
You have verified your email address for <strong>{teamName}</strong>.
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<Button asChild>
|
||||
<Link to="/">
|
||||
<Trans>Continue</Trans>
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="font-semibold text-4xl">
|
||||
<Trans>Team email verified!</Trans>
|
||||
</h1>
|
||||
<div className="w-screen max-w-lg px-4">
|
||||
<div className="w-full">
|
||||
<h1 className="font-semibold text-4xl">
|
||||
<Trans>Verify team email</Trans>
|
||||
</h1>
|
||||
|
||||
<p className="mt-2 mb-4 text-muted-foreground text-sm">
|
||||
<Trans>
|
||||
You have verified your email address for <strong>{data.teamName}</strong>.
|
||||
</Trans>
|
||||
</p>
|
||||
<p className="mt-2 text-muted-foreground text-sm">
|
||||
<Trans>
|
||||
<strong>{teamName}</strong> would like to use <strong>{email}</strong> as their team email.
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<Button asChild>
|
||||
<Link to="/">
|
||||
<Trans>Continue</Trans>
|
||||
</Link>
|
||||
</Button>
|
||||
<p className="mt-2 text-muted-foreground text-sm">
|
||||
<Trans>They will be able to view documents associated with this email.</Trans>
|
||||
</p>
|
||||
|
||||
<p className="mt-2 mb-4 text-muted-foreground text-sm">
|
||||
<Trans>Do not proceed if you are unsure about this request.</Trans>
|
||||
</p>
|
||||
|
||||
<Button loading={isPending} onClick={async () => completeTeamEmailVerification({ token })}>
|
||||
<Trans>Verify email</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user