Add CurrentUser context and use it in documents

Signed-off-by: Émile Ré <nemile.re@gmail.com>
This commit is contained in:
Émile Ré
2026-01-02 16:22:11 +01:00
committed by Bryan Frimin
parent 26371857e9
commit ecf27751ac
13 changed files with 139 additions and 194 deletions

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<30bea11238a554ed69a88c9f07d33d9e>> * @generated SignedSource<<d0098f8499fc70b98d7264cb756b2c6b>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -22,7 +22,6 @@ export type MemberListFragment$data = {
}>; }>;
readonly totalCount: number | null | undefined; readonly totalCount: number | null | undefined;
}; };
readonly " $fragmentSpreads": FragmentRefs<"MemberListItem_currentRoleFragment">;
readonly " $fragmentType": "MemberListFragment"; readonly " $fragmentType": "MemberListFragment";
}; };
export type MemberListFragment$key = { export type MemberListFragment$key = {
@@ -108,11 +107,6 @@ return {
}, },
"name": "MemberListFragment", "name": "MemberListFragment",
"selections": [ "selections": [
{
"args": null,
"kind": "FragmentSpread",
"name": "MemberListItem_currentRoleFragment"
},
{ {
"kind": "RequiredField", "kind": "RequiredField",
"field": { "field": {
@@ -245,6 +239,6 @@ return {
}; };
})(); })();
(node as any).hash = "909f6c9ed0fcaf7b0d170bded8c97562"; (node as any).hash = "763331f25e735e2645b0c6949ecad362";
export default node; export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<6c195e6a85a33dea6ae254caa60b79df>> * @generated SignedSource<<1a9ee1bf35493db9d5edbbd07f917ff5>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -109,14 +109,7 @@ v12 = {
"name": "id", "name": "id",
"storageKey": null "storageKey": null
}, },
v13 = { v13 = [
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "role",
"storageKey": null
},
v14 = [
(v7/*: any*/), (v7/*: any*/),
(v8/*: any*/), (v8/*: any*/),
(v9/*: any*/), (v9/*: any*/),
@@ -199,20 +192,7 @@ return {
"selections": [ "selections": [
{ {
"alias": null, "alias": null,
"args": null, "args": (v13/*: any*/),
"concreteType": "Membership",
"kind": "LinkedField",
"name": "viewerMembership",
"plural": false,
"selections": [
(v13/*: any*/),
(v12/*: any*/)
],
"storageKey": null
},
{
"alias": null,
"args": (v14/*: any*/),
"concreteType": "MembershipConnection", "concreteType": "MembershipConnection",
"kind": "LinkedField", "kind": "LinkedField",
"name": "members", "name": "members",
@@ -242,7 +222,13 @@ return {
"plural": false, "plural": false,
"selections": [ "selections": [
(v12/*: any*/), (v12/*: any*/),
(v13/*: any*/), {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "role",
"storageKey": null
},
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -384,7 +370,7 @@ return {
}, },
{ {
"alias": null, "alias": null,
"args": (v14/*: any*/), "args": (v13/*: any*/),
"filters": [ "filters": [
"orderBy" "orderBy"
], ],
@@ -403,16 +389,16 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "a879076e1e75bea4d524852533db17b6", "cacheID": "7d90790bce5773e286196cec19c376c9",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "MemberListFragment_RefetchQuery", "name": "MemberListFragment_RefetchQuery",
"operationKind": "query", "operationKind": "query",
"text": "query MemberListFragment_RefetchQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 20\n $last: Int = null\n $order: MembershipOrder = {direction: ASC, field: CREATED_AT}\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...MemberListFragment_16fISc\n id\n }\n}\n\nfragment MemberListFragment_16fISc on Organization {\n ...MemberListItem_currentRoleFragment\n members(first: $first, after: $after, last: $last, before: $before, orderBy: $order) {\n totalCount\n edges {\n node {\n id\n ...MemberListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment MemberListItemFragment on Membership {\n id\n role\n profile {\n fullName\n id\n }\n identity {\n email\n id\n }\n createdAt\n canUpdate: permission(action: \"iam:membership:update\")\n canDelete: permission(action: \"iam:membership:delete\")\n}\n\nfragment MemberListItem_currentRoleFragment on Organization {\n viewerMembership {\n role\n id\n }\n}\n" "text": "query MemberListFragment_RefetchQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 20\n $last: Int = null\n $order: MembershipOrder = {direction: ASC, field: CREATED_AT}\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...MemberListFragment_16fISc\n id\n }\n}\n\nfragment MemberListFragment_16fISc on Organization {\n members(first: $first, after: $after, last: $last, before: $before, orderBy: $order) {\n totalCount\n edges {\n node {\n id\n ...MemberListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment MemberListItemFragment on Membership {\n id\n role\n profile {\n fullName\n id\n }\n identity {\n email\n id\n }\n createdAt\n canUpdate: permission(action: \"iam:membership:update\")\n canDelete: permission(action: \"iam:membership:delete\")\n}\n"
} }
}; };
})(); })();
(node as any).hash = "909f6c9ed0fcaf7b0d170bded8c97562"; (node as any).hash = "763331f25e735e2645b0c6949ecad362";
export default node; export default node;

View File

@@ -1,60 +0,0 @@
/**
* @generated SignedSource<<89f39cf7aa4cd7702b62f549ed8defdd>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ReaderFragment } from 'relay-runtime';
export type MembershipRole = "ADMIN" | "AUDITOR" | "EMPLOYEE" | "OWNER" | "VIEWER";
import { FragmentRefs } from "relay-runtime";
export type MemberListItem_currentRoleFragment$data = {
readonly viewerMembership: {
readonly role: MembershipRole;
};
readonly " $fragmentType": "MemberListItem_currentRoleFragment";
};
export type MemberListItem_currentRoleFragment$key = {
readonly " $data"?: MemberListItem_currentRoleFragment$data;
readonly " $fragmentSpreads": FragmentRefs<"MemberListItem_currentRoleFragment">;
};
const node: ReaderFragment = {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "MemberListItem_currentRoleFragment",
"selections": [
{
"kind": "RequiredField",
"field": {
"alias": null,
"args": null,
"concreteType": "Membership",
"kind": "LinkedField",
"name": "viewerMembership",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "role",
"storageKey": null
}
],
"storageKey": null
},
"action": "THROW"
}
],
"type": "Organization",
"abstractKey": null
};
(node as any).hash = "9e7e3eefcb6f2f4398b0703a9342548b";
export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<70828342a675d5b198c87a4a00455155>> * @generated SignedSource<<3ba2ace33d45535d6c6981f283be9643>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -509,12 +509,12 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "acfe76e30a3a7db24fec4acf2e3fd987", "cacheID": "a5ff95870e5a269d1b03a123a459678e",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "MembersPageQuery", "name": "MembersPageQuery",
"operationKind": "query", "operationKind": "query",
"text": "query MembersPageQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n ...InviteUserDialog_currentRoleFragment\n ...MemberListFragment_1jRT0c\n members(first: 20, orderBy: {direction: ASC, field: CREATED_AT}) {\n totalCount\n }\n ...InvitationListFragment_1jRT0c\n invitations(first: 20, orderBy: {direction: ASC, field: CREATED_AT}) {\n totalCount\n }\n canInviteUser: permission(action: \"iam:invitation:create\")\n }\n id\n }\n}\n\nfragment InvitationListFragment_1jRT0c on Organization {\n invitations(first: 20, orderBy: {direction: ASC, field: CREATED_AT}) {\n totalCount\n edges {\n node {\n id\n ...InvitationListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment InvitationListItemFragment on Invitation {\n id\n fullName\n email\n role\n status\n createdAt\n expiresAt\n acceptedAt\n canDelete: permission(action: \"iam:invitation:delete\")\n}\n\nfragment InviteUserDialog_currentRoleFragment on Organization {\n viewerMembership {\n role\n id\n }\n}\n\nfragment MemberListFragment_1jRT0c on Organization {\n ...MemberListItem_currentRoleFragment\n members(first: 20, orderBy: {direction: ASC, field: CREATED_AT}) {\n totalCount\n edges {\n node {\n id\n ...MemberListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment MemberListItemFragment on Membership {\n id\n role\n profile {\n fullName\n id\n }\n identity {\n email\n id\n }\n createdAt\n canUpdate: permission(action: \"iam:membership:update\")\n canDelete: permission(action: \"iam:membership:delete\")\n}\n\nfragment MemberListItem_currentRoleFragment on Organization {\n viewerMembership {\n role\n id\n }\n}\n" "text": "query MembersPageQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n ...InviteUserDialog_currentRoleFragment\n ...MemberListFragment_1jRT0c\n members(first: 20, orderBy: {direction: ASC, field: CREATED_AT}) {\n totalCount\n }\n ...InvitationListFragment_1jRT0c\n invitations(first: 20, orderBy: {direction: ASC, field: CREATED_AT}) {\n totalCount\n }\n canInviteUser: permission(action: \"iam:invitation:create\")\n }\n id\n }\n}\n\nfragment InvitationListFragment_1jRT0c on Organization {\n invitations(first: 20, orderBy: {direction: ASC, field: CREATED_AT}) {\n totalCount\n edges {\n node {\n id\n ...InvitationListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment InvitationListItemFragment on Invitation {\n id\n fullName\n email\n role\n status\n createdAt\n expiresAt\n acceptedAt\n canDelete: permission(action: \"iam:invitation:delete\")\n}\n\nfragment InviteUserDialog_currentRoleFragment on Organization {\n viewerMembership {\n role\n id\n }\n}\n\nfragment MemberListFragment_1jRT0c on Organization {\n members(first: 20, orderBy: {direction: ASC, field: CREATED_AT}) {\n totalCount\n edges {\n node {\n id\n ...MemberListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment MemberListItemFragment on Membership {\n id\n role\n profile {\n fullName\n id\n }\n identity {\n email\n id\n }\n createdAt\n canUpdate: permission(action: \"iam:membership:update\")\n canDelete: permission(action: \"iam:membership:delete\")\n}\n"
} }
}; };
})(); })();

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<5020a4587a487d695572eb77c5e9a959>> * @generated SignedSource<<4268d9e187cf29b47f9438cf48cc3e73>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -19,6 +19,9 @@ export type MembershipLayoutQuery$data = {
readonly organization: { readonly organization: {
readonly __typename: "Organization"; readonly __typename: "Organization";
readonly viewerMembership: { readonly viewerMembership: {
readonly profile: {
readonly fullName: string;
};
readonly role: MembershipRole; readonly role: MembershipRole;
}; };
readonly " $fragmentSpreads": FragmentRefs<"MembershipsDropdown_organizationFragment" | "SessionDropdownFragment" | "SidebarFragment">; readonly " $fragmentSpreads": FragmentRefs<"MembershipsDropdown_organizationFragment" | "SessionDropdownFragment" | "SidebarFragment">;
@@ -28,6 +31,7 @@ export type MembershipLayoutQuery$data = {
readonly __typename: "%other"; readonly __typename: "%other";
}; };
readonly viewer: { readonly viewer: {
readonly email: string;
readonly pendingInvitations: { readonly pendingInvitations: {
readonly totalCount: number; readonly totalCount: number;
}; };
@@ -75,10 +79,24 @@ v5 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "totalCount", "name": "fullName",
"storageKey": null "storageKey": null
}, },
v6 = { v6 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "email",
"storageKey": null
},
v7 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "totalCount",
"storageKey": null
},
v8 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
@@ -141,7 +159,23 @@ return {
"name": "viewerMembership", "name": "viewerMembership",
"plural": false, "plural": false,
"selections": [ "selections": [
(v4/*: any*/) (v4/*: any*/),
{
"kind": "RequiredField",
"field": {
"alias": null,
"args": null,
"concreteType": "MembershipProfile",
"kind": "LinkedField",
"name": "profile",
"plural": false,
"selections": [
(v5/*: any*/)
],
"storageKey": null
},
"action": "THROW"
}
], ],
"storageKey": null "storageKey": null
}, },
@@ -166,6 +200,7 @@ return {
"name": "viewer", "name": "viewer",
"plural": false, "plural": false,
"selections": [ "selections": [
(v6/*: any*/),
{ {
"args": null, "args": null,
"kind": "FragmentSpread", "kind": "FragmentSpread",
@@ -183,7 +218,7 @@ return {
"selections": [ "selections": [
{ {
"kind": "RequiredField", "kind": "RequiredField",
"field": (v5/*: any*/), "field": (v7/*: any*/),
"action": "THROW" "action": "THROW"
} }
], ],
@@ -257,14 +292,8 @@ return {
"name": "identity", "name": "identity",
"plural": false, "plural": false,
"selections": [ "selections": [
{ (v6/*: any*/),
"alias": null, (v8/*: any*/)
"args": null,
"kind": "ScalarField",
"name": "email",
"storageKey": null
},
(v6/*: any*/)
], ],
"storageKey": null "storageKey": null
}, },
@@ -276,18 +305,12 @@ return {
"name": "profile", "name": "profile",
"plural": false, "plural": false,
"selections": [ "selections": [
{ (v5/*: any*/),
"alias": null, (v8/*: any*/)
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
},
(v6/*: any*/)
], ],
"storageKey": null "storageKey": null
}, },
(v6/*: any*/), (v8/*: any*/),
(v4/*: any*/) (v4/*: any*/)
], ],
"storageKey": null "storageKey": null
@@ -563,7 +586,7 @@ return {
"type": "Organization", "type": "Organization",
"abstractKey": null "abstractKey": null
}, },
(v6/*: any*/) (v8/*: any*/)
], ],
"storageKey": null "storageKey": null
}, },
@@ -575,6 +598,7 @@ return {
"name": "viewer", "name": "viewer",
"plural": false, "plural": false,
"selections": [ "selections": [
(v6/*: any*/),
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -583,27 +607,27 @@ return {
"name": "pendingInvitations", "name": "pendingInvitations",
"plural": false, "plural": false,
"selections": [ "selections": [
(v5/*: any*/) (v7/*: any*/)
], ],
"storageKey": null "storageKey": null
}, },
(v6/*: any*/) (v8/*: any*/)
], ],
"storageKey": null "storageKey": null
} }
] ]
}, },
"params": { "params": {
"cacheID": "6b0b3a64c02e1c0ff744e57b395d14c8", "cacheID": "5f50873fa9c14115e307b43d7c1ae822",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "MembershipLayoutQuery", "name": "MembershipLayoutQuery",
"operationKind": "query", "operationKind": "query",
"text": "query MembershipLayoutQuery(\n $organizationId: ID!\n $hideSidebar: Boolean!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n ...MembershipsDropdown_organizationFragment\n ...SessionDropdownFragment\n ...SidebarFragment @skip(if: $hideSidebar)\n viewerMembership {\n role\n id\n }\n }\n id\n }\n viewer {\n ...MembershipsDropdown_viewerFragment\n pendingInvitations {\n totalCount\n }\n id\n }\n}\n\nfragment MembershipsDropdown_organizationFragment on Organization {\n name\n}\n\nfragment MembershipsDropdown_viewerFragment on Identity {\n pendingInvitations {\n totalCount\n }\n}\n\nfragment SessionDropdownFragment on Organization {\n canDelete: permission(action: \"iam:organization:delete\")\n viewerMembership {\n identity {\n email\n id\n }\n profile {\n fullName\n id\n }\n id\n }\n}\n\nfragment SidebarFragment on Organization {\n canListMeetings: permission(action: \"core:meeting:list\")\n canListTasks: permission(action: \"core:task:list\")\n canListMeasures: permission(action: \"core:measures:list\")\n canListRisks: permission(action: \"core:risk:list\")\n canListFrameworks: permission(action: \"core:frameworks:list\")\n canListPeople: permission(action: \"core:people:list\")\n canListVendors: permission(action: \"core:vendor:list\")\n canListDocuments: permission(action: \"core:document:list\")\n canListAssets: permission(action: \"core:asset:list\")\n canListData: permission(action: \"core:datum:list\")\n canListAudits: permission(action: \"core:audit:list\")\n canListNonconformities: permission(action: \"core:nonconformity:list\")\n canListObligations: permission(action: \"core:obligation:list\")\n canListContinualImprovements: permission(action: \"core:continual-improvement:list\")\n canListProcessingActivities: permission(action: \"core:processing-activity:list\")\n canListRightsRequests: permission(action: \"core:rights-request:list\")\n canListSnapshots: permission(action: \"core:snapshot:list\")\n canGetTrustCenter: permission(action: \"core:trust-center:get\")\n canUpdateOrganization: permission(action: \"iam:organization:update\")\n canListStatesOfApplicability: permission(action: \"core:state-of-applicability:list\")\n}\n" "text": "query MembershipLayoutQuery(\n $organizationId: ID!\n $hideSidebar: Boolean!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n ...MembershipsDropdown_organizationFragment\n ...SessionDropdownFragment\n ...SidebarFragment @skip(if: $hideSidebar)\n viewerMembership {\n role\n profile {\n fullName\n id\n }\n id\n }\n }\n id\n }\n viewer {\n email\n ...MembershipsDropdown_viewerFragment\n pendingInvitations {\n totalCount\n }\n id\n }\n}\n\nfragment MembershipsDropdown_organizationFragment on Organization {\n name\n}\n\nfragment MembershipsDropdown_viewerFragment on Identity {\n pendingInvitations {\n totalCount\n }\n}\n\nfragment SessionDropdownFragment on Organization {\n canDelete: permission(action: \"iam:organization:delete\")\n viewerMembership {\n identity {\n email\n id\n }\n profile {\n fullName\n id\n }\n id\n }\n}\n\nfragment SidebarFragment on Organization {\n canListMeetings: permission(action: \"core:meeting:list\")\n canListTasks: permission(action: \"core:task:list\")\n canListMeasures: permission(action: \"core:measures:list\")\n canListRisks: permission(action: \"core:risk:list\")\n canListFrameworks: permission(action: \"core:frameworks:list\")\n canListPeople: permission(action: \"core:people:list\")\n canListVendors: permission(action: \"core:vendor:list\")\n canListDocuments: permission(action: \"core:document:list\")\n canListAssets: permission(action: \"core:asset:list\")\n canListData: permission(action: \"core:datum:list\")\n canListAudits: permission(action: \"core:audit:list\")\n canListNonconformities: permission(action: \"core:nonconformity:list\")\n canListObligations: permission(action: \"core:obligation:list\")\n canListContinualImprovements: permission(action: \"core:continual-improvement:list\")\n canListProcessingActivities: permission(action: \"core:processing-activity:list\")\n canListRightsRequests: permission(action: \"core:rights-request:list\")\n canListSnapshots: permission(action: \"core:snapshot:list\")\n canGetTrustCenter: permission(action: \"core:trust-center:get\")\n canUpdateOrganization: permission(action: \"iam:organization:update\")\n canListStatesOfApplicability: permission(action: \"core:state-of-applicability:list\")\n}\n"
} }
}; };
})(); })();
(node as any).hash = "2d75823d6ae9eee901e54b71d4c397cf"; (node as any).hash = "e52fc7540b2908e5b4f2c4cef33b5b81";
export default node; export default node;

View File

@@ -15,7 +15,11 @@ import { useFormWithSchema } from "/hooks/useFormWithSchema";
const pdfDownloadSchema = z.object({ const pdfDownloadSchema = z.object({
withWatermark: z.boolean(), withWatermark: z.boolean(),
watermarkEmail: z.string().email("Please enter a valid email address").optional().or(z.literal("")), watermarkEmail: z
.string()
.email("Please enter a valid email address")
.optional()
.or(z.literal("")),
withSignatures: z.boolean(), withSignatures: z.boolean(),
}); });
@@ -25,7 +29,7 @@ type Props = {
children: ReactNode; children: ReactNode;
onDownload: (options: PdfDownloadFormData) => void; onDownload: (options: PdfDownloadFormData) => void;
isLoading?: boolean; isLoading?: boolean;
defaultEmail?: string; defaultEmail: string;
}; };
export type PdfDownloadDialogRef = { export type PdfDownloadDialogRef = {
@@ -34,20 +38,18 @@ export type PdfDownloadDialogRef = {
}; };
export const PdfDownloadDialog = forwardRef<PdfDownloadDialogRef, Props>( export const PdfDownloadDialog = forwardRef<PdfDownloadDialogRef, Props>(
({ children, onDownload, isLoading = false, defaultEmail = "" }, ref) => { ({ children, onDownload, isLoading = false, defaultEmail }, ref) => {
const { __ } = useTranslate(); const { __ } = useTranslate();
const dialogRef = useDialogRef(); const dialogRef = useDialogRef();
const { register, handleSubmit, formState, watch, setValue } = useFormWithSchema( const { register, handleSubmit, formState, watch, setValue } =
pdfDownloadSchema, useFormWithSchema(pdfDownloadSchema, {
{
defaultValues: { defaultValues: {
withWatermark: false, withWatermark: false,
watermarkEmail: defaultEmail, watermarkEmail: defaultEmail,
withSignatures: true, withSignatures: true,
}, },
} });
);
const watchWatermark = watch("withWatermark"); const watchWatermark = watch("withWatermark");
const watchSignatures = watch("withSignatures"); const watchSignatures = watch("withSignatures");
@@ -69,7 +71,11 @@ export const PdfDownloadDialog = forwardRef<PdfDownloadDialogRef, Props>(
return ( return (
<> <>
<div onClick={() => dialogRef.current?.open()}>{children}</div> <div onClick={() => dialogRef.current?.open()}>{children}</div>
<Dialog className="max-w-md" ref={dialogRef} title={__("Download PDF Options")}> <Dialog
className="max-w-md"
ref={dialogRef}
title={__("Download PDF Options")}
>
<form onSubmit={onSubmit}> <form onSubmit={onSubmit}>
<DialogContent className="space-y-4" padded> <DialogContent className="space-y-4" padded>
<div className="space-y-4"> <div className="space-y-4">
@@ -83,7 +89,9 @@ export const PdfDownloadDialog = forwardRef<PdfDownloadDialogRef, Props>(
{__("Include signatures")} {__("Include signatures")}
</label> </label>
<p className="text-xs text-txt-secondary mt-1"> <p className="text-xs text-txt-secondary mt-1">
{__("Show signature information and approval details in the PDF")} {__(
"Show signature information and approval details in the PDF",
)}
</p> </p>
</div> </div>
</div> </div>
@@ -98,7 +106,9 @@ export const PdfDownloadDialog = forwardRef<PdfDownloadDialogRef, Props>(
{__("Add watermark")} {__("Add watermark")}
</label> </label>
<p className="text-xs text-txt-secondary mt-1"> <p className="text-xs text-txt-secondary mt-1">
{__("Add confidential watermark with email and timestamp")} {__(
"Add confidential watermark with email and timestamp",
)}
</p> </p>
</div> </div>
</div> </div>
@@ -119,10 +129,7 @@ export const PdfDownloadDialog = forwardRef<PdfDownloadDialogRef, Props>(
</div> </div>
</DialogContent> </DialogContent>
<DialogFooter> <DialogFooter>
<Button <Button type="submit" disabled={isLoading}>
type="submit"
disabled={isLoading}
>
{isLoading ? ( {isLoading ? (
<> <>
<Spinner size={16} /> <Spinner size={16} />
@@ -137,5 +144,5 @@ export const PdfDownloadDialog = forwardRef<PdfDownloadDialogRef, Props>(
</Dialog> </Dialog>
</> </>
); );
} },
); );

View File

@@ -8,6 +8,7 @@ import { SessionDropdown } from "./_components/SessionDropdown";
import { Suspense } from "react"; import { Suspense } from "react";
import { useTranslate } from "@probo/i18n"; import { useTranslate } from "@probo/i18n";
import { CoreRelayProvider } from "/providers/CoreRelayProvider"; import { CoreRelayProvider } from "/providers/CoreRelayProvider";
import { CurrentUser } from "/providers/CurrentUser";
export const membershipLayoutQuery = graphql` export const membershipLayoutQuery = graphql`
query MembershipLayoutQuery($organizationId: ID!, $hideSidebar: Boolean!) { query MembershipLayoutQuery($organizationId: ID!, $hideSidebar: Boolean!) {
@@ -19,10 +20,14 @@ export const membershipLayoutQuery = graphql`
...SidebarFragment @skip(if: $hideSidebar) ...SidebarFragment @skip(if: $hideSidebar)
viewerMembership @required(action: THROW) { viewerMembership @required(action: THROW) {
role role
profile @required(action: THROW) {
fullName
}
} }
} }
} }
viewer @required(action: THROW) { viewer @required(action: THROW) {
email
...MembershipsDropdown_viewerFragment ...MembershipsDropdown_viewerFragment
pendingInvitations @required(action: THROW) { pendingInvitations @required(action: THROW) {
totalCount @required(action: THROW) totalCount @required(action: THROW)
@@ -77,7 +82,15 @@ export function MembershipLayout(props: {
sidebar={!hideSidebar && <Sidebar fKey={organization} />} sidebar={!hideSidebar && <Sidebar fKey={organization} />}
> >
<CoreRelayProvider> <CoreRelayProvider>
<CurrentUser
value={{
email: viewer.email,
fullName: organization.viewerMembership.profile.fullName,
role: organization.viewerMembership.role,
}}
>
<Outlet context={organization.viewerMembership.role} /> <Outlet context={organization.viewerMembership.role} />
</CurrentUser>
</CoreRelayProvider> </CoreRelayProvider>
</Layout> </Layout>
); );

View File

@@ -1,4 +1,4 @@
import { getAssignableRoles, Role, sprintf } from "@probo/helpers"; import { getAssignableRoles, sprintf } from "@probo/helpers";
import { useTranslate } from "@probo/i18n"; import { useTranslate } from "@probo/i18n";
import { import {
DialogContent, DialogContent,
@@ -10,11 +10,12 @@ import {
Spinner, Spinner,
Dialog, Dialog,
} from "@probo/ui"; } from "@probo/ui";
import { useState } from "react"; import { use, useState } from "react";
import { graphql } from "relay-runtime"; import { graphql } from "relay-runtime";
import { useMutationWithToasts } from "/hooks/useMutationWithToasts"; import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
import { useOrganizationId } from "/hooks/useOrganizationId"; import { useOrganizationId } from "/hooks/useOrganizationId";
import type { MemberListItemFragment$data } from "/__generated__/iam/MemberListItemFragment.graphql"; import type { MemberListItemFragment$data } from "/__generated__/iam/MemberListItemFragment.graphql";
import { CurrentUser } from "/providers/CurrentUser";
const updateMembershipMutation = graphql` const updateMembershipMutation = graphql`
mutation EditMemberDialog_updateMutation($input: UpdateMembershipInput!) { mutation EditMemberDialog_updateMutation($input: UpdateMembershipInput!) {
@@ -28,16 +29,16 @@ const updateMembershipMutation = graphql`
`; `;
export function EditMemberDialog(props: { export function EditMemberDialog(props: {
currentRole: Role;
membership: MemberListItemFragment$data; membership: MemberListItemFragment$data;
onClose: () => void; onClose: () => void;
}) { }) {
const { currentRole, membership, onClose } = props; const { membership, onClose } = props;
const organizationId = useOrganizationId(); const organizationId = useOrganizationId();
const { __ } = useTranslate(); const { __ } = useTranslate();
const availableRoles = getAssignableRoles(currentRole); const { role } = use(CurrentUser);
const availableRoles = getAssignableRoles(role);
const [selectedRole, setSelectedRole] = useState<string>(membership.role); const [selectedRole, setSelectedRole] = useState<string>(membership.role);

View File

@@ -19,7 +19,6 @@ const fragment = graphql`
before: { type: "CursorKey", defaultValue: null } before: { type: "CursorKey", defaultValue: null }
last: { type: "Int", defaultValue: null } last: { type: "Int", defaultValue: null }
) { ) {
...MemberListItem_currentRoleFragment
members( members(
first: $first first: $first
after: $after after: $after
@@ -94,7 +93,6 @@ export function MemberList(props: { fKey: MemberListFragment$key }) {
key={membership.id} key={membership.id}
fKey={membership} fKey={membership}
onRefetch={refetchMemberships} onRefetch={refetchMemberships}
viewerFKey={membersPagination.data}
/> />
)) ))
)} )}

View File

@@ -10,15 +10,15 @@ import {
useConfirm, useConfirm,
} from "@probo/ui"; } from "@probo/ui";
import clsx from "clsx"; import clsx from "clsx";
import { useState } from "react"; import { use, useState } from "react";
import { useFragment } from "react-relay"; import { useFragment } from "react-relay";
import { graphql } from "relay-runtime"; import { graphql } from "relay-runtime";
import type { MemberListItemFragment$key } from "/__generated__/iam/MemberListItemFragment.graphql"; import type { MemberListItemFragment$key } from "/__generated__/iam/MemberListItemFragment.graphql";
import type { MemberListItem_currentRoleFragment$key } from "/__generated__/iam/MemberListItem_currentRoleFragment.graphql";
import { useMutationWithToasts } from "/hooks/useMutationWithToasts"; import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
import { useOrganizationId } from "/hooks/useOrganizationId"; import { useOrganizationId } from "/hooks/useOrganizationId";
import { sprintf } from "@probo/helpers"; import { sprintf } from "@probo/helpers";
import { EditMemberDialog } from "./EditMemberDialog"; import { EditMemberDialog } from "./EditMemberDialog";
import { CurrentUser } from "/providers/CurrentUser";
const fragment = graphql` const fragment = graphql`
fragment MemberListItemFragment on Membership { fragment MemberListItemFragment on Membership {
@@ -36,14 +36,6 @@ const fragment = graphql`
} }
`; `;
const currentRoleFragment = graphql`
fragment MemberListItem_currentRoleFragment on Organization {
viewerMembership @required(action: THROW) {
role
}
}
`;
const removeMemberMutation = graphql` const removeMemberMutation = graphql`
mutation MemberListItem_removeMutation( mutation MemberListItem_removeMutation(
$input: RemoveMemberInput! $input: RemoveMemberInput!
@@ -58,10 +50,9 @@ const removeMemberMutation = graphql`
export function MemberListItem(props: { export function MemberListItem(props: {
connectionId: string; connectionId: string;
fKey: MemberListItemFragment$key; fKey: MemberListItemFragment$key;
viewerFKey: MemberListItem_currentRoleFragment$key;
onRefetch: () => void; onRefetch: () => void;
}) { }) {
const { fKey, connectionId, viewerFKey } = props; const { fKey, connectionId } = props;
const organizationId = useOrganizationId(); const organizationId = useOrganizationId();
const { __ } = useTranslate(); const { __ } = useTranslate();
@@ -69,15 +60,10 @@ export function MemberListItem(props: {
const [dialogOpen, setDialogOpen] = useState(false); const [dialogOpen, setDialogOpen] = useState(false);
const membership = useFragment<MemberListItemFragment$key>(fragment, fKey); const membership = useFragment<MemberListItemFragment$key>(fragment, fKey);
const { viewerMembership } = const { role } = use(CurrentUser);
useFragment<MemberListItem_currentRoleFragment$key>(
currentRoleFragment,
viewerFKey,
);
// Only OWNER can edit OWNER members // Only OWNER can edit OWNER members
const canEditThisRole = const canEditThisRole = membership.role === "OWNER" ? role === "OWNER" : true;
membership.role === "OWNER" ? viewerMembership.role === "OWNER" : true;
const [removeMembership, isRemoving] = useMutationWithToasts( const [removeMembership, isRemoving] = useMutationWithToasts(
removeMemberMutation, removeMemberMutation,
@@ -162,7 +148,6 @@ export function MemberListItem(props: {
{dialogOpen && ( {dialogOpen && (
<EditMemberDialog <EditMemberDialog
currentRole={viewerMembership.role}
onClose={() => setDialogOpen(false)} onClose={() => setDialogOpen(false)}
membership={membership} membership={membership}
/> />

View File

@@ -65,7 +65,7 @@ import {
PdfDownloadDialog, PdfDownloadDialog,
type PdfDownloadDialogRef, type PdfDownloadDialogRef,
} from "/components/documents/PdfDownloadDialog"; } from "/components/documents/PdfDownloadDialog";
import { useRef, useState } from "react"; import { use, useRef, useState } from "react";
import type { NodeOf } from "/types.ts"; import type { NodeOf } from "/types.ts";
import clsx from "clsx"; import clsx from "clsx";
import { PeopleSelectField } from "/components/form/PeopleSelectField"; import { PeopleSelectField } from "/components/form/PeopleSelectField";
@@ -74,6 +74,7 @@ import { DocumentTypeOptions } from "/components/form/DocumentTypeOptions";
import { DocumentClassificationOptions } from "/components/form/DocumentClassificationOptions"; import { DocumentClassificationOptions } from "/components/form/DocumentClassificationOptions";
import { z } from "zod"; import { z } from "zod";
import { useFormWithSchema } from "/hooks/useFormWithSchema"; import { useFormWithSchema } from "/hooks/useFormWithSchema";
import { CurrentUser } from "/providers/CurrentUser";
type Props = { type Props = {
queryRef: PreloadedQuery<DocumentGraphNodeQuery>; queryRef: PreloadedQuery<DocumentGraphNodeQuery>;
@@ -92,12 +93,6 @@ const documentFragment = graphql`
canUpdate: permission(action: "core:document:update") canUpdate: permission(action: "core:document:update")
canDelete: permission(action: "core:document:delete") canDelete: permission(action: "core:document:delete")
canPublish: permission(action: "core:document-version:publish") canPublish: permission(action: "core:document-version:publish")
# canSendSigningNotifications: permission(
# action: "core:document:send-signing-notifications"
# )
# canRequestSignatures: permission(
# action: "core:document-version:request-signature"
# )
...DocumentControlsTabFragment ...DocumentControlsTabFragment
controlsInfo: controls(first: 0) { controlsInfo: controls(first: 0) {
totalCount totalCount
@@ -187,15 +182,6 @@ const documentUpdateSchema = z.object({
classification: z.enum(documentClassifications), classification: z.enum(documentClassifications),
}); });
// FIXME
// const UserEmailQuery = graphql`
// query DocumentDetailPageUserEmailQuery {
// viewer {
// email
// }
// }
// `;
export default function DocumentDetailPage(props: Props) { export default function DocumentDetailPage(props: Props) {
const { versionId } = useParams<{ versionId?: string }>(); const { versionId } = useParams<{ versionId?: string }>();
const node = usePreloadedQuery(documentNodeQuery, props.queryRef).node; const node = usePreloadedQuery(documentNodeQuery, props.queryRef).node;
@@ -237,11 +223,7 @@ export default function DocumentDetailPage(props: Props) {
}, },
); );
// const userEmailData = useLazyLoadQuery<DocumentDetailPageUserEmailQuery>( const { email: defaultEmail } = use(CurrentUser);
// UserEmailQuery,
// {}
// );
// const defaultEmail = userEmailData.viewer.user.email;
const [updateDocument, isUpdatingDocument] = const [updateDocument, isUpdatingDocument] =
useMutationWithToasts<DocumentDetailPageUpdateMutation>( useMutationWithToasts<DocumentDetailPageUpdateMutation>(
updateDocumentMutation, updateDocumentMutation,
@@ -454,7 +436,7 @@ export default function DocumentDetailPage(props: Props) {
ref={pdfDownloadDialogRef} ref={pdfDownloadDialogRef}
onDownload={handleDownloadPdf} onDownload={handleDownloadPdf}
isLoading={isExporting} isLoading={isExporting}
// defaultEmail={defaultEmail} defaultEmail={defaultEmail}
> >
{null} {null}
</PdfDownloadDialog> </PdfDownloadDialog>

View File

@@ -0,0 +1,14 @@
import { Role } from "@probo/helpers";
import { createContext } from "react";
type CurrentUserContextValue = {
email: string;
fullName: string;
role: Role;
};
export const CurrentUser = createContext<CurrentUserContextValue>({
email: "",
fullName: "",
role: Role.VIEWER,
});

View File

@@ -2,7 +2,6 @@ import {
createBrowserRouter, createBrowserRouter,
Navigate, Navigate,
redirect, redirect,
useOutletContext,
useRouteError, useRouteError,
} from "react-router"; } from "react-router";
import { CenteredLayout, CenteredLayoutSkeleton } from "@probo/ui"; import { CenteredLayout, CenteredLayoutSkeleton } from "@probo/ui";
@@ -35,6 +34,8 @@ import {
UnAuthenticatedError, UnAuthenticatedError,
UnauthorizedError, UnauthorizedError,
} from "@probo/relay"; } from "@probo/relay";
import { CurrentUser } from "./providers/CurrentUser.tsx";
import { use } from "react";
/** /**
* Top level error boundary * Top level error boundary
@@ -152,7 +153,7 @@ const routes = [
{ {
path: "", path: "",
Component: () => { Component: () => {
const role = useOutletContext<Role>(); const { role } = use(CurrentUser);
switch (role) { switch (role) {
case Role.EMPLOYEE: case Role.EMPLOYEE:
return <Navigate to="employee" />; return <Navigate to="employee" />;