Add vendor certifications tab
Signed-off-by: Bryan Frimin <bryan@getprobo.com> Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
committed by
Sacha Al Himdani
parent
b94b58e2de
commit
df23fd635f
@@ -1,42 +1,43 @@
|
|||||||
import { Avatar, Option, Select } from "@probo/ui";
|
import { Avatar, Field, Option, Select } from "@probo/ui";
|
||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
import { useTranslate } from "@probo/i18n";
|
import { useTranslate } from "@probo/i18n";
|
||||||
import { type Control, Controller } from "react-hook-form";
|
import { type Control, Controller } from "react-hook-form";
|
||||||
import { usePeople } from "/hooks/graph/PeopleGraph.ts";
|
import { usePeople } from "/hooks/graph/PeopleGraph.ts";
|
||||||
|
|
||||||
export function PeopleSelect({
|
type Props = {
|
||||||
organization,
|
organizationId: string;
|
||||||
name,
|
|
||||||
control,
|
|
||||||
}: {
|
|
||||||
organization: string;
|
|
||||||
name: string;
|
|
||||||
control: Control<any>;
|
control: Control<any>;
|
||||||
}) {
|
name: string;
|
||||||
|
label?: string;
|
||||||
|
error?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function PeopleSelectField({
|
||||||
|
organizationId,
|
||||||
|
control,
|
||||||
|
...props
|
||||||
|
}: Props) {
|
||||||
return (
|
return (
|
||||||
<Suspense
|
<Field {...props}>
|
||||||
fallback={<Select variant="editor" disabled placeholder="Loading..." />}
|
<Suspense
|
||||||
>
|
fallback={<Select variant="editor" disabled placeholder="Loading..." />}
|
||||||
<PeopleSelectWithQuery
|
>
|
||||||
organization={organization}
|
<PeopleSelectWithQuery
|
||||||
name={name}
|
organizationId={organizationId}
|
||||||
control={control}
|
control={control}
|
||||||
/>
|
name={props.name}
|
||||||
</Suspense>
|
/>
|
||||||
|
</Suspense>
|
||||||
|
</Field>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function PeopleSelectWithQuery({
|
function PeopleSelectWithQuery(
|
||||||
organization,
|
props: Pick<Props, "organizationId" | "control" | "name">
|
||||||
name,
|
) {
|
||||||
control,
|
|
||||||
}: {
|
|
||||||
organization: string;
|
|
||||||
name: string;
|
|
||||||
control: Control;
|
|
||||||
}) {
|
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const people = usePeople(organization);
|
const { name, organizationId, control } = props;
|
||||||
|
const people = usePeople(organizationId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -51,6 +52,7 @@ function PeopleSelectWithQuery({
|
|||||||
onValueChange={field.onChange}
|
onValueChange={field.onChange}
|
||||||
key={people?.length.toString() ?? "0"}
|
key={people?.length.toString() ?? "0"}
|
||||||
{...field}
|
{...field}
|
||||||
|
className="w-full"
|
||||||
value={field.value ?? ""}
|
value={field.value ?? ""}
|
||||||
>
|
>
|
||||||
{people?.map((p) => (
|
{people?.map((p) => (
|
||||||
178
apps/console2/src/hooks/forms/__generated__/useVendorFormFragment.graphql.ts
generated
Normal file
178
apps/console2/src/hooks/forms/__generated__/useVendorFormFragment.graphql.ts
generated
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<ea3e7fd6a3b7dd5f78622e9f21cdb1d3>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ReaderFragment } from 'relay-runtime';
|
||||||
|
import { FragmentRefs } from "relay-runtime";
|
||||||
|
export type useVendorFormFragment$data = {
|
||||||
|
readonly businessOwner: {
|
||||||
|
readonly id: string;
|
||||||
|
} | null | undefined;
|
||||||
|
readonly certifications: ReadonlyArray<string>;
|
||||||
|
readonly dataProcessingAgreementUrl: string | null | undefined;
|
||||||
|
readonly description: string | null | undefined;
|
||||||
|
readonly headquarterAddress: string | null | undefined;
|
||||||
|
readonly id: string;
|
||||||
|
readonly legalName: string | null | undefined;
|
||||||
|
readonly name: string;
|
||||||
|
readonly privacyPolicyUrl: string | null | undefined;
|
||||||
|
readonly securityOwner: {
|
||||||
|
readonly id: string;
|
||||||
|
} | null | undefined;
|
||||||
|
readonly securityPageUrl: string | null | undefined;
|
||||||
|
readonly serviceLevelAgreementUrl: string | null | undefined;
|
||||||
|
readonly statusPageUrl: string | null | undefined;
|
||||||
|
readonly termsOfServiceUrl: string | null | undefined;
|
||||||
|
readonly trustPageUrl: string | null | undefined;
|
||||||
|
readonly websiteUrl: string | null | undefined;
|
||||||
|
readonly " $fragmentType": "useVendorFormFragment";
|
||||||
|
};
|
||||||
|
export type useVendorFormFragment$key = {
|
||||||
|
readonly " $data"?: useVendorFormFragment$data;
|
||||||
|
readonly " $fragmentSpreads": FragmentRefs<"useVendorFormFragment">;
|
||||||
|
};
|
||||||
|
|
||||||
|
const node: ReaderFragment = (function(){
|
||||||
|
var v0 = {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "id",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
v1 = [
|
||||||
|
(v0/*: any*/)
|
||||||
|
];
|
||||||
|
return {
|
||||||
|
"argumentDefinitions": [],
|
||||||
|
"kind": "Fragment",
|
||||||
|
"metadata": null,
|
||||||
|
"name": "useVendorFormFragment",
|
||||||
|
"selections": [
|
||||||
|
(v0/*: any*/),
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "name",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "description",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "statusPageUrl",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "termsOfServiceUrl",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "privacyPolicyUrl",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "serviceLevelAgreementUrl",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "dataProcessingAgreementUrl",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "websiteUrl",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "legalName",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "headquarterAddress",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "certifications",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "securityPageUrl",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "trustPageUrl",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "People",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "businessOwner",
|
||||||
|
"plural": false,
|
||||||
|
"selections": (v1/*: any*/),
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "People",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "securityOwner",
|
||||||
|
"plural": false,
|
||||||
|
"selections": (v1/*: any*/),
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Vendor",
|
||||||
|
"abstractKey": null
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
(node as any).hash = "87f1029f8c634a5f7efdb6d7abe17709";
|
||||||
|
|
||||||
|
export default node;
|
||||||
265
apps/console2/src/hooks/forms/__generated__/useVendorFormMutation.graphql.ts
generated
Normal file
265
apps/console2/src/hooks/forms/__generated__/useVendorFormMutation.graphql.ts
generated
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<6a680118da31fa610f9638d27a9b48d1>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
|
import { FragmentRefs } from "relay-runtime";
|
||||||
|
export type UpdateVendorInput = {
|
||||||
|
businessOwnerId?: string | null | undefined;
|
||||||
|
category?: string | null | undefined;
|
||||||
|
certifications?: ReadonlyArray<string> | null | undefined;
|
||||||
|
dataProcessingAgreementUrl?: string | null | undefined;
|
||||||
|
description?: string | null | undefined;
|
||||||
|
headquarterAddress?: string | null | undefined;
|
||||||
|
id: string;
|
||||||
|
legalName?: string | null | undefined;
|
||||||
|
name?: string | null | undefined;
|
||||||
|
privacyPolicyUrl?: string | null | undefined;
|
||||||
|
securityOwnerId?: string | null | undefined;
|
||||||
|
securityPageUrl?: string | null | undefined;
|
||||||
|
serviceLevelAgreementUrl?: string | null | undefined;
|
||||||
|
statusPageUrl?: string | null | undefined;
|
||||||
|
termsOfServiceUrl?: string | null | undefined;
|
||||||
|
trustPageUrl?: string | null | undefined;
|
||||||
|
websiteUrl?: string | null | undefined;
|
||||||
|
};
|
||||||
|
export type useVendorFormMutation$variables = {
|
||||||
|
input: UpdateVendorInput;
|
||||||
|
};
|
||||||
|
export type useVendorFormMutation$data = {
|
||||||
|
readonly updateVendor: {
|
||||||
|
readonly vendor: {
|
||||||
|
readonly " $fragmentSpreads": FragmentRefs<"useVendorFormFragment">;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export type useVendorFormMutation = {
|
||||||
|
response: useVendorFormMutation$data;
|
||||||
|
variables: useVendorFormMutation$variables;
|
||||||
|
};
|
||||||
|
|
||||||
|
const node: ConcreteRequest = (function(){
|
||||||
|
var v0 = [
|
||||||
|
{
|
||||||
|
"defaultValue": null,
|
||||||
|
"kind": "LocalArgument",
|
||||||
|
"name": "input"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
v1 = [
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "input",
|
||||||
|
"variableName": "input"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
v2 = {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "id",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
v3 = [
|
||||||
|
(v2/*: any*/)
|
||||||
|
];
|
||||||
|
return {
|
||||||
|
"fragment": {
|
||||||
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
|
"kind": "Fragment",
|
||||||
|
"metadata": null,
|
||||||
|
"name": "useVendorFormMutation",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": (v1/*: any*/),
|
||||||
|
"concreteType": "UpdateVendorPayload",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "updateVendor",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "Vendor",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "vendor",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"args": null,
|
||||||
|
"kind": "FragmentSpread",
|
||||||
|
"name": "useVendorFormFragment"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Mutation",
|
||||||
|
"abstractKey": null
|
||||||
|
},
|
||||||
|
"kind": "Request",
|
||||||
|
"operation": {
|
||||||
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
|
"kind": "Operation",
|
||||||
|
"name": "useVendorFormMutation",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": (v1/*: any*/),
|
||||||
|
"concreteType": "UpdateVendorPayload",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "updateVendor",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "Vendor",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "vendor",
|
||||||
|
"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
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "statusPageUrl",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "termsOfServiceUrl",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "privacyPolicyUrl",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "serviceLevelAgreementUrl",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "dataProcessingAgreementUrl",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "websiteUrl",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "legalName",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "headquarterAddress",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "certifications",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "securityPageUrl",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "trustPageUrl",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "People",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "businessOwner",
|
||||||
|
"plural": false,
|
||||||
|
"selections": (v3/*: any*/),
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "People",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "securityOwner",
|
||||||
|
"plural": false,
|
||||||
|
"selections": (v3/*: any*/),
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"cacheID": "324b4589b4692806f555489703521bcd",
|
||||||
|
"id": null,
|
||||||
|
"metadata": {},
|
||||||
|
"name": "useVendorFormMutation",
|
||||||
|
"operationKind": "mutation",
|
||||||
|
"text": "mutation useVendorFormMutation(\n $input: UpdateVendorInput!\n) {\n updateVendor(input: $input) {\n vendor {\n ...useVendorFormFragment\n id\n }\n }\n}\n\nfragment useVendorFormFragment on Vendor {\n id\n name\n description\n statusPageUrl\n termsOfServiceUrl\n privacyPolicyUrl\n serviceLevelAgreementUrl\n dataProcessingAgreementUrl\n websiteUrl\n legalName\n headquarterAddress\n certifications\n securityPageUrl\n trustPageUrl\n businessOwner {\n id\n }\n securityOwner {\n id\n }\n}\n"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
(node as any).hash = "f49cfca25ffd99e8bbdc2e2e3c78b400";
|
||||||
|
|
||||||
|
export default node;
|
||||||
108
apps/console2/src/hooks/forms/useVendorForm.tsx
Normal file
108
apps/console2/src/hooks/forms/useVendorForm.tsx
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
import { useFormWithSchema } from "../useFormWithSchema";
|
||||||
|
import { graphql } from "relay-runtime";
|
||||||
|
import { useFragment, useMutation } from "react-relay";
|
||||||
|
import type { useVendorFormFragment$key } from "./__generated__/useVendorFormFragment.graphql";
|
||||||
|
import { useMutationWithToasts } from "../useMutationWithToasts";
|
||||||
|
import { useTranslate } from "@probo/i18n";
|
||||||
|
|
||||||
|
const schema = z.object({
|
||||||
|
name: z.string(),
|
||||||
|
description: z.string(),
|
||||||
|
statusPageUrl: z.string(),
|
||||||
|
termsOfServiceUrl: z.string(),
|
||||||
|
privacyPolicyUrl: z.string(),
|
||||||
|
serviceLevelAgreementUrl: z.string(),
|
||||||
|
dataProcessingAgreementUrl: z.string(),
|
||||||
|
websiteUrl: z.string(),
|
||||||
|
legalName: z.string(),
|
||||||
|
headquarterAddress: z.string(),
|
||||||
|
certifications: z.array(z.string()),
|
||||||
|
securityPageUrl: z.string(),
|
||||||
|
trustPageUrl: z.string(),
|
||||||
|
businessOwnerId: z.string(),
|
||||||
|
securityOwnerId: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const vendorFormFragment = graphql`
|
||||||
|
fragment useVendorFormFragment on Vendor {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
description
|
||||||
|
statusPageUrl
|
||||||
|
termsOfServiceUrl
|
||||||
|
privacyPolicyUrl
|
||||||
|
serviceLevelAgreementUrl
|
||||||
|
dataProcessingAgreementUrl
|
||||||
|
websiteUrl
|
||||||
|
legalName
|
||||||
|
headquarterAddress
|
||||||
|
certifications
|
||||||
|
securityPageUrl
|
||||||
|
trustPageUrl
|
||||||
|
businessOwner {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
securityOwner {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const vendorUpdateQuery = graphql`
|
||||||
|
mutation useVendorFormMutation($input: UpdateVendorInput!) {
|
||||||
|
updateVendor(input: $input) {
|
||||||
|
vendor {
|
||||||
|
...useVendorFormFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function useVendorForm(vendorKey: useVendorFormFragment$key) {
|
||||||
|
const vendor = useFragment(vendorFormFragment, vendorKey);
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
|
||||||
|
const [mutate] = useMutationWithToasts(vendorUpdateQuery, {
|
||||||
|
successMessage: __("Vendor updated successfully."),
|
||||||
|
errorMessage: __("Failed to update vendor. Please try again."),
|
||||||
|
});
|
||||||
|
|
||||||
|
const form = useFormWithSchema(schema, {
|
||||||
|
defaultValues: {
|
||||||
|
name: vendor.name,
|
||||||
|
description: vendor.description ?? "",
|
||||||
|
statusPageUrl: vendor.statusPageUrl ?? "",
|
||||||
|
termsOfServiceUrl: vendor.termsOfServiceUrl ?? "",
|
||||||
|
privacyPolicyUrl: vendor.privacyPolicyUrl ?? "",
|
||||||
|
serviceLevelAgreementUrl: vendor.serviceLevelAgreementUrl ?? "",
|
||||||
|
dataProcessingAgreementUrl: vendor.dataProcessingAgreementUrl ?? "",
|
||||||
|
websiteUrl: vendor.websiteUrl ?? "",
|
||||||
|
legalName: vendor.legalName ?? "",
|
||||||
|
headquarterAddress: vendor.headquarterAddress ?? "",
|
||||||
|
certifications: [...(vendor.certifications ?? [])],
|
||||||
|
securityPageUrl: vendor.securityPageUrl ?? "",
|
||||||
|
trustPageUrl: vendor.trustPageUrl ?? "",
|
||||||
|
businessOwnerId: vendor.businessOwner?.id,
|
||||||
|
securityOwnerId: vendor.securityOwner?.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSubmit = form.handleSubmit((data) => {
|
||||||
|
return mutate({
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
id: vendor.id,
|
||||||
|
...data,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).then(() => {
|
||||||
|
form.reset(data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...form,
|
||||||
|
handleSubmit,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -7,14 +7,13 @@ import {
|
|||||||
useRefetchableFragment,
|
useRefetchableFragment,
|
||||||
type PreloadedQuery,
|
type PreloadedQuery,
|
||||||
} from "react-relay";
|
} from "react-relay";
|
||||||
import { useMemo, useTransition } from "react";
|
import { useMemo } from "react";
|
||||||
import type { PeopleGraphPaginatedQuery } from "./__generated__/PeopleGraphPaginatedQuery.graphql";
|
import type { PeopleGraphPaginatedQuery } from "./__generated__/PeopleGraphPaginatedQuery.graphql";
|
||||||
import type { PeopleGraphPaginatedFragment$key } from "./__generated__/PeopleGraphPaginatedFragment.graphql";
|
import type { PeopleGraphPaginatedFragment$key } from "./__generated__/PeopleGraphPaginatedFragment.graphql";
|
||||||
import { useConfirm } from "@probo/ui";
|
import { useConfirm } from "@probo/ui";
|
||||||
import type { PeopleGraphDeleteMutation } from "./__generated__/PeopleGraphDeleteMutation.graphql";
|
import type { PeopleGraphDeleteMutation } from "./__generated__/PeopleGraphDeleteMutation.graphql";
|
||||||
import { sprintf } from "@probo/helpers";
|
import { sprintf } from "@probo/helpers";
|
||||||
import { useTranslate } from "@probo/i18n";
|
import { useTranslate } from "@probo/i18n";
|
||||||
import type { PeopleGraphNodeQuery$data } from "./__generated__/PeopleGraphNodeQuery.graphql";
|
|
||||||
|
|
||||||
const peopleQuery = graphql`
|
const peopleQuery = graphql`
|
||||||
query PeopleGraphQuery($organizationId: ID!) {
|
query PeopleGraphQuery($organizationId: ID!) {
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ import { graphql } from "relay-runtime";
|
|||||||
import { useMutationWithToasts } from "../useMutationWithToasts.ts";
|
import { useMutationWithToasts } from "../useMutationWithToasts.ts";
|
||||||
import type { VendorGraphCreateMutation } from "./__generated__/VendorGraphCreateMutation.graphql.ts";
|
import type { VendorGraphCreateMutation } from "./__generated__/VendorGraphCreateMutation.graphql.ts";
|
||||||
import type { VendorGraphDeleteMutation } from "./__generated__/VendorGraphDeleteMutation.graphql.ts";
|
import type { VendorGraphDeleteMutation } from "./__generated__/VendorGraphDeleteMutation.graphql.ts";
|
||||||
|
import { useMutation } from "react-relay";
|
||||||
|
import { useConfirm } from "@probo/ui";
|
||||||
|
import { sprintf } from "@probo/helpers";
|
||||||
|
|
||||||
const createVendorMutation = graphql`
|
const createVendorMutation = graphql`
|
||||||
mutation VendorGraphCreateMutation(
|
mutation VendorGraphCreateMutation(
|
||||||
@@ -32,7 +35,7 @@ export function useCreateVendorMutation() {
|
|||||||
{
|
{
|
||||||
successMessage: __("Vendor created successfully."),
|
successMessage: __("Vendor created successfully."),
|
||||||
errorMessage: __("Failed to create vendor. Please try again."),
|
errorMessage: __("Failed to create vendor. Please try again."),
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,14 +50,88 @@ const deleteVendorMutation = graphql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export function useDeleteVendorMutation() {
|
export const useDeleteVendor = (
|
||||||
|
vendor: { id?: string; name?: string },
|
||||||
|
connectionId: string
|
||||||
|
) => {
|
||||||
|
const [mutate] = useMutation<VendorGraphDeleteMutation>(deleteVendorMutation);
|
||||||
|
const confirm = useConfirm();
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
|
|
||||||
return useMutationWithToasts<VendorGraphDeleteMutation>(
|
return () => {
|
||||||
deleteVendorMutation,
|
if (!vendor.id || !vendor.name) {
|
||||||
{
|
return alert(__("Failed to delete vendor: missing id or name"));
|
||||||
successMessage: __("Vendor deleted successfully."),
|
}
|
||||||
errorMessage: __("Failed to delete vendor. Please try again."),
|
confirm(
|
||||||
},
|
() =>
|
||||||
);
|
new Promise((resolve) => {
|
||||||
}
|
mutate({
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
vendorId: vendor.id!,
|
||||||
|
},
|
||||||
|
connections: [connectionId],
|
||||||
|
},
|
||||||
|
onCompleted: () => resolve(),
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
message: sprintf(
|
||||||
|
__(
|
||||||
|
'This will permanently delete vendor "%s". This action cannot be undone.'
|
||||||
|
),
|
||||||
|
vendor.name
|
||||||
|
),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const vendorConnectionKey = "VendorsPage_vendors";
|
||||||
|
|
||||||
|
export const vendorsQuery = graphql`
|
||||||
|
query VendorGraphListQuery($organizationId: ID!) {
|
||||||
|
node(id: $organizationId) {
|
||||||
|
... on Organization {
|
||||||
|
vendors(first: 25) @connection(key: "VendorsPage_vendors") {
|
||||||
|
__id
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
websiteUrl
|
||||||
|
updatedAt
|
||||||
|
riskAssessments(
|
||||||
|
first: 1
|
||||||
|
orderBy: { direction: DESC, field: ASSESSED_AT }
|
||||||
|
) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
assessedAt
|
||||||
|
expiresAt
|
||||||
|
dataSensitivity
|
||||||
|
businessImpact
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const vendorNodeQuery = graphql`
|
||||||
|
query VendorGraphNodeQuery($vendorId: ID!) {
|
||||||
|
node(id: $vendorId) {
|
||||||
|
... on Vendor {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
websiteUrl
|
||||||
|
...useVendorFormFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @generated SignedSource<<2de1bbc55eb7b45a9546c8ec306ad979>>
|
* @generated SignedSource<<bf60e491fedeea556cb63210ab364d6c>>
|
||||||
* @lightSyntaxTransform
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -11,10 +11,10 @@
|
|||||||
import { ConcreteRequest } from 'relay-runtime';
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
export type BusinessImpact = "CRITICAL" | "HIGH" | "LOW" | "MEDIUM";
|
export type BusinessImpact = "CRITICAL" | "HIGH" | "LOW" | "MEDIUM";
|
||||||
export type DataSensitivity = "CRITICAL" | "HIGH" | "LOW" | "MEDIUM" | "NONE";
|
export type DataSensitivity = "CRITICAL" | "HIGH" | "LOW" | "MEDIUM" | "NONE";
|
||||||
export type VendorsPageQuery$variables = {
|
export type VendorGraphListQuery$variables = {
|
||||||
organizationId: string;
|
organizationId: string;
|
||||||
};
|
};
|
||||||
export type VendorsPageQuery$data = {
|
export type VendorGraphListQuery$data = {
|
||||||
readonly node: {
|
readonly node: {
|
||||||
readonly vendors?: {
|
readonly vendors?: {
|
||||||
readonly __id: string;
|
readonly __id: string;
|
||||||
@@ -40,9 +40,9 @@ export type VendorsPageQuery$data = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export type VendorsPageQuery = {
|
export type VendorGraphListQuery = {
|
||||||
response: VendorsPageQuery$data;
|
response: VendorGraphListQuery$data;
|
||||||
variables: VendorsPageQuery$variables;
|
variables: VendorGraphListQuery$variables;
|
||||||
};
|
};
|
||||||
|
|
||||||
const node: ConcreteRequest = (function(){
|
const node: ConcreteRequest = (function(){
|
||||||
@@ -253,7 +253,7 @@ return {
|
|||||||
"argumentDefinitions": (v0/*: any*/),
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
"kind": "Fragment",
|
"kind": "Fragment",
|
||||||
"metadata": null,
|
"metadata": null,
|
||||||
"name": "VendorsPageQuery",
|
"name": "VendorGraphListQuery",
|
||||||
"selections": [
|
"selections": [
|
||||||
{
|
{
|
||||||
"alias": null,
|
"alias": null,
|
||||||
@@ -291,7 +291,7 @@ return {
|
|||||||
"operation": {
|
"operation": {
|
||||||
"argumentDefinitions": (v0/*: any*/),
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
"kind": "Operation",
|
"kind": "Operation",
|
||||||
"name": "VendorsPageQuery",
|
"name": "VendorGraphListQuery",
|
||||||
"selections": [
|
"selections": [
|
||||||
{
|
{
|
||||||
"alias": null,
|
"alias": null,
|
||||||
@@ -335,7 +335,7 @@ return {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"params": {
|
"params": {
|
||||||
"cacheID": "cfdef0fe4c1b737de9dfbf0a27a3de44",
|
"cacheID": "ce175c4c5d08117d3acdbd3eed92ca95",
|
||||||
"id": null,
|
"id": null,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"connection": [
|
"connection": [
|
||||||
@@ -350,13 +350,13 @@ return {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"name": "VendorsPageQuery",
|
"name": "VendorGraphListQuery",
|
||||||
"operationKind": "query",
|
"operationKind": "query",
|
||||||
"text": "query VendorsPageQuery(\n $organizationId: ID!\n) {\n node(id: $organizationId) {\n __typename\n ... on Organization {\n vendors(first: 25) {\n edges {\n node {\n id\n name\n websiteUrl\n updatedAt\n riskAssessments(first: 1, orderBy: {direction: DESC, field: ASSESSED_AT}) {\n edges {\n node {\n id\n assessedAt\n expiresAt\n dataSensitivity\n businessImpact\n }\n }\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n id\n }\n}\n"
|
"text": "query VendorGraphListQuery(\n $organizationId: ID!\n) {\n node(id: $organizationId) {\n __typename\n ... on Organization {\n vendors(first: 25) {\n edges {\n node {\n id\n name\n websiteUrl\n updatedAt\n riskAssessments(first: 1, orderBy: {direction: DESC, field: ASSESSED_AT}) {\n edges {\n node {\n id\n assessedAt\n expiresAt\n dataSensitivity\n businessImpact\n }\n }\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n id\n }\n}\n"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
(node as any).hash = "ce37725ed4c8f58ae2e4af7cf6ee8564";
|
(node as any).hash = "ef2b4fb44af8cef6cd3f50a6746c6de3";
|
||||||
|
|
||||||
export default node;
|
export default node;
|
||||||
251
apps/console2/src/hooks/graph/__generated__/VendorGraphNodeQuery.graphql.ts
generated
Normal file
251
apps/console2/src/hooks/graph/__generated__/VendorGraphNodeQuery.graphql.ts
generated
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<57ff86dca8786cdaf42f34f9cbe379a1>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
|
import { FragmentRefs } from "relay-runtime";
|
||||||
|
export type VendorGraphNodeQuery$variables = {
|
||||||
|
vendorId: string;
|
||||||
|
};
|
||||||
|
export type VendorGraphNodeQuery$data = {
|
||||||
|
readonly node: {
|
||||||
|
readonly id?: string;
|
||||||
|
readonly name?: string;
|
||||||
|
readonly websiteUrl?: string | null | undefined;
|
||||||
|
readonly " $fragmentSpreads": FragmentRefs<"useVendorFormFragment">;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export type VendorGraphNodeQuery = {
|
||||||
|
response: VendorGraphNodeQuery$data;
|
||||||
|
variables: VendorGraphNodeQuery$variables;
|
||||||
|
};
|
||||||
|
|
||||||
|
const node: ConcreteRequest = (function(){
|
||||||
|
var v0 = [
|
||||||
|
{
|
||||||
|
"defaultValue": null,
|
||||||
|
"kind": "LocalArgument",
|
||||||
|
"name": "vendorId"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
v1 = [
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "id",
|
||||||
|
"variableName": "vendorId"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
v2 = {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "id",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
v3 = {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "name",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
v4 = {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "websiteUrl",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
v5 = [
|
||||||
|
(v2/*: any*/)
|
||||||
|
];
|
||||||
|
return {
|
||||||
|
"fragment": {
|
||||||
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
|
"kind": "Fragment",
|
||||||
|
"metadata": null,
|
||||||
|
"name": "VendorGraphNodeQuery",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": (v1/*: any*/),
|
||||||
|
"concreteType": null,
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"kind": "InlineFragment",
|
||||||
|
"selections": [
|
||||||
|
(v2/*: any*/),
|
||||||
|
(v3/*: any*/),
|
||||||
|
(v4/*: any*/),
|
||||||
|
{
|
||||||
|
"args": null,
|
||||||
|
"kind": "FragmentSpread",
|
||||||
|
"name": "useVendorFormFragment"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Vendor",
|
||||||
|
"abstractKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Query",
|
||||||
|
"abstractKey": null
|
||||||
|
},
|
||||||
|
"kind": "Request",
|
||||||
|
"operation": {
|
||||||
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
|
"kind": "Operation",
|
||||||
|
"name": "VendorGraphNodeQuery",
|
||||||
|
"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": [
|
||||||
|
(v3/*: any*/),
|
||||||
|
(v4/*: any*/),
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "description",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "statusPageUrl",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "termsOfServiceUrl",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "privacyPolicyUrl",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "serviceLevelAgreementUrl",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "dataProcessingAgreementUrl",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "legalName",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "headquarterAddress",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "certifications",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "securityPageUrl",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "trustPageUrl",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "People",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "businessOwner",
|
||||||
|
"plural": false,
|
||||||
|
"selections": (v5/*: any*/),
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "People",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "securityOwner",
|
||||||
|
"plural": false,
|
||||||
|
"selections": (v5/*: any*/),
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Vendor",
|
||||||
|
"abstractKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"cacheID": "3e7b2628c65e007a1603b982e41fea12",
|
||||||
|
"id": null,
|
||||||
|
"metadata": {},
|
||||||
|
"name": "VendorGraphNodeQuery",
|
||||||
|
"operationKind": "query",
|
||||||
|
"text": "query VendorGraphNodeQuery(\n $vendorId: ID!\n) {\n node(id: $vendorId) {\n __typename\n ... on Vendor {\n id\n name\n websiteUrl\n ...useVendorFormFragment\n }\n id\n }\n}\n\nfragment useVendorFormFragment on Vendor {\n id\n name\n description\n statusPageUrl\n termsOfServiceUrl\n privacyPolicyUrl\n serviceLevelAgreementUrl\n dataProcessingAgreementUrl\n websiteUrl\n legalName\n headquarterAddress\n certifications\n securityPageUrl\n trustPageUrl\n businessOwner {\n id\n }\n securityOwner {\n id\n }\n}\n"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
(node as any).hash = "9a718caa45c75f17f4b910ca2c555781";
|
||||||
|
|
||||||
|
export default node;
|
||||||
@@ -26,36 +26,43 @@ export function useMutationWithToasts<T extends MutationParameters>(
|
|||||||
}
|
}
|
||||||
) => {
|
) => {
|
||||||
const options = { ...baseOptions, ...queryOptions };
|
const options = { ...baseOptions, ...queryOptions };
|
||||||
mutate({
|
return new Promise<void>((resolve, reject) =>
|
||||||
...options,
|
mutate({
|
||||||
onCompleted: (response, error) => {
|
...options,
|
||||||
options.onCompleted?.(response, error);
|
onCompleted: (response, error) => {
|
||||||
if (error) {
|
options.onCompleted?.(response, error);
|
||||||
|
if (error) {
|
||||||
|
toast({
|
||||||
|
title: __("Error"),
|
||||||
|
description:
|
||||||
|
options.errorMessage ??
|
||||||
|
__("Failed to commit this operation."),
|
||||||
|
variant: "error",
|
||||||
|
});
|
||||||
|
reject(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
toast({
|
||||||
|
title: __("Success"),
|
||||||
|
description:
|
||||||
|
options.successMessage ??
|
||||||
|
__("Operation completed successfully"),
|
||||||
|
variant: "success",
|
||||||
|
});
|
||||||
|
options.onSuccess?.();
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
toast({
|
toast({
|
||||||
title: __("Error"),
|
title: __("Error"),
|
||||||
description:
|
description:
|
||||||
options.errorMessage ?? __("Failed to commit this operation."),
|
options.errorMessage ?? __("Failed to commit this operation."),
|
||||||
variant: "error",
|
variant: "error",
|
||||||
});
|
});
|
||||||
return;
|
reject(error);
|
||||||
}
|
},
|
||||||
toast({
|
})
|
||||||
title: __("Success"),
|
);
|
||||||
description:
|
|
||||||
options.successMessage ?? __("Operation completed successfully"),
|
|
||||||
variant: "success",
|
|
||||||
});
|
|
||||||
options.onSuccess?.();
|
|
||||||
},
|
|
||||||
onError: () => {
|
|
||||||
toast({
|
|
||||||
title: __("Error"),
|
|
||||||
description:
|
|
||||||
options.errorMessage ?? __("Failed to commit this operation."),
|
|
||||||
variant: "error",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
[mutate]
|
[mutate]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,12 +12,12 @@ import {
|
|||||||
Textarea,
|
Textarea,
|
||||||
useDialogRef,
|
useDialogRef,
|
||||||
} from "@probo/ui";
|
} from "@probo/ui";
|
||||||
import { useState, type ReactNode } from "react";
|
import { type ReactNode } from "react";
|
||||||
import { graphql } from "relay-runtime";
|
import { graphql } from "relay-runtime";
|
||||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||||
import { usePolicyForm } from "../../../../hooks/forms/usePolicyForm";
|
import { usePolicyForm } from "/hooks/forms/usePolicyForm";
|
||||||
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
|
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
|
||||||
import { PeopleSelect } from "/components/form/PeopleSelect";
|
import { PeopleSelectField } from "/components/form/PeopleSelectField";
|
||||||
import type { CreatePolicyDialogMutation } from "./__generated__/CreatePolicyDialogMutation.graphql";
|
import type { CreatePolicyDialogMutation } from "./__generated__/CreatePolicyDialogMutation.graphql";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -112,10 +112,10 @@ export function CreatePolicyDialog({ trigger, connection }: Props) {
|
|||||||
label={__("Owner")}
|
label={__("Owner")}
|
||||||
error={errors.ownerId?.message}
|
error={errors.ownerId?.message}
|
||||||
>
|
>
|
||||||
<PeopleSelect
|
<PeopleSelectField
|
||||||
name="ownerId"
|
name="ownerId"
|
||||||
control={control}
|
control={control}
|
||||||
organization={organizationId}
|
organizationId={organizationId}
|
||||||
/>
|
/>
|
||||||
</PropertyRow>
|
</PropertyRow>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ import {
|
|||||||
Textarea,
|
Textarea,
|
||||||
useDialogRef,
|
useDialogRef,
|
||||||
} from "@probo/ui";
|
} from "@probo/ui";
|
||||||
import { useMemo, useState, type ReactNode } from "react";
|
import { useMemo, type ReactNode } from "react";
|
||||||
import { useFetchQuery } from "/hooks/useFetchQuery";
|
import { useFetchQuery } from "/hooks/useFetchQuery";
|
||||||
import { graphql } from "relay-runtime";
|
import { graphql } from "relay-runtime";
|
||||||
import { PeopleSelect } from "/components/form/PeopleSelect";
|
import { PeopleSelectField } from "/components/form/PeopleSelectField";
|
||||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||||
import { useToggle } from "@probo/hooks";
|
import { useToggle } from "@probo/hooks";
|
||||||
import {
|
import {
|
||||||
@@ -196,10 +196,10 @@ export default function FormRiskDialog({
|
|||||||
label={__("Owner")}
|
label={__("Owner")}
|
||||||
error={errors.ownerId?.message}
|
error={errors.ownerId?.message}
|
||||||
>
|
>
|
||||||
<PeopleSelect
|
<PeopleSelectField
|
||||||
name="ownerId"
|
name="ownerId"
|
||||||
control={control}
|
control={control}
|
||||||
organization={organizationId}
|
organizationId={organizationId}
|
||||||
/>
|
/>
|
||||||
</PropertyRow>
|
</PropertyRow>
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
DialogFooter,
|
DialogFooter,
|
||||||
Avatar,
|
Avatar,
|
||||||
IconPlusLarge,
|
IconPlusLarge,
|
||||||
|
useDialogRef,
|
||||||
} from "@probo/ui";
|
} from "@probo/ui";
|
||||||
import { useVendorSearch } from "/hooks/useVendorSearch";
|
import { useVendorSearch } from "/hooks/useVendorSearch";
|
||||||
import { faviconUrl } from "@probo/helpers";
|
import { faviconUrl } from "@probo/helpers";
|
||||||
@@ -15,20 +16,19 @@ import { useCreateVendorMutation } from "/hooks/graph/VendorGraph";
|
|||||||
import { type ReactNode, useState } from "react";
|
import { type ReactNode, useState } from "react";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
trigger: ReactNode;
|
children: ReactNode;
|
||||||
organizationId: string;
|
organizationId: string;
|
||||||
connection: string;
|
connection: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function CreateVendorDialog({
|
export function CreateVendorDialog({
|
||||||
trigger,
|
children,
|
||||||
organizationId,
|
organizationId,
|
||||||
connection,
|
connection,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const { search, vendors, query } = useVendorSearch();
|
const { search, vendors, query } = useVendorSearch();
|
||||||
const [createVendor] = useCreateVendorMutation();
|
const [createVendor] = useCreateVendorMutation();
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
|
|
||||||
const onSelect = (vendor: Vendor | string) => {
|
const onSelect = (vendor: Vendor | string) => {
|
||||||
const input =
|
const input =
|
||||||
@@ -61,18 +61,15 @@ export function CreateVendorDialog({
|
|||||||
connections: [connection],
|
connections: [connection],
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
setOpen(false);
|
dialogRef.current?.close();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const dialogRef = useDialogRef();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog ref={dialogRef} trigger={children} title={__("Add a vendor")}>
|
||||||
title={__("Add a vendor")}
|
|
||||||
trigger={trigger}
|
|
||||||
open={open}
|
|
||||||
onOpenChange={setOpen}
|
|
||||||
>
|
|
||||||
<DialogContent className="p-6">
|
<DialogContent className="p-6">
|
||||||
<Combobox onSearch={search} placeholder={__("Type vendor's name")}>
|
<Combobox onSearch={search} placeholder={__("Type vendor's name")}>
|
||||||
{vendors.map((vendor) => (
|
{vendors.map((vendor) => (
|
||||||
|
|||||||
97
apps/console2/src/pages/organizations/vendors/VendorDetailPage.tsx
vendored
Normal file
97
apps/console2/src/pages/organizations/vendors/VendorDetailPage.tsx
vendored
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import {
|
||||||
|
ConnectionHandler,
|
||||||
|
usePreloadedQuery,
|
||||||
|
type PreloadedQuery,
|
||||||
|
} from "react-relay";
|
||||||
|
import type { VendorGraphNodeQuery } from "/hooks/graph/__generated__/VendorGraphNodeQuery.graphql";
|
||||||
|
import {
|
||||||
|
useDeleteVendor,
|
||||||
|
vendorConnectionKey,
|
||||||
|
vendorNodeQuery,
|
||||||
|
} from "/hooks/graph/VendorGraph";
|
||||||
|
import {
|
||||||
|
ActionDropdown,
|
||||||
|
Avatar,
|
||||||
|
Breadcrumb,
|
||||||
|
DropdownItem,
|
||||||
|
IconTrashCan,
|
||||||
|
TabLink,
|
||||||
|
Tabs,
|
||||||
|
} from "@probo/ui";
|
||||||
|
import { useTranslate } from "@probo/i18n";
|
||||||
|
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||||
|
import { Outlet } from "react-router";
|
||||||
|
import { faviconUrl } from "@probo/helpers";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
queryRef: PreloadedQuery<VendorGraphNodeQuery>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function VendorDetailPage(props: Props) {
|
||||||
|
const data = usePreloadedQuery(vendorNodeQuery, props.queryRef);
|
||||||
|
const vendor = data.node;
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
const organizationId = useOrganizationId();
|
||||||
|
const deleteVendor = useDeleteVendor(
|
||||||
|
vendor,
|
||||||
|
ConnectionHandler.getConnectionID(organizationId, vendorConnectionKey)
|
||||||
|
);
|
||||||
|
const logo = faviconUrl(vendor.websiteUrl);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<Breadcrumb
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
label: __("Vendors"),
|
||||||
|
to: `/organizations/${organizationId}/vendors`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: vendor.name ?? "",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<div className="space-y-4">
|
||||||
|
{logo && (
|
||||||
|
<img
|
||||||
|
src={logo}
|
||||||
|
alt={vendor.name ?? ""}
|
||||||
|
className="shadow-mid rounded-2xl"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className="text-2xl">{vendor.name}</div>
|
||||||
|
</div>
|
||||||
|
<ActionDropdown variant="secondary">
|
||||||
|
<DropdownItem
|
||||||
|
variant="danger"
|
||||||
|
icon={IconTrashCan}
|
||||||
|
onClick={deleteVendor}
|
||||||
|
>
|
||||||
|
{__("Delete")}
|
||||||
|
</DropdownItem>
|
||||||
|
</ActionDropdown>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<TabLink
|
||||||
|
to={`/organizations/${organizationId}/vendors/${vendor.id}/overview`}
|
||||||
|
>
|
||||||
|
{__("Overview")}
|
||||||
|
</TabLink>
|
||||||
|
<TabLink
|
||||||
|
to={`/organizations/${organizationId}/vendors/${vendor.id}/certifications`}
|
||||||
|
>
|
||||||
|
{__("Certifications")}
|
||||||
|
</TabLink>
|
||||||
|
<TabLink
|
||||||
|
to={`/organizations/${organizationId}/vendors/${vendor.id}/compliance`}
|
||||||
|
>
|
||||||
|
{__("Compliance reports")}
|
||||||
|
</TabLink>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
<Outlet context={{ vendor }} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -14,68 +14,34 @@ import {
|
|||||||
ActionDropdown,
|
ActionDropdown,
|
||||||
DropdownItem,
|
DropdownItem,
|
||||||
IconTrashCan,
|
IconTrashCan,
|
||||||
useConfirm,
|
|
||||||
} from "@probo/ui";
|
} from "@probo/ui";
|
||||||
import { useTranslate } from "@probo/i18n";
|
import { useTranslate } from "@probo/i18n";
|
||||||
import { usePageTitle } from "@probo/hooks";
|
import { usePageTitle } from "@probo/hooks";
|
||||||
import { ConnectionHandler, graphql } from "relay-runtime";
|
import { usePreloadedQuery, type PreloadedQuery } from "react-relay";
|
||||||
import { useLazyLoadQuery } from "react-relay";
|
|
||||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||||
import type {
|
import { faviconUrl } from "@probo/helpers";
|
||||||
VendorsPageQuery,
|
|
||||||
VendorsPageQuery$data,
|
|
||||||
} from "./__generated__/VendorsPageQuery.graphql";
|
|
||||||
import { faviconUrl, sprintf } from "@probo/helpers";
|
|
||||||
import type { NodeOf } from "/types";
|
import type { NodeOf } from "/types";
|
||||||
import { CreateVendorDialog } from "./CreateVendorDialog";
|
import { CreateVendorDialog } from "./CreateVendorDialog";
|
||||||
import { useDeleteVendorMutation } from "/hooks/graph/VendorGraph";
|
import { useDeleteVendor, vendorsQuery } from "/hooks/graph/VendorGraph";
|
||||||
|
import type {
|
||||||
|
VendorGraphListQuery,
|
||||||
|
VendorGraphListQuery$data,
|
||||||
|
} from "/hooks/graph/__generated__/VendorGraphListQuery.graphql";
|
||||||
|
|
||||||
const vendorsQuery = graphql`
|
type Vendor = NodeOf<Required<VendorGraphListQuery$data["node"]>["vendors"]>;
|
||||||
query VendorsPageQuery($organizationId: ID!) {
|
|
||||||
node(id: $organizationId) {
|
|
||||||
... on Organization {
|
|
||||||
vendors(first: 25) @connection(key: "VendorsPage_vendors") {
|
|
||||||
__id
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
websiteUrl
|
|
||||||
updatedAt
|
|
||||||
riskAssessments(
|
|
||||||
first: 1
|
|
||||||
orderBy: { direction: DESC, field: ASSESSED_AT }
|
|
||||||
) {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
id
|
|
||||||
assessedAt
|
|
||||||
expiresAt
|
|
||||||
dataSensitivity
|
|
||||||
businessImpact
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
type Vendor = NodeOf<Required<VendorsPageQuery$data["node"]>["vendors"]>;
|
type Props = {
|
||||||
|
queryRef: PreloadedQuery<VendorGraphListQuery>;
|
||||||
|
};
|
||||||
|
|
||||||
export default function VendorsPage() {
|
export default function VendorsPage(props: Props) {
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
|
|
||||||
const data = useLazyLoadQuery<VendorsPageQuery>(vendorsQuery, {
|
const data = usePreloadedQuery(vendorsQuery, props.queryRef);
|
||||||
organizationId,
|
|
||||||
});
|
|
||||||
console.log(data);
|
|
||||||
|
|
||||||
const vendors = data.node?.vendors?.edges.map((edge) => edge.node);
|
const vendors = data.node?.vendors?.edges.map((edge) => edge.node);
|
||||||
|
const connectionId = data.node!.vendors!.__id;
|
||||||
|
|
||||||
usePageTitle(__("Vendors"));
|
usePageTitle(__("Vendors"));
|
||||||
|
|
||||||
@@ -90,8 +56,9 @@ export default function VendorsPage() {
|
|||||||
<CreateVendorDialog
|
<CreateVendorDialog
|
||||||
connection={data.node!.vendors!.__id}
|
connection={data.node!.vendors!.__id}
|
||||||
organizationId={organizationId}
|
organizationId={organizationId}
|
||||||
trigger={<Button icon={IconPlusLarge}>{__("Add vendor")}</Button>}
|
>
|
||||||
/>
|
<Button icon={IconPlusLarge}>{__("Add vendor")}</Button>
|
||||||
|
</CreateVendorDialog>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<Table>
|
<Table>
|
||||||
<Thead>
|
<Thead>
|
||||||
@@ -110,6 +77,7 @@ export default function VendorsPage() {
|
|||||||
key={vendor.id}
|
key={vendor.id}
|
||||||
vendor={vendor}
|
vendor={vendor}
|
||||||
organizationId={organizationId}
|
organizationId={organizationId}
|
||||||
|
connectionId={connectionId}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Tbody>
|
</Tbody>
|
||||||
@@ -121,9 +89,11 @@ export default function VendorsPage() {
|
|||||||
function VendorRow({
|
function VendorRow({
|
||||||
vendor,
|
vendor,
|
||||||
organizationId,
|
organizationId,
|
||||||
|
connectionId,
|
||||||
}: {
|
}: {
|
||||||
vendor: Vendor;
|
vendor: Vendor;
|
||||||
organizationId: string;
|
organizationId: string;
|
||||||
|
connectionId: string;
|
||||||
}) {
|
}) {
|
||||||
const { __, dateFormat } = useTranslate();
|
const { __, dateFormat } = useTranslate();
|
||||||
const latestAssessment = vendor.riskAssessments?.edges[0]?.node;
|
const latestAssessment = vendor.riskAssessments?.edges[0]?.node;
|
||||||
@@ -131,42 +101,11 @@ function VendorRow({
|
|||||||
? new Date(latestAssessment.expiresAt) < new Date()
|
? new Date(latestAssessment.expiresAt) < new Date()
|
||||||
: false;
|
: false;
|
||||||
|
|
||||||
const [deleteVendor] = useDeleteVendorMutation();
|
const deleteVendor = useDeleteVendor(vendor, connectionId);
|
||||||
const confirm = useConfirm();
|
|
||||||
|
|
||||||
const onDelete = () => {
|
|
||||||
confirm(
|
|
||||||
() =>
|
|
||||||
new Promise<void>((resolve) => {
|
|
||||||
deleteVendor({
|
|
||||||
variables: {
|
|
||||||
input: {
|
|
||||||
vendorId: vendor.id,
|
|
||||||
},
|
|
||||||
connections: [
|
|
||||||
ConnectionHandler.getConnectionID(
|
|
||||||
organizationId,
|
|
||||||
"VendorsPage_vendors"
|
|
||||||
),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
onCompleted: () => resolve(),
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
message: sprintf(
|
|
||||||
__(
|
|
||||||
'This will permanently delete the vendor "%s". This action cannot be undone.'
|
|
||||||
),
|
|
||||||
vendor.name
|
|
||||||
),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Tr to={`/organizations/${organizationId}/vendors/${vendor.id}`}>
|
<Tr to={`/organizations/${organizationId}/vendors/${vendor.id}/overview`}>
|
||||||
<Td>
|
<Td>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<Avatar name={vendor.name} src={faviconUrl(vendor.websiteUrl)} />
|
<Avatar name={vendor.name} src={faviconUrl(vendor.websiteUrl)} />
|
||||||
@@ -196,7 +135,7 @@ function VendorRow({
|
|||||||
<Td noLink width={50} className="text-end">
|
<Td noLink width={50} className="text-end">
|
||||||
<ActionDropdown>
|
<ActionDropdown>
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
onClick={onDelete}
|
onClick={deleteVendor}
|
||||||
variant="danger"
|
variant="danger"
|
||||||
icon={IconTrashCan}
|
icon={IconTrashCan}
|
||||||
>
|
>
|
||||||
|
|||||||
167
apps/console2/src/pages/organizations/vendors/tabs/VendorCertificationsTab.tsx
vendored
Normal file
167
apps/console2/src/pages/organizations/vendors/tabs/VendorCertificationsTab.tsx
vendored
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
import { useTranslate } from "@probo/i18n";
|
||||||
|
import {
|
||||||
|
Badge,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Combobox,
|
||||||
|
ComboboxItem,
|
||||||
|
IconCrossLargeX,
|
||||||
|
IconPlusLarge,
|
||||||
|
} from "@probo/ui";
|
||||||
|
import { Controller } from "react-hook-form";
|
||||||
|
import { useVendorForm } from "/hooks/forms/useVendorForm";
|
||||||
|
import type { useVendorFormFragment$key } from "/hooks/forms/__generated__/useVendorFormFragment.graphql";
|
||||||
|
import { useOutletContext } from "react-router";
|
||||||
|
import {
|
||||||
|
certificationCategoryLabel,
|
||||||
|
certifications,
|
||||||
|
objectEntries,
|
||||||
|
} from "@probo/helpers";
|
||||||
|
import { useRef, useState } from "react";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vendor certifications tab
|
||||||
|
*/
|
||||||
|
export default function VendorCertificationsTab() {
|
||||||
|
const { vendor } = useOutletContext<{
|
||||||
|
vendor: useVendorFormFragment$key & { name: string };
|
||||||
|
}>();
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
const { control, handleSubmit } = useVendorForm(vendor);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form className="space-y-4" onSubmit={handleSubmit}>
|
||||||
|
<Card padded>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="certifications"
|
||||||
|
render={({ field }) => (
|
||||||
|
<Certifications
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
value={field.value}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button type="submit">{__("Update vendor")}</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
type CertificationsProps = {
|
||||||
|
value: string[];
|
||||||
|
onValueChange: (value: string[]) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all certifications badges
|
||||||
|
*/
|
||||||
|
function Certifications(props: CertificationsProps) {
|
||||||
|
const categorizedCertifications = Object.values(certifications).flat();
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
const animateBadge = useRef(false);
|
||||||
|
const categories = objectEntries(certifications)
|
||||||
|
.map(
|
||||||
|
([key, value]) =>
|
||||||
|
[key, value.filter((c) => props.value.includes(c))] as const
|
||||||
|
)
|
||||||
|
.filter(([_, certifications]) => certifications.length > 0);
|
||||||
|
categories.push([
|
||||||
|
"custom",
|
||||||
|
props.value.filter((c) => !categorizedCertifications.includes(c)),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const addCertificate = (name: string) => {
|
||||||
|
animateBadge.current = true;
|
||||||
|
props.onValueChange([...props.value, name]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeCertificate = (name: string) => {
|
||||||
|
animateBadge.current = true;
|
||||||
|
props.onValueChange(props.value.filter((v) => v !== name));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
{categories.map(([key, certifications]) => (
|
||||||
|
<div key={key} className="space-y-2">
|
||||||
|
<div className="text-sm font-medium text-txt-secondary">
|
||||||
|
{certificationCategoryLabel(__, key)}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{certifications.map((certification) => (
|
||||||
|
<Badge asChild size="md" key={certification}>
|
||||||
|
<button
|
||||||
|
onClick={() => removeCertificate(certification)}
|
||||||
|
type="button"
|
||||||
|
className={clsx(
|
||||||
|
"hover:bg-subtle-hover cursor-pointer",
|
||||||
|
animateBadge.current &&
|
||||||
|
"starting:opacity-0 starting:w-0 w-max transition-all duration-500 starting:bg-accent"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{certification}
|
||||||
|
<div className="w-0 overflow-hidden group-hover:w-4 duration-200">
|
||||||
|
<IconCrossLargeX size={12} />
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<CertificationInput
|
||||||
|
certifications={categorizedCertifications.filter(
|
||||||
|
(c) => !props.value.includes(c)
|
||||||
|
)}
|
||||||
|
onAdd={addCertificate}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Input to add a new certification
|
||||||
|
*/
|
||||||
|
function CertificationInput({
|
||||||
|
certifications,
|
||||||
|
onAdd,
|
||||||
|
}: {
|
||||||
|
certifications: string[];
|
||||||
|
onAdd: (name: string) => void;
|
||||||
|
}) {
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
const isCustom = !certifications.includes(search.trim());
|
||||||
|
const filteredCertifications = certifications.filter((c) =>
|
||||||
|
c.toLowerCase().includes(search.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{search}
|
||||||
|
<Combobox
|
||||||
|
autoSelect
|
||||||
|
resetValueOnHide
|
||||||
|
onSelect={onAdd}
|
||||||
|
onSearch={setSearch}
|
||||||
|
placeholder={__("Add a new certification")}
|
||||||
|
>
|
||||||
|
{filteredCertifications.map((certification) => (
|
||||||
|
<ComboboxItem key={certification} value={certification}>
|
||||||
|
{certification}
|
||||||
|
</ComboboxItem>
|
||||||
|
))}
|
||||||
|
{isCustom && search.trim().length >= 2 && (
|
||||||
|
<ComboboxItem value={search.trim()}>
|
||||||
|
<IconPlusLarge size={20} />
|
||||||
|
{__("Add a custom certification")} : {search}
|
||||||
|
</ComboboxItem>
|
||||||
|
)}
|
||||||
|
</Combobox>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
51
apps/console2/src/pages/organizations/vendors/tabs/VendorComplianceTab.tsx
vendored
Normal file
51
apps/console2/src/pages/organizations/vendors/tabs/VendorComplianceTab.tsx
vendored
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { useOutletContext } from "react-router";
|
||||||
|
import { graphql } from "relay-runtime";
|
||||||
|
import type { VendorComplianceTabFragment$key } from "./__generated__/VendorComplianceTabFragment.graphql";
|
||||||
|
import { useTranslate } from "@probo/i18n";
|
||||||
|
import { usePageTitle } from "@probo/hooks";
|
||||||
|
|
||||||
|
const complianceReportsFragment = graphql`
|
||||||
|
fragment VendorComplianceTabFragment on Vendor
|
||||||
|
@refetchable(queryName: "ComplianceReportListQuery")
|
||||||
|
@argumentDefinitions(
|
||||||
|
first: { type: "Int", defaultValue: 50 }
|
||||||
|
order: { type: "VendorComplianceReportOrder", defaultValue: null }
|
||||||
|
after: { type: "CursorKey", defaultValue: null }
|
||||||
|
before: { type: "CursorKey", defaultValue: null }
|
||||||
|
last: { type: "Int", defaultValue: null }
|
||||||
|
) {
|
||||||
|
complianceReports(
|
||||||
|
first: $first
|
||||||
|
after: $after
|
||||||
|
last: $last
|
||||||
|
before: $before
|
||||||
|
orderBy: $order
|
||||||
|
) @connection(key: "VendorComplianceTabFragment_complianceReports") {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
...VendorComplianceTabFragment_report
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const complianceReportFragment = graphql`
|
||||||
|
fragment VendorComplianceTabFragment_report on VendorComplianceReport {
|
||||||
|
reportDate
|
||||||
|
validUntil
|
||||||
|
reportName
|
||||||
|
fileUrl
|
||||||
|
fileSize
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default function VendorComplianceTab() {
|
||||||
|
const { vendor } = useOutletContext<{
|
||||||
|
vendor: VendorComplianceTabFragment$key & { name: string };
|
||||||
|
}>();
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
|
||||||
|
usePageTitle(vendor.name + " - " + __("Compliance reports"));
|
||||||
|
return <div>Vendor Compliance</div>;
|
||||||
|
}
|
||||||
142
apps/console2/src/pages/organizations/vendors/tabs/VendorOverviewTab.tsx
vendored
Normal file
142
apps/console2/src/pages/organizations/vendors/tabs/VendorOverviewTab.tsx
vendored
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
import { useTranslate } from "@probo/i18n";
|
||||||
|
import { useVendorForm } from "/hooks/forms/useVendorForm";
|
||||||
|
import type { useVendorFormFragment$key } from "/hooks/forms/__generated__/useVendorFormFragment.graphql";
|
||||||
|
import { useOutletContext } from "react-router";
|
||||||
|
import { Button, Card, Field, Input } from "@probo/ui";
|
||||||
|
import { PeopleSelectField } from "/components/form/PeopleSelectField";
|
||||||
|
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import { usePageTitle } from "@probo/hooks";
|
||||||
|
|
||||||
|
export default function VendorOverviewTab() {
|
||||||
|
const { vendor } = useOutletContext<{
|
||||||
|
vendor: useVendorFormFragment$key & { name: string };
|
||||||
|
}>();
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
const organizationId = useOrganizationId();
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { errors, isDirty, isSubmitting },
|
||||||
|
} = useVendorForm(vendor);
|
||||||
|
|
||||||
|
const urls = useMemo(
|
||||||
|
() =>
|
||||||
|
[
|
||||||
|
{ name: "statusPageUrl", label: __("Status page URL") },
|
||||||
|
{ name: "termsOfServiceUrl", label: __("Terms of service URL") },
|
||||||
|
{ name: "privacyPolicyUrl", label: __("Privacy policy URL") },
|
||||||
|
{
|
||||||
|
name: "serviceLevelAgreementUrl",
|
||||||
|
label: __("Service level agreement URL"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dataProcessingAgreementUrl",
|
||||||
|
label: __("Data processing agreement URL"),
|
||||||
|
},
|
||||||
|
{ name: "securityPageUrl", label: __("Security page URL") },
|
||||||
|
{ name: "trustPageUrl", label: __("Trust page URL") },
|
||||||
|
] as const,
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
usePageTitle(vendor.name + " - " + __("Overview"));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-12">
|
||||||
|
{/* Vendor Details */}
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h2 className="text-base font-medium">{__("Vendor details")}</h2>
|
||||||
|
<Card className="space-y-4" padded>
|
||||||
|
<Field
|
||||||
|
{...register("name")}
|
||||||
|
label={__("Name")}
|
||||||
|
type="text"
|
||||||
|
error={errors.name?.message}
|
||||||
|
/>
|
||||||
|
<Field
|
||||||
|
{...register("description")}
|
||||||
|
label={__("Description")}
|
||||||
|
type="textarea"
|
||||||
|
error={errors.description?.message}
|
||||||
|
/>
|
||||||
|
<Field
|
||||||
|
{...register("legalName")}
|
||||||
|
label={__("Legal name")}
|
||||||
|
type="text"
|
||||||
|
error={errors.legalName?.message}
|
||||||
|
/>
|
||||||
|
<Field
|
||||||
|
{...register("headquarterAddress")}
|
||||||
|
label={__("Headquarter address")}
|
||||||
|
type="textarea"
|
||||||
|
error={errors.headquarterAddress?.message}
|
||||||
|
/>
|
||||||
|
<Field
|
||||||
|
{...register("websiteUrl")}
|
||||||
|
label={__("Website URL")}
|
||||||
|
type="text"
|
||||||
|
error={errors.websiteUrl?.message}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Ownership */}
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h2 className="text-base font-medium">{__("Ownership details`")}</h2>
|
||||||
|
<Card className="space-y-4" padded>
|
||||||
|
<PeopleSelectField
|
||||||
|
organizationId={organizationId}
|
||||||
|
control={control}
|
||||||
|
name="businessOwnerId"
|
||||||
|
label={__("Business owner")}
|
||||||
|
error={errors.businessOwnerId?.message}
|
||||||
|
/>
|
||||||
|
<PeopleSelectField
|
||||||
|
organizationId={organizationId}
|
||||||
|
control={control}
|
||||||
|
name="securityOwnerId"
|
||||||
|
label={__("Security owner")}
|
||||||
|
error={errors.securityOwnerId?.message}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Links */}
|
||||||
|
<div className="space-y-4 mb-4">
|
||||||
|
<h2 className="text-base font-medium">{__("Links")}</h2>
|
||||||
|
<Card className="divide-y divide-border-low">
|
||||||
|
{urls.map((url) => (
|
||||||
|
<div className="grid grid-cols-2 items-center divide-x divide-border-low">
|
||||||
|
<label
|
||||||
|
className="p-4 text-sm font-medium text-txt-secondary"
|
||||||
|
htmlFor={url.name}
|
||||||
|
>
|
||||||
|
{url.label}
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
className="p-4 focus:bg-tertiary-pressed outline-none"
|
||||||
|
id={url.name}
|
||||||
|
key={url.name}
|
||||||
|
{...register(url.name)}
|
||||||
|
type="text"
|
||||||
|
placeholder="https://..."
|
||||||
|
variant="ghost"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Submit */}
|
||||||
|
<div className="flex justify-end">
|
||||||
|
{isDirty && (
|
||||||
|
<Button type="submit" disabled={isSubmitting}>
|
||||||
|
{__("Update vendor")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
339
apps/console2/src/pages/organizations/vendors/tabs/__generated__/ComplianceReportListQuery.graphql.ts
generated
vendored
Normal file
339
apps/console2/src/pages/organizations/vendors/tabs/__generated__/ComplianceReportListQuery.graphql.ts
generated
vendored
Normal file
@@ -0,0 +1,339 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<75defbef64a4ee18b514465a44c8eea1>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
|
import { FragmentRefs } from "relay-runtime";
|
||||||
|
export type OrderDirection = "ASC" | "DESC";
|
||||||
|
export type VendorComplianceReportOrderField = "CREATED_AT" | "REPORT_DATE";
|
||||||
|
export type VendorComplianceReportOrder = {
|
||||||
|
direction: OrderDirection;
|
||||||
|
field: VendorComplianceReportOrderField;
|
||||||
|
};
|
||||||
|
export type ComplianceReportListQuery$variables = {
|
||||||
|
after?: any | null | undefined;
|
||||||
|
before?: any | null | undefined;
|
||||||
|
first?: number | null | undefined;
|
||||||
|
id: string;
|
||||||
|
last?: number | null | undefined;
|
||||||
|
order?: VendorComplianceReportOrder | null | undefined;
|
||||||
|
};
|
||||||
|
export type ComplianceReportListQuery$data = {
|
||||||
|
readonly node: {
|
||||||
|
readonly " $fragmentSpreads": FragmentRefs<"VendorComplianceTabFragment">;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export type ComplianceReportListQuery = {
|
||||||
|
response: ComplianceReportListQuery$data;
|
||||||
|
variables: ComplianceReportListQuery$variables;
|
||||||
|
};
|
||||||
|
|
||||||
|
const node: ConcreteRequest = (function(){
|
||||||
|
var v0 = {
|
||||||
|
"defaultValue": null,
|
||||||
|
"kind": "LocalArgument",
|
||||||
|
"name": "after"
|
||||||
|
},
|
||||||
|
v1 = {
|
||||||
|
"defaultValue": null,
|
||||||
|
"kind": "LocalArgument",
|
||||||
|
"name": "before"
|
||||||
|
},
|
||||||
|
v2 = {
|
||||||
|
"defaultValue": 50,
|
||||||
|
"kind": "LocalArgument",
|
||||||
|
"name": "first"
|
||||||
|
},
|
||||||
|
v3 = {
|
||||||
|
"defaultValue": null,
|
||||||
|
"kind": "LocalArgument",
|
||||||
|
"name": "id"
|
||||||
|
},
|
||||||
|
v4 = {
|
||||||
|
"defaultValue": null,
|
||||||
|
"kind": "LocalArgument",
|
||||||
|
"name": "last"
|
||||||
|
},
|
||||||
|
v5 = {
|
||||||
|
"defaultValue": null,
|
||||||
|
"kind": "LocalArgument",
|
||||||
|
"name": "order"
|
||||||
|
},
|
||||||
|
v6 = [
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "id",
|
||||||
|
"variableName": "id"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
v7 = {
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "after",
|
||||||
|
"variableName": "after"
|
||||||
|
},
|
||||||
|
v8 = {
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "before",
|
||||||
|
"variableName": "before"
|
||||||
|
},
|
||||||
|
v9 = {
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "first",
|
||||||
|
"variableName": "first"
|
||||||
|
},
|
||||||
|
v10 = {
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "last",
|
||||||
|
"variableName": "last"
|
||||||
|
},
|
||||||
|
v11 = {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "__typename",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
v12 = {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "id",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
v13 = [
|
||||||
|
(v7/*: any*/),
|
||||||
|
(v8/*: any*/),
|
||||||
|
(v9/*: any*/),
|
||||||
|
(v10/*: any*/),
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "orderBy",
|
||||||
|
"variableName": "order"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
return {
|
||||||
|
"fragment": {
|
||||||
|
"argumentDefinitions": [
|
||||||
|
(v0/*: any*/),
|
||||||
|
(v1/*: any*/),
|
||||||
|
(v2/*: any*/),
|
||||||
|
(v3/*: any*/),
|
||||||
|
(v4/*: any*/),
|
||||||
|
(v5/*: any*/)
|
||||||
|
],
|
||||||
|
"kind": "Fragment",
|
||||||
|
"metadata": null,
|
||||||
|
"name": "ComplianceReportListQuery",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": (v6/*: any*/),
|
||||||
|
"concreteType": null,
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"args": [
|
||||||
|
(v7/*: any*/),
|
||||||
|
(v8/*: any*/),
|
||||||
|
(v9/*: any*/),
|
||||||
|
(v10/*: any*/),
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "order",
|
||||||
|
"variableName": "order"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"kind": "FragmentSpread",
|
||||||
|
"name": "VendorComplianceTabFragment"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Query",
|
||||||
|
"abstractKey": null
|
||||||
|
},
|
||||||
|
"kind": "Request",
|
||||||
|
"operation": {
|
||||||
|
"argumentDefinitions": [
|
||||||
|
(v0/*: any*/),
|
||||||
|
(v1/*: any*/),
|
||||||
|
(v2/*: any*/),
|
||||||
|
(v4/*: any*/),
|
||||||
|
(v5/*: any*/),
|
||||||
|
(v3/*: any*/)
|
||||||
|
],
|
||||||
|
"kind": "Operation",
|
||||||
|
"name": "ComplianceReportListQuery",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": (v6/*: any*/),
|
||||||
|
"concreteType": null,
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v11/*: any*/),
|
||||||
|
(v12/*: any*/),
|
||||||
|
{
|
||||||
|
"kind": "InlineFragment",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": (v13/*: any*/),
|
||||||
|
"concreteType": "VendorComplianceReportConnection",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "complianceReports",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "VendorComplianceReportEdge",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "edges",
|
||||||
|
"plural": true,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "VendorComplianceReport",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "reportDate",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "validUntil",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "reportName",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "fileUrl",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "fileSize",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
(v12/*: any*/),
|
||||||
|
(v11/*: 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
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": (v13/*: any*/),
|
||||||
|
"filters": [
|
||||||
|
"orderBy"
|
||||||
|
],
|
||||||
|
"handle": "connection",
|
||||||
|
"key": "VendorComplianceTabFragment_complianceReports",
|
||||||
|
"kind": "LinkedHandle",
|
||||||
|
"name": "complianceReports"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Vendor",
|
||||||
|
"abstractKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"cacheID": "76e0b5dbc01424695b8c642e27c0f4ed",
|
||||||
|
"id": null,
|
||||||
|
"metadata": {},
|
||||||
|
"name": "ComplianceReportListQuery",
|
||||||
|
"operationKind": "query",
|
||||||
|
"text": "query ComplianceReportListQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 50\n $last: Int = null\n $order: VendorComplianceReportOrder = null\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...VendorComplianceTabFragment_16fISc\n id\n }\n}\n\nfragment VendorComplianceTabFragment_16fISc on Vendor {\n complianceReports(first: $first, after: $after, last: $last, before: $before, orderBy: $order) {\n edges {\n node {\n ...VendorComplianceTabFragment_report\n id\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment VendorComplianceTabFragment_report on VendorComplianceReport {\n reportDate\n validUntil\n reportName\n fileUrl\n fileSize\n}\n"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
(node as any).hash = "7aae49a4b414ed4db5e7b39f8969c02f";
|
||||||
|
|
||||||
|
export default node;
|
||||||
209
apps/console2/src/pages/organizations/vendors/tabs/__generated__/VendorComplianceTabFragment.graphql.ts
generated
vendored
Normal file
209
apps/console2/src/pages/organizations/vendors/tabs/__generated__/VendorComplianceTabFragment.graphql.ts
generated
vendored
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<079a3066496976bd1238ba194f5a0660>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ReaderFragment } from 'relay-runtime';
|
||||||
|
import { FragmentRefs } from "relay-runtime";
|
||||||
|
export type VendorComplianceTabFragment$data = {
|
||||||
|
readonly complianceReports: {
|
||||||
|
readonly edges: ReadonlyArray<{
|
||||||
|
readonly node: {
|
||||||
|
readonly " $fragmentSpreads": FragmentRefs<"VendorComplianceTabFragment_report">;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
readonly id: string;
|
||||||
|
readonly " $fragmentType": "VendorComplianceTabFragment";
|
||||||
|
};
|
||||||
|
export type VendorComplianceTabFragment$key = {
|
||||||
|
readonly " $data"?: VendorComplianceTabFragment$data;
|
||||||
|
readonly " $fragmentSpreads": FragmentRefs<"VendorComplianceTabFragment">;
|
||||||
|
};
|
||||||
|
|
||||||
|
import ComplianceReportListQuery_graphql from './ComplianceReportListQuery.graphql';
|
||||||
|
|
||||||
|
const node: ReaderFragment = (function(){
|
||||||
|
var v0 = [
|
||||||
|
"complianceReports"
|
||||||
|
];
|
||||||
|
return {
|
||||||
|
"argumentDefinitions": [
|
||||||
|
{
|
||||||
|
"defaultValue": null,
|
||||||
|
"kind": "LocalArgument",
|
||||||
|
"name": "after"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultValue": null,
|
||||||
|
"kind": "LocalArgument",
|
||||||
|
"name": "before"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultValue": 50,
|
||||||
|
"kind": "LocalArgument",
|
||||||
|
"name": "first"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultValue": null,
|
||||||
|
"kind": "LocalArgument",
|
||||||
|
"name": "last"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultValue": null,
|
||||||
|
"kind": "LocalArgument",
|
||||||
|
"name": "order"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"kind": "Fragment",
|
||||||
|
"metadata": {
|
||||||
|
"connection": [
|
||||||
|
{
|
||||||
|
"count": null,
|
||||||
|
"cursor": null,
|
||||||
|
"direction": "bidirectional",
|
||||||
|
"path": (v0/*: any*/)
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"refetch": {
|
||||||
|
"connection": {
|
||||||
|
"forward": {
|
||||||
|
"count": "first",
|
||||||
|
"cursor": "after"
|
||||||
|
},
|
||||||
|
"backward": {
|
||||||
|
"count": "last",
|
||||||
|
"cursor": "before"
|
||||||
|
},
|
||||||
|
"path": (v0/*: any*/)
|
||||||
|
},
|
||||||
|
"fragmentPathInResult": [
|
||||||
|
"node"
|
||||||
|
],
|
||||||
|
"operation": ComplianceReportListQuery_graphql,
|
||||||
|
"identifierInfo": {
|
||||||
|
"identifierField": "id",
|
||||||
|
"identifierQueryVariableName": "id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "VendorComplianceTabFragment",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": "complianceReports",
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "orderBy",
|
||||||
|
"variableName": "order"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"concreteType": "VendorComplianceReportConnection",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "__VendorComplianceTabFragment_complianceReports_connection",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "VendorComplianceReportEdge",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "edges",
|
||||||
|
"plural": true,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "VendorComplianceReport",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"args": null,
|
||||||
|
"kind": "FragmentSpread",
|
||||||
|
"name": "VendorComplianceTabFragment_report"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "id",
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Vendor",
|
||||||
|
"abstractKey": null
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
(node as any).hash = "7aae49a4b414ed4db5e7b39f8969c02f";
|
||||||
|
|
||||||
|
export default node;
|
||||||
74
apps/console2/src/pages/organizations/vendors/tabs/__generated__/VendorComplianceTabFragment_report.graphql.ts
generated
vendored
Normal file
74
apps/console2/src/pages/organizations/vendors/tabs/__generated__/VendorComplianceTabFragment_report.graphql.ts
generated
vendored
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<01d2d791b86ae99330d4ba2ecb01f1ed>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ReaderFragment } from 'relay-runtime';
|
||||||
|
import { FragmentRefs } from "relay-runtime";
|
||||||
|
export type VendorComplianceTabFragment_report$data = {
|
||||||
|
readonly fileSize: number;
|
||||||
|
readonly fileUrl: string;
|
||||||
|
readonly reportDate: any;
|
||||||
|
readonly reportName: string;
|
||||||
|
readonly validUntil: any | null | undefined;
|
||||||
|
readonly " $fragmentType": "VendorComplianceTabFragment_report";
|
||||||
|
};
|
||||||
|
export type VendorComplianceTabFragment_report$key = {
|
||||||
|
readonly " $data"?: VendorComplianceTabFragment_report$data;
|
||||||
|
readonly " $fragmentSpreads": FragmentRefs<"VendorComplianceTabFragment_report">;
|
||||||
|
};
|
||||||
|
|
||||||
|
const node: ReaderFragment = {
|
||||||
|
"argumentDefinitions": [],
|
||||||
|
"kind": "Fragment",
|
||||||
|
"metadata": null,
|
||||||
|
"name": "VendorComplianceTabFragment_report",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "reportDate",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "validUntil",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "reportName",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "fileUrl",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "fileSize",
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "VendorComplianceReport",
|
||||||
|
"abstractKey": null
|
||||||
|
};
|
||||||
|
|
||||||
|
(node as any).hash = "d39b70dfd0a316b158d7dc7253d0ff5e";
|
||||||
|
|
||||||
|
export default node;
|
||||||
@@ -18,6 +18,7 @@ import { useCleanup } from "./hooks/useDelayedEffect.ts";
|
|||||||
import { riskRoutes } from "./routes/riskRoutes.ts";
|
import { riskRoutes } from "./routes/riskRoutes.ts";
|
||||||
import { measureRoutes } from "./routes/measureRoutes.ts";
|
import { measureRoutes } from "./routes/measureRoutes.ts";
|
||||||
import { policiesRoutes } from "./routes/policiesRoutes.ts";
|
import { policiesRoutes } from "./routes/policiesRoutes.ts";
|
||||||
|
import { vendorRoutes } from "./routes/vendorRoutes.ts";
|
||||||
import { organizationViewQuery } from "./hooks/graph/OrganizationGraph.ts";
|
import { organizationViewQuery } from "./hooks/graph/OrganizationGraph.ts";
|
||||||
import { peopleRoutes } from "./routes/peopleRoutes.ts";
|
import { peopleRoutes } from "./routes/peopleRoutes.ts";
|
||||||
|
|
||||||
@@ -79,17 +80,11 @@ const routes = [
|
|||||||
}),
|
}),
|
||||||
Component: lazy(() => import("./pages/organizations/SettingsPage")),
|
Component: lazy(() => import("./pages/organizations/SettingsPage")),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "vendors",
|
|
||||||
fallback: PageSkeleton,
|
|
||||||
Component: lazy(
|
|
||||||
() => import("./pages/organizations/vendors/VendorsPage")
|
|
||||||
),
|
|
||||||
},
|
|
||||||
...riskRoutes,
|
...riskRoutes,
|
||||||
...measureRoutes,
|
...measureRoutes,
|
||||||
...policiesRoutes,
|
...policiesRoutes,
|
||||||
...peopleRoutes,
|
...peopleRoutes,
|
||||||
|
...vendorRoutes,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
] satisfies AppRoute[];
|
] satisfies AppRoute[];
|
||||||
|
|||||||
51
apps/console2/src/routes/vendorRoutes.ts
Normal file
51
apps/console2/src/routes/vendorRoutes.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { lazy } from "react";
|
||||||
|
import { loadQuery } from "react-relay";
|
||||||
|
import type { AppRoute } from "/routes.tsx";
|
||||||
|
import { relayEnvironment } from "/providers/RelayProviders";
|
||||||
|
import { PageSkeleton } from "/components/skeletons/PageSkeleton";
|
||||||
|
import { vendorNodeQuery, vendorsQuery } from "/hooks/graph/VendorGraph";
|
||||||
|
|
||||||
|
export const vendorRoutes = [
|
||||||
|
{
|
||||||
|
path: "vendors",
|
||||||
|
fallback: PageSkeleton,
|
||||||
|
queryLoader: ({ organizationId }) =>
|
||||||
|
loadQuery(relayEnvironment, vendorsQuery, { organizationId }),
|
||||||
|
Component: lazy(() => import("/pages/organizations/vendors/VendorsPage")),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "vendors/:vendorId",
|
||||||
|
fallback: PageSkeleton,
|
||||||
|
queryLoader: ({ vendorId }) =>
|
||||||
|
loadQuery(relayEnvironment, vendorNodeQuery, {
|
||||||
|
vendorId,
|
||||||
|
}),
|
||||||
|
Component: lazy(
|
||||||
|
() => import("../pages/organizations/vendors/VendorDetailPage")
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "overview",
|
||||||
|
Component: lazy(
|
||||||
|
() => import("../pages/organizations/vendors/tabs/VendorOverviewTab")
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "certifications",
|
||||||
|
Component: lazy(
|
||||||
|
() =>
|
||||||
|
import(
|
||||||
|
"../pages/organizations/vendors/tabs/VendorCertificationsTab"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "compliance",
|
||||||
|
Component: lazy(
|
||||||
|
() =>
|
||||||
|
import("../pages/organizations/vendors/tabs/VendorComplianceTab")
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
] satisfies AppRoute[];
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
"target": "ES2020",
|
"target": "ES2020",
|
||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
|||||||
34
packages/helpers/src/certifications.ts
Normal file
34
packages/helpers/src/certifications.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
type Translator = (s: string) => string;
|
||||||
|
|
||||||
|
export const certifications = {
|
||||||
|
securityStandards: [
|
||||||
|
"SOC 2",
|
||||||
|
"ISO 27001",
|
||||||
|
"HITRUST",
|
||||||
|
"NIST",
|
||||||
|
"SOC 2 Type 2",
|
||||||
|
"SOC 2 Type 1",
|
||||||
|
],
|
||||||
|
regulatoryLegal: ["HIPAA", "FERPA", "FISMA", "PIPEDA", "GDPR", "CCPA"],
|
||||||
|
industrySpecific: ["FinTech", "MPAA", "GSMA"],
|
||||||
|
internationalGov: ["FedRAMP", "ENS High", "IRAP", "CJIS"],
|
||||||
|
custom: [] as string[],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const certificationCategoryLabel = (
|
||||||
|
__: Translator,
|
||||||
|
category: keyof typeof certifications,
|
||||||
|
) => {
|
||||||
|
switch (category) {
|
||||||
|
case "securityStandards":
|
||||||
|
return __("Security Standards");
|
||||||
|
case "regulatoryLegal":
|
||||||
|
return __("Regulatory & Legal");
|
||||||
|
case "industrySpecific":
|
||||||
|
return __("Industry-Specific");
|
||||||
|
case "internationalGov":
|
||||||
|
return __("International & Government");
|
||||||
|
default:
|
||||||
|
return __("Custom certifications");
|
||||||
|
}
|
||||||
|
};
|
||||||
7
packages/helpers/src/dom.ts
Normal file
7
packages/helpers/src/dom.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export function withViewTransition(fn: () => void) {
|
||||||
|
if (!document.startViewTransition) {
|
||||||
|
fn();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
document.startViewTransition(fn);
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
export { objectKeys } from "./object";
|
export { objectKeys, objectEntries } from "./object";
|
||||||
export { sprintf, faviconUrl } from "./string";
|
export { sprintf, faviconUrl } from "./string";
|
||||||
export {
|
export {
|
||||||
getTreatment,
|
getTreatment,
|
||||||
@@ -6,7 +6,9 @@ export {
|
|||||||
getRiskLikelihoods,
|
getRiskLikelihoods,
|
||||||
getSeverity,
|
getSeverity,
|
||||||
} from "./risk";
|
} from "./risk";
|
||||||
|
export { withViewTransition } from "./dom";
|
||||||
export { times, groupBy, isEmpty } from "./array";
|
export { times, groupBy, isEmpty } from "./array";
|
||||||
export { randomInt } from "./number";
|
export { randomInt } from "./number";
|
||||||
export { getMeasureStateLabel, measureStates } from "./measure";
|
export { getMeasureStateLabel, measureStates } from "./measure";
|
||||||
export { getRole, getRoles, peopleRoles } from "./people";
|
export { getRole, getRoles, peopleRoles } from "./people";
|
||||||
|
export { certificationCategoryLabel, certifications } from "./certifications";
|
||||||
|
|||||||
@@ -4,3 +4,7 @@
|
|||||||
export function objectKeys<T extends Record<string, unknown>>(object: T) {
|
export function objectKeys<T extends Record<string, unknown>>(object: T) {
|
||||||
return Object.keys(object) as (keyof T)[];
|
return Object.keys(object) as (keyof T)[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function objectEntries<T extends Record<string, unknown>>(object: T) {
|
||||||
|
return Object.entries(object) as [keyof T, T[keyof T]][];
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
"@radix-ui/react-tabs": "^1.1.12",
|
"@radix-ui/react-tabs": "^1.1.12",
|
||||||
"@tailwindcss/vite": "^4.1.7",
|
"@tailwindcss/vite": "^4.1.7",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"react-dropzone": "^14.3.8",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
"rehype-raw": "^7.0.0",
|
"rehype-raw": "^7.0.0",
|
||||||
"remark-gfm": "^4.0.1",
|
"remark-gfm": "^4.0.1",
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import type { HTMLAttributes } from "react";
|
import type { HTMLAttributes } from "react";
|
||||||
import { tv } from "tailwind-variants";
|
import { tv } from "tailwind-variants";
|
||||||
|
import { Slot } from "../Slot";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
asChild?: boolean;
|
||||||
variant?:
|
variant?:
|
||||||
| "success"
|
| "success"
|
||||||
| "warning"
|
| "warning"
|
||||||
@@ -11,10 +13,10 @@ type Props = {
|
|||||||
| "outline"
|
| "outline"
|
||||||
| "highlight";
|
| "highlight";
|
||||||
size?: "sm" | "md";
|
size?: "sm" | "md";
|
||||||
} & HTMLAttributes<HTMLDivElement>;
|
} & HTMLAttributes<HTMLElement>;
|
||||||
|
|
||||||
const badge = tv({
|
const badge = tv({
|
||||||
base: "font-medium rounded-lg w-max flex gap-1 items-center",
|
base: "font-medium rounded-lg w-max flex gap-1 items-center group whitespace-nowrap",
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
success: "bg-success text-txt-success",
|
success: "bg-success text-txt-success",
|
||||||
@@ -37,5 +39,7 @@ const badge = tv({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export function Badge(props: Props) {
|
export function Badge(props: Props) {
|
||||||
return <div {...props} className={badge(props)} />;
|
const Component = props.asChild ? Slot : "div";
|
||||||
|
const { asChild, size, variant, ...restProps } = props;
|
||||||
|
return <Component {...restProps} className={badge(props)} />;
|
||||||
}
|
}
|
||||||
|
|||||||
12
packages/ui/src/Atoms/Dropzone/Dropzone.stories.tsx
Normal file
12
packages/ui/src/Atoms/Dropzone/Dropzone.stories.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Dropzone } from "./Dropzone";
|
||||||
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "Atoms/Dropzone",
|
||||||
|
component: Dropzone,
|
||||||
|
argTypes: {},
|
||||||
|
} satisfies Meta<typeof Dropzone>;
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof Dropzone>;
|
||||||
|
|
||||||
|
export const Default: Story = {};
|
||||||
24
packages/ui/src/Atoms/Dropzone/Dropzone.tsx
Normal file
24
packages/ui/src/Atoms/Dropzone/Dropzone.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { useCallback } from "react";
|
||||||
|
import { useDropzone } from "react-dropzone";
|
||||||
|
|
||||||
|
type Props = {};
|
||||||
|
|
||||||
|
export function Dropzone({}: Props) {
|
||||||
|
const onDrop = useCallback((acceptedFiles) => {
|
||||||
|
// Do something with the files
|
||||||
|
}, []);
|
||||||
|
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
||||||
|
onDrop,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div {...getRootProps()}>
|
||||||
|
<input {...getInputProps()} />
|
||||||
|
{isDragActive ? (
|
||||||
|
<p>Drop the files here ...</p>
|
||||||
|
) : (
|
||||||
|
<p>Drag 'n' drop some files here, or click to select files</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -83,7 +83,10 @@ export function Select({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Root onValueChange={onValueChange} value={value}>
|
<Root onValueChange={onValueChange} value={value}>
|
||||||
<Trigger {...props} className={trigger()}>
|
<Trigger
|
||||||
|
{...props}
|
||||||
|
className={trigger({ className: props.className })}
|
||||||
|
>
|
||||||
<Value placeholder={placeholder} />
|
<Value placeholder={placeholder} />
|
||||||
<Icon className={icon()}>
|
<Icon className={icon()}>
|
||||||
<IconChevronGrabberVertical size={16} />
|
<IconChevronGrabberVertical size={16} />
|
||||||
|
|||||||
@@ -18,13 +18,34 @@ type Props = {
|
|||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
onSearch: (query: string) => void;
|
onSearch: (query: string) => void;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
autoSelect?: boolean;
|
||||||
|
onSelect?: (value: string) => void;
|
||||||
|
resetValueOnHide?: boolean;
|
||||||
|
value?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Combobox({ children, onSearch, placeholder }: Props) {
|
export function Combobox({
|
||||||
|
children,
|
||||||
|
onSearch,
|
||||||
|
placeholder,
|
||||||
|
autoSelect,
|
||||||
|
onSelect,
|
||||||
|
resetValueOnHide,
|
||||||
|
value,
|
||||||
|
}: Props) {
|
||||||
const showDropdown = !isEmpty(children);
|
const showDropdown = !isEmpty(children);
|
||||||
return (
|
return (
|
||||||
<ComboboxProvider setValue={onSearch}>
|
<ComboboxProvider
|
||||||
<AriaKitCombobox placeholder={placeholder} className={input()} />
|
value={value}
|
||||||
|
setValue={onSearch}
|
||||||
|
setSelectedValue={(v) => onSelect?.(v as string)}
|
||||||
|
resetValueOnHide={resetValueOnHide}
|
||||||
|
>
|
||||||
|
<AriaKitCombobox
|
||||||
|
autoSelect={autoSelect}
|
||||||
|
placeholder={placeholder}
|
||||||
|
className={input()}
|
||||||
|
/>
|
||||||
{showDropdown && (
|
{showDropdown && (
|
||||||
<ComboboxPopover gutter={4} sameWidth className={dropdown()}>
|
<ComboboxPopover gutter={4} sameWidth className={dropdown()}>
|
||||||
{children}
|
{children}
|
||||||
@@ -39,7 +60,7 @@ export function ComboboxItem({
|
|||||||
...props
|
...props
|
||||||
}: PropsWithChildren<ComponentProps<typeof AriaKitComboboxItem>>) {
|
}: PropsWithChildren<ComponentProps<typeof AriaKitComboboxItem>>) {
|
||||||
return (
|
return (
|
||||||
<AriaKitComboboxItem className={dropdownItem()} {...props}>
|
<AriaKitComboboxItem hideOnClick className={dropdownItem()} {...props}>
|
||||||
{children}
|
{children}
|
||||||
</AriaKitComboboxItem>
|
</AriaKitComboboxItem>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { tv } from "tailwind-variants";
|
|||||||
import { Label } from "../../Atoms/Label/Label";
|
import { Label } from "../../Atoms/Label/Label";
|
||||||
import { Input } from "../../Atoms/Input/Input";
|
import { Input } from "../../Atoms/Input/Input";
|
||||||
import { Textarea } from "../../Atoms/Textarea/Textarea";
|
import { Textarea } from "../../Atoms/Textarea/Textarea";
|
||||||
import { type ComponentProps } from "react";
|
import { type ComponentProps, type ReactNode } from "react";
|
||||||
import { Select } from "../../Atoms/Select/Select";
|
import { Select } from "../../Atoms/Select/Select";
|
||||||
|
|
||||||
type BaseProps<T extends string, P> = {
|
type BaseProps<T extends string, P> = {
|
||||||
@@ -10,7 +10,8 @@ type BaseProps<T extends string, P> = {
|
|||||||
help?: string;
|
help?: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
onValueChange?: (s: string) => void;
|
onValueChange?: (s: string) => void;
|
||||||
type: T;
|
type?: T;
|
||||||
|
children?: ReactNode;
|
||||||
} & P;
|
} & P;
|
||||||
|
|
||||||
type Props =
|
type Props =
|
||||||
@@ -33,6 +34,7 @@ const { base: baseClass, label: labelClass, help: helpClass } = field();
|
|||||||
|
|
||||||
export function Field(props: Props) {
|
export function Field(props: Props) {
|
||||||
const showHelp = props.help && !props.error;
|
const showHelp = props.help && !props.error;
|
||||||
|
const childrenAsInput = !props.type && props.children;
|
||||||
return (
|
return (
|
||||||
<div className={baseClass()}>
|
<div className={baseClass()}>
|
||||||
{props.label && (
|
{props.label && (
|
||||||
@@ -40,7 +42,7 @@ export function Field(props: Props) {
|
|||||||
{props.label}
|
{props.label}
|
||||||
</Label>
|
</Label>
|
||||||
)}
|
)}
|
||||||
{getInput(props)}
|
{childrenAsInput ? props.children : getInput(props)}
|
||||||
{showHelp && <span className={helpClass()}>{props.help}</span>}
|
{showHelp && <span className={helpClass()}>{props.help}</span>}
|
||||||
{props.error && (
|
{props.error && (
|
||||||
<span className="text-txt-danger text-sm mt-1">
|
<span className="text-txt-danger text-sm mt-1">
|
||||||
|
|||||||
@@ -249,6 +249,11 @@ button:focus-visible {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Allow interpolation : https://developer.chrome.com/docs/css-ui/animate-to-height-auto */
|
||||||
|
:root {
|
||||||
|
interpolate-size: allow-keywords;
|
||||||
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 7px;
|
width: 7px;
|
||||||
height: 7px;
|
height: 7px;
|
||||||
|
|||||||
Reference in New Issue
Block a user