mirror of
https://github.com/documenso/documenso.git
synced 2025-11-23 05:01:54 +10:00
feat: signing order (#1290)
Adds the ability to specify an optional signing order for documents. When specified a document will be considered sequential with recipients only being allowed to sign in the order that they were specified in.
This commit is contained in:
@ -29,9 +29,16 @@ export type SigningFormProps = {
|
||||
recipient: Recipient;
|
||||
fields: Field[];
|
||||
redirectUrl?: string | null;
|
||||
isRecipientsTurn: boolean;
|
||||
};
|
||||
|
||||
export const SigningForm = ({ document, recipient, fields, redirectUrl }: SigningFormProps) => {
|
||||
export const SigningForm = ({
|
||||
document,
|
||||
recipient,
|
||||
fields,
|
||||
redirectUrl,
|
||||
isRecipientsTurn,
|
||||
}: SigningFormProps) => {
|
||||
const router = useRouter();
|
||||
const analytics = useAnalytics();
|
||||
const { data: session } = useSession();
|
||||
@ -150,6 +157,7 @@ export const SigningForm = ({ document, recipient, fields, redirectUrl }: Signin
|
||||
fields={fields}
|
||||
fieldsValidated={fieldsValidated}
|
||||
role={recipient.role}
|
||||
disabled={!isRecipientsTurn}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -213,6 +221,7 @@ export const SigningForm = ({ document, recipient, fields, redirectUrl }: Signin
|
||||
fields={fields}
|
||||
fieldsValidated={fieldsValidated}
|
||||
role={recipient.role}
|
||||
disabled={!isRecipientsTurn}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -9,6 +9,7 @@ import { isRecipientAuthorized } from '@documenso/lib/server-only/document/is-re
|
||||
import { viewedDocument } from '@documenso/lib/server-only/document/viewed-document';
|
||||
import { getCompletedFieldsForToken } from '@documenso/lib/server-only/field/get-completed-fields-for-token';
|
||||
import { getFieldsForToken } from '@documenso/lib/server-only/field/get-fields-for-token';
|
||||
import { getIsRecipientsTurnToSign } from '@documenso/lib/server-only/recipient/get-is-recipient-turn';
|
||||
import { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-recipient-by-token';
|
||||
import { getRecipientSignatures } from '@documenso/lib/server-only/recipient/get-recipient-signatures';
|
||||
import { getUserByEmail } from '@documenso/lib/server-only/user/get-user-by-email';
|
||||
@ -42,6 +43,12 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
|
||||
|
||||
const requestMetadata = extractNextHeaderRequestMetadata(requestHeaders);
|
||||
|
||||
const isRecipientsTurn = await getIsRecipientsTurnToSign({ token });
|
||||
|
||||
if (!isRecipientsTurn) {
|
||||
return redirect(`/sign/${token}/waiting`);
|
||||
}
|
||||
|
||||
const [document, fields, recipient, completedFields] = await Promise.all([
|
||||
getDocumentAndSenderByToken({
|
||||
token,
|
||||
@ -146,6 +153,7 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
|
||||
document={document}
|
||||
fields={fields}
|
||||
completedFields={completedFields}
|
||||
isRecipientsTurn={isRecipientsTurn}
|
||||
/>
|
||||
</DocumentAuthProvider>
|
||||
</SigningProvider>
|
||||
|
||||
@ -23,6 +23,7 @@ export type SignDialogProps = {
|
||||
fieldsValidated: () => void | Promise<void>;
|
||||
onSignatureComplete: () => void | Promise<void>;
|
||||
role: RecipientRole;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export const SignDialog = ({
|
||||
@ -32,6 +33,7 @@ export const SignDialog = ({
|
||||
fieldsValidated,
|
||||
onSignatureComplete,
|
||||
role,
|
||||
disabled = false,
|
||||
}: SignDialogProps) => {
|
||||
const [showDialog, setShowDialog] = useState(false);
|
||||
const truncatedTitle = truncateTitle(documentTitle);
|
||||
@ -54,6 +56,7 @@ export const SignDialog = ({
|
||||
size="lg"
|
||||
onClick={fieldsValidated}
|
||||
loading={isSubmitting}
|
||||
disabled={disabled}
|
||||
>
|
||||
{isComplete ? <Trans>Complete</Trans> : <Trans>Next field</Trans>}
|
||||
</Button>
|
||||
|
||||
@ -39,6 +39,7 @@ export type SigningPageViewProps = {
|
||||
recipient: Recipient;
|
||||
fields: Field[];
|
||||
completedFields: CompletedField[];
|
||||
isRecipientsTurn: boolean;
|
||||
};
|
||||
|
||||
export const SigningPageView = ({
|
||||
@ -46,6 +47,7 @@ export const SigningPageView = ({
|
||||
recipient,
|
||||
fields,
|
||||
completedFields,
|
||||
isRecipientsTurn,
|
||||
}: SigningPageViewProps) => {
|
||||
const { documentData, documentMeta } = document;
|
||||
|
||||
@ -99,6 +101,7 @@ export const SigningPageView = ({
|
||||
recipient={recipient}
|
||||
fields={fields}
|
||||
redirectUrl={documentMeta?.redirectUrl}
|
||||
isRecipientsTurn={isRecipientsTurn}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
100
apps/web/src/app/(signing)/sign/[token]/waiting/page.tsx
Normal file
100
apps/web/src/app/(signing)/sign/[token]/waiting/page.tsx
Normal file
@ -0,0 +1,100 @@
|
||||
import Link from 'next/link';
|
||||
import { notFound, redirect } from 'next/navigation';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
|
||||
import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server';
|
||||
import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
|
||||
import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id';
|
||||
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
|
||||
import { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-recipient-by-token';
|
||||
import { getTeamById } from '@documenso/lib/server-only/team/get-team';
|
||||
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
|
||||
import type { Team } from '@documenso/prisma/client';
|
||||
import { DocumentStatus } from '@documenso/prisma/client';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
|
||||
type WaitingForTurnToSignPageProps = {
|
||||
params: { token?: string };
|
||||
};
|
||||
|
||||
export default async function WaitingForTurnToSignPage({
|
||||
params: { token },
|
||||
}: WaitingForTurnToSignPageProps) {
|
||||
setupI18nSSR();
|
||||
|
||||
if (!token) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
const { user } = await getServerComponentSession();
|
||||
|
||||
const [document, recipient] = await Promise.all([
|
||||
getDocumentAndSenderByToken({ token }).catch(() => null),
|
||||
getRecipientByToken({ token }).catch(() => null),
|
||||
]);
|
||||
|
||||
if (!document || !recipient) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
if (document.status === DocumentStatus.COMPLETED) {
|
||||
return redirect(`/sign/${token}/complete`);
|
||||
}
|
||||
|
||||
let isOwnerOrTeamMember = false;
|
||||
|
||||
let team: Team | null = null;
|
||||
|
||||
if (user) {
|
||||
isOwnerOrTeamMember = await getDocumentById({
|
||||
id: document.id,
|
||||
userId: user.id,
|
||||
teamId: document.teamId ?? undefined,
|
||||
})
|
||||
.then((document) => !!document)
|
||||
.catch(() => false);
|
||||
|
||||
if (document.teamId) {
|
||||
team = await getTeamById({
|
||||
userId: user.id,
|
||||
teamId: document.teamId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-col items-center justify-center px-4 py-12 sm:px-6 lg:px-8">
|
||||
<div className="w-full max-w-md text-center">
|
||||
<h2 className="tracking-tigh text-3xl font-bold">
|
||||
<Trans>Waiting for Your Turn</Trans>
|
||||
</h2>
|
||||
|
||||
<p className="text-muted-foreground mt-2 text-sm">
|
||||
<Trans>
|
||||
It's currently not your turn to sign. You will receive an email with instructions once
|
||||
it's your turn to sign the document.
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<p className="text-muted-foreground mt-4 text-sm">
|
||||
<Trans>Please check your email for updates.</Trans>
|
||||
</p>
|
||||
|
||||
<div className="mt-4">
|
||||
{isOwnerOrTeamMember ? (
|
||||
<Button variant="link" asChild>
|
||||
<Link href={`${formatDocumentsPath(team?.url)}/${document.id}`}>
|
||||
<Trans>Were you trying to edit this document instead?</Trans>
|
||||
</Link>
|
||||
</Button>
|
||||
) : (
|
||||
<Button variant="link" asChild>
|
||||
<Link href="/documents">Return Home</Link>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user