mirror of
https://github.com/documenso/documenso.git
synced 2025-11-15 01:01:49 +10:00
Compare commits
1 Commits
v1.5.5-rc.
...
chore/subj
| Author | SHA1 | Date | |
|---|---|---|---|
| 5f2eac9b5d |
@ -8,7 +8,6 @@ import { DOCUMENSO_ENCRYPTION_KEY } from '@documenso/lib/constants/crypto';
|
|||||||
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
|
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
|
||||||
import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id';
|
import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id';
|
||||||
import { getServerComponentFlag } from '@documenso/lib/server-only/feature-flags/get-server-component-feature-flag';
|
import { getServerComponentFlag } from '@documenso/lib/server-only/feature-flags/get-server-component-feature-flag';
|
||||||
import { getCompletedFieldsForDocument } from '@documenso/lib/server-only/field/get-completed-fields-for-document';
|
|
||||||
import { getRecipientsForDocument } from '@documenso/lib/server-only/recipient/get-recipients-for-document';
|
import { getRecipientsForDocument } from '@documenso/lib/server-only/recipient/get-recipients-for-document';
|
||||||
import { symmetricDecrypt } from '@documenso/lib/universal/crypto';
|
import { symmetricDecrypt } from '@documenso/lib/universal/crypto';
|
||||||
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
|
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
|
||||||
@ -21,7 +20,6 @@ import { LazyPDFViewer } from '@documenso/ui/primitives/lazy-pdf-viewer';
|
|||||||
|
|
||||||
import { StackAvatarsWithTooltip } from '~/components/(dashboard)/avatar/stack-avatars-with-tooltip';
|
import { StackAvatarsWithTooltip } from '~/components/(dashboard)/avatar/stack-avatars-with-tooltip';
|
||||||
import { DocumentHistorySheet } from '~/components/document/document-history-sheet';
|
import { DocumentHistorySheet } from '~/components/document/document-history-sheet';
|
||||||
import { DocumentReadOnlyFields } from '~/components/document/document-read-only-fields';
|
|
||||||
import {
|
import {
|
||||||
DocumentStatus as DocumentStatusComponent,
|
DocumentStatus as DocumentStatusComponent,
|
||||||
FRIENDLY_STATUS_MAP,
|
FRIENDLY_STATUS_MAP,
|
||||||
@ -86,16 +84,11 @@ export const DocumentPageView = async ({ params, team }: DocumentPageViewProps)
|
|||||||
documentMeta.password = securePassword;
|
documentMeta.password = securePassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [recipients, completedFields] = await Promise.all([
|
const recipients = await getRecipientsForDocument({
|
||||||
getRecipientsForDocument({
|
|
||||||
documentId,
|
documentId,
|
||||||
teamId: team?.id,
|
teamId: team?.id,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
}),
|
});
|
||||||
getCompletedFieldsForDocument({
|
|
||||||
documentId,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const documentWithRecipients = {
|
const documentWithRecipients = {
|
||||||
...document,
|
...document,
|
||||||
@ -162,13 +155,6 @@ export const DocumentPageView = async ({ params, team }: DocumentPageViewProps)
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{document.status === DocumentStatus.PENDING && (
|
|
||||||
<DocumentReadOnlyFields
|
|
||||||
fields={completedFields}
|
|
||||||
documentMeta={document.documentMeta || undefined}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="col-span-12 lg:col-span-6 xl:col-span-5">
|
<div className="col-span-12 lg:col-span-6 xl:col-span-5">
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<section className="border-border bg-widget flex flex-col rounded-xl border pb-4 pt-6">
|
<section className="border-border bg-widget flex flex-col rounded-xl border pb-4 pt-6">
|
||||||
|
|||||||
@ -224,6 +224,10 @@ export const EditDocumentForm = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setSubjectFormFields = (subject?: string, message?: string) => {
|
||||||
|
// Add functionality here
|
||||||
|
};
|
||||||
|
|
||||||
const onAddFieldsFormSubmit = async (data: TAddFieldsFormSchema) => {
|
const onAddFieldsFormSubmit = async (data: TAddFieldsFormSchema) => {
|
||||||
try {
|
try {
|
||||||
await addFields({
|
await addFields({
|
||||||
@ -359,6 +363,7 @@ export const EditDocumentForm = ({
|
|||||||
fields={fields}
|
fields={fields}
|
||||||
onSubmit={onAddSubjectFormSubmit}
|
onSubmit={onAddSubjectFormSubmit}
|
||||||
isDocumentPdfLoaded={isDocumentPdfLoaded}
|
isDocumentPdfLoaded={isDocumentPdfLoaded}
|
||||||
|
setSubjectFormFields={setSubjectFormFields}
|
||||||
/>
|
/>
|
||||||
</Stepper>
|
</Stepper>
|
||||||
</DocumentFlowFormContainer>
|
</DocumentFlowFormContainer>
|
||||||
|
|||||||
@ -133,11 +133,7 @@ export const DocumentLogsPageView = async ({ params, team }: DocumentLogsPageVie
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-4 flex w-full flex-row sm:mt-0 sm:w-auto sm:self-end">
|
<div className="mt-4 flex w-full flex-row sm:mt-0 sm:w-auto sm:self-end">
|
||||||
<DownloadCertificateButton
|
<DownloadCertificateButton className="mr-2" documentId={document.id} />
|
||||||
className="mr-2"
|
|
||||||
documentId={document.id}
|
|
||||||
documentStatus={document.status}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<DownloadAuditLogButton documentId={document.id} />
|
<DownloadAuditLogButton documentId={document.id} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import { DownloadIcon } from 'lucide-react';
|
import { DownloadIcon } from 'lucide-react';
|
||||||
|
|
||||||
import { DocumentStatus } from '@documenso/prisma/client';
|
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
import { cn } from '@documenso/ui/lib/utils';
|
import { cn } from '@documenso/ui/lib/utils';
|
||||||
import { Button } from '@documenso/ui/primitives/button';
|
import { Button } from '@documenso/ui/primitives/button';
|
||||||
@ -11,13 +10,11 @@ import { useToast } from '@documenso/ui/primitives/use-toast';
|
|||||||
export type DownloadCertificateButtonProps = {
|
export type DownloadCertificateButtonProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
documentId: number;
|
documentId: number;
|
||||||
documentStatus: DocumentStatus;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DownloadCertificateButton = ({
|
export const DownloadCertificateButton = ({
|
||||||
className,
|
className,
|
||||||
documentId,
|
documentId,
|
||||||
documentStatus,
|
|
||||||
}: DownloadCertificateButtonProps) => {
|
}: DownloadCertificateButtonProps) => {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
@ -72,7 +69,6 @@ export const DownloadCertificateButton = ({
|
|||||||
className={cn('w-full sm:w-auto', className)}
|
className={cn('w-full sm:w-auto', className)}
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
disabled={documentStatus !== DocumentStatus.COMPLETED}
|
|
||||||
onClick={() => void onDownloadCertificatesClick()}
|
onClick={() => void onDownloadCertificatesClick()}
|
||||||
>
|
>
|
||||||
{!isLoading && <DownloadIcon className="mr-1.5 h-4 w-4" />}
|
{!isLoading && <DownloadIcon className="mr-1.5 h-4 w-4" />}
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-c
|
|||||||
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
|
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
|
||||||
import { isRecipientAuthorized } from '@documenso/lib/server-only/document/is-recipient-authorized';
|
import { isRecipientAuthorized } from '@documenso/lib/server-only/document/is-recipient-authorized';
|
||||||
import { viewedDocument } from '@documenso/lib/server-only/document/viewed-document';
|
import { viewedDocument } from '@documenso/lib/server-only/document/viewed-document';
|
||||||
import { getCompletedFieldsForToken } from '@documenso/lib/server-only/field/get-completed-fields-for-token';
|
|
||||||
import { getFieldsForToken } from '@documenso/lib/server-only/field/get-fields-for-token';
|
import { getFieldsForToken } from '@documenso/lib/server-only/field/get-fields-for-token';
|
||||||
import { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-recipient-by-token';
|
import { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-recipient-by-token';
|
||||||
import { getRecipientSignatures } from '@documenso/lib/server-only/recipient/get-recipient-signatures';
|
import { getRecipientSignatures } from '@documenso/lib/server-only/recipient/get-recipient-signatures';
|
||||||
@ -38,7 +37,7 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
|
|||||||
|
|
||||||
const requestMetadata = extractNextHeaderRequestMetadata(requestHeaders);
|
const requestMetadata = extractNextHeaderRequestMetadata(requestHeaders);
|
||||||
|
|
||||||
const [document, fields, recipient, completedFields] = await Promise.all([
|
const [document, fields, recipient] = await Promise.all([
|
||||||
getDocumentAndSenderByToken({
|
getDocumentAndSenderByToken({
|
||||||
token,
|
token,
|
||||||
userId: user?.id,
|
userId: user?.id,
|
||||||
@ -46,7 +45,6 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
|
|||||||
}).catch(() => null),
|
}).catch(() => null),
|
||||||
getFieldsForToken({ token }),
|
getFieldsForToken({ token }),
|
||||||
getRecipientByToken({ token }).catch(() => null),
|
getRecipientByToken({ token }).catch(() => null),
|
||||||
getCompletedFieldsForToken({ token }),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -127,12 +125,7 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
|
|||||||
signature={user?.email === recipient.email ? user.signature : undefined}
|
signature={user?.email === recipient.email ? user.signature : undefined}
|
||||||
>
|
>
|
||||||
<DocumentAuthProvider document={document} recipient={recipient} user={user}>
|
<DocumentAuthProvider document={document} recipient={recipient} user={user}>
|
||||||
<SigningPageView
|
<SigningPageView recipient={recipient} document={document} fields={fields} />
|
||||||
recipient={recipient}
|
|
||||||
document={document}
|
|
||||||
fields={fields}
|
|
||||||
completedFields={completedFields}
|
|
||||||
/>
|
|
||||||
</DocumentAuthProvider>
|
</DocumentAuthProvider>
|
||||||
</SigningProvider>
|
</SigningProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -4,14 +4,12 @@ import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-form
|
|||||||
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
||||||
import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
|
import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
|
||||||
import type { DocumentAndSender } from '@documenso/lib/server-only/document/get-document-by-token';
|
import type { DocumentAndSender } from '@documenso/lib/server-only/document/get-document-by-token';
|
||||||
import type { CompletedField } from '@documenso/lib/types/fields';
|
|
||||||
import type { Field, Recipient } from '@documenso/prisma/client';
|
import type { Field, Recipient } from '@documenso/prisma/client';
|
||||||
import { FieldType, RecipientRole } from '@documenso/prisma/client';
|
import { FieldType, RecipientRole } from '@documenso/prisma/client';
|
||||||
import { Card, CardContent } from '@documenso/ui/primitives/card';
|
import { Card, CardContent } from '@documenso/ui/primitives/card';
|
||||||
import { ElementVisible } from '@documenso/ui/primitives/element-visible';
|
import { ElementVisible } from '@documenso/ui/primitives/element-visible';
|
||||||
import { LazyPDFViewer } from '@documenso/ui/primitives/lazy-pdf-viewer';
|
import { LazyPDFViewer } from '@documenso/ui/primitives/lazy-pdf-viewer';
|
||||||
|
|
||||||
import { DocumentReadOnlyFields } from '~/components/document/document-read-only-fields';
|
|
||||||
import { truncateTitle } from '~/helpers/truncate-title';
|
import { truncateTitle } from '~/helpers/truncate-title';
|
||||||
|
|
||||||
import { DateField } from './date-field';
|
import { DateField } from './date-field';
|
||||||
@ -25,15 +23,9 @@ export type SigningPageViewProps = {
|
|||||||
document: DocumentAndSender;
|
document: DocumentAndSender;
|
||||||
recipient: Recipient;
|
recipient: Recipient;
|
||||||
fields: Field[];
|
fields: Field[];
|
||||||
completedFields: CompletedField[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SigningPageView = ({
|
export const SigningPageView = ({ document, recipient, fields }: SigningPageViewProps) => {
|
||||||
document,
|
|
||||||
recipient,
|
|
||||||
fields,
|
|
||||||
completedFields,
|
|
||||||
}: SigningPageViewProps) => {
|
|
||||||
const truncatedTitle = truncateTitle(document.title);
|
const truncatedTitle = truncateTitle(document.title);
|
||||||
|
|
||||||
const { documentData, documentMeta } = document;
|
const { documentData, documentMeta } = document;
|
||||||
@ -78,8 +70,6 @@ export const SigningPageView = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DocumentReadOnlyFields fields={completedFields} />
|
|
||||||
|
|
||||||
<ElementVisible target={PDF_VIEWER_PAGE_SELECTOR}>
|
<ElementVisible target={PDF_VIEWER_PAGE_SELECTOR}>
|
||||||
{fields.map((field) =>
|
{fields.map((field) =>
|
||||||
match(field.type)
|
match(field.type)
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { useRef, useState } from 'react';
|
||||||
|
|
||||||
import { getRecipientType } from '@documenso/lib/client-only/recipient-type';
|
import { getRecipientType } from '@documenso/lib/client-only/recipient-type';
|
||||||
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
|
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
|
||||||
import { recipientAbbreviation } from '@documenso/lib/utils/recipient-formatter';
|
import { recipientAbbreviation } from '@documenso/lib/utils/recipient-formatter';
|
||||||
import type { DocumentStatus, Recipient } from '@documenso/prisma/client';
|
import type { DocumentStatus, Recipient } from '@documenso/prisma/client';
|
||||||
import { PopoverHover } from '@documenso/ui/primitives/popover';
|
import { Popover, PopoverContent, PopoverTrigger } from '@documenso/ui/primitives/popover';
|
||||||
|
|
||||||
import { AvatarWithRecipient } from './avatar-with-recipient';
|
import { AvatarWithRecipient } from './avatar-with-recipient';
|
||||||
import { StackAvatar } from './stack-avatar';
|
import { StackAvatar } from './stack-avatar';
|
||||||
@ -23,6 +25,11 @@ export const StackAvatarsWithTooltip = ({
|
|||||||
position,
|
position,
|
||||||
children,
|
children,
|
||||||
}: StackAvatarsWithTooltipProps) => {
|
}: StackAvatarsWithTooltipProps) => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const isControlled = useRef(false);
|
||||||
|
const isMouseOverTimeout = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
const waitingRecipients = recipients.filter(
|
const waitingRecipients = recipients.filter(
|
||||||
(recipient) => getRecipientType(recipient) === 'waiting',
|
(recipient) => getRecipientType(recipient) === 'waiting',
|
||||||
);
|
);
|
||||||
@ -39,13 +46,55 @@ export const StackAvatarsWithTooltip = ({
|
|||||||
(recipient) => getRecipientType(recipient) === 'unsigned',
|
(recipient) => getRecipientType(recipient) === 'unsigned',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onMouseEnter = () => {
|
||||||
|
if (isMouseOverTimeout.current) {
|
||||||
|
clearTimeout(isMouseOverTimeout.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isControlled.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isMouseOverTimeout.current = setTimeout(() => {
|
||||||
|
setOpen((o) => (!o ? true : o));
|
||||||
|
}, 200);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMouseLeave = () => {
|
||||||
|
if (isMouseOverTimeout.current) {
|
||||||
|
clearTimeout(isMouseOverTimeout.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isControlled.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setOpen((o) => (o ? false : o));
|
||||||
|
}, 200);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onOpenChange = (newOpen: boolean) => {
|
||||||
|
isControlled.current = newOpen;
|
||||||
|
|
||||||
|
setOpen(newOpen);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PopoverHover
|
<Popover open={open} onOpenChange={onOpenChange}>
|
||||||
trigger={children || <StackAvatars recipients={recipients} />}
|
<PopoverTrigger
|
||||||
contentProps={{
|
className="flex cursor-pointer"
|
||||||
className: 'flex flex-col gap-y-5 py-2',
|
onMouseEnter={onMouseEnter}
|
||||||
side: position,
|
onMouseLeave={onMouseLeave}
|
||||||
}}
|
>
|
||||||
|
{children || <StackAvatars recipients={recipients} />}
|
||||||
|
</PopoverTrigger>
|
||||||
|
|
||||||
|
<PopoverContent
|
||||||
|
side={position}
|
||||||
|
onMouseEnter={onMouseEnter}
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
|
className="flex flex-col gap-y-5 py-2"
|
||||||
>
|
>
|
||||||
{completedRecipients.length > 0 && (
|
{completedRecipients.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
@ -107,6 +156,7 @@ export const StackAvatarsWithTooltip = ({
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</PopoverHover>
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,112 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { useState } from 'react';
|
|
||||||
|
|
||||||
import { P, match } from 'ts-pattern';
|
|
||||||
|
|
||||||
import {
|
|
||||||
DEFAULT_DOCUMENT_DATE_FORMAT,
|
|
||||||
convertToLocalSystemFormat,
|
|
||||||
} 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';
|
|
||||||
import type { CompletedField } from '@documenso/lib/types/fields';
|
|
||||||
import { extractInitials } from '@documenso/lib/utils/recipient-formatter';
|
|
||||||
import type { DocumentMeta } from '@documenso/prisma/client';
|
|
||||||
import { FieldType } from '@documenso/prisma/client';
|
|
||||||
import { FieldRootContainer } from '@documenso/ui/components/field/field';
|
|
||||||
import { Avatar, AvatarFallback } from '@documenso/ui/primitives/avatar';
|
|
||||||
import { Button } from '@documenso/ui/primitives/button';
|
|
||||||
import { FRIENDLY_FIELD_TYPE } from '@documenso/ui/primitives/document-flow/types';
|
|
||||||
import { ElementVisible } from '@documenso/ui/primitives/element-visible';
|
|
||||||
import { PopoverHover } from '@documenso/ui/primitives/popover';
|
|
||||||
|
|
||||||
export type DocumentReadOnlyFieldsProps = {
|
|
||||||
fields: CompletedField[];
|
|
||||||
documentMeta?: DocumentMeta;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DocumentReadOnlyFields = ({ documentMeta, fields }: DocumentReadOnlyFieldsProps) => {
|
|
||||||
const [hiddenFieldIds, setHiddenFieldIds] = useState<Record<string, boolean>>({});
|
|
||||||
|
|
||||||
const handleHideField = (fieldId: string) => {
|
|
||||||
setHiddenFieldIds((prev) => ({ ...prev, [fieldId]: true }));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ElementVisible target={PDF_VIEWER_PAGE_SELECTOR}>
|
|
||||||
{fields.map(
|
|
||||||
(field) =>
|
|
||||||
!hiddenFieldIds[field.secondaryId] && (
|
|
||||||
<FieldRootContainer
|
|
||||||
field={field}
|
|
||||||
key={field.id}
|
|
||||||
cardClassName="border-gray-100/50 !shadow-none backdrop-blur-[1px] bg-background/90"
|
|
||||||
>
|
|
||||||
<div className="absolute -right-3 -top-3">
|
|
||||||
<PopoverHover
|
|
||||||
trigger={
|
|
||||||
<Avatar className="dark:border-border h-8 w-8 border-2 border-solid border-gray-200/50 transition-colors hover:border-gray-200">
|
|
||||||
<AvatarFallback className="bg-neutral-50 text-xs text-gray-400">
|
|
||||||
{extractInitials(field.Recipient.name || field.Recipient.email)}
|
|
||||||
</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
}
|
|
||||||
contentProps={{
|
|
||||||
className: 'flex w-fit flex-col py-2.5 text-sm',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
<span className="font-semibold">
|
|
||||||
{field.Recipient.name
|
|
||||||
? `${field.Recipient.name} (${field.Recipient.email})`
|
|
||||||
: field.Recipient.email}{' '}
|
|
||||||
</span>
|
|
||||||
inserted a {FRIENDLY_FIELD_TYPE[field.type].toLowerCase()}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
className="mt-2.5 h-6 text-xs focus:outline-none focus-visible:ring-0"
|
|
||||||
onClick={() => handleHideField(field.secondaryId)}
|
|
||||||
>
|
|
||||||
Hide field
|
|
||||||
</Button>
|
|
||||||
</PopoverHover>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="text-muted-foreground break-all text-sm">
|
|
||||||
{match(field)
|
|
||||||
.with({ type: FieldType.SIGNATURE }, (field) =>
|
|
||||||
field.Signature?.signatureImageAsBase64 ? (
|
|
||||||
<img
|
|
||||||
src={field.Signature.signatureImageAsBase64}
|
|
||||||
alt="Signature"
|
|
||||||
className="h-full w-full object-contain dark:invert"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<p className="font-signature text-muted-foreground text-lg duration-200 sm:text-xl md:text-2xl lg:text-3xl">
|
|
||||||
{field.Signature?.typedSignature}
|
|
||||||
</p>
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.with(
|
|
||||||
{ type: P.union(FieldType.NAME, FieldType.TEXT, FieldType.EMAIL) },
|
|
||||||
() => field.customText,
|
|
||||||
)
|
|
||||||
.with({ type: FieldType.DATE }, () =>
|
|
||||||
convertToLocalSystemFormat(
|
|
||||||
field.customText,
|
|
||||||
documentMeta?.dateFormat ?? DEFAULT_DOCUMENT_DATE_FORMAT,
|
|
||||||
documentMeta?.timezone ?? DEFAULT_DOCUMENT_TIME_ZONE,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.with({ type: FieldType.FREE_SIGNATURE }, () => null)
|
|
||||||
.exhaustive()}
|
|
||||||
</div>
|
|
||||||
</FieldRootContainer>
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
</ElementVisible>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -3,7 +3,7 @@ import { createTrpcContext } from '@documenso/trpc/server/context';
|
|||||||
import { appRouter } from '@documenso/trpc/server/router';
|
import { appRouter } from '@documenso/trpc/server/router';
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
maxDuration: 120,
|
maxDuration: 60,
|
||||||
api: {
|
api: {
|
||||||
bodyParser: {
|
bodyParser: {
|
||||||
sizeLimit: '50mb',
|
sizeLimit: '50mb',
|
||||||
|
|||||||
96
package-lock.json
generated
96
package-lock.json
generated
@ -22,7 +22,7 @@
|
|||||||
"eslint-config-custom": "*",
|
"eslint-config-custom": "*",
|
||||||
"husky": "^9.0.11",
|
"husky": "^9.0.11",
|
||||||
"lint-staged": "^15.2.2",
|
"lint-staged": "^15.2.2",
|
||||||
"playwright": "1.43.0",
|
"playwright": "1.41.0",
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.5.1",
|
||||||
"rimraf": "^5.0.1",
|
"rimraf": "^5.0.1",
|
||||||
"turbo": "^1.9.3"
|
"turbo": "^1.9.3"
|
||||||
@ -4702,26 +4702,13 @@
|
|||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@playwright/browser-chromium": {
|
|
||||||
"version": "1.43.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@playwright/browser-chromium/-/browser-chromium-1.43.0.tgz",
|
|
||||||
"integrity": "sha512-F0S4KIqSqQqm9EgsdtWjaJRpgP8cD2vWZHPSB41YI00PtXUobiv/3AnYISeL7wNuTanND7giaXQ4SIjkcIq3KQ==",
|
|
||||||
"dev": true,
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"dependencies": {
|
|
||||||
"playwright-core": "1.43.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=16"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@playwright/test": {
|
"node_modules/@playwright/test": {
|
||||||
"version": "1.43.1",
|
"version": "1.40.0",
|
||||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.43.1.tgz",
|
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.40.0.tgz",
|
||||||
"integrity": "sha512-HgtQzFgNEEo4TE22K/X7sYTYNqEMMTZmFS8kTq6m8hXj+m1D8TgwgIbumHddJa9h4yl4GkKb8/bgAl2+g7eDgA==",
|
"integrity": "sha512-PdW+kn4eV99iP5gxWNSDQCbhMaDVej+RXL5xr6t04nbKLCBwYtA046t7ofoczHOm8u6c+45hpDKQVZqtqwkeQg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright": "1.43.1"
|
"playwright": "1.40.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js"
|
"playwright": "cli.js"
|
||||||
@ -4745,12 +4732,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@playwright/test/node_modules/playwright": {
|
"node_modules/@playwright/test/node_modules/playwright": {
|
||||||
"version": "1.43.1",
|
"version": "1.40.0",
|
||||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.43.1.tgz",
|
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.40.0.tgz",
|
||||||
"integrity": "sha512-V7SoH0ai2kNt1Md9E3Gwas5B9m8KR2GVvwZnAI6Pg0m3sh7UvgiYhRrhsziCmqMJNouPckiOhk8T+9bSAK0VIA==",
|
"integrity": "sha512-gyHAgQjiDf1m34Xpwzaqb76KgfzYrhK7iih+2IzcOCoZWr/8ZqmdBw+t0RU85ZmfJMgtgAiNtBQ/KS2325INXw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.43.1"
|
"playwright-core": "1.40.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js"
|
"playwright": "cli.js"
|
||||||
@ -4763,9 +4750,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@playwright/test/node_modules/playwright-core": {
|
"node_modules/@playwright/test/node_modules/playwright-core": {
|
||||||
"version": "1.43.1",
|
"version": "1.40.0",
|
||||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.43.1.tgz",
|
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.40.0.tgz",
|
||||||
"integrity": "sha512-EI36Mto2Vrx6VF7rm708qSnesVQKbxEWvPrfA1IPY6HgczBplDx7ENtx+K2n4kJ41sLLkuGfmb0ZLSSXlDhqPg==",
|
"integrity": "sha512-fvKewVJpGeca8t0ipM56jkVSU6Eo0RmFvQ/MaCQNDYm+sdvKkMBBWTE1FdeMqIdumRaXXjZChWHvIzCGM/tA/Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright-core": "cli.js"
|
"playwright-core": "cli.js"
|
||||||
@ -17673,11 +17660,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/playwright": {
|
"node_modules/playwright": {
|
||||||
"version": "1.43.0",
|
"version": "1.41.0",
|
||||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.43.0.tgz",
|
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.41.0.tgz",
|
||||||
"integrity": "sha512-SiOKHbVjTSf6wHuGCbqrEyzlm6qvXcv7mENP+OZon1I07brfZLGdfWV0l/efAzVx7TF3Z45ov1gPEkku9q25YQ==",
|
"integrity": "sha512-XOsfl5ZtAik/T9oek4V0jAypNlaCNzuKOwVhqhgYT3os6kH34PzbRb74F0VWcLYa5WFdnmxl7qyAHBXvPv7lqQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.43.0"
|
"playwright-core": "1.41.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js"
|
"playwright": "cli.js"
|
||||||
@ -17689,17 +17676,6 @@
|
|||||||
"fsevents": "2.3.2"
|
"fsevents": "2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/playwright-core": {
|
|
||||||
"version": "1.43.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.43.0.tgz",
|
|
||||||
"integrity": "sha512-iWFjyBUH97+pUFiyTqSLd8cDMMOS0r2ZYz2qEsPjH8/bX++sbIJT35MSwKnp1r/OQBAqC5XO99xFbJ9XClhf4w==",
|
|
||||||
"bin": {
|
|
||||||
"playwright-core": "cli.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=16"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/playwright/node_modules/fsevents": {
|
"node_modules/playwright/node_modules/fsevents": {
|
||||||
"version": "2.3.2",
|
"version": "2.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||||
@ -17713,6 +17689,17 @@
|
|||||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/playwright/node_modules/playwright-core": {
|
||||||
|
"version": "1.41.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.41.0.tgz",
|
||||||
|
"integrity": "sha512-UGKASUhXmvqm2Lxa1fNr8sFwAtqjpgBRr9jQ7XBI8Rn5uFiEowGUGwrruUQsVPIom4bk7Lt+oLGpXobnXzrBIw==",
|
||||||
|
"bin": {
|
||||||
|
"playwright-core": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/possible-typed-array-names": {
|
"node_modules/possible-typed-array-names": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",
|
||||||
@ -24981,7 +24968,7 @@
|
|||||||
"next-auth": "4.24.5",
|
"next-auth": "4.24.5",
|
||||||
"oslo": "^0.17.0",
|
"oslo": "^0.17.0",
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
"playwright": "1.43.0",
|
"playwright": "1.41.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"remeda": "^1.27.1",
|
"remeda": "^1.27.1",
|
||||||
"stripe": "^12.7.0",
|
"stripe": "^12.7.0",
|
||||||
@ -24989,10 +24976,23 @@
|
|||||||
"zod": "^3.22.4"
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/browser-chromium": "1.43.0",
|
"@playwright/browser-chromium": "1.41.0",
|
||||||
"@types/luxon": "^3.3.1"
|
"@types/luxon": "^3.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"packages/lib/node_modules/@playwright/browser-chromium": {
|
||||||
|
"version": "1.41.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@playwright/browser-chromium/-/browser-chromium-1.41.0.tgz",
|
||||||
|
"integrity": "sha512-TaHfh3rDsz4+tVKdMMo4kdFOk8/4U6cPyMXHhoiJVmhOhjHXjR0qPMoa5gz5jDGl478cn5SoXmtgKPgTDFuS0g==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"dependencies": {
|
||||||
|
"playwright-core": "1.41.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
"packages/lib/node_modules/nanoid": {
|
"packages/lib/node_modules/nanoid": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-4.0.2.tgz",
|
||||||
@ -25010,6 +25010,18 @@
|
|||||||
"node": "^14 || ^16 || >=18"
|
"node": "^14 || ^16 || >=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"packages/lib/node_modules/playwright-core": {
|
||||||
|
"version": "1.41.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.41.0.tgz",
|
||||||
|
"integrity": "sha512-UGKASUhXmvqm2Lxa1fNr8sFwAtqjpgBRr9jQ7XBI8Rn5uFiEowGUGwrruUQsVPIom4bk7Lt+oLGpXobnXzrBIw==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"playwright-core": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
"packages/prettier-config": {
|
"packages/prettier-config": {
|
||||||
"name": "@documenso/prettier-config",
|
"name": "@documenso/prettier-config",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
|
|||||||
@ -38,7 +38,7 @@
|
|||||||
"eslint-config-custom": "*",
|
"eslint-config-custom": "*",
|
||||||
"husky": "^9.0.11",
|
"husky": "^9.0.11",
|
||||||
"lint-staged": "^15.2.2",
|
"lint-staged": "^15.2.2",
|
||||||
"playwright": "1.43.0",
|
"playwright": "1.41.0",
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.5.1",
|
||||||
"rimraf": "^5.0.1",
|
"rimraf": "^5.0.1",
|
||||||
"turbo": "^1.9.3"
|
"turbo": "^1.9.3"
|
||||||
|
|||||||
@ -39,7 +39,7 @@
|
|||||||
"next-auth": "4.24.5",
|
"next-auth": "4.24.5",
|
||||||
"oslo": "^0.17.0",
|
"oslo": "^0.17.0",
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
"playwright": "1.43.0",
|
"playwright": "1.41.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"remeda": "^1.27.1",
|
"remeda": "^1.27.1",
|
||||||
"stripe": "^12.7.0",
|
"stripe": "^12.7.0",
|
||||||
@ -48,6 +48,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/luxon": "^3.3.1",
|
"@types/luxon": "^3.3.1",
|
||||||
"@playwright/browser-chromium": "1.43.0"
|
"@playwright/browser-chromium": "1.41.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4,8 +4,6 @@ import { mailer } from '@documenso/email/mailer';
|
|||||||
import { render } from '@documenso/email/render';
|
import { render } from '@documenso/email/render';
|
||||||
import { DocumentInviteEmailTemplate } from '@documenso/email/templates/document-invite';
|
import { DocumentInviteEmailTemplate } from '@documenso/email/templates/document-invite';
|
||||||
import { FROM_ADDRESS, FROM_NAME } from '@documenso/lib/constants/email';
|
import { FROM_ADDRESS, FROM_NAME } from '@documenso/lib/constants/email';
|
||||||
import { sealDocument } from '@documenso/lib/server-only/document/seal-document';
|
|
||||||
import { updateDocument } from '@documenso/lib/server-only/document/update-document';
|
|
||||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||||
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||||
@ -213,31 +211,6 @@ export const sendDocument = async ({
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const allRecipientsHaveNoActionToTake = document.Recipient.every(
|
|
||||||
(recipient) => recipient.role === RecipientRole.CC,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (allRecipientsHaveNoActionToTake) {
|
|
||||||
const updatedDocument = await updateDocument({
|
|
||||||
documentId,
|
|
||||||
userId,
|
|
||||||
teamId,
|
|
||||||
data: { status: DocumentStatus.COMPLETED },
|
|
||||||
});
|
|
||||||
|
|
||||||
await sealDocument({ documentId: updatedDocument.id, requestMetadata });
|
|
||||||
|
|
||||||
// Keep the return type the same for the `sendDocument` method
|
|
||||||
return await prisma.document.findFirstOrThrow({
|
|
||||||
where: {
|
|
||||||
id: documentId,
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
Recipient: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedDocument = await prisma.$transaction(async (tx) => {
|
const updatedDocument = await prisma.$transaction(async (tx) => {
|
||||||
if (document.status === DocumentStatus.DRAFT) {
|
if (document.status === DocumentStatus.DRAFT) {
|
||||||
await tx.documentAuditLog.create({
|
await tx.documentAuditLog.create({
|
||||||
|
|||||||
@ -1,29 +0,0 @@
|
|||||||
import { prisma } from '@documenso/prisma';
|
|
||||||
import { SigningStatus } from '@documenso/prisma/client';
|
|
||||||
|
|
||||||
export type GetCompletedFieldsForDocumentOptions = {
|
|
||||||
documentId: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getCompletedFieldsForDocument = async ({
|
|
||||||
documentId,
|
|
||||||
}: GetCompletedFieldsForDocumentOptions) => {
|
|
||||||
return await prisma.field.findMany({
|
|
||||||
where: {
|
|
||||||
documentId,
|
|
||||||
Recipient: {
|
|
||||||
signingStatus: SigningStatus.SIGNED,
|
|
||||||
},
|
|
||||||
inserted: true,
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
Signature: true,
|
|
||||||
Recipient: {
|
|
||||||
select: {
|
|
||||||
name: true,
|
|
||||||
email: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
import { prisma } from '@documenso/prisma';
|
|
||||||
import { SigningStatus } from '@documenso/prisma/client';
|
|
||||||
|
|
||||||
export type GetCompletedFieldsForTokenOptions = {
|
|
||||||
token: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getCompletedFieldsForToken = async ({ token }: GetCompletedFieldsForTokenOptions) => {
|
|
||||||
return await prisma.field.findMany({
|
|
||||||
where: {
|
|
||||||
Document: {
|
|
||||||
Recipient: {
|
|
||||||
some: {
|
|
||||||
token,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Recipient: {
|
|
||||||
signingStatus: SigningStatus.SIGNED,
|
|
||||||
},
|
|
||||||
inserted: true,
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
Signature: true,
|
|
||||||
Recipient: {
|
|
||||||
select: {
|
|
||||||
name: true,
|
|
||||||
email: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -18,9 +18,7 @@ export const getCertificatePdf = async ({ documentId }: GetCertificatePdfOptions
|
|||||||
let browser: Browser;
|
let browser: Browser;
|
||||||
|
|
||||||
if (process.env.NEXT_PRIVATE_BROWSERLESS_URL) {
|
if (process.env.NEXT_PRIVATE_BROWSERLESS_URL) {
|
||||||
// !: Use CDP rather than the default `connect` method to avoid coupling to the playwright version.
|
browser = await chromium.connect(process.env.NEXT_PRIVATE_BROWSERLESS_URL);
|
||||||
// !: Previously we would have to keep the playwright version in sync with the browserless version to avoid errors.
|
|
||||||
browser = await chromium.connectOverCDP(process.env.NEXT_PRIVATE_BROWSERLESS_URL);
|
|
||||||
} else {
|
} else {
|
||||||
browser = await chromium.launch();
|
browser = await chromium.launch();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -89,10 +89,6 @@ export const createDocumentFromTemplate = async ({
|
|||||||
|
|
||||||
const documentRecipient = document.Recipient.find((doc) => doc.email === recipient?.email);
|
const documentRecipient = document.Recipient.find((doc) => doc.email === recipient?.email);
|
||||||
|
|
||||||
if (!documentRecipient) {
|
|
||||||
throw new Error('Recipient not found.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: field.type,
|
type: field.type,
|
||||||
page: field.page,
|
page: field.page,
|
||||||
@ -103,7 +99,7 @@ export const createDocumentFromTemplate = async ({
|
|||||||
customText: field.customText,
|
customText: field.customText,
|
||||||
inserted: field.inserted,
|
inserted: field.inserted,
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
recipientId: documentRecipient.id,
|
recipientId: documentRecipient?.id || null,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -81,10 +81,6 @@ export const duplicateTemplate = async ({
|
|||||||
(doc) => doc.email === recipient?.email,
|
(doc) => doc.email === recipient?.email,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!duplicatedTemplateRecipient) {
|
|
||||||
throw new Error('Recipient not found.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: field.type,
|
type: field.type,
|
||||||
page: field.page,
|
page: field.page,
|
||||||
@ -95,7 +91,7 @@ export const duplicateTemplate = async ({
|
|||||||
customText: field.customText,
|
customText: field.customText,
|
||||||
inserted: field.inserted,
|
inserted: field.inserted,
|
||||||
templateId: duplicatedTemplate.id,
|
templateId: duplicatedTemplate.id,
|
||||||
recipientId: duplicatedTemplateRecipient.id,
|
recipientId: duplicatedTemplateRecipient?.id || null,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -4,7 +4,6 @@ export const getWebhooksByUserId = async (userId: number) => {
|
|||||||
return await prisma.webhook.findMany({
|
return await prisma.webhook.findMany({
|
||||||
where: {
|
where: {
|
||||||
userId,
|
userId,
|
||||||
teamId: null,
|
|
||||||
},
|
},
|
||||||
orderBy: {
|
orderBy: {
|
||||||
createdAt: 'desc',
|
createdAt: 'desc',
|
||||||
|
|||||||
@ -1,3 +0,0 @@
|
|||||||
import type { getCompletedFieldsForToken } from '../server-only/field/get-completed-fields-for-token';
|
|
||||||
|
|
||||||
export type CompletedField = Awaited<ReturnType<typeof getCompletedFieldsForToken>>[number];
|
|
||||||
@ -17,7 +17,6 @@ export const getFlag = async (
|
|||||||
options?: GetFlagOptions,
|
options?: GetFlagOptions,
|
||||||
): Promise<TFeatureFlagValue> => {
|
): Promise<TFeatureFlagValue> => {
|
||||||
const requestHeaders = options?.requestHeaders ?? {};
|
const requestHeaders = options?.requestHeaders ?? {};
|
||||||
delete requestHeaders['content-length'];
|
|
||||||
|
|
||||||
if (!isFeatureFlagEnabled()) {
|
if (!isFeatureFlagEnabled()) {
|
||||||
return LOCAL_FEATURE_FLAGS[flag] ?? true;
|
return LOCAL_FEATURE_FLAGS[flag] ?? true;
|
||||||
@ -26,7 +25,7 @@ export const getFlag = async (
|
|||||||
const url = new URL(`${APP_BASE_URL()}/api/feature-flag/get`);
|
const url = new URL(`${APP_BASE_URL()}/api/feature-flag/get`);
|
||||||
url.searchParams.set('flag', flag);
|
url.searchParams.set('flag', flag);
|
||||||
|
|
||||||
return await fetch(url, {
|
const response = await fetch(url, {
|
||||||
headers: {
|
headers: {
|
||||||
...requestHeaders,
|
...requestHeaders,
|
||||||
},
|
},
|
||||||
@ -36,10 +35,9 @@ export const getFlag = async (
|
|||||||
})
|
})
|
||||||
.then(async (res) => res.json())
|
.then(async (res) => res.json())
|
||||||
.then((res) => ZFeatureFlagValueSchema.parse(res))
|
.then((res) => ZFeatureFlagValueSchema.parse(res))
|
||||||
.catch((err) => {
|
.catch(() => false);
|
||||||
console.error(err);
|
|
||||||
return LOCAL_FEATURE_FLAGS[flag] ?? false;
|
return response;
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -52,7 +50,6 @@ export const getAllFlags = async (
|
|||||||
options?: GetFlagOptions,
|
options?: GetFlagOptions,
|
||||||
): Promise<Record<string, TFeatureFlagValue>> => {
|
): Promise<Record<string, TFeatureFlagValue>> => {
|
||||||
const requestHeaders = options?.requestHeaders ?? {};
|
const requestHeaders = options?.requestHeaders ?? {};
|
||||||
delete requestHeaders['content-length'];
|
|
||||||
|
|
||||||
if (!isFeatureFlagEnabled()) {
|
if (!isFeatureFlagEnabled()) {
|
||||||
return LOCAL_FEATURE_FLAGS;
|
return LOCAL_FEATURE_FLAGS;
|
||||||
@ -70,10 +67,7 @@ export const getAllFlags = async (
|
|||||||
})
|
})
|
||||||
.then(async (res) => res.json())
|
.then(async (res) => res.json())
|
||||||
.then((res) => z.record(z.string(), ZFeatureFlagValueSchema).parse(res))
|
.then((res) => z.record(z.string(), ZFeatureFlagValueSchema).parse(res))
|
||||||
.catch((err) => {
|
.catch(() => LOCAL_FEATURE_FLAGS);
|
||||||
console.error(err);
|
|
||||||
return LOCAL_FEATURE_FLAGS;
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -95,10 +89,7 @@ export const getAllAnonymousFlags = async (): Promise<Record<string, TFeatureFla
|
|||||||
})
|
})
|
||||||
.then(async (res) => res.json())
|
.then(async (res) => res.json())
|
||||||
.then((res) => z.record(z.string(), ZFeatureFlagValueSchema).parse(res))
|
.then((res) => z.record(z.string(), ZFeatureFlagValueSchema).parse(res))
|
||||||
.catch((err) => {
|
.catch(() => LOCAL_FEATURE_FLAGS);
|
||||||
console.error(err);
|
|
||||||
return LOCAL_FEATURE_FLAGS;
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
interface GetFlagOptions {
|
interface GetFlagOptions {
|
||||||
|
|||||||
@ -1,11 +0,0 @@
|
|||||||
/*
|
|
||||||
Warnings:
|
|
||||||
|
|
||||||
- Made the column `recipientId` on table `Field` required. This step will fail if there are existing NULL values in that column.
|
|
||||||
|
|
||||||
*/
|
|
||||||
-- Drop all Fields where the recipientId is null
|
|
||||||
DELETE FROM "Field" WHERE "recipientId" IS NULL;
|
|
||||||
|
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "Field" ALTER COLUMN "recipientId" SET NOT NULL;
|
|
||||||
@ -387,7 +387,7 @@ model Field {
|
|||||||
secondaryId String @unique @default(cuid())
|
secondaryId String @unique @default(cuid())
|
||||||
documentId Int?
|
documentId Int?
|
||||||
templateId Int?
|
templateId Int?
|
||||||
recipientId Int
|
recipientId Int?
|
||||||
type FieldType
|
type FieldType
|
||||||
page Int
|
page Int
|
||||||
positionX Decimal @default(0)
|
positionX Decimal @default(0)
|
||||||
@ -398,7 +398,7 @@ model Field {
|
|||||||
inserted Boolean
|
inserted Boolean
|
||||||
Document Document? @relation(fields: [documentId], references: [id], onDelete: Cascade)
|
Document Document? @relation(fields: [documentId], references: [id], onDelete: Cascade)
|
||||||
Template Template? @relation(fields: [templateId], references: [id], onDelete: Cascade)
|
Template Template? @relation(fields: [templateId], references: [id], onDelete: Cascade)
|
||||||
Recipient Recipient @relation(fields: [recipientId], references: [id], onDelete: Cascade)
|
Recipient Recipient? @relation(fields: [recipientId], references: [id], onDelete: Cascade)
|
||||||
Signature Signature?
|
Signature Signature?
|
||||||
|
|
||||||
@@index([documentId])
|
@@index([documentId])
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import { DateTime } from 'luxon';
|
|||||||
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
|
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
|
||||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||||
import { DOCUMENSO_ENCRYPTION_KEY } from '@documenso/lib/constants/crypto';
|
import { DOCUMENSO_ENCRYPTION_KEY } from '@documenso/lib/constants/crypto';
|
||||||
import { AppError } from '@documenso/lib/errors/app-error';
|
|
||||||
import { encryptSecondaryData } from '@documenso/lib/server-only/crypto/encrypt';
|
import { encryptSecondaryData } from '@documenso/lib/server-only/crypto/encrypt';
|
||||||
import { upsertDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta';
|
import { upsertDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta';
|
||||||
import { createDocument } from '@documenso/lib/server-only/document/create-document';
|
import { createDocument } from '@documenso/lib/server-only/document/create-document';
|
||||||
@ -21,7 +20,6 @@ import { updateDocumentSettings } from '@documenso/lib/server-only/document/upda
|
|||||||
import { updateTitle } from '@documenso/lib/server-only/document/update-title';
|
import { updateTitle } from '@documenso/lib/server-only/document/update-title';
|
||||||
import { symmetricEncrypt } from '@documenso/lib/universal/crypto';
|
import { symmetricEncrypt } from '@documenso/lib/universal/crypto';
|
||||||
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
import { DocumentStatus } from '@documenso/prisma/client';
|
|
||||||
|
|
||||||
import { authenticatedProcedure, procedure, router } from '../trpc';
|
import { authenticatedProcedure, procedure, router } from '../trpc';
|
||||||
import {
|
import {
|
||||||
@ -223,6 +221,10 @@ export const documentRouter = router({
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
getDocumentMetaById: authenticatedProcedure
|
||||||
|
.input(ZSetSettingsForDocumentMutationSchema)
|
||||||
|
.mutation(async ({ input, ctx }) => {}),
|
||||||
|
|
||||||
setTitleForDocument: authenticatedProcedure
|
setTitleForDocument: authenticatedProcedure
|
||||||
.input(ZSetTitleForDocumentMutationSchema)
|
.input(ZSetTitleForDocumentMutationSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
@ -415,10 +417,6 @@ export const documentRouter = router({
|
|||||||
teamId,
|
teamId,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (document.status !== DocumentStatus.COMPLETED) {
|
|
||||||
throw new AppError('DOCUMENT_NOT_COMPLETE');
|
|
||||||
}
|
|
||||||
|
|
||||||
const encrypted = encryptSecondaryData({
|
const encrypted = encryptSecondaryData({
|
||||||
data: document.id.toString(),
|
data: document.id.toString(),
|
||||||
expiresAt: DateTime.now().plus({ minutes: 5 }).toJSDate().valueOf(),
|
expiresAt: DateTime.now().plus({ minutes: 5 }).toJSDate().valueOf(),
|
||||||
|
|||||||
@ -19,7 +19,6 @@ export type FieldContainerPortalProps = {
|
|||||||
field: Field;
|
field: Field;
|
||||||
className?: string;
|
className?: string;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
cardClassName?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function FieldContainerPortal({
|
export function FieldContainerPortal({
|
||||||
@ -45,7 +44,7 @@ export function FieldContainerPortal({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FieldRootContainer({ field, children, cardClassName }: FieldContainerPortalProps) {
|
export function FieldRootContainer({ field, children }: FieldContainerPortalProps) {
|
||||||
const [isValidating, setIsValidating] = useState(false);
|
const [isValidating, setIsValidating] = useState(false);
|
||||||
|
|
||||||
const ref = React.useRef<HTMLDivElement>(null);
|
const ref = React.useRef<HTMLDivElement>(null);
|
||||||
@ -79,7 +78,6 @@ export function FieldRootContainer({ field, children, cardClassName }: FieldCont
|
|||||||
{
|
{
|
||||||
'border-orange-300 ring-1 ring-orange-300': !field.inserted && isValidating,
|
'border-orange-300 ring-1 ring-orange-300': !field.inserted && isValidating,
|
||||||
},
|
},
|
||||||
cardClassName,
|
|
||||||
)}
|
)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
data-inserted={field.inserted ? 'true' : 'false'}
|
data-inserted={field.inserted ? 'true' : 'false'}
|
||||||
|
|||||||
@ -9,11 +9,7 @@ import { useForm } from 'react-hook-form';
|
|||||||
import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
|
||||||
import { DOCUMENT_AUTH_TYPES } from '@documenso/lib/constants/document-auth';
|
import { DOCUMENT_AUTH_TYPES } from '@documenso/lib/constants/document-auth';
|
||||||
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
||||||
import {
|
import { DocumentAccessAuth, DocumentActionAuth } from '@documenso/lib/types/document-auth';
|
||||||
DocumentAccessAuth,
|
|
||||||
DocumentActionAuth,
|
|
||||||
DocumentAuth,
|
|
||||||
} from '@documenso/lib/types/document-auth';
|
|
||||||
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
|
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
|
||||||
import { DocumentStatus, type Field, type Recipient, SendStatus } from '@documenso/prisma/client';
|
import { DocumentStatus, type Field, type Recipient, SendStatus } from '@documenso/prisma/client';
|
||||||
import type { DocumentWithData } from '@documenso/prisma/types/document-with-data';
|
import type { DocumentWithData } from '@documenso/prisma/types/document-with-data';
|
||||||
@ -220,9 +216,9 @@ export const AddSettingsFormPartial = ({
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<ul className="ml-3.5 list-outside list-disc space-y-0.5 py-2">
|
<ul className="ml-3.5 list-outside list-disc space-y-0.5 py-2">
|
||||||
{/* <li>
|
<li>
|
||||||
<strong>Require account</strong> - The recipient must be signed in
|
<strong>Require account</strong> - The recipient must be signed in
|
||||||
</li> */}
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong>Require passkey</strong> - The recipient must have an account
|
<strong>Require passkey</strong> - The recipient must have an account
|
||||||
and passkey configured via their settings
|
and passkey configured via their settings
|
||||||
@ -246,9 +242,7 @@ export const AddSettingsFormPartial = ({
|
|||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
|
|
||||||
<SelectContent position="popper">
|
<SelectContent position="popper">
|
||||||
{Object.values(DocumentActionAuth)
|
{Object.values(DocumentActionAuth).map((authType) => (
|
||||||
.filter((auth) => auth !== DocumentAuth.ACCOUNT)
|
|
||||||
.map((authType) => (
|
|
||||||
<SelectItem key={authType} value={authType}>
|
<SelectItem key={authType} value={authType}>
|
||||||
{DOCUMENT_AUTH_TYPES[authType].value}
|
{DOCUMENT_AUTH_TYPES[authType].value}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
|
|||||||
@ -28,6 +28,8 @@ export const ZAddSettingsFormSchema = z.object({
|
|||||||
ZDocumentActionAuthTypesSchema.optional(),
|
ZDocumentActionAuthTypesSchema.optional(),
|
||||||
),
|
),
|
||||||
meta: z.object({
|
meta: z.object({
|
||||||
|
subject: z.string().optional(),
|
||||||
|
message: z.string().optional(),
|
||||||
timezone: z.string().optional().default(DEFAULT_DOCUMENT_TIME_ZONE),
|
timezone: z.string().optional().default(DEFAULT_DOCUMENT_TIME_ZONE),
|
||||||
dateFormat: z.string().optional().default(DEFAULT_DOCUMENT_DATE_FORMAT),
|
dateFormat: z.string().optional().default(DEFAULT_DOCUMENT_DATE_FORMAT),
|
||||||
redirectUrl: z
|
redirectUrl: z
|
||||||
|
|||||||
@ -302,10 +302,10 @@ export const AddSignersFormPartial = ({
|
|||||||
global action signing authentication method configured in
|
global action signing authentication method configured in
|
||||||
the "General Settings" step
|
the "General Settings" step
|
||||||
</li>
|
</li>
|
||||||
{/* <li>
|
<li>
|
||||||
<strong>Require account</strong> - The recipient must be
|
<strong>Require account</strong> - The recipient must be
|
||||||
signed in
|
signed in
|
||||||
</li> */}
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong>Require passkey</strong> - The recipient must have
|
<strong>Require passkey</strong> - The recipient must have
|
||||||
an account and passkey configured via their settings
|
an account and passkey configured via their settings
|
||||||
@ -326,9 +326,7 @@ export const AddSignersFormPartial = ({
|
|||||||
{/* Note: -1 is remapped in the Zod schema to the required value. */}
|
{/* Note: -1 is remapped in the Zod schema to the required value. */}
|
||||||
<SelectItem value="-1">Inherit authentication method</SelectItem>
|
<SelectItem value="-1">Inherit authentication method</SelectItem>
|
||||||
|
|
||||||
{Object.values(RecipientActionAuth)
|
{Object.values(RecipientActionAuth).map((authType) => (
|
||||||
.filter((auth) => auth !== RecipientActionAuth.ACCOUNT)
|
|
||||||
.map((authType) => (
|
|
||||||
<SelectItem key={authType} value={authType}>
|
<SelectItem key={authType} value={authType}>
|
||||||
{DOCUMENT_AUTH_TYPES[authType].value}
|
{DOCUMENT_AUTH_TYPES[authType].value}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
|
|
||||||
@ -30,6 +32,8 @@ export type AddSubjectFormProps = {
|
|||||||
document: DocumentWithData;
|
document: DocumentWithData;
|
||||||
onSubmit: (_data: TAddSubjectFormSchema) => void;
|
onSubmit: (_data: TAddSubjectFormSchema) => void;
|
||||||
isDocumentPdfLoaded: boolean;
|
isDocumentPdfLoaded: boolean;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
setSubjectFormFields: (subject?: string, message?: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AddSubjectFormPartial = ({
|
export const AddSubjectFormPartial = ({
|
||||||
@ -39,10 +43,12 @@ export const AddSubjectFormPartial = ({
|
|||||||
document,
|
document,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
isDocumentPdfLoaded,
|
isDocumentPdfLoaded,
|
||||||
|
setSubjectFormFields,
|
||||||
}: AddSubjectFormProps) => {
|
}: AddSubjectFormProps) => {
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
|
getValues,
|
||||||
formState: { errors, isSubmitting },
|
formState: { errors, isSubmitting },
|
||||||
} = useForm<TAddSubjectFormSchema>({
|
} = useForm<TAddSubjectFormSchema>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@ -57,6 +63,13 @@ export const AddSubjectFormPartial = ({
|
|||||||
const onFormSubmit = handleSubmit(onSubmit);
|
const onFormSubmit = handleSubmit(onSubmit);
|
||||||
const { currentStep, totalSteps, previousStep } = useStep();
|
const { currentStep, totalSteps, previousStep } = useStep();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
const { meta } = getValues();
|
||||||
|
setSubjectFormFields(meta.subject, meta.message);
|
||||||
|
};
|
||||||
|
}, [getValues, setSubjectFormFields]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DocumentFlowFormContainerHeader
|
<DocumentFlowFormContainerHeader
|
||||||
|
|||||||
@ -30,66 +30,4 @@ const PopoverContent = React.forwardRef<
|
|||||||
|
|
||||||
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
|
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
|
||||||
|
|
||||||
type PopoverHoverProps = {
|
export { Popover, PopoverTrigger, PopoverContent };
|
||||||
trigger: React.ReactNode;
|
|
||||||
children: React.ReactNode;
|
|
||||||
contentProps?: React.ComponentPropsWithoutRef<typeof PopoverContent>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const PopoverHover = ({ trigger, children, contentProps }: PopoverHoverProps) => {
|
|
||||||
const [open, setOpen] = React.useState(false);
|
|
||||||
|
|
||||||
const isControlled = React.useRef(false);
|
|
||||||
const isMouseOver = React.useRef<boolean>(false);
|
|
||||||
|
|
||||||
const onMouseEnter = () => {
|
|
||||||
isMouseOver.current = true;
|
|
||||||
|
|
||||||
if (isControlled.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setOpen(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onMouseLeave = () => {
|
|
||||||
isMouseOver.current = false;
|
|
||||||
|
|
||||||
if (isControlled.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
setOpen(isMouseOver.current);
|
|
||||||
}, 200);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onOpenChange = (newOpen: boolean) => {
|
|
||||||
isControlled.current = newOpen;
|
|
||||||
|
|
||||||
setOpen(newOpen);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Popover open={open} onOpenChange={onOpenChange}>
|
|
||||||
<PopoverTrigger
|
|
||||||
className="flex cursor-pointer outline-none"
|
|
||||||
onMouseEnter={onMouseEnter}
|
|
||||||
onMouseLeave={onMouseLeave}
|
|
||||||
>
|
|
||||||
{trigger}
|
|
||||||
</PopoverTrigger>
|
|
||||||
|
|
||||||
<PopoverContent
|
|
||||||
side="top"
|
|
||||||
onMouseEnter={onMouseEnter}
|
|
||||||
onMouseLeave={onMouseLeave}
|
|
||||||
{...contentProps}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export { Popover, PopoverTrigger, PopoverContent, PopoverHover };
|
|
||||||
|
|||||||
Reference in New Issue
Block a user