Compare commits

..

18 Commits

Author SHA1 Message Date
Lucas Smith b0d004c7cf fix: remove final references to tailwind-config package 2026-06-29 14:18:58 +10:00
Lucas Smith 403c1ca916 feat: move to css based config 2026-06-29 14:02:22 +10:00
Lucas Smith b79895b38c feat: upgrade to tailwind@4 2026-06-29 13:26:14 +10:00
Lucas Smith 187b612568 chore: add translations (#3012) 2026-06-23 15:12:23 +10:00
Lucas Smith b37529a1cf fix: show warning on overlapping fields (#3017) 2026-06-23 15:11:57 +10:00
Lucas Smith 04f6e76178 feat: cap automated reminders before resend (#3016)
Stop sending automated reminders after a configurable threshold
(default 5) and reset the count on manual resend.
2026-06-23 15:11:52 +10:00
Lucas Smith f2525ae95b feat: add API endpoint to reject documents on behalf of recipients (#3007)
Programmatically record an external rejection on behalf of a recipient
who declined outside the platform. Flags the rejection as external in
the audit log, optionally attributes it to a specific team member via
actAsEmail, and enforces team membership and document visibility.
2026-06-22 21:59:07 +10:00
David Nguyen 2f24a8eab2 fix: set send status on resend (#3011) 2026-06-22 17:00:24 +10:00
David Nguyen d9b7722325 fix: correctly use default distribute envelope tab (#3010) 2026-06-22 16:27:50 +10:00
github-actions[bot] 783123f72b chore: extract translations (#2987) 2026-06-22 16:06:57 +10:00
Lucas Smith e8ed1c3d99 fix: respect branding enabled for recipient routes (#3009) 2026-06-22 16:06:06 +10:00
David Nguyen c23d739f76 feat: allow additional envelope duplicate settings (#3008) 2026-06-22 14:41:38 +10:00
Lucas Smith 0bf58ca66e feat: add custom brand colours to emails (#3005) 2026-06-22 14:33:34 +10:00
David Nguyen dee3259088 fix: remove old dialogs (#3006) 2026-06-22 14:17:22 +10:00
Nandini Dhanrale 6ad1a2dfaf fix: signing request email renders blank when organisation/team branding is enabled (#2968) 2026-06-22 14:15:12 +10:00
Abdelrahman Abdelhamed 306e7fe5ed fix: render unicode characters in typed signatures (#2728) 2026-06-22 13:40:56 +10:00
Yash Singh 219db32fdf fix: only send S3 checksums when required to support S3-compatible storage (#2984) 2026-06-22 13:35:37 +10:00
David Nguyen 948d1bbf12 fix: improve team member removal ux (#3001) 2026-06-22 12:16:55 +10:00
382 changed files with 8884 additions and 4215 deletions
-1
View File
@@ -65,7 +65,6 @@ Documenso is an open-source document signing platform built as a **monorepo** us
| Package | Description |
| ---------------------------- | ------------------------- |
| `@documenso/app-tests` | E2E tests (Playwright) |
| `@documenso/tailwind-config` | Shared Tailwind config |
| `@documenso/tsconfig` | Shared TypeScript configs |
## Tech Stack
@@ -1,10 +1,11 @@
import { createMDX } from 'fumadocs-mdx/next';
import type { NextConfig } from 'next';
const withMDX = createMDX();
/** @type {import('next').NextConfig} */
const config = {
const config: NextConfig = {
reactStrictMode: true,
// biome-ignore lint/suspicious/useAwait: config file
async rewrites() {
return [
{
@@ -13,6 +14,7 @@ const config = {
},
];
},
// biome-ignore lint/suspicious/useAwait: config file
async redirects() {
return [
// ============================================================
+2 -2
View File
@@ -16,7 +16,7 @@
"fumadocs-ui": "16.5.0",
"lucide-react": "^0.563.0",
"mermaid": "^11.12.2",
"next": "16.2.6",
"next": "16.2.9",
"next-plausible": "^3.12.5",
"next-themes": "^0.4.6",
"react": "^19.2.4",
@@ -30,7 +30,7 @@
"@types/react": "^19.2.10",
"@types/react-dom": "^19.2.3",
"postcss": "^8.5.14",
"tailwindcss": "^4.1.18",
"tailwindcss": "^4.3.1",
"typescript": "^5.9.3"
}
}
@@ -68,7 +68,7 @@ export async function GET(_req: Request, { params }: RouteContext<'/og/docs/[...
}}
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={logoSrc} alt="Documenso" height="28" />
<img src={logoSrc} alt="Documenso" style={{ height: 28 }} />
<span
style={{
color: '#D4D4D8',
+1 -1
View File
@@ -12,7 +12,7 @@
"dependencies": {
"@documenso/prisma": "*",
"luxon": "^3.7.2",
"next": "16.2.6"
"next": "16.2.9"
},
"devDependencies": {
"@types/node": "^20",
+14 -3
View File
@@ -1,5 +1,15 @@
@import "@documenso/ui/styles/theme.css";
/* Content sources: this app plus the shared `ui` and `email` packages it renders. */
@source "./**/*.{ts,tsx}";
@source "../../../packages/ui/primitives/**/*.{ts,tsx}";
@source "../../../packages/ui/components/**/*.{ts,tsx}";
@source "../../../packages/ui/icons/**/*.{ts,tsx}";
@source "../../../packages/ui/lib/**/*.{ts,tsx}";
@source "../../../packages/email/templates/**/*.{ts,tsx}";
@source "../../../packages/email/template-components/**/*.{ts,tsx}";
@source "../../../packages/email/providers/**/*.{ts,tsx}";
/* Inter Variable Fonts */
@font-face {
font-family: "Inter";
@@ -64,8 +74,9 @@
@layer base {
:root {
--font-sans: "Inter";
--font-signature: "Caveat";
--font-noto: "Noto Sans", "Noto Sans Korean", "Noto Sans Japanese", "Noto Sans Chinese";
/* Consumed by the `--font-*` theme tokens in @documenso/ui/styles/theme.css. */
--font-family-sans: "Inter";
--font-family-signature: "Caveat";
--font-family-noto: "Noto Sans", "Noto Sans Korean", "Noto Sans Japanese", "Noto Sans Chinese";
}
}
@@ -73,7 +73,7 @@ export const AccountDeleteDialog = ({ className }: AccountDeleteDialogProps) =>
</AlertDescription>
</div>
<div className="flex-shrink-0">
<div className="shrink-0">
<Dialog onOpenChange={() => setEnteredEmail('')}>
<DialogTrigger asChild>
<Button variant="destructive">
@@ -82,7 +82,7 @@ export const AccountDeleteDialog = ({ className }: AccountDeleteDialogProps) =>
</DialogTrigger>
<DialogContent>
<DialogHeader className="space-y-4">
<DialogHeader className="twv3-space-y-4">
<DialogTitle>
<Trans>Delete Account</Trans>
</DialogTitle>
@@ -71,7 +71,7 @@ export const AdminDocumentDeleteDialog = ({ envelopeId }: AdminDocumentDeleteDia
</AlertDescription>
</div>
<div className="flex-shrink-0">
<div className="shrink-0">
<Dialog>
<DialogTrigger asChild>
<Button variant="destructive">
@@ -80,7 +80,7 @@ export const AdminDocumentDeleteDialog = ({ envelopeId }: AdminDocumentDeleteDia
</DialogTrigger>
<DialogContent>
<DialogHeader className="space-y-4">
<DialogHeader className="twv3-space-y-4">
<DialogTitle>
<Trans>Delete Document</Trans>
</DialogTitle>
@@ -90,7 +90,7 @@ export const AdminOrganisationCreateDialog = ({ trigger, ownerUserId, ...props }
<Dialog {...props} open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
{trigger ?? (
<Button className="flex-shrink-0" variant="secondary">
<Button className="shrink-0" variant="secondary">
<Trans>Create organisation</Trans>
</Button>
)}
@@ -109,7 +109,7 @@ export const AdminOrganisationCreateDialog = ({ trigger, ownerUserId, ...props }
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<fieldset className="twv3-space-y-4 flex h-full flex-col" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="name"
@@ -128,7 +128,7 @@ export const AdminOrganisationDeleteDialog = ({
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<fieldset className="twv3-space-y-4 flex h-full flex-col" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="organisationName"
@@ -151,7 +151,7 @@ export const AdminOrganisationDeleteDialog = ({
control={form.control}
name="sendEmailToOwner"
render={({ field }) => (
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
<FormItem className="twv3-space-x-3 twv3-space-y-0 flex flex-row items-start">
<FormControl>
<Checkbox
id="admin-delete-organisation-send-email"
@@ -76,7 +76,7 @@ export const AdminOrganisationMemberDeleteDialog = ({
</DialogTrigger>
<DialogContent>
<DialogHeader className="space-y-4">
<DialogHeader className="twv3-space-y-4">
<DialogTitle>
<Trans>Remove Organisation Member</Trans>
</DialogTitle>
@@ -110,12 +110,12 @@ export const AdminOrganisationSyncSubscriptionDialog = ({
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<fieldset className="twv3-space-y-4 flex h-full flex-col" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="syncClaims"
render={({ field }) => (
<FormItem className="flex flex-row items-center space-x-3 space-y-0">
<FormItem className="twv3-space-x-3 twv3-space-y-0 flex flex-row items-center">
<FormControl>
<Checkbox
id="admin-sync-subscription-sync-claims"
@@ -128,7 +128,7 @@ export const AdminSwapSubscriptionDialog = ({
</DialogDescription>
</DialogHeader>
<fieldset className="flex flex-col space-y-4" disabled={isSubmitting}>
<fieldset className="twv3-space-y-4 flex flex-col" disabled={isSubmitting}>
<div className="flex flex-col gap-2">
<label className="font-medium text-sm">
<Trans>Target Organisation</Trans>
@@ -76,7 +76,7 @@ export const AdminTeamMemberDeleteDialog = ({
</DialogTrigger>
<DialogContent>
<DialogHeader className="space-y-4">
<DialogHeader className="twv3-space-y-4">
<DialogTitle>
<Trans>Remove Team Member</Trans>
</DialogTitle>
@@ -82,7 +82,7 @@ export const AdminUserCreateDialog = ({ trigger, ...props }: AdminUserCreateDial
<Dialog {...props} open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
{trigger ?? (
<Button className="flex-shrink-0" variant="secondary">
<Button className="shrink-0" variant="secondary">
<Trans>Create User</Trans>
</Button>
)}
@@ -101,7 +101,7 @@ export const AdminUserCreateDialog = ({ trigger, ...props }: AdminUserCreateDial
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<fieldset className="twv3-space-y-4 flex h-full flex-col" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="email"
@@ -77,7 +77,7 @@ export const AdminUserDeleteDialog = ({ className, user }: AdminUserDeleteDialog
</AlertDescription>
</div>
<div className="flex-shrink-0">
<div className="shrink-0">
<Dialog>
<DialogTrigger asChild>
<Button variant="destructive">
@@ -86,7 +86,7 @@ export const AdminUserDeleteDialog = ({ className, user }: AdminUserDeleteDialog
</DialogTrigger>
<DialogContent>
<DialogHeader className="space-y-4">
<DialogHeader className="twv3-space-y-4">
<DialogTitle>
<Trans>Delete Account</Trans>
</DialogTitle>
@@ -74,7 +74,7 @@ export const AdminUserDisableDialog = ({ className, userToDisable }: AdminUserDi
</AlertDescription>
</div>
<div className="flex-shrink-0">
<div className="shrink-0">
<Dialog>
<DialogTrigger asChild>
<Button variant="destructive">
@@ -83,7 +83,7 @@ export const AdminUserDisableDialog = ({ className, userToDisable }: AdminUserDi
</DialogTrigger>
<DialogContent>
<DialogHeader className="space-y-4">
<DialogHeader className="twv3-space-y-4">
<DialogTitle>
<Trans>Disable Account</Trans>
</DialogTitle>
@@ -74,7 +74,7 @@ export const AdminUserEnableDialog = ({ className, userToEnable }: AdminUserEnab
</AlertDescription>
</div>
<div className="flex-shrink-0">
<div className="shrink-0">
<Dialog>
<DialogTrigger asChild>
<Button>
@@ -83,7 +83,7 @@ export const AdminUserEnableDialog = ({ className, userToEnable }: AdminUserEnab
</DialogTrigger>
<DialogContent>
<DialogHeader className="space-y-4">
<DialogHeader className="twv3-space-y-4">
<DialogTitle>
<Trans>Enable Account</Trans>
</DialogTitle>
@@ -90,7 +90,7 @@ export const AdminUserResetTwoFactorDialog = ({ className, user }: AdminUserRese
</AlertDescription>
</div>
<div className="flex-shrink-0">
<div className="shrink-0">
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogTrigger asChild>
<Button variant="destructive">
@@ -99,7 +99,7 @@ export const AdminUserResetTwoFactorDialog = ({ className, user }: AdminUserRese
</DialogTrigger>
<DialogContent>
<DialogHeader className="space-y-4">
<DialogHeader className="twv3-space-y-4">
<DialogTitle>
<Trans>Reset Two Factor Authentication</Trans>
</DialogTitle>
@@ -71,7 +71,7 @@ export const AiFeaturesEnableDialog = ({ open, onOpenChange, onEnabled }: AiFeat
</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div className="twv3-space-y-4">
<p className="text-muted-foreground text-sm">
<Trans>
Turn on AI detection to automatically find recipients and fields in your documents. AI providers do not
@@ -158,7 +158,7 @@ export const AiFieldDetectionDialog = ({
</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div className="twv3-space-y-4">
<p className="text-muted-foreground text-sm">
<Trans>
We'll scan your document to find form fields like signature lines, text inputs, checkboxes, and more.
@@ -166,14 +166,14 @@ export const AiFieldDetectionDialog = ({
</Trans>
</p>
<Alert className="flex items-center gap-2 space-y-0" variant="neutral">
<Alert className="twv3-space-y-0 flex items-center gap-2" variant="neutral">
<ShieldCheckIcon className="h-5 w-5 stroke-green-600" />
<AlertDescription className="mt-0">
<Trans>Your document is processed securely using AI services that don't retain your data.</Trans>
</AlertDescription>
</Alert>
<div className="space-y-1.5">
<div className="twv3-space-y-1.5">
<Label htmlFor="context">
<Trans>Context</Trans>
</Label>
@@ -145,7 +145,7 @@ export const AiRecipientDetectionDialog = ({
</Trans>
</p>
<Alert className="mt-4 flex items-center gap-2 space-y-0" variant="neutral">
<Alert className="twv3-space-y-0 mt-4 flex items-center gap-2" variant="neutral">
<ShieldCheckIcon className="h-5 w-5 stroke-green-600" />
<AlertDescription className="mt-0">
<Trans>Your document is processed securely using AI services that don't retain your data.</Trans>
@@ -50,7 +50,7 @@ export const ClaimCreateDialog = ({ licenseFlags }: ClaimCreateDialogProps) => {
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
<Button className="flex-shrink-0" variant="secondary">
<Button className="shrink-0" variant="secondary">
<Trans>Create claim</Trans>
</Button>
</DialogTrigger>
@@ -75,7 +75,7 @@ export const ClaimUpdateDialog = ({ claim, trigger, licenseFlags }: ClaimUpdateD
licenseFlags={licenseFlags}
formSubmitTrigger={
<>
<div className="flex items-center space-x-2">
<div className="twv3-space-x-2 flex items-center">
<Checkbox
id="backport-email-transport"
checked={backportEmailTransport}
@@ -1,243 +0,0 @@
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { FolderType } from '@documenso/lib/types/folder-type';
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
import { trpc } from '@documenso/trpc/react';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@documenso/ui/primitives/dialog';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { FolderIcon, HomeIcon, Loader2, Search } from 'lucide-react';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useNavigate } from 'react-router';
import { z } from 'zod';
import { useCurrentTeam } from '~/providers/team';
export type DocumentMoveToFolderDialogProps = {
documentId: number;
open: boolean;
onOpenChange: (open: boolean) => void;
currentFolderId?: string;
} & Omit<DialogPrimitive.DialogProps, 'children'>;
const ZMoveDocumentFormSchema = z.object({
folderId: z.string().nullable().optional(),
});
type TMoveDocumentFormSchema = z.infer<typeof ZMoveDocumentFormSchema>;
export const DocumentMoveToFolderDialog = ({
documentId,
open,
onOpenChange,
currentFolderId,
...props
}: DocumentMoveToFolderDialogProps) => {
const { _ } = useLingui();
const { toast } = useToast();
const navigate = useNavigate();
const team = useCurrentTeam();
const [searchTerm, setSearchTerm] = useState('');
const form = useForm<TMoveDocumentFormSchema>({
resolver: zodResolver(ZMoveDocumentFormSchema),
defaultValues: {
folderId: currentFolderId,
},
});
const { data: folders, isLoading: isFoldersLoading } = trpc.folder.findFoldersInternal.useQuery(
{
parentId: currentFolderId,
type: FolderType.DOCUMENT,
},
{
enabled: open,
},
);
const { mutateAsync: updateDocument } = trpc.document.update.useMutation();
useEffect(() => {
if (!open) {
form.reset();
setSearchTerm('');
} else {
form.reset({ folderId: currentFolderId });
}
}, [open, currentFolderId, form]);
const onSubmit = async (data: TMoveDocumentFormSchema) => {
try {
await updateDocument({
documentId,
data: {
folderId: data.folderId ?? null,
},
});
const documentsPath = formatDocumentsPath(team.url);
if (data.folderId) {
await navigate(`${documentsPath}/f/${data.folderId}`);
} else {
await navigate(documentsPath);
}
toast({
title: _(msg`Document moved`),
description: _(msg`The document has been moved successfully.`),
variant: 'default',
});
onOpenChange(false);
} catch (err) {
const error = AppError.parseError(err);
if (error.code === AppErrorCode.NOT_FOUND) {
toast({
title: _(msg`Error`),
description: _(msg`The folder you are trying to move the document to does not exist.`),
variant: 'destructive',
});
return;
}
if (error.code === AppErrorCode.UNAUTHORIZED) {
toast({
title: _(msg`Error`),
description: _(msg`You are not allowed to move this document.`),
variant: 'destructive',
});
return;
}
toast({
title: _(msg`Error`),
description: _(msg`An error occurred while moving the document.`),
variant: 'destructive',
});
}
};
const filteredFolders = folders?.data.filter((folder) =>
folder.name.toLowerCase().includes(searchTerm.toLowerCase()),
);
return (
<Dialog {...props} open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>
<Trans>Move Document to Folder</Trans>
</DialogTitle>
<DialogDescription>
<Trans>Select a folder to move this document to.</Trans>
</DialogDescription>
</DialogHeader>
<div className="relative">
<Search className="absolute top-3 left-2 h-4 w-4 text-muted-foreground" />
<Input
placeholder={_(msg`Search folders...`)}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-8"
/>
</div>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col gap-y-4">
<FormField
control={form.control}
name="folderId"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans>Folder</Trans>
</FormLabel>
<FormControl>
<div className="max-h-96 space-y-2 overflow-y-auto">
{isFoldersLoading ? (
<div className="flex h-10 items-center justify-center">
<Loader2 className="h-4 w-4 animate-spin" />
</div>
) : (
<>
<Button
type="button"
variant={field.value === null ? 'default' : 'outline'}
className="w-full justify-start"
onClick={() => field.onChange(null)}
disabled={currentFolderId === null}
>
<HomeIcon className="mr-2 h-4 w-4" />
<Trans>Home (No Folder)</Trans>
</Button>
{filteredFolders?.map((folder) => (
<Button
key={folder.id}
type="button"
variant={field.value === folder.id ? 'default' : 'outline'}
className="w-full justify-start"
onClick={() => field.onChange(folder.id)}
disabled={currentFolderId === folder.id}
>
<FolderIcon className="mr-2 h-4 w-4" />
{folder.name}
</Button>
))}
{searchTerm && filteredFolders?.length === 0 && (
<div className="px-2 py-2 text-center text-muted-foreground text-sm">
<Trans>No folders found</Trans>
</div>
)}
</>
)}
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<DialogFooter>
<Button type="button" variant="secondary" onClick={() => onOpenChange(false)}>
<Trans>Cancel</Trans>
</Button>
<Button
type="submit"
disabled={isFoldersLoading || form.formState.isSubmitting || currentFolderId === null}
>
<Trans>Move</Trans>
</Button>
</DialogFooter>
</form>
</Form>
</DialogContent>
</Dialog>
);
};
@@ -1,203 +0,0 @@
import { useSession } from '@documenso/lib/client-only/providers/session';
import { getRecipientType } from '@documenso/lib/client-only/recipient-type';
import { AppError } from '@documenso/lib/errors/app-error';
import type { TRecipientLite } from '@documenso/lib/types/recipient';
import { recipientAbbreviation } from '@documenso/lib/utils/recipient-formatter';
import type { Document } from '@documenso/prisma/types/document-legacy-schema';
import { trpc as trpcReact } from '@documenso/trpc/react';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
import { Checkbox } from '@documenso/ui/primitives/checkbox';
import {
Dialog,
DialogClose,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import { DropdownMenuItem } from '@documenso/ui/primitives/dropdown-menu';
import { Form, FormControl, FormField, FormItem, FormLabel } from '@documenso/ui/primitives/form/form';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { SigningStatus, type Team, type User } from '@prisma/client';
import { History } from 'lucide-react';
import { useState } from 'react';
import { useForm, useWatch } from 'react-hook-form';
import * as z from 'zod';
import { useCurrentTeam } from '~/providers/team';
import { getDistributeErrorMessage } from '~/utils/toast-error-messages';
import { StackAvatar } from '../general/stack-avatar';
const FORM_ID = 'resend-email';
export type DocumentResendDialogProps = {
document: Pick<Document, 'id' | 'userId' | 'teamId' | 'status'> & {
user: Pick<User, 'id' | 'name' | 'email'>;
recipients: TRecipientLite[];
team: Pick<Team, 'id' | 'url'> | null;
};
recipients: TRecipientLite[];
};
export const ZResendDocumentFormSchema = z.object({
recipients: z.array(z.number()).min(1, {
message: 'You must select at least one item.',
}),
});
export type TResendDocumentFormSchema = z.infer<typeof ZResendDocumentFormSchema>;
export const DocumentResendDialog = ({ document, recipients }: DocumentResendDialogProps) => {
const { user } = useSession();
const team = useCurrentTeam();
const { toast } = useToast();
const { _ } = useLingui();
const [isOpen, setIsOpen] = useState(false);
const isOwner = document.userId === user.id;
const isCurrentTeamDocument = team && document.team?.url === team.url;
const isDisabled =
(!isOwner && !isCurrentTeamDocument) ||
document.status !== 'PENDING' ||
!recipients.some((r) => r.signingStatus === SigningStatus.NOT_SIGNED);
const { mutateAsync: resendDocument } = trpcReact.document.redistribute.useMutation();
const form = useForm<TResendDocumentFormSchema>({
resolver: zodResolver(ZResendDocumentFormSchema),
defaultValues: {
recipients: [],
},
});
const {
handleSubmit,
formState: { isSubmitting },
} = form;
const selectedRecipients = useWatch({
control: form.control,
name: 'recipients',
});
const onFormSubmit = async ({ recipients }: TResendDocumentFormSchema) => {
try {
await resendDocument({ documentId: document.id, recipients });
toast({
title: _(msg`Document re-sent`),
description: _(msg`Your document has been re-sent successfully.`),
duration: 5000,
});
setIsOpen(false);
} catch (err) {
const error = AppError.parseError(err);
const errorMessage = getDistributeErrorMessage(error.code);
toast({
title: _(errorMessage.title),
description: _(errorMessage.description),
variant: 'destructive',
duration: 7500,
});
}
};
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<DropdownMenuItem disabled={isDisabled} onSelect={(e) => e.preventDefault()}>
<History className="mr-2 h-4 w-4" />
<Trans>Resend</Trans>
</DropdownMenuItem>
</DialogTrigger>
<DialogContent className="sm:max-w-sm" hideClose>
<DialogHeader>
<DialogTitle asChild>
<h1 className="text-center text-xl">
<Trans>Who do you want to remind?</Trans>
</h1>
</DialogTitle>
</DialogHeader>
<Form {...form}>
<form id={FORM_ID} onSubmit={handleSubmit(onFormSubmit)} className="px-3">
<FormField
control={form.control}
name="recipients"
render={({ field: { value, onChange } }) => (
<>
{recipients.map((recipient) => (
<FormItem key={recipient.id} className="flex flex-row items-center justify-between gap-x-3">
<FormLabel
className={cn('my-2 flex items-center gap-2 font-normal', {
'opacity-50': !value.includes(recipient.id),
})}
>
<StackAvatar
key={recipient.id}
type={getRecipientType(recipient)}
fallbackText={recipientAbbreviation(recipient)}
/>
{recipient.email}
</FormLabel>
<FormControl>
<Checkbox
className="h-5 w-5 rounded-full border border-neutral-400"
value={recipient.id}
checked={value.includes(recipient.id)}
onCheckedChange={(checked: boolean) =>
checked
? onChange([...value, recipient.id])
: onChange(value.filter((v) => v !== recipient.id))
}
/>
</FormControl>
</FormItem>
))}
</>
)}
/>
</form>
</Form>
<DialogFooter>
<div className="flex w-full flex-1 flex-nowrap gap-4">
<DialogClose asChild>
<Button
type="button"
className="flex-1 bg-black/5 hover:bg-black/10 dark:bg-muted dark:hover:bg-muted/80"
variant="secondary"
disabled={isSubmitting}
>
<Trans>Cancel</Trans>
</Button>
</DialogClose>
<Button
className="flex-1"
loading={isSubmitting}
type="submit"
form={FORM_ID}
disabled={isSubmitting || selectedRecipients.length === 0}
>
<Trans>Send reminder</Trans>
</Button>
</div>
</DialogFooter>
</DialogContent>
</Dialog>
);
};
@@ -59,7 +59,7 @@ export const EmailTransportCreateDialog = ({ trigger }: EmailTransportCreateDial
<Dialog open={open} onOpenChange={(value) => !isPending && setOpen(value)}>
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
{trigger ?? (
<Button className="flex-shrink-0">
<Button className="shrink-0">
<Trans>Add transport</Trans>
</Button>
)}
@@ -86,7 +86,7 @@ export const EmailTransportSendTestDialog = ({ transportId, trigger }: EmailTran
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset disabled={form.formState.isSubmitting} className="space-y-4">
<fieldset disabled={form.formState.isSubmitting} className="twv3-space-y-4">
<FormField
control={form.control}
name="to"
@@ -3,12 +3,13 @@ import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/org
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
import { AppError } from '@documenso/lib/errors/app-error';
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
import { hasOverlappingFields } from '@documenso/lib/utils/fields-overlap';
import { getRecipientsWithMissingFields } from '@documenso/lib/utils/recipients';
import { zEmail } from '@documenso/lib/utils/zod';
import { trpc, trpc as trpcReact } from '@documenso/trpc/react';
import { DocumentSendEmailMessageHelper } from '@documenso/ui/components/document/document-send-email-message-helper';
import { cn } from '@documenso/ui/lib/utils';
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
@@ -32,7 +33,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
import { DocumentDistributionMethod, DocumentStatus, EnvelopeType } from '@prisma/client';
import { AnimatePresence, motion } from 'framer-motion';
import { InfoIcon } from 'lucide-react';
import { AlertTriangleIcon, InfoIcon } from 'lucide-react';
import { useEffect, useMemo, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useNavigate } from 'react-router';
@@ -138,6 +139,27 @@ export const EnvelopeDistributeDialog = ({
});
}, [recipientsWithIndex, envelope.authOptions]);
/**
* Whether any fields significantly overlap each other. This is surfaced as a
* non-blocking warning since overlapping fields still allow sending, but can
* complicate the signing process or cause fields to behave unexpectedly.
*/
const hasOverlappingEnvelopeFields = useMemo(
() =>
hasOverlappingFields(
envelope.fields.map((field) => ({
id: field.id,
envelopeItemId: field.envelopeItemId,
page: field.page,
positionX: Number(field.positionX),
positionY: Number(field.positionY),
width: Number(field.width),
height: Number(field.height),
})),
),
[envelope.fields],
);
const invalidEnvelopeCode = useMemo(() => {
if (recipientsMissingSignatureFields.length > 0) {
return 'MISSING_SIGNATURES';
@@ -206,6 +228,11 @@ export const EnvelopeDistributeDialog = ({
};
useEffect(() => {
// Default the distribution method tab to the envelope's configured setting.
if (isOpen && envelope.documentMeta) {
setValue('meta.distributionMethod', envelope.documentMeta.distributionMethod);
}
// Resync the whole envelope if the envelope is mid saving.
if (isOpen && (isAutosaving || autosaveError)) {
void handleSync();
@@ -235,6 +262,24 @@ export const EnvelopeDistributeDialog = ({
<Form {...form}>
<form onSubmit={handleSubmit(onFormSubmit)}>
<fieldset disabled={isSubmitting}>
{hasOverlappingEnvelopeFields && (
<Alert variant="warning" className="mb-4 flex flex-row items-start gap-3">
<AlertTriangleIcon className="mt-0.5 h-5 w-5 shrink-0" />
<div className="flex flex-col gap-1">
<AlertTitle>
<Trans>Overlapping fields detected</Trans>
</AlertTitle>
<AlertDescription>
<Trans>
Some fields are placed on top of each other. This may complicate the signing process or cause
fields to not work as expected.
</Trans>
</AlertDescription>
</div>
</Alert>
)}
<Tabs
onValueChange={(value) =>
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
@@ -163,14 +163,14 @@ export const EnvelopeDownloadDialog = ({
{isLoadingEnvelopeItems
? Array.from({ length: 1 }).map((_, index) => (
<div key={index} className="flex items-center gap-2 rounded-lg border border-border bg-card p-4">
<Skeleton className="h-10 w-10 flex-shrink-0 rounded-lg" />
<Skeleton className="h-10 w-10 shrink-0 rounded-lg" />
<div className="flex w-full flex-col gap-2">
<Skeleton className="h-4 w-28 rounded-lg" />
<Skeleton className="h-4 w-20 rounded-lg" />
</div>
<Skeleton className="h-10 w-20 flex-shrink-0 rounded-lg" />
<Skeleton className="h-10 w-20 shrink-0 rounded-lg" />
</div>
))
: envelopeItems.map((item) => (
@@ -178,7 +178,7 @@ export const EnvelopeDownloadDialog = ({
key={item.id}
className="flex items-center gap-4 rounded-lg border border-border bg-card p-4 transition-colors hover:bg-accent/50"
>
<div className="flex-shrink-0">
<div className="shrink-0">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10">
<FileTextIcon className="h-5 w-5 text-primary" />
</div>
@@ -194,7 +194,7 @@ export const EnvelopeDownloadDialog = ({
</p>
</div>
<div className="flex flex-shrink-0 items-center gap-2">
<div className="flex shrink-0 items-center gap-2">
<Button
variant="outline"
size="sm"
@@ -1,6 +1,7 @@
import { formatDocumentsPath, formatTemplatesPath } from '@documenso/lib/utils/teams';
import { trpc } from '@documenso/trpc/react';
import { Button } from '@documenso/ui/primitives/button';
import { Checkbox } from '@documenso/ui/primitives/checkbox';
import {
Dialog,
DialogClose,
@@ -11,10 +12,12 @@ import {
DialogTitle,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import { Label } from '@documenso/ui/primitives/label';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { Trans, useLingui } from '@lingui/react/macro';
import { EnvelopeType } from '@prisma/client';
import { useState } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { useNavigate } from 'react-router';
import { useCurrentTeam } from '~/providers/team';
@@ -37,6 +40,15 @@ export const EnvelopeDuplicateDialog = ({ envelopeId, envelopeType, trigger }: E
const isDocument = envelopeType === EnvelopeType.DOCUMENT;
const form = useForm({
defaultValues: {
includeRecipients: true,
includeFields: true,
},
});
const includeRecipients = form.watch('includeRecipients');
const { mutateAsync: duplicateEnvelope, isPending: isDuplicating } = trpc.envelope.duplicate.useMutation({
onSuccess: async ({ id }) => {
toast({
@@ -55,8 +67,14 @@ export const EnvelopeDuplicateDialog = ({ envelopeId, envelopeType, trigger }: E
});
const onDuplicate = async () => {
const { includeRecipients, includeFields } = form.getValues();
try {
await duplicateEnvelope({ envelopeId });
await duplicateEnvelope({
envelopeId,
includeRecipients,
includeFields: includeRecipients && includeFields,
});
} catch {
toast({
title: t`Something went wrong`,
@@ -70,7 +88,20 @@ export const EnvelopeDuplicateDialog = ({ envelopeId, envelopeType, trigger }: E
};
return (
<Dialog open={open} onOpenChange={(value) => !isDuplicating && setOpen(value)}>
<Dialog
open={open}
onOpenChange={(value) => {
if (isDuplicating) {
return;
}
setOpen(value);
if (!value) {
form.reset();
}
}}
>
{trigger && <DialogTrigger asChild>{trigger}</DialogTrigger>}
<DialogContent>
@@ -87,6 +118,49 @@ export const EnvelopeDuplicateDialog = ({ envelopeId, envelopeType, trigger }: E
</DialogDescription>
</DialogHeader>
<div className="twv3-space-y-4">
<Controller
control={form.control}
name="includeRecipients"
render={({ field }) => (
<div className="twv3-space-x-2 flex items-center">
<Checkbox
id="envelopeDuplicateIncludeRecipients"
checked={field.value}
onCheckedChange={(checked) => {
field.onChange(checked === true);
if (!checked) {
form.setValue('includeFields', false);
}
}}
/>
<Label htmlFor="envelopeDuplicateIncludeRecipients">
<Trans>Include Recipients</Trans>
</Label>
</div>
)}
/>
<Controller
control={form.control}
name="includeFields"
render={({ field }) => (
<div className="twv3-space-x-2 flex items-center">
<Checkbox
id="envelopeDuplicateIncludeFields"
checked={field.value}
disabled={!includeRecipients}
onCheckedChange={(checked) => field.onChange(checked === true)}
/>
<Label htmlFor="envelopeDuplicateIncludeFields" className={!includeRecipients ? 'opacity-50' : ''}>
<Trans>Include Fields</Trans>
</Label>
</div>
)}
/>
</div>
<DialogFooter>
<DialogClose asChild>
<Button type="button" variant="secondary" disabled={isDuplicating}>
@@ -225,7 +225,7 @@ export const EnvelopeItemEditDialog = ({
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<fieldset disabled={form.formState.isSubmitting} className="space-y-4">
<fieldset disabled={form.formState.isSubmitting} className="twv3-space-y-4">
<FormField
control={form.control}
name="title"
@@ -253,13 +253,13 @@ export const EnvelopeItemEditDialog = ({
</FormLabel>
{replacementFile ? (
<div className="mt-1.5 space-y-2">
<div className="twv3-space-y-2 mt-1.5">
<div
data-testid="envelope-item-edit-selected-file"
className="flex items-center justify-between rounded-md border border-border bg-muted/50 px-3 py-2"
>
<div className="flex min-w-0 items-center space-x-2">
<FileIcon className="h-4 w-4 flex-shrink-0 text-muted-foreground" />
<div className="twv3-space-x-2 flex min-w-0 items-center">
<FileIcon className="h-4 w-4 shrink-0 text-muted-foreground" />
<div className="min-w-0">
<p className="truncate font-medium text-sm">{replacementFile.file.name}</p>
<p className="text-muted-foreground text-xs">
@@ -309,7 +309,7 @@ export const EnvelopeItemEditDialog = ({
)}
>
<input {...getInputProps()} />
<div className="flex items-center space-x-2 text-muted-foreground text-sm">
<div className="twv3-space-x-2 flex items-center text-muted-foreground text-sm">
<UploadIcon className="h-4 w-4" />
<span>
<Trans>Drop PDF here or click to select</Trans>
@@ -25,14 +25,16 @@ import { Trans, useLingui } from '@lingui/react/macro';
import { DocumentStatus, EnvelopeType, SigningStatus } from '@prisma/client';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { match } from 'ts-pattern';
import * as z from 'zod';
import { getDistributeErrorMessage } from '~/utils/toast-error-messages';
import { StackAvatar } from '../general/stack-avatar';
export type EnvelopeRedistributeDialogProps = {
envelope: Pick<TEnvelope, 'id' | 'userId' | 'teamId' | 'status' | 'type' | 'documentMeta'> & {
envelope: Pick<TEnvelope, 'id' | 'status' | 'type'> & {
recipients: TEnvelopeRecipientLite[];
};
envelopeType?: EnvelopeType;
trigger?: React.ReactNode;
};
@@ -44,7 +46,7 @@ export const ZEnvelopeRedistributeFormSchema = z.object({
export type TEnvelopeRedistributeFormSchema = z.infer<typeof ZEnvelopeRedistributeFormSchema>;
export const EnvelopeRedistributeDialog = ({ envelope, trigger }: EnvelopeRedistributeDialogProps) => {
export const EnvelopeRedistributeDialog = ({ envelope, envelopeType, trigger }: EnvelopeRedistributeDialogProps) => {
const recipients = envelope.recipients;
const { toast } = useToast();
@@ -70,9 +72,23 @@ export const EnvelopeRedistributeDialog = ({ envelope, trigger }: EnvelopeRedist
try {
await redistributeEnvelope({ envelopeId: envelope.id, recipients });
const successMessage = match(envelopeType)
.with(EnvelopeType.DOCUMENT, () => ({
title: t`Document resent`,
description: t`Your document has been resent successfully.`,
}))
.with(EnvelopeType.TEMPLATE, () => ({
title: t`Template resent`,
description: t`Your template has been resent successfully.`,
}))
.otherwise(() => ({
title: t`Envelope resent`,
description: t`Your envelope has been resent successfully.`,
}));
toast({
title: t`Envelope resent`,
description: t`Your envelope has been resent successfully.`,
title: successMessage.title,
description: successMessage.description,
duration: 5000,
});
@@ -116,12 +116,12 @@ export const EnvelopeSaveAsTemplateDialog = ({
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="twv3-space-y-4">
<Controller
control={form.control}
name="includeRecipients"
render={({ field }) => (
<div className="flex items-center space-x-2">
<div className="twv3-space-x-2 flex items-center">
<Checkbox
id="envelopeIncludeRecipients"
checked={field.value}
@@ -144,7 +144,7 @@ export const EnvelopeSaveAsTemplateDialog = ({
control={form.control}
name="includeFields"
render={({ field }) => (
<div className="flex items-center space-x-2">
<div className="twv3-space-x-2 flex items-center">
<Checkbox
id="envelopeIncludeFields"
checked={field.value}
@@ -28,7 +28,7 @@ export type EnvelopesBulkMoveDialogProps = {
open: boolean;
onOpenChange: (open: boolean) => void;
currentFolderId?: string;
onSuccess?: () => void;
onSuccess?: (folderId: string | null) => Promise<void> | void;
} & Omit<DialogPrimitive.DialogProps, 'children'>;
const ZBulkMoveFormSchema = z.object({
@@ -99,11 +99,12 @@ export const EnvelopesBulkMoveDialog = ({
await trpcUtils.template.findTemplates.invalidate();
}
await onSuccess?.(data.folderId);
toast({
description: t`Selected items have been moved.`,
});
onSuccess?.();
onOpenChange(false);
} catch (err) {
const error = AppError.parseError(err);
@@ -172,7 +173,7 @@ export const EnvelopesBulkMoveDialog = ({
</FormLabel>
<FormControl>
<div className="max-h-96 space-y-2 overflow-y-auto">
<div className="twv3-space-y-2 max-h-96 overflow-y-auto">
{isFoldersLoading ? (
<div className="flex h-10 items-center justify-center">
<Loader2 className="h-4 w-4 animate-spin" />
@@ -103,7 +103,7 @@ export const FolderCreateDialog = ({ type, trigger, parentFolderId, ...props }:
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<fieldset disabled={form.formState.isSubmitting} className="space-y-4">
<fieldset disabled={form.formState.isSubmitting} className="twv3-space-y-4">
<FormField
control={form.control}
name="name"
@@ -113,7 +113,7 @@ export const FolderDeleteDialog = ({ folder, isOpen, onOpenChange }: FolderDelet
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset disabled={form.formState.isSubmitting} className="space-y-4">
<fieldset disabled={form.formState.isSubmitting} className="twv3-space-y-4">
<FormField
control={form.control}
name="confirmText"
@@ -126,14 +126,14 @@ export const FolderMoveDialog = ({ foldersData, folder, isOpen, onOpenChange }:
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset disabled={form.formState.isSubmitting} className="space-y-4">
<fieldset disabled={form.formState.isSubmitting} className="twv3-space-y-4">
<FormField
control={form.control}
name="targetFolderId"
render={({ field }) => (
<FormItem>
<FormControl>
<div className="max-h-96 space-y-2 overflow-y-auto">
<div className="twv3-space-y-2 max-h-96 overflow-y-auto">
<Button
type="button"
variant={!field.value ? 'default' : 'outline'}
@@ -105,7 +105,7 @@ export const FolderUpdateDialog = ({ folder, isOpen, onOpenChange }: FolderUpdat
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)} className="space-y-4">
<form onSubmit={form.handleSubmit(onFormSubmit)} className="twv3-space-y-4">
<FormField
control={form.control}
name="name"
@@ -136,7 +136,7 @@ export const OrganisationCreateDialog = ({ trigger, ...props }: OrganisationCrea
<Dialog {...props} open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
{trigger ?? (
<Button className="flex-shrink-0" variant="secondary">
<Button className="shrink-0" variant="secondary">
<Trans>Create organisation</Trans>
</Button>
)}
@@ -199,7 +199,7 @@ export const OrganisationCreateDialog = ({ trigger, ...props }: OrganisationCrea
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<fieldset className="twv3-space-y-4 flex h-full flex-col" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="name"
@@ -305,7 +305,7 @@ const BillingPlanForm = ({ value, onChange, plans, canCreateFreeOrganisation }:
};
return (
<div className="space-y-4">
<div className="twv3-space-y-4">
<Tabs
className="flex w-full items-center justify-center"
defaultValue="monthlyPrice"
@@ -327,7 +327,7 @@ const BillingPlanForm = ({ value, onChange, plans, canCreateFreeOrganisation }:
<button
onClick={() => onChange('')}
className={cn(
'flex cursor-pointer items-center space-x-2 rounded-md border p-4 transition-all hover:border-primary hover:shadow-sm',
'twv3-space-x-2 flex cursor-pointer items-center rounded-md border p-4 transition-all hover:border-primary hover:shadow-xs',
{
'border-primary ring-2 ring-primary/10 ring-offset-1': '' === value,
},
@@ -360,7 +360,7 @@ const BillingPlanForm = ({ value, onChange, plans, canCreateFreeOrganisation }:
key={plan[billingPeriod]?.id}
onClick={() => onChange(plan[billingPeriod]?.id ?? '')}
className={cn(
'flex cursor-pointer items-center space-x-2 rounded-md border p-4 transition-all hover:border-primary hover:shadow-sm',
'twv3-space-x-2 flex cursor-pointer items-center rounded-md border p-4 transition-all hover:border-primary hover:shadow-xs',
{
'border-primary ring-2 ring-primary/10 ring-offset-1': plan[billingPeriod]?.id === value,
},
@@ -382,7 +382,7 @@ const BillingPlanForm = ({ value, onChange, plans, canCreateFreeOrganisation }:
<Link
to="https://documen.so/enterprise-cta"
target="_blank"
className="flex items-center space-x-2 rounded-md border bg-muted/30 p-4"
className="twv3-space-x-2 flex items-center rounded-md border bg-muted/30 p-4"
>
<div className="flex-1 font-normal">
<p className="font-medium text-muted-foreground">
@@ -117,7 +117,7 @@ export const OrganisationDeleteDialog = ({ trigger }: OrganisationDeleteDialogPr
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<fieldset className="twv3-space-y-4 flex h-full flex-col" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="organisationName"
@@ -114,7 +114,7 @@ export const OrganisationEmailCreateDialog = ({
<Dialog {...props} open={open} onOpenChange={(value) => !isPending && setOpen(value)}>
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
{trigger ?? (
<Button className="flex-shrink-0" variant="secondary">
<Button className="shrink-0" variant="secondary">
<Trans>Add Email</Trans>
</Button>
)}
@@ -135,7 +135,7 @@ export const OrganisationEmailCreateDialog = ({
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset className="flex h-full flex-col space-y-4" disabled={isPending}>
<fieldset className="twv3-space-y-4 flex h-full flex-col" disabled={isPending}>
<FormField
control={form.control}
name="emailName"
@@ -113,7 +113,7 @@ export const OrganisationEmailDomainCreateDialog = ({ trigger, ...props }: Organ
<Dialog {...props} open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
{trigger ?? (
<Button className="flex-shrink-0" variant="secondary">
<Button className="shrink-0" variant="secondary">
<Trans>Add Email Domain</Trans>
</Button>
)}
@@ -135,7 +135,7 @@ export const OrganisationEmailDomainCreateDialog = ({ trigger, ...props }: Organ
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<fieldset className="twv3-space-y-4 flex h-full flex-col" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="domain"
@@ -106,7 +106,7 @@ export const OrganisationEmailDomainDeleteDialog = ({
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset disabled={form.formState.isSubmitting} className="space-y-4">
<fieldset disabled={form.formState.isSubmitting} className="twv3-space-y-4">
<FormField
control={form.control}
name="confirmText"
@@ -59,11 +59,11 @@ export const OrganisationEmailDomainRecordContent = ({ records }: { records: Dom
</DialogDescription>
</DialogHeader>
<div className="space-y-6">
<div className="space-y-4">
<div className="twv3-space-y-6">
<div className="twv3-space-y-4">
{records.map((record) => (
<div className="space-y-4 rounded-md border p-4" key={record.name}>
<div className="space-y-2">
<div className="twv3-space-y-4 rounded-md border p-4" key={record.name}>
<div className="twv3-space-y-2">
<Label>
<Trans>Record Type</Trans>
</Label>
@@ -79,7 +79,7 @@ export const OrganisationEmailDomainRecordContent = ({ records }: { records: Dom
</div>
</div>
<div className="space-y-2">
<div className="twv3-space-y-2">
<Label>
<Trans>Record Name</Trans>
</Label>
@@ -95,7 +95,7 @@ export const OrganisationEmailDomainRecordContent = ({ records }: { records: Dom
</div>
</div>
<div className="space-y-2">
<div className="twv3-space-y-2">
<Label>
<Trans>Record Value</Trans>
</Label>
@@ -115,7 +115,7 @@ export const OrganisationEmailUpdateDialog = ({
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset className="flex h-full flex-col space-y-4" disabled={isPending}>
<fieldset className="twv3-space-y-4 flex h-full flex-col" disabled={isPending}>
<FormField
control={form.control}
name="emailName"
@@ -108,7 +108,7 @@ export const OrganisationGroupCreateDialog = ({ trigger, ...props }: Organisatio
<Dialog {...props} open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
{trigger ?? (
<Button className="flex-shrink-0" variant="secondary">
<Button className="shrink-0" variant="secondary">
<Trans>Create group</Trans>
</Button>
)}
@@ -127,7 +127,7 @@ export const OrganisationGroupCreateDialog = ({ trigger, ...props }: Organisatio
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<fieldset className="twv3-space-y-4 flex h-full flex-col" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="name"
@@ -313,10 +313,10 @@ export const OrganisationMemberInviteDialog = ({ trigger, ...props }: Organisati
<TabsContent value="INDIVIDUAL">
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<div className="custom-scrollbar -m-1 max-h-[60vh] space-y-4 overflow-y-auto p-1">
<fieldset className="twv3-space-y-4 flex h-full flex-col" disabled={form.formState.isSubmitting}>
<div className="custom-scrollbar twv3-space-y-4 -m-1 max-h-[60vh] overflow-y-auto p-1">
{organisationMemberInvites.map((organisationMemberInvite, index) => (
<div className="flex w-full flex-row space-x-4" key={organisationMemberInvite.id}>
<div className="twv3-space-x-4 flex w-full flex-row" key={organisationMemberInvite.id}>
<FormField
control={form.control}
name={`invitations.${index}.email`}
@@ -409,7 +409,7 @@ export const OrganisationMemberInviteDialog = ({ trigger, ...props }: Organisati
</TabsContent>
<TabsContent value="BULK">
<div className="mt-4 space-y-4">
<div className="twv3-space-y-4 mt-4">
<Card gradient className="h-32">
<CardContent
className="flex h-full cursor-pointer flex-col items-center justify-center rounded-lg p-0 text-muted-foreground/80 transition-colors hover:text-muted-foreground/90"
@@ -147,7 +147,7 @@ export const PasskeyCreateDialog = ({ trigger, onSuccess, ...props }: PasskeyCre
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<fieldset className="twv3-space-y-4 flex h-full flex-col" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="passkeyName"
@@ -214,7 +214,7 @@ export const ManagePublicTemplateDialog = ({
return (
<Dialog {...props} open={isOpen || open} onOpenChange={handleOnOpenChange}>
<fieldset disabled={isLoading} className="relative flex-shrink-0">
<fieldset disabled={isLoading} className="relative shrink-0">
<DialogTrigger asChild>{trigger}</DialogTrigger>
<AnimateGenericFadeInOut motionKey={currentStep}>
@@ -311,7 +311,7 @@ export const ManagePublicTemplateDialog = ({
</DialogHeader>
<Form {...form}>
<form className="flex h-full flex-col space-y-4" onSubmit={form.handleSubmit(onFormSubmit)}>
<form className="twv3-space-y-4 flex h-full flex-col" onSubmit={form.handleSubmit(onFormSubmit)}>
<FormField
control={form.control}
name="publicTitle"
@@ -102,8 +102,8 @@ export const SignFieldCheckboxDialog = createCallable<SignFieldCheckboxDialogPro
call.end(data.values.map((value, i) => (value.checked ? i : null)).filter((value) => value !== null)),
)}
>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<ul className="space-y-3">
<fieldset className="twv3-space-y-4 flex h-full flex-col" disabled={form.formState.isSubmitting}>
<ul className="twv3-space-y-3">
{(formValues.values || []).map((value, index) => (
<li key={`checkbox-${index}`}>
<FormField
@@ -51,7 +51,7 @@ export const SignFieldEmailDialog = createCallable<SignFieldEmailDialogProps, st
<Form {...form}>
<form onSubmit={form.handleSubmit((data) => call.end(data.email))}>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<fieldset className="twv3-space-y-4 flex h-full flex-col" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="email"
@@ -49,7 +49,7 @@ export const SignFieldInitialsDialog = createCallable<SignFieldInitialsDialogPro
<Form {...form}>
<form onSubmit={form.handleSubmit((data) => call.end(data.initials))}>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<fieldset className="twv3-space-y-4 flex h-full flex-col" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="initials"
@@ -49,7 +49,7 @@ export const SignFieldNameDialog = createCallable<SignFieldNameDialogProps, stri
<Form {...form}>
<form onSubmit={form.handleSubmit((data) => call.end(data.name))}>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<fieldset className="twv3-space-y-4 flex h-full flex-col" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="name"
@@ -107,7 +107,7 @@ export const SignFieldNumberDialog = createCallable<SignFieldNumberDialogProps,
<Form {...form}>
<form onSubmit={form.handleSubmit((data) => call.end(data.number))}>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<fieldset className="twv3-space-y-4 flex h-full flex-col" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="number"
@@ -51,7 +51,7 @@ export const SignFieldTextDialog = createCallable<SignFieldTextDialogProps, stri
<Form {...form}>
<form onSubmit={form.handleSubmit((data) => call.end(data.text))}>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<fieldset className="twv3-space-y-4 flex h-full flex-col" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="text"
@@ -154,7 +154,7 @@ export const TeamCreateDialog = ({ trigger, onCreated, ...props }: TeamCreateDia
<Dialog {...props} open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
{trigger ?? (
<Button className="flex-shrink-0" variant="secondary">
<Button className="shrink-0" variant="secondary">
<Trans>Create team</Trans>
</Button>
)}
@@ -195,7 +195,7 @@ export const TeamCreateDialog = ({ trigger, onCreated, ...props }: TeamCreateDia
{dialogState === 'form' && (
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<fieldset className="twv3-space-y-4 flex h-full flex-col" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="teamName"
@@ -256,7 +256,7 @@ export const TeamCreateDialog = ({ trigger, onCreated, ...props }: TeamCreateDia
control={form.control}
name="inheritMembers"
render={({ field }) => (
<FormItem className="flex items-center space-x-2">
<FormItem className="twv3-space-x-2 flex items-center">
<FormControl>
<div className="flex items-center">
<Checkbox id="inherit-members" checked={field.value} onCheckedChange={field.onChange} />
@@ -142,7 +142,7 @@ export const TeamDeleteDialog = ({ trigger, teamId, teamName, redirectTo }: Team
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<fieldset className="twv3-space-y-4 flex h-full flex-col" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="teamName"
@@ -119,7 +119,7 @@ export const TeamEmailAddDialog = ({ teamId, trigger, ...props }: TeamEmailAddDi
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<fieldset className="twv3-space-y-4 flex h-full flex-col" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="name"
@@ -104,7 +104,7 @@ export const TeamEmailUpdateDialog = ({ teamEmail, trigger, ...props }: TeamEmai
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<fieldset className="twv3-space-y-4 flex h-full flex-col" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="name"
@@ -202,10 +202,10 @@ export const TeamGroupCreateDialog = ({ ...props }: TeamGroupCreateDialogProps)
{step === 'ROLES' && (
<>
<div className="custom-scrollbar -m-1 max-h-[60vh] space-y-4 overflow-y-auto p-1">
<div className="custom-scrollbar twv3-space-y-4 -m-1 max-h-[60vh] overflow-y-auto p-1">
{form.getValues('groups').map((group, index) => (
<div className="flex w-full flex-row space-x-4" key={index}>
<div className="w-full space-y-2">
<div className="twv3-space-x-4 flex w-full flex-row" key={index}>
<div className="twv3-space-y-2 w-full">
{index === 0 && (
<FormLabel>
<Trans>Group</Trans>
@@ -241,7 +241,7 @@ export const TeamMemberCreateDialog = ({ trigger, ...props }: TeamMemberCreateDi
control={form.control}
name="members"
render={({ field }) => (
<FormItem className="space-y-2">
<FormItem className="twv3-space-y-2">
<FormLabel>
<Trans>Members</Trans>
</FormLabel>
@@ -310,7 +310,7 @@ export const TeamMemberCreateDialog = ({ trigger, ...props }: TeamMemberCreateDi
</FormDescription>
{canInviteOrganisationMembers && (
<Alert variant="neutral" className="mt-2 flex items-center gap-2 space-y-0">
<Alert variant="neutral" className="twv3-space-y-0 mt-2 flex items-center gap-2">
<div>
<UserPlusIcon className="h-5 w-5 text-muted-foreground" />
</div>
@@ -358,10 +358,10 @@ export const TeamMemberCreateDialog = ({ trigger, ...props }: TeamMemberCreateDi
{step === 'MEMBERS' && (
<>
<div className="custom-scrollbar -m-1 max-h-[60vh] space-y-4 overflow-y-auto p-1">
<div className="custom-scrollbar twv3-space-y-4 -m-1 max-h-[60vh] overflow-y-auto p-1">
{form.getValues('members').map((member, index) => (
<div className="flex w-full flex-row space-x-4" key={index}>
<div className="w-full space-y-2">
<div className="twv3-space-x-4 flex w-full flex-row" key={index}>
<div className="twv3-space-y-2 w-full">
{index === 0 && (
<FormLabel>
<Trans>Member</Trans>
@@ -16,6 +16,17 @@ import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { useState } from 'react';
import { match } from 'ts-pattern';
/**
* The reason a team member cannot be removed from the team. When set, the delete
* dialog explains the reason instead of offering a confirm button.
*/
export type TeamMemberDeleteDisableReason =
| 'TEAM_OWNER'
| 'HIGHER_ROLE'
| 'INHERIT_MEMBER_ENABLED'
| 'INHERITED_MEMBER';
export type TeamMemberDeleteDialogProps = {
teamId: number;
@@ -23,7 +34,7 @@ export type TeamMemberDeleteDialogProps = {
memberId: string;
memberName: string;
memberEmail: string;
isInheritMemberEnabled: boolean | null;
disableReason?: TeamMemberDeleteDisableReason | null;
trigger?: React.ReactNode;
};
@@ -34,7 +45,7 @@ export const TeamMemberDeleteDialog = ({
memberId,
memberName,
memberEmail,
isInheritMemberEnabled,
disableReason,
}: TeamMemberDeleteDialogProps) => {
const [open, setOpen] = useState(false);
@@ -86,10 +97,19 @@ export const TeamMemberDeleteDialog = ({
</DialogDescription>
</DialogHeader>
{isInheritMemberEnabled ? (
{disableReason ? (
<Alert variant="neutral">
<AlertDescription>
<Trans>You cannot remove members from this team if the inherit member feature is enabled.</Trans>
{match(disableReason)
.with('TEAM_OWNER', () => <Trans>You cannot remove the organisation owner from the team.</Trans>)
.with('HIGHER_ROLE', () => <Trans>You cannot remove a member with a role higher than your own.</Trans>)
.with('INHERIT_MEMBER_ENABLED', () => (
<Trans>You cannot remove members from this team while the inherit member feature is enabled.</Trans>
))
.with('INHERITED_MEMBER', () => (
<Trans>This member is inherited from a group and cannot be removed from the team directly.</Trans>
))
.exhaustive()}
</AlertDescription>
</Alert>
) : (
@@ -109,11 +129,10 @@ export const TeamMemberDeleteDialog = ({
<Trans>Close</Trans>
</Button>
{!isInheritMemberEnabled && (
{!disableReason && (
<Button
type="submit"
variant="destructive"
disabled={Boolean(isInheritMemberEnabled)}
loading={isDeletingTeamMember}
onClick={async () => deleteTeamMember({ teamId, memberId })}
>
@@ -222,7 +222,7 @@ export const TemplateBulkSendDialog = ({ templateId, recipients, trigger, onSucc
control={form.control}
name="sendImmediately"
render={({ field }) => (
<FormItem className="flex items-center space-x-2">
<FormItem className="twv3-space-x-2 flex items-center">
<FormControl>
<div className="flex items-center">
<Checkbox id="send-immediately" checked={field.value} onCheckedChange={field.onChange} />
@@ -211,7 +211,7 @@ export const TemplateDirectLinkDialog = ({
</DialogDescription>
</DialogHeader>
<ul className="mt-4 space-y-4 pl-12">
<ul className="twv3-space-y-4 mt-4 pl-12">
{DIRECT_TEMPLATE_DOCUMENTATION.map((step, index) => (
<li className="relative" key={index}>
<div className="absolute -left-12">
@@ -259,7 +259,7 @@ export const TemplateDirectLinkDialog = ({
.with({ token: P.nullish, currentStep: 'SELECT_RECIPIENT' }, () => (
<DialogContent className="relative">
{isCreatingTemplateDirectLink && validDirectTemplateRecipients.length !== 0 && (
<div className="absolute inset-0 z-50 flex items-center justify-center rounded bg-white/50 dark:bg-black/50">
<div className="absolute inset-0 z-50 flex items-center justify-center rounded-sm bg-white/50 dark:bg-black/50">
<LoaderIcon className="h-6 w-6 animate-spin text-gray-500" />
</div>
)}
@@ -405,7 +405,7 @@ export const TemplateDirectLinkDialog = ({
className="h-8 w-8"
onClick={() => void onCopyClick(token)}
>
<ClipboardCopyIcon className="h-4 w-4 flex-shrink-0" />
<ClipboardCopyIcon className="h-4 w-4 shrink-0" />
</Button>
</div>
</div>
@@ -1,232 +0,0 @@
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { FolderType } from '@documenso/lib/types/folder-type';
import { formatTemplatesPath } from '@documenso/lib/utils/teams';
import { trpc } from '@documenso/trpc/react';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@documenso/ui/primitives/dialog';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type * as DialogPrimitive from '@radix-ui/react-dialog';
import { FolderIcon, HomeIcon, Loader2, Search } from 'lucide-react';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useNavigate } from 'react-router';
import { z } from 'zod';
import { useCurrentTeam } from '~/providers/team';
export type TemplateMoveToFolderDialogProps = {
templateId: number;
templateTitle: string;
isOpen: boolean;
onOpenChange: (open: boolean) => void;
currentFolderId?: string | null;
} & Omit<DialogPrimitive.DialogProps, 'children'>;
const ZMoveTemplateFormSchema = z.object({
folderId: z.string().nullable().optional(),
});
type TMoveTemplateFormSchema = z.infer<typeof ZMoveTemplateFormSchema>;
export function TemplateMoveToFolderDialog({
templateId,
templateTitle,
isOpen,
onOpenChange,
currentFolderId,
...props
}: TemplateMoveToFolderDialogProps) {
const { _ } = useLingui();
const { toast } = useToast();
const navigate = useNavigate();
const team = useCurrentTeam();
const [searchTerm, setSearchTerm] = useState('');
const form = useForm<TMoveTemplateFormSchema>({
resolver: zodResolver(ZMoveTemplateFormSchema),
defaultValues: {
folderId: currentFolderId ?? null,
},
});
const { data: folders, isLoading: isFoldersLoading } = trpc.folder.findFoldersInternal.useQuery(
{
parentId: currentFolderId ?? null,
type: FolderType.TEMPLATE,
},
{
enabled: isOpen,
},
);
const { mutateAsync: updateTemplate } = trpc.template.updateTemplate.useMutation();
useEffect(() => {
if (!isOpen) {
form.reset();
setSearchTerm('');
} else {
form.reset({ folderId: currentFolderId ?? null });
}
}, [isOpen, currentFolderId, form]);
const onSubmit = async (data: TMoveTemplateFormSchema) => {
try {
await updateTemplate({
templateId,
data: {
folderId: data.folderId ?? null,
},
});
toast({
title: _(msg`Template moved`),
description: _(msg`The template has been moved successfully.`),
variant: 'default',
});
onOpenChange(false);
const templatesPath = formatTemplatesPath(team.url);
if (data.folderId) {
void navigate(`${templatesPath}/f/${data.folderId}`);
} else {
void navigate(templatesPath);
}
} catch (err) {
const error = AppError.parseError(err);
if (error.code === AppErrorCode.NOT_FOUND) {
toast({
title: _(msg`Error`),
description: _(msg`The folder you are trying to move the template to does not exist.`),
variant: 'destructive',
});
return;
}
toast({
title: _(msg`Error`),
description: _(msg`An error occurred while moving the template.`),
variant: 'destructive',
});
}
};
const filteredFolders = folders?.data?.filter((folder) =>
folder.name.toLowerCase().includes(searchTerm.toLowerCase()),
);
return (
<Dialog {...props} open={isOpen} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>
<Trans>Move Template to Folder</Trans>
</DialogTitle>
<DialogDescription>
<Trans>Move &quot;{templateTitle}&quot; to a folder</Trans>
</DialogDescription>
</DialogHeader>
<div className="relative">
<Search className="absolute top-3 left-2 h-4 w-4 text-muted-foreground" />
<Input
placeholder={_(msg`Search folders...`)}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-8"
/>
</div>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="mt-4 flex flex-col gap-y-4">
<FormField
control={form.control}
name="folderId"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans>Folder</Trans>
</FormLabel>
<FormControl>
<div className="max-h-96 space-y-2 overflow-y-auto">
{isFoldersLoading ? (
<div className="flex h-10 items-center justify-center">
<Loader2 className="h-4 w-4 animate-spin" />
</div>
) : (
<>
<Button
type="button"
variant={field.value === null ? 'default' : 'outline'}
className="w-full justify-start"
onClick={() => field.onChange(null)}
disabled={currentFolderId === null}
>
<HomeIcon className="mr-2 h-4 w-4" />
<Trans>Home (No Folder)</Trans>
</Button>
{filteredFolders?.map((folder) => (
<Button
key={folder.id}
type="button"
variant={field.value === folder.id ? 'default' : 'outline'}
className="w-full justify-start"
onClick={() => field.onChange(folder.id)}
disabled={currentFolderId === folder.id}
>
<FolderIcon className="mr-2 h-4 w-4" />
{folder.name}
</Button>
))}
{searchTerm && filteredFolders?.length === 0 && (
<div className="px-2 py-2 text-center text-muted-foreground text-sm">
<Trans>No folders found</Trans>
</div>
)}
</>
)}
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<DialogFooter>
<Button type="button" variant="secondary" onClick={() => onOpenChange(false)}>
<Trans>Cancel</Trans>
</Button>
<Button type="submit" disabled={isFoldersLoading || form.formState.isSubmitting}>
<Trans>Move</Trans>
</Button>
</DialogFooter>
</form>
</Form>
</DialogContent>
</Dialog>
);
}
@@ -240,9 +240,9 @@ export function TemplateUseDialog({
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<fieldset className="flex h-full flex-col" disabled={form.formState.isSubmitting}>
<div className="custom-scrollbar -m-1 max-h-[60vh] space-y-4 overflow-y-auto p-1">
<div className="custom-scrollbar twv3-space-y-4 -m-1 max-h-[60vh] overflow-y-auto p-1">
{formRecipients.map((recipient, index) => (
<div className="flex w-full flex-row space-x-4" key={recipient.id}>
<div className="twv3-space-x-4 flex w-full flex-row" key={recipient.id}>
{templateSigningOrder === DocumentSigningOrder.SEQUENTIAL && (
<FormField
control={form.control}
@@ -337,7 +337,7 @@ export function TemplateUseDialog({
<InfoIcon className="mx-1 h-4 w-4" />
</TooltipTrigger>
<TooltipContent className="z-[99999] max-w-md space-y-2 p-4 text-muted-foreground">
<TooltipContent className="twv3-space-y-2 z-[99999] max-w-md p-4 text-muted-foreground">
<p>
<Trans>
The document will be immediately sent to recipients if this is checked.
@@ -362,7 +362,7 @@ export function TemplateUseDialog({
<TooltipTrigger type="button">
<InfoIcon className="mx-1 h-4 w-4" />
</TooltipTrigger>
<TooltipContent className="z-[99999] max-w-md space-y-2 p-4 text-muted-foreground">
<TooltipContent className="twv3-space-y-2 z-[99999] max-w-md p-4 text-muted-foreground">
<p>
<Trans>Create the document as pending and ready to sign.</Trans>
</p>
@@ -414,7 +414,7 @@ export function TemplateUseDialog({
<TooltipTrigger type="button">
<InfoIcon className="mx-1 h-4 w-4" />
</TooltipTrigger>
<TooltipContent className="z-[99999] max-w-md space-y-2 p-4 text-muted-foreground">
<TooltipContent className="twv3-space-y-2 z-[99999] max-w-md p-4 text-muted-foreground">
<p>
<Trans>
Upload a custom document to use instead of the template's default document
@@ -429,7 +429,7 @@ export function TemplateUseDialog({
/>
{form.watch('useCustomDocument') && (
<div className="my-4 space-y-2">
<div className="twv3-space-y-2 my-4">
{isLoadingEnvelopeItems ? (
<SpinnerBox className="py-16" />
) : (
@@ -445,7 +445,7 @@ export function TemplateUseDialog({
key={item.id}
className="flex items-center gap-4 rounded-lg border border-border bg-card p-4 transition-colors hover:bg-accent/10"
>
<div className="flex-shrink-0">
<div className="shrink-0">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10">
<FileTextIcon className="h-5 w-5 text-primary" />
</div>
@@ -464,7 +464,7 @@ export function TemplateUseDialog({
</p>
</div>
<div className="flex flex-shrink-0 items-center gap-2">
<div className="flex shrink-0 items-center gap-2">
{field.value ? (
<div className="">
<Button
@@ -117,7 +117,7 @@ export default function TokenDeleteDialog({ token, onDelete, children }: TokenDe
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<fieldset className="twv3-space-y-4 flex h-full flex-col" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="tokenName"
@@ -94,7 +94,7 @@ export const WebhookCreateDialog = ({ trigger, ...props }: WebhookCreateDialogPr
<Dialog open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)} {...props}>
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
{trigger ?? (
<Button className="flex-shrink-0">
<Button className="shrink-0">
<Trans>Create Webhook</Trans>
</Button>
)}
@@ -112,7 +112,7 @@ export const WebhookCreateDialog = ({ trigger, ...props }: WebhookCreateDialogPr
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<fieldset className="twv3-space-y-4 flex h-full flex-col" disabled={form.formState.isSubmitting}>
<div className="flex flex-col-reverse gap-4 md:flex-row">
<FormField
control={form.control}
@@ -108,7 +108,7 @@ export const WebhookDeleteDialog = ({ webhook, children }: WebhookDeleteDialogPr
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<fieldset className="twv3-space-y-4 flex h-full flex-col" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="webhookUrl"
@@ -88,7 +88,7 @@ export const WebhookTestDialog = ({ webhook, children }: WebhookTestDialogProps)
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
<fieldset className="twv3-space-y-4 flex h-full flex-col" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="event"
@@ -75,7 +75,7 @@ export const ConfigureDocumentAdvancedSettings = ({
</TabsList>
<TabsContent value="general" className="mt-0">
<div className="flex flex-col space-y-6">
<div className="twv3-space-y-6 flex flex-col">
{features.allowConfigureSignatureTypes && (
<FormField
control={control}
@@ -215,7 +215,7 @@ export const ConfigureDocumentAdvancedSettings = ({
{features.allowConfigureCommunication && (
<TabsContent value="communication" className="mt-0">
<div className="flex flex-col space-y-6">
<div className="twv3-space-y-6 flex flex-col">
<FormField
control={control}
name="meta.distributionMethod"
@@ -254,7 +254,7 @@ export const ConfigureDocumentAdvancedSettings = ({
/>
<fieldset
className="flex flex-col space-y-6 disabled:cursor-not-allowed disabled:opacity-60"
className="twv3-space-y-6 flex flex-col disabled:cursor-not-allowed disabled:opacity-60"
disabled={!isEmailDistribution}
>
<FormField
@@ -147,7 +147,7 @@ export const ConfigureDocumentRecipients = ({ control, isSubmitting }: Configure
control={control}
name="meta.signingOrder"
render={({ field }) => (
<FormItem className="mb-6 flex flex-row items-center space-x-2 space-y-0">
<FormItem className="twv3-space-x-2 twv3-space-y-0 mb-6 flex flex-row items-center">
<FormControl>
<Checkbox
{...field}
@@ -173,7 +173,7 @@ export const ConfigureDocumentRecipients = ({ control, isSubmitting }: Configure
control={control}
name="meta.allowDictateNextSigner"
render={({ field: { value, ...field } }) => (
<FormItem className="mb-6 flex flex-row items-center space-x-2 space-y-0">
<FormItem className="twv3-space-x-2 twv3-space-y-0 mb-6 flex flex-row items-center">
<FormControl>
<Checkbox
{...field}
@@ -221,7 +221,7 @@ export const ConfigureDocumentRecipients = ({ control, isSubmitting }: Configure
>
<Droppable droppableId="signers">
{(provided) => (
<div {...provided.droppableProps} ref={provided.innerRef} className="space-y-2">
<div {...provided.droppableProps} ref={provided.innerRef} className="twv3-space-y-2">
{signers.map((signer, index) => (
<Draggable
key={signer.id}
@@ -254,7 +254,7 @@ export const ConfigureDocumentRecipients = ({ control, isSubmitting }: Configure
'mb-6': errors?.signers?.[index] && !errors?.signers?.[index]?.signingOrder,
})}
>
<GripVertical className="h-5 w-5 flex-shrink-0 opacity-40" />
<GripVertical className="h-5 w-5 shrink-0 opacity-40" />
<FormControl>
<Input
type="number"
@@ -158,7 +158,7 @@ export const ConfigureDocumentUpload = ({ isSubmitting = false }: ConfigureDocum
/>
<div
className={cn('flex flex-col space-y-1', {
className={cn('twv3-space-y-1 flex flex-col', {
'text-primary': isDragActive,
'text-muted-foreground': !isDragActive,
})}
@@ -75,7 +75,7 @@ export const ConfigureDocumentView = ({
const onFormSubmit = handleSubmit(onSubmit);
return (
<div className="flex w-full flex-col space-y-8">
<div className="twv3-space-y-8 flex w-full flex-col">
<div>
<h2 className="mb-1 font-semibold text-foreground text-xl">
{isTemplate ? <Trans>Configure Template</Trans> : <Trans>Configure Document</Trans>}
@@ -91,7 +91,7 @@ export const ConfigureDocumentView = ({
</div>
<Form {...form}>
<div className="flex flex-col space-y-8">
<div className="twv3-space-y-8 flex flex-col">
<div>
<FormField
control={control}
@@ -462,7 +462,7 @@ export const ConfigureFieldsView = ({
<hr className="my-6" />
<div className="space-y-2">
<div className="twv3-space-y-2">
<FieldSelector
selectedField={selectedField}
onSelectedFieldChange={setSelectedField}
@@ -604,7 +604,7 @@ export const ConfigureFieldsView = ({
<hr className="my-6" />
<div className="space-y-2">
<div className="twv3-space-y-2">
<FieldSelector
selectedField={selectedField}
onSelectedFieldChange={(field) => {
@@ -372,7 +372,7 @@ export const EmbedDirectTemplateClientPage = ({
{/* Widget */}
<div
key={isExpanded ? 'expanded' : 'collapsed'}
className="group/document-widget fixed bottom-8 left-0 z-50 h-fit max-h-[calc(100dvh-2rem)] w-full flex-shrink-0 px-6 md:sticky md:top-4 md:bottom-[unset] md:z-auto md:w-[350px] md:px-0"
className="group/document-widget fixed bottom-8 left-0 z-50 h-fit max-h-[calc(100dvh-2rem)] w-full shrink-0 px-6 md:sticky md:top-4 md:bottom-[unset] md:z-auto md:w-[350px] md:px-0"
data-expanded={isExpanded || undefined}
>
<div className="flex h-fit w-full flex-col rounded-xl border border-border bg-widget px-4 py-4 md:min-h-[min(calc(100dvh-2rem),48rem)] md:py-6">
@@ -304,7 +304,7 @@ export const EmbedSignDocumentV1ClientPage = ({
{/* Widget */}
<div
key={isExpanded ? 'expanded' : 'collapsed'}
className="embed--DocumentWidgetContainer group/document-widget fixed bottom-8 left-0 z-50 h-fit max-h-[calc(100dvh-2rem)] w-full flex-shrink-0 px-6 md:sticky md:top-4 md:bottom-[unset] md:z-auto md:w-[350px] md:px-0"
className="embed--DocumentWidgetContainer group/document-widget fixed bottom-8 left-0 z-50 h-fit max-h-[calc(100dvh-2rem)] w-full shrink-0 px-6 md:sticky md:top-4 md:bottom-[unset] md:z-auto md:w-[350px] md:px-0"
data-expanded={isExpanded || undefined}
>
<div className="embed--DocumentWidget flex w-full flex-col rounded-xl border border-border bg-widget px-4 py-4 md:py-6">
@@ -369,7 +369,7 @@ export const EmbedSignDocumentV1ClientPage = ({
<fieldset className="mt-2 rounded-2xl border border-border bg-white p-3 dark:bg-background">
<RadioGroup
className="gap-0 space-y-3 shadow-none"
className="twv3-space-y-3 gap-0 shadow-none"
value={selectedSignerId?.toString()}
onValueChange={(value) => setSelectedSignerId(Number(value))}
>
@@ -234,7 +234,7 @@ export const MultiSignDocumentSigningView = ({
{document.status !== DocumentStatus.COMPLETED && (
<div
key={isExpanded ? 'expanded' : 'collapsed'}
className="embed--DocumentWidgetContainer group/document-widget fixed bottom-8 left-0 z-50 h-fit max-h-[calc(100dvh-2rem)] w-full flex-shrink-0 px-6 md:sticky md:top-0 md:bottom-[unset] md:z-auto md:w-[350px] md:px-0"
className="embed--DocumentWidgetContainer group/document-widget fixed bottom-8 left-0 z-50 h-fit max-h-[calc(100dvh-2rem)] w-full shrink-0 px-6 md:sticky md:top-0 md:bottom-[unset] md:z-auto md:w-[350px] md:px-0"
data-expanded={isExpanded || undefined}
>
<div className="embed--DocumentWidget flex w-full flex-col rounded-xl border border-border bg-widget px-4 py-4 md:py-6">
@@ -98,7 +98,7 @@ export const DisableAuthenticatorAppDialog = () => {
return (
<Dialog open={isOpen} onOpenChange={onCloseTwoFactorDisableDialog}>
<DialogTrigger asChild={true}>
<Button className="flex-shrink-0" variant="destructive">
<Button className="shrink-0" variant="destructive">
<Trans>Disable 2FA</Trans>
</Button>
</DialogTrigger>
@@ -139,7 +139,7 @@ export const EnableAuthenticatorAppDialog = ({ onSuccess }: EnableAuthenticatorA
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild={true}>
<Button
className="flex-shrink-0"
className="shrink-0"
loading={isSettingUp2FA}
onClick={(e) => {
e.preventDefault();
@@ -75,7 +75,7 @@ export const ViewRecoveryCodesDialog = () => {
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button className="flex-shrink-0">
<Button className="shrink-0">
<Trans>View Codes</Trans>
</Button>
</DialogTrigger>
@@ -120,7 +120,7 @@ export const ViewRecoveryCodesDialog = () => {
</DialogDescription>
</DialogHeader>
<fieldset className="flex flex-col space-y-4" disabled={form.formState.isSubmitting}>
<fieldset className="twv3-space-y-4 flex flex-col" disabled={form.formState.isSubmitting}>
<FormField
name="token"
control={form.control}
@@ -500,7 +500,7 @@ export function BrandingPreferencesForm({
<Accordion type="single" collapsible>
<AccordionItem value="custom-css" className="border-none">
<AccordionTrigger className="rounded border px-3 py-2 text-left text-foreground hover:bg-muted/40 hover:no-underline">
<AccordionTrigger className="rounded-sm border px-3 py-2 text-left text-foreground hover:bg-muted/40 hover:no-underline">
<Trans>Advanced Custom CSS</Trans>
</AccordionTrigger>
@@ -538,7 +538,7 @@ export function BrandingPreferencesForm({
</div>
)}
<div className="flex flex-row justify-end space-x-4">
<div className="twv3-space-x-4 flex flex-row justify-end">
<Button type="submit" loading={form.formState.isSubmitting}>
<Trans>Update</Trans>
</Button>
@@ -552,7 +552,7 @@ export const DocumentPreferencesForm = ({
)}
{(field.value !== null || !canInherit) && (
<div className="space-y-4">
<div className="twv3-space-y-4">
<DefaultRecipientsMultiSelectCombobox
listValues={recipients}
onChange={field.onChange}
@@ -756,7 +756,7 @@ export const DocumentPreferencesForm = ({
/>
)}
<div className="flex flex-row justify-end space-x-4">
<div className="twv3-space-x-4 flex flex-row justify-end">
<Button type="submit" loading={form.formState.isSubmitting}>
<Trans>Update</Trans>
</Button>
@@ -272,7 +272,7 @@ export const EditorFieldCheckboxForm = ({
<EditorGenericReadOnlyField formControl={form.control} />
<section className="space-y-2">
<section className="twv3-space-y-2">
<div className="-mx-4 mt-2 mb-4">
<Separator />
</div>
@@ -287,7 +287,7 @@ export const EditorFieldCheckboxForm = ({
</button>
</div>
<ul className="space-y-2">
<ul className="twv3-space-y-2">
{(formValues.values || []).map((value, index) => (
<li key={`checkbox-value-${index}`} className="flex flex-row items-center gap-2">
<FormField
@@ -190,7 +190,7 @@ export const EditorFieldDropdownForm = ({
<EditorGenericReadOnlyField formControl={form.control} />
<section className="space-y-2">
<section className="twv3-space-y-2">
<div className="-mx-4 mt-2 mb-4">
<Separator />
</div>
@@ -205,7 +205,7 @@ export const EditorFieldDropdownForm = ({
</button>
</div>
<ul className="space-y-2">
<ul className="twv3-space-y-2">
{(formValues.values || []).map((value, index) => (
<li key={`dropdown-value-${index}`} className="flex flex-row gap-2">
<FormField
@@ -237,7 +237,7 @@ export const EditorGenericRequiredField = ({
control={formControl}
name="required"
render={({ field }) => (
<FormItem className={cn('flex items-center space-x-2', className)}>
<FormItem className={cn('twv3-space-x-2 flex items-center', className)}>
<FormControl>
<div className="flex items-center">
<Checkbox
@@ -281,7 +281,7 @@ export const EditorGenericReadOnlyField = ({
control={formControl}
name="readOnly"
render={({ field }) => (
<FormItem className={cn('flex items-center space-x-2', className)}>
<FormItem className={cn('twv3-space-x-2 flex items-center', className)}>
<FormControl>
<div className="flex items-center">
<Checkbox
@@ -230,7 +230,7 @@ export const EditorFieldNumberForm = ({
<EditorGenericReadOnlyField formControl={form.control} />
{/* Validation section */}
<section className="space-y-2">
<section className="twv3-space-y-2">
<div className="-mx-4 mt-2 mb-4">
<Separator />
</div>
@@ -149,7 +149,7 @@ export const EditorFieldRadioForm = ({
<EditorGenericReadOnlyField formControl={form.control} />
<section className="space-y-2">
<section className="twv3-space-y-2">
<div className="-mx-4 mt-2 mb-4">
<Separator />
</div>
@@ -164,7 +164,7 @@ export const EditorFieldRadioForm = ({
</button>
</div>
<ul className="space-y-2">
<ul className="twv3-space-y-2">
{(formValues.values || []).map((value, index) => (
<li key={`radio-value-${index}`} className="flex flex-row items-center gap-2">
<FormField
@@ -185,7 +185,7 @@ export const EmailPreferencesForm = ({ settings, onFormSubmit, canInherit }: Ema
)}
{field.value && (
<div className="space-y-2 rounded-md border p-4">
<div className="twv3-space-y-2 rounded-md border p-4">
<DocumentEmailCheckboxes
value={field.value ?? DEFAULT_DOCUMENT_EMAIL_SETTINGS}
onChange={(value) => field.onChange(value)}
@@ -203,7 +203,7 @@ export const EmailPreferencesForm = ({ settings, onFormSubmit, canInherit }: Ema
)}
/>
<div className="flex flex-row justify-end space-x-4">
<div className="twv3-space-x-4 flex flex-row justify-end">
<Button type="submit" loading={form.formState.isSubmitting}>
<Trans>Update</Trans>
</Button>
@@ -67,7 +67,7 @@ export const EmailTransportForm = ({
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset disabled={form.formState.isSubmitting} className="space-y-4">
<fieldset disabled={form.formState.isSubmitting} className="twv3-space-y-4">
<FormField
control={form.control}
name="name"
@@ -137,7 +137,7 @@ export const OrganisationUpdateForm = () => {
)}
/>
<div className="flex flex-row justify-end space-x-4">
<div className="twv3-space-x-4 flex flex-row justify-end">
<AnimatePresence>
{form.formState.isDirty && (
<motion.div
@@ -148,7 +148,7 @@ export const PublicProfileForm = ({ className, profile, onProfileUpdate }: Publi
<Button
type="button"
variant="none"
className="h-7 rounded bg-neutral-50 pr-0.5 pl-2 font-normal dark:border dark:border-neutral-500 dark:bg-neutral-600"
className="h-7 rounded-sm bg-neutral-50 pr-0.5 pl-2 font-normal dark:border dark:border-neutral-500 dark:bg-neutral-600"
onClick={async () => onCopy()}
>
<p>
@@ -156,7 +156,7 @@ export const PublicProfileForm = ({ className, profile, onProfileUpdate }: Publi
<span className="font-semibold">{field.value}</span>
</p>
<div className="ml-1 flex h-6 w-6 items-center justify-center rounded transition-all hover:bg-neutral-200 hover:active:bg-neutral-300 dark:hover:bg-neutral-500 dark:hover:active:bg-neutral-400">
<div className="ml-1 flex h-6 w-6 items-center justify-center rounded-sm transition-all hover:bg-neutral-200 hover:active:bg-neutral-300 dark:hover:bg-neutral-500 dark:hover:active:bg-neutral-400">
<AnimatePresence mode="wait" initial={false}>
<motion.div
key={copiedTimeout ? 'copied' : 'copy'}
@@ -228,7 +228,7 @@ export const PublicProfileForm = ({ className, profile, onProfileUpdate }: Publi
}}
/>
<div className="flex flex-row justify-end space-x-4">
<div className="twv3-space-x-4 flex flex-row justify-end">
<AnimatePresence>
{form.formState.isDirty && (
<motion.div
+1 -1
View File
@@ -209,7 +209,7 @@ export const SignUpForm = ({
return (
<div className={cn('flex justify-center gap-x-12', className)}>
<div className="relative hidden flex-1 overflow-hidden rounded-xl border border-border xl:flex">
<div className="absolute -inset-8 -z-[2] backdrop-blur">
<div className="absolute -inset-8 -z-[2] backdrop-blur-sm">
<img
src={communityCardsImage}
alt="community-cards"
@@ -72,7 +72,7 @@ export const SubscriptionClaimForm = ({
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset disabled={form.formState.isSubmitting} className="space-y-4">
<fieldset disabled={form.formState.isSubmitting} className="twv3-space-y-4">
<FormField
control={form.control}
name="name"
@@ -190,7 +190,7 @@ export const SubscriptionClaimForm = ({
<Trans>Feature Flags</Trans>
</FormLabel>
<div className="mt-2 space-y-2 rounded-md border p-4">
<div className="twv3-space-y-2 mt-2 rounded-md border p-4">
{Object.values(SUBSCRIPTION_CLAIM_FEATURE_FLAGS).map(({ key, label, isEnterprise }) => {
const isRestrictedFeature = isEnterprise && !licenseFlags?.[key as keyof TLicenseClaim]; // eslint-disable-line @typescript-eslint/consistent-type-assertions
@@ -200,7 +200,7 @@ export const SubscriptionClaimForm = ({
control={form.control}
name={`flags.${key}`}
render={({ field }) => (
<FormItem className="flex items-center space-x-2">
<FormItem className="twv3-space-x-2 flex items-center">
<FormControl>
<div className="flex items-center">
<Checkbox
@@ -135,7 +135,7 @@ export const TeamUpdateForm = ({ teamId, teamName, teamUrl }: UpdateTeamDialogPr
)}
/>
<div className="flex flex-row justify-end space-x-4">
<div className="twv3-space-x-4 flex flex-row justify-end">
<AnimatePresence>
{form.formState.isDirty && (
<motion.div
@@ -133,7 +133,7 @@ export const AdminGlobalSettingsSection = ({ settings, isTeam = false }: AdminGl
{isTeam && parsedEmailSettings.success && (
<DetailsCard label={<Trans>Email document settings</Trans>}>
<div className="mt-1 space-y-1 pr-3 pb-2 text-xs">
<div className="twv3-space-y-1 mt-1 pr-3 pb-2 text-xs">
{emailSettingsKeys.map((key) => (
<div key={key} className="flex items-center justify-between gap-2">
<span className="text-muted-foreground">{_(EMAIL_SETTINGS_LABELS[key])}</span>
@@ -77,7 +77,7 @@ export const AdminLicenseCard = ({ licenseData }: AdminLicenseCardProps) => {
const enabledFlags = Object.entries(license.flags).filter(([, enabled]) => enabled);
return (
<div className="relative max-w-full overflow-hidden rounded-lg border border-border bg-background px-4 pt-4 pb-6 shadow shadow-transparent duration-200 hover:shadow-border/80">
<div className="relative max-w-full overflow-hidden rounded-lg border border-border bg-background px-4 pt-4 pb-6 shadow-sm shadow-transparent duration-200 hover:shadow-border/80">
<div className="absolute top-3 right-3">
<AdminLicenseResyncButton />
</div>
@@ -14,7 +14,7 @@ export type MonthlyActiveUsersChartProps = {
const CustomTooltip = ({ active, payload, label }: TooltipProps<ValueType, NameType>) => {
if (active && payload && payload.length) {
return (
<div className="z-100 w-60 space-y-1 rounded-md border border-solid bg-white p-2 px-3">
<div className="twv3-space-y-1 z-100 w-60 rounded-md border border-solid bg-white p-2 px-3">
<p>{label}</p>
<p className="text-documenso">
{payload[0].name === 'cume_count' ? 'Cumulative MAU' : 'Monthly Active Users'}:{' '}
@@ -20,7 +20,7 @@ const CustomTooltip = ({
}: TooltipProps<ValueType, NameType> & { tooltip?: string }) => {
if (active && payload && payload.length) {
return (
<div className="z-100 w-60 space-y-1 rounded-md border border-solid bg-white p-2 px-3">
<div className="twv3-space-y-1 z-100 w-60 rounded-md border border-solid bg-white p-2 px-3">
<p className="">{label}</p>
<p className="text-documenso">
{`${tooltip} : `}

Some files were not shown because too many files have changed in this diff Show More