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 { useTranslate } from "@probo/i18n";
|
||||
import { type Control, Controller } from "react-hook-form";
|
||||
import { usePeople } from "/hooks/graph/PeopleGraph.ts";
|
||||
|
||||
export function PeopleSelect({
|
||||
organization,
|
||||
name,
|
||||
control,
|
||||
}: {
|
||||
organization: string;
|
||||
name: string;
|
||||
type Props = {
|
||||
organizationId: string;
|
||||
control: Control<any>;
|
||||
}) {
|
||||
name: string;
|
||||
label?: string;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
export function PeopleSelectField({
|
||||
organizationId,
|
||||
control,
|
||||
...props
|
||||
}: Props) {
|
||||
return (
|
||||
<Suspense
|
||||
fallback={<Select variant="editor" disabled placeholder="Loading..." />}
|
||||
>
|
||||
<PeopleSelectWithQuery
|
||||
organization={organization}
|
||||
name={name}
|
||||
control={control}
|
||||
/>
|
||||
</Suspense>
|
||||
<Field {...props}>
|
||||
<Suspense
|
||||
fallback={<Select variant="editor" disabled placeholder="Loading..." />}
|
||||
>
|
||||
<PeopleSelectWithQuery
|
||||
organizationId={organizationId}
|
||||
control={control}
|
||||
name={props.name}
|
||||
/>
|
||||
</Suspense>
|
||||
</Field>
|
||||
);
|
||||
}
|
||||
|
||||
function PeopleSelectWithQuery({
|
||||
organization,
|
||||
name,
|
||||
control,
|
||||
}: {
|
||||
organization: string;
|
||||
name: string;
|
||||
control: Control;
|
||||
}) {
|
||||
function PeopleSelectWithQuery(
|
||||
props: Pick<Props, "organizationId" | "control" | "name">
|
||||
) {
|
||||
const { __ } = useTranslate();
|
||||
const people = usePeople(organization);
|
||||
const { name, organizationId, control } = props;
|
||||
const people = usePeople(organizationId);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -51,6 +52,7 @@ function PeopleSelectWithQuery({
|
||||
onValueChange={field.onChange}
|
||||
key={people?.length.toString() ?? "0"}
|
||||
{...field}
|
||||
className="w-full"
|
||||
value={field.value ?? ""}
|
||||
>
|
||||
{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,
|
||||
type PreloadedQuery,
|
||||
} from "react-relay";
|
||||
import { useMemo, useTransition } from "react";
|
||||
import { useMemo } from "react";
|
||||
import type { PeopleGraphPaginatedQuery } from "./__generated__/PeopleGraphPaginatedQuery.graphql";
|
||||
import type { PeopleGraphPaginatedFragment$key } from "./__generated__/PeopleGraphPaginatedFragment.graphql";
|
||||
import { useConfirm } from "@probo/ui";
|
||||
import type { PeopleGraphDeleteMutation } from "./__generated__/PeopleGraphDeleteMutation.graphql";
|
||||
import { sprintf } from "@probo/helpers";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import type { PeopleGraphNodeQuery$data } from "./__generated__/PeopleGraphNodeQuery.graphql";
|
||||
|
||||
const peopleQuery = graphql`
|
||||
query PeopleGraphQuery($organizationId: ID!) {
|
||||
|
||||
@@ -3,6 +3,9 @@ import { graphql } from "relay-runtime";
|
||||
import { useMutationWithToasts } from "../useMutationWithToasts.ts";
|
||||
import type { VendorGraphCreateMutation } from "./__generated__/VendorGraphCreateMutation.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`
|
||||
mutation VendorGraphCreateMutation(
|
||||
@@ -32,7 +35,7 @@ export function useCreateVendorMutation() {
|
||||
{
|
||||
successMessage: __("Vendor created successfully."),
|
||||
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();
|
||||
|
||||
return useMutationWithToasts<VendorGraphDeleteMutation>(
|
||||
deleteVendorMutation,
|
||||
{
|
||||
successMessage: __("Vendor deleted successfully."),
|
||||
errorMessage: __("Failed to delete vendor. Please try again."),
|
||||
},
|
||||
);
|
||||
}
|
||||
return () => {
|
||||
if (!vendor.id || !vendor.name) {
|
||||
return alert(__("Failed to delete vendor: missing id or name"));
|
||||
}
|
||||
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
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -11,10 +11,10 @@
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type BusinessImpact = "CRITICAL" | "HIGH" | "LOW" | "MEDIUM";
|
||||
export type DataSensitivity = "CRITICAL" | "HIGH" | "LOW" | "MEDIUM" | "NONE";
|
||||
export type VendorsPageQuery$variables = {
|
||||
export type VendorGraphListQuery$variables = {
|
||||
organizationId: string;
|
||||
};
|
||||
export type VendorsPageQuery$data = {
|
||||
export type VendorGraphListQuery$data = {
|
||||
readonly node: {
|
||||
readonly vendors?: {
|
||||
readonly __id: string;
|
||||
@@ -40,9 +40,9 @@ export type VendorsPageQuery$data = {
|
||||
};
|
||||
};
|
||||
};
|
||||
export type VendorsPageQuery = {
|
||||
response: VendorsPageQuery$data;
|
||||
variables: VendorsPageQuery$variables;
|
||||
export type VendorGraphListQuery = {
|
||||
response: VendorGraphListQuery$data;
|
||||
variables: VendorGraphListQuery$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
@@ -253,7 +253,7 @@ return {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "VendorsPageQuery",
|
||||
"name": "VendorGraphListQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
@@ -291,7 +291,7 @@ return {
|
||||
"operation": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "VendorsPageQuery",
|
||||
"name": "VendorGraphListQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
@@ -335,7 +335,7 @@ return {
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "cfdef0fe4c1b737de9dfbf0a27a3de44",
|
||||
"cacheID": "ce175c4c5d08117d3acdbd3eed92ca95",
|
||||
"id": null,
|
||||
"metadata": {
|
||||
"connection": [
|
||||
@@ -350,13 +350,13 @@ return {
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "VendorsPageQuery",
|
||||
"name": "VendorGraphListQuery",
|
||||
"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;
|
||||
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 };
|
||||
mutate({
|
||||
...options,
|
||||
onCompleted: (response, error) => {
|
||||
options.onCompleted?.(response, error);
|
||||
if (error) {
|
||||
return new Promise<void>((resolve, reject) =>
|
||||
mutate({
|
||||
...options,
|
||||
onCompleted: (response, 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({
|
||||
title: __("Error"),
|
||||
description:
|
||||
options.errorMessage ?? __("Failed to commit this operation."),
|
||||
variant: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
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",
|
||||
});
|
||||
},
|
||||
});
|
||||
reject(error);
|
||||
},
|
||||
})
|
||||
);
|
||||
},
|
||||
[mutate]
|
||||
);
|
||||
|
||||
@@ -12,12 +12,12 @@ import {
|
||||
Textarea,
|
||||
useDialogRef,
|
||||
} from "@probo/ui";
|
||||
import { useState, type ReactNode } from "react";
|
||||
import { type ReactNode } from "react";
|
||||
import { graphql } from "relay-runtime";
|
||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||
import { usePolicyForm } from "../../../../hooks/forms/usePolicyForm";
|
||||
import { usePolicyForm } from "/hooks/forms/usePolicyForm";
|
||||
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
|
||||
import { PeopleSelect } from "/components/form/PeopleSelect";
|
||||
import { PeopleSelectField } from "/components/form/PeopleSelectField";
|
||||
import type { CreatePolicyDialogMutation } from "./__generated__/CreatePolicyDialogMutation.graphql";
|
||||
|
||||
type Props = {
|
||||
@@ -112,10 +112,10 @@ export function CreatePolicyDialog({ trigger, connection }: Props) {
|
||||
label={__("Owner")}
|
||||
error={errors.ownerId?.message}
|
||||
>
|
||||
<PeopleSelect
|
||||
<PeopleSelectField
|
||||
name="ownerId"
|
||||
control={control}
|
||||
organization={organizationId}
|
||||
organizationId={organizationId}
|
||||
/>
|
||||
</PropertyRow>
|
||||
</div>
|
||||
|
||||
@@ -15,10 +15,10 @@ import {
|
||||
Textarea,
|
||||
useDialogRef,
|
||||
} from "@probo/ui";
|
||||
import { useMemo, useState, type ReactNode } from "react";
|
||||
import { useMemo, type ReactNode } from "react";
|
||||
import { useFetchQuery } from "/hooks/useFetchQuery";
|
||||
import { graphql } from "relay-runtime";
|
||||
import { PeopleSelect } from "/components/form/PeopleSelect";
|
||||
import { PeopleSelectField } from "/components/form/PeopleSelectField";
|
||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||
import { useToggle } from "@probo/hooks";
|
||||
import {
|
||||
@@ -196,10 +196,10 @@ export default function FormRiskDialog({
|
||||
label={__("Owner")}
|
||||
error={errors.ownerId?.message}
|
||||
>
|
||||
<PeopleSelect
|
||||
<PeopleSelectField
|
||||
name="ownerId"
|
||||
control={control}
|
||||
organization={organizationId}
|
||||
organizationId={organizationId}
|
||||
/>
|
||||
</PropertyRow>
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
DialogFooter,
|
||||
Avatar,
|
||||
IconPlusLarge,
|
||||
useDialogRef,
|
||||
} from "@probo/ui";
|
||||
import { useVendorSearch } from "/hooks/useVendorSearch";
|
||||
import { faviconUrl } from "@probo/helpers";
|
||||
@@ -15,20 +16,19 @@ import { useCreateVendorMutation } from "/hooks/graph/VendorGraph";
|
||||
import { type ReactNode, useState } from "react";
|
||||
|
||||
type Props = {
|
||||
trigger: ReactNode;
|
||||
children: ReactNode;
|
||||
organizationId: string;
|
||||
connection: string;
|
||||
};
|
||||
|
||||
export function CreateVendorDialog({
|
||||
trigger,
|
||||
children,
|
||||
organizationId,
|
||||
connection,
|
||||
}: Props) {
|
||||
const { __ } = useTranslate();
|
||||
const { search, vendors, query } = useVendorSearch();
|
||||
const [createVendor] = useCreateVendorMutation();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const onSelect = (vendor: Vendor | string) => {
|
||||
const input =
|
||||
@@ -61,18 +61,15 @@ export function CreateVendorDialog({
|
||||
connections: [connection],
|
||||
},
|
||||
onSuccess: () => {
|
||||
setOpen(false);
|
||||
dialogRef.current?.close();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const dialogRef = useDialogRef();
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
title={__("Add a vendor")}
|
||||
trigger={trigger}
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
>
|
||||
<Dialog ref={dialogRef} trigger={children} title={__("Add a vendor")}>
|
||||
<DialogContent className="p-6">
|
||||
<Combobox onSearch={search} placeholder={__("Type vendor's name")}>
|
||||
{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,
|
||||
DropdownItem,
|
||||
IconTrashCan,
|
||||
useConfirm,
|
||||
} from "@probo/ui";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { usePageTitle } from "@probo/hooks";
|
||||
import { ConnectionHandler, graphql } from "relay-runtime";
|
||||
import { useLazyLoadQuery } from "react-relay";
|
||||
import { usePreloadedQuery, type PreloadedQuery } from "react-relay";
|
||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||
import type {
|
||||
VendorsPageQuery,
|
||||
VendorsPageQuery$data,
|
||||
} from "./__generated__/VendorsPageQuery.graphql";
|
||||
import { faviconUrl, sprintf } from "@probo/helpers";
|
||||
import { faviconUrl } from "@probo/helpers";
|
||||
import type { NodeOf } from "/types";
|
||||
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`
|
||||
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<VendorGraphListQuery$data["node"]>["vendors"]>;
|
||||
|
||||
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 organizationId = useOrganizationId();
|
||||
|
||||
const data = useLazyLoadQuery<VendorsPageQuery>(vendorsQuery, {
|
||||
organizationId,
|
||||
});
|
||||
console.log(data);
|
||||
const data = usePreloadedQuery(vendorsQuery, props.queryRef);
|
||||
|
||||
const vendors = data.node?.vendors?.edges.map((edge) => edge.node);
|
||||
const connectionId = data.node!.vendors!.__id;
|
||||
|
||||
usePageTitle(__("Vendors"));
|
||||
|
||||
@@ -90,8 +56,9 @@ export default function VendorsPage() {
|
||||
<CreateVendorDialog
|
||||
connection={data.node!.vendors!.__id}
|
||||
organizationId={organizationId}
|
||||
trigger={<Button icon={IconPlusLarge}>{__("Add vendor")}</Button>}
|
||||
/>
|
||||
>
|
||||
<Button icon={IconPlusLarge}>{__("Add vendor")}</Button>
|
||||
</CreateVendorDialog>
|
||||
</PageHeader>
|
||||
<Table>
|
||||
<Thead>
|
||||
@@ -110,6 +77,7 @@ export default function VendorsPage() {
|
||||
key={vendor.id}
|
||||
vendor={vendor}
|
||||
organizationId={organizationId}
|
||||
connectionId={connectionId}
|
||||
/>
|
||||
))}
|
||||
</Tbody>
|
||||
@@ -121,9 +89,11 @@ export default function VendorsPage() {
|
||||
function VendorRow({
|
||||
vendor,
|
||||
organizationId,
|
||||
connectionId,
|
||||
}: {
|
||||
vendor: Vendor;
|
||||
organizationId: string;
|
||||
connectionId: string;
|
||||
}) {
|
||||
const { __, dateFormat } = useTranslate();
|
||||
const latestAssessment = vendor.riskAssessments?.edges[0]?.node;
|
||||
@@ -131,42 +101,11 @@ function VendorRow({
|
||||
? new Date(latestAssessment.expiresAt) < new Date()
|
||||
: false;
|
||||
|
||||
const [deleteVendor] = useDeleteVendorMutation();
|
||||
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
|
||||
),
|
||||
}
|
||||
);
|
||||
};
|
||||
const deleteVendor = useDeleteVendor(vendor, connectionId);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tr to={`/organizations/${organizationId}/vendors/${vendor.id}`}>
|
||||
<Tr to={`/organizations/${organizationId}/vendors/${vendor.id}/overview`}>
|
||||
<Td>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Avatar name={vendor.name} src={faviconUrl(vendor.websiteUrl)} />
|
||||
@@ -196,7 +135,7 @@ function VendorRow({
|
||||
<Td noLink width={50} className="text-end">
|
||||
<ActionDropdown>
|
||||
<DropdownItem
|
||||
onClick={onDelete}
|
||||
onClick={deleteVendor}
|
||||
variant="danger"
|
||||
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 { measureRoutes } from "./routes/measureRoutes.ts";
|
||||
import { policiesRoutes } from "./routes/policiesRoutes.ts";
|
||||
import { vendorRoutes } from "./routes/vendorRoutes.ts";
|
||||
import { organizationViewQuery } from "./hooks/graph/OrganizationGraph.ts";
|
||||
import { peopleRoutes } from "./routes/peopleRoutes.ts";
|
||||
|
||||
@@ -79,17 +80,11 @@ const routes = [
|
||||
}),
|
||||
Component: lazy(() => import("./pages/organizations/SettingsPage")),
|
||||
},
|
||||
{
|
||||
path: "vendors",
|
||||
fallback: PageSkeleton,
|
||||
Component: lazy(
|
||||
() => import("./pages/organizations/vendors/VendorsPage")
|
||||
),
|
||||
},
|
||||
...riskRoutes,
|
||||
...measureRoutes,
|
||||
...policiesRoutes,
|
||||
...peopleRoutes,
|
||||
...vendorRoutes,
|
||||
],
|
||||
},
|
||||
] 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",
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"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 {
|
||||
getTreatment,
|
||||
@@ -6,7 +6,9 @@ export {
|
||||
getRiskLikelihoods,
|
||||
getSeverity,
|
||||
} from "./risk";
|
||||
export { withViewTransition } from "./dom";
|
||||
export { times, groupBy, isEmpty } from "./array";
|
||||
export { randomInt } from "./number";
|
||||
export { getMeasureStateLabel, measureStates } from "./measure";
|
||||
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) {
|
||||
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",
|
||||
"@tailwindcss/vite": "^4.1.7",
|
||||
"clsx": "^2.1.1",
|
||||
"react-dropzone": "^14.3.8",
|
||||
"react-markdown": "^10.1.0",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import type { HTMLAttributes } from "react";
|
||||
import { tv } from "tailwind-variants";
|
||||
import { Slot } from "../Slot";
|
||||
|
||||
type Props = {
|
||||
asChild?: boolean;
|
||||
variant?:
|
||||
| "success"
|
||||
| "warning"
|
||||
@@ -11,10 +13,10 @@ type Props = {
|
||||
| "outline"
|
||||
| "highlight";
|
||||
size?: "sm" | "md";
|
||||
} & HTMLAttributes<HTMLDivElement>;
|
||||
} & HTMLAttributes<HTMLElement>;
|
||||
|
||||
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: {
|
||||
variant: {
|
||||
success: "bg-success text-txt-success",
|
||||
@@ -37,5 +39,7 @@ const badge = tv({
|
||||
});
|
||||
|
||||
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 (
|
||||
<Root onValueChange={onValueChange} value={value}>
|
||||
<Trigger {...props} className={trigger()}>
|
||||
<Trigger
|
||||
{...props}
|
||||
className={trigger({ className: props.className })}
|
||||
>
|
||||
<Value placeholder={placeholder} />
|
||||
<Icon className={icon()}>
|
||||
<IconChevronGrabberVertical size={16} />
|
||||
|
||||
@@ -18,13 +18,34 @@ type Props = {
|
||||
loading?: boolean;
|
||||
onSearch: (query: string) => void;
|
||||
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);
|
||||
return (
|
||||
<ComboboxProvider setValue={onSearch}>
|
||||
<AriaKitCombobox placeholder={placeholder} className={input()} />
|
||||
<ComboboxProvider
|
||||
value={value}
|
||||
setValue={onSearch}
|
||||
setSelectedValue={(v) => onSelect?.(v as string)}
|
||||
resetValueOnHide={resetValueOnHide}
|
||||
>
|
||||
<AriaKitCombobox
|
||||
autoSelect={autoSelect}
|
||||
placeholder={placeholder}
|
||||
className={input()}
|
||||
/>
|
||||
{showDropdown && (
|
||||
<ComboboxPopover gutter={4} sameWidth className={dropdown()}>
|
||||
{children}
|
||||
@@ -39,7 +60,7 @@ export function ComboboxItem({
|
||||
...props
|
||||
}: PropsWithChildren<ComponentProps<typeof AriaKitComboboxItem>>) {
|
||||
return (
|
||||
<AriaKitComboboxItem className={dropdownItem()} {...props}>
|
||||
<AriaKitComboboxItem hideOnClick className={dropdownItem()} {...props}>
|
||||
{children}
|
||||
</AriaKitComboboxItem>
|
||||
);
|
||||
|
||||
@@ -2,7 +2,7 @@ import { tv } from "tailwind-variants";
|
||||
import { Label } from "../../Atoms/Label/Label";
|
||||
import { Input } from "../../Atoms/Input/Input";
|
||||
import { Textarea } from "../../Atoms/Textarea/Textarea";
|
||||
import { type ComponentProps } from "react";
|
||||
import { type ComponentProps, type ReactNode } from "react";
|
||||
import { Select } from "../../Atoms/Select/Select";
|
||||
|
||||
type BaseProps<T extends string, P> = {
|
||||
@@ -10,7 +10,8 @@ type BaseProps<T extends string, P> = {
|
||||
help?: string;
|
||||
error?: string;
|
||||
onValueChange?: (s: string) => void;
|
||||
type: T;
|
||||
type?: T;
|
||||
children?: ReactNode;
|
||||
} & P;
|
||||
|
||||
type Props =
|
||||
@@ -33,6 +34,7 @@ const { base: baseClass, label: labelClass, help: helpClass } = field();
|
||||
|
||||
export function Field(props: Props) {
|
||||
const showHelp = props.help && !props.error;
|
||||
const childrenAsInput = !props.type && props.children;
|
||||
return (
|
||||
<div className={baseClass()}>
|
||||
{props.label && (
|
||||
@@ -40,7 +42,7 @@ export function Field(props: Props) {
|
||||
{props.label}
|
||||
</Label>
|
||||
)}
|
||||
{getInput(props)}
|
||||
{childrenAsInput ? props.children : getInput(props)}
|
||||
{showHelp && <span className={helpClass()}>{props.help}</span>}
|
||||
{props.error && (
|
||||
<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 {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
|
||||
Reference in New Issue
Block a user