feat: unify settings (#3128)

This commit is contained in:
David Nguyen
2026-08-09 16:00:55 +10:00
committed by GitHub
parent f0ab7c112e
commit d6cf3fec4b
120 changed files with 4181 additions and 1859 deletions
@@ -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>
);
}
};