Remove old frontend
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
@@ -46,7 +46,7 @@ bin/probod: pkg/server/api/console/v1/schema/schema.go pkg/server/api/console/v1
|
||||
.PHONY: @probo/console
|
||||
@probo/console: NODE_ENV=production
|
||||
@probo/console:
|
||||
$(NPM) --workspace $@ run typecheck
|
||||
$(NPM) --workspace $@ run check
|
||||
$(NPM) --workspace $@ run build
|
||||
|
||||
pkg/server/api/console/v1/schema/schema.go \
|
||||
@@ -60,7 +60,7 @@ help: ## Show this help
|
||||
|
||||
.PHONY:dev
|
||||
dev: ## Start the development server
|
||||
parallel -j 2 --line-buffer ::: "gow -r=false run cmd/probod/main.go" "cd apps/console2 && npm run dev"
|
||||
parallel -j 2 --line-buffer ::: "gow -r=false run cmd/probod/main.go" "cd apps/console && npm run dev"
|
||||
|
||||
.PHONY: fmt
|
||||
fmt: fmt-go ## Format Go code
|
||||
@@ -90,4 +90,3 @@ stack-ps: ## List the docker stack containers
|
||||
.PHONY: psql
|
||||
psql: ## Open a psql shell to the postgres container
|
||||
$(DOCKER_COMPOSE) exec postgres psql -U probod -d probod
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
LIVE_RELOAD=true
|
||||
|
||||
POSTHOG_HOST=
|
||||
POSTHOG_KEY=
|
||||
FARO_PUSH_URL=
|
||||
|
||||
API_SERVER_HOST="http://localhost:8080"
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
LIVE_RELOAD=
|
||||
|
||||
POSTHOG_HOST=
|
||||
POSTHOG_KEY=
|
||||
FARO_PUSH_URL=
|
||||
|
||||
HTTP_ENDPOINT="http://example.com"
|
||||
@@ -1,6 +0,0 @@
|
||||
POSTHOG_HOST=
|
||||
POSTHOG_KEY=
|
||||
FARO_PUSH_URL=
|
||||
|
||||
API_SERVER_HOST=
|
||||
|
||||
27
apps/console/.gitignore
vendored
@@ -1,2 +1,25 @@
|
||||
!dist/.keep
|
||||
dist/
|
||||
# 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?
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.ts",
|
||||
"css": "src/App.css",
|
||||
"baseColor": "zinc",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"iconLibrary": "lucide"
|
||||
}
|
||||
@@ -1,32 +1,28 @@
|
||||
import globals from "globals";
|
||||
import pluginJs from "@eslint/js";
|
||||
import tseslint from "typescript-eslint";
|
||||
import pluginReact from "eslint-plugin-react";
|
||||
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'
|
||||
|
||||
/** @type {import('eslint').Linter.Config[]} */
|
||||
export default [
|
||||
{ files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"] },
|
||||
export default tseslint.config(
|
||||
{ ignores: ['dist'] },
|
||||
{
|
||||
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
parserOptions: {
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
pluginJs.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
pluginReact.configs.flat.recommended,
|
||||
{
|
||||
settings: {
|
||||
react: {
|
||||
version: "detect",
|
||||
},
|
||||
plugins: {
|
||||
'react-hooks': reactHooks,
|
||||
'react-refresh': reactRefresh,
|
||||
},
|
||||
rules: {
|
||||
"react/react-in-jsx-scope": "off",
|
||||
...reactHooks.configs.recommended.rules,
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
)
|
||||
|
||||
@@ -1,47 +1,37 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Probo - Console</title>
|
||||
|
||||
<link rel="icon shortcut" href="/assets/favicon.ico" sizes="32x32" />
|
||||
<link rel="icon" href="/assets/favicon.svg" type="image/svg+xml" />
|
||||
<link
|
||||
rel="icon"
|
||||
href="/assets/favicon-dark.svg"
|
||||
type="image/svg+xml"
|
||||
media="(prefers-color-scheme: dark)"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
href="/assets/favicon-16x16.png"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
href="/assets/favicon-32x32.png"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
href="/assets/apple-touch-icon.png"
|
||||
sizes="180x180"
|
||||
/>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
|
||||
<meta name="description" content="The open-source compliance platform" />
|
||||
<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>Probo Console</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
</head>
|
||||
<body class="font-sans">
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/App.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,93 +1,61 @@
|
||||
{
|
||||
"name": "@probo/console",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "npx node --env-file .env.development scripts/esbuild.mjs watch",
|
||||
"build": "npx node --env-file .env.production scripts/esbuild.mjs",
|
||||
"relay": "npx relay-compiler --output quiet-with-errors",
|
||||
"relay:watch": "npx relay-compiler --output quiet-with-errors --watch",
|
||||
"lint": "eslint src --ext .ts,.tsx",
|
||||
"typecheck": "npx tsc --noEmit"
|
||||
"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/react-lazy": "1.0.0",
|
||||
"@probo/vendors": "^0.0.1",
|
||||
"@radix-ui/colors": "^3.0.0",
|
||||
"@radix-ui/react-avatar": "^1.1.3",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-collapsible": "^1.1.3",
|
||||
"@radix-ui/react-dialog": "^1.1.6",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.6",
|
||||
"@radix-ui/react-label": "^2.0.2",
|
||||
"@radix-ui/react-popover": "^1.1.6",
|
||||
"@radix-ui/react-progress": "^1.1.2",
|
||||
"@radix-ui/react-radio-group": "^1.1.3",
|
||||
"@radix-ui/react-select": "^2.0.0",
|
||||
"@radix-ui/react-separator": "^1.1.2",
|
||||
"@radix-ui/react-slot": "^1.1.2",
|
||||
"@radix-ui/react-switch": "^1.1.4",
|
||||
"@radix-ui/react-tabs": "^1.1.3",
|
||||
"@radix-ui/react-toast": "^1.2.6",
|
||||
"@radix-ui/react-tooltip": "^1.1.8",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"@probo/ui": "1.0.0",
|
||||
"@probo/vendors": "0.0.1",
|
||||
"@radix-ui/react-dialog": "^1.1.14",
|
||||
"@radix-ui/react-label": "^2.1.7",
|
||||
"@radix-ui/react-select": "^2.2.5",
|
||||
"@tanstack/react-query": "^5.76.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.0.4",
|
||||
"date-fns": "^4.1.0",
|
||||
"fuse.js": "^7.1.0",
|
||||
"lucide-react": "^0.475.0",
|
||||
"posthog-js": "^1.215.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-helmet-async": "^2.0.5",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-relay": "^18.2.0",
|
||||
"react-router": "^7.1.5",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"relay-runtime": "^18.2.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"tailwind-merge": "^3.0.2",
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
"minisearch": "^7.1.2",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-error-boundary": "^6.0.0",
|
||||
"react-hook-form": "^7.56.4",
|
||||
"react-relay": "^19.0.0",
|
||||
"react-router": "^7.6.0",
|
||||
"relay-runtime": "^19.0.0",
|
||||
"use-debounce": "^10.0.5",
|
||||
"usehooks-ts": "^3.1.1",
|
||||
"zod": "^3.25.17"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.26.7",
|
||||
"@babel/preset-env": "^7.26.7",
|
||||
"@babel/preset-typescript": "^7.26.0",
|
||||
"@eslint/js": "^9.21.0",
|
||||
"@probo/esbuild-plugin-postcss": "^0.0.1",
|
||||
"@probo/tsconfig": "^0.0.1",
|
||||
"@tailwindcss/postcss": "^4.0.9",
|
||||
"@types/babel-core": "^6.25.10",
|
||||
"@types/node": "^22.13.0",
|
||||
"@types/react": "^18.3.18",
|
||||
"@types/react-dom": "^18.3.5",
|
||||
"@types/react-relay": "^18.2.0",
|
||||
"@types/relay-runtime": "^18.2.5",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"babel-plugin-relay": "^18.2.0",
|
||||
"esbuild": "^0.25.0",
|
||||
"eslint": "^9.21.0",
|
||||
"eslint-plugin-react": "^7.37.4",
|
||||
"@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",
|
||||
"relay-compiler": "^18.2.0",
|
||||
"tailwindcss": "^4.0.9",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.25.0"
|
||||
},
|
||||
"relay": {
|
||||
"src": "src",
|
||||
"language": "typescript",
|
||||
"schema": "../../pkg/server/api/console/v1/schema.graphql",
|
||||
"noFutureProofEnums": true,
|
||||
"customScalarTypes": {
|
||||
"Datetime": "string",
|
||||
"CursorKey": "string"
|
||||
},
|
||||
"excludes": [
|
||||
"**/node_modules/**",
|
||||
"**/__mocks__/**",
|
||||
"**/__generated__/**"
|
||||
]
|
||||
"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.5"
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 280 KiB |
|
Before Width: | Height: | Size: 36 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="9" height="9" viewBox="0 0 9 9" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.239641 0.239641C0.559163 -0.0798804 1.0772 -0.0798804 1.39672 0.239641L4.5 3.34292L7.60326 0.239641C7.92278 -0.0798804 8.44086 -0.0798804 8.76038 0.239641C9.07985 0.559163 9.07985 1.0772 8.76038 1.39672L5.65707 4.5L8.76038 7.60326C9.07985 7.92278 9.07985 8.44086 8.76038 8.76038C8.44086 9.07985 7.92278 9.07985 7.60326 8.76038L4.5 5.65707L1.39672 8.76038C1.0772 9.07985 0.559163 9.07985 0.239641 8.76038C-0.0798804 8.44086 -0.0798804 7.92278 0.239641 7.60326L3.34292 4.5L0.239641 1.39672C-0.0798804 1.0772 -0.0798804 0.559163 0.239641 0.239641Z" fill="#6B716A"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 714 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 4.0 MiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 4.0 MiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 625 B After Width: | Height: | Size: 625 B |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 945 B After Width: | Height: | Size: 945 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6 0C6.36819 0 6.66667 0.298477 6.66667 0.666667V6.05719L7.86193 4.86193C8.12228 4.60158 8.54439 4.60158 8.80474 4.86193C9.06509 5.12228 9.06509 5.54439 8.80474 5.80474L6.4714 8.13807C6.21106 8.39842 5.78895 8.39842 5.5286 8.13807L3.19526 5.80474C2.93491 5.54439 2.93491 5.12228 3.19526 4.86193C3.45561 4.60158 3.87772 4.60158 4.13807 4.86193L5.33333 6.05719V0.666667C5.33333 0.298477 5.63181 0 6 0ZM0.666667 7.33333C1.03486 7.33333 1.33333 7.63181 1.33333 8V9.33333C1.33333 10.0697 1.93028 10.6667 2.66667 10.6667H9.33333C10.0697 10.6667 10.6667 10.0697 10.6667 9.33333V8C10.6667 7.63181 10.9651 7.33333 11.3333 7.33333C11.7015 7.33333 12 7.63181 12 8V9.33333C12 10.8061 10.8061 12 9.33333 12H2.66667C1.19392 12 0 10.8061 0 9.33333V8C0 7.63181 0.298477 7.33333 0.666667 7.33333Z" fill="#141E12"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 950 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.99966 1.39993C3.90702 1.39993 1.39993 3.90702 1.39993 6.99966C1.39993 10.0923 3.90701 12.5994 6.99966 12.5994C10.0923 12.5994 12.5994 10.0923 12.5994 6.99966C12.5994 3.90701 10.0923 1.39993 6.99966 1.39993ZM0 6.99966C0 3.13386 3.13386 0 6.99966 0C10.8655 0 13.9993 3.13386 13.9993 6.99966C13.9993 10.8655 10.8655 13.9993 6.99966 13.9993C3.13386 13.9993 0 10.8655 0 6.99966ZM7.69962 3.49983V6.70972L9.73947 8.74957L8.74957 9.73947L6.29969 7.28959V3.49983H7.69962Z" fill="#141E12"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 636 B |
@@ -1,5 +0,0 @@
|
||||
<svg width="14" height="3" viewBox="0 0 14 3" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.59993 1.39998C5.59993 0.626783 6.22671 0 6.99991 0C7.77311 0 8.3999 0.626783 8.3999 1.39998C8.3999 2.17318 7.77311 2.79997 6.99991 2.79997C6.22671 2.79997 5.59993 2.17318 5.59993 1.39998Z" fill="#141E12"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.1999 1.39998C11.1999 0.626783 11.8266 0 12.5998 0C13.373 0 13.9998 0.626783 13.9998 1.39998C13.9998 2.17318 13.373 2.79997 12.5998 2.79997C11.8266 2.79997 11.1999 2.17318 11.1999 1.39998Z" fill="#141E12"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 1.39998C0 0.626774 0.626806 0 1.39998 0C2.17316 0 2.79997 0.626774 2.79997 1.39998C2.79997 2.17319 2.17316 2.79997 1.39998 2.79997C0.626806 2.79997 0 2.17319 0 1.39998Z" fill="#141E12"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 857 B |
@@ -1,5 +0,0 @@
|
||||
<svg width="14" height="3" viewBox="0 0 14 3" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.59993 1.39998C5.59993 0.626783 6.22671 0 6.99991 0C7.77311 0 8.3999 0.626783 8.3999 1.39998C8.3999 2.17318 7.77311 2.79997 6.99991 2.79997C6.22671 2.79997 5.59993 2.17318 5.59993 1.39998Z" fill="#141E12"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.1999 1.39998C11.1999 0.626783 11.8266 0 12.5998 0C13.373 0 13.9998 0.626783 13.9998 1.39998C13.9998 2.17318 13.373 2.79997 12.5998 2.79997C11.8266 2.79997 11.1999 2.17318 11.1999 1.39998Z" fill="#141E12"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 1.39998C0 0.626774 0.626806 0 1.39998 0C2.17316 0 2.79997 0.626774 2.79997 1.39998C2.79997 2.17319 2.17316 2.79997 1.39998 2.79997C0.626806 2.79997 0 2.17319 0 1.39998Z" fill="#141E12"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 857 B |
@@ -1,49 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Probo - Console</title>
|
||||
|
||||
<link rel="icon shortcut" href="/assets/favicon.ico" sizes="32x32" />
|
||||
<link rel="icon" href="/assets/favicon.svg" type="image/svg+xml" />
|
||||
<link
|
||||
rel="icon"
|
||||
href="/assets/favicon-dark.svg"
|
||||
type="image/svg+xml"
|
||||
media="(prefers-color-scheme: dark)"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
href="/assets/favicon-16x16.png"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
href="/assets/favicon-32x32.png"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
href="/assets/apple-touch-icon.png"
|
||||
sizes="180x180"
|
||||
/>
|
||||
|
||||
<meta name="description" content="The open-source compliance platform" />
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<link rel="stylesheet" href="/App.css" />
|
||||
</head>
|
||||
<body class="font-sans">
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/App.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"src": "./src",
|
||||
"schema": "./schema.graphql",
|
||||
"schema": "../../pkg/server/api/console/v1/schema.graphql",
|
||||
"language": "typescript",
|
||||
"eagerEsModules": true
|
||||
"eagerEsModules": true,
|
||||
"noFutureProofEnums": true
|
||||
}
|
||||
|
||||
@@ -1,278 +0,0 @@
|
||||
import babel from "@babel/core";
|
||||
import postCssPlugin from "@probo/esbuild-plugin-postcss";
|
||||
import autoprefixer from "autoprefixer";
|
||||
import { execSync, spawn } from "child_process";
|
||||
import * as esbuild from "esbuild";
|
||||
import fs from "node:fs/promises";
|
||||
import http from "node:http";
|
||||
import path from "node:path";
|
||||
import process from "node:process";
|
||||
import tailwindcssPlugin from "@tailwindcss/postcss";
|
||||
|
||||
async function copyRecursive(src, dest) {
|
||||
await fs.mkdir(dest, { recursive: true });
|
||||
const files = await fs.readdir(src);
|
||||
|
||||
for (const file of files) {
|
||||
const srcPath = path.join(src, file);
|
||||
const destPath = path.join(dest, file);
|
||||
|
||||
const fileInfo = await fs.lstat(srcPath);
|
||||
|
||||
if (fileInfo.isDirectory()) {
|
||||
await copyRecursive(srcPath, destPath);
|
||||
} else {
|
||||
await fs.copyFile(srcPath, destPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function runRelayCompilerInWatchMode() {
|
||||
const relayProcess = spawn("npm", ["run", "relay:watch"]);
|
||||
|
||||
relayProcess.stdout.on("data", (data) => {
|
||||
console.log(`[relay] ${data}`);
|
||||
});
|
||||
|
||||
relayProcess.stderr.on("data", (data) => {
|
||||
console.error(`[relay] ${data}`);
|
||||
});
|
||||
|
||||
relayProcess.on("error", (error) => {
|
||||
console.error("Failed to start Relay compiler:", error);
|
||||
});
|
||||
|
||||
console.log("Relay compiler started in watch mode.");
|
||||
}
|
||||
|
||||
const envVars = Object.fromEntries(
|
||||
Object.entries(process.env).map(([key, value]) => [
|
||||
`process.env.${key}`,
|
||||
JSON.stringify(value),
|
||||
])
|
||||
);
|
||||
|
||||
const hotReloading = {
|
||||
name: "hot-reloading",
|
||||
async setup(build) {
|
||||
if (!process.env.LIVE_RELOAD) {
|
||||
return;
|
||||
}
|
||||
|
||||
build.initialOptions.banner = build.initialOptions.banner || {};
|
||||
build.initialOptions.banner.js = `
|
||||
${build.initialOptions.banner.js || ""};
|
||||
|
||||
if (!window.__esbuild_event_source__) {
|
||||
window.__esbuild_event_source__ = true;
|
||||
new EventSource('/esbuild').addEventListener('change', (e) => {
|
||||
const { added, removed, updated } = JSON.parse(e.data);
|
||||
|
||||
if (!added.length && !removed.length && updated.length === 1) {
|
||||
for (const link of document.getElementsByTagName("link")) {
|
||||
const url = new URL(link.href);
|
||||
|
||||
if (url.host === location.host && url.pathname === updated[0]) {
|
||||
const next = link.cloneNode();
|
||||
next.href = updated[0] + '?' + Math.random().toString(36).slice(2);
|
||||
next.onload = () => link.remove();
|
||||
link.parentNode.insertBefore(next, link.nextSibling);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
location.reload();
|
||||
});
|
||||
}
|
||||
`;
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
const relayPlugin = {
|
||||
name: "relay-compiler",
|
||||
setup(build) {
|
||||
build.initialOptions.loader = {
|
||||
...build.initialOptions.loader,
|
||||
".graphql": "text",
|
||||
};
|
||||
|
||||
build.onLoad({ filter: /\.(ts|tsx)$/ }, async (args) => {
|
||||
const source = await fs.readFile(args.path, "utf8");
|
||||
|
||||
const result = await babel.transformAsync(source, {
|
||||
filename: args.path,
|
||||
presets: [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
modules: false,
|
||||
},
|
||||
],
|
||||
"@babel/preset-typescript",
|
||||
],
|
||||
plugins: ["relay"],
|
||||
sourceMaps: true,
|
||||
});
|
||||
|
||||
return {
|
||||
contents: result.code,
|
||||
loader: "default",
|
||||
};
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const htmlPlugin = {
|
||||
name: 'html-plugin',
|
||||
setup(build) {
|
||||
build.onEnd(async (result) => {
|
||||
// do not run this in watch mode
|
||||
if (process.argv[2] === "watch") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.errors.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result.metafile) {
|
||||
console.error('Metafile not available');
|
||||
return;
|
||||
}
|
||||
|
||||
const outputs = Object.keys(result.metafile.outputs);
|
||||
|
||||
const jsOutputs = outputs.filter(file => file.endsWith('.js') && file.includes('/App-'));
|
||||
const cssOutputs = outputs.filter(file => file.endsWith('.css') && file.includes('/App-'));
|
||||
|
||||
if (jsOutputs.length === 0 || cssOutputs.length === 0) {
|
||||
console.error('Could not find entry point outputs');
|
||||
return;
|
||||
}
|
||||
|
||||
const jsFile = path.basename(jsOutputs[0]);
|
||||
const cssFile = path.basename(cssOutputs[0]);
|
||||
|
||||
console.log('Found JS file:', jsFile);
|
||||
console.log('Found CSS file:', cssFile);
|
||||
|
||||
const htmlPath = path.join(process.cwd(), 'dist', 'index.html');
|
||||
let html = await fs.readFile(htmlPath, "utf8");
|
||||
|
||||
html = html.replace('"/App.css"', `"/${cssFile}"`);
|
||||
html = html.replace('"/App.js"', `"/${jsFile}"`);
|
||||
|
||||
await fs.writeFile(htmlPath, html);
|
||||
console.log("Successfully updated index.html with hashed filenames");
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const defaultOptions = {
|
||||
logLevel: "info",
|
||||
entryPoints: ["src/App.tsx"],
|
||||
bundle: true,
|
||||
minify: true,
|
||||
publicPath: "/",
|
||||
outdir: "dist",
|
||||
assetNames: "assets/[name]-[hash]",
|
||||
chunkNames: "chunks/[name]-[hash]",
|
||||
entryNames: "[dir]/[name]-[hash]",
|
||||
splitting: true,
|
||||
format: "esm",
|
||||
sourcemap: "external",
|
||||
metafile: true,
|
||||
loader: {
|
||||
".png": "file",
|
||||
".jpg": "file",
|
||||
".jpeg": "file",
|
||||
".svg": "file",
|
||||
".gif": "file",
|
||||
},
|
||||
plugins: [
|
||||
relayPlugin,
|
||||
hotReloading,
|
||||
htmlPlugin,
|
||||
postCssPlugin({
|
||||
plugins: [tailwindcssPlugin(), autoprefixer()],
|
||||
}),
|
||||
],
|
||||
define: {
|
||||
...envVars,
|
||||
},
|
||||
};
|
||||
|
||||
await copyRecursive("public", "dist");
|
||||
|
||||
const [, , command] = process.argv;
|
||||
|
||||
if (command === "watch") {
|
||||
runRelayCompilerInWatchMode();
|
||||
|
||||
const ctx = await esbuild.context({
|
||||
...defaultOptions,
|
||||
minify: false,
|
||||
entryNames: "[dir]/[name]"
|
||||
});
|
||||
await ctx.watch();
|
||||
let { host, port } = await ctx.serve({ servedir: "dist" });
|
||||
http
|
||||
.createServer((req, res) => {
|
||||
const options = {
|
||||
hostname: host,
|
||||
port: port,
|
||||
path: req.url,
|
||||
method: req.method,
|
||||
headers: req.headers,
|
||||
};
|
||||
|
||||
const urlObj = new URL(req.url, `http://${req.headers.host}`);
|
||||
const extensionPattern = /\.[^/]+$/;
|
||||
|
||||
const proxyReq = http.request(options, (proxyRes) => {
|
||||
if (
|
||||
proxyRes.statusCode === 404 &&
|
||||
extensionPattern.test(urlObj.pathname)
|
||||
) {
|
||||
res.writeHead(404, { "Content-Type": "text/plain" });
|
||||
res.end("404 - Not Found");
|
||||
return;
|
||||
}
|
||||
|
||||
if (proxyRes.statusCode === 404) {
|
||||
const indexPath = path.join(
|
||||
import.meta.dirname,
|
||||
"../dist",
|
||||
"index.html"
|
||||
);
|
||||
|
||||
fs.readFile(indexPath, "utf8")
|
||||
.then((data) => {
|
||||
res.writeHead(200, { "Content-Type": "text/html" });
|
||||
res.end(data);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Error reading index.html:", err);
|
||||
res.writeHead(500, { "Content-Type": "text/plain" });
|
||||
res.end("500 - Internal Server Error");
|
||||
});
|
||||
|
||||
proxyRes.resume();
|
||||
return;
|
||||
}
|
||||
|
||||
res.writeHead(proxyRes.statusCode, proxyRes.headers);
|
||||
proxyRes.pipe(res, { end: true });
|
||||
});
|
||||
|
||||
req.pipe(proxyReq, { end: true });
|
||||
})
|
||||
.listen(3000, () => {
|
||||
console.log(`listening on http://localhost:3000`);
|
||||
});
|
||||
} else {
|
||||
execSync("npm run relay", { stdio: "inherit" });
|
||||
await esbuild.build(defaultOptions);
|
||||
}
|
||||
@@ -1,323 +0,0 @@
|
||||
@import "tailwindcss";
|
||||
@import "@radix-ui/colors/blue.css";
|
||||
@import "@radix-ui/colors/blue-dark.css";
|
||||
@import "@radix-ui/colors/olive.css";
|
||||
@import "@radix-ui/colors/olive-alpha.css";
|
||||
@import "@radix-ui/colors/olive-dark.css";
|
||||
@import "@radix-ui/colors/olive-dark-alpha.css";
|
||||
@import "@radix-ui/colors/lime.css";
|
||||
@import "@radix-ui/colors/lime-dark.css";
|
||||
@import "@radix-ui/colors/red.css";
|
||||
@import "@radix-ui/colors/red-dark.css";
|
||||
@import "@radix-ui/colors/red-dark-alpha.css";
|
||||
@import "@radix-ui/colors/orange.css";
|
||||
@import "@radix-ui/colors/orange-dark.css";
|
||||
|
||||
@import "./styles/document-content.css";
|
||||
|
||||
@plugin 'tailwindcss-animate';
|
||||
@plugin "@tailwindcss/typography";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@theme {
|
||||
/* Legacy */
|
||||
/* --color-border: hsl(var(--border)); -> solid-b
|
||||
--color-input: hsl(var(--input)); -> solid-b
|
||||
--color-ring: hsl(var(--ring)); -> active-b
|
||||
--color-background: hsl(var(--background)); -> level-0
|
||||
--color-foreground: hsl(var(--foreground)); -> primary
|
||||
|
||||
--color-primary: hsl(var(--primary)); -> primary
|
||||
--color-primary-foreground: hsl(var(--primary-foreground)); -> invert
|
||||
|
||||
--color-secondary: hsl(var(--secondary)); -> invert
|
||||
--color-secondary-foreground: hsl(var(--secondary-foreground)); -> secondary
|
||||
|
||||
--color-destructive: hsl(var(--destructive)); -> danger-bg
|
||||
--color-destructive-foreground: hsl(var(--destructive-foreground)); -> danger
|
||||
|
||||
--color-muted: hsl(var(--muted)); -> invert-bg
|
||||
--color-muted-foreground: hsl(var(--muted-foreground)); -> tertiary
|
||||
|
||||
--color-accent: hsl(var(--accent)); -> accent-bg
|
||||
--color-accent-foreground: hsl(var(--accent-foreground)); -> accent
|
||||
|
||||
--color-popover: hsl(var(--popover)); -> level-0
|
||||
--color-popover-foreground: hsl(var(--popover-foreground)); -> primary
|
||||
|
||||
--color-card: hsl(var(--card)); -> level-0
|
||||
--color-card-foreground: hsl(var(--card-foreground)); -> primary
|
||||
|
||||
--color-sidebar: hsl(var(--sidebar-background)); -> level-0
|
||||
--color-sidebar-foreground: hsl(var(--sidebar-foreground)); -> secondary
|
||||
--color-sidebar-primary: hsl(var(--sidebar-primary)); -> not used
|
||||
--color-sidebar-primary-foreground: hsl(var(--sidebar-primary-foreground));
|
||||
--color-sidebar-accent: hsl(var(--sidebar-accent));
|
||||
--color-sidebar-accent-foreground: hsl(var(--sidebar-accent-foreground));
|
||||
--color-sidebar-border: hsl(var(--sidebar-border)); -> solid-b
|
||||
--color-sidebar-ring: hsl(var(--sidebar-ring)); -> active-b */
|
||||
|
||||
/* New */
|
||||
/* Surfaces */
|
||||
--color-level-0: var(--level-0);
|
||||
--color-level-1: var(--level-1);
|
||||
--color-level-2: var(--level-2);
|
||||
--color-level-3: var(--level-3);
|
||||
|
||||
/* Text colors */
|
||||
--color-primary: var(--primary);
|
||||
--color-invert: var(--invert);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-tertiary: var(--tertiary);
|
||||
--color-quaternary: var(--quaternary);
|
||||
--color-disabled: var(--disabled);
|
||||
--color-accent: var(--accent);
|
||||
--color-danger: var(--danger);
|
||||
--color-success: var(--success);
|
||||
--color-info: var(--info);
|
||||
--color-warning: var(--warning);
|
||||
|
||||
/* Background colors */
|
||||
--color-primary-bg: var(--primary-bg);
|
||||
--color-invert-bg: var(--invert-bg);
|
||||
--color-secondary-bg: var(--secondary-bg);
|
||||
--color-tertiary-bg: var(--tertiary-bg);
|
||||
--color-subtle-bg: var(--subtle-bg);
|
||||
--color-highlight-bg: var(--highlight-bg);
|
||||
--color-accent-bg: var(--accent-bg);
|
||||
--color-active-bg: var(--active-bg);
|
||||
--color-danger-bg: var(--danger-bg);
|
||||
--color-danger-plain-bg: var(--danger-plain-bg);
|
||||
--color-success-bg: var(--success-bg);
|
||||
--color-info-bg: var(--info-bg);
|
||||
--color-warning-bg: var(--warning-bg);
|
||||
/* Hover */
|
||||
--color-h-primary-bg: var(--h-primary-bg);
|
||||
--color-h-invert-bg: var(--h-invert-bg);
|
||||
--color-h-secondary-bg: var(--h-secondary-bg);
|
||||
--color-h-tertiary-bg: var(--h-tertiary-bg);
|
||||
--color-h-subtle-bg: var(--h-subtle-bg);
|
||||
--color-h-highlight-bg: var(--h-highlight-bg);
|
||||
--color-h-accent-bg: var(--h-accent-bg);
|
||||
--color-h-active-bg: var(--h-active-bg);
|
||||
/* Pressed */
|
||||
--color-p-primary-bg: var(--p-primary-bg);
|
||||
--color-p-invert-bg: var(--p-invert-bg);
|
||||
--color-p-secondary-bg: var(--p-secondary-bg);
|
||||
--color-p-tertiary-bg: var(--p-tertiary-bg);
|
||||
--color-p-subtle-bg: var(--p-subtle-bg);
|
||||
--color-p-highlight-bg: var(--p-highlight-bg);
|
||||
--color-p-accent-bg: var(--p-accent-bg);
|
||||
--color-p-active-bg: var(--p-active-bg);
|
||||
|
||||
/* Borders */
|
||||
--color-solid-b: var(--solid-b);
|
||||
--color-low-b: var(--low-b);
|
||||
--color-mid-b: var(--mid-b);
|
||||
--color-strong-b: var(--strong-b);
|
||||
--color-active-b: var(--active-b);
|
||||
--color-accent-b: var(--accent-b);
|
||||
--color-danger-b: var(--danger-b);
|
||||
--color-success-b: var(--success-b);
|
||||
--color-warning-b: var(--warning-b);
|
||||
--color-info-b: var(--info-b);
|
||||
}
|
||||
|
||||
/*
|
||||
The default border color has changed to `currentColor` in Tailwind CSS v4,
|
||||
so we've added these compatibility styles to make sure everything still
|
||||
looks the same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add an explicit border
|
||||
color utility to any element that depends on these defaults.
|
||||
*/
|
||||
@layer base {
|
||||
*,
|
||||
::after,
|
||||
::before,
|
||||
::backdrop,
|
||||
::file-selector-button {
|
||||
border-color: var(--color-mid-b, currentColor);
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
/* Legacy */
|
||||
/* :root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 47.4% 11.2%;
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 47.4% 11.2%;
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 47.4% 11.2%;
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
--destructive: 0 100% 50%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--ring: 215 20.2% 65.1%;
|
||||
--sidebar-background: 0 0% 98%;
|
||||
--sidebar-foreground: 240 5.3% 26.1%;
|
||||
--sidebar-primary: 240 5.9% 10%;
|
||||
--sidebar-primary-foreground: 0 0% 98%;
|
||||
--sidebar-accent: 240 4.8% 95.9%;
|
||||
--sidebar-accent-foreground: 240 5.9% 10%;
|
||||
--sidebar-border: 220 13% 91%;
|
||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 224 71% 4%;
|
||||
--foreground: 213 31% 91%;
|
||||
--muted: 223 47% 11%;
|
||||
--muted-foreground: 215.4 16.3% 56.9%;
|
||||
--accent: 216 34% 17%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
--popover: 224 71% 4%;
|
||||
--popover-foreground: 215 20.2% 65.1%;
|
||||
--border: 216 34% 17%;
|
||||
--input: 216 34% 17%;
|
||||
--card: 224 71% 4%;
|
||||
--card-foreground: 213 31% 91%;
|
||||
--primary: 210 40% 98%;
|
||||
--primary-foreground: 222.2 47.4% 1.2%;
|
||||
--secondary: 222.2 47.4% 11.2%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
--destructive: 0 63% 31%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--ring: 216 34% 17%;
|
||||
--sidebar-background: 240 5.9% 10%;
|
||||
--sidebar-foreground: 240 4.8% 95.9%;
|
||||
--sidebar-primary: 224.3 76.3% 48%;
|
||||
--sidebar-primary-foreground: 0 0% 100%;
|
||||
--sidebar-accent: 240 3.7% 15.9%;
|
||||
--sidebar-accent-foreground: 240 4.8% 95.9%;
|
||||
--sidebar-border: 240 3.7% 15.9%;
|
||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||
} */
|
||||
|
||||
/* New */
|
||||
:root {
|
||||
/* Surfaces */
|
||||
--level-0: #FAFAF5;
|
||||
--level-1: #FFFFFF;
|
||||
--level-2: #FFFFFF;
|
||||
--level-3: var(--olive-12);
|
||||
|
||||
/* Text colors */
|
||||
--primary: var(--olive-12);
|
||||
--invert: var(--olive-1);
|
||||
--secondary: var(--olive-11);
|
||||
--tertiary: var(--olive-10);
|
||||
--quaternary: var(--olive-8);
|
||||
--disabled: var(--olive-7);
|
||||
--accent: var(--lime-10);
|
||||
--danger: var(--red-11);
|
||||
--success: var(--lime-11);
|
||||
--info: var(--blue-11);
|
||||
--warning: var(--orange-11);
|
||||
|
||||
/* Background colors */
|
||||
--primary-bg: var(--olive-12);
|
||||
--invert-bg: #ffffff;
|
||||
--secondary-bg: var(--olive-1);
|
||||
--tertiary-bg: transparent;
|
||||
--subtle-bg: var(--olive-a2);
|
||||
--highlight-bg: var(--olive-a3);
|
||||
--accent-bg: var(--olive-12);
|
||||
--active-bg: var(--lime-4);
|
||||
--danger-bg: var(--red-3);
|
||||
--danger-plain-bg: var(--red-10);
|
||||
--success-bg: var(--lime-3);
|
||||
--info-bg: var(--blue-3);
|
||||
--warning-bg: var(--orange-3);
|
||||
/* Hover */
|
||||
--h-primary-bg: #273025;
|
||||
--h-invert-bg: var(--olive-2);
|
||||
--h-secondary-bg: var(--olive-2);
|
||||
--h-tertiary-bg: var(--olive-a2);
|
||||
--h-subtle-bg: var(--olive-a3);
|
||||
--h-highlight-bg: var(--olive-a4);
|
||||
--h-accent-bg: #1E2C1C;
|
||||
--h-active-bg: var(--lime-5);
|
||||
/* Pressed */
|
||||
--p-primary-bg: #30392E;
|
||||
--p-invert-bg: var(--olive-3);
|
||||
--p-secondary-bg: var(--olive-3);
|
||||
--p-tertiary-bg: var(--olive-a3);
|
||||
--p-subtle-bg: var(--olive-a4);
|
||||
--p-highlight-bg: var(--olive-a5);
|
||||
--p-accent-bg: #293A25;
|
||||
--p-active-bg: var(--lime-6);
|
||||
|
||||
/* Borders */
|
||||
--solid-b: var(--olive-4);
|
||||
--low-b: var(--olive-a4);
|
||||
--mid-b: var(--olive-a7);
|
||||
--strong-b: var(--olive-a8);
|
||||
--active-b: var(--olive-12);
|
||||
--accent-b: var(--lime-11);
|
||||
--danger-b: var(--red-10);
|
||||
--success-b: var(--lime-10);
|
||||
--warning-b: var(--orange-10);
|
||||
--info-b: var(--blue-10);
|
||||
}
|
||||
|
||||
.dark {
|
||||
/* Surfaces */
|
||||
--level-0: var(--olive-1);
|
||||
--level-1: var(--olive-2);
|
||||
--level-2: var(--olive-3);
|
||||
--level-3: var(--olive-7);
|
||||
|
||||
/* Text colors */
|
||||
--success: var(--lime-a11);
|
||||
|
||||
/* Background colors */
|
||||
--primary-bg: var(--lime-10);
|
||||
--invert-bg: var(--olive-1);
|
||||
--secondary-bg: var(--olive-a3);
|
||||
--active-bg: var(--olive-a4);
|
||||
--accent-bg: var(--lime-10);
|
||||
--danger-bg: var(--red-a3);
|
||||
--danger-plain-bg: var(--red-a10);
|
||||
--success-bg: var(--lime-a3);
|
||||
--info-bg: var(--blue-a3);
|
||||
--warning-bg: var(--orange-a3);
|
||||
/* Hover */
|
||||
--h-primary-bg: #C4F042E1;
|
||||
--h-secondary-bg: var(--olive-a3);
|
||||
--h-highlight-bg: var(--olive-a5);
|
||||
--h-accent-bg: var(--lime-a9);
|
||||
--h-active-bg: var(--olive-a5);
|
||||
/* Pressed */
|
||||
--p-primary-bg: #C4F042D7;
|
||||
--p-secondary-bg: var(--olive-a4);
|
||||
--p-highlight-bg: var(--olive-a6);
|
||||
--p-accent-bg: var(--lime-a10);
|
||||
--p-active-bg: var(--olive-a6);
|
||||
|
||||
/* Borders */
|
||||
--solid-b: var(--olive-5);
|
||||
--mid-b: var(--olive-a5);
|
||||
--active-b: var(--olive-a12);
|
||||
--warning-b: var(--orange-a10);
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-solid-b;
|
||||
}
|
||||
body {
|
||||
@apply font-sans antialiased bg-level-0 text-primary;
|
||||
}
|
||||
}
|
||||
@@ -1,91 +1,12 @@
|
||||
import posthog from "posthog-js";
|
||||
import { PostHogProvider } from "posthog-js/react";
|
||||
import { lazy } from "@probo/react-lazy";
|
||||
import { StrictMode, Suspense } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { HelmetProvider } from "react-helmet-async";
|
||||
import { RelayEnvironmentProvider } from "react-relay";
|
||||
import { BrowserRouter, Route, Routes, useLocation } from "react-router";
|
||||
import "./App.css";
|
||||
import "./styles/document-content.css";
|
||||
import ErrorBoundary from "./components/ErrorBoundary";
|
||||
import AuthLayout from "./layouts/AuthLayout";
|
||||
import { RelayEnvironment } from "./RelayEnvironment";
|
||||
import { AuthenticationRoutes } from "./pages/authentication/Routes";
|
||||
import { OrganizationsRoutes } from "./pages/organizations/Routes";
|
||||
import SigningRequestsPage from "./pages/SigningRequestsPage";
|
||||
import { RouterProvider } from "react-router";
|
||||
import { router } from "./routes";
|
||||
import { Toasts } from "@probo/ui";
|
||||
|
||||
posthog.init(process.env.POSTHOG_KEY!, {
|
||||
api_host: process.env.POSTHOG_HOST,
|
||||
session_recording: {
|
||||
maskAllInputs: true,
|
||||
},
|
||||
loaded: (posthog) => {
|
||||
if (!process.env.POSTHOG_KEY) posthog.debug();
|
||||
},
|
||||
});
|
||||
|
||||
const OrganizationSelectionPage = lazy(
|
||||
() => import("./pages/OrganizationSelectionPage"),
|
||||
);
|
||||
|
||||
function App() {
|
||||
export function App() {
|
||||
return (
|
||||
<StrictMode>
|
||||
<PostHogProvider client={posthog}>
|
||||
<RelayEnvironmentProvider environment={RelayEnvironment}>
|
||||
<HelmetProvider>
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="/*">
|
||||
<Route
|
||||
index
|
||||
element={
|
||||
<Suspense>
|
||||
<ErrorBoundaryWithLocation>
|
||||
<OrganizationSelectionPage />
|
||||
</ErrorBoundaryWithLocation>
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="organizations/*"
|
||||
element={<OrganizationsRoutes />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="documents/signing-requests"
|
||||
element={<SigningRequestsPage />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="*"
|
||||
element={
|
||||
<AuthLayout>
|
||||
<AuthenticationRoutes />
|
||||
</AuthLayout>
|
||||
}
|
||||
/>
|
||||
</Route>
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</HelmetProvider>
|
||||
</RelayEnvironmentProvider>
|
||||
</PostHogProvider>
|
||||
</StrictMode>
|
||||
<>
|
||||
<RouterProvider router={router} />
|
||||
<Toasts />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function ErrorBoundaryWithLocation({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const location = useLocation();
|
||||
return <ErrorBoundary key={location.pathname}>{children}</ErrorBoundary>;
|
||||
}
|
||||
|
||||
const container = document.getElementById("root");
|
||||
const root = createRoot(container!);
|
||||
root.render(<App />);
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
import {
|
||||
Environment,
|
||||
FetchFunction,
|
||||
Network,
|
||||
RecordSource,
|
||||
Store,
|
||||
} from "relay-runtime";
|
||||
import { buildEndpoint } from "./utils";
|
||||
import { GraphQLError } from "graphql";
|
||||
|
||||
export class UnAuthenticatedError extends Error {
|
||||
constructor() {
|
||||
super("UNAUTHENTICATED");
|
||||
this.name = "UnAuthenticatedError";
|
||||
}
|
||||
}
|
||||
|
||||
export class InternalServerError extends Error {
|
||||
constructor() {
|
||||
super("INTERNAL_SERVER_ERROR");
|
||||
this.name = "InternalServerError";
|
||||
}
|
||||
}
|
||||
|
||||
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/console/v1/query"),
|
||||
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
|
||||
}' with variables '${JSON.stringify(variables)}': ${JSON.stringify(
|
||||
json.errors
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
return json;
|
||||
};
|
||||
|
||||
function createRelayEnvironment() {
|
||||
const source = new RecordSource();
|
||||
const store = new Store(source, {
|
||||
queryCacheExpirationTime: 1 * 60 * 1000,
|
||||
gcReleaseBufferSize: 20,
|
||||
});
|
||||
|
||||
return new Environment({
|
||||
network: Network.create(fetchRelay),
|
||||
store,
|
||||
});
|
||||
}
|
||||
|
||||
export const RelayEnvironment: Environment = createRelayEnvironment();
|
||||
@@ -1,65 +0,0 @@
|
||||
import * as React from "react";
|
||||
import { Suspense, useEffect } from "react";
|
||||
import {
|
||||
graphql,
|
||||
PreloadedQuery,
|
||||
usePreloadedQuery,
|
||||
useQueryLoader,
|
||||
} from "react-relay";
|
||||
|
||||
import { NavUser } from "@/components/NavUser";
|
||||
import { Sidebar } from "@/components/ui/sidebar";
|
||||
import type { AppSidebarQuery as AppSidebarQueryType } from "./__generated__/AppSidebarQuery.graphql";
|
||||
import { OrganizationSwitcher } from "@/components/OrganizationSwitcher";
|
||||
import { AppSidebarShell } from "./AppSidebarShell";
|
||||
import { AppSidebarSkeleton } from "./AppSidebarSkeleton";
|
||||
import { NavMain } from "./NavMain";
|
||||
|
||||
const AppSidebarQuery = graphql`
|
||||
query AppSidebarQuery {
|
||||
viewer {
|
||||
id
|
||||
...OrganizationSwitcher_organizations
|
||||
...NavUser_viewer
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
function AppSidebarContent({
|
||||
queryRef,
|
||||
...props
|
||||
}: React.ComponentProps<typeof Sidebar> & {
|
||||
queryRef: PreloadedQuery<AppSidebarQueryType>;
|
||||
}) {
|
||||
const data = usePreloadedQuery(AppSidebarQuery, queryRef);
|
||||
|
||||
return (
|
||||
<AppSidebarShell
|
||||
organizationSwitcher={
|
||||
<OrganizationSwitcher organizations={data.viewer} />
|
||||
}
|
||||
navMain={<NavMain />}
|
||||
navUser={<NavUser viewer={data.viewer} />}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function AppSidebar(props: React.ComponentProps<typeof Sidebar>) {
|
||||
const [queryRef, loadQuery] =
|
||||
useQueryLoader<AppSidebarQueryType>(AppSidebarQuery);
|
||||
|
||||
useEffect(() => {
|
||||
loadQuery({});
|
||||
}, [loadQuery]);
|
||||
|
||||
if (!queryRef) {
|
||||
return <AppSidebarSkeleton {...props} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Suspense fallback={<AppSidebarSkeleton {...props} />}>
|
||||
<AppSidebarContent queryRef={queryRef} {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import { NavSecondary } from "./NavSecondary";
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
SidebarFooter,
|
||||
SidebarHeader,
|
||||
} from "./ui/sidebar";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
interface AppSidebarShellProps extends React.ComponentProps<typeof Sidebar> {
|
||||
navUser: ReactNode;
|
||||
navMain: ReactNode;
|
||||
organizationSwitcher: ReactNode;
|
||||
}
|
||||
|
||||
export function AppSidebarShell({
|
||||
navUser,
|
||||
navMain,
|
||||
organizationSwitcher,
|
||||
...props
|
||||
}: AppSidebarShellProps) {
|
||||
return (
|
||||
<Sidebar variant="sidebar" {...props}>
|
||||
<SidebarHeader>{organizationSwitcher}</SidebarHeader>
|
||||
<SidebarContent>
|
||||
{navMain}
|
||||
<NavSecondary className="mt-auto" />
|
||||
</SidebarContent>
|
||||
<SidebarFooter>{navUser}</SidebarFooter>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { AppSidebarShell } from "./AppSidebarShell";
|
||||
import { NavMainSkeleton } from "./NavMainSkeleton";
|
||||
import { NavUserSkeleton } from "./NavUserSkeleton";
|
||||
import { OrganizationSwitcherSkeleton } from "./OrganizationSwitcherSkeleton";
|
||||
import { Sidebar } from "./ui/sidebar";
|
||||
|
||||
export function AppSidebarSkeleton(
|
||||
props: React.ComponentProps<typeof Sidebar>,
|
||||
) {
|
||||
return (
|
||||
<AppSidebarShell
|
||||
organizationSwitcher={<OrganizationSwitcherSkeleton />}
|
||||
navMain={<NavMainSkeleton />}
|
||||
navUser={<NavUserSkeleton />}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,284 +0,0 @@
|
||||
.document-editor-container {
|
||||
border-radius: 0.5rem;
|
||||
position: relative;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
margin-top: 0.5rem;
|
||||
border: 1px solid var(--solid-b);
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.document-editor {
|
||||
background: var(--bg-invert-bg);
|
||||
position: relative;
|
||||
border-radius: 0.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.document-editor-inner {
|
||||
background: #fff;
|
||||
position: relative;
|
||||
border-radius: 0 0 0.5rem 0.5rem;
|
||||
flex: 1;
|
||||
min-height: 250px;
|
||||
}
|
||||
|
||||
.document-editor-input {
|
||||
min-height: 250px;
|
||||
max-height: 500px;
|
||||
resize: none;
|
||||
font-size: 15px;
|
||||
position: relative;
|
||||
tab-size: 1;
|
||||
outline: 0;
|
||||
padding: 15px 10px;
|
||||
overflow-y: auto;
|
||||
width: 100%;
|
||||
display: block;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.document-editor-placeholder {
|
||||
color: #999;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
text-overflow: ellipsis;
|
||||
top: 15px;
|
||||
left: 10px;
|
||||
font-size: 15px;
|
||||
user-select: none;
|
||||
display: inline-block;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.document-editor-paragraph {
|
||||
margin: 0 0 15px 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.document-editor .toolbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid hsl(var(--solid-b));
|
||||
background-color: hsl(var(--invert-bg));
|
||||
border-radius: 0.5rem 0.5rem 0 0;
|
||||
}
|
||||
|
||||
.document-editor .toolbar button {
|
||||
border: 0;
|
||||
display: flex;
|
||||
background: none;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.document-editor .toolbar button:hover {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.document-editor .toolbar button.active {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.document-editor .toolbar .divider {
|
||||
width: 1px;
|
||||
background-color: hsl(var(--solid-b));
|
||||
margin: 0 8px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
/* Typography */
|
||||
.editor-heading-h1 {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
margin: 0 0 12px 0;
|
||||
}
|
||||
|
||||
.editor-heading-h2 {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.editor-heading-h3 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.editor-heading-h4 {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.editor-heading-h5 {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.editor-quote {
|
||||
margin: 0;
|
||||
margin-left: 20px;
|
||||
padding-left: 16px;
|
||||
border-left: 4px solid hsl(var(--solid-b));
|
||||
color: hsl(var(--tertiary));
|
||||
}
|
||||
|
||||
.editor-list-ol {
|
||||
padding: 0;
|
||||
margin: 0 0 0 16px;
|
||||
list-style-type: decimal;
|
||||
display: block;
|
||||
counter-reset: li;
|
||||
}
|
||||
|
||||
.editor-list-ul {
|
||||
padding: 0;
|
||||
margin: 0 0 0 16px;
|
||||
list-style-type: disc;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.editor-listitem {
|
||||
margin: 8px 0 0 0;
|
||||
display: list-item;
|
||||
position: relative;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
/* Ensure list items display consistently across browsers */
|
||||
li.editor-listitem::marker {
|
||||
font-weight: normal;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* Specifically for ordered lists */
|
||||
ol.editor-list-ol > .editor-listitem {
|
||||
list-style-type: decimal;
|
||||
list-style-position: outside;
|
||||
}
|
||||
|
||||
/* For nested ordered lists */
|
||||
ol.editor-list-ol ol.editor-list-ol > .editor-listitem {
|
||||
list-style-type: lower-alpha;
|
||||
}
|
||||
|
||||
.editor-listitem--checked,
|
||||
.editor-listitem--unchecked {
|
||||
position: relative;
|
||||
margin-left: 0;
|
||||
padding-left: 24px;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.editor-listitem--checked:before,
|
||||
.editor-listitem--unchecked:before {
|
||||
content: '';
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 2px;
|
||||
background-size: 16px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.editor-listitem--checked:before {
|
||||
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 11 12 14 22 4"></polyline><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path></svg>');
|
||||
}
|
||||
|
||||
.editor-listitem--unchecked:before {
|
||||
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect></svg>');
|
||||
}
|
||||
|
||||
.editor-nested-listitem {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
/* Text formatting */
|
||||
.editor-text-bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.editor-text-italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.editor-text-underline {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.editor-text-strikethrough {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.editor-text-underlineStrikethrough {
|
||||
text-decoration: underline line-through;
|
||||
}
|
||||
|
||||
.editor-text-code {
|
||||
background-color: hsl(var(--invert-bg));
|
||||
padding: 1px 4px;
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.editor-link {
|
||||
color: hsl(var(--primary));
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.editor-code {
|
||||
background-color: hsl(var(--invert-bg));
|
||||
font-family: monospace;
|
||||
display: block;
|
||||
padding: 8px 16px;
|
||||
line-height: 1.6;
|
||||
font-size: 13px;
|
||||
border-radius: 0.5rem;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
/* Code highlight */
|
||||
.editor-tokenComment {
|
||||
color: slategray;
|
||||
}
|
||||
|
||||
.editor-tokenPunctuation {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.editor-tokenProperty {
|
||||
color: #905;
|
||||
}
|
||||
|
||||
.editor-tokenSelector {
|
||||
color: #690;
|
||||
}
|
||||
|
||||
.editor-tokenOperator {
|
||||
color: #9a6e3a;
|
||||
}
|
||||
|
||||
.editor-tokenAttr {
|
||||
color: #07a;
|
||||
}
|
||||
|
||||
.editor-tokenVariable {
|
||||
color: #e90;
|
||||
}
|
||||
|
||||
.editor-tokenFunction {
|
||||
color: #dd4a68;
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
import { HelpCircle } from "lucide-react";
|
||||
import { Label } from "./ui/label";
|
||||
import { Textarea } from "./ui/textarea";
|
||||
import { Input } from "./ui/input";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export function EditableField({
|
||||
label,
|
||||
value,
|
||||
onChange,
|
||||
type = "text",
|
||||
helpText,
|
||||
required,
|
||||
multiline = false,
|
||||
}: {
|
||||
label: string;
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
type?: string;
|
||||
helpText?: string;
|
||||
required?: boolean;
|
||||
multiline?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Label htmlFor={label} className="text-sm font-medium">
|
||||
{label}
|
||||
{required && <span className="text-red-500">*</span>}
|
||||
</Label>
|
||||
{helpText && (
|
||||
<div className="relative flex items-center">
|
||||
<HelpCircle className="h-4 w-4 text-tertiary" />
|
||||
<span className="sr-only">{helpText}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{multiline ? (
|
||||
<Textarea
|
||||
id={label}
|
||||
value={value}
|
||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
|
||||
onChange(e.target.value)
|
||||
}
|
||||
className={cn(
|
||||
"w-full resize-none",
|
||||
required && !value && "border-red-500",
|
||||
)}
|
||||
placeholder={`Enter ${label.toLowerCase()}`}
|
||||
rows={4}
|
||||
/>
|
||||
) : (
|
||||
<Input
|
||||
id={label}
|
||||
type={type}
|
||||
value={value}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
onChange(e.target.value)
|
||||
}
|
||||
className={cn("w-full", required && !value && "border-red-500")}
|
||||
placeholder={`Enter ${label.toLowerCase()}`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
import { Component, ErrorInfo, ReactNode } from "react";
|
||||
import { ErrorPage } from "@/pages/ErrorPage";
|
||||
import { UnAuthenticatedError } from "@/RelayEnvironment";
|
||||
import { Navigate } from "react-router";
|
||||
|
||||
interface ErrorBoundaryProps {
|
||||
children: ReactNode;
|
||||
fallback?: ReactNode;
|
||||
}
|
||||
|
||||
interface ErrorBoundaryState {
|
||||
hasError: boolean;
|
||||
error?: Error;
|
||||
isUnAuthenticated: boolean;
|
||||
}
|
||||
|
||||
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||
constructor(props: ErrorBoundaryProps) {
|
||||
super(props);
|
||||
this.state = { hasError: false, isUnAuthenticated: false };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
||||
if (error instanceof UnAuthenticatedError) {
|
||||
return { hasError: true, error, isUnAuthenticated: true };
|
||||
}
|
||||
return { hasError: true, error, isUnAuthenticated: false };
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, info: ErrorInfo): void {
|
||||
console.error("ErrorBoundary caught an error:", error);
|
||||
console.error("Error info:", info.componentStack);
|
||||
}
|
||||
|
||||
render(): ReactNode {
|
||||
if (this.state.isUnAuthenticated) {
|
||||
return <Navigate to="/login" replace state={{ authRequired: true }} />;
|
||||
}
|
||||
|
||||
if (this.state.hasError) {
|
||||
if (this.props.fallback) {
|
||||
return this.props.fallback;
|
||||
}
|
||||
return <ErrorPage error={this.state.error} />;
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
export default ErrorBoundary;
|
||||
@@ -1,210 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
ChevronRight,
|
||||
SquareCheck,
|
||||
Inbox,
|
||||
Store,
|
||||
type LucideIcon,
|
||||
Flame,
|
||||
BookOpen,
|
||||
FileText,
|
||||
Settings,
|
||||
Users,
|
||||
Box,
|
||||
Database
|
||||
} from "lucide-react";
|
||||
import { Link, useLocation, useParams } from "react-router";
|
||||
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "@/components/ui/collapsible";
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarMenu,
|
||||
SidebarMenuAction,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
SidebarMenuSub,
|
||||
SidebarMenuSubButton,
|
||||
SidebarMenuSubItem,
|
||||
} from "@/components/ui/sidebar";
|
||||
|
||||
interface NavItem {
|
||||
title: string;
|
||||
url?: string;
|
||||
icon: LucideIcon;
|
||||
isActive?: boolean;
|
||||
items?: {
|
||||
title: string;
|
||||
url: string;
|
||||
icon: LucideIcon;
|
||||
}[];
|
||||
}
|
||||
|
||||
export function NavMain() {
|
||||
const location = useLocation();
|
||||
const { organizationId } = useParams();
|
||||
const items: NavItem[] = getNavItems(organizationId);
|
||||
|
||||
if (!organizationId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isItemActive = (item: { url?: string; items?: { url: string }[] }) => {
|
||||
if (item.url && location.pathname.startsWith(item.url)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.items?.length) {
|
||||
return item.items.some((subItem) =>
|
||||
location.pathname.startsWith(subItem.url),
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
return (
|
||||
<SidebarGroup>
|
||||
<SidebarMenu>
|
||||
{items.map((item) => {
|
||||
const active = isItemActive(item) || item.isActive;
|
||||
|
||||
return (
|
||||
<Collapsible key={item.title} asChild defaultOpen={active}>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
asChild
|
||||
tooltip={item.title}
|
||||
data-active={active ? "true" : undefined}
|
||||
>
|
||||
<Link to={item.url ?? "#"}>
|
||||
<item.icon
|
||||
className={active ? "text-lime-9" : "text-lime-6"}
|
||||
/>
|
||||
<span className="font-medium">{item.title}</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
{item.items?.length ? (
|
||||
<>
|
||||
<CollapsibleTrigger asChild>
|
||||
<SidebarMenuAction className="data-[state=open]:rotate-90">
|
||||
<ChevronRight />
|
||||
<span className="sr-only">Toggle</span>
|
||||
</SidebarMenuAction>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<SidebarMenuSub>
|
||||
{item.items?.map((subItem) => {
|
||||
const subItemActive = location.pathname.startsWith(
|
||||
subItem.url,
|
||||
);
|
||||
|
||||
return (
|
||||
<SidebarMenuSubItem key={subItem.title}>
|
||||
<SidebarMenuSubButton
|
||||
asChild
|
||||
data-active={subItemActive ? "true" : undefined}
|
||||
>
|
||||
<Link to={subItem.url}>
|
||||
<subItem.icon
|
||||
className={
|
||||
subItemActive
|
||||
? "text-lime-9"
|
||||
: "text-lime-6"
|
||||
}
|
||||
/>
|
||||
<span className="font-medium">
|
||||
{subItem.title}
|
||||
</span>
|
||||
</Link>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
);
|
||||
})}
|
||||
</SidebarMenuSub>
|
||||
</CollapsibleContent>
|
||||
</>
|
||||
) : null}
|
||||
</SidebarMenuItem>
|
||||
</Collapsible>
|
||||
);
|
||||
})}
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
);
|
||||
}
|
||||
|
||||
function getNavItems(organizationId?: string): NavItem[] {
|
||||
return [
|
||||
{
|
||||
title: "Tasks",
|
||||
icon: Inbox,
|
||||
url: organizationId
|
||||
? `/organizations/${organizationId}/tasks`
|
||||
: undefined,
|
||||
},
|
||||
{
|
||||
title: "Measures",
|
||||
icon: SquareCheck,
|
||||
url: organizationId
|
||||
? `/organizations/${organizationId}/measures`
|
||||
: undefined,
|
||||
},
|
||||
{
|
||||
title: "Risks",
|
||||
icon: Flame,
|
||||
url: organizationId
|
||||
? `/organizations/${organizationId}/risks`
|
||||
: undefined,
|
||||
},
|
||||
{
|
||||
title: "Frameworks",
|
||||
url: organizationId
|
||||
? `/organizations/${organizationId}/frameworks`
|
||||
: undefined,
|
||||
icon: BookOpen,
|
||||
},
|
||||
{
|
||||
title: "People",
|
||||
url: `/organizations/${organizationId}/people`,
|
||||
icon: Users,
|
||||
},
|
||||
{
|
||||
title: "Vendors",
|
||||
url: `/organizations/${organizationId}/vendors`,
|
||||
icon: Store,
|
||||
},
|
||||
{
|
||||
title: "Documents",
|
||||
url: organizationId
|
||||
? `/organizations/${organizationId}/documents`
|
||||
: undefined,
|
||||
icon: FileText,
|
||||
},
|
||||
{
|
||||
title: "Assets",
|
||||
url: organizationId
|
||||
? `/organizations/${organizationId}/assets`
|
||||
: undefined,
|
||||
icon: Box,
|
||||
},
|
||||
{
|
||||
title: "Data",
|
||||
url: organizationId
|
||||
? `/organizations/${organizationId}/data`
|
||||
: undefined,
|
||||
icon: Database,
|
||||
},
|
||||
{
|
||||
title: "Settings",
|
||||
url: organizationId
|
||||
? `/organizations/${organizationId}/settings`
|
||||
: undefined,
|
||||
icon: Settings,
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupLabel,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "@/components/ui/sidebar";
|
||||
|
||||
export function NavMainSkeleton() {
|
||||
return (
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel className="pl-3">
|
||||
<div className="py-0.5">
|
||||
<div className="h-3 w-24 rounded-sm bg-subtle-bg animate-pulse" />
|
||||
</div>
|
||||
</SidebarGroupLabel>
|
||||
<SidebarMenu className="space-y-1.5">
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton className="animate-pulse">
|
||||
<div className="h-4 w-4 rounded-md bg-lime-5" />
|
||||
<div className="py-[3px]">
|
||||
<div className="h-3.5 w-32 rounded-md bg-subtle-bg" />
|
||||
</div>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton className="animate-pulse">
|
||||
<div className="h-4 w-4 rounded-md bg-lime-6" />
|
||||
<div className="py-[3px]">
|
||||
<div className="h-3.5 w-32 rounded-md bg-subtle-bg" />
|
||||
</div>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton className="animate-pulse">
|
||||
<div className="h-4 w-4 rounded-md bg-lime-6" />
|
||||
<div className="py-[3px]">
|
||||
<div className="h-3.5 w-32 rounded-md bg-subtle-bg" />
|
||||
</div>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton className="animate-pulse">
|
||||
<div className="h-4 w-4 rounded-md bg-lime-6" />
|
||||
<div className="py-[3px]">
|
||||
<div className="h-3.5 w-32 rounded-md bg-subtle-bg" />
|
||||
</div>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
);
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { LifeBuoy, Send } from "lucide-react";
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupContent,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "@/components/ui/sidebar";
|
||||
|
||||
const navItems = [
|
||||
{
|
||||
title: "Support",
|
||||
url: "mailto:support@getprobo.com",
|
||||
icon: LifeBuoy,
|
||||
},
|
||||
{
|
||||
title: "Feedback",
|
||||
url: "#",
|
||||
icon: Send,
|
||||
},
|
||||
];
|
||||
|
||||
export function NavSecondary(
|
||||
props: React.ComponentPropsWithoutRef<typeof SidebarGroup>,
|
||||
) {
|
||||
return (
|
||||
<SidebarGroup {...props}>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
{navItems.map((item) => (
|
||||
<SidebarMenuItem key={item.title}>
|
||||
<SidebarMenuButton
|
||||
asChild={item.url !== "#"}
|
||||
size="sm"
|
||||
tooltip={item.title}
|
||||
className={
|
||||
item.url === "#" ? "opacity-50 cursor-not-allowed" : ""
|
||||
}
|
||||
>
|
||||
{item.url !== "#" ? (
|
||||
<a href={item.url}>
|
||||
<item.icon />
|
||||
<span className="font-medium">{item.title}</span>
|
||||
</a>
|
||||
) : (
|
||||
<>
|
||||
<item.icon />
|
||||
<span className="font-medium">{item.title}</span>
|
||||
</>
|
||||
)}
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
);
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { ChevronsUpDown, LogOut } from "lucide-react";
|
||||
import { graphql, useFragment } from "react-relay";
|
||||
|
||||
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
useSidebar,
|
||||
} from "@/components/ui/sidebar";
|
||||
import { NavUser_viewer$key } from "./__generated__/NavUser_viewer.graphql";
|
||||
import { buildEndpoint } from "@/utils";
|
||||
|
||||
export const navUserFragment = graphql`
|
||||
fragment NavUser_viewer on Viewer {
|
||||
user {
|
||||
id
|
||||
fullName
|
||||
email
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export function NavUser({ viewer }: { viewer: NavUser_viewer$key }) {
|
||||
const { isMobile } = useSidebar();
|
||||
const currentUser = useFragment(navUserFragment, viewer).user;
|
||||
|
||||
const handleLogout = async () => {
|
||||
fetch(buildEndpoint("/api/console/v1/auth/logout"), {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
}).then(() => {
|
||||
window.location.href = "https://www.getprobo.com";
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuButton size="lg" variant="outline">
|
||||
<Avatar className="h-9 w-9">
|
||||
<AvatarFallback className="bg-highlight-bg">
|
||||
{currentUser.fullName.substring(0, 2).toUpperCase()}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-semibold">
|
||||
{currentUser.fullName}
|
||||
</span>
|
||||
<span className="truncate text-xs text-secondary/70">
|
||||
{currentUser.email}
|
||||
</span>
|
||||
</div>
|
||||
<ChevronsUpDown className="ml-auto size-4" />
|
||||
</SidebarMenuButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
|
||||
side={isMobile ? "bottom" : "right"}
|
||||
align="end"
|
||||
sideOffset={4}
|
||||
>
|
||||
<DropdownMenuLabel className="p-0 font-normal">
|
||||
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||
<Avatar className="h-8 w-8 bg-highlight-bg">
|
||||
<AvatarFallback>
|
||||
{currentUser.fullName.substring(0, 2).toUpperCase()}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-semibold">
|
||||
{currentUser.fullName}
|
||||
</span>
|
||||
<span className="truncate text-xs">{currentUser.email}</span>
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={handleLogout}>
|
||||
<LogOut />
|
||||
Log out
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
);
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "@/components/ui/sidebar";
|
||||
|
||||
export function NavUserSkeleton() {
|
||||
return (
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
isActive
|
||||
disabled
|
||||
size="lg"
|
||||
className="data-[state=open]:bg-accent data-[state=open]:text-accent animate-pulse"
|
||||
>
|
||||
<div className="bg-subtle-bg size-9 rounded-full animate-pulse" />
|
||||
<div className="flex-1 space-y-[3px] animate-pulse">
|
||||
<div className="h-3.5 w-20 rounded-sm bg-subtle-bg" />
|
||||
<div className="h-3.5 w-30 rounded-sm bg-subtle-bg" />
|
||||
</div>
|
||||
<div className="ml-auto size-4 rounded-lg bg-subtle-bg" />
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
);
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { ChevronsUpDown, Plus } from "lucide-react";
|
||||
import { graphql, useFragment } from "react-relay";
|
||||
import { Link, useNavigate, useParams } from "react-router";
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
useSidebar,
|
||||
} from "@/components/ui/sidebar";
|
||||
import {
|
||||
OrganizationSwitcher_organizations$key,
|
||||
OrganizationSwitcher_organizations$data,
|
||||
} from "./__generated__/OrganizationSwitcher_organizations.graphql";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export const organizationSwitcherFragment = graphql`
|
||||
fragment OrganizationSwitcher_organizations on Viewer {
|
||||
organizations(first: 25)
|
||||
@connection(key: "OrganizationSwitcher_organizations") {
|
||||
__id
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
logoUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
type Organization =
|
||||
OrganizationSwitcher_organizations$data["organizations"]["edges"][0]["node"];
|
||||
|
||||
const LogoComponent = ({
|
||||
org,
|
||||
className,
|
||||
}: {
|
||||
org: Organization;
|
||||
className?: string;
|
||||
}) => {
|
||||
if (org.logoUrl) {
|
||||
return (
|
||||
<img
|
||||
src={org.logoUrl}
|
||||
alt={org.name}
|
||||
className={cn("rounded-md", className)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return org.name.substring(0, 2).toUpperCase();
|
||||
};
|
||||
|
||||
export function OrganizationSwitcher({
|
||||
organizations,
|
||||
}: {
|
||||
organizations: OrganizationSwitcher_organizations$key;
|
||||
}) {
|
||||
const { isMobile } = useSidebar();
|
||||
const data = useFragment(organizationSwitcherFragment, organizations);
|
||||
const navigate = useNavigate();
|
||||
const { organizationId } = useParams();
|
||||
const [currentOrganization, setCurrentOrganization] =
|
||||
useState<Organization | null>(null);
|
||||
const hasOrganizations =
|
||||
data.organizations && data.organizations.edges.length > 0;
|
||||
|
||||
useEffect(() => {
|
||||
if (hasOrganizations) {
|
||||
const org = data.organizations.edges.find(
|
||||
(edge) => edge.node.id === organizationId,
|
||||
);
|
||||
if (org) {
|
||||
setCurrentOrganization(org.node);
|
||||
}
|
||||
}
|
||||
}, [data.organizations, organizationId, hasOrganizations]);
|
||||
|
||||
const handleOrganizationSwitch = (org: Organization) => {
|
||||
navigate(`/organizations/${org.id}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuButton
|
||||
size="lg"
|
||||
variant="ghost"
|
||||
className={cn(
|
||||
"gap-2.5",
|
||||
!currentOrganization && "border border-dashed",
|
||||
)}
|
||||
>
|
||||
<div className="flex aspect-square size-8 items-center justify-center rounded-sm bg-highlight-bg">
|
||||
{currentOrganization ? (
|
||||
<LogoComponent org={currentOrganization} className="size-8" />
|
||||
) : (
|
||||
<div className="size-8 bg-highlight-bg rounded-sm" />
|
||||
)}
|
||||
</div>
|
||||
<div className="grid text-left leading-tight text-primary">
|
||||
{currentOrganization ? (
|
||||
<>
|
||||
<span className="truncate font-medium text-lg leading-5">
|
||||
{currentOrganization.name}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<span className="truncate text-secondary font-medium">
|
||||
Select Organization
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<ChevronsUpDown className="ml-auto" />
|
||||
</SidebarMenuButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
|
||||
align="start"
|
||||
side={isMobile ? "bottom" : "right"}
|
||||
sideOffset={4}
|
||||
>
|
||||
<DropdownMenuLabel className="text-xs text-tertiary">
|
||||
Organizations
|
||||
</DropdownMenuLabel>
|
||||
{hasOrganizations &&
|
||||
data.organizations.edges.map((edge, index) => (
|
||||
<DropdownMenuItem
|
||||
key={edge.node.id}
|
||||
onClick={() => handleOrganizationSwitch(edge.node)}
|
||||
className={`gap-2 p-2 ${
|
||||
edge.node.id === organizationId ? "bg-subtle-bg" : ""
|
||||
}`}
|
||||
>
|
||||
<div className="flex size-6 items-center justify-center rounded-sm bg-highlight-bg">
|
||||
<LogoComponent
|
||||
org={edge.node}
|
||||
className="size-4 shrink-0"
|
||||
/>
|
||||
</div>
|
||||
{edge.node.name}
|
||||
<DropdownMenuShortcut>⌘{index + 1}</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem asChild>
|
||||
<Link
|
||||
to="/organizations/new"
|
||||
className="gap-2 p-2 cursor-pointer"
|
||||
>
|
||||
<div className="flex size-6 items-center justify-center rounded-md border bg-secondary-bg">
|
||||
<Plus className="size-4" />
|
||||
</div>
|
||||
<div className="font-medium text-tertiary">
|
||||
Add organization
|
||||
</div>
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
);
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "@/components/ui/sidebar";
|
||||
|
||||
export function OrganizationSwitcherSkeleton() {
|
||||
return (
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
size="lg"
|
||||
className="data-[state=open]:bg-accent-bg data-[state=open]:text-accent gap-2.5"
|
||||
>
|
||||
<div className="size-8 items-center justify-center rounded-md bg-subtle-bg animate-pulse" />
|
||||
<div className="flex flex-col items-start">
|
||||
<div className="py-[1px]">
|
||||
<div className="h-4.5 animate-pulse w-16 rounded-sm bg-subtle-bg" />
|
||||
</div>
|
||||
<div className="py-0.5">
|
||||
<div className="h-3 animate-pulse w-8 rounded-sm bg-subtle-bg" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-auto size-4 rounded-lg bg-subtle-bg animate-pulse" />
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
);
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import { FC, ReactNode } from "react";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
|
||||
export interface PageContainerProps {
|
||||
children: ReactNode;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export const PageContainer: FC<PageContainerProps> = ({ children, title }) => {
|
||||
return (
|
||||
<>
|
||||
<Helmet>{`${title} - Probo Console`}</Helmet>
|
||||
<div className="container">{children}</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const PageContainerSkeleton: FC<{
|
||||
title?: string;
|
||||
children: ReactNode;
|
||||
}> = ({ children, title }) =>
|
||||
title ? (
|
||||
<PageContainer title={title}>{children}</PageContainer>
|
||||
) : (
|
||||
<div className="container">{children}</div>
|
||||
);
|
||||
@@ -1,110 +0,0 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { FC, ReactNode } from "react";
|
||||
|
||||
export interface PageHeaderProps {
|
||||
title: ReactNode;
|
||||
actions?: ReactNode;
|
||||
description?: ReactNode;
|
||||
}
|
||||
|
||||
export function PageHeaderShell({
|
||||
className,
|
||||
title,
|
||||
actions,
|
||||
description,
|
||||
}: PageHeaderProps & { className?: string }) {
|
||||
return (
|
||||
<div className={cn("space-y-6", className)}>
|
||||
{actions ? (
|
||||
<div className="flex items-center justify-between">
|
||||
{title}
|
||||
{actions}
|
||||
</div>
|
||||
) : (
|
||||
title
|
||||
)}
|
||||
{description}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function PageHeader({
|
||||
className,
|
||||
title,
|
||||
actions,
|
||||
description,
|
||||
}: PageHeaderProps & { className?: string }) {
|
||||
return (
|
||||
<PageHeaderShell
|
||||
className={className}
|
||||
title={<PageHeading>{title}</PageHeading>}
|
||||
description={<PageDescription>{description}</PageDescription>}
|
||||
actions={actions}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function PageHeading({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
className?: string;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
return <h1 className={cn("text-3xl font-medium", className)}>{children}</h1>;
|
||||
}
|
||||
|
||||
const PageHeadingSkeleton: FC = () => {
|
||||
return (
|
||||
<div className="py-[3px] w-2/5">
|
||||
<div className="bg-subtle-bg animate rounded-lg h-7.5" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export function PageDescription({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
className?: string;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className={cn("text-md text-tertiary text-left", className)}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function PageDescriptionSkeleton() {
|
||||
return (
|
||||
<div className="py-[5px]">
|
||||
<div className="bg-subtle-bg animate rounded-md w-100 h-4.5" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const PageHeaderSkeleton: FC<{
|
||||
className?: string;
|
||||
title?: string;
|
||||
actions?: ReactNode;
|
||||
description?: ReactNode;
|
||||
withDescription?: boolean;
|
||||
}> = ({ className, title, actions, description, withDescription = false }) => {
|
||||
return (
|
||||
<PageHeaderShell
|
||||
className={className}
|
||||
title={
|
||||
title ? <PageHeading>{title}</PageHeading> : <PageHeadingSkeleton />
|
||||
}
|
||||
description={
|
||||
description ? (
|
||||
<PageDescription>{description}</PageDescription>
|
||||
) : withDescription ? (
|
||||
<PageDescriptionSkeleton />
|
||||
) : null
|
||||
}
|
||||
actions={actions}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,47 +0,0 @@
|
||||
import { FC, ReactNode } from "react";
|
||||
import {
|
||||
PageContainer,
|
||||
PageContainerProps,
|
||||
PageContainerSkeleton,
|
||||
} from "./PageContainer";
|
||||
import { PageHeader, PageHeaderProps, PageHeaderSkeleton } from "./PageHeader";
|
||||
|
||||
export const PageTemplate: FC<PageContainerProps & PageHeaderProps> = ({
|
||||
actions,
|
||||
title,
|
||||
description,
|
||||
children,
|
||||
}) => {
|
||||
return (
|
||||
<PageContainer title={title}>
|
||||
<PageHeader
|
||||
className="mb-12"
|
||||
title={title}
|
||||
description={description}
|
||||
actions={actions}
|
||||
/>
|
||||
{children}
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export const PageTemplateSkeleton: FC<{
|
||||
title?: string;
|
||||
description?: ReactNode;
|
||||
withDescription?: boolean;
|
||||
actions?: ReactNode;
|
||||
children: ReactNode;
|
||||
}> = ({ actions, children, description, withDescription = false, title }) => {
|
||||
return (
|
||||
<PageContainerSkeleton title={title}>
|
||||
<PageHeaderSkeleton
|
||||
className="mb-17"
|
||||
title={title}
|
||||
description={description}
|
||||
withDescription={withDescription}
|
||||
actions={actions}
|
||||
/>
|
||||
{children}
|
||||
</PageContainerSkeleton>
|
||||
);
|
||||
};
|
||||
@@ -1,74 +0,0 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { graphql, useFragment } from "react-relay";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { PeopleSelector_organization$key } from "./__generated__/PeopleSelector_organization.graphql";
|
||||
|
||||
const peopleSelectorFragment = graphql`
|
||||
fragment PeopleSelector_organization on Organization {
|
||||
id
|
||||
peoples(first: 100, orderBy: { direction: ASC, field: FULL_NAME })
|
||||
@connection(key: "PeopleSelector_organization_peoples") {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
fullName
|
||||
primaryEmailAddress
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface PeopleSelectorProps {
|
||||
organizationRef: PeopleSelector_organization$key;
|
||||
selectedPersonId: string | null;
|
||||
onSelect: (personId: string) => void;
|
||||
placeholder?: string;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
export default function PeopleSelector({
|
||||
organizationRef,
|
||||
selectedPersonId,
|
||||
onSelect,
|
||||
placeholder = "Select a person",
|
||||
required = false,
|
||||
}: PeopleSelectorProps) {
|
||||
const organization = useFragment(peopleSelectorFragment, organizationRef);
|
||||
const [value, setValue] = useState<string>(selectedPersonId || "");
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedPersonId) {
|
||||
setValue(selectedPersonId);
|
||||
}
|
||||
}, [selectedPersonId]);
|
||||
|
||||
const handleValueChange = (newValue: string) => {
|
||||
setValue(newValue);
|
||||
onSelect(newValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<Select value={value} onValueChange={handleValueChange} required={required}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={placeholder} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{organization.peoples?.edges?.map(
|
||||
(edge) =>
|
||||
edge?.node && (
|
||||
<SelectItem key={edge.node.id} value={edge.node.id}>
|
||||
{edge.node.fullName} ({edge.node.primaryEmailAddress})
|
||||
</SelectItem>
|
||||
),
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
import { Component, ErrorInfo, ReactNode } from "react";
|
||||
import { ErrorPage } from "@/pages/ErrorPage";
|
||||
import { UnAuthenticatedError } from "@/RelayEnvironment";
|
||||
|
||||
interface ErrorBoundaryProps {
|
||||
children: ReactNode;
|
||||
fallback?: ReactNode;
|
||||
}
|
||||
|
||||
interface ErrorBoundaryState {
|
||||
hasError: boolean;
|
||||
error?: Error;
|
||||
}
|
||||
|
||||
class VisitorErrorBoundary extends Component<
|
||||
ErrorBoundaryProps,
|
||||
ErrorBoundaryState
|
||||
> {
|
||||
constructor(props: ErrorBoundaryProps) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
||||
if (error instanceof UnAuthenticatedError) {
|
||||
return { hasError: false, error: undefined };
|
||||
}
|
||||
return { hasError: true, error };
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, info: ErrorInfo): void {
|
||||
console.error("ErrorBoundary caught an error:", error);
|
||||
console.error("Error info:", info.componentStack);
|
||||
}
|
||||
|
||||
render(): ReactNode {
|
||||
if (this.state.hasError) {
|
||||
if (this.props.fallback) {
|
||||
return this.props.fallback;
|
||||
}
|
||||
return <ErrorPage error={this.state.error} />;
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
export default VisitorErrorBoundary;
|
||||
@@ -1,240 +0,0 @@
|
||||
/**
|
||||
* @generated SignedSource<<72d01b98680bc82c2d5bdc8c2fa2d49d>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type AppSidebarQuery$variables = Record<PropertyKey, never>;
|
||||
export type AppSidebarQuery$data = {
|
||||
readonly viewer: {
|
||||
readonly id: string;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"NavUser_viewer" | "OrganizationSwitcher_organizations">;
|
||||
};
|
||||
};
|
||||
export type AppSidebarQuery = {
|
||||
response: AppSidebarQuery$data;
|
||||
variables: AppSidebarQuery$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
v1 = [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 25
|
||||
}
|
||||
];
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": [],
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "AppSidebarQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Viewer",
|
||||
"kind": "LinkedField",
|
||||
"name": "viewer",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v0/*: any*/),
|
||||
{
|
||||
"args": null,
|
||||
"kind": "FragmentSpread",
|
||||
"name": "OrganizationSwitcher_organizations"
|
||||
},
|
||||
{
|
||||
"args": null,
|
||||
"kind": "FragmentSpread",
|
||||
"name": "NavUser_viewer"
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Query",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": [],
|
||||
"kind": "Operation",
|
||||
"name": "AppSidebarQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Viewer",
|
||||
"kind": "LinkedField",
|
||||
"name": "viewer",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v0/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": "OrganizationConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "organizations",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "OrganizationEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Organization",
|
||||
"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": "__typename",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "cursor",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PageInfo",
|
||||
"kind": "LinkedField",
|
||||
"name": "pageInfo",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "endCursor",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "hasNextPage",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"kind": "ClientExtension",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__id",
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"storageKey": "organizations(first:25)"
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v1/*: any*/),
|
||||
"filters": null,
|
||||
"handle": "connection",
|
||||
"key": "OrganizationSwitcher_organizations",
|
||||
"kind": "LinkedHandle",
|
||||
"name": "organizations"
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "User",
|
||||
"kind": "LinkedField",
|
||||
"name": "user",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v0/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "fullName",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "email",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "51c72d5d6b9e1d4e5e7f45e7280a0534",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "AppSidebarQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query AppSidebarQuery {\n viewer {\n id\n ...OrganizationSwitcher_organizations\n ...NavUser_viewer\n }\n}\n\nfragment NavUser_viewer on Viewer {\n user {\n id\n fullName\n email\n }\n}\n\nfragment OrganizationSwitcher_organizations on Viewer {\n organizations(first: 25) {\n edges {\n node {\n id\n name\n logoUrl\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "ebe1f481a1454380e017fb976b8bb8a5";
|
||||
|
||||
export default node;
|
||||
@@ -1,71 +0,0 @@
|
||||
/**
|
||||
* @generated SignedSource<<0d5ada0f912fcf3a8f2f3328b0943b28>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ReaderFragment } from 'relay-runtime';
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type NavUser_viewer$data = {
|
||||
readonly user: {
|
||||
readonly email: string;
|
||||
readonly fullName: string;
|
||||
readonly id: string;
|
||||
};
|
||||
readonly " $fragmentType": "NavUser_viewer";
|
||||
};
|
||||
export type NavUser_viewer$key = {
|
||||
readonly " $data"?: NavUser_viewer$data;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"NavUser_viewer">;
|
||||
};
|
||||
|
||||
const node: ReaderFragment = {
|
||||
"argumentDefinitions": [],
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "NavUser_viewer",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "User",
|
||||
"kind": "LinkedField",
|
||||
"name": "user",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "fullName",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "email",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Viewer",
|
||||
"abstractKey": null
|
||||
};
|
||||
|
||||
(node as any).hash = "05e26e238976738b4cff26f37c5f29e7";
|
||||
|
||||
export default node;
|
||||
@@ -1,160 +0,0 @@
|
||||
/**
|
||||
* @generated SignedSource<<152991faff70c7ef6c4751fac48090be>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ReaderFragment } from 'relay-runtime';
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type OrganizationSwitcher_organizations$data = {
|
||||
readonly organizations: {
|
||||
readonly __id: string;
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly id: string;
|
||||
readonly logoUrl: string | null | undefined;
|
||||
readonly name: string;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
readonly " $fragmentType": "OrganizationSwitcher_organizations";
|
||||
};
|
||||
export type OrganizationSwitcher_organizations$key = {
|
||||
readonly " $data"?: OrganizationSwitcher_organizations$data;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"OrganizationSwitcher_organizations">;
|
||||
};
|
||||
|
||||
const node: ReaderFragment = {
|
||||
"argumentDefinitions": [],
|
||||
"kind": "Fragment",
|
||||
"metadata": {
|
||||
"connection": [
|
||||
{
|
||||
"count": null,
|
||||
"cursor": null,
|
||||
"direction": "forward",
|
||||
"path": [
|
||||
"organizations"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "OrganizationSwitcher_organizations",
|
||||
"selections": [
|
||||
{
|
||||
"alias": "organizations",
|
||||
"args": null,
|
||||
"concreteType": "OrganizationConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "__OrganizationSwitcher_organizations_connection",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "OrganizationEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Organization",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"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": "logoUrl",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__typename",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "cursor",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PageInfo",
|
||||
"kind": "LinkedField",
|
||||
"name": "pageInfo",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "endCursor",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "hasNextPage",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"kind": "ClientExtension",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__id",
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Viewer",
|
||||
"abstractKey": null
|
||||
};
|
||||
|
||||
(node as any).hash = "4bc97175bc64a6c7554a6ec865d12850";
|
||||
|
||||
export default node;
|
||||
@@ -1,161 +0,0 @@
|
||||
/**
|
||||
* @generated SignedSource<<a048edd52203c53c6736b800456816b0>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ReaderFragment } from 'relay-runtime';
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type PeopleSelector_organization$data = {
|
||||
readonly id: string;
|
||||
readonly peoples: {
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly fullName: string;
|
||||
readonly id: string;
|
||||
readonly primaryEmailAddress: string;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
readonly " $fragmentType": "PeopleSelector_organization";
|
||||
};
|
||||
export type PeopleSelector_organization$key = {
|
||||
readonly " $data"?: PeopleSelector_organization$data;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"PeopleSelector_organization">;
|
||||
};
|
||||
|
||||
const node: ReaderFragment = (function(){
|
||||
var v0 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
};
|
||||
return {
|
||||
"argumentDefinitions": [],
|
||||
"kind": "Fragment",
|
||||
"metadata": {
|
||||
"connection": [
|
||||
{
|
||||
"count": null,
|
||||
"cursor": null,
|
||||
"direction": "forward",
|
||||
"path": [
|
||||
"peoples"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "PeopleSelector_organization",
|
||||
"selections": [
|
||||
(v0/*: any*/),
|
||||
{
|
||||
"alias": "peoples",
|
||||
"args": [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "orderBy",
|
||||
"value": {
|
||||
"direction": "ASC",
|
||||
"field": "FULL_NAME"
|
||||
}
|
||||
}
|
||||
],
|
||||
"concreteType": "PeopleConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "__PeopleSelector_organization_peoples_connection",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PeopleEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "People",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v0/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "fullName",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "primaryEmailAddress",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__typename",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "cursor",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PageInfo",
|
||||
"kind": "LinkedField",
|
||||
"name": "pageInfo",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "endCursor",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "hasNextPage",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": "__PeopleSelector_organization_peoples_connection(orderBy:{\"direction\":\"ASC\",\"field\":\"FULL_NAME\"})"
|
||||
}
|
||||
],
|
||||
"type": "Organization",
|
||||
"abstractKey": null
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "77094e387ff3e5dca822fc2360094744";
|
||||
|
||||
export default node;
|
||||