1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
bin/
|
bin/
|
||||||
|
node_modules/
|
||||||
|
|||||||
7
apps/console/.env.example
Normal file
7
apps/console/.env.example
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
LIVE_RELOAD=
|
||||||
|
|
||||||
|
POSTHOG_HOST=
|
||||||
|
POSTHOG_KEY=
|
||||||
|
FARO_PUSH_URL=
|
||||||
|
|
||||||
|
HTTP_ENDPOINT="http://example.com"
|
||||||
2
apps/console/.gitattributes
vendored
Normal file
2
apps/console/.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
__generated__/*.graphql linguist-generated
|
||||||
|
__generated__/*.js linguist-generated
|
||||||
52
apps/console/package.json
Normal file
52
apps/console/package.json
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"name": "@probo/console",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"posthog-js": "^1.215.1",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"react-helmet-async": "^2.0.5",
|
||||||
|
"react-relay": "^18.2.0",
|
||||||
|
"react-router": "^7.1.5",
|
||||||
|
"relay-runtime": "^18.2.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.26.7",
|
||||||
|
"@babel/preset-env": "^7.26.7",
|
||||||
|
"@babel/preset-typescript": "^7.26.0",
|
||||||
|
"@deanc/esbuild-plugin-postcss": "^1.0.2",
|
||||||
|
"@probo/tsconfig": "^0.0.1",
|
||||||
|
"@tailwindcss/postcss": "^4.0.2",
|
||||||
|
"@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.24.2",
|
||||||
|
"relay-compiler": "^18.2.0",
|
||||||
|
"tailwindcss": "^4.0.2",
|
||||||
|
"typescript": "^5.7.3"
|
||||||
|
},
|
||||||
|
"relay": {
|
||||||
|
"src": "src",
|
||||||
|
"language": "typescript",
|
||||||
|
"schema": "../../pkg/api/console/v1/schema.graphql",
|
||||||
|
"noFutureProofEnums": true,
|
||||||
|
"excludes": [
|
||||||
|
"**/node_modules/**",
|
||||||
|
"**/__mocks__/**",
|
||||||
|
"**/__generated__/**"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
23
apps/console/public/index.html
Normal file
23
apps/console/public/index.html
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<!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" href="/favicon.ico" />
|
||||||
|
<meta name="description" content="TODO" />
|
||||||
|
|
||||||
|
<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="bg-gray-50 text-gray-900 font-sans">
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/App.js" defer></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
217
apps/console/scripts/esbuild.mjs
Normal file
217
apps/console/scripts/esbuild.mjs
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
import babel from "@babel/core";
|
||||||
|
import postCssPlugin from "@deanc/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 tailwindcss 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 || ""};
|
||||||
|
|
||||||
|
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 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]",
|
||||||
|
splitting: true,
|
||||||
|
format: "esm",
|
||||||
|
sourcemap: "external",
|
||||||
|
loader: {
|
||||||
|
".png": "file",
|
||||||
|
".jpg": "file",
|
||||||
|
".jpeg": "file",
|
||||||
|
".svg": "file",
|
||||||
|
".gif": "file",
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
relayPlugin,
|
||||||
|
hotReloading,
|
||||||
|
postCssPlugin({
|
||||||
|
plugins: [tailwindcss(), autoprefixer()],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
define: {
|
||||||
|
...envVars,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await copyRecursive("public", "dist");
|
||||||
|
|
||||||
|
const [, , command] = process.argv;
|
||||||
|
|
||||||
|
if (command === "watch") {
|
||||||
|
runRelayCompilerInWatchMode();
|
||||||
|
|
||||||
|
const ctx = await esbuild.context({ ...defaultOptions, minify: false });
|
||||||
|
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 extensionPattern = /\.[^\/]+$/;
|
||||||
|
|
||||||
|
const proxyReq = http.request(options, (proxyRes) => {
|
||||||
|
if (proxyRes.statusCode === 404 && extensionPattern.test(req.url)) {
|
||||||
|
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) => {
|
||||||
|
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);
|
||||||
|
}
|
||||||
3
apps/console/src/App.css
Normal file
3
apps/console/src/App.css
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
57
apps/console/src/App.tsx
Normal file
57
apps/console/src/App.tsx
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import posthog from "posthog-js";
|
||||||
|
import { PostHogProvider } from "posthog-js/react";
|
||||||
|
import { lazy, 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,
|
||||||
|
} from "react-router";
|
||||||
|
import "App.css";
|
||||||
|
import ErrorBoundary from "./components/ErrorBoundary";
|
||||||
|
import AuthenticateLayout from "./layouts/AuthenticateLayout";
|
||||||
|
import { RelayEnvironment } from "./RelayEnvironment";
|
||||||
|
|
||||||
|
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 HomePage = lazy(() => import("./pages/HomePage"));
|
||||||
|
const NotFoundPage = lazy(() => import("./pages/NotFoundPage"));
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<StrictMode>
|
||||||
|
<ErrorBoundary fallback={<p>Something went wrong</p>}>
|
||||||
|
<PostHogProvider client={posthog}>
|
||||||
|
<RelayEnvironmentProvider environment={RelayEnvironment}>
|
||||||
|
<HelmetProvider>
|
||||||
|
<BrowserRouter>
|
||||||
|
<Suspense>
|
||||||
|
<Routes>
|
||||||
|
<Route path="/" element={<AuthenticateLayout />}>
|
||||||
|
<Route index element={<HomePage />} />
|
||||||
|
</Route>
|
||||||
|
<Route path="*" element={<NotFoundPage />} />
|
||||||
|
</Routes>
|
||||||
|
</Suspense>
|
||||||
|
</BrowserRouter>
|
||||||
|
</HelmetProvider>
|
||||||
|
</RelayEnvironmentProvider>
|
||||||
|
</PostHogProvider>
|
||||||
|
</ErrorBoundary>
|
||||||
|
</StrictMode>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const container = document.getElementById("root");
|
||||||
|
const root = createRoot(container!);
|
||||||
|
root.render(<App />);
|
||||||
48
apps/console/src/RelayEnvironment.ts
Normal file
48
apps/console/src/RelayEnvironment.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import {
|
||||||
|
Environment,
|
||||||
|
FetchFunction,
|
||||||
|
Network,
|
||||||
|
RecordSource,
|
||||||
|
Store,
|
||||||
|
} from "relay-runtime";
|
||||||
|
import { buildEndpoint } from "./utils";
|
||||||
|
|
||||||
|
const fetchRelay: FetchFunction = async (request, variables) => {
|
||||||
|
const response = await fetch(buildEndpoint("/console/v1/query"), {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
Accept:
|
||||||
|
"application/graphql-response+json; charset=utf-8, application/json; charset=utf-8",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
operationName: request.name,
|
||||||
|
query: request.text,
|
||||||
|
variables,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const json = await response.json();
|
||||||
|
|
||||||
|
if (Array.isArray(json.errors)) {
|
||||||
|
throw new Error(
|
||||||
|
`Error fetching GraphQL query '${
|
||||||
|
request.name
|
||||||
|
}' with variables '${JSON.stringify(variables)}': ${JSON.stringify(
|
||||||
|
json.errors,
|
||||||
|
)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return json;
|
||||||
|
};
|
||||||
|
|
||||||
|
function createRelayEnvironment() {
|
||||||
|
return new Environment({
|
||||||
|
network: Network.create(fetchRelay),
|
||||||
|
store: new Store(new RecordSource()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RelayEnvironment: Environment = createRelayEnvironment();
|
||||||
37
apps/console/src/components/ErrorBoundary.tsx
Normal file
37
apps/console/src/components/ErrorBoundary.tsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { Component, ErrorInfo, ReactNode } from "react";
|
||||||
|
|
||||||
|
interface ErrorBoundaryProps {
|
||||||
|
children: ReactNode;
|
||||||
|
fallback: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ErrorBoundaryState {
|
||||||
|
hasError: boolean;
|
||||||
|
error: Error | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||||
|
constructor(props: ErrorBoundaryProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = { hasError: false, error: undefined };
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
||||||
|
return { hasError: true, error: 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) {
|
||||||
|
return this.props.fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.props.children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ErrorBoundary;
|
||||||
9
apps/console/src/layouts/AuthenticateLayout.tsx
Normal file
9
apps/console/src/layouts/AuthenticateLayout.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { Outlet } from "react-router";
|
||||||
|
|
||||||
|
export default function AuthenticateLayout() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
57
apps/console/src/pages/HomePage.tsx
Normal file
57
apps/console/src/pages/HomePage.tsx
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { Suspense, useEffect } from "react";
|
||||||
|
import {
|
||||||
|
graphql,
|
||||||
|
PreloadedQuery,
|
||||||
|
usePreloadedQuery,
|
||||||
|
useQueryLoader,
|
||||||
|
} from "react-relay";
|
||||||
|
import { Link } from "react-router";
|
||||||
|
import type { HomePageQuery as HomePageQueryType } from "./__generated__/HomePageQuery.graphql";
|
||||||
|
|
||||||
|
export const HomePageQuery = graphql`
|
||||||
|
query HomePageQuery {
|
||||||
|
node(id: "AZSfP_xAcAC5IAAAAAAltA") {
|
||||||
|
id
|
||||||
|
... on Organization {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default function HomePage() {
|
||||||
|
const [queryRef, loadQuery] =
|
||||||
|
useQueryLoader<HomePageQueryType>(HomePageQuery);
|
||||||
|
|
||||||
|
useEffect(() => loadQuery({}), [loadQuery]);
|
||||||
|
|
||||||
|
if (!queryRef) {
|
||||||
|
return <HomePageFallback />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<HomePageFallback />}>
|
||||||
|
<HomePageContent queryRef={queryRef} />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function HomePageFallback() {
|
||||||
|
return <div>Loading...</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function HomePageContent({
|
||||||
|
queryRef,
|
||||||
|
}: {
|
||||||
|
queryRef: PreloadedQuery<HomePageQueryType>;
|
||||||
|
}) {
|
||||||
|
const data = usePreloadedQuery(HomePageQuery, queryRef);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>User</h1>
|
||||||
|
<pre>{JSON.stringify(data, null, 2)}</pre>
|
||||||
|
<Link to="/foobar">Go to FooPage</Link>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
10
apps/console/src/pages/NotFoundPage.tsx
Normal file
10
apps/console/src/pages/NotFoundPage.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { Link } from "react-router";
|
||||||
|
|
||||||
|
export default function NotFoundPage() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<p>The page might have been moved, deleted, or never existed.</p>
|
||||||
|
<Link to="/">Return to Home</Link>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
118
apps/console/src/pages/__generated__/HomePageQuery.graphql.ts
generated
Normal file
118
apps/console/src/pages/__generated__/HomePageQuery.graphql.ts
generated
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<269bb5911dc11e2bda71f8c506e1d7e7>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
|
export type HomePageQuery$variables = Record<PropertyKey, never>;
|
||||||
|
export type HomePageQuery$data = {
|
||||||
|
readonly node: {
|
||||||
|
readonly id: string;
|
||||||
|
readonly name?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export type HomePageQuery = {
|
||||||
|
response: HomePageQuery$data;
|
||||||
|
variables: HomePageQuery$variables;
|
||||||
|
};
|
||||||
|
|
||||||
|
const node: ConcreteRequest = (function(){
|
||||||
|
var v0 = [
|
||||||
|
{
|
||||||
|
"kind": "Literal",
|
||||||
|
"name": "id",
|
||||||
|
"value": "AZSfP_xAcAC5IAAAAAAltA"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
v1 = {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "id",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
v2 = {
|
||||||
|
"kind": "InlineFragment",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "name",
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Organization",
|
||||||
|
"abstractKey": null
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
"fragment": {
|
||||||
|
"argumentDefinitions": [],
|
||||||
|
"kind": "Fragment",
|
||||||
|
"metadata": null,
|
||||||
|
"name": "HomePageQuery",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": (v0/*: any*/),
|
||||||
|
"concreteType": null,
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v1/*: any*/),
|
||||||
|
(v2/*: any*/)
|
||||||
|
],
|
||||||
|
"storageKey": "node(id:\"AZSfP_xAcAC5IAAAAAAltA\")"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Query",
|
||||||
|
"abstractKey": null
|
||||||
|
},
|
||||||
|
"kind": "Request",
|
||||||
|
"operation": {
|
||||||
|
"argumentDefinitions": [],
|
||||||
|
"kind": "Operation",
|
||||||
|
"name": "HomePageQuery",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": (v0/*: any*/),
|
||||||
|
"concreteType": null,
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "__typename",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
(v1/*: any*/),
|
||||||
|
(v2/*: any*/)
|
||||||
|
],
|
||||||
|
"storageKey": "node(id:\"AZSfP_xAcAC5IAAAAAAltA\")"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"cacheID": "5d18865912a8a8c16bc28f5506766d82",
|
||||||
|
"id": null,
|
||||||
|
"metadata": {},
|
||||||
|
"name": "HomePageQuery",
|
||||||
|
"operationKind": "query",
|
||||||
|
"text": "query HomePageQuery {\n node(id: \"AZSfP_xAcAC5IAAAAAAltA\") {\n __typename\n id\n ... on Organization {\n name\n }\n }\n}\n"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
(node as any).hash = "0da7306fdc31722644ce24a893020fae";
|
||||||
|
|
||||||
|
export default node;
|
||||||
14
apps/console/src/utils.ts
Normal file
14
apps/console/src/utils.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export function buildEndpoint(path: string): string {
|
||||||
|
const host = process.env.API_SERVER_HOST!;
|
||||||
|
const formattedHost =
|
||||||
|
host.startsWith("http://") || host.startsWith("https://")
|
||||||
|
? host
|
||||||
|
: `https://${host}`;
|
||||||
|
const url = new URL(formattedHost);
|
||||||
|
|
||||||
|
if (path) {
|
||||||
|
url.pathname = path.startsWith("/") ? path : `/${path}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return url.toString();
|
||||||
|
}
|
||||||
9
apps/console/tailwind.config.ts
Normal file
9
apps/console/tailwind.config.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import type { Config } from "tailwindcss";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
content: ["src/**/*.tsx"],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
} satisfies Config;
|
||||||
6
apps/console/tsconfig.json
Normal file
6
apps/console/tsconfig.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"extends": "@probo/tsconfig/react.json",
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
|
|
||||||
4266
package-lock.json
generated
4266
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user