@@ -176,3 +176,4 @@ goreleaser-snapshot: ## Build a snapshot release with goreleaser
|
||||
.PHONY: goreleaser-check
|
||||
goreleaser-check: ## Check goreleaser configuration
|
||||
goreleaser check
|
||||
|
||||
|
||||
@@ -1,54 +1 @@
|
||||
# React + TypeScript + Vite
|
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||
|
||||
Currently, two official plugins are available:
|
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
|
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||
|
||||
## Expanding the ESLint configuration
|
||||
|
||||
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
||||
|
||||
```js
|
||||
export default tseslint.config({
|
||||
extends: [
|
||||
// Remove ...tseslint.configs.recommended and replace with this
|
||||
...tseslint.configs.recommendedTypeChecked,
|
||||
// Alternatively, use this for stricter rules
|
||||
...tseslint.configs.strictTypeChecked,
|
||||
// Optionally, add this for stylistic rules
|
||||
...tseslint.configs.stylisticTypeChecked,
|
||||
],
|
||||
languageOptions: {
|
||||
// other options...
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
||||
|
||||
```js
|
||||
// eslint.config.js
|
||||
import reactX from 'eslint-plugin-react-x'
|
||||
import reactDom from 'eslint-plugin-react-dom'
|
||||
|
||||
export default tseslint.config({
|
||||
plugins: {
|
||||
// Add the react-x and react-dom plugins
|
||||
'react-x': reactX,
|
||||
'react-dom': reactDom,
|
||||
},
|
||||
rules: {
|
||||
// other rules...
|
||||
// Enable its recommended typescript rules
|
||||
...reactX.configs['recommended-typescript'].rules,
|
||||
...reactDom.configs.recommended.rules,
|
||||
},
|
||||
})
|
||||
```
|
||||
# Console app
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { useForm } from "react-hook-form";
|
||||
import type { z, ZodType } from "zod";
|
||||
import type { z, ZodTypeAny } from "zod";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
|
||||
export function useFormWithSchema<T extends ZodType<any, any, any>>(
|
||||
export function useFormWithSchema<T extends ZodTypeAny>(
|
||||
schema: T,
|
||||
options: Parameters<typeof useForm<z.infer<T>>>[0]
|
||||
options: Parameters<typeof useForm<z.infer<T>>>[0],
|
||||
) {
|
||||
return useForm<z.infer<T>>({
|
||||
...options,
|
||||
|
||||
1
apps/trust/.env.example
Normal file
@@ -0,0 +1 @@
|
||||
VITE_API_URL=http://localhost:8080
|
||||
2
apps/trust/.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
__generated__/*.graphql linguist-generated
|
||||
__generated__/*.js linguist-generated
|
||||
26
apps/trust/.gitignore
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
public/pdfjs
|
||||
# Logs
|
||||
old
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
2
apps/trust/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# TrustCenter app
|
||||
|
||||
28
apps/trust/eslint.config.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import js from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
import tseslint from 'typescript-eslint'
|
||||
|
||||
export default tseslint.config(
|
||||
{ ignores: ['dist'] },
|
||||
{
|
||||
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
},
|
||||
plugins: {
|
||||
'react-hooks': reactHooks,
|
||||
'react-refresh': reactRefresh,
|
||||
},
|
||||
rules: {
|
||||
...reactHooks.configs.recommended.rules,
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
},
|
||||
)
|
||||
5
apps/trust/graphql.config.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
schema: '../../pkg/server/api/trust/v1/schema.graphql'
|
||||
extensions:
|
||||
endpoints:
|
||||
TrustCenter:
|
||||
url: http://localhost:8080/api/trust/v1/graphql
|
||||
37
apps/trust/index.html
Normal file
@@ -0,0 +1,37 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
|
||||
<link rel="apple-touch-icon-precomposed" sizes="57x57" href="/favicons/apple-touch-icon-57x57.png" />
|
||||
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="/favicons/apple-touch-icon-114x114.png" />
|
||||
<link rel="apple-touch-icon-precomposed" sizes="72x72" href="/favicons/apple-touch-icon-72x72.png" />
|
||||
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="/favicons/apple-touch-icon-144x144.png" />
|
||||
<link rel="apple-touch-icon-precomposed" sizes="60x60" href="/favicons/apple-touch-icon-60x60.png" />
|
||||
<link rel="apple-touch-icon-precomposed" sizes="120x120" href="/favicons/apple-touch-icon-120x120.png" />
|
||||
<link rel="apple-touch-icon-precomposed" sizes="76x76" href="/favicons/apple-touch-icon-76x76.png" />
|
||||
<link rel="apple-touch-icon-precomposed" sizes="152x152" href="/favicons/apple-touch-icon-152x152.png" />
|
||||
<link rel="icon" type="image/png" href="/favicons/favicon-196x196.png" sizes="196x196" />
|
||||
<link rel="icon" type="image/png" href="/favicons/favicon-96x96.png" sizes="96x96" />
|
||||
<link rel="icon" type="image/png" href="/favicons/favicon-32x32.png" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="/favicons/favicon-16x16.png" sizes="16x16" />
|
||||
<link rel="icon" type="image/png" href="/favicons/favicon-128.png" sizes="128x128" />
|
||||
<meta name="application-name" content=" " />
|
||||
<meta name="msapplication-TileColor" content="#FFFFFF" />
|
||||
<meta name="msapplication-TileImage" content="/favicons/mstile-144x144.png" />
|
||||
<meta name="msapplication-square70x70logo" content="/favicons/mstile-70x70.png" />
|
||||
<meta name="msapplication-square150x150logo" content="/favicons/mstile-150x150.png" />
|
||||
<meta name="msapplication-wide310x150logo" content="/favicons/mstile-310x150.png" />
|
||||
<meta name="msapplication-square310x310logo" content="/favicons/mstile-310x310.png" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Trust Center</title>
|
||||
</head>
|
||||
|
||||
<body class="text-txt-primary bg-level-0">
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
57
apps/trust/package.json
Normal file
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"name": "@probo/trust",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"relay": "find src -type d -name \"__generated__\" -exec rm -rf {} + && npx relay-compiler ./relay.config.json",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint .",
|
||||
"check": "tsc --noEmit -p tsconfig.app.json",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^5.0.1",
|
||||
"@probo/helpers": "1.0.0",
|
||||
"@probo/hooks": "1.0.0",
|
||||
"@probo/i18n": "1.0.0",
|
||||
"@probo/ui": "1.0.0",
|
||||
"clsx": "^2.1.1",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-error-boundary": "^6.0.0",
|
||||
"react-hook-form": "^7.56.4",
|
||||
"react-pdf": "^10.1.0",
|
||||
"react-relay": "^19.0.0",
|
||||
"react-router": "^7.6.0",
|
||||
"relay-runtime": "^19.0.0",
|
||||
"usehooks-ts": "^3.1.1",
|
||||
"zod": "^3.25.71"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.25.0",
|
||||
"@tailwindcss/vite": "^4.1.7",
|
||||
"@types/node": "^22.15.21",
|
||||
"@types/react": "^19.1.2",
|
||||
"@types/react-dom": "^19.1.2",
|
||||
"@types/react-relay": "^18.2.1",
|
||||
"@types/relay-runtime": "^19.0.1",
|
||||
"@vitejs/plugin-react": "^4.4.1",
|
||||
"babel-plugin-relay": "^19.0.0",
|
||||
"eslint": "^9.25.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.19",
|
||||
"globals": "^16.0.0",
|
||||
"graphql": "^16.11.0",
|
||||
"prettier": "^3.5.3",
|
||||
"relay-compiler": "^19.0.0",
|
||||
"tailwindcss": "^4.1.7",
|
||||
"typescript": "~5.8.3",
|
||||
"typescript-eslint": "^8.30.1",
|
||||
"vite": "^6.3.6"
|
||||
},
|
||||
"volta": {
|
||||
"node": "24.4.0"
|
||||
}
|
||||
}
|
||||
BIN
apps/trust/public/favicons/apple-touch-icon-114x114.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
apps/trust/public/favicons/apple-touch-icon-120x120.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
apps/trust/public/favicons/apple-touch-icon-144x144.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
apps/trust/public/favicons/apple-touch-icon-152x152.png
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
apps/trust/public/favicons/apple-touch-icon-57x57.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
apps/trust/public/favicons/apple-touch-icon-60x60.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
apps/trust/public/favicons/apple-touch-icon-72x72.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
apps/trust/public/favicons/apple-touch-icon-76x76.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
apps/trust/public/favicons/favicon-128.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
apps/trust/public/favicons/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 625 B |
BIN
apps/trust/public/favicons/favicon-196x196.png
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
BIN
apps/trust/public/favicons/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
apps/trust/public/favicons/favicon-96x96.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
apps/trust/public/favicons/favicon.ico
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
apps/trust/public/favicons/mstile-144x144.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
apps/trust/public/favicons/mstile-150x150.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
apps/trust/public/favicons/mstile-310x150.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
apps/trust/public/favicons/mstile-310x310.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
apps/trust/public/favicons/mstile-70x70.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
apps/trust/public/logos/gdpr.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
apps/trust/public/logos/hipaa.png
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
apps/trust/public/logos/iso27001.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
apps/trust/public/logos/soc2.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
apps/trust/public/logos/sox.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
2
apps/trust/public/robots.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
User-agent: *
|
||||
Disallow: /
|
||||
7
apps/trust/relay.config.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"src": "./src",
|
||||
"schema": "../../pkg/server/api/trust/v1/schema.graphql",
|
||||
"language": "typescript",
|
||||
"eagerEsModules": true,
|
||||
"noFutureProofEnums": true
|
||||
}
|
||||
12
apps/trust/src/App.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { RouterProvider } from "react-router";
|
||||
import { router } from "./routes";
|
||||
import { Toasts } from "@probo/ui";
|
||||
|
||||
export function App() {
|
||||
return (
|
||||
<>
|
||||
<RouterProvider router={router} />
|
||||
<Toasts />
|
||||
</>
|
||||
);
|
||||
}
|
||||
153
apps/trust/src/components/AuditRow.tsx
Normal file
@@ -0,0 +1,153 @@
|
||||
import { graphql } from "relay-runtime";
|
||||
import type { AuditRowFragment$key } from "./__generated__/AuditRowFragment.graphql";
|
||||
import { useFragment } from "react-relay";
|
||||
import {
|
||||
Breadcrumb,
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
FrameworkLogo,
|
||||
IconArrowInbox,
|
||||
IconLock,
|
||||
IconMedal,
|
||||
Spinner,
|
||||
Table,
|
||||
} from "@probo/ui";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useIsAuthenticated } from "/hooks/useIsAuthenticated";
|
||||
import type { AuditRowDownloadMutation } from "./__generated__/AuditRowDownloadMutation.graphql";
|
||||
import { useMutationWithToasts } from "/hooks/useMutationWithToast";
|
||||
import { downloadFile } from "@probo/helpers";
|
||||
import type { PropsWithChildren } from "react";
|
||||
import { useLocation } from "react-router";
|
||||
import { RequestAccessDialog } from "/components/RequestAccessDialog.tsx";
|
||||
|
||||
const downloadMutation = graphql`
|
||||
mutation AuditRowDownloadMutation($input: ExportReportPDFInput!) {
|
||||
exportReportPDF(input: $input) {
|
||||
data
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const auditRowFragment = graphql`
|
||||
fragment AuditRowFragment on Audit {
|
||||
report {
|
||||
id
|
||||
filename
|
||||
}
|
||||
framework {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export function AuditRow(props: { audit: AuditRowFragment$key }) {
|
||||
const audit = useFragment(auditRowFragment, props.audit);
|
||||
const { __ } = useTranslate();
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
const [commitDownload, downloading] =
|
||||
useMutationWithToasts<AuditRowDownloadMutation>(downloadMutation);
|
||||
const handleDownload = () => {
|
||||
if (!audit.report?.id) {
|
||||
return;
|
||||
}
|
||||
commitDownload({
|
||||
variables: {
|
||||
input: {
|
||||
reportId: audit.report.id,
|
||||
},
|
||||
},
|
||||
onSuccess(response) {
|
||||
downloadFile(response.exportReportPDF.data, audit.report!.filename);
|
||||
},
|
||||
});
|
||||
};
|
||||
return (
|
||||
<div className="text-sm border-1 border-border-solid -mt-[1px] flex gap-3 flex-col md:flex-row md:justify-between px-6 py-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<IconMedal size={16} className="flex-none" />
|
||||
{audit.framework.name}
|
||||
</div>
|
||||
{audit.report ? (
|
||||
isAuthenticated ? (
|
||||
<Button
|
||||
className="w-full md:w-max"
|
||||
variant="secondary"
|
||||
disabled={downloading}
|
||||
icon={downloading ? Spinner : IconArrowInbox}
|
||||
onClick={handleDownload}
|
||||
>
|
||||
{__("Download")}
|
||||
</Button>
|
||||
) : (
|
||||
<RequestAccessDialog>
|
||||
<Button
|
||||
className="w-full md:w-max"
|
||||
variant="secondary"
|
||||
icon={IconLock}
|
||||
>
|
||||
{__("Request access")}
|
||||
</Button>
|
||||
</RequestAccessDialog>
|
||||
)
|
||||
) : (
|
||||
<span className=" text-txt-secondary">{__("No report")}</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function AuditRowAvatar(props: { audit: AuditRowFragment$key }) {
|
||||
const audit = useFragment(auditRowFragment, props.audit);
|
||||
return (
|
||||
<AuditDialog audit={props.audit}>
|
||||
<button className="block cursor-pointer aspect-square">
|
||||
<FrameworkLogo
|
||||
alt={audit.framework.name}
|
||||
name={audit.framework.name}
|
||||
className="size-full"
|
||||
/>
|
||||
</button>
|
||||
</AuditDialog>
|
||||
);
|
||||
}
|
||||
|
||||
function AuditDialog(
|
||||
props: PropsWithChildren<{ audit: AuditRowFragment$key }>,
|
||||
) {
|
||||
const audit = useFragment(auditRowFragment, props.audit);
|
||||
const location = useLocation();
|
||||
const { __ } = useTranslate();
|
||||
const items = [
|
||||
{
|
||||
label: __("Certifications"),
|
||||
to: location.pathname,
|
||||
},
|
||||
{
|
||||
label: audit.framework.name,
|
||||
to: location.pathname,
|
||||
},
|
||||
];
|
||||
return (
|
||||
<Dialog
|
||||
trigger={props.children}
|
||||
className="max-w-[500px]"
|
||||
title={<Breadcrumb items={items} />}
|
||||
>
|
||||
<DialogContent className="p-4 lg:p-8 space-y-6">
|
||||
<FrameworkLogo
|
||||
alt={audit.framework.name}
|
||||
name={audit.framework.name}
|
||||
className="size-24 block mx-auto"
|
||||
/>
|
||||
<h2 className="text-xl font-semibold mb-1">{audit.framework.name}</h2>
|
||||
<p className="text-txt-secondary">Framework description</p>
|
||||
<Table>
|
||||
<AuditRow audit={props.audit} />
|
||||
</Table>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
80
apps/trust/src/components/DocumentRow.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import { graphql } from "relay-runtime";
|
||||
import type { DocumentRowFragment$key } from "./__generated__/DocumentRowFragment.graphql";
|
||||
import { useFragment } from "react-relay";
|
||||
import {
|
||||
Button,
|
||||
IconArrowInbox,
|
||||
IconLock,
|
||||
IconPageTextLine,
|
||||
Spinner,
|
||||
} from "@probo/ui";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useIsAuthenticated } from "/hooks/useIsAuthenticated";
|
||||
import type { DocumentRowDownloadMutation } from "./__generated__/DocumentRowDownloadMutation.graphql";
|
||||
import { useMutationWithToasts } from "/hooks/useMutationWithToast";
|
||||
import { downloadFile } from "@probo/helpers";
|
||||
import { RequestAccessDialog } from "/components/RequestAccessDialog.tsx";
|
||||
|
||||
const downloadMutation = graphql`
|
||||
mutation DocumentRowDownloadMutation($input: ExportDocumentPDFInput!) {
|
||||
exportDocumentPDF(input: $input) {
|
||||
data
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const documentRowFragment = graphql`
|
||||
fragment DocumentRowFragment on Document {
|
||||
id
|
||||
title
|
||||
}
|
||||
`;
|
||||
|
||||
export function DocumentRow(props: { document: DocumentRowFragment$key }) {
|
||||
const document = useFragment(documentRowFragment, props.document);
|
||||
const { __ } = useTranslate();
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
const [commitDownload, downloading] =
|
||||
useMutationWithToasts<DocumentRowDownloadMutation>(downloadMutation);
|
||||
const handleDownload = () => {
|
||||
commitDownload({
|
||||
variables: {
|
||||
input: {
|
||||
documentId: document.id,
|
||||
},
|
||||
},
|
||||
onSuccess(response) {
|
||||
downloadFile(response.exportDocumentPDF.data, document.title);
|
||||
},
|
||||
});
|
||||
};
|
||||
return (
|
||||
<div className="text-sm border-1 border-border-solid -mt-[1px] flex gap-3 flex-col md:flex-row md:justify-between px-6 py-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<IconPageTextLine size={16} className=" flex-none" />
|
||||
{document.title}
|
||||
</div>
|
||||
{isAuthenticated ? (
|
||||
<Button
|
||||
className="w-full md:w-max"
|
||||
variant="secondary"
|
||||
disabled={downloading}
|
||||
icon={downloading ? Spinner : IconArrowInbox}
|
||||
onClick={handleDownload}
|
||||
>
|
||||
{__("Download")}
|
||||
</Button>
|
||||
) : (
|
||||
<RequestAccessDialog>
|
||||
<Button
|
||||
className="w-full md:w-max"
|
||||
variant="secondary"
|
||||
icon={IconLock}
|
||||
>
|
||||
{__("Request access")}
|
||||
</Button>
|
||||
</RequestAccessDialog>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
115
apps/trust/src/components/NDADialog.tsx
Normal file
@@ -0,0 +1,115 @@
|
||||
import { Button, Card, Logo, Spinner } from "@probo/ui";
|
||||
import { sprintf } from "@probo/helpers";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useEffect } from "react";
|
||||
import { PDFPreview } from "./PDFPreview";
|
||||
import { useWindowSize } from "usehooks-ts";
|
||||
import clsx from "clsx";
|
||||
import { graphql } from "relay-runtime";
|
||||
import { useMutationWithToasts } from "/hooks/useMutationWithToast";
|
||||
|
||||
const signMutation = graphql`
|
||||
mutation NDADialogSignMutation($input: AcceptNonDisclosureAgreementInput!) {
|
||||
acceptNonDisclosureAgreement(input: $input) {
|
||||
success
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export function NDADialog({
|
||||
name,
|
||||
url,
|
||||
fileName,
|
||||
trustCenterId,
|
||||
}: {
|
||||
name: string;
|
||||
url?: string | null;
|
||||
fileName?: string | null;
|
||||
trustCenterId: string;
|
||||
}) {
|
||||
const { __ } = useTranslate();
|
||||
useEffect(() => {
|
||||
document.body.style.setProperty("overflow", "hidden");
|
||||
return () => {
|
||||
document.body.style.removeProperty("overflow");
|
||||
};
|
||||
}, []);
|
||||
const { width } = useWindowSize();
|
||||
const isMobile = width < 1100;
|
||||
const isDesktop = !isMobile;
|
||||
const [commitSigning, isSigning] = useMutationWithToasts(signMutation, {
|
||||
onSuccess: () => {
|
||||
window.location.reload();
|
||||
},
|
||||
});
|
||||
|
||||
const handleSign = () => {
|
||||
commitSigning({
|
||||
variables: {
|
||||
input: {
|
||||
trustCenterId,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-level-2 z-100 flex flex-col lg:h-screen">
|
||||
<header className="flex items-center h-12 justify-between border-b border-border-solid px-4 flex-none">
|
||||
<Logo />
|
||||
</header>
|
||||
<div className="grid lg:grid-cols-2 min-h-0 h-full">
|
||||
<div className="max-w-[440px] mx-auto py-20">
|
||||
<h1 className="text-2xl font-semibold mb-4">
|
||||
{__("Review & Sign NDA")}
|
||||
</h1>
|
||||
<p className="text-txt-secondary">
|
||||
{sprintf(
|
||||
__(
|
||||
"Access to %s Trust Center documents requires signing a Non-Disclosure Agreement (NDA). Please review the agreement below. Once signed, you’ll receive immediate access to the requested documents.",
|
||||
),
|
||||
name,
|
||||
)}
|
||||
</p>
|
||||
{isMobile && url && (
|
||||
<Card className="flex justify-between py-3 px-4 text-sm items-center my-6">
|
||||
{fileName}
|
||||
<Button variant="secondary" asChild>
|
||||
<a target="_blank" rel="noopener noreferrer" href={url}>
|
||||
{__("View document")}
|
||||
</a>
|
||||
</Button>
|
||||
</Card>
|
||||
)}
|
||||
<Button
|
||||
onClick={handleSign}
|
||||
className="h-10 w-full my-8"
|
||||
disabled={isSigning}
|
||||
icon={isSigning ? Spinner : undefined}
|
||||
>
|
||||
{__("Review & Sign")}
|
||||
</Button>
|
||||
<p className="text-xs text-txt-secondary">
|
||||
{__(
|
||||
"By clicking Review & Sign, you agree to the terms of this NDA. If you have questions about the NDA, please contact security@probo.com.",
|
||||
)}
|
||||
</p>
|
||||
<a
|
||||
href="https://www.getprobo.com/"
|
||||
className={clsx(
|
||||
"flex gap-1 text-sm font-medium text-txt-tertiary items-center w-max mx-auto",
|
||||
isMobile ? "mt-15" : "mt-30",
|
||||
)}
|
||||
>
|
||||
Powered by <Logo withPicto className="h-6" />
|
||||
</a>
|
||||
</div>
|
||||
{isDesktop && (
|
||||
<div className="bg-subtle h-full border-l border-border-solid min-h-0">
|
||||
{url && <PDFPreview src={url} name={fileName ?? ""} />}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
125
apps/trust/src/components/OrganizationSidebar.tsx
Normal file
@@ -0,0 +1,125 @@
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { Button, Card, IconBlock, IconLock, IconMedal } from "@probo/ui";
|
||||
import type { TrustGraphQuery$data } from "/queries/__generated__/TrustGraphQuery.graphql";
|
||||
import type { PropsWithChildren } from "react";
|
||||
import { domain } from "@probo/helpers";
|
||||
import { AuditRowAvatar } from "./AuditRow";
|
||||
import { RequestAccessDialog } from "./RequestAccessDialog";
|
||||
import { useIsAuthenticated } from "/hooks/useIsAuthenticated";
|
||||
|
||||
export function OrganizationSidebar({
|
||||
trustCenter,
|
||||
}: {
|
||||
trustCenter: TrustGraphQuery$data["trustCenterBySlug"];
|
||||
}) {
|
||||
const { __ } = useTranslate();
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
|
||||
if (!trustCenter) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="p-6 relative overflow-hidden border-b-1 border-border-low isolate">
|
||||
<div className="h-21 bg-[#044E4114] absolute top-0 left-0 right-0 -z-1"></div>
|
||||
{trustCenter.organization.logoUrl ? (
|
||||
<img
|
||||
alt=""
|
||||
src={trustCenter.organization.logoUrl}
|
||||
className="size-24 rounded-2xl border border-border-mid shadow-mid"
|
||||
/>
|
||||
) : (
|
||||
<div className="size-24 rounded-2xl border border-border-mid bg-level-1 shadow-mid" />
|
||||
)}
|
||||
<h1 className="text-2xl mt-6">{trustCenter.organization.name}</h1>
|
||||
<p className="text-sm text-txt-secondary mt-1">
|
||||
{trustCenter.organization.description}
|
||||
</p>
|
||||
|
||||
<hr className="my-6 -mx-6 h-[1px] bg-border-low border-none" />
|
||||
|
||||
{/* Business information */}
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-xs text-txt-secondary flex gap-1 items-center">
|
||||
<IconBlock size={16} />
|
||||
{__("Business information")}
|
||||
</h2>
|
||||
{trustCenter.organization.websiteUrl && (
|
||||
<BusinessInfo label={__("Website")}>
|
||||
<a
|
||||
href={trustCenter.organization.websiteUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span className="text-txt-info hover:underline ">
|
||||
{domain(trustCenter.organization.websiteUrl)}
|
||||
</span>
|
||||
</a>
|
||||
</BusinessInfo>
|
||||
)}
|
||||
{trustCenter.organization.email && (
|
||||
<BusinessInfo label={__("Contact")}>
|
||||
{trustCenter.organization.email}
|
||||
</BusinessInfo>
|
||||
)}
|
||||
{trustCenter.organization.headquarterAddress && (
|
||||
<BusinessInfo label={__("HQ address")}>
|
||||
{trustCenter.organization.headquarterAddress}
|
||||
</BusinessInfo>
|
||||
)}
|
||||
|
||||
<hr className="my-6 -mx-6 h-[1px] bg-border-low border-none" />
|
||||
|
||||
{/* Certifications */}
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-xs text-txt-secondary flex gap-1 items-center">
|
||||
<IconMedal size={16} />
|
||||
{__("Certifications")}
|
||||
</h2>
|
||||
<div
|
||||
className="grid grid-cols-4 gap-4"
|
||||
style={{
|
||||
gridTemplateColumns: "repeat(auto-fit, 75px",
|
||||
}}
|
||||
>
|
||||
{trustCenter.audits.edges.map((audit) => (
|
||||
<AuditRowAvatar key={audit.node.id} audit={audit.node} />
|
||||
))}
|
||||
{trustCenter.audits.edges.map((audit) => (
|
||||
<AuditRowAvatar key={audit.node.id} audit={audit.node} />
|
||||
))}
|
||||
{trustCenter.audits.edges.map((audit) => (
|
||||
<AuditRowAvatar key={audit.node.id} audit={audit.node} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr className="my-6 -mx-6 h-[1px] bg-border-low border-none" />
|
||||
|
||||
{/* Actions */}
|
||||
{!isAuthenticated && (
|
||||
<RequestAccessDialog>
|
||||
<Button variant="primary" icon={IconLock} className="w-full h-10">
|
||||
{__("Request access")}
|
||||
</Button>
|
||||
</RequestAccessDialog>
|
||||
)}
|
||||
{/* <Button variant="secondary" icon={IconMail} className="w-full h-10">
|
||||
{__("Subscribe to updates")}
|
||||
</Button>*/}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function BusinessInfo({
|
||||
children,
|
||||
label,
|
||||
}: PropsWithChildren<{ label: string }>) {
|
||||
return (
|
||||
<div>
|
||||
<div className="text-xs text-txt-secondary">{label}</div>
|
||||
<div className="text-sm">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
130
apps/trust/src/components/PDFPreview.tsx
Normal file
@@ -0,0 +1,130 @@
|
||||
import { Document, Page, pdfjs } from "react-pdf";
|
||||
import "react-pdf/dist/Page/TextLayer.css";
|
||||
import "react-pdf/dist/Page/AnnotationLayer.css";
|
||||
import { type ComponentProps, useRef, useState } from "react";
|
||||
import {
|
||||
IconArrowInbox,
|
||||
IconChevronLeft,
|
||||
IconChevronRight,
|
||||
IconPlusLarge,
|
||||
Spinner,
|
||||
} from "@probo/ui";
|
||||
import { times } from "@probo/helpers";
|
||||
import { IconMinusLarge } from "@probo/ui/src/Atoms/Icons/IconMinusLarge.tsx";
|
||||
|
||||
// Worker for PDF.js
|
||||
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`;
|
||||
|
||||
const btnClass =
|
||||
"size-8 grid place-items-center hover:bg-secondary-hover cursor-pointer rounded-sm disabled:opacity-30 transition-all";
|
||||
|
||||
export function PDFPreview({ src, name }: { src: string; name?: string }) {
|
||||
const [numPages, setNumPages] = useState(0);
|
||||
const [scale, setScale] = useState(1.0);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const documentRef: ComponentProps<typeof Document>["ref"] = useRef(null);
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const onDocumentLoadSuccess: ComponentProps<
|
||||
typeof Document
|
||||
>["onLoadSuccess"] = (document) => {
|
||||
setNumPages(document.numPages);
|
||||
setCurrentPage(1);
|
||||
};
|
||||
|
||||
const zoomFactor = (factor: number) => () => {
|
||||
setScale(scale * factor);
|
||||
};
|
||||
|
||||
const movePage = (direction: 1 | -1) => () => {
|
||||
if (currentPage === 1 && direction === -1) {
|
||||
return;
|
||||
}
|
||||
const newPage = currentPage + direction;
|
||||
const page = documentRef.current?.pages.current[newPage - 1];
|
||||
if (!page) {
|
||||
return;
|
||||
}
|
||||
page.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "start",
|
||||
inline: "center",
|
||||
});
|
||||
setCurrentPage(newPage);
|
||||
};
|
||||
|
||||
const resolveCurrentPage = () => {
|
||||
if (!wrapperRef.current) {
|
||||
return;
|
||||
}
|
||||
const pages = documentRef.current?.pages.current;
|
||||
if (!pages?.length) {
|
||||
return;
|
||||
}
|
||||
const parentRect = wrapperRef.current.getBoundingClientRect();
|
||||
const parentMiddleY = parentRect.top + parentRect.height / 2;
|
||||
for (let i = 0; i < pages.length; i++) {
|
||||
const childRect = pages[i].getBoundingClientRect();
|
||||
if (childRect.top <= parentMiddleY && childRect.bottom >= parentMiddleY) {
|
||||
return setCurrentPage(i + 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="grid grid-rows-[max-content_1fr] h-full bg-subtle">
|
||||
{/* Custom Zoom Controls */}
|
||||
<nav className="flex-none flex items-center gap-2 bg-level-1 py-3 text-sm pl-4 pr-3 text-txt-primary">
|
||||
<div>{name}</div>
|
||||
<div className="mx-auto flex gap-1 items-center">
|
||||
<button
|
||||
onClick={movePage(-1)}
|
||||
className={btnClass}
|
||||
disabled={currentPage === 1}
|
||||
>
|
||||
<IconChevronLeft size={16} />
|
||||
</button>
|
||||
<div>
|
||||
{currentPage} / {numPages}
|
||||
</div>
|
||||
<button onClick={movePage(1)} className={btnClass}>
|
||||
<IconChevronRight size={16} />
|
||||
</button>
|
||||
</div>
|
||||
<button onClick={zoomFactor(0.8)} className={btnClass}>
|
||||
<IconMinusLarge size={16} />
|
||||
</button>
|
||||
<button onClick={zoomFactor(1.2)} className={btnClass}>
|
||||
<IconPlusLarge size={16} />
|
||||
</button>
|
||||
<button onClick={zoomFactor(1.2)} className={btnClass}>
|
||||
<IconArrowInbox size={16} />
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
{/* PDF Document */}
|
||||
<div
|
||||
className="overflow-auto scroll-p-6"
|
||||
onScrollEnd={resolveCurrentPage}
|
||||
ref={wrapperRef}
|
||||
>
|
||||
<Document
|
||||
file={src}
|
||||
onLoadSuccess={onDocumentLoadSuccess}
|
||||
className="flex flex-col gap-4 py-10"
|
||||
ref={documentRef}
|
||||
>
|
||||
{numPages === 0 && <Spinner className="mx-auto" />}
|
||||
{times(numPages, (index) => (
|
||||
<Page
|
||||
className="w-max h-max mx-auto shadow-mid"
|
||||
key={index.toString()}
|
||||
pageNumber={index + 1}
|
||||
scale={scale} // Apply zoom via scale prop
|
||||
/>
|
||||
))}
|
||||
</Document>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
88
apps/trust/src/components/PageError.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import { useLocation, useRouteError } from "react-router";
|
||||
import { IconPageCross } from "@probo/ui";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
const classNames = {
|
||||
wrapper: "py-10 text-center space-y-2 ",
|
||||
title: "text-2xl flex gap-2 font-semibold items-center justify-center",
|
||||
description: "text-base text-txt-tertiary",
|
||||
detail:
|
||||
"text-sm text-txt-tertiary font-mono text-start border border-border-low p-2 rounded bg-level-1 mt-2",
|
||||
};
|
||||
|
||||
type Props = {
|
||||
resetErrorBoundary?: () => void;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
export function PageError({ resetErrorBoundary, error: propsError }: Props) {
|
||||
const error = useRouteError() ?? propsError;
|
||||
const { __ } = useTranslate();
|
||||
const location = useLocation();
|
||||
const baseLocation = useRef(location);
|
||||
|
||||
// Reset error boundary on page change
|
||||
useEffect(() => {
|
||||
if (
|
||||
location.pathname !== baseLocation.current.pathname &&
|
||||
resetErrorBoundary
|
||||
) {
|
||||
resetErrorBoundary();
|
||||
}
|
||||
}, [location, resetErrorBoundary]);
|
||||
|
||||
if (!error || (error && error.toString().includes("PAGE_NOT_FOUND"))) {
|
||||
return (
|
||||
<div className={classNames.wrapper}>
|
||||
<h1 className={classNames.title}>
|
||||
<IconPageCross size={26} />
|
||||
{__("Page not found")}
|
||||
</h1>
|
||||
<p className={classNames.description}>
|
||||
{__("The page you are looking for does not exist")}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
error
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.match(/(token|expired|invalid|401|unauthorized)/)
|
||||
) {
|
||||
const isExpiredToken = error.toString().toLowerCase().includes("expired");
|
||||
const title = isExpiredToken
|
||||
? __("Expired token")
|
||||
: __("Invalid Access Link");
|
||||
const description = isExpiredToken
|
||||
? __(
|
||||
"This access link has expired. Trust center access links are valid for 7 days for security reasons."
|
||||
)
|
||||
: __(
|
||||
"This access link is not valid. It may have been revoked or the link might be incorrect."
|
||||
);
|
||||
return (
|
||||
<div className={classNames.wrapper}>
|
||||
<h1 className={classNames.title}>
|
||||
<IconPageCross size={26} />
|
||||
{title}
|
||||
</h1>
|
||||
<p className={classNames.description}>{description}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames.wrapper}>
|
||||
<h1 className={classNames.title}>{__("Unexpected error :(")}</h1>
|
||||
<details>
|
||||
<summary className={classNames.description}>
|
||||
{__("Something went wrong")}
|
||||
</summary>
|
||||
<p className={classNames.detail}>{error.toString()}</p>
|
||||
</details>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
116
apps/trust/src/components/RequestAccessDialog.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogTitle,
|
||||
Field,
|
||||
useDialogRef,
|
||||
useToast,
|
||||
} from "@probo/ui";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { sprintf } from "@probo/helpers";
|
||||
import { z } from "zod";
|
||||
import { useFormWithSchema } from "/hooks/useFormWithSchema";
|
||||
import { graphql } from "relay-runtime";
|
||||
import { useMutationWithToasts } from "/hooks/useMutationWithToast";
|
||||
import { useTrustCenter } from "/hooks/useTrustCenter";
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const schema = z.object({
|
||||
name: z.string(),
|
||||
email: z.string().email(),
|
||||
});
|
||||
|
||||
const requestAccessMutation = graphql`
|
||||
mutation RequestAccessDialogMutation($input: CreateTrustCenterAccessInput!) {
|
||||
createTrustCenterAccess(input: $input) {
|
||||
trustCenterAccess {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export function RequestAccessDialog({ children }: Props) {
|
||||
const trustCenter = useTrustCenter();
|
||||
const { toast } = useToast();
|
||||
const { __ } = useTranslate();
|
||||
const { handleSubmit, register } = useFormWithSchema(schema, {
|
||||
defaultValues: {
|
||||
name: "",
|
||||
email: "",
|
||||
},
|
||||
});
|
||||
const dialogRef = useDialogRef();
|
||||
const [commitRequestAccess, isRequestingAccess] = useMutationWithToasts(
|
||||
requestAccessMutation,
|
||||
{
|
||||
onSuccess: () => {
|
||||
toast({
|
||||
title: __("Success"),
|
||||
description: __("Access request submitted successfully"),
|
||||
variant: "success",
|
||||
});
|
||||
dialogRef.current?.close();
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const onSubmit = handleSubmit((data) => {
|
||||
commitRequestAccess({
|
||||
variables: {
|
||||
input: {
|
||||
trustCenterId: trustCenter.id,
|
||||
name: data.name,
|
||||
email: data.email,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
return (
|
||||
<Dialog
|
||||
ref={dialogRef}
|
||||
trigger={children}
|
||||
className="max-w-[500px] text-txt-primary"
|
||||
>
|
||||
<form onSubmit={onSubmit}>
|
||||
<DialogTitle className="text-2xl font-semibold mb-4 pt-4 md:pt-8 px-4 md:px-8">
|
||||
{__("Request access to documentation")}
|
||||
</DialogTitle>
|
||||
<DialogContent className="px-4 md:px-8 pb-4 md:pb-8 text-txt-primary">
|
||||
<p className="text-txt-secondary mb-4">
|
||||
{sprintf(
|
||||
__(
|
||||
"Request access to %s's Trust Center. Your request will be reviewed and you will receive an email notification with access instructions if approved.",
|
||||
),
|
||||
trustCenter.organization.name,
|
||||
)}
|
||||
</p>
|
||||
<div className="space-y-4">
|
||||
<Field
|
||||
label={__("Full name")}
|
||||
placeholder="John Doe"
|
||||
{...register("name")}
|
||||
type="text"
|
||||
/>
|
||||
<Field
|
||||
label={__("Email")}
|
||||
placeholder="john.doe@acme.com"
|
||||
{...register("email")}
|
||||
type="email"
|
||||
/>
|
||||
</div>
|
||||
</DialogContent>
|
||||
<DialogFooter>
|
||||
<Button disabled={isRequestingAccess} type="submit">
|
||||
{__("Continue")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
9
apps/trust/src/components/RowHeader.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { PropsWithChildren } from "react";
|
||||
|
||||
export function RowHeader({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<div className="bg-subtle text-xs font-medium text-txt-tertiary uppercase ">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
18
apps/trust/src/components/Rows.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { PropsWithChildren } from "react";
|
||||
import clsx from "clsx";
|
||||
|
||||
export function Rows({
|
||||
children,
|
||||
className,
|
||||
}: PropsWithChildren<{ className?: string }>) {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"*:first:rounded-t-lg *:last:rounded-b-lg *:px-6 *:py-3 *:-mt-[1px] *:border *:border-border-solid",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
25
apps/trust/src/components/Skeletons/MainSkeleton.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Skeleton, TabLink, Tabs } from "@probo/ui";
|
||||
import { TabSkeleton } from "./TabSkeleton";
|
||||
import { useParams } from "react-router";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
|
||||
export function MainSkeleton() {
|
||||
const { slug } = useParams<{ slug: string }>();
|
||||
const baseTabUrl = `/trust/${slug}`;
|
||||
const { __ } = useTranslate();
|
||||
return (
|
||||
<div className="grid grid-cols-1 max-w-[1280px] mx-4 pt-6 gap-4 lg:mx-auto lg:gap-10 lg:pt-20 lg:grid-cols-[400px_1fr] ">
|
||||
<Skeleton className="w-full h-300" />
|
||||
<main>
|
||||
<Tabs className="mb-8">
|
||||
<TabLink to={`${baseTabUrl}/overview`}>{__("Overview")}</TabLink>
|
||||
<TabLink to={`${baseTabUrl}/documents`}>{__("Documents")}</TabLink>
|
||||
<TabLink to={`${baseTabUrl}/subprocessors`}>
|
||||
{__("Subprocessors")}
|
||||
</TabLink>
|
||||
</Tabs>
|
||||
<TabSkeleton />
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
11
apps/trust/src/components/Skeletons/TabSkeleton.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Skeleton } from "@probo/ui";
|
||||
|
||||
export function TabSkeleton() {
|
||||
return (
|
||||
<div className="h-64">
|
||||
<Skeleton className="h-6 w-[110px] mb-1" />
|
||||
<Skeleton className="h-6 w-[250px] mb-4" />
|
||||
<Skeleton className="w-full h-[180px]" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
53
apps/trust/src/components/VendorRow.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import { graphql } from "relay-runtime";
|
||||
import type { VendorRowFragment$key } from "./__generated__/VendorRowFragment.graphql";
|
||||
import { useFragment } from "react-relay";
|
||||
import { IconPin } from "@probo/ui";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { faviconUrl, getCountryName } from "@probo/helpers";
|
||||
|
||||
const vendorRowFragment = graphql`
|
||||
fragment VendorRowFragment on Vendor {
|
||||
id
|
||||
name
|
||||
category
|
||||
websiteUrl
|
||||
privacyPolicyUrl
|
||||
countries
|
||||
}
|
||||
`;
|
||||
|
||||
export function VendorRow(props: { vendor: VendorRowFragment$key }) {
|
||||
const vendor = useFragment(vendorRowFragment, props.vendor);
|
||||
const logo = faviconUrl(vendor.websiteUrl);
|
||||
const { __ } = useTranslate();
|
||||
|
||||
return (
|
||||
<div className="flex text-sm leading-tight gap-3 md:items-center">
|
||||
{logo ? (
|
||||
<img src={logo} className="size-8 md:size-6 flex-none" alt="" />
|
||||
) : (
|
||||
<div className="size-8 md:size-6 flex-none" />
|
||||
)}
|
||||
<div className="flex flex-col md:flex-row flex-1 gap-0.5">
|
||||
<div>{vendor.name}</div>
|
||||
{vendor.privacyPolicyUrl && (
|
||||
<a
|
||||
href={vendor.privacyPolicyUrl}
|
||||
target="_blank"
|
||||
className="text-txt-info md:mx-auto"
|
||||
>
|
||||
{vendor.privacyPolicyUrl.split("//").at(-1)}
|
||||
</a>
|
||||
)}
|
||||
<div className="flex gap-1 text-txt-secondary items-center">
|
||||
<IconPin size={16} className="flex-none" />
|
||||
<span>
|
||||
{vendor.countries
|
||||
.map((country) => getCountryName(__, country))
|
||||
.join(", ")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
92
apps/trust/src/components/__generated__/AuditRowDownloadMutation.graphql.ts
generated
Normal file
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* @generated SignedSource<<ac19789e6716697e6723a0a7b108cb81>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type ExportReportPDFInput = {
|
||||
reportId: string;
|
||||
};
|
||||
export type AuditRowDownloadMutation$variables = {
|
||||
input: ExportReportPDFInput;
|
||||
};
|
||||
export type AuditRowDownloadMutation$data = {
|
||||
readonly exportReportPDF: {
|
||||
readonly data: string;
|
||||
};
|
||||
};
|
||||
export type AuditRowDownloadMutation = {
|
||||
response: AuditRowDownloadMutation$data;
|
||||
variables: AuditRowDownloadMutation$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = [
|
||||
{
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "input"
|
||||
}
|
||||
],
|
||||
v1 = [
|
||||
{
|
||||
"alias": null,
|
||||
"args": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "input",
|
||||
"variableName": "input"
|
||||
}
|
||||
],
|
||||
"concreteType": "ExportReportPDFPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "exportReportPDF",
|
||||
"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": "AuditRowDownloadMutation",
|
||||
"selections": (v1/*: any*/),
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "AuditRowDownloadMutation",
|
||||
"selections": (v1/*: any*/)
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "79de6a9f2755587818dac20ec392b8e7",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "AuditRowDownloadMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation AuditRowDownloadMutation(\n $input: ExportReportPDFInput!\n) {\n exportReportPDF(input: $input) {\n data\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "e3d7ceb7a0da979dad83b1b8289e192e";
|
||||
|
||||
export default node;
|
||||
89
apps/trust/src/components/__generated__/AuditRowFragment.graphql.ts
generated
Normal file
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* @generated SignedSource<<5ad8bcd5ca3b7635248d13cef0a24edf>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ReaderFragment } from 'relay-runtime';
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type AuditRowFragment$data = {
|
||||
readonly framework: {
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
};
|
||||
readonly report: {
|
||||
readonly filename: string;
|
||||
readonly id: string;
|
||||
} | null | undefined;
|
||||
readonly " $fragmentType": "AuditRowFragment";
|
||||
};
|
||||
export type AuditRowFragment$key = {
|
||||
readonly " $data"?: AuditRowFragment$data;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"AuditRowFragment">;
|
||||
};
|
||||
|
||||
const node: ReaderFragment = (function(){
|
||||
var v0 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
};
|
||||
return {
|
||||
"argumentDefinitions": [],
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "AuditRowFragment",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Report",
|
||||
"kind": "LinkedField",
|
||||
"name": "report",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v0/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "filename",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Framework",
|
||||
"kind": "LinkedField",
|
||||
"name": "framework",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v0/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "name",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Audit",
|
||||
"abstractKey": null
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "255e447eb5ac9b4cb889e7a8f0463902";
|
||||
|
||||
export default node;
|
||||
92
apps/trust/src/components/__generated__/DocumentRowDownloadMutation.graphql.ts
generated
Normal file
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* @generated SignedSource<<cae4044b1357cd4510e12f83f19001ba>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type ExportDocumentPDFInput = {
|
||||
documentId: string;
|
||||
};
|
||||
export type DocumentRowDownloadMutation$variables = {
|
||||
input: ExportDocumentPDFInput;
|
||||
};
|
||||
export type DocumentRowDownloadMutation$data = {
|
||||
readonly exportDocumentPDF: {
|
||||
readonly data: string;
|
||||
};
|
||||
};
|
||||
export type DocumentRowDownloadMutation = {
|
||||
response: DocumentRowDownloadMutation$data;
|
||||
variables: DocumentRowDownloadMutation$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": "DocumentRowDownloadMutation",
|
||||
"selections": (v1/*: any*/),
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "DocumentRowDownloadMutation",
|
||||
"selections": (v1/*: any*/)
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "126681ef86a7a6aa5c831bdf53b7cf3e",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "DocumentRowDownloadMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation DocumentRowDownloadMutation(\n $input: ExportDocumentPDFInput!\n) {\n exportDocumentPDF(input: $input) {\n data\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "8e48a364a7f9cf3d3a51a9db60ff792f";
|
||||
|
||||
export default node;
|
||||
50
apps/trust/src/components/__generated__/DocumentRowFragment.graphql.ts
generated
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* @generated SignedSource<<266bdec238fe7397f3cff10dc7da8fc6>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ReaderFragment } from 'relay-runtime';
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type DocumentRowFragment$data = {
|
||||
readonly id: string;
|
||||
readonly title: string;
|
||||
readonly " $fragmentType": "DocumentRowFragment";
|
||||
};
|
||||
export type DocumentRowFragment$key = {
|
||||
readonly " $data"?: DocumentRowFragment$data;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"DocumentRowFragment">;
|
||||
};
|
||||
|
||||
const node: ReaderFragment = {
|
||||
"argumentDefinitions": [],
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "DocumentRowFragment",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "title",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Document",
|
||||
"abstractKey": null
|
||||
};
|
||||
|
||||
(node as any).hash = "437d68812bcc68724d2e347fea22b5e3";
|
||||
|
||||
export default node;
|
||||
92
apps/trust/src/components/__generated__/NDADialogSignMutation.graphql.ts
generated
Normal file
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* @generated SignedSource<<febdedca828f8b6dc384f0d4f08e02f8>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type AcceptNonDisclosureAgreementInput = {
|
||||
trustCenterId: string;
|
||||
};
|
||||
export type NDADialogSignMutation$variables = {
|
||||
input: AcceptNonDisclosureAgreementInput;
|
||||
};
|
||||
export type NDADialogSignMutation$data = {
|
||||
readonly acceptNonDisclosureAgreement: {
|
||||
readonly success: boolean;
|
||||
};
|
||||
};
|
||||
export type NDADialogSignMutation = {
|
||||
response: NDADialogSignMutation$data;
|
||||
variables: NDADialogSignMutation$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = [
|
||||
{
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "input"
|
||||
}
|
||||
],
|
||||
v1 = [
|
||||
{
|
||||
"alias": null,
|
||||
"args": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "input",
|
||||
"variableName": "input"
|
||||
}
|
||||
],
|
||||
"concreteType": "AcceptNonDisclosureAgreementPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "acceptNonDisclosureAgreement",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "success",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
];
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "NDADialogSignMutation",
|
||||
"selections": (v1/*: any*/),
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "NDADialogSignMutation",
|
||||
"selections": (v1/*: any*/)
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "130cfc307dca0525194e0103a0548bd0",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "NDADialogSignMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation NDADialogSignMutation(\n $input: AcceptNonDisclosureAgreementInput!\n) {\n acceptNonDisclosureAgreement(input: $input) {\n success\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "1b9447e5cbb2ec7dce4f3f9c68493555";
|
||||
|
||||
export default node;
|
||||
107
apps/trust/src/components/__generated__/RequestAccessDialogMutation.graphql.ts
generated
Normal file
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* @generated SignedSource<<843902dd4fb6e2a5ee4d2d175ddcab03>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type CreateTrustCenterAccessInput = {
|
||||
email: string;
|
||||
name: string;
|
||||
trustCenterId: string;
|
||||
};
|
||||
export type RequestAccessDialogMutation$variables = {
|
||||
input: CreateTrustCenterAccessInput;
|
||||
};
|
||||
export type RequestAccessDialogMutation$data = {
|
||||
readonly createTrustCenterAccess: {
|
||||
readonly trustCenterAccess: {
|
||||
readonly id: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
export type RequestAccessDialogMutation = {
|
||||
response: RequestAccessDialogMutation$data;
|
||||
variables: RequestAccessDialogMutation$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
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
];
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "RequestAccessDialogMutation",
|
||||
"selections": (v1/*: any*/),
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "RequestAccessDialogMutation",
|
||||
"selections": (v1/*: any*/)
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "13fedcfe7c72292417b76b3624c5434f",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "RequestAccessDialogMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation RequestAccessDialogMutation(\n $input: CreateTrustCenterAccessInput!\n) {\n createTrustCenterAccess(input: $input) {\n trustCenterAccess {\n id\n }\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "18075ac4298dd3ca05bbcf8fb1717a9d";
|
||||
|
||||
export default node;
|
||||
84
apps/trust/src/components/__generated__/VendorRowFragment.graphql.ts
generated
Normal file
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* @generated SignedSource<<4f486a2ab88b07376ece5f29705c2553>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ReaderFragment } from 'relay-runtime';
|
||||
export type CountryCode = "AD" | "AE" | "AF" | "AG" | "AI" | "AL" | "AM" | "AO" | "AQ" | "AR" | "AS" | "AT" | "AU" | "AW" | "AX" | "AZ" | "BA" | "BB" | "BD" | "BE" | "BF" | "BG" | "BH" | "BI" | "BJ" | "BL" | "BM" | "BN" | "BO" | "BQ" | "BR" | "BS" | "BT" | "BV" | "BW" | "BY" | "BZ" | "CA" | "CC" | "CD" | "CF" | "CG" | "CH" | "CI" | "CK" | "CL" | "CM" | "CN" | "CO" | "CR" | "CU" | "CV" | "CW" | "CX" | "CY" | "CZ" | "DE" | "DJ" | "DK" | "DM" | "DO" | "DZ" | "EC" | "EE" | "EG" | "EH" | "ER" | "ES" | "ET" | "FI" | "FJ" | "FK" | "FM" | "FO" | "FR" | "GA" | "GB" | "GD" | "GE" | "GF" | "GG" | "GH" | "GI" | "GL" | "GM" | "GN" | "GP" | "GQ" | "GR" | "GT" | "GU" | "GW" | "GY" | "HK" | "HM" | "HN" | "HR" | "HT" | "HU" | "ID" | "IE" | "IL" | "IM" | "IN" | "IO" | "IQ" | "IR" | "IS" | "IT" | "JE" | "JM" | "JO" | "JP" | "KE" | "KG" | "KH" | "KI" | "KM" | "KN" | "KP" | "KR" | "KW" | "KY" | "KZ" | "LA" | "LB" | "LC" | "LI" | "LK" | "LR" | "LS" | "LT" | "LU" | "LV" | "LY" | "MA" | "MC" | "MD" | "ME" | "MF" | "MG" | "MH" | "MK" | "ML" | "MM" | "MN" | "MO" | "MP" | "MQ" | "MR" | "MS" | "MT" | "MU" | "MV" | "MW" | "MX" | "MY" | "MZ" | "NA" | "NC" | "NE" | "NF" | "NG" | "NI" | "NL" | "NO" | "NP" | "NR" | "NU" | "NZ" | "OM" | "PA" | "PE" | "PF" | "PG" | "PH" | "PK" | "PL" | "PM" | "PN" | "PR" | "PS" | "PT" | "PW" | "PY" | "QA" | "RE" | "RO" | "RS" | "RU" | "RW" | "SA" | "SB" | "SC" | "SD" | "SE" | "SG" | "SH" | "SI" | "SJ" | "SK" | "SL" | "SM" | "SN" | "SO" | "SR" | "SS" | "ST" | "SV" | "SX" | "SY" | "SZ" | "TC" | "TD" | "TF" | "TG" | "TH" | "TJ" | "TK" | "TL" | "TM" | "TN" | "TO" | "TR" | "TT" | "TV" | "TW" | "TZ" | "UA" | "UG" | "UM" | "US" | "UY" | "UZ" | "VA" | "VC" | "VE" | "VG" | "VI" | "VN" | "VU" | "WF" | "WS" | "YE" | "YT" | "ZA" | "ZM" | "ZW";
|
||||
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";
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type VendorRowFragment$data = {
|
||||
readonly category: VendorCategory;
|
||||
readonly countries: ReadonlyArray<CountryCode>;
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly privacyPolicyUrl: string | null | undefined;
|
||||
readonly websiteUrl: string | null | undefined;
|
||||
readonly " $fragmentType": "VendorRowFragment";
|
||||
};
|
||||
export type VendorRowFragment$key = {
|
||||
readonly " $data"?: VendorRowFragment$data;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"VendorRowFragment">;
|
||||
};
|
||||
|
||||
const node: ReaderFragment = {
|
||||
"argumentDefinitions": [],
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "VendorRowFragment",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "name",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "category",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "websiteUrl",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "privacyPolicyUrl",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "countries",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Vendor",
|
||||
"abstractKey": null
|
||||
};
|
||||
|
||||
(node as any).hash = "eba669a2b65ef7b9990799516b7d9932";
|
||||
|
||||
export default node;
|
||||
10
apps/trust/src/helpers/documents.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export function documentTypeLabel(type: string, __: (s: string) => string) {
|
||||
switch (type) {
|
||||
case "POLICY":
|
||||
return __("Policy");
|
||||
case "ISMS":
|
||||
return __("Security");
|
||||
default:
|
||||
return __("Other");
|
||||
}
|
||||
}
|
||||
19
apps/trust/src/hooks/useDelayedEffect.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
/**
|
||||
* Hook to handle cleanup after a delay
|
||||
*
|
||||
* Used for disposing the graphQL query when the component unmounts
|
||||
*/
|
||||
export function useCleanup(callback: () => void, delay: number) {
|
||||
const timer = useRef<ReturnType<typeof setTimeout>>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (timer.current) {
|
||||
clearTimeout(timer.current);
|
||||
}
|
||||
return () => {
|
||||
timer.current = setTimeout(callback, delay);
|
||||
};
|
||||
}, [callback, delay]);
|
||||
}
|
||||
13
apps/trust/src/hooks/useFormWithSchema.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { useForm } from "react-hook-form";
|
||||
import type { z, ZodTypeAny } from "zod";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
|
||||
export function useFormWithSchema<T extends ZodTypeAny>(
|
||||
schema: T,
|
||||
options: Parameters<typeof useForm<z.infer<T>>>[0],
|
||||
) {
|
||||
return useForm<z.infer<T>>({
|
||||
...options,
|
||||
resolver: zodResolver(schema),
|
||||
});
|
||||
}
|
||||
6
apps/trust/src/hooks/useIsAuthenticated.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { useContext } from "react";
|
||||
import { AuthContext } from "/providers/AuthProvider";
|
||||
|
||||
export function useIsAuthenticated(): boolean {
|
||||
return useContext(AuthContext).isAuthenticated;
|
||||
}
|
||||
63
apps/trust/src/hooks/useMutationWithToast.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { useCallback } from "react";
|
||||
import { useMutation, type UseMutationConfig } from "react-relay";
|
||||
import { useToast } from "@probo/ui";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import type { MutationParameters, GraphQLTaggedNode } from "relay-runtime";
|
||||
|
||||
/**
|
||||
* A decorated useMutation hook that emits toast notifications on success or error.
|
||||
*/
|
||||
export function useMutationWithToasts<T extends MutationParameters>(
|
||||
query: GraphQLTaggedNode,
|
||||
baseOptions?: {
|
||||
onSuccess?: (response: T["response"]) => void;
|
||||
errorMessage?: string;
|
||||
}
|
||||
) {
|
||||
const [mutate, isLoading] = useMutation<T>(query);
|
||||
const { toast } = useToast();
|
||||
const { __ } = useTranslate();
|
||||
const mutateWithToast = useCallback(
|
||||
(
|
||||
queryOptions: UseMutationConfig<T> & {
|
||||
onSuccess?: (response: T["response"]) => void;
|
||||
errorMessage?: string;
|
||||
}
|
||||
) => {
|
||||
const options = { ...baseOptions, ...queryOptions };
|
||||
return new Promise<void>((resolve, reject) =>
|
||||
mutate({
|
||||
...queryOptions,
|
||||
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;
|
||||
}
|
||||
options.onSuccess?.(response);
|
||||
resolve();
|
||||
},
|
||||
onError: (error) => {
|
||||
toast({
|
||||
title: __("Error"),
|
||||
description:
|
||||
options.errorMessage ?? __("Failed to commit this operation."),
|
||||
variant: "error",
|
||||
});
|
||||
reject(error);
|
||||
},
|
||||
})
|
||||
);
|
||||
},
|
||||
[mutate]
|
||||
);
|
||||
|
||||
return [mutateWithToast, isLoading] as const;
|
||||
}
|
||||
13
apps/trust/src/hooks/useTrustCenter.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { TrustCenterContext } from "/providers/TrustCenterProvider";
|
||||
import { useContext } from "react";
|
||||
|
||||
export function useTrustCenter(): {
|
||||
id: string;
|
||||
organization: { name: string };
|
||||
} {
|
||||
const context = useContext(TrustCenterContext);
|
||||
if (!context) {
|
||||
throw new Error("useTrustCenter must be used within a TrustCenterProvider");
|
||||
}
|
||||
return context;
|
||||
}
|
||||
10
apps/trust/src/index.css
Normal file
@@ -0,0 +1,10 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Geist:wght@100..900&display=swap");
|
||||
@import "tailwindcss";
|
||||
@import "@probo/ui/src/theme.css";
|
||||
@import "tw-animate-css";
|
||||
@source "../../../packages/ui/src";
|
||||
@source "../../../packages/helpers/src";
|
||||
|
||||
.react-pdf__Page__annotations.annotationLayer {
|
||||
display: none;
|
||||
}
|
||||
67
apps/trust/src/layouts/MainLayout.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import { type PreloadedQuery, usePreloadedQuery } from "react-relay";
|
||||
import type { TrustGraphQuery } from "/queries/__generated__/TrustGraphQuery.graphql.ts";
|
||||
import { trustGraphQuery } from "/queries/TrustGraph.ts";
|
||||
import { Logo, TabLink, Tabs } from "@probo/ui";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { OrganizationSidebar } from "/components/OrganizationSidebar";
|
||||
import { Outlet } from "react-router";
|
||||
import { NDADialog } from "/components/NDADialog";
|
||||
import { AuthProvider } from "/providers/AuthProvider";
|
||||
import { TrustCenterProvider } from "/providers/TrustCenterProvider";
|
||||
|
||||
type Props = {
|
||||
queryRef: PreloadedQuery<TrustGraphQuery>;
|
||||
};
|
||||
|
||||
export function MainLayout(props: Props) {
|
||||
const { __ } = useTranslate();
|
||||
const trustCenter = usePreloadedQuery(
|
||||
trustGraphQuery,
|
||||
props.queryRef,
|
||||
).trustCenterBySlug;
|
||||
|
||||
if (!trustCenter) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const baseTabUrl = `/trust/${trustCenter.slug}`;
|
||||
const showNDADialog =
|
||||
trustCenter.isUserAuthenticated &&
|
||||
!trustCenter.hasAcceptedNonDisclosureAgreement;
|
||||
return (
|
||||
<AuthProvider isAuthenticated={trustCenter.isUserAuthenticated}>
|
||||
<TrustCenterProvider trustCenter={trustCenter}>
|
||||
{showNDADialog && (
|
||||
<NDADialog
|
||||
name={trustCenter.organization.name}
|
||||
trustCenterId={trustCenter.id}
|
||||
url={trustCenter.ndaFileUrl}
|
||||
fileName={trustCenter.ndaFileName}
|
||||
/>
|
||||
)}
|
||||
<div className="grid grid-cols-1 max-w-[1280px] mx-4 pt-6 gap-4 lg:mx-auto lg:gap-10 lg:pt-20 lg:grid-cols-[400px_1fr] lg:items-start ">
|
||||
<OrganizationSidebar trustCenter={trustCenter} />
|
||||
<main>
|
||||
<Tabs className="mb-8">
|
||||
<TabLink to={`${baseTabUrl}/overview`}>{__("Overview")}</TabLink>
|
||||
<TabLink to={`${baseTabUrl}/documents`}>
|
||||
{__("Documents")}
|
||||
</TabLink>
|
||||
<TabLink to={`${baseTabUrl}/subprocessors`}>
|
||||
{__("Subprocessors")}
|
||||
</TabLink>
|
||||
</Tabs>
|
||||
<Outlet context={{ trustCenter }} />
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<a
|
||||
href="https://www.getprobo.com/"
|
||||
className="flex gap-2 text-sm font-medium text-txt-tertiary items-center w-max mx-auto my-10"
|
||||
>
|
||||
{__("Powered by")} <Logo withPicto className="h-6" />
|
||||
</a>
|
||||
</TrustCenterProvider>
|
||||
</AuthProvider>
|
||||
);
|
||||
}
|
||||
27
apps/trust/src/main.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import "./index.css";
|
||||
import { App } from "./App";
|
||||
import { RelayProvider } from "./providers/RelayProviders";
|
||||
import { TranslatorProvider } from "./providers/TranslatorProvider";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
refetchOnWindowFocus: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<StrictMode>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<RelayProvider>
|
||||
<TranslatorProvider>
|
||||
<App />
|
||||
</TranslatorProvider>
|
||||
</RelayProvider>
|
||||
</QueryClientProvider>
|
||||
</StrictMode>
|
||||
);
|
||||
3
apps/trust/src/module.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
declare module "https://cdn.jsdelivr.net/npm/pdfjs-dist@5.4.149/+esm" {
|
||||
export * from "pdfjs-dist";
|
||||
}
|
||||
80
apps/trust/src/pages/AccessPage.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useNavigate, useParams, useSearchParams } from "react-router";
|
||||
import { buildEndpoint } from "/providers/RelayProviders";
|
||||
import { PageError } from "/components/PageError";
|
||||
import { Spinner } from "@probo/ui";
|
||||
|
||||
/**
|
||||
* Page requested with an access token to authenticate the user for the Trust center
|
||||
*/
|
||||
export function AccessPage() {
|
||||
const { __ } = useTranslate();
|
||||
const { slug } = useParams<{ slug: string }>();
|
||||
const [searchParams] = useSearchParams();
|
||||
const token = searchParams.get("token");
|
||||
const navigate = useNavigate();
|
||||
|
||||
const isValidRequest = !!(slug && token);
|
||||
const [error, setError] = useState<string | null>(() => {
|
||||
if (!slug) {
|
||||
return __("Invalid trust center");
|
||||
}
|
||||
if (!token) {
|
||||
return __("Invalid access token");
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
// Initiate an authentication attempt
|
||||
useEffect(() => {
|
||||
if (!isValidRequest) {
|
||||
return;
|
||||
}
|
||||
fetch(buildEndpoint("/api/trust/v1/auth/authenticate"), {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
token,
|
||||
}),
|
||||
})
|
||||
.then((response) => {
|
||||
// For invalid response throw an error
|
||||
if (!response.ok) {
|
||||
const defaultMessage = `HTTP ${response.status}: ${response.statusText}`;
|
||||
return response
|
||||
.json()
|
||||
.then((json) => {
|
||||
throw new Error(json.message ?? defaultMessage);
|
||||
})
|
||||
.catch(() => {
|
||||
throw new Error(defaultMessage);
|
||||
});
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
navigate(`/trust/${slug}`);
|
||||
return;
|
||||
}
|
||||
throw new Error(data.message ?? __("Authentication failed"));
|
||||
})
|
||||
.catch((error) => {
|
||||
setError(error.message);
|
||||
});
|
||||
}, [isValidRequest, slug, token, __, navigate]);
|
||||
|
||||
if (error) {
|
||||
return <PageError error={error} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-4 text-center flex items-center justify-center gap-2">
|
||||
<Spinner size={16} />
|
||||
{__("Redirecting to trust center")}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
42
apps/trust/src/pages/DocumentsPage.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { type PreloadedQuery, usePreloadedQuery } from "react-relay";
|
||||
import { trustDocumentsQuery } from "/queries/TrustGraph";
|
||||
import type { TrustGraphDocumentsQuery } from "/queries/__generated__/TrustGraphDocumentsQuery.graphql.ts";
|
||||
import { groupBy, objectEntries } from "@probo/helpers";
|
||||
import { documentTypeLabel } from "/helpers/documents";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { Fragment } from "react";
|
||||
import { DocumentRow } from "/components/DocumentRow";
|
||||
import { Rows } from "/components/Rows.tsx";
|
||||
import { RowHeader } from "/components/RowHeader.tsx";
|
||||
|
||||
type Props = {
|
||||
queryRef: PreloadedQuery<TrustGraphDocumentsQuery>;
|
||||
};
|
||||
|
||||
export function DocumentsPage({ queryRef }: Props) {
|
||||
const { __ } = useTranslate();
|
||||
const data = usePreloadedQuery(trustDocumentsQuery, queryRef);
|
||||
const documents =
|
||||
data.trustCenterBySlug?.documents.edges.map((edge) => edge.node) ?? [];
|
||||
const documentsPerType = groupBy(documents, (document) =>
|
||||
documentTypeLabel(document.documentType, __),
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
<h2 className="font-medium mb-1">{__("Documents")}</h2>
|
||||
<p className="text-sm text-txt-secondary mb-4">
|
||||
{__("Security and compliance documentation:")}
|
||||
</p>
|
||||
<Rows className="mb-8">
|
||||
{objectEntries(documentsPerType).map(([label, documents]) => (
|
||||
<Fragment key={label}>
|
||||
<RowHeader>{label}</RowHeader>
|
||||
{documents.map((document) => (
|
||||
<DocumentRow key={document.id} document={document} />
|
||||
))}
|
||||
</Fragment>
|
||||
))}
|
||||
</Rows>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
147
apps/trust/src/pages/OverviewPage.tsx
Normal file
@@ -0,0 +1,147 @@
|
||||
import { useFragment } from "react-relay";
|
||||
import { graphql } from "relay-runtime";
|
||||
import type { OverviewFragment$key } from "./__generated__/OverviewFragment.graphql";
|
||||
import { Link, useOutletContext } from "react-router";
|
||||
import { groupBy, objectEntries, sprintf } from "@probo/helpers";
|
||||
import type { TrustGraphQuery$data } from "/queries/__generated__/TrustGraphQuery.graphql";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { Card, IconChevronRight } from "@probo/ui";
|
||||
import { AuditRow } from "/components/AuditRow";
|
||||
import { documentTypeLabel } from "/helpers/documents";
|
||||
import { Fragment } from "react";
|
||||
import { DocumentRow } from "/components/DocumentRow";
|
||||
import { VendorRow } from "/components/VendorRow";
|
||||
import { RowHeader } from "/components/RowHeader.tsx";
|
||||
import { Rows } from "/components/Rows.tsx";
|
||||
|
||||
const overviewFragment = graphql`
|
||||
fragment OverviewFragment on TrustCenter {
|
||||
references(first: 14) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
logoUrl
|
||||
websiteUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
vendors(first: 3) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
...VendorRowFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
documents(first: 5) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
...DocumentRowFragment
|
||||
documentType
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export function OverviewPage() {
|
||||
const { trustCenter } = useOutletContext<{
|
||||
trustCenter: OverviewFragment$key &
|
||||
TrustGraphQuery$data["trustCenterBySlug"];
|
||||
}>();
|
||||
const { __ } = useTranslate();
|
||||
const fragment = useFragment(overviewFragment, trustCenter);
|
||||
const documentsPerType = groupBy(
|
||||
fragment.documents.edges.map((edge) => edge.node),
|
||||
(node) => documentTypeLabel(node.documentType, __),
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
<h2 className="font-medium mb-1">{__("Documents")}</h2>
|
||||
<p className="text-sm text-txt-secondary mb-4">
|
||||
{__("Security and compliance documentation:")}
|
||||
</p>
|
||||
<Rows className="mb-8">
|
||||
<RowHeader>{__("Certifications")}</RowHeader>
|
||||
{trustCenter.audits.edges.map((edge) => (
|
||||
<AuditRow key={edge.node.id} audit={edge.node} />
|
||||
))}
|
||||
{objectEntries(documentsPerType).map(([label, documents]) => (
|
||||
<Fragment key={label}>
|
||||
<RowHeader>{label}</RowHeader>
|
||||
{documents.map((document) => (
|
||||
<DocumentRow key={document.id} document={document} />
|
||||
))}
|
||||
</Fragment>
|
||||
))}
|
||||
<Link
|
||||
to={`/trust/${trustCenter.slug}/documents`}
|
||||
className="text-sm font-medium flex gap-2 items-center"
|
||||
>
|
||||
{__("See all documents")}
|
||||
<IconChevronRight size={16} />
|
||||
</Link>
|
||||
</Rows>
|
||||
|
||||
<h2 className="font-medium mb-1">{__("Subprocessors")}</h2>
|
||||
<p className="text-sm text-txt-secondary mb-4">
|
||||
{sprintf(
|
||||
__("Third-party subprocessors %s work with:"),
|
||||
trustCenter.organization.name,
|
||||
)}
|
||||
</p>
|
||||
<Rows className="mb-8 *:py-5">
|
||||
{fragment.vendors.edges.map((edge) => (
|
||||
<VendorRow key={edge.node.id} vendor={edge.node} />
|
||||
))}
|
||||
<Link
|
||||
to={`/trust/${trustCenter.slug}/subprocessors`}
|
||||
className="text-sm font-medium flex gap-2 items-center"
|
||||
>
|
||||
{__("See all subprocessors")}
|
||||
<IconChevronRight size={16} />
|
||||
</Link>
|
||||
</Rows>
|
||||
|
||||
<References
|
||||
references={fragment.references.edges.map((edge) => edge.node)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type Reference = {
|
||||
name: string;
|
||||
logoUrl: string;
|
||||
websiteUrl: string;
|
||||
id: string;
|
||||
};
|
||||
|
||||
function References({ references }: { references: Reference[] }) {
|
||||
const { __ } = useTranslate();
|
||||
return (
|
||||
<div>
|
||||
<h2 className="font-medium mb-4">{__("Trusted by")}</h2>
|
||||
<Card className="grid grid-cols-2 flex-wrap p-6 gap-4 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-7">
|
||||
{references.map((reference) => (
|
||||
<a
|
||||
key={reference.id}
|
||||
href={reference.websiteUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex flex-col justify-center items-center gap-2"
|
||||
>
|
||||
<img
|
||||
src={reference.logoUrl}
|
||||
alt={reference.name}
|
||||
className="rounded-2xl size-12 block"
|
||||
/>
|
||||
<span className="text-xs text-txt-secondary">{reference.name}</span>
|
||||
</a>
|
||||
))}
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
34
apps/trust/src/pages/SubprocessorsPage.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { type PreloadedQuery, usePreloadedQuery } from "react-relay";
|
||||
import { trustVendorsQuery } from "/queries/TrustGraph";
|
||||
import { sprintf } from "@probo/helpers";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import type { TrustGraphVendorsQuery } from "/queries/__generated__/TrustGraphVendorsQuery.graphql";
|
||||
import { VendorRow } from "/components/VendorRow";
|
||||
import { Rows } from "/components/Rows.tsx";
|
||||
|
||||
type Props = {
|
||||
queryRef: PreloadedQuery<TrustGraphVendorsQuery>;
|
||||
};
|
||||
|
||||
export function SubprocessorsPage({ queryRef }: Props) {
|
||||
const { __ } = useTranslate();
|
||||
const data = usePreloadedQuery(trustVendorsQuery, queryRef);
|
||||
const vendors =
|
||||
data.trustCenterBySlug?.vendors.edges.map((edge) => edge.node) ?? [];
|
||||
return (
|
||||
<div>
|
||||
<h2 className="font-medium mb-1">{__("Subprocessors")}</h2>
|
||||
<p className="text-sm text-txt-secondary mb-4">
|
||||
{sprintf(
|
||||
__("Third-party subprocessors %s work with:"),
|
||||
data.trustCenterBySlug?.organization.name ?? "",
|
||||
)}
|
||||
</p>
|
||||
<Rows>
|
||||
{vendors.map((vendor) => (
|
||||
<VendorRow key={vendor.id} vendor={vendor} />
|
||||
))}
|
||||
</Rows>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
229
apps/trust/src/pages/__generated__/OverviewFragment.graphql.ts
generated
Normal file
@@ -0,0 +1,229 @@
|
||||
/**
|
||||
* @generated SignedSource<<0767a7e9f6fb3f0464027b92a5239521>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ReaderFragment } from 'relay-runtime';
|
||||
export type DocumentType = "ISMS" | "OTHER" | "POLICY";
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type OverviewFragment$data = {
|
||||
readonly documents: {
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly documentType: DocumentType;
|
||||
readonly id: string;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"DocumentRowFragment">;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
readonly references: {
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly id: string;
|
||||
readonly logoUrl: string;
|
||||
readonly name: string;
|
||||
readonly websiteUrl: string;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
readonly vendors: {
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly id: string;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"VendorRowFragment">;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
readonly " $fragmentType": "OverviewFragment";
|
||||
};
|
||||
export type OverviewFragment$key = {
|
||||
readonly " $data"?: OverviewFragment$data;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"OverviewFragment">;
|
||||
};
|
||||
|
||||
const node: ReaderFragment = (function(){
|
||||
var v0 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
};
|
||||
return {
|
||||
"argumentDefinitions": [],
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "OverviewFragment",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 14
|
||||
}
|
||||
],
|
||||
"concreteType": "TrustCenterReferenceConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "references",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "TrustCenterReferenceEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "TrustCenterReference",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v0/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "name",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "logoUrl",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "websiteUrl",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": "references(first:14)"
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 3
|
||||
}
|
||||
],
|
||||
"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": [
|
||||
(v0/*: any*/),
|
||||
{
|
||||
"args": null,
|
||||
"kind": "FragmentSpread",
|
||||
"name": "VendorRowFragment"
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": "vendors(first:3)"
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 5
|
||||
}
|
||||
],
|
||||
"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": [
|
||||
(v0/*: any*/),
|
||||
{
|
||||
"args": null,
|
||||
"kind": "FragmentSpread",
|
||||
"name": "DocumentRowFragment"
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "documentType",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": "documents(first:5)"
|
||||
}
|
||||
],
|
||||
"type": "TrustCenter",
|
||||
"abstractKey": null
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "2f42031d2017248051c8dfea72b4e502";
|
||||
|
||||
export default node;
|
||||
13
apps/trust/src/providers/AuthProvider.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { createContext, useMemo } from "react";
|
||||
|
||||
export const AuthContext = createContext({ isAuthenticated: false });
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode;
|
||||
isAuthenticated: boolean;
|
||||
};
|
||||
|
||||
export function AuthProvider({ children, isAuthenticated }: Props) {
|
||||
const value = useMemo(() => ({ isAuthenticated }), [isAuthenticated]);
|
||||
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
||||
}
|
||||
146
apps/trust/src/providers/RelayProviders.tsx
Normal file
@@ -0,0 +1,146 @@
|
||||
import {
|
||||
Environment,
|
||||
type FetchFunction,
|
||||
Network,
|
||||
RecordSource,
|
||||
Store,
|
||||
} from "relay-runtime";
|
||||
import { GraphQLError } from "graphql";
|
||||
import type { PropsWithChildren } from "react";
|
||||
import { RelayEnvironmentProvider } from "react-relay";
|
||||
|
||||
export class UnAuthenticatedError extends Error {
|
||||
constructor() {
|
||||
super("UNAUTHENTICATED");
|
||||
this.name = "UnAuthenticatedError";
|
||||
}
|
||||
}
|
||||
|
||||
export class InternalServerError extends Error {
|
||||
constructor() {
|
||||
super("INTERNAL_SERVER_ERROR");
|
||||
this.name = "InternalServerError";
|
||||
}
|
||||
}
|
||||
|
||||
export function buildEndpoint(path: string): string {
|
||||
const host = import.meta.env.VITE_API_URL;
|
||||
|
||||
if (!host) {
|
||||
return path;
|
||||
}
|
||||
|
||||
const formattedHost =
|
||||
host.startsWith("http://") || host.startsWith("https://")
|
||||
? host
|
||||
: `https://${host}`;
|
||||
|
||||
const url = new URL(formattedHost);
|
||||
|
||||
if (path) {
|
||||
url.pathname = path.startsWith("/") ? path : `/${path}`;
|
||||
}
|
||||
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
const hasUnauthenticatedError = (error: GraphQLError) =>
|
||||
error.extensions?.code == "UNAUTHENTICATED";
|
||||
|
||||
const fetchRelay: FetchFunction = async (
|
||||
request,
|
||||
variables,
|
||||
_,
|
||||
uploadables,
|
||||
) => {
|
||||
const requestInit: RequestInit = {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {},
|
||||
};
|
||||
|
||||
if (uploadables) {
|
||||
const formData = new FormData();
|
||||
formData.append(
|
||||
"operations",
|
||||
JSON.stringify({
|
||||
operationName: request.name,
|
||||
query: request.text,
|
||||
variables: variables,
|
||||
}),
|
||||
);
|
||||
|
||||
const uploadableMap: {
|
||||
[key: string]: string[];
|
||||
} = {};
|
||||
|
||||
Object.keys(uploadables).forEach((key, index) => {
|
||||
uploadableMap[index] = [`variables.${key}`];
|
||||
});
|
||||
|
||||
formData.append("map", JSON.stringify(uploadableMap));
|
||||
|
||||
Object.keys(uploadables).forEach((key, index) => {
|
||||
formData.append(index.toString(), uploadables[key]);
|
||||
});
|
||||
|
||||
requestInit.body = formData;
|
||||
} else {
|
||||
requestInit.headers = {
|
||||
Accept:
|
||||
"application/graphql-response+json; charset=utf-8, application/json; charset=utf-8",
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
|
||||
requestInit.body = JSON.stringify({
|
||||
operationName: request.name,
|
||||
query: request.text,
|
||||
variables,
|
||||
});
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
buildEndpoint("/api/trust/v1/graphql"),
|
||||
requestInit,
|
||||
);
|
||||
|
||||
if (response.status === 500) {
|
||||
throw new InternalServerError();
|
||||
}
|
||||
|
||||
const json = await response.json();
|
||||
|
||||
if (json.errors) {
|
||||
const errors = json.errors as GraphQLError[];
|
||||
|
||||
if (errors.find(hasUnauthenticatedError)) {
|
||||
throw new UnAuthenticatedError();
|
||||
}
|
||||
|
||||
throw new Error(`Error fetching GraphQL query '${request.name}'`);
|
||||
}
|
||||
|
||||
return json;
|
||||
};
|
||||
|
||||
const source = new RecordSource();
|
||||
const store = new Store(source, {
|
||||
queryCacheExpirationTime: 1 * 60 * 1000,
|
||||
gcReleaseBufferSize: 20,
|
||||
});
|
||||
|
||||
export const relayEnvironment = new Environment({
|
||||
network: Network.create(fetchRelay),
|
||||
store,
|
||||
});
|
||||
|
||||
/**
|
||||
* Provider for relay with the probo environment
|
||||
*/
|
||||
export function RelayProvider({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<RelayEnvironmentProvider environment={relayEnvironment}>
|
||||
{children}
|
||||
</RelayEnvironmentProvider>
|
||||
);
|
||||
}
|
||||
18
apps/trust/src/providers/TranslatorProvider.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { PropsWithChildren } from "react";
|
||||
import { TranslatorProvider as ProboTranslatorProvider } from "../../../../packages/i18n/TranslatorProvider";
|
||||
|
||||
// TODO : implement a way to retrieve translations strings
|
||||
const loader = () => {
|
||||
return Promise.resolve({} as Record<string, string>);
|
||||
};
|
||||
|
||||
/**
|
||||
* Provider for the translator
|
||||
*/
|
||||
export function TranslatorProvider({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<ProboTranslatorProvider lang="en" loader={loader}>
|
||||
{children}
|
||||
</ProboTranslatorProvider>
|
||||
);
|
||||
}
|
||||
20
apps/trust/src/providers/TrustCenterProvider.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { createContext, type ReactNode } from "react";
|
||||
import type { TrustGraphQuery$data } from "/queries/__generated__/TrustGraphQuery.graphql";
|
||||
|
||||
export const TrustCenterContext = createContext<
|
||||
TrustGraphQuery$data["trustCenterBySlug"] | null
|
||||
>(null);
|
||||
|
||||
export const TrustCenterProvider = ({
|
||||
children,
|
||||
trustCenter,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
trustCenter: TrustGraphQuery$data["trustCenterBySlug"];
|
||||
}) => {
|
||||
return (
|
||||
<TrustCenterContext.Provider value={trustCenter}>
|
||||
{children}
|
||||
</TrustCenterContext.Provider>
|
||||
);
|
||||
};
|
||||
70
apps/trust/src/queries/TrustGraph.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { graphql } from "relay-runtime";
|
||||
|
||||
export const trustGraphQuery = graphql`
|
||||
query TrustGraphQuery($slug: String!) {
|
||||
trustCenterBySlug(slug: $slug) {
|
||||
id
|
||||
slug
|
||||
isUserAuthenticated
|
||||
hasAcceptedNonDisclosureAgreement
|
||||
ndaFileName
|
||||
ndaFileUrl
|
||||
organization {
|
||||
name
|
||||
description
|
||||
websiteUrl
|
||||
logoUrl
|
||||
email
|
||||
headquarterAddress
|
||||
}
|
||||
...OverviewFragment
|
||||
audits(first: 50) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
...AuditRowFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const trustDocumentsQuery = graphql`
|
||||
query TrustGraphDocumentsQuery($slug: String!) {
|
||||
trustCenterBySlug(slug: $slug) {
|
||||
id
|
||||
organization {
|
||||
name
|
||||
}
|
||||
documents(first: 50) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
documentType
|
||||
...DocumentRowFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const trustVendorsQuery = graphql`
|
||||
query TrustGraphVendorsQuery($slug: String!) {
|
||||
trustCenterBySlug(slug: $slug) {
|
||||
id
|
||||
organization {
|
||||
name
|
||||
}
|
||||
vendors(first: 50) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
...VendorRowFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
245
apps/trust/src/queries/__generated__/TrustGraphDocumentsQuery.graphql.ts
generated
Normal file
@@ -0,0 +1,245 @@
|
||||
/**
|
||||
* @generated SignedSource<<55d256287ee091b57754d2d76080ad25>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type DocumentType = "ISMS" | "OTHER" | "POLICY";
|
||||
export type TrustGraphDocumentsQuery$variables = {
|
||||
slug: string;
|
||||
};
|
||||
export type TrustGraphDocumentsQuery$data = {
|
||||
readonly trustCenterBySlug: {
|
||||
readonly documents: {
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly documentType: DocumentType;
|
||||
readonly id: string;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"DocumentRowFragment">;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
readonly id: string;
|
||||
readonly organization: {
|
||||
readonly name: string;
|
||||
};
|
||||
} | null | undefined;
|
||||
};
|
||||
export type TrustGraphDocumentsQuery = {
|
||||
response: TrustGraphDocumentsQuery$data;
|
||||
variables: TrustGraphDocumentsQuery$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": "name",
|
||||
"storageKey": null
|
||||
},
|
||||
v4 = [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 50
|
||||
}
|
||||
],
|
||||
v5 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "documentType",
|
||||
"storageKey": null
|
||||
};
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "TrustGraphDocumentsQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": "TrustCenter",
|
||||
"kind": "LinkedField",
|
||||
"name": "trustCenterBySlug",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Organization",
|
||||
"kind": "LinkedField",
|
||||
"name": "organization",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v4/*: 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*/),
|
||||
(v5/*: any*/),
|
||||
{
|
||||
"args": null,
|
||||
"kind": "FragmentSpread",
|
||||
"name": "DocumentRowFragment"
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": "documents(first:50)"
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Query",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "TrustGraphDocumentsQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": "TrustCenter",
|
||||
"kind": "LinkedField",
|
||||
"name": "trustCenterBySlug",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Organization",
|
||||
"kind": "LinkedField",
|
||||
"name": "organization",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
(v2/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v4/*: 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*/),
|
||||
(v5/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "title",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": "documents(first:50)"
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "71e1dc4102d19cdd2fb8d0ff60a391ca",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "TrustGraphDocumentsQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query TrustGraphDocumentsQuery(\n $slug: String!\n) {\n trustCenterBySlug(slug: $slug) {\n id\n organization {\n name\n id\n }\n documents(first: 50) {\n edges {\n node {\n id\n documentType\n ...DocumentRowFragment\n }\n }\n }\n }\n}\n\nfragment DocumentRowFragment on Document {\n id\n title\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "9c56f70fca2afb316f4fd38d646ec545";
|
||||
|
||||
export default node;
|
||||
525
apps/trust/src/queries/__generated__/TrustGraphQuery.graphql.ts
generated
Normal file
@@ -0,0 +1,525 @@
|
||||
/**
|
||||
* @generated SignedSource<<669a97d59bae0528905d34c18586ce11>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type TrustGraphQuery$variables = {
|
||||
slug: string;
|
||||
};
|
||||
export type TrustGraphQuery$data = {
|
||||
readonly trustCenterBySlug: {
|
||||
readonly audits: {
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly id: string;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"AuditRowFragment">;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
readonly hasAcceptedNonDisclosureAgreement: boolean;
|
||||
readonly id: string;
|
||||
readonly isUserAuthenticated: boolean;
|
||||
readonly ndaFileName: string | null | undefined;
|
||||
readonly ndaFileUrl: string | null | undefined;
|
||||
readonly organization: {
|
||||
readonly description: string | null | undefined;
|
||||
readonly email: string | null | undefined;
|
||||
readonly headquarterAddress: string | null | undefined;
|
||||
readonly logoUrl: string | null | undefined;
|
||||
readonly name: string;
|
||||
readonly websiteUrl: string | null | undefined;
|
||||
};
|
||||
readonly slug: string;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"OverviewFragment">;
|
||||
} | null | undefined;
|
||||
};
|
||||
export type TrustGraphQuery = {
|
||||
response: TrustGraphQuery$data;
|
||||
variables: TrustGraphQuery$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": "slug",
|
||||
"storageKey": null
|
||||
},
|
||||
v4 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "isUserAuthenticated",
|
||||
"storageKey": null
|
||||
},
|
||||
v5 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "hasAcceptedNonDisclosureAgreement",
|
||||
"storageKey": null
|
||||
},
|
||||
v6 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "ndaFileName",
|
||||
"storageKey": null
|
||||
},
|
||||
v7 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "ndaFileUrl",
|
||||
"storageKey": null
|
||||
},
|
||||
v8 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "name",
|
||||
"storageKey": null
|
||||
},
|
||||
v9 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "description",
|
||||
"storageKey": null
|
||||
},
|
||||
v10 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "websiteUrl",
|
||||
"storageKey": null
|
||||
},
|
||||
v11 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "logoUrl",
|
||||
"storageKey": null
|
||||
},
|
||||
v12 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "email",
|
||||
"storageKey": null
|
||||
},
|
||||
v13 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "headquarterAddress",
|
||||
"storageKey": null
|
||||
},
|
||||
v14 = [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 50
|
||||
}
|
||||
];
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "TrustGraphQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": "TrustCenter",
|
||||
"kind": "LinkedField",
|
||||
"name": "trustCenterBySlug",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
(v3/*: any*/),
|
||||
(v4/*: any*/),
|
||||
(v5/*: any*/),
|
||||
(v6/*: any*/),
|
||||
(v7/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Organization",
|
||||
"kind": "LinkedField",
|
||||
"name": "organization",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v8/*: any*/),
|
||||
(v9/*: any*/),
|
||||
(v10/*: any*/),
|
||||
(v11/*: any*/),
|
||||
(v12/*: any*/),
|
||||
(v13/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"args": null,
|
||||
"kind": "FragmentSpread",
|
||||
"name": "OverviewFragment"
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v14/*: 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*/),
|
||||
{
|
||||
"args": null,
|
||||
"kind": "FragmentSpread",
|
||||
"name": "AuditRowFragment"
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": "audits(first:50)"
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Query",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "TrustGraphQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": "TrustCenter",
|
||||
"kind": "LinkedField",
|
||||
"name": "trustCenterBySlug",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
(v3/*: any*/),
|
||||
(v4/*: any*/),
|
||||
(v5/*: any*/),
|
||||
(v6/*: any*/),
|
||||
(v7/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Organization",
|
||||
"kind": "LinkedField",
|
||||
"name": "organization",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v8/*: any*/),
|
||||
(v9/*: any*/),
|
||||
(v10/*: any*/),
|
||||
(v11/*: any*/),
|
||||
(v12/*: any*/),
|
||||
(v13/*: any*/),
|
||||
(v2/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 14
|
||||
}
|
||||
],
|
||||
"concreteType": "TrustCenterReferenceConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "references",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "TrustCenterReferenceEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "TrustCenterReference",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
(v8/*: any*/),
|
||||
(v11/*: any*/),
|
||||
(v10/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": "references(first:14)"
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 3
|
||||
}
|
||||
],
|
||||
"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*/),
|
||||
(v8/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "category",
|
||||
"storageKey": null
|
||||
},
|
||||
(v10/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "privacyPolicyUrl",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "countries",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": "vendors(first:3)"
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 5
|
||||
}
|
||||
],
|
||||
"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:5)"
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v14/*: 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": "Report",
|
||||
"kind": "LinkedField",
|
||||
"name": "report",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "filename",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Framework",
|
||||
"kind": "LinkedField",
|
||||
"name": "framework",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
(v8/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": "audits(first:50)"
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "00f2723e0dac553f037af3570903fb51",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "TrustGraphQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query TrustGraphQuery(\n $slug: String!\n) {\n trustCenterBySlug(slug: $slug) {\n id\n slug\n isUserAuthenticated\n hasAcceptedNonDisclosureAgreement\n ndaFileName\n ndaFileUrl\n organization {\n name\n description\n websiteUrl\n logoUrl\n email\n headquarterAddress\n id\n }\n ...OverviewFragment\n audits(first: 50) {\n edges {\n node {\n id\n ...AuditRowFragment\n }\n }\n }\n }\n}\n\nfragment AuditRowFragment on Audit {\n report {\n id\n filename\n }\n framework {\n id\n name\n }\n}\n\nfragment DocumentRowFragment on Document {\n id\n title\n}\n\nfragment OverviewFragment on TrustCenter {\n references(first: 14) {\n edges {\n node {\n id\n name\n logoUrl\n websiteUrl\n }\n }\n }\n vendors(first: 3) {\n edges {\n node {\n id\n ...VendorRowFragment\n }\n }\n }\n documents(first: 5) {\n edges {\n node {\n id\n ...DocumentRowFragment\n documentType\n }\n }\n }\n}\n\nfragment VendorRowFragment on Vendor {\n id\n name\n category\n websiteUrl\n privacyPolicyUrl\n countries\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "192a8d21a41871de201009a2dae65870";
|
||||
|
||||
export default node;
|
||||
256
apps/trust/src/queries/__generated__/TrustGraphVendorsQuery.graphql.ts
generated
Normal file
@@ -0,0 +1,256 @@
|
||||
/**
|
||||
* @generated SignedSource<<43ebc34eede5683873c41a65f93e58b1>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type TrustGraphVendorsQuery$variables = {
|
||||
slug: string;
|
||||
};
|
||||
export type TrustGraphVendorsQuery$data = {
|
||||
readonly trustCenterBySlug: {
|
||||
readonly id: string;
|
||||
readonly organization: {
|
||||
readonly name: string;
|
||||
};
|
||||
readonly vendors: {
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly id: string;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"VendorRowFragment">;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
} | null | undefined;
|
||||
};
|
||||
export type TrustGraphVendorsQuery = {
|
||||
response: TrustGraphVendorsQuery$data;
|
||||
variables: TrustGraphVendorsQuery$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": "name",
|
||||
"storageKey": null
|
||||
},
|
||||
v4 = [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 50
|
||||
}
|
||||
];
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "TrustGraphVendorsQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": "TrustCenter",
|
||||
"kind": "LinkedField",
|
||||
"name": "trustCenterBySlug",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Organization",
|
||||
"kind": "LinkedField",
|
||||
"name": "organization",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v4/*: 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*/),
|
||||
{
|
||||
"args": null,
|
||||
"kind": "FragmentSpread",
|
||||
"name": "VendorRowFragment"
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": "vendors(first:50)"
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Query",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "TrustGraphVendorsQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": "TrustCenter",
|
||||
"kind": "LinkedField",
|
||||
"name": "trustCenterBySlug",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Organization",
|
||||
"kind": "LinkedField",
|
||||
"name": "organization",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
(v2/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v4/*: 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*/),
|
||||
(v3/*: 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
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "countries",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": "vendors(first:50)"
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "1975e4b062f66fef036fc08bd7ff0b53",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "TrustGraphVendorsQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query TrustGraphVendorsQuery(\n $slug: String!\n) {\n trustCenterBySlug(slug: $slug) {\n id\n organization {\n name\n id\n }\n vendors(first: 50) {\n edges {\n node {\n id\n ...VendorRowFragment\n }\n }\n }\n }\n}\n\nfragment VendorRowFragment on Vendor {\n id\n name\n category\n websiteUrl\n privacyPolicyUrl\n countries\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "b21c7b35a682aa1fc4210e1c717e55b4";
|
||||
|
||||
export default node;
|
||||
153
apps/trust/src/routes.tsx
Normal file
@@ -0,0 +1,153 @@
|
||||
import {
|
||||
createBrowserRouter,
|
||||
Navigate,
|
||||
redirect,
|
||||
type RouteObject,
|
||||
useLoaderData,
|
||||
useRouteError,
|
||||
} from "react-router";
|
||||
import { type ComponentType, Fragment, Suspense } from "react";
|
||||
import {
|
||||
relayEnvironment,
|
||||
UnAuthenticatedError,
|
||||
} from "./providers/RelayProviders";
|
||||
import { loadQuery, type PreloadedQuery } from "react-relay";
|
||||
import { useCleanup } from "./hooks/useDelayedEffect";
|
||||
import { PageError } from "./components/PageError";
|
||||
import { MainLayout } from "/layouts/MainLayout";
|
||||
import {
|
||||
trustGraphQuery,
|
||||
trustDocumentsQuery,
|
||||
trustVendorsQuery,
|
||||
} from "/queries/TrustGraph";
|
||||
import { OverviewPage } from "/pages/OverviewPage";
|
||||
import { DocumentsPage } from "/pages/DocumentsPage";
|
||||
import { SubprocessorsPage } from "/pages/SubprocessorsPage";
|
||||
import { AccessPage } from "./pages/AccessPage.tsx";
|
||||
import { TabSkeleton } from "./components/Skeletons/TabSkeleton";
|
||||
import { MainSkeleton } from "./components/Skeletons/MainSkeleton";
|
||||
|
||||
export type AppRoute = Omit<RouteObject, "Component" | "children"> & {
|
||||
Component?: ComponentType<any>;
|
||||
children?: AppRoute[];
|
||||
fallback?: ComponentType;
|
||||
queryLoader?: (params: any) => PreloadedQuery<any>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Top level error boundary
|
||||
*/
|
||||
function ErrorBoundary({ error: propsError }: { error?: string }) {
|
||||
const error = useRouteError() ?? propsError;
|
||||
|
||||
if (error instanceof UnAuthenticatedError) {
|
||||
return <Navigate to="/auth/login" />;
|
||||
}
|
||||
|
||||
return <PageError error={error?.toString()} />;
|
||||
}
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: "/",
|
||||
Component: Fragment,
|
||||
ErrorBoundary: ErrorBoundary,
|
||||
},
|
||||
{
|
||||
path: "/trust/:slug/access",
|
||||
Component: AccessPage,
|
||||
ErrorBoundary: ErrorBoundary,
|
||||
},
|
||||
{
|
||||
path: "/trust/:slug",
|
||||
queryLoader: ({ slug }) =>
|
||||
loadQuery(relayEnvironment, trustGraphQuery, { slug: slug }),
|
||||
Component: MainLayout,
|
||||
fallback: MainSkeleton,
|
||||
ErrorBoundary: ErrorBoundary,
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
loader: ({ params }) => {
|
||||
throw redirect(`/trust/${params.slug}/overview`);
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "overview",
|
||||
fallback: TabSkeleton,
|
||||
Component: OverviewPage,
|
||||
},
|
||||
{
|
||||
path: "documents",
|
||||
fallback: TabSkeleton,
|
||||
Component: DocumentsPage,
|
||||
queryLoader: ({ slug }) =>
|
||||
loadQuery(relayEnvironment, trustDocumentsQuery, { slug: slug }),
|
||||
},
|
||||
{
|
||||
path: "subprocessors",
|
||||
fallback: TabSkeleton,
|
||||
Component: SubprocessorsPage,
|
||||
queryLoader: ({ slug }) =>
|
||||
loadQuery(relayEnvironment, trustVendorsQuery, { slug: slug }),
|
||||
},
|
||||
],
|
||||
},
|
||||
// Fallback URL to the NotFound Page
|
||||
{
|
||||
path: "*",
|
||||
Component: PageError,
|
||||
},
|
||||
] satisfies AppRoute[];
|
||||
|
||||
/**
|
||||
* Wrap components with suspense to handle lazy loading & relay loading states
|
||||
*/
|
||||
function routeTransformer({
|
||||
fallback: FallbackComponent,
|
||||
queryLoader,
|
||||
...route
|
||||
}: AppRoute): RouteObject {
|
||||
let result = { ...route };
|
||||
if (FallbackComponent && route.Component) {
|
||||
const OriginalComponent = route.Component;
|
||||
result = {
|
||||
...result,
|
||||
Component: (props) => (
|
||||
<Suspense fallback={<FallbackComponent />}>
|
||||
<OriginalComponent {...props} />
|
||||
</Suspense>
|
||||
),
|
||||
};
|
||||
}
|
||||
if (queryLoader && route.Component) {
|
||||
const OriginalComponent = route.Component;
|
||||
result = {
|
||||
...result,
|
||||
loader: ({ params }) => {
|
||||
const query = queryLoader(params as Record<string, string>);
|
||||
return {
|
||||
queryRef: query,
|
||||
dispose: query.dispose,
|
||||
};
|
||||
},
|
||||
Component: () => {
|
||||
const { queryRef, dispose } = useLoaderData();
|
||||
|
||||
useCleanup(dispose, 1000);
|
||||
|
||||
return (
|
||||
<Suspense fallback={FallbackComponent ? <FallbackComponent /> : null}>
|
||||
<OriginalComponent queryRef={queryRef} />
|
||||
</Suspense>
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
...result,
|
||||
children: route.children?.map(routeTransformer),
|
||||
} as RouteObject;
|
||||
}
|
||||
|
||||
export const router = createBrowserRouter(routes.map(routeTransformer));
|
||||
8
apps/trust/src/types.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export type NodeOf<T> =
|
||||
NonNullable<T> extends
|
||||
| { readonly edges: ReadonlyArray<{ readonly node: infer U }> }
|
||||
| undefined
|
||||
? U
|
||||
: never;
|
||||
|
||||
export type ItemOf<T> = T extends (infer U)[] ? U : never;
|
||||
9
apps/trust/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_API_URL: string;
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv;
|
||||
}
|
||||
31
apps/trust/tsconfig.app.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"/*": ["src/*"]
|
||||
},
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
7
apps/trust/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
25
apps/trust/tsconfig.node.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
33
apps/trust/vite.config.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import { fileURLToPath, URL } from "node:url";
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react({ babel: { plugins: ["relay"] } }), tailwindcss()],
|
||||
server: {
|
||||
port: 5174,
|
||||
proxy: {
|
||||
"/api": {
|
||||
target: "http://localhost:8080",
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"/type": fileURLToPath(new URL("./src/type.ts", import.meta.url)),
|
||||
"/components": fileURLToPath(
|
||||
new URL("./src/components", import.meta.url)
|
||||
),
|
||||
"/queries": fileURLToPath(new URL("./src/queries", import.meta.url)),
|
||||
"/helpers": fileURLToPath(new URL("./src/helpers", import.meta.url)),
|
||||
"/hooks": fileURLToPath(new URL("./src/hooks", import.meta.url)),
|
||||
"/layouts": fileURLToPath(new URL("./src/layouts", import.meta.url)),
|
||||
"/pages": fileURLToPath(new URL("./src/pages", import.meta.url)),
|
||||
"/routes": fileURLToPath(new URL("./src/routes", import.meta.url)),
|
||||
"/providers": fileURLToPath(new URL("./src/providers", import.meta.url)),
|
||||
},
|
||||
},
|
||||
});
|
||||
333
package-lock.json
generated
@@ -68,6 +68,50 @@
|
||||
"vite": "^6.3.6"
|
||||
}
|
||||
},
|
||||
"apps/trust": {
|
||||
"name": "@probo/trust",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^5.0.1",
|
||||
"@probo/helpers": "1.0.0",
|
||||
"@probo/hooks": "1.0.0",
|
||||
"@probo/i18n": "1.0.0",
|
||||
"@probo/ui": "1.0.0",
|
||||
"clsx": "^2.1.1",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-error-boundary": "^6.0.0",
|
||||
"react-hook-form": "^7.56.4",
|
||||
"react-pdf": "^10.1.0",
|
||||
"react-relay": "^19.0.0",
|
||||
"react-router": "^7.6.0",
|
||||
"relay-runtime": "^19.0.0",
|
||||
"usehooks-ts": "^3.1.1",
|
||||
"zod": "^3.25.71"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.25.0",
|
||||
"@tailwindcss/vite": "^4.1.7",
|
||||
"@types/node": "^22.15.21",
|
||||
"@types/react": "^19.1.2",
|
||||
"@types/react-dom": "^19.1.2",
|
||||
"@types/react-relay": "^18.2.1",
|
||||
"@types/relay-runtime": "^19.0.1",
|
||||
"@vitejs/plugin-react": "^4.4.1",
|
||||
"babel-plugin-relay": "^19.0.0",
|
||||
"eslint": "^9.25.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.19",
|
||||
"globals": "^16.0.0",
|
||||
"graphql": "^16.11.0",
|
||||
"prettier": "^3.5.3",
|
||||
"relay-compiler": "^19.0.0",
|
||||
"tailwindcss": "^4.1.7",
|
||||
"typescript": "~5.8.3",
|
||||
"typescript-eslint": "^8.30.1",
|
||||
"vite": "^6.3.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@adobe/css-tools": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.3.tgz",
|
||||
@@ -1305,6 +1349,191 @@
|
||||
"react": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas": {
|
||||
"version": "0.1.80",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.80.tgz",
|
||||
"integrity": "sha512-DxuT1ClnIPts1kQx8FBmkk4BQDTfI5kIzywAaMjQSXfNnra5UFU9PwurXrl+Je3bJ6BGsp/zmshVVFbCmyI+ww==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"workspaces": [
|
||||
"e2e/*"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@napi-rs/canvas-android-arm64": "0.1.80",
|
||||
"@napi-rs/canvas-darwin-arm64": "0.1.80",
|
||||
"@napi-rs/canvas-darwin-x64": "0.1.80",
|
||||
"@napi-rs/canvas-linux-arm-gnueabihf": "0.1.80",
|
||||
"@napi-rs/canvas-linux-arm64-gnu": "0.1.80",
|
||||
"@napi-rs/canvas-linux-arm64-musl": "0.1.80",
|
||||
"@napi-rs/canvas-linux-riscv64-gnu": "0.1.80",
|
||||
"@napi-rs/canvas-linux-x64-gnu": "0.1.80",
|
||||
"@napi-rs/canvas-linux-x64-musl": "0.1.80",
|
||||
"@napi-rs/canvas-win32-x64-msvc": "0.1.80"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-android-arm64": {
|
||||
"version": "0.1.80",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.80.tgz",
|
||||
"integrity": "sha512-sk7xhN/MoXeuExlggf91pNziBxLPVUqF2CAVnB57KLG/pz7+U5TKG8eXdc3pm0d7Od0WreB6ZKLj37sX9muGOQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-darwin-arm64": {
|
||||
"version": "0.1.80",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.80.tgz",
|
||||
"integrity": "sha512-O64APRTXRUiAz0P8gErkfEr3lipLJgM6pjATwavZ22ebhjYl/SUbpgM0xcWPQBNMP1n29afAC/Us5PX1vg+JNQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-darwin-x64": {
|
||||
"version": "0.1.80",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.80.tgz",
|
||||
"integrity": "sha512-FqqSU7qFce0Cp3pwnTjVkKjjOtxMqRe6lmINxpIZYaZNnVI0H5FtsaraZJ36SiTHNjZlUB69/HhxNDT1Aaa9vA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-linux-arm-gnueabihf": {
|
||||
"version": "0.1.80",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.80.tgz",
|
||||
"integrity": "sha512-eyWz0ddBDQc7/JbAtY4OtZ5SpK8tR4JsCYEZjCE3dI8pqoWUC8oMwYSBGCYfsx2w47cQgQCgMVRVTFiiO38hHQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-linux-arm64-gnu": {
|
||||
"version": "0.1.80",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.80.tgz",
|
||||
"integrity": "sha512-qwA63t8A86bnxhuA/GwOkK3jvb+XTQaTiVML0vAWoHyoZYTjNs7BzoOONDgTnNtr8/yHrq64XXzUoLqDzU+Uuw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-linux-arm64-musl": {
|
||||
"version": "0.1.80",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.80.tgz",
|
||||
"integrity": "sha512-1XbCOz/ymhj24lFaIXtWnwv/6eFHXDrjP0jYkc6iHQ9q8oXKzUX1Lc6bu+wuGiLhGh2GS/2JlfORC5ZcXimRcg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-linux-riscv64-gnu": {
|
||||
"version": "0.1.80",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.80.tgz",
|
||||
"integrity": "sha512-XTzR125w5ZMs0lJcxRlS1K3P5RaZ9RmUsPtd1uGt+EfDyYMu4c6SEROYsxyatbbu/2+lPe7MPHOO/0a0x7L/gw==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-linux-x64-gnu": {
|
||||
"version": "0.1.80",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.80.tgz",
|
||||
"integrity": "sha512-BeXAmhKg1kX3UCrJsYbdQd3hIMDH/K6HnP/pG2LuITaXhXBiNdh//TVVVVCBbJzVQaV5gK/4ZOCMrQW9mvuTqA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-linux-x64-musl": {
|
||||
"version": "0.1.80",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.80.tgz",
|
||||
"integrity": "sha512-x0XvZWdHbkgdgucJsRxprX/4o4sEed7qo9rCQA9ugiS9qE2QvP0RIiEugtZhfLH3cyI+jIRFJHV4Fuz+1BHHMg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-win32-x64-msvc": {
|
||||
"version": "0.1.80",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.80.tgz",
|
||||
"integrity": "sha512-Z8jPsM6df5V8B1HrCHB05+bDiCxjE9QA//3YrkKIdVDEwn5RKaqOxCJDRJkl48cJbylcrJbW4HxZbTte8juuPg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
@@ -1389,6 +1618,10 @@
|
||||
"resolved": "packages/react-lazy",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@probo/trust": {
|
||||
"resolved": "apps/trust",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@probo/tsconfig": {
|
||||
"resolved": "packages/tsconfig",
|
||||
"link": true
|
||||
@@ -7179,6 +7412,15 @@
|
||||
"source-map-js": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/make-cancellable-promise": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/make-cancellable-promise/-/make-cancellable-promise-2.0.0.tgz",
|
||||
"integrity": "sha512-3SEQqTpV9oqVsIWqAcmDuaNeo7yBO3tqPtqGRcKkEo0lrzD3wqbKG9mkxO65KoOgXqj+zH2phJ2LiAsdzlogSw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/wojtekmaj/make-cancellable-promise?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/make-dir": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
|
||||
@@ -7208,6 +7450,15 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/make-event-props": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/make-event-props/-/make-event-props-2.0.0.tgz",
|
||||
"integrity": "sha512-G/hncXrl4Qt7mauJEXSg3AcdYzmpkIITTNl5I+rH9sog5Yw0kK6vseJjCaPfOXqOqQuPUP89Rkhfz5kPS8ijtw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/wojtekmaj/make-event-props?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/map-or-similar": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/map-or-similar/-/map-or-similar-1.5.0.tgz",
|
||||
@@ -7527,6 +7778,23 @@
|
||||
"map-or-similar": "^1.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/merge-refs": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/merge-refs/-/merge-refs-2.0.0.tgz",
|
||||
"integrity": "sha512-3+B21mYK2IqUWnd2EivABLT7ueDhb0b8/dGK8LoFQPrU61YITeCMn14F7y7qZafWNZhUEKb24cJdiT5Wxs3prg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/wojtekmaj/merge-refs?sponsor=1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/merge2": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||
@@ -8522,6 +8790,18 @@
|
||||
"node": ">= 14.16"
|
||||
}
|
||||
},
|
||||
"node_modules/pdfjs-dist": {
|
||||
"version": "5.3.93",
|
||||
"resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.3.93.tgz",
|
||||
"integrity": "sha512-w3fQKVL1oGn8FRyx5JUG5tnbblggDqyx2XzA5brsJ5hSuS+I0NdnJANhmeWKLjotdbPQucLBug5t0MeWr0AAdg==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=20.16.0 || >=22.3.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@napi-rs/canvas": "^0.1.71"
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
@@ -8952,6 +9232,35 @@
|
||||
"react": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/react-pdf": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-10.1.0.tgz",
|
||||
"integrity": "sha512-iUI1YqWgwwZcsXjrehTp3Yi8nT/bvTaWULaRMMyJWvoqqSlopk4LQQ9GDqUnDtX3gzT2glrqrLbjIPl56a+Q3w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"clsx": "^2.0.0",
|
||||
"dequal": "^2.0.3",
|
||||
"make-cancellable-promise": "^2.0.0",
|
||||
"make-event-props": "^2.0.0",
|
||||
"merge-refs": "^2.0.0",
|
||||
"pdfjs-dist": "5.3.93",
|
||||
"tiny-invariant": "^1.0.0",
|
||||
"warning": "^4.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/wojtekmaj/react-pdf?sponsor=1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
"version": "0.17.0",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
|
||||
@@ -9852,7 +10161,6 @@
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
|
||||
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tinybench": {
|
||||
@@ -10782,6 +11090,15 @@
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/warning": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
||||
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/web-namespaces": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz",
|
||||
@@ -10994,20 +11311,6 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz",
|
||||
"integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==",
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.6"
|
||||
}
|
||||
},
|
||||
"node_modules/yocto-queue": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
|
||||
@@ -25,6 +25,6 @@
|
||||
"license": "MIT",
|
||||
"packageManager": "npm@11.1.0",
|
||||
"volta": {
|
||||
"node": "v24.4.0"
|
||||
"node": "24.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export { objectKeys, objectEntries, cleanFormData } from "./object";
|
||||
export { sprintf, faviconUrl, slugify } from "./string";
|
||||
export { sprintf, faviconUrl, slugify, domain } from "./string";
|
||||
export {
|
||||
getTreatment,
|
||||
getRiskImpacts,
|
||||
@@ -12,14 +12,37 @@ export { randomInt } from "./number";
|
||||
export { getMeasureStateLabel, measureStates } from "./measure";
|
||||
export { getRole, getRoles, peopleRoles } from "./people";
|
||||
export { certificationCategoryLabel, certifications } from "./certifications";
|
||||
export { getCountryName, getCountryOptions, getCountryLabel, countries, type CountryCode } from "./countries";
|
||||
export {
|
||||
getCountryName,
|
||||
getCountryOptions,
|
||||
getCountryLabel,
|
||||
countries,
|
||||
type CountryCode,
|
||||
} from "./countries";
|
||||
export { availableFrameworks } from "./frameworks";
|
||||
export { getDocumentTypeLabel, documentTypes } from "./documents";
|
||||
export { getAssetTypeVariant, getCriticityVariant } from "./assets";
|
||||
export { getSnapshotTypeLabel, getSnapshotTypeUrlPath, snapshotTypes, validateSnapshotConsistency } from "./snapshots";
|
||||
export { getAuditStateLabel, getAuditStateVariant, auditStates } from "./audits";
|
||||
export { getStatusVariant, getStatusLabel, getStatusOptions } from "./registryStatus";
|
||||
export { getObligationStatusVariant, getObligationStatusLabel, getObligationStatusOptions } from "./obligationStatus";
|
||||
export {
|
||||
getSnapshotTypeLabel,
|
||||
getSnapshotTypeUrlPath,
|
||||
snapshotTypes,
|
||||
validateSnapshotConsistency,
|
||||
} from "./snapshots";
|
||||
export {
|
||||
getAuditStateLabel,
|
||||
getAuditStateVariant,
|
||||
auditStates,
|
||||
} from "./audits";
|
||||
export {
|
||||
getStatusVariant,
|
||||
getStatusLabel,
|
||||
getStatusOptions,
|
||||
} from "./registryStatus";
|
||||
export {
|
||||
getObligationStatusVariant,
|
||||
getObligationStatusLabel,
|
||||
getObligationStatusOptions,
|
||||
} from "./obligationStatus";
|
||||
export { promisifyMutation } from "./relay";
|
||||
export { fileType, fileSize } from "./file";
|
||||
export { formatDatetime, formatDate } from "./date";
|
||||
|
||||
@@ -30,3 +30,11 @@ export function slugify(str: string): string {
|
||||
.replace(/[^a-z0-9 ]/g, "") // remove all chars not letters, numbers and spaces (to be replaced)
|
||||
.replace(/\s+/g, "-");
|
||||
}
|
||||
|
||||
export function domain(url: string): string {
|
||||
const parts = url.split("//");
|
||||
if (parts.length > 1) {
|
||||
url = parts[1];
|
||||
}
|
||||
return url.split("/")[0];
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 17C12.2833 17 12.5208 16.9042 12.7125 16.7125C12.9042 16.5208 13 16.2833 13 16V12C13 11.7167 12.9042 11.4792 12.7125 11.2875C12.5208 11.0958 12.2833 11 12 11C11.7167 11 11.4792 11.0958 11.2875 11.2875C11.0958 11.4792 11 11.7167 11 12V16C11 16.2833 11.0958 16.5208 11.2875 16.7125C11.4792 16.9042 11.7167 17 12 17ZM12 9C12.2833 9 12.5208 8.90417 12.7125 8.7125C12.9042 8.52083 13 8.28333 13 8C13 7.71667 12.9042 7.47917 12.7125 7.2875C12.5208 7.09583 12.2833 7 12 7C11.7167 7 11.4792 7.09583 11.2875 7.2875C11.0958 7.47917 11 7.71667 11 8C11 8.28333 11.0958 8.52083 11.2875 8.7125C11.4792 8.90417 11.7167 9 12 9ZM12 21.9C11.8833 21.9 11.775 21.8917 11.675 21.875C11.575 21.8583 11.475 21.8333 11.375 21.8C9.125 21.05 7.33333 19.6625 6 17.6375C4.66667 15.6125 4 13.4333 4 11.1V6.375C4 5.95833 4.12083 5.58333 4.3625 5.25C4.60417 4.91667 4.91667 4.675 5.3 4.525L11.3 2.275C11.5333 2.19167 11.7667 2.15 12 2.15C12.2333 2.15 12.4667 2.19167 12.7 2.275L18.7 4.525C19.0833 4.675 19.3958 4.91667 19.6375 5.25C19.8792 5.58333 20 5.95833 20 6.375V11.1C20 13.4333 19.3333 15.6125 18 17.6375C16.6667 19.6625 14.875 21.05 12.625 21.8C12.525 21.8333 12.425 21.8583 12.325 21.875C12.225 21.8917 12.1167 21.9 12 21.9ZM12 19.9C13.7333 19.35 15.1667 18.25 16.3 16.6C17.4333 14.95 18 13.1167 18 11.1V6.375L12 4.125L6 6.375V11.1C6 13.1167 6.56667 14.95 7.7 16.6C8.83333 18.25 10.2667 19.35 12 19.9Z" fill="#141E12"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.95 12.7L9.55 11.3C9.35 11.1 9.11667 11 8.85 11C8.58333 11 8.35 11.1 8.15 11.3C7.95 11.5 7.85 11.7375 7.85 12.0125C7.85 12.2875 7.95 12.525 8.15 12.725L10.25 14.85C10.45 15.05 10.6833 15.15 10.95 15.15C11.2167 15.15 11.45 15.05 11.65 14.85L15.9 10.6C16.1 10.4 16.2 10.1625 16.2 9.8875C16.2 9.6125 16.1 9.375 15.9 9.175C15.7 8.975 15.4625 8.875 15.1875 8.875C14.9125 8.875 14.675 8.975 14.475 9.175L10.95 12.7ZM12 21.9C11.8833 21.9 11.775 21.8917 11.675 21.875C11.575 21.8583 11.475 21.8333 11.375 21.8C9.125 21.05 7.33333 19.6625 6 17.6375C4.66667 15.6125 4 13.4333 4 11.1V6.375C4 5.95833 4.12083 5.58333 4.3625 5.25C4.60417 4.91667 4.91667 4.675 5.3 4.525L11.3 2.275C11.5333 2.19167 11.7667 2.15 12 2.15C12.2333 2.15 12.4667 2.19167 12.7 2.275L18.7 4.525C19.0833 4.675 19.3958 4.91667 19.6375 5.25C19.8792 5.58333 20 5.95833 20 6.375V11.1C20 13.4333 19.3333 15.6125 18 17.6375C16.6667 19.6625 14.875 21.05 12.625 21.8C12.525 21.8333 12.425 21.8583 12.325 21.875C12.225 21.8917 12.1167 21.9 12 21.9ZM12 19.9C13.7333 19.35 15.1667 18.25 16.3 16.6C17.4333 14.95 18 13.1167 18 11.1V6.375L12 4.125L6 6.375V11.1C6 13.1167 6.56667 14.95 7.7 16.6C8.83333 18.25 10.2667 19.35 12 19.9Z" fill="#141E12"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 13.4L13.4 14.8C13.5833 14.9833 13.8167 15.075 14.1 15.075C14.3833 15.075 14.6167 14.9833 14.8 14.8C14.9833 14.6167 15.075 14.3833 15.075 14.1C15.075 13.8167 14.9833 13.5833 14.8 13.4L13.4 12L14.8 10.6C14.9833 10.4167 15.075 10.1833 15.075 9.9C15.075 9.61667 14.9833 9.38334 14.8 9.2C14.6167 9.01667 14.3833 8.925 14.1 8.925C13.8167 8.925 13.5833 9.01667 13.4 9.2L12 10.6L10.6 9.2C10.4167 9.01667 10.1833 8.925 9.9 8.925C9.61667 8.925 9.38333 9.01667 9.2 9.2C9.01667 9.38334 8.925 9.61667 8.925 9.9C8.925 10.1833 9.01667 10.4167 9.2 10.6L10.6 12L9.2 13.4C9.01667 13.5833 8.925 13.8167 8.925 14.1C8.925 14.3833 9.01667 14.6167 9.2 14.8C9.38333 14.9833 9.61667 15.075 9.9 15.075C10.1833 15.075 10.4167 14.9833 10.6 14.8L12 13.4ZM12 21.9C11.8833 21.9 11.775 21.8917 11.675 21.875C11.575 21.8583 11.475 21.8333 11.375 21.8C9.125 21.05 7.33333 19.6625 6 17.6375C4.66667 15.6125 4 13.4333 4 11.1V6.375C4 5.95833 4.12083 5.58333 4.3625 5.25C4.60417 4.91667 4.91667 4.675 5.3 4.525L11.3 2.275C11.5333 2.19167 11.7667 2.15 12 2.15C12.2333 2.15 12.4667 2.19167 12.7 2.275L18.7 4.525C19.0833 4.675 19.3958 4.91667 19.6375 5.25C19.8792 5.58333 20 5.95833 20 6.375V11.1C20 13.4333 19.3333 15.6125 18 17.6375C16.6667 19.6625 14.875 21.05 12.625 21.8C12.525 21.8333 12.425 21.8583 12.325 21.875C12.225 21.8917 12.1167 21.9 12 21.9ZM12 19.9C13.7333 19.35 15.1667 18.25 16.3 16.6C17.4333 14.95 18 13.1167 18 11.1V6.375L12 4.125L6 6.375V11.1C6 13.1167 6.56667 14.95 7.7 16.6C8.83333 18.25 10.2667 19.35 12 19.9Z" fill="#141E12"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.6 KiB |
19
packages/ui/src/Atoms/Icons/IconMail.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { IconProps } from "./type.ts";
|
||||
|
||||
export function IconMail({ size = 24, className }: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 16 17"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M13.33 6.27v5.07h1.34V6.27h-1.34Zm-1.46 6.54H4.13v1.33h7.74v-1.33Zm-9.2-1.47V6.27H1.33v5.07h1.34Zm1.46-6.53h7.74V3.47H4.13v1.34Zm0 8c-.38 0-.63 0-.82-.02a.76.76 0 0 1-.28-.05l-.6 1.18c.25.13.51.18.77.2.26.02.57.02.93.02v-1.33Zm-2.8-1.47c0 .36 0 .68.02.93.03.26.07.53.2.78l1.19-.6a.76.76 0 0 1-.06-.29l-.01-.82H1.33Zm1.7 1.4a.67.67 0 0 1-.3-.3l-1.18.6c.2.39.5.7.88.88l.6-1.18Zm10.3-1.4-.01.82a.76.76 0 0 1-.06.28l1.19.6c.13-.24.17-.5.2-.77.02-.25.02-.57.02-.93h-1.34Zm-1.46 2.8c.36 0 .67 0 .93-.02s.52-.07.77-.2l-.6-1.18a.76.76 0 0 1-.28.05c-.2.02-.44.02-.82.02v1.33Zm1.4-1.7a.67.67 0 0 1-.3.3l.6 1.18a2 2 0 0 0 .88-.87l-1.19-.6Zm1.4-6.17c0-.36 0-.67-.02-.93a2.03 2.03 0 0 0-.2-.77l-1.19.6c.02.03.04.1.06.28l.01.82h1.34Zm-2.8-1.46.82.01c.18.02.25.04.28.06l.6-1.19a2.04 2.04 0 0 0-.77-.2c-.26-.02-.57-.02-.93-.02v1.34Zm2.58-.24a2 2 0 0 0-.88-.88l-.6 1.2c.12.05.23.16.3.28l1.18-.6ZM2.67 6.27l.01-.82a.76.76 0 0 1 .06-.28l-1.19-.6c-.13.25-.17.51-.2.77-.02.26-.02.57-.02.93h1.34Zm1.46-2.8c-.36 0-.67 0-.93.02-.26.03-.52.07-.77.2l.6 1.2c.03-.03.1-.05.28-.07l.82-.01V3.47Zm-1.4 1.7a.67.67 0 0 1 .3-.29l-.6-1.19a2 2 0 0 0-.88.88l1.19.6Zm10.85-1.12L8.42 8.27l.85 1.03 5.15-4.22-.84-1.03Zm-6 4.22L2.42 4.05l-.84 1.03L6.73 9.3l.85-1.03Zm.84 0c-.24.2-.6.2-.84 0L6.73 9.3a2 2 0 0 0 2.54 0l-.85-1.03Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
21
packages/ui/src/Atoms/Icons/IconMedal.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { IconProps } from "./type.ts";
|
||||
|
||||
export function IconMedal({ size = 24, className }: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 16 16"
|
||||
fill="transparent"
|
||||
className={className}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M5.33331 10.1673V14.0249C5.33331 14.3965 5.72447 14.6383 6.05692 14.4721L7.77638 13.6123C7.91711 13.5419 8.08285 13.5419 8.22358 13.6123L9.94305 14.4721C10.2755 14.6383 10.6666 14.3965 10.6666 14.0249V10.1673M12.6666 6.00065C12.6666 8.57798 10.5773 10.6673 7.99998 10.6673C5.42265 10.6673 3.33331 8.57798 3.33331 6.00065C3.33331 3.42332 5.42265 1.33398 7.99998 1.33398C10.5773 1.33398 12.6666 3.42332 12.6666 6.00065Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.33333"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
19
packages/ui/src/Atoms/Icons/IconMinusLarge.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import type {IconProps} from "./type.ts";
|
||||
|
||||
export function IconMinusLarge({size = 16, className}: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
className={className}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M13.3333 7.33203C13.7015 7.33203 14 7.63051 14 7.9987C14 8.36689 13.7015 8.66536 13.3333 8.66536H2.66667C2.29848 8.66536 2 8.36689 2 7.9987C2 7.63051 2.29848 7.33203 2.66667 7.33203H13.3333Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
27
packages/ui/src/Atoms/Icons/IconPin.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import type { IconProps } from "./type.ts";
|
||||
|
||||
export function IconPin({ size = 24, className }: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 17 16"
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.33"
|
||||
d="M10 6.67a1.67 1.67 0 1 1-3.33 0 1.67 1.67 0 0 1 3.33 0Z"
|
||||
/>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.33"
|
||||
d="M13 6.67c0 2.91-2.6 5.55-3.91 6.71-.44.38-1.07.38-1.51 0-1.32-1.16-3.91-3.8-3.91-6.71a4.67 4.67 0 1 1 9.33 0Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20.4 13.9032L17.1216 10.6248C15.9516 9.4548 14.0484 9.4548 12.8796 10.6248L4.48679 19.0176C4.46639 19.038 4.46039 19.0668 4.44239 19.0884C5.10239 19.884 6.08639 20.4 7.19999 20.4H16.8C18.7884 20.4 20.4 18.7884 20.4 16.8V13.9032Z" fill="#141E12"/>
|
||||
<path d="M7.1996 4.80038C5.87436 4.8006 4.80029 5.87454 4.80019 7.1998V16.8004C4.8004 18.1256 5.87443 19.1996 7.1996 19.1998H16.8002C18.1254 19.1997 19.1994 18.1256 19.1996 16.8004V7.1998C19.1995 5.87447 18.1255 4.80049 16.8002 4.80038H7.1996ZM16.8002 2.39999C19.451 2.4001 21.5999 4.54899 21.6 7.1998V16.8004C21.5998 19.4511 19.4509 21.6001 16.8002 21.6002H7.1996C4.54894 21.6 2.40001 19.451 2.3998 16.8004V7.1998C2.3999 4.54905 4.54888 2.4002 7.1996 2.39999H16.8002Z" fill="#141E12"/>
|
||||
<path d="M8.99999 10.8C9.9941 10.8 10.8 9.99411 10.8 8.99999C10.8 8.00588 9.9941 7.19999 8.99999 7.19999C8.00588 7.19999 7.19999 8.00588 7.19999 8.99999C7.19999 9.99411 8.00588 10.8 8.99999 10.8Z" fill="#141E12"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.0491 4.80802C19.0491 4.80802 19.0491 4.80801 19.0491 4.80802L4.8771 9.53241C4.83932 9.545 4.82796 9.55887 4.8227 9.56584C4.81377 9.57765 4.80347 9.59908 4.80073 9.62873C4.79798 9.65838 4.80416 9.6814 4.8108 9.69473C4.81474 9.70264 4.82338 9.71838 4.85816 9.73769L9.81272 12.4902L12.7019 9.60107C13.1705 9.13244 13.9303 9.13244 14.3989 9.60107C14.8676 10.0697 14.8676 10.8295 14.3989 11.2981L11.5098 14.1873L14.2622 19.1416C14.2815 19.1764 14.2974 19.1853 14.3053 19.1892C14.3186 19.1958 14.3416 19.202 14.3713 19.1993C14.4009 19.1965 14.4224 19.1862 14.4342 19.1773C14.4411 19.172 14.455 19.1607 14.4676 19.1229L19.192 4.95092C19.192 4.95093 19.192 4.95091 19.192 4.95092C19.2031 4.91743 19.2002 4.90003 19.1976 4.88993C19.1937 4.87511 19.1838 4.85485 19.1645 4.83553C19.1452 4.8162 19.1249 4.80626 19.1101 4.80244C19.1 4.79983 19.0826 4.79686 19.0491 4.80802ZM9.13872 14.8613L3.69304 11.8359C1.78214 10.7748 2.0434 7.94718 4.1181 7.25559C4.11809 7.25559 4.11811 7.25558 4.1181 7.25559L18.2901 2.53119C20.2542 1.87648 22.1235 3.74576 21.4688 5.70987L16.7444 19.8819C16.7444 19.8819 16.7444 19.8819 16.7444 19.8819C16.0529 21.9565 13.2254 22.2178 12.1642 20.3072C12.1642 20.3071 12.1642 20.3072 12.1642 20.3072L9.13872 14.8613Z" fill="#141E12"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |