mirror of
https://github.com/documenso/documenso.git
synced 2026-07-24 17:04:12 +10:00
Merge branch 'main' into feat/signing-required-field-colors
Resolves conflict in document-signing-field-container.tsx (kept HEAD's required-field-color logic) and applies biome formatting to the auto-merged envelope-signer-page-renderer.tsx.
This commit is contained in:
+18
-18
@@ -1,9 +1,9 @@
|
||||
@import '@documenso/ui/styles/theme.css';
|
||||
@import "@documenso/ui/styles/theme.css";
|
||||
|
||||
/* Inter Variable Fonts */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
src: url('/fonts/inter-variablefont_opsz,wght.ttf') format('truetype-variations');
|
||||
font-family: "Inter";
|
||||
src: url("/fonts/inter-variablefont_opsz,wght.ttf") format("truetype-variations");
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
@@ -11,8 +11,8 @@
|
||||
|
||||
/* Inter Italic Variable Fonts */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
src: url('/fonts/inter-italic-variablefont_opsz,wght.ttf') format('truetype-variations');
|
||||
font-family: "Inter";
|
||||
src: url("/fonts/inter-italic-variablefont_opsz,wght.ttf") format("truetype-variations");
|
||||
font-weight: 100 900;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
@@ -20,16 +20,16 @@
|
||||
|
||||
/* Caveat Variable Font */
|
||||
@font-face {
|
||||
font-family: 'Caveat';
|
||||
src: url('/fonts/caveat-variablefont_wght.ttf') format('truetype-variations');
|
||||
font-family: "Caveat";
|
||||
src: url("/fonts/caveat-variablefont_wght.ttf") format("truetype-variations");
|
||||
font-weight: 400 600;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Noto Sans';
|
||||
src: url('/fonts/noto-sans.ttf') format('truetype-variations');
|
||||
font-family: "Noto Sans";
|
||||
src: url("/fonts/noto-sans.ttf") format("truetype-variations");
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
@@ -37,8 +37,8 @@
|
||||
|
||||
/* Korean noto sans */
|
||||
@font-face {
|
||||
font-family: 'Noto Sans Korean';
|
||||
src: url('/fonts/noto-sans-korean.ttf') format('truetype-variations');
|
||||
font-family: "Noto Sans Korean";
|
||||
src: url("/fonts/noto-sans-korean.ttf") format("truetype-variations");
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
@@ -46,8 +46,8 @@
|
||||
|
||||
/* Japanese noto sans */
|
||||
@font-face {
|
||||
font-family: 'Noto Sans Japanese';
|
||||
src: url('/fonts/noto-sans-japanese.ttf') format('truetype-variations');
|
||||
font-family: "Noto Sans Japanese";
|
||||
src: url("/fonts/noto-sans-japanese.ttf") format("truetype-variations");
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
@@ -55,8 +55,8 @@
|
||||
|
||||
/* Chinese noto sans */
|
||||
@font-face {
|
||||
font-family: 'Noto Sans Chinese';
|
||||
src: url('/fonts/noto-sans-chinese.ttf') format('truetype-variations');
|
||||
font-family: "Noto Sans Chinese";
|
||||
src: url("/fonts/noto-sans-chinese.ttf") format("truetype-variations");
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
@@ -64,8 +64,8 @@
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--font-sans: 'Inter';
|
||||
--font-signature: 'Caveat';
|
||||
--font-noto: 'Noto Sans', 'Noto Sans Korean', 'Noto Sans Japanese', 'Noto Sans Chinese';
|
||||
--font-sans: "Inter";
|
||||
--font-signature: "Caveat";
|
||||
--font-noto: "Noto Sans", "Noto Sans Korean", "Noto Sans Japanese", "Noto Sans Chinese";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
|
||||
import { authClient } from '@documenso/auth/client';
|
||||
import { useSession } from '@documenso/lib/client-only/providers/session';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
@@ -21,6 +15,10 @@ import {
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import { Label } from '@documenso/ui/primitives/label';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
|
||||
export type AccountDeleteDialogProps = {
|
||||
className?: string;
|
||||
@@ -36,8 +34,7 @@ export const AccountDeleteDialog = ({ className }: AccountDeleteDialogProps) =>
|
||||
|
||||
const [enteredEmail, setEnteredEmail] = useState<string>('');
|
||||
|
||||
const { mutateAsync: deleteAccount, isPending: isDeletingAccount } =
|
||||
trpc.profile.deleteAccount.useMutation();
|
||||
const { mutateAsync: deleteAccount, isPending: isDeletingAccount } = trpc.profile.deleteAccount.useMutation();
|
||||
|
||||
const onDeleteAccount = async () => {
|
||||
try {
|
||||
@@ -63,18 +60,15 @@ export const AccountDeleteDialog = ({ className }: AccountDeleteDialogProps) =>
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<Alert
|
||||
className="flex flex-col items-center justify-between gap-4 p-6 md:flex-row"
|
||||
variant="neutral"
|
||||
>
|
||||
<Alert className="flex flex-col items-center justify-between gap-4 p-6 md:flex-row" variant="neutral">
|
||||
<div>
|
||||
<AlertTitle>
|
||||
<Trans>Delete Account</Trans>
|
||||
</AlertTitle>
|
||||
<AlertDescription className="mr-2">
|
||||
<Trans>
|
||||
Delete your account and all its contents, including completed documents. This action
|
||||
is irreversible and will cancel your subscription, so proceed with caution.
|
||||
Delete your account and all its contents, including completed documents. This action is irreversible and
|
||||
will cancel your subscription, so proceed with caution.
|
||||
</Trans>
|
||||
</AlertDescription>
|
||||
</div>
|
||||
@@ -109,10 +103,8 @@ export const AccountDeleteDialog = ({ className }: AccountDeleteDialogProps) =>
|
||||
|
||||
<DialogDescription>
|
||||
<Trans>
|
||||
Documenso will delete{' '}
|
||||
<span className="font-semibold">all of your documents</span>, along with all of
|
||||
your completed documents, signatures, and all other resources belonging to your
|
||||
Account.
|
||||
Documenso will delete <span className="font-semibold">all of your documents</span>, along with all
|
||||
of your completed documents, signatures, and all other resources belonging to your Account.
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
@@ -121,9 +113,7 @@ export const AccountDeleteDialog = ({ className }: AccountDeleteDialogProps) =>
|
||||
<div>
|
||||
<Label>
|
||||
<Trans>
|
||||
Please type{' '}
|
||||
<span className="text-muted-foreground font-semibold">{user.email}</span> to
|
||||
confirm.
|
||||
Please type <span className="font-semibold text-muted-foreground">{user.email}</span> to confirm.
|
||||
</Trans>
|
||||
</Label>
|
||||
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { useNavigate } from 'react-router';
|
||||
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
@@ -19,6 +12,11 @@ import {
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
|
||||
export type AdminDocumentDeleteDialogProps = {
|
||||
envelopeId: string;
|
||||
@@ -32,8 +30,7 @@ export const AdminDocumentDeleteDialog = ({ envelopeId }: AdminDocumentDeleteDia
|
||||
|
||||
const [reason, setReason] = useState('');
|
||||
|
||||
const { mutateAsync: deleteDocument, isPending: isDeletingDocument } =
|
||||
trpc.admin.document.delete.useMutation();
|
||||
const { mutateAsync: deleteDocument, isPending: isDeletingDocument } = trpc.admin.document.delete.useMutation();
|
||||
|
||||
const handleDeleteDocument = async () => {
|
||||
try {
|
||||
@@ -64,18 +61,13 @@ export const AdminDocumentDeleteDialog = ({ envelopeId }: AdminDocumentDeleteDia
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<Alert
|
||||
className="flex flex-col items-center justify-between gap-4 p-6 md:flex-row"
|
||||
variant="neutral"
|
||||
>
|
||||
<Alert className="flex flex-col items-center justify-between gap-4 p-6 md:flex-row" variant="neutral">
|
||||
<div>
|
||||
<AlertTitle>
|
||||
<Trans>Delete Document</Trans>
|
||||
</AlertTitle>
|
||||
<AlertDescription className="mr-2">
|
||||
<Trans>
|
||||
Delete the document. This action is irreversible so proceed with caution.
|
||||
</Trans>
|
||||
<Trans>Delete the document. This action is irreversible so proceed with caution.</Trans>
|
||||
</AlertDescription>
|
||||
</div>
|
||||
|
||||
@@ -105,12 +97,7 @@ export const AdminDocumentDeleteDialog = ({ envelopeId }: AdminDocumentDeleteDia
|
||||
<Trans>To confirm, please enter the reason</Trans>
|
||||
</DialogDescription>
|
||||
|
||||
<Input
|
||||
className="mt-2"
|
||||
type="text"
|
||||
value={reason}
|
||||
onChange={(e) => setReason(e.target.value)}
|
||||
/>
|
||||
<Input className="mt-2" type="text" value={reason} onChange={(e) => setReason(e.target.value)} />
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router';
|
||||
import type { z } from 'zod';
|
||||
|
||||
import { AppError } from '@documenso/lib/errors/app-error';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { ZCreateAdminOrganisationRequestSchema } from '@documenso/trpc/server/admin-router/create-admin-organisation.types';
|
||||
@@ -22,16 +12,16 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
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 { Trans, useLingui } from '@lingui/react/macro';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router';
|
||||
import type { z } from 'zod';
|
||||
|
||||
export type OrganisationCreateDialogProps = {
|
||||
trigger?: React.ReactNode;
|
||||
@@ -44,11 +34,7 @@ const ZCreateAdminOrganisationFormSchema = ZCreateAdminOrganisationRequestSchema
|
||||
|
||||
type TCreateOrganisationFormSchema = z.infer<typeof ZCreateAdminOrganisationFormSchema>;
|
||||
|
||||
export const AdminOrganisationCreateDialog = ({
|
||||
trigger,
|
||||
ownerUserId,
|
||||
...props
|
||||
}: OrganisationCreateDialogProps) => {
|
||||
export const AdminOrganisationCreateDialog = ({ trigger, ownerUserId, ...props }: OrganisationCreateDialogProps) => {
|
||||
const { t } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
@@ -101,11 +87,7 @@ export const AdminOrganisationCreateDialog = ({
|
||||
}, [open, form]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
{...props}
|
||||
open={open}
|
||||
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
|
||||
>
|
||||
<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">
|
||||
@@ -127,10 +109,7 @@ export const AdminOrganisationCreateDialog = ({
|
||||
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onFormSubmit)}>
|
||||
<fieldset
|
||||
className="flex h-full flex-col space-y-4"
|
||||
disabled={form.formState.isSubmitting}
|
||||
>
|
||||
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
@@ -149,10 +128,7 @@ export const AdminOrganisationCreateDialog = ({
|
||||
|
||||
<Alert variant="neutral">
|
||||
<AlertDescription className="mt-0">
|
||||
<Trans>
|
||||
You will need to configure any claims or subscription after creating this
|
||||
organisation
|
||||
</Trans>
|
||||
<Trans>You will need to configure any claims or subscription after creating this organisation</Trans>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { useNavigate } from 'react-router';
|
||||
|
||||
import { AppError } from '@documenso/lib/errors/app-error';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
|
||||
@@ -20,6 +13,11 @@ import {
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
|
||||
export type AdminOrganisationMemberDeleteDialogProps = {
|
||||
organisationId: string;
|
||||
@@ -42,33 +40,32 @@ export const AdminOrganisationMemberDeleteDialog = ({
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const { mutateAsync: deleteOrganisationMember, isPending } =
|
||||
trpc.admin.organisationMember.delete.useMutation({
|
||||
onSuccess: async () => {
|
||||
toast({
|
||||
title: _(msg`Success`),
|
||||
description: _(msg`Member has been removed from the organisation.`),
|
||||
duration: 5000,
|
||||
});
|
||||
const { mutateAsync: deleteOrganisationMember, isPending } = trpc.admin.organisationMember.delete.useMutation({
|
||||
onSuccess: async () => {
|
||||
toast({
|
||||
title: _(msg`Success`),
|
||||
description: _(msg`Member has been removed from the organisation.`),
|
||||
duration: 5000,
|
||||
});
|
||||
|
||||
setOpen(false);
|
||||
setOpen(false);
|
||||
|
||||
// Refresh the page to show updated data
|
||||
await navigate(0);
|
||||
},
|
||||
onError: (err) => {
|
||||
const error = AppError.parseError(err);
|
||||
// Refresh the page to show updated data
|
||||
await navigate(0);
|
||||
},
|
||||
onError: (err) => {
|
||||
const error = AppError.parseError(err);
|
||||
|
||||
console.error(error);
|
||||
console.error(error);
|
||||
|
||||
toast({
|
||||
title: _(msg`An error occurred`),
|
||||
description: _(msg`We couldn't remove this member. Please try again later.`),
|
||||
variant: 'destructive',
|
||||
duration: 10000,
|
||||
});
|
||||
},
|
||||
});
|
||||
toast({
|
||||
title: _(msg`An error occurred`),
|
||||
description: _(msg`We couldn't remove this member. Please try again later.`),
|
||||
variant: 'destructive',
|
||||
duration: 10000,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={(value) => !isPending && setOpen(value)}>
|
||||
|
||||
@@ -1,15 +1,3 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { OrganisationMemberRole } from '@prisma/client';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { match } from 'ts-pattern';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { getHighestOrganisationRoleInGroup } from '@documenso/lib/utils/organisations';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import type { TGetAdminOrganisationResponse } from '@documenso/trpc/server/admin-router/get-admin-organisation.types';
|
||||
@@ -23,22 +11,18 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@documenso/ui/primitives/select';
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { OrganisationMemberRole } from '@prisma/client';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { match } from 'ts-pattern';
|
||||
import { z } from 'zod';
|
||||
|
||||
export type AdminOrganisationMemberUpdateDialogProps = {
|
||||
trigger?: React.ReactNode;
|
||||
@@ -69,9 +53,7 @@ export const AdminOrganisationMemberUpdateDialog = ({
|
||||
// Determine the current role value for the form
|
||||
const currentRoleValue = isOwner
|
||||
? 'OWNER'
|
||||
: getHighestOrganisationRoleInGroup(
|
||||
organisationMember.organisationGroupMembers.map((ogm) => ogm.group),
|
||||
);
|
||||
: getHighestOrganisationRoleInGroup(organisationMember.organisationGroupMembers.map((ogm) => ogm.group));
|
||||
const organisationMemberName = organisationMember.user.name ?? organisationMember.user.email;
|
||||
|
||||
const form = useForm<ZUpdateOrganisationMemberSchema>({
|
||||
@@ -81,8 +63,7 @@ export const AdminOrganisationMemberUpdateDialog = ({
|
||||
},
|
||||
});
|
||||
|
||||
const { mutateAsync: updateOrganisationMemberRole } =
|
||||
trpc.admin.organisationMember.updateRole.useMutation();
|
||||
const { mutateAsync: updateOrganisationMemberRole } = trpc.admin.organisationMember.updateRole.useMutation();
|
||||
|
||||
const onFormSubmit = async ({ role }: ZUpdateOrganisationMemberSchema) => {
|
||||
try {
|
||||
@@ -135,11 +116,7 @@ export const AdminOrganisationMemberUpdateDialog = ({
|
||||
}, [open, currentRoleValue, form]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
{...props}
|
||||
open={open}
|
||||
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
|
||||
>
|
||||
<Dialog {...props} open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
|
||||
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
|
||||
{trigger ?? (
|
||||
<Button variant="secondary">
|
||||
@@ -156,8 +133,7 @@ export const AdminOrganisationMemberUpdateDialog = ({
|
||||
|
||||
<DialogDescription className="mt-4">
|
||||
<Trans>
|
||||
You are currently updating <span className="font-bold">{organisationMemberName}</span>
|
||||
.
|
||||
You are currently updating <span className="font-bold">{organisationMemberName}</span>.
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
|
||||
import { AppError } from '@documenso/lib/errors/app-error';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
|
||||
@@ -15,14 +10,10 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@documenso/ui/primitives/select';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
export type AdminSwapSubscriptionDialogProps = {
|
||||
open: boolean;
|
||||
@@ -68,8 +59,7 @@ export const AdminSwapSubscriptionDialog = ({
|
||||
}
|
||||
|
||||
const hasActiveSubscription =
|
||||
org.subscription &&
|
||||
(org.subscription.status === 'ACTIVE' || org.subscription.status === 'PAST_DUE');
|
||||
org.subscription && (org.subscription.status === 'ACTIVE' || org.subscription.status === 'PAST_DUE');
|
||||
|
||||
return !hasActiveSubscription;
|
||||
});
|
||||
@@ -133,15 +123,14 @@ export const AdminSwapSubscriptionDialog = ({
|
||||
|
||||
<DialogDescription>
|
||||
<Trans>
|
||||
Move the subscription from "{sourceOrganisationName}" to another organisation owned by
|
||||
this user.
|
||||
Move the subscription from "{sourceOrganisationName}" to another organisation owned by this user.
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<fieldset className="flex flex-col space-y-4" disabled={isSubmitting}>
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-sm font-medium">
|
||||
<label className="font-medium text-sm">
|
||||
<Trans>Target Organisation</Trans>
|
||||
</label>
|
||||
|
||||
@@ -159,7 +148,7 @@ export const AdminSwapSubscriptionDialog = ({
|
||||
</Select>
|
||||
|
||||
{eligibleOrgs.length === 0 && orgsData && (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<p className="text-muted-foreground text-sm">
|
||||
<Trans>No eligible organisations found. The target must be on the free plan.</Trans>
|
||||
</p>
|
||||
)}
|
||||
@@ -169,8 +158,8 @@ export const AdminSwapSubscriptionDialog = ({
|
||||
<Alert variant="warning">
|
||||
<AlertDescription className="mt-0">
|
||||
<Trans>
|
||||
This will move the subscription from "{sourceOrganisationName}" to "
|
||||
{selectedOrg.name}". The source organisation will be reset to the free plan.
|
||||
This will move the subscription from "{sourceOrganisationName}" to "{selectedOrg.name}". The source
|
||||
organisation will be reset to the free plan.
|
||||
</Trans>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
@@ -181,12 +170,7 @@ export const AdminSwapSubscriptionDialog = ({
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
onClick={onSubmit}
|
||||
disabled={!selectedOrgId}
|
||||
loading={isSubmitting}
|
||||
>
|
||||
<Button type="button" onClick={onSubmit} disabled={!selectedOrgId} loading={isSubmitting}>
|
||||
<Trans>Move Subscription</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { useNavigate } from 'react-router';
|
||||
|
||||
import { AppError } from '@documenso/lib/errors/app-error';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
|
||||
@@ -20,6 +13,11 @@ import {
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
|
||||
export type AdminTeamMemberDeleteDialogProps = {
|
||||
teamId: number;
|
||||
@@ -93,8 +91,8 @@ export const AdminTeamMemberDeleteDialog = ({
|
||||
<div>
|
||||
<DialogDescription>
|
||||
<Trans>
|
||||
You are about to remove the following user from the team{' '}
|
||||
<span className="font-semibold">{teamName}</span>:
|
||||
You are about to remove the following user from the team <span className="font-semibold">{teamName}</span>
|
||||
:
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import type { TGetUserResponse } from '@documenso/trpc/server/admin-router/get-user.types';
|
||||
@@ -22,6 +14,12 @@ import {
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
export type AdminUserDeleteDialogProps = {
|
||||
className?: string;
|
||||
@@ -34,8 +32,7 @@ export const AdminUserDeleteDialog = ({ className, user }: AdminUserDeleteDialog
|
||||
const navigate = useNavigate();
|
||||
const [email, setEmail] = useState('');
|
||||
|
||||
const { mutateAsync: deleteUser, isPending: isDeletingUser } =
|
||||
trpc.admin.user.delete.useMutation();
|
||||
const { mutateAsync: deleteUser, isPending: isDeletingUser } = trpc.admin.user.delete.useMutation();
|
||||
|
||||
const onDeleteAccount = async () => {
|
||||
try {
|
||||
@@ -69,16 +66,13 @@ export const AdminUserDeleteDialog = ({ className, user }: AdminUserDeleteDialog
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<Alert
|
||||
className="flex flex-col items-center justify-between gap-4 p-6 md:flex-row"
|
||||
variant="neutral"
|
||||
>
|
||||
<Alert className="flex flex-col items-center justify-between gap-4 p-6 md:flex-row" variant="neutral">
|
||||
<div>
|
||||
<AlertTitle>Delete Account</AlertTitle>
|
||||
<AlertDescription className="mr-2">
|
||||
<Trans>
|
||||
Delete the users account and all its contents. This action is irreversible and will
|
||||
cancel their subscription, so proceed with caution.
|
||||
Delete the users account and all its contents. This action is irreversible and will cancel their
|
||||
subscription, so proceed with caution.
|
||||
</Trans>
|
||||
</AlertDescription>
|
||||
</div>
|
||||
@@ -111,12 +105,7 @@ export const AdminUserDeleteDialog = ({ className, user }: AdminUserDeleteDialog
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
|
||||
<Input
|
||||
className="mt-2"
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
<Input className="mt-2" type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import type { TGetUserResponse } from '@documenso/trpc/server/admin-router/get-user.types';
|
||||
@@ -21,23 +14,24 @@ import {
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
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';
|
||||
|
||||
export type AdminUserDisableDialogProps = {
|
||||
className?: string;
|
||||
userToDisable: TGetUserResponse;
|
||||
};
|
||||
|
||||
export const AdminUserDisableDialog = ({
|
||||
className,
|
||||
userToDisable,
|
||||
}: AdminUserDisableDialogProps) => {
|
||||
export const AdminUserDisableDialog = ({ className, userToDisable }: AdminUserDisableDialogProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const [email, setEmail] = useState('');
|
||||
|
||||
const { mutateAsync: disableUser, isPending: isDisablingUser } =
|
||||
trpc.admin.user.disable.useMutation();
|
||||
const { mutateAsync: disableUser, isPending: isDisablingUser } = trpc.admin.user.disable.useMutation();
|
||||
|
||||
const onDisableAccount = async () => {
|
||||
try {
|
||||
@@ -69,16 +63,13 @@ export const AdminUserDisableDialog = ({
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<Alert
|
||||
className="flex flex-col items-center justify-between gap-4 p-6 md:flex-row"
|
||||
variant="neutral"
|
||||
>
|
||||
<Alert className="flex flex-col items-center justify-between gap-4 p-6 md:flex-row" variant="neutral">
|
||||
<div>
|
||||
<AlertTitle>Disable Account</AlertTitle>
|
||||
<AlertDescription className="mr-2">
|
||||
<Trans>
|
||||
Disabling the user results in the user not being able to use the account. It also
|
||||
disables all the related contents such as subscription, webhooks, teams, and API keys.
|
||||
Disabling the user results in the user not being able to use the account. It also disables all the related
|
||||
contents such as subscription, webhooks, teams, and API keys.
|
||||
</Trans>
|
||||
</AlertDescription>
|
||||
</div>
|
||||
@@ -100,9 +91,8 @@ export const AdminUserDisableDialog = ({
|
||||
<Alert variant="destructive">
|
||||
<AlertDescription className="selection:bg-red-100">
|
||||
<Trans>
|
||||
This action is reversible, but please be careful as the account may be
|
||||
affected permanently (e.g. their settings and contents not being restored
|
||||
properly).
|
||||
This action is reversible, but please be careful as the account may be affected permanently (e.g.
|
||||
their settings and contents not being restored properly).
|
||||
</Trans>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
@@ -116,12 +106,7 @@ export const AdminUserDisableDialog = ({
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
|
||||
<Input
|
||||
className="mt-2"
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
<Input className="mt-2" type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import type { TGetUserResponse } from '@documenso/trpc/server/admin-router/get-user.types';
|
||||
@@ -21,6 +14,11 @@ import {
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
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';
|
||||
|
||||
export type AdminUserEnableDialogProps = {
|
||||
className?: string;
|
||||
@@ -33,8 +31,7 @@ export const AdminUserEnableDialog = ({ className, userToEnable }: AdminUserEnab
|
||||
|
||||
const [email, setEmail] = useState('');
|
||||
|
||||
const { mutateAsync: enableUser, isPending: isEnablingUser } =
|
||||
trpc.admin.user.enable.useMutation();
|
||||
const { mutateAsync: enableUser, isPending: isEnablingUser } = trpc.admin.user.enable.useMutation();
|
||||
|
||||
const onEnableAccount = async () => {
|
||||
try {
|
||||
@@ -66,16 +63,13 @@ export const AdminUserEnableDialog = ({ className, userToEnable }: AdminUserEnab
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<Alert
|
||||
className="flex flex-col items-center justify-between gap-4 p-6 md:flex-row"
|
||||
variant="neutral"
|
||||
>
|
||||
<Alert className="flex flex-col items-center justify-between gap-4 p-6 md:flex-row" variant="neutral">
|
||||
<div>
|
||||
<AlertTitle>Enable Account</AlertTitle>
|
||||
<AlertDescription className="mr-2">
|
||||
<Trans>
|
||||
Enabling the account results in the user being able to use the account again, and all
|
||||
the related features such as webhooks, teams, and API keys for example.
|
||||
Enabling the account results in the user being able to use the account again, and all the related features
|
||||
such as webhooks, teams, and API keys for example.
|
||||
</Trans>
|
||||
</AlertDescription>
|
||||
</div>
|
||||
@@ -103,20 +97,11 @@ export const AdminUserEnableDialog = ({ className, userToEnable }: AdminUserEnab
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
|
||||
<Input
|
||||
className="mt-2"
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
<Input className="mt-2" type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button
|
||||
onClick={onEnableAccount}
|
||||
loading={isEnablingUser}
|
||||
disabled={email !== userToEnable.email}
|
||||
>
|
||||
<Button onClick={onEnableAccount} loading={isEnablingUser} disabled={email !== userToEnable.email}>
|
||||
<Trans>Enable account</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { useRevalidator } from 'react-router';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import type { TGetUserResponse } from '@documenso/trpc/server/admin-router/get-user.types';
|
||||
@@ -22,24 +14,26 @@ import {
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
import { useRevalidator } from 'react-router';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
export type AdminUserResetTwoFactorDialogProps = {
|
||||
className?: string;
|
||||
user: TGetUserResponse;
|
||||
};
|
||||
|
||||
export const AdminUserResetTwoFactorDialog = ({
|
||||
className,
|
||||
user,
|
||||
}: AdminUserResetTwoFactorDialogProps) => {
|
||||
export const AdminUserResetTwoFactorDialog = ({ className, user }: AdminUserResetTwoFactorDialogProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
const { revalidate } = useRevalidator();
|
||||
const [email, setEmail] = useState('');
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const { mutateAsync: resetTwoFactor, isPending: isResettingTwoFactor } =
|
||||
trpc.admin.user.resetTwoFactor.useMutation();
|
||||
const { mutateAsync: resetTwoFactor, isPending: isResettingTwoFactor } = trpc.admin.user.resetTwoFactor.useMutation();
|
||||
|
||||
const onResetTwoFactor = async () => {
|
||||
try {
|
||||
@@ -64,9 +58,7 @@ export const AdminUserResetTwoFactorDialog = ({
|
||||
AppErrorCode.UNAUTHORIZED,
|
||||
() => msg`You are not authorized to reset two factor authentcation for this user.`,
|
||||
)
|
||||
.otherwise(
|
||||
() => msg`An error occurred while resetting two factor authentication for the user.`,
|
||||
);
|
||||
.otherwise(() => msg`An error occurred while resetting two factor authentication for the user.`);
|
||||
|
||||
toast({
|
||||
title: _(msg`Error`),
|
||||
@@ -87,16 +79,13 @@ export const AdminUserResetTwoFactorDialog = ({
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<Alert
|
||||
className="flex flex-col items-center justify-between gap-4 p-6 md:flex-row"
|
||||
variant="neutral"
|
||||
>
|
||||
<Alert className="flex flex-col items-center justify-between gap-4 p-6 md:flex-row" variant="neutral">
|
||||
<div>
|
||||
<AlertTitle>Reset Two Factor Authentication</AlertTitle>
|
||||
<AlertDescription className="mr-2">
|
||||
<Trans>
|
||||
Reset the users two factor authentication. This action is irreversible and will
|
||||
disable two factor authentication for the user.
|
||||
Reset the users two factor authentication. This action is irreversible and will disable two factor
|
||||
authentication for the user.
|
||||
</Trans>
|
||||
</AlertDescription>
|
||||
</div>
|
||||
@@ -119,8 +108,7 @@ export const AdminUserResetTwoFactorDialog = ({
|
||||
<Alert variant="destructive">
|
||||
<AlertDescription className="selection:bg-red-100">
|
||||
<Trans>
|
||||
This action is irreversible. Please ensure you have informed the user before
|
||||
proceeding.
|
||||
This action is irreversible. Please ensure you have informed the user before proceeding.
|
||||
</Trans>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
@@ -132,12 +120,7 @@ export const AdminUserResetTwoFactorDialog = ({
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
|
||||
<Input
|
||||
className="mt-2"
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
<Input className="mt-2" type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
|
||||
@@ -1,20 +1,11 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { OrganisationMemberRole, TeamMemberRole } from '@prisma/client';
|
||||
|
||||
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@documenso/ui/primitives/dialog';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { OrganisationMemberRole, TeamMemberRole } from '@prisma/client';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useCurrentTeam } from '~/providers/team';
|
||||
|
||||
@@ -24,11 +15,7 @@ type AiFeaturesEnableDialogProps = {
|
||||
onEnabled: () => void;
|
||||
};
|
||||
|
||||
export const AiFeaturesEnableDialog = ({
|
||||
open,
|
||||
onOpenChange,
|
||||
onEnabled,
|
||||
}: AiFeaturesEnableDialogProps) => {
|
||||
export const AiFeaturesEnableDialog = ({ open, onOpenChange, onEnabled }: AiFeaturesEnableDialogProps) => {
|
||||
const { t } = useLingui();
|
||||
|
||||
const team = useCurrentTeam();
|
||||
@@ -71,11 +58,7 @@ export const AiFeaturesEnableDialog = ({
|
||||
onOpenChange(false);
|
||||
} catch (err) {
|
||||
console.error('Failed to enable AI features', err);
|
||||
setError(
|
||||
err instanceof Error
|
||||
? err.message
|
||||
: t`We couldn't enable AI features right now. Please try again.`,
|
||||
);
|
||||
setError(err instanceof Error ? err.message : t`We couldn't enable AI features right now. Please try again.`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -89,39 +72,38 @@ export const AiFeaturesEnableDialog = ({
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<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 retain your data for training.
|
||||
Turn on AI detection to automatically find recipients and fields in your documents. AI providers do not
|
||||
retain your data for training.
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<Alert variant="neutral">
|
||||
<AlertDescription>
|
||||
<Trans>
|
||||
Your document content will be sent securely to our AI provider solely for detection
|
||||
and will not be stored or used for training.
|
||||
Your document content will be sent securely to our AI provider solely for detection and will not be
|
||||
stored or used for training.
|
||||
</Trans>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
{canEnableAiFeatures ? (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<p className="text-muted-foreground text-sm">
|
||||
<Trans>
|
||||
You're an admin. You can enable AI features for this team right away. Everyone on
|
||||
the team will see AI detection once enabled.
|
||||
You're an admin. You can enable AI features for this team right away. Everyone on the team will see AI
|
||||
detection once enabled.
|
||||
</Trans>
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<p className="text-muted-foreground text-sm">
|
||||
<Trans>
|
||||
AI features are disabled for your team. Please ask your team owner or organisation
|
||||
owner to enable them.
|
||||
AI features are disabled for your team. Please ask your team owner or organisation owner to enable them.
|
||||
</Trans>
|
||||
</p>
|
||||
)}
|
||||
|
||||
{error ? <p className="text-sm text-destructive">{error}</p> : null}
|
||||
{error ? <p className="text-destructive text-sm">{error}</p> : null}
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
|
||||
@@ -1,29 +1,17 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import type { NormalizedFieldWithContext } from '@documenso/lib/server-only/ai/envelope/detect-fields/types';
|
||||
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@documenso/ui/primitives/dialog';
|
||||
import { Label } from '@documenso/ui/primitives/label';
|
||||
import { Textarea } from '@documenso/ui/primitives/textarea';
|
||||
import type { MessageDescriptor } from '@lingui/core';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Plural, Trans } from '@lingui/react/macro';
|
||||
import { CheckIcon, FormInputIcon, ShieldCheckIcon } from 'lucide-react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import type { NormalizedFieldWithContext } from '@documenso/lib/server-only/ai/envelope/detect-fields/types';
|
||||
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import { Label } from '@documenso/ui/primitives/label';
|
||||
import { Textarea } from '@documenso/ui/primitives/textarea';
|
||||
|
||||
import {
|
||||
AiApiError,
|
||||
type DetectFieldsProgressEvent,
|
||||
detectFields,
|
||||
} from '../../../server/api/ai/detect-fields.client';
|
||||
import { AiApiError, type DetectFieldsProgressEvent, detectFields } from '../../../server/api/ai/detect-fields.client';
|
||||
import { AnimatedDocumentScanner } from '../general/animated-document-scanner';
|
||||
|
||||
type DialogState = 'PROMPT' | 'PROCESSING' | 'REVIEW' | 'ERROR' | 'RATE_LIMITED';
|
||||
@@ -171,20 +159,17 @@ export const AiFieldDetectionDialog = ({
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<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. Detected fields will be suggested for you to review.
|
||||
We'll scan your document to find form fields like signature lines, text inputs, checkboxes, and more.
|
||||
Detected fields will be suggested for you to review.
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<Alert className="flex items-center gap-2 space-y-0" 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>
|
||||
<Trans>Your document is processed securely using AI services that don't retain your data.</Trans>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
@@ -200,7 +185,7 @@ export const AiFieldDetectionDialog = ({
|
||||
rows={2}
|
||||
className="resize-none"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<p className="text-muted-foreground text-xs">
|
||||
<Trans>Help the AI assign fields to the right recipients.</Trans>
|
||||
</p>
|
||||
</div>
|
||||
@@ -231,7 +216,7 @@ export const AiFieldDetectionDialog = ({
|
||||
<p className="mt-8 text-muted-foreground">{_(PROCESSING_MESSAGES[messageIndex])}</p>
|
||||
|
||||
{progress && (
|
||||
<p className="mt-2 text-xs text-muted-foreground/60">
|
||||
<p className="mt-2 text-muted-foreground/60 text-xs">
|
||||
<Plural
|
||||
value={progress.fieldsDetected}
|
||||
one={
|
||||
@@ -248,7 +233,7 @@ export const AiFieldDetectionDialog = ({
|
||||
</p>
|
||||
)}
|
||||
|
||||
<p className="mt-2 max-w-[40ch] text-center text-xs text-muted-foreground/60">
|
||||
<p className="mt-2 max-w-[40ch] text-center text-muted-foreground/60 text-xs">
|
||||
<Trans>This can take a minute or two depending on the size of your document.</Trans>
|
||||
</p>
|
||||
|
||||
@@ -278,16 +263,16 @@ export const AiFieldDetectionDialog = ({
|
||||
{detectedFields.length === 0 ? (
|
||||
<div className="flex flex-col items-center py-8">
|
||||
<FormInputIcon className="h-12 w-12 text-muted-foreground/50" />
|
||||
<p className="mt-4 text-center text-sm text-muted-foreground">
|
||||
<p className="mt-4 text-center text-muted-foreground text-sm">
|
||||
<Trans>No fields were detected in your document.</Trans>
|
||||
</p>
|
||||
<p className="mt-1 text-center text-xs text-muted-foreground/70">
|
||||
<p className="mt-1 text-center text-muted-foreground/70 text-xs">
|
||||
<Trans>You can add fields manually in the editor.</Trans>
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<p className="text-muted-foreground text-sm">
|
||||
<Plural
|
||||
value={detectedFields.length}
|
||||
one="We found # field in your document."
|
||||
@@ -299,7 +284,7 @@ export const AiFieldDetectionDialog = ({
|
||||
{fieldCountsByType.map(([type, count]) => (
|
||||
<li key={type} className="flex items-center justify-between px-4 py-3">
|
||||
<span className="text-sm">{_(FIELD_TYPE_LABELS[type]) || type}</span>
|
||||
<span className="text-sm font-medium text-muted-foreground">{count}</span>
|
||||
<span className="font-medium text-muted-foreground text-sm">{count}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
@@ -314,7 +299,7 @@ export const AiFieldDetectionDialog = ({
|
||||
|
||||
{detectedFields.length > 0 && (
|
||||
<Button type="button" onClick={onAddFields}>
|
||||
<CheckIcon className="-ml-1 mr-2 h-4 w-4" />
|
||||
<CheckIcon className="mr-2 -ml-1 h-4 w-4" />
|
||||
<Trans>Add fields</Trans>
|
||||
</Button>
|
||||
)}
|
||||
@@ -331,11 +316,11 @@ export const AiFieldDetectionDialog = ({
|
||||
</DialogHeader>
|
||||
|
||||
<div className="py-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<p className="text-muted-foreground text-sm">
|
||||
<Trans>Something went wrong while detecting fields.</Trans>
|
||||
</p>
|
||||
|
||||
{error && <p className="mt-2 text-sm text-destructive">{error}</p>}
|
||||
{error && <p className="mt-2 text-destructive text-sm">{error}</p>}
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
@@ -358,10 +343,8 @@ export const AiFieldDetectionDialog = ({
|
||||
</DialogHeader>
|
||||
|
||||
<div className="py-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<Trans>
|
||||
You've made too many detection requests. Please wait a minute before trying again.
|
||||
</Trans>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
<Trans>You've made too many detection requests. Please wait a minute before trying again.</Trans>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,22 +1,14 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Plural, Trans } from '@lingui/react/macro';
|
||||
import { CheckIcon, ShieldCheckIcon, UserIcon, XIcon } from 'lucide-react';
|
||||
|
||||
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
|
||||
import type { TDetectedRecipientSchema } from '@documenso/lib/server-only/ai/envelope/detect-recipients/schema';
|
||||
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
|
||||
import { AvatarWithText } from '@documenso/ui/primitives/avatar';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@documenso/ui/primitives/dialog';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Plural, Trans } from '@lingui/react/macro';
|
||||
import { CheckIcon, ShieldCheckIcon, UserIcon, XIcon } from 'lucide-react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import {
|
||||
AiApiError,
|
||||
@@ -146,20 +138,17 @@ export const AiRecipientDetectionDialog = ({
|
||||
</DialogHeader>
|
||||
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<p className="text-muted-foreground text-sm">
|
||||
<Trans>
|
||||
We'll scan your document to find signature fields and identify who needs to sign.
|
||||
Detected recipients will be suggested for you to review.
|
||||
We'll scan your document to find signature fields and identify who needs to sign. Detected recipients
|
||||
will be suggested for you to review.
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<Alert className="mt-4 flex items-center gap-2 space-y-0" 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>
|
||||
<Trans>Your document is processed securely using AI services that don't retain your data.</Trans>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</div>
|
||||
@@ -189,7 +178,7 @@ export const AiRecipientDetectionDialog = ({
|
||||
<p className="mt-8 text-muted-foreground">{_(PROCESSING_MESSAGES[messageIndex])}</p>
|
||||
|
||||
{progress && (
|
||||
<p className="mt-2 text-xs text-muted-foreground/60">
|
||||
<p className="mt-2 text-muted-foreground/60 text-xs">
|
||||
<Plural
|
||||
value={progress.recipientsDetected}
|
||||
one={
|
||||
@@ -206,7 +195,7 @@ export const AiRecipientDetectionDialog = ({
|
||||
</p>
|
||||
)}
|
||||
|
||||
<p className="mt-2 max-w-[40ch] text-center text-xs text-muted-foreground/60">
|
||||
<p className="mt-2 max-w-[40ch] text-center text-muted-foreground/60 text-xs">
|
||||
<Trans>This can take a minute or two depending on the size of your document.</Trans>
|
||||
</p>
|
||||
|
||||
@@ -236,16 +225,16 @@ export const AiRecipientDetectionDialog = ({
|
||||
{detectedRecipients.length === 0 ? (
|
||||
<div className="flex flex-col items-center py-8">
|
||||
<UserIcon className="h-12 w-12 text-muted-foreground/50" />
|
||||
<p className="mt-4 text-center text-sm text-muted-foreground">
|
||||
<p className="mt-4 text-center text-muted-foreground text-sm">
|
||||
<Trans>No recipients were detected in your document.</Trans>
|
||||
</p>
|
||||
<p className="mt-1 text-center text-xs text-muted-foreground/70">
|
||||
<p className="mt-1 text-center text-muted-foreground/70 text-xs">
|
||||
<Trans>You can add recipients manually in the editor.</Trans>
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<p className="text-muted-foreground text-sm">
|
||||
<Plural
|
||||
value={detectedRecipients.length}
|
||||
one="We found # recipient in your document."
|
||||
@@ -265,13 +254,13 @@ export const AiRecipientDetectionDialog = ({
|
||||
: '?'
|
||||
}
|
||||
primaryText={
|
||||
<p className="text-sm font-medium text-foreground">
|
||||
<p className="font-medium text-foreground text-sm">
|
||||
{recipient.name || _(msg`Unknown name`)}
|
||||
</p>
|
||||
}
|
||||
secondaryText={
|
||||
<div className="text-xs text-muted-foreground">
|
||||
<p className="italic text-muted-foreground/70">
|
||||
<div className="text-muted-foreground text-xs">
|
||||
<p className="text-muted-foreground/70 italic">
|
||||
{recipient.email || _(msg`No email detected`)}
|
||||
</p>
|
||||
<p>{_(RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName)}</p>
|
||||
@@ -304,7 +293,7 @@ export const AiRecipientDetectionDialog = ({
|
||||
|
||||
{detectedRecipients.length > 0 && (
|
||||
<Button type="button" onClick={onAddRecipients}>
|
||||
<CheckIcon className="-ml-1 mr-2 h-4 w-4" />
|
||||
<CheckIcon className="mr-2 -ml-1 h-4 w-4" />
|
||||
<Trans>Add recipients</Trans>
|
||||
</Button>
|
||||
)}
|
||||
@@ -321,11 +310,11 @@ export const AiRecipientDetectionDialog = ({
|
||||
</DialogHeader>
|
||||
|
||||
<div className="py-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<p className="text-muted-foreground text-sm">
|
||||
<Trans>Something went wrong while detecting recipients.</Trans>
|
||||
</p>
|
||||
|
||||
{error && <p className="mt-2 text-sm text-destructive">{error}</p>}
|
||||
{error && <p className="mt-2 text-destructive text-sm">{error}</p>}
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
@@ -349,10 +338,8 @@ export const AiRecipientDetectionDialog = ({
|
||||
</DialogHeader>
|
||||
|
||||
<div className="py-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<Trans>
|
||||
You've made too many detection requests. Please wait a minute before trying again.
|
||||
</Trans>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
<Trans>You've made too many detection requests. Please wait a minute before trying again.</Trans>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { zEmail } from '@documenso/lib/utils/zod';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import {
|
||||
@@ -15,15 +8,13 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { DocumentSigningDisclosure } from '../general/document-signing/document-signing-disclosure';
|
||||
|
||||
@@ -104,9 +95,8 @@ export function AssistantConfirmationDialog({
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
<Trans>
|
||||
Are you sure you want to complete the document? This action cannot be undone.
|
||||
Please ensure that you have completed prefilling all relevant fields before
|
||||
proceeding.
|
||||
Are you sure you want to complete the document? This action cannot be undone. Please ensure that you
|
||||
have completed prefilling all relevant fields before proceeding.
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
@@ -116,7 +106,7 @@ export function AssistantConfirmationDialog({
|
||||
<div className="mt-4 flex flex-col gap-4">
|
||||
{!isEditingNextSigner && (
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<p className="text-muted-foreground text-sm">
|
||||
<Trans>
|
||||
The next recipient to sign this document will be{' '}
|
||||
<span className="font-semibold">{form.watch('name')}</span> (
|
||||
@@ -147,11 +137,7 @@ export function AssistantConfirmationDialog({
|
||||
<Trans>Name</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
className="mt-2"
|
||||
placeholder={t`Enter the next signer's name`}
|
||||
/>
|
||||
<Input {...field} className="mt-2" placeholder={t`Enter the next signer's name`} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import type { z } from 'zod';
|
||||
|
||||
import type { TLicenseClaim } from '@documenso/lib/types/license';
|
||||
import { generateDefaultSubscriptionClaim } from '@documenso/lib/utils/organisations-claims';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
@@ -18,6 +13,9 @@ import {
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
import type { z } from 'zod';
|
||||
|
||||
import { SubscriptionClaimForm } from '../forms/subscription-claim-form';
|
||||
|
||||
@@ -75,12 +73,7 @@ export const ClaimCreateDialog = ({ licenseFlags }: ClaimCreateDialogProps) => {
|
||||
licenseFlags={licenseFlags}
|
||||
formSubmitTrigger={
|
||||
<DialogFooter>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => setOpen(false)}
|
||||
disabled={isPending}
|
||||
>
|
||||
<Button type="button" variant="outline" onClick={() => setOpen(false)} disabled={isPending}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
@@ -15,6 +11,8 @@ import {
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
|
||||
export type ClaimDeleteDialogProps = {
|
||||
claimId: string;
|
||||
@@ -23,12 +21,7 @@ export type ClaimDeleteDialogProps = {
|
||||
trigger: React.ReactNode;
|
||||
};
|
||||
|
||||
export const ClaimDeleteDialog = ({
|
||||
claimId,
|
||||
claimName,
|
||||
claimLocked,
|
||||
trigger,
|
||||
}: ClaimDeleteDialogProps) => {
|
||||
export const ClaimDeleteDialog = ({ claimId, claimName, claimLocked, trigger }: ClaimDeleteDialogProps) => {
|
||||
const { t } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
|
||||
import type { TLicenseClaim } from '@documenso/lib/types/license';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import type { TFindSubscriptionClaimsResponse } from '@documenso/trpc/server/admin-router/find-subscription-claims.types';
|
||||
@@ -16,6 +12,8 @@ import {
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { SubscriptionClaimForm } from '../forms/subscription-claim-form';
|
||||
|
||||
@@ -74,12 +72,7 @@ export const ClaimUpdateDialog = ({ claim, trigger, licenseFlags }: ClaimUpdateD
|
||||
licenseFlags={licenseFlags}
|
||||
formSubmitTrigger={
|
||||
<DialogFooter>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => setOpen(false)}
|
||||
disabled={isPending}
|
||||
>
|
||||
<Button type="button" variant="outline" onClick={() => setOpen(false)} disabled={isPending}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
|
||||
@@ -1,15 +1,3 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
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 { useForm } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { FolderType } from '@documenso/lib/types/folder-type';
|
||||
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
|
||||
@@ -23,16 +11,19 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
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';
|
||||
|
||||
@@ -165,7 +156,7 @@ export const DocumentMoveToFolderDialog = ({
|
||||
</DialogHeader>
|
||||
|
||||
<div className="relative">
|
||||
<Search className="text-muted-foreground absolute left-2 top-3 h-4 w-4" />
|
||||
<Search className="absolute top-3 left-2 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder={_(msg`Search folders...`)}
|
||||
value={searchTerm}
|
||||
@@ -219,7 +210,7 @@ export const DocumentMoveToFolderDialog = ({
|
||||
))}
|
||||
|
||||
{searchTerm && filteredFolders?.length === 0 && (
|
||||
<div className="text-muted-foreground px-2 py-2 text-center text-sm">
|
||||
<div className="px-2 py-2 text-center text-muted-foreground text-sm">
|
||||
<Trans>No folders found</Trans>
|
||||
</div>
|
||||
)}
|
||||
@@ -239,9 +230,7 @@ export const DocumentMoveToFolderDialog = ({
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={
|
||||
isFoldersLoading || form.formState.isSubmitting || currentFolderId === null
|
||||
}
|
||||
disabled={isFoldersLoading || form.formState.isSubmitting || currentFolderId === null}
|
||||
>
|
||||
<Trans>Move</Trans>
|
||||
</Button>
|
||||
|
||||
@@ -1,14 +1,3 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
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 { useForm, useWatch } from 'react-hook-form';
|
||||
import * as z from 'zod';
|
||||
|
||||
import { useSession } from '@documenso/lib/client-only/providers/session';
|
||||
import { getRecipientType } from '@documenso/lib/client-only/recipient-type';
|
||||
import type { TRecipientLite } from '@documenso/lib/types/recipient';
|
||||
@@ -28,14 +17,17 @@ import {
|
||||
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 { 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';
|
||||
|
||||
@@ -142,10 +134,7 @@ export const DocumentResendDialog = ({ document, recipients }: DocumentResendDia
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<>
|
||||
{recipients.map((recipient) => (
|
||||
<FormItem
|
||||
key={recipient.id}
|
||||
className="flex flex-row items-center justify-between gap-x-3"
|
||||
>
|
||||
<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),
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { DocumentStatus, EnvelopeType } from '@prisma/client';
|
||||
import { P, match } from 'ts-pattern';
|
||||
|
||||
import { useLimits } from '@documenso/ee/server-only/limits/provider/client';
|
||||
import { trpc as trpcReact } from '@documenso/trpc/react';
|
||||
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
|
||||
@@ -22,6 +14,11 @@ import {
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { DocumentStatus, EnvelopeType } from '@prisma/client';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { match, P } from 'ts-pattern';
|
||||
|
||||
type EnvelopeDeleteDialogProps = {
|
||||
id: string;
|
||||
@@ -130,13 +127,13 @@ export const EnvelopeDeleteDialog = ({
|
||||
<AlertDescription>
|
||||
{type === EnvelopeType.DOCUMENT ? (
|
||||
<Trans>
|
||||
Please note that this action is <strong>irreversible</strong>. Once confirmed,
|
||||
this document will be permanently deleted.
|
||||
Please note that this action is <strong>irreversible</strong>. Once confirmed, this document will
|
||||
be permanently deleted.
|
||||
</Trans>
|
||||
) : (
|
||||
<Trans>
|
||||
Please note that this action is <strong>irreversible</strong>. Once confirmed,
|
||||
this template will be permanently deleted.
|
||||
Please note that this action is <strong>irreversible</strong>. Once confirmed, this template will
|
||||
be permanently deleted.
|
||||
</Trans>
|
||||
)}
|
||||
</AlertDescription>
|
||||
|
||||
@@ -1,16 +1,3 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { DocumentDistributionMethod, DocumentStatus, EnvelopeType } from '@prisma/client';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { InfoIcon } from 'lucide-react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { match } from 'ts-pattern';
|
||||
import * as z from 'zod';
|
||||
|
||||
import { useCurrentEnvelopeEditor } from '@documenso/lib/client-only/providers/envelope-editor-provider';
|
||||
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
|
||||
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
||||
@@ -32,27 +19,24 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@documenso/ui/primitives/select';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
|
||||
import { SpinnerBox } from '@documenso/ui/primitives/spinner';
|
||||
import { Tabs, TabsList, TabsTrigger } from '@documenso/ui/primitives/tabs';
|
||||
import { Textarea } from '@documenso/ui/primitives/textarea';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
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 { useEffect, useMemo, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { match } from 'ts-pattern';
|
||||
import * as z from 'zod';
|
||||
|
||||
export type EnvelopeDistributeDialogProps = {
|
||||
onDistribute?: () => Promise<void>;
|
||||
@@ -66,10 +50,7 @@ export const ZEnvelopeDistributeFormSchema = z.object({
|
||||
emailReplyTo: z.preprocess((val) => (val === '' ? undefined : val), zEmail().optional()),
|
||||
subject: z.string(),
|
||||
message: z.string(),
|
||||
distributionMethod: z
|
||||
.nativeEnum(DocumentDistributionMethod)
|
||||
.optional()
|
||||
.default(DocumentDistributionMethod.EMAIL),
|
||||
distributionMethod: z.nativeEnum(DocumentDistributionMethod).optional().default(DocumentDistributionMethod.EMAIL),
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -100,8 +81,7 @@ export const EnvelopeDistributeDialog = ({
|
||||
emailReplyTo: envelope.documentMeta?.emailReplyTo || undefined,
|
||||
subject: envelope.documentMeta?.subject ?? '',
|
||||
message: envelope.documentMeta?.message ?? '',
|
||||
distributionMethod:
|
||||
envelope.documentMeta?.distributionMethod || DocumentDistributionMethod.EMAIL,
|
||||
distributionMethod: envelope.documentMeta?.distributionMethod || DocumentDistributionMethod.EMAIL,
|
||||
},
|
||||
},
|
||||
resolver: zodResolver(ZEnvelopeDistributeFormSchema),
|
||||
@@ -114,16 +94,15 @@ export const EnvelopeDistributeDialog = ({
|
||||
formState: { isSubmitting },
|
||||
} = form;
|
||||
|
||||
const { data: emailData, isLoading: isLoadingEmails } =
|
||||
trpc.enterprise.organisation.email.find.useQuery(
|
||||
{
|
||||
organisationId: organisation.id,
|
||||
perPage: 100,
|
||||
},
|
||||
{
|
||||
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||
},
|
||||
);
|
||||
const { data: emailData, isLoading: isLoadingEmails } = trpc.enterprise.organisation.email.find.useQuery(
|
||||
{
|
||||
organisationId: organisation.id,
|
||||
perPage: 100,
|
||||
},
|
||||
{
|
||||
...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||
},
|
||||
);
|
||||
|
||||
const emails = emailData?.data || [];
|
||||
|
||||
@@ -153,9 +132,7 @@ export const EnvelopeDistributeDialog = ({
|
||||
recipientAuth: recipient.authOptions,
|
||||
});
|
||||
|
||||
return (
|
||||
(auth.recipientAccessAuthRequired || auth.recipientActionAuthRequired) && !recipient.email
|
||||
);
|
||||
return (auth.recipientAccessAuthRequired || auth.recipientActionAuthRequired) && !recipient.email;
|
||||
});
|
||||
}, [recipientsWithIndex, envelope.authOptions]);
|
||||
|
||||
@@ -310,14 +287,9 @@ export const EnvelopeDistributeDialog = ({
|
||||
<Select
|
||||
{...field}
|
||||
value={field.value === null ? '-1' : field.value}
|
||||
onValueChange={(value) =>
|
||||
field.onChange(value === '-1' ? null : value)
|
||||
}
|
||||
onValueChange={(value) => field.onChange(value === '-1' ? null : value)}
|
||||
>
|
||||
<SelectTrigger
|
||||
loading={isLoadingEmails}
|
||||
className="bg-background"
|
||||
>
|
||||
<SelectTrigger loading={isLoadingEmails} className="bg-background">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
|
||||
@@ -346,8 +318,7 @@ export const EnvelopeDistributeDialog = ({
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<Trans>
|
||||
Reply To Email{' '}
|
||||
<span className="text-muted-foreground">(Optional)</span>
|
||||
Reply To Email <span className="text-muted-foreground">(Optional)</span>
|
||||
</Trans>
|
||||
</FormLabel>
|
||||
|
||||
@@ -367,8 +338,7 @@ export const EnvelopeDistributeDialog = ({
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<Trans>
|
||||
Subject{' '}
|
||||
<span className="text-muted-foreground">(Optional)</span>
|
||||
Subject <span className="text-muted-foreground">(Optional)</span>
|
||||
</Trans>
|
||||
</FormLabel>
|
||||
|
||||
@@ -387,8 +357,7 @@ export const EnvelopeDistributeDialog = ({
|
||||
<FormItem>
|
||||
<FormLabel className="flex flex-row items-center">
|
||||
<Trans>
|
||||
Message{' '}
|
||||
<span className="text-muted-foreground">(Optional)</span>
|
||||
Message <span className="text-muted-foreground">(Optional)</span>
|
||||
</Trans>
|
||||
<Tooltip>
|
||||
<TooltipTrigger type="button">
|
||||
@@ -422,15 +391,15 @@ export const EnvelopeDistributeDialog = ({
|
||||
exit={{ opacity: 0, transition: { duration: 0.15 } }}
|
||||
className="min-h-60 rounded-lg border"
|
||||
>
|
||||
<div className="py-24 text-center text-sm text-muted-foreground">
|
||||
<div className="py-24 text-center text-muted-foreground text-sm">
|
||||
<p>
|
||||
<Trans>We won't send anything to notify recipients.</Trans>
|
||||
</p>
|
||||
|
||||
<p className="mt-2">
|
||||
<Trans>
|
||||
We will generate signing links for you, which you can send to the
|
||||
recipients through your method of choice.
|
||||
We will generate signing links for you, which you can send to the recipients through your
|
||||
method of choice.
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
@@ -470,7 +439,7 @@ export const EnvelopeDistributeDialog = ({
|
||||
<AlertDescription>
|
||||
<Trans>The following signers are missing signature fields:</Trans>
|
||||
|
||||
<ul className="ml-2 mt-1 list-inside list-disc">
|
||||
<ul className="mt-1 ml-2 list-inside list-disc">
|
||||
{recipientsMissingSignatureFields.map((recipient) => (
|
||||
<li key={recipient.id}>
|
||||
{recipient.email || recipient.name || t`Recipient ${recipient.index + 1}`}
|
||||
@@ -483,7 +452,7 @@ export const EnvelopeDistributeDialog = ({
|
||||
<AlertDescription>
|
||||
<Trans>The following recipients require an email address:</Trans>
|
||||
|
||||
<ul className="ml-2 mt-1 list-inside list-disc">
|
||||
<ul className="mt-1 ml-2 list-inside list-disc">
|
||||
{recipientsMissingRequiredEmail.map((recipient) => (
|
||||
<li key={recipient.id}>
|
||||
{recipient.email || recipient.name || t`Recipient ${recipient.index + 1}`}
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { DocumentStatus, type EnvelopeItem } from '@prisma/client';
|
||||
import { DownloadIcon, FileTextIcon } from 'lucide-react';
|
||||
|
||||
import { downloadPDF } from '@documenso/lib/client-only/download-pdf';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
@@ -18,6 +11,10 @@ import {
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import { Skeleton } from '@documenso/ui/primitives/skeleton';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { DocumentStatus, type EnvelopeItem } from '@prisma/client';
|
||||
import { DownloadIcon, FileTextIcon } from 'lucide-react';
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
type EnvelopeItemToDownload = Pick<EnvelopeItem, 'id' | 'envelopeId' | 'title' | 'order'>;
|
||||
|
||||
@@ -65,10 +62,8 @@ export const EnvelopeDownloadDialog = ({
|
||||
[envelopeItemIdAndVersion: string]: boolean;
|
||||
}>({});
|
||||
|
||||
const generateDownloadKey = (
|
||||
envelopeItemId: string,
|
||||
version: 'original' | 'signed' | 'pending',
|
||||
) => `${envelopeItemId}-${version}`;
|
||||
const generateDownloadKey = (envelopeItemId: string, version: 'original' | 'signed' | 'pending') =>
|
||||
`${envelopeItemId}-${version}`;
|
||||
|
||||
// The dialog shows the original document alongside one of:
|
||||
// - "Signed" (when the envelope is COMPLETED)
|
||||
@@ -96,24 +91,20 @@ export const EnvelopeDownloadDialog = ({
|
||||
return null;
|
||||
}, [envelopeStatus, isLegacy, token, t]);
|
||||
|
||||
const { data: envelopeItemsPayload, isLoading: isLoadingEnvelopeItems } =
|
||||
trpc.envelope.item.getManyByToken.useQuery(
|
||||
{
|
||||
envelopeId,
|
||||
access: token ? { type: 'recipient', token } : { type: 'user' },
|
||||
},
|
||||
{
|
||||
initialData: initialEnvelopeItems ? { data: initialEnvelopeItems } : undefined,
|
||||
enabled: open,
|
||||
},
|
||||
);
|
||||
const { data: envelopeItemsPayload, isLoading: isLoadingEnvelopeItems } = trpc.envelope.item.getManyByToken.useQuery(
|
||||
{
|
||||
envelopeId,
|
||||
access: token ? { type: 'recipient', token } : { type: 'user' },
|
||||
},
|
||||
{
|
||||
initialData: initialEnvelopeItems ? { data: initialEnvelopeItems } : undefined,
|
||||
enabled: open,
|
||||
},
|
||||
);
|
||||
|
||||
const envelopeItems = envelopeItemsPayload?.data || [];
|
||||
|
||||
const onDownload = async (
|
||||
envelopeItem: EnvelopeItemToDownload,
|
||||
version: 'original' | 'signed' | 'pending',
|
||||
) => {
|
||||
const onDownload = async (envelopeItem: EnvelopeItemToDownload, version: 'original' | 'signed' | 'pending') => {
|
||||
const { id: envelopeItemId } = envelopeItem;
|
||||
|
||||
if (isDownloadingState[generateDownloadKey(envelopeItemId, version)]) {
|
||||
@@ -169,13 +160,9 @@ export const EnvelopeDownloadDialog = ({
|
||||
</DialogHeader>
|
||||
|
||||
<div className="flex w-full flex-col gap-4 overflow-hidden">
|
||||
{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"
|
||||
>
|
||||
{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" />
|
||||
|
||||
<div className="flex w-full flex-col gap-2">
|
||||
@@ -185,64 +172,59 @@ export const EnvelopeDownloadDialog = ({
|
||||
|
||||
<Skeleton className="h-10 w-20 flex-shrink-0 rounded-lg" />
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
envelopeItems.map((item) => (
|
||||
<div
|
||||
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="flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10">
|
||||
<FileTextIcon className="h-5 w-5 text-primary" />
|
||||
))
|
||||
: envelopeItems.map((item) => (
|
||||
<div
|
||||
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="flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10">
|
||||
<FileTextIcon className="h-5 w-5 text-primary" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="min-w-0 flex-1">
|
||||
{/* Todo: Envelopes - Fix overflow */}
|
||||
<h4 className="truncate text-sm font-medium text-foreground" title={item.title}>
|
||||
{item.title}
|
||||
</h4>
|
||||
<p className="mt-0.5 text-xs text-muted-foreground">
|
||||
<Trans>PDF Document</Trans>
|
||||
</p>
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
{/* Todo: Envelopes - Fix overflow */}
|
||||
<h4 className="truncate font-medium text-foreground text-sm" title={item.title}>
|
||||
{item.title}
|
||||
</h4>
|
||||
<p className="mt-0.5 text-muted-foreground text-xs">
|
||||
<Trans>PDF Document</Trans>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-shrink-0 items-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="text-xs"
|
||||
onClick={async () => onDownload(item, 'original')}
|
||||
loading={isDownloadingState[generateDownloadKey(item.id, 'original')]}
|
||||
>
|
||||
{!isDownloadingState[generateDownloadKey(item.id, 'original')] && (
|
||||
<DownloadIcon className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
<Trans context="Original document (adjective)">Original</Trans>
|
||||
</Button>
|
||||
|
||||
{secondaryDownload && (
|
||||
<div className="flex flex-shrink-0 items-center gap-2">
|
||||
<Button
|
||||
variant="default"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="text-xs"
|
||||
onClick={async () => onDownload(item, secondaryDownload.version)}
|
||||
loading={
|
||||
isDownloadingState[generateDownloadKey(item.id, secondaryDownload.version)]
|
||||
}
|
||||
onClick={async () => onDownload(item, 'original')}
|
||||
loading={isDownloadingState[generateDownloadKey(item.id, 'original')]}
|
||||
>
|
||||
{!isDownloadingState[
|
||||
generateDownloadKey(item.id, secondaryDownload.version)
|
||||
] && <DownloadIcon className="mr-2 h-4 w-4" />}
|
||||
{secondaryDownload.label}
|
||||
{!isDownloadingState[generateDownloadKey(item.id, 'original')] && (
|
||||
<DownloadIcon className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
<Trans context="Original document (adjective)">Original</Trans>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{secondaryDownload && (
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
className="text-xs"
|
||||
onClick={async () => onDownload(item, secondaryDownload.version)}
|
||||
loading={isDownloadingState[generateDownloadKey(item.id, secondaryDownload.version)]}
|
||||
>
|
||||
{!isDownloadingState[generateDownloadKey(item.id, secondaryDownload.version)] && (
|
||||
<DownloadIcon className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
{secondaryDownload.label}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
))}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
import { useNavigate } from 'react-router';
|
||||
|
||||
import { formatDocumentsPath, formatTemplatesPath } from '@documenso/lib/utils/teams';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
@@ -18,6 +12,10 @@ import {
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
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 { useNavigate } from 'react-router';
|
||||
|
||||
import { useCurrentTeam } from '~/providers/team';
|
||||
|
||||
@@ -27,11 +25,7 @@ type EnvelopeDuplicateDialogProps = {
|
||||
trigger?: React.ReactNode;
|
||||
};
|
||||
|
||||
export const EnvelopeDuplicateDialog = ({
|
||||
envelopeId,
|
||||
envelopeType,
|
||||
trigger,
|
||||
}: EnvelopeDuplicateDialogProps) => {
|
||||
export const EnvelopeDuplicateDialog = ({ envelopeId, envelopeType, trigger }: EnvelopeDuplicateDialogProps) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
@@ -43,23 +37,22 @@ export const EnvelopeDuplicateDialog = ({
|
||||
|
||||
const isDocument = envelopeType === EnvelopeType.DOCUMENT;
|
||||
|
||||
const { mutateAsync: duplicateEnvelope, isPending: isDuplicating } =
|
||||
trpc.envelope.duplicate.useMutation({
|
||||
onSuccess: async ({ id }) => {
|
||||
toast({
|
||||
title: isDocument ? t`Document Duplicated` : t`Template Duplicated`,
|
||||
description: isDocument
|
||||
? t`Your document has been successfully duplicated.`
|
||||
: t`Your template has been successfully duplicated.`,
|
||||
duration: 5000,
|
||||
});
|
||||
const { mutateAsync: duplicateEnvelope, isPending: isDuplicating } = trpc.envelope.duplicate.useMutation({
|
||||
onSuccess: async ({ id }) => {
|
||||
toast({
|
||||
title: isDocument ? t`Document Duplicated` : t`Template Duplicated`,
|
||||
description: isDocument
|
||||
? t`Your document has been successfully duplicated.`
|
||||
: t`Your template has been successfully duplicated.`,
|
||||
duration: 5000,
|
||||
});
|
||||
|
||||
const path = isDocument ? formatDocumentsPath(team.url) : formatTemplatesPath(team.url);
|
||||
const path = isDocument ? formatDocumentsPath(team.url) : formatTemplatesPath(team.url);
|
||||
|
||||
await navigate(`${path}/${id}/edit`);
|
||||
setOpen(false);
|
||||
},
|
||||
});
|
||||
await navigate(`${path}/${id}/edit`);
|
||||
setOpen(false);
|
||||
},
|
||||
});
|
||||
|
||||
const onDuplicate = async () => {
|
||||
try {
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
@@ -16,6 +11,8 @@ import {
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
|
||||
export type EnvelopeItemDeleteDialogProps = {
|
||||
canItemBeDeleted: boolean;
|
||||
@@ -39,28 +36,27 @@ export const EnvelopeItemDeleteDialog = ({
|
||||
const { t } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const { mutateAsync: deleteEnvelopeItem, isPending: isDeleting } =
|
||||
trpc.envelope.item.delete.useMutation({
|
||||
onSuccess: () => {
|
||||
toast({
|
||||
title: t`Success`,
|
||||
description: t`You have successfully removed this envelope item.`,
|
||||
duration: 5000,
|
||||
});
|
||||
const { mutateAsync: deleteEnvelopeItem, isPending: isDeleting } = trpc.envelope.item.delete.useMutation({
|
||||
onSuccess: () => {
|
||||
toast({
|
||||
title: t`Success`,
|
||||
description: t`You have successfully removed this envelope item.`,
|
||||
duration: 5000,
|
||||
});
|
||||
|
||||
onDelete?.(envelopeItemId);
|
||||
onDelete?.(envelopeItemId);
|
||||
|
||||
setOpen(false);
|
||||
},
|
||||
onError: () => {
|
||||
toast({
|
||||
title: t`An unknown error occurred`,
|
||||
description: t`We encountered an unknown error while attempting to remove this envelope item. Please try again later.`,
|
||||
variant: 'destructive',
|
||||
duration: 10000,
|
||||
});
|
||||
},
|
||||
});
|
||||
setOpen(false);
|
||||
},
|
||||
onError: () => {
|
||||
toast({
|
||||
title: t`An unknown error occurred`,
|
||||
description: t`We encountered an unknown error while attempting to remove this envelope item. Please try again later.`,
|
||||
variant: 'destructive',
|
||||
duration: 10000,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={(value) => !isDeleting && setOpen(value)}>
|
||||
@@ -74,16 +70,12 @@ export const EnvelopeItemDeleteDialog = ({
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription className="mt-4">
|
||||
<Trans>
|
||||
You are about to remove the following document and all associated fields
|
||||
</Trans>
|
||||
<Trans>You are about to remove the following document and all associated fields</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<Alert variant="neutral">
|
||||
<AlertDescription className="text-center font-semibold">
|
||||
{envelopeItemTitle}
|
||||
</AlertDescription>
|
||||
<AlertDescription className="text-center font-semibold">{envelopeItemTitle}</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
<fieldset disabled={isDeleting}>
|
||||
@@ -116,9 +108,7 @@ export const EnvelopeItemDeleteDialog = ({
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription className="mt-4">
|
||||
<Trans>
|
||||
You cannot delete this item because the document has been sent to recipients.
|
||||
</Trans>
|
||||
<Trans>You cannot delete this item because the document has been sent to recipients.</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Plural, Trans, useLingui } from '@lingui/react/macro';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { AlertTriangleIcon, FileIcon, UploadIcon, XIcon } from 'lucide-react';
|
||||
import { type FileRejection, useDropzone } from 'react-dropzone';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { useCurrentEnvelopeEditor } from '@documenso/lib/client-only/providers/envelope-editor-provider';
|
||||
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT } from '@documenso/lib/constants/app';
|
||||
import { megabytesToBytes } from '@documenso/lib/universal/unit-convertions';
|
||||
@@ -28,16 +18,17 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
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 { Plural, Trans, useLingui } from '@lingui/react/macro';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { AlertTriangleIcon, FileIcon, UploadIcon, XIcon } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { type FileRejection, useDropzone } from 'react-dropzone';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
const ZEditEnvelopeItemFormSchema = z.object({
|
||||
title: ZDocumentTitleSchema,
|
||||
@@ -66,9 +57,7 @@ export const EnvelopeItemEditDialog = ({
|
||||
const { envelope, editorFields, setLocalEnvelope, isEmbedded } = useCurrentEnvelopeEditor();
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [replacementFile, setReplacementFile] = useState<{ file: File; pageCount: number } | null>(
|
||||
null,
|
||||
);
|
||||
const [replacementFile, setReplacementFile] = useState<{ file: File; pageCount: number } | null>(null);
|
||||
const [isDropping, setIsDropping] = useState(false);
|
||||
|
||||
const form = useForm<TEditEnvelopeItemFormSchema>({
|
||||
@@ -82,9 +71,7 @@ export const EnvelopeItemEditDialog = ({
|
||||
onSuccess: ({ data, fields }) => {
|
||||
setLocalEnvelope({
|
||||
envelopeItems: envelope.envelopeItems.map((item) =>
|
||||
item.id === data.id
|
||||
? { ...item, documentDataId: data.documentDataId, title: data.title }
|
||||
: item,
|
||||
item.id === data.id ? { ...item, documentDataId: data.documentDataId, title: data.title } : item,
|
||||
),
|
||||
});
|
||||
|
||||
@@ -98,8 +85,7 @@ export const EnvelopeItemEditDialog = ({
|
||||
const fieldsOnExcessPages =
|
||||
replacementFile !== null
|
||||
? envelope.fields.filter(
|
||||
(field) =>
|
||||
field.envelopeItemId === envelopeItem.id && field.page > replacementFile.pageCount,
|
||||
(field) => field.envelopeItemId === envelopeItem.id && field.page > replacementFile.pageCount,
|
||||
)
|
||||
: [];
|
||||
|
||||
@@ -222,11 +208,7 @@ export const EnvelopeItemEditDialog = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
{...props}
|
||||
open={isOpen}
|
||||
onOpenChange={(value) => !form.formState.isSubmitting && setIsOpen(value)}
|
||||
>
|
||||
<Dialog {...props} open={isOpen} onOpenChange={(value) => !form.formState.isSubmitting && setIsOpen(value)}>
|
||||
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
|
||||
{trigger}
|
||||
</DialogTrigger>
|
||||
@@ -279,18 +261,12 @@ export const EnvelopeItemEditDialog = ({
|
||||
<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="min-w-0">
|
||||
<p className="truncate text-sm font-medium">
|
||||
{replacementFile.file.name}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<p className="truncate font-medium text-sm">{replacementFile.file.name}</p>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
{formatFileSize(replacementFile.file.size)}
|
||||
{isDropping ? ' · …' : ' · '}
|
||||
{!isDropping && replacementFile.pageCount !== null && (
|
||||
<Plural
|
||||
one="1 page"
|
||||
other="# pages"
|
||||
value={replacementFile.pageCount}
|
||||
/>
|
||||
<Plural one="1 page" other="# pages" value={replacementFile.pageCount} />
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
@@ -326,14 +302,14 @@ export const EnvelopeItemEditDialog = ({
|
||||
data-testid="envelope-item-edit-dropzone"
|
||||
{...getRootProps()}
|
||||
className={cn(
|
||||
'mt-1.5 flex cursor-pointer items-center justify-center rounded-md border border-dashed border-border px-4 py-4 transition-colors',
|
||||
'mt-1.5 flex cursor-pointer items-center justify-center rounded-md border border-border border-dashed px-4 py-4 transition-colors',
|
||||
isDragActive
|
||||
? 'border-primary/50 bg-primary/5'
|
||||
: 'hover:border-muted-foreground/50 hover:bg-muted/50',
|
||||
)}
|
||||
>
|
||||
<input {...getInputProps()} />
|
||||
<div className="flex items-center space-x-2 text-sm text-muted-foreground">
|
||||
<div className="flex items-center space-x-2 text-muted-foreground text-sm">
|
||||
<UploadIcon className="h-4 w-4" />
|
||||
<span>
|
||||
<Trans>Drop PDF here or click to select</Trans>
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { DocumentStatus, EnvelopeType, SigningStatus } from '@prisma/client';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import * as z from 'zod';
|
||||
|
||||
import { getRecipientType } from '@documenso/lib/client-only/recipient-type';
|
||||
import type { TEnvelope } from '@documenso/lib/types/envelope';
|
||||
import type { TEnvelopeRecipientLite } from '@documenso/lib/types/recipient';
|
||||
@@ -26,14 +16,15 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
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 { 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 * as z from 'zod';
|
||||
|
||||
import { StackAvatar } from '../general/stack-avatar';
|
||||
|
||||
@@ -52,10 +43,7 @@ export const ZEnvelopeRedistributeFormSchema = z.object({
|
||||
|
||||
export type TEnvelopeRedistributeFormSchema = z.infer<typeof ZEnvelopeRedistributeFormSchema>;
|
||||
|
||||
export const EnvelopeRedistributeDialog = ({
|
||||
envelope,
|
||||
trigger,
|
||||
}: EnvelopeRedistributeDialogProps) => {
|
||||
export const EnvelopeRedistributeDialog = ({ envelope, trigger }: EnvelopeRedistributeDialogProps) => {
|
||||
const recipients = envelope.recipients;
|
||||
|
||||
const { toast } = useToast();
|
||||
|
||||
@@ -1,20 +1,12 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
|
||||
import { trpc as trpcReact } from '@documenso/trpc/react';
|
||||
import { DOCUMENT_TITLE_MAX_LENGTH } from '@documenso/trpc/server/document-router/schema';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@documenso/ui/primitives/dialog';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import { Label } from '@documenso/ui/primitives/label';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export type EnvelopeRenameDialogProps = {
|
||||
id: string;
|
||||
@@ -89,9 +81,7 @@ export const EnvelopeRenameDialog = ({
|
||||
<Dialog open={open} onOpenChange={(value) => !isPending && onOpenChange(value)}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{isTemplate ? <Trans>Rename Template</Trans> : <Trans>Rename Document</Trans>}
|
||||
</DialogTitle>
|
||||
<DialogTitle>{isTemplate ? <Trans>Rename Template</Trans> : <Trans>Rename Document</Trans>}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="py-2">
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router';
|
||||
|
||||
import { formatTemplatesPath } from '@documenso/lib/utils/teams';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
@@ -20,6 +14,10 @@ import {
|
||||
} 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 { useState } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router';
|
||||
|
||||
import { useCurrentTeam } from '~/providers/team';
|
||||
|
||||
@@ -153,10 +151,7 @@ export const EnvelopeSaveAsTemplateDialog = ({
|
||||
disabled={!includeRecipients}
|
||||
onCheckedChange={(checked) => field.onChange(checked === true)}
|
||||
/>
|
||||
<Label
|
||||
htmlFor="envelopeIncludeFields"
|
||||
className={!includeRecipients ? 'opacity-50' : ''}
|
||||
>
|
||||
<Label htmlFor="envelopeIncludeFields" className={!includeRecipients ? 'opacity-50' : ''}>
|
||||
<Trans>Include Fields</Trans>
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
import { plural } from '@lingui/core/macro';
|
||||
import { Plural, useLingui } from '@lingui/react/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
@@ -16,6 +10,10 @@ import {
|
||||
DialogTitle,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { plural } from '@lingui/core/macro';
|
||||
import { Plural, Trans, useLingui } from '@lingui/react/macro';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
|
||||
export type EnvelopesBulkDeleteDialogProps = {
|
||||
envelopeIds: string[];
|
||||
@@ -88,9 +86,7 @@ export const EnvelopesBulkDeleteDialog = ({
|
||||
<Dialog {...props} open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{isDocument ? <Trans>Delete Documents</Trans> : <Trans>Delete Templates</Trans>}
|
||||
</DialogTitle>
|
||||
<DialogTitle>{isDocument ? <Trans>Delete Documents</Trans> : <Trans>Delete Templates</Trans>}</DialogTitle>
|
||||
|
||||
<DialogDescription>
|
||||
{isDocument ? (
|
||||
@@ -149,12 +145,7 @@ export const EnvelopesBulkDeleteDialog = ({
|
||||
</Alert>
|
||||
|
||||
<DialogFooter>
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
onClick={() => onOpenChange(false)}
|
||||
disabled={isPending}
|
||||
>
|
||||
<Button type="button" variant="secondary" onClick={() => onOpenChange(false)} disabled={isPending}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
|
||||
@@ -1,15 +1,3 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Plural, useLingui } from '@lingui/react/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { FolderIcon, HomeIcon, Loader2, Search } from 'lucide-react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { match } from 'ts-pattern';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
@@ -21,16 +9,18 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
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 { Plural, Trans, useLingui } from '@lingui/react/macro';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
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 { match } from 'ts-pattern';
|
||||
import { z } from 'zod';
|
||||
|
||||
export type EnvelopesBulkMoveDialogProps = {
|
||||
envelopeIds: string[];
|
||||
@@ -119,10 +109,7 @@ export const EnvelopesBulkMoveDialog = ({
|
||||
const error = AppError.parseError(err);
|
||||
|
||||
const errorMessage = match(error.code)
|
||||
.with(
|
||||
AppErrorCode.NOT_FOUND,
|
||||
() => t`The folder you are trying to move the items to does not exist.`,
|
||||
)
|
||||
.with(AppErrorCode.NOT_FOUND, () => t`The folder you are trying to move the items to does not exist.`)
|
||||
.with(AppErrorCode.UNAUTHORIZED, () => t`You are not allowed to move these items.`)
|
||||
.with(AppErrorCode.INVALID_BODY, () => t`All items must be of the same type.`)
|
||||
.otherwise(() => t`An error occurred while moving the items.`);
|
||||
@@ -143,11 +130,7 @@ export const EnvelopesBulkMoveDialog = ({
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{isDocument ? (
|
||||
<Trans>Move Documents to Folder</Trans>
|
||||
) : (
|
||||
<Trans>Move Templates to Folder</Trans>
|
||||
)}
|
||||
{isDocument ? <Trans>Move Documents to Folder</Trans> : <Trans>Move Templates to Folder</Trans>}
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription>
|
||||
@@ -168,7 +151,7 @@ export const EnvelopesBulkMoveDialog = ({
|
||||
</DialogHeader>
|
||||
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2 top-3 h-4 w-4 text-muted-foreground" />
|
||||
<Search className="absolute top-3 left-2 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder={t`Search folders...`}
|
||||
value={searchTerm}
|
||||
@@ -222,7 +205,7 @@ export const EnvelopesBulkMoveDialog = ({
|
||||
))}
|
||||
|
||||
{searchTerm && filteredFolders?.length === 0 && (
|
||||
<div className="px-2 py-2 text-center text-sm text-muted-foreground">
|
||||
<div className="px-2 py-2 text-center text-muted-foreground text-sm">
|
||||
<Trans>No folders found</Trans>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,14 +1,3 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import type { FolderType } from '@prisma/client';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { FolderPlusIcon } from 'lucide-react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useParams } from 'react-router';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import {
|
||||
@@ -20,16 +9,18 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
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 { Trans, useLingui } from '@lingui/react/macro';
|
||||
import type { FolderType } from '@prisma/client';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { FolderPlusIcon } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useParams } from 'react-router';
|
||||
import { z } from 'zod';
|
||||
|
||||
const ZCreateFolderFormSchema = z.object({
|
||||
name: z.string().min(1, { message: 'Folder name is required' }),
|
||||
@@ -43,12 +34,7 @@ export type FolderCreateDialogProps = {
|
||||
parentFolderId?: string | null;
|
||||
} & Omit<DialogPrimitive.DialogProps, 'children'>;
|
||||
|
||||
export const FolderCreateDialog = ({
|
||||
type,
|
||||
trigger,
|
||||
parentFolderId,
|
||||
...props
|
||||
}: FolderCreateDialogProps) => {
|
||||
export const FolderCreateDialog = ({ type, trigger, parentFolderId, ...props }: FolderCreateDialogProps) => {
|
||||
const { t } = useLingui();
|
||||
const { toast } = useToast();
|
||||
const { folderId } = useParams();
|
||||
@@ -98,11 +84,7 @@ export const FolderCreateDialog = ({
|
||||
<Dialog {...props} open={isCreateFolderOpen} onOpenChange={setIsCreateFolderOpen}>
|
||||
<DialogTrigger asChild>
|
||||
{trigger ?? (
|
||||
<Button
|
||||
variant="outline"
|
||||
className="flex items-center"
|
||||
data-testid="folder-create-button"
|
||||
>
|
||||
<Button variant="outline" className="flex items-center" data-testid="folder-create-button">
|
||||
<FolderPlusIcon className="mr-2 h-4 w-4" />
|
||||
<Trans>Create Folder</Trans>
|
||||
</Button>
|
||||
@@ -139,11 +121,7 @@ export const FolderCreateDialog = ({
|
||||
/>
|
||||
|
||||
<DialogFooter>
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
onClick={() => setIsCreateFolderOpen(false)}
|
||||
>
|
||||
<Button type="button" variant="secondary" onClick={() => setIsCreateFolderOpen(false)}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import type { TFolderWithSubfolders } from '@documenso/trpc/server/folder-router/schema';
|
||||
@@ -20,16 +11,15 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
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 { Trans, useLingui } from '@lingui/react/macro';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
export type FolderDeleteDialogProps = {
|
||||
folder: TFolderWithSubfolders;
|
||||
@@ -110,14 +100,12 @@ export const FolderDeleteDialog = ({ folder, isOpen, onOpenChange }: FolderDelet
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
{(folder._count.documents > 0 ||
|
||||
folder._count.templates > 0 ||
|
||||
folder._count.subfolders > 0) && (
|
||||
{(folder._count.documents > 0 || folder._count.templates > 0 || folder._count.subfolders > 0) && (
|
||||
<Alert variant="destructive">
|
||||
<AlertDescription>
|
||||
<Trans>
|
||||
This folder contains multiple items. Deleting it will remove all subfolders and move
|
||||
all nested documents and templates to the root folder.
|
||||
This folder contains multiple items. Deleting it will remove all subfolders and move all nested
|
||||
documents and templates to the root folder.
|
||||
</Trans>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
@@ -134,9 +122,7 @@ export const FolderDeleteDialog = ({ folder, isOpen, onOpenChange }: FolderDelet
|
||||
<FormLabel>
|
||||
<Trans>
|
||||
Confirm by typing:{' '}
|
||||
<span className="font-sm text-destructive font-semibold">
|
||||
{deleteMessage}
|
||||
</span>
|
||||
<span className="font-semibold font-sm text-destructive">{deleteMessage}</span>
|
||||
</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { FolderIcon, HomeIcon, Search } from 'lucide-react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import type { TFolderWithSubfolders } from '@documenso/trpc/server/folder-router/schema';
|
||||
@@ -20,15 +10,16 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import { Form, FormControl, FormField, FormItem, 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 { Trans, useLingui } from '@lingui/react/macro';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { FolderIcon, HomeIcon, Search } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
export type FolderMoveDialogProps = {
|
||||
foldersData: TFolderWithSubfolders[] | undefined;
|
||||
@@ -43,12 +34,7 @@ const ZMoveFolderFormSchema = z.object({
|
||||
|
||||
type TMoveFolderFormSchema = z.infer<typeof ZMoveFolderFormSchema>;
|
||||
|
||||
export const FolderMoveDialog = ({
|
||||
foldersData,
|
||||
folder,
|
||||
isOpen,
|
||||
onOpenChange,
|
||||
}: FolderMoveDialogProps) => {
|
||||
export const FolderMoveDialog = ({ foldersData, folder, isOpen, onOpenChange }: FolderMoveDialogProps) => {
|
||||
const { t } = useLingui();
|
||||
const { toast } = useToast();
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
@@ -129,7 +115,7 @@ export const FolderMoveDialog = ({
|
||||
</DialogHeader>
|
||||
|
||||
<div className="relative">
|
||||
<Search className="text-muted-foreground absolute left-2 top-3 h-4 w-4" />
|
||||
<Search className="absolute top-3 left-2 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder={t`Search folders...`}
|
||||
value={searchTerm}
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { DocumentVisibility } from '@documenso/lib/types/document-visibility';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
@@ -21,23 +12,16 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@documenso/ui/primitives/select';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { useOptionalCurrentTeam } from '~/providers/team';
|
||||
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import type { MessageDescriptor } from '@lingui/core';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { ExternalLinkIcon } from 'lucide-react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { Link, useSearchParams } from 'react-router';
|
||||
import { match } from 'ts-pattern';
|
||||
import type { z } from 'zod';
|
||||
|
||||
import type { InternalClaimPlans } from '@documenso/ee/server-only/stripe/get-internal-claim-plans';
|
||||
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
|
||||
import { useSession } from '@documenso/lib/client-only/providers/session';
|
||||
@@ -34,18 +20,22 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import { SpinnerBox } from '@documenso/ui/primitives/spinner';
|
||||
import { Tabs, TabsList, TabsTrigger } from '@documenso/ui/primitives/tabs';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import type { MessageDescriptor } from '@lingui/core';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { ExternalLinkIcon } from 'lucide-react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { Link, useSearchParams } from 'react-router';
|
||||
import { match } from 'ts-pattern';
|
||||
import type { z } from 'zod';
|
||||
|
||||
import { IndividualPersonalLayoutCheckoutButton } from '../general/billing-plans';
|
||||
|
||||
@@ -70,9 +60,7 @@ export const OrganisationCreateDialog = ({ trigger, ...props }: OrganisationCrea
|
||||
|
||||
const actionSearchParam = searchParams?.get('action');
|
||||
|
||||
const [step, setStep] = useState<'billing' | 'create'>(
|
||||
IS_BILLING_ENABLED() ? 'billing' : 'create',
|
||||
);
|
||||
const [step, setStep] = useState<'billing' | 'create'>(IS_BILLING_ENABLED() ? 'billing' : 'create');
|
||||
|
||||
const [selectedPriceId, setSelectedPriceId] = useState<string>('');
|
||||
|
||||
@@ -145,11 +133,7 @@ export const OrganisationCreateDialog = ({ trigger, ...props }: OrganisationCrea
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
{...props}
|
||||
open={open}
|
||||
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
|
||||
>
|
||||
<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">
|
||||
@@ -215,10 +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="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
@@ -237,11 +218,7 @@ export const OrganisationCreateDialog = ({ trigger, ...props }: OrganisationCrea
|
||||
|
||||
<DialogFooter>
|
||||
{IS_BILLING_ENABLED() ? (
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
onClick={() => setStep('billing')}
|
||||
>
|
||||
<Button type="button" variant="secondary" onClick={() => setStep('billing')}>
|
||||
<Trans>Back</Trans>
|
||||
</Button>
|
||||
) : (
|
||||
@@ -290,30 +267,23 @@ type BillingPlanFormProps = {
|
||||
canCreateFreeOrganisation: boolean;
|
||||
};
|
||||
|
||||
const BillingPlanForm = ({
|
||||
value,
|
||||
onChange,
|
||||
plans,
|
||||
canCreateFreeOrganisation,
|
||||
}: BillingPlanFormProps) => {
|
||||
const BillingPlanForm = ({ value, onChange, plans, canCreateFreeOrganisation }: BillingPlanFormProps) => {
|
||||
const { t } = useLingui();
|
||||
|
||||
const [billingPeriod, setBillingPeriod] = useState<'monthlyPrice' | 'yearlyPrice'>('yearlyPrice');
|
||||
|
||||
const dynamicPlans = useMemo(() => {
|
||||
return [INTERNAL_CLAIM_ID.INDIVIDUAL, INTERNAL_CLAIM_ID.TEAM, INTERNAL_CLAIM_ID.PLATFORM].map(
|
||||
(planId) => {
|
||||
const plan = plans[planId];
|
||||
return [INTERNAL_CLAIM_ID.INDIVIDUAL, INTERNAL_CLAIM_ID.TEAM, INTERNAL_CLAIM_ID.PLATFORM].map((planId) => {
|
||||
const plan = plans[planId];
|
||||
|
||||
return {
|
||||
id: planId,
|
||||
name: plan.name,
|
||||
description: parseMessageDescriptorMacro(t, internalClaimsDescription[planId]),
|
||||
monthlyPrice: plan.monthlyPrice,
|
||||
yearlyPrice: plan.yearlyPrice,
|
||||
};
|
||||
},
|
||||
);
|
||||
return {
|
||||
id: planId,
|
||||
name: plan.name,
|
||||
description: parseMessageDescriptorMacro(t, internalClaimsDescription[planId]),
|
||||
monthlyPrice: plan.monthlyPrice,
|
||||
yearlyPrice: plan.yearlyPrice,
|
||||
};
|
||||
});
|
||||
}, [plans]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -357,9 +327,9 @@ const BillingPlanForm = ({
|
||||
<button
|
||||
onClick={() => onChange('')}
|
||||
className={cn(
|
||||
'hover:border-primary flex cursor-pointer items-center space-x-2 rounded-md border p-4 transition-all hover:shadow-sm',
|
||||
'flex cursor-pointer items-center space-x-2 rounded-md border p-4 transition-all hover:border-primary hover:shadow-sm',
|
||||
{
|
||||
'ring-primary/10 border-primary ring-2 ring-offset-1': '' === value,
|
||||
'border-primary ring-2 ring-primary/10 ring-offset-1': '' === value,
|
||||
},
|
||||
)}
|
||||
disabled={!canCreateFreeOrganisation}
|
||||
@@ -390,10 +360,9 @@ const BillingPlanForm = ({
|
||||
key={plan[billingPeriod]?.id}
|
||||
onClick={() => onChange(plan[billingPeriod]?.id ?? '')}
|
||||
className={cn(
|
||||
'hover:border-primary flex cursor-pointer items-center space-x-2 rounded-md border p-4 transition-all hover:shadow-sm',
|
||||
'flex cursor-pointer items-center space-x-2 rounded-md border p-4 transition-all hover:border-primary hover:shadow-sm',
|
||||
{
|
||||
'ring-primary/10 border-primary ring-2 ring-offset-1':
|
||||
plan[billingPeriod]?.id === value,
|
||||
'border-primary ring-2 ring-primary/10 ring-offset-1': plan[billingPeriod]?.id === value,
|
||||
},
|
||||
)}
|
||||
>
|
||||
@@ -401,14 +370,10 @@ const BillingPlanForm = ({
|
||||
<p className="font-medium">{plan.name}</p>
|
||||
<p className="text-muted-foreground">{plan.description}</p>
|
||||
</div>
|
||||
<div className="whitespace-nowrap text-right text-sm font-medium">
|
||||
<div className="whitespace-nowrap text-right font-medium text-sm">
|
||||
<p>{plan[billingPeriod]?.friendlyPrice}</p>
|
||||
<span className="text-muted-foreground text-xs">
|
||||
{billingPeriod === 'monthlyPrice' ? (
|
||||
<Trans>per month</Trans>
|
||||
) : (
|
||||
<Trans>per year</Trans>
|
||||
)}
|
||||
{billingPeriod === 'monthlyPrice' ? <Trans>per month</Trans> : <Trans>per year</Trans>}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
@@ -417,13 +382,13 @@ const BillingPlanForm = ({
|
||||
<Link
|
||||
to="https://documen.so/enterprise-cta"
|
||||
target="_blank"
|
||||
className="bg-muted/30 flex items-center space-x-2 rounded-md border p-4"
|
||||
className="flex items-center space-x-2 rounded-md border bg-muted/30 p-4"
|
||||
>
|
||||
<div className="flex-1 font-normal">
|
||||
<p className="text-muted-foreground font-medium">
|
||||
<p className="font-medium text-muted-foreground">
|
||||
<Trans>Enterprise</Trans>
|
||||
</p>
|
||||
<p className="text-muted-foreground flex flex-row items-center gap-1">
|
||||
<p className="flex flex-row items-center gap-1 text-muted-foreground">
|
||||
<Trans>Contact sales here</Trans>
|
||||
<ExternalLinkIcon className="h-4 w-4" />
|
||||
</p>
|
||||
@@ -434,7 +399,7 @@ const BillingPlanForm = ({
|
||||
<div className="mt-6 text-center">
|
||||
<Link
|
||||
to="https://documenso.com/pricing"
|
||||
className="text-primary hover:text-primary/80 flex items-center justify-center gap-1 text-sm hover:underline"
|
||||
className="flex items-center justify-center gap-1 text-primary text-sm hover:text-primary/80 hover:underline"
|
||||
target="_blank"
|
||||
>
|
||||
<Trans>Compare all plans and features in detail</Trans>
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
|
||||
import { useSession } from '@documenso/lib/client-only/providers/session';
|
||||
import { AppError } from '@documenso/lib/errors/app-error';
|
||||
@@ -22,16 +12,17 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
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 { useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { z } from 'zod';
|
||||
|
||||
export type OrganisationDeleteDialogProps = {
|
||||
trigger?: React.ReactNode;
|
||||
@@ -117,19 +108,16 @@ export const OrganisationDeleteDialog = ({ trigger }: OrganisationDeleteDialogPr
|
||||
|
||||
<DialogDescription>
|
||||
<Trans>
|
||||
You are about to delete <span className="font-semibold">{organisation.name}</span>.
|
||||
All data related to this organisation such as teams, documents, and all other
|
||||
resources will be deleted. This action is irreversible.
|
||||
You are about to delete <span className="font-semibold">{organisation.name}</span>. All data related to
|
||||
this organisation such as teams, documents, and all other resources will be deleted. This action is
|
||||
irreversible.
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onFormSubmit)}>
|
||||
<fieldset
|
||||
className="flex h-full flex-col space-y-4"
|
||||
disabled={form.formState.isSubmitting}
|
||||
>
|
||||
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="organisationName"
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import type { z } from 'zod';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { ZCreateOrganisationEmailRequestSchema } from '@documenso/trpc/server/enterprise-router/create-organisation-email.types';
|
||||
@@ -30,6 +22,12 @@ import {
|
||||
} 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 { Trans, useLingui } from '@lingui/react/macro';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import type { z } from 'zod';
|
||||
|
||||
type EmailDomain = {
|
||||
id: string;
|
||||
@@ -69,8 +67,7 @@ export const OrganisationEmailCreateDialog = ({
|
||||
},
|
||||
});
|
||||
|
||||
const { mutateAsync: createOrganisationEmail, isPending } =
|
||||
trpc.enterprise.organisation.email.create.useMutation();
|
||||
const { mutateAsync: createOrganisationEmail, isPending } = trpc.enterprise.organisation.email.create.useMutation();
|
||||
|
||||
// Reset state when dialog closes
|
||||
useEffect(() => {
|
||||
@@ -176,14 +173,14 @@ export const OrganisationEmailCreateDialog = ({
|
||||
}}
|
||||
placeholder={t`support`}
|
||||
/>
|
||||
<div className="bg-muted text-muted-foreground absolute bottom-0 right-0 top-0 flex items-center rounded-r-md border px-3 py-2 text-sm">
|
||||
<div className="absolute top-0 right-0 bottom-0 flex items-center rounded-r-md border bg-muted px-3 py-2 text-muted-foreground text-sm">
|
||||
@{emailDomain.domain}
|
||||
</div>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
{!form.formState.errors.email && (
|
||||
<span className="text-foreground/50 text-xs font-normal">
|
||||
<span className="font-normal text-foreground/50 text-xs">
|
||||
{field.value ? (
|
||||
field.value
|
||||
) : (
|
||||
@@ -225,11 +222,7 @@ export const OrganisationEmailCreateDialog = ({
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
data-testid="dialog-create-organisation-email-button"
|
||||
loading={isPending}
|
||||
>
|
||||
<Button type="submit" data-testid="dialog-create-organisation-email-button" loading={isPending}>
|
||||
<Trans>Create Email</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
|
||||
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
|
||||
@@ -17,6 +12,8 @@ import {
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
|
||||
export type OrganisationEmailDeleteDialogProps = {
|
||||
emailId: string;
|
||||
@@ -24,11 +21,7 @@ export type OrganisationEmailDeleteDialogProps = {
|
||||
trigger?: React.ReactNode;
|
||||
};
|
||||
|
||||
export const OrganisationEmailDeleteDialog = ({
|
||||
trigger,
|
||||
emailId,
|
||||
email,
|
||||
}: OrganisationEmailDeleteDialogProps) => {
|
||||
export const OrganisationEmailDeleteDialog = ({ trigger, emailId, email }: OrganisationEmailDeleteDialogProps) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const { t } = useLingui();
|
||||
@@ -36,26 +29,25 @@ export const OrganisationEmailDeleteDialog = ({
|
||||
|
||||
const organisation = useCurrentOrganisation();
|
||||
|
||||
const { mutateAsync: deleteEmail, isPending: isDeleting } =
|
||||
trpc.enterprise.organisation.email.delete.useMutation({
|
||||
onSuccess: () => {
|
||||
toast({
|
||||
title: t`Success`,
|
||||
description: t`You have successfully removed this email from the organisation.`,
|
||||
duration: 5000,
|
||||
});
|
||||
const { mutateAsync: deleteEmail, isPending: isDeleting } = trpc.enterprise.organisation.email.delete.useMutation({
|
||||
onSuccess: () => {
|
||||
toast({
|
||||
title: t`Success`,
|
||||
description: t`You have successfully removed this email from the organisation.`,
|
||||
duration: 5000,
|
||||
});
|
||||
|
||||
setOpen(false);
|
||||
},
|
||||
onError: () => {
|
||||
toast({
|
||||
title: t`An unknown error occurred`,
|
||||
description: t`We encountered an unknown error while attempting to remove this email. Please try again later.`,
|
||||
variant: 'destructive',
|
||||
duration: 10000,
|
||||
});
|
||||
},
|
||||
});
|
||||
setOpen(false);
|
||||
},
|
||||
onError: () => {
|
||||
toast({
|
||||
title: t`An unknown error occurred`,
|
||||
description: t`We encountered an unknown error while attempting to remove this email. Please try again later.`,
|
||||
variant: 'destructive',
|
||||
duration: 10000,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={(value) => !isDeleting && setOpen(value)}>
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import type { z } from 'zod';
|
||||
|
||||
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
@@ -32,6 +23,12 @@ import {
|
||||
} 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 { Trans, useLingui } from '@lingui/react/macro';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import type { z } from 'zod';
|
||||
|
||||
import { OrganisationEmailDomainRecordContent } from './organisation-email-domain-records-dialog';
|
||||
|
||||
@@ -43,9 +40,7 @@ const ZCreateOrganisationEmailDomainFormSchema = ZCreateOrganisationEmailDomainR
|
||||
domain: true,
|
||||
});
|
||||
|
||||
type TCreateOrganisationEmailDomainFormSchema = z.infer<
|
||||
typeof ZCreateOrganisationEmailDomainFormSchema
|
||||
>;
|
||||
type TCreateOrganisationEmailDomainFormSchema = z.infer<typeof ZCreateOrganisationEmailDomainFormSchema>;
|
||||
|
||||
type DomainRecord = {
|
||||
name: string;
|
||||
@@ -53,10 +48,7 @@ type DomainRecord = {
|
||||
type: string;
|
||||
};
|
||||
|
||||
export const OrganisationEmailDomainCreateDialog = ({
|
||||
trigger,
|
||||
...props
|
||||
}: OrganisationEmailCreateDialogProps) => {
|
||||
export const OrganisationEmailDomainCreateDialog = ({ trigger, ...props }: OrganisationEmailCreateDialogProps) => {
|
||||
const { t } = useLingui();
|
||||
const { toast } = useToast();
|
||||
const organisation = useCurrentOrganisation();
|
||||
@@ -72,8 +64,7 @@ export const OrganisationEmailDomainCreateDialog = ({
|
||||
},
|
||||
});
|
||||
|
||||
const { mutateAsync: createOrganisationEmail } =
|
||||
trpc.enterprise.organisation.emailDomain.create.useMutation();
|
||||
const { mutateAsync: createOrganisationEmail } = trpc.enterprise.organisation.emailDomain.create.useMutation();
|
||||
|
||||
// Reset state when dialog closes
|
||||
useEffect(() => {
|
||||
@@ -119,11 +110,7 @@ export const OrganisationEmailDomainCreateDialog = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
{...props}
|
||||
open={open}
|
||||
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
|
||||
>
|
||||
<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">
|
||||
@@ -140,18 +127,15 @@ export const OrganisationEmailDomainCreateDialog = ({
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
<Trans>
|
||||
Add a custom domain to send emails on behalf of your organisation. We'll generate
|
||||
DKIM records that you need to add to your DNS provider.
|
||||
Add a custom domain to send emails on behalf of your organisation. We'll generate DKIM records that you
|
||||
need to add to your DNS provider.
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onFormSubmit)}>
|
||||
<fieldset
|
||||
className="flex h-full flex-col space-y-4"
|
||||
disabled={form.formState.isSubmitting}
|
||||
>
|
||||
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="domain"
|
||||
@@ -165,10 +149,7 @@ export const OrganisationEmailDomainCreateDialog = ({
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
<FormDescription>
|
||||
<Trans>
|
||||
Enter the domain you want to use for sending emails (without http:// or
|
||||
www)
|
||||
</Trans>
|
||||
<Trans>Enter the domain you want to use for sending emails (without http:// or www)</Trans>
|
||||
</FormDescription>
|
||||
</FormItem>
|
||||
)}
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
@@ -18,16 +10,14 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
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 { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
export type OrganisationEmailDomainDeleteDialogProps = {
|
||||
emailDomainId: string;
|
||||
@@ -107,10 +97,9 @@ export const OrganisationEmailDomainDeleteDialog = ({
|
||||
|
||||
<DialogDescription className="mt-4">
|
||||
<Trans>
|
||||
You are about to remove the email domain{' '}
|
||||
<span className="font-semibold">{emailDomain}</span> from{' '}
|
||||
<span className="font-semibold">{organisation.name}</span>. All emails associated with
|
||||
this domain will be deleted.
|
||||
You are about to remove the email domain <span className="font-semibold">{emailDomain}</span> from{' '}
|
||||
<span className="font-semibold">{organisation.name}</span>. All emails associated with this domain will be
|
||||
deleted.
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
@@ -126,9 +115,7 @@ export const OrganisationEmailDomainDeleteDialog = ({
|
||||
<FormLabel>
|
||||
<Trans>
|
||||
Confirm by typing{' '}
|
||||
<span className="font-sm text-destructive font-semibold">
|
||||
{deleteMessage}
|
||||
</span>
|
||||
<span className="font-semibold font-sm text-destructive">{deleteMessage}</span>
|
||||
</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
|
||||
import { CopyTextButton } from '@documenso/ui/components/common/copy-text-button';
|
||||
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
@@ -18,6 +14,8 @@ import {
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import { Label } from '@documenso/ui/primitives/label';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
|
||||
export type OrganisationEmailDomainRecordsDialogProps = {
|
||||
trigger: React.ReactNode;
|
||||
@@ -72,7 +70,7 @@ export const OrganisationEmailDomainRecordContent = ({ records }: { records: Dom
|
||||
|
||||
<div className="relative">
|
||||
<Input className="pr-12" disabled value={record.type} />
|
||||
<div className="absolute bottom-0 right-2 top-0 flex items-center justify-center">
|
||||
<div className="absolute top-0 right-2 bottom-0 flex items-center justify-center">
|
||||
<CopyTextButton
|
||||
value={record.type}
|
||||
onCopySuccess={() => toast({ title: t`Copied to clipboard` })}
|
||||
@@ -88,7 +86,7 @@ export const OrganisationEmailDomainRecordContent = ({ records }: { records: Dom
|
||||
|
||||
<div className="relative">
|
||||
<Input className="pr-12" disabled value={record.name} />
|
||||
<div className="absolute bottom-0 right-2 top-0 flex items-center justify-center">
|
||||
<div className="absolute top-0 right-2 bottom-0 flex items-center justify-center">
|
||||
<CopyTextButton
|
||||
value={record.name}
|
||||
onCopySuccess={() => toast({ title: t`Copied to clipboard` })}
|
||||
@@ -104,7 +102,7 @@ export const OrganisationEmailDomainRecordContent = ({ records }: { records: Dom
|
||||
|
||||
<div className="relative">
|
||||
<Input className="pr-12" disabled value={record.value} />
|
||||
<div className="absolute bottom-0 right-2 top-0 flex items-center justify-center">
|
||||
<div className="absolute top-0 right-2 bottom-0 flex items-center justify-center">
|
||||
<CopyTextButton
|
||||
value={record.value}
|
||||
onCopySuccess={() => toast({ title: t`Copied to clipboard` })}
|
||||
@@ -119,9 +117,8 @@ export const OrganisationEmailDomainRecordContent = ({ records }: { records: Dom
|
||||
<Alert variant="neutral">
|
||||
<AlertDescription>
|
||||
<Trans>
|
||||
Once you update your DNS records, it may take up to 48 hours for it to be propogated.
|
||||
Once the DNS propagation is complete you will need to come back and press the "Sync"
|
||||
domains button.
|
||||
Once you update your DNS records, it may take up to 48 hours for it to be propogated. Once the DNS
|
||||
propagation is complete you will need to come back and press the "Sync" domains button.
|
||||
</Trans>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import type { z } from 'zod';
|
||||
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import type { TGetOrganisationEmailDomainResponse } from '@documenso/trpc/server/enterprise-router/get-organisation-email-domain.types';
|
||||
import { ZUpdateOrganisationEmailRequestSchema } from '@documenso/trpc/server/enterprise-router/update-organisation-email.types';
|
||||
@@ -30,6 +22,12 @@ import {
|
||||
} 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 { Trans, useLingui } from '@lingui/react/macro';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import type { z } from 'zod';
|
||||
|
||||
export type OrganisationEmailUpdateDialogProps = {
|
||||
trigger: React.ReactNode;
|
||||
@@ -61,8 +59,7 @@ export const OrganisationEmailUpdateDialog = ({
|
||||
},
|
||||
});
|
||||
|
||||
const { mutateAsync: updateOrganisationEmail, isPending } =
|
||||
trpc.enterprise.organisation.email.update.useMutation();
|
||||
const { mutateAsync: updateOrganisationEmail, isPending } = trpc.enterprise.organisation.email.update.useMutation();
|
||||
|
||||
const onFormSubmit = async ({ emailName }: ZUpdateOrganisationEmailSchema) => {
|
||||
try {
|
||||
@@ -98,11 +95,7 @@ export const OrganisationEmailUpdateDialog = ({
|
||||
}, [open, form]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
{...props}
|
||||
open={open}
|
||||
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
|
||||
>
|
||||
<Dialog {...props} open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
|
||||
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
|
||||
{trigger}
|
||||
</DialogTrigger>
|
||||
@@ -115,8 +108,7 @@ export const OrganisationEmailUpdateDialog = ({
|
||||
|
||||
<DialogDescription>
|
||||
<Trans>
|
||||
You are currently updating{' '}
|
||||
<span className="font-bold">{organisationEmail.email}</span>
|
||||
You are currently updating <span className="font-bold">{organisationEmail.email}</span>
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { OrganisationMemberRole } from '@prisma/client';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import type { z } from 'zod';
|
||||
|
||||
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
|
||||
import { ORGANISATION_MEMBER_ROLE_HIERARCHY } from '@documenso/lib/constants/organisations';
|
||||
import { EXTENDED_ORGANISATION_MEMBER_ROLE_MAP } from '@documenso/lib/constants/organisations-translations';
|
||||
@@ -34,14 +24,15 @@ import {
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@documenso/ui/primitives/select';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { OrganisationMemberRole } from '@prisma/client';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import type { z } from 'zod';
|
||||
|
||||
import {
|
||||
type OrganisationMemberOption,
|
||||
@@ -60,10 +51,7 @@ const ZCreateOrganisationGroupFormSchema = ZCreateOrganisationGroupRequestSchema
|
||||
|
||||
type TCreateOrganisationGroupFormSchema = z.infer<typeof ZCreateOrganisationGroupFormSchema>;
|
||||
|
||||
export const OrganisationGroupCreateDialog = ({
|
||||
trigger,
|
||||
...props
|
||||
}: OrganisationGroupCreateDialogProps) => {
|
||||
export const OrganisationGroupCreateDialog = ({ trigger, ...props }: OrganisationGroupCreateDialogProps) => {
|
||||
const { t } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
@@ -82,11 +70,7 @@ export const OrganisationGroupCreateDialog = ({
|
||||
|
||||
const { mutateAsync: createOrganisationGroup } = trpc.organisation.group.create.useMutation();
|
||||
|
||||
const onFormSubmit = async ({
|
||||
name,
|
||||
organisationRole,
|
||||
memberIds,
|
||||
}: TCreateOrganisationGroupFormSchema) => {
|
||||
const onFormSubmit = async ({ name, organisationRole, memberIds }: TCreateOrganisationGroupFormSchema) => {
|
||||
try {
|
||||
await createOrganisationGroup({
|
||||
organisationId: organisation.id,
|
||||
@@ -121,11 +105,7 @@ export const OrganisationGroupCreateDialog = ({
|
||||
}, [open, form]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
{...props}
|
||||
open={open}
|
||||
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
|
||||
>
|
||||
<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">
|
||||
@@ -147,10 +127,7 @@ export const OrganisationGroupCreateDialog = ({
|
||||
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onFormSubmit)}>
|
||||
<fieldset
|
||||
className="flex h-full flex-col space-y-4"
|
||||
disabled={form.formState.isSubmitting}
|
||||
>
|
||||
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
@@ -182,9 +159,7 @@ export const OrganisationGroupCreateDialog = ({
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectContent position="popper">
|
||||
{ORGANISATION_MEMBER_ROLE_HIERARCHY[
|
||||
organisation.currentOrganisationRole
|
||||
].map((role) => (
|
||||
{ORGANISATION_MEMBER_ROLE_HIERARCHY[organisation.currentOrganisationRole].map((role) => (
|
||||
<SelectItem key={role} value={role}>
|
||||
{t(EXTENDED_ORGANISATION_MEMBER_ROLE_MAP[role]) ?? role}
|
||||
</SelectItem>
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
|
||||
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
|
||||
@@ -18,6 +12,10 @@ import {
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
|
||||
export type OrganisationGroupDeleteDialogProps = {
|
||||
organisationGroupId: string;
|
||||
@@ -37,28 +35,27 @@ export const OrganisationGroupDeleteDialog = ({
|
||||
|
||||
const organisation = useCurrentOrganisation();
|
||||
|
||||
const { mutateAsync: deleteGroup, isPending: isDeleting } =
|
||||
trpc.organisation.group.delete.useMutation({
|
||||
onSuccess: () => {
|
||||
toast({
|
||||
title: _(msg`Success`),
|
||||
description: _(msg`You have successfully removed this group from the organisation.`),
|
||||
duration: 5000,
|
||||
});
|
||||
const { mutateAsync: deleteGroup, isPending: isDeleting } = trpc.organisation.group.delete.useMutation({
|
||||
onSuccess: () => {
|
||||
toast({
|
||||
title: _(msg`Success`),
|
||||
description: _(msg`You have successfully removed this group from the organisation.`),
|
||||
duration: 5000,
|
||||
});
|
||||
|
||||
setOpen(false);
|
||||
},
|
||||
onError: () => {
|
||||
toast({
|
||||
title: _(msg`An unknown error occurred`),
|
||||
description: _(
|
||||
msg`We encountered an unknown error while attempting to remove this group. Please try again later.`,
|
||||
),
|
||||
variant: 'destructive',
|
||||
duration: 10000,
|
||||
});
|
||||
},
|
||||
});
|
||||
setOpen(false);
|
||||
},
|
||||
onError: () => {
|
||||
toast({
|
||||
title: _(msg`An unknown error occurred`),
|
||||
description: _(
|
||||
msg`We encountered an unknown error while attempting to remove this group. Please try again later.`,
|
||||
),
|
||||
variant: 'destructive',
|
||||
duration: 10000,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={(value) => !isDeleting && setOpen(value)}>
|
||||
@@ -85,9 +82,7 @@ export const OrganisationGroupDeleteDialog = ({
|
||||
</DialogHeader>
|
||||
|
||||
<Alert variant="neutral">
|
||||
<AlertDescription className="text-center font-semibold">
|
||||
{organisationGroupName}
|
||||
</AlertDescription>
|
||||
<AlertDescription className="text-center font-semibold">{organisationGroupName}</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
<fieldset disabled={isDeleting}>
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type { OrganisationMemberRole } from '@prisma/client';
|
||||
|
||||
import { ORGANISATION_MEMBER_ROLE_MAP } from '@documenso/lib/constants/organisations-translations';
|
||||
import { formatAvatarUrl } from '@documenso/lib/utils/avatars';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
@@ -20,6 +14,9 @@ import {
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import type { OrganisationMemberRole } from '@prisma/client';
|
||||
import { useState } from 'react';
|
||||
|
||||
export type OrganisationLeaveDialogProps = {
|
||||
organisationId: string;
|
||||
@@ -41,26 +38,25 @@ export const OrganisationLeaveDialog = ({
|
||||
const { t } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const { mutateAsync: leaveOrganisation, isPending: isLeavingOrganisation } =
|
||||
trpc.organisation.leave.useMutation({
|
||||
onSuccess: () => {
|
||||
toast({
|
||||
title: t`Success`,
|
||||
description: t`You have successfully left this organisation.`,
|
||||
duration: 5000,
|
||||
});
|
||||
const { mutateAsync: leaveOrganisation, isPending: isLeavingOrganisation } = trpc.organisation.leave.useMutation({
|
||||
onSuccess: () => {
|
||||
toast({
|
||||
title: t`Success`,
|
||||
description: t`You have successfully left this organisation.`,
|
||||
duration: 5000,
|
||||
});
|
||||
|
||||
setOpen(false);
|
||||
},
|
||||
onError: () => {
|
||||
toast({
|
||||
title: t`An unknown error occurred`,
|
||||
description: t`We encountered an unknown error while attempting to leave this organisation. Please try again later.`,
|
||||
variant: 'destructive',
|
||||
duration: 10000,
|
||||
});
|
||||
},
|
||||
});
|
||||
setOpen(false);
|
||||
},
|
||||
onError: () => {
|
||||
toast({
|
||||
title: t`An unknown error occurred`,
|
||||
description: t`We encountered an unknown error while attempting to leave this organisation. Please try again later.`,
|
||||
variant: 'destructive',
|
||||
duration: 10000,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={(value) => !isLeavingOrganisation && setOpen(value)}>
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
|
||||
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Alert } from '@documenso/ui/primitives/alert';
|
||||
@@ -19,6 +13,10 @@ import {
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
|
||||
export type OrganisationMemberDeleteDialogProps = {
|
||||
organisationMemberId: string;
|
||||
@@ -81,8 +79,8 @@ export const OrganisationMemberDeleteDialog = ({
|
||||
|
||||
<DialogDescription className="mt-4">
|
||||
<Trans>
|
||||
You are about to remove the following user from{' '}
|
||||
<span className="font-semibold">{organisation.name}</span>.
|
||||
You are about to remove the following user from <span className="font-semibold">{organisation.name}</span>
|
||||
.
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -1,16 +1,3 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { OrganisationMemberRole } from '@prisma/client';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { Download, Mail, MailIcon, PlusCircle, Trash, Upload, UsersIcon } from 'lucide-react';
|
||||
import Papa, { type ParseResult } from 'papaparse';
|
||||
import { useFieldArray, useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { downloadFile } from '@documenso/lib/client-only/download-file';
|
||||
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
|
||||
import { IS_BILLING_ENABLED, SUPPORT_EMAIL } from '@documenso/lib/constants/app';
|
||||
@@ -33,25 +20,23 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@documenso/ui/primitives/select';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
|
||||
import { SpinnerBox } from '@documenso/ui/primitives/spinner';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@documenso/ui/primitives/tabs';
|
||||
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 { OrganisationMemberRole } from '@prisma/client';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { Download, Mail, MailIcon, PlusCircle, Trash, Upload, UsersIcon } from 'lucide-react';
|
||||
import Papa, { type ParseResult } from 'papaparse';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useFieldArray, useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
export type OrganisationMemberInviteDialogProps = {
|
||||
trigger?: React.ReactNode;
|
||||
@@ -100,10 +85,7 @@ const ZImportOrganisationMemberSchema = z.array(
|
||||
}),
|
||||
);
|
||||
|
||||
export const OrganisationMemberInviteDialog = ({
|
||||
trigger,
|
||||
...props
|
||||
}: OrganisationMemberInviteDialogProps) => {
|
||||
export const OrganisationMemberInviteDialog = ({ trigger, ...props }: OrganisationMemberInviteDialogProps) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const [invitationType, setInvitationType] = useState<TabTypes>('INDIVIDUAL');
|
||||
@@ -134,8 +116,7 @@ export const OrganisationMemberInviteDialog = ({
|
||||
name: 'invitations',
|
||||
});
|
||||
|
||||
const { mutateAsync: createOrganisationMemberInvites } =
|
||||
trpc.organisation.member.invite.createMany.useMutation();
|
||||
const { mutateAsync: createOrganisationMemberInvites } = trpc.organisation.member.invite.createMany.useMutation();
|
||||
|
||||
const { data: fullOrganisation } = trpc.organisation.get.useQuery({
|
||||
organisationReference: organisation.id,
|
||||
@@ -242,9 +223,7 @@ export const OrganisationMemberInviteDialog = ({
|
||||
|
||||
toast({
|
||||
title: _(msg`Something went wrong`),
|
||||
description: _(
|
||||
msg`Please check the CSV file and make sure it is according to our format`,
|
||||
),
|
||||
description: _(msg`Please check the CSV file and make sure it is according to our format`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@@ -259,8 +238,7 @@ export const OrganisationMemberInviteDialog = ({
|
||||
{ email: 'member@documenso.com', role: 'Member' },
|
||||
];
|
||||
|
||||
const csvContent =
|
||||
'Email address,Role\n' + data.map((row) => `${row.email},${row.role}`).join('\n');
|
||||
const csvContent = 'Email address,Role\n' + data.map((row) => `${row.email},${row.role}`).join('\n');
|
||||
|
||||
const blob = new Blob([csvContent], {
|
||||
type: 'text/csv',
|
||||
@@ -273,11 +251,7 @@ export const OrganisationMemberInviteDialog = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
{...props}
|
||||
open={open}
|
||||
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
|
||||
>
|
||||
<Dialog {...props} open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
|
||||
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
|
||||
{trigger ?? (
|
||||
<Button variant="secondary">
|
||||
@@ -301,15 +275,11 @@ export const OrganisationMemberInviteDialog = ({
|
||||
|
||||
{dialogState === 'alert' && (
|
||||
<>
|
||||
<Alert
|
||||
className="flex flex-col justify-between p-6 sm:flex-row sm:items-center"
|
||||
variant="neutral"
|
||||
>
|
||||
<Alert className="flex flex-col justify-between p-6 sm:flex-row sm:items-center" variant="neutral">
|
||||
<AlertDescription>
|
||||
<Trans>
|
||||
Your plan does not support inviting members. Please upgrade or your plan or
|
||||
contact sales at <a href={`mailto:${SUPPORT_EMAIL}`}>{SUPPORT_EMAIL}</a> if you
|
||||
would like to discuss your options.
|
||||
Your plan does not support inviting members. Please upgrade or your plan or contact sales at{' '}
|
||||
<a href={`mailto:${SUPPORT_EMAIL}`}>{SUPPORT_EMAIL}</a> if you would like to discuss your options.
|
||||
</Trans>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
@@ -343,16 +313,10 @@ export const OrganisationMemberInviteDialog = ({
|
||||
<TabsContent value="INDIVIDUAL">
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onFormSubmit)}>
|
||||
<fieldset
|
||||
className="flex h-full flex-col space-y-4"
|
||||
disabled={form.formState.isSubmitting}
|
||||
>
|
||||
<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">
|
||||
{organisationMemberInvites.map((organisationMemberInvite, index) => (
|
||||
<div
|
||||
className="flex w-full flex-row space-x-4"
|
||||
key={organisationMemberInvite.id}
|
||||
>
|
||||
<div className="flex w-full flex-row space-x-4" key={organisationMemberInvite.id}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`invitations.${index}.email`}
|
||||
@@ -388,13 +352,13 @@ export const OrganisationMemberInviteDialog = ({
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectContent position="popper">
|
||||
{ORGANISATION_MEMBER_ROLE_HIERARCHY[
|
||||
organisation.currentOrganisationRole
|
||||
].map((role) => (
|
||||
<SelectItem key={role} value={role}>
|
||||
{_(ORGANISATION_MEMBER_ROLE_MAP[role]) ?? role}
|
||||
</SelectItem>
|
||||
))}
|
||||
{ORGANISATION_MEMBER_ROLE_HIERARCHY[organisation.currentOrganisationRole].map(
|
||||
(role) => (
|
||||
<SelectItem key={role} value={role}>
|
||||
{_(ORGANISATION_MEMBER_ROLE_MAP[role]) ?? role}
|
||||
</SelectItem>
|
||||
),
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
@@ -457,13 +421,7 @@ export const OrganisationMemberInviteDialog = ({
|
||||
<Trans>Click here to upload</Trans>
|
||||
</p>
|
||||
|
||||
<input
|
||||
onChange={onFileInputChange}
|
||||
type="file"
|
||||
ref={fileInputRef}
|
||||
accept=".csv"
|
||||
hidden
|
||||
/>
|
||||
<input onChange={onFileInputChange} type="file" ref={fileInputRef} accept=".csv" hidden />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
|
||||
@@ -1,14 +1,3 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { OrganisationMemberRole } from '@prisma/client';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ORGANISATION_MEMBER_ROLE_HIERARCHY } from '@documenso/lib/constants/organisations';
|
||||
import { ORGANISATION_MEMBER_ROLE_MAP } from '@documenso/lib/constants/organisations-translations';
|
||||
import { isOrganisationRoleWithinUserHierarchy } from '@documenso/lib/utils/organisations';
|
||||
@@ -23,22 +12,18 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@documenso/ui/primitives/select';
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
|
||||
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 { OrganisationMemberRole } from '@prisma/client';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
export type OrganisationMemberUpdateDialogProps = {
|
||||
currentUserOrganisationRole: OrganisationMemberRole;
|
||||
@@ -113,9 +98,7 @@ export const OrganisationMemberUpdateDialog = ({
|
||||
|
||||
form.reset();
|
||||
|
||||
if (
|
||||
!isOrganisationRoleWithinUserHierarchy(currentUserOrganisationRole, organisationMemberRole)
|
||||
) {
|
||||
if (!isOrganisationRoleWithinUserHierarchy(currentUserOrganisationRole, organisationMemberRole)) {
|
||||
setOpen(false);
|
||||
|
||||
toast({
|
||||
@@ -127,11 +110,7 @@ export const OrganisationMemberUpdateDialog = ({
|
||||
}, [open, currentUserOrganisationRole, organisationMemberRole, form, toast]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
{...props}
|
||||
open={open}
|
||||
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
|
||||
>
|
||||
<Dialog {...props} open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
|
||||
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
|
||||
{trigger ?? (
|
||||
<Button variant="secondary">
|
||||
@@ -148,8 +127,7 @@ export const OrganisationMemberUpdateDialog = ({
|
||||
|
||||
<DialogDescription className="mt-4">
|
||||
<Trans>
|
||||
You are currently updating <span className="font-bold">{organisationMemberName}</span>
|
||||
.
|
||||
You are currently updating <span className="font-bold">{organisationMemberName}</span>.
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
@@ -172,13 +150,11 @@ export const OrganisationMemberUpdateDialog = ({
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectContent className="w-full" position="popper">
|
||||
{ORGANISATION_MEMBER_ROLE_HIERARCHY[currentUserOrganisationRole].map(
|
||||
(role) => (
|
||||
<SelectItem key={role} value={role}>
|
||||
{_(ORGANISATION_MEMBER_ROLE_MAP[role]) ?? role}
|
||||
</SelectItem>
|
||||
),
|
||||
)}
|
||||
{ORGANISATION_MEMBER_ROLE_HIERARCHY[currentUserOrganisationRole].map((role) => (
|
||||
<SelectItem key={role} value={role}>
|
||||
{_(ORGANISATION_MEMBER_ROLE_MAP[role]) ?? role}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
@@ -1,15 +1,3 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Plural, Trans, useLingui } from '@lingui/react/macro';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { startRegistration } from '@simplewebauthn/browser';
|
||||
import { KeyRoundIcon } from 'lucide-react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { match } from 'ts-pattern';
|
||||
import { UAParser } from 'ua-parser-js';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { MAXIMUM_PASSKEYS } from '@documenso/lib/constants/auth';
|
||||
import { AppError } from '@documenso/lib/errors/app-error';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
@@ -24,16 +12,19 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
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 { Plural, Trans, useLingui } from '@lingui/react/macro';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { startRegistration } from '@simplewebauthn/browser';
|
||||
import { KeyRoundIcon } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { match } from 'ts-pattern';
|
||||
import { UAParser } from 'ua-parser-js';
|
||||
import { z } from 'zod';
|
||||
|
||||
export type PasskeyCreateDialogProps = {
|
||||
trigger?: React.ReactNode;
|
||||
@@ -133,15 +124,11 @@ export const PasskeyCreateDialog = ({ trigger, onSuccess, ...props }: PasskeyCre
|
||||
}, [open, form]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
{...props}
|
||||
open={open}
|
||||
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
|
||||
>
|
||||
<Dialog {...props} open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
|
||||
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
|
||||
{trigger ?? (
|
||||
<Button variant="secondary" loading={isPending}>
|
||||
<KeyRoundIcon className="-ml-1 mr-1 h-5 w-5" />
|
||||
<KeyRoundIcon className="mr-1 -ml-1 h-5 w-5" />
|
||||
<Trans>Add passkey</Trans>
|
||||
</Button>
|
||||
)}
|
||||
@@ -154,19 +141,13 @@ export const PasskeyCreateDialog = ({ trigger, onSuccess, ...props }: PasskeyCre
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription className="mt-4">
|
||||
<Trans>
|
||||
Passkeys allow you to sign in and authenticate using biometrics, password managers,
|
||||
etc.
|
||||
</Trans>
|
||||
<Trans>Passkeys allow you to sign in and authenticate using biometrics, password managers, etc.</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onFormSubmit)}>
|
||||
<fieldset
|
||||
className="flex h-full flex-col space-y-4"
|
||||
disabled={form.formState.isSubmitting}
|
||||
>
|
||||
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="passkeyName"
|
||||
@@ -186,15 +167,15 @@ export const PasskeyCreateDialog = ({ trigger, onSuccess, ...props }: PasskeyCre
|
||||
<Alert variant="neutral">
|
||||
<AlertDescription>
|
||||
<Trans>
|
||||
When you click continue, you will be prompted to add the first available
|
||||
authenticator on your system.
|
||||
When you click continue, you will be prompted to add the first available authenticator on your
|
||||
system.
|
||||
</Trans>
|
||||
</AlertDescription>
|
||||
|
||||
<AlertDescription className="mt-2">
|
||||
<Trans>
|
||||
If you do not want to use the authenticator prompted, you can close it, which
|
||||
will then display the next available authenticator.
|
||||
If you do not want to use the authenticator prompted, you can close it, which will then display the
|
||||
next available authenticator.
|
||||
</Trans>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
@@ -219,9 +200,7 @@ export const PasskeyCreateDialog = ({ trigger, onSuccess, ...props }: PasskeyCre
|
||||
.with('InvalidStateError', () => (
|
||||
<>
|
||||
<AlertTitle className="text-sm">
|
||||
<Trans>
|
||||
Passkey creation cancelled due to one of the following reasons:
|
||||
</Trans>
|
||||
<Trans>Passkey creation cancelled due to one of the following reasons:</Trans>
|
||||
</AlertTitle>
|
||||
<AlertDescription>
|
||||
<ul className="mt-1 list-inside list-disc">
|
||||
|
||||
@@ -1,17 +1,4 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Plural, Trans } from '@lingui/react/macro';
|
||||
import { type TemplateDirectLink, TemplateType } from '@prisma/client';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { CheckCircle2Icon, CircleIcon } from 'lucide-react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { P, match } from 'ts-pattern';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { type Template } from '@documenso/prisma/types/template-legacy-schema';
|
||||
import type { Template } from '@documenso/prisma/types/template-legacy-schema';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import {
|
||||
MAX_TEMPLATE_PUBLIC_DESCRIPTION_LENGTH,
|
||||
@@ -29,25 +16,22 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@documenso/ui/primitives/table';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@documenso/ui/primitives/table';
|
||||
import { Textarea } from '@documenso/ui/primitives/textarea';
|
||||
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 { Plural, Trans } from '@lingui/react/macro';
|
||||
import { type TemplateDirectLink, TemplateType } from '@prisma/client';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { CheckCircle2Icon, CircleIcon } from 'lucide-react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { match, P } from 'ts-pattern';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { useCurrentTeam } from '~/providers/team';
|
||||
|
||||
@@ -145,10 +129,7 @@ export const ManagePublicTemplateDialog = ({
|
||||
}
|
||||
};
|
||||
|
||||
const onFormSubmit = async ({
|
||||
publicTitle,
|
||||
publicDescription,
|
||||
}: TUpdatePublicTemplateFormSchema) => {
|
||||
const onFormSubmit = async ({ publicTitle, publicDescription }: TUpdatePublicTemplateFormSchema) => {
|
||||
if (!selectedTemplateId) {
|
||||
return;
|
||||
}
|
||||
@@ -251,9 +232,7 @@ export const ManagePublicTemplateDialog = ({
|
||||
|
||||
<DialogDescription>
|
||||
{team ? (
|
||||
<Trans>
|
||||
Select a template you'd like to display on your team's public profile
|
||||
</Trans>
|
||||
<Trans>Select a template you'd like to display on your team's public profile</Trans>
|
||||
) : (
|
||||
<Trans>Select a template you'd like to display on your public profile</Trans>
|
||||
)}
|
||||
@@ -290,13 +269,9 @@ export const ManagePublicTemplateDialog = ({
|
||||
key={row.id}
|
||||
onClick={() => setSelectedTemplateId(row.id)}
|
||||
>
|
||||
<TableCell className="text-muted-foreground max-w-[30ch] text-sm">
|
||||
{row.title}
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[30ch] text-muted-foreground text-sm">{row.title}</TableCell>
|
||||
|
||||
<TableCell className="text-muted-foreground text-sm">
|
||||
{i18n.date(row.createdAt)}
|
||||
</TableCell>
|
||||
<TableCell className="text-muted-foreground text-sm">{i18n.date(row.createdAt)}</TableCell>
|
||||
|
||||
<TableCell>
|
||||
{selectedTemplateId === row.id ? (
|
||||
@@ -317,11 +292,7 @@ export const ManagePublicTemplateDialog = ({
|
||||
</Button>
|
||||
</DialogClose>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
disabled={selectedTemplateId === null}
|
||||
onClick={() => onManageStep()}
|
||||
>
|
||||
<Button type="button" disabled={selectedTemplateId === null} onClick={() => onManageStep()}>
|
||||
<Trans>Continue</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
@@ -340,10 +311,7 @@ export const ManagePublicTemplateDialog = ({
|
||||
</DialogHeader>
|
||||
|
||||
<Form {...form}>
|
||||
<form
|
||||
className="flex h-full flex-col space-y-4"
|
||||
onSubmit={form.handleSubmit(onFormSubmit)}
|
||||
>
|
||||
<form className="flex h-full flex-col space-y-4" onSubmit={form.handleSubmit(onFormSubmit)}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="publicTitle"
|
||||
@@ -351,10 +319,7 @@ export const ManagePublicTemplateDialog = ({
|
||||
<FormItem>
|
||||
<FormLabel required>Title</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={_(msg`The public name for your template`)}
|
||||
{...field}
|
||||
/>
|
||||
<Input placeholder={_(msg`The public name for your template`)} {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -365,17 +330,14 @@ export const ManagePublicTemplateDialog = ({
|
||||
control={form.control}
|
||||
name="publicDescription"
|
||||
render={({ field }) => {
|
||||
const remaningLength =
|
||||
MAX_TEMPLATE_PUBLIC_DESCRIPTION_LENGTH - (field.value || '').length;
|
||||
const remaningLength = MAX_TEMPLATE_PUBLIC_DESCRIPTION_LENGTH - (field.value || '').length;
|
||||
|
||||
return (
|
||||
<FormItem>
|
||||
<FormLabel required>Description</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder={_(
|
||||
msg`The public description that will be displayed with this template`,
|
||||
)}
|
||||
placeholder={_(msg`The public description that will be displayed with this template`)}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
|
||||
import { authClient } from '@documenso/auth/client';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import {
|
||||
@@ -15,6 +11,8 @@ import {
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
|
||||
type SessionLogoutAllDialogProps = {
|
||||
onSuccess?: () => Promise<unknown>;
|
||||
@@ -71,8 +69,8 @@ export const SessionLogoutAllDialog = ({ onSuccess, disabled }: SessionLogoutAll
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
<Trans>
|
||||
This will sign you out of all other devices. You will need to sign in again on those
|
||||
devices to continue using your account.
|
||||
This will sign you out of all other devices. You will need to sign in again on those devices to continue
|
||||
using your account.
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { Plural, Trans } from '@lingui/react/macro';
|
||||
import { createCallable } from 'react-call';
|
||||
import { useForm, useWatch } from 'react-hook-form';
|
||||
import { match } from 'ts-pattern';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { validateCheckboxLength } from '@documenso/lib/advanced-fields-validation/validate-checkbox';
|
||||
import { type TCheckboxFieldMeta } from '@documenso/lib/types/field-meta';
|
||||
import type { TCheckboxFieldMeta } from '@documenso/lib/types/field-meta';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { Checkbox } from '@documenso/ui/primitives/checkbox';
|
||||
@@ -20,6 +12,13 @@ import {
|
||||
DialogTitle,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import { Form, FormControl, FormField, FormItem } from '@documenso/ui/primitives/form/form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { Plural, Trans } from '@lingui/react/macro';
|
||||
import { createCallable } from 'react-call';
|
||||
import { useForm, useWatch } from 'react-hook-form';
|
||||
import { match } from 'ts-pattern';
|
||||
import { z } from 'zod';
|
||||
|
||||
export type SignFieldCheckboxDialogProps = {
|
||||
fieldMeta: TCheckboxFieldMeta;
|
||||
@@ -28,157 +27,133 @@ export type SignFieldCheckboxDialogProps = {
|
||||
preselectedIndices: number[];
|
||||
};
|
||||
|
||||
export const SignFieldCheckboxDialog = createCallable<
|
||||
SignFieldCheckboxDialogProps,
|
||||
number[] | null
|
||||
>(({ call, fieldMeta, validationRule, validationLength, preselectedIndices }) => {
|
||||
const ZSignFieldCheckboxFormSchema = z
|
||||
.object({
|
||||
values: z.array(
|
||||
z.object({
|
||||
checked: z.boolean(),
|
||||
value: z.string(),
|
||||
}),
|
||||
),
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
// Allow unselecting all options if the field is not required even if
|
||||
// validation is not met.
|
||||
if (!fieldMeta.required && data.values.every((value) => !value.checked)) {
|
||||
return;
|
||||
}
|
||||
export const SignFieldCheckboxDialog = createCallable<SignFieldCheckboxDialogProps, number[] | null>(
|
||||
({ call, fieldMeta, validationRule, validationLength, preselectedIndices }) => {
|
||||
const ZSignFieldCheckboxFormSchema = z
|
||||
.object({
|
||||
values: z.array(
|
||||
z.object({
|
||||
checked: z.boolean(),
|
||||
value: z.string(),
|
||||
}),
|
||||
),
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
// Allow unselecting all options if the field is not required even if
|
||||
// validation is not met.
|
||||
if (!fieldMeta.required && data.values.every((value) => !value.checked)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const numberOfSelectedValues = data.values.filter((value) => value.checked).length;
|
||||
const numberOfSelectedValues = data.values.filter((value) => value.checked).length;
|
||||
|
||||
const isValid = validateCheckboxLength(
|
||||
numberOfSelectedValues,
|
||||
validationRule,
|
||||
validationLength,
|
||||
);
|
||||
const isValid = validateCheckboxLength(numberOfSelectedValues, validationRule, validationLength);
|
||||
|
||||
if (!isValid) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: msg`Validation failed`.id,
|
||||
});
|
||||
}
|
||||
if (!isValid) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: msg`Validation failed`.id,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const form = useForm<z.infer<typeof ZSignFieldCheckboxFormSchema>>({
|
||||
resolver: zodResolver(ZSignFieldCheckboxFormSchema),
|
||||
defaultValues: {
|
||||
values: (fieldMeta.values || []).map((value, index) => ({
|
||||
checked: preselectedIndices.includes(index) || false,
|
||||
value: value.value,
|
||||
})),
|
||||
},
|
||||
});
|
||||
|
||||
const form = useForm<z.infer<typeof ZSignFieldCheckboxFormSchema>>({
|
||||
resolver: zodResolver(ZSignFieldCheckboxFormSchema),
|
||||
defaultValues: {
|
||||
values: (fieldMeta.values || []).map((value, index) => ({
|
||||
checked: preselectedIndices.includes(index) || false,
|
||||
value: value.value,
|
||||
})),
|
||||
},
|
||||
});
|
||||
const formValues = useWatch({
|
||||
control: form.control,
|
||||
});
|
||||
|
||||
const formValues = useWatch({
|
||||
control: form.control,
|
||||
});
|
||||
return (
|
||||
<Dialog open={true} onOpenChange={(value) => (!value ? call.end(null) : null)}>
|
||||
<DialogContent position="center">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{fieldMeta.label || <Trans>Select Options</Trans>}</DialogTitle>
|
||||
|
||||
return (
|
||||
<Dialog open={true} onOpenChange={(value) => (!value ? call.end(null) : null)}>
|
||||
<DialogContent position="center">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{fieldMeta.label || <Trans>Select Options</Trans>}</DialogTitle>
|
||||
|
||||
<DialogDescription
|
||||
className={cn('mt-4', {
|
||||
'text-destructive': Object.keys(form.formState.errors).length > 0,
|
||||
})}
|
||||
>
|
||||
{match(validationRule)
|
||||
.with('>=', () => (
|
||||
<Plural
|
||||
value={validationLength}
|
||||
one="Select at least # option"
|
||||
other="Select at least # options"
|
||||
/>
|
||||
))
|
||||
.with('=', () => (
|
||||
<Plural
|
||||
value={validationLength}
|
||||
one="Select exactly # option"
|
||||
other="Select exactly # options"
|
||||
/>
|
||||
))
|
||||
.with('<=', () => (
|
||||
<Plural
|
||||
value={validationLength}
|
||||
one="Select at most # option"
|
||||
other="Select at most # options"
|
||||
/>
|
||||
))
|
||||
.exhaustive()}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit((data) =>
|
||||
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}
|
||||
<DialogDescription
|
||||
className={cn('mt-4', {
|
||||
'text-destructive': Object.keys(form.formState.errors).length > 0,
|
||||
})}
|
||||
>
|
||||
<ul className="space-y-3">
|
||||
{(formValues.values || []).map((value, index) => (
|
||||
<li key={`checkbox-${index}`}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`values.${index}`}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<div className="flex items-center">
|
||||
<Checkbox
|
||||
id={`checkbox-value-${index}`}
|
||||
className="h-5 w-5 border-foreground/30 data-[state=checked]:bg-primary"
|
||||
checked={field.value.checked}
|
||||
onCheckedChange={(checked) => {
|
||||
field.onChange({
|
||||
...field.value,
|
||||
checked,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{match(validationRule)
|
||||
.with('>=', () => (
|
||||
<Plural value={validationLength} one="Select at least # option" other="Select at least # options" />
|
||||
))
|
||||
.with('=', () => (
|
||||
<Plural value={validationLength} one="Select exactly # option" other="Select exactly # options" />
|
||||
))
|
||||
.with('<=', () => (
|
||||
<Plural value={validationLength} one="Select at most # option" other="Select at most # options" />
|
||||
))
|
||||
.exhaustive()}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<label
|
||||
className="ml-2 w-full text-sm text-muted-foreground"
|
||||
htmlFor={`checkbox-value-${index}`}
|
||||
>
|
||||
{value.value}
|
||||
</label>
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit((data) =>
|
||||
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">
|
||||
{(formValues.values || []).map((value, index) => (
|
||||
<li key={`checkbox-${index}`}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`values.${index}`}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<div className="flex items-center">
|
||||
<Checkbox
|
||||
id={`checkbox-value-${index}`}
|
||||
className="h-5 w-5 border-foreground/30 data-[state=checked]:bg-primary"
|
||||
checked={field.value.checked}
|
||||
onCheckedChange={(checked) => {
|
||||
field.onChange({
|
||||
...field.value,
|
||||
checked,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="secondary" onClick={() => call.end(null)}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
<label
|
||||
className="ml-2 w-full text-muted-foreground text-sm"
|
||||
htmlFor={`checkbox-value-${index}`}
|
||||
>
|
||||
{value.value}
|
||||
</label>
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<Button type="submit">
|
||||
<Trans>Confirm</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</fieldset>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
});
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="secondary" onClick={() => call.end(null)}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<Button type="submit">
|
||||
<Trans>Confirm</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</fieldset>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { createCallable } from 'react-call';
|
||||
|
||||
import type { TDropdownFieldMeta } from '@documenso/lib/types/field-meta';
|
||||
import {
|
||||
CommandDialog,
|
||||
@@ -10,6 +7,8 @@ import {
|
||||
CommandItem,
|
||||
CommandList,
|
||||
} from '@documenso/ui/primitives/command';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { createCallable } from 'react-call';
|
||||
|
||||
export type SignFieldDropdownDialogProps = {
|
||||
fieldMeta: TDropdownFieldMeta;
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { createCallable } from 'react-call';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { zEmail } from '@documenso/lib/utils/zod';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import {
|
||||
@@ -15,14 +8,14 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import { Form, FormControl, FormField, FormItem, FormMessage } from '@documenso/ui/primitives/form/form';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { createCallable } from 'react-call';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
const ZSignFieldEmailFormSchema = z.object({
|
||||
email: zEmail().min(1, { message: msg`Email is required`.id }),
|
||||
@@ -58,10 +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="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { createCallable } from 'react-call';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import {
|
||||
Dialog,
|
||||
@@ -14,15 +7,14 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { createCallable } from 'react-call';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
const ZSignFieldInitialsFormSchema = z.object({
|
||||
initials: z.string().min(1, { message: msg`Initials are required`.id }),
|
||||
@@ -34,64 +26,59 @@ export type SignFieldInitialsDialogProps = {
|
||||
//
|
||||
};
|
||||
|
||||
export const SignFieldInitialsDialog = createCallable<SignFieldInitialsDialogProps, string | null>(
|
||||
({ call }) => {
|
||||
const form = useForm<TSignFieldInitialsFormSchema>({
|
||||
resolver: zodResolver(ZSignFieldInitialsFormSchema),
|
||||
defaultValues: {
|
||||
initials: '',
|
||||
},
|
||||
});
|
||||
export const SignFieldInitialsDialog = createCallable<SignFieldInitialsDialogProps, string | null>(({ call }) => {
|
||||
const form = useForm<TSignFieldInitialsFormSchema>({
|
||||
resolver: zodResolver(ZSignFieldInitialsFormSchema),
|
||||
defaultValues: {
|
||||
initials: '',
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Dialog open={true} onOpenChange={(value) => (!value ? call.end(null) : null)}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
<Trans>Enter Initials</Trans>
|
||||
</DialogTitle>
|
||||
return (
|
||||
<Dialog open={true} onOpenChange={(value) => (!value ? call.end(null) : null)}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
<Trans>Enter Initials</Trans>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription className="mt-4">
|
||||
<Trans>Please enter your initials</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogDescription className="mt-4">
|
||||
<Trans>Please enter your initials</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<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}
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="initials"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel required>
|
||||
<Trans>Initials</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<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}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="initials"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel required>
|
||||
<Trans>Initials</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="secondary" onClick={() => call.end(null)}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="secondary" onClick={() => call.end(null)}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<Button type="submit">
|
||||
<Trans>Enter</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</fieldset>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
},
|
||||
);
|
||||
<Button type="submit">
|
||||
<Trans>Enter</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</fieldset>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { createCallable } from 'react-call';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import {
|
||||
Dialog,
|
||||
@@ -14,14 +7,14 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import { Form, FormControl, FormField, FormItem, FormMessage } from '@documenso/ui/primitives/form/form';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { createCallable } from 'react-call';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
const ZSignFieldNameFormSchema = z.object({
|
||||
name: z.string().min(1, { message: msg`Name is required`.id }),
|
||||
@@ -33,61 +26,56 @@ export type SignFieldNameDialogProps = {
|
||||
//
|
||||
};
|
||||
|
||||
export const SignFieldNameDialog = createCallable<SignFieldNameDialogProps, string | null>(
|
||||
({ call }) => {
|
||||
const form = useForm<TSignFieldNameFormSchema>({
|
||||
resolver: zodResolver(ZSignFieldNameFormSchema),
|
||||
defaultValues: {
|
||||
name: '',
|
||||
},
|
||||
});
|
||||
export const SignFieldNameDialog = createCallable<SignFieldNameDialogProps, string | null>(({ call }) => {
|
||||
const form = useForm<TSignFieldNameFormSchema>({
|
||||
resolver: zodResolver(ZSignFieldNameFormSchema),
|
||||
defaultValues: {
|
||||
name: '',
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Dialog open={true} onOpenChange={(value) => (!value ? call.end(null) : null)}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
<Trans>Enter Name</Trans>
|
||||
</DialogTitle>
|
||||
return (
|
||||
<Dialog open={true} onOpenChange={(value) => (!value ? call.end(null) : null)}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
<Trans>Enter Name</Trans>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription className="mt-4">
|
||||
<Trans>Please enter your full name</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogDescription className="mt-4">
|
||||
<Trans>Please enter your full name</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<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}
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<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}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="secondary" onClick={() => call.end(null)}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="secondary" onClick={() => call.end(null)}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<Button type="submit">
|
||||
<Trans>Enter</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</fieldset>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
},
|
||||
);
|
||||
<Button type="submit">
|
||||
<Trans>Enter</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</fieldset>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { createCallable } from 'react-call';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { TNumberFieldMeta } from '@documenso/lib/types/field-meta';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
@@ -17,14 +10,13 @@ import {
|
||||
DialogTitle,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import { numberFormatValues } from '@documenso/ui/primitives/document-flow/field-items-advanced-settings/constants';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import { Form, FormControl, FormField, FormItem, FormMessage } from '@documenso/ui/primitives/form/form';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { createCallable } from 'react-call';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
export type SignFieldNumberDialogProps = {
|
||||
fieldMeta: TNumberFieldMeta;
|
||||
@@ -115,10 +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="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="number"
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { createCallable } from 'react-call';
|
||||
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@documenso/ui/primitives/dialog';
|
||||
import { SignaturePad } from '@documenso/ui/primitives/signature-pad';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
import { createCallable } from 'react-call';
|
||||
|
||||
import { DocumentSigningDisclosure } from '../general/document-signing/document-signing-disclosure';
|
||||
|
||||
@@ -23,18 +15,8 @@ export type SignFieldSignatureDialogProps = {
|
||||
drawSignatureEnabled?: boolean;
|
||||
};
|
||||
|
||||
export const SignFieldSignatureDialog = createCallable<
|
||||
SignFieldSignatureDialogProps,
|
||||
string | null
|
||||
>(
|
||||
({
|
||||
call,
|
||||
fullName,
|
||||
typedSignatureEnabled,
|
||||
uploadSignatureEnabled,
|
||||
drawSignatureEnabled,
|
||||
initialSignature,
|
||||
}) => {
|
||||
export const SignFieldSignatureDialog = createCallable<SignFieldSignatureDialogProps, string | null>(
|
||||
({ call, fullName, typedSignatureEnabled, uploadSignatureEnabled, drawSignatureEnabled, initialSignature }) => {
|
||||
const [localSignature, setLocalSignature] = useState(initialSignature);
|
||||
|
||||
return (
|
||||
@@ -64,11 +46,7 @@ export const SignFieldSignatureDialog = createCallable<
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
disabled={!localSignature}
|
||||
onClick={() => call.end(localSignature || null)}
|
||||
>
|
||||
<Button type="button" disabled={!localSignature} onClick={() => call.end(localSignature || null)}>
|
||||
<Trans>Sign</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { Plural, useLingui } from '@lingui/react/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { createCallable } from 'react-call';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { TTextFieldMeta } from '@documenso/lib/types/field-meta';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
@@ -17,14 +9,14 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import { Form, FormControl, FormField, FormItem, FormMessage } from '@documenso/ui/primitives/form/form';
|
||||
import { Textarea } from '@documenso/ui/primitives/textarea';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { Plural, Trans, useLingui } from '@lingui/react/macro';
|
||||
import { createCallable } from 'react-call';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
const ZSignFieldTextFormSchema = z.object({
|
||||
text: z.string().min(1, { message: msg`Text is required`.id }),
|
||||
@@ -36,80 +28,73 @@ export type SignFieldTextDialogProps = {
|
||||
fieldMeta?: TTextFieldMeta;
|
||||
};
|
||||
|
||||
export const SignFieldTextDialog = createCallable<SignFieldTextDialogProps, string | null>(
|
||||
({ call, fieldMeta }) => {
|
||||
const { t } = useLingui();
|
||||
export const SignFieldTextDialog = createCallable<SignFieldTextDialogProps, string | null>(({ call, fieldMeta }) => {
|
||||
const { t } = useLingui();
|
||||
|
||||
const form = useForm<TSignFieldTextFormSchema>({
|
||||
resolver: zodResolver(ZSignFieldTextFormSchema),
|
||||
defaultValues: {
|
||||
text: '',
|
||||
},
|
||||
});
|
||||
const form = useForm<TSignFieldTextFormSchema>({
|
||||
resolver: zodResolver(ZSignFieldTextFormSchema),
|
||||
defaultValues: {
|
||||
text: '',
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Dialog open={true} onOpenChange={(value) => (!value ? call.end(null) : null)}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{fieldMeta?.label || <Trans>Enter Text</Trans>}</DialogTitle>
|
||||
return (
|
||||
<Dialog open={true} onOpenChange={(value) => (!value ? call.end(null) : null)}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{fieldMeta?.label || <Trans>Enter Text</Trans>}</DialogTitle>
|
||||
|
||||
<DialogDescription className="mt-4">
|
||||
<Trans>Please enter a value</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogDescription className="mt-4">
|
||||
<Trans>Please enter a value</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<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}
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="text"
|
||||
render={({ field, fieldState }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
id="custom-text"
|
||||
placeholder={fieldMeta?.placeholder ?? t`Enter your text here`}
|
||||
className={cn('w-full rounded-md', {
|
||||
'border-2 border-red-300 text-left ring-2 ring-red-200 ring-offset-2 ring-offset-red-200 focus-visible:border-red-400 focus-visible:ring-4 focus-visible:ring-red-200 focus-visible:ring-offset-2 focus-visible:ring-offset-red-200':
|
||||
fieldState.error,
|
||||
})}
|
||||
{...field}
|
||||
<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}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="text"
|
||||
render={({ field, fieldState }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
id="custom-text"
|
||||
placeholder={fieldMeta?.placeholder ?? t`Enter your text here`}
|
||||
className={cn('w-full rounded-md', {
|
||||
'border-2 border-red-300 text-left ring-2 ring-red-200 ring-offset-2 ring-offset-red-200 focus-visible:border-red-400 focus-visible:ring-4 focus-visible:ring-red-200 focus-visible:ring-offset-2 focus-visible:ring-offset-red-200':
|
||||
fieldState.error,
|
||||
})}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
{fieldMeta?.characterLimit !== undefined && fieldMeta?.characterLimit > 0 && !fieldState.error && (
|
||||
<div className="text-muted-foreground text-sm">
|
||||
<Plural
|
||||
value={fieldMeta?.characterLimit - (field.value?.length ?? 0)}
|
||||
one="# character remaining"
|
||||
other="# characters remaining"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
{fieldMeta?.characterLimit !== undefined &&
|
||||
fieldMeta?.characterLimit > 0 &&
|
||||
!fieldState.error && (
|
||||
<div className="text-sm text-muted-foreground">
|
||||
<Plural
|
||||
value={fieldMeta?.characterLimit - (field.value?.length ?? 0)}
|
||||
one="# character remaining"
|
||||
other="# characters remaining"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="secondary" onClick={() => call.end(null)}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="secondary" onClick={() => call.end(null)}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<Button type="submit">
|
||||
<Trans>Enter</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</fieldset>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
},
|
||||
);
|
||||
<Button type="submit">
|
||||
<Trans>Enter</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</fieldset>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,22 +1,7 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
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 { useForm } from 'react-hook-form';
|
||||
import { useSearchParams } from 'react-router';
|
||||
import type { z } from 'zod';
|
||||
|
||||
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
|
||||
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
|
||||
import { useSession } from '@documenso/lib/client-only/providers/session';
|
||||
import {
|
||||
IS_BILLING_ENABLED,
|
||||
NEXT_PUBLIC_WEBAPP_URL,
|
||||
SUPPORT_EMAIL,
|
||||
} from '@documenso/lib/constants/app';
|
||||
import { IS_BILLING_ENABLED, NEXT_PUBLIC_WEBAPP_URL, SUPPORT_EMAIL } from '@documenso/lib/constants/app';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { ZCreateTeamRequestSchema } from '@documenso/trpc/server/team-router/create-team.types';
|
||||
@@ -32,17 +17,19 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import { SpinnerBox } from '@documenso/ui/primitives/spinner';
|
||||
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 { useEffect, useMemo, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useSearchParams } from 'react-router';
|
||||
import type { z } from 'zod';
|
||||
|
||||
export type TeamCreateDialogProps = {
|
||||
trigger?: React.ReactNode;
|
||||
@@ -118,9 +105,7 @@ export const TeamCreateDialog = ({ trigger, onCreated, ...props }: TeamCreateDia
|
||||
|
||||
toast({
|
||||
title: _(msg`An unknown error occurred`),
|
||||
description: _(
|
||||
msg`We encountered an unknown error while attempting to create a team. Please try again later.`,
|
||||
),
|
||||
description: _(msg`We encountered an unknown error while attempting to create a team. Please try again later.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@@ -166,11 +151,7 @@ export const TeamCreateDialog = ({ trigger, onCreated, ...props }: TeamCreateDia
|
||||
}, [open, form]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
{...props}
|
||||
open={open}
|
||||
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
|
||||
>
|
||||
<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">
|
||||
@@ -194,15 +175,11 @@ export const TeamCreateDialog = ({ trigger, onCreated, ...props }: TeamCreateDia
|
||||
|
||||
{dialogState === 'alert' && (
|
||||
<>
|
||||
<Alert
|
||||
className="flex flex-col justify-between p-6 sm:flex-row sm:items-center"
|
||||
variant="neutral"
|
||||
>
|
||||
<Alert className="flex flex-col justify-between p-6 sm:flex-row sm:items-center" variant="neutral">
|
||||
<AlertDescription className="mt-0">
|
||||
<Trans>
|
||||
You have reached the maximum number of teams for your plan. Please contact sales
|
||||
at <a href={`mailto:${SUPPORT_EMAIL}`}>{SUPPORT_EMAIL}</a> if you would like to
|
||||
adjust your plan.
|
||||
You have reached the maximum number of teams for your plan. Please contact sales at{' '}
|
||||
<a href={`mailto:${SUPPORT_EMAIL}`}>{SUPPORT_EMAIL}</a> if you would like to adjust your plan.
|
||||
</Trans>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
@@ -218,10 +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="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="teamName"
|
||||
@@ -264,7 +238,7 @@ export const TeamCreateDialog = ({ trigger, onCreated, ...props }: TeamCreateDia
|
||||
<Input className="bg-background" {...field} />
|
||||
</FormControl>
|
||||
{!form.formState.errors.teamUrl && (
|
||||
<span className="text-xs font-normal text-foreground/50">
|
||||
<span className="font-normal text-foreground/50 text-xs">
|
||||
{field.value ? (
|
||||
`${NEXT_PUBLIC_WEBAPP_URL()}/t/${field.value}`
|
||||
) : (
|
||||
@@ -285,16 +259,9 @@ export const TeamCreateDialog = ({ trigger, onCreated, ...props }: TeamCreateDia
|
||||
<FormItem className="flex items-center space-x-2">
|
||||
<FormControl>
|
||||
<div className="flex items-center">
|
||||
<Checkbox
|
||||
id="inherit-members"
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
<Checkbox id="inherit-members" checked={field.value} onCheckedChange={field.onChange} />
|
||||
|
||||
<label
|
||||
className="ml-2 text-sm text-muted-foreground"
|
||||
htmlFor="inherit-members"
|
||||
>
|
||||
<label className="ml-2 text-muted-foreground text-sm" htmlFor="inherit-members">
|
||||
<Trans>Allow all organisation members to access this team</Trans>
|
||||
</label>
|
||||
</div>
|
||||
@@ -308,11 +275,7 @@ export const TeamCreateDialog = ({ trigger, onCreated, ...props }: TeamCreateDia
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
data-testid="dialog-create-team-button"
|
||||
loading={form.formState.isSubmitting}
|
||||
>
|
||||
<Button type="submit" data-testid="dialog-create-team-button" loading={form.formState.isSubmitting}>
|
||||
<Trans>Create Team</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
|
||||
import { useSession } from '@documenso/lib/client-only/providers/session';
|
||||
import { AppError } from '@documenso/lib/errors/app-error';
|
||||
@@ -22,24 +12,19 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@documenso/ui/primitives/select';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
|
||||
import type { Toast } from '@documenso/ui/primitives/use-toast';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { z } from 'zod';
|
||||
|
||||
export type TeamDeleteDialogProps = {
|
||||
teamId: number;
|
||||
@@ -48,12 +33,7 @@ export type TeamDeleteDialogProps = {
|
||||
trigger?: React.ReactNode;
|
||||
};
|
||||
|
||||
export const TeamDeleteDialog = ({
|
||||
trigger,
|
||||
teamId,
|
||||
teamName,
|
||||
redirectTo,
|
||||
}: TeamDeleteDialogProps) => {
|
||||
export const TeamDeleteDialog = ({ trigger, teamId, teamName, redirectTo }: TeamDeleteDialogProps) => {
|
||||
const navigate = useNavigate();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
@@ -154,18 +134,15 @@ export const TeamDeleteDialog = ({
|
||||
|
||||
<DialogDescription className="mt-4">
|
||||
<Trans>
|
||||
Please note that you will lose access to all documents associated with this team & all
|
||||
the members will be removed and notified
|
||||
Please note that you will lose access to all documents associated with this team & all the members will be
|
||||
removed and notified
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onFormSubmit)}>
|
||||
<fieldset
|
||||
className="flex h-full flex-col space-y-4"
|
||||
disabled={form.formState.isSubmitting}
|
||||
>
|
||||
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="teamName"
|
||||
@@ -196,9 +173,7 @@ export const TeamDeleteDialog = ({
|
||||
<FormControl>
|
||||
<Select {...field} onValueChange={field.onChange}>
|
||||
<SelectTrigger>
|
||||
<SelectValue
|
||||
placeholder={_(msg`Don't transfer (Delete all documents)`)}
|
||||
/>
|
||||
<SelectValue placeholder={_(msg`Don't transfer (Delete all documents)`)} />
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectContent>
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { Plus } from 'lucide-react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useRevalidator } from 'react-router';
|
||||
import type { z } from 'zod';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { ZCreateTeamEmailVerificationMutationSchema } from '@documenso/trpc/server/team-router/schema';
|
||||
@@ -21,16 +11,17 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
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 { Trans, useLingui } from '@lingui/react/macro';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { Plus } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useRevalidator } from 'react-router';
|
||||
import type { z } from 'zod';
|
||||
|
||||
export type TeamEmailAddDialogProps = {
|
||||
teamId: number;
|
||||
@@ -59,8 +50,7 @@ export const TeamEmailAddDialog = ({ teamId, trigger, ...props }: TeamEmailAddDi
|
||||
},
|
||||
});
|
||||
|
||||
const { mutateAsync: sendTeamEmailVerification, isPending } =
|
||||
trpc.team.email.verification.send.useMutation();
|
||||
const { mutateAsync: sendTeamEmailVerification, isPending } = trpc.team.email.verification.send.useMutation();
|
||||
|
||||
const onFormSubmit = async ({ name, email }: TCreateTeamEmailFormSchema) => {
|
||||
try {
|
||||
@@ -106,15 +96,11 @@ export const TeamEmailAddDialog = ({ teamId, trigger, ...props }: TeamEmailAddDi
|
||||
}, [open, form]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
{...props}
|
||||
open={open}
|
||||
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
|
||||
>
|
||||
<Dialog {...props} open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
|
||||
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
|
||||
{trigger ?? (
|
||||
<Button variant="outline" loading={isPending} className="bg-background">
|
||||
<Plus className="-ml-1 mr-1 h-5 w-5" />
|
||||
<Plus className="mr-1 -ml-1 h-5 w-5" />
|
||||
<Trans>Add email</Trans>
|
||||
</Button>
|
||||
)}
|
||||
@@ -133,10 +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="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
@@ -162,11 +145,7 @@ export const TeamEmailAddDialog = ({ teamId, trigger, ...props }: TeamEmailAddDi
|
||||
<Trans>Email</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
className="bg-background"
|
||||
placeholder="example@example.com"
|
||||
{...field}
|
||||
/>
|
||||
<Input className="bg-background" placeholder="example@example.com" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type { Prisma } from '@prisma/client';
|
||||
import { useRevalidator } from 'react-router';
|
||||
|
||||
import { formatAvatarUrl } from '@documenso/lib/utils/avatars';
|
||||
import { extractInitials } from '@documenso/lib/utils/recipient-formatter';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
@@ -22,6 +14,12 @@ import {
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type { Prisma } from '@prisma/client';
|
||||
import { useState } from 'react';
|
||||
import { useRevalidator } from 'react-router';
|
||||
|
||||
export type TeamEmailDeleteDialogProps = {
|
||||
trigger?: React.ReactNode;
|
||||
@@ -47,24 +45,23 @@ export const TeamEmailDeleteDialog = ({ trigger, teamName, team }: TeamEmailDele
|
||||
const { toast } = useToast();
|
||||
const { revalidate } = useRevalidator();
|
||||
|
||||
const { mutateAsync: deleteTeamEmail, isPending: isDeletingTeamEmail } =
|
||||
trpc.team.email.delete.useMutation({
|
||||
onSuccess: () => {
|
||||
toast({
|
||||
title: _(msg`Success`),
|
||||
description: _(msg`Team email has been removed`),
|
||||
duration: 5000,
|
||||
});
|
||||
},
|
||||
onError: () => {
|
||||
toast({
|
||||
title: _(msg`Something went wrong`),
|
||||
description: _(msg`Unable to remove team email at this time. Please try again.`),
|
||||
variant: 'destructive',
|
||||
duration: 10000,
|
||||
});
|
||||
},
|
||||
});
|
||||
const { mutateAsync: deleteTeamEmail, isPending: isDeletingTeamEmail } = trpc.team.email.delete.useMutation({
|
||||
onSuccess: () => {
|
||||
toast({
|
||||
title: _(msg`Success`),
|
||||
description: _(msg`Team email has been removed`),
|
||||
duration: 5000,
|
||||
});
|
||||
},
|
||||
onError: () => {
|
||||
toast({
|
||||
title: _(msg`Something went wrong`),
|
||||
description: _(msg`Unable to remove team email at this time. Please try again.`),
|
||||
variant: 'destructive',
|
||||
duration: 10000,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const { mutateAsync: deleteTeamEmailVerification, isPending: isDeletingTeamEmailVerification } =
|
||||
trpc.team.email.verification.delete.useMutation({
|
||||
@@ -115,8 +112,7 @@ export const TeamEmailDeleteDialog = ({ trigger, teamName, team }: TeamEmailDele
|
||||
|
||||
<DialogDescription className="mt-4">
|
||||
<Trans>
|
||||
You are about to delete the following team email from{' '}
|
||||
<span className="font-semibold">{teamName}</span>.
|
||||
You are about to delete the following team email from <span className="font-semibold">{teamName}</span>.
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
@@ -125,19 +121,13 @@ export const TeamEmailDeleteDialog = ({ trigger, teamName, team }: TeamEmailDele
|
||||
<AvatarWithText
|
||||
avatarClass="h-12 w-12"
|
||||
avatarSrc={formatAvatarUrl(team.avatarImageId)}
|
||||
avatarFallback={extractInitials(
|
||||
(team.teamEmail?.name || team.emailVerification?.name) ?? '',
|
||||
)}
|
||||
avatarFallback={extractInitials((team.teamEmail?.name || team.emailVerification?.name) ?? '')}
|
||||
primaryText={
|
||||
<span className="text-foreground/80 text-sm font-semibold">
|
||||
<span className="font-semibold text-foreground/80 text-sm">
|
||||
{team.teamEmail?.name || team.emailVerification?.name}
|
||||
</span>
|
||||
}
|
||||
secondaryText={
|
||||
<span className="text-sm">
|
||||
{team.teamEmail?.email || team.emailVerification?.email}
|
||||
</span>
|
||||
}
|
||||
secondaryText={<span className="text-sm">{team.teamEmail?.email || team.emailVerification?.email}</span>}
|
||||
/>
|
||||
</Alert>
|
||||
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import type { TeamEmail } from '@prisma/client';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useRevalidator } from 'react-router';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import {
|
||||
@@ -19,16 +9,17 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
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 { Trans, useLingui } from '@lingui/react/macro';
|
||||
import type { TeamEmail } from '@prisma/client';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useRevalidator } from 'react-router';
|
||||
import { z } from 'zod';
|
||||
|
||||
export type TeamEmailUpdateDialogProps = {
|
||||
teamEmail: TeamEmail;
|
||||
@@ -41,11 +32,7 @@ const ZUpdateTeamEmailFormSchema = z.object({
|
||||
|
||||
type TUpdateTeamEmailFormSchema = z.infer<typeof ZUpdateTeamEmailFormSchema>;
|
||||
|
||||
export const TeamEmailUpdateDialog = ({
|
||||
teamEmail,
|
||||
trigger,
|
||||
...props
|
||||
}: TeamEmailUpdateDialogProps) => {
|
||||
export const TeamEmailUpdateDialog = ({ teamEmail, trigger, ...props }: TeamEmailUpdateDialogProps) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const { t } = useLingui();
|
||||
@@ -95,11 +82,7 @@ export const TeamEmailUpdateDialog = ({
|
||||
}, [open, form]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
{...props}
|
||||
open={open}
|
||||
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
|
||||
>
|
||||
<Dialog {...props} open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
|
||||
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
|
||||
{trigger ?? (
|
||||
<Button variant="outline" className="bg-background">
|
||||
@@ -121,10 +104,7 @@ export const TeamEmailUpdateDialog = ({
|
||||
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onFormSubmit)}>
|
||||
<fieldset
|
||||
className="flex h-full flex-col space-y-4"
|
||||
disabled={form.formState.isSubmitting}
|
||||
>
|
||||
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { TeamMemberRole } from '@prisma/client';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { match } from 'ts-pattern';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { TEAM_MEMBER_ROLE_HIERARCHY } from '@documenso/lib/constants/teams';
|
||||
import { TEAM_MEMBER_ROLE_MAP } from '@documenso/lib/constants/teams-translations';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
@@ -31,14 +21,16 @@ import {
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@documenso/ui/primitives/select';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { TeamMemberRole } from '@prisma/client';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { match } from 'ts-pattern';
|
||||
import { z } from 'zod';
|
||||
|
||||
import {
|
||||
type OrganisationGroupOption,
|
||||
@@ -173,9 +165,8 @@ export const TeamGroupCreateDialog = ({ ...props }: TeamGroupCreateDialogProps)
|
||||
groups.map((group) => ({
|
||||
organisationGroupId: group.id,
|
||||
teamRole:
|
||||
field.value.find(
|
||||
(value) => value.organisationGroupId === group.id,
|
||||
)?.teamRole || TeamMemberRole.MEMBER,
|
||||
field.value.find((value) => value.organisationGroupId === group.id)?.teamRole ||
|
||||
TeamMemberRole.MEMBER,
|
||||
})),
|
||||
);
|
||||
}}
|
||||
@@ -224,8 +215,8 @@ export const TeamGroupCreateDialog = ({ ...props }: TeamGroupCreateDialogProps)
|
||||
readOnly
|
||||
className="bg-background"
|
||||
value={
|
||||
selectedGroups.find(({ id }) => id === group.organisationGroupId)
|
||||
?.name || t`Untitled Group`
|
||||
selectedGroups.find(({ id }) => id === group.organisationGroupId)?.name ||
|
||||
t`Untitled Group`
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
@@ -247,13 +238,11 @@ export const TeamGroupCreateDialog = ({ ...props }: TeamGroupCreateDialogProps)
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectContent position="popper">
|
||||
{TEAM_MEMBER_ROLE_HIERARCHY[team.currentTeamRole].map(
|
||||
(role) => (
|
||||
<SelectItem key={role} value={role}>
|
||||
{t(TEAM_MEMBER_ROLE_MAP[role]) ?? role}
|
||||
</SelectItem>
|
||||
),
|
||||
)}
|
||||
{TEAM_MEMBER_ROLE_HIERARCHY[team.currentTeamRole].map((role) => (
|
||||
<SelectItem key={role} value={role}>
|
||||
{t(TEAM_MEMBER_ROLE_MAP[role]) ?? role}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type { TeamMemberRole } from '@prisma/client';
|
||||
|
||||
import { isTeamRoleWithinUserHierarchy } from '@documenso/lib/utils/teams';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
|
||||
@@ -19,6 +12,11 @@ import {
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type { TeamMemberRole } from '@prisma/client';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useCurrentTeam } from '~/providers/team';
|
||||
|
||||
@@ -82,8 +80,7 @@ export const TeamGroupDeleteDialog = ({
|
||||
|
||||
<DialogDescription className="mt-4">
|
||||
<Trans context="Removing group from team">
|
||||
You are about to remove the following group from{' '}
|
||||
<span className="font-semibold">{team.name}</span>.
|
||||
You are about to remove the following group from <span className="font-semibold">{team.name}</span>.
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
@@ -91,9 +88,7 @@ export const TeamGroupDeleteDialog = ({
|
||||
{isTeamRoleWithinUserHierarchy(team.currentTeamRole, teamGroupRole) ? (
|
||||
<>
|
||||
<Alert variant="neutral">
|
||||
<AlertDescription className="text-center font-semibold">
|
||||
{teamGroupName}
|
||||
</AlertDescription>
|
||||
<AlertDescription className="text-center font-semibold">{teamGroupName}</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
<fieldset disabled={isDeleting}>
|
||||
|
||||
@@ -1,14 +1,3 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { TeamMemberRole } from '@prisma/client';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { TEAM_MEMBER_ROLE_HIERARCHY } from '@documenso/lib/constants/teams';
|
||||
import { EXTENDED_TEAM_MEMBER_ROLE_MAP } from '@documenso/lib/constants/teams-translations';
|
||||
import { isTeamRoleWithinUserHierarchy } from '@documenso/lib/utils/teams';
|
||||
@@ -24,22 +13,18 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@documenso/ui/primitives/select';
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
|
||||
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 { TeamMemberRole } from '@prisma/client';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { useCurrentTeam } from '~/providers/team';
|
||||
|
||||
@@ -117,11 +102,7 @@ export const TeamGroupUpdateDialog = ({
|
||||
}, [open, team.currentTeamRole, teamGroupRole, form, toast]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
{...props}
|
||||
open={open}
|
||||
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
|
||||
>
|
||||
<Dialog {...props} open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
|
||||
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
|
||||
{trigger ?? (
|
||||
<Button variant="secondary">
|
||||
@@ -138,8 +119,7 @@ export const TeamGroupUpdateDialog = ({
|
||||
|
||||
<DialogDescription>
|
||||
<Trans>
|
||||
You are currently updating the <span className="font-bold">{teamGroupName}</span> team
|
||||
group.
|
||||
You are currently updating the <span className="font-bold">{teamGroupName}</span> team group.
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import type { TeamGroup } from '@prisma/client';
|
||||
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import {
|
||||
@@ -14,6 +11,8 @@ import {
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import type { TeamGroup } from '@prisma/client';
|
||||
|
||||
import { useCurrentTeam } from '~/providers/team';
|
||||
|
||||
@@ -60,8 +59,8 @@ export const TeamMemberInheritDisableDialog = ({ group }: TeamMemberInheritDisab
|
||||
|
||||
<DialogDescription className="mt-4">
|
||||
<Trans>
|
||||
You are about to remove default access to this team for all organisation members. Any
|
||||
members not explicitly added to this team will no longer have access.
|
||||
You are about to remove default access to this team for all organisation members. Any members not
|
||||
explicitly added to this team will no longer have access.
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { OrganisationGroupType, OrganisationMemberRole, TeamMemberRole } from '@prisma/client';
|
||||
|
||||
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
@@ -15,6 +12,8 @@ import {
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { OrganisationGroupType, OrganisationMemberRole, TeamMemberRole } from '@prisma/client';
|
||||
|
||||
import { useCurrentTeam } from '~/providers/team';
|
||||
|
||||
@@ -81,8 +80,7 @@ export const TeamMemberInheritEnableDialog = () => {
|
||||
|
||||
<DialogDescription className="mt-4">
|
||||
<Trans>
|
||||
You are about to give all organisation members access to this team under their
|
||||
organisation role.
|
||||
You are about to give all organisation members access to this team under their organisation role.
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -1,15 +1,3 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { TeamMemberRole } from '@prisma/client';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { InfoIcon, UserPlusIcon } from 'lucide-react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { Link } from 'react-router';
|
||||
import { match } from 'ts-pattern';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
|
||||
import { TEAM_MEMBER_ROLE_HIERARCHY } from '@documenso/lib/constants/teams';
|
||||
import { TEAM_MEMBER_ROLE_MAP } from '@documenso/lib/constants/teams-translations';
|
||||
@@ -36,15 +24,19 @@ import {
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@documenso/ui/primitives/select';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { TeamMemberRole } from '@prisma/client';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { InfoIcon, UserPlusIcon } from 'lucide-react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { Link } from 'react-router';
|
||||
import { match } from 'ts-pattern';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { OrganisationMemberInviteDialog } from '~/components/dialogs/organisation-member-invite-dialog';
|
||||
import {
|
||||
@@ -195,8 +187,8 @@ export const TeamMemberCreateDialog = ({ trigger, ...props }: TeamMemberCreateDi
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="z-[99999] max-w-xs text-muted-foreground">
|
||||
<Trans>
|
||||
To be able to add members to a team, you must first add them to the
|
||||
organisation. For more information, please see the{' '}
|
||||
To be able to add members to a team, you must first add them to the organisation. For more
|
||||
information, please see the{' '}
|
||||
<Link
|
||||
to="https://docs.documenso.com/users/organisations/members"
|
||||
target="_blank"
|
||||
@@ -256,25 +248,23 @@ export const TeamMemberCreateDialog = ({ trigger, ...props }: TeamMemberCreateDi
|
||||
|
||||
<FormControl>
|
||||
{hasNoAvailableMembers ? (
|
||||
<div className="flex flex-col items-center justify-center rounded-lg border border-dashed border-muted-foreground/25 bg-muted/30 px-6 py-12 text-center">
|
||||
<div className="flex flex-col items-center justify-center rounded-lg border border-muted-foreground/25 border-dashed bg-muted/30 px-6 py-12 text-center">
|
||||
<div className="mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-muted">
|
||||
<UserPlusIcon className="h-6 w-6 text-muted-foreground" />
|
||||
</div>
|
||||
<h3 className="mb-2 text-sm font-semibold">
|
||||
<h3 className="mb-2 font-semibold text-sm">
|
||||
<Trans>No organisation members available</Trans>
|
||||
</h3>
|
||||
<p className="mb-6 max-w-sm text-sm text-muted-foreground">
|
||||
<p className="mb-6 max-w-sm text-muted-foreground text-sm">
|
||||
{canInviteOrganisationMembers ? (
|
||||
<Trans>
|
||||
To add members to this team, you must first add them to the
|
||||
organisation.
|
||||
To add members to this team, you must first add them to the organisation.
|
||||
</Trans>
|
||||
) : (
|
||||
<Trans>
|
||||
To add members to this team, they must first be invited to the
|
||||
organisation. Only organisation admins and managers can invite
|
||||
new members — please contact one of them to invite members on
|
||||
your behalf.
|
||||
To add members to this team, they must first be invited to the organisation. Only
|
||||
organisation admins and managers can invite new members — please contact one of them
|
||||
to invite members on your behalf.
|
||||
</Trans>
|
||||
)}
|
||||
</p>
|
||||
@@ -302,9 +292,8 @@ export const TeamMemberCreateDialog = ({ trigger, ...props }: TeamMemberCreateDi
|
||||
members.map((member) => ({
|
||||
organisationMemberId: member.id,
|
||||
teamRole:
|
||||
field.value.find(
|
||||
(entry) => entry.organisationMemberId === member.id,
|
||||
)?.teamRole || TeamMemberRole.MEMBER,
|
||||
field.value.find((entry) => entry.organisationMemberId === member.id)?.teamRole ||
|
||||
TeamMemberRole.MEMBER,
|
||||
})),
|
||||
);
|
||||
}}
|
||||
@@ -321,10 +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="mt-2 flex items-center gap-2 space-y-0">
|
||||
<div>
|
||||
<UserPlusIcon className="h-5 w-5 text-muted-foreground" />
|
||||
</div>
|
||||
@@ -337,7 +323,7 @@ export const TeamMemberCreateDialog = ({ trigger, ...props }: TeamMemberCreateDi
|
||||
<Button
|
||||
type="button"
|
||||
variant="link"
|
||||
className="h-auto p-0 text-sm font-medium text-documenso-700 hover:text-documenso-600"
|
||||
className="h-auto p-0 font-medium text-documenso-700 text-sm hover:text-documenso-600"
|
||||
>
|
||||
<Trans>Invite them to the organisation first</Trans>
|
||||
</Button>
|
||||
@@ -384,10 +370,7 @@ export const TeamMemberCreateDialog = ({ trigger, ...props }: TeamMemberCreateDi
|
||||
<Input
|
||||
readOnly
|
||||
className="bg-background"
|
||||
value={
|
||||
selectedMembers.find(({ id }) => id === member.organisationMemberId)
|
||||
?.name || ''
|
||||
}
|
||||
value={selectedMembers.find(({ id }) => id === member.organisationMemberId)?.name || ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -408,13 +391,11 @@ export const TeamMemberCreateDialog = ({ trigger, ...props }: TeamMemberCreateDi
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectContent position="popper">
|
||||
{TEAM_MEMBER_ROLE_HIERARCHY[team.currentTeamRole].map(
|
||||
(role) => (
|
||||
<SelectItem key={role} value={role}>
|
||||
{t(TEAM_MEMBER_ROLE_MAP[role]) ?? role}
|
||||
</SelectItem>
|
||||
),
|
||||
)}
|
||||
{TEAM_MEMBER_ROLE_HIERARCHY[team.currentTeamRole].map((role) => (
|
||||
<SelectItem key={role} value={role}>
|
||||
{t(TEAM_MEMBER_ROLE_MAP[role]) ?? role}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
|
||||
import { AvatarWithText } from '@documenso/ui/primitives/avatar';
|
||||
@@ -18,6 +12,10 @@ import {
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
|
||||
export type TeamMemberDeleteDialogProps = {
|
||||
teamId: number;
|
||||
@@ -43,28 +41,27 @@ export const TeamMemberDeleteDialog = ({
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const { mutateAsync: deleteTeamMember, isPending: isDeletingTeamMember } =
|
||||
trpc.team.member.delete.useMutation({
|
||||
onSuccess: () => {
|
||||
toast({
|
||||
title: _(msg`Success`),
|
||||
description: _(msg`You have successfully removed this user from the team.`),
|
||||
duration: 5000,
|
||||
});
|
||||
const { mutateAsync: deleteTeamMember, isPending: isDeletingTeamMember } = trpc.team.member.delete.useMutation({
|
||||
onSuccess: () => {
|
||||
toast({
|
||||
title: _(msg`Success`),
|
||||
description: _(msg`You have successfully removed this user from the team.`),
|
||||
duration: 5000,
|
||||
});
|
||||
|
||||
setOpen(false);
|
||||
},
|
||||
onError: () => {
|
||||
toast({
|
||||
title: _(msg`An unknown error occurred`),
|
||||
description: _(
|
||||
msg`We encountered an unknown error while attempting to remove this user. Please try again later.`,
|
||||
),
|
||||
variant: 'destructive',
|
||||
duration: 10000,
|
||||
});
|
||||
},
|
||||
});
|
||||
setOpen(false);
|
||||
},
|
||||
onError: () => {
|
||||
toast({
|
||||
title: _(msg`An unknown error occurred`),
|
||||
description: _(
|
||||
msg`We encountered an unknown error while attempting to remove this user. Please try again later.`,
|
||||
),
|
||||
variant: 'destructive',
|
||||
duration: 10000,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={(value) => !isDeletingTeamMember && setOpen(value)}>
|
||||
@@ -84,8 +81,7 @@ export const TeamMemberDeleteDialog = ({
|
||||
|
||||
<DialogDescription className="mt-4">
|
||||
<Trans>
|
||||
You are about to remove the following user from{' '}
|
||||
<span className="font-semibold">{teamName}</span>.
|
||||
You are about to remove the following user from <span className="font-semibold">{teamName}</span>.
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
@@ -93,9 +89,7 @@ export const TeamMemberDeleteDialog = ({
|
||||
{isInheritMemberEnabled ? (
|
||||
<Alert variant="neutral">
|
||||
<AlertDescription>
|
||||
<Trans>
|
||||
You cannot remove members from this team if the inherit member feature is enabled.
|
||||
</Trans>
|
||||
<Trans>You cannot remove members from this team if the inherit member feature is enabled.</Trans>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
) : (
|
||||
|
||||
@@ -1,14 +1,3 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { TeamMemberRole } from '@prisma/client';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { TEAM_MEMBER_ROLE_HIERARCHY } from '@documenso/lib/constants/teams';
|
||||
import { EXTENDED_TEAM_MEMBER_ROLE_MAP } from '@documenso/lib/constants/teams-translations';
|
||||
import { isTeamRoleWithinUserHierarchy } from '@documenso/lib/utils/teams';
|
||||
@@ -23,22 +12,18 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@documenso/ui/primitives/select';
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
|
||||
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 { TeamMemberRole } from '@prisma/client';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
export type TeamMemberUpdateDialogProps = {
|
||||
currentUserTeamRole: TeamMemberRole;
|
||||
@@ -125,11 +110,7 @@ export const TeamMemberUpdateDialog = ({
|
||||
}, [open, currentUserTeamRole, memberTeamRole, form, toast]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
{...props}
|
||||
open={open}
|
||||
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
|
||||
>
|
||||
<Dialog {...props} open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
|
||||
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
|
||||
{trigger ?? (
|
||||
<Button variant="secondary">
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { File as FileIcon, Upload, X } from 'lucide-react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { Checkbox } from '@documenso/ui/primitives/checkbox';
|
||||
@@ -20,6 +12,13 @@ import {
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import { Form, FormControl, FormField, FormItem } 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 { File as FileIcon, Upload, X } from 'lucide-react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { useCurrentTeam } from '~/providers/team';
|
||||
|
||||
@@ -37,12 +36,7 @@ export type TemplateBulkSendDialogProps = {
|
||||
onSuccess?: () => void;
|
||||
};
|
||||
|
||||
export const TemplateBulkSendDialog = ({
|
||||
templateId,
|
||||
recipients,
|
||||
trigger,
|
||||
onSuccess,
|
||||
}: TemplateBulkSendDialogProps) => {
|
||||
export const TemplateBulkSendDialog = ({ templateId, recipients, trigger, onSuccess }: TemplateBulkSendDialogProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
@@ -58,10 +52,7 @@ export const TemplateBulkSendDialog = ({
|
||||
const { mutateAsync: uploadBulkSend } = trpc.template.uploadBulkSend.useMutation();
|
||||
|
||||
const onDownloadTemplate = () => {
|
||||
const headers = recipients.flatMap((_, index) => [
|
||||
`recipient_${index + 1}_email`,
|
||||
`recipient_${index + 1}_name`,
|
||||
]);
|
||||
const headers = recipients.flatMap((_, index) => [`recipient_${index + 1}_email`, `recipient_${index + 1}_name`]);
|
||||
|
||||
const exampleRow = recipients.flatMap((recipient) => [recipient.email, recipient.name || '']);
|
||||
|
||||
@@ -92,9 +83,7 @@ export const TemplateBulkSendDialog = ({
|
||||
|
||||
toast({
|
||||
title: _(msg`Success`),
|
||||
description: _(
|
||||
msg`Your bulk send has been initiated. You will receive an email notification upon completion.`,
|
||||
),
|
||||
description: _(msg`Your bulk send has been initiated. You will receive an email notification upon completion.`),
|
||||
});
|
||||
|
||||
form.reset();
|
||||
@@ -129,8 +118,8 @@ export const TemplateBulkSendDialog = ({
|
||||
|
||||
<DialogDescription>
|
||||
<Trans>
|
||||
Upload a CSV file to create multiple documents from this template. Each row represents
|
||||
one document with its recipient details.
|
||||
Upload a CSV file to create multiple documents from this template. Each row represents one document with
|
||||
its recipient details.
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
@@ -138,14 +127,14 @@ export const TemplateBulkSendDialog = ({
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col gap-y-4">
|
||||
<div className="rounded-lg border bg-muted/70 p-4">
|
||||
<h3 className="text-sm font-medium">
|
||||
<h3 className="font-medium text-sm">
|
||||
<Trans>CSV Structure</Trans>
|
||||
</h3>
|
||||
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
<p className="mt-1 text-muted-foreground text-sm">
|
||||
<Trans>
|
||||
For each recipient, provide their email (required) and name (optional) in separate
|
||||
columns. Download the template CSV below for the correct format.
|
||||
For each recipient, provide their email (required) and name (optional) in separate columns. Download
|
||||
the template CSV below for the correct format.
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
@@ -153,11 +142,9 @@ export const TemplateBulkSendDialog = ({
|
||||
<Trans>Current recipients:</Trans>
|
||||
</p>
|
||||
|
||||
<ul className="mt-2 list-inside list-disc text-sm text-muted-foreground">
|
||||
<ul className="mt-2 list-inside list-disc text-muted-foreground text-sm">
|
||||
{recipients.map((recipient, index) => (
|
||||
<li key={index}>
|
||||
{recipient.name ? `${recipient.name} (${recipient.email})` : recipient.email}
|
||||
</li>
|
||||
<li key={index}>{recipient.name ? `${recipient.name} (${recipient.email})` : recipient.email}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
@@ -167,7 +154,7 @@ export const TemplateBulkSendDialog = ({
|
||||
<Trans>Download Template CSV</Trans>
|
||||
</Button>
|
||||
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<p className="text-muted-foreground text-xs">
|
||||
<Trans>Pre-formatted CSV template with example data.</Trans>
|
||||
</p>
|
||||
</div>
|
||||
@@ -207,7 +194,7 @@ export const TemplateBulkSendDialog = ({
|
||||
<Button
|
||||
type="button"
|
||||
variant="link"
|
||||
className="p-0 text-xs text-destructive hover:text-destructive"
|
||||
className="p-0 text-destructive text-xs hover:text-destructive"
|
||||
onClick={() => onChange(null)}
|
||||
disabled={form.formState.isSubmitting}
|
||||
>
|
||||
@@ -220,12 +207,11 @@ export const TemplateBulkSendDialog = ({
|
||||
)}
|
||||
</FormControl>
|
||||
|
||||
{error && <p className="text-sm text-destructive">{error.message}</p>}
|
||||
{error && <p className="text-destructive text-sm">{error.message}</p>}
|
||||
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<p className="text-muted-foreground text-xs">
|
||||
<Trans>
|
||||
Maximum file size: 4MB. Maximum 100 rows per upload. Blank values will use
|
||||
template defaults.
|
||||
Maximum file size: 4MB. Maximum 100 rows per upload. Blank values will use template defaults.
|
||||
</Trans>
|
||||
</p>
|
||||
</FormItem>
|
||||
@@ -239,15 +225,11 @@ export const TemplateBulkSendDialog = ({
|
||||
<FormItem className="flex items-center space-x-2">
|
||||
<FormControl>
|
||||
<div className="flex items-center">
|
||||
<Checkbox
|
||||
id="send-immediately"
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
<Checkbox id="send-immediately" checked={field.value} onCheckedChange={field.onChange} />
|
||||
|
||||
<label
|
||||
htmlFor="send-immediately"
|
||||
className="ml-2 flex items-center text-sm text-muted-foreground"
|
||||
className="ml-2 flex items-center text-muted-foreground text-sm"
|
||||
>
|
||||
<Trans>Send documents to recipients immediately</Trans>
|
||||
</label>
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { RecipientRole, type TemplateDirectLink } from '@prisma/client';
|
||||
import {
|
||||
CircleDotIcon,
|
||||
CircleIcon,
|
||||
ClipboardCopyIcon,
|
||||
InfoIcon,
|
||||
LinkIcon,
|
||||
LoaderIcon,
|
||||
} from 'lucide-react';
|
||||
import { Link, useRevalidator } from 'react-router';
|
||||
import { P, match } from 'ts-pattern';
|
||||
|
||||
import { useLimits } from '@documenso/ee/server-only/limits/provider/client';
|
||||
import { useCopyToClipboard } from '@documenso/lib/client-only/hooks/use-copy-to-clipboard';
|
||||
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
|
||||
@@ -39,16 +22,17 @@ import {
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import { Label } from '@documenso/ui/primitives/label';
|
||||
import { Switch } from '@documenso/ui/primitives/switch';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@documenso/ui/primitives/table';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@documenso/ui/primitives/table';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { RecipientRole, type TemplateDirectLink } from '@prisma/client';
|
||||
import { CircleDotIcon, CircleIcon, ClipboardCopyIcon, InfoIcon, LinkIcon, LoaderIcon } from 'lucide-react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { Link, useRevalidator } from 'react-router';
|
||||
import { match, P } from 'ts-pattern';
|
||||
|
||||
type TemplateDirectLinkDialogProps = {
|
||||
templateId: number;
|
||||
@@ -82,17 +66,14 @@ export const TemplateDirectLinkDialog = ({
|
||||
const [isEnabled, setIsEnabled] = useState(directLink?.enabled ?? false);
|
||||
const [token, setToken] = useState(directLink?.token ?? null);
|
||||
const [selectedRecipientId, setSelectedRecipientId] = useState<number | null>(null);
|
||||
const [currentStep, setCurrentStep] = useState<TemplateDirectLinkStep>(
|
||||
token ? 'MANAGE' : 'ONBOARD',
|
||||
);
|
||||
const [currentStep, setCurrentStep] = useState<TemplateDirectLinkStep>(token ? 'MANAGE' : 'ONBOARD');
|
||||
|
||||
const organisation = useCurrentOrganisation();
|
||||
|
||||
const validDirectTemplateRecipients = useMemo(
|
||||
() =>
|
||||
recipients.filter(
|
||||
(recipient) =>
|
||||
recipient.role !== RecipientRole.CC && recipient.role !== RecipientRole.ASSISTANT,
|
||||
(recipient) => recipient.role !== RecipientRole.CC && recipient.role !== RecipientRole.ASSISTANT,
|
||||
),
|
||||
[recipients],
|
||||
);
|
||||
@@ -166,9 +147,7 @@ export const TemplateDirectLinkDialog = ({
|
||||
onError: () => {
|
||||
toast({
|
||||
title: _(msg`Something went wrong`),
|
||||
description: _(
|
||||
msg`We encountered an error while removing the direct template link. Please try again later.`,
|
||||
),
|
||||
description: _(msg`We encountered an error while removing the direct template link. Please try again later.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
},
|
||||
@@ -195,8 +174,7 @@ export const TemplateDirectLinkDialog = ({
|
||||
});
|
||||
};
|
||||
|
||||
const isLoading =
|
||||
isCreatingTemplateDirectLink || isTogglingTemplateAccess || isDeletingTemplateDirectLink;
|
||||
const isLoading = isCreatingTemplateDirectLink || isTogglingTemplateAccess || isDeletingTemplateDirectLink;
|
||||
|
||||
useEffect(() => {
|
||||
resetCreateTemplateDirectLink();
|
||||
@@ -237,13 +215,13 @@ export const TemplateDirectLinkDialog = ({
|
||||
{DIRECT_TEMPLATE_DOCUMENTATION.map((step, index) => (
|
||||
<li className="relative" key={index}>
|
||||
<div className="absolute -left-12">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-full border-[3px] border-neutral-200 text-sm font-bold">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-full border-[3px] border-neutral-200 font-bold text-sm">
|
||||
{index + 1}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 className="font-semibold">{_(step.title)}</h3>
|
||||
<p className="mt-1 text-sm text-muted-foreground">{_(step.description)}</p>
|
||||
<p className="mt-1 text-muted-foreground text-sm">{_(step.description)}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
@@ -252,14 +230,12 @@ export const TemplateDirectLinkDialog = ({
|
||||
<Alert variant="warning">
|
||||
<AlertTitle>
|
||||
<Trans>
|
||||
Direct template link usage exceeded ({quota.directTemplates}/
|
||||
{quota.directTemplates})
|
||||
Direct template link usage exceeded ({quota.directTemplates}/{quota.directTemplates})
|
||||
</Trans>
|
||||
</AlertTitle>
|
||||
<AlertDescription>
|
||||
<Trans>
|
||||
You have reached the maximum limit of {quota.directTemplates} direct
|
||||
templates.{' '}
|
||||
You have reached the maximum limit of {quota.directTemplates} direct templates.{' '}
|
||||
<Link
|
||||
className="mt-1 block underline underline-offset-4"
|
||||
to={`/o/${organisation.url}/settings/billing`}
|
||||
@@ -329,13 +305,13 @@ export const TemplateDirectLinkDialog = ({
|
||||
onClick={async () => onRecipientTableRowClick(row.id)}
|
||||
>
|
||||
<TableCell>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
<div className="text-muted-foreground text-sm">
|
||||
<p>{row.name}</p>
|
||||
<p className="text-xs text-muted-foreground/70">{row.email}</p>
|
||||
<p className="text-muted-foreground/70 text-xs">{row.email}</p>
|
||||
</div>
|
||||
</TableCell>
|
||||
|
||||
<TableCell className="text-sm text-muted-foreground">
|
||||
<TableCell className="text-muted-foreground text-sm">
|
||||
{_(RECIPIENT_ROLES_DESCRIPTION[row.role].roleName)}
|
||||
</TableCell>
|
||||
|
||||
@@ -353,13 +329,11 @@ export const TemplateDirectLinkDialog = ({
|
||||
</div>
|
||||
|
||||
{/* Prevent creating placeholder direct template recipient if the email already exists. */}
|
||||
{!recipients.some(
|
||||
(recipient) => recipient.email === DIRECT_TEMPLATE_RECIPIENT_EMAIL,
|
||||
) && (
|
||||
{!recipients.some((recipient) => recipient.email === DIRECT_TEMPLATE_RECIPIENT_EMAIL) && (
|
||||
<DialogFooter className="mx-auto">
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
{validDirectTemplateRecipients.length !== 0 && (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<p className="text-muted-foreground text-sm">
|
||||
<Trans>Or</Trans>
|
||||
</p>
|
||||
)}
|
||||
@@ -402,19 +376,12 @@ export const TemplateDirectLinkDialog = ({
|
||||
<InfoIcon className="h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="z-9999 max-w-md p-4 text-foreground">
|
||||
<Trans>
|
||||
Disabling direct link signing will prevent anyone from accessing the
|
||||
link.
|
||||
</Trans>
|
||||
<Trans>Disabling direct link signing will prevent anyone from accessing the link.</Trans>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</Label>
|
||||
|
||||
<Switch
|
||||
className="mt-2"
|
||||
checked={isEnabled}
|
||||
onCheckedChange={(value) => setIsEnabled(value)}
|
||||
/>
|
||||
<Switch className="mt-2" checked={isEnabled} onCheckedChange={(value) => setIsEnabled(value)} />
|
||||
</div>
|
||||
|
||||
<div className="mt-2">
|
||||
@@ -431,7 +398,7 @@ export const TemplateDirectLinkDialog = ({
|
||||
className="pr-12"
|
||||
/>
|
||||
|
||||
<div className="absolute bottom-0 right-1 top-0 flex items-center justify-center">
|
||||
<div className="absolute top-0 right-1 bottom-0 flex items-center justify-center">
|
||||
<Button
|
||||
variant="none"
|
||||
type="button"
|
||||
@@ -482,18 +449,13 @@ export const TemplateDirectLinkDialog = ({
|
||||
|
||||
<DialogDescription>
|
||||
<Trans>
|
||||
Please note that proceeding will remove direct linking recipient and turn it
|
||||
into a placeholder.
|
||||
Please note that proceeding will remove direct linking recipient and turn it into a placeholder.
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogFooter>
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
onClick={() => setCurrentStep('MANAGE')}
|
||||
>
|
||||
<Button type="button" variant="secondary" onClick={() => setCurrentStep('MANAGE')}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
|
||||
@@ -1,15 +1,3 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
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 { useForm } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { FolderType } from '@documenso/lib/types/folder-type';
|
||||
import { formatTemplatesPath } from '@documenso/lib/utils/teams';
|
||||
@@ -23,16 +11,19 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
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';
|
||||
|
||||
@@ -157,7 +148,7 @@ export function TemplateMoveToFolderDialog({
|
||||
</DialogHeader>
|
||||
|
||||
<div className="relative">
|
||||
<Search className="text-muted-foreground absolute left-2 top-3 h-4 w-4" />
|
||||
<Search className="absolute top-3 left-2 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder={_(msg`Search folders...`)}
|
||||
value={searchTerm}
|
||||
@@ -211,7 +202,7 @@ export function TemplateMoveToFolderDialog({
|
||||
))}
|
||||
|
||||
{searchTerm && filteredFolders?.length === 0 && (
|
||||
<div className="text-muted-foreground px-2 py-2 text-center text-sm">
|
||||
<div className="px-2 py-2 text-center text-muted-foreground text-sm">
|
||||
<Trans>No folders found</Trans>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,25 +1,9 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { DocumentDistributionMethod, DocumentSigningOrder } from '@prisma/client';
|
||||
import { FileTextIcon, InfoIcon, Plus, UploadCloudIcon, X } from 'lucide-react';
|
||||
import { useFieldArray, useForm } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { match } from 'ts-pattern';
|
||||
import * as z from 'zod';
|
||||
|
||||
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT } from '@documenso/lib/constants/app';
|
||||
import {
|
||||
TEMPLATE_RECIPIENT_EMAIL_PLACEHOLDER_REGEX,
|
||||
TEMPLATE_RECIPIENT_NAME_PLACEHOLDER_REGEX,
|
||||
} from '@documenso/lib/constants/template';
|
||||
import {
|
||||
DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
|
||||
SKIP_QUERY_BATCH_META,
|
||||
} from '@documenso/lib/constants/trpc';
|
||||
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION, SKIP_QUERY_BATCH_META } from '@documenso/lib/constants/trpc';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { type TRecipientLite, ZRecipientEmailSchema } from '@documenso/lib/types/recipient';
|
||||
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
|
||||
@@ -37,18 +21,22 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import { SpinnerBox } from '@documenso/ui/primitives/spinner';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
|
||||
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 { DocumentDistributionMethod, DocumentSigningOrder } from '@prisma/client';
|
||||
import { FileTextIcon, InfoIcon, Plus, UploadCloudIcon, X } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useFieldArray, useForm } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { match } from 'ts-pattern';
|
||||
import * as z from 'zod';
|
||||
|
||||
const ZAddRecipientsForNewDocumentSchema = z.object({
|
||||
distributeDocument: z.boolean(),
|
||||
@@ -126,13 +114,9 @@ export function TemplateUseDialog({
|
||||
recipients: recipients
|
||||
.sort((a, b) => (a.signingOrder || 0) - (b.signingOrder || 0))
|
||||
.map((recipient) => {
|
||||
const isRecipientEmailPlaceholder = recipient.email.match(
|
||||
TEMPLATE_RECIPIENT_EMAIL_PLACEHOLDER_REGEX,
|
||||
);
|
||||
const isRecipientEmailPlaceholder = recipient.email.match(TEMPLATE_RECIPIENT_EMAIL_PLACEHOLDER_REGEX);
|
||||
|
||||
const isRecipientNamePlaceholder = recipient.name.match(
|
||||
TEMPLATE_RECIPIENT_NAME_PLACEHOLDER_REGEX,
|
||||
);
|
||||
const isRecipientNamePlaceholder = recipient.name.match(TEMPLATE_RECIPIENT_NAME_PLACEHOLDER_REGEX);
|
||||
|
||||
return {
|
||||
id: recipient.id,
|
||||
@@ -154,8 +138,7 @@ export function TemplateUseDialog({
|
||||
name: 'customDocumentData',
|
||||
});
|
||||
|
||||
const { mutateAsync: createDocumentFromTemplate } =
|
||||
trpc.template.createDocumentFromTemplate.useMutation();
|
||||
const { mutateAsync: createDocumentFromTemplate } = trpc.template.createDocumentFromTemplate.useMutation();
|
||||
|
||||
const onSubmit = async (data: TAddRecipientsForNewDocumentSchema) => {
|
||||
try {
|
||||
@@ -190,10 +173,7 @@ export function TemplateUseDialog({
|
||||
|
||||
let documentPath = `${documentRootPath}/${envelopeId}`;
|
||||
|
||||
if (
|
||||
data.distributeDocument &&
|
||||
documentDistributionMethod === DocumentDistributionMethod.NONE
|
||||
) {
|
||||
if (data.distributeDocument && documentDistributionMethod === DocumentDistributionMethod.NONE) {
|
||||
documentPath += '?action=view-signing-links';
|
||||
}
|
||||
|
||||
@@ -202,24 +182,15 @@ export function TemplateUseDialog({
|
||||
const error = AppError.parseError(err);
|
||||
|
||||
const errorMessage = match(error.code)
|
||||
.with(
|
||||
'DOCUMENT_SEND_FAILED',
|
||||
() => msg`The document was created but could not be sent to recipients.`,
|
||||
)
|
||||
.with('DOCUMENT_SEND_FAILED', () => msg`The document was created but could not be sent to recipients.`)
|
||||
.with(
|
||||
AppErrorCode.INVALID_BODY,
|
||||
AppErrorCode.INVALID_REQUEST,
|
||||
() =>
|
||||
msg`The document could not be created because of missing or invalid information. Please review the template's recipients and fields.`,
|
||||
)
|
||||
.with(
|
||||
AppErrorCode.NOT_FOUND,
|
||||
() => msg`The template or one of its recipients could not be found.`,
|
||||
)
|
||||
.with(
|
||||
AppErrorCode.LIMIT_EXCEEDED,
|
||||
() => msg`You have reached your document limit for this plan.`,
|
||||
)
|
||||
.with(AppErrorCode.NOT_FOUND, () => msg`The template or one of its recipients could not be found.`)
|
||||
.with(AppErrorCode.LIMIT_EXCEEDED, () => msg`You have reached your document limit for this plan.`)
|
||||
.otherwise(() => msg`An error occurred while creating document from template.`);
|
||||
|
||||
toast({
|
||||
@@ -258,7 +229,7 @@ export function TemplateUseDialog({
|
||||
<DialogTrigger asChild>
|
||||
{trigger || (
|
||||
<Button variant="outline" className="bg-background">
|
||||
<Plus className="-ml-1 mr-2 h-4 w-4" />
|
||||
<Plus className="mr-2 -ml-1 h-4 w-4" />
|
||||
<Trans>Use Template</Trans>
|
||||
</Button>
|
||||
)}
|
||||
@@ -298,10 +269,7 @@ export function TemplateUseDialog({
|
||||
{...field}
|
||||
disabled
|
||||
className="items-center justify-center"
|
||||
value={
|
||||
field.value?.toString() ||
|
||||
recipients[index]?.signingOrder?.toString()
|
||||
}
|
||||
value={field.value?.toString() || recipients[index]?.signingOrder?.toString()}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@@ -371,7 +339,7 @@ export function TemplateUseDialog({
|
||||
|
||||
{documentDistributionMethod === DocumentDistributionMethod.EMAIL && (
|
||||
<label
|
||||
className="ml-2 flex items-center text-sm text-muted-foreground"
|
||||
className="ml-2 flex items-center text-muted-foreground text-sm"
|
||||
htmlFor="distributeDocument"
|
||||
>
|
||||
<Trans>Send document</Trans>
|
||||
@@ -383,15 +351,12 @@ export function TemplateUseDialog({
|
||||
<TooltipContent className="z-[99999] max-w-md space-y-2 p-4 text-muted-foreground">
|
||||
<p>
|
||||
<Trans>
|
||||
The document will be immediately sent to recipients if this
|
||||
is checked.
|
||||
The document will be immediately sent to recipients if this is checked.
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<Trans>
|
||||
Otherwise, the document will be created as a draft.
|
||||
</Trans>
|
||||
<Trans>Otherwise, the document will be created as a draft.</Trans>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
@@ -400,7 +365,7 @@ export function TemplateUseDialog({
|
||||
|
||||
{documentDistributionMethod === DocumentDistributionMethod.NONE && (
|
||||
<label
|
||||
className="ml-2 flex items-center text-sm text-muted-foreground"
|
||||
className="ml-2 flex items-center text-muted-foreground text-sm"
|
||||
htmlFor="distributeDocument"
|
||||
>
|
||||
<Trans>Create as pending</Trans>
|
||||
@@ -410,9 +375,7 @@ export function TemplateUseDialog({
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="z-[99999] max-w-md space-y-2 p-4 text-muted-foreground">
|
||||
<p>
|
||||
<Trans>
|
||||
Create the document as pending and ready to sign.
|
||||
</Trans>
|
||||
<Trans>Create the document as pending and ready to sign.</Trans>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
@@ -421,8 +384,8 @@ export function TemplateUseDialog({
|
||||
|
||||
<p className="mt-2">
|
||||
<Trans>
|
||||
We will generate signing links for you, which you can send
|
||||
to the recipients through your method of choice.
|
||||
We will generate signing links for you, which you can send to the recipients
|
||||
through your method of choice.
|
||||
</Trans>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
@@ -454,7 +417,7 @@ export function TemplateUseDialog({
|
||||
}}
|
||||
/>
|
||||
<label
|
||||
className="ml-2 flex items-center text-sm text-muted-foreground"
|
||||
className="ml-2 flex items-center text-muted-foreground text-sm"
|
||||
htmlFor="useCustomDocument"
|
||||
>
|
||||
<Trans>Upload custom document</Trans>
|
||||
@@ -465,8 +428,7 @@ export function TemplateUseDialog({
|
||||
<TooltipContent className="z-[99999] max-w-md space-y-2 p-4 text-muted-foreground">
|
||||
<p>
|
||||
<Trans>
|
||||
Upload a custom document to use instead of the template's default
|
||||
document
|
||||
Upload a custom document to use instead of the template's default document
|
||||
</Trans>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
@@ -501,16 +463,11 @@ export function TemplateUseDialog({
|
||||
</div>
|
||||
|
||||
<div className="min-w-0 flex-1">
|
||||
<h4 className="truncate text-sm font-medium text-foreground">
|
||||
{item.title}
|
||||
</h4>
|
||||
<p className="mt-0.5 text-xs text-muted-foreground">
|
||||
<h4 className="truncate font-medium text-foreground text-sm">{item.title}</h4>
|
||||
<p className="mt-0.5 text-muted-foreground text-xs">
|
||||
{field.value ? (
|
||||
<div>
|
||||
<Trans>
|
||||
Custom {(field.value.size / (1024 * 1024)).toFixed(2)}{' '}
|
||||
MB file
|
||||
</Trans>
|
||||
<Trans>Custom {(field.value.size / (1024 * 1024)).toFixed(2)} MB file</Trans>
|
||||
</div>
|
||||
) : (
|
||||
<Trans>Default file</Trans>
|
||||
@@ -579,10 +536,7 @@ export function TemplateUseDialog({
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
file.size >
|
||||
APP_DOCUMENT_UPLOAD_SIZE_LIMIT * 1024 * 1024
|
||||
) {
|
||||
if (file.size > APP_DOCUMENT_UPLOAD_SIZE_LIMIT * 1024 * 1024) {
|
||||
form.setError('customDocumentData', {
|
||||
type: 'manual',
|
||||
message: _(
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
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 { ApiToken } from '@prisma/client';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import {
|
||||
@@ -19,16 +9,17 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
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 { ApiToken } from '@prisma/client';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { useCurrentTeam } from '~/providers/team';
|
||||
|
||||
@@ -102,10 +93,7 @@ export default function TokenDeleteDialog({ token, onDelete, children }: TokenDe
|
||||
}, [isOpen, form]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={isOpen}
|
||||
onOpenChange={(value) => !form.formState.isSubmitting && setIsOpen(value)}
|
||||
>
|
||||
<Dialog open={isOpen} onOpenChange={(value) => !form.formState.isSubmitting && setIsOpen(value)}>
|
||||
<DialogTrigger asChild={true}>
|
||||
{children ?? (
|
||||
<Button className="mr-4" variant="destructive">
|
||||
@@ -122,18 +110,14 @@ export default function TokenDeleteDialog({ token, onDelete, children }: TokenDe
|
||||
|
||||
<DialogDescription>
|
||||
<Trans>
|
||||
Please note that this action is irreversible. Once confirmed, your token will be
|
||||
permanently deleted.
|
||||
Please note that this action is irreversible. Once confirmed, your token will be permanently deleted.
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<fieldset
|
||||
className="flex h-full flex-col space-y-4"
|
||||
disabled={form.formState.isSubmitting}
|
||||
>
|
||||
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="tokenName"
|
||||
@@ -142,9 +126,7 @@ export default function TokenDeleteDialog({ token, onDelete, children }: TokenDe
|
||||
<FormLabel>
|
||||
<Trans>
|
||||
Confirm by typing:{' '}
|
||||
<span className="font-sm text-destructive font-semibold">
|
||||
{deleteMessage}
|
||||
</span>
|
||||
<span className="font-semibold font-sm text-destructive">{deleteMessage}</span>
|
||||
</Trans>
|
||||
</FormLabel>
|
||||
|
||||
@@ -158,12 +140,7 @@ export default function TokenDeleteDialog({ token, onDelete, children }: TokenDe
|
||||
|
||||
<DialogFooter>
|
||||
<div className="flex w-full flex-nowrap gap-4">
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
className="flex-1"
|
||||
onClick={() => setIsOpen(false)}
|
||||
>
|
||||
<Button type="button" variant="secondary" className="flex-1" onClick={() => setIsOpen(false)}>
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
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 { useForm } from 'react-hook-form';
|
||||
import type { z } from 'zod';
|
||||
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { ZCreateWebhookRequestSchema } from '@documenso/trpc/server/webhook-router/schema';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
@@ -33,6 +23,14 @@ import { Input } from '@documenso/ui/primitives/input';
|
||||
import { PasswordInput } from '@documenso/ui/primitives/password-input';
|
||||
import { Switch } from '@documenso/ui/primitives/switch';
|
||||
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 { useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import type { z } from 'zod';
|
||||
|
||||
import { useCurrentTeam } from '~/providers/team';
|
||||
|
||||
@@ -66,12 +64,7 @@ export const WebhookCreateDialog = ({ trigger, ...props }: WebhookCreateDialogPr
|
||||
|
||||
const { mutateAsync: createWebhook } = trpc.webhook.createWebhook.useMutation();
|
||||
|
||||
const onSubmit = async ({
|
||||
enabled,
|
||||
eventTriggers,
|
||||
secret,
|
||||
webhookUrl,
|
||||
}: TCreateWebhookFormSchema) => {
|
||||
const onSubmit = async ({ enabled, eventTriggers, secret, webhookUrl }: TCreateWebhookFormSchema) => {
|
||||
try {
|
||||
await createWebhook({
|
||||
enabled,
|
||||
@@ -98,11 +91,7 @@ export const WebhookCreateDialog = ({ trigger, ...props }: WebhookCreateDialogPr
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
|
||||
{...props}
|
||||
>
|
||||
<Dialog open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)} {...props}>
|
||||
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
|
||||
{trigger ?? (
|
||||
<Button className="flex-shrink-0">
|
||||
@@ -123,10 +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="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
|
||||
<div className="flex flex-col-reverse gap-4 md:flex-row">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -160,11 +146,7 @@ export const WebhookCreateDialog = ({ trigger, ...props }: WebhookCreateDialogPr
|
||||
|
||||
<div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
className="bg-background"
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
<Switch className="bg-background" checked={field.value} onCheckedChange={field.onChange} />
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
@@ -209,17 +191,13 @@ export const WebhookCreateDialog = ({ trigger, ...props }: WebhookCreateDialogPr
|
||||
<Trans>Secret</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<PasswordInput
|
||||
className="bg-background"
|
||||
{...field}
|
||||
value={field.value ?? ''}
|
||||
/>
|
||||
<PasswordInput className="bg-background" {...field} value={field.value ?? ''} />
|
||||
</FormControl>
|
||||
|
||||
<FormDescription>
|
||||
<Trans>
|
||||
A secret that will be sent to your URL so you can verify that the request
|
||||
has been sent by Documenso.
|
||||
A secret that will be sent to your URL so you can verify that the request has been sent by
|
||||
Documenso.
|
||||
</Trans>
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
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 { Webhook } from '@prisma/client';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import {
|
||||
@@ -19,16 +9,17 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
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 { Webhook } from '@prisma/client';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { useCurrentTeam } from '~/providers/team';
|
||||
|
||||
@@ -79,9 +70,7 @@ export const WebhookDeleteDialog = ({ webhook, children }: WebhookDeleteDialogPr
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: _(msg`An unknown error occurred`),
|
||||
description: _(
|
||||
msg`We encountered an unknown error while attempting to delete it. Please try again later.`,
|
||||
),
|
||||
description: _(msg`We encountered an unknown error while attempting to delete it. Please try again later.`),
|
||||
variant: 'destructive',
|
||||
duration: 5000,
|
||||
});
|
||||
@@ -112,18 +101,14 @@ export const WebhookDeleteDialog = ({ webhook, children }: WebhookDeleteDialogPr
|
||||
|
||||
<DialogDescription>
|
||||
<Trans>
|
||||
Please note that this action is irreversible. Once confirmed, your webhook will be
|
||||
permanently deleted.
|
||||
Please note that this action is irreversible. Once confirmed, your webhook will be permanently deleted.
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<fieldset
|
||||
className="flex h-full flex-col space-y-4"
|
||||
disabled={form.formState.isSubmitting}
|
||||
>
|
||||
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="webhookUrl"
|
||||
@@ -132,9 +117,7 @@ export const WebhookDeleteDialog = ({ webhook, children }: WebhookDeleteDialogPr
|
||||
<FormLabel>
|
||||
<Trans>
|
||||
Confirm by typing:{' '}
|
||||
<span className="font-sm text-destructive font-semibold">
|
||||
{deleteMessage}
|
||||
</span>
|
||||
<span className="font-semibold font-sm text-destructive">{deleteMessage}</span>
|
||||
</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type { Webhook } from '@prisma/client';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import type { z } from 'zod';
|
||||
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { ZEditWebhookRequestSchema } from '@documenso/trpc/server/webhook-router/schema';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
@@ -34,6 +24,13 @@ import { Input } from '@documenso/ui/primitives/input';
|
||||
import { PasswordInput } from '@documenso/ui/primitives/password-input';
|
||||
import { Switch } from '@documenso/ui/primitives/switch';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import type { Webhook } from '@prisma/client';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import type { z } from 'zod';
|
||||
|
||||
import { WebhookMultiSelectCombobox } from '../general/webhook-multiselect-combobox';
|
||||
|
||||
@@ -86,11 +83,7 @@ export const WebhookEditDialog = ({ trigger, webhook, ...props }: WebhookEditDia
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
|
||||
{...props}
|
||||
>
|
||||
<Dialog open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)} {...props}>
|
||||
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
|
||||
{trigger}
|
||||
</DialogTrigger>
|
||||
@@ -105,10 +98,7 @@ export const WebhookEditDialog = ({ trigger, webhook, ...props }: WebhookEditDia
|
||||
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<fieldset
|
||||
className="flex h-full flex-col gap-y-6"
|
||||
disabled={form.formState.isSubmitting}
|
||||
>
|
||||
<fieldset className="flex h-full flex-col gap-y-6" disabled={form.formState.isSubmitting}>
|
||||
<div className="flex flex-col-reverse gap-4 md:flex-row">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -140,11 +130,7 @@ export const WebhookEditDialog = ({ trigger, webhook, ...props }: WebhookEditDia
|
||||
|
||||
<div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
className="bg-background"
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
<Switch className="bg-background" checked={field.value} onCheckedChange={field.onChange} />
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
@@ -187,17 +173,13 @@ export const WebhookEditDialog = ({ trigger, webhook, ...props }: WebhookEditDia
|
||||
<FormItem>
|
||||
<FormLabel>Secret</FormLabel>
|
||||
<FormControl>
|
||||
<PasswordInput
|
||||
className="bg-background"
|
||||
{...field}
|
||||
value={field.value ?? ''}
|
||||
/>
|
||||
<PasswordInput className="bg-background" {...field} value={field.value ?? ''} />
|
||||
</FormControl>
|
||||
|
||||
<FormDescription>
|
||||
<Trans>
|
||||
A secret that will be sent to your URL so you can verify that the request
|
||||
has been sent by Documenso.
|
||||
A secret that will be sent to your URL so you can verify that the request has been sent by
|
||||
Documenso.
|
||||
</Trans>
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import type { Webhook } from '@prisma/client';
|
||||
import { WebhookTriggerEvents } from '@prisma/client';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { toFriendlyWebhookEventName } from '@documenso/lib/universal/webhook/to-friendly-webhook-event-name';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
@@ -19,22 +10,16 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@documenso/ui/primitives/select';
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import type { Webhook } from '@prisma/client';
|
||||
import { WebhookTriggerEvents } from '@prisma/client';
|
||||
import { useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
export type WebhookTestDialogProps = {
|
||||
webhook: Pick<Webhook, 'id' | 'webhookUrl' | 'eventTriggers'>;
|
||||
@@ -97,18 +82,13 @@ export const WebhookTestDialog = ({ webhook, children }: WebhookTestDialogProps)
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription>
|
||||
<Trans>
|
||||
Send a test webhook with sample data to verify your integration is working correctly.
|
||||
</Trans>
|
||||
<Trans>Send a test webhook with sample data to verify your integration is working correctly.</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<fieldset
|
||||
className="flex h-full flex-col space-y-4"
|
||||
disabled={form.formState.isSubmitting}
|
||||
>
|
||||
<fieldset className="flex h-full flex-col space-y-4" disabled={form.formState.isSubmitting}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="event"
|
||||
@@ -137,10 +117,10 @@ export const WebhookTestDialog = ({ webhook, children }: WebhookTestDialogProps)
|
||||
/>
|
||||
|
||||
<div className="rounded-md border p-4">
|
||||
<h4 className="mb-2 text-sm font-medium">
|
||||
<h4 className="mb-2 font-medium text-sm">
|
||||
<Trans>Webhook URL</Trans>
|
||||
</h4>
|
||||
<p className="text-muted-foreground break-all text-sm">{webhook.webhookUrl}</p>
|
||||
<p className="break-all text-muted-foreground text-sm">{webhook.webhookUrl}</p>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
|
||||
+13
-22
@@ -1,11 +1,3 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { DocumentDistributionMethod } from '@prisma/client';
|
||||
import { InfoIcon } from 'lucide-react';
|
||||
import type { Control } from 'react-hook-form';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
|
||||
import { DATE_FORMATS } from '@documenso/lib/constants/date-formats';
|
||||
import { DOCUMENT_SIGNATURE_TYPES } from '@documenso/lib/constants/document';
|
||||
import { SUPPORTED_LANGUAGES } from '@documenso/lib/constants/i18n';
|
||||
@@ -23,16 +15,17 @@ import {
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import { MultiSelectCombobox } from '@documenso/ui/primitives/multi-select-combobox';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@documenso/ui/primitives/select';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@documenso/ui/primitives/tabs';
|
||||
import { Textarea } from '@documenso/ui/primitives/textarea';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { DocumentDistributionMethod } from '@prisma/client';
|
||||
import { InfoIcon } from 'lucide-react';
|
||||
import type { Control } from 'react-hook-form';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
|
||||
import { useConfigureDocument } from './configure-document-context';
|
||||
import type { TConfigureEmbedFormSchema } from './configure-document-view.types';
|
||||
@@ -60,11 +53,11 @@ export const ConfigureDocumentAdvancedSettings = ({
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3 className="mb-1 text-lg font-medium text-foreground">
|
||||
<h3 className="mb-1 font-medium text-foreground text-lg">
|
||||
<Trans>Advanced Settings</Trans>
|
||||
</h3>
|
||||
|
||||
<p className="mb-6 text-sm text-muted-foreground">
|
||||
<p className="mb-6 text-muted-foreground text-sm">
|
||||
<Trans>Configure additional options and preferences</Trans>
|
||||
</p>
|
||||
|
||||
@@ -205,9 +198,7 @@ export const ConfigureDocumentAdvancedSettings = ({
|
||||
<InfoIcon className="mx-2 h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-xs text-muted-foreground">
|
||||
<Trans>
|
||||
Add a URL to redirect the user to once the document is signed
|
||||
</Trans>
|
||||
<Trans>Add a URL to redirect the user to once the document is signed</Trans>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</FormLabel>
|
||||
@@ -252,8 +243,8 @@ export const ConfigureDocumentAdvancedSettings = ({
|
||||
|
||||
<FormDescription>
|
||||
<Trans>
|
||||
Choose how to distribute your document to recipients. Email will send
|
||||
notifications, None will generate signing links for manual distribution.
|
||||
Choose how to distribute your document to recipients. Email will send notifications, None will
|
||||
generate signing links for manual distribution.
|
||||
</Trans>
|
||||
</FormDescription>
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { useCallback, useRef } from 'react';
|
||||
|
||||
import { RecipientRoleSelect } from '@documenso/ui/components/recipient/recipient-role-select';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { Checkbox } from '@documenso/ui/primitives/checkbox';
|
||||
import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
|
||||
import type { DropResult, SensorAPI } from '@hello-pangea/dnd';
|
||||
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
@@ -9,23 +14,10 @@ import { DocumentSigningOrder, RecipientRole } from '@prisma/client';
|
||||
import { motion } from 'framer-motion';
|
||||
import { GripVertical, HelpCircle, Plus, Trash } from 'lucide-react';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import type { Control } from 'react-hook-form';
|
||||
import { useFieldArray, useFormContext, useFormState } from 'react-hook-form';
|
||||
|
||||
import { RecipientRoleSelect } from '@documenso/ui/components/recipient/recipient-role-select';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { Checkbox } from '@documenso/ui/primitives/checkbox';
|
||||
import {
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
|
||||
|
||||
import { useConfigureDocument } from './configure-document-context';
|
||||
import type { TConfigureEmbedFormSchema } from './configure-document-view.types';
|
||||
|
||||
@@ -37,10 +29,7 @@ export interface ConfigureDocumentRecipientsProps {
|
||||
isSubmitting: boolean;
|
||||
}
|
||||
|
||||
export const ConfigureDocumentRecipients = ({
|
||||
control,
|
||||
isSubmitting,
|
||||
}: ConfigureDocumentRecipientsProps) => {
|
||||
export const ConfigureDocumentRecipients = ({ control, isSubmitting }: ConfigureDocumentRecipientsProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { isTemplate } = useConfigureDocument();
|
||||
|
||||
@@ -66,16 +55,14 @@ export const ConfigureDocumentRecipients = ({
|
||||
});
|
||||
|
||||
const onAddSigner = useCallback(() => {
|
||||
const recipientSigningOrder =
|
||||
signers.length > 0 ? (signers[signers.length - 1]?.signingOrder || 0) + 1 : 1;
|
||||
const recipientSigningOrder = signers.length > 0 ? (signers[signers.length - 1]?.signingOrder || 0) + 1 : 1;
|
||||
|
||||
appendSigner({
|
||||
formId: nanoid(8),
|
||||
name: '',
|
||||
email: '',
|
||||
role: RecipientRole.SIGNER,
|
||||
signingOrder:
|
||||
signingOrder === DocumentSigningOrder.SEQUENTIAL ? recipientSigningOrder : undefined,
|
||||
signingOrder: signingOrder === DocumentSigningOrder.SEQUENTIAL ? recipientSigningOrder : undefined,
|
||||
});
|
||||
}, [appendSigner, signers]);
|
||||
|
||||
@@ -116,7 +103,9 @@ export const ConfigureDocumentRecipients = ({
|
||||
|
||||
const onDragEnd = useCallback(
|
||||
(result: DropResult) => {
|
||||
if (!result.destination) return;
|
||||
if (!result.destination) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the move function from useFieldArray which preserves input values
|
||||
move(result.source.index, result.destination.index);
|
||||
@@ -146,11 +135,11 @@ export const ConfigureDocumentRecipients = ({
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3 className="text-foreground mb-1 text-lg font-medium">
|
||||
<h3 className="mb-1 font-medium text-foreground text-lg">
|
||||
<Trans>Recipients</Trans>
|
||||
</h3>
|
||||
|
||||
<p className="text-muted-foreground mb-6 text-sm">
|
||||
<p className="mb-6 text-muted-foreground text-sm">
|
||||
<Trans>Add signers and configure signing preferences</Trans>
|
||||
</p>
|
||||
|
||||
@@ -165,9 +154,7 @@ export const ConfigureDocumentRecipients = ({
|
||||
id="signingOrder"
|
||||
checked={field.value === DocumentSigningOrder.SEQUENTIAL}
|
||||
onCheckedChange={(checked) =>
|
||||
onSigningOrderChange(
|
||||
checked ? DocumentSigningOrder.SEQUENTIAL : DocumentSigningOrder.PARALLEL,
|
||||
)
|
||||
onSigningOrderChange(checked ? DocumentSigningOrder.SEQUENTIAL : DocumentSigningOrder.PARALLEL)
|
||||
}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
@@ -206,15 +193,15 @@ export const ConfigureDocumentRecipients = ({
|
||||
</FormLabel>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="text-muted-foreground ml-1 cursor-help">
|
||||
<span className="ml-1 cursor-help text-muted-foreground">
|
||||
<HelpCircle className="h-3.5 w-3.5" />
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-80 p-4">
|
||||
<p>
|
||||
<Trans>
|
||||
When enabled, signers can choose who should sign next in the sequence instead
|
||||
of following the predefined order.
|
||||
When enabled, signers can choose who should sign next in the sequence instead of following the
|
||||
predefined order.
|
||||
</Trans>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
@@ -249,8 +236,7 @@ export const ConfigureDocumentRecipients = ({
|
||||
{...provided.dragHandleProps}
|
||||
disabled={signer.disabled}
|
||||
className={cn('py-1', {
|
||||
'bg-widget-foreground pointer-events-none rounded-md pt-2':
|
||||
snapshot.isDragging,
|
||||
'pointer-events-none rounded-md bg-widget-foreground pt-2': snapshot.isDragging,
|
||||
})}
|
||||
>
|
||||
<motion.div
|
||||
@@ -265,9 +251,7 @@ export const ConfigureDocumentRecipients = ({
|
||||
render={({ field }) => (
|
||||
<FormItem
|
||||
className={cn('flex w-16 flex-none items-center gap-x-1', {
|
||||
'mb-6':
|
||||
errors?.signers?.[index] &&
|
||||
!errors?.signers?.[index]?.signingOrder,
|
||||
'mb-6': errors?.signers?.[index] && !errors?.signers?.[index]?.signingOrder,
|
||||
})}
|
||||
>
|
||||
<GripVertical className="h-5 w-5 flex-shrink-0 opacity-40" />
|
||||
@@ -325,8 +309,7 @@ export const ConfigureDocumentRecipients = ({
|
||||
render={({ field }) => (
|
||||
<FormItem
|
||||
className={cn('flex-1', {
|
||||
'mb-6':
|
||||
errors?.signers?.[index] && !errors?.signers?.[index]?.email,
|
||||
'mb-6': errors?.signers?.[index] && !errors?.signers?.[index]?.email,
|
||||
})}
|
||||
>
|
||||
<FormLabel className="sr-only">
|
||||
@@ -374,12 +357,7 @@ export const ConfigureDocumentRecipients = ({
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
disabled={
|
||||
isSubmitting ||
|
||||
signers.length === 1 ||
|
||||
snapshot.isDragging ||
|
||||
signer.disabled
|
||||
}
|
||||
disabled={isSubmitting || signers.length === 1 || snapshot.isDragging || signer.disabled}
|
||||
onClick={() => removeSigner(index)}
|
||||
>
|
||||
<Trash className="h-4 w-4" />
|
||||
@@ -396,14 +374,8 @@ export const ConfigureDocumentRecipients = ({
|
||||
</DragDropContext>
|
||||
|
||||
<div className="mt-4 flex justify-end">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
className="w-auto"
|
||||
disabled={isSubmitting}
|
||||
onClick={onAddSigner}
|
||||
>
|
||||
<Plus className="-ml-1 mr-2 h-5 w-5" />
|
||||
<Button type="button" variant="outline" className="w-auto" disabled={isSubmitting} onClick={onAddSigner}>
|
||||
<Plus className="mr-2 -ml-1 h-5 w-5" />
|
||||
<Trans>Add Signer</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { Cloud, FileText, Loader, X } from 'lucide-react';
|
||||
import { type FileRejection, useDropzone } from 'react-dropzone';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
|
||||
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT } from '@documenso/lib/constants/app';
|
||||
import { buildDropzoneRejectionDescription } from '@documenso/ui/lib/handle-dropzone-rejection';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import {
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { Cloud, FileText, Loader, X } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { type FileRejection, useDropzone } from 'react-dropzone';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
|
||||
import { useConfigureDocument } from './configure-document-context';
|
||||
import type { TConfigureEmbedFormSchema } from './configure-document-view.types';
|
||||
@@ -107,11 +99,13 @@ export const ConfigureDocumentUpload = ({ isSubmitting = false }: ConfigureDocum
|
||||
};
|
||||
|
||||
const formatFileSize = (bytes: number) => {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
if (bytes === 0) {
|
||||
return '0 Bytes';
|
||||
}
|
||||
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
return `${parseFloat((bytes / Math.pow(1024, i)).toFixed(2))} ${sizes[i]}`;
|
||||
return `${parseFloat((bytes / 1024 ** i).toFixed(2))} ${sizes[i]}`;
|
||||
};
|
||||
|
||||
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
||||
@@ -145,11 +139,10 @@ export const ConfigureDocumentUpload = ({ isSubmitting = false }: ConfigureDocum
|
||||
<div
|
||||
{...getRootProps()}
|
||||
className={cn(
|
||||
'relative flex min-h-[160px] cursor-pointer flex-col items-center justify-center rounded-lg border border-dashed border-border bg-background transition',
|
||||
'relative flex min-h-[160px] cursor-pointer flex-col items-center justify-center rounded-lg border border-border border-dashed bg-background transition',
|
||||
{
|
||||
'border-primary/50 bg-primary/5': isDragActive,
|
||||
'hover:bg-muted/30':
|
||||
!isDragActive && !isSubmitting && !isLoading && !isPersisted,
|
||||
'hover:bg-muted/30': !isDragActive && !isSubmitting && !isLoading && !isPersisted,
|
||||
'cursor-not-allowed opacity-60': isSubmitting || isLoading || isPersisted,
|
||||
},
|
||||
)}
|
||||
@@ -170,7 +163,7 @@ export const ConfigureDocumentUpload = ({ isSubmitting = false }: ConfigureDocum
|
||||
'text-muted-foreground': !isDragActive,
|
||||
})}
|
||||
>
|
||||
<p className="text-sm font-medium">
|
||||
<p className="font-medium text-sm">
|
||||
{isDragActive ? (
|
||||
<Trans>Drop your document here</Trans>
|
||||
) : isPersisted ? (
|
||||
@@ -183,9 +176,7 @@ export const ConfigureDocumentUpload = ({ isSubmitting = false }: ConfigureDocum
|
||||
{isPersisted ? (
|
||||
<Trans>This document cannot be changed</Trans>
|
||||
) : (
|
||||
<Trans>
|
||||
.PDF documents accepted (max {APP_DOCUMENT_UPLOAD_SIZE_LIMIT}MB)
|
||||
</Trans>
|
||||
<Trans>.PDF documents accepted (max {APP_DOCUMENT_UPLOAD_SIZE_LIMIT}MB)</Trans>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
@@ -207,10 +198,8 @@ export const ConfigureDocumentUpload = ({ isSubmitting = false }: ConfigureDocum
|
||||
</div>
|
||||
|
||||
<div className="flex-1">
|
||||
<div className="text-sm font-medium">{documentData.name}</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{formatFileSize(documentData.size)}
|
||||
</div>
|
||||
<div className="font-medium text-sm">{documentData.name}</div>
|
||||
<div className="text-muted-foreground text-xs">{formatFileSize(documentData.size)}</div>
|
||||
</div>
|
||||
|
||||
{!isPersisted && (
|
||||
|
||||
@@ -1,23 +1,15 @@
|
||||
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { DocumentDistributionMethod, DocumentSigningOrder, RecipientRole } from '@prisma/client';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
|
||||
import { ConfigureDocumentAdvancedSettings } from './configure-document-advanced-settings';
|
||||
import { useConfigureDocument } from './configure-document-context';
|
||||
import { ConfigureDocumentRecipients } from './configure-document-recipients';
|
||||
@@ -45,9 +37,7 @@ export const ConfigureDocumentView = ({
|
||||
const { isTemplate } = useConfigureDocument();
|
||||
|
||||
const form = useForm<TConfigureEmbedFormSchema>({
|
||||
resolver: zodResolver(
|
||||
type === 'template' ? ZConfigureTemplateEmbedFormSchema : ZConfigureEmbedFormSchema,
|
||||
),
|
||||
resolver: zodResolver(type === 'template' ? ZConfigureTemplateEmbedFormSchema : ZConfigureEmbedFormSchema),
|
||||
defaultValues: {
|
||||
title: defaultValues?.title || '',
|
||||
signers: defaultValues?.signers || [
|
||||
@@ -63,8 +53,7 @@ export const ConfigureDocumentView = ({
|
||||
meta: {
|
||||
subject: defaultValues?.meta?.subject || '',
|
||||
message: defaultValues?.meta?.message || '',
|
||||
distributionMethod:
|
||||
defaultValues?.meta?.distributionMethod || DocumentDistributionMethod.EMAIL,
|
||||
distributionMethod: defaultValues?.meta?.distributionMethod || DocumentDistributionMethod.EMAIL,
|
||||
emailSettings: defaultValues?.meta?.emailSettings || ZDocumentEmailSettingsSchema.parse({}),
|
||||
dateFormat: defaultValues?.meta?.dateFormat || DEFAULT_DOCUMENT_DATE_FORMAT,
|
||||
timezone: defaultValues?.meta?.timezone || DEFAULT_DOCUMENT_TIME_ZONE,
|
||||
@@ -88,7 +77,7 @@ export const ConfigureDocumentView = ({
|
||||
return (
|
||||
<div className="flex w-full flex-col space-y-8">
|
||||
<div>
|
||||
<h2 className="text-foreground mb-1 text-xl font-semibold">
|
||||
<h2 className="mb-1 font-semibold text-foreground text-xl">
|
||||
{isTemplate ? <Trans>Configure Template</Trans> : <Trans>Configure Document</Trans>}
|
||||
</h2>
|
||||
|
||||
@@ -126,12 +115,7 @@ export const ConfigureDocumentView = ({
|
||||
<ConfigureDocumentAdvancedSettings control={control} isSubmitting={isSubmitting} />
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
type="button"
|
||||
onClick={onFormSubmit}
|
||||
disabled={isSubmitting}
|
||||
className="w-full sm:w-auto"
|
||||
>
|
||||
<Button type="button" onClick={onFormSubmit} disabled={isSubmitting} className="w-full sm:w-auto">
|
||||
<Trans>Continue</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import {
|
||||
ZDocumentMetaDateFormatSchema,
|
||||
ZDocumentMetaLanguageSchema,
|
||||
} from '@documenso/lib/types/document-meta';
|
||||
import { ZDocumentMetaDateFormatSchema, ZDocumentMetaLanguageSchema } from '@documenso/lib/types/document-meta';
|
||||
import { ZRecipientEmailSchema } from '@documenso/lib/types/recipient';
|
||||
import { zEmail } from '@documenso/lib/utils/zod';
|
||||
import { DocumentDistributionMethod } from '@documenso/prisma/generated/types';
|
||||
import { z } from 'zod';
|
||||
|
||||
// Define the schema for configuration
|
||||
export type TConfigureEmbedFormSchema = z.infer<typeof ZConfigureEmbedFormSchema>;
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type { EnvelopeItem, FieldType } from '@prisma/client';
|
||||
import { ReadStatus, SendStatus, SigningStatus } from '@prisma/client';
|
||||
import { ChevronsUpDown } from 'lucide-react';
|
||||
import { useFieldArray, useForm } from 'react-hook-form';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
|
||||
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
|
||||
import { useDocumentElement } from '@documenso/lib/client-only/hooks/use-document-element';
|
||||
import { PDF_VIEWER_PAGE_SELECTOR, getPdfPagesCount } from '@documenso/lib/constants/pdf-viewer';
|
||||
import { getPdfPagesCount, PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
||||
import { type TFieldMetaSchema, ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
import type { TRecipientLite } from '@documenso/lib/types/recipient';
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
@@ -28,6 +17,15 @@ import { Form } from '@documenso/ui/primitives/form/form';
|
||||
import { RecipientSelector } from '@documenso/ui/primitives/recipient-selector';
|
||||
import { Sheet, SheetContent, SheetTrigger } from '@documenso/ui/primitives/sheet';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type { EnvelopeItem, FieldType } from '@prisma/client';
|
||||
import { ReadStatus, SendStatus, SigningStatus } from '@prisma/client';
|
||||
import { ChevronsUpDown } from 'lucide-react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useFieldArray, useForm } from 'react-hook-form';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
|
||||
import { FieldAdvancedSettingsDrawer } from '~/components/embed/authoring/field-advanced-settings-drawer';
|
||||
import PDFViewerLazy from '~/components/general/pdf-viewer/pdf-viewer-lazy';
|
||||
@@ -139,16 +137,10 @@ export const ConfigureFieldsView = ({
|
||||
y: 0,
|
||||
});
|
||||
const [activeFieldId, setActiveFieldId] = useState<string | null>(null);
|
||||
const [lastActiveField, setLastActiveField] = useState<
|
||||
TConfigureFieldsFormSchema['fields'][0] | null
|
||||
>(null);
|
||||
const [fieldClipboard, setFieldClipboard] = useState<
|
||||
TConfigureFieldsFormSchema['fields'][0] | null
|
||||
>(null);
|
||||
const [lastActiveField, setLastActiveField] = useState<TConfigureFieldsFormSchema['fields'][0] | null>(null);
|
||||
const [fieldClipboard, setFieldClipboard] = useState<TConfigureFieldsFormSchema['fields'][0] | null>(null);
|
||||
const [showAdvancedSettings, setShowAdvancedSettings] = useState(false);
|
||||
const [currentField, setCurrentField] = useState<TConfigureFieldsFormSchema['fields'][0] | null>(
|
||||
null,
|
||||
);
|
||||
const [currentField, setCurrentField] = useState<TConfigureFieldsFormSchema['fields'][0] | null>(null);
|
||||
|
||||
const fieldBounds = useRef({
|
||||
height: DEFAULT_HEIGHT_PX,
|
||||
@@ -266,15 +258,12 @@ export const ConfigureFieldsView = ({
|
||||
|
||||
const onMouseMove = useCallback(
|
||||
(event: MouseEvent) => {
|
||||
if (!selectedField) return;
|
||||
if (!selectedField) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsFieldWithinBounds(
|
||||
isWithinPageBounds(
|
||||
event,
|
||||
PDF_VIEWER_PAGE_SELECTOR,
|
||||
fieldBounds.current.width,
|
||||
fieldBounds.current.height,
|
||||
),
|
||||
isWithinPageBounds(event, PDF_VIEWER_PAGE_SELECTOR, fieldBounds.current.width, fieldBounds.current.height),
|
||||
);
|
||||
|
||||
setCoords({
|
||||
@@ -295,12 +284,7 @@ export const ConfigureFieldsView = ({
|
||||
|
||||
if (
|
||||
!$page ||
|
||||
!isWithinPageBounds(
|
||||
event,
|
||||
PDF_VIEWER_PAGE_SELECTOR,
|
||||
fieldBounds.current.width,
|
||||
fieldBounds.current.height,
|
||||
)
|
||||
!isWithinPageBounds(event, PDF_VIEWER_PAGE_SELECTOR, fieldBounds.current.width, fieldBounds.current.height)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@@ -359,12 +343,7 @@ export const ConfigureFieldsView = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
x: pageX,
|
||||
y: pageY,
|
||||
width: pageWidth,
|
||||
height: pageHeight,
|
||||
} = getFieldPosition($page, node);
|
||||
const { x: pageX, y: pageY, width: pageWidth, height: pageHeight } = getFieldPosition($page, node);
|
||||
|
||||
update(index, {
|
||||
...field,
|
||||
@@ -466,11 +445,11 @@ export const ConfigureFieldsView = ({
|
||||
{!isMobile && (
|
||||
<div className="order-2 col-span-12 md:order-1 md:col-span-4">
|
||||
<div className="sticky top-4 max-h-[calc(100vh-2rem)] rounded-lg border border-border bg-widget p-4 pb-6">
|
||||
<h2 className="mb-1 text-lg font-medium">
|
||||
<h2 className="mb-1 font-medium text-lg">
|
||||
<Trans>Configure Fields</Trans>
|
||||
</h2>
|
||||
|
||||
<p className="mb-6 text-sm text-muted-foreground">
|
||||
<p className="mb-6 text-muted-foreground text-sm">
|
||||
<Trans>Configure the fields you want to place on the document.</Trans>
|
||||
</p>
|
||||
|
||||
@@ -524,7 +503,7 @@ export const ConfigureFieldsView = ({
|
||||
{selectedField && (
|
||||
<div
|
||||
className={cn(
|
||||
'dark:text-muted-background pointer-events-none fixed z-50 flex cursor-pointer flex-col items-center justify-center bg-white text-muted-foreground transition duration-200 [container-type:size]',
|
||||
'pointer-events-none fixed z-50 flex cursor-pointer flex-col items-center justify-center bg-white text-muted-foreground transition duration-200 [container-type:size] dark:text-muted-background',
|
||||
selectedRecipientStyles.base,
|
||||
{
|
||||
'-rotate-6 scale-90 opacity-50 dark:bg-black/20': !isFieldWithinBounds,
|
||||
@@ -539,17 +518,13 @@ export const ConfigureFieldsView = ({
|
||||
width: fieldBounds.current.width,
|
||||
}}
|
||||
>
|
||||
<span className="text-[clamp(0.425rem,25cqw,0.825rem)]">
|
||||
{_(FRIENDLY_FIELD_TYPE[selectedField])}
|
||||
</span>
|
||||
<span className="text-[clamp(0.425rem,25cqw,0.825rem)]">{_(FRIENDLY_FIELD_TYPE[selectedField])}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Form {...form}>
|
||||
<div>
|
||||
{normalizedDocumentData && (
|
||||
<PDFViewerLazy data={normalizedDocumentData} scrollParentRef="window" />
|
||||
)}
|
||||
{normalizedDocumentData && <PDFViewerLazy data={normalizedDocumentData} scrollParentRef="window" />}
|
||||
|
||||
<ElementVisible target={PDF_VIEWER_PAGE_SELECTOR}>
|
||||
{localFields.map((field, index) => {
|
||||
@@ -593,8 +568,8 @@ export const ConfigureFieldsView = ({
|
||||
{isMobile && (
|
||||
<Sheet open={isDrawerOpen} onOpenChange={setIsDrawerOpen}>
|
||||
<SheetTrigger asChild>
|
||||
<div className="fixed bottom-6 left-6 right-6 z-50 flex items-center justify-between gap-2 rounded-lg border border-border bg-widget p-4">
|
||||
<span className="text-lg font-medium">
|
||||
<div className="fixed right-6 bottom-6 left-6 z-50 flex items-center justify-between gap-2 rounded-lg border border-border bg-widget p-4">
|
||||
<span className="font-medium text-lg">
|
||||
<Trans>Configure Fields</Trans>
|
||||
</span>
|
||||
|
||||
@@ -612,11 +587,11 @@ export const ConfigureFieldsView = ({
|
||||
size="xl"
|
||||
className="h-fit max-h-[80vh] overflow-y-auto rounded-t-xl bg-widget p-4"
|
||||
>
|
||||
<h2 className="mb-1 text-lg font-medium">
|
||||
<h2 className="mb-1 font-medium text-lg">
|
||||
<Trans>Configure Fields</Trans>
|
||||
</h2>
|
||||
|
||||
<p className="mb-6 text-sm text-muted-foreground">
|
||||
<p className="mb-6 text-muted-foreground text-sm">
|
||||
<Trans>Configure the fields you want to place on the document.</Trans>
|
||||
</p>
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
import { FieldType } from '@documenso/prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZConfigureFieldsFormSchema = z.object({
|
||||
fields: z.array(
|
||||
@@ -24,6 +23,4 @@ export const ZConfigureFieldsFormSchema = z.object({
|
||||
|
||||
export type TConfigureFieldsFormSchema = z.infer<typeof ZConfigureFieldsFormSchema>;
|
||||
|
||||
export type TConfigureFieldsFormSchemaField = z.infer<
|
||||
typeof ZConfigureFieldsFormSchema
|
||||
>['fields'][number];
|
||||
export type TConfigureFieldsFormSchemaField = z.infer<typeof ZConfigureFieldsFormSchema>['fields'][number];
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { type TFieldMetaSchema as FieldMeta } from '@documenso/lib/types/field-meta';
|
||||
import type { TFieldMetaSchema as FieldMeta } from '@documenso/lib/types/field-meta';
|
||||
import { parseMessageDescriptor } from '@documenso/lib/utils/i18n';
|
||||
import { FieldAdvancedSettings } from '@documenso/ui/primitives/document-flow/field-item-advanced-settings';
|
||||
import { FRIENDLY_FIELD_TYPE } from '@documenso/ui/primitives/document-flow/types';
|
||||
import { Sheet, SheetContent, SheetTitle } from '@documenso/ui/primitives/sheet';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import type { TConfigureFieldsFormSchemaField } from './configure-fields-view.types';
|
||||
|
||||
@@ -42,10 +41,7 @@ export const FieldAdvancedSettingsDrawer = ({
|
||||
|
||||
<FieldAdvancedSettings
|
||||
title={msg`Advanced settings`}
|
||||
description={msg`Configure the ${parseMessageDescriptor(
|
||||
_,
|
||||
FRIENDLY_FIELD_TYPE[currentField.type],
|
||||
)} field`}
|
||||
description={msg`Configure the ${parseMessageDescriptor(_, FRIENDLY_FIELD_TYPE[currentField.type])} field`}
|
||||
field={currentField}
|
||||
fields={fields}
|
||||
onAdvancedSettings={() => onOpenChange(false)}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
|
||||
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
|
||||
import { SignInForm } from '~/components/forms/signin';
|
||||
import { BrandingLogo } from '~/components/general/branding-logo';
|
||||
@@ -29,10 +28,7 @@ export const EmbedAuthenticationRequired = ({
|
||||
|
||||
<Alert className="mt-8" variant="warning">
|
||||
<AlertDescription>
|
||||
<Trans>
|
||||
To view this document you need to be signed into your account, please sign in to
|
||||
continue.
|
||||
</Trans>
|
||||
<Trans>To view this document you need to be signed into your account, please sign in to continue.</Trans>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Loader } from 'lucide-react';
|
||||
|
||||
export const EmbedClientLoading = () => {
|
||||
return (
|
||||
<div className="fixed left-0 top-0 z-[9999] flex h-full w-full items-center justify-center bg-background">
|
||||
<div className="fixed top-0 left-0 z-[9999] flex h-full w-full items-center justify-center bg-background">
|
||||
<Loader className="mr-2 h-4 w-4 animate-spin" />
|
||||
|
||||
<span>
|
||||
|
||||
@@ -1,30 +1,10 @@
|
||||
import { useEffect, useLayoutEffect, useState } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import {
|
||||
type DocumentMeta,
|
||||
type EnvelopeItem,
|
||||
type Field,
|
||||
FieldType,
|
||||
type Recipient,
|
||||
type Signature,
|
||||
} from '@prisma/client';
|
||||
import { LucideChevronDown, LucideChevronUp } from 'lucide-react';
|
||||
import { DateTime } from 'luxon';
|
||||
import { useSearchParams } from 'react-router';
|
||||
|
||||
import { useThrottleFn } from '@documenso/lib/client-only/hooks/use-throttle-fn';
|
||||
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
||||
import { APP_I18N_OPTIONS } from '@documenso/lib/constants/i18n';
|
||||
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
|
||||
import { ZDirectTemplateEmbedDataSchema } from '@documenso/lib/types/embed-direct-template-schema';
|
||||
import {
|
||||
isFieldUnsignedAndRequired,
|
||||
isRequiredField,
|
||||
} from '@documenso/lib/utils/advanced-fields-helpers';
|
||||
import { isFieldUnsignedAndRequired, isRequiredField } from '@documenso/lib/utils/advanced-fields-helpers';
|
||||
import { getDocumentDataUrlForPdfViewer } from '@documenso/lib/utils/envelope-download';
|
||||
import { sortFieldsByPosition, validateFieldsInserted } from '@documenso/lib/utils/fields';
|
||||
import { dynamicActivate } from '@documenso/lib/utils/i18n';
|
||||
@@ -43,6 +23,21 @@ import { Input } from '@documenso/ui/primitives/input';
|
||||
import { Label } from '@documenso/ui/primitives/label';
|
||||
import { SignaturePadDialog } from '@documenso/ui/primitives/signature-pad/signature-pad-dialog';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import {
|
||||
type DocumentMeta,
|
||||
type EnvelopeItem,
|
||||
type Field,
|
||||
FieldType,
|
||||
type Recipient,
|
||||
type Signature,
|
||||
} from '@prisma/client';
|
||||
import { LucideChevronDown, LucideChevronUp } from 'lucide-react';
|
||||
import { DateTime } from 'luxon';
|
||||
import { useEffect, useLayoutEffect, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router';
|
||||
|
||||
import { BrandingLogo } from '~/components/general/branding-logo';
|
||||
import PDFViewerLazy from '~/components/general/pdf-viewer/pdf-viewer-lazy';
|
||||
@@ -83,8 +78,7 @@ export const EmbedDirectTemplateClientPage = ({
|
||||
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
const { fullName, email, signature, setFullName, setEmail, setSignature } =
|
||||
useRequiredDocumentSigningContext();
|
||||
const { fullName, email, signature, setFullName, setEmail, setSignature } = useRequiredDocumentSigningContext();
|
||||
|
||||
const [hasFinishedInit, setHasFinishedInit] = useState(false);
|
||||
const [hasDocumentLoaded, setHasDocumentLoaded] = useState(false);
|
||||
@@ -134,10 +128,8 @@ export const EmbedDirectTemplateClientPage = ({
|
||||
created: new Date(),
|
||||
recipientId: 1,
|
||||
fieldId: 1,
|
||||
signatureImageAsBase64:
|
||||
payload.value && payload.value.startsWith('data:') ? payload.value : null,
|
||||
typedSignature:
|
||||
payload.value && !payload.value.startsWith('data:') ? payload.value : null,
|
||||
signatureImageAsBase64: payload.value && payload.value.startsWith('data:') ? payload.value : null,
|
||||
typedSignature: payload.value && !payload.value.startsWith('data:') ? payload.value : null,
|
||||
} satisfies Signature;
|
||||
}
|
||||
|
||||
@@ -269,9 +261,7 @@ export const EmbedDirectTemplateClientPage = ({
|
||||
|
||||
toast({
|
||||
title: _(msg`Something went wrong`),
|
||||
description: _(
|
||||
msg`We were unable to submit this document at this time. Please try again later.`,
|
||||
),
|
||||
description: _(msg`We were unable to submit this document at this time. Please try again later.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@@ -377,31 +367,23 @@ 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:bottom-[unset] md:top-4 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 flex-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">
|
||||
{/* Header */}
|
||||
<div>
|
||||
<div className="flex items-center justify-between gap-x-2">
|
||||
<h3 className="text-xl font-semibold text-foreground md:text-2xl">
|
||||
<h3 className="font-semibold text-foreground text-xl md:text-2xl">
|
||||
<Trans>Sign document</Trans>
|
||||
</h3>
|
||||
|
||||
{isExpanded ? (
|
||||
<Button
|
||||
variant="outline"
|
||||
className="h-8 w-8 p-0 md:hidden"
|
||||
onClick={() => setIsExpanded(false)}
|
||||
>
|
||||
<Button variant="outline" className="h-8 w-8 p-0 md:hidden" onClick={() => setIsExpanded(false)}>
|
||||
<LucideChevronDown className="h-5 w-5 text-muted-foreground" />
|
||||
</Button>
|
||||
) : pendingFields.length > 0 ? (
|
||||
<Button
|
||||
variant="outline"
|
||||
className="h-8 w-8 p-0 md:hidden"
|
||||
onClick={() => setIsExpanded(true)}
|
||||
>
|
||||
<Button variant="outline" className="h-8 w-8 p-0 md:hidden" onClick={() => setIsExpanded(true)}>
|
||||
<LucideChevronUp className="h-5 w-5 text-muted-foreground" />
|
||||
</Button>
|
||||
) : (
|
||||
@@ -420,11 +402,11 @@ export const EmbedDirectTemplateClientPage = ({
|
||||
</div>
|
||||
|
||||
<div className="hidden group-data-[expanded]/document-widget:block md:block">
|
||||
<p className="mt-2 text-sm text-muted-foreground">
|
||||
<p className="mt-2 text-muted-foreground text-sm">
|
||||
<Trans>Sign the document to complete the process.</Trans>
|
||||
</p>
|
||||
|
||||
<hr className="mb-8 mt-4 border-border" />
|
||||
<hr className="mt-4 mb-8 border-border" />
|
||||
</div>
|
||||
|
||||
{/* Form */}
|
||||
@@ -453,10 +435,7 @@ export const EmbedDirectTemplateClientPage = ({
|
||||
<Input
|
||||
type="email"
|
||||
id="email"
|
||||
className={cn(
|
||||
'mt-2 bg-background',
|
||||
emailError && 'border-destructive ring-2 ring-destructive/20',
|
||||
)}
|
||||
className={cn('mt-2 bg-background', emailError && 'border-destructive ring-2 ring-destructive/20')}
|
||||
disabled={isEmailLocked}
|
||||
value={email}
|
||||
onChange={(e) => {
|
||||
@@ -467,9 +446,7 @@ export const EmbedDirectTemplateClientPage = ({
|
||||
}}
|
||||
/>
|
||||
|
||||
{emailError && (
|
||||
<p className="mt-2 text-xs font-medium text-destructive">{emailError}</p>
|
||||
)}
|
||||
{emailError && <p className="mt-2 font-medium text-destructive text-xs">{emailError}</p>}
|
||||
</div>
|
||||
|
||||
{hasSignatureField && (
|
||||
@@ -516,9 +493,7 @@ export const EmbedDirectTemplateClientPage = ({
|
||||
</div>
|
||||
|
||||
{showPendingFieldTooltip && pendingFields.length > 0 && (
|
||||
<ElementVisible
|
||||
target={`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${pendingFields[0].page}"]`}
|
||||
>
|
||||
<ElementVisible target={`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${pendingFields[0].page}"]`}>
|
||||
<FieldToolTip key={pendingFields[0].id} field={pendingFields[0]} color="warning">
|
||||
<Trans>Click to insert field</Trans>
|
||||
</FieldToolTip>
|
||||
@@ -535,7 +510,7 @@ export const EmbedDirectTemplateClientPage = ({
|
||||
</div>
|
||||
|
||||
{!hidePoweredBy && (
|
||||
<div className="fixed bottom-0 left-0 z-40 rounded-tr bg-primary px-2 py-1 text-xs font-medium text-primary-foreground opacity-60 hover:opacity-100">
|
||||
<div className="fixed bottom-0 left-0 z-40 rounded-tr bg-primary px-2 py-1 font-medium text-primary-foreground text-xs opacity-60 hover:opacity-100">
|
||||
<span>
|
||||
<Trans>Powered by</Trans>
|
||||
</span>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type { Signature } from '@prisma/client';
|
||||
|
||||
import signingCelebration from '@documenso/assets/images/signing-celebration.png';
|
||||
import { SigningCard3D } from '@documenso/ui/components/signing-card';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type { Signature } from '@prisma/client';
|
||||
|
||||
export type EmbedDocumentCompletedPageProps = {
|
||||
name?: string;
|
||||
@@ -12,7 +11,7 @@ export type EmbedDocumentCompletedPageProps = {
|
||||
export const EmbedDocumentCompleted = ({ name, signature }: EmbedDocumentCompletedPageProps) => {
|
||||
return (
|
||||
<div className="embed--DocumentCompleted relative mx-auto flex min-h-[100dvh] max-w-screen-lg flex-col items-center justify-center p-6">
|
||||
<h3 className="text-foreground text-2xl font-semibold">
|
||||
<h3 className="font-semibold text-2xl text-foreground">
|
||||
<Trans>Document Completed!</Trans>
|
||||
</h3>
|
||||
|
||||
@@ -25,10 +24,9 @@ export const EmbedDocumentCompleted = ({ name, signature }: EmbedDocumentComplet
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p className="text-muted-foreground mt-8 max-w-[50ch] text-center text-sm">
|
||||
<p className="mt-8 max-w-[50ch] text-center text-muted-foreground text-sm">
|
||||
<Trans>
|
||||
The document is now completed, please follow any instructions provided within the parent
|
||||
application.
|
||||
The document is now completed, please follow any instructions provided within the parent application.
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import type { DocumentMeta } from '@prisma/client';
|
||||
import { type Field, FieldType } from '@prisma/client';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
||||
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
|
||||
@@ -18,6 +14,9 @@ import type {
|
||||
TSignFieldWithTokenMutationSchema,
|
||||
} from '@documenso/trpc/server/field-router/schema';
|
||||
import { ElementVisible } from '@documenso/ui/primitives/element-visible';
|
||||
import type { DocumentMeta } from '@prisma/client';
|
||||
import { type Field, FieldType } from '@prisma/client';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { DocumentSigningCheckboxField } from '~/components/general/document-signing/document-signing-checkbox-field';
|
||||
import { DocumentSigningDateField } from '~/components/general/document-signing/document-signing-date-field';
|
||||
@@ -34,22 +33,13 @@ export type EmbedDocumentFieldsProps = {
|
||||
fields: Field[];
|
||||
metadata?: Pick<
|
||||
DocumentMeta,
|
||||
| 'timezone'
|
||||
| 'dateFormat'
|
||||
| 'typedSignatureEnabled'
|
||||
| 'uploadSignatureEnabled'
|
||||
| 'drawSignatureEnabled'
|
||||
'timezone' | 'dateFormat' | 'typedSignatureEnabled' | 'uploadSignatureEnabled' | 'drawSignatureEnabled'
|
||||
> | null;
|
||||
onSignField?: (value: TSignFieldWithTokenMutationSchema) => Promise<void> | void;
|
||||
onUnsignField?: (value: TRemovedSignedFieldWithTokenMutationSchema) => Promise<void> | void;
|
||||
};
|
||||
|
||||
export const EmbedDocumentFields = ({
|
||||
fields,
|
||||
metadata,
|
||||
onSignField,
|
||||
onUnsignField,
|
||||
}: EmbedDocumentFieldsProps) => {
|
||||
export const EmbedDocumentFields = ({ fields, metadata, onSignField, onUnsignField }: EmbedDocumentFieldsProps) => {
|
||||
return (
|
||||
<ElementVisible target={PDF_VIEWER_PAGE_SELECTOR}>
|
||||
{fields.map((field) =>
|
||||
|
||||
@@ -6,25 +6,25 @@ export const EmbedDocumentRejected = () => {
|
||||
<div className="embed--DocumentRejected relative mx-auto flex min-h-[100dvh] max-w-screen-lg flex-col items-center justify-center p-6">
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="flex items-center gap-x-4">
|
||||
<XCircle className="text-destructive h-10 w-10" />
|
||||
<XCircle className="h-10 w-10 text-destructive" />
|
||||
|
||||
<h2 className="max-w-[35ch] text-center text-2xl font-semibold leading-normal md:text-3xl lg:text-4xl">
|
||||
<h2 className="max-w-[35ch] text-center font-semibold text-2xl leading-normal md:text-3xl lg:text-4xl">
|
||||
<Trans>Document Rejected</Trans>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="text-destructive mt-4 flex items-center text-center text-sm">
|
||||
<div className="mt-4 flex items-center text-center text-destructive text-sm">
|
||||
<Trans>You have rejected this document</Trans>
|
||||
</div>
|
||||
|
||||
<p className="text-muted-foreground mt-6 max-w-[60ch] text-center text-sm">
|
||||
<p className="mt-6 max-w-[60ch] text-center text-muted-foreground text-sm">
|
||||
<Trans>
|
||||
The document owner has been notified of your decision. They may contact you with further
|
||||
instructions if necessary.
|
||||
The document owner has been notified of your decision. They may contact you with further instructions if
|
||||
necessary.
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<p className="text-muted-foreground mt-2 max-w-[60ch] text-center text-sm">
|
||||
<p className="mt-2 max-w-[60ch] text-center text-muted-foreground text-sm">
|
||||
<Trans>No further action is required from you at this time.</Trans>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
import { useEffect, useId, useLayoutEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type { DocumentMeta, EnvelopeItem } from '@prisma/client';
|
||||
import { type Field, RecipientRole, SigningStatus } from '@prisma/client';
|
||||
import { LucideChevronDown, LucideChevronUp } from 'lucide-react';
|
||||
|
||||
import { useThrottleFn } from '@documenso/lib/client-only/hooks/use-throttle-fn';
|
||||
import { APP_I18N_OPTIONS } from '@documenso/lib/constants/i18n';
|
||||
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
||||
@@ -30,6 +21,13 @@ import { Label } from '@documenso/ui/primitives/label';
|
||||
import { RadioGroup, RadioGroupItem } from '@documenso/ui/primitives/radio-group';
|
||||
import { SignaturePadDialog } from '@documenso/ui/primitives/signature-pad/signature-pad-dialog';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type { DocumentMeta, EnvelopeItem } from '@prisma/client';
|
||||
import { type Field, RecipientRole, SigningStatus } from '@prisma/client';
|
||||
import { LucideChevronDown, LucideChevronUp } from 'lucide-react';
|
||||
import { useEffect, useId, useLayoutEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { BrandingLogo } from '~/components/general/branding-logo';
|
||||
import PDFViewerLazy from '~/components/general/pdf-viewer/pdf-viewer-lazy';
|
||||
@@ -76,15 +74,12 @@ export const EmbedSignDocumentV1ClientPage = ({
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const { fullName, email, signature, setFullName, setEmail, setSignature } =
|
||||
useRequiredDocumentSigningContext();
|
||||
const { fullName, email, signature, setFullName, setEmail, setSignature } = useRequiredDocumentSigningContext();
|
||||
|
||||
const [hasFinishedInit, setHasFinishedInit] = useState(false);
|
||||
const [hasDocumentLoaded, setHasDocumentLoaded] = useState(false);
|
||||
const [hasCompletedDocument, setHasCompletedDocument] = useState(isCompleted);
|
||||
const [hasRejectedDocument, setHasRejectedDocument] = useState(
|
||||
recipient.signingStatus === SigningStatus.REJECTED,
|
||||
);
|
||||
const [hasRejectedDocument, setHasRejectedDocument] = useState(recipient.signingStatus === SigningStatus.REJECTED);
|
||||
const [selectedSignerId, setSelectedSignerId] = useState<number | null>(
|
||||
allRecipients.length > 0 ? allRecipients[0].id : null,
|
||||
);
|
||||
@@ -93,8 +88,7 @@ export const EmbedSignDocumentV1ClientPage = ({
|
||||
const [isNameLocked, setIsNameLocked] = useState(false);
|
||||
const [isEmailLocked, setIsEmailLocked] = useState(!!email);
|
||||
const [showPendingFieldTooltip, setShowPendingFieldTooltip] = useState(false);
|
||||
const [_showOtherRecipientsCompletedFields, setShowOtherRecipientsCompletedFields] =
|
||||
useState(false);
|
||||
const [_showOtherRecipientsCompletedFields, setShowOtherRecipientsCompletedFields] = useState(false);
|
||||
|
||||
const [allowDocumentRejection, setAllowDocumentRejection] = useState(false);
|
||||
|
||||
@@ -105,9 +99,7 @@ export const EmbedSignDocumentV1ClientPage = ({
|
||||
|
||||
const [pendingFields, _completedFields] = [
|
||||
sortFieldsByPosition(
|
||||
fields.filter(
|
||||
(field) => field.recipientId === recipient.id && isFieldUnsignedAndRequired(field),
|
||||
),
|
||||
fields.filter((field) => field.recipientId === recipient.id && isFieldUnsignedAndRequired(field)),
|
||||
),
|
||||
fields.filter((field) => field.inserted),
|
||||
];
|
||||
@@ -115,10 +107,7 @@ export const EmbedSignDocumentV1ClientPage = ({
|
||||
const { mutateAsync: completeDocumentWithToken, isPending: isSubmitting } =
|
||||
trpc.recipient.completeDocumentWithToken.useMutation();
|
||||
|
||||
const fieldsRequiringValidation = useMemo(
|
||||
() => fields.filter(isFieldUnsignedAndRequired),
|
||||
[fields],
|
||||
);
|
||||
const fieldsRequiringValidation = useMemo(() => fields.filter(isFieldUnsignedAndRequired), [fields]);
|
||||
|
||||
const hasSignatureField = fields.some((field) => isSignatureFieldType(field.type));
|
||||
|
||||
@@ -175,9 +164,7 @@ export const EmbedSignDocumentV1ClientPage = ({
|
||||
|
||||
toast({
|
||||
title: _(msg`Something went wrong`),
|
||||
description: _(
|
||||
msg`We were unable to submit this document at this time. Please try again later.`,
|
||||
),
|
||||
description: _(msg`We were unable to submit this document at this time. Please try again later.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@@ -293,11 +280,7 @@ export const EmbedSignDocumentV1ClientPage = ({
|
||||
<DocumentSigningAttachmentsPopover envelopeId={envelopeId} token={token} />
|
||||
|
||||
{allowDocumentRejection && (
|
||||
<DocumentSigningRejectDialog
|
||||
documentId={documentId}
|
||||
token={token}
|
||||
onRejected={onDocumentRejected}
|
||||
/>
|
||||
<DocumentSigningRejectDialog documentId={documentId} token={token} onRejected={onDocumentRejected} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -321,19 +304,15 @@ 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:bottom-[unset] md:top-4 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 flex-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">
|
||||
{/* Header */}
|
||||
<div className="embed--DocumentWidgetHeader">
|
||||
<div className="flex items-center justify-between gap-x-2">
|
||||
<h3 className="text-xl font-semibold text-foreground md:text-2xl">
|
||||
{isAssistantMode ? (
|
||||
<Trans>Assist with signing</Trans>
|
||||
) : (
|
||||
<Trans>Sign document</Trans>
|
||||
)}
|
||||
<h3 className="font-semibold text-foreground text-xl md:text-2xl">
|
||||
{isAssistantMode ? <Trans>Assist with signing</Trans> : <Trans>Sign document</Trans>}
|
||||
</h3>
|
||||
|
||||
{isExpanded ? (
|
||||
@@ -357,9 +336,7 @@ export const EmbedSignDocumentV1ClientPage = ({
|
||||
variant="default"
|
||||
size="sm"
|
||||
className="md:hidden"
|
||||
disabled={
|
||||
isThrottled || (!isAssistantMode && hasSignatureField && !signatureValid)
|
||||
}
|
||||
disabled={isThrottled || (!isAssistantMode && hasSignatureField && !signatureValid)}
|
||||
loading={isSubmitting}
|
||||
onClick={() => throttledOnCompleteClick()}
|
||||
>
|
||||
@@ -370,7 +347,7 @@ export const EmbedSignDocumentV1ClientPage = ({
|
||||
</div>
|
||||
|
||||
<div className="embed--DocumentWidgetContent hidden group-data-[expanded]/document-widget:block md:block">
|
||||
<p className="mt-2 text-sm text-muted-foreground">
|
||||
<p className="mt-2 text-muted-foreground text-sm">
|
||||
{isAssistantMode ? (
|
||||
<Trans>Help complete the document for other signers.</Trans>
|
||||
) : (
|
||||
@@ -378,7 +355,7 @@ export const EmbedSignDocumentV1ClientPage = ({
|
||||
)}
|
||||
</p>
|
||||
|
||||
<hr className="mb-8 mt-4 border-border" />
|
||||
<hr className="mt-4 mb-8 border-border" />
|
||||
</div>
|
||||
|
||||
{/* Form */}
|
||||
@@ -419,15 +396,13 @@ export const EmbedSignDocumentV1ClientPage = ({
|
||||
{r.name}
|
||||
|
||||
{r.id === recipient.id && (
|
||||
<span className="ml-2 text-muted-foreground">
|
||||
{_(msg`(You)`)}
|
||||
</span>
|
||||
<span className="ml-2 text-muted-foreground">{_(msg`(You)`)}</span>
|
||||
)}
|
||||
</Label>
|
||||
<p className="text-xs text-muted-foreground">{r.email}</p>
|
||||
<p className="text-muted-foreground text-xs">{r.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs leading-[inherit] text-muted-foreground">
|
||||
<div className="text-muted-foreground text-xs leading-[inherit]">
|
||||
{r.fields.length} {r.fields.length === 1 ? 'field' : 'fields'}
|
||||
</div>
|
||||
</div>
|
||||
@@ -516,9 +491,7 @@ export const EmbedSignDocumentV1ClientPage = ({
|
||||
</div>
|
||||
|
||||
{showPendingFieldTooltip && pendingFields.length > 0 && (
|
||||
<ElementVisible
|
||||
target={`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${pendingFields[0].page}"]`}
|
||||
>
|
||||
<ElementVisible target={`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${pendingFields[0].page}"]`}>
|
||||
<FieldToolTip key={pendingFields[0].id} field={pendingFields[0]} color="warning">
|
||||
<Trans>Click to insert field</Trans>
|
||||
</FieldToolTip>
|
||||
@@ -533,7 +506,7 @@ export const EmbedSignDocumentV1ClientPage = ({
|
||||
</div>
|
||||
|
||||
{!hidePoweredBy && (
|
||||
<div className="fixed bottom-0 left-0 z-40 rounded-tr bg-primary px-2 py-1 text-xs font-medium text-primary-foreground opacity-60 hover:opacity-100">
|
||||
<div className="fixed bottom-0 left-0 z-40 rounded-tr bg-primary px-2 py-1 font-medium text-primary-foreground text-xs opacity-60 hover:opacity-100">
|
||||
<span>
|
||||
<Trans>Powered by</Trans>
|
||||
</span>
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { useEffect, useLayoutEffect, useState } from 'react';
|
||||
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { APP_I18N_OPTIONS } from '@documenso/lib/constants/i18n';
|
||||
import { ZSignDocumentEmbedDataSchema } from '@documenso/lib/types/embed-document-sign-schema';
|
||||
import { mapSecondaryIdToDocumentId } from '@documenso/lib/utils/envelope';
|
||||
import { dynamicActivate } from '@documenso/lib/utils/i18n';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
import { useEffect, useLayoutEffect, useState } from 'react';
|
||||
|
||||
import { injectCss } from '~/utils/css-vars';
|
||||
|
||||
@@ -38,9 +36,7 @@ export const EmbedSignDocumentV2ClientPage = ({
|
||||
const [hasFinishedInit, setHasFinishedInit] = useState(false);
|
||||
const [allowDocumentRejection, setAllowDocumentRejection] = useState(false);
|
||||
const [isNameLocked, setIsNameLocked] = useState(false);
|
||||
const [isEmailLocked, setIsEmailLocked] = useState(
|
||||
envelope.type === EnvelopeType.DOCUMENT && !!email,
|
||||
);
|
||||
const [isEmailLocked, setIsEmailLocked] = useState(envelope.type === EnvelopeType.DOCUMENT && !!email);
|
||||
|
||||
const onDocumentCompleted = (data: {
|
||||
token: string;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export const EmbedDocumentWaitingForTurn = () => {
|
||||
const [hasPostedMessage, setHasPostedMessage] = useState(false);
|
||||
@@ -25,19 +24,19 @@ export const EmbedDocumentWaitingForTurn = () => {
|
||||
|
||||
return (
|
||||
<div className="embed--WaitingForTurn relative mx-auto flex min-h-[100dvh] max-w-screen-lg flex-col items-center justify-center p-6">
|
||||
<h3 className="text-foreground text-center text-2xl font-bold">
|
||||
<h3 className="text-center font-bold text-2xl text-foreground">
|
||||
<Trans>Waiting for Your Turn</Trans>
|
||||
</h3>
|
||||
|
||||
<div className="mt-8 max-w-[50ch] text-center">
|
||||
<p className="text-muted-foreground text-sm">
|
||||
<Trans>
|
||||
It's currently not your turn to sign. Please check back soon as this document should be
|
||||
available for you to sign shortly.
|
||||
It's currently not your turn to sign. Please check back soon as this document should be available for you to
|
||||
sign shortly.
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<p className="text-muted-foreground mt-4 text-sm">
|
||||
<p className="mt-4 text-muted-foreground text-sm">
|
||||
<Trans>Please check with the parent application for more information.</Trans>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user