diff --git a/apps/marketing/src/app/(marketing)/singleplayer/[token]/success/page.tsx b/apps/marketing/src/app/(marketing)/singleplayer/[token]/success/page.tsx index 6e02a470f..fbf020c38 100644 --- a/apps/marketing/src/app/(marketing)/singleplayer/[token]/success/page.tsx +++ b/apps/marketing/src/app/(marketing)/singleplayer/[token]/success/page.tsx @@ -1,6 +1,7 @@ import { notFound } from 'next/navigation'; import { getDocumentAndRecipientByToken } from '@documenso/lib/server-only/document/get-document-by-token'; +import { getRecipientSignatures } from '@documenso/lib/server-only/recipient/get-recipient-signatures'; import { DocumentStatus } from '@documenso/prisma/client'; import { SinglePlayerModeSuccess } from '~/components/(marketing)/single-player-mode/single-player-mode-success'; @@ -26,5 +27,7 @@ export default async function SinglePlayerModeSuccessPage({ return notFound(); } - return ; + const signatures = await getRecipientSignatures({ recipientId: document.Recipient.id }); + + return ; } diff --git a/apps/marketing/src/app/(marketing)/singleplayer/client.tsx b/apps/marketing/src/app/(marketing)/singleplayer/client.tsx index 747b77a15..7b500d295 100644 --- a/apps/marketing/src/app/(marketing)/singleplayer/client.tsx +++ b/apps/marketing/src/app/(marketing)/singleplayer/client.tsx @@ -159,11 +159,11 @@ export const SinglePlayerClient = () => { const onFileDrop = async (file: File) => { try { const arrayBuffer = await file.arrayBuffer(); - const base64String = base64.encode(new Uint8Array(arrayBuffer)); + const fileBase64 = base64.encode(new Uint8Array(arrayBuffer)); setUploadedFile({ file, - fileBase64: `data:application/pdf;base64,${base64String}`, + fileBase64, }); analytics.capture('Marketing: SPM - Document uploaded'); @@ -182,7 +182,15 @@ export const SinglePlayerClient = () => {

Single Player Mode

- View our{' '} + Create a{' '} + + free account + {' '} + or view our{' '} & { duration?: number }) { +}: React.ComponentPropsWithoutRef & { duration?: number }) => { const isMounted = useIsMounted(); const { width, height } = useWindowSize(); @@ -43,4 +43,4 @@ export default function ConfettiScreen({ />, document.body, ); -} +}; diff --git a/apps/marketing/src/components/(marketing)/single-player-mode/single-player-mode-success.tsx b/apps/marketing/src/components/(marketing)/single-player-mode/single-player-mode-success.tsx index 49a9efcae..f92c64c84 100644 --- a/apps/marketing/src/components/(marketing)/single-player-mode/single-player-mode-success.tsx +++ b/apps/marketing/src/components/(marketing)/single-player-mode/single-player-mode-success.tsx @@ -5,8 +5,7 @@ import { useEffect, useState } from 'react'; import Link from 'next/link'; import { useFeatureFlags } from '@documenso/lib/client-only/providers/feature-flag'; -import { base64 } from '@documenso/lib/universal/base64'; -import { getFile } from '@documenso/lib/universal/upload/get-file'; +import { DocumentStatus, Signature } from '@documenso/prisma/client'; import { DocumentWithRecipient } from '@documenso/prisma/types/document-with-recipient'; import DocumentDialog from '@documenso/ui/components/document/document-dialog'; import { DocumentDownloadButton } from '@documenso/ui/components/document/document-download-button'; @@ -14,53 +13,28 @@ import { DocumentShareButton } from '@documenso/ui/components/document/document- import { SigningCard3D } from '@documenso/ui/components/signing-card'; import { cn } from '@documenso/ui/lib/utils'; import { Button } from '@documenso/ui/primitives/button'; -import { useToast } from '@documenso/ui/primitives/use-toast'; import signingCelebration from '~/assets/signing-celebration.png'; -import ConfettiScreen from '~/components/(marketing)/confetti-screen'; - -import { DocumentStatus } from '.prisma/client'; +import { ConfettiScreen } from '~/components/(marketing)/confetti-screen'; interface SinglePlayerModeSuccessProps { className?: string; document: DocumentWithRecipient; + signatures: Signature[]; } -export const SinglePlayerModeSuccess = ({ className, document }: SinglePlayerModeSuccessProps) => { +export const SinglePlayerModeSuccess = ({ + className, + document, + signatures, +}: SinglePlayerModeSuccessProps) => { const { getFlag } = useFeatureFlags(); const isConfettiEnabled = getFlag('marketing_spm_confetti'); const [showDocumentDialog, setShowDocumentDialog] = useState(false); - const [isFetchingDocumentFile, setIsFetchingDocumentFile] = useState(false); - const [documentFile, setDocumentFile] = useState(null); - const { toast } = useToast(); - - const onShowDocumentClick = async () => { - if (isFetchingDocumentFile) { - return; - } - - setIsFetchingDocumentFile(true); - - try { - const data = await getFile(document.documentData); - - setDocumentFile(base64.encode(data)); - - setShowDocumentDialog(true); - } catch { - toast({ - title: 'Something went wrong.', - description: 'We were unable to retrieve the document at this time. Please try again.', - variant: 'destructive', - duration: 7500, - }); - } - - setIsFetchingDocumentFile(false); - }; + const { documentData } = document; useEffect(() => { window.scrollTo({ top: 0 }); @@ -80,6 +54,7 @@ export const SinglePlayerModeSuccess = ({ className, document }: SinglePlayerMod @@ -99,11 +74,7 @@ export const SinglePlayerModeSuccess = ({ className, document }: SinglePlayerMod disabled={document.status !== DocumentStatus.COMPLETED} /> - @@ -123,7 +94,7 @@ export const SinglePlayerModeSuccess = ({ className, document }: SinglePlayerMod

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 5402d12cb..d5852ecdd 100644 --- a/apps/web/src/app/(signing)/sign/[token]/complete/page.tsx +++ b/apps/web/src/app/(signing)/sign/[token]/complete/page.tsx @@ -7,6 +7,7 @@ import { match } from 'ts-pattern'; import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-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'; import { DocumentStatus, FieldType } from '@documenso/prisma/client'; import { DocumentDownloadButton } from '@documenso/ui/components/document/document-download-button'; import { DocumentShareButton } from '@documenso/ui/components/document/document-share-button'; @@ -46,6 +47,8 @@ export default async function CompletedSigningPage({ return notFound(); } + const signatures = await getRecipientSignatures({ recipientId: recipient.id }); + const recipientName = recipient.name || fields.find((field) => field.type === FieldType.NAME)?.customText || @@ -54,7 +57,11 @@ export default async function CompletedSigningPage({ return (
{/* Card with recipient */} - +
{match(document.status) @@ -72,7 +79,8 @@ export default async function CompletedSigningPage({ ))}

- You have signed "{document.title}" + You have signed + "{document.title}"

{match(document.status) diff --git a/package-lock.json b/package-lock.json index 45ddd83a2..648e1ca5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19913,7 +19913,8 @@ "react-pdf": "7.3.3", "react-rnd": "^10.4.1", "tailwind-merge": "^1.12.0", - "tailwindcss-animate": "^1.0.5" + "tailwindcss-animate": "^1.0.5", + "ts-pattern": "^5.0.5" }, "devDependencies": { "@documenso/tailwind-config": "*", diff --git a/packages/lib/server-only/recipient/get-recipient-signatures.ts b/packages/lib/server-only/recipient/get-recipient-signatures.ts new file mode 100644 index 000000000..9b37e84b5 --- /dev/null +++ b/packages/lib/server-only/recipient/get-recipient-signatures.ts @@ -0,0 +1,15 @@ +import { prisma } from '@documenso/prisma'; + +export type GetRecipientSignaturesOptions = { + recipientId: number; +}; + +export const getRecipientSignatures = async ({ recipientId }: GetRecipientSignaturesOptions) => { + return await prisma.signature.findMany({ + where: { + Field: { + recipientId, + }, + }, + }); +}; diff --git a/packages/ui/components/document/document-dialog.tsx b/packages/ui/components/document/document-dialog.tsx index e90147806..6099fecff 100644 --- a/packages/ui/components/document/document-dialog.tsx +++ b/packages/ui/components/document/document-dialog.tsx @@ -5,20 +5,20 @@ import { useState } from 'react'; import * as DialogPrimitive from '@radix-ui/react-dialog'; import { X } from 'lucide-react'; -import { DocumentDataType } from '@documenso/prisma/client'; +import { DocumentData } from '@documenso/prisma/client'; import { cn } from '../../lib/utils'; import { Dialog, DialogOverlay, DialogPortal } from '../../primitives/dialog'; import { LazyPDFViewerNoLoader } from '../../primitives/lazy-pdf-viewer'; export type DocumentDialogProps = { - document: string; + documentData: DocumentData; } & Omit; /** * A dialog which renders the provided document. */ -export default function DocumentDialog({ document, ...props }: DocumentDialogProps) { +export default function DocumentDialog({ documentData, ...props }: DocumentDialogProps) { const [documentLoaded, setDocumentLoaded] = useState(false); const onDocumentLoad = () => { @@ -41,12 +41,7 @@ export default function DocumentDialog({ document, ...props }: DocumentDialogPro > e.stopPropagation()} onDocumentLoad={onDocumentLoad} /> diff --git a/packages/ui/components/signing-card.tsx b/packages/ui/components/signing-card.tsx index 3c65ba9dc..ab057c4e5 100644 --- a/packages/ui/components/signing-card.tsx +++ b/packages/ui/components/signing-card.tsx @@ -5,23 +5,31 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import Image, { StaticImageData } from 'next/image'; import { animate, motion, useMotionTemplate, useMotionValue, useTransform } from 'framer-motion'; +import { P, match } from 'ts-pattern'; +import { Signature } from '@documenso/prisma/client'; import { cn } from '@documenso/ui/lib/utils'; import { Card, CardContent } from '@documenso/ui/primitives/card'; export type SigningCardProps = { className?: string; name: string; + signature?: Signature; signingCelebrationImage?: StaticImageData; }; /** * 2D signing card. */ -export const SigningCard = ({ className, name, signingCelebrationImage }: SigningCardProps) => { +export const SigningCard = ({ + className, + name, + signature, + signingCelebrationImage, +}: SigningCardProps) => { return (
- + {signingCelebrationImage && ( @@ -33,7 +41,12 @@ export const SigningCard = ({ className, name, signingCelebrationImage }: Signin /** * 3D signing card that follows the mouse movement within a certain range. */ -export const SigningCard3D = ({ className, name, signingCelebrationImage }: SigningCardProps) => { +export const SigningCard3D = ({ + className, + name, + signature, + signingCelebrationImage, +}: SigningCardProps) => { // Should use % based dimensions by calculating the window height/width. const boundary = 400; @@ -130,7 +143,7 @@ export const SigningCard3D = ({ className, name, signingCelebrationImage }: Sign rotateY, }} > - + {signingCelebrationImage && ( @@ -142,10 +155,11 @@ export const SigningCard3D = ({ className, name, signingCelebrationImage }: Sign type SigningCardContentProps = { name: string; + signature?: Signature; className?: string; }; -const SigningCardContent = ({ className, name }: SigningCardContentProps) => { +const SigningCardContent = ({ className, name, signature }: SigningCardContentProps) => { return ( { container: 'main', }} > - - {name} - + {match(signature) + .with({ signatureImageAsBase64: P.string }, (signature) => ( + signature + )) + .with({ typedSignature: P.string }, (signature) => ( + + {signature.typedSignature} + + )) + .otherwise(() => ( + + {name} + + ))} ); diff --git a/packages/ui/package.json b/packages/ui/package.json index c92cbbff2..c317f7e48 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -67,6 +67,7 @@ "react-pdf": "7.3.3", "react-rnd": "^10.4.1", "tailwind-merge": "^1.12.0", - "tailwindcss-animate": "^1.0.5" + "tailwindcss-animate": "^1.0.5", + "ts-pattern": "^5.0.5" } }