Add react setup

Signed-off-by: gearnode <bryan@frimin.fr>
This commit is contained in:
gearnode
2025-01-31 21:01:21 -08:00
parent 1bcf32c3b2
commit b3dcfc6b53
18 changed files with 4936 additions and 0 deletions

3
apps/console/src/App.css Normal file
View File

@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

57
apps/console/src/App.tsx Normal file
View 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 />);

View 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();

View 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;

View File

@@ -0,0 +1,9 @@
import { Outlet } from "react-router";
export default function AuthenticateLayout() {
return (
<div>
<Outlet />
</div>
)
}

View 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>
);
}

View 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>
);
}

View 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
View 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();
}