Compare commits

...

22 Commits

Author SHA1 Message Date
Lucas Smith c5c87e3fd1 v2.2.7 2025-12-16 12:38:53 +11:00
Lucas Smith 24a74c7b57 chore: add translations (#2321)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-12-16 12:06:58 +11:00
Lucas Smith f0a5a7e816 feat: prefill typed signature with user's full name (#2324)
Add fullName prop to signature pad components to automatically populate
typed signature
field with signer's name. Updates signature dialog, type component, and
all signing forms
across embed, document, template, and envelope flows to pass through the
user's full
name for better user experience.
2025-12-16 12:06:04 +11:00
Catalin Pit 8462cd13fd fix: assignment operator for directRecipientName (#2323) 2025-12-16 12:04:19 +11:00
Lucas Smith 576846de32 fix: fallback for certficate sent date when using link distribution (#2316) 2025-12-16 11:40:16 +11:00
Lucas Smith 06071ea035 fix: memory leak in PDF to images conversion (#2325)
Add proper cleanup for PDF.js pages and loading task to prevent memory
leaks when
processing multiple PDF pages. Ensure page cleanup is called after each
page is
rendered and both PDF document and loading task are properly destroyed
with error
handling.
2025-12-16 11:34:30 +11:00
dzhou777 b45a2691ba fix: Unhide text field scrollbar (#2277) 2025-12-15 15:52:39 +11:00
Ted Liang f31cc575d0 fix: white-label for next-button, progress-bar, and steps (#2319) 2025-12-15 15:51:11 +11:00
github-actions[bot] 05d7015ef0 chore: extract translations (#2320) 2025-12-15 13:06:08 +11:00
Chenyang Gao 2ca5d6cfaa fix: local job retry loop for webhook calls (#2295) 2025-12-15 13:04:35 +11:00
Ryan Wagoner 04814ca14e fix: on error job should resubmit with isRetry (#2072) 2025-12-15 13:03:04 +11:00
Ryan Wagoner dd1dccdb6a fix: organisation invite member should be case insensitive (#2068) 2025-12-15 12:50:27 +11:00
Valentin Cocaud df4316ac5c fix: log unknown errors in the auth error handler (#2014) 2025-12-15 12:44:03 +11:00
Catalin Pit 02f1264eea feat: unlink documents from deleted organization (#2006) 2025-12-15 12:17:13 +11:00
github-actions[bot] 928edb8645 chore: extract translations (#2302) 2025-12-15 12:11:55 +11:00
Konrad 54b0e4964e chore(i18n): improve punctuation (#2307) 2025-12-15 12:00:51 +11:00
Konrad 68e6ccdd19 fix(i18n): mark sr-only strings for translation (#2309) 2025-12-15 11:51:02 +11:00
Konrad 09ab7e9a09 fix(i18n): mark "(Optional)" strings for translation (#2310) 2025-12-15 11:50:06 +11:00
Konrad 3bb0777914 fix(i18n): mark field content for translation (#2306) 2025-12-15 11:49:23 +11:00
Catalin Pit 4d6389e901 fix(api): replace generic errors with AppError in getApiTokenByToken (#2315) 2025-12-15 11:47:38 +11:00
Vincent Vu 51e3d5030d fix(security): CVE-2025-55184, CVE-2025-55183 (#2314) 2025-12-12 16:50:00 +11:00
David Nguyen 0cebdec637 fix: remove legacy envelope uploads (#2303) 2025-12-11 14:09:38 +11:00
86 changed files with 3020 additions and 2108 deletions
+2 -2
View File
@@ -15,7 +15,7 @@
"@documenso/tailwind-config": "*",
"@documenso/trpc": "*",
"@documenso/ui": "*",
"next": "^15.5.7",
"next": "15.5.9",
"next-plausible": "^3.12.5",
"nextra": "^3",
"nextra-theme-docs": "^3",
@@ -29,4 +29,4 @@
"pagefind": "^1.2.0",
"typescript": "5.6.2"
}
}
}
+2 -2
View File
@@ -12,11 +12,11 @@
"dependencies": {
"@documenso/prisma": "*",
"luxon": "^3.7.2",
"next": "^15.5.7"
"next": "15.5.9"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "18.3.27",
"typescript": "5.6.2"
}
}
}
@@ -156,8 +156,8 @@ export const AdminOrganisationMemberUpdateDialog = ({
<DialogDescription className="mt-4">
<Trans>
You are currently updating{' '}
<span className="font-bold">{organisationMemberName}.</span>
You are currently updating <span className="font-bold">{organisationMemberName}</span>
.
</Trans>
</DialogDescription>
</DialogHeader>
@@ -353,8 +353,10 @@ export const EnvelopeDistributeDialog = ({
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans>Reply To Email</Trans>{' '}
<span className="text-muted-foreground">(Optional)</span>
<Trans>
Reply To Email{' '}
<span className="text-muted-foreground">(Optional)</span>
</Trans>
</FormLabel>
<FormControl>
@@ -372,8 +374,10 @@ export const EnvelopeDistributeDialog = ({
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans>Subject</Trans>{' '}
<span className="text-muted-foreground">(Optional)</span>
<Trans>
Subject{' '}
<span className="text-muted-foreground">(Optional)</span>
</Trans>
</FormLabel>
<FormControl>
@@ -390,8 +394,10 @@ export const EnvelopeDistributeDialog = ({
render={({ field }) => (
<FormItem>
<FormLabel className="flex flex-row items-center">
<Trans>Message</Trans>{' '}
<span className="text-muted-foreground">(Optional)</span>
<Trans>
Message{' '}
<span className="text-muted-foreground">(Optional)</span>
</Trans>
<Tooltip>
<TooltipTrigger type="button">
<InfoIcon className="mx-2 h-4 w-4" />
@@ -117,7 +117,7 @@ export const EnvelopeItemDeleteDialog = ({
<DialogDescription className="mt-4">
<Trans>
You cannot delete this item because the document has been sent to recipients
You cannot delete this item because the document has been sent to recipients.
</Trans>
</DialogDescription>
</DialogHeader>
@@ -121,7 +121,7 @@ export const OrganisationEmailDomainRecordContent = ({ records }: { records: Dom
<Trans>
Once you update your DNS records, it may take up to 48 hours for it to be propogated.
Once the DNS propagation is complete you will need to come back and press the "Sync"
domains button
domains button.
</Trans>
</AlertDescription>
</Alert>
@@ -148,8 +148,8 @@ export const OrganisationMemberUpdateDialog = ({
<DialogDescription className="mt-4">
<Trans>
You are currently updating{' '}
<span className="font-bold">{organisationMemberName}.</span>
You are currently updating <span className="font-bold">{organisationMemberName}</span>
.
</Trans>
</DialogDescription>
</DialogHeader>
@@ -17,6 +17,7 @@ import { DocumentSigningDisclosure } from '../general/document-signing/document-
export type SignFieldSignatureDialogProps = {
initialSignature?: string;
fullName?: string;
typedSignatureEnabled?: boolean;
uploadSignatureEnabled?: boolean;
drawSignatureEnabled?: boolean;
@@ -28,6 +29,7 @@ export const SignFieldSignatureDialog = createCallable<
>(
({
call,
fullName,
typedSignatureEnabled,
uploadSignatureEnabled,
drawSignatureEnabled,
@@ -46,6 +48,7 @@ export const SignFieldSignatureDialog = createCallable<
</DialogHeader>
<SignaturePad
fullName={fullName}
value={localSignature ?? ''}
onChange={({ value }) => setLocalSignature(value)}
typedSignatureEnabled={typedSignatureEnabled}
@@ -8,6 +8,7 @@ import { useForm } from 'react-hook-form';
import { useNavigate } from 'react-router';
import { z } from 'zod';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { useSession } from '@documenso/lib/client-only/providers/session';
import { AppError } from '@documenso/lib/errors/app-error';
import { trpc } from '@documenso/trpc/react';
@@ -30,6 +31,13 @@ import {
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@documenso/ui/primitives/select';
import type { Toast } from '@documenso/ui/primitives/use-toast';
import { useToast } from '@documenso/ui/primitives/use-toast';
@@ -53,26 +61,34 @@ export const TeamDeleteDialog = ({
const { toast } = useToast();
const { refreshSession } = useSession();
const currentOrganisation = useCurrentOrganisation();
const deleteMessage = _(msg`delete ${teamName}`);
const filteredTeams = currentOrganisation.teams.filter((team) => team.id !== teamId);
const ZDeleteTeamFormSchema = z.object({
teamName: z.literal(deleteMessage, {
errorMap: () => ({ message: _(msg`You must enter '${deleteMessage}' to proceed`) }),
}),
transferTeamId: z.string().optional(),
});
const form = useForm({
resolver: zodResolver(ZDeleteTeamFormSchema),
defaultValues: {
teamName: '',
transferTeamId: undefined,
},
});
const { mutateAsync: deleteTeam } = trpc.team.delete.useMutation();
const onFormSubmit = async () => {
const onFormSubmit = async (data: z.infer<typeof ZDeleteTeamFormSchema>) => {
try {
await deleteTeam({ teamId });
const transferTeamId = data.transferTeamId ? parseInt(data.transferTeamId, 10) : undefined;
await deleteTeam({ teamId, transferTeamId: transferTeamId || undefined });
await refreshSession();
@@ -168,6 +184,43 @@ export const TeamDeleteDialog = ({
)}
/>
{filteredTeams.length > 0 && (
<FormField
control={form.control}
name="transferTeamId"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans>Transfer documents to a different team</Trans>
</FormLabel>
<FormControl>
<Select {...field} onValueChange={field.onChange}>
<SelectTrigger>
<SelectValue
placeholder={_(msg`Don't transfer (Delete all documents)`)}
/>
</SelectTrigger>
<SelectContent>
<SelectItem value="-1">
<Trans>Don't transfer (Delete all documents)</Trans>
</SelectItem>
{filteredTeams.map((team) => (
<SelectItem key={team.id} value={team.id.toString()}>
{team.name}
</SelectItem>
))}
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)}
<DialogFooter>
<Button type="button" variant="secondary" onClick={() => setOpen(false)}>
<Trans>Cancel</Trans>
@@ -146,7 +146,7 @@ export const TeamMemberUpdateDialog = ({
<DialogDescription>
<Trans>
You are currently updating <span className="font-bold">{memberName}.</span>
You are currently updating <span className="font-bold">{memberName}</span>.
</Trans>
</DialogDescription>
</DialogHeader>
@@ -1,136 +0,0 @@
import { useState } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { FilePlus, Loader } from 'lucide-react';
import { useNavigate } from 'react-router';
import { useSession } from '@documenso/lib/client-only/providers/session';
import { formatTemplatesPath } from '@documenso/lib/utils/teams';
import { trpc } from '@documenso/trpc/react';
import type { TCreateTemplatePayloadSchema } from '@documenso/trpc/server/template-router/schema';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import { DocumentDropzone } from '@documenso/ui/primitives/document-dropzone';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { useCurrentTeam } from '~/providers/team';
type TemplateCreateDialogProps = {
folderId?: string;
};
export const TemplateCreateDialog = ({ folderId }: TemplateCreateDialogProps) => {
const navigate = useNavigate();
const { user } = useSession();
const { toast } = useToast();
const { _ } = useLingui();
const team = useCurrentTeam();
const { mutateAsync: createTemplate } = trpc.template.createTemplate.useMutation();
const [showTemplateCreateDialog, setShowTemplateCreateDialog] = useState(false);
const [isUploadingFile, setIsUploadingFile] = useState(false);
const onFileDrop = async (files: File[]) => {
const file = files[0];
if (isUploadingFile) {
return;
}
setIsUploadingFile(true);
try {
const payload = {
title: file.name,
folderId: folderId,
} satisfies TCreateTemplatePayloadSchema;
const formData = new FormData();
formData.append('payload', JSON.stringify(payload));
formData.append('file', file);
const { envelopeId: id } = await createTemplate(formData);
toast({
title: _(msg`Template document uploaded`),
description: _(
msg`Your document has been uploaded successfully. You will be redirected to the template page.`,
),
duration: 5000,
});
setShowTemplateCreateDialog(false);
await navigate(`${formatTemplatesPath(team.url)}/${id}/edit`);
} catch {
toast({
title: _(msg`Something went wrong`),
description: _(msg`Please try again later.`),
variant: 'destructive',
});
setIsUploadingFile(false);
}
};
return (
<Dialog
open={showTemplateCreateDialog}
onOpenChange={(value) => !isUploadingFile && setShowTemplateCreateDialog(value)}
>
<DialogTrigger asChild>
<Button className="cursor-pointer" disabled={!user.emailVerified}>
<FilePlus className="-ml-1 mr-2 h-4 w-4" />
<Trans>Template (Legacy)</Trans>
</Button>
</DialogTrigger>
<DialogContent className="w-full max-w-xl">
<DialogHeader>
<DialogTitle>
<Trans>New Template</Trans>
</DialogTitle>
<DialogDescription>
<Trans>
Templates allow you to quickly generate documents with pre-filled recipients and
fields.
</Trans>
</DialogDescription>
</DialogHeader>
<div className="relative">
<DocumentDropzone className="h-[40vh]" onDrop={onFileDrop} type="template" />
{isUploadingFile && (
<div className="bg-background/50 absolute inset-0 flex items-center justify-center rounded-lg">
<Loader className="text-muted-foreground h-12 w-12 animate-spin" />
</div>
)}
</div>
<DialogFooter>
<DialogClose asChild>
<Button type="button" variant="secondary" disabled={isUploadingFile}>
<Trans>Close</Trans>
</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
);
};
@@ -219,9 +219,8 @@ export const WebhookCreateDialog = ({ trigger, ...props }: WebhookCreateDialogPr
<FormDescription>
<Trans>
A secret that will be sent to your URL so you can verify that the request
has been sent by Documenso
has been sent by Documenso.
</Trans>
.
</FormDescription>
<FormMessage />
</FormItem>
@@ -438,6 +438,7 @@ export const EmbedDirectTemplateClientPage = ({
className="mt-2"
disabled={isThrottled || isSubmitting}
disableAnimation
fullName={fullName}
value={signature ?? ''}
onChange={(v) => setSignature(v ?? '')}
typedSignatureEnabled={metadata?.typedSignatureEnabled}
@@ -455,6 +455,7 @@ export const EmbedSignDocumentV1ClientPage = ({
className="mt-2"
disabled={isThrottled || isSubmitting}
disableAnimation
fullName={fullName}
value={signature ?? ''}
onChange={(v) => setSignature(v ?? '')}
typedSignatureEnabled={metadata?.typedSignatureEnabled}
@@ -319,6 +319,7 @@ export const MultiSignDocumentSigningView = ({
className="mt-2"
disabled={isSubmitting}
disableAnimation
fullName={fullName}
value={signature ?? ''}
onChange={(v) => setSignature(v ?? '')}
typedSignatureEnabled={
+2 -1
View File
@@ -110,7 +110,7 @@ export const ProfileForm = ({ className }: ProfileFormProps) => {
<Label htmlFor="email" className="text-muted-foreground">
<Trans>Email</Trans>
</Label>
<Input id="email" type="email" className="bg-muted mt-2" value={user.email} disabled />
<Input id="email" type="email" className="mt-2 bg-muted" value={user.email} disabled />
</div>
<FormField
@@ -124,6 +124,7 @@ export const ProfileForm = ({ className }: ProfileFormProps) => {
<FormControl>
<SignaturePadDialog
disabled={isSubmitting}
fullName={user.name ?? ''}
value={value}
onChange={(v) => onChange(v ?? '')}
/>
+2 -2
View File
@@ -244,11 +244,11 @@ export const SignInForm = ({
const errorMessage = match(error.code)
.with(
AuthenticationErrorCode.InvalidCredentials,
() => msg`The email or password provided is incorrect`,
() => msg`The email or password provided is incorrect.`,
)
.with(
AuthenticationErrorCode.InvalidTwoFactorCode,
() => msg`The two-factor authentication code provided is incorrect`,
() => msg`The two-factor authentication code provided is incorrect.`,
)
.otherwise(() => handleFallbackErrorMessages(error.code));
+1 -1
View File
@@ -131,7 +131,7 @@ export const ApiTokenForm = ({ className, tokens }: ApiTokenFormProps) => {
const errorMessage = match(error.code)
.with(
AppErrorCode.UNAUTHORIZED,
() => msg`You do not have permission to create a token for this team`,
() => msg`You do not have permission to create a token for this team.`,
)
.otherwise(() => msg`Something went wrong. Please try again later.`);
@@ -417,6 +417,7 @@ export const DirectTemplateSigningForm = ({
<SignaturePadDialog
className="mt-2"
disabled={isSubmitting}
fullName={fullName}
value={signature ?? ''}
onChange={(value) => setSignature(value)}
typedSignatureEnabled={template.templateMeta?.typedSignatureEnabled}
@@ -433,7 +434,7 @@ export const DirectTemplateSigningForm = ({
<div className="mt-4 flex gap-x-4">
<Button
className="dark:bg-muted dark:hover:bg-muted/80 w-full bg-black/5 hover:bg-black/10"
className="w-full bg-black/5 hover:bg-black/10 dark:bg-muted dark:hover:bg-muted/80"
size="lg"
variant="secondary"
disabled={isSubmitting}
@@ -280,6 +280,7 @@ export const DocumentSigningForm = ({
<SignaturePadDialog
className="mt-2"
disabled={isSubmitting}
fullName={fullName}
value={signature ?? ''}
onChange={(v) => setSignature(v ?? '')}
typedSignatureEnabled={document.documentMeta?.typedSignatureEnabled}
@@ -106,7 +106,7 @@ export const DocumentSigningMobileWidget = () => {
<motion.div
layout="size"
layoutId="document-signing-mobile-widget-progress-bar"
className="bg-documenso absolute inset-y-0 left-0"
className="bg-primary absolute inset-y-0 left-0"
style={{
width: `${100 - (100 / requiredRecipientFields.length) * (recipientFieldsRemaining.length ?? 0)}%`,
}}
@@ -109,7 +109,7 @@ export const DocumentSigningPageViewV2 = () => {
<motion.div
layout="size"
layoutId="document-flow-container-step"
className="absolute inset-y-0 left-0 bg-documenso"
className="absolute inset-y-0 left-0 bg-primary"
style={{
width: `${100 - (100 / requiredRecipientFields.length) * (recipientFieldsRemaining.length ?? 0)}%`,
}}
@@ -56,8 +56,11 @@ export const DocumentSigningSignatureField = ({
const containerRef = useRef<HTMLDivElement>(null);
const [fontSize, setFontSize] = useState(2);
const { signature: providedSignature, setSignature: setProvidedSignature } =
useRequiredDocumentSigningContext();
const {
fullName,
signature: providedSignature,
setSignature: setProvidedSignature,
} = useRequiredDocumentSigningContext();
const { executeActionAuthProcedure } = useRequiredDocumentSigningAuthContext();
@@ -236,13 +239,13 @@ export const DocumentSigningSignatureField = ({
type="Signature"
>
{isLoading && (
<div className="bg-background absolute inset-0 flex items-center justify-center rounded-md">
<Loader className="text-primary h-5 w-5 animate-spin md:h-8 md:w-8" />
<div className="absolute inset-0 flex items-center justify-center rounded-md bg-background">
<Loader className="h-5 w-5 animate-spin text-primary md:h-8 md:w-8" />
</div>
)}
{state === 'empty' && (
<p className="group-hover:text-primary font-signature text-muted-foreground group-hover:text-recipient-green text-[clamp(0.575rem,25cqw,1.2rem)] text-xl duration-200">
<p className="font-signature text-[clamp(0.575rem,25cqw,1.2rem)] text-xl text-muted-foreground duration-200 group-hover:text-primary group-hover:text-recipient-green">
<Trans>Signature</Trans>
</p>
)}
@@ -259,7 +262,7 @@ export const DocumentSigningSignatureField = ({
<div ref={containerRef} className="flex h-full w-full items-center justify-center p-2">
<p
ref={signatureRef}
className="font-signature text-muted-foreground w-full overflow-hidden break-all text-center leading-tight duration-200"
className="w-full overflow-hidden break-all text-center font-signature leading-tight text-muted-foreground duration-200"
style={{ fontSize: `${fontSize}rem` }}
>
{signature?.typedSignature}
@@ -272,12 +275,13 @@ export const DocumentSigningSignatureField = ({
<DialogTitle>
<Trans>
Sign as {recipient.name}{' '}
<div className="text-muted-foreground h-5">({recipient.email})</div>
<div className="h-5 text-muted-foreground">({recipient.email})</div>
</Trans>
</DialogTitle>
<SignaturePad
className="mt-2"
fullName={fullName}
value={localSignature ?? ''}
onChange={({ value }) => setLocalSignature(value)}
typedSignatureEnabled={typedSignatureEnabled}
@@ -14,9 +14,10 @@ import { useSession } from '@documenso/lib/client-only/providers/session';
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT } from '@documenso/lib/constants/app';
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
import { formatDocumentsPath, formatTemplatesPath } from '@documenso/lib/utils/teams';
import { trpc } from '@documenso/trpc/react';
import type { TCreateDocumentPayloadSchema } from '@documenso/trpc/server/document-router/create-document.types';
import type { TCreateTemplatePayloadSchema } from '@documenso/trpc/server/template-router/schema';
import { cn } from '@documenso/ui/lib/utils';
import { DocumentUploadButton as DocumentUploadButtonPrimitive } from '@documenso/ui/primitives/document-upload-button';
import {
@@ -31,9 +32,13 @@ import { useCurrentTeam } from '~/providers/team';
export type DocumentUploadButtonLegacyProps = {
className?: string;
type: EnvelopeType;
};
export const DocumentUploadButtonLegacy = ({ className }: DocumentUploadButtonLegacyProps) => {
export const DocumentUploadButtonLegacy = ({
className,
type,
}: DocumentUploadButtonLegacyProps) => {
const { _ } = useLingui();
const { toast } = useToast();
const { user } = useSession();
@@ -54,8 +59,18 @@ export const DocumentUploadButtonLegacy = ({ className }: DocumentUploadButtonLe
const [isLoading, setIsLoading] = useState(false);
const { mutateAsync: createDocument } = trpc.document.create.useMutation();
const { mutateAsync: createTemplate } = trpc.template.createTemplate.useMutation();
const disabledMessage = useMemo(() => {
if (!user.emailVerified) {
return msg`Verify your email to upload documents.`;
}
// No errors for templates.
if (type === EnvelopeType.TEMPLATE) {
return;
}
if (organisation.subscription && remaining.documents === 0) {
return msg`Document upload disabled due to unpaid invoices`;
}
@@ -64,11 +79,8 @@ export const DocumentUploadButtonLegacy = ({ className }: DocumentUploadButtonLe
return msg`You have reached your document limit.`;
}
if (!user.emailVerified) {
return msg`Verify your email to upload documents.`;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [remaining.documents, user.emailVerified, team]);
}, [remaining.documents, user.emailVerified, team, type]);
const onFileDrop = async (file: File) => {
try {
@@ -80,44 +92,62 @@ export const DocumentUploadButtonLegacy = ({ className }: DocumentUploadButtonLe
meta: {
timezone: userTimezone,
},
} satisfies TCreateDocumentPayloadSchema;
} satisfies TCreateDocumentPayloadSchema | TCreateTemplatePayloadSchema;
const formData = new FormData();
formData.append('payload', JSON.stringify(payload));
formData.append('file', file);
const { envelopeId: id } = await createDocument(formData);
// Handle legacy document creation.
if (type === EnvelopeType.DOCUMENT) {
const { envelopeId: id } = await createDocument(formData);
void refreshLimits();
void refreshLimits();
await navigate(`${formatDocumentsPath(team.url)}/${id}/edit`);
await navigate(`${formatDocumentsPath(team.url)}/${id}/edit`);
toast({
title: _(msg`Document uploaded`),
description: _(msg`Your document has been uploaded successfully.`),
duration: 5000,
});
toast({
title: _(msg`Document uploaded`),
description: _(msg`Your document has been uploaded successfully.`),
duration: 5000,
});
analytics.capture('App: Document Uploaded', {
userId: user.id,
documentId: id,
timestamp: new Date().toISOString(),
});
analytics.capture('App: Document Uploaded', {
userId: user.id,
documentId: id,
timestamp: new Date().toISOString(),
});
}
// Handle legacy template creation.
if (type === EnvelopeType.TEMPLATE) {
const { envelopeId: id } = await createTemplate(formData);
await navigate(`${formatTemplatesPath(team.url)}/${id}/edit`);
toast({
title: _(msg`Template document uploaded`),
description: _(
msg`Your document has been uploaded successfully. You will be redirected to the template page.`,
),
duration: 5000,
});
}
} catch (err) {
const error = AppError.parseError(err);
console.error(err);
const errorMessage = match(error.code)
.with('INVALID_DOCUMENT_FILE', () => msg`You cannot upload encrypted PDFs`)
.with('INVALID_DOCUMENT_FILE', () => msg`You cannot upload encrypted PDFs.`)
.with(
AppErrorCode.LIMIT_EXCEEDED,
() => msg`You have reached your document limit for this month. Please upgrade your plan.`,
)
.with(
'ENVELOPE_ITEM_LIMIT_EXCEEDED',
() => msg`You have reached the limit of the number of files per envelope`,
() => msg`You have reached the limit of the number of files per envelope.`,
)
.otherwise(() => msg`An error occurred while uploading your document.`);
@@ -149,17 +179,18 @@ export const DocumentUploadButtonLegacy = ({ className }: DocumentUploadButtonLe
<div>
<DocumentUploadButtonPrimitive
loading={isLoading}
disabled={remaining.documents === 0 || !user.emailVerified}
disabled={disabledMessage !== undefined}
disabledMessage={disabledMessage}
onDrop={async (files) => onFileDrop(files[0])}
onDropRejected={onFileDropRejected}
type={EnvelopeType.DOCUMENT}
type={type}
internalVersion="1"
/>
</div>
</TooltipTrigger>
{team?.id === undefined &&
type === EnvelopeType.DOCUMENT &&
remaining.documents > 0 &&
Number.isFinite(remaining.documents) && (
<TooltipContent>
@@ -687,8 +687,10 @@ export const EnvelopeEditorSettingsDialog = ({
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans>Reply To Email</Trans>{' '}
<span className="text-muted-foreground">(Optional)</span>
<Trans>
Reply To Email{' '}
<span className="text-muted-foreground">(Optional)</span>
</Trans>
</FormLabel>
<FormControl>
@@ -726,8 +728,9 @@ export const EnvelopeEditorSettingsDialog = ({
render={({ field }) => (
<FormItem>
<FormLabel className="flex flex-row items-center">
<Trans>Message</Trans>{' '}
<span className="text-muted-foreground">(Optional)</span>
<Trans>
Message <span className="text-muted-foreground">(Optional)</span>
</Trans>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="mx-2 h-4 w-4" />
@@ -175,7 +175,7 @@ export default function EnvelopeEditor() {
<motion.div
layout="size"
layoutId="document-flow-container-step"
className="absolute inset-y-0 left-0 bg-documenso"
className="absolute inset-y-0 left-0 bg-primary"
style={{
width: `${(100 / envelopeEditorSteps.length) * (currentStepData.order ?? 0)}%`,
}}
@@ -41,7 +41,7 @@ export default function EnvelopeSignerForm() {
if (recipient.role === RecipientRole.ASSISTANT) {
return (
<fieldset className="embed--DocumentWidgetForm dark:bg-background border-border rounded-2xl sm:border sm:p-3">
<fieldset className="embed--DocumentWidgetForm rounded-2xl border-border sm:border sm:p-3 dark:bg-background">
<RadioGroup
className="gap-0 space-y-2 shadow-none sm:space-y-3"
value={selectedAssistantRecipient?.id?.toString()}
@@ -54,7 +54,7 @@ export default function EnvelopeSignerForm() {
.map((r) => (
<div
key={r.id}
className="bg-widget border-border relative flex flex-col gap-4 rounded-lg border p-4"
className="relative flex flex-col gap-4 rounded-lg border border-border bg-widget p-4"
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
@@ -69,15 +69,15 @@ export default function EnvelopeSignerForm() {
{r.name}
{r.id === recipient.id && (
<span className="text-muted-foreground ml-2">
<span className="ml-2 text-muted-foreground">
<Trans>(You)</Trans>
</span>
)}
</Label>
<p className="text-muted-foreground text-xs">{r.email}</p>
<p className="text-xs text-muted-foreground">{r.email}</p>
</div>
</div>
<div className="text-muted-foreground text-xs leading-[inherit]">
<div className="text-xs leading-[inherit] text-muted-foreground">
<Plural
value={assistantFields.filter((field) => field.recipientId === r.id).length}
one="# field"
@@ -103,7 +103,7 @@ export default function EnvelopeSignerForm() {
<Input
type="text"
id="full-name"
className="bg-background mt-2"
className="mt-2 bg-background"
value={fullName}
disabled={isNameLocked}
onChange={(e) => !isNameLocked && setFullName(e.target.value.trimStart())}
@@ -119,6 +119,7 @@ export default function EnvelopeSignerForm() {
<SignaturePadDialog
className="mt-2"
disabled={isSubmitting}
fullName={fullName}
value={signature ?? ''}
onChange={(v) => setSignature(v ?? '')}
typedSignatureEnabled={envelope.documentMeta.typedSignatureEnabled}
@@ -374,6 +374,7 @@ export default function EnvelopeSignerPageRenderer() {
.with({ type: FieldType.SIGNATURE }, (field) => {
handleSignatureFieldClick({
field,
fullName,
signature,
typedSignatureEnabled: envelope.documentMeta.typedSignatureEnabled,
uploadSignatureEnabled: envelope.documentMeta.uploadSignatureEnabled,
@@ -123,14 +123,14 @@ export const EnvelopeDropZoneWrapper = ({
const error = AppError.parseError(err);
const errorMessage = match(error.code)
.with('INVALID_DOCUMENT_FILE', () => t`You cannot upload encrypted PDFs`)
.with('INVALID_DOCUMENT_FILE', () => t`You cannot upload encrypted PDFs.`)
.with(
AppErrorCode.LIMIT_EXCEEDED,
() => t`You have reached your document limit for this month. Please upgrade your plan.`,
)
.with(
'ENVELOPE_ITEM_LIMIT_EXCEEDED',
() => t`You have reached the limit of the number of files per envelope`,
() => t`You have reached the limit of the number of files per envelope.`,
)
.otherwise(() => t`An error occurred during upload.`);
@@ -126,14 +126,14 @@ export const EnvelopeUploadButton = ({ className, type, folderId }: EnvelopeUplo
console.error(err);
const errorMessage = match(error.code)
.with('INVALID_DOCUMENT_FILE', () => t`You cannot upload encrypted PDFs`)
.with('INVALID_DOCUMENT_FILE', () => t`You cannot upload encrypted PDFs.`)
.with(
AppErrorCode.LIMIT_EXCEEDED,
() => t`You have reached your document limit for this month. Please upgrade your plan.`,
)
.with(
'ENVELOPE_ITEM_LIMIT_EXCEEDED',
() => t`You have reached the limit of the number of files per envelope`,
() => t`You have reached the limit of the number of files per envelope.`,
)
.otherwise(() => t`An error occurred while uploading your document.`);
@@ -15,7 +15,6 @@ import { FolderCreateDialog } from '~/components/dialogs/folder-create-dialog';
import { FolderDeleteDialog } from '~/components/dialogs/folder-delete-dialog';
import { FolderMoveDialog } from '~/components/dialogs/folder-move-dialog';
import { FolderUpdateDialog } from '~/components/dialogs/folder-update-dialog';
import { TemplateCreateDialog } from '~/components/dialogs/template-create-dialog';
import { DocumentUploadButtonLegacy } from '~/components/general/document/document-upload-button-legacy';
import { FolderCard, FolderCardEmpty } from '~/components/general/folder/folder-card';
import { useCurrentTeam } from '~/providers/team';
@@ -70,7 +69,7 @@ export const FolderGrid = ({ type, parentId }: FolderGridProps) => {
<div>
<div className="mb-4 flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
<div
className="text-muted-foreground hover:text-muted-foreground/80 flex flex-1 items-center text-sm font-medium"
className="flex flex-1 items-center text-sm font-medium text-muted-foreground hover:text-muted-foreground/80"
data-testid="folder-grid-breadcrumbs"
>
<Link to={formatRootPath()} className="flex items-center">
@@ -100,10 +99,9 @@ export const FolderGrid = ({ type, parentId }: FolderGridProps) => {
<div className="flex gap-4 sm:flex-row sm:justify-end">
<EnvelopeUploadButton type={type} folderId={parentId || undefined} />
{type === FolderType.DOCUMENT ? (
<DocumentUploadButtonLegacy /> // If you delete this, delete the component as well.
) : (
<TemplateCreateDialog folderId={parentId ?? undefined} /> // If you delete this, delete the component as well.
{/* If you delete this, delete the component as well. */}
{organisation.organisationClaim.flags.allowLegacyEnvelopes && (
<DocumentUploadButtonLegacy type={type} />
)}
<FolderCreateDialog type={type} />
@@ -113,7 +111,7 @@ export const FolderGrid = ({ type, parentId }: FolderGridProps) => {
{isPending ? (
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
{Array.from({ length: 4 }).map((_, index) => (
<div key={index} className="border-border bg-card h-full rounded-lg border px-4 py-5">
<div key={index} className="h-full rounded-lg border border-border bg-card px-4 py-5">
<div className="flex items-center gap-3">
<Skeleton className="h-8 w-8 rounded" />
<div className="flex w-full items-center justify-between">
@@ -194,7 +192,7 @@ export const FolderGrid = ({ type, parentId }: FolderGridProps) => {
{foldersData.folders.length > 12 && (
<div className="mt-2 flex items-center justify-center">
<Link
className="text-muted-foreground hover:text-foreground text-sm font-medium"
className="text-sm font-medium text-muted-foreground hover:text-foreground"
to={formatViewAllFoldersPath()}
>
View all folders
@@ -178,7 +178,9 @@ const TeamDropdownMenu = ({ team }: { team: TGetOrganisationSessionResponse[0]['
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="sm" className="h-8 w-8 p-0">
<MoreVerticalIcon className="h-4 w-4" />
<span className="sr-only">Open menu</span>
<span className="sr-only">
<Trans>Open menu</Trans>
</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" onClick={(e) => e.stopPropagation()}>
@@ -87,9 +87,9 @@ export default function OrganisationSettingsBrandingPage() {
const settingsHeaderText = t`Branding Preferences`;
const settingsHeaderSubtitle = isPersonalLayoutMode
? t`Here you can set your general branding preferences`
? t`Here you can set your general branding preferences.`
: team
? t`Here you can set branding preferences for your team`
? t`Here you can set branding preferences for your team.`
: t`Here you can set branding preferences for your organisation. Teams will inherit these settings by default.`;
return (
@@ -112,7 +112,7 @@ export default function OrganisationSettingsDocumentPage() {
const settingsHeaderText = t`Document Preferences`;
const settingsHeaderSubtitle = isPersonalLayoutMode
? t`Here you can set your general document preferences`
? t`Here you can set your general document preferences.`
: t`Here you can set document preferences for your organisation. Teams will inherit these settings by default.`;
return (
@@ -65,7 +65,7 @@ export default function OrganisationSettingsGeneral() {
<div className="max-w-2xl">
<SettingsHeader
title={t`Email Preferences`}
subtitle={t`You can manage your email preferences here`}
subtitle={t`You can manage your email preferences here.`}
/>
<section>
@@ -63,7 +63,7 @@ export default function TeamEmailSettingsGeneral() {
<div className="max-w-2xl">
<SettingsHeader
title={t`Email Preferences`}
subtitle={t`You can manage your email preferences here`}
subtitle={t`You can manage your email preferences here.`}
/>
<section>
@@ -185,6 +185,9 @@ export default function SigningCertificate({ loaderData }: Route.ComponentProps)
(log) =>
log.type === DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT && log.data.recipientId === recipientId,
),
[DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_SENT]: auditLogs[
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_SENT
].filter((log) => log.type === DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_SENT),
[DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_OPENED]: auditLogs[
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_OPENED
].filter(
@@ -245,11 +248,11 @@ export default function SigningCertificate({ loaderData }: Route.ComponentProps)
<TableCell truncate={false} className="w-[min-content] max-w-[220px] align-top">
<div className="hyphens-auto break-words font-medium">{recipient.name}</div>
<div className="break-all">{recipient.email}</div>
<p className="text-muted-foreground mt-2 text-sm print:text-xs">
<p className="mt-2 text-sm text-muted-foreground print:text-xs">
{_(RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName)}
</p>
<p className="text-muted-foreground mt-2 text-sm print:text-xs">
<p className="mt-2 text-sm text-muted-foreground print:text-xs">
<span className="font-medium">{_(msg`Authentication Level`)}:</span>{' '}
<span className="block">{getAuthenticationLevel(recipient.id)}</span>
</p>
@@ -273,13 +276,13 @@ export default function SigningCertificate({ loaderData }: Route.ComponentProps)
)}
{signature.signature?.typedSignature && (
<p className="font-signature text-center text-sm">
<p className="text-center font-signature text-sm">
{signature.signature?.typedSignature}
</p>
)}
</div>
<p className="text-muted-foreground mt-2 text-sm print:text-xs">
<p className="mt-2 text-sm text-muted-foreground print:text-xs">
<span className="font-medium">{_(msg`Signature ID`)}:</span>{' '}
<span className="block font-mono uppercase">
{signature.secondaryId}
@@ -290,14 +293,14 @@ export default function SigningCertificate({ loaderData }: Route.ComponentProps)
<p className="text-muted-foreground">N/A</p>
)}
<p className="text-muted-foreground mt-2 text-sm print:text-xs">
<p className="mt-2 text-sm text-muted-foreground print:text-xs">
<span className="font-medium">{_(msg`IP Address`)}:</span>{' '}
<span className="inline-block">
{logs.DOCUMENT_RECIPIENT_COMPLETED[0]?.ipAddress ?? _(msg`Unknown`)}
</span>
</p>
<p className="text-muted-foreground mt-1 text-sm print:text-xs">
<p className="mt-1 text-sm text-muted-foreground print:text-xs">
<span className="font-medium">{_(msg`Device`)}:</span>{' '}
<span className="inline-block">
{getDevice(logs.DOCUMENT_RECIPIENT_COMPLETED[0]?.userAgent)}
@@ -307,18 +310,22 @@ export default function SigningCertificate({ loaderData }: Route.ComponentProps)
<TableCell truncate={false} className="w-[min-content] align-top">
<div className="space-y-1">
<p className="text-muted-foreground text-sm print:text-xs">
<p className="text-sm text-muted-foreground print:text-xs">
<span className="font-medium">{_(msg`Sent`)}:</span>{' '}
<span className="inline-block">
{logs.EMAIL_SENT[0]
? DateTime.fromJSDate(logs.EMAIL_SENT[0].createdAt)
.setLocale(APP_I18N_OPTIONS.defaultLocale)
.toFormat('yyyy-MM-dd hh:mm:ss a (ZZZZ)')
: _(msg`Unknown`)}
: logs.DOCUMENT_SENT[0]
? DateTime.fromJSDate(logs.DOCUMENT_SENT[0].createdAt)
.setLocale(APP_I18N_OPTIONS.defaultLocale)
.toFormat('yyyy-MM-dd hh:mm:ss a (ZZZZ)')
: _(msg`Unknown`)}
</span>
</p>
<p className="text-muted-foreground text-sm print:text-xs">
<p className="text-sm text-muted-foreground print:text-xs">
<span className="font-medium">{_(msg`Viewed`)}:</span>{' '}
<span className="inline-block">
{logs.DOCUMENT_OPENED[0]
@@ -330,7 +337,7 @@ export default function SigningCertificate({ loaderData }: Route.ComponentProps)
</p>
{logs.DOCUMENT_RECIPIENT_REJECTED[0] ? (
<p className="text-muted-foreground text-sm print:text-xs">
<p className="text-sm text-muted-foreground print:text-xs">
<span className="font-medium">{_(msg`Rejected`)}:</span>{' '}
<span className="inline-block">
{logs.DOCUMENT_RECIPIENT_REJECTED[0]
@@ -341,7 +348,7 @@ export default function SigningCertificate({ loaderData }: Route.ComponentProps)
</span>
</p>
) : (
<p className="text-muted-foreground text-sm print:text-xs">
<p className="text-sm text-muted-foreground print:text-xs">
<span className="font-medium">{_(msg`Signed`)}:</span>{' '}
<span className="inline-block">
{logs.DOCUMENT_RECIPIENT_COMPLETED[0]
@@ -355,7 +362,7 @@ export default function SigningCertificate({ loaderData }: Route.ComponentProps)
</p>
)}
<p className="text-muted-foreground text-sm print:text-xs">
<p className="text-sm text-muted-foreground print:text-xs">
<span className="font-medium">{_(msg`Reason`)}:</span>{' '}
<span className="inline-block">
{recipient.signingStatus === SigningStatus.REJECTED
@@ -371,8 +371,9 @@ const SigningPageV1 = ({ data }: { data: Awaited<ReturnType<typeof handleV1Loade
to="https://documenso.com"
className="text-documenso-700 hover:text-documenso-600"
>
Check out Documenso.
Check out Documenso
</Link>
.
</Trans>
</p>
)}
@@ -470,8 +471,9 @@ const SigningPageV2 = ({ data }: { data: Awaited<ReturnType<typeof handleV2Loade
to="https://documenso.com"
className="text-documenso-700 hover:text-documenso-600"
>
Check out Documenso.
Check out Documenso
</Link>
.
</Trans>
</p>
)}
@@ -8,6 +8,7 @@ import { SignFieldSignatureDialog } from '~/components/dialogs/sign-field-signat
type HandleSignatureFieldClickOptions = {
field: TFieldSignature;
fullName?: string;
signature: string | null;
typedSignatureEnabled?: boolean;
uploadSignatureEnabled?: boolean;
@@ -17,8 +18,14 @@ type HandleSignatureFieldClickOptions = {
export const handleSignatureFieldClick = async (
options: HandleSignatureFieldClickOptions,
): Promise<Extract<TSignEnvelopeFieldValue, { type: typeof FieldType.SIGNATURE }> | null> => {
const { field, signature, typedSignatureEnabled, uploadSignatureEnabled, drawSignatureEnabled } =
options;
const {
field,
fullName,
signature,
typedSignatureEnabled,
uploadSignatureEnabled,
drawSignatureEnabled,
} = options;
if (field.type !== FieldType.SIGNATURE) {
throw new AppError(AppErrorCode.INVALID_REQUEST, {
@@ -37,6 +44,7 @@ export const handleSignatureFieldClick = async (
if (!signatureToInsert) {
signatureToInsert = await SignFieldSignatureDialog.call({
fullName,
typedSignatureEnabled,
uploadSignatureEnabled,
drawSignatureEnabled,
+1 -1
View File
@@ -107,5 +107,5 @@
"vite-plugin-babel-macros": "^1.0.6",
"vite-tsconfig-paths": "^5.1.4"
},
"version": "2.2.6"
"version": "2.2.7"
}
+16 -16
View File
@@ -1,12 +1,12 @@
{
"name": "@documenso/root",
"version": "2.2.6",
"version": "2.2.7",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@documenso/root",
"version": "2.2.6",
"version": "2.2.7",
"hasInstallScript": true,
"workspaces": [
"apps/*",
@@ -78,7 +78,7 @@
"@documenso/tailwind-config": "*",
"@documenso/trpc": "*",
"@documenso/ui": "*",
"next": "^15.5.7",
"next": "15.5.9",
"next-plausible": "^3.12.5",
"nextra": "^3",
"nextra-theme-docs": "^3",
@@ -99,7 +99,7 @@
"dependencies": {
"@documenso/prisma": "*",
"luxon": "^3.7.2",
"next": "^15.5.7"
"next": "15.5.9"
},
"devDependencies": {
"@types/node": "^20",
@@ -109,7 +109,7 @@
},
"apps/remix": {
"name": "@documenso/remix",
"version": "2.2.6",
"version": "2.2.7",
"dependencies": {
"@cantoo/pdf-lib": "^2.5.3",
"@documenso/api": "*",
@@ -5036,9 +5036,9 @@
}
},
"node_modules/@next/env": {
"version": "15.5.7",
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.7.tgz",
"integrity": "sha512-4h6Y2NyEkIEN7Z8YxkA27pq6zTkS09bUSYC0xjd0NpwFxjnIKeZEeH591o5WECSmjpUhLn3H2QLJcDye3Uzcvg==",
"version": "15.5.9",
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.9.tgz",
"integrity": "sha512-4GlTZ+EJM7WaW2HEZcyU317tIQDjkQIyENDLxYJfSWlfqguN+dHkZgyQTV/7ykvobU7yEH5gKvreNrH4B6QgIg==",
"license": "MIT"
},
"node_modules/@next/eslint-plugin-next": {
@@ -26396,12 +26396,12 @@
}
},
"node_modules/jws": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz",
"integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==",
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
"integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
"license": "MIT",
"dependencies": {
"jwa": "^2.0.0",
"jwa": "^2.0.1",
"safe-buffer": "^5.0.1"
}
},
@@ -28912,12 +28912,12 @@
}
},
"node_modules/next": {
"version": "15.5.7",
"resolved": "https://registry.npmjs.org/next/-/next-15.5.7.tgz",
"integrity": "sha512-+t2/0jIJ48kUpGKkdlhgkv+zPTEOoXyr60qXe68eB/pl3CMJaLeIGjzp5D6Oqt25hCBiBTt8wEeeAzfJvUKnPQ==",
"version": "15.5.9",
"resolved": "https://registry.npmjs.org/next/-/next-15.5.9.tgz",
"integrity": "sha512-agNLK89seZEtC5zUHwtut0+tNrc0Xw4FT/Dg+B/VLEo9pAcS9rtTKpek3V6kVcVwsB2YlqMaHdfZL4eLEVYuCg==",
"license": "MIT",
"dependencies": {
"@next/env": "15.5.7",
"@next/env": "15.5.9",
"@swc/helpers": "0.5.15",
"caniuse-lite": "^1.0.30001579",
"postcss": "8.4.31",
+1 -1
View File
@@ -5,7 +5,7 @@
"apps/*",
"packages/*"
],
"version": "2.2.6",
"version": "2.2.7",
"scripts": {
"postinstall": "patch-package",
"build": "turbo run build",
@@ -364,6 +364,25 @@ test('[TEAMS]: can create a template subfolder inside a template folder', async
test('[TEAMS]: can create a template inside a template folder', async ({ page }) => {
const { team, teamOwner } = await seedTeamDocuments();
const organisationClaim = await prisma.organisationClaim.findFirstOrThrow({
where: {
organisation: {
id: team.organisationId,
},
},
});
await prisma.organisationClaim.update({
where: {
id: organisationClaim.id,
},
data: {
flags: {
allowLegacyEnvelopes: true,
},
},
});
const folder = await seedBlankFolder(teamOwner, team.id, {
createFolderOptions: {
name: 'Team Client Templates',
@@ -380,16 +399,15 @@ test('[TEAMS]: can create a template inside a template folder', async ({ page })
await expect(page.getByText('Team Client Templates')).toBeVisible();
await page.getByRole('button', { name: 'Template (Legacy)' }).click();
// Upload document.
const [fileChooser] = await Promise.all([
page.waitForEvent('filechooser'),
page.getByRole('button', { name: 'Template (Legacy)' }).click(),
]);
await page.getByText('Upload Template Document').click();
await page.locator('input[type="file"]').nth(0).waitFor({ state: 'attached' });
await page
.locator('input[type="file"]')
.nth(0)
.setInputFiles(path.join(__dirname, '../../../assets/documenso-supporter-pledge.pdf'));
await fileChooser.setFiles(
path.join(__dirname, '../../../assets/documenso-supporter-pledge.pdf'),
);
await page.waitForTimeout(3000);
+1
View File
@@ -81,6 +81,7 @@ auth.onError((err, c) => {
}
// Handle other errors
console.error('Unknown Error:', err);
return c.json(
{
code: AppErrorCode.UNKNOWN_ERROR,
@@ -17,8 +17,9 @@ export const TemplateFooter = ({ isDocument = true }: TemplateFooterProps) => {
<Trans>
This document was sent using{' '}
<Link className="text-[#7AC455]" href="https://documen.so/mail-footer">
Documenso.
Documenso
</Link>
.
</Trans>
</Text>
)}
@@ -106,7 +106,7 @@ export const ConfirmTeamEmailTemplate = ({
<Text className="mt-2 text-sm">
<Trans>
You can revoke access at any time in your team settings on Documenso{' '}
<Link href={`${baseUrl}/settings/teams`}>here.</Link>
<Link href={`${baseUrl}/settings/teams`}>here</Link>.
</Trans>
</Text>
</Section>
+2 -1
View File
@@ -73,8 +73,9 @@ export const ResetPasswordTemplate = ({
Didn't request a password change? We are here to help you secure your account,
just{' '}
<Link className="text-documenso-700 font-normal" href="mailto:hi@documenso.com">
contact us.
contact us
</Link>
.
</Trans>
</Text>
</Section>
+2
View File
@@ -63,6 +63,7 @@ export class LocalJobProvider extends BaseJobProvider {
jobId: pendingJob.id,
jobDefinitionId: pendingJob.jobId,
data: options,
isRetry: false,
});
}),
);
@@ -198,6 +199,7 @@ export class LocalJobProvider extends BaseJobProvider {
jobId,
jobDefinitionId: backgroundJob.jobId,
data: options,
isRetry: true,
});
}
+11 -4
View File
@@ -44,10 +44,12 @@ export type PdfToImagesOptions = {
export const pdfToImages = async (pdfBytes: Uint8Array, options: PdfToImagesOptions = {}) => {
const { scale = 2 } = options;
const pdf = await pdfjsLib.getDocument({
const task = await pdfjsLib.getDocument({
data: pdfBytes,
CanvasFactory: SkiaCanvasFactory,
}).promise;
});
const pdf = await task.promise;
const images = await pMap(
Array.from({ length: pdf.numPages }),
@@ -68,18 +70,23 @@ export const pdfToImages = async (pdfBytes: Uint8Array, options: PdfToImagesOpti
viewport,
}).promise;
return {
const result = {
pageNumber,
image: await canvas.toBuffer('jpeg'),
width: Math.floor(viewport.width),
height: Math.floor(viewport.height),
mimeType: 'image/jpeg',
};
void page.cleanup();
return result;
},
{ concurrency: 10 },
);
void pdf.destroy();
void pdf.destroy().catch((e) => console.error(e));
void task.destroy().catch((e) => console.error(e));
return images;
};
@@ -20,6 +20,7 @@ export const getDocumentCertificateAuditLogs = async ({
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_FIELD_INSERTED,
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_OPENED,
DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT,
DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_SENT,
],
},
},
@@ -37,6 +38,9 @@ export const getDocumentCertificateAuditLogs = async ({
[DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_OPENED]: auditLogs.filter(
(log) => log.type === DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_OPENED,
),
[DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_SENT]: auditLogs.filter(
(log) => log.type === DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_SENT,
),
[DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_FIELD_INSERTED]: auditLogs.filter(
(log) => log.type === DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_FIELD_INSERTED,
),
@@ -0,0 +1,52 @@
import { DocumentStatus, EnvelopeType } from '@prisma/client';
import { prisma } from '@documenso/prisma';
import { deletedAccountServiceAccount } from '../user/service-accounts/deleted-account';
export type OrphanEnvelopesOptions = {
teamId: number;
};
export const orphanEnvelopes = async ({ teamId }: OrphanEnvelopesOptions) => {
const serviceAccount = await deletedAccountServiceAccount();
// Transfer all inflight and completed envelopes to the service account.
await prisma.envelope.updateMany({
where: {
teamId,
type: EnvelopeType.DOCUMENT,
status: {
in: [DocumentStatus.PENDING, DocumentStatus.REJECTED, DocumentStatus.COMPLETED],
},
deletedAt: null,
},
data: {
userId: serviceAccount.id,
teamId: serviceAccount.ownedOrganisations[0].teams[0].id,
deletedAt: new Date(),
},
});
// Transfer any remaining deleted envelopes to the service account.
await prisma.envelope.updateMany({
where: {
teamId,
type: EnvelopeType.DOCUMENT,
status: {
in: [DocumentStatus.PENDING, DocumentStatus.REJECTED, DocumentStatus.COMPLETED],
},
},
data: {
userId: serviceAccount.id,
teamId: serviceAccount.ownedOrganisations[0].teams[0].id,
},
});
// Then delete anything remaining across documents and templates.
await prisma.envelope.deleteMany({
where: {
teamId,
},
});
};
@@ -0,0 +1,20 @@
import { prisma } from '@documenso/prisma';
export type TransferTeamEnvelopesOptions = {
sourceTeamId: number;
targetTeamId: number;
};
export const transferTeamEnvelopes = async ({
sourceTeamId,
targetTeamId,
}: TransferTeamEnvelopesOptions) => {
await prisma.envelope.updateMany({
where: {
teamId: sourceTeamId,
},
data: {
teamId: targetTeamId,
},
});
};
@@ -40,7 +40,10 @@ export const acceptOrganisationInvitation = async ({
const user = await prisma.user.findFirst({
where: {
email: organisationMemberInvite.email,
email: {
equals: organisationMemberInvite.email,
mode: 'insensitive',
},
},
select: {
id: true,
@@ -1,5 +1,6 @@
import { prisma } from '@documenso/prisma';
import { AppError, AppErrorCode } from '../../errors/app-error';
import { hashString } from '../auth/hash';
export const getApiTokenByToken = async ({ token }: { token: string }) => {
@@ -38,11 +39,17 @@ export const getApiTokenByToken = async ({ token }: { token: string }) => {
});
if (!apiToken) {
throw new Error('Invalid token');
throw new AppError(AppErrorCode.UNAUTHORIZED, {
message: 'Invalid token',
statusCode: 401,
});
}
if (apiToken.expires && apiToken.expires < new Date()) {
throw new Error('Expired token');
throw new AppError(AppErrorCode.EXPIRED_CODE, {
message: 'Expired token',
statusCode: 401,
});
}
// Handle a silly choice from many moons ago
@@ -54,7 +61,10 @@ export const getApiTokenByToken = async ({ token }: { token: string }) => {
// This will never happen but we need to narrow types
if (!user) {
throw new Error('Invalid token');
throw new AppError(AppErrorCode.UNAUTHORIZED, {
message: 'Invalid token',
statusCode: 401,
});
}
return {
@@ -1,5 +1,6 @@
import { prisma } from '@documenso/prisma';
import { AppError, AppErrorCode } from '../../errors/app-error';
import { hashString } from '../auth/hash';
export const getUserByApiToken = async ({ token }: { token: string }) => {
@@ -19,14 +20,20 @@ export const getUserByApiToken = async ({ token }: { token: string }) => {
});
if (!user) {
throw new Error('Invalid token');
throw new AppError(AppErrorCode.UNAUTHORIZED, {
message: 'Invalid token',
statusCode: 401,
});
}
const retrievedToken = user.apiTokens.find((apiToken) => apiToken.token === hashedToken);
// This should be impossible but we need to satisfy TypeScript
if (!retrievedToken) {
throw new Error('Invalid token');
throw new AppError(AppErrorCode.UNAUTHORIZED, {
message: 'Invalid token',
statusCode: 401,
});
}
if (retrievedToken.expires && retrievedToken.expires < new Date()) {
@@ -185,7 +185,7 @@ export const createDocumentFromDirectTemplate = async ({
documentAuth: directTemplateEnvelope.authOptions,
});
const directRecipientName = user?.name || initialDirectRecipientName;
let directRecipientName = user?.name || initialDirectRecipientName;
// Ensure typesafety when we add more options.
const isAccessAuthValid = match(derivedRecipientAccessAuth.at(0))
@@ -238,7 +238,7 @@ export const createDocumentFromDirectTemplate = async ({
}
if (templateField.type === FieldType.NAME && directRecipientName === undefined) {
directRecipientName === signedFieldValue?.value;
directRecipientName = signedFieldValue?.value;
}
const derivedRecipientActionAuth = await validateFieldAuth({
+54 -18
View File
@@ -1,9 +1,7 @@
import { DocumentStatus, EnvelopeType } from '@prisma/client';
import { prisma } from '@documenso/prisma';
import { AppError, AppErrorCode } from '../../errors/app-error';
import { deletedAccountServiceAccount } from './service-accounts/deleted-account';
import { orphanEnvelopes } from '../envelope/orphan-envelopes';
export type DeleteUserOptions = {
id: number;
@@ -14,6 +12,30 @@ export const deleteUser = async ({ id }: DeleteUserOptions) => {
where: {
id,
},
include: {
ownedOrganisations: {
include: {
teams: {
select: {
id: true,
},
},
},
},
organisationMember: {
include: {
organisation: {
include: {
teams: {
select: {
id: true,
},
},
},
},
},
},
},
});
if (!user) {
@@ -22,22 +44,36 @@ export const deleteUser = async ({ id }: DeleteUserOptions) => {
});
}
const serviceAccount = await deletedAccountServiceAccount();
// Get team IDs from organisations the user owns.
const ownedTeamIds = user.ownedOrganisations.flatMap((org) => org.teams.map((team) => team.id));
// TODO: Send out cancellations for all pending docs
await prisma.envelope.updateMany({
where: {
userId: user.id,
type: EnvelopeType.DOCUMENT,
status: {
in: [DocumentStatus.PENDING, DocumentStatus.REJECTED, DocumentStatus.COMPLETED],
},
},
data: {
userId: serviceAccount.id,
deletedAt: new Date(),
},
});
// Get team IDs from organisations the user is a member of (but not owner).
const memberTeams = user.organisationMember
.filter((member) => member.organisation.ownerUserId !== user.id)
.flatMap((member) =>
member.organisation.teams.map((team) => ({
teamId: team.id,
orgOwnerId: member.organisation.ownerUserId,
})),
);
// For teams where user is the org owner - orphan their envelopes.
await Promise.all(ownedTeamIds.map(async (teamId) => orphanEnvelopes({ teamId })));
// For teams where user is a member (not owner) - transfer envelopes to team owner.
await Promise.all(
memberTeams.map(async ({ teamId, orgOwnerId }) => {
return prisma.envelope.updateMany({
where: {
userId: user.id,
teamId,
},
data: {
userId: orgOwnerId,
},
});
}),
);
return await prisma.user.delete({
where: {
@@ -5,6 +5,20 @@ export const deletedAccountServiceAccount = async () => {
where: {
email: 'deleted-account@documenso.com',
},
select: {
id: true,
email: true,
ownedOrganisations: {
select: {
id: true,
teams: {
select: {
id: true,
},
},
},
},
},
});
if (!serviceAccount) {
File diff suppressed because it is too large Load Diff
+152 -95
View File
@@ -96,6 +96,11 @@ msgstr "{0, plural, one {# field} other {# fields}}"
msgid "{0, plural, one {# folder} other {# folders}}"
msgstr "{0, plural, one {# folder} other {# folders}}"
#. placeholder {0}: detectedRecipients.length
#: apps/remix/app/components/general/envelope-editor/envelope-editor-recipient-form.tsx
msgid "{0, plural, one {# recipient have been added from AI detection.} other {# recipients have been added from AI detection.}}"
msgstr "{0, plural, one {# recipient have been added from AI detection.} other {# recipients have been added from AI detection.}}"
#. placeholder {0}: template.recipients.length
#: apps/remix/app/routes/_recipient+/d.$token+/_index.tsx
msgid "{0, plural, one {# recipient} other {# recipients}}"
@@ -146,11 +151,44 @@ msgstr "{0, plural, one {1 matching field} other {# matching fields}}"
msgid "{0, plural, one {1 Recipient} other {# Recipients}}"
msgstr "{0, plural, one {1 Recipient} other {# Recipients}}"
#. placeholder {0}: progress.fieldsDetected
#. placeholder {1}: progress.pagesProcessed
#. placeholder {2}: progress.totalPages
#. placeholder {3}: progress.pagesProcessed
#. placeholder {4}: progress.totalPages
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
msgid "{0, plural, one {Page {1} of {2} - # field found} other {Page {3} of {4} - # fields found}}"
msgstr "{0, plural, one {Page {1} of {2} - # field found} other {Page {3} of {4} - # fields found}}"
#. placeholder {0}: progress.recipientsDetected
#. placeholder {1}: progress.pagesProcessed
#. placeholder {2}: progress.totalPages
#. placeholder {3}: progress.pagesProcessed
#. placeholder {4}: progress.totalPages
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
msgid "{0, plural, one {Page {1} of {2} - # recipient found} other {Page {3} of {4} - # recipients found}}"
msgstr "{0, plural, one {Page {1} of {2} - # recipient found} other {Page {3} of {4} - # recipients found}}"
#. placeholder {0}: detectedRecipients.length
#: apps/remix/app/components/general/envelope-editor/envelope-editor-recipient-form.tsx
msgid "{0, plural, one {Recipient added} other {Recipients added}}"
msgstr "{0, plural, one {Recipient added} other {Recipients added}}"
#. placeholder {0}: pendingRecipients.length
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id._index.tsx
msgid "{0, plural, one {Waiting on 1 recipient} other {Waiting on # recipients}}"
msgstr "{0, plural, one {Waiting on 1 recipient} other {Waiting on # recipients}}"
#. placeholder {0}: detectedFields.length
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
msgid "{0, plural, one {We found # field in your document.} other {We found # fields in your document.}}"
msgstr "{0, plural, one {We found # field in your document.} other {We found # fields in your document.}}"
#. placeholder {0}: detectedRecipients.length
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
msgid "{0, plural, one {We found # recipient in your document.} other {We found # recipients in your document.}}"
msgstr "{0, plural, one {We found # recipient in your document.} other {We found # recipients in your document.}}"
#. placeholder {0}: _(FRIENDLY_FIELD_TYPE[fieldType as FieldType])
#. placeholder {0}: route.label
#: apps/remix/app/components/general/document-signing/document-signing-auto-sign.tsx
@@ -195,11 +233,6 @@ msgstr "{0} of {1} documents remaining this month."
msgid "{0} on behalf of \"{1}\" has invited you to {recipientActionVerb} the document \"{2}\"."
msgstr "{0} on behalf of \"{1}\" has invited you to {recipientActionVerb} the document \"{2}\"."
#. placeholder {0}: detectedRecipients.length
#: apps/remix/app/components/general/envelope-editor/envelope-editor-recipient-form.tsx
msgid "{0} recipient(s) have been added from AI detection."
msgstr "{0} recipient(s) have been added from AI detection."
#. placeholder {0}: organisation.name
#: apps/remix/app/routes/_authenticated+/o.$orgUrl._index.tsx
msgid "{0} Teams"
@@ -846,9 +879,6 @@ msgid "A request to use your email has been initiated by {0} on Documenso"
msgstr "A request to use your email has been initiated by {0} on Documenso"
#: apps/remix/app/components/dialogs/webhook-create-dialog.tsx
msgid "A secret that will be sent to your URL so you can verify that the request has been sent by Documenso"
msgstr "A secret that will be sent to your URL so you can verify that the request has been sent by Documenso"
#: apps/remix/app/components/dialogs/webhook-edit-dialog.tsx
msgid "A secret that will be sent to your URL so you can verify that the request has been sent by Documenso."
msgstr "A secret that will be sent to your URL so you can verify that the request has been sent by Documenso."
@@ -1283,6 +1313,10 @@ msgstr "After submission, a document will be automatically generated and added t
msgid "AI Features"
msgstr "AI Features"
#: apps/remix/app/components/dialogs/ai-features-enable-dialog.tsx
msgid "AI features are disabled for your team. Please ask your team owner or organisation owner to enable them."
msgstr "AI features are disabled for your team. Please ask your team owner or organisation owner to enable them."
#: apps/remix/app/components/general/document/document-status.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id._index.tsx
msgid "All"
@@ -2260,6 +2294,10 @@ msgstr "Charts"
msgid "Checkbox"
msgstr "Checkbox"
#: packages/ui/primitives/document-flow/field-content.tsx
msgid "Checkbox option"
msgstr "Checkbox option"
#: apps/remix/app/components/general/envelope-editor/envelope-editor-fields-page.tsx
msgid "Checkbox Settings"
msgstr "Checkbox Settings"
@@ -2363,6 +2401,7 @@ msgstr "Client Secret"
msgid "Client secret is required"
msgstr "Client secret is required"
#: apps/remix/app/components/dialogs/ai-features-enable-dialog.tsx
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
@@ -2375,7 +2414,6 @@ msgstr "Client secret is required"
#: apps/remix/app/components/dialogs/team-group-delete-dialog.tsx
#: apps/remix/app/components/dialogs/team-group-update-dialog.tsx
#: apps/remix/app/components/dialogs/team-member-delete-dialog.tsx
#: apps/remix/app/components/dialogs/template-create-dialog.tsx
#: apps/remix/app/components/dialogs/template-use-dialog.tsx
#: apps/remix/app/components/dialogs/webhook-edit-dialog.tsx
#: apps/remix/app/components/dialogs/webhook-test-dialog.tsx
@@ -2386,7 +2424,9 @@ msgstr "Client secret is required"
#: apps/remix/app/components/general/document-signing/document-signing-auth-2fa.tsx
#: apps/remix/app/components/general/document-signing/document-signing-auth-passkey.tsx
#: apps/remix/app/components/general/document/document-recipient-link-copy-dialog.tsx
#: packages/ui/primitives/dialog.tsx
#: packages/ui/primitives/document-flow/missing-signature-field-dialog.tsx
#: packages/ui/primitives/sheet.tsx
msgid "Close"
msgstr "Close"
@@ -3283,8 +3323,8 @@ msgid "Device"
msgstr "Device"
#: packages/email/templates/reset-password.tsx
msgid "Didn't request a password change? We are here to help you secure your account, just <0>contact us.</0>"
msgstr "Didn't request a password change? We are here to help you secure your account, just <0>contact us.</0>"
msgid "Didn't request a password change? We are here to help you secure your account, just <0>contact us</0>."
msgstr "Didn't request a password change? We are here to help you secure your account, just <0>contact us</0>."
#: apps/remix/app/components/general/template/template-direct-link-badge.tsx
#: apps/remix/app/components/tables/templates-table.tsx
@@ -3825,6 +3865,11 @@ msgstr "Domain Name"
msgid "Don't have an account? <0>Sign up</0>"
msgstr "Don't have an account? <0>Sign up</0>"
#: apps/remix/app/components/dialogs/team-delete-dialog.tsx
#: apps/remix/app/components/dialogs/team-delete-dialog.tsx
msgid "Don't transfer (Delete all documents)"
msgstr "Don't transfer (Delete all documents)"
#: apps/remix/app/components/forms/2fa/enable-authenticator-app-dialog.tsx
#: apps/remix/app/components/forms/2fa/view-recovery-codes-dialog.tsx
#: apps/remix/app/components/general/document/document-certificate-qr-view.tsx
@@ -4179,6 +4224,15 @@ msgstr "Enable account"
msgid "Enable Account"
msgstr "Enable Account"
#: apps/remix/app/components/general/envelope-editor/envelope-editor-recipient-form.tsx
msgid "Enable AI detection"
msgstr "Enable AI detection"
#: apps/remix/app/components/dialogs/ai-features-enable-dialog.tsx
#: apps/remix/app/components/dialogs/ai-features-enable-dialog.tsx
msgid "Enable AI features"
msgstr "Enable AI features"
#: apps/remix/app/components/forms/document-preferences-form.tsx
msgid "Enable AI-powered features such as automatic recipient detection. When enabled, document content will be sent to AI providers. We only use providers that do not retain data for training and prefer European regions where available."
msgstr "Enable AI-powered features such as automatic recipient detection. When enabled, document content will be sent to AI providers. We only use providers that do not retain data for training and prefer European regions where available."
@@ -4817,10 +4871,26 @@ msgstr "Go home"
msgid "Go to document"
msgstr "Go to document"
#: packages/ui/primitives/data-table-pagination.tsx
msgid "Go to first page"
msgstr "Go to first page"
#: packages/ui/primitives/data-table-pagination.tsx
msgid "Go to last page"
msgstr "Go to last page"
#: packages/ui/primitives/data-table-pagination.tsx
msgid "Go to next page"
msgstr "Go to next page"
#: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx
msgid "Go to owner"
msgstr "Go to owner"
#: packages/ui/primitives/data-table-pagination.tsx
msgid "Go to previous page"
msgstr "Go to previous page"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl._index.tsx
msgid "Go to team"
msgstr "Go to team"
@@ -4901,8 +4971,8 @@ msgid "Here you can set branding preferences for your organisation. Teams will i
msgstr "Here you can set branding preferences for your organisation. Teams will inherit these settings by default."
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
msgid "Here you can set branding preferences for your team"
msgstr "Here you can set branding preferences for your team"
msgid "Here you can set branding preferences for your team."
msgstr "Here you can set branding preferences for your team."
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.document.tsx
msgid "Here you can set document preferences for your organisation. Teams will inherit these settings by default."
@@ -4917,12 +4987,12 @@ msgid "Here you can set preferences and defaults for your team."
msgstr "Here you can set preferences and defaults for your team."
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
msgid "Here you can set your general branding preferences"
msgstr "Here you can set your general branding preferences"
msgid "Here you can set your general branding preferences."
msgstr "Here you can set your general branding preferences."
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.document.tsx
msgid "Here you can set your general document preferences"
msgstr "Here you can set your general document preferences"
msgid "Here you can set your general document preferences."
msgstr "Here you can set your general document preferences."
#: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx
msgid "Here's how it works:"
@@ -5702,15 +5772,15 @@ msgstr "Member Since"
msgid "Members"
msgstr "Members"
#: apps/remix/app/components/dialogs/envelope-distribute-dialog.tsx
#: apps/remix/app/components/forms/support-ticket-form.tsx
#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx
#: packages/ui/primitives/template-flow/add-template-settings.tsx
msgid "Message"
msgstr "Message"
#: apps/remix/app/components/dialogs/envelope-distribute-dialog.tsx
#: apps/remix/app/components/embed/authoring/configure-document-advanced-settings.tsx
#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx
#: packages/ui/primitives/template-flow/add-template-settings.tsx
msgid "Message <0>(Optional)</0>"
msgstr "Message <0>(Optional)</0>"
@@ -5860,10 +5930,6 @@ msgstr "Never expire"
msgid "New Password"
msgstr "New Password"
#: apps/remix/app/components/dialogs/template-create-dialog.tsx
msgid "New Template"
msgstr "New Template"
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
#: apps/remix/app/components/dialogs/team-member-create-dialog.tsx
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
@@ -6096,8 +6162,8 @@ msgid "Once you have scanned the QR code or entered the code manually, enter the
msgstr "Once you have scanned the QR code or entered the code manually, enter the code provided by your authenticator app below."
#: apps/remix/app/components/dialogs/organisation-email-domain-records-dialog.tsx
msgid "Once you update your DNS records, it may take up to 48 hours for it to be propogated. Once the DNS propagation is complete you will need to come back and press the \"Sync\" domains button"
msgstr "Once you update your DNS records, it may take up to 48 hours for it to be propogated. Once the DNS propagation is complete you will need to come back and press the \"Sync\" domains button"
msgid "Once you update your DNS records, it may take up to 48 hours for it to be propogated. Once the DNS propagation is complete you will need to come back and press the \"Sync\" domains button."
msgstr "Once you update your DNS records, it may take up to 48 hours for it to be propogated. Once the DNS propagation is complete you will need to come back and press the \"Sync\" domains button."
#: packages/lib/constants/template.ts
msgid "Once your template is set up, share the link anywhere you want. The person who opens the link will be able to enter their information in the direct link recipient field and complete any other fields assigned to them."
@@ -6131,6 +6197,10 @@ msgstr "Only PDF files are allowed"
msgid "Oops! Something went wrong."
msgstr "Oops! Something went wrong."
#: apps/remix/app/routes/_authenticated+/o.$orgUrl._index.tsx
msgid "Open menu"
msgstr "Open menu"
#: apps/remix/app/components/general/stack-avatars-with-tooltip.tsx
msgid "Opened"
msgstr "Opened"
@@ -6324,20 +6394,6 @@ msgstr "Ownership transferred to {organisationMemberName}."
msgid "Page {0} of {1}"
msgstr "Page {0} of {1}"
#. placeholder {0}: progress.pagesProcessed
#. placeholder {1}: progress.totalPages
#. placeholder {2}: progress.fieldsDetected
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
msgid "Page {0} of {1} - {2} field(s) found"
msgstr "Page {0} of {1} - {2} field(s) found"
#. placeholder {0}: progress.pagesProcessed
#. placeholder {1}: progress.totalPages
#. placeholder {2}: progress.recipientsDetected
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
msgid "Page {0} of {1} - {2} recipient(s) found"
msgstr "Page {0} of {1} - {2} recipient(s) found"
#. placeholder {0}: i + 1
#: packages/ui/components/pdf-viewer/pdf-viewer-konva.tsx
#: packages/ui/primitives/pdf-viewer/base.tsx
@@ -6683,10 +6739,6 @@ msgstr "Please try a different domain."
msgid "Please try again and make sure you enter the correct email address."
msgstr "Please try again and make sure you enter the correct email address."
#: apps/remix/app/components/dialogs/template-create-dialog.tsx
msgid "Please try again later."
msgstr "Please try again later."
#: packages/ui/components/pdf-viewer/pdf-viewer-konva.tsx
#: packages/ui/components/pdf-viewer/pdf-viewer-konva.tsx
#: packages/ui/primitives/pdf-viewer/base.tsx
@@ -6964,10 +7016,6 @@ msgstr "Recipient updated"
msgid "Recipients"
msgstr "Recipients"
#: apps/remix/app/components/general/envelope-editor/envelope-editor-recipient-form.tsx
msgid "Recipients added"
msgstr "Recipients added"
#: apps/remix/app/routes/_authenticated+/admin+/stats.tsx
msgid "Recipients metrics"
msgstr "Recipients metrics"
@@ -7141,8 +7189,8 @@ msgstr "Reply to email"
#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx
#: packages/ui/primitives/template-flow/add-template-settings.tsx
msgid "Reply To Email"
msgstr "Reply To Email"
msgid "Reply To Email <0>(Optional)</0>"
msgstr "Reply To Email <0>(Optional)</0>"
#: apps/remix/app/components/general/webhook-logs-sheet.tsx
msgid "Request"
@@ -7445,6 +7493,7 @@ msgid "See the background jobs tab for the status"
msgstr "See the background jobs tab for the status"
#: apps/remix/app/components/general/document-signing/document-signing-dropdown-field.tsx
#: packages/ui/primitives/document-flow/field-content.tsx
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx
#: packages/ui/primitives/document-flow/types.ts
msgid "Select"
@@ -8084,7 +8133,6 @@ msgstr "Some signers have not been assigned a signature field. Please assign at
#: apps/remix/app/components/dialogs/team-email-delete-dialog.tsx
#: apps/remix/app/components/dialogs/team-inherit-member-disable-dialog.tsx
#: apps/remix/app/components/dialogs/team-inherit-member-enable-dialog.tsx
#: apps/remix/app/components/dialogs/template-create-dialog.tsx
#: apps/remix/app/components/dialogs/template-delete-dialog.tsx
#: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx
#: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx
@@ -8247,14 +8295,14 @@ msgstr "Stripe customer created successfully"
msgid "Stripe Customer ID"
msgstr "Stripe Customer ID"
#: apps/remix/app/components/dialogs/envelope-distribute-dialog.tsx
#: apps/remix/app/components/forms/support-ticket-form.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx
msgid "Subject"
msgstr "Subject"
#: apps/remix/app/components/dialogs/envelope-distribute-dialog.tsx
#: apps/remix/app/components/embed/authoring/configure-document-advanced-settings.tsx
#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx
#: packages/ui/primitives/template-flow/add-template-settings.tsx
msgid "Subject <0>(Optional)</0>"
msgstr "Subject <0>(Optional)</0>"
@@ -8575,7 +8623,6 @@ msgstr "Teams that this organisation group is currently assigned to"
msgid "Template"
msgstr "Template"
#: apps/remix/app/components/dialogs/template-create-dialog.tsx
#: packages/ui/primitives/document-upload-button.tsx
msgid "Template (Legacy)"
msgstr "Template (Legacy)"
@@ -8588,7 +8635,7 @@ msgstr "Template Created"
msgid "Template deleted"
msgstr "Template deleted"
#: apps/remix/app/components/dialogs/template-create-dialog.tsx
#: apps/remix/app/components/general/document/document-upload-button-legacy.tsx
msgid "Template document uploaded"
msgstr "Template document uploaded"
@@ -8650,10 +8697,6 @@ msgstr "Template uploaded"
msgid "Templates"
msgstr "Templates"
#: apps/remix/app/components/dialogs/template-create-dialog.tsx
msgid "Templates allow you to quickly generate documents with pre-filled recipients and fields."
msgstr "Templates allow you to quickly generate documents with pre-filled recipients and fields."
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id._index.tsx
msgid "Test"
msgstr "Test"
@@ -8821,8 +8864,8 @@ msgid "The email domain you are looking for may have been removed, renamed or ma
msgstr "The email domain you are looking for may have been removed, renamed or may have never existed."
#: apps/remix/app/components/forms/signin.tsx
msgid "The email or password provided is incorrect"
msgstr "The email or password provided is incorrect"
msgid "The email or password provided is incorrect."
msgstr "The email or password provided is incorrect."
#: apps/remix/app/components/dialogs/webhook-create-dialog.tsx
#: apps/remix/app/components/dialogs/webhook-edit-dialog.tsx
@@ -9020,8 +9063,8 @@ msgid "The token you have used to reset your password is either expired or it ne
msgstr "The token you have used to reset your password is either expired or it never existed. If you have still forgotten your password, please request a new reset link."
#: apps/remix/app/components/forms/signin.tsx
msgid "The two-factor authentication code provided is incorrect"
msgstr "The two-factor authentication code provided is incorrect"
msgid "The two-factor authentication code provided is incorrect."
msgstr "The two-factor authentication code provided is incorrect."
#: apps/remix/app/components/forms/editor/editor-field-signature-form.tsx
msgid "The typed signature font size"
@@ -9191,8 +9234,8 @@ msgid "This document was created using a direct link."
msgstr "This document was created using a direct link."
#: packages/email/template-components/template-footer.tsx
msgid "This document was sent using <0>Documenso.</0>"
msgstr "This document was sent using <0>Documenso.</0>"
msgid "This document was sent using <0>Documenso</0>."
msgstr "This document was sent using <0>Documenso</0>."
#: apps/remix/app/components/dialogs/envelope-duplicate-dialog.tsx
msgid "This document will be duplicated."
@@ -9523,6 +9566,10 @@ msgstr "Total Signers that Signed Up"
msgid "Total Users"
msgstr "Total Users"
#: apps/remix/app/components/dialogs/team-delete-dialog.tsx
msgid "Transfer documents to a different team"
msgstr "Transfer documents to a different team"
#: apps/remix/app/components/dialogs/webhook-create-dialog.tsx
#: apps/remix/app/components/dialogs/webhook-edit-dialog.tsx
msgid "Triggers"
@@ -9535,6 +9582,10 @@ msgstr "Triggers"
msgid "Try again"
msgstr "Try again"
#: apps/remix/app/components/dialogs/ai-features-enable-dialog.tsx
msgid "Turn on AI detection to automatically find recipients and fields in your documents. AI providers do not retain your data for training."
msgstr "Turn on AI detection to automatically find recipients and fields in your documents. AI providers do not retain your data for training."
#: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx
msgid "Two factor authentication"
msgstr "Two factor authentication"
@@ -9681,6 +9732,10 @@ msgstr "Unauthorized"
msgid "Uncompleted"
msgstr "Uncompleted"
#: packages/ui/primitives/signature-pad/signature-pad-draw.tsx
msgid "Undo"
msgstr "Undo"
#: apps/remix/app/routes/_authenticated+/settings+/security.linked-accounts.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
@@ -10293,8 +10348,8 @@ msgstr "Waiting for Your Turn"
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
msgid "Want to send slick signing links like this one? <0>Check out Documenso.</0>"
msgstr "Want to send slick signing links like this one? <0>Check out Documenso.</0>"
msgid "Want to send slick signing links like this one? <0>Check out Documenso</0>."
msgstr "Want to send slick signing links like this one? <0>Check out Documenso</0>."
#: apps/remix/app/routes/_profile+/_layout.tsx
msgid "Want your own public profile?"
@@ -10329,6 +10384,10 @@ msgstr "We are unable to update this passkey at the moment. Please try again lat
msgid "We couldn't create a Stripe customer. Please try again."
msgstr "We couldn't create a Stripe customer. Please try again."
#: apps/remix/app/components/dialogs/ai-features-enable-dialog.tsx
msgid "We couldn't enable AI features right now. Please try again."
msgstr "We couldn't enable AI features right now. Please try again."
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.groups.$id.tsx
msgid "We couldn't update the group. Please try again."
msgstr "We couldn't update the group. Please try again."
@@ -10529,16 +10588,6 @@ msgstr "We encountered an unknown error while attempting update the team email.
msgid "We encountered an unknown error while attempting update your profile. Please try again later."
msgstr "We encountered an unknown error while attempting update your profile. Please try again later."
#. placeholder {0}: detectedFields.length
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
msgid "We found {0} field(s) in your document."
msgstr "We found {0} field(s) in your document."
#. placeholder {0}: detectedRecipients.length
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
msgid "We found {0} recipient(s) in your document."
msgstr "We found {0} recipient(s) in your document."
#: apps/remix/app/components/dialogs/team-email-add-dialog.tsx
msgid "We have sent a confirmation email for verification."
msgstr "We have sent a confirmation email for verification."
@@ -10900,13 +10949,13 @@ msgid "You are currently updating <0>{0}</0>"
msgstr "You are currently updating <0>{0}</0>"
#: apps/remix/app/components/dialogs/team-member-update-dialog.tsx
msgid "You are currently updating <0>{memberName}.</0>"
msgstr "You are currently updating <0>{memberName}.</0>"
msgid "You are currently updating <0>{memberName}</0>."
msgstr "You are currently updating <0>{memberName}</0>."
#: apps/remix/app/components/dialogs/admin-organisation-member-update-dialog.tsx
#: apps/remix/app/components/dialogs/organisation-member-update-dialog.tsx
msgid "You are currently updating <0>{organisationMemberName}.</0>"
msgstr "You are currently updating <0>{organisationMemberName}.</0>"
msgid "You are currently updating <0>{organisationMemberName}</0>."
msgstr "You are currently updating <0>{organisationMemberName}</0>."
#: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx
msgid "You are currently updating the <0>{passkeyName}</0> passkey."
@@ -10967,16 +11016,16 @@ msgstr "You can enable access to allow all organisation members to access this t
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.email.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.email.tsx
msgid "You can manage your email preferences here"
msgstr "You can manage your email preferences here"
msgid "You can manage your email preferences here."
msgstr "You can manage your email preferences here."
#: apps/remix/app/components/general/envelope-editor/envelope-editor-fields-page.tsx
msgid "You can only detect fields in draft envelopes"
msgstr "You can only detect fields in draft envelopes"
#: packages/email/templates/confirm-team-email.tsx
msgid "You can revoke access at any time in your team settings on Documenso <0>here.</0>"
msgstr "You can revoke access at any time in your team settings on Documenso <0>here.</0>"
msgid "You can revoke access at any time in your team settings on Documenso <0>here</0>."
msgstr "You can revoke access at any time in your team settings on Documenso <0>here</0>."
#: apps/remix/app/components/forms/public-profile-form.tsx
msgid "You can update the profile URL by updating the team URL in the general settings page."
@@ -11009,8 +11058,8 @@ msgid "You cannot delete a group which has a higher role than you."
msgstr "You cannot delete a group which has a higher role than you."
#: apps/remix/app/components/dialogs/envelope-item-delete-dialog.tsx
msgid "You cannot delete this item because the document has been sent to recipients"
msgstr "You cannot delete this item because the document has been sent to recipients"
msgid "You cannot delete this item because the document has been sent to recipients."
msgstr "You cannot delete this item because the document has been sent to recipients."
#: apps/remix/app/components/dialogs/team-group-update-dialog.tsx
msgid "You cannot modify a group which has a higher role than you."
@@ -11036,8 +11085,8 @@ msgstr "You cannot upload documents at this time."
#: apps/remix/app/components/general/document/document-upload-button-legacy.tsx
#: apps/remix/app/components/general/envelope/envelope-drop-zone-wrapper.tsx
#: apps/remix/app/components/general/envelope/envelope-upload-button.tsx
msgid "You cannot upload encrypted PDFs"
msgstr "You cannot upload encrypted PDFs"
msgid "You cannot upload encrypted PDFs."
msgstr "You cannot upload encrypted PDFs."
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.billing.tsx
msgid "You currently have an inactive <0>{currentProductName}</0> subscription"
@@ -11048,8 +11097,8 @@ msgid "You currently have no access to any teams within this organisation. Pleas
msgstr "You currently have no access to any teams within this organisation. Please contact your organisation to request access."
#: apps/remix/app/components/forms/token.tsx
msgid "You do not have permission to create a token for this team"
msgstr "You do not have permission to create a token for this team"
msgid "You do not have permission to create a token for this team."
msgstr "You do not have permission to create a token for this team."
#: apps/remix/app/components/tables/user-billing-organisations-table.tsx
msgid "You don't manage billing for any organisations."
@@ -11118,8 +11167,8 @@ msgstr "You have not yet created or received any documents. To create a document
#: apps/remix/app/components/general/document/document-upload-button-legacy.tsx
#: apps/remix/app/components/general/envelope/envelope-drop-zone-wrapper.tsx
#: apps/remix/app/components/general/envelope/envelope-upload-button.tsx
msgid "You have reached the limit of the number of files per envelope"
msgstr "You have reached the limit of the number of files per envelope"
msgid "You have reached the limit of the number of files per envelope."
msgstr "You have reached the limit of the number of files per envelope."
#. placeholder {0}: quota.directTemplates
#: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx
@@ -11274,6 +11323,10 @@ msgstr "You will now be required to enter a code from your authenticator app whe
msgid "You will receive an email copy of the signed document once everyone has signed."
msgstr "You will receive an email copy of the signed document once everyone has signed."
#: apps/remix/app/components/dialogs/ai-features-enable-dialog.tsx
msgid "You're an admin. You can enable AI features for this team right away. Everyone on the team will see AI detection once enabled."
msgstr "You're an admin. You can enable AI features for this team right away. Everyone on the team will see AI detection once enabled."
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
msgid "You've made too many detection requests. Please wait a minute before trying again."
@@ -11332,6 +11385,10 @@ msgstr "Your current plan is past due."
msgid "Your direct signing templates"
msgstr "Your direct signing templates"
#: apps/remix/app/components/dialogs/ai-features-enable-dialog.tsx
msgid "Your document content will be sent securely to our AI provider solely for detection and will not be stored or used for training."
msgstr "Your document content will be sent securely to our AI provider solely for detection and will not be stored or used for training."
#: apps/remix/app/components/embed/authoring/configure-document-upload.tsx
#: apps/remix/app/components/general/document/document-upload-button-legacy.tsx
msgid "Your document failed to upload."
@@ -11367,7 +11424,7 @@ msgstr "Your document has been successfully duplicated."
msgid "Your document has been uploaded successfully."
msgstr "Your document has been uploaded successfully."
#: apps/remix/app/components/dialogs/template-create-dialog.tsx
#: apps/remix/app/components/general/document/document-upload-button-legacy.tsx
msgid "Your document has been uploaded successfully. You will be redirected to the template page."
msgstr "Your document has been uploaded successfully. You will be redirected to the template page."
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+153 -97
View File
@@ -96,6 +96,11 @@ msgstr "{0, plural, one {# campo} other {# campos}}"
msgid "{0, plural, one {# folder} other {# folders}}"
msgstr "{0, plural, one {# pasta} other {# pastas}}"
#. placeholder {0}: detectedRecipients.length
#: apps/remix/app/components/general/envelope-editor/envelope-editor-recipient-form.tsx
msgid "{0, plural, one {# recipient have been added from AI detection.} other {# recipients have been added from AI detection.}}"
msgstr ""
#. placeholder {0}: template.recipients.length
#: apps/remix/app/routes/_recipient+/d.$token+/_index.tsx
msgid "{0, plural, one {# recipient} other {# recipients}}"
@@ -146,11 +151,44 @@ msgstr "{0, plural, one {1 campo correspondente} other {# campos correspondentes
msgid "{0, plural, one {1 Recipient} other {# Recipients}}"
msgstr "{0, plural, one {1 Destinatário} other {# Destinatários}}"
#. placeholder {0}: progress.fieldsDetected
#. placeholder {1}: progress.pagesProcessed
#. placeholder {2}: progress.totalPages
#. placeholder {3}: progress.pagesProcessed
#. placeholder {4}: progress.totalPages
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
msgid "{0, plural, one {Page {1} of {2} - # field found} other {Page {3} of {4} - # fields found}}"
msgstr ""
#. placeholder {0}: progress.recipientsDetected
#. placeholder {1}: progress.pagesProcessed
#. placeholder {2}: progress.totalPages
#. placeholder {3}: progress.pagesProcessed
#. placeholder {4}: progress.totalPages
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
msgid "{0, plural, one {Page {1} of {2} - # recipient found} other {Page {3} of {4} - # recipients found}}"
msgstr ""
#. placeholder {0}: detectedRecipients.length
#: apps/remix/app/components/general/envelope-editor/envelope-editor-recipient-form.tsx
msgid "{0, plural, one {Recipient added} other {Recipients added}}"
msgstr ""
#. placeholder {0}: pendingRecipients.length
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id._index.tsx
msgid "{0, plural, one {Waiting on 1 recipient} other {Waiting on # recipients}}"
msgstr "{0, plural, one {Aguardando 1 destinatário} other {Aguardando # destinatários}}"
#. placeholder {0}: detectedFields.length
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
msgid "{0, plural, one {We found # field in your document.} other {We found # fields in your document.}}"
msgstr ""
#. placeholder {0}: detectedRecipients.length
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
msgid "{0, plural, one {We found # recipient in your document.} other {We found # recipients in your document.}}"
msgstr ""
#. placeholder {0}: _(FRIENDLY_FIELD_TYPE[fieldType as FieldType])
#. placeholder {0}: route.label
#: apps/remix/app/components/general/document-signing/document-signing-auto-sign.tsx
@@ -195,11 +233,6 @@ msgstr "{0} de {1} documentos restantes este mês."
msgid "{0} on behalf of \"{1}\" has invited you to {recipientActionVerb} the document \"{2}\"."
msgstr "{0} em nome de \"{1}\" convidou você para {recipientActionVerb} o documento \"{2}\"."
#. placeholder {0}: detectedRecipients.length
#: apps/remix/app/components/general/envelope-editor/envelope-editor-recipient-form.tsx
msgid "{0} recipient(s) have been added from AI detection."
msgstr "{0} destinatário(s) foram adicionados pela detecção de IA."
#. placeholder {0}: organisation.name
#: apps/remix/app/routes/_authenticated+/o.$orgUrl._index.tsx
msgid "{0} Teams"
@@ -846,9 +879,6 @@ msgid "A request to use your email has been initiated by {0} on Documenso"
msgstr "Uma solicitação para usar seu e-mail foi iniciada por {0} no Documenso"
#: apps/remix/app/components/dialogs/webhook-create-dialog.tsx
msgid "A secret that will be sent to your URL so you can verify that the request has been sent by Documenso"
msgstr "Um segredo que será enviado para sua URL para que você possa verificar se a solicitação foi enviada pelo Documenso"
#: apps/remix/app/components/dialogs/webhook-edit-dialog.tsx
msgid "A secret that will be sent to your URL so you can verify that the request has been sent by Documenso."
msgstr "Um segredo que será enviado para sua URL para que você possa verificar se a solicitação foi enviada pelo Documenso."
@@ -1283,6 +1313,10 @@ msgstr "Após o envio, um documento será gerado automaticamente e adicionado à
msgid "AI Features"
msgstr "Recursos de IA"
#: apps/remix/app/components/dialogs/ai-features-enable-dialog.tsx
msgid "AI features are disabled for your team. Please ask your team owner or organisation owner to enable them."
msgstr ""
#: apps/remix/app/components/general/document/document-status.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id._index.tsx
msgid "All"
@@ -2260,6 +2294,10 @@ msgstr "Gráficos"
msgid "Checkbox"
msgstr "Caixa de seleção"
#: packages/ui/primitives/document-flow/field-content.tsx
msgid "Checkbox option"
msgstr ""
#: apps/remix/app/components/general/envelope-editor/envelope-editor-fields-page.tsx
msgid "Checkbox Settings"
msgstr "Configurações da Caixa de Seleção"
@@ -2363,6 +2401,7 @@ msgstr "Segredo do Cliente"
msgid "Client secret is required"
msgstr "Segredo do Cliente é obrigatório"
#: apps/remix/app/components/dialogs/ai-features-enable-dialog.tsx
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
@@ -2375,7 +2414,6 @@ msgstr "Segredo do Cliente é obrigatório"
#: apps/remix/app/components/dialogs/team-group-delete-dialog.tsx
#: apps/remix/app/components/dialogs/team-group-update-dialog.tsx
#: apps/remix/app/components/dialogs/team-member-delete-dialog.tsx
#: apps/remix/app/components/dialogs/template-create-dialog.tsx
#: apps/remix/app/components/dialogs/template-use-dialog.tsx
#: apps/remix/app/components/dialogs/webhook-edit-dialog.tsx
#: apps/remix/app/components/dialogs/webhook-test-dialog.tsx
@@ -2386,7 +2424,9 @@ msgstr "Segredo do Cliente é obrigatório"
#: apps/remix/app/components/general/document-signing/document-signing-auth-2fa.tsx
#: apps/remix/app/components/general/document-signing/document-signing-auth-passkey.tsx
#: apps/remix/app/components/general/document/document-recipient-link-copy-dialog.tsx
#: packages/ui/primitives/dialog.tsx
#: packages/ui/primitives/document-flow/missing-signature-field-dialog.tsx
#: packages/ui/primitives/sheet.tsx
msgid "Close"
msgstr "Fechar"
@@ -3283,8 +3323,8 @@ msgid "Device"
msgstr "Dispositivo"
#: packages/email/templates/reset-password.tsx
msgid "Didn't request a password change? We are here to help you secure your account, just <0>contact us.</0>"
msgstr "Não solicitou uma alteração de senha? Estamos aqui para ajudá-lo a proteger sua conta, basta <0>entrar em contato conosco.</0>"
msgid "Didn't request a password change? We are here to help you secure your account, just <0>contact us</0>."
msgstr ""
#: apps/remix/app/components/general/template/template-direct-link-badge.tsx
#: apps/remix/app/components/tables/templates-table.tsx
@@ -3825,6 +3865,11 @@ msgstr "Nome do Domínio"
msgid "Don't have an account? <0>Sign up</0>"
msgstr "Não tem uma conta? <0>Inscreva-se</0>"
#: apps/remix/app/components/dialogs/team-delete-dialog.tsx
#: apps/remix/app/components/dialogs/team-delete-dialog.tsx
msgid "Don't transfer (Delete all documents)"
msgstr ""
#: apps/remix/app/components/forms/2fa/enable-authenticator-app-dialog.tsx
#: apps/remix/app/components/forms/2fa/view-recovery-codes-dialog.tsx
#: apps/remix/app/components/general/document/document-certificate-qr-view.tsx
@@ -4179,6 +4224,15 @@ msgstr "Ativar conta"
msgid "Enable Account"
msgstr "Ativar Conta"
#: apps/remix/app/components/general/envelope-editor/envelope-editor-recipient-form.tsx
msgid "Enable AI detection"
msgstr ""
#: apps/remix/app/components/dialogs/ai-features-enable-dialog.tsx
#: apps/remix/app/components/dialogs/ai-features-enable-dialog.tsx
msgid "Enable AI features"
msgstr ""
#: apps/remix/app/components/forms/document-preferences-form.tsx
msgid "Enable AI-powered features such as automatic recipient detection. When enabled, document content will be sent to AI providers. We only use providers that do not retain data for training and prefer European regions where available."
msgstr "Ative recursos com tecnologia de IA, como detecção automática de destinatários. Quando ativado, o conteúdo do documento será enviado para provedores de IA. Usamos apenas provedores que não retêm dados para treinamento e preferimos regiões europeias quando disponíveis."
@@ -4817,10 +4871,26 @@ msgstr "Ir para o início"
msgid "Go to document"
msgstr "Ir para o documento"
#: packages/ui/primitives/data-table-pagination.tsx
msgid "Go to first page"
msgstr ""
#: packages/ui/primitives/data-table-pagination.tsx
msgid "Go to last page"
msgstr ""
#: packages/ui/primitives/data-table-pagination.tsx
msgid "Go to next page"
msgstr ""
#: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx
msgid "Go to owner"
msgstr "Ir para o proprietário"
#: packages/ui/primitives/data-table-pagination.tsx
msgid "Go to previous page"
msgstr ""
#: apps/remix/app/routes/_authenticated+/o.$orgUrl._index.tsx
msgid "Go to team"
msgstr "Ir para a equipe"
@@ -4901,8 +4971,8 @@ msgid "Here you can set branding preferences for your organisation. Teams will i
msgstr "Aqui você pode definir preferências de marca para sua organização. As equipes herdarão essas configurações por padrão."
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
msgid "Here you can set branding preferences for your team"
msgstr "Aqui você pode definir preferências de marca para sua equipe"
msgid "Here you can set branding preferences for your team."
msgstr ""
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.document.tsx
msgid "Here you can set document preferences for your organisation. Teams will inherit these settings by default."
@@ -4917,12 +4987,12 @@ msgid "Here you can set preferences and defaults for your team."
msgstr "Aqui você pode definir preferências e padrões para sua equipe."
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.branding.tsx
msgid "Here you can set your general branding preferences"
msgstr "Aqui você pode definir suas preferências gerais de marca"
msgid "Here you can set your general branding preferences."
msgstr ""
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.document.tsx
msgid "Here you can set your general document preferences"
msgstr "Aqui você pode definir suas preferências gerais de documento"
msgid "Here you can set your general document preferences."
msgstr ""
#: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx
msgid "Here's how it works:"
@@ -5702,15 +5772,15 @@ msgstr "Membro Desde"
msgid "Members"
msgstr "Membros"
#: apps/remix/app/components/dialogs/envelope-distribute-dialog.tsx
#: apps/remix/app/components/forms/support-ticket-form.tsx
#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx
#: packages/ui/primitives/template-flow/add-template-settings.tsx
msgid "Message"
msgstr "Mensagem"
#: apps/remix/app/components/dialogs/envelope-distribute-dialog.tsx
#: apps/remix/app/components/embed/authoring/configure-document-advanced-settings.tsx
#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx
#: packages/ui/primitives/template-flow/add-template-settings.tsx
msgid "Message <0>(Optional)</0>"
msgstr "Mensagem <0>(Opcional)</0>"
@@ -5860,10 +5930,6 @@ msgstr "Nunca expirar"
msgid "New Password"
msgstr "Nova Senha"
#: apps/remix/app/components/dialogs/template-create-dialog.tsx
msgid "New Template"
msgstr "Novo Modelo"
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
#: apps/remix/app/components/dialogs/team-member-create-dialog.tsx
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
@@ -6096,8 +6162,8 @@ msgid "Once you have scanned the QR code or entered the code manually, enter the
msgstr "Depois de escanear o código QR ou inserir o código manualmente, insira o código fornecido pelo seu aplicativo autenticador abaixo."
#: apps/remix/app/components/dialogs/organisation-email-domain-records-dialog.tsx
msgid "Once you update your DNS records, it may take up to 48 hours for it to be propogated. Once the DNS propagation is complete you will need to come back and press the \"Sync\" domains button"
msgstr "Depois de atualizar seus registros DNS, pode levar até 48 horas para que eles sejam propagados. Assim que a propagação do DNS estiver concluída, você precisará voltar e pressionar o botão \"Sincronizar\" domínios"
msgid "Once you update your DNS records, it may take up to 48 hours for it to be propogated. Once the DNS propagation is complete you will need to come back and press the \"Sync\" domains button."
msgstr ""
#: packages/lib/constants/template.ts
msgid "Once your template is set up, share the link anywhere you want. The person who opens the link will be able to enter their information in the direct link recipient field and complete any other fields assigned to them."
@@ -6131,6 +6197,10 @@ msgstr "Apenas arquivos PDF são permitidos"
msgid "Oops! Something went wrong."
msgstr "Ops! Algo deu errado."
#: apps/remix/app/routes/_authenticated+/o.$orgUrl._index.tsx
msgid "Open menu"
msgstr ""
#: apps/remix/app/components/general/stack-avatars-with-tooltip.tsx
msgid "Opened"
msgstr "Aberto"
@@ -6324,20 +6394,6 @@ msgstr "Propriedade transferida para {organisationMemberName}."
msgid "Page {0} of {1}"
msgstr "Página {0} de {1}"
#. placeholder {0}: progress.pagesProcessed
#. placeholder {1}: progress.totalPages
#. placeholder {2}: progress.fieldsDetected
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
msgid "Page {0} of {1} - {2} field(s) found"
msgstr "Página {0} de {1} - {2} campo(s) encontrado(s)"
#. placeholder {0}: progress.pagesProcessed
#. placeholder {1}: progress.totalPages
#. placeholder {2}: progress.recipientsDetected
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
msgid "Page {0} of {1} - {2} recipient(s) found"
msgstr "Página {0} de {1} - {2} destinatário(s) encontrado(s)"
#. placeholder {0}: i + 1
#: packages/ui/components/pdf-viewer/pdf-viewer-konva.tsx
#: packages/ui/primitives/pdf-viewer/base.tsx
@@ -6683,10 +6739,6 @@ msgstr "Por favor, tente um domínio diferente."
msgid "Please try again and make sure you enter the correct email address."
msgstr "Por favor, tente novamente e certifique-se de inserir o endereço de e-mail correto."
#: apps/remix/app/components/dialogs/template-create-dialog.tsx
msgid "Please try again later."
msgstr "Por favor, tente novamente mais tarde."
#: packages/ui/components/pdf-viewer/pdf-viewer-konva.tsx
#: packages/ui/components/pdf-viewer/pdf-viewer-konva.tsx
#: packages/ui/primitives/pdf-viewer/base.tsx
@@ -6964,10 +7016,6 @@ msgstr "Destinatário atualizado"
msgid "Recipients"
msgstr "Destinatários"
#: apps/remix/app/components/general/envelope-editor/envelope-editor-recipient-form.tsx
msgid "Recipients added"
msgstr "Destinatários adicionados"
#: apps/remix/app/routes/_authenticated+/admin+/stats.tsx
msgid "Recipients metrics"
msgstr "Métricas de destinatários"
@@ -7141,8 +7189,8 @@ msgstr "Responder ao e-mail"
#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx
#: packages/ui/primitives/template-flow/add-template-settings.tsx
msgid "Reply To Email"
msgstr "Responder Para E-mail"
msgid "Reply To Email <0>(Optional)</0>"
msgstr ""
#: apps/remix/app/components/general/webhook-logs-sheet.tsx
msgid "Request"
@@ -7445,6 +7493,7 @@ msgid "See the background jobs tab for the status"
msgstr "Veja a aba de trabalhos em segundo plano para o status"
#: apps/remix/app/components/general/document-signing/document-signing-dropdown-field.tsx
#: packages/ui/primitives/document-flow/field-content.tsx
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx
#: packages/ui/primitives/document-flow/types.ts
msgid "Select"
@@ -8084,7 +8133,6 @@ msgstr "Alguns signatários não receberam um campo de assinatura. Por favor, at
#: apps/remix/app/components/dialogs/team-email-delete-dialog.tsx
#: apps/remix/app/components/dialogs/team-inherit-member-disable-dialog.tsx
#: apps/remix/app/components/dialogs/team-inherit-member-enable-dialog.tsx
#: apps/remix/app/components/dialogs/template-create-dialog.tsx
#: apps/remix/app/components/dialogs/template-delete-dialog.tsx
#: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx
#: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx
@@ -8247,14 +8295,14 @@ msgstr "Cliente Stripe criado com sucesso"
msgid "Stripe Customer ID"
msgstr "ID do Cliente Stripe"
#: apps/remix/app/components/dialogs/envelope-distribute-dialog.tsx
#: apps/remix/app/components/forms/support-ticket-form.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx
msgid "Subject"
msgstr "Assunto"
#: apps/remix/app/components/dialogs/envelope-distribute-dialog.tsx
#: apps/remix/app/components/embed/authoring/configure-document-advanced-settings.tsx
#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
#: packages/ui/primitives/document-flow/add-subject.tsx
#: packages/ui/primitives/template-flow/add-template-settings.tsx
msgid "Subject <0>(Optional)</0>"
msgstr "Assunto <0>(Opcional)</0>"
@@ -8575,7 +8623,6 @@ msgstr "Equipes às quais este grupo da organização está atualmente atribuíd
msgid "Template"
msgstr "Modelo"
#: apps/remix/app/components/dialogs/template-create-dialog.tsx
#: packages/ui/primitives/document-upload-button.tsx
msgid "Template (Legacy)"
msgstr "Modelo (Legado)"
@@ -8588,7 +8635,7 @@ msgstr "Modelo Criado"
msgid "Template deleted"
msgstr "Modelo excluído"
#: apps/remix/app/components/dialogs/template-create-dialog.tsx
#: apps/remix/app/components/general/document/document-upload-button-legacy.tsx
msgid "Template document uploaded"
msgstr "Documento de modelo enviado"
@@ -8650,10 +8697,6 @@ msgstr "Modelo enviado"
msgid "Templates"
msgstr "Modelos"
#: apps/remix/app/components/dialogs/template-create-dialog.tsx
msgid "Templates allow you to quickly generate documents with pre-filled recipients and fields."
msgstr "Modelos permitem gerar documentos rapidamente com destinatários e campos pré-preenchidos."
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id._index.tsx
msgid "Test"
msgstr "Teste"
@@ -8821,8 +8864,8 @@ msgid "The email domain you are looking for may have been removed, renamed or ma
msgstr "O domínio de e-mail que você está procurando pode ter sido removido, renomeado ou nunca ter existido."
#: apps/remix/app/components/forms/signin.tsx
msgid "The email or password provided is incorrect"
msgstr "O e-mail ou senha fornecidos estão incorretos"
msgid "The email or password provided is incorrect."
msgstr ""
#: apps/remix/app/components/dialogs/webhook-create-dialog.tsx
#: apps/remix/app/components/dialogs/webhook-edit-dialog.tsx
@@ -9020,8 +9063,8 @@ msgid "The token you have used to reset your password is either expired or it ne
msgstr "O token que você usou para redefinir sua senha expirou ou nunca existiu. Se você ainda esqueceu sua senha, solicite um novo link de redefinição."
#: apps/remix/app/components/forms/signin.tsx
msgid "The two-factor authentication code provided is incorrect"
msgstr "O código de autenticação de dois fatores fornecido está incorreto"
msgid "The two-factor authentication code provided is incorrect."
msgstr ""
#: apps/remix/app/components/forms/editor/editor-field-signature-form.tsx
msgid "The typed signature font size"
@@ -9099,7 +9142,6 @@ msgstr "Esta conta foi desativada. Entre em contato com o suporte."
#: apps/remix/app/components/forms/signin.tsx
msgid "This account has not been verified. Please verify your account before signing in."
msgstr "Esta conta não foi verificada. Verifique sua conta antes de entrar."
msgstr "Esta conta não foi verificada. Verifique sua conta antes de entrar."
#: apps/remix/app/components/dialogs/admin-user-reset-two-factor-dialog.tsx
msgid "This action is irreversible. Please ensure you have informed the user before proceeding."
@@ -9192,8 +9234,8 @@ msgid "This document was created using a direct link."
msgstr "Este documento foi criado usando um link direto."
#: packages/email/template-components/template-footer.tsx
msgid "This document was sent using <0>Documenso.</0>"
msgstr "Este documento foi enviado usando <0>Documenso.</0>"
msgid "This document was sent using <0>Documenso</0>."
msgstr ""
#: apps/remix/app/components/dialogs/envelope-duplicate-dialog.tsx
msgid "This document will be duplicated."
@@ -9524,6 +9566,10 @@ msgstr "Total de Signatários que se Inscreveram"
msgid "Total Users"
msgstr "Total de Usuários"
#: apps/remix/app/components/dialogs/team-delete-dialog.tsx
msgid "Transfer documents to a different team"
msgstr ""
#: apps/remix/app/components/dialogs/webhook-create-dialog.tsx
#: apps/remix/app/components/dialogs/webhook-edit-dialog.tsx
msgid "Triggers"
@@ -9536,6 +9582,10 @@ msgstr "Gatilhos"
msgid "Try again"
msgstr "Tente novamente"
#: apps/remix/app/components/dialogs/ai-features-enable-dialog.tsx
msgid "Turn on AI detection to automatically find recipients and fields in your documents. AI providers do not retain your data for training."
msgstr ""
#: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx
msgid "Two factor authentication"
msgstr "Autenticação de dois fatores"
@@ -9682,6 +9732,10 @@ msgstr "Não autorizado"
msgid "Uncompleted"
msgstr "Não concluído"
#: packages/ui/primitives/signature-pad/signature-pad-draw.tsx
msgid "Undo"
msgstr ""
#: apps/remix/app/routes/_authenticated+/settings+/security.linked-accounts.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
@@ -10294,8 +10348,8 @@ msgstr "Aguardando Sua Vez"
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
#: apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx
msgid "Want to send slick signing links like this one? <0>Check out Documenso.</0>"
msgstr "Quer enviar links de assinatura elegantes como este? <0>Confira o Documenso.</0>"
msgid "Want to send slick signing links like this one? <0>Check out Documenso</0>."
msgstr ""
#: apps/remix/app/routes/_profile+/_layout.tsx
msgid "Want your own public profile?"
@@ -10330,6 +10384,10 @@ msgstr "Não conseguimos atualizar esta passkey no momento. Por favor, tente nov
msgid "We couldn't create a Stripe customer. Please try again."
msgstr "Não conseguimos criar um cliente Stripe. Por favor, tente novamente."
#: apps/remix/app/components/dialogs/ai-features-enable-dialog.tsx
msgid "We couldn't enable AI features right now. Please try again."
msgstr ""
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.groups.$id.tsx
msgid "We couldn't update the group. Please try again."
msgstr "Não conseguimos atualizar o grupo. Por favor, tente novamente."
@@ -10530,16 +10588,6 @@ msgstr "Encontramos um erro desconhecido ao tentar atualizar o e-mail da equipe.
msgid "We encountered an unknown error while attempting update your profile. Please try again later."
msgstr "Encontramos um erro desconhecido ao tentar atualizar seu perfil. Por favor, tente novamente mais tarde."
#. placeholder {0}: detectedFields.length
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
msgid "We found {0} field(s) in your document."
msgstr "Encontramos {0} campo(s) no seu documento."
#. placeholder {0}: detectedRecipients.length
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
msgid "We found {0} recipient(s) in your document."
msgstr "Encontramos {0} destinatário(s) no seu documento."
#: apps/remix/app/components/dialogs/team-email-add-dialog.tsx
msgid "We have sent a confirmation email for verification."
msgstr "Enviamos um e-mail de confirmação para verificação."
@@ -10901,13 +10949,13 @@ msgid "You are currently updating <0>{0}</0>"
msgstr "Você está atualizando <0>{0}</0>"
#: apps/remix/app/components/dialogs/team-member-update-dialog.tsx
msgid "You are currently updating <0>{memberName}.</0>"
msgstr "Você está atualizando <0>{memberName}.</0>"
msgid "You are currently updating <0>{memberName}</0>."
msgstr ""
#: apps/remix/app/components/dialogs/admin-organisation-member-update-dialog.tsx
#: apps/remix/app/components/dialogs/organisation-member-update-dialog.tsx
msgid "You are currently updating <0>{organisationMemberName}.</0>"
msgstr "Você está atualizando <0>{organisationMemberName}.</0>"
msgid "You are currently updating <0>{organisationMemberName}</0>."
msgstr ""
#: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx
msgid "You are currently updating the <0>{passkeyName}</0> passkey."
@@ -10968,16 +11016,16 @@ msgstr "Você pode ativar o acesso para permitir que todos os membros da organiz
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.email.tsx
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.email.tsx
msgid "You can manage your email preferences here"
msgstr "Você pode gerenciar suas preferências de e-mail aqui"
msgid "You can manage your email preferences here."
msgstr ""
#: apps/remix/app/components/general/envelope-editor/envelope-editor-fields-page.tsx
msgid "You can only detect fields in draft envelopes"
msgstr "Você só pode detectar campos em envelopes de rascunho"
#: packages/email/templates/confirm-team-email.tsx
msgid "You can revoke access at any time in your team settings on Documenso <0>here.</0>"
msgstr "Você pode revogar o acesso a qualquer momento nas configurações da sua equipe no Documenso <0>aqui.</0>"
msgid "You can revoke access at any time in your team settings on Documenso <0>here</0>."
msgstr ""
#: apps/remix/app/components/forms/public-profile-form.tsx
msgid "You can update the profile URL by updating the team URL in the general settings page."
@@ -11010,8 +11058,8 @@ msgid "You cannot delete a group which has a higher role than you."
msgstr "Você não pode excluir um grupo que tem uma função superior à sua."
#: apps/remix/app/components/dialogs/envelope-item-delete-dialog.tsx
msgid "You cannot delete this item because the document has been sent to recipients"
msgstr "Você não pode excluir este item porque o documento foi enviado aos destinatários"
msgid "You cannot delete this item because the document has been sent to recipients."
msgstr ""
#: apps/remix/app/components/dialogs/team-group-update-dialog.tsx
msgid "You cannot modify a group which has a higher role than you."
@@ -11037,8 +11085,8 @@ msgstr "Você não pode enviar documentos neste momento."
#: apps/remix/app/components/general/document/document-upload-button-legacy.tsx
#: apps/remix/app/components/general/envelope/envelope-drop-zone-wrapper.tsx
#: apps/remix/app/components/general/envelope/envelope-upload-button.tsx
msgid "You cannot upload encrypted PDFs"
msgstr "Você não pode enviar PDFs criptografados"
msgid "You cannot upload encrypted PDFs."
msgstr ""
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.billing.tsx
msgid "You currently have an inactive <0>{currentProductName}</0> subscription"
@@ -11049,8 +11097,8 @@ msgid "You currently have no access to any teams within this organisation. Pleas
msgstr "Você atualmente não tem acesso a nenhuma equipe dentro desta organização. Por favor, entre em contato com sua organização para solicitar acesso."
#: apps/remix/app/components/forms/token.tsx
msgid "You do not have permission to create a token for this team"
msgstr "Você não tem permissão para criar um token para esta equipe"
msgid "You do not have permission to create a token for this team."
msgstr ""
#: apps/remix/app/components/tables/user-billing-organisations-table.tsx
msgid "You don't manage billing for any organisations."
@@ -11119,8 +11167,8 @@ msgstr "Você ainda não criou ou recebeu nenhum documento. Para criar um docume
#: apps/remix/app/components/general/document/document-upload-button-legacy.tsx
#: apps/remix/app/components/general/envelope/envelope-drop-zone-wrapper.tsx
#: apps/remix/app/components/general/envelope/envelope-upload-button.tsx
msgid "You have reached the limit of the number of files per envelope"
msgstr "Você atingiu o limite do número de arquivos por envelope"
msgid "You have reached the limit of the number of files per envelope."
msgstr ""
#. placeholder {0}: quota.directTemplates
#: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx
@@ -11275,6 +11323,10 @@ msgstr "Agora será necessário inserir um código do seu aplicativo autenticado
msgid "You will receive an email copy of the signed document once everyone has signed."
msgstr "Você receberá uma cópia por e-mail do documento assinado assim que todos assinarem."
#: apps/remix/app/components/dialogs/ai-features-enable-dialog.tsx
msgid "You're an admin. You can enable AI features for this team right away. Everyone on the team will see AI detection once enabled."
msgstr ""
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
#: apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx
msgid "You've made too many detection requests. Please wait a minute before trying again."
@@ -11333,6 +11385,10 @@ msgstr "Seu plano atual está vencido."
msgid "Your direct signing templates"
msgstr "Seus modelos de assinatura direta"
#: apps/remix/app/components/dialogs/ai-features-enable-dialog.tsx
msgid "Your document content will be sent securely to our AI provider solely for detection and will not be stored or used for training."
msgstr ""
#: apps/remix/app/components/embed/authoring/configure-document-upload.tsx
#: apps/remix/app/components/general/document/document-upload-button-legacy.tsx
msgid "Your document failed to upload."
@@ -11368,7 +11424,7 @@ msgstr "Seu documento foi duplicado com sucesso."
msgid "Your document has been uploaded successfully."
msgstr "Seu documento foi enviado com sucesso."
#: apps/remix/app/components/dialogs/template-create-dialog.tsx
#: apps/remix/app/components/general/document/document-upload-button-legacy.tsx
msgid "Your document has been uploaded successfully. You will be redirected to the template page."
msgstr "Seu documento foi enviado com sucesso. Você será redirecionado para a página do modelo."
@@ -11559,4 +11615,4 @@ msgstr "Seu código de verificação:"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.sso.tsx
msgid "your-domain.com another-domain.com"
msgstr "seu-dominio.com outro-dominio.com"
msgstr "seu-dominio.com outro-dominio.com"
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -31,7 +31,7 @@ export const ZClaimFlagsSchema = z.object({
authenticationPortal: z.boolean().optional(),
allowEnvelopes: z.boolean().optional(),
allowLegacyEnvelopes: z.boolean().optional(),
});
export type TClaimFlags = z.infer<typeof ZClaimFlagsSchema>;
@@ -84,9 +84,9 @@ export const SUBSCRIPTION_CLAIM_FEATURE_FLAGS: Record<
key: 'authenticationPortal',
label: 'Authentication portal',
},
allowEnvelopes: {
key: 'allowEnvelopes',
label: 'Allow envelopes',
allowLegacyEnvelopes: {
key: 'allowLegacyEnvelopes',
label: 'Allow Legacy Envelopes',
},
};
+12
View File
@@ -60,6 +60,18 @@ export const seedUser = async ({
},
include: {
teams: true,
organisationClaim: true,
},
});
await prisma.organisationClaim.update({
where: {
id: organisation.organisationClaim.id,
},
data: {
flags: {
allowLegacyEnvelopes: true,
},
},
});
@@ -3,6 +3,7 @@ import {
ORGANISATION_USER_ACCOUNT_TYPE,
} from '@documenso/lib/constants/organisations';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { orphanEnvelopes } from '@documenso/lib/server-only/envelope/orphan-envelopes';
import { buildOrganisationWhereQuery } from '@documenso/lib/utils/organisations';
import { prisma } from '@documenso/prisma';
@@ -32,6 +33,19 @@ export const deleteOrganisationRoute = authenticatedProcedure
userId: user.id,
roles: ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['DELETE_ORGANISATION'],
}),
select: {
id: true,
owner: {
select: {
id: true,
},
},
teams: {
select: {
id: true,
},
},
},
});
if (!organisation) {
@@ -40,6 +54,9 @@ export const deleteOrganisationRoute = authenticatedProcedure
});
}
// Orphan all envelopes to get rid of foreign key constraints.
await Promise.all(organisation.teams.map(async (team) => orphanEnvelopes({ teamId: team.id })));
await prisma.$transaction(async (tx) => {
await tx.account.deleteMany({
where: {
@@ -1,4 +1,10 @@
import { TeamMemberRole } from '@prisma/client';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { orphanEnvelopes } from '@documenso/lib/server-only/envelope/orphan-envelopes';
import { transferTeamEnvelopes } from '@documenso/lib/server-only/envelope/transfer-team-envelopes';
import { deleteTeam } from '@documenso/lib/server-only/team/delete-team';
import { getTeamById } from '@documenso/lib/server-only/team/get-team';
import { authenticatedProcedure } from '../trpc';
import { ZDeleteTeamRequestSchema, ZDeleteTeamResponseSchema } from './delete-team.types';
@@ -8,15 +14,40 @@ export const deleteTeamRoute = authenticatedProcedure
.input(ZDeleteTeamRequestSchema)
.output(ZDeleteTeamResponseSchema)
.mutation(async ({ input, ctx }) => {
const { teamId } = input;
const { teamId, transferTeamId } = input;
const { user } = ctx;
const team = await getTeamById({ userId: user.id, teamId });
if (team.currentTeamRole !== TeamMemberRole.ADMIN) {
throw new AppError(AppErrorCode.UNAUTHORIZED, {
message: 'You are not allowed to delete this team',
});
}
ctx.logger.info({
input: {
teamId,
},
});
const transferTeam = transferTeamId
? await getTeamById({ userId: user.id, teamId: transferTeamId }).catch(() => {
throw new AppError(AppErrorCode.INVALID_REQUEST, {
message: 'Invalid transfer team ID',
});
})
: undefined;
if (transferTeam) {
await transferTeamEnvelopes({
sourceTeamId: teamId,
targetTeamId: transferTeam.id,
});
} else {
await orphanEnvelopes({ teamId });
}
await deleteTeam({
userId: user.id,
teamId,
@@ -12,6 +12,7 @@ import { z } from 'zod';
export const ZDeleteTeamRequestSchema = z.object({
teamId: z.number(),
transferTeamId: z.number().optional(),
});
export const ZDeleteTeamResponseSchema = z.void();
@@ -17,7 +17,7 @@ const EnvelopePdfViewer = lazy(async () => import('./pdf-viewer-konva'));
export const PDFViewerKonvaLazy = (props: PDFViewerProps) => {
return (
<Suspense fallback={<div>Loading client component...</div>}>
<Suspense fallback={<div>Loading...</div>}>
<EnvelopePdfViewer {...props} />
</Suspense>
);
@@ -95,7 +95,9 @@ export function DataTablePagination<TData>({
onClick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()}
>
<span className="sr-only">Go to first page</span>
<span className="sr-only">
<Trans>Go to first page</Trans>
</span>
<ChevronsLeft className="h-4 w-4" />
</Button>
<Button
@@ -104,7 +106,9 @@ export function DataTablePagination<TData>({
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
<span className="sr-only">Go to previous page</span>
<span className="sr-only">
<Trans>Go to previous page</Trans>
</span>
<ChevronLeft className="h-4 w-4" />
</Button>
<Button
@@ -113,7 +117,9 @@ export function DataTablePagination<TData>({
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
<span className="sr-only">Go to next page</span>
<span className="sr-only">
<Trans>Go to next page</Trans>
</span>
<ChevronRight className="h-4 w-4" />
</Button>
<Button
@@ -122,7 +128,9 @@ export function DataTablePagination<TData>({
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
disabled={!table.getCanNextPage()}
>
<span className="sr-only">Go to last page</span>
<span className="sr-only">
<Trans>Go to last page</Trans>
</span>
<ChevronsRight className="h-4 w-4" />
</Button>
</div>
+4 -1
View File
@@ -1,5 +1,6 @@
import * as React from 'react';
import { Trans } from '@lingui/react/macro';
import * as DialogPrimitive from '@radix-ui/react-dialog';
import { X } from 'lucide-react';
@@ -81,7 +82,9 @@ const DialogContent = React.forwardRef<
className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:pointer-events-none"
>
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
<span className="sr-only">
<Trans>Close</Trans>
</span>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Content>
@@ -257,8 +257,10 @@ export const AddSubjectFormPartial = ({
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans>Reply To Email</Trans>{' '}
<span className="text-muted-foreground">(Optional)</span>
<Trans>
Reply To Email{' '}
<span className="text-muted-foreground">(Optional)</span>
</Trans>
</FormLabel>
<FormControl>
@@ -295,8 +297,9 @@ export const AddSubjectFormPartial = ({
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans>Subject</Trans>{' '}
<span className="text-muted-foreground">(Optional)</span>
<Trans>
Subject <span className="text-muted-foreground">(Optional)</span>
</Trans>
</FormLabel>
<FormControl>
@@ -313,8 +316,9 @@ export const AddSubjectFormPartial = ({
render={({ field }) => (
<FormItem>
<FormLabel className="flex flex-row items-center">
<Trans>Message</Trans>{' '}
<span className="text-muted-foreground">(Optional)</span>
<Trans>
Message <span className="text-muted-foreground">(Optional)</span>
</Trans>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="mx-2 h-4 w-4" />
@@ -67,7 +67,7 @@ export const DocumentFlowFormContainerContent = ({
}: DocumentFlowFormContainerContentProps) => {
return (
<div
className={cn('custom-scrollbar -mx-2 flex flex-1 flex-col overflow-hidden px-2', className)}
className={cn('custom-scrollbar -mx-2 flex flex-1 flex-col px-2', className)}
{...props}
>
<div className="flex flex-1 flex-col">{children}</div>
@@ -112,7 +112,7 @@ export const DocumentFlowFormContainerStep = ({
<motion.div
layout="size"
layoutId="document-flow-container-step"
className="bg-documenso absolute inset-y-0 left-0"
className="bg-primary absolute inset-y-0 left-0"
style={{
width: `${(100 / maxStep) * step}%`,
}}
@@ -161,7 +161,7 @@ export const DocumentFlowFormContainerActions = ({
<Button
type="button"
className="bg-documenso flex-1"
className="bg-primary flex-1"
size="lg"
disabled={disabled || disableNextStep || loading || !canGoNext}
loading={loading}
@@ -1,4 +1,5 @@
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { DocumentMeta, Signature } from '@prisma/client';
import { FieldType } from '@prisma/client';
import { ChevronDown } from 'lucide-react';
@@ -62,7 +63,7 @@ export const FieldContent = ({ field, documentMeta }: FieldIconProps) => {
<div className="flex items-center">
<Checkbox className="h-3 w-3" disabled />
<Label className="text-foreground ml-1.5 text-xs font-normal opacity-50">
Checkbox option
<Trans>Checkbox option</Trans>
</Label>
</div>
</div>
@@ -140,7 +141,9 @@ export const FieldContent = ({ field, documentMeta }: FieldIconProps) => {
) {
return (
<div className="text-field-card-foreground flex flex-row items-center py-0.5 text-[clamp(0.07rem,25cqw,0.825rem)] text-sm">
<p>Select</p>
<p>
<Trans>Select</Trans>
</p>
<ChevronDown className="h-4 w-4" />
</div>
);
+4 -1
View File
@@ -1,5 +1,6 @@
import * as React from 'react';
import { Trans } from '@lingui/react/macro';
import * as SheetPrimitive from '@radix-ui/react-dialog';
import type { VariantProps } from 'class-variance-authority';
import { cva } from 'class-variance-authority';
@@ -160,7 +161,9 @@ const SheetContent = React.forwardRef<
{children}
<SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:pointer-events-none">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
<span className="sr-only">
<Trans>Close</Trans>
</span>
</SheetPrimitive.Close>
</SheetPrimitive.Content>
</SheetPortal>
@@ -15,6 +15,7 @@ import { SignatureRender } from './signature-render';
export type SignaturePadDialogProps = Omit<HTMLAttributes<HTMLCanvasElement>, 'onChange'> & {
disabled?: boolean;
fullName?: string;
value?: string;
onChange: (_value: string) => void;
dialogConfirmText?: MessageDescriptor | string;
@@ -26,6 +27,7 @@ export type SignaturePadDialogProps = Omit<HTMLAttributes<HTMLCanvasElement>, 'o
export const SignaturePadDialog = ({
className,
fullName,
value,
onChange,
disabled = false,
@@ -43,7 +45,7 @@ export const SignaturePadDialog = ({
return (
<div
className={cn(
'aspect-signature-pad bg-background relative block w-full select-none rounded-lg border',
'relative block aspect-signature-pad w-full select-none rounded-lg border bg-background',
className,
{
'pointer-events-none opacity-50': disabled,
@@ -112,6 +114,7 @@ export const SignaturePadDialog = ({
<DialogContent hideClose={true} className="p-6 pt-4">
<SignaturePad
id="signature"
fullName={fullName}
value={value}
className={className}
disabled={disabled}
@@ -318,7 +318,9 @@ export const SignaturePadDraw = ({
onClick={onUndoClick}
>
<Undo2 className="h-4 w-4" />
<span className="sr-only">Undo</span>
<span className="sr-only">
<Trans>Undo</Trans>
</span>
</button>
</div>
)}
@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useEffect, useRef } from 'react';
import { useLingui } from '@lingui/react/macro';
@@ -7,13 +7,27 @@ import { cn } from '../../lib/utils';
export type SignaturePadTypeProps = {
className?: string;
value?: string;
defaultValue?: string;
onChange: (_value: string) => void;
};
export const SignaturePadType = ({ className, value, onChange }: SignaturePadTypeProps) => {
export const SignaturePadType = ({
className,
value,
defaultValue,
onChange,
}: SignaturePadTypeProps) => {
const { t } = useLingui();
const $isDirty = useRef(false);
// Colors don't actually work for text.
const [selectedColor, setSelectedColor] = useState('black');
useEffect(() => {
if (!$isDirty.current && !value && defaultValue) {
$isDirty.current = true;
onChange(defaultValue);
}
}, [defaultValue, value, onChange]);
return (
<div className={cn('flex h-full w-full items-center justify-center', className)}>
@@ -23,7 +37,10 @@ export const SignaturePadType = ({ className, value, onChange }: SignaturePadTyp
className="w-full bg-transparent px-4 text-center font-signature text-7xl text-black placeholder:text-4xl focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 dark:text-white"
// style={{ color: selectedColor }}
value={value}
onChange={(event) => onChange(event.target.value.trimStart())}
onChange={(event) => {
onChange(event.target.value.trimStart());
$isDirty.current = true;
}}
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
@@ -21,6 +21,7 @@ export type SignaturePadValue = {
};
export type SignaturePadProps = Omit<HTMLAttributes<HTMLCanvasElement>, 'onChange'> & {
fullName?: string;
value?: string;
onChange?: (_value: SignaturePadValue) => void;
@@ -34,6 +35,7 @@ export type SignaturePadProps = Omit<HTMLAttributes<HTMLCanvasElement>, 'onChang
};
export const SignaturePad = ({
fullName,
value = '',
onChange,
disabled = false,
@@ -168,7 +170,7 @@ export const SignaturePad = ({
<TabsContent
value="draw"
className="border-border aspect-signature-pad dark:bg-background relative flex items-center justify-center rounded-md border bg-neutral-50 text-center"
className="relative flex aspect-signature-pad items-center justify-center rounded-md border border-border bg-neutral-50 text-center dark:bg-background"
>
<SignaturePadDraw
className="h-full w-full"
@@ -179,15 +181,19 @@ export const SignaturePad = ({
<TabsContent
value="text"
className="border-border aspect-signature-pad dark:bg-background relative flex items-center justify-center rounded-md border bg-neutral-50 text-center"
className="relative flex aspect-signature-pad items-center justify-center rounded-md border border-border bg-neutral-50 text-center dark:bg-background"
>
<SignaturePadType value={typedSignature} onChange={onTypedSignatureChange} />
<SignaturePadType
value={typedSignature}
defaultValue={fullName}
onChange={onTypedSignatureChange}
/>
</TabsContent>
<TabsContent
value="image"
className={cn(
'border-border aspect-signature-pad dark:bg-background relative rounded-md border bg-neutral-50',
'relative aspect-signature-pad rounded-md border border-border bg-neutral-50 dark:bg-background',
{
'bg-white': imageSignature,
},
@@ -518,8 +518,10 @@ export const AddTemplateSettingsFormPartial = ({
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans>Reply To Email</Trans>{' '}
<span className="text-muted-foreground">(Optional)</span>
<Trans>
Reply To Email{' '}
<span className="text-muted-foreground">(Optional)</span>
</Trans>
</FormLabel>
<FormControl>
@@ -557,8 +559,9 @@ export const AddTemplateSettingsFormPartial = ({
render={({ field }) => (
<FormItem>
<FormLabel className="flex flex-row items-center">
<Trans>Message</Trans>{' '}
<span className="text-muted-foreground">(Optional)</span>
<Trans>
Message <span className="text-muted-foreground">(Optional)</span>
</Trans>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="mx-2 h-4 w-4" />