Add policies listing page

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Jonathan
2025-05-26 13:29:48 +02:00
committed by Sacha Al Himdani
parent 7903bd3a77
commit abe0ce1dde
36 changed files with 3343 additions and 138 deletions

View File

@@ -20,11 +20,12 @@ export function ControlledField({
name={name}
render={({ field }) => (
<>
{/* @ts-expect-error field is too dynamic */}
<Field
{...props}
{...field}
// TODO : Find a better way to handle this case (comparing number and string for select create issues)
value={field.value ? field.value.toString() : null}
value={field.value ? field.value.toString() : ""}
onValueChange={field.onChange}
/>
</>
@@ -48,6 +49,7 @@ export function ControlledSelect({
{...props}
{...field}
onValueChange={field.onChange}
value={field.value ?? ""}
/>
)}
/>

View File

@@ -72,8 +72,9 @@ function PeopleSelectWithQuery({
variant="editor"
placeholder={__("Select an owner")}
onValueChange={field.onChange}
key={people?.length ?? 0}
key={people?.length.toString() ?? "0"}
{...field}
value={field.value ?? ""}
>
{people?.map((p) => (
<Option key={p.id} value={p.id} className="flex gap-2">

View File

@@ -0,0 +1,76 @@
/**
* @generated SignedSource<<3ffcc7618d81b47e39ca5731fe75e2ed>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ReaderFragment } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type usePolicyFormFragment$data = {
readonly description: string;
readonly id: string;
readonly owner: {
readonly id: string;
};
readonly title: string;
readonly " $fragmentType": "usePolicyFormFragment";
};
export type usePolicyFormFragment$key = {
readonly " $data"?: usePolicyFormFragment$data;
readonly " $fragmentSpreads": FragmentRefs<"usePolicyFormFragment">;
};
const node: ReaderFragment = (function(){
var v0 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
};
return {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "usePolicyFormFragment",
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "title",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "description",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "owner",
"plural": false,
"selections": [
(v0/*: any*/)
],
"storageKey": null
}
],
"type": "Policy",
"abstractKey": null
};
})();
(node as any).hash = "619d6845e45fb8c052a8989ea546414d";
export default node;

View File

@@ -0,0 +1,12 @@
import { z } from "zod";
import { useFormWithSchema } from "../useFormWithSchema";
export const policySchema = z.object({
title: z.string(),
content: z.string(),
ownerId: z.string(),
});
export const usePolicyForm = () => {
return useFormWithSchema(policySchema, {});
};

View File

@@ -0,0 +1,46 @@
import { useTranslate } from "@probo/i18n";
import { graphql } from "relay-runtime";
import { useMutationWithToasts } from "../useMutationWithToasts";
import type { PolicyGraphDeleteMutation } from "./__generated__/PolicyGraphDeleteMutation.graphql";
export const policiesQuery = graphql`
query PolicyGraphListQuery($organizationId: ID!) {
organization: node(id: $organizationId) {
id
...PoliciesPageListFragment
}
}
`;
const deletePolicyMutation = graphql`
mutation PolicyGraphDeleteMutation(
$input: DeletePolicyInput!
$connections: [ID!]!
) {
deletePolicy(input: $input) {
deletedPolicyId @deleteEdge(connections: $connections)
}
}
`;
export function useDeletePolicyMutation() {
const { __ } = useTranslate();
return useMutationWithToasts<PolicyGraphDeleteMutation>(
deletePolicyMutation,
{
successMessage: __("Policy deleted successfully."),
errorMessage: __("Failed to delete policy. Please try again."),
}
);
}
export const policyNodeQuery = graphql`
query PolicyGraphNodeQuery($policyId: ID!) {
node(id: $policyId) {
... on Policy {
...PolicyPagePolicyFragment
}
}
}
`;

View File

@@ -0,0 +1,132 @@
/**
* @generated SignedSource<<baab5b247999631abac20d0f5ab13925>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type DeletePolicyInput = {
policyId: string;
};
export type PolicyGraphDeleteMutation$variables = {
connections: ReadonlyArray<string>;
input: DeletePolicyInput;
};
export type PolicyGraphDeleteMutation$data = {
readonly deletePolicy: {
readonly deletedPolicyId: string;
};
};
export type PolicyGraphDeleteMutation = {
response: PolicyGraphDeleteMutation$data;
variables: PolicyGraphDeleteMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "connections"
},
v1 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
},
v2 = [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "deletedPolicyId",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": [
(v0/*: any*/),
(v1/*: any*/)
],
"kind": "Fragment",
"metadata": null,
"name": "PolicyGraphDeleteMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "DeletePolicyPayload",
"kind": "LinkedField",
"name": "deletePolicy",
"plural": false,
"selections": [
(v3/*: any*/)
],
"storageKey": null
}
],
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [
(v1/*: any*/),
(v0/*: any*/)
],
"kind": "Operation",
"name": "PolicyGraphDeleteMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "DeletePolicyPayload",
"kind": "LinkedField",
"name": "deletePolicy",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"filters": null,
"handle": "deleteEdge",
"key": "",
"kind": "ScalarHandle",
"name": "deletedPolicyId",
"handleArgs": [
{
"kind": "Variable",
"name": "connections",
"variableName": "connections"
}
]
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "d635ed7113bd8ad32056f5b4fbb95b22",
"id": null,
"metadata": {},
"name": "PolicyGraphDeleteMutation",
"operationKind": "mutation",
"text": "mutation PolicyGraphDeleteMutation(\n $input: DeletePolicyInput!\n) {\n deletePolicy(input: $input) {\n deletedPolicyId\n }\n}\n"
}
};
})();
(node as any).hash = "f5178185f41864d20506abc44b37fa67";
export default node;

View File

@@ -0,0 +1,375 @@
/**
* @generated SignedSource<<26cd4feab2589e8b641b339c977905a3>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type PolicyGraphListQuery$variables = {
organizationId: string;
};
export type PolicyGraphListQuery$data = {
readonly organization: {
readonly id: string;
readonly " $fragmentSpreads": FragmentRefs<"PoliciesPageListFragment">;
};
};
export type PolicyGraphListQuery = {
response: PolicyGraphListQuery$data;
variables: PolicyGraphListQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "organizationId"
}
],
v1 = [
{
"kind": "Variable",
"name": "id",
"variableName": "organizationId"
}
],
v2 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
v4 = {
"kind": "Literal",
"name": "first",
"value": 100
},
v5 = [
(v4/*: any*/),
{
"kind": "Literal",
"name": "orderBy",
"value": {
"direction": "DESC",
"field": "CREATED_AT"
}
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "PolicyGraphListQuery",
"selections": [
{
"alias": "organization",
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
{
"args": null,
"kind": "FragmentSpread",
"name": "PoliciesPageListFragment"
}
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "PolicyGraphListQuery",
"selections": [
{
"alias": "organization",
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v3/*: any*/),
(v2/*: any*/),
{
"kind": "InlineFragment",
"selections": [
{
"alias": null,
"args": (v5/*: any*/),
"concreteType": "PolicyConnection",
"kind": "LinkedField",
"name": "policies",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Policy",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "title",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "description",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "owner",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": [
{
"kind": "Literal",
"name": "first",
"value": 1
}
],
"concreteType": "PolicyVersionConnection",
"kind": "LinkedField",
"name": "versions",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersion",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "status",
"storageKey": null
},
{
"alias": null,
"args": [
(v4/*: any*/)
],
"concreteType": "PolicyVersionSignatureConnection",
"kind": "LinkedField",
"name": "signatures",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionSignatureEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionSignature",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "state",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "signatures(first:100)"
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "versions(first:1)"
},
(v3/*: any*/)
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasPreviousPage",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "startCursor",
"storageKey": null
}
],
"storageKey": null
},
{
"kind": "ClientExtension",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__id",
"storageKey": null
}
]
}
],
"storageKey": "policies(first:100,orderBy:{\"direction\":\"DESC\",\"field\":\"CREATED_AT\"})"
},
{
"alias": null,
"args": (v5/*: any*/),
"filters": [
"orderBy"
],
"handle": "connection",
"key": "PoliciesPageFragment_policies",
"kind": "LinkedHandle",
"name": "policies"
}
],
"type": "Organization",
"abstractKey": null
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "838223ae76604de3893662f5a496c1d3",
"id": null,
"metadata": {},
"name": "PolicyGraphListQuery",
"operationKind": "query",
"text": "query PolicyGraphListQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ...PoliciesPageListFragment\n }\n}\n\nfragment PoliciesPageListFragment on Organization {\n policies(first: 100, orderBy: {direction: DESC, field: CREATED_AT}) {\n edges {\n node {\n id\n ...PoliciesPageRowFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n}\n\nfragment PoliciesPageRowFragment on Policy {\n id\n title\n description\n updatedAt\n owner {\n id\n fullName\n }\n versions(first: 1) {\n edges {\n node {\n id\n status\n signatures(first: 100) {\n edges {\n node {\n id\n state\n }\n }\n }\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "29761736de791476004e17ad21659a59";
export default node;

View File

@@ -0,0 +1,277 @@
/**
* @generated SignedSource<<88a40781a78a282ebde97d4cb73bb996>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type PolicyGraphNodeQuery$variables = {
policyId: string;
};
export type PolicyGraphNodeQuery$data = {
readonly node: {
readonly " $fragmentSpreads": FragmentRefs<"PolicyPagePolicyFragment">;
};
};
export type PolicyGraphNodeQuery = {
response: PolicyGraphNodeQuery$data;
variables: PolicyGraphNodeQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "policyId"
}
],
v1 = [
{
"kind": "Variable",
"name": "id",
"variableName": "policyId"
}
],
v2 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "PolicyGraphNodeQuery",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"kind": "InlineFragment",
"selections": [
{
"args": null,
"kind": "FragmentSpread",
"name": "PolicyPagePolicyFragment"
}
],
"type": "Policy",
"abstractKey": null
}
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "PolicyGraphNodeQuery",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
(v2/*: any*/),
{
"kind": "InlineFragment",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "title",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "owner",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": [
{
"kind": "Literal",
"name": "first",
"value": 1
}
],
"concreteType": "PolicyVersionConnection",
"kind": "LinkedField",
"name": "versions",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersion",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "content",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "status",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "publishedAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "version",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
},
{
"alias": null,
"args": [
{
"kind": "Literal",
"name": "first",
"value": 100
}
],
"concreteType": "PolicyVersionSignatureConnection",
"kind": "LinkedField",
"name": "signatures",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionSignatureEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionSignature",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "state",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "signatures(first:100)"
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "versions(first:1)"
}
],
"type": "Policy",
"abstractKey": null
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "061b3441def7dfcf73881e57077e1738",
"id": null,
"metadata": {},
"name": "PolicyGraphNodeQuery",
"operationKind": "query",
"text": "query PolicyGraphNodeQuery(\n $policyId: ID!\n) {\n node(id: $policyId) {\n __typename\n ... on Policy {\n ...PolicyPagePolicyFragment\n }\n id\n }\n}\n\nfragment PolicyPagePolicyFragment on Policy {\n id\n title\n owner {\n id\n fullName\n }\n versions(first: 1) {\n edges {\n node {\n id\n content\n status\n publishedAt\n version\n updatedAt\n signatures(first: 100) {\n edges {\n node {\n id\n state\n }\n }\n }\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "c06b24e46b33741678e07787ab1c69f3";
export default node;

View File

@@ -4,9 +4,3 @@
@import "tw-animate-css";
@source "../../../packages/ui/src";
@source "../../../packages/helpers/src";
*:focus-visible {
box-shadow: var(--shadow-focus);
outline: none;
border-radius: 0.25rem;
}

View File

@@ -8,12 +8,8 @@
/* eslint-disable */
// @ts-nocheck
import { ReaderFragment } from "relay-runtime";
export type MeasureState =
| "IMPLEMENTED"
| "IN_PROGRESS"
| "NOT_APPLICABLE"
| "NOT_STARTED";
import { ReaderFragment } from 'relay-runtime';
export type MeasureState = "IMPLEMENTED" | "IN_PROGRESS" | "NOT_APPLICABLE" | "NOT_STARTED" | "%future added value";
import { FragmentRefs } from "relay-runtime";
export type MeasuresPageFragment$data = {
readonly measures: {
@@ -35,135 +31,137 @@ export type MeasuresPageFragment$key = {
};
const node: ReaderFragment = {
argumentDefinitions: [],
kind: "Fragment",
metadata: {
connection: [
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": {
"connection": [
{
count: null,
cursor: null,
direction: "forward",
path: ["measures"],
},
],
"count": null,
"cursor": null,
"direction": "forward",
"path": [
"measures"
]
}
]
},
name: "MeasuresPageFragment",
selections: [
"name": "MeasuresPageFragment",
"selections": [
{
alias: "measures",
args: null,
concreteType: "MeasureConnection",
kind: "LinkedField",
name: "__MeasuresGraphListQuery__measures_connection",
plural: false,
selections: [
"alias": "measures",
"args": null,
"concreteType": "MeasureConnection",
"kind": "LinkedField",
"name": "__MeasuresGraphListQuery__measures_connection",
"plural": false,
"selections": [
{
alias: null,
args: null,
concreteType: "MeasureEdge",
kind: "LinkedField",
name: "edges",
plural: true,
selections: [
"alias": null,
"args": null,
"concreteType": "MeasureEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
alias: null,
args: null,
concreteType: "Measure",
kind: "LinkedField",
name: "node",
plural: false,
selections: [
"alias": null,
"args": null,
"concreteType": "Measure",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
alias: null,
args: null,
kind: "ScalarField",
name: "id",
storageKey: null,
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
alias: null,
args: null,
kind: "ScalarField",
name: "name",
storageKey: null,
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
alias: null,
args: null,
kind: "ScalarField",
name: "category",
storageKey: null,
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "category",
"storageKey": null
},
{
alias: null,
args: null,
kind: "ScalarField",
name: "state",
storageKey: null,
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "state",
"storageKey": null
},
{
alias: null,
args: null,
kind: "ScalarField",
name: "__typename",
storageKey: null,
},
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
}
],
storageKey: null,
"storageKey": null
},
{
alias: null,
args: null,
kind: "ScalarField",
name: "cursor",
storageKey: null,
},
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
}
],
storageKey: null,
"storageKey": null
},
{
alias: null,
args: null,
concreteType: "PageInfo",
kind: "LinkedField",
name: "pageInfo",
plural: false,
selections: [
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
alias: null,
args: null,
kind: "ScalarField",
name: "endCursor",
storageKey: null,
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
},
{
alias: null,
args: null,
kind: "ScalarField",
name: "hasNextPage",
storageKey: null,
},
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
}
],
storageKey: null,
"storageKey": null
},
{
kind: "ClientExtension",
selections: [
"kind": "ClientExtension",
"selections": [
{
alias: null,
args: null,
kind: "ScalarField",
name: "__id",
storageKey: null,
},
],
},
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__id",
"storageKey": null
}
]
}
],
storageKey: null,
},
"storageKey": null
}
],
type: "Organization",
abstractKey: null,
"type": "Organization",
"abstractKey": null
};
(node as any).hash = "6a45bee844bf79dc4540e20edf10ad78";

View File

@@ -0,0 +1,139 @@
import { useTranslate } from "@probo/i18n";
import {
Badge,
Breadcrumb,
Button,
Dialog,
DialogContent,
DialogFooter,
Input,
Label,
PropertyRow,
Textarea,
} from "@probo/ui";
import { useState, type ReactNode } from "react";
import { graphql } from "relay-runtime";
import { useOrganizationId } from "/hooks/useOrganizationId";
import { usePolicyForm } from "../../../hooks/forms/usePolicyForm";
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
import type { FormPolicyDialogMutation } from "./__generated__/FormPolicyDialogMutation.graphql";
import { PeopleSelect } from "/components/form/PeopleSelect";
type Props = {
trigger?: ReactNode;
open?: boolean;
onSuccess?: () => void;
connection: string;
};
const createPolicyMutation = graphql`
mutation FormPolicyDialogMutation(
$input: CreatePolicyInput!
$connections: [ID!]!
) {
createPolicy(input: $input) {
policyEdge @prependEdge(connections: $connections) {
node {
...PoliciesPageRowFragment
}
}
}
}
`;
/**
* Dialog to create or update a policy
*/
export default function FormPolicyDialog({
trigger,
onSuccess,
open,
connection,
}: Props) {
const { __ } = useTranslate();
const organizationId = useOrganizationId();
const { control, handleSubmit, register, formState, reset } = usePolicyForm();
const errors = formState.errors ?? {};
const [createPolicy, isLoading] =
useMutationWithToasts<FormPolicyDialogMutation>(createPolicyMutation);
const onSubmit = handleSubmit((data) => {
createPolicy({
variables: {
input: {
...data,
organizationId,
},
connections: [connection!],
},
successMessage: __("Policy created successfully."),
errorMessage: __("Failed to create policy. Please try again."),
onSuccess: () => {
setOpen(false);
reset();
onSuccess?.();
},
});
});
const [isOpen, setOpen] = useState(!!open);
return (
<Dialog
open={isOpen}
onOpenChange={setOpen}
trigger={trigger}
title={<Breadcrumb items={[__("Policies"), __("New Policy")]} />}
>
<form onSubmit={onSubmit}>
<DialogContent className="grid grid-cols-[1fr_420px]">
<div className="py-8 px-10 space-y-4">
<Input
id="title"
aria-label={__("Title")}
required
variant="title"
placeholder={__("Policy title")}
{...register("title")}
/>
<Textarea
id="content"
variant="ghost"
autogrow
placeholder={__("Add description")}
aria-label={__("Description")}
{...register("content")}
/>
</div>
{/* Properties form */}
<div className="py-5 px-6 bg-subtle">
<Label>{__("Properties")}</Label>
<PropertyRow label={__("Status")}>
<Badge variant="neutral" size="md">
{__("Draft")}
</Badge>
</PropertyRow>
<PropertyRow
id="ownerId"
label={__("Owner")}
error={errors.ownerId?.message}
>
<PeopleSelect
name="ownerId"
control={control}
organization={organizationId}
/>
</PropertyRow>
</div>
</DialogContent>
<DialogFooter>
<Button type="submit" disabled={isLoading}>
{__("Create policy")}
</Button>
</DialogFooter>
</form>
</Dialog>
);
}

View File

@@ -0,0 +1,223 @@
import { useTranslate } from "@probo/i18n";
import {
PageHeader,
Table,
Tbody,
Thead,
Tr,
Th,
Td,
Avatar,
Badge,
ActionDropdown,
DropdownItem,
IconPencil,
ConfirmDialog,
IconTrashCan,
Button,
IconPlusLarge,
} from "@probo/ui";
import {
useFragment,
usePreloadedQuery,
type PreloadedQuery,
} from "react-relay";
import { graphql } from "relay-runtime";
import type { PolicyGraphListQuery } from "/hooks/graph/__generated__/PolicyGraphListQuery.graphql";
import {
policiesQuery,
useDeletePolicyMutation,
} from "/hooks/graph/PolicyGraph";
import type { PoliciesPageListFragment$key } from "./__generated__/PoliciesPageListFragment.graphql";
import { usePageTitle } from "@probo/hooks";
import { sprintf } from "@probo/helpers";
import FormPolicyDialog from "./FormPolicyDialog";
import type { PoliciesPageRowFragment$key } from "./__generated__/PoliciesPageRowFragment.graphql";
const policiesFragment = graphql`
fragment PoliciesPageListFragment on Organization {
policies(
first: 100
after: null
last: null
before: null
orderBy: { direction: DESC, field: CREATED_AT }
) @connection(key: "PoliciesPageFragment_policies") {
__id
edges {
node {
id
...PoliciesPageRowFragment
}
}
}
}
`;
type Props = {
queryRef: PreloadedQuery<PolicyGraphListQuery>;
};
export default function PoliciesPage(props: Props) {
const { __ } = useTranslate();
const organization = usePreloadedQuery(
policiesQuery,
props.queryRef
).organization;
const data = useFragment<PoliciesPageListFragment$key>(
policiesFragment,
organization
);
const policies = data.policies.edges.map((edge) => edge.node);
const connectionId = data.policies.__id;
usePageTitle(__("Policies"));
return (
<div className="space-y-6">
<PageHeader
title={__("Policies")}
description={__("Manage your organization’s policies")}
>
<FormPolicyDialog
connection={connectionId}
trigger={<Button icon={IconPlusLarge}>{__("New policy")}</Button>}
/>
</PageHeader>
<Table>
<Thead>
<Tr>
<Th>{__("Name")}</Th>
<Th>{__("Status")}</Th>
<Th>{__("Owner")}</Th>
<Th>{__("Last update")}</Th>
<Th>{__("Signatures")}</Th>
<Th></Th>
</Tr>
</Thead>
<Tbody>
{policies.map((policy) => (
<PolicyRow
key={policy.id}
policy={policy}
organizationId={organization.id}
connectionId={connectionId}
/>
))}
</Tbody>
</Table>
</div>
);
}
const rowFragment = graphql`
fragment PoliciesPageRowFragment on Policy {
id
title
description
updatedAt
owner {
id
fullName
}
versions(first: 1) {
edges {
node {
id
status
signatures(first: 100) {
edges {
node {
id
state
}
}
}
}
}
}
}
`;
function PolicyRow({
policy: policyKey,
organizationId,
connectionId,
}: {
policy: PoliciesPageRowFragment$key;
organizationId: string;
connectionId: string;
}) {
const policy = useFragment<PoliciesPageRowFragment$key>(
rowFragment,
policyKey
);
const lastVersion = policy.versions.edges[0].node;
const isDraft = lastVersion.status === "DRAFT";
const { __, dateFormat } = useTranslate();
const signatures = lastVersion.signatures.edges.map((edge) => edge.node);
const signedCount = signatures.filter(
(signature) => signature.state === "SIGNED"
).length;
const [deletePolicy, isDeleting] = useDeletePolicyMutation();
const handleDelete = () => {
deletePolicy({
variables: {
input: { policyId: policy.id },
connections: [connectionId],
},
});
};
return (
<Tr to={`/organizations/${organizationId}/policies/${policy.id}`}>
<Td>{policy.title}</Td>
<Td>
<Badge variant={isDraft ? "neutral" : "success"}>
{isDraft ? __("Draft") : __("Published")}
</Badge>
</Td>
<Td>
<div className="flex gap-2 items-center">
<Avatar name={policy.owner.fullName} />
{policy.owner.fullName}
</div>
</Td>
<Td>
{dateFormat(policy.updatedAt, {
year: "numeric",
month: "short",
day: "numeric",
weekday: "short",
})}
</Td>
<Td>
{signedCount}/{signatures.length}
</Td>
<Td noLink width={50} className="text-end">
<ActionDropdown>
<DropdownItem icon={IconPencil}>{__("Edit")}</DropdownItem>
<ConfirmDialog
message={sprintf(
__(
'This will permanently delete the policy "%s". This action cannot be undone.'
),
policy.title
)}
onConfirm={handleDelete}
>
<DropdownItem
disabled={isDeleting}
variant="danger"
icon={IconTrashCan}
>
{__("Delete")}
</DropdownItem>
</ConfirmDialog>
</ActionDropdown>
</Td>
</Tr>
);
}

View File

@@ -0,0 +1,177 @@
import type { PreloadedQuery } from "react-relay";
import type { PolicyGraphNodeQuery } from "/hooks/graph/__generated__/PolicyGraphNodeQuery.graphql";
import {
graphql,
loadQuery,
useFragment,
usePreloadedQuery,
} from "react-relay";
import { policyNodeQuery } from "/hooks/graph/PolicyGraph";
import { usePageTitle } from "@probo/hooks";
import type { PolicyPagePolicyFragment$key } from "./__generated__/PolicyPagePolicyFragment.graphql";
import { useTranslate } from "@probo/i18n";
import {
PageHeader,
Breadcrumb,
IconCheckmark1,
Markdown,
PropertyRow,
Drawer,
Badge,
Avatar,
} from "@probo/ui";
import { useOrganizationId } from "/hooks/useOrganizationId";
import { Button } from "@probo/ui";
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
type Props = {
queryRef: PreloadedQuery<PolicyGraphNodeQuery>;
};
const policyFragment = graphql`
fragment PolicyPagePolicyFragment on Policy {
id
title
owner {
id
fullName
}
versions(first: 1) {
edges {
node {
id
content
status
publishedAt
version
updatedAt
signatures(first: 100) {
edges {
node {
id
state
}
}
}
}
}
}
}
`;
const publishPolicyVersionMutation = graphql`
mutation PolicyPagePublishMutation($input: PublishPolicyVersionInput!) {
publishPolicyVersion(input: $input) {
policy {
id
}
}
}
`;
export default function PolicyPage(props: Props) {
const node = usePreloadedQuery(policyNodeQuery, props.queryRef).node;
const policy = useFragment(
policyFragment,
node as PolicyPagePolicyFragment$key
);
const { __, dateFormat } = useTranslate();
const organizationId = useOrganizationId();
const lastVersion = policy.versions.edges[0].node;
const isDraft = lastVersion.status === "DRAFT";
const [publishPolicyVersion, isPublishing] = useMutationWithToasts(
publishPolicyVersionMutation,
{
successMessage: __("Policy published successfully."),
errorMessage: __("Failed to publish policy. Please try again."),
}
);
usePageTitle(policy.title);
const handlePublish = () => {
publishPolicyVersion({
variables: {
input: { policyId: policy.id },
},
onSuccess: () => {
// Refresh the whole query to get the new version
loadQuery(
props.queryRef.environment,
policyNodeQuery,
props.queryRef.variables,
{ fetchPolicy: "network-only" }
);
},
});
};
return (
<>
<div className="space-y-6">
<div className="flex justify-between items-center mb-4">
<Breadcrumb
items={[
{
label: __("Policies"),
to: `/organizations/${organizationId}/policies`,
},
{
label: policy.title,
},
]}
/>
<div className="flex gap-2">
{isDraft && (
<Button
onClick={handlePublish}
icon={IconCheckmark1}
disabled={isPublishing}
>
{__("Publish")}
</Button>
)}
</div>
</div>
<PageHeader title={policy.title} />
<Markdown content={lastVersion.content} />
</div>
<Drawer>
<div className="text-base text-txt-primary font-medium mb-4">
{__("Properties")}
</div>
<PropertyRow label={__("Owner")}>
<Badge variant="highlight" size="md" className="gap-2">
<Avatar name={policy.owner?.fullName ?? ""} />
{policy.owner?.fullName}
</Badge>
</PropertyRow>
<PropertyRow label={__("Status")}>
<Badge
variant={lastVersion.status === "DRAFT" ? "highlight" : "success"}
size="md"
className="gap-2"
>
{lastVersion.status === "DRAFT" ? __("Draft") : __("Published")}
</Badge>
</PropertyRow>
<PropertyRow label={__("Version")}>
<div className="text-sm text-txt-secondary">
{lastVersion.version}
</div>
</PropertyRow>
<PropertyRow label={__("Last modified")}>
<div className="text-sm text-txt-secondary">
{dateFormat(lastVersion.updatedAt)}
</div>
</PropertyRow>
{lastVersion.publishedAt && (
<PropertyRow label={__("Published Date")}>
<div className="text-sm text-txt-secondary">
{dateFormat(lastVersion.publishedAt)}
</div>
</PropertyRow>
)}
</Drawer>
</>
);
}

View File

@@ -0,0 +1,322 @@
/**
* @generated SignedSource<<418554f5765131f61a3dfed83db0da76>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type CreatePolicyInput = {
content: string;
organizationId: string;
ownerId: string;
title: string;
};
export type FormPolicyDialogMutation$variables = {
connections: ReadonlyArray<string>;
input: CreatePolicyInput;
};
export type FormPolicyDialogMutation$data = {
readonly createPolicy: {
readonly policyEdge: {
readonly node: {
readonly " $fragmentSpreads": FragmentRefs<"PoliciesPageRowFragment">;
};
};
};
};
export type FormPolicyDialogMutation = {
response: FormPolicyDialogMutation$data;
variables: FormPolicyDialogMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "connections"
},
v1 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
},
v2 = [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": [
(v0/*: any*/),
(v1/*: any*/)
],
"kind": "Fragment",
"metadata": null,
"name": "FormPolicyDialogMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "CreatePolicyPayload",
"kind": "LinkedField",
"name": "createPolicy",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyEdge",
"kind": "LinkedField",
"name": "policyEdge",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Policy",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"args": null,
"kind": "FragmentSpread",
"name": "PoliciesPageRowFragment"
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [
(v1/*: any*/),
(v0/*: any*/)
],
"kind": "Operation",
"name": "FormPolicyDialogMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "CreatePolicyPayload",
"kind": "LinkedField",
"name": "createPolicy",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyEdge",
"kind": "LinkedField",
"name": "policyEdge",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Policy",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "title",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "description",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "owner",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": [
{
"kind": "Literal",
"name": "first",
"value": 1
}
],
"concreteType": "PolicyVersionConnection",
"kind": "LinkedField",
"name": "versions",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersion",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "status",
"storageKey": null
},
{
"alias": null,
"args": [
{
"kind": "Literal",
"name": "first",
"value": 100
}
],
"concreteType": "PolicyVersionSignatureConnection",
"kind": "LinkedField",
"name": "signatures",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionSignatureEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionSignature",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "state",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "signatures(first:100)"
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "versions(first:1)"
}
],
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"filters": null,
"handle": "prependEdge",
"key": "",
"kind": "LinkedHandle",
"name": "policyEdge",
"handleArgs": [
{
"kind": "Variable",
"name": "connections",
"variableName": "connections"
}
]
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "5967e987ab20d7823e7456a22aec54f2",
"id": null,
"metadata": {},
"name": "FormPolicyDialogMutation",
"operationKind": "mutation",
"text": "mutation FormPolicyDialogMutation(\n $input: CreatePolicyInput!\n) {\n createPolicy(input: $input) {\n policyEdge {\n node {\n ...PoliciesPageRowFragment\n id\n }\n }\n }\n}\n\nfragment PoliciesPageRowFragment on Policy {\n id\n title\n description\n updatedAt\n owner {\n id\n fullName\n }\n versions(first: 1) {\n edges {\n node {\n id\n status\n signatures(first: 100) {\n edges {\n node {\n id\n state\n }\n }\n }\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "c2612f85b9170d81cb9fb4908f752069";
export default node;

View File

@@ -0,0 +1,173 @@
/**
* @generated SignedSource<<cd6428905d88d31ab94cb3cbcd9fcd2c>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ReaderFragment } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type PoliciesPageListFragment$data = {
readonly policies: {
readonly __id: string;
readonly edges: ReadonlyArray<{
readonly node: {
readonly id: string;
readonly " $fragmentSpreads": FragmentRefs<"PoliciesPageRowFragment">;
};
}>;
};
readonly " $fragmentType": "PoliciesPageListFragment";
};
export type PoliciesPageListFragment$key = {
readonly " $data"?: PoliciesPageListFragment$data;
readonly " $fragmentSpreads": FragmentRefs<"PoliciesPageListFragment">;
};
const node: ReaderFragment = {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": {
"connection": [
{
"count": null,
"cursor": null,
"direction": "bidirectional",
"path": [
"policies"
]
}
]
},
"name": "PoliciesPageListFragment",
"selections": [
{
"alias": "policies",
"args": [
{
"kind": "Literal",
"name": "orderBy",
"value": {
"direction": "DESC",
"field": "CREATED_AT"
}
}
],
"concreteType": "PolicyConnection",
"kind": "LinkedField",
"name": "__PoliciesPageFragment_policies_connection",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Policy",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"args": null,
"kind": "FragmentSpread",
"name": "PoliciesPageRowFragment"
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasPreviousPage",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "startCursor",
"storageKey": null
}
],
"storageKey": null
},
{
"kind": "ClientExtension",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__id",
"storageKey": null
}
]
}
],
"storageKey": "__PoliciesPageFragment_policies_connection(orderBy:{\"direction\":\"DESC\",\"field\":\"CREATED_AT\"})"
}
],
"type": "Organization",
"abstractKey": null
};
(node as any).hash = "2f34601ba53a0900494a1bb0d8be1773";
export default node;

View File

@@ -0,0 +1,204 @@
/**
* @generated SignedSource<<76980bd62a1a415efbb27298d27874da>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ReaderFragment } from 'relay-runtime';
export type PolicyStatus = "DRAFT" | "PUBLISHED" | "%future added value";
export type PolicyVersionSignatureState = "REQUESTED" | "SIGNED" | "%future added value";
import { FragmentRefs } from "relay-runtime";
export type PoliciesPageRowFragment$data = {
readonly description: string;
readonly id: string;
readonly owner: {
readonly fullName: string;
readonly id: string;
};
readonly title: string;
readonly updatedAt: any;
readonly versions: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly id: string;
readonly signatures: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly id: string;
readonly state: PolicyVersionSignatureState;
};
}>;
};
readonly status: PolicyStatus;
};
}>;
};
readonly " $fragmentType": "PoliciesPageRowFragment";
};
export type PoliciesPageRowFragment$key = {
readonly " $data"?: PoliciesPageRowFragment$data;
readonly " $fragmentSpreads": FragmentRefs<"PoliciesPageRowFragment">;
};
const node: ReaderFragment = (function(){
var v0 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
};
return {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "PoliciesPageRowFragment",
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "title",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "description",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "owner",
"plural": false,
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": [
{
"kind": "Literal",
"name": "first",
"value": 1
}
],
"concreteType": "PolicyVersionConnection",
"kind": "LinkedField",
"name": "versions",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersion",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "status",
"storageKey": null
},
{
"alias": null,
"args": [
{
"kind": "Literal",
"name": "first",
"value": 100
}
],
"concreteType": "PolicyVersionSignatureConnection",
"kind": "LinkedField",
"name": "signatures",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionSignatureEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionSignature",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "state",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "signatures(first:100)"
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "versions(first:1)"
}
],
"type": "Policy",
"abstractKey": null
};
})();
(node as any).hash = "b50c23e038e757685dfc7535825a389c";
export default node;

View File

@@ -0,0 +1,220 @@
/**
* @generated SignedSource<<c59210c06978720ee4b52c41f0337ecf>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ReaderFragment } from 'relay-runtime';
export type PolicyStatus = "DRAFT" | "PUBLISHED" | "%future added value";
export type PolicyVersionSignatureState = "REQUESTED" | "SIGNED" | "%future added value";
import { FragmentRefs } from "relay-runtime";
export type PolicyPagePolicyFragment$data = {
readonly id: string;
readonly owner: {
readonly fullName: string;
readonly id: string;
};
readonly title: string;
readonly versions: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly content: string;
readonly id: string;
readonly publishedAt: any | null | undefined;
readonly signatures: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly id: string;
readonly state: PolicyVersionSignatureState;
};
}>;
};
readonly status: PolicyStatus;
readonly updatedAt: any;
readonly version: number;
};
}>;
};
readonly " $fragmentType": "PolicyPagePolicyFragment";
};
export type PolicyPagePolicyFragment$key = {
readonly " $data"?: PolicyPagePolicyFragment$data;
readonly " $fragmentSpreads": FragmentRefs<"PolicyPagePolicyFragment">;
};
const node: ReaderFragment = (function(){
var v0 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
};
return {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "PolicyPagePolicyFragment",
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "title",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "owner",
"plural": false,
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": [
{
"kind": "Literal",
"name": "first",
"value": 1
}
],
"concreteType": "PolicyVersionConnection",
"kind": "LinkedField",
"name": "versions",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersion",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "content",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "status",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "publishedAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "version",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
},
{
"alias": null,
"args": [
{
"kind": "Literal",
"name": "first",
"value": 100
}
],
"concreteType": "PolicyVersionSignatureConnection",
"kind": "LinkedField",
"name": "signatures",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionSignatureEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionSignature",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "state",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "signatures(first:100)"
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "versions(first:1)"
}
],
"type": "Policy",
"abstractKey": null
};
})();
(node as any).hash = "6b70d6d05d6e1081afae758389d070b2";
export default node;

View File

@@ -0,0 +1,249 @@
/**
* @generated SignedSource<<f3471e799b803d8bd39ad2a76603716e>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type PolicyPagePolicyQuery$variables = {
id: string;
};
export type PolicyPagePolicyQuery$data = {
readonly node: {
readonly " $fragmentSpreads": FragmentRefs<"PolicyPagePolicyFragment">;
};
};
export type PolicyPagePolicyQuery = {
response: PolicyPagePolicyQuery$data;
variables: PolicyPagePolicyQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "id"
}
],
v1 = [
{
"kind": "Variable",
"name": "id",
"variableName": "id"
}
],
v2 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "PolicyPagePolicyQuery",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"args": null,
"kind": "FragmentSpread",
"name": "PolicyPagePolicyFragment"
}
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "PolicyPagePolicyQuery",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
(v2/*: any*/),
{
"kind": "InlineFragment",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "title",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "description",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "owner",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": [
{
"kind": "Literal",
"name": "first",
"value": 1
}
],
"concreteType": "PolicyVersionConnection",
"kind": "LinkedField",
"name": "versions",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersion",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "status",
"storageKey": null
},
{
"alias": null,
"args": [
{
"kind": "Literal",
"name": "first",
"value": 100
}
],
"concreteType": "PolicyVersionSignatureConnection",
"kind": "LinkedField",
"name": "signatures",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionSignatureEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionSignature",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "state",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "signatures(first:100)"
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "versions(first:1)"
}
],
"type": "Policy",
"abstractKey": null
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "a7dd624a52cc7f0193b7670476c9de0d",
"id": null,
"metadata": {},
"name": "PolicyPagePolicyQuery",
"operationKind": "query",
"text": "query PolicyPagePolicyQuery(\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...PolicyPagePolicyFragment\n id\n }\n}\n\nfragment PolicyPagePolicyFragment on Policy {\n id\n title\n description\n owner {\n id\n fullName\n }\n versions(first: 1) {\n edges {\n node {\n id\n status\n signatures(first: 100) {\n edges {\n node {\n id\n state\n }\n }\n }\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "a1317341ce16c9f3cfd8f6a6a620c077";
export default node;

View File

@@ -0,0 +1,249 @@
/**
* @generated SignedSource<<9647dfc253447fe799b8178349c656d0>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type PolicyPagePolicyRefetch$variables = {
id: string;
};
export type PolicyPagePolicyRefetch$data = {
readonly node: {
readonly " $fragmentSpreads": FragmentRefs<"PolicyPagePolicyFragment">;
};
};
export type PolicyPagePolicyRefetch = {
response: PolicyPagePolicyRefetch$data;
variables: PolicyPagePolicyRefetch$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "id"
}
],
v1 = [
{
"kind": "Variable",
"name": "id",
"variableName": "id"
}
],
v2 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "PolicyPagePolicyRefetch",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"args": null,
"kind": "FragmentSpread",
"name": "PolicyPagePolicyFragment"
}
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "PolicyPagePolicyRefetch",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
(v2/*: any*/),
{
"kind": "InlineFragment",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "title",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "description",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "owner",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": [
{
"kind": "Literal",
"name": "first",
"value": 1
}
],
"concreteType": "PolicyVersionConnection",
"kind": "LinkedField",
"name": "versions",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersion",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "status",
"storageKey": null
},
{
"alias": null,
"args": [
{
"kind": "Literal",
"name": "first",
"value": 100
}
],
"concreteType": "PolicyVersionSignatureConnection",
"kind": "LinkedField",
"name": "signatures",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionSignatureEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PolicyVersionSignature",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "state",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "signatures(first:100)"
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "versions(first:1)"
}
],
"type": "Policy",
"abstractKey": null
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "6eec3f5bb581dbce013634d36d9b43d9",
"id": null,
"metadata": {},
"name": "PolicyPagePolicyRefetch",
"operationKind": "query",
"text": "query PolicyPagePolicyRefetch(\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...PolicyPagePolicyFragment\n id\n }\n}\n\nfragment PolicyPagePolicyFragment on Policy {\n id\n title\n description\n owner {\n id\n fullName\n }\n versions(first: 1) {\n edges {\n node {\n id\n status\n signatures(first: 100) {\n edges {\n node {\n id\n state\n }\n }\n }\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "e553858c09ded278d1d30aaad4f7ede9";
export default node;

View File

@@ -0,0 +1,105 @@
/**
* @generated SignedSource<<c68450dee24a94b20d5c4985a83511e0>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type PublishPolicyVersionInput = {
policyId: string;
};
export type PolicyPagePublishMutation$variables = {
input: PublishPolicyVersionInput;
};
export type PolicyPagePublishMutation$data = {
readonly publishPolicyVersion: {
readonly policy: {
readonly id: string;
};
};
};
export type PolicyPagePublishMutation = {
response: PolicyPagePublishMutation$data;
variables: PolicyPagePublishMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
}
],
v1 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
"concreteType": "PublishPolicyVersionPayload",
"kind": "LinkedField",
"name": "publishPolicyVersion",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Policy",
"kind": "LinkedField",
"name": "policy",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "PolicyPagePublishMutation",
"selections": (v1/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "PolicyPagePublishMutation",
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "4279dd7a9a12d8d5fb6e07c1875de00a",
"id": null,
"metadata": {},
"name": "PolicyPagePublishMutation",
"operationKind": "mutation",
"text": "mutation PolicyPagePublishMutation(\n $input: PublishPolicyVersionInput!\n) {\n publishPolicyVersion(input: $input) {\n policy {\n id\n }\n }\n}\n"
}
};
})();
(node as any).hash = "8ee6cdf68342db34ec04aab82313eff0";
export default node;

View File

@@ -13,6 +13,7 @@ import { type PreloadedQuery } from "react-relay";
import { useCleanup } from "./hooks/useDelayedEffect.ts";
import { riskRoutes } from "./routes/riskRoutes.ts";
import { measureRoutes } from "./routes/measureRoutes.ts";
import { policiesRoutes } from "./routes/policiesRoutes.ts";
function ErrorBoundary() {
const error = useRouteError();
@@ -72,6 +73,7 @@ const routes = [
},
...riskRoutes,
...measureRoutes,
...policiesRoutes,
],
},
] satisfies AppRoute[];

View File

@@ -0,0 +1,24 @@
import { lazy } from "react";
import { loadQuery } from "react-relay";
import type { AppRoute } from "/routes.tsx";
import { relayEnvironment } from "/providers/RelayProviders";
import { policiesQuery } from "/hooks/graph/PolicyGraph";
import { policyNodeQuery } from "/hooks/graph/PolicyGraph";
import { PageSkeleton } from "/components/skeletons/PageSkeleton";
export const policiesRoutes = [
{
path: "policies",
fallback: PageSkeleton,
queryLoader: ({ organizationId }) =>
loadQuery(relayEnvironment, policiesQuery, { organizationId }),
Component: lazy(() => import("/pages/organizations/policies/PoliciesPage")),
},
{
path: "policies/:policyId",
fallback: PageSkeleton,
queryLoader: ({ policyId }) =>
loadQuery(relayEnvironment, policyNodeQuery, { policyId }),
Component: lazy(() => import("/pages/organizations/policies/PolicyPage")),
},
] satisfies AppRoute[];