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:
Lucas Smith
2024-08-28 14:08:35 +10:00
committed by GitHub
parent 7943ed5353
commit dfa89ffe44
18 changed files with 352 additions and 97 deletions

View File

@ -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; const { team } = teamEmailVerification;
let isTeamEmailVerificationError = false; let isTeamEmailVerificationError = false;
try { try {
await prisma.$transaction([ await prisma.$transaction([
prisma.teamEmailVerification.updateMany({
where: {
teamId: team.id,
email: teamEmailVerification.email,
},
data: {
completed: true,
},
}),
prisma.teamEmailVerification.deleteMany({ prisma.teamEmailVerification.deleteMany({
where: { where: {
teamId: team.id, teamId: team.id,
expiresAt: {
lt: new Date(),
},
}, },
}), }),
prisma.teamEmail.create({ prisma.teamEmail.create({

View File

@ -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; const { team } = teamTransferVerification;
let isTransferError = false; let isTransferError = false;

View File

@ -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>
);
};

View File

@ -1,12 +1,21 @@
import Link from 'next/link'; import Link from 'next/link';
import { Trans } from '@lingui/macro'; 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 { 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 { Button } from '@documenso/ui/primitives/button';
import { VerifyEmailPageClient } from './client';
export type PageProps = { export type PageProps = {
params: { params: {
token: string; token: string;
@ -39,8 +48,8 @@ export default async function VerifyEmailPage({ params: { token } }: PageProps)
const verified = await verifyEmail({ token }); const verified = await verifyEmail({ token });
if (verified === null) { return await match(verified)
return ( .with(EMAIL_VERIFICATION_STATE.NOT_FOUND, () => (
<div className="w-screen max-w-lg px-4"> <div className="w-screen max-w-lg px-4">
<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">
@ -67,11 +76,8 @@ export default async function VerifyEmailPage({ params: { token } }: PageProps)
</div> </div>
</div> </div>
</div> </div>
); ))
} .with(EMAIL_VERIFICATION_STATE.EXPIRED, () => (
if (!verified) {
return (
<div className="w-screen max-w-lg px-4"> <div className="w-screen max-w-lg px-4">
<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">
@ -98,34 +104,27 @@ export default async function VerifyEmailPage({ params: { token } }: PageProps)
</div> </div>
</div> </div>
</div> </div>
); ))
} .with(EMAIL_VERIFICATION_STATE.VERIFIED, async () => {
const { user } = await prisma.verificationToken.findFirstOrThrow({
where: {
token,
},
include: {
user: true,
},
});
return ( const data = encryptSecondaryData({
<div className="w-screen max-w-lg px-4"> data: JSON.stringify({
<div className="flex w-full items-start"> userId: user.id,
<div className="mr-4 mt-1 hidden md:block"> email: user.email,
<CheckCircle2 className="h-10 w-10 text-green-500" strokeWidth={2} /> }),
</div> expiresAt: DateTime.now().plus({ minutes: 5 }).toMillis(),
});
<div> return <VerifyEmailPageClient signInData={data} />;
<h2 className="text-2xl font-bold md:text-4xl"> })
<Trans>Email Confirmed!</Trans> .with(EMAIL_VERIFICATION_STATE.ALREADY_VERIFIED, () => <VerifyEmailPageClient />)
</h2> .exhaustive();
<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>
);
} }

View File

@ -41,7 +41,8 @@ test('[USER] can sign up with email and password', async ({ page }: { page: Page
await expect(page.getByRole('heading')).toContainText('Email Confirmed!'); await expect(page.getByRole('heading')).toContainText('Email Confirmed!');
await page.getByRole('link', { name: 'Go back home' }).click(); // We now automatically redirect to the home page
// await page.getByRole('link', { name: 'Go back home' }).click();
await page.waitForURL('/documents'); await page.waitForURL('/documents');

View File

@ -17,6 +17,7 @@ import { AppError, AppErrorCode } from '../errors/app-error';
import { jobsClient } from '../jobs/client'; import { jobsClient } from '../jobs/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 { decryptSecondaryData } from '../server-only/crypto/decrypt';
import { getMostRecentVerificationTokenByUserId } from '../server-only/user/get-most-recent-verification-token-by-user-id'; import { getMostRecentVerificationTokenByUserId } from '../server-only/user/get-most-recent-verification-token-by-user-id';
import { getUserByEmail } from '../server-only/user/get-user-by-email'; import { getUserByEmail } from '../server-only/user/get-user-by-email';
import type { TAuthenticationResponseJSONSchema } from '../types/webauthn'; import type { TAuthenticationResponseJSONSchema } from '../types/webauthn';
@ -267,6 +268,55 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = {
}, },
}); });
return {
id: Number(user.id),
email: user.email,
name: user.name,
emailVerified: user.emailVerified?.toISOString() ?? null,
} satisfies User;
},
}),
CredentialsProvider({
id: 'manual',
name: 'Manual',
credentials: {
credential: { label: 'Credential', type: 'credential' },
},
async authorize(credentials, req) {
const credential = credentials?.credential;
if (typeof credential !== 'string' || credential.length === 0) {
throw new AppError(AppErrorCode.INVALID_REQUEST);
}
const decryptedCredential = decryptSecondaryData(credential);
if (!decryptedCredential) {
throw new AppError(AppErrorCode.INVALID_REQUEST);
}
const parsedCredential = JSON.parse(decryptedCredential);
if (typeof parsedCredential !== 'object' || parsedCredential === null) {
throw new AppError(AppErrorCode.INVALID_REQUEST);
}
const { userId, email } = parsedCredential;
if (typeof userId !== 'number' || typeof email !== 'string') {
throw new AppError(AppErrorCode.INVALID_REQUEST);
}
const user = await prisma.user.findFirst({
where: {
id: userId,
},
});
if (!user) {
throw new AppError(AppErrorCode.INVALID_REQUEST);
}
return { return {
id: Number(user.id), id: Number(user.id),
email: user.email, email: user.email,

View File

@ -1,6 +1,7 @@
import { updateSubscriptionItemQuantity } from '@documenso/ee/server-only/stripe/update-subscription-item-quantity'; import { updateSubscriptionItemQuantity } from '@documenso/ee/server-only/stripe/update-subscription-item-quantity';
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app'; import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
import { prisma } from '@documenso/prisma'; import { prisma } from '@documenso/prisma';
import { TeamMemberInviteStatus } from '@documenso/prisma/client';
import { jobs } from '../../jobs/client'; import { jobs } from '../../jobs/client';
@ -22,6 +23,9 @@ export const acceptTeamInvitation = async ({ userId, teamId }: AcceptTeamInvitat
where: { where: {
teamId, teamId,
email: user.email, email: user.email,
status: {
not: TeamMemberInviteStatus.DECLINED,
},
}, },
include: { include: {
team: { team: {
@ -37,6 +41,10 @@ export const acceptTeamInvitation = async ({ userId, teamId }: AcceptTeamInvitat
}, },
}); });
if (teamMemberInvite.status === TeamMemberInviteStatus.ACCEPTED) {
return;
}
const { team } = teamMemberInvite; const { team } = teamMemberInvite;
const teamMember = await tx.teamMember.create({ const teamMember = await tx.teamMember.create({
@ -47,10 +55,13 @@ export const acceptTeamInvitation = async ({ userId, teamId }: AcceptTeamInvitat
}, },
}); });
await tx.teamMemberInvite.delete({ await tx.teamMemberInvite.update({
where: { where: {
id: teamMemberInvite.id, id: teamMemberInvite.id,
}, },
data: {
status: TeamMemberInviteStatus.ACCEPTED,
},
}); });
if (IS_BILLING_ENABLED() && team.subscription) { if (IS_BILLING_ENABLED() && team.subscription) {

View File

@ -28,11 +28,24 @@ export const transferTeamOwnership = async ({ token }: TransferTeamOwnershipOpti
const { team, userId: newOwnerUserId } = teamTransferVerification; const { team, userId: newOwnerUserId } = teamTransferVerification;
await tx.teamTransferVerification.delete({ await Promise.all([
where: { tx.teamTransferVerification.updateMany({
teamId: team.id, where: {
}, teamId: team.id,
}); },
data: {
completed: true,
},
}),
tx.teamTransferVerification.deleteMany({
where: {
teamId: team.id,
expiresAt: {
lt: new Date(),
},
},
}),
]);
const newOwnerUser = await tx.user.findFirstOrThrow({ const newOwnerUser = await tx.user.findFirstOrThrow({
where: { where: {

View File

@ -4,6 +4,13 @@ import { prisma } from '@documenso/prisma';
import { jobsClient } from '../../jobs/client'; import { jobsClient } from '../../jobs/client';
export const EMAIL_VERIFICATION_STATE = {
NOT_FOUND: 'NOT_FOUND',
VERIFIED: 'VERIFIED',
EXPIRED: 'EXPIRED',
ALREADY_VERIFIED: 'ALREADY_VERIFIED',
} as const;
export type VerifyEmailProps = { export type VerifyEmailProps = {
token: string; token: string;
}; };
@ -19,7 +26,7 @@ export const verifyEmail = async ({ token }: VerifyEmailProps) => {
}); });
if (!verificationToken) { if (!verificationToken) {
return null; return EMAIL_VERIFICATION_STATE.NOT_FOUND;
} }
// check if the token is valid or expired // check if the token is valid or expired
@ -48,10 +55,14 @@ export const verifyEmail = async ({ token }: VerifyEmailProps) => {
}); });
} }
return valid; return EMAIL_VERIFICATION_STATE.EXPIRED;
} }
const [updatedUser, deletedToken] = await prisma.$transaction([ if (verificationToken.completed) {
return EMAIL_VERIFICATION_STATE.ALREADY_VERIFIED;
}
const [updatedUser] = await prisma.$transaction([
prisma.user.update({ prisma.user.update({
where: { where: {
id: verificationToken.userId, id: verificationToken.userId,
@ -60,16 +71,28 @@ export const verifyEmail = async ({ token }: VerifyEmailProps) => {
emailVerified: new Date(), emailVerified: new Date(),
}, },
}), }),
prisma.verificationToken.updateMany({
where: {
userId: verificationToken.userId,
},
data: {
completed: true,
},
}),
// Tidy up old expired tokens
prisma.verificationToken.deleteMany({ prisma.verificationToken.deleteMany({
where: { where: {
userId: verificationToken.userId, userId: verificationToken.userId,
expires: {
lt: new Date(),
},
}, },
}), }),
]); ]);
if (!updatedUser || !deletedToken) { if (!updatedUser) {
throw new Error('Something went wrong while verifying your email. Please try again.'); throw new Error('Something went wrong while verifying your email. Please try again.');
} }
return !!updatedUser && !!deletedToken; return EMAIL_VERIFICATION_STATE.VERIFIED;
}; };

View File

@ -748,4 +748,3 @@ msgstr "Sie können derzeit keine Dokumente hochladen."
#: packages/ui/primitives/document-dropzone.tsx:69 #: packages/ui/primitives/document-dropzone.tsx:69
msgid "You have reached your document limit." msgid "You have reached your document limit."
msgstr "Sie haben Ihr Dokumentenlimit erreicht." msgstr "Sie haben Ihr Dokumentenlimit erreicht."

File diff suppressed because one or more lines are too long

View File

@ -617,4 +617,3 @@ msgstr "Sie können Documenso kostenlos selbst hosten oder unsere sofort einsatz
#: apps/marketing/src/components/(marketing)/carousel.tsx:265 #: apps/marketing/src/components/(marketing)/carousel.tsx:265
msgid "Your browser does not support the video tag." msgid "Your browser does not support the video tag."
msgstr "Ihr Browser unterstützt das Video-Tag nicht." msgstr "Ihr Browser unterstützt das Video-Tag nicht."

File diff suppressed because one or more lines are too long

View File

@ -845,8 +845,10 @@ msgstr ""
#: apps/web/src/app/(dashboard)/settings/security/passkeys/create-passkey-dialog.tsx:252 #: apps/web/src/app/(dashboard)/settings/security/passkeys/create-passkey-dialog.tsx:252
#: apps/web/src/app/(unauthenticated)/team/invite/[token]/page.tsx:135 #: apps/web/src/app/(unauthenticated)/team/invite/[token]/page.tsx:135
#: apps/web/src/app/(unauthenticated)/team/verify/email/[token]/page.tsx:108 #: apps/web/src/app/(unauthenticated)/team/verify/email/[token]/page.tsx:69
#: apps/web/src/app/(unauthenticated)/team/verify/transfer/[token]/page.tsx:99 #: apps/web/src/app/(unauthenticated)/team/verify/email/[token]/page.tsx:143
#: apps/web/src/app/(unauthenticated)/team/verify/transfer/[token]/page.tsx:72
#: apps/web/src/app/(unauthenticated)/team/verify/transfer/[token]/page.tsx:122
#: apps/web/src/components/templates/manage-public-template-dialog.tsx:330 #: apps/web/src/components/templates/manage-public-template-dialog.tsx:330
msgid "Continue" msgid "Continue"
msgstr "" msgstr ""
@ -1406,7 +1408,7 @@ msgstr ""
msgid "Email Address" msgid "Email Address"
msgstr "" msgstr ""
#: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:113 #: apps/web/src/app/(unauthenticated)/verify-email/[token]/client.tsx:36
msgid "Email Confirmed!" msgid "Email Confirmed!"
msgstr "" msgstr ""
@ -1577,9 +1579,9 @@ msgstr ""
msgid "Go Back" msgid "Go Back"
msgstr "" msgstr ""
#: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:64 #: apps/web/src/app/(unauthenticated)/verify-email/[token]/client.tsx:48
#: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:95 #: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:73
#: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:124 #: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:101
#: apps/web/src/app/(unauthenticated)/verify-email/page.tsx:38 #: apps/web/src/app/(unauthenticated)/verify-email/page.tsx:38
msgid "Go back home" msgid "Go back home"
msgstr "" msgstr ""
@ -1730,7 +1732,7 @@ msgstr ""
msgid "It looks like {0} hasn't added any documents to their profile yet." msgid "It looks like {0} hasn't added any documents to their profile yet."
msgstr "" msgstr ""
#: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:87 #: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:93
msgid "It seems that the provided token has expired. We've just sent you another token, please check your email and try again." msgid "It seems that the provided token has expired. We've just sent you another token, please check your email and try again."
msgstr "" msgstr ""
@ -1738,7 +1740,7 @@ msgstr ""
msgid "It seems that there is no token provided, if you are trying to verify your email please follow the link in your email." msgid "It seems that there is no token provided, if you are trying to verify your email please follow the link in your email."
msgstr "" msgstr ""
#: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:31 #: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:40
msgid "It seems that there is no token provided. Please check your email and try again." msgid "It seems that there is no token provided. Please check your email and try again."
msgstr "" msgstr ""
@ -2025,7 +2027,7 @@ msgstr ""
msgid "No results found." msgid "No results found."
msgstr "" msgstr ""
#: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:28 #: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:37
msgid "No token provided" msgid "No token provided"
msgstr "" msgstr ""
@ -2805,7 +2807,7 @@ msgstr ""
#: apps/web/src/app/(signing)/sign/[token]/signing-auth-page.tsx:43 #: apps/web/src/app/(signing)/sign/[token]/signing-auth-page.tsx:43
#: apps/web/src/app/(teams)/t/[teamUrl]/layout-billing-banner.tsx:53 #: apps/web/src/app/(teams)/t/[teamUrl]/layout-billing-banner.tsx:53
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/team-email-dropdown.tsx:39 #: apps/web/src/app/(teams)/t/[teamUrl]/settings/team-email-dropdown.tsx:39
#: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:52 #: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:61
#: apps/web/src/components/(teams)/dialogs/create-team-checkout-dialog.tsx:50 #: apps/web/src/components/(teams)/dialogs/create-team-checkout-dialog.tsx:50
#: apps/web/src/components/(teams)/dialogs/create-team-checkout-dialog.tsx:99 #: apps/web/src/components/(teams)/dialogs/create-team-checkout-dialog.tsx:99
#: apps/web/src/components/(teams)/dialogs/invite-team-member-dialog.tsx:210 #: apps/web/src/components/(teams)/dialogs/invite-team-member-dialog.tsx:210
@ -2818,11 +2820,11 @@ msgstr ""
msgid "Something went wrong" msgid "Something went wrong"
msgstr "" msgstr ""
#: apps/web/src/app/(unauthenticated)/team/verify/transfer/[token]/page.tsx:75 #: apps/web/src/app/(unauthenticated)/team/verify/transfer/[token]/page.tsx:98
msgid "Something went wrong while attempting to transfer the ownership of team <0>{0}</0> to your. Please try again later or contact support." msgid "Something went wrong while attempting to transfer the ownership of team <0>{0}</0> to your. Please try again later or contact support."
msgstr "" msgstr ""
#: apps/web/src/app/(unauthenticated)/team/verify/email/[token]/page.tsx:85 #: apps/web/src/app/(unauthenticated)/team/verify/email/[token]/page.tsx:120
msgid "Something went wrong while attempting to verify your email address for <0>{0}</0>. Please try again later." msgid "Something went wrong while attempting to verify your email address for <0>{0}</0>. Please try again later."
msgstr "" msgstr ""
@ -2932,15 +2934,19 @@ msgstr ""
msgid "Team Email" msgid "Team Email"
msgstr "" msgstr ""
#: apps/web/src/app/(unauthenticated)/team/verify/email/[token]/page.tsx:57
msgid "Team email already verified!"
msgstr ""
#: apps/web/src/components/(teams)/dialogs/remove-team-email-dialog.tsx:58 #: apps/web/src/components/(teams)/dialogs/remove-team-email-dialog.tsx:58
msgid "Team email has been removed" msgid "Team email has been removed"
msgstr "" msgstr ""
#: apps/web/src/app/(unauthenticated)/team/verify/email/[token]/page.tsx:81 #: apps/web/src/app/(unauthenticated)/team/verify/email/[token]/page.tsx:116
msgid "Team email verification" msgid "Team email verification"
msgstr "" msgstr ""
#: apps/web/src/app/(unauthenticated)/team/verify/email/[token]/page.tsx:97 #: apps/web/src/app/(unauthenticated)/team/verify/email/[token]/page.tsx:132
msgid "Team email verified!" msgid "Team email verified!"
msgstr "" msgstr ""
@ -2975,11 +2981,15 @@ msgstr ""
msgid "Team only templates are not linked anywhere and are visible only to your team." msgid "Team only templates are not linked anywhere and are visible only to your team."
msgstr "" msgstr ""
#: apps/web/src/app/(unauthenticated)/team/verify/transfer/[token]/page.tsx:71 #: apps/web/src/app/(unauthenticated)/team/verify/transfer/[token]/page.tsx:94
msgid "Team ownership transfer" msgid "Team ownership transfer"
msgstr "" msgstr ""
#: apps/web/src/app/(unauthenticated)/team/verify/transfer/[token]/page.tsx:87 #: apps/web/src/app/(unauthenticated)/team/verify/transfer/[token]/page.tsx:60
msgid "Team ownership transfer already completed!"
msgstr ""
#: apps/web/src/app/(unauthenticated)/team/verify/transfer/[token]/page.tsx:110
msgid "Team ownership transferred!" msgid "Team ownership transferred!"
msgstr "" msgstr ""
@ -3109,7 +3119,7 @@ msgstr ""
msgid "The events that will trigger a webhook to be sent to your URL." msgid "The events that will trigger a webhook to be sent to your URL."
msgstr "" msgstr ""
#: apps/web/src/app/(unauthenticated)/team/verify/transfer/[token]/page.tsx:91 #: apps/web/src/app/(unauthenticated)/team/verify/transfer/[token]/page.tsx:114
msgid "The ownership of team <0>{0}</0> has been successfully transferred to you." msgid "The ownership of team <0>{0}</0> has been successfully transferred to you."
msgstr "" msgstr ""
@ -3955,7 +3965,7 @@ msgstr ""
msgid "We were unable to verify your details. Please try again or contact support" msgid "We were unable to verify your details. Please try again or contact support"
msgstr "" msgstr ""
#: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:56 #: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:65
msgid "We were unable to verify your email. If your email is not verified already, please try again." msgid "We were unable to verify your email. If your email is not verified already, please try again."
msgstr "" msgstr ""
@ -4131,6 +4141,14 @@ msgstr ""
msgid "You have accepted an invitation from <0>{0}</0> to join their team." msgid "You have accepted an invitation from <0>{0}</0> to join their team."
msgstr "" msgstr ""
#: apps/web/src/app/(unauthenticated)/team/verify/transfer/[token]/page.tsx:64
msgid "You have already completed the ownership transfer for <0>{0}</0>."
msgstr ""
#: apps/web/src/app/(unauthenticated)/team/verify/email/[token]/page.tsx:61
msgid "You have already verified your email address for <0>{0}</0>."
msgstr ""
#: apps/web/src/app/(unauthenticated)/team/decline/[token]/page.tsx:95 #: apps/web/src/app/(unauthenticated)/team/decline/[token]/page.tsx:95
#: apps/web/src/app/(unauthenticated)/team/invite/[token]/page.tsx:100 #: apps/web/src/app/(unauthenticated)/team/invite/[token]/page.tsx:100
msgid "You have been invited by <0>{0}</0> to join their team." msgid "You have been invited by <0>{0}</0> to join their team."
@ -4187,7 +4205,7 @@ msgstr ""
msgid "You have updated {teamMemberName}." msgid "You have updated {teamMemberName}."
msgstr "" msgstr ""
#: apps/web/src/app/(unauthenticated)/team/verify/email/[token]/page.tsx:101 #: apps/web/src/app/(unauthenticated)/team/verify/email/[token]/page.tsx:136
msgid "You have verified your email address for <0>{0}</0>." msgid "You have verified your email address for <0>{0}</0>."
msgstr "" msgstr ""
@ -4289,7 +4307,7 @@ msgstr ""
msgid "Your documents" msgid "Your documents"
msgstr "" msgstr ""
#: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:117 #: apps/web/src/app/(unauthenticated)/verify-email/[token]/client.tsx:40
msgid "Your email has been successfully confirmed! You can now use all features of Documenso." msgid "Your email has been successfully confirmed! You can now use all features of Documenso."
msgstr "" msgstr ""
@ -4364,7 +4382,7 @@ msgstr ""
msgid "Your templates has been saved successfully." msgid "Your templates has been saved successfully."
msgstr "" msgstr ""
#: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:83 #: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:89
msgid "Your token has expired!" msgid "Your token has expired!"
msgstr "" msgstr ""
@ -4376,4 +4394,3 @@ msgstr ""
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/tokens/page.tsx:87 #: apps/web/src/app/(teams)/t/[teamUrl]/settings/tokens/page.tsx:87
msgid "Your tokens will be shown here once you create them." msgid "Your tokens will be shown here once you create them."
msgstr "" msgstr ""

File diff suppressed because one or more lines are too long

View File

@ -840,8 +840,10 @@ msgstr "Content"
#: apps/web/src/app/(dashboard)/settings/security/passkeys/create-passkey-dialog.tsx:252 #: apps/web/src/app/(dashboard)/settings/security/passkeys/create-passkey-dialog.tsx:252
#: apps/web/src/app/(unauthenticated)/team/invite/[token]/page.tsx:135 #: apps/web/src/app/(unauthenticated)/team/invite/[token]/page.tsx:135
#: apps/web/src/app/(unauthenticated)/team/verify/email/[token]/page.tsx:108 #: apps/web/src/app/(unauthenticated)/team/verify/email/[token]/page.tsx:69
#: apps/web/src/app/(unauthenticated)/team/verify/transfer/[token]/page.tsx:99 #: apps/web/src/app/(unauthenticated)/team/verify/email/[token]/page.tsx:143
#: apps/web/src/app/(unauthenticated)/team/verify/transfer/[token]/page.tsx:72
#: apps/web/src/app/(unauthenticated)/team/verify/transfer/[token]/page.tsx:122
#: apps/web/src/components/templates/manage-public-template-dialog.tsx:330 #: apps/web/src/components/templates/manage-public-template-dialog.tsx:330
msgid "Continue" msgid "Continue"
msgstr "Continue" msgstr "Continue"
@ -1401,7 +1403,7 @@ msgstr "Email address"
msgid "Email Address" msgid "Email Address"
msgstr "Email Address" msgstr "Email Address"
#: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:113 #: apps/web/src/app/(unauthenticated)/verify-email/[token]/client.tsx:36
msgid "Email Confirmed!" msgid "Email Confirmed!"
msgstr "Email Confirmed!" msgstr "Email Confirmed!"
@ -1572,9 +1574,9 @@ msgstr "General"
msgid "Go Back" msgid "Go Back"
msgstr "Go Back" msgstr "Go Back"
#: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:64 #: apps/web/src/app/(unauthenticated)/verify-email/[token]/client.tsx:48
#: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:95 #: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:73
#: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:124 #: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:101
#: apps/web/src/app/(unauthenticated)/verify-email/page.tsx:38 #: apps/web/src/app/(unauthenticated)/verify-email/page.tsx:38
msgid "Go back home" msgid "Go back home"
msgstr "Go back home" msgstr "Go back home"
@ -1725,7 +1727,7 @@ msgstr "Invoice"
msgid "It looks like {0} hasn't added any documents to their profile yet." msgid "It looks like {0} hasn't added any documents to their profile yet."
msgstr "It looks like {0} hasn't added any documents to their profile yet." msgstr "It looks like {0} hasn't added any documents to their profile yet."
#: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:87 #: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:93
msgid "It seems that the provided token has expired. We've just sent you another token, please check your email and try again." msgid "It seems that the provided token has expired. We've just sent you another token, please check your email and try again."
msgstr "It seems that the provided token has expired. We've just sent you another token, please check your email and try again." msgstr "It seems that the provided token has expired. We've just sent you another token, please check your email and try again."
@ -1733,7 +1735,7 @@ msgstr "It seems that the provided token has expired. We've just sent you anothe
msgid "It seems that there is no token provided, if you are trying to verify your email please follow the link in your email." msgid "It seems that there is no token provided, if you are trying to verify your email please follow the link in your email."
msgstr "It seems that there is no token provided, if you are trying to verify your email please follow the link in your email." msgstr "It seems that there is no token provided, if you are trying to verify your email please follow the link in your email."
#: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:31 #: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:40
msgid "It seems that there is no token provided. Please check your email and try again." msgid "It seems that there is no token provided. Please check your email and try again."
msgstr "It seems that there is no token provided. Please check your email and try again." msgstr "It seems that there is no token provided. Please check your email and try again."
@ -2020,7 +2022,7 @@ msgstr "No recipients"
msgid "No results found." msgid "No results found."
msgstr "No results found." msgstr "No results found."
#: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:28 #: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:37
msgid "No token provided" msgid "No token provided"
msgstr "No token provided" msgstr "No token provided"
@ -2800,7 +2802,7 @@ msgstr "Site Settings"
#: apps/web/src/app/(signing)/sign/[token]/signing-auth-page.tsx:43 #: apps/web/src/app/(signing)/sign/[token]/signing-auth-page.tsx:43
#: apps/web/src/app/(teams)/t/[teamUrl]/layout-billing-banner.tsx:53 #: apps/web/src/app/(teams)/t/[teamUrl]/layout-billing-banner.tsx:53
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/team-email-dropdown.tsx:39 #: apps/web/src/app/(teams)/t/[teamUrl]/settings/team-email-dropdown.tsx:39
#: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:52 #: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:61
#: apps/web/src/components/(teams)/dialogs/create-team-checkout-dialog.tsx:50 #: apps/web/src/components/(teams)/dialogs/create-team-checkout-dialog.tsx:50
#: apps/web/src/components/(teams)/dialogs/create-team-checkout-dialog.tsx:99 #: apps/web/src/components/(teams)/dialogs/create-team-checkout-dialog.tsx:99
#: apps/web/src/components/(teams)/dialogs/invite-team-member-dialog.tsx:210 #: apps/web/src/components/(teams)/dialogs/invite-team-member-dialog.tsx:210
@ -2813,11 +2815,11 @@ msgstr "Site Settings"
msgid "Something went wrong" msgid "Something went wrong"
msgstr "Something went wrong" msgstr "Something went wrong"
#: apps/web/src/app/(unauthenticated)/team/verify/transfer/[token]/page.tsx:75 #: apps/web/src/app/(unauthenticated)/team/verify/transfer/[token]/page.tsx:98
msgid "Something went wrong while attempting to transfer the ownership of team <0>{0}</0> to your. Please try again later or contact support." msgid "Something went wrong while attempting to transfer the ownership of team <0>{0}</0> to your. Please try again later or contact support."
msgstr "Something went wrong while attempting to transfer the ownership of team <0>{0}</0> to your. Please try again later or contact support." msgstr "Something went wrong while attempting to transfer the ownership of team <0>{0}</0> to your. Please try again later or contact support."
#: apps/web/src/app/(unauthenticated)/team/verify/email/[token]/page.tsx:85 #: apps/web/src/app/(unauthenticated)/team/verify/email/[token]/page.tsx:120
msgid "Something went wrong while attempting to verify your email address for <0>{0}</0>. Please try again later." msgid "Something went wrong while attempting to verify your email address for <0>{0}</0>. Please try again later."
msgstr "Something went wrong while attempting to verify your email address for <0>{0}</0>. Please try again later." msgstr "Something went wrong while attempting to verify your email address for <0>{0}</0>. Please try again later."
@ -2927,15 +2929,19 @@ msgstr "Team email"
msgid "Team Email" msgid "Team Email"
msgstr "Team Email" msgstr "Team Email"
#: apps/web/src/app/(unauthenticated)/team/verify/email/[token]/page.tsx:57
msgid "Team email already verified!"
msgstr "Team email already verified!"
#: apps/web/src/components/(teams)/dialogs/remove-team-email-dialog.tsx:58 #: apps/web/src/components/(teams)/dialogs/remove-team-email-dialog.tsx:58
msgid "Team email has been removed" msgid "Team email has been removed"
msgstr "Team email has been removed" msgstr "Team email has been removed"
#: apps/web/src/app/(unauthenticated)/team/verify/email/[token]/page.tsx:81 #: apps/web/src/app/(unauthenticated)/team/verify/email/[token]/page.tsx:116
msgid "Team email verification" msgid "Team email verification"
msgstr "Team email verification" msgstr "Team email verification"
#: apps/web/src/app/(unauthenticated)/team/verify/email/[token]/page.tsx:97 #: apps/web/src/app/(unauthenticated)/team/verify/email/[token]/page.tsx:132
msgid "Team email verified!" msgid "Team email verified!"
msgstr "Team email verified!" msgstr "Team email verified!"
@ -2970,11 +2976,15 @@ msgstr "Team Only"
msgid "Team only templates are not linked anywhere and are visible only to your team." msgid "Team only templates are not linked anywhere and are visible only to your team."
msgstr "Team only templates are not linked anywhere and are visible only to your team." msgstr "Team only templates are not linked anywhere and are visible only to your team."
#: apps/web/src/app/(unauthenticated)/team/verify/transfer/[token]/page.tsx:71 #: apps/web/src/app/(unauthenticated)/team/verify/transfer/[token]/page.tsx:94
msgid "Team ownership transfer" msgid "Team ownership transfer"
msgstr "Team ownership transfer" msgstr "Team ownership transfer"
#: apps/web/src/app/(unauthenticated)/team/verify/transfer/[token]/page.tsx:87 #: apps/web/src/app/(unauthenticated)/team/verify/transfer/[token]/page.tsx:60
msgid "Team ownership transfer already completed!"
msgstr "Team ownership transfer already completed!"
#: apps/web/src/app/(unauthenticated)/team/verify/transfer/[token]/page.tsx:110
msgid "Team ownership transferred!" msgid "Team ownership transferred!"
msgstr "Team ownership transferred!" msgstr "Team ownership transferred!"
@ -3104,7 +3114,7 @@ msgstr "The document will be immediately sent to recipients if this is checked."
msgid "The events that will trigger a webhook to be sent to your URL." msgid "The events that will trigger a webhook to be sent to your URL."
msgstr "The events that will trigger a webhook to be sent to your URL." msgstr "The events that will trigger a webhook to be sent to your URL."
#: apps/web/src/app/(unauthenticated)/team/verify/transfer/[token]/page.tsx:91 #: apps/web/src/app/(unauthenticated)/team/verify/transfer/[token]/page.tsx:114
msgid "The ownership of team <0>{0}</0> has been successfully transferred to you." msgid "The ownership of team <0>{0}</0> has been successfully transferred to you."
msgstr "The ownership of team <0>{0}</0> has been successfully transferred to you." msgstr "The ownership of team <0>{0}</0> has been successfully transferred to you."
@ -3950,7 +3960,7 @@ msgstr "We were unable to submit this document at this time. Please try again la
msgid "We were unable to verify your details. Please try again or contact support" msgid "We were unable to verify your details. Please try again or contact support"
msgstr "We were unable to verify your details. Please try again or contact support" msgstr "We were unable to verify your details. Please try again or contact support"
#: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:56 #: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:65
msgid "We were unable to verify your email. If your email is not verified already, please try again." msgid "We were unable to verify your email. If your email is not verified already, please try again."
msgstr "We were unable to verify your email. If your email is not verified already, please try again." msgstr "We were unable to verify your email. If your email is not verified already, please try again."
@ -4126,6 +4136,14 @@ msgstr "You do not currently have a customer record, this should not happen. Ple
msgid "You have accepted an invitation from <0>{0}</0> to join their team." msgid "You have accepted an invitation from <0>{0}</0> to join their team."
msgstr "You have accepted an invitation from <0>{0}</0> to join their team." msgstr "You have accepted an invitation from <0>{0}</0> to join their team."
#: apps/web/src/app/(unauthenticated)/team/verify/transfer/[token]/page.tsx:64
msgid "You have already completed the ownership transfer for <0>{0}</0>."
msgstr "You have already completed the ownership transfer for <0>{0}</0>."
#: apps/web/src/app/(unauthenticated)/team/verify/email/[token]/page.tsx:61
msgid "You have already verified your email address for <0>{0}</0>."
msgstr "You have already verified your email address for <0>{0}</0>."
#: apps/web/src/app/(unauthenticated)/team/decline/[token]/page.tsx:95 #: apps/web/src/app/(unauthenticated)/team/decline/[token]/page.tsx:95
#: apps/web/src/app/(unauthenticated)/team/invite/[token]/page.tsx:100 #: apps/web/src/app/(unauthenticated)/team/invite/[token]/page.tsx:100
msgid "You have been invited by <0>{0}</0> to join their team." msgid "You have been invited by <0>{0}</0> to join their team."
@ -4182,7 +4200,7 @@ msgstr "You have successfully revoked access."
msgid "You have updated {teamMemberName}." msgid "You have updated {teamMemberName}."
msgstr "You have updated {teamMemberName}." msgstr "You have updated {teamMemberName}."
#: apps/web/src/app/(unauthenticated)/team/verify/email/[token]/page.tsx:101 #: apps/web/src/app/(unauthenticated)/team/verify/email/[token]/page.tsx:136
msgid "You have verified your email address for <0>{0}</0>." msgid "You have verified your email address for <0>{0}</0>."
msgstr "You have verified your email address for <0>{0}</0>." msgstr "You have verified your email address for <0>{0}</0>."
@ -4284,7 +4302,7 @@ msgstr "Your document has been uploaded successfully. You will be redirected to
msgid "Your documents" msgid "Your documents"
msgstr "Your documents" msgstr "Your documents"
#: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:117 #: apps/web/src/app/(unauthenticated)/verify-email/[token]/client.tsx:40
msgid "Your email has been successfully confirmed! You can now use all features of Documenso." msgid "Your email has been successfully confirmed! You can now use all features of Documenso."
msgstr "Your email has been successfully confirmed! You can now use all features of Documenso." msgstr "Your email has been successfully confirmed! You can now use all features of Documenso."
@ -4359,7 +4377,7 @@ msgstr "Your template will be duplicated."
msgid "Your templates has been saved successfully." msgid "Your templates has been saved successfully."
msgstr "Your templates has been saved successfully." msgstr "Your templates has been saved successfully."
#: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:83 #: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:89
msgid "Your token has expired!" msgid "Your token has expired!"
msgstr "Your token has expired!" msgstr "Your token has expired!"

View File

@ -0,0 +1,8 @@
-- AlterTable
ALTER TABLE "TeamEmailVerification" ADD COLUMN "completed" BOOLEAN NOT NULL DEFAULT false;
-- AlterTable
ALTER TABLE "TeamTransferVerification" ADD COLUMN "completed" BOOLEAN NOT NULL DEFAULT false;
-- AlterTable
ALTER TABLE "VerificationToken" ADD COLUMN "completed" BOOLEAN NOT NULL DEFAULT false;

View File

@ -149,6 +149,7 @@ model VerificationToken {
secondaryId String @unique @default(cuid()) secondaryId String @unique @default(cuid())
identifier String identifier String
token String @unique token String @unique
completed Boolean @default(false)
expires DateTime expires DateTime
createdAt DateTime @default(now()) createdAt DateTime @default(now())
userId Int userId Int
@ -546,6 +547,7 @@ model TeamEmailVerification {
name String name String
email String email String
token String @unique token String @unique
completed Boolean @default(false)
expiresAt DateTime expiresAt DateTime
createdAt DateTime @default(now()) createdAt DateTime @default(now())
@ -558,6 +560,7 @@ model TeamTransferVerification {
name String name String
email String email String
token String @unique token String @unique
completed Boolean @default(false)
expiresAt DateTime expiresAt DateTime
createdAt DateTime @default(now()) createdAt DateTime @default(now())
clearPaymentMethods Boolean @default(false) clearPaymentMethods Boolean @default(false)