From 1870e050ce2f342030e4592b21b08e3624c242da Mon Sep 17 00:00:00 2001 From: Jonathan Date: Sun, 1 Jun 2025 21:37:11 +0200 Subject: [PATCH] Add frameworks listing and import Signed-off-by: Bryan Frimin Signed-off-by: Sacha Al Himdani --- apps/console2/public/logos/gdpr.png | Bin 0 -> 4777 bytes apps/console2/public/logos/iso27001.png | Bin 0 -> 4800 bytes apps/console2/public/logos/soc2.png | Bin 0 -> 4671 bytes apps/console2/public/logos/sox.png | Bin 0 -> 4715 bytes .../src/hooks/graph/FrameworkGraph.ts | 75 +++++ .../FrameworkGraphDeleteMutation.graphql.ts | 132 ++++++++ .../FrameworkGraphListQuery.graphql.ts | 302 ++++++++++++++++++ .../frameworks/FrameworksPage.tsx | 219 +++++++++++++ .../FrameworksPageCardFragment.graphql.ts | 58 ++++ .../FrameworksPageImportMutation.graphql.ts | 202 ++++++++++++ .../dialogs/CreateFrameworkDialog.tsx | 105 ++++++ .../CreateFrameworkDialogMutation.graphql.ts | 203 ++++++++++++ .../organizations/policies/PoliciesPage.tsx | 106 +++--- apps/console2/src/routes.tsx | 2 + apps/console2/src/routes/frameworkRoutes.ts | 18 ++ packages/helpers/src/frameworks.ts | 26 ++ packages/helpers/src/index.ts | 1 + packages/ui/src/Atoms/Avatar/Avatar.tsx | 34 +- packages/ui/src/Atoms/Dropdown/Dropdown.tsx | 2 +- packages/ui/src/Molecules/Dialog/Dialog.tsx | 11 +- .../FrameworkSelector.stories.tsx | 12 + .../FrameworkSelector/FrameworkSelector.tsx | 76 +++++ packages/ui/src/index.ts | 2 + 23 files changed, 1517 insertions(+), 69 deletions(-) create mode 100644 apps/console2/public/logos/gdpr.png create mode 100644 apps/console2/public/logos/iso27001.png create mode 100644 apps/console2/public/logos/soc2.png create mode 100644 apps/console2/public/logos/sox.png create mode 100644 apps/console2/src/hooks/graph/FrameworkGraph.ts create mode 100644 apps/console2/src/hooks/graph/__generated__/FrameworkGraphDeleteMutation.graphql.ts create mode 100644 apps/console2/src/hooks/graph/__generated__/FrameworkGraphListQuery.graphql.ts create mode 100644 apps/console2/src/pages/organizations/frameworks/FrameworksPage.tsx create mode 100644 apps/console2/src/pages/organizations/frameworks/__generated__/FrameworksPageCardFragment.graphql.ts create mode 100644 apps/console2/src/pages/organizations/frameworks/__generated__/FrameworksPageImportMutation.graphql.ts create mode 100644 apps/console2/src/pages/organizations/frameworks/dialogs/CreateFrameworkDialog.tsx create mode 100644 apps/console2/src/pages/organizations/frameworks/dialogs/__generated__/CreateFrameworkDialogMutation.graphql.ts create mode 100644 apps/console2/src/routes/frameworkRoutes.ts create mode 100644 packages/helpers/src/frameworks.ts create mode 100644 packages/ui/src/Molecules/FrameworkSelector/FrameworkSelector.stories.tsx create mode 100644 packages/ui/src/Molecules/FrameworkSelector/FrameworkSelector.tsx diff --git a/apps/console2/public/logos/gdpr.png b/apps/console2/public/logos/gdpr.png new file mode 100644 index 0000000000000000000000000000000000000000..72510b40a70416c60fb9a1b8197300c639a80b19 GIT binary patch literal 4777 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 B!#w~1 literal 0 HcmV?d00001 diff --git a/apps/console2/public/logos/sox.png b/apps/console2/public/logos/sox.png new file mode 100644 index 0000000000000000000000000000000000000000..7daf0bef5d69497c8113a28515aa670543b162a9 GIT binary patch literal 4715 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 { + const [commitDelete, isDeleting] = useMutationWithToasts( + deleteFrameworkMutation, + { + errorMessage: "Failed to delete framework", + successMessage: "Framework deleted successfully", + } + ); + const confirm = useConfirm(); + const { __ } = useTranslate(); + + return useCallback(() => { + confirm( + () => { + return commitDelete({ + variables: { + input: { + frameworkId: framework.id!, + }, + connections: [connectionId], + }, + }); + }, + { + message: sprintf( + __( + 'This will permanently delete framework "%s". This action cannot be undone.' + ), + framework.name + ), + } + ); + }, [framework, connectionId, commitDelete]); +}; diff --git a/apps/console2/src/hooks/graph/__generated__/FrameworkGraphDeleteMutation.graphql.ts b/apps/console2/src/hooks/graph/__generated__/FrameworkGraphDeleteMutation.graphql.ts new file mode 100644 index 000000000..040bd7711 --- /dev/null +++ b/apps/console2/src/hooks/graph/__generated__/FrameworkGraphDeleteMutation.graphql.ts @@ -0,0 +1,132 @@ +/** + * @generated SignedSource<<05d197ce59adad67cfb89dff4c502c58>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type DeleteFrameworkInput = { + frameworkId: string; +}; +export type FrameworkGraphDeleteMutation$variables = { + connections: ReadonlyArray; + input: DeleteFrameworkInput; +}; +export type FrameworkGraphDeleteMutation$data = { + readonly deleteFramework: { + readonly deletedFrameworkId: string; + }; +}; +export type FrameworkGraphDeleteMutation = { + response: FrameworkGraphDeleteMutation$data; + variables: FrameworkGraphDeleteMutation$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": "deletedFrameworkId", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": [ + (v0/*: any*/), + (v1/*: any*/) + ], + "kind": "Fragment", + "metadata": null, + "name": "FrameworkGraphDeleteMutation", + "selections": [ + { + "alias": null, + "args": (v2/*: any*/), + "concreteType": "DeleteFrameworkPayload", + "kind": "LinkedField", + "name": "deleteFramework", + "plural": false, + "selections": [ + (v3/*: any*/) + ], + "storageKey": null + } + ], + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [ + (v1/*: any*/), + (v0/*: any*/) + ], + "kind": "Operation", + "name": "FrameworkGraphDeleteMutation", + "selections": [ + { + "alias": null, + "args": (v2/*: any*/), + "concreteType": "DeleteFrameworkPayload", + "kind": "LinkedField", + "name": "deleteFramework", + "plural": false, + "selections": [ + (v3/*: any*/), + { + "alias": null, + "args": null, + "filters": null, + "handle": "deleteEdge", + "key": "", + "kind": "ScalarHandle", + "name": "deletedFrameworkId", + "handleArgs": [ + { + "kind": "Variable", + "name": "connections", + "variableName": "connections" + } + ] + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "761a391f497aa0e2e03d0db958089712", + "id": null, + "metadata": {}, + "name": "FrameworkGraphDeleteMutation", + "operationKind": "mutation", + "text": "mutation FrameworkGraphDeleteMutation(\n $input: DeleteFrameworkInput!\n) {\n deleteFramework(input: $input) {\n deletedFrameworkId\n }\n}\n" + } +}; +})(); + +(node as any).hash = "6ebefeb003ce3a27bd134651e1b5c94b"; + +export default node; diff --git a/apps/console2/src/hooks/graph/__generated__/FrameworkGraphListQuery.graphql.ts b/apps/console2/src/hooks/graph/__generated__/FrameworkGraphListQuery.graphql.ts new file mode 100644 index 000000000..0d4e9fd07 --- /dev/null +++ b/apps/console2/src/hooks/graph/__generated__/FrameworkGraphListQuery.graphql.ts @@ -0,0 +1,302 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type FrameworkGraphListQuery$variables = { + organizationId: string; +}; +export type FrameworkGraphListQuery$data = { + readonly organization: { + readonly frameworks?: { + readonly __id: string; + readonly edges: ReadonlyArray<{ + readonly node: { + readonly id: string; + readonly " $fragmentSpreads": FragmentRefs<"FrameworksPageCardFragment">; + }; + }>; + }; + readonly id?: string; + }; +}; +export type FrameworkGraphListQuery = { + response: FrameworkGraphListQuery$data; + variables: FrameworkGraphListQuery$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 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "cursor", + "storageKey": null +}, +v5 = { + "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 + } + ], + "storageKey": null +}, +v6 = { + "kind": "ClientExtension", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__id", + "storageKey": null + } + ] +}, +v7 = [ + { + "kind": "Literal", + "name": "first", + "value": 100 + } +]; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "FrameworkGraphListQuery", + "selections": [ + { + "alias": "organization", + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "kind": "InlineFragment", + "selections": [ + (v2/*: any*/), + { + "alias": "frameworks", + "args": null, + "concreteType": "FrameworkConnection", + "kind": "LinkedField", + "name": "__FrameworksListQuery_frameworks_connection", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "FrameworkEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Framework", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v2/*: any*/), + { + "args": null, + "kind": "FragmentSpread", + "name": "FrameworksPageCardFragment" + }, + (v3/*: any*/) + ], + "storageKey": null + }, + (v4/*: any*/) + ], + "storageKey": null + }, + (v5/*: any*/), + (v6/*: any*/) + ], + "storageKey": null + } + ], + "type": "Organization", + "abstractKey": null + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "FrameworkGraphListQuery", + "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": (v7/*: any*/), + "concreteType": "FrameworkConnection", + "kind": "LinkedField", + "name": "frameworks", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "FrameworkEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Framework", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v2/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "description", + "storageKey": null + }, + (v3/*: any*/) + ], + "storageKey": null + }, + (v4/*: any*/) + ], + "storageKey": null + }, + (v5/*: any*/), + (v6/*: any*/) + ], + "storageKey": "frameworks(first:100)" + }, + { + "alias": null, + "args": (v7/*: any*/), + "filters": null, + "handle": "connection", + "key": "FrameworksListQuery_frameworks", + "kind": "LinkedHandle", + "name": "frameworks" + } + ], + "type": "Organization", + "abstractKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "b31afd68395ae800a18885bca3abfa1c", + "id": null, + "metadata": { + "connection": [ + { + "count": null, + "cursor": null, + "direction": "forward", + "path": [ + "organization", + "frameworks" + ] + } + ] + }, + "name": "FrameworkGraphListQuery", + "operationKind": "query", + "text": "query FrameworkGraphListQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n id\n frameworks(first: 100) {\n edges {\n node {\n id\n ...FrameworksPageCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n id\n }\n}\n\nfragment FrameworksPageCardFragment on Framework {\n id\n name\n description\n}\n" + } +}; +})(); + +(node as any).hash = "4aaead183328e669aba942992db6f89c"; + +export default node; diff --git a/apps/console2/src/pages/organizations/frameworks/FrameworksPage.tsx b/apps/console2/src/pages/organizations/frameworks/FrameworksPage.tsx new file mode 100644 index 000000000..d21b3ad49 --- /dev/null +++ b/apps/console2/src/pages/organizations/frameworks/FrameworksPage.tsx @@ -0,0 +1,219 @@ +import { usePageTitle } from "@probo/hooks"; +import { useTranslate } from "@probo/i18n"; +import { + ActionDropdown, + Avatar, + Button, + Card, + Dropdown, + DropdownItem, + FileButton, + FrameworkSelector, + IconChevronDown, + IconPlusLarge, + IconTrashCan, + IconUpload, + PageHeader, + useDialogRef, +} from "@probo/ui"; +import { + graphql, + useFragment, + usePreloadedQuery, + type PreloadedQuery, +} from "react-relay"; +import type { FrameworkGraphListQuery } from "/hooks/graph/__generated__/FrameworkGraphListQuery.graphql"; +import { + frameworksQuery, + useDeleteFrameworkMutation, +} from "/hooks/graph/FrameworkGraph"; +import { Link } from "react-router"; +import type { FrameworksPageCardFragment$key } from "./__generated__/FrameworksPageCardFragment.graphql"; +import { useMutationWithToasts } from "/hooks/useMutationWithToasts"; +import { useState, type ChangeEventHandler } from "react"; +import { availableFrameworks } from "@probo/helpers"; +import { CreateFrameworkDialog } from "./dialogs/CreateFrameworkDialog"; + +type Props = { + queryRef: PreloadedQuery; +}; + +const importFrameworkMutation = graphql` + mutation FrameworksPageImportMutation( + $input: ImportFrameworkInput! + $connections: [ID!]! + ) { + importFramework(input: $input) { + frameworkEdge @prependEdge(connections: $connections) { + node { + id + ...FrameworksPageCardFragment + } + } + } + } +`; + +const availableLogos = new Map( + availableFrameworks.map((framework) => [framework.name, framework.logo]) +); + +export default function FrameworksPage(props: Props) { + const { __ } = useTranslate(); + usePageTitle(__("Frameworks")); + const data = usePreloadedQuery(frameworksQuery, props.queryRef); + const connectionId = data.organization.frameworks!.__id; + const frameworks = + data.organization.frameworks?.edges.map((edge) => edge.node) ?? []; + const [commitImport, isImporting] = useMutationWithToasts( + importFrameworkMutation, + { + successMessage: __("Framework imported successfully"), + errorMessage: __("Failed to import framework"), + } + ); + const [isUploading, setUploading] = useState(false); + const dialogRef = useDialogRef(); + + const importNamedFramework = async (name: string) => { + // For custom framework, open the form + if (name === "custom") { + console.log(name, dialogRef); + dialogRef.current?.open(); + return; + } + // Otherwise load the JSON and send the file to the server + try { + setUploading(true); + const fileName = `${name}.json`; + const json = await fetch(`/data/frameworks/${fileName}`).then((res) => + res.text() + ); + const file = new File([json], fileName, { + type: "application/json", + }); + await importFile(file); + } finally { + setUploading(false); + } + }; + + const importFile = (file: File) => { + return commitImport({ + variables: { + input: { + organizationId: data.organization.id!, + file: null, + }, + connections: [connectionId], + }, + uploadables: { + "input.file": file, + }, + }); + }; + + const handleUpload: ChangeEventHandler = (event) => { + const input = event.currentTarget; + const file = input.files?.[0]; + if (!file) { + return; + } + importFile(file).finally(() => { + input.value = ""; + }); + }; + + const isLoading = isUploading || isImporting; + + return ( +
+ + + + {__("Import")} + + + + +
+ {frameworks.map((framework) => ( + + ))} +
+
+ ); +} + +const frameworkCardFragment = graphql` + fragment FrameworksPageCardFragment on Framework { + id + name + description + } +`; + +type FrameworkCardProps = { + organizationId: string; + connectionId: string; + framework: FrameworksPageCardFragment$key; +}; + +function FrameworkCard(props: FrameworkCardProps) { + const framework = useFragment(frameworkCardFragment, props.framework); + const deleteFramework = useDeleteFrameworkMutation( + framework, + props.connectionId + ); + const { __ } = useTranslate(); + const logo = availableLogos.get(framework.name); + return ( + +
+ {logo ? ( + + ) : ( + + )} + + + {__("Delete")} + + +
+

+ + {framework.name} + +

+

{framework.description}

+
+ ); +} diff --git a/apps/console2/src/pages/organizations/frameworks/__generated__/FrameworksPageCardFragment.graphql.ts b/apps/console2/src/pages/organizations/frameworks/__generated__/FrameworksPageCardFragment.graphql.ts new file mode 100644 index 000000000..1ecc063c9 --- /dev/null +++ b/apps/console2/src/pages/organizations/frameworks/__generated__/FrameworksPageCardFragment.graphql.ts @@ -0,0 +1,58 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ReaderFragment } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type FrameworksPageCardFragment$data = { + readonly description: string; + readonly id: string; + readonly name: string; + readonly " $fragmentType": "FrameworksPageCardFragment"; +}; +export type FrameworksPageCardFragment$key = { + readonly " $data"?: FrameworksPageCardFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"FrameworksPageCardFragment">; +}; + +const node: ReaderFragment = { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "FrameworksPageCardFragment", + "selections": [ + { + "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": "description", + "storageKey": null + } + ], + "type": "Framework", + "abstractKey": null +}; + +(node as any).hash = "4481f380673963e72f88071031e37d14"; + +export default node; diff --git a/apps/console2/src/pages/organizations/frameworks/__generated__/FrameworksPageImportMutation.graphql.ts b/apps/console2/src/pages/organizations/frameworks/__generated__/FrameworksPageImportMutation.graphql.ts new file mode 100644 index 000000000..5546d50c5 --- /dev/null +++ b/apps/console2/src/pages/organizations/frameworks/__generated__/FrameworksPageImportMutation.graphql.ts @@ -0,0 +1,202 @@ +/** + * @generated SignedSource<<9436ef052ea63a9785d82ec810e810f1>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type ImportFrameworkInput = { + file: any; + organizationId: string; +}; +export type FrameworksPageImportMutation$variables = { + connections: ReadonlyArray; + input: ImportFrameworkInput; +}; +export type FrameworksPageImportMutation$data = { + readonly importFramework: { + readonly frameworkEdge: { + readonly node: { + readonly id: string; + readonly " $fragmentSpreads": FragmentRefs<"FrameworksPageCardFragment">; + }; + }; + }; +}; +export type FrameworksPageImportMutation = { + response: FrameworksPageImportMutation$data; + variables: FrameworksPageImportMutation$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": "FrameworksPageImportMutation", + "selections": [ + { + "alias": null, + "args": (v2/*: any*/), + "concreteType": "ImportFrameworkPayload", + "kind": "LinkedField", + "name": "importFramework", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "FrameworkEdge", + "kind": "LinkedField", + "name": "frameworkEdge", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Framework", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v3/*: any*/), + { + "args": null, + "kind": "FragmentSpread", + "name": "FrameworksPageCardFragment" + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [ + (v1/*: any*/), + (v0/*: any*/) + ], + "kind": "Operation", + "name": "FrameworksPageImportMutation", + "selections": [ + { + "alias": null, + "args": (v2/*: any*/), + "concreteType": "ImportFrameworkPayload", + "kind": "LinkedField", + "name": "importFramework", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "FrameworkEdge", + "kind": "LinkedField", + "name": "frameworkEdge", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Framework", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v3/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "description", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "filters": null, + "handle": "prependEdge", + "key": "", + "kind": "LinkedHandle", + "name": "frameworkEdge", + "handleArgs": [ + { + "kind": "Variable", + "name": "connections", + "variableName": "connections" + } + ] + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "87a38788cb2e060657b552af69acb707", + "id": null, + "metadata": {}, + "name": "FrameworksPageImportMutation", + "operationKind": "mutation", + "text": "mutation FrameworksPageImportMutation(\n $input: ImportFrameworkInput!\n) {\n importFramework(input: $input) {\n frameworkEdge {\n node {\n id\n ...FrameworksPageCardFragment\n }\n }\n }\n}\n\nfragment FrameworksPageCardFragment on Framework {\n id\n name\n description\n}\n" + } +}; +})(); + +(node as any).hash = "e2ff0f6d6780cf967a021a093542dab9"; + +export default node; diff --git a/apps/console2/src/pages/organizations/frameworks/dialogs/CreateFrameworkDialog.tsx b/apps/console2/src/pages/organizations/frameworks/dialogs/CreateFrameworkDialog.tsx new file mode 100644 index 000000000..cc87995ca --- /dev/null +++ b/apps/console2/src/pages/organizations/frameworks/dialogs/CreateFrameworkDialog.tsx @@ -0,0 +1,105 @@ +import { graphql } from "relay-runtime"; +import { useMutationWithToasts } from "/hooks/useMutationWithToasts"; +import { useTranslate } from "@probo/i18n"; +import { + Breadcrumb, + Button, + Dialog, + DialogContent, + DialogFooter, + Field, + Input, + Textarea, + useDialogRef, + type DialogRef, +} from "@probo/ui"; +import type { RefObject } from "react"; +import { z } from "zod"; +import { useFormWithSchema } from "/hooks/useFormWithSchema"; + +const createFrameworkMutation = graphql` + mutation CreateFrameworkDialogMutation( + $input: CreateFrameworkInput! + $connections: [ID!]! + ) { + createFramework(input: $input) { + frameworkEdge @prependEdge(connections: $connections) { + node { + id + ...FrameworksPageCardFragment + } + } + } + } +`; + +type Props = { + connectionId: string; + organizationId: string; + ref: DialogRef; +}; + +const schema = z.object({ + name: z.string().min(1).max(255), + description: z.string().max(255).optional(), +}); + +export function CreateFrameworkDialog(props: Props) { + const { __ } = useTranslate(); + const { + register, + handleSubmit, + formState: { errors }, + reset, + } = useFormWithSchema(schema, {}); + const [commitCreate, isCreating] = useMutationWithToasts( + createFrameworkMutation, + { + successMessage: __("Framework created successfully"), + errorMessage: __("Failed to create framework"), + } + ); + const onSubmit = handleSubmit(async (data) => { + await commitCreate({ + variables: { + input: { + ...data, + organizationId: props.organizationId, + }, + connections: [props.connectionId], + }, + }); + reset(); + props.ref.current?.close(); + }); + + return ( + } + > +
+ + +