Use relay and refacto public trust center
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"relay": "find src -type d -name \"__generated__\" -exec rm -rf {} + && npx relay-compiler ./relay.config.json",
|
||||
"relay": "find src -type d -name \"__generated__\" -exec rm -rf {} + && npx relay-compiler ./relay.config.json && npx relay-compiler ./relay.trust.config.json",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint .",
|
||||
"check": "tsc --noEmit -p tsconfig.app.json",
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"eagerEsModules": true,
|
||||
"noFutureProofEnums": true,
|
||||
"excludes": [
|
||||
"**/PublicTrustCenterGraph.ts"
|
||||
"**/PublicTrustCenterGraph.ts",
|
||||
"**/trust/**"
|
||||
]
|
||||
}
|
||||
|
||||
7
apps/console/relay.trust.config.json
Normal file
7
apps/console/relay.trust.config.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"src": "./src/trust",
|
||||
"schema": "../../pkg/server/api/trust/v1/schema.graphql",
|
||||
"language": "typescript",
|
||||
"eagerEsModules": true,
|
||||
"noFutureProofEnums": true
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
// Manual query definition for trust API (not processed by relay compiler)
|
||||
export const publicTrustCenterQuery = {
|
||||
params: {
|
||||
name: "PublicTrustCenterGraphQuery",
|
||||
operationKind: "query",
|
||||
text: `
|
||||
query PublicTrustCenterGraphQuery($slug: String!) {
|
||||
trustCenterBySlug(slug: $slug) {
|
||||
id
|
||||
active
|
||||
slug
|
||||
organization {
|
||||
id
|
||||
name
|
||||
logoUrl
|
||||
}
|
||||
documents(first: 100) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
title
|
||||
documentType
|
||||
}
|
||||
}
|
||||
}
|
||||
audits(first: 100) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
framework {
|
||||
name
|
||||
}
|
||||
report {
|
||||
id
|
||||
filename
|
||||
downloadUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
vendors(first: 100) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
category
|
||||
websiteUrl
|
||||
privacyPolicyUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
};
|
||||
@@ -17,13 +17,18 @@ export function PublicTrustCenterLayout({ organizationName, organizationLogo, ch
|
||||
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
await fetch(buildEndpoint('/api/trust/v1/auth/logout'), {
|
||||
const response = await fetch(buildEndpoint('/api/trust/v1/auth/logout'), {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Logout failed");
|
||||
}
|
||||
|
||||
window.location.reload();
|
||||
} catch (error) {
|
||||
toast({
|
||||
|
||||
@@ -1,234 +0,0 @@
|
||||
import { useParams, Navigate } from "react-router";
|
||||
import { usePageTitle } from "@probo/hooks";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { sprintf } from "@probo/helpers";
|
||||
import { publicTrustCenterQuery } from "/hooks/graph/PublicTrustCenterGraph";
|
||||
import { PublicTrustCenterLayout } from "/layouts/PublicTrustCenterLayout";
|
||||
import { PublicTrustCenterAudits } from "../components/trustCenter/PublicTrustCenterAudits";
|
||||
import { PublicTrustCenterVendors } from "../components/trustCenter/PublicTrustCenterVendors";
|
||||
import { PublicTrustCenterDocuments } from "../components/trustCenter/PublicTrustCenterDocuments";
|
||||
import { PageError } from "/components/PageError";
|
||||
import { TrustRelayProvider } from "/providers/TrustRelayProvider";
|
||||
import { useState, useEffect } from "react";
|
||||
import { buildEndpoint } from "/providers/RelayProviders";
|
||||
import { Spinner } from "@probo/ui";
|
||||
interface GraphQLError {
|
||||
message: string;
|
||||
path?: string[];
|
||||
locations?: Array<{ line: number; column: number }>;
|
||||
}
|
||||
|
||||
interface GraphQLResponse<T> {
|
||||
data?: T;
|
||||
errors?: GraphQLError[];
|
||||
}
|
||||
|
||||
interface Organization {
|
||||
id: string;
|
||||
name: string;
|
||||
logoUrl?: string;
|
||||
}
|
||||
|
||||
interface Framework {
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface DocumentVersion {
|
||||
id: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
interface DocumentVersionConnection {
|
||||
edges: Array<{
|
||||
node: DocumentVersion;
|
||||
}>;
|
||||
}
|
||||
|
||||
interface Document {
|
||||
id: string;
|
||||
title: string;
|
||||
documentType: string;
|
||||
versions: DocumentVersionConnection;
|
||||
}
|
||||
|
||||
interface Audit {
|
||||
id: string;
|
||||
framework: Framework;
|
||||
validFrom: string;
|
||||
validUntil: string | null;
|
||||
state: string;
|
||||
createdAt: string;
|
||||
report: {
|
||||
id: string;
|
||||
filename: string;
|
||||
downloadUrl: string | null;
|
||||
} | null;
|
||||
}
|
||||
|
||||
interface Vendor {
|
||||
id: string;
|
||||
name: string;
|
||||
category: string;
|
||||
description: string | null;
|
||||
createdAt: string;
|
||||
websiteUrl?: string | null;
|
||||
privacyPolicyUrl?: string | null;
|
||||
}
|
||||
|
||||
interface Connection<T> {
|
||||
edges: Array<{
|
||||
node: T;
|
||||
}>;
|
||||
}
|
||||
|
||||
interface TrustCenter {
|
||||
id: string;
|
||||
active: boolean;
|
||||
slug: string;
|
||||
organization: Organization;
|
||||
documents: Connection<Document>;
|
||||
audits: Connection<Audit>;
|
||||
vendors: Connection<Vendor>;
|
||||
}
|
||||
|
||||
interface PublicTrustCenterData {
|
||||
trustCenterBySlug?: TrustCenter;
|
||||
}
|
||||
|
||||
function PublicTrustCenterContent() {
|
||||
const { __ } = useTranslate();
|
||||
const { slug } = useParams<{ slug: string }>();
|
||||
const [data, setData] = useState<PublicTrustCenterData | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||||
|
||||
const organizationName = data?.trustCenterBySlug?.organization?.name;
|
||||
usePageTitle(
|
||||
organizationName ? `${organizationName} - Trust Center` : "Trust Center"
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!slug) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setIsAuthenticated(false);
|
||||
|
||||
fetch(buildEndpoint("/api/trust/v1/graphql"), {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
body: JSON.stringify({
|
||||
operationName: publicTrustCenterQuery.params.name,
|
||||
query: publicTrustCenterQuery.params.text,
|
||||
variables: { slug },
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((result: GraphQLResponse<PublicTrustCenterData>) => {
|
||||
const accessDeniedErrors =
|
||||
result.errors?.filter((error: GraphQLError) =>
|
||||
error.message.includes("access denied")
|
||||
) || [];
|
||||
|
||||
const nonAuthErrors =
|
||||
result.errors?.filter(
|
||||
(error: GraphQLError) => !error.message.includes("access denied")
|
||||
) || [];
|
||||
|
||||
if (nonAuthErrors.length > 0) {
|
||||
throw new Error(nonAuthErrors[0].message);
|
||||
}
|
||||
|
||||
setIsAuthenticated(accessDeniedErrors.length === 0);
|
||||
setData(result.data || null);
|
||||
})
|
||||
.catch(setError)
|
||||
.finally(() => setLoading(false));
|
||||
}, [slug]);
|
||||
|
||||
if (!slug) {
|
||||
return <Navigate to="/" replace />;
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<Spinner />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <PageError />;
|
||||
}
|
||||
|
||||
const { trustCenterBySlug } = data || {};
|
||||
|
||||
if (!trustCenterBySlug) {
|
||||
return <PageError />;
|
||||
}
|
||||
|
||||
if (!trustCenterBySlug.active) {
|
||||
return <PageError />;
|
||||
}
|
||||
|
||||
const { organization } = trustCenterBySlug;
|
||||
|
||||
const documents = trustCenterBySlug.documents.edges.map((edge) => edge.node);
|
||||
|
||||
const audits = trustCenterBySlug.audits.edges.map((edge) => edge.node);
|
||||
|
||||
const vendors = trustCenterBySlug.vendors.edges.map((edge) => edge.node);
|
||||
|
||||
return (
|
||||
<PublicTrustCenterLayout
|
||||
organizationName={organization.name}
|
||||
organizationLogo={organization.logoUrl}
|
||||
isAuthenticated={isAuthenticated}
|
||||
>
|
||||
<div className="space-y-8">
|
||||
<div className="text-center">
|
||||
<h1 className="text-3xl font-bold text-txt-primary mb-4">
|
||||
{sprintf(__("%s Trust Center"), organization.name)}
|
||||
</h1>
|
||||
<p className="text-lg text-txt-secondary max-w-2xl mx-auto">
|
||||
{__(
|
||||
"Explore our security practices, compliance certifications, and transparency reports."
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<PublicTrustCenterAudits
|
||||
audits={audits}
|
||||
organizationName={organization.name}
|
||||
isAuthenticated={isAuthenticated}
|
||||
trustCenterId={trustCenterBySlug.id}
|
||||
/>
|
||||
<PublicTrustCenterDocuments
|
||||
documents={documents}
|
||||
isAuthenticated={isAuthenticated}
|
||||
trustCenterId={trustCenterBySlug.id}
|
||||
organizationName={organization.name}
|
||||
/>
|
||||
<PublicTrustCenterVendors
|
||||
vendors={vendors}
|
||||
organizationName={organization.name}
|
||||
/>
|
||||
</div>
|
||||
</PublicTrustCenterLayout>
|
||||
);
|
||||
}
|
||||
|
||||
export default function PublicTrustCenterPage() {
|
||||
return (
|
||||
<TrustRelayProvider>
|
||||
<PublicTrustCenterContent />
|
||||
</TrustRelayProvider>
|
||||
);
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
|
||||
import type { PropsWithChildren } from "react";
|
||||
import { RelayEnvironmentProvider } from "react-relay";
|
||||
import { createContext, useContext, useState, useRef } from "react";
|
||||
import { buildEndpoint } from "./RelayProviders";
|
||||
|
||||
export class TrustCenterError extends Error {
|
||||
@@ -17,7 +18,22 @@ export class TrustCenterError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
const fetchTrustRelay: FetchFunction = async (request, variables) => {
|
||||
type TrustAuthContextType = {
|
||||
isAuthenticated: boolean;
|
||||
setAuthenticated: (auth: boolean) => void;
|
||||
};
|
||||
|
||||
const TrustAuthContext = createContext<TrustAuthContextType | null>(null);
|
||||
|
||||
export function useTrustAuth() {
|
||||
const context = useContext(TrustAuthContext);
|
||||
if (!context) {
|
||||
throw new Error('useTrustAuth must be used within a TrustRelayProvider');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
const createFetchTrustRelay = (setAuthenticated: (auth: boolean) => void): FetchFunction => async (request, variables) => {
|
||||
const requestInit: RequestInit = {
|
||||
method: "POST",
|
||||
headers: {
|
||||
@@ -25,7 +41,7 @@ const fetchTrustRelay: FetchFunction = async (request, variables) => {
|
||||
"application/graphql-response+json; charset=utf-8, application/json; charset=utf-8",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
credentials: "include", // Include cookies for authentication
|
||||
credentials: "include",
|
||||
body: JSON.stringify({
|
||||
operationName: request.name,
|
||||
query: request.text,
|
||||
@@ -44,37 +60,58 @@ const fetchTrustRelay: FetchFunction = async (request, variables) => {
|
||||
|
||||
const json = await response.json();
|
||||
|
||||
if (json.errors) {
|
||||
throw new TrustCenterError(
|
||||
`Error fetching GraphQL query '${
|
||||
request.name
|
||||
}' with variables '${JSON.stringify(variables)}': ${JSON.stringify(
|
||||
json.errors
|
||||
)}`
|
||||
if (json.errors?.length > 0) {
|
||||
const hasAccessDeniedErrors = json.errors.some((error: any) =>
|
||||
error.message.toLowerCase().includes("access denied") ||
|
||||
error.message.toLowerCase().includes("unauthorized") ||
|
||||
error.extensions?.code === "UNAUTHENTICATED"
|
||||
);
|
||||
|
||||
if (hasAccessDeniedErrors) {
|
||||
setAuthenticated(false);
|
||||
} else {
|
||||
throw new TrustCenterError(
|
||||
`Error fetching GraphQL query '${
|
||||
request.name
|
||||
}' with variables '${JSON.stringify(variables)}': ${JSON.stringify(
|
||||
json.errors
|
||||
)}`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
setAuthenticated(true);
|
||||
}
|
||||
|
||||
return json;
|
||||
};
|
||||
|
||||
const trustSource = new RecordSource();
|
||||
const trustStore = new Store(trustSource, {
|
||||
queryCacheExpirationTime: 5 * 60 * 1000, // 5 minutes for trust center content
|
||||
gcReleaseBufferSize: 10,
|
||||
});
|
||||
|
||||
export const trustRelayEnvironment = new Environment({
|
||||
network: Network.create(fetchTrustRelay),
|
||||
store: trustStore,
|
||||
});
|
||||
|
||||
/**
|
||||
* Provider for trust center Relay environment (public API)
|
||||
*/
|
||||
export function TrustRelayProvider({ children }: PropsWithChildren) {
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(true);
|
||||
const environmentRef = useRef<Environment | null>(null);
|
||||
|
||||
if (!environmentRef.current) {
|
||||
const trustSource = new RecordSource();
|
||||
const trustStore = new Store(trustSource, {
|
||||
queryCacheExpirationTime: 5 * 60 * 1000, // 5 minutes
|
||||
gcReleaseBufferSize: 10,
|
||||
});
|
||||
|
||||
environmentRef.current = new Environment({
|
||||
network: Network.create(createFetchTrustRelay(setIsAuthenticated)),
|
||||
store: trustStore,
|
||||
});
|
||||
}
|
||||
|
||||
const authContextValue: TrustAuthContextType = {
|
||||
isAuthenticated,
|
||||
setAuthenticated: setIsAuthenticated,
|
||||
};
|
||||
|
||||
return (
|
||||
<RelayEnvironmentProvider environment={trustRelayEnvironment}>
|
||||
{children}
|
||||
</RelayEnvironmentProvider>
|
||||
<TrustAuthContext.Provider value={authContextValue}>
|
||||
<RelayEnvironmentProvider environment={environmentRef.current}>
|
||||
{children}
|
||||
</RelayEnvironmentProvider>
|
||||
</TrustAuthContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ const routes = [
|
||||
path: "/trust/:slug",
|
||||
ErrorBoundary: ErrorBoundary,
|
||||
fallback: PageSkeleton,
|
||||
Component: lazy(() => import("./pages/PublicTrustCenterPage")),
|
||||
Component: lazy(() => import("./trust/pages/PublicTrustCenterPage")),
|
||||
},
|
||||
{
|
||||
path: "/trust/:slug/access",
|
||||
|
||||
@@ -13,7 +13,7 @@ export const trustCenterRoutes = [
|
||||
queryLoader: ({ organizationId }) =>
|
||||
loadQuery(relayEnvironment, trustCenterQuery, { organizationId }),
|
||||
Component: lazy(
|
||||
() => import("/pages/organizations/TrustCenterPage")
|
||||
() => import("/pages/organizations/trustCenter/TrustCenterPage")
|
||||
),
|
||||
children: [
|
||||
{
|
||||
|
||||
@@ -12,41 +12,22 @@ import { useTranslate } from "@probo/i18n";
|
||||
import { sprintf } from "@probo/helpers";
|
||||
import { useFormWithSchema } from "/hooks/useFormWithSchema";
|
||||
import { z } from "zod";
|
||||
import { buildEndpoint } from "/providers/RelayProviders";
|
||||
import { useMutation, graphql } from "react-relay";
|
||||
import type { PublicTrustCenterAccessRequestDialogMutation } from "./__generated__/PublicTrustCenterAccessRequestDialogMutation.graphql";
|
||||
|
||||
// Manual mutation for trust API (not processed by relay compiler)
|
||||
const createTrustCenterAccessMutation = {
|
||||
params: {
|
||||
name: "CreateTrustCenterAccessMutation",
|
||||
operationKind: "mutation",
|
||||
text: `
|
||||
mutation CreateTrustCenterAccessMutation(
|
||||
$input: CreateTrustCenterAccessInput!
|
||||
) {
|
||||
createTrustCenterAccess(input: $input) {
|
||||
trustCenterAccess {
|
||||
id
|
||||
email
|
||||
name
|
||||
}
|
||||
}
|
||||
const CreateTrustCenterAccessMutation = graphql`
|
||||
mutation PublicTrustCenterAccessRequestDialogMutation(
|
||||
$input: CreateTrustCenterAccessInput!
|
||||
) {
|
||||
createTrustCenterAccess(input: $input) {
|
||||
trustCenterAccess {
|
||||
id
|
||||
email
|
||||
name
|
||||
}
|
||||
`
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
type CreateTrustCenterAccessResponse = {
|
||||
data?: {
|
||||
createTrustCenterAccess?: {
|
||||
trustCenterAccess: {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
errors?: Array<{ message: string }>;
|
||||
};
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
trigger: React.ReactNode;
|
||||
@@ -54,7 +35,7 @@ type Props = {
|
||||
organizationName: string;
|
||||
};
|
||||
|
||||
export function TrustCenterAccessRequestDialog({
|
||||
export function PublicTrustCenterAccessRequestDialog({
|
||||
trigger,
|
||||
trustCenterId,
|
||||
organizationName
|
||||
@@ -64,6 +45,8 @@ export function TrustCenterAccessRequestDialog({
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const dialogRef = useDialogRef();
|
||||
|
||||
const [commitMutation] = useMutation<PublicTrustCenterAccessRequestDialogMutation>(CreateTrustCenterAccessMutation);
|
||||
|
||||
const schema = z.object({
|
||||
name: z.string().min(1, __("Name is required")).min(2, __("Name must be at least 2 characters long")),
|
||||
email: z.string().min(1, __("Email is required")).email(__("Please enter a valid email address")),
|
||||
@@ -76,56 +59,38 @@ export function TrustCenterAccessRequestDialog({
|
||||
const onSubmit = handleSubmit(async (data) => {
|
||||
setIsSubmitting(true);
|
||||
|
||||
try {
|
||||
const response = await fetch(buildEndpoint("/api/trust/v1/graphql"), {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
commitMutation({
|
||||
variables: {
|
||||
input: {
|
||||
trustCenterId,
|
||||
email: data.email,
|
||||
name: data.name,
|
||||
},
|
||||
credentials: "include",
|
||||
body: JSON.stringify({
|
||||
operationName: createTrustCenterAccessMutation.params.name,
|
||||
query: createTrustCenterAccessMutation.params.text,
|
||||
variables: {
|
||||
input: {
|
||||
trustCenterId,
|
||||
email: data.email,
|
||||
name: data.name
|
||||
}
|
||||
},
|
||||
}),
|
||||
});
|
||||
},
|
||||
onCompleted: (response) => {
|
||||
if (response.createTrustCenterAccess) {
|
||||
toast({
|
||||
title: __("Request Submitted"),
|
||||
description: __("Your access request has been submitted. You will receive an email if your request is approved."),
|
||||
variant: "success",
|
||||
});
|
||||
|
||||
const result: CreateTrustCenterAccessResponse = await response.json();
|
||||
reset();
|
||||
dialogRef.current?.close();
|
||||
}
|
||||
setIsSubmitting(false);
|
||||
},
|
||||
onError: (error) => {
|
||||
const errorMessage = error.message || __("An error occurred while submitting your request.");
|
||||
|
||||
if (result.errors) {
|
||||
throw new Error(result.errors[0].message);
|
||||
}
|
||||
|
||||
if (result.data?.createTrustCenterAccess) {
|
||||
toast({
|
||||
title: __("Request Submitted"),
|
||||
description: __("Your access request has been submitted. You will receive an email if your request is approved."),
|
||||
variant: "success",
|
||||
title: __("Request Failed"),
|
||||
description: errorMessage,
|
||||
variant: "error",
|
||||
});
|
||||
|
||||
reset();
|
||||
dialogRef.current?.close();
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error
|
||||
? error.message
|
||||
: __("An error occurred while submitting your request.");
|
||||
|
||||
toast({
|
||||
title: __("Request Failed"),
|
||||
description: errorMessage,
|
||||
variant: "error",
|
||||
});
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
setIsSubmitting(false);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -13,26 +13,11 @@ import {
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { sprintf } from "@probo/helpers";
|
||||
import { FrameworkLogo } from "/components/FrameworkLogo";
|
||||
import { TrustCenterAccessRequestDialog } from "./TrustCenterAccessRequestDialog";
|
||||
|
||||
type Audit = {
|
||||
id: string;
|
||||
framework: {
|
||||
name: string;
|
||||
};
|
||||
validFrom: string;
|
||||
validUntil: string | null;
|
||||
state: string;
|
||||
createdAt: string;
|
||||
report: {
|
||||
id: string;
|
||||
filename: string;
|
||||
downloadUrl: string | null;
|
||||
} | null;
|
||||
};
|
||||
import { PublicTrustCenterAccessRequestDialog } from "./PublicTrustCenterAccessRequestDialog";
|
||||
import type { TrustCenterAudit } from "../pages/PublicTrustCenterPage";
|
||||
|
||||
type Props = {
|
||||
audits: Audit[];
|
||||
audits: TrustCenterAudit[];
|
||||
organizationName: string;
|
||||
isAuthenticated: boolean;
|
||||
trustCenterId: string;
|
||||
@@ -105,7 +90,7 @@ export function PublicTrustCenterAudits({
|
||||
{__("No report")}
|
||||
</span>
|
||||
) : !isAuthenticated ? (
|
||||
<TrustCenterAccessRequestDialog
|
||||
<PublicTrustCenterAccessRequestDialog
|
||||
trigger={
|
||||
<Button
|
||||
variant="secondary"
|
||||
@@ -13,47 +13,28 @@ import {
|
||||
useToast,
|
||||
} from "@probo/ui";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { buildEndpoint } from "/providers/RelayProviders";
|
||||
import { TrustCenterAccessRequestDialog } from "./TrustCenterAccessRequestDialog";
|
||||
import { useMutation, graphql } from "react-relay";
|
||||
import { PublicTrustCenterAccessRequestDialog } from "./PublicTrustCenterAccessRequestDialog";
|
||||
import type { PublicTrustCenterDocumentsExportPDFMutation } from "./__generated__/PublicTrustCenterDocumentsExportPDFMutation.graphql";
|
||||
import type { TrustCenterDocument } from "../pages/PublicTrustCenterPage";
|
||||
|
||||
const exportDocumentPDFMutation = {
|
||||
params: {
|
||||
name: "PublicTrustCenterDocumentsExportPDFMutation",
|
||||
operationKind: "mutation",
|
||||
text: `
|
||||
mutation PublicTrustCenterDocumentsExportPDFMutation(
|
||||
$input: ExportDocumentPDFInput!
|
||||
) {
|
||||
exportDocumentPDF(input: $input) {
|
||||
data
|
||||
}
|
||||
}
|
||||
`
|
||||
const ExportDocumentPDFMutation = graphql`
|
||||
mutation PublicTrustCenterDocumentsExportPDFMutation(
|
||||
$input: ExportDocumentPDFInput!
|
||||
) {
|
||||
exportDocumentPDF(input: $input) {
|
||||
data
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
type Document = {
|
||||
id: string;
|
||||
title: string;
|
||||
documentType: string;
|
||||
};
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
documents: Document[];
|
||||
documents: TrustCenterDocument[];
|
||||
isAuthenticated: boolean;
|
||||
trustCenterId: string;
|
||||
organizationName: string;
|
||||
};
|
||||
|
||||
type ExportDocumentPDFResponse = {
|
||||
data?: {
|
||||
exportDocumentPDF?: {
|
||||
data: string;
|
||||
};
|
||||
};
|
||||
errors?: Array<{ message: string }>;
|
||||
};
|
||||
|
||||
export function PublicTrustCenterDocuments({
|
||||
documents,
|
||||
isAuthenticated,
|
||||
@@ -63,43 +44,31 @@ export function PublicTrustCenterDocuments({
|
||||
const { __ } = useTranslate();
|
||||
const { toast } = useToast();
|
||||
|
||||
const handleDownload = async (document: Document) => {
|
||||
try {
|
||||
const response = await fetch(buildEndpoint("/api/trust/v1/graphql"), {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
body: JSON.stringify({
|
||||
operationName: exportDocumentPDFMutation.params.name,
|
||||
query: exportDocumentPDFMutation.params.text,
|
||||
variables: { input: { documentId: document.id } },
|
||||
}),
|
||||
});
|
||||
const [commitMutation] = useMutation<PublicTrustCenterDocumentsExportPDFMutation>(ExportDocumentPDFMutation);
|
||||
|
||||
const result: ExportDocumentPDFResponse = await response.json();
|
||||
|
||||
if (result.errors) {
|
||||
throw new Error(result.errors[0].message);
|
||||
}
|
||||
|
||||
if (result.data?.exportDocumentPDF?.data) {
|
||||
const link = window.document.createElement("a");
|
||||
link.href = result.data.exportDocumentPDF.data;
|
||||
link.download = `${document.title}.pdf`;
|
||||
window.document.body.appendChild(link);
|
||||
link.click();
|
||||
window.document.body.removeChild(link);
|
||||
}
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: __("Download Failed"),
|
||||
description: __("Unable to download the document. Please try again."),
|
||||
variant: "error",
|
||||
});
|
||||
}
|
||||
const handleDownload = async (document: TrustCenterDocument) => {
|
||||
commitMutation({
|
||||
variables: {
|
||||
input: { documentId: document.id }
|
||||
},
|
||||
onCompleted: (response) => {
|
||||
if (response.exportDocumentPDF?.data) {
|
||||
const link = window.document.createElement("a");
|
||||
link.href = response.exportDocumentPDF.data;
|
||||
link.download = `${document.title}.pdf`;
|
||||
window.document.body.appendChild(link);
|
||||
link.click();
|
||||
window.document.body.removeChild(link);
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
toast({
|
||||
title: __("Download Failed"),
|
||||
description: __("Unable to download the document. Please try again."),
|
||||
variant: "error",
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
if (documents.length === 0) {
|
||||
@@ -150,7 +119,7 @@ export function PublicTrustCenterDocuments({
|
||||
</Td>
|
||||
<Td>
|
||||
{!isAuthenticated ? (
|
||||
<TrustCenterAccessRequestDialog
|
||||
<PublicTrustCenterAccessRequestDialog
|
||||
trigger={
|
||||
<Button
|
||||
variant="secondary"
|
||||
@@ -9,19 +9,10 @@ import {
|
||||
} from "@probo/ui";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { faviconUrl, sprintf } from "@probo/helpers";
|
||||
|
||||
type Vendor = {
|
||||
id: string;
|
||||
name: string;
|
||||
category: string;
|
||||
description: string | null;
|
||||
createdAt: string;
|
||||
privacyPolicyUrl?: string | null;
|
||||
websiteUrl?: string | null;
|
||||
};
|
||||
import type { TrustCenterVendor } from "../pages/PublicTrustCenterPage";
|
||||
|
||||
type Props = {
|
||||
vendors: Vendor[];
|
||||
vendors: TrustCenterVendor[];
|
||||
organizationName: string;
|
||||
};
|
||||
|
||||
123
apps/console/src/trust/components/__generated__/PublicTrustCenterAccessRequestDialogMutation.graphql.ts
generated
Normal file
123
apps/console/src/trust/components/__generated__/PublicTrustCenterAccessRequestDialogMutation.graphql.ts
generated
Normal file
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* @generated SignedSource<<4ce5109725ad53ad77aedf4d39bf463b>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type CreateTrustCenterAccessInput = {
|
||||
email: string;
|
||||
name: string;
|
||||
trustCenterId: string;
|
||||
};
|
||||
export type PublicTrustCenterAccessRequestDialogMutation$variables = {
|
||||
input: CreateTrustCenterAccessInput;
|
||||
};
|
||||
export type PublicTrustCenterAccessRequestDialogMutation$data = {
|
||||
readonly createTrustCenterAccess: {
|
||||
readonly trustCenterAccess: {
|
||||
readonly email: string;
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
export type PublicTrustCenterAccessRequestDialogMutation = {
|
||||
response: PublicTrustCenterAccessRequestDialogMutation$data;
|
||||
variables: PublicTrustCenterAccessRequestDialogMutation$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = [
|
||||
{
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "input"
|
||||
}
|
||||
],
|
||||
v1 = [
|
||||
{
|
||||
"alias": null,
|
||||
"args": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "input",
|
||||
"variableName": "input"
|
||||
}
|
||||
],
|
||||
"concreteType": "CreateTrustCenterAccessPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "createTrustCenterAccess",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "TrustCenterAccess",
|
||||
"kind": "LinkedField",
|
||||
"name": "trustCenterAccess",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "email",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "name",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
];
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "PublicTrustCenterAccessRequestDialogMutation",
|
||||
"selections": (v1/*: any*/),
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "PublicTrustCenterAccessRequestDialogMutation",
|
||||
"selections": (v1/*: any*/)
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "bdc03aeaa40a243db4af3886abffb1a0",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "PublicTrustCenterAccessRequestDialogMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation PublicTrustCenterAccessRequestDialogMutation(\n $input: CreateTrustCenterAccessInput!\n) {\n createTrustCenterAccess(input: $input) {\n trustCenterAccess {\n id\n email\n name\n }\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "d895df22aae3dcc8438bb794910cbfcc";
|
||||
|
||||
export default node;
|
||||
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* @generated SignedSource<<e737361b21a8767acd6a604702875029>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type ExportDocumentPDFInput = {
|
||||
documentId: string;
|
||||
};
|
||||
export type PublicTrustCenterDocumentsExportPDFMutation$variables = {
|
||||
input: ExportDocumentPDFInput;
|
||||
};
|
||||
export type PublicTrustCenterDocumentsExportPDFMutation$data = {
|
||||
readonly exportDocumentPDF: {
|
||||
readonly data: string;
|
||||
};
|
||||
};
|
||||
export type PublicTrustCenterDocumentsExportPDFMutation = {
|
||||
response: PublicTrustCenterDocumentsExportPDFMutation$data;
|
||||
variables: PublicTrustCenterDocumentsExportPDFMutation$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = [
|
||||
{
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "input"
|
||||
}
|
||||
],
|
||||
v1 = [
|
||||
{
|
||||
"alias": null,
|
||||
"args": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "input",
|
||||
"variableName": "input"
|
||||
}
|
||||
],
|
||||
"concreteType": "ExportDocumentPDFPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "exportDocumentPDF",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "data",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
];
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "PublicTrustCenterDocumentsExportPDFMutation",
|
||||
"selections": (v1/*: any*/),
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "PublicTrustCenterDocumentsExportPDFMutation",
|
||||
"selections": (v1/*: any*/)
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "bb7c09ea21c22c0a728b18dde2449f72",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "PublicTrustCenterDocumentsExportPDFMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation PublicTrustCenterDocumentsExportPDFMutation(\n $input: ExportDocumentPDFInput!\n) {\n exportDocumentPDF(input: $input) {\n data\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "00a3f3d260fe38b35fe33a0033cf2400";
|
||||
|
||||
export default node;
|
||||
166
apps/console/src/trust/pages/PublicTrustCenterPage.tsx
Normal file
166
apps/console/src/trust/pages/PublicTrustCenterPage.tsx
Normal file
@@ -0,0 +1,166 @@
|
||||
import { useParams, Navigate } from "react-router";
|
||||
import { usePageTitle } from "@probo/hooks";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { PublicTrustCenterLayout } from "/layouts/PublicTrustCenterLayout";
|
||||
import { PublicTrustCenterAudits } from "../components/PublicTrustCenterAudits";
|
||||
import { PublicTrustCenterVendors } from "../components/PublicTrustCenterVendors";
|
||||
import { PublicTrustCenterDocuments } from "../components/PublicTrustCenterDocuments";
|
||||
import { TrustRelayProvider, useTrustAuth } from "/providers/TrustRelayProvider";
|
||||
import { Suspense } from "react";
|
||||
import { useLazyLoadQuery } from "react-relay";
|
||||
import { graphql } from "react-relay";
|
||||
import { Spinner } from "@probo/ui";
|
||||
import type { PublicTrustCenterPageQuery } from "./__generated__/PublicTrustCenterPageQuery.graphql";
|
||||
|
||||
export interface TrustCenterDocument {
|
||||
id: string;
|
||||
title: string;
|
||||
documentType: string;
|
||||
}
|
||||
|
||||
export interface TrustCenterAudit {
|
||||
id: string;
|
||||
framework: {
|
||||
name: string;
|
||||
};
|
||||
report: {
|
||||
id: string;
|
||||
filename: string;
|
||||
downloadUrl: string | null;
|
||||
} | null;
|
||||
}
|
||||
|
||||
export interface TrustCenterVendor {
|
||||
id: string;
|
||||
name: string;
|
||||
category: string;
|
||||
privacyPolicyUrl?: string | null;
|
||||
websiteUrl?: string | null;
|
||||
}
|
||||
|
||||
const PublicTrustCenterQuery = graphql`
|
||||
query PublicTrustCenterPageQuery($slug: String!) {
|
||||
trustCenterBySlug(slug: $slug) {
|
||||
id
|
||||
active
|
||||
slug
|
||||
organization {
|
||||
id
|
||||
name
|
||||
logoUrl
|
||||
}
|
||||
documents(first: 100) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
title
|
||||
documentType
|
||||
}
|
||||
}
|
||||
}
|
||||
audits(first: 100) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
framework {
|
||||
name
|
||||
}
|
||||
report {
|
||||
id
|
||||
filename
|
||||
downloadUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
vendors(first: 100) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
category
|
||||
websiteUrl
|
||||
privacyPolicyUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
function PublicTrustCenterContent() {
|
||||
const { __ } = useTranslate();
|
||||
const { slug } = useParams<{ slug: string }>();
|
||||
const { isAuthenticated } = useTrustAuth();
|
||||
|
||||
if (!slug) {
|
||||
return <Navigate to="/" replace />;
|
||||
}
|
||||
|
||||
const data = useLazyLoadQuery<PublicTrustCenterPageQuery>(PublicTrustCenterQuery, { slug });
|
||||
|
||||
const organization = data?.trustCenterBySlug?.organization;
|
||||
const organizationName = organization?.name || "";
|
||||
|
||||
usePageTitle(
|
||||
organizationName ? `${organizationName} - Trust Center` : "Trust Center"
|
||||
);
|
||||
|
||||
if (!data?.trustCenterBySlug) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<div className="text-center">
|
||||
<h1 className="text-2xl font-bold text-gray-900 mb-2">
|
||||
{__("Trust Center Not Found")}
|
||||
</h1>
|
||||
<p className="text-gray-600">
|
||||
{__("The trust center you're looking for doesn't exist.")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const { documents, audits, vendors } = data.trustCenterBySlug;
|
||||
|
||||
const trustCenterDocuments = documents.edges.map((edge) => edge.node) as TrustCenterDocument[];
|
||||
const trustCenterAudits = audits.edges.map((edge) => edge.node) as TrustCenterAudit[];
|
||||
const trustCenterVendors = vendors.edges.map((edge) => edge.node) as TrustCenterVendor[];
|
||||
|
||||
return (
|
||||
<PublicTrustCenterLayout
|
||||
organizationName={organizationName}
|
||||
organizationLogo={organization?.logoUrl}
|
||||
isAuthenticated={isAuthenticated}
|
||||
>
|
||||
<div className="space-y-12">
|
||||
<PublicTrustCenterAudits
|
||||
audits={trustCenterAudits}
|
||||
organizationName={organizationName}
|
||||
isAuthenticated={isAuthenticated}
|
||||
trustCenterId={data.trustCenterBySlug.id}
|
||||
/>
|
||||
<PublicTrustCenterDocuments
|
||||
documents={trustCenterDocuments}
|
||||
organizationName={organizationName}
|
||||
isAuthenticated={isAuthenticated}
|
||||
trustCenterId={data.trustCenterBySlug.id}
|
||||
/>
|
||||
<PublicTrustCenterVendors
|
||||
vendors={trustCenterVendors}
|
||||
organizationName={organizationName}
|
||||
/>
|
||||
</div>
|
||||
</PublicTrustCenterLayout>
|
||||
);
|
||||
}
|
||||
|
||||
export default function PublicTrustCenterPage() {
|
||||
return (
|
||||
<TrustRelayProvider>
|
||||
<Suspense fallback={<Spinner />}>
|
||||
<PublicTrustCenterContent />
|
||||
</Suspense>
|
||||
</TrustRelayProvider>
|
||||
);
|
||||
}
|
||||
430
apps/console/src/trust/pages/__generated__/PublicTrustCenterPageQuery.graphql.ts
generated
Normal file
430
apps/console/src/trust/pages/__generated__/PublicTrustCenterPageQuery.graphql.ts
generated
Normal file
@@ -0,0 +1,430 @@
|
||||
/**
|
||||
* @generated SignedSource<<7510cf085d283274b579e0571eb503c4>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type DocumentType = "ISMS" | "OTHER" | "POLICY";
|
||||
export type VendorCategory = "ANALYTICS" | "CLOUD_MONITORING" | "CLOUD_PROVIDER" | "COLLABORATION" | "CUSTOMER_SUPPORT" | "DATA_STORAGE_AND_PROCESSING" | "DOCUMENT_MANAGEMENT" | "EMPLOYEE_MANAGEMENT" | "ENGINEERING" | "FINANCE" | "IDENTITY_PROVIDER" | "IT" | "MARKETING" | "OFFICE_OPERATIONS" | "OTHER" | "PASSWORD_MANAGEMENT" | "PRODUCT_AND_DESIGN" | "PROFESSIONAL_SERVICES" | "RECRUITING" | "SALES" | "SECURITY" | "VERSION_CONTROL";
|
||||
export type PublicTrustCenterPageQuery$variables = {
|
||||
slug: string;
|
||||
};
|
||||
export type PublicTrustCenterPageQuery$data = {
|
||||
readonly trustCenterBySlug: {
|
||||
readonly active: boolean;
|
||||
readonly audits: {
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly framework: {
|
||||
readonly name: string;
|
||||
};
|
||||
readonly id: string;
|
||||
readonly report: {
|
||||
readonly downloadUrl: string | null | undefined;
|
||||
readonly filename: string;
|
||||
readonly id: string;
|
||||
} | null | undefined;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
readonly documents: {
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly documentType: DocumentType;
|
||||
readonly id: string;
|
||||
readonly title: string;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
readonly id: string;
|
||||
readonly organization: {
|
||||
readonly id: string;
|
||||
readonly logoUrl: string | null | undefined;
|
||||
readonly name: string;
|
||||
};
|
||||
readonly slug: string;
|
||||
readonly vendors: {
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly category: VendorCategory;
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly privacyPolicyUrl: string | null | undefined;
|
||||
readonly websiteUrl: string | null | undefined;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
} | null | undefined;
|
||||
};
|
||||
export type PublicTrustCenterPageQuery = {
|
||||
response: PublicTrustCenterPageQuery$data;
|
||||
variables: PublicTrustCenterPageQuery$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = [
|
||||
{
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "slug"
|
||||
}
|
||||
],
|
||||
v1 = [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "slug",
|
||||
"variableName": "slug"
|
||||
}
|
||||
],
|
||||
v2 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
v3 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "active",
|
||||
"storageKey": null
|
||||
},
|
||||
v4 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "slug",
|
||||
"storageKey": null
|
||||
},
|
||||
v5 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "name",
|
||||
"storageKey": null
|
||||
},
|
||||
v6 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Organization",
|
||||
"kind": "LinkedField",
|
||||
"name": "organization",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
(v5/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "logoUrl",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
v7 = [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 100
|
||||
}
|
||||
],
|
||||
v8 = {
|
||||
"alias": null,
|
||||
"args": (v7/*: any*/),
|
||||
"concreteType": "DocumentConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "documents",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "DocumentEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Document",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "title",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "documentType",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": "documents(first:100)"
|
||||
},
|
||||
v9 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Report",
|
||||
"kind": "LinkedField",
|
||||
"name": "report",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "filename",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "downloadUrl",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
v10 = {
|
||||
"alias": null,
|
||||
"args": (v7/*: any*/),
|
||||
"concreteType": "VendorConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "vendors",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "VendorEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Vendor",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
(v5/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "category",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "websiteUrl",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "privacyPolicyUrl",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": "vendors(first:100)"
|
||||
};
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "PublicTrustCenterPageQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": "TrustCenter",
|
||||
"kind": "LinkedField",
|
||||
"name": "trustCenterBySlug",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
(v3/*: any*/),
|
||||
(v4/*: any*/),
|
||||
(v6/*: any*/),
|
||||
(v8/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v7/*: any*/),
|
||||
"concreteType": "AuditConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "audits",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "AuditEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Audit",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Framework",
|
||||
"kind": "LinkedField",
|
||||
"name": "framework",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v5/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v9/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": "audits(first:100)"
|
||||
},
|
||||
(v10/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Query",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "PublicTrustCenterPageQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": "TrustCenter",
|
||||
"kind": "LinkedField",
|
||||
"name": "trustCenterBySlug",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
(v3/*: any*/),
|
||||
(v4/*: any*/),
|
||||
(v6/*: any*/),
|
||||
(v8/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v7/*: any*/),
|
||||
"concreteType": "AuditConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "audits",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "AuditEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Audit",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Framework",
|
||||
"kind": "LinkedField",
|
||||
"name": "framework",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v5/*: any*/),
|
||||
(v2/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v9/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": "audits(first:100)"
|
||||
},
|
||||
(v10/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "9d0437e87c7c88083619611892bcc422",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "PublicTrustCenterPageQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query PublicTrustCenterPageQuery(\n $slug: String!\n) {\n trustCenterBySlug(slug: $slug) {\n id\n active\n slug\n organization {\n id\n name\n logoUrl\n }\n documents(first: 100) {\n edges {\n node {\n id\n title\n documentType\n }\n }\n }\n audits(first: 100) {\n edges {\n node {\n id\n framework {\n name\n id\n }\n report {\n id\n filename\n downloadUrl\n }\n }\n }\n }\n vendors(first: 100) {\n edges {\n node {\n id\n name\n category\n websiteUrl\n privacyPolicyUrl\n }\n }\n }\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "7fc4b49f2a965be237cf3d51baa815bd";
|
||||
|
||||
export default node;
|
||||
@@ -102,6 +102,9 @@ func (s TrustCenterAccessService) Create(
|
||||
err := existingAccess.LoadByTrustCenterIDAndEmail(ctx, tx, s.svc.scope, req.TrustCenterID, req.Email)
|
||||
|
||||
if err == nil {
|
||||
if existingAccess.Active {
|
||||
return fmt.Errorf("active trust center access already exists for this email")
|
||||
}
|
||||
if err := existingAccess.Delete(ctx, tx, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot delete existing trust center access: %w", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user