Add document signing on the new ui
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
15
apps/console2/src/components/documentSigning/ProgressBar.tsx
Normal file
15
apps/console2/src/components/documentSigning/ProgressBar.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
interface ProgressBarProps {
|
||||||
|
value: number;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ProgressBar({ value, className }: ProgressBarProps) {
|
||||||
|
return (
|
||||||
|
<div className={`w-full bg-gray-200 rounded-full h-2 ${className}`}>
|
||||||
|
<div
|
||||||
|
className="bg-blue-600 h-2 rounded-full transition-all duration-300"
|
||||||
|
style={{ width: `${Math.min(100, Math.max(0, value))}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import { useTranslate } from "@probo/i18n";
|
|||||||
import { graphql } from "relay-runtime";
|
import { graphql } from "relay-runtime";
|
||||||
import { useMutationWithToasts } from "../useMutationWithToasts";
|
import { useMutationWithToasts } from "../useMutationWithToasts";
|
||||||
import type { DocumentGraphDeleteMutation } from "./__generated__/DocumentGraphDeleteMutation.graphql";
|
import type { DocumentGraphDeleteMutation } from "./__generated__/DocumentGraphDeleteMutation.graphql";
|
||||||
|
import type { DocumentGraphSendSigningNotificationsMutation } from "./__generated__/DocumentGraphSendSigningNotificationsMutation.graphql";
|
||||||
|
|
||||||
export const documentsQuery = graphql`
|
export const documentsQuery = graphql`
|
||||||
query DocumentGraphListQuery($organizationId: ID!) {
|
query DocumentGraphListQuery($organizationId: ID!) {
|
||||||
@@ -37,6 +38,28 @@ export function useDeleteDocumentMutation() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sendSigningNotificationsMutation = graphql`
|
||||||
|
mutation DocumentGraphSendSigningNotificationsMutation(
|
||||||
|
$input: SendSigningNotificationsInput!
|
||||||
|
) {
|
||||||
|
sendSigningNotifications(input: $input) {
|
||||||
|
success
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function useSendSigningNotificationsMutation() {
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
|
||||||
|
return useMutationWithToasts<DocumentGraphSendSigningNotificationsMutation>(
|
||||||
|
sendSigningNotificationsMutation,
|
||||||
|
{
|
||||||
|
successMessage: __("Signing notifications sent successfully."),
|
||||||
|
errorMessage: __("Failed to send signing notifications. Please try again."),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export const documentNodeQuery = graphql`
|
export const documentNodeQuery = graphql`
|
||||||
query DocumentGraphNodeQuery($documentId: ID!) {
|
query DocumentGraphNodeQuery($documentId: ID!) {
|
||||||
node(id: $documentId) {
|
node(id: $documentId) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @generated SignedSource<<eb81ead93d6ec8abc015415a48daaf08>>
|
* @generated SignedSource<<00b3c915ccd04831f9161442e27b635e>>
|
||||||
* @lightSyntaxTransform
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -42,14 +42,6 @@ export type AssetGraphListQuery$data = {
|
|||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
readonly peoples?: {
|
|
||||||
readonly edges: ReadonlyArray<{
|
|
||||||
readonly node: {
|
|
||||||
readonly fullName: string;
|
|
||||||
readonly id: string;
|
|
||||||
};
|
|
||||||
}>;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export type AssetGraphListQuery = {
|
export type AssetGraphListQuery = {
|
||||||
@@ -233,42 +225,7 @@ v15 = [
|
|||||||
"name": "first",
|
"name": "first",
|
||||||
"value": 100
|
"value": 100
|
||||||
}
|
}
|
||||||
],
|
];
|
||||||
v16 = {
|
|
||||||
"alias": null,
|
|
||||||
"args": (v15/*: any*/),
|
|
||||||
"concreteType": "PeopleConnection",
|
|
||||||
"kind": "LinkedField",
|
|
||||||
"name": "peoples",
|
|
||||||
"plural": false,
|
|
||||||
"selections": [
|
|
||||||
{
|
|
||||||
"alias": null,
|
|
||||||
"args": null,
|
|
||||||
"concreteType": "PeopleEdge",
|
|
||||||
"kind": "LinkedField",
|
|
||||||
"name": "edges",
|
|
||||||
"plural": true,
|
|
||||||
"selections": [
|
|
||||||
{
|
|
||||||
"alias": null,
|
|
||||||
"args": null,
|
|
||||||
"concreteType": "People",
|
|
||||||
"kind": "LinkedField",
|
|
||||||
"name": "node",
|
|
||||||
"plural": false,
|
|
||||||
"selections": [
|
|
||||||
(v2/*: any*/),
|
|
||||||
(v8/*: any*/)
|
|
||||||
],
|
|
||||||
"storageKey": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"storageKey": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"storageKey": "peoples(first:100)"
|
|
||||||
};
|
|
||||||
return {
|
return {
|
||||||
"fragment": {
|
"fragment": {
|
||||||
"argumentDefinitions": (v0/*: any*/),
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
@@ -343,8 +300,7 @@ return {
|
|||||||
(v14/*: any*/)
|
(v14/*: any*/)
|
||||||
],
|
],
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
},
|
}
|
||||||
(v16/*: any*/)
|
|
||||||
],
|
],
|
||||||
"type": "Organization",
|
"type": "Organization",
|
||||||
"abstractKey": null
|
"abstractKey": null
|
||||||
@@ -440,8 +396,7 @@ return {
|
|||||||
"key": "AssetsPage_assets",
|
"key": "AssetsPage_assets",
|
||||||
"kind": "LinkedHandle",
|
"kind": "LinkedHandle",
|
||||||
"name": "assets"
|
"name": "assets"
|
||||||
},
|
}
|
||||||
(v16/*: any*/)
|
|
||||||
],
|
],
|
||||||
"type": "Organization",
|
"type": "Organization",
|
||||||
"abstractKey": null
|
"abstractKey": null
|
||||||
@@ -453,7 +408,7 @@ return {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"params": {
|
"params": {
|
||||||
"cacheID": "2b452fda67547e63fb04fd4c6ae3ec7e",
|
"cacheID": "b070f08982995825d2695f833f773f5e",
|
||||||
"id": null,
|
"id": null,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"connection": [
|
"connection": [
|
||||||
@@ -470,11 +425,11 @@ return {
|
|||||||
},
|
},
|
||||||
"name": "AssetGraphListQuery",
|
"name": "AssetGraphListQuery",
|
||||||
"operationKind": "query",
|
"operationKind": "query",
|
||||||
"text": "query AssetGraphListQuery(\n $organizationId: ID!\n) {\n node(id: $organizationId) {\n __typename\n ... on Organization {\n assets(first: 100) {\n edges {\n node {\n id\n name\n amount\n criticity\n assetType\n dataTypesStored\n owner {\n fullName\n id\n }\n vendors(first: 50) {\n edges {\n node {\n id\n name\n websiteUrl\n }\n }\n }\n createdAt\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n peoples(first: 100) {\n edges {\n node {\n id\n fullName\n }\n }\n }\n }\n id\n }\n}\n"
|
"text": "query AssetGraphListQuery(\n $organizationId: ID!\n) {\n node(id: $organizationId) {\n __typename\n ... on Organization {\n assets(first: 100) {\n edges {\n node {\n id\n name\n amount\n criticity\n assetType\n dataTypesStored\n owner {\n fullName\n id\n }\n vendors(first: 50) {\n edges {\n node {\n id\n name\n websiteUrl\n }\n }\n }\n createdAt\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n id\n }\n}\n"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
(node as any).hash = "63f6a6df2bdbcd5539e28f892e54c686";
|
(node as any).hash = "79e2cf57067fabd4b124e6c0b47220dd";
|
||||||
|
|
||||||
export default node;
|
export default node;
|
||||||
|
|||||||
92
apps/console2/src/hooks/graph/__generated__/DocumentGraphSendSigningNotificationsMutation.graphql.ts
generated
Normal file
92
apps/console2/src/hooks/graph/__generated__/DocumentGraphSendSigningNotificationsMutation.graphql.ts
generated
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<242a8628b9203b02dccea5e311141ec4>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
|
export type SendSigningNotificationsInput = {
|
||||||
|
organizationId: string;
|
||||||
|
};
|
||||||
|
export type DocumentGraphSendSigningNotificationsMutation$variables = {
|
||||||
|
input: SendSigningNotificationsInput;
|
||||||
|
};
|
||||||
|
export type DocumentGraphSendSigningNotificationsMutation$data = {
|
||||||
|
readonly sendSigningNotifications: {
|
||||||
|
readonly success: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export type DocumentGraphSendSigningNotificationsMutation = {
|
||||||
|
response: DocumentGraphSendSigningNotificationsMutation$data;
|
||||||
|
variables: DocumentGraphSendSigningNotificationsMutation$variables;
|
||||||
|
};
|
||||||
|
|
||||||
|
const node: ConcreteRequest = (function(){
|
||||||
|
var v0 = [
|
||||||
|
{
|
||||||
|
"defaultValue": null,
|
||||||
|
"kind": "LocalArgument",
|
||||||
|
"name": "input"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
v1 = [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "input",
|
||||||
|
"variableName": "input"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"concreteType": "SendSigningNotificationsPayload",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "sendSigningNotifications",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "success",
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
];
|
||||||
|
return {
|
||||||
|
"fragment": {
|
||||||
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
|
"kind": "Fragment",
|
||||||
|
"metadata": null,
|
||||||
|
"name": "DocumentGraphSendSigningNotificationsMutation",
|
||||||
|
"selections": (v1/*: any*/),
|
||||||
|
"type": "Mutation",
|
||||||
|
"abstractKey": null
|
||||||
|
},
|
||||||
|
"kind": "Request",
|
||||||
|
"operation": {
|
||||||
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
|
"kind": "Operation",
|
||||||
|
"name": "DocumentGraphSendSigningNotificationsMutation",
|
||||||
|
"selections": (v1/*: any*/)
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"cacheID": "111167f828fa6eaad3beb44c43e43f06",
|
||||||
|
"id": null,
|
||||||
|
"metadata": {},
|
||||||
|
"name": "DocumentGraphSendSigningNotificationsMutation",
|
||||||
|
"operationKind": "mutation",
|
||||||
|
"text": "mutation DocumentGraphSendSigningNotificationsMutation(\n $input: SendSigningNotificationsInput!\n) {\n sendSigningNotifications(input: $input) {\n success\n }\n}\n"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
(node as any).hash = "eaf2fba7629e202537cfc7bd9b5c9586";
|
||||||
|
|
||||||
|
export default node;
|
||||||
264
apps/console2/src/pages/DocumentSigningRequestsPage.tsx
Normal file
264
apps/console2/src/pages/DocumentSigningRequestsPage.tsx
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useSearchParams } from "react-router";
|
||||||
|
import { useTranslate } from "@probo/i18n";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Markdown,
|
||||||
|
IconCheckmark1,
|
||||||
|
IconCircleProgress
|
||||||
|
} from "@probo/ui";
|
||||||
|
import { ProgressBar } from "../components/documentSigning/ProgressBar";
|
||||||
|
|
||||||
|
type Document = {
|
||||||
|
document_version_id: string;
|
||||||
|
title: string;
|
||||||
|
content: string;
|
||||||
|
signed?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type DocumentSigningResponse = {
|
||||||
|
documents: Document[];
|
||||||
|
requesterName: string;
|
||||||
|
requesterOrganization: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function DocumentSigningRequestsPage() {
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const token = searchParams.get("token");
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [signingData, setSigningData] = useState<DocumentSigningResponse | null>(null);
|
||||||
|
const [currentDocIndex, setCurrentDocIndex] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!token) {
|
||||||
|
setError(__("Missing signing token. Please check your URL and try again."));
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchDocuments() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`${import.meta.env.VITE_API_URL}/api/console/v1/documents/signing-requests`,
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(__("Failed to fetch signing documents"));
|
||||||
|
}
|
||||||
|
|
||||||
|
const documents: Document[] = await response.json();
|
||||||
|
|
||||||
|
const enhancedDocuments = documents.map((doc) => ({
|
||||||
|
...doc,
|
||||||
|
signed: false,
|
||||||
|
}));
|
||||||
|
|
||||||
|
setSigningData({
|
||||||
|
documents: enhancedDocuments,
|
||||||
|
requesterName: "Requester",
|
||||||
|
requesterOrganization: "Organization",
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
setError(
|
||||||
|
err instanceof Error ? err.message : __("An unknown error occurred"),
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchDocuments();
|
||||||
|
}, [token, __]);
|
||||||
|
|
||||||
|
const handleSignDocument = async () => {
|
||||||
|
if (!signingData || !token) return;
|
||||||
|
|
||||||
|
const docToSign = signingData.documents[currentDocIndex];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`${import.meta.env.VITE_API_URL}/api/console/v1/documents/signing-requests/${docToSign.document_version_id}/sign`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(__("Failed to sign document"));
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedDocs = [...signingData.documents];
|
||||||
|
updatedDocs[currentDocIndex] = {
|
||||||
|
...updatedDocs[currentDocIndex],
|
||||||
|
signed: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
setSigningData({
|
||||||
|
...signingData,
|
||||||
|
documents: updatedDocs,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (currentDocIndex < updatedDocs.length - 1) {
|
||||||
|
setCurrentDocIndex(currentDocIndex + 1);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof Error ? err.message : __("Failed to sign document"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNextDocument = () => {
|
||||||
|
if (signingData && currentDocIndex < signingData.documents.length - 1) {
|
||||||
|
setCurrentDocIndex(currentDocIndex + 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSignedCount = () => {
|
||||||
|
if (!signingData) return 0;
|
||||||
|
return signingData.documents.filter((doc) => doc.signed).length;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getProgressPercentage = () => {
|
||||||
|
if (!signingData || signingData.documents.length === 0) return 0;
|
||||||
|
return (getSignedCount() / signingData.documents.length) * 100;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<title>{__("Loading Signing Requests")}</title>
|
||||||
|
<div className="flex justify-center items-center min-h-screen">
|
||||||
|
<Card padded className="w-full max-w-3xl">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<IconCircleProgress size={24} className="text-txt-accent" />
|
||||||
|
<div>
|
||||||
|
<h1 className="text-xl font-semibold">{__("Loading Signing Requests")}</h1>
|
||||||
|
<p className="text-txt-tertiary">
|
||||||
|
{__("Please wait while we fetch your documents...")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<title>{__("Error")}</title>
|
||||||
|
<div className="flex justify-center items-center min-h-screen">
|
||||||
|
<Card padded className="w-full max-w-3xl">
|
||||||
|
<h1 className="text-xl font-semibold text-red-600 mb-2">{__("Error")}</h1>
|
||||||
|
<p className="text-txt-tertiary mb-4">{error}</p>
|
||||||
|
<Button onClick={() => window.location.reload()}>
|
||||||
|
{__("Try Again")}
|
||||||
|
</Button>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!signingData || signingData.documents.length === 0) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<title>{__("No Documents to Sign")}</title>
|
||||||
|
<div className="flex justify-center items-center min-h-screen">
|
||||||
|
<Card padded className="w-full max-w-3xl">
|
||||||
|
<h1 className="text-xl font-semibold mb-2">{__("No Documents to Sign")}</h1>
|
||||||
|
<p className="text-txt-tertiary">
|
||||||
|
{__("There are no documents requiring your signature at this time.")}
|
||||||
|
</p>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentDoc = signingData.documents[currentDocIndex];
|
||||||
|
const isLastDocument = currentDocIndex === signingData.documents.length - 1;
|
||||||
|
const allSigned = getSignedCount() === signingData.documents.length;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<title>{__("Document Signing")}</title>
|
||||||
|
<div className="container mx-auto py-10 space-y-6">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h1 className="text-3xl font-bold">{__("Document Signing Request")}</h1>
|
||||||
|
<p className="text-txt-tertiary">
|
||||||
|
{__("From")} {signingData.requesterName} {__("at")} {signingData.requesterOrganization}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm text-txt-tertiary">
|
||||||
|
{getSignedCount()} {__("of")} {signingData.documents.length} {__("documents signed")}
|
||||||
|
</span>
|
||||||
|
<span className="text-sm font-medium">
|
||||||
|
{Math.round(getProgressPercentage())}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<ProgressBar value={getProgressPercentage()} className="h-2" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Card padded>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-xl font-semibold">{currentDoc.title}</h2>
|
||||||
|
<p className="text-txt-tertiary">
|
||||||
|
{__("Document")} {currentDocIndex + 1} {__("of")} {signingData.documents.length}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="border rounded-md p-4 min-h-[400px] bg-bg-tertiary">
|
||||||
|
<Markdown content={currentDoc.content} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
{currentDoc.signed ? (
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="flex items-center gap-2 text-green-600 font-medium">
|
||||||
|
<IconCheckmark1 size={16} />
|
||||||
|
{__("Signed")}
|
||||||
|
</div>
|
||||||
|
{!isLastDocument && (
|
||||||
|
<Button onClick={handleNextDocument}>
|
||||||
|
{__("Next Document")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Button onClick={handleSignDocument}>
|
||||||
|
{__("Sign Document")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{allSigned && (
|
||||||
|
<div className="text-green-600 font-medium">
|
||||||
|
{__("All documents have been signed")}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
useConfirm,
|
useConfirm,
|
||||||
ActionDropdown,
|
ActionDropdown,
|
||||||
DropdownItem,
|
DropdownItem,
|
||||||
|
IconBell2,
|
||||||
} from "@probo/ui";
|
} from "@probo/ui";
|
||||||
import {
|
import {
|
||||||
useFragment,
|
useFragment,
|
||||||
@@ -26,6 +27,7 @@ import type { DocumentGraphListQuery } from "/hooks/graph/__generated__/Document
|
|||||||
import {
|
import {
|
||||||
documentsQuery,
|
documentsQuery,
|
||||||
useDeleteDocumentMutation,
|
useDeleteDocumentMutation,
|
||||||
|
useSendSigningNotificationsMutation,
|
||||||
} from "/hooks/graph/DocumentGraph";
|
} from "/hooks/graph/DocumentGraph";
|
||||||
import type { DocumentsPageListFragment$key } from "./__generated__/DocumentsPageListFragment.graphql";
|
import type { DocumentsPageListFragment$key } from "./__generated__/DocumentsPageListFragment.graphql";
|
||||||
import { usePageTitle } from "@probo/hooks";
|
import { usePageTitle } from "@probo/hooks";
|
||||||
@@ -65,18 +67,37 @@ export default function DocumentsPage(props: Props) {
|
|||||||
|
|
||||||
const documents = data.documents.edges.map((edge) => edge.node);
|
const documents = data.documents.edges.map((edge) => edge.node);
|
||||||
const connectionId = data.documents.__id;
|
const connectionId = data.documents.__id;
|
||||||
|
const [sendSigningNotifications] = useSendSigningNotificationsMutation();
|
||||||
|
|
||||||
usePageTitle(__("Documents"));
|
usePageTitle(__("Documents"));
|
||||||
|
|
||||||
|
const handleSendSigningNotifications = () => {
|
||||||
|
sendSigningNotifications({
|
||||||
|
variables: {
|
||||||
|
input: { organizationId: organization.id },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title={__("Documents")}
|
title={__("Documents")}
|
||||||
description={__("Manage your organization's documents")}
|
description={__("Manage your organization's documents")}
|
||||||
>
|
>
|
||||||
<CreateDocumentDialog
|
<div className="flex gap-2">
|
||||||
connection={connectionId}
|
<Button
|
||||||
trigger={<Button icon={IconPlusLarge}>{__("New document")}</Button>}
|
icon={IconBell2}
|
||||||
/>
|
variant="secondary"
|
||||||
|
onClick={handleSendSigningNotifications}
|
||||||
|
>
|
||||||
|
{__("Send signing notifications")}
|
||||||
|
</Button>
|
||||||
|
<CreateDocumentDialog
|
||||||
|
connection={connectionId}
|
||||||
|
trigger={<Button icon={IconPlusLarge}>{__("New document")}</Button>}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<Table>
|
<Table>
|
||||||
<Thead>
|
<Thead>
|
||||||
|
|||||||
@@ -72,6 +72,10 @@ const routes = [
|
|||||||
() => import("./pages/organizations/NewOrganizationPage")
|
() => import("./pages/organizations/NewOrganizationPage")
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "documents/signing-requests",
|
||||||
|
Component: lazy(() => import("./pages/DocumentSigningRequestsPage.tsx")),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user