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();
|
||||
}
|
||||
Reference in New Issue
Block a user