mirror of
https://github.com/documenso/documenso.git
synced 2025-11-14 00:32:43 +10:00
fix: update singleplayer add signature to card
This commit is contained in:
@ -1,6 +1,7 @@
|
|||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
|
|
||||||
import { getDocumentAndRecipientByToken } from '@documenso/lib/server-only/document/get-document-by-token';
|
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 { DocumentStatus } from '@documenso/prisma/client';
|
||||||
|
|
||||||
import { SinglePlayerModeSuccess } from '~/components/(marketing)/single-player-mode/single-player-mode-success';
|
import { SinglePlayerModeSuccess } from '~/components/(marketing)/single-player-mode/single-player-mode-success';
|
||||||
@ -26,5 +27,7 @@ export default async function SinglePlayerModeSuccessPage({
|
|||||||
return notFound();
|
return notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
return <SinglePlayerModeSuccess document={document} />;
|
const signatures = await getRecipientSignatures({ recipientId: document.Recipient.id });
|
||||||
|
|
||||||
|
return <SinglePlayerModeSuccess document={document} signatures={signatures} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -159,11 +159,11 @@ export const SinglePlayerClient = () => {
|
|||||||
const onFileDrop = async (file: File) => {
|
const onFileDrop = async (file: File) => {
|
||||||
try {
|
try {
|
||||||
const arrayBuffer = await file.arrayBuffer();
|
const arrayBuffer = await file.arrayBuffer();
|
||||||
const base64String = base64.encode(new Uint8Array(arrayBuffer));
|
const fileBase64 = base64.encode(new Uint8Array(arrayBuffer));
|
||||||
|
|
||||||
setUploadedFile({
|
setUploadedFile({
|
||||||
file,
|
file,
|
||||||
fileBase64: `data:application/pdf;base64,${base64String}`,
|
fileBase64,
|
||||||
});
|
});
|
||||||
|
|
||||||
analytics.capture('Marketing: SPM - Document uploaded');
|
analytics.capture('Marketing: SPM - Document uploaded');
|
||||||
@ -182,7 +182,15 @@ export const SinglePlayerClient = () => {
|
|||||||
<h1 className="text-3xl font-bold lg:text-5xl">Single Player Mode</h1>
|
<h1 className="text-3xl font-bold lg:text-5xl">Single Player Mode</h1>
|
||||||
|
|
||||||
<p className="text-foreground mx-auto mt-4 max-w-[50ch] text-lg leading-normal">
|
<p className="text-foreground mx-auto mt-4 max-w-[50ch] text-lg leading-normal">
|
||||||
View our{' '}
|
Create a{' '}
|
||||||
|
<Link
|
||||||
|
href={`${process.env.NEXT_PUBLIC_WEBAPP_URL}/signup`}
|
||||||
|
target="_blank"
|
||||||
|
className="hover:text-foreground/80 font-semibold transition-colors"
|
||||||
|
>
|
||||||
|
free account
|
||||||
|
</Link>{' '}
|
||||||
|
or view our{' '}
|
||||||
<Link
|
<Link
|
||||||
href={'/pricing'}
|
href={'/pricing'}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
|||||||
@ -8,10 +8,10 @@ import { createPortal } from 'react-dom';
|
|||||||
import { useIsMounted } from '@documenso/lib/client-only/hooks/use-is-mounted';
|
import { useIsMounted } from '@documenso/lib/client-only/hooks/use-is-mounted';
|
||||||
import { useWindowSize } from '@documenso/lib/client-only/hooks/use-window-size';
|
import { useWindowSize } from '@documenso/lib/client-only/hooks/use-window-size';
|
||||||
|
|
||||||
export default function ConfettiScreen({
|
export const ConfettiScreen = ({
|
||||||
numberOfPieces: numberOfPiecesProp = 200,
|
numberOfPieces: numberOfPiecesProp = 200,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentPropsWithoutRef<typeof Confetti> & { duration?: number }) {
|
}: React.ComponentPropsWithoutRef<typeof Confetti> & { duration?: number }) => {
|
||||||
const isMounted = useIsMounted();
|
const isMounted = useIsMounted();
|
||||||
const { width, height } = useWindowSize();
|
const { width, height } = useWindowSize();
|
||||||
|
|
||||||
@ -43,4 +43,4 @@ export default function ConfettiScreen({
|
|||||||
/>,
|
/>,
|
||||||
document.body,
|
document.body,
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|||||||
@ -5,8 +5,7 @@ import { useEffect, useState } from 'react';
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
import { useFeatureFlags } from '@documenso/lib/client-only/providers/feature-flag';
|
import { useFeatureFlags } from '@documenso/lib/client-only/providers/feature-flag';
|
||||||
import { base64 } from '@documenso/lib/universal/base64';
|
import { DocumentStatus, Signature } from '@documenso/prisma/client';
|
||||||
import { getFile } from '@documenso/lib/universal/upload/get-file';
|
|
||||||
import { DocumentWithRecipient } from '@documenso/prisma/types/document-with-recipient';
|
import { DocumentWithRecipient } from '@documenso/prisma/types/document-with-recipient';
|
||||||
import DocumentDialog from '@documenso/ui/components/document/document-dialog';
|
import DocumentDialog from '@documenso/ui/components/document/document-dialog';
|
||||||
import { DocumentDownloadButton } from '@documenso/ui/components/document/document-download-button';
|
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 { SigningCard3D } from '@documenso/ui/components/signing-card';
|
||||||
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';
|
||||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
|
||||||
|
|
||||||
import signingCelebration from '~/assets/signing-celebration.png';
|
import signingCelebration from '~/assets/signing-celebration.png';
|
||||||
import ConfettiScreen from '~/components/(marketing)/confetti-screen';
|
import { ConfettiScreen } from '~/components/(marketing)/confetti-screen';
|
||||||
|
|
||||||
import { DocumentStatus } from '.prisma/client';
|
|
||||||
|
|
||||||
interface SinglePlayerModeSuccessProps {
|
interface SinglePlayerModeSuccessProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
document: DocumentWithRecipient;
|
document: DocumentWithRecipient;
|
||||||
|
signatures: Signature[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SinglePlayerModeSuccess = ({ className, document }: SinglePlayerModeSuccessProps) => {
|
export const SinglePlayerModeSuccess = ({
|
||||||
|
className,
|
||||||
|
document,
|
||||||
|
signatures,
|
||||||
|
}: SinglePlayerModeSuccessProps) => {
|
||||||
const { getFlag } = useFeatureFlags();
|
const { getFlag } = useFeatureFlags();
|
||||||
|
|
||||||
const isConfettiEnabled = getFlag('marketing_spm_confetti');
|
const isConfettiEnabled = getFlag('marketing_spm_confetti');
|
||||||
|
|
||||||
const [showDocumentDialog, setShowDocumentDialog] = useState(false);
|
const [showDocumentDialog, setShowDocumentDialog] = useState(false);
|
||||||
const [isFetchingDocumentFile, setIsFetchingDocumentFile] = useState(false);
|
|
||||||
const [documentFile, setDocumentFile] = useState<string | null>(null);
|
|
||||||
|
|
||||||
const { toast } = useToast();
|
const { documentData } = document;
|
||||||
|
|
||||||
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);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.scrollTo({ top: 0 });
|
window.scrollTo({ top: 0 });
|
||||||
@ -80,6 +54,7 @@ export const SinglePlayerModeSuccess = ({ className, document }: SinglePlayerMod
|
|||||||
<SigningCard3D
|
<SigningCard3D
|
||||||
className="mt-8"
|
className="mt-8"
|
||||||
name={document.Recipient.name || document.Recipient.email}
|
name={document.Recipient.name || document.Recipient.email}
|
||||||
|
signature={signatures.at(0)}
|
||||||
signingCelebrationImage={signingCelebration}
|
signingCelebrationImage={signingCelebration}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -99,11 +74,7 @@ export const SinglePlayerModeSuccess = ({ className, document }: SinglePlayerMod
|
|||||||
disabled={document.status !== DocumentStatus.COMPLETED}
|
disabled={document.status !== DocumentStatus.COMPLETED}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button
|
<Button onClick={() => setShowDocumentDialog(true)} className="z-10 col-span-2">
|
||||||
onClick={async () => onShowDocumentClick()}
|
|
||||||
loading={isFetchingDocumentFile}
|
|
||||||
className="z-10 col-span-2"
|
|
||||||
>
|
|
||||||
Show document
|
Show document
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@ -123,7 +94,7 @@ export const SinglePlayerModeSuccess = ({ className, document }: SinglePlayerMod
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<DocumentDialog
|
<DocumentDialog
|
||||||
document={documentFile ?? ''}
|
documentData={documentData}
|
||||||
open={showDocumentDialog}
|
open={showDocumentDialog}
|
||||||
onOpenChange={setShowDocumentDialog}
|
onOpenChange={setShowDocumentDialog}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { match } from 'ts-pattern';
|
|||||||
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 { 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 { DocumentStatus, FieldType } from '@documenso/prisma/client';
|
import { DocumentStatus, FieldType } from '@documenso/prisma/client';
|
||||||
import { DocumentDownloadButton } from '@documenso/ui/components/document/document-download-button';
|
import { DocumentDownloadButton } from '@documenso/ui/components/document/document-download-button';
|
||||||
import { DocumentShareButton } from '@documenso/ui/components/document/document-share-button';
|
import { DocumentShareButton } from '@documenso/ui/components/document/document-share-button';
|
||||||
@ -46,6 +47,8 @@ export default async function CompletedSigningPage({
|
|||||||
return notFound();
|
return notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const signatures = await getRecipientSignatures({ recipientId: recipient.id });
|
||||||
|
|
||||||
const recipientName =
|
const recipientName =
|
||||||
recipient.name ||
|
recipient.name ||
|
||||||
fields.find((field) => field.type === FieldType.NAME)?.customText ||
|
fields.find((field) => field.type === FieldType.NAME)?.customText ||
|
||||||
@ -54,7 +57,11 @@ export default async function CompletedSigningPage({
|
|||||||
return (
|
return (
|
||||||
<div className="-mx-4 flex max-w-[100vw] flex-col items-center overflow-x-hidden px-4 pt-24 md:-mx-8 md:px-8 lg:pt-36 xl:pt-44">
|
<div className="-mx-4 flex max-w-[100vw] flex-col items-center overflow-x-hidden px-4 pt-24 md:-mx-8 md:px-8 lg:pt-36 xl:pt-44">
|
||||||
{/* Card with recipient */}
|
{/* Card with recipient */}
|
||||||
<SigningCard3D name={recipientName} signingCelebrationImage={signingCelebration} />
|
<SigningCard3D
|
||||||
|
name={recipientName}
|
||||||
|
signature={signatures.at(0)}
|
||||||
|
signingCelebrationImage={signingCelebration}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="relative mt-6 flex w-full flex-col items-center">
|
<div className="relative mt-6 flex w-full flex-col items-center">
|
||||||
{match(document.status)
|
{match(document.status)
|
||||||
@ -72,7 +79,8 @@ export default async function CompletedSigningPage({
|
|||||||
))}
|
))}
|
||||||
|
|
||||||
<h2 className="mt-6 max-w-[35ch] text-center text-2xl font-semibold leading-normal md:text-3xl lg:text-4xl">
|
<h2 className="mt-6 max-w-[35ch] text-center text-2xl font-semibold leading-normal md:text-3xl lg:text-4xl">
|
||||||
You have signed "{document.title}"
|
You have signed
|
||||||
|
<span className="mt-1.5 block">"{document.title}"</span>
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{match(document.status)
|
{match(document.status)
|
||||||
|
|||||||
3
package-lock.json
generated
3
package-lock.json
generated
@ -19913,7 +19913,8 @@
|
|||||||
"react-pdf": "7.3.3",
|
"react-pdf": "7.3.3",
|
||||||
"react-rnd": "^10.4.1",
|
"react-rnd": "^10.4.1",
|
||||||
"tailwind-merge": "^1.12.0",
|
"tailwind-merge": "^1.12.0",
|
||||||
"tailwindcss-animate": "^1.0.5"
|
"tailwindcss-animate": "^1.0.5",
|
||||||
|
"ts-pattern": "^5.0.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@documenso/tailwind-config": "*",
|
"@documenso/tailwind-config": "*",
|
||||||
|
|||||||
@ -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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -5,20 +5,20 @@ import { useState } from 'react';
|
|||||||
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||||
import { X } from 'lucide-react';
|
import { X } from 'lucide-react';
|
||||||
|
|
||||||
import { DocumentDataType } from '@documenso/prisma/client';
|
import { DocumentData } from '@documenso/prisma/client';
|
||||||
|
|
||||||
import { cn } from '../../lib/utils';
|
import { cn } from '../../lib/utils';
|
||||||
import { Dialog, DialogOverlay, DialogPortal } from '../../primitives/dialog';
|
import { Dialog, DialogOverlay, DialogPortal } from '../../primitives/dialog';
|
||||||
import { LazyPDFViewerNoLoader } from '../../primitives/lazy-pdf-viewer';
|
import { LazyPDFViewerNoLoader } from '../../primitives/lazy-pdf-viewer';
|
||||||
|
|
||||||
export type DocumentDialogProps = {
|
export type DocumentDialogProps = {
|
||||||
document: string;
|
documentData: DocumentData;
|
||||||
} & Omit<DialogPrimitive.DialogProps, 'children'>;
|
} & Omit<DialogPrimitive.DialogProps, 'children'>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A dialog which renders the provided document.
|
* 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 [documentLoaded, setDocumentLoaded] = useState(false);
|
||||||
|
|
||||||
const onDocumentLoad = () => {
|
const onDocumentLoad = () => {
|
||||||
@ -41,12 +41,7 @@ export default function DocumentDialog({ document, ...props }: DocumentDialogPro
|
|||||||
>
|
>
|
||||||
<LazyPDFViewerNoLoader
|
<LazyPDFViewerNoLoader
|
||||||
className="mx-auto w-full max-w-3xl xl:max-w-5xl"
|
className="mx-auto w-full max-w-3xl xl:max-w-5xl"
|
||||||
documentData={{
|
documentData={documentData}
|
||||||
id: '',
|
|
||||||
data: document,
|
|
||||||
initialData: document,
|
|
||||||
type: DocumentDataType.BYTES_64,
|
|
||||||
}}
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
onDocumentLoad={onDocumentLoad}
|
onDocumentLoad={onDocumentLoad}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -5,23 +5,31 @@ import { useCallback, useEffect, useRef, useState } from 'react';
|
|||||||
import Image, { StaticImageData } from 'next/image';
|
import Image, { StaticImageData } from 'next/image';
|
||||||
|
|
||||||
import { animate, motion, useMotionTemplate, useMotionValue, useTransform } from 'framer-motion';
|
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 { cn } from '@documenso/ui/lib/utils';
|
||||||
import { Card, CardContent } from '@documenso/ui/primitives/card';
|
import { Card, CardContent } from '@documenso/ui/primitives/card';
|
||||||
|
|
||||||
export type SigningCardProps = {
|
export type SigningCardProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
signature?: Signature;
|
||||||
signingCelebrationImage?: StaticImageData;
|
signingCelebrationImage?: StaticImageData;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 2D signing card.
|
* 2D signing card.
|
||||||
*/
|
*/
|
||||||
export const SigningCard = ({ className, name, signingCelebrationImage }: SigningCardProps) => {
|
export const SigningCard = ({
|
||||||
|
className,
|
||||||
|
name,
|
||||||
|
signature,
|
||||||
|
signingCelebrationImage,
|
||||||
|
}: SigningCardProps) => {
|
||||||
return (
|
return (
|
||||||
<div className={cn('relative w-full max-w-xs md:max-w-sm', className)}>
|
<div className={cn('relative w-full max-w-xs md:max-w-sm', className)}>
|
||||||
<SigningCardContent name={name} />
|
<SigningCardContent name={name} signature={signature} />
|
||||||
|
|
||||||
{signingCelebrationImage && (
|
{signingCelebrationImage && (
|
||||||
<SigningCardImage signingCelebrationImage={signingCelebrationImage} />
|
<SigningCardImage signingCelebrationImage={signingCelebrationImage} />
|
||||||
@ -33,7 +41,12 @@ export const SigningCard = ({ className, name, signingCelebrationImage }: Signin
|
|||||||
/**
|
/**
|
||||||
* 3D signing card that follows the mouse movement within a certain range.
|
* 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.
|
// Should use % based dimensions by calculating the window height/width.
|
||||||
const boundary = 400;
|
const boundary = 400;
|
||||||
|
|
||||||
@ -130,7 +143,7 @@ export const SigningCard3D = ({ className, name, signingCelebrationImage }: Sign
|
|||||||
rotateY,
|
rotateY,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SigningCardContent className="bg-transparent" name={name} />
|
<SigningCardContent className="bg-transparent" name={name} signature={signature} />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
{signingCelebrationImage && (
|
{signingCelebrationImage && (
|
||||||
@ -142,10 +155,11 @@ export const SigningCard3D = ({ className, name, signingCelebrationImage }: Sign
|
|||||||
|
|
||||||
type SigningCardContentProps = {
|
type SigningCardContentProps = {
|
||||||
name: string;
|
name: string;
|
||||||
|
signature?: Signature;
|
||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SigningCardContent = ({ className, name }: SigningCardContentProps) => {
|
const SigningCardContent = ({ className, name, signature }: SigningCardContentProps) => {
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
className={cn(
|
className={cn(
|
||||||
@ -161,14 +175,36 @@ const SigningCardContent = ({ className, name }: SigningCardContentProps) => {
|
|||||||
container: 'main',
|
container: 'main',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span
|
{match(signature)
|
||||||
className="text-muted-foreground/60 group-hover:text-primary/80 break-all font-semibold duration-300"
|
.with({ signatureImageAsBase64: P.string }, (signature) => (
|
||||||
style={{
|
<img
|
||||||
fontSize: `max(min(4rem, ${(100 / name.length / 2).toFixed(4)}cqw), 1.875rem)`,
|
src={signature.signatureImageAsBase64}
|
||||||
}}
|
alt="signature"
|
||||||
>
|
className="h-full max-w-[100%] dark:invert"
|
||||||
{name}
|
/>
|
||||||
</span>
|
))
|
||||||
|
.with({ typedSignature: P.string }, (signature) => (
|
||||||
|
<span
|
||||||
|
className="text-muted-foreground/60 group-hover:text-primary/80 break-all font-semibold duration-300"
|
||||||
|
style={{
|
||||||
|
fontSize: `max(min(4rem, ${(100 / signature.typedSignature.length / 2).toFixed(
|
||||||
|
4,
|
||||||
|
)}cqw), 1.875rem)`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{signature.typedSignature}
|
||||||
|
</span>
|
||||||
|
))
|
||||||
|
.otherwise(() => (
|
||||||
|
<span
|
||||||
|
className="text-muted-foreground/60 group-hover:text-primary/80 break-all font-semibold duration-300"
|
||||||
|
style={{
|
||||||
|
fontSize: `max(min(4rem, ${(100 / name.length / 2).toFixed(4)}cqw), 1.875rem)`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -67,6 +67,7 @@
|
|||||||
"react-pdf": "7.3.3",
|
"react-pdf": "7.3.3",
|
||||||
"react-rnd": "^10.4.1",
|
"react-rnd": "^10.4.1",
|
||||||
"tailwind-merge": "^1.12.0",
|
"tailwind-merge": "^1.12.0",
|
||||||
"tailwindcss-animate": "^1.0.5"
|
"tailwindcss-animate": "^1.0.5",
|
||||||
|
"ts-pattern": "^5.0.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user