& {
export const FundingRaised = ({ className, data, ...props }: FundingRaisedProps) => {
const formattedData = data.map((item) => ({
amount: Number(item.amount),
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
date: formatMonth(item.date as string),
}));
diff --git a/apps/marketing/src/app/(marketing)/open/metric-card.tsx b/apps/marketing/src/app/(marketing)/open/metric-card.tsx
index 6235f4f5e..f7bf59e62 100644
--- a/apps/marketing/src/app/(marketing)/open/metric-card.tsx
+++ b/apps/marketing/src/app/(marketing)/open/metric-card.tsx
@@ -1,4 +1,4 @@
-import { HTMLAttributes } from 'react';
+import type { HTMLAttributes } from 'react';
import { cn } from '@documenso/ui/lib/utils';
diff --git a/apps/marketing/src/app/(marketing)/open/salary-bands.tsx b/apps/marketing/src/app/(marketing)/open/salary-bands.tsx
index 31c254157..41754cff6 100644
--- a/apps/marketing/src/app/(marketing)/open/salary-bands.tsx
+++ b/apps/marketing/src/app/(marketing)/open/salary-bands.tsx
@@ -1,4 +1,4 @@
-import { HTMLAttributes } from 'react';
+import type { HTMLAttributes } from 'react';
import { cn } from '@documenso/ui/lib/utils';
import {
diff --git a/apps/marketing/src/app/(marketing)/oss-friends/container.tsx b/apps/marketing/src/app/(marketing)/oss-friends/container.tsx
index 0f1f66664..f2ea4e855 100644
--- a/apps/marketing/src/app/(marketing)/oss-friends/container.tsx
+++ b/apps/marketing/src/app/(marketing)/oss-friends/container.tsx
@@ -2,13 +2,14 @@
import Link from 'next/link';
-import { Variants, motion } from 'framer-motion';
+import type { Variants } from 'framer-motion';
+import { motion } from 'framer-motion';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
import { Card, CardContent, CardTitle } from '@documenso/ui/primitives/card';
-import { TOSSFriendsSchema } from './schema';
+import type { TOSSFriendsSchema } from './schema';
const ContainerVariants: Variants = {
initial: {
diff --git a/apps/marketing/src/app/(marketing)/singleplayer/client.tsx b/apps/marketing/src/app/(marketing)/singleplayer/client.tsx
index 3f1c11259..e20b94887 100644
--- a/apps/marketing/src/app/(marketing)/singleplayer/client.tsx
+++ b/apps/marketing/src/app/(marketing)/singleplayer/client.tsx
@@ -158,6 +158,7 @@ export const SinglePlayerClient = () => {
expired: null,
signedAt: null,
readStatus: 'OPENED',
+ documentDeletedAt: null,
signingStatus: 'NOT_SIGNED',
sendStatus: 'NOT_SENT',
role: 'SIGNER',
diff --git a/apps/marketing/src/app/robots.ts b/apps/marketing/src/app/robots.ts
index cc718ff25..a222a892e 100644
--- a/apps/marketing/src/app/robots.ts
+++ b/apps/marketing/src/app/robots.ts
@@ -1,4 +1,4 @@
-import { MetadataRoute } from 'next';
+import type { MetadataRoute } from 'next';
import { getBaseUrl } from '@documenso/lib/universal/get-base-url';
diff --git a/apps/marketing/src/app/sitemap.ts b/apps/marketing/src/app/sitemap.ts
index b9becde3b..4913402f9 100644
--- a/apps/marketing/src/app/sitemap.ts
+++ b/apps/marketing/src/app/sitemap.ts
@@ -1,4 +1,4 @@
-import { MetadataRoute } from 'next';
+import type { MetadataRoute } from 'next';
import { allBlogPosts, allGenericPages } from 'contentlayer/generated';
diff --git a/apps/marketing/src/components/(marketing)/hero.tsx b/apps/marketing/src/components/(marketing)/hero.tsx
index f416cc4ca..5809bd695 100644
--- a/apps/marketing/src/components/(marketing)/hero.tsx
+++ b/apps/marketing/src/components/(marketing)/hero.tsx
@@ -96,7 +96,7 @@ export const Hero = ({ className, ...props }: HeroProps) => {
variants={HeroTitleVariants}
initial="initial"
animate="animate"
- className="text-center text-4xl font-bold leading-tight tracking-tight lg:text-[64px]"
+ className="text-center text-4xl font-bold leading-tight tracking-tight md:text-[48px] lg:text-[64px]"
>
Document signing,
finally open source.
diff --git a/apps/marketing/src/components/(marketing)/open-build-template-bento.tsx b/apps/marketing/src/components/(marketing)/open-build-template-bento.tsx
index 3c76c3547..4d4d6ad8a 100644
--- a/apps/marketing/src/components/(marketing)/open-build-template-bento.tsx
+++ b/apps/marketing/src/components/(marketing)/open-build-template-bento.tsx
@@ -1,4 +1,4 @@
-import { HTMLAttributes } from 'react';
+import type { HTMLAttributes } from 'react';
import Image from 'next/image';
diff --git a/apps/marketing/src/components/(marketing)/widget.tsx b/apps/marketing/src/components/(marketing)/widget.tsx
index 8b6c3cd8e..c4611746a 100644
--- a/apps/marketing/src/components/(marketing)/widget.tsx
+++ b/apps/marketing/src/components/(marketing)/widget.tsx
@@ -346,7 +346,7 @@ export const Widget = ({ className, children, ...props }: WidgetProps) => {
{signatureText && (
{signatureText}
@@ -360,7 +360,7 @@ export const Widget = ({ className, children, ...props }: WidgetProps) => {
>
, 'viewBox'>;
diff --git a/apps/marketing/src/providers/next-theme.tsx b/apps/marketing/src/providers/next-theme.tsx
index 6e9122e5a..d15114606 100644
--- a/apps/marketing/src/providers/next-theme.tsx
+++ b/apps/marketing/src/providers/next-theme.tsx
@@ -3,7 +3,7 @@
import * as React from 'react';
import { ThemeProvider as NextThemesProvider } from 'next-themes';
-import { ThemeProviderProps } from 'next-themes/dist/types';
+import type { ThemeProviderProps } from 'next-themes/dist/types';
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return {children};
diff --git a/apps/web/src/app/(dashboard)/documents/[id]/document-page-view-dropdown.tsx b/apps/web/src/app/(dashboard)/documents/[id]/document-page-view-dropdown.tsx
index 7b6bb8a91..35dbaa8f1 100644
--- a/apps/web/src/app/(dashboard)/documents/[id]/document-page-view-dropdown.tsx
+++ b/apps/web/src/app/(dashboard)/documents/[id]/document-page-view-dropdown.tsx
@@ -19,7 +19,7 @@ import { useSession } from 'next-auth/react';
import { downloadPDF } from '@documenso/lib/client-only/download-pdf';
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
import { DocumentStatus } from '@documenso/prisma/client';
-import type { Document, Recipient, Team, User } from '@documenso/prisma/client';
+import type { Document, Recipient, Team, TeamEmail, User } from '@documenso/prisma/client';
import { trpc as trpcClient } from '@documenso/trpc/client';
import { DocumentShareButton } from '@documenso/ui/components/document/document-share-button';
import {
@@ -41,7 +41,7 @@ export type DocumentPageViewDropdownProps = {
Recipient: Recipient[];
team: Pick | null;
};
- team?: Pick;
+ team?: Pick & { teamEmail: TeamEmail | null };
};
export const DocumentPageViewDropdown = ({ document, team }: DocumentPageViewDropdownProps) => {
@@ -59,9 +59,10 @@ export const DocumentPageViewDropdown = ({ document, team }: DocumentPageViewDro
const isOwner = document.User.id === session.user.id;
const isDraft = document.status === DocumentStatus.DRAFT;
+ const isDeleted = document.deletedAt !== null;
const isComplete = document.status === DocumentStatus.COMPLETED;
- const isDocumentDeletable = isOwner;
const isCurrentTeamDocument = team && document.team?.url === team.url;
+ const canManageDocument = Boolean(isOwner || isCurrentTeamDocument);
const documentsPath = formatDocumentsPath(team?.url);
@@ -118,7 +119,7 @@ export const DocumentPageViewDropdown = ({ document, team }: DocumentPageViewDro
- Logs
+ Audit Log
@@ -127,7 +128,10 @@ export const DocumentPageViewDropdown = ({ document, team }: DocumentPageViewDro
Duplicate
- setDeleteDialogOpen(true)} disabled={!isDocumentDeletable}>
+ setDeleteDialogOpen(true)}
+ disabled={Boolean(!canManageDocument && team?.teamEmail) || isDeleted}
+ >
Delete
@@ -154,15 +158,15 @@ export const DocumentPageViewDropdown = ({ document, team }: DocumentPageViewDro
/>
- {isDocumentDeletable && (
-
- )}
+
+
{isDuplicateDialogOpen && (
{
@@ -83,11 +86,16 @@ export const DocumentPageView = async ({ params, team }: DocumentPageViewProps)
documentMeta.password = securePassword;
}
- const recipients = await getRecipientsForDocument({
- documentId,
- teamId: team?.id,
- userId: user.id,
- });
+ const [recipients, completedFields] = await Promise.all([
+ getRecipientsForDocument({
+ documentId,
+ teamId: team?.id,
+ userId: user.id,
+ }),
+ getCompletedFieldsForDocument({
+ documentId,
+ }),
+ ]);
const documentWithRecipients = {
...document,
@@ -118,11 +126,17 @@ export const DocumentPageView = async ({ params, team }: DocumentPageViewProps)
-
+
{recipients.length} Recipient(s)
)}
+
+ {document.deletedAt && Document deleted}
@@ -148,6 +162,13 @@ export const DocumentPageView = async ({ params, team }: DocumentPageViewProps)
+ {document.status === DocumentStatus.PENDING && (
+
+ )}
+
diff --git a/apps/web/src/app/(dashboard)/documents/[id]/edit/document-edit-page-view.tsx b/apps/web/src/app/(dashboard)/documents/[id]/edit/document-edit-page-view.tsx
index 8a78ca9aa..5c2a64870 100644
--- a/apps/web/src/app/(dashboard)/documents/[id]/edit/document-edit-page-view.tsx
+++ b/apps/web/src/app/(dashboard)/documents/[id]/edit/document-edit-page-view.tsx
@@ -92,7 +92,11 @@ export const DocumentEditPageView = async ({ params, team }: DocumentEditPageVie
-
+
{recipients.length} Recipient(s)
diff --git a/apps/web/src/app/(dashboard)/documents/[id]/logs/document-logs-page-view.tsx b/apps/web/src/app/(dashboard)/documents/[id]/logs/document-logs-page-view.tsx
index 2d786b9c9..0556fcd2d 100644
--- a/apps/web/src/app/(dashboard)/documents/[id]/logs/document-logs-page-view.tsx
+++ b/apps/web/src/app/(dashboard)/documents/[id]/logs/document-logs-page-view.tsx
@@ -133,7 +133,11 @@ export const DocumentLogsPageView = async ({ params, team }: DocumentLogsPageVie
-
+
diff --git a/apps/web/src/app/(dashboard)/documents/[id]/logs/download-certificate-button.tsx b/apps/web/src/app/(dashboard)/documents/[id]/logs/download-certificate-button.tsx
index 49a330b94..1f2028358 100644
--- a/apps/web/src/app/(dashboard)/documents/[id]/logs/download-certificate-button.tsx
+++ b/apps/web/src/app/(dashboard)/documents/[id]/logs/download-certificate-button.tsx
@@ -2,6 +2,7 @@
import { DownloadIcon } from 'lucide-react';
+import { DocumentStatus } from '@documenso/prisma/client';
import { trpc } from '@documenso/trpc/react';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
@@ -10,11 +11,13 @@ import { useToast } from '@documenso/ui/primitives/use-toast';
export type DownloadCertificateButtonProps = {
className?: string;
documentId: number;
+ documentStatus: DocumentStatus;
};
export const DownloadCertificateButton = ({
className,
documentId,
+ documentStatus,
}: DownloadCertificateButtonProps) => {
const { toast } = useToast();
@@ -69,6 +72,7 @@ export const DownloadCertificateButton = ({
className={cn('w-full sm:w-auto', className)}
loading={isLoading}
variant="outline"
+ disabled={documentStatus !== DocumentStatus.COMPLETED}
onClick={() => void onDownloadCertificatesClick()}
>
{!isLoading &&
}
diff --git a/apps/web/src/app/(dashboard)/documents/data-table-action-dropdown.tsx b/apps/web/src/app/(dashboard)/documents/data-table-action-dropdown.tsx
index a43d37af7..aed95662b 100644
--- a/apps/web/src/app/(dashboard)/documents/data-table-action-dropdown.tsx
+++ b/apps/web/src/app/(dashboard)/documents/data-table-action-dropdown.tsx
@@ -15,7 +15,6 @@ import {
Pencil,
Share,
Trash2,
- XCircle,
} from 'lucide-react';
import { useSession } from 'next-auth/react';
@@ -45,7 +44,7 @@ export type DataTableActionDropdownProps = {
Recipient: Recipient[];
team: Pick
| null;
};
- team?: Pick;
+ team?: Pick & { teamEmail?: string };
};
export const DataTableActionDropdown = ({ row, team }: DataTableActionDropdownProps) => {
@@ -67,8 +66,8 @@ export const DataTableActionDropdown = ({ row, team }: DataTableActionDropdownPr
// const isPending = row.status === DocumentStatus.PENDING;
const isComplete = row.status === DocumentStatus.COMPLETED;
// const isSigned = recipient?.signingStatus === SigningStatus.SIGNED;
- const isDocumentDeletable = isOwner;
const isCurrentTeamDocument = team && row.team?.url === team.url;
+ const canManageDocument = Boolean(isOwner || isCurrentTeamDocument);
const documentsPath = formatDocumentsPath(team?.url);
@@ -107,14 +106,14 @@ export const DataTableActionDropdown = ({ row, team }: DataTableActionDropdownPr
return (
-
+
Action
- {recipient && recipient?.role !== RecipientRole.CC && (
+ {!isDraft && recipient && recipient?.role !== RecipientRole.CC && (
{recipient?.role === RecipientRole.VIEWER && (
@@ -141,7 +140,7 @@ export const DataTableActionDropdown = ({ row, team }: DataTableActionDropdownPr
)}
-
+
Edit
@@ -158,14 +157,18 @@ export const DataTableActionDropdown = ({ row, team }: DataTableActionDropdownPr
Duplicate
-
+ {/* No point displaying this if there's no functionality. */}
+ {/*
Void
-
+ */}
- setDeleteDialogOpen(true)} disabled={!isDocumentDeletable}>
+ setDeleteDialogOpen(true)}
+ disabled={Boolean(!canManageDocument && team?.teamEmail)}
+ >
- Delete
+ {canManageDocument ? 'Delete' : 'Hide'}
Share
@@ -186,16 +189,16 @@ export const DataTableActionDropdown = ({ row, team }: DataTableActionDropdownPr
/>
- {isDocumentDeletable && (
-
- )}
+
+
{isDuplicateDialogOpen && (
;
showSenderColumn?: boolean;
- team?: Pick;
+ team?: Pick & { teamEmail?: string };
};
export const DocumentsDataTable = ({
@@ -76,7 +76,12 @@ export const DocumentsDataTable = ({
{
header: 'Recipient',
accessorKey: 'recipient',
- cell: ({ row }) => ,
+ cell: ({ row }) => (
+
+ ),
},
{
header: 'Status',
diff --git a/apps/web/src/app/(dashboard)/documents/delete-document-dialog.tsx b/apps/web/src/app/(dashboard)/documents/delete-document-dialog.tsx
index 59fd21e60..558d39558 100644
--- a/apps/web/src/app/(dashboard)/documents/delete-document-dialog.tsx
+++ b/apps/web/src/app/(dashboard)/documents/delete-document-dialog.tsx
@@ -2,8 +2,11 @@ import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
+import { match } from 'ts-pattern';
+
import { DocumentStatus } from '@documenso/prisma/client';
import { trpc as trpcReact } from '@documenso/trpc/react';
+import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
@@ -23,6 +26,7 @@ type DeleteDocumentDialogProps = {
status: DocumentStatus;
documentTitle: string;
teamId?: number;
+ canManageDocument: boolean;
};
export const DeleteDocumentDialog = ({
@@ -32,6 +36,7 @@ export const DeleteDocumentDialog = ({
status,
documentTitle,
teamId,
+ canManageDocument,
}: DeleteDocumentDialogProps) => {
const router = useRouter();
@@ -83,47 +88,82 @@ export const DeleteDocumentDialog = ({
diff --git a/apps/web/src/app/(dashboard)/documents/documents-page-view.tsx b/apps/web/src/app/(dashboard)/documents/documents-page-view.tsx
index 9059b8e88..84f6bfe3f 100644
--- a/apps/web/src/app/(dashboard)/documents/documents-page-view.tsx
+++ b/apps/web/src/app/(dashboard)/documents/documents-page-view.tsx
@@ -41,7 +41,9 @@ export const DocumentsPageView = async ({ searchParams = {}, team }: DocumentsPa
const page = Number(searchParams.page) || 1;
const perPage = Number(searchParams.perPage) || 20;
const senderIds = parseToIntegerArray(searchParams.senderIds ?? '');
- const currentTeam = team ? { id: team.id, url: team.url } : undefined;
+ const currentTeam = team
+ ? { id: team.id, url: team.url, teamEmail: team.teamEmail?.email }
+ : undefined;
const getStatOptions: GetStatsInput = {
user,
diff --git a/apps/web/src/app/(dashboard)/documents/empty-state.tsx b/apps/web/src/app/(dashboard)/documents/empty-state.tsx
index b6d2f74e2..e1af23bf2 100644
--- a/apps/web/src/app/(dashboard)/documents/empty-state.tsx
+++ b/apps/web/src/app/(dashboard)/documents/empty-state.tsx
@@ -37,7 +37,10 @@ export const EmptyDocumentState = ({ status }: EmptyDocumentProps) => {
}));
return (
-
+
diff --git a/apps/web/src/app/(dashboard)/templates/new-template-dialog.tsx b/apps/web/src/app/(dashboard)/templates/new-template-dialog.tsx
index 37d60f946..ab05ac3dc 100644
--- a/apps/web/src/app/(dashboard)/templates/new-template-dialog.tsx
+++ b/apps/web/src/app/(dashboard)/templates/new-template-dialog.tsx
@@ -18,7 +18,10 @@ import { Button } from '@documenso/ui/primitives/button';
import { Card, CardContent } from '@documenso/ui/primitives/card';
import {
Dialog,
+ DialogClose,
DialogContent,
+ DialogDescription,
+ DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
@@ -34,7 +37,6 @@ import {
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
-import { Label } from '@documenso/ui/primitives/label';
import { useToast } from '@documenso/ui/primitives/use-toast';
const ZCreateTemplateFormSchema = z.object({
@@ -61,8 +63,7 @@ export const NewTemplateDialog = ({ teamId, templateRootPath }: NewTemplateDialo
resolver: zodResolver(ZCreateTemplateFormSchema),
});
- const { mutateAsync: createTemplate, isLoading: isCreatingTemplate } =
- trpc.template.createTemplate.useMutation();
+ const { mutateAsync: createTemplate } = trpc.template.createTemplate.useMutation();
const [showNewTemplateDialog, setShowNewTemplateDialog] = useState(false);
const [uploadedFile, setUploadedFile] = useState<{ file: File; fileBase64: string } | null>();
@@ -140,6 +141,7 @@ export const NewTemplateDialog = ({ teamId, templateRootPath }: NewTemplateDialo
useEffect(() => {
if (!showNewTemplateDialog) {
form.reset();
+ setUploadedFile(null);
}
}, [form, showNewTemplateDialog]);
@@ -154,20 +156,23 @@ export const NewTemplateDialog = ({ teamId, templateRootPath }: NewTemplateDialo
- New Template
+ New Template
+
+ Templates allow you to quickly generate documents with pre-filled recipients and fields.
+
-
-
+
);
diff --git a/apps/web/src/app/(signing)/sign/[token]/complete/page.tsx b/apps/web/src/app/(signing)/sign/[token]/complete/page.tsx
index cfed976e5..1b7adfe70 100644
--- a/apps/web/src/app/(signing)/sign/[token]/complete/page.tsx
+++ b/apps/web/src/app/(signing)/sign/[token]/complete/page.tsx
@@ -131,7 +131,7 @@ export default async function CompletedSigningPage({
))
.with({ deletedAt: null }, () => (
-
+
Waiting for others to sign
diff --git a/apps/web/src/app/(signing)/sign/[token]/page.tsx b/apps/web/src/app/(signing)/sign/[token]/page.tsx
index e83f675ce..b066193e6 100644
--- a/apps/web/src/app/(signing)/sign/[token]/page.tsx
+++ b/apps/web/src/app/(signing)/sign/[token]/page.tsx
@@ -6,6 +6,7 @@ import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-c
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
import { isRecipientAuthorized } from '@documenso/lib/server-only/document/is-recipient-authorized';
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 { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-recipient-by-token';
import { getRecipientSignatures } from '@documenso/lib/server-only/recipient/get-recipient-signatures';
@@ -37,7 +38,7 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
const requestMetadata = extractNextHeaderRequestMetadata(requestHeaders);
- const [document, fields, recipient] = await Promise.all([
+ const [document, fields, recipient, completedFields] = await Promise.all([
getDocumentAndSenderByToken({
token,
userId: user?.id,
@@ -45,9 +46,15 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
}).catch(() => null),
getFieldsForToken({ token }),
getRecipientByToken({ token }).catch(() => null),
+ getCompletedFieldsForToken({ token }),
]);
- if (!document || !document.documentData || !recipient) {
+ if (
+ !document ||
+ !document.documentData ||
+ !recipient ||
+ document.status === DocumentStatus.DRAFT
+ ) {
return notFound();
}
@@ -120,7 +127,12 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
signature={user?.email === recipient.email ? user.signature : undefined}
>
-
+
);
diff --git a/apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx b/apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx
index c04679956..4691d0d4c 100644
--- a/apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx
+++ b/apps/web/src/app/(signing)/sign/[token]/signing-page-view.tsx
@@ -4,12 +4,14 @@ import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-form
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
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 { CompletedField } from '@documenso/lib/types/fields';
import type { Field, Recipient } from '@documenso/prisma/client';
import { FieldType, RecipientRole } from '@documenso/prisma/client';
import { Card, CardContent } from '@documenso/ui/primitives/card';
import { ElementVisible } from '@documenso/ui/primitives/element-visible';
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 { DateField } from './date-field';
@@ -23,9 +25,15 @@ export type SigningPageViewProps = {
document: DocumentAndSender;
recipient: Recipient;
fields: Field[];
+ completedFields: CompletedField[];
};
-export const SigningPageView = ({ document, recipient, fields }: SigningPageViewProps) => {
+export const SigningPageView = ({
+ document,
+ recipient,
+ fields,
+ completedFields,
+}: SigningPageViewProps) => {
const truncatedTitle = truncateTitle(document.title);
const { documentData, documentMeta } = document;
@@ -70,6 +78,8 @@ export const SigningPageView = ({ document, recipient, fields }: SigningPageView
+
+
{fields.map((field) =>
match(field.type)
diff --git a/apps/web/src/components/(dashboard)/avatar/avatar-with-recipient.tsx b/apps/web/src/components/(dashboard)/avatar/avatar-with-recipient.tsx
index 69dd88d79..cd7cd2305 100644
--- a/apps/web/src/components/(dashboard)/avatar/avatar-with-recipient.tsx
+++ b/apps/web/src/components/(dashboard)/avatar/avatar-with-recipient.tsx
@@ -8,6 +8,7 @@ import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
import { recipientAbbreviation } from '@documenso/lib/utils/recipient-formatter';
import type { Recipient } from '@documenso/prisma/client';
+import { DocumentStatus } from '@documenso/prisma/client';
import { cn } from '@documenso/ui/lib/utils';
import { useToast } from '@documenso/ui/primitives/use-toast';
@@ -15,18 +16,21 @@ import { StackAvatar } from './stack-avatar';
export type AvatarWithRecipientProps = {
recipient: Recipient;
+ documentStatus: DocumentStatus;
};
-export function AvatarWithRecipient({ recipient }: AvatarWithRecipientProps) {
+export function AvatarWithRecipient({ recipient, documentStatus }: AvatarWithRecipientProps) {
const [, copy] = useCopyToClipboard();
const { toast } = useToast();
+ const signingToken = documentStatus === DocumentStatus.PENDING ? recipient.token : null;
+
const onRecipientClick = () => {
- if (!recipient.token) {
+ if (!signingToken) {
return;
}
- void copy(`${NEXT_PUBLIC_WEBAPP_URL()}/sign/${recipient.token}`).then(() => {
+ void copy(`${NEXT_PUBLIC_WEBAPP_URL()}/sign/${signingToken}`).then(() => {
toast({
title: 'Copied to clipboard',
description: 'The signing link has been copied to your clipboard.',
@@ -37,10 +41,10 @@ export function AvatarWithRecipient({ recipient }: AvatarWithRecipientProps) {
return (
-
-
-
{recipient.email}
-
- {RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName}
-
-
+
+
+
{recipient.email}
+
+ {RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName}
+
);
diff --git a/apps/web/src/components/(dashboard)/avatar/stack-avatars-with-tooltip.tsx b/apps/web/src/components/(dashboard)/avatar/stack-avatars-with-tooltip.tsx
index 10f7d1e6a..6bc8cf9af 100644
--- a/apps/web/src/components/(dashboard)/avatar/stack-avatars-with-tooltip.tsx
+++ b/apps/web/src/components/(dashboard)/avatar/stack-avatars-with-tooltip.tsx
@@ -1,33 +1,28 @@
'use client';
-import { useRef, useState } from 'react';
-
import { getRecipientType } from '@documenso/lib/client-only/recipient-type';
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
import { recipientAbbreviation } from '@documenso/lib/utils/recipient-formatter';
-import type { Recipient } from '@documenso/prisma/client';
-import { Popover, PopoverContent, PopoverTrigger } from '@documenso/ui/primitives/popover';
+import type { DocumentStatus, Recipient } from '@documenso/prisma/client';
+import { PopoverHover } from '@documenso/ui/primitives/popover';
import { AvatarWithRecipient } from './avatar-with-recipient';
import { StackAvatar } from './stack-avatar';
import { StackAvatars } from './stack-avatars';
export type StackAvatarsWithTooltipProps = {
+ documentStatus: DocumentStatus;
recipients: Recipient[];
position?: 'top' | 'bottom';
children?: React.ReactNode;
};
export const StackAvatarsWithTooltip = ({
+ documentStatus,
recipients,
position,
children,
}: StackAvatarsWithTooltipProps) => {
- const [open, setOpen] = useState(false);
-
- const isControlled = useRef(false);
- const isMouseOverTimeout = useRef
(null);
-
const waitingRecipients = recipients.filter(
(recipient) => getRecipientType(recipient) === 'waiting',
);
@@ -44,105 +39,74 @@ export const StackAvatarsWithTooltip = ({
(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 (
-
-
- {children || }
-
-
-
- {completedRecipients.length > 0 && (
-
-
Completed
- {completedRecipients.map((recipient: Recipient) => (
-
-
-
-
{recipient.email}
-
- {RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName}
-
-
+
}
+ contentProps={{
+ className: 'flex flex-col gap-y-5 py-2',
+ side: position,
+ }}
+ >
+ {completedRecipients.length > 0 && (
+
+
Completed
+ {completedRecipients.map((recipient: Recipient) => (
+
+
+
+
{recipient.email}
+
+ {RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName}
+
- ))}
-
- )}
+
+ ))}
+
+ )}
- {waitingRecipients.length > 0 && (
-
-
Waiting
- {waitingRecipients.map((recipient: Recipient) => (
-
- ))}
-
- )}
+ {waitingRecipients.length > 0 && (
+
+
Waiting
+ {waitingRecipients.map((recipient: Recipient) => (
+
+ ))}
+
+ )}
- {openedRecipients.length > 0 && (
-
-
Opened
- {openedRecipients.map((recipient: Recipient) => (
-
- ))}
-
- )}
+ {openedRecipients.length > 0 && (
+
+
Opened
+ {openedRecipients.map((recipient: Recipient) => (
+
+ ))}
+
+ )}
- {uncompletedRecipients.length > 0 && (
-
-
Uncompleted
- {uncompletedRecipients.map((recipient: Recipient) => (
-
- ))}
-
- )}
-
-
+ {uncompletedRecipients.length > 0 && (
+
+
Uncompleted
+ {uncompletedRecipients.map((recipient: Recipient) => (
+
+ ))}
+
+ )}
+
);
};
diff --git a/apps/web/src/components/(dashboard)/common/command-menu.tsx b/apps/web/src/components/(dashboard)/common/command-menu.tsx
index bdc6c2064..812efd4b9 100644
--- a/apps/web/src/components/(dashboard)/common/command-menu.tsx
+++ b/apps/web/src/components/(dashboard)/common/command-menu.tsx
@@ -5,7 +5,6 @@ import { useCallback, useMemo, useState } from 'react';
import { useRouter } from 'next/navigation';
import { Loader, Monitor, Moon, Sun } from 'lucide-react';
-import { useSession } from 'next-auth/react';
import { useTheme } from 'next-themes';
import { useHotkeys } from 'react-hotkeys-hook';
@@ -18,7 +17,6 @@ import {
DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
SKIP_QUERY_BATCH_META,
} from '@documenso/lib/constants/trpc';
-import type { Document, Recipient } from '@documenso/prisma/client';
import { trpc as trpcReact } from '@documenso/trpc/react';
import {
CommandDialog,
@@ -71,7 +69,6 @@ export type CommandMenuProps = {
export function CommandMenu({ open, onOpenChange }: CommandMenuProps) {
const { setTheme } = useTheme();
- const { data: session } = useSession();
const router = useRouter();
@@ -93,17 +90,6 @@ export function CommandMenu({ open, onOpenChange }: CommandMenuProps) {
},
);
- const isOwner = useCallback(
- (document: Document) => document.userId === session?.user.id,
- [session?.user.id],
- );
-
- const getSigningLink = useCallback(
- (recipients: Recipient[]) =>
- `/sign/${recipients.find((r) => r.email === session?.user.email)?.token}`,
- [session?.user.email],
- );
-
const searchResults = useMemo(() => {
if (!searchDocumentsData) {
return [];
@@ -111,10 +97,10 @@ export function CommandMenu({ open, onOpenChange }: CommandMenuProps) {
return searchDocumentsData.map((document) => ({
label: document.title,
- path: isOwner(document) ? `/documents/${document.id}` : getSigningLink(document.Recipient),
- value: [document.id, document.title, ...document.Recipient.map((r) => r.email)].join(' '),
+ path: document.path,
+ value: document.value,
}));
- }, [searchDocumentsData, isOwner, getSigningLink]);
+ }, [searchDocumentsData]);
const currentPage = pages[pages.length - 1];
diff --git a/apps/web/src/components/(dashboard)/layout/desktop-nav.tsx b/apps/web/src/components/(dashboard)/layout/desktop-nav.tsx
index 262e297d6..975ef7d0d 100644
--- a/apps/web/src/components/(dashboard)/layout/desktop-nav.tsx
+++ b/apps/web/src/components/(dashboard)/layout/desktop-nav.tsx
@@ -1,5 +1,3 @@
-'use client';
-
import type { HTMLAttributes } from 'react';
import { useEffect, useState } from 'react';
@@ -12,8 +10,6 @@ import { getRootHref } from '@documenso/lib/utils/params';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
-import { CommandMenu } from '../common/command-menu';
-
const navigationLinks = [
{
href: '/documents',
@@ -25,13 +21,14 @@ const navigationLinks = [
},
];
-export type DesktopNavProps = HTMLAttributes
;
+export type DesktopNavProps = HTMLAttributes & {
+ setIsCommandMenuOpen: (value: boolean) => void;
+};
-export const DesktopNav = ({ className, ...props }: DesktopNavProps) => {
+export const DesktopNav = ({ className, setIsCommandMenuOpen, ...props }: DesktopNavProps) => {
const pathname = usePathname();
const params = useParams();
- const [open, setOpen] = useState(false);
const [modifierKey, setModifierKey] = useState(() => 'Ctrl');
const rootHref = getRootHref(params, { returnEmptyRootString: true });
@@ -70,12 +67,10 @@ export const DesktopNav = ({ className, ...props }: DesktopNavProps) => {
))}
-
-