1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-07-29 22:01:14 +03:00

frontend: Migrate to jotai and urql

This cuts the bundle size by 50% and makes it easier to reason about state.
It removes the usage of react-router-dom and replaces it with a simple router atom based on jotai-location.
Since the screens will be quite simple, I don't expect that we'll need the advanced caching features of react-relay, hence the switch to urql.
This commit is contained in:
Quentin Gliech
2023-03-20 18:08:58 +01:00
parent 17e4bb70c1
commit b26d12f52f
40 changed files with 3008 additions and 3112 deletions

View File

@ -1,6 +1,5 @@
import { ArgTypes, Decorator, Parameters } from "@storybook/react"; import { ArgTypes, Decorator, Parameters } from "@storybook/react";
import { useLayoutEffect } from "react"; import { useLayoutEffect } from "react";
import { createMemoryRouter, RouterProvider } from "react-router-dom";
import "../src/index.css"; import "../src/index.css";
export const parameters: Parameters = { export const parameters: Parameters = {
@ -60,15 +59,4 @@ const withThemeProvider: Decorator = (Story, context) => {
); );
}; };
const withRouter: Decorator = (Story, _context) => { export const decorators: Decorator[] = [withThemeProvider];
const router = createMemoryRouter([
{
path: "/*",
element: <Story />,
},
]);
return <RouterProvider router={router} />;
};
export const decorators: Decorator[] = [withThemeProvider, withRouter];

16
frontend/codegen.ts Normal file
View File

@ -0,0 +1,16 @@
import { CodegenConfig } from "@graphql-codegen/cli";
const config: CodegenConfig = {
schema: "./schema.graphql",
documents: ["src/**/*.tsx", "!src/gql/**/*"],
ignoreNoDocuments: true, // for better experience with the watcher
generates: {
"./src/gql/": {
preset: "client",
plugins: [],
},
},
hooks: { afterAllFileWrite: ["eslint --fix"] },
};
export default config;

File diff suppressed because it is too large Load Diff

View File

@ -5,9 +5,8 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"generate": "relay-compiler && eslint --fix .", "generate": "graphql-codegen && eslint --fix .",
"lint": "relay-compiler --validate && eslint . && tsc", "lint": "graphql-codegen && eslint . && tsc",
"relay": "relay-compiler",
"build": "npm run lint && vite build --base=./ && npm run build:templates", "build": "npm run lint && vite build --base=./ && npm run build:templates",
"build:templates": "tailwindcss --postcss --minify --config ./tailwind.templates.config.cjs -o dist/tailwind.css", "build:templates": "tailwindcss --postcss --minify --config ./tailwind.templates.config.cjs -o dist/tailwind.css",
"preview": "vite preview", "preview": "vite preview",
@ -17,14 +16,18 @@
"build-storybook": "storybook build" "build-storybook": "storybook build"
}, },
"dependencies": { "dependencies": {
"@urql/core": "^3.2.2",
"date-fns": "^2.29.3", "date-fns": "^2.29.3",
"graphql": "^16.6.0",
"jotai": "^2.0.3",
"jotai-location": "^0.5.1",
"jotai-urql": "^0.5.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0"
"react-relay": "^15.0.0",
"react-router-dom": "^6.9.0",
"relay-runtime": "^15.0.0"
}, },
"devDependencies": { "devDependencies": {
"@graphql-codegen/cli": "^3.2.2",
"@graphql-codegen/client-preset": "^2.1.1",
"@graphql-eslint/eslint-plugin": "^3.16.1", "@graphql-eslint/eslint-plugin": "^3.16.1",
"@storybook/addon-actions": "^7.0.0-rc.3", "@storybook/addon-actions": "^7.0.0-rc.3",
"@storybook/addon-backgrounds": "^7.0.0-rc.3", "@storybook/addon-backgrounds": "^7.0.0-rc.3",
@ -40,10 +43,7 @@
"@types/node": "^18.15.3", "@types/node": "^18.15.3",
"@types/react": "^18.0.28", "@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"@types/react-relay": "^14.1.3",
"@types/react-test-renderer": "^18.0.0", "@types/react-test-renderer": "^18.0.0",
"@types/relay-runtime": "^14.1.9",
"@types/relay-test-utils": "^14.1.0",
"@vitejs/plugin-react": "^3.1.0", "@vitejs/plugin-react": "^3.1.0",
"@vitest/coverage-c8": "^0.29.3", "@vitest/coverage-c8": "^0.29.3",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
@ -54,14 +54,12 @@
"postcss": "^8.4.21", "postcss": "^8.4.21",
"prettier": "^2.8.4", "prettier": "^2.8.4",
"react-test-renderer": "^18.2.0", "react-test-renderer": "^18.2.0",
"relay-compiler": "^15.0.0",
"relay-test-utils": "^15.0.0",
"storybook": "^7.0.0-rc.3", "storybook": "^7.0.0-rc.3",
"tailwindcss": "^3.2.7", "tailwindcss": "^3.2.7",
"typescript": "^5.0.2", "typescript": "^5.0.2",
"vite": "^4.2.0", "vite": "^4.2.0",
"vitest": "^0.29.3",
"vite-plugin-eslint": "^1.8.1", "vite-plugin-eslint": "^1.8.1",
"vite-plugin-relay-lite": "^0.4.3" "vite-plugin-graphql-codegen": "^3.1.0",
"vitest": "^0.29.3"
} }
} }

View File

@ -1,11 +0,0 @@
{
"src": "./src",
"schema": "./schema.graphql",
"language": "typescript",
"eagerEsModules": true,
"exclude": [
"**/node_modules/**",
"**/__mocks__/**",
"**/__generated__/**"
]
}

View File

@ -1,74 +0,0 @@
// Copyright 2022 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {
Environment,
Network,
RecordSource,
Store,
Variables,
RequestParameters,
CacheConfig,
} from "relay-runtime";
async function fetchRelay(
params: RequestParameters,
variables: Variables,
_cacheConfig: CacheConfig
) {
const response = await fetch("/graphql", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
query: params.text,
variables,
}),
});
// Get the response as JSON
const json = await response.json();
// GraphQL returns exceptions (for example, a missing required variable) in the "errors"
// property of the response. If any exceptions occurred when processing the request,
// throw an error to indicate to the developer what went wrong.
if (Array.isArray(json.errors)) {
console.log(json.errors);
throw new Error(
`Error fetching GraphQL query '${
params.name
}' with variables '${JSON.stringify(variables)}': ${JSON.stringify(
json.errors
)}`
);
}
// Otherwise, return the full payload.
return json;
}
// Export a singleton instance of Relay Environment configured with our network layer:
const environment = new Environment({
network: Network.create(fetchRelay),
store: new Store(new RecordSource(), {
// This property tells Relay to not immediately clear its cache when the user
// navigates around the app. Relay will hold onto the specified number of
// query results, allowing the user to return to recently visited pages
// and reusing cached data if its available/fresh.
gcReleaseBufferSize: 10,
}),
});
export default environment;

View File

@ -12,52 +12,153 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { lazy, Suspense } from "react"; import { lazy, Suspense, useTransition } from "react";
import { createBrowserRouter, Outlet, RouterProvider } from "react-router-dom"; import { atomWithLocation } from "jotai-location";
import { atom, useAtomValue, useSetAtom } from "jotai";
import Layout from "./components/Layout"; import Layout from "./components/Layout";
import LoadingSpinner from "./components/LoadingSpinner"; import LoadingSpinner from "./components/LoadingSpinner";
type Location = {
pathname?: string;
searchParams?: URLSearchParams;
};
type HomeRoute = { type: "home" };
type DumbRoute = { type: "dumb" };
type OAuth2ClientRoute = { type: "client"; id: string };
type BrowserSessionRoute = { type: "session"; id: string };
type UnknownRoute = { type: "unknown"; segments: string[] };
export type Route =
| HomeRoute
| DumbRoute
| OAuth2ClientRoute
| BrowserSessionRoute
| UnknownRoute;
const routeToSegments = (route: Route): string[] => {
switch (route.type) {
case "home":
return [];
case "dumb":
return ["dumb"];
case "client":
return ["client", route.id];
case "session":
return ["session", route.id];
case "unknown":
return route.segments;
}
};
const segmentsToRoute = (segments: string[]): Route => {
if (segments.length === 0 || (segments.length === 1 && segments[0] === "")) {
return { type: "home" };
}
if (segments.length === 1 && segments[0] === "dumb") {
return { type: "dumb" };
}
if (segments.length === 2 && segments[0] === "client") {
return { type: "client", id: segments[1] };
}
if (segments.length === 2 && segments[0] === "session") {
return { type: "session", id: segments[1] };
}
return { type: "unknown", segments };
};
const routeToPath = (route: Route): string =>
routeToSegments(route)
.map((part) => encodeURIComponent(part))
.join("/");
const pathToRoute = (path: string): Route => {
const segments = path.split("/").map(decodeURIComponent);
return segmentsToRoute(segments);
};
const locationToRoute = (location: Location): Route => {
if (
!location.pathname ||
!location.pathname.startsWith(window.APP_CONFIG.root)
) {
throw new Error("Invalid location");
}
const path = location.pathname.slice(window.APP_CONFIG.root.length);
return pathToRoute(path);
};
const locationAtom = atomWithLocation();
export const routeAtom = atom(
(get) => locationToRoute(get(locationAtom)),
(_get, set, value: Route) => {
set(locationAtom, {
pathname: window.APP_CONFIG.root + routeToPath(value),
});
}
);
const Home = lazy(() => import("./pages/Home")); const Home = lazy(() => import("./pages/Home"));
const OAuth2Client = lazy(() => import("./pages/OAuth2Client")); const OAuth2Client = lazy(() => import("./pages/OAuth2Client"));
const BrowserSession = lazy(() => import("./pages/BrowserSession")); const BrowserSession = lazy(() => import("./pages/BrowserSession"));
export const router = createBrowserRouter( const InnerRouter: React.FC = () => {
[ const route = useAtomValue(routeAtom);
{
path: "/", switch (route.type) {
element: ( case "home":
<Layout> return <Home />;
<Suspense fallback={<LoadingSpinner />}> case "client":
<Outlet /> return <OAuth2Client id={route.id} />;
</Suspense> case "session":
</Layout> return <BrowserSession id={route.id} />;
), case "dumb":
children: [ return <>Dumb route.</>;
{ case "unknown":
index: true, return <>Unknown route {JSON.stringify(route.segments)}</>;
element: <Home />,
},
{
path: "dumb",
element: <>Hello from another dumb page.</>,
},
{
path: "client/:id",
element: <OAuth2Client />,
},
{
path: "session/:id",
element: <BrowserSession />,
},
],
},
],
{
basename: window.APP_CONFIG.root,
} }
};
const Router = () => (
<Layout>
<Suspense fallback={<LoadingSpinner />}>
<InnerRouter />
</Suspense>
</Layout>
); );
const Router = () => <RouterProvider router={router} />; export const Link: React.FC<
{
route: Route;
children: React.ReactNode;
} & React.HTMLProps<HTMLAnchorElement>
> = ({ route, children, ...props }) => {
const path = routeToPath(route);
const setRoute = useSetAtom(routeAtom);
// TODO: we should probably have more user control over this
const [isPending, startTransition] = useTransition();
return (
<a
href={path}
onClick={(e) => {
e.preventDefault();
startTransition(() => {
setRoute(route);
});
}}
{...props}
>
{isPending ? "Loading..." : children}
</a>
);
};
export default Router; export default Router;

24
frontend/src/atoms.ts Normal file
View File

@ -0,0 +1,24 @@
// Copyright 2023 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import type { ReactElement } from "react";
import { useHydrateAtoms } from "jotai/utils";
import { clientAtom } from "jotai-urql";
import { client } from "./graphql";
export const HydrateAtoms = ({ children }: { children: ReactElement }) => {
useHydrateAtoms([[clientAtom, client]]);
return children;
};

View File

@ -12,33 +12,30 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import type { BrowserSession_session$key } from "./__generated__/BrowserSession_session.graphql";
import { graphql, useFragment } from "react-relay";
import Block from "./Block"; import Block from "./Block";
import { Body, Subtitle } from "./Typography"; import { Body, Subtitle } from "./Typography";
import DateTime from "./DateTime"; import DateTime from "./DateTime";
import { Link } from "react-router-dom"; import { Link } from "../Router";
import { FragmentType, graphql, useFragment } from "../gql";
const FRAGMENT = graphql(/* GraphQL */ `
fragment BrowserSession_session on BrowserSession {
id
createdAt
lastAuthentication {
id
createdAt
}
}
`);
type Props = { type Props = {
session: BrowserSession_session$key; session: FragmentType<typeof FRAGMENT>;
isCurrent: boolean; isCurrent: boolean;
}; };
const BrowserSession: React.FC<Props> = ({ session, isCurrent }) => { const BrowserSession: React.FC<Props> = ({ session, isCurrent }) => {
const data = useFragment( const data = useFragment(FRAGMENT, session);
graphql`
fragment BrowserSession_session on BrowserSession {
id
createdAt
lastAuthentication {
id
createdAt
}
}
`,
session
);
const lastAuthentication = data.lastAuthentication?.createdAt; const lastAuthentication = data.lastAuthentication?.createdAt;
const createdAt = data.createdAt; const createdAt = data.createdAt;
@ -48,7 +45,7 @@ const BrowserSession: React.FC<Props> = ({ session, isCurrent }) => {
{isCurrent && <Subtitle>Current session</Subtitle>} {isCurrent && <Subtitle>Current session</Subtitle>}
<Body> <Body>
<Link <Link
to={`/session/${data.id}`} route={{ type: "session", id: data.id }}
className="text-links hover:text-links/75" className="text-links hover:text-links/75"
> >
Started: <DateTime datetime={createdAt} /> Started: <DateTime datetime={createdAt} />

View File

@ -12,38 +12,32 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { graphql, usePaginationFragment } from "react-relay";
import BlockList from "./BlockList"; import BlockList from "./BlockList";
import BrowserSession from "./BrowserSession"; import BrowserSession from "./BrowserSession";
import Button from "./Button";
import { Title } from "./Typography"; import { Title } from "./Typography";
import { FragmentType, graphql, useFragment } from "../gql";
import { BrowserSessionList_user$key } from "./__generated__/BrowserSessionList_user.graphql"; const FRAGMENT = graphql(/* GraphQL */ `
fragment BrowserSessionList_user on User {
browserSessions(first: $count, after: $cursor) {
edges {
cursor
node {
id
...BrowserSession_session
}
}
}
}
`);
type Props = { type Props = {
user: BrowserSessionList_user$key; user: FragmentType<typeof FRAGMENT>;
currentSessionId: string; currentSessionId: string;
}; };
const BrowserSessionList: React.FC<Props> = ({ user, currentSessionId }) => { const BrowserSessionList: React.FC<Props> = ({ user, currentSessionId }) => {
const { data, loadNext, hasNext } = usePaginationFragment( const data = useFragment(FRAGMENT, user);
graphql`
fragment BrowserSessionList_user on User
@refetchable(queryName: "BrowserSessionListQuery") {
browserSessions(first: $count, after: $cursor)
@connection(key: "BrowserSessionList_user_browserSessions") {
edges {
cursor
node {
id
...BrowserSession_session
}
}
}
}
`,
user
);
return ( return (
<BlockList> <BlockList>
@ -55,7 +49,6 @@ const BrowserSessionList: React.FC<Props> = ({ user, currentSessionId }) => {
isCurrent={n.node.id === currentSessionId} isCurrent={n.node.id === currentSessionId}
/> />
))} ))}
{hasNext && <Button onClick={() => loadNext(2)}>Load more</Button>}
</BlockList> </BlockList>
); );
}; };

View File

@ -12,33 +12,31 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import type { CompatSsoLogin_login$key } from "./__generated__/CompatSsoLogin_login.graphql";
import { graphql, useFragment } from "react-relay";
import Block from "./Block"; import Block from "./Block";
import { Body, Bold, Code } from "./Typography"; import { Body, Bold, Code } from "./Typography";
import DateTime from "./DateTime"; import DateTime from "./DateTime";
import { FragmentType, graphql, useFragment } from "../gql";
const FRAGMENT = graphql(/* GraphQL */ `
fragment CompatSsoLogin_login on CompatSsoLogin {
id
redirectUri
createdAt
session {
id
createdAt
deviceId
finishedAt
}
}
`);
type Props = { type Props = {
login: CompatSsoLogin_login$key; login: FragmentType<typeof FRAGMENT>;
}; };
const CompatSsoLogin: React.FC<Props> = ({ login }) => { const CompatSsoLogin: React.FC<Props> = ({ login }) => {
const data = useFragment( const data = useFragment(FRAGMENT, login);
graphql`
fragment CompatSsoLogin_login on CompatSsoLogin {
id
redirectUri
createdAt
session {
id
createdAt
deviceId
finishedAt
}
}
`,
login
);
let info = null; let info = null;
if (data.session) { if (data.session) {

View File

@ -12,35 +12,30 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { graphql, usePaginationFragment } from "react-relay";
import BlockList from "./BlockList"; import BlockList from "./BlockList";
import Button from "./Button";
import CompatSsoLogin from "./CompatSsoLogin"; import CompatSsoLogin from "./CompatSsoLogin";
import { Title } from "./Typography"; import { Title } from "./Typography";
import { CompatSsoLoginList_user$key } from "./__generated__/CompatSsoLoginList_user.graphql"; import { FragmentType, graphql, useFragment } from "../gql";
const FRAGMENT = graphql(/* GraphQL */ `
fragment CompatSsoLoginList_user on User {
compatSsoLogins(first: $count, after: $cursor) {
edges {
node {
id
...CompatSsoLogin_login
}
}
}
}
`);
type Props = { type Props = {
user: CompatSsoLoginList_user$key; user: FragmentType<typeof FRAGMENT>;
}; };
const CompatSsoLoginList: React.FC<Props> = ({ user }) => { const CompatSsoLoginList: React.FC<Props> = ({ user }) => {
const { data, loadNext, hasNext } = usePaginationFragment( const data = useFragment(FRAGMENT, user);
graphql`
fragment CompatSsoLoginList_user on User
@refetchable(queryName: "CompatSsoLoginListQuery") {
compatSsoLogins(first: $count, after: $cursor)
@connection(key: "CompatSsoLoginList_user_compatSsoLogins") {
edges {
node {
id
...CompatSsoLogin_login
}
}
}
}
`,
user
);
return ( return (
<BlockList> <BlockList>
@ -48,7 +43,6 @@ const CompatSsoLoginList: React.FC<Props> = ({ user }) => {
{data.compatSsoLogins.edges.map((n) => ( {data.compatSsoLogins.edges.map((n) => (
<CompatSsoLogin login={n.node} key={n.node.id} /> <CompatSsoLogin login={n.node} key={n.node.id} />
))} ))}
{hasNext && <Button onClick={() => loadNext(2)}>Load more</Button>}
</BlockList> </BlockList>
); );
}; };

View File

@ -19,8 +19,8 @@ const Layout: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
return ( return (
<div className="bg-grey-25 text-black-900 dark:bg-black-800 dark:text-white flex flex-col min-h-screen"> <div className="bg-grey-25 text-black-900 dark:bg-black-800 dark:text-white flex flex-col min-h-screen">
<NavBar className="mx-auto px-3 py-4 container"> <NavBar className="mx-auto px-3 py-4 container">
<NavItem to="/">Home</NavItem> <NavItem route={{ type: "home" }}>Home</NavItem>
<NavItem to="/dumb">Dumb</NavItem> <NavItem route={{ type: "dumb" }}>Dumb</NavItem>
</NavBar> </NavBar>
<main className="mx-auto p-4 container">{children}</main> <main className="mx-auto p-4 container">{children}</main>

View File

@ -12,25 +12,29 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { NavLink, To } from "react-router-dom"; import { useAtomValue } from "jotai";
import { Link, Route, routeAtom } from "../Router";
const NavItem: React.FC<{ to: To; children: React.ReactNode }> = ({ const NavItem: React.FC<{ route: Route; children: React.ReactNode }> = ({
to, route,
children, children,
}) => ( }) => {
<li className="m-1 mr-0"> const currentRoute = useAtomValue(routeAtom);
<NavLink return (
to={to} <li className="m-1 mr-0">
className={({ isActive }) => <Link
(isActive route={route}
? "bg-accent text-white" className={
: "hover:bg-grey-100 dark:hover:bg-grey-450 opacity-80 hover:opacity-100") + (currentRoute.type === route.type
" p-2 rounded block uppercase font-medium" ? "bg-accent text-white"
} : "hover:bg-grey-100 dark:hover:bg-grey-450 opacity-80 hover:opacity-100") +
> " p-2 rounded block uppercase font-medium"
{children} }
</NavLink> >
</li> {children}
); </Link>
</li>
);
};
export default NavItem; export default NavItem;

View File

@ -1,95 +0,0 @@
// Copyright 2022 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import type { Meta, StoryObj } from "@storybook/react";
import {
graphql,
RelayEnvironmentProvider,
useLazyLoadQuery,
} from "react-relay";
import { createMockEnvironment, MockPayloadGenerator } from "relay-test-utils";
import OAuth2Session from "./OAuth2Session";
import { OAuth2SessionStoriesQuery } from "./__generated__/OAuth2SessionStoriesQuery.graphql";
type TemplateProps = {
scope: string;
clientId: string;
clientName: string;
clientUri: string;
};
const Template: React.FC<TemplateProps> = ({
scope,
clientId,
clientName,
clientUri,
}) => {
const environment = createMockEnvironment();
environment.mock.queueOperationResolver((operation) =>
MockPayloadGenerator.generate(operation, {
Oauth2Session() {
return {
scope,
};
},
Oauth2Client() {
return {
clientId,
clientName,
clientUri,
};
},
})
);
const Render = () => {
const data = useLazyLoadQuery<OAuth2SessionStoriesQuery>(
graphql`
query OAuth2SessionStoriesQuery @relay_test_operation {
session: node(id: "test-id") {
...OAuth2Session_session
}
}
`,
{}
);
return <OAuth2Session session={data.session!!} />;
};
return (
<RelayEnvironmentProvider environment={environment}>
<Render />
</RelayEnvironmentProvider>
);
};
const meta = {
title: "Components/OAuth 2.0 Session",
component: Template,
tags: ["autodocs"],
args: {
scope: "openid",
clientId: "aaabbbcccdddeee",
clientName: "My client",
clientUri: "https://example.com/",
},
} satisfies Meta<typeof Template>;
export default meta;
type Story = StoryObj<typeof Template>;
export const Basic: Story = {};

View File

@ -12,38 +12,36 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import type { OAuth2Session_session$key } from "./__generated__/OAuth2Session_session.graphql";
import { graphql, useFragment } from "react-relay";
import { Body, Bold, Code } from "./Typography"; import { Body, Bold, Code } from "./Typography";
import Block from "./Block"; import Block from "./Block";
import { Link } from "react-router-dom"; import { Link } from "../Router";
import { FragmentType, graphql, useFragment } from "../gql";
const FRAGMENT = graphql(/* GraphQL */ `
fragment OAuth2Session_session on Oauth2Session {
id
scope
client {
id
clientId
clientName
clientUri
}
}
`);
type Props = { type Props = {
session: OAuth2Session_session$key; session: FragmentType<typeof FRAGMENT>;
}; };
const OAuth2Session: React.FC<Props> = ({ session }) => { const OAuth2Session: React.FC<Props> = ({ session }) => {
const data = useFragment( const data = useFragment(FRAGMENT, session);
graphql`
fragment OAuth2Session_session on Oauth2Session {
id
scope
client {
id
clientId
clientName
clientUri
}
}
`,
session
);
return ( return (
<Block> <Block>
<Body> <Body>
<Link <Link
to={`/client/${data.client.id}`} route={{ type: "client", id: data.client.id }}
className="text-links hover:text-links/75" className="text-links hover:text-links/75"
> >
Client ID: <Code>{data.client.clientId}</Code> Client ID: <Code>{data.client.clientId}</Code>

View File

@ -12,37 +12,32 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { graphql, usePaginationFragment } from "react-relay";
import BlockList from "./BlockList"; import BlockList from "./BlockList";
import Button from "./Button";
import OAuth2Session from "./OAuth2Session"; import OAuth2Session from "./OAuth2Session";
import { Title } from "./Typography"; import { Title } from "./Typography";
import { OAuth2SessionList_user$key } from "./__generated__/OAuth2SessionList_user.graphql"; import { FragmentType, graphql, useFragment } from "../gql";
const FRAGMENT = graphql(/* GraphQL */ `
fragment OAuth2SessionList_user on User {
oauth2Sessions(first: $count, after: $cursor) {
edges {
cursor
node {
id
...OAuth2Session_session
}
}
}
}
`);
type Props = { type Props = {
user: OAuth2SessionList_user$key; user: FragmentType<typeof FRAGMENT>;
}; };
const OAuth2SessionList: React.FC<Props> = ({ user }) => { const OAuth2SessionList: React.FC<Props> = ({ user }) => {
const { data, loadNext, hasNext } = usePaginationFragment( const data = useFragment(FRAGMENT, user);
graphql`
fragment OAuth2SessionList_user on User
@refetchable(queryName: "OAuth2SessionListQuery") {
oauth2Sessions(first: $count, after: $cursor)
@connection(key: "OAuth2SessionList_user_oauth2Sessions") {
edges {
cursor
node {
id
...OAuth2Session_session
}
}
}
}
`,
user
);
return ( return (
<BlockList> <BlockList>
@ -50,7 +45,6 @@ const OAuth2SessionList: React.FC<Props> = ({ user }) => {
{data.oauth2Sessions.edges.map((n) => ( {data.oauth2Sessions.edges.map((n) => (
<OAuth2Session key={n.cursor} session={n.node} /> <OAuth2Session key={n.cursor} session={n.node} />
))} ))}
{hasNext && <Button onClick={() => loadNext(2)}>Load more</Button>}
</BlockList> </BlockList>
); );
}; };

View File

@ -1,244 +0,0 @@
/**
* @generated SignedSource<<2e2beb7aa6522ccc21b080d150de618a>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest, Query } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type BrowserSessionListQuery$variables = {
count?: number | null;
cursor?: string | null;
id: string;
};
export type BrowserSessionListQuery$data = {
readonly node: {
readonly " $fragmentSpreads": FragmentRefs<"BrowserSessionList_user">;
} | null;
};
export type BrowserSessionListQuery = {
response: BrowserSessionListQuery$data;
variables: BrowserSessionListQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "count"
},
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "cursor"
},
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "id"
}
],
v1 = [
{
"kind": "Variable",
"name": "id",
"variableName": "id"
}
],
v2 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v4 = [
{
"kind": "Variable",
"name": "after",
"variableName": "cursor"
},
{
"kind": "Variable",
"name": "first",
"variableName": "count"
}
],
v5 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "BrowserSessionListQuery",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"args": null,
"kind": "FragmentSpread",
"name": "BrowserSessionList_user"
}
],
"storageKey": null
}
],
"type": "RootQuery",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "BrowserSessionListQuery",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
(v3/*: any*/),
{
"kind": "InlineFragment",
"selections": [
{
"alias": null,
"args": (v4/*: any*/),
"concreteType": "BrowserSessionConnection",
"kind": "LinkedField",
"name": "browserSessions",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "BrowserSessionEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "BrowserSession",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v3/*: any*/),
(v5/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Authentication",
"kind": "LinkedField",
"name": "lastAuthentication",
"plural": false,
"selections": [
(v3/*: any*/),
(v5/*: any*/)
],
"storageKey": null
},
(v2/*: any*/)
],
"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": null
},
{
"alias": null,
"args": (v4/*: any*/),
"filters": null,
"handle": "connection",
"key": "BrowserSessionList_user_browserSessions",
"kind": "LinkedHandle",
"name": "browserSessions"
}
],
"type": "User",
"abstractKey": null
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "c05f614c382cae0bed080006b43f14f3",
"id": null,
"metadata": {},
"name": "BrowserSessionListQuery",
"operationKind": "query",
"text": "query BrowserSessionListQuery(\n $count: Int\n $cursor: String\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...BrowserSessionList_user\n id\n }\n}\n\nfragment BrowserSessionList_user on User {\n browserSessions(first: $count, after: $cursor) {\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n __typename\n }\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n id\n}\n\nfragment BrowserSession_session on BrowserSession {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n}\n"
}
};
})();
(node as any).hash = "5f21c429aa98b854c17d3f9eb83b81d8";
export default node;

View File

@ -1,170 +0,0 @@
/**
* @generated SignedSource<<3b7505958556a142e2d9926ae631f0e0>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ReaderFragment, RefetchableFragment } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type BrowserSessionList_user$data = {
readonly browserSessions: {
readonly edges: ReadonlyArray<{
readonly cursor: string;
readonly node: {
readonly id: string;
readonly " $fragmentSpreads": FragmentRefs<"BrowserSession_session">;
};
}>;
};
readonly id: string;
readonly " $fragmentType": "BrowserSessionList_user";
};
export type BrowserSessionList_user$key = {
readonly " $data"?: BrowserSessionList_user$data;
readonly " $fragmentSpreads": FragmentRefs<"BrowserSessionList_user">;
};
import BrowserSessionListQuery_graphql from './BrowserSessionListQuery.graphql';
const node: ReaderFragment = (function(){
var v0 = [
"browserSessions"
],
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
};
return {
"argumentDefinitions": [
{
"kind": "RootArgument",
"name": "count"
},
{
"kind": "RootArgument",
"name": "cursor"
}
],
"kind": "Fragment",
"metadata": {
"connection": [
{
"count": "count",
"cursor": "cursor",
"direction": "forward",
"path": (v0/*: any*/)
}
],
"refetch": {
"connection": {
"forward": {
"count": "count",
"cursor": "cursor"
},
"backward": null,
"path": (v0/*: any*/)
},
"fragmentPathInResult": [
"node"
],
"operation": BrowserSessionListQuery_graphql,
"identifierField": "id"
}
},
"name": "BrowserSessionList_user",
"selections": [
{
"alias": "browserSessions",
"args": null,
"concreteType": "BrowserSessionConnection",
"kind": "LinkedField",
"name": "__BrowserSessionList_user_browserSessions_connection",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "BrowserSessionEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "BrowserSession",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v1/*: any*/),
{
"args": null,
"kind": "FragmentSpread",
"name": "BrowserSession_session"
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
}
],
"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": null
},
(v1/*: any*/)
],
"type": "User",
"abstractKey": null
};
})();
(node as any).hash = "5f21c429aa98b854c17d3f9eb83b81d8";
export default node;

View File

@ -1,71 +0,0 @@
/**
* @generated SignedSource<<f204c3033b21cfa962537b2dd72f4469>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { Fragment, ReaderFragment } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type BrowserSession_session$data = {
readonly createdAt: any;
readonly id: string;
readonly lastAuthentication: {
readonly createdAt: any;
readonly id: string;
} | null;
readonly " $fragmentType": "BrowserSession_session";
};
export type BrowserSession_session$key = {
readonly " $data"?: BrowserSession_session$data;
readonly " $fragmentSpreads": FragmentRefs<"BrowserSession_session">;
};
const node: ReaderFragment = (function(){
var v0 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
};
return {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "BrowserSession_session",
"selections": [
(v0/*: any*/),
(v1/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Authentication",
"kind": "LinkedField",
"name": "lastAuthentication",
"plural": false,
"selections": [
(v0/*: any*/),
(v1/*: any*/)
],
"storageKey": null
}
],
"type": "BrowserSession",
"abstractKey": null
};
})();
(node as any).hash = "04d6adf0b2a1bf2098938ef30f195c4a";
export default node;

View File

@ -1,265 +0,0 @@
/**
* @generated SignedSource<<3e111db0c30af3251a88326deea5562c>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest, Query } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type CompatSsoLoginListQuery$variables = {
count?: number | null;
cursor?: string | null;
id: string;
};
export type CompatSsoLoginListQuery$data = {
readonly node: {
readonly " $fragmentSpreads": FragmentRefs<"CompatSsoLoginList_user">;
} | null;
};
export type CompatSsoLoginListQuery = {
response: CompatSsoLoginListQuery$data;
variables: CompatSsoLoginListQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "count"
},
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "cursor"
},
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "id"
}
],
v1 = [
{
"kind": "Variable",
"name": "id",
"variableName": "id"
}
],
v2 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v4 = [
{
"kind": "Variable",
"name": "after",
"variableName": "cursor"
},
{
"kind": "Variable",
"name": "first",
"variableName": "count"
}
],
v5 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "CompatSsoLoginListQuery",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"args": null,
"kind": "FragmentSpread",
"name": "CompatSsoLoginList_user"
}
],
"storageKey": null
}
],
"type": "RootQuery",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "CompatSsoLoginListQuery",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
(v3/*: any*/),
{
"kind": "InlineFragment",
"selections": [
{
"alias": null,
"args": (v4/*: any*/),
"concreteType": "CompatSsoLoginConnection",
"kind": "LinkedField",
"name": "compatSsoLogins",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "CompatSsoLoginEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "CompatSsoLogin",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "redirectUri",
"storageKey": null
},
(v5/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "CompatSession",
"kind": "LinkedField",
"name": "session",
"plural": false,
"selections": [
(v3/*: any*/),
(v5/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "deviceId",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "finishedAt",
"storageKey": null
}
],
"storageKey": null
},
(v2/*: any*/)
],
"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": null
},
{
"alias": null,
"args": (v4/*: any*/),
"filters": null,
"handle": "connection",
"key": "CompatSsoLoginList_user_compatSsoLogins",
"kind": "LinkedHandle",
"name": "compatSsoLogins"
}
],
"type": "User",
"abstractKey": null
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "45c6342d3a53a3e3b2b18617796cf654",
"id": null,
"metadata": {},
"name": "CompatSsoLoginListQuery",
"operationKind": "query",
"text": "query CompatSsoLoginListQuery(\n $count: Int\n $cursor: String\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...CompatSsoLoginList_user\n id\n }\n}\n\nfragment CompatSsoLoginList_user on User {\n compatSsoLogins(first: $count, after: $cursor) {\n edges {\n node {\n id\n ...CompatSsoLogin_login\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n id\n}\n\nfragment CompatSsoLogin_login on CompatSsoLogin {\n id\n redirectUri\n createdAt\n session {\n id\n createdAt\n deviceId\n finishedAt\n }\n}\n"
}
};
})();
(node as any).hash = "cafc795d1bf9643ac6155c017e66c858";
export default node;

View File

@ -1,169 +0,0 @@
/**
* @generated SignedSource<<4ace8ea8668e3dc638df21400c690bd8>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ReaderFragment, RefetchableFragment } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type CompatSsoLoginList_user$data = {
readonly compatSsoLogins: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly id: string;
readonly " $fragmentSpreads": FragmentRefs<"CompatSsoLogin_login">;
};
}>;
};
readonly id: string;
readonly " $fragmentType": "CompatSsoLoginList_user";
};
export type CompatSsoLoginList_user$key = {
readonly " $data"?: CompatSsoLoginList_user$data;
readonly " $fragmentSpreads": FragmentRefs<"CompatSsoLoginList_user">;
};
import CompatSsoLoginListQuery_graphql from './CompatSsoLoginListQuery.graphql';
const node: ReaderFragment = (function(){
var v0 = [
"compatSsoLogins"
],
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
};
return {
"argumentDefinitions": [
{
"kind": "RootArgument",
"name": "count"
},
{
"kind": "RootArgument",
"name": "cursor"
}
],
"kind": "Fragment",
"metadata": {
"connection": [
{
"count": "count",
"cursor": "cursor",
"direction": "forward",
"path": (v0/*: any*/)
}
],
"refetch": {
"connection": {
"forward": {
"count": "count",
"cursor": "cursor"
},
"backward": null,
"path": (v0/*: any*/)
},
"fragmentPathInResult": [
"node"
],
"operation": CompatSsoLoginListQuery_graphql,
"identifierField": "id"
}
},
"name": "CompatSsoLoginList_user",
"selections": [
{
"alias": "compatSsoLogins",
"args": null,
"concreteType": "CompatSsoLoginConnection",
"kind": "LinkedField",
"name": "__CompatSsoLoginList_user_compatSsoLogins_connection",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "CompatSsoLoginEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "CompatSsoLogin",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v1/*: any*/),
{
"args": null,
"kind": "FragmentSpread",
"name": "CompatSsoLogin_login"
},
{
"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": null
},
(v1/*: any*/)
],
"type": "User",
"abstractKey": null
};
})();
(node as any).hash = "cafc795d1bf9643ac6155c017e66c858";
export default node;

View File

@ -1,95 +0,0 @@
/**
* @generated SignedSource<<20e2b233e5154ea60632046abc2aa29a>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { Fragment, ReaderFragment } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type CompatSsoLogin_login$data = {
readonly createdAt: any;
readonly id: string;
readonly redirectUri: any;
readonly session: {
readonly createdAt: any;
readonly deviceId: string;
readonly finishedAt: any | null;
readonly id: string;
} | null;
readonly " $fragmentType": "CompatSsoLogin_login";
};
export type CompatSsoLogin_login$key = {
readonly " $data"?: CompatSsoLogin_login$data;
readonly " $fragmentSpreads": FragmentRefs<"CompatSsoLogin_login">;
};
const node: ReaderFragment = (function(){
var v0 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
};
return {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "CompatSsoLogin_login",
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "redirectUri",
"storageKey": null
},
(v1/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "CompatSession",
"kind": "LinkedField",
"name": "session",
"plural": false,
"selections": [
(v0/*: any*/),
(v1/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "deviceId",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "finishedAt",
"storageKey": null
}
],
"storageKey": null
}
],
"type": "CompatSsoLogin",
"abstractKey": null
};
})();
(node as any).hash = "7be3b416b1023cea0de7a87b9738e5d5";
export default node;

View File

@ -1,263 +0,0 @@
/**
* @generated SignedSource<<816340b05858a7b5a61aca0a72c21c46>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest, Query } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type OAuth2SessionListQuery$variables = {
count?: number | null;
cursor?: string | null;
id: string;
};
export type OAuth2SessionListQuery$data = {
readonly node: {
readonly " $fragmentSpreads": FragmentRefs<"OAuth2SessionList_user">;
} | null;
};
export type OAuth2SessionListQuery = {
response: OAuth2SessionListQuery$data;
variables: OAuth2SessionListQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "count"
},
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "cursor"
},
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "id"
}
],
v1 = [
{
"kind": "Variable",
"name": "id",
"variableName": "id"
}
],
v2 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v4 = [
{
"kind": "Variable",
"name": "after",
"variableName": "cursor"
},
{
"kind": "Variable",
"name": "first",
"variableName": "count"
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "OAuth2SessionListQuery",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"args": null,
"kind": "FragmentSpread",
"name": "OAuth2SessionList_user"
}
],
"storageKey": null
}
],
"type": "RootQuery",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "OAuth2SessionListQuery",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
(v3/*: any*/),
{
"kind": "InlineFragment",
"selections": [
{
"alias": null,
"args": (v4/*: any*/),
"concreteType": "Oauth2SessionConnection",
"kind": "LinkedField",
"name": "oauth2Sessions",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Oauth2SessionEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "Oauth2Session",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "scope",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "Oauth2Client",
"kind": "LinkedField",
"name": "client",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "clientId",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "clientName",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "clientUri",
"storageKey": null
}
],
"storageKey": null
},
(v2/*: any*/)
],
"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": null
},
{
"alias": null,
"args": (v4/*: any*/),
"filters": null,
"handle": "connection",
"key": "OAuth2SessionList_user_oauth2Sessions",
"kind": "LinkedHandle",
"name": "oauth2Sessions"
}
],
"type": "User",
"abstractKey": null
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "26d1b723940d396f1af608f36ff05ce6",
"id": null,
"metadata": {},
"name": "OAuth2SessionListQuery",
"operationKind": "query",
"text": "query OAuth2SessionListQuery(\n $count: Int\n $cursor: String\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...OAuth2SessionList_user\n id\n }\n}\n\nfragment OAuth2SessionList_user on User {\n oauth2Sessions(first: $count, after: $cursor) {\n edges {\n cursor\n node {\n id\n ...OAuth2Session_session\n __typename\n }\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n id\n}\n\nfragment OAuth2Session_session on Oauth2Session {\n id\n scope\n client {\n id\n clientId\n clientName\n clientUri\n }\n}\n"
}
};
})();
(node as any).hash = "17ef23458a73705550aa33e7f73e41cf";
export default node;

View File

@ -1,170 +0,0 @@
/**
* @generated SignedSource<<8cb936a8cc1a9ab2a877a4ea92622c88>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ReaderFragment, RefetchableFragment } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type OAuth2SessionList_user$data = {
readonly id: string;
readonly oauth2Sessions: {
readonly edges: ReadonlyArray<{
readonly cursor: string;
readonly node: {
readonly id: string;
readonly " $fragmentSpreads": FragmentRefs<"OAuth2Session_session">;
};
}>;
};
readonly " $fragmentType": "OAuth2SessionList_user";
};
export type OAuth2SessionList_user$key = {
readonly " $data"?: OAuth2SessionList_user$data;
readonly " $fragmentSpreads": FragmentRefs<"OAuth2SessionList_user">;
};
import OAuth2SessionListQuery_graphql from './OAuth2SessionListQuery.graphql';
const node: ReaderFragment = (function(){
var v0 = [
"oauth2Sessions"
],
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
};
return {
"argumentDefinitions": [
{
"kind": "RootArgument",
"name": "count"
},
{
"kind": "RootArgument",
"name": "cursor"
}
],
"kind": "Fragment",
"metadata": {
"connection": [
{
"count": "count",
"cursor": "cursor",
"direction": "forward",
"path": (v0/*: any*/)
}
],
"refetch": {
"connection": {
"forward": {
"count": "count",
"cursor": "cursor"
},
"backward": null,
"path": (v0/*: any*/)
},
"fragmentPathInResult": [
"node"
],
"operation": OAuth2SessionListQuery_graphql,
"identifierField": "id"
}
},
"name": "OAuth2SessionList_user",
"selections": [
{
"alias": "oauth2Sessions",
"args": null,
"concreteType": "Oauth2SessionConnection",
"kind": "LinkedField",
"name": "__OAuth2SessionList_user_oauth2Sessions_connection",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Oauth2SessionEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "Oauth2Session",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v1/*: any*/),
{
"args": null,
"kind": "FragmentSpread",
"name": "OAuth2Session_session"
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
}
],
"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": null
},
(v1/*: any*/)
],
"type": "User",
"abstractKey": null
};
})();
(node as any).hash = "17ef23458a73705550aa33e7f73e41cf";
export default node;

View File

@ -1,197 +0,0 @@
/**
* @generated SignedSource<<82179715f61e6a03437d696941ceaf16>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest, Query } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type OAuth2SessionStoriesQuery$variables = {};
export type OAuth2SessionStoriesQuery$data = {
readonly session: {
readonly " $fragmentSpreads": FragmentRefs<"OAuth2Session_session">;
} | null;
};
export type OAuth2SessionStoriesQuery = {
response: OAuth2SessionStoriesQuery$data;
variables: OAuth2SessionStoriesQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"kind": "Literal",
"name": "id",
"value": "test-id"
}
],
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v2 = {
"enumValues": null,
"nullable": false,
"plural": false,
"type": "String"
},
v3 = {
"enumValues": null,
"nullable": false,
"plural": false,
"type": "ID"
};
return {
"fragment": {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "OAuth2SessionStoriesQuery",
"selections": [
{
"alias": "session",
"args": (v0/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"args": null,
"kind": "FragmentSpread",
"name": "OAuth2Session_session"
}
],
"storageKey": "node(id:\"test-id\")"
}
],
"type": "RootQuery",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [],
"kind": "Operation",
"name": "OAuth2SessionStoriesQuery",
"selections": [
{
"alias": "session",
"args": (v0/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
(v1/*: any*/),
{
"kind": "InlineFragment",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "scope",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "Oauth2Client",
"kind": "LinkedField",
"name": "client",
"plural": false,
"selections": [
(v1/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "clientId",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "clientName",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "clientUri",
"storageKey": null
}
],
"storageKey": null
}
],
"type": "Oauth2Session",
"abstractKey": null
}
],
"storageKey": "node(id:\"test-id\")"
}
]
},
"params": {
"cacheID": "2b2911cfb421c557245313732f0813e0",
"id": null,
"metadata": {
"relayTestingSelectionTypeInfo": {
"session": {
"enumValues": null,
"nullable": true,
"plural": false,
"type": "Node"
},
"session.__typename": (v2/*: any*/),
"session.client": {
"enumValues": null,
"nullable": false,
"plural": false,
"type": "Oauth2Client"
},
"session.client.clientId": (v2/*: any*/),
"session.client.clientName": {
"enumValues": null,
"nullable": true,
"plural": false,
"type": "String"
},
"session.client.clientUri": {
"enumValues": null,
"nullable": true,
"plural": false,
"type": "Url"
},
"session.client.id": (v3/*: any*/),
"session.id": (v3/*: any*/),
"session.scope": (v2/*: any*/)
}
},
"name": "OAuth2SessionStoriesQuery",
"operationKind": "query",
"text": "query OAuth2SessionStoriesQuery {\n session: node(id: \"test-id\") {\n __typename\n ...OAuth2Session_session\n id\n }\n}\n\nfragment OAuth2Session_session on Oauth2Session {\n id\n scope\n client {\n id\n clientId\n clientName\n clientUri\n }\n}\n"
}
};
})();
(node as any).hash = "d60f8f0abfc0b70236d89328bc5c0e85";
export default node;

View File

@ -1,92 +0,0 @@
/**
* @generated SignedSource<<599452efb8ce96e81a5e9500668ff055>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { Fragment, ReaderFragment } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type OAuth2Session_session$data = {
readonly client: {
readonly clientId: string;
readonly clientName: string | null;
readonly clientUri: any | null;
readonly id: string;
};
readonly id: string;
readonly scope: string;
readonly " $fragmentType": "OAuth2Session_session";
};
export type OAuth2Session_session$key = {
readonly " $data"?: OAuth2Session_session$data;
readonly " $fragmentSpreads": FragmentRefs<"OAuth2Session_session">;
};
const node: ReaderFragment = (function(){
var v0 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
};
return {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "OAuth2Session_session",
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "scope",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "Oauth2Client",
"kind": "LinkedField",
"name": "client",
"plural": false,
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "clientId",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "clientName",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "clientUri",
"storageKey": null
}
],
"storageKey": null
}
],
"type": "Oauth2Session",
"abstractKey": null
};
})();
(node as any).hash = "d9fa36c7f93b7cef4d5a038d19f768b1";
export default node;

View File

@ -0,0 +1,54 @@
import {
ResultOf,
TypedDocumentNode as DocumentNode,
} from "@graphql-typed-document-node/core";
export type FragmentType<TDocumentType extends DocumentNode<any, any>> =
TDocumentType extends DocumentNode<infer TType, any>
? TType extends { " $fragmentName"?: infer TKey }
? TKey extends string
? { " $fragmentRefs"?: { [key in TKey]: TType } }
: never
: never
: never;
// return non-nullable if `fragmentType` is non-nullable
export function useFragment<TType>(
_documentNode: DocumentNode<TType, any>,
fragmentType: FragmentType<DocumentNode<TType, any>>
): TType;
// return nullable if `fragmentType` is nullable
export function useFragment<TType>(
_documentNode: DocumentNode<TType, any>,
fragmentType: FragmentType<DocumentNode<TType, any>> | null | undefined
): TType | null | undefined;
// return array of non-nullable if `fragmentType` is array of non-nullable
export function useFragment<TType>(
_documentNode: DocumentNode<TType, any>,
fragmentType: ReadonlyArray<FragmentType<DocumentNode<TType, any>>>
): ReadonlyArray<TType>;
// return array of nullable if `fragmentType` is array of nullable
export function useFragment<TType>(
_documentNode: DocumentNode<TType, any>,
fragmentType:
| ReadonlyArray<FragmentType<DocumentNode<TType, any>>>
| null
| undefined
): ReadonlyArray<TType> | null | undefined;
export function useFragment<TType>(
_documentNode: DocumentNode<TType, any>,
fragmentType:
| FragmentType<DocumentNode<TType, any>>
| ReadonlyArray<FragmentType<DocumentNode<TType, any>>>
| null
| undefined
): TType | ReadonlyArray<TType> | null | undefined {
return fragmentType as any;
}
export function makeFragmentData<
F extends DocumentNode,
FT extends ResultOf<F>
>(data: FT, _fragment: F): FragmentType<F> {
return data as FragmentType<F>;
}

82
frontend/src/gql/gql.ts Normal file
View File

@ -0,0 +1,82 @@
/* eslint-disable */
import * as types from './graphql';
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
/**
* Map of all GraphQL operations in the project.
*
* This map has several performance disadvantages:
* 1. It is not tree-shakeable, so it will include all operations in the project.
* 2. It is not minifiable, so the string of a GraphQL query will be multiple times inside the bundle.
* 3. It does not support dead code elimination, so it will add unused operations.
*
* Therefore it is highly recommended to use the babel or swc plugin for production.
*/
const documents = {
"\n fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n }\n": types.BrowserSession_SessionFragmentDoc,
"\n fragment BrowserSessionList_user on User {\n browserSessions(first: $count, after: $cursor) {\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n }\n }\n": types.BrowserSessionList_UserFragmentDoc,
"\n fragment CompatSsoLogin_login on CompatSsoLogin {\n id\n redirectUri\n createdAt\n session {\n id\n createdAt\n deviceId\n finishedAt\n }\n }\n": types.CompatSsoLogin_LoginFragmentDoc,
"\n fragment CompatSsoLoginList_user on User {\n compatSsoLogins(first: $count, after: $cursor) {\n edges {\n node {\n id\n ...CompatSsoLogin_login\n }\n }\n }\n }\n": types.CompatSsoLoginList_UserFragmentDoc,
"\n fragment OAuth2Session_session on Oauth2Session {\n id\n scope\n client {\n id\n clientId\n clientName\n clientUri\n }\n }\n": types.OAuth2Session_SessionFragmentDoc,
"\n fragment OAuth2SessionList_user on User {\n oauth2Sessions(first: $count, after: $cursor) {\n edges {\n cursor\n node {\n id\n ...OAuth2Session_session\n }\n }\n }\n }\n": types.OAuth2SessionList_UserFragmentDoc,
"\n query BrowserSessionQuery($id: ID!) {\n browserSession(id: $id) {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n }\n": types.BrowserSessionQueryDocument,
"\n query HomeQuery($count: Int!, $cursor: String) {\n currentBrowserSession {\n id\n user {\n id\n username\n\n ...CompatSsoLoginList_user\n ...BrowserSessionList_user\n ...OAuth2SessionList_user\n }\n }\n }\n": types.HomeQueryDocument,
"\n query OAuth2ClientQuery($id: ID!) {\n oauth2Client(id: $id) {\n id\n clientId\n clientName\n clientUri\n tosUri\n policyUri\n redirectUris\n }\n }\n": types.OAuth2ClientQueryDocument,
};
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*
*
* @example
* ```ts
* const query = graphql(`query GetUser($id: ID!) { user(id: $id) { name } }`);
* ```
*
* The query argument is unknown!
* Please regenerate the types.
*/
export function graphql(source: string): unknown;
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n }\n"): (typeof documents)["\n fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n fragment BrowserSessionList_user on User {\n browserSessions(first: $count, after: $cursor) {\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n }\n }\n"): (typeof documents)["\n fragment BrowserSessionList_user on User {\n browserSessions(first: $count, after: $cursor) {\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n fragment CompatSsoLogin_login on CompatSsoLogin {\n id\n redirectUri\n createdAt\n session {\n id\n createdAt\n deviceId\n finishedAt\n }\n }\n"): (typeof documents)["\n fragment CompatSsoLogin_login on CompatSsoLogin {\n id\n redirectUri\n createdAt\n session {\n id\n createdAt\n deviceId\n finishedAt\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n fragment CompatSsoLoginList_user on User {\n compatSsoLogins(first: $count, after: $cursor) {\n edges {\n node {\n id\n ...CompatSsoLogin_login\n }\n }\n }\n }\n"): (typeof documents)["\n fragment CompatSsoLoginList_user on User {\n compatSsoLogins(first: $count, after: $cursor) {\n edges {\n node {\n id\n ...CompatSsoLogin_login\n }\n }\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n fragment OAuth2Session_session on Oauth2Session {\n id\n scope\n client {\n id\n clientId\n clientName\n clientUri\n }\n }\n"): (typeof documents)["\n fragment OAuth2Session_session on Oauth2Session {\n id\n scope\n client {\n id\n clientId\n clientName\n clientUri\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n fragment OAuth2SessionList_user on User {\n oauth2Sessions(first: $count, after: $cursor) {\n edges {\n cursor\n node {\n id\n ...OAuth2Session_session\n }\n }\n }\n }\n"): (typeof documents)["\n fragment OAuth2SessionList_user on User {\n oauth2Sessions(first: $count, after: $cursor) {\n edges {\n cursor\n node {\n id\n ...OAuth2Session_session\n }\n }\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n query BrowserSessionQuery($id: ID!) {\n browserSession(id: $id) {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n }\n"): (typeof documents)["\n query BrowserSessionQuery($id: ID!) {\n browserSession(id: $id) {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n query HomeQuery($count: Int!, $cursor: String) {\n currentBrowserSession {\n id\n user {\n id\n username\n\n ...CompatSsoLoginList_user\n ...BrowserSessionList_user\n ...OAuth2SessionList_user\n }\n }\n }\n"): (typeof documents)["\n query HomeQuery($count: Int!, $cursor: String) {\n currentBrowserSession {\n id\n user {\n id\n username\n\n ...CompatSsoLoginList_user\n ...BrowserSessionList_user\n ...OAuth2SessionList_user\n }\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n query OAuth2ClientQuery($id: ID!) {\n oauth2Client(id: $id) {\n id\n clientId\n clientName\n clientUri\n tosUri\n policyUri\n redirectUris\n }\n }\n"): (typeof documents)["\n query OAuth2ClientQuery($id: ID!) {\n oauth2Client(id: $id) {\n id\n clientId\n clientName\n clientUri\n tosUri\n policyUri\n redirectUris\n }\n }\n"];
export function graphql(source: string) {
return (documents as any)[source] ?? {};
}
export type DocumentType<TDocumentNode extends DocumentNode<any, any>> = TDocumentNode extends DocumentNode< infer TType, any> ? TType : never;

507
frontend/src/gql/graphql.ts Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
export * from "./fragment-masking";
export * from "./gql";

25
frontend/src/graphql.ts Normal file
View File

@ -0,0 +1,25 @@
// Copyright 2023 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {
createClient,
dedupExchange,
cacheExchange,
fetchExchange,
} from "@urql/core";
export const client = createClient({
url: "/graphql",
exchanges: [dedupExchange, cacheExchange, fetchExchange],
});

View File

@ -14,18 +14,20 @@
import React from "react"; import React from "react";
import ReactDOM from "react-dom/client"; import ReactDOM from "react-dom/client";
import { RelayEnvironmentProvider } from "react-relay"; import { Provider } from "jotai";
import LoadingScreen from "./components/LoadingScreen"; import LoadingScreen from "./components/LoadingScreen";
import RelayEnvironment from "./RelayEnvironment";
import Router from "./Router"; import Router from "./Router";
import { HydrateAtoms } from "./atoms";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode> <React.StrictMode>
<RelayEnvironmentProvider environment={RelayEnvironment}> <Provider>
<React.Suspense fallback={<LoadingScreen />}> <HydrateAtoms>
<Router /> <React.Suspense fallback={<LoadingScreen />}>
</React.Suspense> <Router />
</RelayEnvironmentProvider> </React.Suspense>
</HydrateAtoms>
</Provider>
</React.StrictMode> </React.StrictMode>
); );

View File

@ -12,35 +12,31 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { graphql, useLazyLoadQuery } from "react-relay"; import { useAtomValue } from "jotai";
import { useParams } from "react-router-dom"; import { atomsWithQuery } from "jotai-urql";
import { useMemo } from "react";
import { graphql } from "../gql";
import type { BrowserSessionQuery } from "./__generated__/BrowserSessionQuery.graphql"; const QUERY = graphql(/* GraphQL */ `
query BrowserSessionQuery($id: ID!) {
const BrowserSession: React.FC = () => { browserSession(id: $id) {
const { id } = useParams(); id
if (!id) { createdAt
throw new Error("Missing parameter"); lastAuthentication {
} id
createdAt
const data = useLazyLoadQuery<BrowserSessionQuery>(
graphql`
query BrowserSessionQuery($id: ID!) {
browserSession(id: $id) {
id
createdAt
lastAuthentication {
id
createdAt
}
user {
id
username
}
}
} }
`, user {
{ id } id
username
}
}
}
`);
const BrowserSession: React.FC<{ id: string }> = ({ id }) => {
const data = useAtomValue(
useMemo(() => atomsWithQuery(QUERY, () => ({ id })), [id])[0]
); );
return ( return (

View File

@ -12,33 +12,35 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { graphql, useLazyLoadQuery } from "react-relay"; import { useAtomValue } from "jotai";
import BrowserSessionList from "../components/BrowserSessionList"; import { atomsWithQuery } from "jotai-urql";
import BrowserSessionList from "../components/BrowserSessionList";
import CompatSsoLoginList from "../components/CompatSsoLoginList"; import CompatSsoLoginList from "../components/CompatSsoLoginList";
import OAuth2SessionList from "../components/OAuth2SessionList"; import OAuth2SessionList from "../components/OAuth2SessionList";
import Typography from "../components/Typography"; import Typography from "../components/Typography";
import type { HomeQuery } from "./__generated__/HomeQuery.graphql"; import { graphql } from "../gql";
const QUERY = graphql(/* GraphQL */ `
query HomeQuery($count: Int!, $cursor: String) {
currentBrowserSession {
id
user {
id
username
...CompatSsoLoginList_user
...BrowserSessionList_user
...OAuth2SessionList_user
}
}
}
`);
const [homeDataAtom] = atomsWithQuery(QUERY, () => ({ count: 10 }));
const Home: React.FC = () => { const Home: React.FC = () => {
const data = useLazyLoadQuery<HomeQuery>( const data = useAtomValue(homeDataAtom);
graphql`
query HomeQuery($count: Int!, $cursor: String) {
currentBrowserSession {
id
user {
id
username
...CompatSsoLoginList_user
...BrowserSessionList_user
...OAuth2SessionList_user
}
}
}
`,
{ count: 2 }
);
if (data.currentBrowserSession) { if (data.currentBrowserSession) {
const session = data.currentBrowserSession; const session = data.currentBrowserSession;

View File

@ -12,32 +12,28 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { graphql, useLazyLoadQuery } from "react-relay"; import { useAtomValue } from "jotai";
import { useParams } from "react-router-dom"; import { useMemo } from "react";
import { atomsWithQuery } from "jotai-urql";
import { graphql } from "../gql";
import type { OAuth2ClientQuery } from "./__generated__/OAuth2ClientQuery.graphql"; const QUERY = graphql(/* GraphQL */ `
query OAuth2ClientQuery($id: ID!) {
const OAuth2Client: React.FC = () => { oauth2Client(id: $id) {
const { id } = useParams(); id
if (!id) { clientId
throw new Error("Missing parameter"); clientName
clientUri
tosUri
policyUri
redirectUris
}
} }
`);
const data = useLazyLoadQuery<OAuth2ClientQuery>( const OAuth2Client: React.FC<{ id: string }> = ({ id }) => {
graphql` const data = useAtomValue(
query OAuth2ClientQuery($id: ID!) { useMemo(() => atomsWithQuery(QUERY, () => ({ id })), [id])[0]
oauth2Client(id: $id) {
id
clientId
clientName
clientUri
tosUri
policyUri
redirectUris
}
}
`,
{ id }
); );
return ( return (

View File

@ -1,139 +0,0 @@
/**
* @generated SignedSource<<f53c0d7dd710ddd990f1dc215274fc5f>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest, Query } from 'relay-runtime';
export type BrowserSessionQuery$variables = {
id: string;
};
export type BrowserSessionQuery$data = {
readonly browserSession: {
readonly createdAt: any;
readonly id: string;
readonly lastAuthentication: {
readonly createdAt: any;
readonly id: string;
} | null;
readonly user: {
readonly id: string;
readonly username: string;
};
} | null;
};
export type BrowserSessionQuery = {
response: BrowserSessionQuery$data;
variables: BrowserSessionQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "id"
}
],
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v2 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
v3 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "id",
"variableName": "id"
}
],
"concreteType": "BrowserSession",
"kind": "LinkedField",
"name": "browserSession",
"plural": false,
"selections": [
(v1/*: any*/),
(v2/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Authentication",
"kind": "LinkedField",
"name": "lastAuthentication",
"plural": false,
"selections": [
(v1/*: any*/),
(v2/*: any*/)
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "User",
"kind": "LinkedField",
"name": "user",
"plural": false,
"selections": [
(v1/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "username",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "BrowserSessionQuery",
"selections": (v3/*: any*/),
"type": "RootQuery",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "BrowserSessionQuery",
"selections": (v3/*: any*/)
},
"params": {
"cacheID": "5374afccfa4da28a64cdce6585ac1907",
"id": null,
"metadata": {},
"name": "BrowserSessionQuery",
"operationKind": "query",
"text": "query BrowserSessionQuery(\n $id: ID!\n) {\n browserSession(id: $id) {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n}\n"
}
};
})();
(node as any).hash = "c73293a99a0214448861bed340594304";
export default node;

View File

@ -1,441 +0,0 @@
/**
* @generated SignedSource<<b9d5536e28bde5c8929747fab28aae0e>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest, Query } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type HomeQuery$variables = {
count: number;
cursor?: string | null;
};
export type HomeQuery$data = {
readonly currentBrowserSession: {
readonly id: string;
readonly user: {
readonly id: string;
readonly username: string;
readonly " $fragmentSpreads": FragmentRefs<"BrowserSessionList_user" | "CompatSsoLoginList_user" | "OAuth2SessionList_user">;
};
} | null;
};
export type HomeQuery = {
response: HomeQuery$data;
variables: HomeQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "count"
},
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "cursor"
}
],
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v2 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "username",
"storageKey": null
},
v3 = [
{
"kind": "Variable",
"name": "after",
"variableName": "cursor"
},
{
"kind": "Variable",
"name": "first",
"variableName": "count"
}
],
v4 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
v5 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
v6 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
},
v7 = {
"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
};
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "HomeQuery",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "BrowserSession",
"kind": "LinkedField",
"name": "currentBrowserSession",
"plural": false,
"selections": [
(v1/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "User",
"kind": "LinkedField",
"name": "user",
"plural": false,
"selections": [
(v1/*: any*/),
(v2/*: any*/),
{
"args": null,
"kind": "FragmentSpread",
"name": "CompatSsoLoginList_user"
},
{
"args": null,
"kind": "FragmentSpread",
"name": "BrowserSessionList_user"
},
{
"args": null,
"kind": "FragmentSpread",
"name": "OAuth2SessionList_user"
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"type": "RootQuery",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "HomeQuery",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "BrowserSession",
"kind": "LinkedField",
"name": "currentBrowserSession",
"plural": false,
"selections": [
(v1/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "User",
"kind": "LinkedField",
"name": "user",
"plural": false,
"selections": [
(v1/*: any*/),
(v2/*: any*/),
{
"alias": null,
"args": (v3/*: any*/),
"concreteType": "CompatSsoLoginConnection",
"kind": "LinkedField",
"name": "compatSsoLogins",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "CompatSsoLoginEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "CompatSsoLogin",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v1/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "redirectUri",
"storageKey": null
},
(v4/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "CompatSession",
"kind": "LinkedField",
"name": "session",
"plural": false,
"selections": [
(v1/*: any*/),
(v4/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "deviceId",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "finishedAt",
"storageKey": null
}
],
"storageKey": null
},
(v5/*: any*/)
],
"storageKey": null
},
(v6/*: any*/)
],
"storageKey": null
},
(v7/*: any*/)
],
"storageKey": null
},
{
"alias": null,
"args": (v3/*: any*/),
"filters": null,
"handle": "connection",
"key": "CompatSsoLoginList_user_compatSsoLogins",
"kind": "LinkedHandle",
"name": "compatSsoLogins"
},
{
"alias": null,
"args": (v3/*: any*/),
"concreteType": "BrowserSessionConnection",
"kind": "LinkedField",
"name": "browserSessions",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "BrowserSessionEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
(v6/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "BrowserSession",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v1/*: any*/),
(v4/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Authentication",
"kind": "LinkedField",
"name": "lastAuthentication",
"plural": false,
"selections": [
(v1/*: any*/),
(v4/*: any*/)
],
"storageKey": null
},
(v5/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
},
(v7/*: any*/)
],
"storageKey": null
},
{
"alias": null,
"args": (v3/*: any*/),
"filters": null,
"handle": "connection",
"key": "BrowserSessionList_user_browserSessions",
"kind": "LinkedHandle",
"name": "browserSessions"
},
{
"alias": null,
"args": (v3/*: any*/),
"concreteType": "Oauth2SessionConnection",
"kind": "LinkedField",
"name": "oauth2Sessions",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Oauth2SessionEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
(v6/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Oauth2Session",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v1/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "scope",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "Oauth2Client",
"kind": "LinkedField",
"name": "client",
"plural": false,
"selections": [
(v1/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "clientId",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "clientName",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "clientUri",
"storageKey": null
}
],
"storageKey": null
},
(v5/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
},
(v7/*: any*/)
],
"storageKey": null
},
{
"alias": null,
"args": (v3/*: any*/),
"filters": null,
"handle": "connection",
"key": "OAuth2SessionList_user_oauth2Sessions",
"kind": "LinkedHandle",
"name": "oauth2Sessions"
}
],
"storageKey": null
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "c2c8afebb1acbce26f8d164d911c5833",
"id": null,
"metadata": {},
"name": "HomeQuery",
"operationKind": "query",
"text": "query HomeQuery(\n $count: Int!\n $cursor: String\n) {\n currentBrowserSession {\n id\n user {\n id\n username\n ...CompatSsoLoginList_user\n ...BrowserSessionList_user\n ...OAuth2SessionList_user\n }\n }\n}\n\nfragment BrowserSessionList_user on User {\n browserSessions(first: $count, after: $cursor) {\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n __typename\n }\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n id\n}\n\nfragment BrowserSession_session on BrowserSession {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n}\n\nfragment CompatSsoLoginList_user on User {\n compatSsoLogins(first: $count, after: $cursor) {\n edges {\n node {\n id\n ...CompatSsoLogin_login\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n id\n}\n\nfragment CompatSsoLogin_login on CompatSsoLogin {\n id\n redirectUri\n createdAt\n session {\n id\n createdAt\n deviceId\n finishedAt\n }\n}\n\nfragment OAuth2SessionList_user on User {\n oauth2Sessions(first: $count, after: $cursor) {\n edges {\n cursor\n node {\n id\n ...OAuth2Session_session\n __typename\n }\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n id\n}\n\nfragment OAuth2Session_session on Oauth2Session {\n id\n scope\n client {\n id\n clientId\n clientName\n clientUri\n }\n}\n"
}
};
})();
(node as any).hash = "f26e9c3756edccc8584de5f359fd96bd";
export default node;

View File

@ -1,137 +0,0 @@
/**
* @generated SignedSource<<fdd196c5ae97e597805f98077fefcd7d>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest, Query } from 'relay-runtime';
export type OAuth2ClientQuery$variables = {
id: string;
};
export type OAuth2ClientQuery$data = {
readonly oauth2Client: {
readonly clientId: string;
readonly clientName: string | null;
readonly clientUri: any | null;
readonly id: string;
readonly policyUri: any | null;
readonly redirectUris: ReadonlyArray<any>;
readonly tosUri: any | null;
} | null;
};
export type OAuth2ClientQuery = {
response: OAuth2ClientQuery$data;
variables: OAuth2ClientQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "id"
}
],
v1 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "id",
"variableName": "id"
}
],
"concreteType": "Oauth2Client",
"kind": "LinkedField",
"name": "oauth2Client",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "clientId",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "clientName",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "clientUri",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "tosUri",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "policyUri",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "redirectUris",
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "OAuth2ClientQuery",
"selections": (v1/*: any*/),
"type": "RootQuery",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "OAuth2ClientQuery",
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "49e24d5c368a8c2c148643b971fe179c",
"id": null,
"metadata": {},
"name": "OAuth2ClientQuery",
"operationKind": "query",
"text": "query OAuth2ClientQuery(\n $id: ID!\n) {\n oauth2Client(id: $id) {\n id\n clientId\n clientName\n clientUri\n tosUri\n policyUri\n redirectUris\n }\n}\n"
}
};
})();
(node as any).hash = "31b4804eb435b5822480ff57775d13a4";
export default node;

View File

@ -16,7 +16,7 @@
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import eslint from "vite-plugin-eslint"; import eslint from "vite-plugin-eslint";
import react from "@vitejs/plugin-react"; import react from "@vitejs/plugin-react";
import relay from "vite-plugin-relay-lite"; import codegen from "vite-plugin-graphql-codegen";
export default defineConfig({ export default defineConfig({
base: "/app/", base: "/app/",
@ -26,12 +26,12 @@ export default defineConfig({
sourcemap: true, sourcemap: true,
}, },
plugins: [ plugins: [
codegen(),
react(), react(),
eslint({ eslint({
// Explicitly set the config file, else storybook gets confused // Explicitly set the config file, else storybook gets confused
overrideConfigFile: "./.eslintrc.cjs", overrideConfigFile: "./.eslintrc.cjs",
}), }),
relay(),
], ],
server: { server: {
proxy: { proxy: {
@ -44,7 +44,7 @@ export default defineConfig({
coverage: { coverage: {
provider: "c8", provider: "c8",
src: ["./src/"], src: ["./src/"],
exclude: ["**/__generated__/**", "**/*.d.ts", "**/*.stories.*"], exclude: ["**/gql/**", "**/*.d.ts", "**/*.stories.*"],
all: true, all: true,
}, },
}, },