mirror of
https://github.com/documenso/documenso.git
synced 2025-11-18 10:42:01 +10:00
feat: show recipient as expired on document page view
This commit is contained in:
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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" />
|
||||||
|
|||||||
Reference in New Issue
Block a user