mirror of
https://github.com/documenso/documenso.git
synced 2026-06-30 16:20:54 +10:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b0d004c7cf | |||
| 403c1ca916 | |||
| b79895b38c | |||
| 187b612568 | |||
| b37529a1cf | |||
| 04f6e76178 | |||
| f2525ae95b | |||
| 2f24a8eab2 | |||
| d9b7722325 | |||
| 783123f72b | |||
| e8ed1c3d99 | |||
| c23d739f76 | |||
| 0bf58ca66e | |||
| dee3259088 | |||
| 6ad1a2dfaf | |||
| 306e7fe5ed | |||
| 219db32fdf | |||
| 948d1bbf12 |
@@ -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 [
|
||||
// ============================================================
|
||||
@@ -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',
|
||||
|
||||
@@ -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
@@ -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 "{templateTitle}" 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
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user