Files
documenso/apps/remix/app/components/general/envelope-signing/envelope-signer-header.tsx
T
Catalin Pit a39e034876 feat: hide the reject option when the document disallows rejection
Hides the reject dialog on the V2 signing page sidebar and mobile menu,
the embed V1 signing page and the multi-sign embed view when the
document's allowDocumentRejection is false. In embeds the reject option
is shown only when both the embed's allowDocumentRejection option and
the document setting allow it, and the embed V1 footer layout follows the
same condition. Exposes the field in the multi-sign document response so
the embed view can read it.

Documents the relationship between the embed's allowDocumentRejection
option and the document setting in the iframe and embedded editor docs,
and in the API description of the field.

Part of #2561
2026-09-14 16:45:27 +03:00

138 lines
5.3 KiB
TypeScript

import { mapSecondaryIdToDocumentId } from '@documenso/lib/utils/envelope';
import { Badge } from '@documenso/ui/primitives/badge';
import { Button } from '@documenso/ui/primitives/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@documenso/ui/primitives/dropdown-menu';
import { Separator } from '@documenso/ui/primitives/separator';
import { Plural, Trans } from '@lingui/react/macro';
import { EnvelopeType, RecipientRole } from '@prisma/client';
import { BanIcon, DownloadCloudIcon } from 'lucide-react';
import { Link } from 'react-router';
import { match } from 'ts-pattern';
import { EnvelopeDownloadDialog } from '~/components/dialogs/envelope-download-dialog';
import { useEmbedSigningContext } from '~/components/embed/embed-signing-context';
import { BrandingLogo } from '~/components/general/branding-logo';
import { BrandingLogoIcon } from '../branding-logo-icon';
import { DocumentSigningRejectDialog } from '../document-signing/document-signing-reject-dialog';
import { useRequiredEnvelopeSigningContext } from '../document-signing/envelope-signing-provider';
import { EnvelopeSignerCompleteDialog } from './envelope-signing-complete-dialog';
export const EnvelopeSignerHeader = () => {
const { envelopeData, envelope, recipientFieldsRemaining, recipient } = useRequiredEnvelopeSigningContext();
const isEmbedSigning = useEmbedSigningContext() !== null;
const hasCustomBrandingLogo = envelopeData.settings.brandingEnabled && Boolean(envelopeData.settings.brandingLogo);
return (
<nav className="embed--DocumentWidgetHeader flex max-w-screen flex-row justify-between border-border border-b bg-background px-4 py-3 md:px-6">
{/* Left side - Logo and title */}
<div className="flex min-w-0 flex-1 items-center space-x-2 md:w-auto md:flex-none">
{!isEmbedSigning &&
(hasCustomBrandingLogo ? (
<img
src={`/api/branding/logo/team/${envelope.teamId}`}
alt={`${envelope.team.name}'s Logo`}
className="h-6 w-auto flex-shrink-0"
/>
) : (
<Link to="/" className="flex-shrink-0">
<BrandingLogo className="hidden h-6 w-auto md:block" />
<BrandingLogoIcon className="h-6 w-auto md:hidden" />
</Link>
))}
<h1 title={envelope.title} className="min-w-0 truncate font-semibold text-base text-foreground md:hidden">
{envelope.title}
</h1>
{!isEmbedSigning && <Separator orientation="vertical" className="hidden h-6 md:block" />}
<div className="hidden items-center space-x-2 md:flex">
<h1 className="whitespace-nowrap font-medium text-foreground text-sm">{envelope.title}</h1>
<Badge>
{match(recipient.role)
.with(RecipientRole.VIEWER, () => <Trans>Viewer</Trans>)
.with(RecipientRole.SIGNER, () => <Trans>Signer</Trans>)
.with(RecipientRole.APPROVER, () => <Trans>Approver</Trans>)
.with(RecipientRole.ASSISTANT, () => <Trans>Assistant</Trans>)
.otherwise(() => null)}
</Badge>
</div>
</div>
{/* Right side - Desktop content */}
<div className="hidden items-center space-x-2 lg:flex">
<p className="mr-2 flex-shrink-0 text-muted-foreground text-sm">
<Plural one="1 Field Remaining" other="# Fields Remaining" value={recipientFieldsRemaining.length} />
</p>
<EnvelopeSignerCompleteDialog />
</div>
{/* Mobile Actions button */}
<div className="flex-shrink-0 lg:hidden">
<MobileDropdownMenu />
</div>
</nav>
);
};
const MobileDropdownMenu = () => {
const { envelope, recipient } = useRequiredEnvelopeSigningContext();
const { allowDocumentRejection = true } = useEmbedSigningContext() || {};
// Both the document settings and the embed host (if any) must allow rejection.
const isDocumentRejectionAllowed =
envelope.type === EnvelopeType.DOCUMENT && envelope.documentMeta.allowDocumentRejection && allowDocumentRejection;
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm">
<Trans>Actions</Trans>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<EnvelopeDownloadDialog
envelopeId={envelope.id}
envelopeStatus={envelope.status}
envelopeItems={envelope.envelopeItems}
token={recipient.token}
trigger={
<DropdownMenuItem asChild onSelect={(e) => e.preventDefault()}>
<div>
<DownloadCloudIcon className="mr-2 h-4 w-4" />
<Trans>Download PDF</Trans>
</div>
</DropdownMenuItem>
}
/>
{isDocumentRejectionAllowed && (
<DocumentSigningRejectDialog
documentId={mapSecondaryIdToDocumentId(envelope.secondaryId)}
token={recipient.token}
trigger={
<DropdownMenuItem asChild onSelect={(e) => e.preventDefault()}>
<div>
<BanIcon className="mr-2 h-4 w-4" />
<Trans>Reject</Trans>
</div>
</DropdownMenuItem>
}
/>
)}
</DropdownMenuContent>
</DropdownMenu>
);
};