feat: show recipient as expired on document page view

This commit is contained in:
Ephraim Atta-Duncan
2024-11-17 17:52:28 +00:00
parent 6e9d17f8ea
commit ba627e22c5
4 changed files with 73 additions and 8 deletions

View File

@ -12,13 +12,14 @@ import {
MailOpenIcon, MailOpenIcon,
PenIcon, PenIcon,
PlusIcon, PlusIcon,
Timer,
} from 'lucide-react'; } from 'lucide-react';
import { match } from 'ts-pattern'; import { match } from 'ts-pattern';
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles'; import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
import { formatSigningLink } from '@documenso/lib/utils/recipients'; import { formatSigningLink } from '@documenso/lib/utils/recipients';
import { DocumentStatus, RecipientRole, SigningStatus } from '@documenso/prisma/client';
import type { Document, Recipient } from '@documenso/prisma/client'; import type { Document, Recipient } from '@documenso/prisma/client';
import { DocumentStatus, RecipientRole, SigningStatus } from '@documenso/prisma/client';
import { CopyTextButton } from '@documenso/ui/components/common/copy-text-button'; import { CopyTextButton } from '@documenso/ui/components/common/copy-text-button';
import { SignatureIcon } from '@documenso/ui/icons/signature'; import { SignatureIcon } from '@documenso/ui/icons/signature';
import { AvatarWithText } from '@documenso/ui/primitives/avatar'; import { AvatarWithText } from '@documenso/ui/primitives/avatar';
@ -132,6 +133,14 @@ export const DocumentPageViewRecipients = ({
</Badge> </Badge>
)} )}
{document.status !== DocumentStatus.DRAFT &&
recipient.signingStatus === SigningStatus.EXPIRED && (
<Badge variant="destructive">
<Timer className="mr-1 h-3 w-3" />
<Trans>Expired</Trans>
</Badge>
)}
{document.status !== DocumentStatus.DRAFT && {document.status !== DocumentStatus.DRAFT &&
recipient.signingStatus === SigningStatus.REJECTED && ( recipient.signingStatus === SigningStatus.REJECTED && (
<PopoverHover <PopoverHover

View File

@ -15,9 +15,8 @@ import { getRecipientsForDocument } from '@documenso/lib/server-only/recipient/g
import { DocumentVisibility } from '@documenso/lib/types/document-visibility'; import { DocumentVisibility } from '@documenso/lib/types/document-visibility';
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';
import { DocumentStatus } from '@documenso/prisma/client';
import type { Team, TeamEmail } from '@documenso/prisma/client'; import type { Team, TeamEmail } from '@documenso/prisma/client';
import { TeamMemberRole } from '@documenso/prisma/client'; import { DocumentStatus, SigningStatus, TeamMemberRole } from '@documenso/prisma/client';
import { Badge } from '@documenso/ui/primitives/badge'; import { Badge } from '@documenso/ui/primitives/badge';
import { Button } from '@documenso/ui/primitives/button'; import { Button } from '@documenso/ui/primitives/button';
import { Card, CardContent } from '@documenso/ui/primitives/card'; import { Card, CardContent } from '@documenso/ui/primitives/card';
@ -218,7 +217,7 @@ export const DocumentPageView = async ({ params, team }: DocumentPageViewProps)
<DocumentPageViewDropdown document={documentWithRecipients} team={team} /> <DocumentPageViewDropdown document={documentWithRecipients} team={team} />
</div> </div>
<p className="text-muted-foreground mt-2 px-4 text-sm "> <p className="text-muted-foreground mt-2 px-4 text-sm">
{match(document.status) {match(document.status)
.with(DocumentStatus.COMPLETED, () => ( .with(DocumentStatus.COMPLETED, () => (
<Trans>This document has been signed by all recipients</Trans> <Trans>This document has been signed by all recipients</Trans>
@ -228,8 +227,52 @@ export const DocumentPageView = async ({ params, team }: DocumentPageViewProps)
)) ))
.with(DocumentStatus.PENDING, () => { .with(DocumentStatus.PENDING, () => {
const pendingRecipients = recipients.filter( const pendingRecipients = recipients.filter(
(recipient) => recipient.signingStatus === 'NOT_SIGNED', (recipient) => recipient.signingStatus === SigningStatus.NOT_SIGNED,
); );
const rejectedCount = recipients.filter(
(recipient) => recipient.signingStatus === SigningStatus.REJECTED,
).length;
const expiredCount = recipients.filter(
(recipient) => recipient.signingStatus === SigningStatus.EXPIRED,
).length;
if (rejectedCount > 0 && expiredCount > 0) {
return (
<>
<Plural
value={rejectedCount}
one="1 recipient has rejected the document"
other="# recipients have rejected the document"
/>
{' and '}
<Plural
value={expiredCount}
one="1 recipient's signing link has expired"
other="# recipients' signing links have expired"
/>
</>
);
}
if (rejectedCount > 0) {
return (
<Plural
value={rejectedCount}
one="1 recipient has rejected the document"
other="# recipients have rejected the document"
/>
);
}
if (expiredCount > 0) {
return (
<Plural
value={expiredCount}
one="1 recipient's signing link has expired"
other="# recipients' signing links have expired"
/>
);
}
return ( return (
<Plural <Plural

View File

@ -2,7 +2,7 @@ import Link from 'next/link';
import { notFound } from 'next/navigation'; import { notFound } from 'next/navigation';
import { Trans } from '@lingui/macro'; import { Trans } from '@lingui/macro';
import { Clock } from 'lucide-react'; import { Timer } from 'lucide-react';
import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server'; import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server';
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session'; import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
@ -67,7 +67,7 @@ export default async function ExpiredSigningPage({ params: { token } }: ExpiredS
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<div className="flex items-center gap-x-4"> <div className="flex items-center gap-x-4">
<Clock className="text-destructive h-10 w-10" /> <Timer className="text-destructive h-10 w-10" />
<h2 className="max-w-[35ch] text-center text-2xl font-semibold leading-normal md:text-3xl lg:text-4xl"> <h2 className="max-w-[35ch] text-center text-2xl font-semibold leading-normal md:text-3xl lg:text-4xl">
<Trans>Document Expired</Trans> <Trans>Document Expired</Trans>
</h2> </h2>

View File

@ -4,7 +4,7 @@ import { useState } from 'react';
import { Trans } from '@lingui/macro'; import { Trans } from '@lingui/macro';
import { useLingui } from '@lingui/react'; import { useLingui } from '@lingui/react';
import { Clock, EyeOffIcon } from 'lucide-react'; import { AlertTriangle, Clock, EyeOffIcon, Timer } from 'lucide-react';
import { P, match } from 'ts-pattern'; import { P, match } from 'ts-pattern';
import { import {
@ -75,6 +75,9 @@ export const DocumentReadOnlyFields = ({
variant={ variant={
field.Recipient.signingStatus === SigningStatus.SIGNED field.Recipient.signingStatus === SigningStatus.SIGNED
? 'default' ? 'default'
: field.Recipient.signingStatus === SigningStatus.REJECTED ||
field.Recipient.signingStatus === SigningStatus.EXPIRED
? 'destructive'
: 'secondary' : 'secondary'
} }
> >
@ -83,6 +86,16 @@ export const DocumentReadOnlyFields = ({
<SignatureIcon className="mr-1 h-3 w-3" /> <SignatureIcon className="mr-1 h-3 w-3" />
<Trans>Signed</Trans> <Trans>Signed</Trans>
</> </>
) : field.Recipient.signingStatus === SigningStatus.REJECTED ? (
<>
<AlertTriangle className="mr-1 h-3 w-3" />
<Trans>Rejected</Trans>
</>
) : field.Recipient.signingStatus === SigningStatus.EXPIRED ? (
<>
<Timer className="mr-1 h-3 w-3" />
<Trans>Expired</Trans>
</>
) : ( ) : (
<> <>
<Clock className="mr-1 h-3 w-3" /> <Clock className="mr-1 h-3 w-3" />