mirror of
https://github.com/documenso/documenso.git
synced 2025-11-23 13:11:32 +10:00
feat: web i18n (#1286)
This commit is contained in:
@ -4,6 +4,8 @@ import { useEffect, useMemo, useState, useTransition } from 'react';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import { msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Loader } from 'lucide-react';
|
||||
|
||||
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
||||
@ -39,8 +41,10 @@ export const CheckboxField = ({
|
||||
onSignField,
|
||||
onUnsignField,
|
||||
}: CheckboxFieldProps) => {
|
||||
const router = useRouter();
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const router = useRouter();
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const { executeActionAuthProcedure } = useRequiredDocumentAuthContext();
|
||||
|
||||
@ -115,8 +119,8 @@ export const CheckboxField = ({
|
||||
console.error(err);
|
||||
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'An error occurred while signing the document.',
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while signing the document.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@ -144,8 +148,8 @@ export const CheckboxField = ({
|
||||
console.error(err);
|
||||
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'An error occurred while removing the signature.',
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while removing the signature.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@ -205,8 +209,8 @@ export const CheckboxField = ({
|
||||
console.error(err);
|
||||
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'An error occurred while updating the signature.',
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while updating the signature.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
} finally {
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
@ -49,8 +51,10 @@ export const ZClaimAccountFormSchema = z
|
||||
export type TClaimAccountFormSchema = z.infer<typeof ZClaimAccountFormSchema>;
|
||||
|
||||
export const ClaimAccount = ({ defaultName, defaultEmail }: ClaimAccountProps) => {
|
||||
const analytics = useAnalytics();
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const analytics = useAnalytics();
|
||||
const router = useRouter();
|
||||
|
||||
const { mutateAsync: signup } = trpc.auth.signup.useMutation();
|
||||
@ -71,9 +75,10 @@ export const ClaimAccount = ({ defaultName, defaultEmail }: ClaimAccountProps) =
|
||||
router.push(`/unverified-account`);
|
||||
|
||||
toast({
|
||||
title: 'Registration Successful',
|
||||
description:
|
||||
'You have successfully registered. Please verify your account by clicking on the link you received in the email.',
|
||||
title: _(msg`Registration Successful`),
|
||||
description: _(
|
||||
msg`You have successfully registered. Please verify your account by clicking on the link you received in the email.`,
|
||||
),
|
||||
duration: 5000,
|
||||
});
|
||||
|
||||
@ -84,15 +89,16 @@ export const ClaimAccount = ({ defaultName, defaultEmail }: ClaimAccountProps) =
|
||||
} catch (error) {
|
||||
if (error instanceof TRPCClientError && error.data?.code === 'BAD_REQUEST') {
|
||||
toast({
|
||||
title: 'An error occurred',
|
||||
title: _(msg`An error occurred`),
|
||||
description: error.message,
|
||||
variant: 'destructive',
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
title: 'An unknown error occurred',
|
||||
description:
|
||||
'We encountered an unknown error while attempting to sign you up. Please try again later.',
|
||||
title: _(msg`An unknown error occurred`),
|
||||
description: _(
|
||||
msg`We encountered an unknown error while attempting to sign you up. Please try again later.`,
|
||||
),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@ -109,9 +115,11 @@ export const ClaimAccount = ({ defaultName, defaultEmail }: ClaimAccountProps) =
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormLabel>
|
||||
<Trans>Name</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Enter your name" />
|
||||
<Input {...field} placeholder={_(msg`Enter your name`)} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@ -122,9 +130,11 @@ export const ClaimAccount = ({ defaultName, defaultEmail }: ClaimAccountProps) =
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="mt-4">
|
||||
<FormLabel>Email address</FormLabel>
|
||||
<FormLabel>
|
||||
<Trans>Email address</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Enter your email" />
|
||||
<Input {...field} placeholder={_(msg`Enter your email`)} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@ -135,9 +145,11 @@ export const ClaimAccount = ({ defaultName, defaultEmail }: ClaimAccountProps) =
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<FormItem className="mt-4">
|
||||
<FormLabel>Set a password</FormLabel>
|
||||
<FormLabel>
|
||||
<Trans>Set a password</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<PasswordInput {...field} placeholder="Pick a password" />
|
||||
<PasswordInput {...field} placeholder={_(msg`Pick a password`)} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@ -145,7 +157,7 @@ export const ClaimAccount = ({ defaultName, defaultEmail }: ClaimAccountProps) =
|
||||
/>
|
||||
|
||||
<Button type="submit" className="mt-6 w-full" loading={form.formState.isSubmitting}>
|
||||
Claim account
|
||||
<Trans>Claim account</Trans>
|
||||
</Button>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { FileSearch } from 'lucide-react';
|
||||
|
||||
import type { DocumentData } from '@documenso/prisma/client';
|
||||
@ -30,7 +31,7 @@ export const DocumentPreviewButton = ({
|
||||
{...props}
|
||||
>
|
||||
<FileSearch className="mr-2 h-5 w-5" strokeWidth={1.7} />
|
||||
View Original Document
|
||||
<Trans>View Original Document</Trans>
|
||||
</Button>
|
||||
|
||||
<DocumentDialog documentData={documentData} open={showDialog} onOpenChange={setShowDialog} />
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server';
|
||||
|
||||
import { RefreshOnFocus } from '~/components/(dashboard)/refresh-on-focus/refresh-on-focus';
|
||||
|
||||
export type SigningLayoutProps = {
|
||||
@ -7,6 +9,8 @@ export type SigningLayoutProps = {
|
||||
};
|
||||
|
||||
export default function SigningLayout({ children }: SigningLayoutProps) {
|
||||
setupI18nSSR();
|
||||
|
||||
return (
|
||||
<div>
|
||||
{children}
|
||||
|
||||
@ -1,12 +1,15 @@
|
||||
import Link from 'next/link';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { CheckCircle2, Clock8 } from 'lucide-react';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import { env } from 'next-runtime-env';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import signingCelebration from '@documenso/assets/images/signing-celebration.png';
|
||||
import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server';
|
||||
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
|
||||
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
|
||||
import { isRecipientAuthorized } from '@documenso/lib/server-only/document/is-recipient-authorized';
|
||||
@ -37,6 +40,10 @@ export type CompletedSigningPageProps = {
|
||||
export default async function CompletedSigningPage({
|
||||
params: { token },
|
||||
}: CompletedSigningPageProps) {
|
||||
setupI18nSSR();
|
||||
|
||||
const { _ } = useLingui();
|
||||
|
||||
const NEXT_PUBLIC_DISABLE_SIGNUP = env('NEXT_PUBLIC_DISABLE_SIGNUP');
|
||||
|
||||
if (!token) {
|
||||
@ -122,47 +129,58 @@ export default async function CompletedSigningPage({
|
||||
/>
|
||||
|
||||
<h2 className="mt-6 max-w-[35ch] text-center text-2xl font-semibold leading-normal md:text-3xl lg:text-4xl">
|
||||
Document
|
||||
{recipient.role === RecipientRole.SIGNER && ' Signed '}
|
||||
{recipient.role === RecipientRole.VIEWER && ' Viewed '}
|
||||
{recipient.role === RecipientRole.APPROVER && ' Approved '}
|
||||
{recipient.role === RecipientRole.SIGNER && <Trans>Document Signed</Trans>}
|
||||
{recipient.role === RecipientRole.VIEWER && <Trans>Document Viewed</Trans>}
|
||||
{recipient.role === RecipientRole.APPROVER && <Trans>Document Approved</Trans>}
|
||||
</h2>
|
||||
|
||||
{match({ status: document.status, deletedAt: document.deletedAt })
|
||||
.with({ status: DocumentStatus.COMPLETED }, () => (
|
||||
<div className="text-documenso-700 mt-4 flex items-center text-center">
|
||||
<CheckCircle2 className="mr-2 h-5 w-5" />
|
||||
<span className="text-sm">Everyone has signed</span>
|
||||
<span className="text-sm">
|
||||
<Trans>Everyone has signed</Trans>
|
||||
</span>
|
||||
</div>
|
||||
))
|
||||
.with({ deletedAt: null }, () => (
|
||||
<div className="mt-4 flex items-center text-center text-blue-600">
|
||||
<Clock8 className="mr-2 h-5 w-5" />
|
||||
<span className="text-sm">Waiting for others to sign</span>
|
||||
<span className="text-sm">
|
||||
<Trans>Waiting for others to sign</Trans>
|
||||
</span>
|
||||
</div>
|
||||
))
|
||||
.otherwise(() => (
|
||||
<div className="flex items-center text-center text-red-600">
|
||||
<Clock8 className="mr-2 h-5 w-5" />
|
||||
<span className="text-sm">Document no longer available to sign</span>
|
||||
<span className="text-sm">
|
||||
<Trans>Document no longer available to sign</Trans>
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{match({ status: document.status, deletedAt: document.deletedAt })
|
||||
.with({ status: DocumentStatus.COMPLETED }, () => (
|
||||
<p className="text-muted-foreground/60 mt-2.5 max-w-[60ch] text-center text-sm font-medium md:text-base">
|
||||
Everyone has signed! You will receive an Email copy of the signed document.
|
||||
<Trans>
|
||||
Everyone has signed! You will receive an Email copy of the signed document.
|
||||
</Trans>
|
||||
</p>
|
||||
))
|
||||
.with({ deletedAt: null }, () => (
|
||||
<p className="text-muted-foreground/60 mt-2.5 max-w-[60ch] text-center text-sm font-medium md:text-base">
|
||||
You will receive an Email copy of the signed document once everyone has signed.
|
||||
<Trans>
|
||||
You will receive an Email copy of the signed document once everyone has signed.
|
||||
</Trans>
|
||||
</p>
|
||||
))
|
||||
.otherwise(() => (
|
||||
<p className="text-muted-foreground/60 mt-2.5 max-w-[60ch] text-center text-sm font-medium md:text-base">
|
||||
This document has been cancelled by the owner and is no longer available for others
|
||||
to sign.
|
||||
<Trans>
|
||||
This document has been cancelled by the owner and is no longer available for
|
||||
others to sign.
|
||||
</Trans>
|
||||
</p>
|
||||
))}
|
||||
|
||||
@ -179,7 +197,7 @@ export default async function CompletedSigningPage({
|
||||
) : (
|
||||
<DocumentPreviewButton
|
||||
className="text-[11px]"
|
||||
title="Signatures will appear once the document has been completed"
|
||||
title={_(msg`Signatures will appear once the document has been completed`)}
|
||||
documentData={documentData}
|
||||
/>
|
||||
)}
|
||||
@ -189,11 +207,11 @@ export default async function CompletedSigningPage({
|
||||
{canSignUp && (
|
||||
<div className={`flex max-w-xl flex-col items-center justify-center p-4 md:p-12`}>
|
||||
<h2 className="mt-8 text-center text-xl font-semibold md:mt-0">
|
||||
Need to sign documents?
|
||||
<Trans>Need to sign documents?</Trans>
|
||||
</h2>
|
||||
|
||||
<p className="text-muted-foreground/60 mt-4 max-w-[55ch] text-center leading-normal">
|
||||
Create your account and start using state-of-the-art document signing.
|
||||
<Trans>Create your account and start using state-of-the-art document signing.</Trans>
|
||||
</p>
|
||||
|
||||
<ClaimAccount defaultName={recipientName} defaultEmail={recipient.email} />
|
||||
@ -202,7 +220,7 @@ export default async function CompletedSigningPage({
|
||||
|
||||
{isLoggedIn && (
|
||||
<Link href="/documents" className="text-documenso-700 hover:text-documenso-600 mt-36">
|
||||
Go Back Home
|
||||
<Trans>Go Back Home</Trans>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -4,6 +4,8 @@ import { useTransition } from 'react';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Loader } from 'lucide-react';
|
||||
|
||||
import {
|
||||
@ -44,6 +46,7 @@ export const DateField = ({
|
||||
}: DateFieldProps) => {
|
||||
const router = useRouter();
|
||||
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const [isPending, startTransition] = useTransition();
|
||||
@ -62,7 +65,9 @@ export const DateField = ({
|
||||
|
||||
const isDifferentTime = field.inserted && localDateString !== field.customText;
|
||||
|
||||
const tooltipText = `"${field.customText}" will appear on the document as it has a timezone of "${timezone}".`;
|
||||
const tooltipText = _(
|
||||
msg`"${field.customText}" will appear on the document as it has a timezone of "${timezone}".`,
|
||||
);
|
||||
|
||||
const onSign = async (authOptions?: TRecipientActionAuth) => {
|
||||
try {
|
||||
@ -91,8 +96,8 @@ export const DateField = ({
|
||||
console.error(err);
|
||||
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'An error occurred while signing the document.',
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while signing the document.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@ -117,8 +122,8 @@ export const DateField = ({
|
||||
console.error(err);
|
||||
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'An error occurred while removing the signature.',
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while removing the signature.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@ -140,7 +145,7 @@ export const DateField = ({
|
||||
|
||||
{!field.inserted && (
|
||||
<p className="group-hover:text-primary text-muted-foreground duration-200 group-hover:text-yellow-300">
|
||||
Date
|
||||
<Trans>Date</Trans>
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
@ -100,15 +101,20 @@ export const DocumentActionAuth2FA = ({
|
||||
<Alert variant="warning">
|
||||
<AlertDescription>
|
||||
<p>
|
||||
{recipient.role === RecipientRole.VIEWER && actionTarget === 'DOCUMENT'
|
||||
? 'You need to setup 2FA to mark this document as viewed.'
|
||||
: `You need to setup 2FA to ${actionVerb.toLowerCase()} this ${actionTarget.toLowerCase()}.`}
|
||||
{recipient.role === RecipientRole.VIEWER && actionTarget === 'DOCUMENT' ? (
|
||||
<Trans>You need to setup 2FA to mark this document as viewed.</Trans>
|
||||
) : (
|
||||
// Todo: Translate
|
||||
`You need to setup 2FA to ${actionVerb.toLowerCase()} this ${actionTarget.toLowerCase()}.`
|
||||
)}
|
||||
</p>
|
||||
|
||||
{user?.identityProvider === 'DOCUMENSO' && (
|
||||
<p className="mt-2">
|
||||
By enabling 2FA, you will be required to enter a code from your authenticator app
|
||||
every time you sign in.
|
||||
<Trans>
|
||||
By enabling 2FA, you will be required to enter a code from your authenticator app
|
||||
every time you sign in.
|
||||
</Trans>
|
||||
</p>
|
||||
)}
|
||||
</AlertDescription>
|
||||
@ -116,7 +122,7 @@ export const DocumentActionAuth2FA = ({
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="secondary" onClick={() => onOpenChange(false)}>
|
||||
Close
|
||||
<Trans>Close</Trans>
|
||||
</Button>
|
||||
|
||||
<EnableAuthenticatorAppDialog onSuccess={() => setIs2FASetupSuccessful(true)} />
|
||||
@ -156,20 +162,24 @@ export const DocumentActionAuth2FA = ({
|
||||
|
||||
{formErrorCode && (
|
||||
<Alert variant="destructive">
|
||||
<AlertTitle>Unauthorized</AlertTitle>
|
||||
<AlertTitle>
|
||||
<Trans>Unauthorized</Trans>
|
||||
</AlertTitle>
|
||||
<AlertDescription>
|
||||
We were unable to verify your details. Please try again or contact support
|
||||
<Trans>
|
||||
We were unable to verify your details. Please try again or contact support
|
||||
</Trans>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="secondary" onClick={() => onOpenChange(false)}>
|
||||
Cancel
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<Button type="submit" loading={isCurrentlyAuthenticating}>
|
||||
Sign
|
||||
<Trans>Sign</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</div>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { DateTime } from 'luxon';
|
||||
import { signOut } from 'next-auth/react';
|
||||
|
||||
@ -53,11 +54,14 @@ export const DocumentActionAuthAccount = ({
|
||||
<AlertDescription>
|
||||
{actionTarget === 'DOCUMENT' && recipient.role === RecipientRole.VIEWER ? (
|
||||
<span>
|
||||
To mark this document as viewed, you need to be logged in as{' '}
|
||||
<strong>{recipient.email}</strong>
|
||||
<Trans>
|
||||
To mark this document as viewed, you need to be logged in as{' '}
|
||||
<strong>{recipient.email}</strong>
|
||||
</Trans>
|
||||
</span>
|
||||
) : (
|
||||
<span>
|
||||
{/* Todo: Translate */}
|
||||
To {actionVerb.toLowerCase()} this {actionTarget.toLowerCase()}, you need to be logged
|
||||
in as <strong>{recipient.email}</strong>
|
||||
</span>
|
||||
@ -67,11 +71,11 @@ export const DocumentActionAuthAccount = ({
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="secondary" onClick={() => onOpenChange(false)}>
|
||||
Cancel
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<Button onClick={async () => handleChangeAccount(recipient.email)} loading={isSigningOut}>
|
||||
Login
|
||||
<Trans>Login</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</fieldset>
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { P, match } from 'ts-pattern';
|
||||
|
||||
import {
|
||||
@ -55,10 +56,10 @@ export const DocumentActionAuthDialog = ({
|
||||
<Dialog open={open} onOpenChange={handleOnOpenChange}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{title || 'Sign field'}</DialogTitle>
|
||||
<DialogTitle>{title || <Trans>Sign field</Trans>}</DialogTitle>
|
||||
|
||||
<DialogDescription>
|
||||
{description || 'Reauthentication is required to sign this field'}
|
||||
{description || <Trans>Reauthentication is required to sign this field</Trans>}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { browserSupportsWebAuthn, startAuthentication } from '@simplewebauthn/browser';
|
||||
import { Loader } from 'lucide-react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
@ -54,6 +56,8 @@ export const DocumentActionAuthPasskey = ({
|
||||
open,
|
||||
onOpenChange,
|
||||
}: DocumentActionAuthPasskeyProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const {
|
||||
recipient,
|
||||
passkeyData,
|
||||
@ -123,6 +127,7 @@ export const DocumentActionAuthPasskey = ({
|
||||
<div className="space-y-4">
|
||||
<Alert variant="warning">
|
||||
<AlertDescription>
|
||||
{/* Todo: Translate */}
|
||||
Your browser does not support passkeys, which is required to {actionVerb.toLowerCase()}{' '}
|
||||
this {actionTarget.toLowerCase()}.
|
||||
</AlertDescription>
|
||||
@ -130,7 +135,7 @@ export const DocumentActionAuthPasskey = ({
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="secondary" onClick={() => onOpenChange(false)}>
|
||||
Close
|
||||
<Trans>Close</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</div>
|
||||
@ -149,16 +154,18 @@ export const DocumentActionAuthPasskey = ({
|
||||
return (
|
||||
<div className="h-28 space-y-4">
|
||||
<Alert variant="destructive">
|
||||
<AlertDescription>Something went wrong while loading your passkeys.</AlertDescription>
|
||||
<AlertDescription>
|
||||
<Trans>Something went wrong while loading your passkeys.</Trans>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="secondary" onClick={() => onOpenChange(false)}>
|
||||
Cancel
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<Button type="button" onClick={() => void refetchPasskeys()}>
|
||||
Retry
|
||||
<Trans>Retry</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</div>
|
||||
@ -170,6 +177,7 @@ export const DocumentActionAuthPasskey = ({
|
||||
<div className="space-y-4">
|
||||
<Alert variant="warning">
|
||||
<AlertDescription>
|
||||
{/* Todo: Translate */}
|
||||
{recipient.role === RecipientRole.VIEWER && actionTarget === 'DOCUMENT'
|
||||
? 'You need to setup a passkey to mark this document as viewed.'
|
||||
: `You need to setup a passkey to ${actionVerb.toLowerCase()} this ${actionTarget.toLowerCase()}.`}
|
||||
@ -178,12 +186,16 @@ export const DocumentActionAuthPasskey = ({
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="secondary" onClick={() => onOpenChange(false)}>
|
||||
Cancel
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<CreatePasskeyDialog
|
||||
onSuccess={async () => refetchPasskeys()}
|
||||
trigger={<Button>Setup</Button>}
|
||||
trigger={
|
||||
<Button>
|
||||
<Trans>Setup</Trans>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</DialogFooter>
|
||||
</div>
|
||||
@ -207,7 +219,7 @@ export const DocumentActionAuthPasskey = ({
|
||||
<SelectTrigger className="bg-background text-muted-foreground">
|
||||
<SelectValue
|
||||
data-testid="documentAccessSelectValue"
|
||||
placeholder="Select passkey"
|
||||
placeholder={_(msg`Select passkey`)}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
|
||||
|
||||
@ -4,6 +4,8 @@ import { useEffect, useState, useTransition } from 'react';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import { msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Loader } from 'lucide-react';
|
||||
|
||||
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
||||
@ -43,8 +45,10 @@ export const DropdownField = ({
|
||||
onSignField,
|
||||
onUnsignField,
|
||||
}: DropdownFieldProps) => {
|
||||
const router = useRouter();
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const router = useRouter();
|
||||
const [isPending, startTransition] = useTransition();
|
||||
|
||||
const { executeActionAuthProcedure } = useRequiredDocumentAuthContext();
|
||||
@ -98,8 +102,8 @@ export const DropdownField = ({
|
||||
console.error(err);
|
||||
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'An error occurred while signing the document.',
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while signing the document.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@ -129,8 +133,8 @@ export const DropdownField = ({
|
||||
console.error(err);
|
||||
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'An error occurred while removing the signature.',
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while removing the signature.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@ -185,7 +189,7 @@ export const DropdownField = ({
|
||||
},
|
||||
)}
|
||||
>
|
||||
<SelectValue placeholder={'-- Select --'} />
|
||||
<SelectValue placeholder={`-- ${_(msg`Select`)} --`} />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="w-full ring-0 focus:ring-0" position="popper">
|
||||
{parsedFieldMeta?.values?.map((item, index) => (
|
||||
|
||||
@ -4,6 +4,8 @@ import { useTransition } from 'react';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Loader } from 'lucide-react';
|
||||
|
||||
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
||||
@ -31,6 +33,7 @@ export type EmailFieldProps = {
|
||||
export const EmailField = ({ field, recipient, onSignField, onUnsignField }: EmailFieldProps) => {
|
||||
const router = useRouter();
|
||||
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const { email: providedEmail } = useRequiredSigningContext();
|
||||
@ -77,8 +80,8 @@ export const EmailField = ({ field, recipient, onSignField, onUnsignField }: Ema
|
||||
console.error(err);
|
||||
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'An error occurred while signing the document.',
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while signing the document.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@ -103,8 +106,8 @@ export const EmailField = ({ field, recipient, onSignField, onUnsignField }: Ema
|
||||
console.error(err);
|
||||
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'An error occurred while removing the signature.',
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while removing the signature.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@ -120,7 +123,7 @@ export const EmailField = ({ field, recipient, onSignField, onUnsignField }: Ema
|
||||
|
||||
{!field.inserted && (
|
||||
<p className="group-hover:text-primary text-muted-foreground duration-200 group-hover:text-yellow-300">
|
||||
Email
|
||||
<Trans>Email</Trans>
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import { useMemo, useState } from 'react';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
@ -103,7 +104,7 @@ export const SigningForm = ({ document, recipient, fields, redirectUrl }: Signin
|
||||
>
|
||||
{validateUninsertedFields && uninsertedFields[0] && (
|
||||
<FieldToolTip key={uninsertedFields[0].id} field={uninsertedFields[0]} color="warning">
|
||||
Click to insert field
|
||||
<Trans>Click to insert field</Trans>
|
||||
</FieldToolTip>
|
||||
)}
|
||||
|
||||
@ -123,7 +124,7 @@ export const SigningForm = ({ document, recipient, fields, redirectUrl }: Signin
|
||||
{recipient.role === RecipientRole.VIEWER ? (
|
||||
<>
|
||||
<p className="text-muted-foreground mt-2 text-sm">
|
||||
Please mark as viewed to complete
|
||||
<Trans>Please mark as viewed to complete</Trans>
|
||||
</p>
|
||||
|
||||
<hr className="border-border mb-8 mt-4" />
|
||||
@ -139,7 +140,7 @@ export const SigningForm = ({ document, recipient, fields, redirectUrl }: Signin
|
||||
disabled={typeof window !== 'undefined' && window.history.length <= 1}
|
||||
onClick={() => router.back()}
|
||||
>
|
||||
Cancel
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<SignDialog
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server';
|
||||
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
|
||||
import type { GetTeamsResponse } from '@documenso/lib/server-only/team/get-teams';
|
||||
import { getTeams } from '@documenso/lib/server-only/team/get-teams';
|
||||
@ -12,6 +13,8 @@ export type SigningLayoutProps = {
|
||||
};
|
||||
|
||||
export default async function SigningLayout({ children }: SigningLayoutProps) {
|
||||
setupI18nSSR();
|
||||
|
||||
const { user, session } = await getServerComponentSession();
|
||||
|
||||
let teams: GetTeamsResponse = [];
|
||||
|
||||
@ -4,6 +4,8 @@ import { useState, useTransition } from 'react';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Loader } from 'lucide-react';
|
||||
|
||||
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
||||
@ -36,6 +38,7 @@ export type NameFieldProps = {
|
||||
export const NameField = ({ field, recipient, onSignField, onUnsignField }: NameFieldProps) => {
|
||||
const router = useRouter();
|
||||
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const { fullName: providedFullName, setFullName: setProvidedFullName } =
|
||||
@ -115,8 +118,8 @@ export const NameField = ({ field, recipient, onSignField, onUnsignField }: Name
|
||||
console.error(err);
|
||||
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'An error occurred while signing the document.',
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while signing the document.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@ -141,8 +144,8 @@ export const NameField = ({ field, recipient, onSignField, onUnsignField }: Name
|
||||
console.error(err);
|
||||
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'An error occurred while removing the signature.',
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while removing the signature.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@ -164,7 +167,7 @@ export const NameField = ({ field, recipient, onSignField, onUnsignField }: Name
|
||||
|
||||
{!field.inserted && (
|
||||
<p className="group-hover:text-primary text-muted-foreground duration-200 group-hover:text-yellow-300">
|
||||
Name
|
||||
<Trans>Name</Trans>
|
||||
</p>
|
||||
)}
|
||||
|
||||
@ -177,12 +180,18 @@ export const NameField = ({ field, recipient, onSignField, onUnsignField }: Name
|
||||
<Dialog open={showFullNameModal} onOpenChange={setShowFullNameModal}>
|
||||
<DialogContent>
|
||||
<DialogTitle>
|
||||
Sign as {recipient.name}{' '}
|
||||
<div className="text-muted-foreground">({recipient.email})</div>
|
||||
<Trans>
|
||||
Sign as
|
||||
<div>
|
||||
{recipient.name} <div className="text-muted-foreground">({recipient.email})</div>
|
||||
</div>
|
||||
</Trans>
|
||||
</DialogTitle>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="signature">Full Name</Label>
|
||||
<Label htmlFor="signature">
|
||||
<Trans>Full Name</Trans>
|
||||
</Label>
|
||||
|
||||
<Input
|
||||
type="text"
|
||||
@ -203,7 +212,7 @@ export const NameField = ({ field, recipient, onSignField, onUnsignField }: Name
|
||||
setLocalFullName('');
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@ -212,7 +221,7 @@ export const NameField = ({ field, recipient, onSignField, onUnsignField }: Name
|
||||
disabled={!localFullName}
|
||||
onClick={() => onDialogSignClick()}
|
||||
>
|
||||
Sign
|
||||
<Trans>Sign</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
</DialogFooter>
|
||||
|
||||
@ -4,6 +4,7 @@ import React from 'react';
|
||||
|
||||
import Link from 'next/link';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { Clock8 } from 'lucide-react';
|
||||
import { useSession } from 'next-auth/react';
|
||||
|
||||
@ -35,31 +36,37 @@ export const NoLongerAvailable = ({
|
||||
<div className="relative mt-2 flex w-full flex-col items-center">
|
||||
<div className="mt-8 flex items-center text-center text-red-600">
|
||||
<Clock8 className="mr-2 h-5 w-5" />
|
||||
<span className="text-sm">Document Cancelled</span>
|
||||
<span className="text-sm">
|
||||
<Trans>Document Cancelled</Trans>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h2 className="mt-6 max-w-[35ch] text-center text-2xl font-semibold leading-normal md:text-3xl lg:text-4xl">
|
||||
<span className="mt-1.5 block">"{document.title}"</span>
|
||||
is no longer available to sign
|
||||
<Trans>
|
||||
<span className="mt-1.5 block">"{document.title}"</span>
|
||||
is no longer available to sign
|
||||
</Trans>
|
||||
</h2>
|
||||
|
||||
<p className="text-muted-foreground/60 mt-2.5 max-w-[60ch] text-center text-sm font-medium md:text-base">
|
||||
This document has been cancelled by the owner.
|
||||
<Trans>This document has been cancelled by the owner.</Trans>
|
||||
</p>
|
||||
|
||||
{session?.user ? (
|
||||
<Link href="/documents" className="text-documenso-700 hover:text-documenso-600 mt-36">
|
||||
Go Back Home
|
||||
<Trans>Go Back Home</Trans>
|
||||
</Link>
|
||||
) : (
|
||||
<p className="text-muted-foreground/60 mt-36 text-sm">
|
||||
Want to send slick signing links like this one?{' '}
|
||||
<Link
|
||||
href="https://documenso.com"
|
||||
className="text-documenso-700 hover:text-documenso-600"
|
||||
>
|
||||
Check out Documenso.
|
||||
</Link>
|
||||
<Trans>
|
||||
Want to send slick signing links like this one?{' '}
|
||||
<Link
|
||||
href="https://documenso.com"
|
||||
className="text-documenso-700 hover:text-documenso-600"
|
||||
>
|
||||
Check out Documenso.
|
||||
</Link>
|
||||
</Trans>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -4,6 +4,8 @@ import { useEffect, useState, useTransition } from 'react';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Hash, Loader } from 'lucide-react';
|
||||
|
||||
import { validateNumberField } from '@documenso/lib/advanced-fields-validation/validate-number';
|
||||
@ -43,8 +45,10 @@ export type NumberFieldProps = {
|
||||
};
|
||||
|
||||
export const NumberField = ({ field, recipient, onSignField, onUnsignField }: NumberFieldProps) => {
|
||||
const router = useRouter();
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const router = useRouter();
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const [showRadioModal, setShowRadioModal] = useState(false);
|
||||
|
||||
@ -142,8 +146,8 @@ export const NumberField = ({ field, recipient, onSignField, onUnsignField }: Nu
|
||||
console.error(err);
|
||||
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'An error occurred while signing the document.',
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while signing the document.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@ -187,8 +191,8 @@ export const NumberField = ({ field, recipient, onSignField, onUnsignField }: Nu
|
||||
console.error(err);
|
||||
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'An error occurred while removing the signature.',
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while removing the signature.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@ -263,7 +267,7 @@ export const NumberField = ({ field, recipient, onSignField, onUnsignField }: Nu
|
||||
<Dialog open={showRadioModal} onOpenChange={setShowRadioModal}>
|
||||
<DialogContent>
|
||||
<DialogTitle>
|
||||
{parsedFieldMeta?.label ? parsedFieldMeta?.label : 'Add number'}
|
||||
{parsedFieldMeta?.label ? parsedFieldMeta?.label : <Trans>Add number</Trans>}
|
||||
</DialogTitle>
|
||||
|
||||
<div>
|
||||
@ -320,7 +324,7 @@ export const NumberField = ({ field, recipient, onSignField, onUnsignField }: Nu
|
||||
setLocalNumber('');
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@ -329,7 +333,7 @@ export const NumberField = ({ field, recipient, onSignField, onUnsignField }: Nu
|
||||
disabled={!localNumber || userInputHasErrors}
|
||||
onClick={() => onDialogSignClick()}
|
||||
>
|
||||
Save
|
||||
<Trans>Save</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
</DialogFooter>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { headers } from 'next/headers';
|
||||
import { notFound, redirect } from 'next/navigation';
|
||||
|
||||
import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server';
|
||||
import { DOCUMENSO_ENCRYPTION_KEY } from '@documenso/lib/constants/crypto';
|
||||
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
|
||||
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
|
||||
@ -29,6 +30,8 @@ export type SigningPageProps = {
|
||||
};
|
||||
|
||||
export default async function SigningPage({ params: { token } }: SigningPageProps) {
|
||||
setupI18nSSR();
|
||||
|
||||
if (!token) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
@ -4,6 +4,8 @@ import { useEffect, useState, useTransition } from 'react';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import { msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Loader } from 'lucide-react';
|
||||
|
||||
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
||||
@ -32,8 +34,10 @@ export type RadioFieldProps = {
|
||||
};
|
||||
|
||||
export const RadioField = ({ field, recipient, onSignField, onUnsignField }: RadioFieldProps) => {
|
||||
const router = useRouter();
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const router = useRouter();
|
||||
const [isPending, startTransition] = useTransition();
|
||||
|
||||
const parsedFieldMeta = ZRadioFieldMeta.parse(field.fieldMeta);
|
||||
@ -94,8 +98,8 @@ export const RadioField = ({ field, recipient, onSignField, onUnsignField }: Rad
|
||||
console.error(err);
|
||||
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'An error occurred while signing the document.',
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while signing the document.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@ -121,8 +125,8 @@ export const RadioField = ({ field, recipient, onSignField, onUnsignField }: Rad
|
||||
console.error(err);
|
||||
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'An error occurred while removing the signature.',
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while removing the signature.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
|
||||
import type { Field } from '@documenso/prisma/client';
|
||||
import { RecipientRole } from '@documenso/prisma/client';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
@ -53,36 +55,42 @@ export const SignDialog = ({
|
||||
onClick={fieldsValidated}
|
||||
loading={isSubmitting}
|
||||
>
|
||||
{isComplete ? 'Complete' : 'Next field'}
|
||||
{isComplete ? <Trans>Complete</Trans> : <Trans>Next field</Trans>}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent>
|
||||
<DialogTitle>
|
||||
<div className="text-foreground text-xl font-semibold">
|
||||
{role === RecipientRole.VIEWER && 'Complete Viewing'}
|
||||
{role === RecipientRole.SIGNER && 'Complete Signing'}
|
||||
{role === RecipientRole.APPROVER && 'Complete Approval'}
|
||||
{role === RecipientRole.VIEWER && <Trans>Complete Viewing</Trans>}
|
||||
{role === RecipientRole.SIGNER && <Trans>Complete Signing</Trans>}
|
||||
{role === RecipientRole.APPROVER && <Trans>Complete Approval</Trans>}
|
||||
</div>
|
||||
</DialogTitle>
|
||||
|
||||
<div className="text-muted-foreground max-w-[50ch]">
|
||||
{role === RecipientRole.VIEWER && (
|
||||
<span>
|
||||
You are about to complete viewing "{truncatedTitle}".
|
||||
<br /> Are you sure?
|
||||
<Trans>
|
||||
You are about to complete viewing "{truncatedTitle}".
|
||||
<br /> Are you sure?
|
||||
</Trans>
|
||||
</span>
|
||||
)}
|
||||
{role === RecipientRole.SIGNER && (
|
||||
<span>
|
||||
You are about to complete signing "{truncatedTitle}".
|
||||
<br /> Are you sure?
|
||||
<Trans>
|
||||
You are about to complete signing "{truncatedTitle}".
|
||||
<br /> Are you sure?
|
||||
</Trans>
|
||||
</span>
|
||||
)}
|
||||
{role === RecipientRole.APPROVER && (
|
||||
<span>
|
||||
You are about to complete approving "{truncatedTitle}".
|
||||
<br /> Are you sure?
|
||||
<Trans>
|
||||
You are about to complete approving "{truncatedTitle}".
|
||||
<br /> Are you sure?
|
||||
</Trans>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@ -93,13 +101,13 @@ export const SignDialog = ({
|
||||
<div className="flex w-full flex-1 flex-nowrap gap-4">
|
||||
<Button
|
||||
type="button"
|
||||
className="dark:bg-muted dark:hover:bg-muted/80 flex-1 bg-black/5 hover:bg-black/10"
|
||||
className="dark:bg-muted dark:hover:bg-muted/80 flex-1 bg-black/5 hover:bg-black/10"
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
setShowDialog(false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@ -109,9 +117,9 @@ export const SignDialog = ({
|
||||
loading={isSubmitting}
|
||||
onClick={onSignatureComplete}
|
||||
>
|
||||
{role === RecipientRole.VIEWER && 'Mark as Viewed'}
|
||||
{role === RecipientRole.SIGNER && 'Sign'}
|
||||
{role === RecipientRole.APPROVER && 'Approve'}
|
||||
{role === RecipientRole.VIEWER && <Trans>Mark as Viewed</Trans>}
|
||||
{role === RecipientRole.SIGNER && <Trans>Sign</Trans>}
|
||||
{role === RecipientRole.APPROVER && <Trans>Approve</Trans>}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogFooter>
|
||||
|
||||
@ -4,6 +4,8 @@ import { useMemo, useState, useTransition } from 'react';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Loader } from 'lucide-react';
|
||||
|
||||
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
||||
@ -45,6 +47,7 @@ export const SignatureField = ({
|
||||
}: SignatureFieldProps) => {
|
||||
const router = useRouter();
|
||||
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const { signature: providedSignature, setSignature: setProvidedSignature } =
|
||||
@ -142,8 +145,8 @@ export const SignatureField = ({
|
||||
console.error(err);
|
||||
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'An error occurred while signing the document.',
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while signing the document.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@ -168,8 +171,8 @@ export const SignatureField = ({
|
||||
console.error(err);
|
||||
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'An error occurred while removing the signature.',
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while removing the signature.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@ -191,7 +194,7 @@ export const SignatureField = ({
|
||||
|
||||
{state === 'empty' && (
|
||||
<p className="group-hover:text-primary font-signature text-muted-foreground text-xl duration-200 group-hover:text-yellow-300">
|
||||
Signature
|
||||
<Trans>Signature</Trans>
|
||||
</p>
|
||||
)}
|
||||
|
||||
@ -213,12 +216,16 @@ export const SignatureField = ({
|
||||
<Dialog open={showSignatureModal} onOpenChange={setShowSignatureModal}>
|
||||
<DialogContent>
|
||||
<DialogTitle>
|
||||
Sign as {recipient.name}{' '}
|
||||
<div className="text-muted-foreground h-5">({recipient.email})</div>
|
||||
<Trans>
|
||||
Sign as {recipient.name}{' '}
|
||||
<div className="text-muted-foreground h-5">({recipient.email})</div>
|
||||
</Trans>
|
||||
</DialogTitle>
|
||||
|
||||
<div className="">
|
||||
<Label htmlFor="signature">Signature</Label>
|
||||
<Label htmlFor="signature">
|
||||
<Trans>Signature</Trans>
|
||||
</Label>
|
||||
|
||||
<SignaturePad
|
||||
id="signature"
|
||||
@ -233,14 +240,14 @@ export const SignatureField = ({
|
||||
<div className="flex w-full flex-1 flex-nowrap gap-4">
|
||||
<Button
|
||||
type="button"
|
||||
className="dark:bg-muted dark:hover:bg-muted/80 flex-1 bg-black/5 hover:bg-black/10"
|
||||
className="dark:bg-muted dark:hover:bg-muted/80 flex-1 bg-black/5 hover:bg-black/10"
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
setShowSignatureModal(false);
|
||||
setLocalSignature(null);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@ -249,7 +256,7 @@ export const SignatureField = ({
|
||||
disabled={!localSignature}
|
||||
onClick={() => onDialogSignClick()}
|
||||
>
|
||||
Sign
|
||||
<Trans>Sign</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
</DialogFooter>
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { DateTime } from 'luxon';
|
||||
import { signOut } from 'next-auth/react';
|
||||
|
||||
@ -15,6 +17,7 @@ export type SigningAuthPageViewProps = {
|
||||
};
|
||||
|
||||
export const SigningAuthPageView = ({ email, emailHasAccount }: SigningAuthPageViewProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const [isSigningOut, setIsSigningOut] = useState(false);
|
||||
@ -37,8 +40,8 @@ export const SigningAuthPageView = ({ email, emailHasAccount }: SigningAuthPageV
|
||||
});
|
||||
} catch {
|
||||
toast({
|
||||
title: 'Something went wrong',
|
||||
description: 'We were unable to log you out at this time.',
|
||||
title: _(msg`Something went wrong`),
|
||||
description: _(msg`We were unable to log you out at this time.`),
|
||||
duration: 10000,
|
||||
variant: 'destructive',
|
||||
});
|
||||
@ -50,10 +53,14 @@ export const SigningAuthPageView = ({ email, emailHasAccount }: SigningAuthPageV
|
||||
return (
|
||||
<div className="mx-auto flex h-[70vh] w-full max-w-md flex-col items-center justify-center">
|
||||
<div>
|
||||
<h1 className="text-3xl font-semibold">Authentication required</h1>
|
||||
<h1 className="text-3xl font-semibold">
|
||||
<Trans>Authentication required</Trans>
|
||||
</h1>
|
||||
|
||||
<p className="text-muted-foreground mt-2 text-sm">
|
||||
You need to be logged in as <strong>{email}</strong> to view this page.
|
||||
<Trans>
|
||||
You need to be logged in as <strong>{email}</strong> to view this page.
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<Button
|
||||
@ -62,7 +69,7 @@ export const SigningAuthPageView = ({ email, emailHasAccount }: SigningAuthPageV
|
||||
onClick={async () => handleChangeAccount(email)}
|
||||
loading={isSigningOut}
|
||||
>
|
||||
{emailHasAccount ? 'Login' : 'Sign up'}
|
||||
{emailHasAccount ? <Trans>Login</Trans> : <Trans>Sign up</Trans>}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { X } from 'lucide-react';
|
||||
|
||||
import { type TRecipientActionAuth } from '@documenso/lib/types/document-auth';
|
||||
@ -140,7 +141,7 @@ export const SigningFieldContainer = ({
|
||||
{readOnlyField && (
|
||||
<button className="bg-background/40 absolute inset-0 z-10 flex h-full w-full items-center justify-center rounded-md text-sm opacity-0 duration-200 group-hover:opacity-100">
|
||||
<span className="bg-foreground/50 dark:bg-background/50 text-background dark:text-foreground rounded-xl p-2">
|
||||
Read only field
|
||||
<Trans>Read only field</Trans>
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
@ -152,7 +153,7 @@ export const SigningFieldContainer = ({
|
||||
className="text-destructive bg-background/40 absolute inset-0 z-10 flex h-full w-full items-center justify-center rounded-md text-sm opacity-0 duration-200 group-hover:opacity-100"
|
||||
onClick={onRemoveSignedFieldClick}
|
||||
>
|
||||
Remove
|
||||
<Trans>Remove</Trans>
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
|
||||
@ -176,7 +177,7 @@ export const SigningFieldContainer = ({
|
||||
className="text-destructive bg-background/50 absolute inset-0 z-10 flex h-full w-full items-center justify-center rounded-md text-sm opacity-0 duration-200 group-hover:opacity-100"
|
||||
onClick={onRemoveSignedFieldClick}
|
||||
>
|
||||
Remove
|
||||
<Trans>Remove</Trans>
|
||||
</button>
|
||||
)}
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
||||
@ -63,10 +64,17 @@ export const SigningPageView = ({
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-muted-foreground">
|
||||
({document.User.email}) has invited you to{' '}
|
||||
{recipient.role === RecipientRole.VIEWER && 'view'}
|
||||
{recipient.role === RecipientRole.SIGNER && 'sign'}
|
||||
{recipient.role === RecipientRole.APPROVER && 'approve'} this document.
|
||||
{match(recipient.role)
|
||||
.with(RecipientRole.VIEWER, () => (
|
||||
<Trans>({document.User.email}) has invited you to view this document</Trans>
|
||||
))
|
||||
.with(RecipientRole.SIGNER, () => (
|
||||
<Trans>({document.User.email}) has invited you to sign this document</Trans>
|
||||
))
|
||||
.with(RecipientRole.APPROVER, () => (
|
||||
<Trans>({document.User.email}) has invited you to approve this document</Trans>
|
||||
))
|
||||
.otherwise(() => null)}
|
||||
</p>
|
||||
|
||||
<div className="mt-8 grid grid-cols-12 gap-y-8 lg:gap-x-8 lg:gap-y-0">
|
||||
|
||||
@ -4,6 +4,8 @@ import { useEffect, useState, useTransition } from 'react';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import { Plural, Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Loader, Type } from 'lucide-react';
|
||||
|
||||
import { validateTextField } from '@documenso/lib/advanced-fields-validation/validate-text';
|
||||
@ -35,9 +37,11 @@ export type TextFieldProps = {
|
||||
};
|
||||
|
||||
export const TextField = ({ field, recipient, onSignField, onUnsignField }: TextFieldProps) => {
|
||||
const router = useRouter();
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const initialErrors: Record<string, string[]> = {
|
||||
required: [],
|
||||
characterLimit: [],
|
||||
@ -160,8 +164,8 @@ export const TextField = ({ field, recipient, onSignField, onUnsignField }: Text
|
||||
console.error(err);
|
||||
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'An error occurred while signing the document.',
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while signing the document.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@ -188,8 +192,8 @@ export const TextField = ({ field, recipient, onSignField, onUnsignField }: Text
|
||||
console.error(err);
|
||||
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'An error occurred while removing the text.',
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while removing the text.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@ -220,7 +224,7 @@ export const TextField = ({ field, recipient, onSignField, onUnsignField }: Text
|
||||
? parsedField?.text.substring(0, 20) + '...'
|
||||
: undefined;
|
||||
|
||||
const fieldDisplayName = labelDisplay ? labelDisplay : textDisplay ? textDisplay : 'Add text';
|
||||
const fieldDisplayName = labelDisplay ? labelDisplay : textDisplay;
|
||||
const charactersRemaining = (parsedFieldMeta?.characterLimit ?? 0) - (localText.length ?? 0);
|
||||
|
||||
return (
|
||||
@ -249,7 +253,7 @@ export const TextField = ({ field, recipient, onSignField, onUnsignField }: Text
|
||||
>
|
||||
<span className="flex items-center justify-center gap-x-1">
|
||||
<Type />
|
||||
{fieldDisplayName}
|
||||
{fieldDisplayName || <Trans>Add text</Trans>}
|
||||
</span>
|
||||
</p>
|
||||
)}
|
||||
@ -264,12 +268,14 @@ export const TextField = ({ field, recipient, onSignField, onUnsignField }: Text
|
||||
|
||||
<Dialog open={showCustomTextModal} onOpenChange={setShowCustomTextModal}>
|
||||
<DialogContent>
|
||||
<DialogTitle>{parsedFieldMeta?.label ? parsedFieldMeta?.label : 'Add Text'}</DialogTitle>
|
||||
<DialogTitle>
|
||||
{parsedFieldMeta?.label ? parsedFieldMeta?.label : <Trans>Add Text</Trans>}
|
||||
</DialogTitle>
|
||||
|
||||
<div>
|
||||
<Textarea
|
||||
id="custom-text"
|
||||
placeholder={parsedFieldMeta?.placeholder ?? 'Enter your text here'}
|
||||
placeholder={parsedFieldMeta?.placeholder ?? _(msg`Enter your text here`)}
|
||||
className={cn('mt-2 w-full rounded-md', {
|
||||
'border-2 border-red-300 ring-2 ring-red-200 ring-offset-2 ring-offset-red-200 focus-visible:border-red-400 focus-visible:ring-4 focus-visible:ring-red-200 focus-visible:ring-offset-2 focus-visible:ring-offset-red-200':
|
||||
userInputHasErrors,
|
||||
@ -283,7 +289,11 @@ export const TextField = ({ field, recipient, onSignField, onUnsignField }: Text
|
||||
parsedFieldMeta?.characterLimit > 0 &&
|
||||
!userInputHasErrors && (
|
||||
<div className="text-muted-foreground text-sm">
|
||||
{charactersRemaining} characters remaining
|
||||
<Plural
|
||||
value={charactersRemaining}
|
||||
one="1 character remaining"
|
||||
other={`${charactersRemaining} characters remaining`}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -297,7 +307,13 @@ export const TextField = ({ field, recipient, onSignField, onUnsignField }: Text
|
||||
{errors.characterLimit.map((error, index) => (
|
||||
<p key={index} className="text-red-500">
|
||||
{error}{' '}
|
||||
{charactersRemaining < 0 && `(${Math.abs(charactersRemaining)} characters over)`}
|
||||
{charactersRemaining < 0 && (
|
||||
<Plural
|
||||
value={Math.abs(charactersRemaining)}
|
||||
one="(1 character over)"
|
||||
other="(# characters over)"
|
||||
/>
|
||||
)}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
@ -314,7 +330,7 @@ export const TextField = ({ field, recipient, onSignField, onUnsignField }: Text
|
||||
setLocalCustomText('');
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@ -323,7 +339,7 @@ export const TextField = ({ field, recipient, onSignField, onUnsignField }: Text
|
||||
disabled={!localText || userInputHasErrors}
|
||||
onClick={() => onDialogSignClick()}
|
||||
>
|
||||
Save
|
||||
<Trans>Save</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
</DialogFooter>
|
||||
|
||||
Reference in New Issue
Block a user