mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 00:03:33 +10:00
fix: make invite and confirmations long lived (#1309)
Previously we would delete all invites and confirmation tokens upon completing the action that they represent. This change instead adds a flag on each token indicating whether it has been completed so we can action a completed token differently in the UI to reduce confusion for users. This had been brought up a number of times where confirmation emails, team member invites and other items may have been actioned and forgotten about causing an error toast/page upon subsequent revisit.
This commit is contained in:
@ -50,15 +50,50 @@ export default async function VerifyTeamEmailPage({ params: { token } }: VerifyT
|
||||
);
|
||||
}
|
||||
|
||||
if (teamEmailVerification.completed) {
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-4xl font-semibold">
|
||||
<Trans>Team email already verified!</Trans>
|
||||
</h1>
|
||||
|
||||
<p className="text-muted-foreground mb-4 mt-2 text-sm">
|
||||
<Trans>
|
||||
You have already verified your email address for{' '}
|
||||
<strong>{teamEmailVerification.team.name}</strong>.
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<Button asChild>
|
||||
<Link href="/">
|
||||
<Trans>Continue</Trans>
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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({
|
||||
|
||||
@ -53,6 +53,29 @@ export default async function VerifyTeamTransferPage({
|
||||
);
|
||||
}
|
||||
|
||||
if (teamTransferVerification.completed) {
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-4xl font-semibold">
|
||||
<Trans>Team ownership transfer already completed!</Trans>
|
||||
</h1>
|
||||
|
||||
<p className="text-muted-foreground mb-4 mt-2 text-sm">
|
||||
<Trans>
|
||||
You have already completed the ownership transfer for{' '}
|
||||
<strong>{teamTransferVerification.team.name}</strong>.
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<Button asChild>
|
||||
<Link href="/">
|
||||
<Trans>Continue</Trans>
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const { team } = teamTransferVerification;
|
||||
|
||||
let isTransferError = false;
|
||||
|
||||
@ -0,0 +1,56 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import Link from 'next/link';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { CheckCircle2 } from 'lucide-react';
|
||||
import { signIn } from 'next-auth/react';
|
||||
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
|
||||
export type VerifyEmailPageClientProps = {
|
||||
signInData?: string;
|
||||
};
|
||||
|
||||
export const VerifyEmailPageClient = ({ signInData }: VerifyEmailPageClientProps) => {
|
||||
useEffect(() => {
|
||||
if (signInData) {
|
||||
void signIn('manual', {
|
||||
credential: signInData,
|
||||
callbackUrl: '/documents',
|
||||
});
|
||||
}
|
||||
}, [signInData]);
|
||||
|
||||
return (
|
||||
<div className="w-screen max-w-lg px-4">
|
||||
<div className="flex w-full items-start">
|
||||
<div className="mr-4 mt-1 hidden md:block">
|
||||
<CheckCircle2 className="h-10 w-10 text-green-500" strokeWidth={2} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold md:text-4xl">
|
||||
<Trans>Email Confirmed!</Trans>
|
||||
</h2>
|
||||
|
||||
<p className="text-muted-foreground mt-4">
|
||||
<Trans>
|
||||
Your email has been successfully confirmed! You can now use all features of Documenso.
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
{!signInData && (
|
||||
<Button className="mt-4" asChild>
|
||||
<Link href="/">
|
||||
<Trans>Go back home</Trans>
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -1,12 +1,21 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { AlertTriangle, CheckCircle2, XCircle, XOctagon } from 'lucide-react';
|
||||
import { AlertTriangle, XCircle, XOctagon } from 'lucide-react';
|
||||
import { DateTime } from 'luxon';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server';
|
||||
import { verifyEmail } from '@documenso/lib/server-only/user/verify-email';
|
||||
import { encryptSecondaryData } from '@documenso/lib/server-only/crypto/encrypt';
|
||||
import {
|
||||
EMAIL_VERIFICATION_STATE,
|
||||
verifyEmail,
|
||||
} from '@documenso/lib/server-only/user/verify-email';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
|
||||
import { VerifyEmailPageClient } from './client';
|
||||
|
||||
export type PageProps = {
|
||||
params: {
|
||||
token: string;
|
||||
@ -39,8 +48,8 @@ export default async function VerifyEmailPage({ params: { token } }: PageProps)
|
||||
|
||||
const verified = await verifyEmail({ token });
|
||||
|
||||
if (verified === null) {
|
||||
return (
|
||||
return await match(verified)
|
||||
.with(EMAIL_VERIFICATION_STATE.NOT_FOUND, () => (
|
||||
<div className="w-screen max-w-lg px-4">
|
||||
<div className="flex w-full items-start">
|
||||
<div className="mr-4 mt-1 hidden md:block">
|
||||
@ -67,11 +76,8 @@ export default async function VerifyEmailPage({ params: { token } }: PageProps)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!verified) {
|
||||
return (
|
||||
))
|
||||
.with(EMAIL_VERIFICATION_STATE.EXPIRED, () => (
|
||||
<div className="w-screen max-w-lg px-4">
|
||||
<div className="flex w-full items-start">
|
||||
<div className="mr-4 mt-1 hidden md:block">
|
||||
@ -98,34 +104,27 @@ export default async function VerifyEmailPage({ params: { token } }: PageProps)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
))
|
||||
.with(EMAIL_VERIFICATION_STATE.VERIFIED, async () => {
|
||||
const { user } = await prisma.verificationToken.findFirstOrThrow({
|
||||
where: {
|
||||
token,
|
||||
},
|
||||
include: {
|
||||
user: true,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="w-screen max-w-lg px-4">
|
||||
<div className="flex w-full items-start">
|
||||
<div className="mr-4 mt-1 hidden md:block">
|
||||
<CheckCircle2 className="h-10 w-10 text-green-500" strokeWidth={2} />
|
||||
</div>
|
||||
const data = encryptSecondaryData({
|
||||
data: JSON.stringify({
|
||||
userId: user.id,
|
||||
email: user.email,
|
||||
}),
|
||||
expiresAt: DateTime.now().plus({ minutes: 5 }).toMillis(),
|
||||
});
|
||||
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold md:text-4xl">
|
||||
<Trans>Email Confirmed!</Trans>
|
||||
</h2>
|
||||
|
||||
<p className="text-muted-foreground mt-4">
|
||||
<Trans>
|
||||
Your email has been successfully confirmed! You can now use all features of Documenso.
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<Button className="mt-4" asChild>
|
||||
<Link href="/">
|
||||
<Trans>Go back home</Trans>
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return <VerifyEmailPageClient signInData={data} />;
|
||||
})
|
||||
.with(EMAIL_VERIFICATION_STATE.ALREADY_VERIFIED, () => <VerifyEmailPageClient />)
|
||||
.exhaustive();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user