You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-11-24 23:01:05 +03:00
WIP: start replacing jotai-urql with urql
This commit is contained in:
15
frontend/package-lock.json
generated
15
frontend/package-lock.json
generated
@@ -34,7 +34,8 @@
|
|||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-i18next": "^14.0.5",
|
"react-i18next": "^14.0.5",
|
||||||
"ua-parser-js": "^1.0.37"
|
"ua-parser-js": "^1.0.37",
|
||||||
|
"urql": "^4.0.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@graphql-codegen/cli": "^5.0.2",
|
"@graphql-codegen/cli": "^5.0.2",
|
||||||
@@ -23549,6 +23550,18 @@
|
|||||||
"integrity": "sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==",
|
"integrity": "sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/urql": {
|
||||||
|
"version": "4.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/urql/-/urql-4.0.6.tgz",
|
||||||
|
"integrity": "sha512-meXJ2puOd64uCGKh7Fse2R7gPa8+ZpBOoA62jN7CPXXUt7SVZSdeXWSpB3HvlfzLUkEqsWbvshwrgeWRYNNGaQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@urql/core": "^4.2.0",
|
||||||
|
"wonka": "^6.3.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">= 16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/use-callback-ref": {
|
"node_modules/use-callback-ref": {
|
||||||
"version": "1.3.1",
|
"version": "1.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.1.tgz",
|
||||||
|
|||||||
@@ -42,7 +42,8 @@
|
|||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-i18next": "^14.0.5",
|
"react-i18next": "^14.0.5",
|
||||||
"ua-parser-js": "^1.0.37"
|
"ua-parser-js": "^1.0.37",
|
||||||
|
"urql": "^4.0.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@graphql-codegen/cli": "^5.0.2",
|
"@graphql-codegen/cli": "^5.0.2",
|
||||||
|
|||||||
@@ -15,8 +15,9 @@
|
|||||||
import { AnyVariables, CombinedError, OperationContext } from "@urql/core";
|
import { AnyVariables, CombinedError, OperationContext } from "@urql/core";
|
||||||
import { atom, WritableAtom } from "jotai";
|
import { atom, WritableAtom } from "jotai";
|
||||||
import { useHydrateAtoms } from "jotai/utils";
|
import { useHydrateAtoms } from "jotai/utils";
|
||||||
import { AtomWithQuery, atomWithQuery, clientAtom } from "jotai-urql";
|
import { AtomWithQuery, clientAtom } from "jotai-urql";
|
||||||
import type { ReactElement } from "react";
|
import type { ReactElement } from "react";
|
||||||
|
import { useQuery } from "urql";
|
||||||
|
|
||||||
import { graphql } from "./gql";
|
import { graphql } from "./gql";
|
||||||
import { client } from "./graphql";
|
import { client } from "./graphql";
|
||||||
@@ -73,51 +74,15 @@ const CURRENT_VIEWER_QUERY = graphql(/* GraphQL */ `
|
|||||||
... on User {
|
... on User {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
... on Anonymous {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const currentViewerAtom = atomWithQuery({ query: CURRENT_VIEWER_QUERY });
|
export const useCurrentUserId = (): string | null => {
|
||||||
|
const [result] = useQuery({ query: CURRENT_VIEWER_QUERY });
|
||||||
export const currentUserIdAtom: GqlAtom<string | null> = mapQueryAtom(
|
if (result.error) throw result.error;
|
||||||
currentViewerAtom,
|
if (!result.data) throw new Error(); // Suspense mode is enabled
|
||||||
(data) => {
|
return result.data.viewer.__typename === "User"
|
||||||
if (data.viewer.__typename === "User") {
|
? result.data.viewer.id
|
||||||
return data.viewer.id;
|
: null;
|
||||||
}
|
};
|
||||||
return null;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const CURRENT_VIEWER_SESSION_QUERY = graphql(/* GraphQL */ `
|
|
||||||
query CurrentViewerSessionQuery {
|
|
||||||
viewerSession {
|
|
||||||
__typename
|
|
||||||
... on BrowserSession {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
|
|
||||||
... on Anonymous {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
const currentViewerSessionAtom = atomWithQuery({
|
|
||||||
query: CURRENT_VIEWER_SESSION_QUERY,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const currentBrowserSessionIdAtom: GqlAtom<string | null> = mapQueryAtom(
|
|
||||||
currentViewerSessionAtom,
|
|
||||||
(data) => {
|
|
||||||
if (data.viewerSession.__typename === "BrowserSession") {
|
|
||||||
return data.viewerSession.id;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import { atomFamily } from "jotai/utils";
|
|||||||
import { atomWithMutation } from "jotai-urql";
|
import { atomWithMutation } from "jotai-urql";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
|
|
||||||
import { currentBrowserSessionIdAtom, currentUserIdAtom } from "../atoms";
|
|
||||||
import { FragmentType, graphql, useFragment } from "../gql";
|
import { FragmentType, graphql, useFragment } from "../gql";
|
||||||
import {
|
import {
|
||||||
parseUserAgent,
|
parseUserAgent,
|
||||||
@@ -74,21 +73,12 @@ export const useEndBrowserSession = (
|
|||||||
): (() => Promise<void>) => {
|
): (() => Promise<void>) => {
|
||||||
const endSession = useSetAtom(endBrowserSessionFamily(sessionId));
|
const endSession = useSetAtom(endBrowserSessionFamily(sessionId));
|
||||||
|
|
||||||
// Pull those atoms to reset them when the current session is ended
|
|
||||||
const currentUserId = useSetAtom(currentUserIdAtom);
|
|
||||||
const currentBrowserSessionId = useSetAtom(currentBrowserSessionIdAtom);
|
|
||||||
|
|
||||||
const onSessionEnd = useCallback(async (): Promise<void> => {
|
const onSessionEnd = useCallback(async (): Promise<void> => {
|
||||||
await endSession();
|
await endSession();
|
||||||
if (isCurrent) {
|
if (isCurrent) {
|
||||||
currentBrowserSessionId({
|
window.location.reload();
|
||||||
requestPolicy: "network-only",
|
|
||||||
});
|
|
||||||
currentUserId({
|
|
||||||
requestPolicy: "network-only",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, [isCurrent, endSession, currentBrowserSessionId, currentUserId]);
|
}, [isCurrent, endSession]);
|
||||||
|
|
||||||
return onSessionEnd;
|
return onSessionEnd;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,28 +15,17 @@
|
|||||||
// @vitest-environment happy-dom
|
// @vitest-environment happy-dom
|
||||||
|
|
||||||
import { render } from "@testing-library/react";
|
import { render } from "@testing-library/react";
|
||||||
import { describe, expect, it, vi, afterAll, beforeEach } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
import { currentUserIdAtom, GqlResult } from "../../atoms";
|
|
||||||
import { WithLocation } from "../../test-utils/WithLocation";
|
import { WithLocation } from "../../test-utils/WithLocation";
|
||||||
|
|
||||||
import Layout from "./Layout";
|
import Layout from "./Layout";
|
||||||
|
|
||||||
describe("<Layout />", () => {
|
describe("<Layout />", () => {
|
||||||
beforeEach(() => {
|
|
||||||
vi.spyOn(currentUserIdAtom, "read").mockResolvedValue(
|
|
||||||
"abc123" as unknown as GqlResult<string | null>,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
vi.restoreAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders app navigation correctly", async () => {
|
it("renders app navigation correctly", async () => {
|
||||||
const component = render(
|
const component = render(
|
||||||
<WithLocation path="/">
|
<WithLocation path="/">
|
||||||
<Layout />
|
<Layout userId="abc123" />
|
||||||
</WithLocation>,
|
</WithLocation>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -15,37 +15,25 @@
|
|||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { currentUserIdAtom } from "../../atoms";
|
|
||||||
import { isErr, unwrapErr, unwrapOk } from "../../result";
|
|
||||||
import { appConfigAtom, routeAtom } from "../../routing";
|
import { appConfigAtom, routeAtom } from "../../routing";
|
||||||
import Footer from "../Footer";
|
import Footer from "../Footer";
|
||||||
import GraphQLError from "../GraphQLError";
|
|
||||||
import NavBar from "../NavBar";
|
import NavBar from "../NavBar";
|
||||||
import NavItem from "../NavItem";
|
import NavItem from "../NavItem";
|
||||||
import NotLoggedIn from "../NotLoggedIn";
|
|
||||||
import UserGreeting from "../UserGreeting";
|
import UserGreeting from "../UserGreeting";
|
||||||
|
|
||||||
import styles from "./Layout.module.css";
|
import styles from "./Layout.module.css";
|
||||||
|
|
||||||
const Layout: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
|
const Layout: React.FC<{
|
||||||
|
userId: string;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}> = ({ userId, children }) => {
|
||||||
const route = useAtomValue(routeAtom);
|
const route = useAtomValue(routeAtom);
|
||||||
const appConfig = useAtomValue(appConfigAtom);
|
const appConfig = useAtomValue(appConfigAtom);
|
||||||
const result = useAtomValue(currentUserIdAtom);
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
if (isErr(result)) return <GraphQLError error={unwrapErr(result)} />;
|
|
||||||
|
|
||||||
// Hide the nav bar & user greeting on the verify-email page
|
// Hide the nav bar & user greeting on the verify-email page
|
||||||
const shouldHideNavBar = route.type === "verify-email";
|
const shouldHideNavBar = route.type === "verify-email";
|
||||||
|
|
||||||
const userId = unwrapOk(result);
|
|
||||||
if (userId === null)
|
|
||||||
return (
|
|
||||||
<div className={styles.container}>
|
|
||||||
<NotLoggedIn />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.layoutContainer}>
|
<div className={styles.layoutContainer}>
|
||||||
{shouldHideNavBar ? null : (
|
{shouldHideNavBar ? null : (
|
||||||
|
|||||||
@@ -13,10 +13,8 @@ import { TypedDocumentNode as DocumentNode } from "@graphql-typed-document-node/
|
|||||||
* Therefore it is highly recommended to use the babel or swc plugin for production.
|
* Therefore it is highly recommended to use the babel or swc plugin for production.
|
||||||
*/
|
*/
|
||||||
const documents = {
|
const documents = {
|
||||||
"\n query CurrentViewerQuery {\n viewer {\n __typename\n ... on User {\n id\n }\n\n ... on Anonymous {\n id\n }\n }\n }\n":
|
"\n query CurrentViewerQuery {\n viewer {\n __typename\n ... on User {\n id\n }\n }\n }\n":
|
||||||
types.CurrentViewerQueryDocument,
|
types.CurrentViewerQueryDocument,
|
||||||
"\n query CurrentViewerSessionQuery {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n }\n\n ... on Anonymous {\n id\n }\n }\n }\n":
|
|
||||||
types.CurrentViewerSessionQueryDocument,
|
|
||||||
"\n fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n finishedAt\n userAgent\n lastActiveIp\n lastActiveAt\n lastAuthentication {\n id\n createdAt\n }\n }\n":
|
"\n fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n finishedAt\n userAgent\n lastActiveIp\n lastActiveAt\n lastAuthentication {\n id\n createdAt\n }\n }\n":
|
||||||
types.BrowserSession_SessionFragmentDoc,
|
types.BrowserSession_SessionFragmentDoc,
|
||||||
"\n mutation EndBrowserSession($id: ID!) {\n endBrowserSession(input: { browserSessionId: $id }) {\n status\n browserSession {\n id\n ...BrowserSession_session\n }\n }\n }\n":
|
"\n mutation EndBrowserSession($id: ID!) {\n endBrowserSession(input: { browserSessionId: $id }) {\n status\n browserSession {\n id\n ...BrowserSession_session\n }\n }\n }\n":
|
||||||
@@ -79,6 +77,8 @@ const documents = {
|
|||||||
types.SessionsOverviewQueryDocument,
|
types.SessionsOverviewQueryDocument,
|
||||||
"\n query VerifyEmailQuery($id: ID!) {\n userEmail(id: $id) {\n ...UserEmail_verifyEmail\n }\n }\n":
|
"\n query VerifyEmailQuery($id: ID!) {\n userEmail(id: $id) {\n ...UserEmail_verifyEmail\n }\n }\n":
|
||||||
types.VerifyEmailQueryDocument,
|
types.VerifyEmailQueryDocument,
|
||||||
|
"\n query CurrentViewerSessionQuery {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n }\n }\n }\n":
|
||||||
|
types.CurrentViewerSessionQueryDocument,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -99,14 +99,8 @@ 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.
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||||
*/
|
*/
|
||||||
export function graphql(
|
export function graphql(
|
||||||
source: "\n query CurrentViewerQuery {\n viewer {\n __typename\n ... on User {\n id\n }\n\n ... on Anonymous {\n id\n }\n }\n }\n",
|
source: "\n query CurrentViewerQuery {\n viewer {\n __typename\n ... on User {\n id\n }\n }\n }\n",
|
||||||
): (typeof documents)["\n query CurrentViewerQuery {\n viewer {\n __typename\n ... on User {\n id\n }\n\n ... on Anonymous {\n id\n }\n }\n }\n"];
|
): (typeof documents)["\n query CurrentViewerQuery {\n viewer {\n __typename\n ... on User {\n id\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 CurrentViewerSessionQuery {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n }\n\n ... on Anonymous {\n id\n }\n }\n }\n",
|
|
||||||
): (typeof documents)["\n query CurrentViewerSessionQuery {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n }\n\n ... on Anonymous {\n id\n }\n }\n }\n"];
|
|
||||||
/**
|
/**
|
||||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||||
*/
|
*/
|
||||||
@@ -293,6 +287,12 @@ export function graphql(
|
|||||||
export function graphql(
|
export function graphql(
|
||||||
source: "\n query VerifyEmailQuery($id: ID!) {\n userEmail(id: $id) {\n ...UserEmail_verifyEmail\n }\n }\n",
|
source: "\n query VerifyEmailQuery($id: ID!) {\n userEmail(id: $id) {\n ...UserEmail_verifyEmail\n }\n }\n",
|
||||||
): (typeof documents)["\n query VerifyEmailQuery($id: ID!) {\n userEmail(id: $id) {\n ...UserEmail_verifyEmail\n }\n }\n"];
|
): (typeof documents)["\n query VerifyEmailQuery($id: ID!) {\n userEmail(id: $id) {\n ...UserEmail_verifyEmail\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 CurrentViewerSessionQuery {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n }\n }\n }\n",
|
||||||
|
): (typeof documents)["\n query CurrentViewerSessionQuery {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n }\n }\n }\n"];
|
||||||
|
|
||||||
export function graphql(source: string) {
|
export function graphql(source: string) {
|
||||||
return (documents as any)[source] ?? {};
|
return (documents as any)[source] ?? {};
|
||||||
|
|||||||
@@ -1111,21 +1111,7 @@ export type CurrentViewerQueryQueryVariables = Exact<{ [key: string]: never }>;
|
|||||||
|
|
||||||
export type CurrentViewerQueryQuery = {
|
export type CurrentViewerQueryQuery = {
|
||||||
__typename?: "Query";
|
__typename?: "Query";
|
||||||
viewer:
|
viewer: { __typename: "Anonymous" } | { __typename: "User"; id: string };
|
||||||
| { __typename: "Anonymous"; id: string }
|
|
||||||
| { __typename: "User"; id: string };
|
|
||||||
};
|
|
||||||
|
|
||||||
export type CurrentViewerSessionQueryQueryVariables = Exact<{
|
|
||||||
[key: string]: never;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
export type CurrentViewerSessionQueryQuery = {
|
|
||||||
__typename?: "Query";
|
|
||||||
viewerSession:
|
|
||||||
| { __typename: "Anonymous"; id: string }
|
|
||||||
| { __typename: "BrowserSession"; id: string }
|
|
||||||
| { __typename: "Oauth2Session" };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BrowserSession_SessionFragment = {
|
export type BrowserSession_SessionFragment = {
|
||||||
@@ -1680,6 +1666,18 @@ export type VerifyEmailQueryQuery = {
|
|||||||
| null;
|
| null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type CurrentViewerSessionQueryQueryVariables = Exact<{
|
||||||
|
[key: string]: never;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type CurrentViewerSessionQueryQuery = {
|
||||||
|
__typename?: "Query";
|
||||||
|
viewerSession:
|
||||||
|
| { __typename: "Anonymous" }
|
||||||
|
| { __typename: "BrowserSession"; id: string }
|
||||||
|
| { __typename: "Oauth2Session" };
|
||||||
|
};
|
||||||
|
|
||||||
export const BrowserSession_SessionFragmentDoc = {
|
export const BrowserSession_SessionFragmentDoc = {
|
||||||
kind: "Document",
|
kind: "Document",
|
||||||
definitions: [
|
definitions: [
|
||||||
@@ -2090,19 +2088,6 @@ export const CurrentViewerQueryDocument = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
kind: "InlineFragment",
|
|
||||||
typeCondition: {
|
|
||||||
kind: "NamedType",
|
|
||||||
name: { kind: "Name", value: "Anonymous" },
|
|
||||||
},
|
|
||||||
selectionSet: {
|
|
||||||
kind: "SelectionSet",
|
|
||||||
selections: [
|
|
||||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -2114,60 +2099,6 @@ export const CurrentViewerQueryDocument = {
|
|||||||
CurrentViewerQueryQuery,
|
CurrentViewerQueryQuery,
|
||||||
CurrentViewerQueryQueryVariables
|
CurrentViewerQueryQueryVariables
|
||||||
>;
|
>;
|
||||||
export const CurrentViewerSessionQueryDocument = {
|
|
||||||
kind: "Document",
|
|
||||||
definitions: [
|
|
||||||
{
|
|
||||||
kind: "OperationDefinition",
|
|
||||||
operation: "query",
|
|
||||||
name: { kind: "Name", value: "CurrentViewerSessionQuery" },
|
|
||||||
selectionSet: {
|
|
||||||
kind: "SelectionSet",
|
|
||||||
selections: [
|
|
||||||
{
|
|
||||||
kind: "Field",
|
|
||||||
name: { kind: "Name", value: "viewerSession" },
|
|
||||||
selectionSet: {
|
|
||||||
kind: "SelectionSet",
|
|
||||||
selections: [
|
|
||||||
{ kind: "Field", name: { kind: "Name", value: "__typename" } },
|
|
||||||
{
|
|
||||||
kind: "InlineFragment",
|
|
||||||
typeCondition: {
|
|
||||||
kind: "NamedType",
|
|
||||||
name: { kind: "Name", value: "BrowserSession" },
|
|
||||||
},
|
|
||||||
selectionSet: {
|
|
||||||
kind: "SelectionSet",
|
|
||||||
selections: [
|
|
||||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
kind: "InlineFragment",
|
|
||||||
typeCondition: {
|
|
||||||
kind: "NamedType",
|
|
||||||
name: { kind: "Name", value: "Anonymous" },
|
|
||||||
},
|
|
||||||
selectionSet: {
|
|
||||||
kind: "SelectionSet",
|
|
||||||
selections: [
|
|
||||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
} as unknown as DocumentNode<
|
|
||||||
CurrentViewerSessionQueryQuery,
|
|
||||||
CurrentViewerSessionQueryQueryVariables
|
|
||||||
>;
|
|
||||||
export const EndBrowserSessionDocument = {
|
export const EndBrowserSessionDocument = {
|
||||||
kind: "Document",
|
kind: "Document",
|
||||||
definitions: [
|
definitions: [
|
||||||
@@ -4435,3 +4366,44 @@ export const VerifyEmailQueryDocument = {
|
|||||||
VerifyEmailQueryQuery,
|
VerifyEmailQueryQuery,
|
||||||
VerifyEmailQueryQueryVariables
|
VerifyEmailQueryQueryVariables
|
||||||
>;
|
>;
|
||||||
|
export const CurrentViewerSessionQueryDocument = {
|
||||||
|
kind: "Document",
|
||||||
|
definitions: [
|
||||||
|
{
|
||||||
|
kind: "OperationDefinition",
|
||||||
|
operation: "query",
|
||||||
|
name: { kind: "Name", value: "CurrentViewerSessionQuery" },
|
||||||
|
selectionSet: {
|
||||||
|
kind: "SelectionSet",
|
||||||
|
selections: [
|
||||||
|
{
|
||||||
|
kind: "Field",
|
||||||
|
name: { kind: "Name", value: "viewerSession" },
|
||||||
|
selectionSet: {
|
||||||
|
kind: "SelectionSet",
|
||||||
|
selections: [
|
||||||
|
{ kind: "Field", name: { kind: "Name", value: "__typename" } },
|
||||||
|
{
|
||||||
|
kind: "InlineFragment",
|
||||||
|
typeCondition: {
|
||||||
|
kind: "NamedType",
|
||||||
|
name: { kind: "Name", value: "BrowserSession" },
|
||||||
|
},
|
||||||
|
selectionSet: {
|
||||||
|
kind: "SelectionSet",
|
||||||
|
selections: [
|
||||||
|
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as unknown as DocumentNode<
|
||||||
|
CurrentViewerSessionQueryQuery,
|
||||||
|
CurrentViewerSessionQueryQueryVariables
|
||||||
|
>;
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ const exchanges = [
|
|||||||
|
|
||||||
export const client = createClient({
|
export const client = createClient({
|
||||||
url: appConfig.graphqlEndpoint,
|
url: appConfig.graphqlEndpoint,
|
||||||
|
suspense: true,
|
||||||
// Add the devtools exchange in development
|
// Add the devtools exchange in development
|
||||||
exchanges: import.meta.env.DEV ? [devtoolsExchange, ...exchanges] : exchanges,
|
exchanges: import.meta.env.DEV ? [devtoolsExchange, ...exchanges] : exchanges,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -18,32 +18,49 @@ import { DevTools } from "jotai-devtools";
|
|||||||
import { Suspense, StrictMode } from "react";
|
import { Suspense, StrictMode } from "react";
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
import { I18nextProvider } from "react-i18next";
|
import { I18nextProvider } from "react-i18next";
|
||||||
|
import { Provider as UrqlProvider } from "urql";
|
||||||
|
|
||||||
import { HydrateAtoms } from "./atoms";
|
import { HydrateAtoms, useCurrentUserId } from "./atoms";
|
||||||
|
import ErrorBoundary from "./components/ErrorBoundary";
|
||||||
import Layout from "./components/Layout";
|
import Layout from "./components/Layout";
|
||||||
import LoadingScreen from "./components/LoadingScreen";
|
import LoadingScreen from "./components/LoadingScreen";
|
||||||
import LoadingSpinner from "./components/LoadingSpinner";
|
import LoadingSpinner from "./components/LoadingSpinner";
|
||||||
|
import NotLoggedIn from "./components/NotLoggedIn";
|
||||||
|
import { client } from "./graphql";
|
||||||
import i18n from "./i18n";
|
import i18n from "./i18n";
|
||||||
import { Router } from "./routing";
|
import { Router } from "./routing";
|
||||||
import "./main.css";
|
import "./main.css";
|
||||||
|
|
||||||
|
const App: React.FC = () => {
|
||||||
|
const userId = useCurrentUserId();
|
||||||
|
if (userId === null) return <NotLoggedIn />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout userId={userId}>
|
||||||
|
<Suspense fallback={<LoadingSpinner />}>
|
||||||
|
<Router userId={userId} />
|
||||||
|
</Suspense>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
createRoot(document.getElementById("root") as HTMLElement).render(
|
createRoot(document.getElementById("root") as HTMLElement).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
|
<ErrorBoundary>
|
||||||
|
<UrqlProvider value={client}>
|
||||||
<Provider>
|
<Provider>
|
||||||
{import.meta.env.DEV && <DevTools />}
|
{import.meta.env.DEV && <DevTools />}
|
||||||
<HydrateAtoms>
|
<HydrateAtoms>
|
||||||
<Suspense fallback={<LoadingScreen />}>
|
<Suspense fallback={<LoadingScreen />}>
|
||||||
<I18nextProvider i18n={i18n}>
|
<I18nextProvider i18n={i18n}>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Layout>
|
<App />
|
||||||
<Suspense fallback={<LoadingSpinner />}>
|
|
||||||
<Router />
|
|
||||||
</Suspense>
|
|
||||||
</Layout>
|
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
</I18nextProvider>
|
</I18nextProvider>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</HydrateAtoms>
|
</HydrateAtoms>
|
||||||
</Provider>
|
</Provider>
|
||||||
|
</UrqlProvider>
|
||||||
|
</ErrorBoundary>
|
||||||
</StrictMode>,
|
</StrictMode>,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,17 +12,13 @@
|
|||||||
// 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 { useAtomValue } from "jotai";
|
import { useQuery } from "urql";
|
||||||
import { atomFamily } from "jotai/utils";
|
|
||||||
import { atomWithQuery } from "jotai-urql";
|
|
||||||
|
|
||||||
import { mapQueryAtom } from "../atoms";
|
|
||||||
import ErrorBoundary from "../components/ErrorBoundary";
|
import ErrorBoundary from "../components/ErrorBoundary";
|
||||||
import GraphQLError from "../components/GraphQLError";
|
import GraphQLError from "../components/GraphQLError";
|
||||||
import NotFound from "../components/NotFound";
|
import NotFound from "../components/NotFound";
|
||||||
import BrowserSessionDetail from "../components/SessionDetail/BrowserSessionDetail";
|
import BrowserSessionDetail from "../components/SessionDetail/BrowserSessionDetail";
|
||||||
import { graphql } from "../gql";
|
import { graphql } from "../gql";
|
||||||
import { isErr, unwrapErr, unwrapOk } from "../result";
|
|
||||||
|
|
||||||
const QUERY = graphql(/* GraphQL */ `
|
const QUERY = graphql(/* GraphQL */ `
|
||||||
query BrowserSessionQuery($id: ID!) {
|
query BrowserSessionQuery($id: ID!) {
|
||||||
@@ -33,25 +29,15 @@ const QUERY = graphql(/* GraphQL */ `
|
|||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const browserSessionFamily = atomFamily((id: string) => {
|
|
||||||
const browserSessionQueryAtom = atomWithQuery({
|
|
||||||
query: QUERY,
|
|
||||||
getVariables: () => ({ id }),
|
|
||||||
});
|
|
||||||
|
|
||||||
const browserSessionAtom = mapQueryAtom(
|
|
||||||
browserSessionQueryAtom,
|
|
||||||
(data) => data?.browserSession,
|
|
||||||
);
|
|
||||||
|
|
||||||
return browserSessionAtom;
|
|
||||||
});
|
|
||||||
|
|
||||||
const BrowserSession: React.FC<{ id: string }> = ({ id }) => {
|
const BrowserSession: React.FC<{ id: string }> = ({ id }) => {
|
||||||
const result = useAtomValue(browserSessionFamily(id));
|
const [result] = useQuery({
|
||||||
if (isErr(result)) return <GraphQLError error={unwrapErr(result)} />;
|
query: QUERY,
|
||||||
|
variables: { id },
|
||||||
|
});
|
||||||
|
if (result.error) return <GraphQLError error={result.error} />;
|
||||||
|
if (!result.data) throw new Error(); // Suspense mode is enabled
|
||||||
|
|
||||||
const browserSession = unwrapOk(result);
|
const browserSession = result.data.browserSession;
|
||||||
if (!browserSession) return <NotFound />;
|
if (!browserSession) return <NotFound />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -12,27 +12,10 @@
|
|||||||
// 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 { useAtomValue } from "jotai";
|
|
||||||
|
|
||||||
import { currentUserIdAtom } from "../atoms";
|
|
||||||
import List from "../components/BrowserSessionList";
|
import List from "../components/BrowserSessionList";
|
||||||
import ErrorBoundary from "../components/ErrorBoundary";
|
|
||||||
import GraphQLError from "../components/GraphQLError";
|
|
||||||
import NotLoggedIn from "../components/NotLoggedIn";
|
|
||||||
import { isErr, unwrapErr, unwrapOk } from "../result";
|
|
||||||
|
|
||||||
const BrowserSessionList: React.FC = () => {
|
const BrowserSessionList: React.FC<{ userId: string }> = ({ userId }) => {
|
||||||
const result = useAtomValue(currentUserIdAtom);
|
return <List userId={userId} />;
|
||||||
if (isErr(result)) return <GraphQLError error={unwrapErr(result)} />;
|
|
||||||
|
|
||||||
const userId = unwrapOk(result);
|
|
||||||
if (userId === null) return <NotLoggedIn />;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ErrorBoundary>
|
|
||||||
<List userId={userId} />
|
|
||||||
</ErrorBoundary>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default BrowserSessionList;
|
export default BrowserSessionList;
|
||||||
|
|||||||
@@ -12,17 +12,13 @@
|
|||||||
// 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 { useAtomValue } from "jotai";
|
import { useQuery } from "urql";
|
||||||
import { atomFamily } from "jotai/utils";
|
|
||||||
import { atomWithQuery } from "jotai-urql";
|
|
||||||
|
|
||||||
import { mapQueryAtom } from "../atoms";
|
|
||||||
import OAuth2ClientDetail from "../components/Client/OAuth2ClientDetail";
|
import OAuth2ClientDetail from "../components/Client/OAuth2ClientDetail";
|
||||||
import ErrorBoundary from "../components/ErrorBoundary";
|
import ErrorBoundary from "../components/ErrorBoundary";
|
||||||
import GraphQLError from "../components/GraphQLError";
|
import GraphQLError from "../components/GraphQLError";
|
||||||
import NotFound from "../components/NotFound";
|
import NotFound from "../components/NotFound";
|
||||||
import { graphql } from "../gql";
|
import { graphql } from "../gql";
|
||||||
import { isErr, unwrapErr, unwrapOk } from "../result";
|
|
||||||
|
|
||||||
const QUERY = graphql(/* GraphQL */ `
|
const QUERY = graphql(/* GraphQL */ `
|
||||||
query OAuth2ClientQuery($id: ID!) {
|
query OAuth2ClientQuery($id: ID!) {
|
||||||
@@ -32,25 +28,15 @@ const QUERY = graphql(/* GraphQL */ `
|
|||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const oauth2ClientFamily = atomFamily((id: string) => {
|
|
||||||
const oauth2ClientQueryAtom = atomWithQuery({
|
|
||||||
query: QUERY,
|
|
||||||
getVariables: () => ({ id }),
|
|
||||||
});
|
|
||||||
|
|
||||||
const oauth2ClientAtom = mapQueryAtom(
|
|
||||||
oauth2ClientQueryAtom,
|
|
||||||
(data) => data?.oauth2Client,
|
|
||||||
);
|
|
||||||
|
|
||||||
return oauth2ClientAtom;
|
|
||||||
});
|
|
||||||
|
|
||||||
const OAuth2Client: React.FC<{ id: string }> = ({ id }) => {
|
const OAuth2Client: React.FC<{ id: string }> = ({ id }) => {
|
||||||
const result = useAtomValue(oauth2ClientFamily(id));
|
const [result] = useQuery({
|
||||||
if (isErr(result)) return <GraphQLError error={unwrapErr(result)} />;
|
query: QUERY,
|
||||||
|
variables: { id },
|
||||||
|
});
|
||||||
|
if (result.error) return <GraphQLError error={result.error} />;
|
||||||
|
if (!result.data) throw new Error(); // Suspense mode is enabled
|
||||||
|
|
||||||
const oauth2Client = unwrapOk(result);
|
const oauth2Client = result.data.oauth2Client;
|
||||||
if (!oauth2Client) return <NotFound />;
|
if (!oauth2Client) return <NotFound />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -12,22 +12,10 @@
|
|||||||
// 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 { useAtomValue } from "jotai";
|
|
||||||
|
|
||||||
import { currentUserIdAtom } from "../atoms";
|
|
||||||
import ErrorBoundary from "../components/ErrorBoundary";
|
import ErrorBoundary from "../components/ErrorBoundary";
|
||||||
import GraphQLError from "../components/GraphQLError";
|
|
||||||
import NotLoggedIn from "../components/NotLoggedIn";
|
|
||||||
import UserProfile from "../components/UserProfile";
|
import UserProfile from "../components/UserProfile";
|
||||||
import { isErr, unwrapErr, unwrapOk } from "../result";
|
|
||||||
|
|
||||||
const Profile: React.FC = () => {
|
|
||||||
const result = useAtomValue(currentUserIdAtom);
|
|
||||||
if (isErr(result)) return <GraphQLError error={unwrapErr(result)} />;
|
|
||||||
|
|
||||||
const userId = unwrapOk(result);
|
|
||||||
if (userId === null) return <NotLoggedIn />;
|
|
||||||
|
|
||||||
|
const Profile: React.FC<{ userId: string }> = ({ userId }) => {
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<UserProfile userId={userId} />
|
<UserProfile userId={userId} />
|
||||||
|
|||||||
@@ -12,27 +12,13 @@
|
|||||||
// 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 { useAtomValue } from "jotai";
|
|
||||||
|
|
||||||
import { currentUserIdAtom } from "../atoms";
|
|
||||||
import ErrorBoundary from "../components/ErrorBoundary";
|
|
||||||
import GraphQLError from "../components/GraphQLError";
|
|
||||||
import NotLoggedIn from "../components/NotLoggedIn";
|
|
||||||
import UserSessionDetail from "../components/SessionDetail";
|
import UserSessionDetail from "../components/SessionDetail";
|
||||||
import { isErr, unwrapErr, unwrapOk } from "../result";
|
|
||||||
|
|
||||||
const SessionDetail: React.FC<{ deviceId: string }> = ({ deviceId }) => {
|
const SessionDetail: React.FC<{ userId: string; deviceId: string }> = ({
|
||||||
const result = useAtomValue(currentUserIdAtom);
|
userId,
|
||||||
if (isErr(result)) return <GraphQLError error={unwrapErr(result)} />;
|
deviceId,
|
||||||
|
}) => {
|
||||||
const userId = unwrapOk(result);
|
return <UserSessionDetail userId={userId} deviceId={deviceId} />;
|
||||||
if (userId === null) return <NotLoggedIn />;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ErrorBoundary>
|
|
||||||
<UserSessionDetail userId={userId} deviceId={deviceId} />
|
|
||||||
</ErrorBoundary>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SessionDetail;
|
export default SessionDetail;
|
||||||
|
|||||||
@@ -12,16 +12,13 @@
|
|||||||
// 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 { useAtomValue } from "jotai";
|
import { useQuery } from "urql";
|
||||||
import { atomWithQuery } from "jotai-urql";
|
|
||||||
|
|
||||||
import { mapQueryAtom } from "../atoms";
|
|
||||||
import ErrorBoundary from "../components/ErrorBoundary";
|
import ErrorBoundary from "../components/ErrorBoundary";
|
||||||
import GraphQLError from "../components/GraphQLError";
|
import GraphQLError from "../components/GraphQLError";
|
||||||
import NotLoggedIn from "../components/NotLoggedIn";
|
import NotLoggedIn from "../components/NotLoggedIn";
|
||||||
import UserSessionsOverview from "../components/UserSessionsOverview";
|
import UserSessionsOverview from "../components/UserSessionsOverview";
|
||||||
import { graphql } from "../gql";
|
import { graphql } from "../gql";
|
||||||
import { isErr, unwrapErr, unwrapOk } from "../result";
|
|
||||||
|
|
||||||
const QUERY = graphql(/* GraphQL */ `
|
const QUERY = graphql(/* GraphQL */ `
|
||||||
query SessionsOverviewQuery {
|
query SessionsOverviewQuery {
|
||||||
@@ -36,23 +33,13 @@ const QUERY = graphql(/* GraphQL */ `
|
|||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const sessionsOverviewQueryAtom = atomWithQuery({
|
|
||||||
query: QUERY,
|
|
||||||
});
|
|
||||||
|
|
||||||
const sessionsOverviewAtom = mapQueryAtom(sessionsOverviewQueryAtom, (data) => {
|
|
||||||
if (data.viewer?.__typename === "User") {
|
|
||||||
return data.viewer;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
const SessionsOverview: React.FC = () => {
|
const SessionsOverview: React.FC = () => {
|
||||||
const result = useAtomValue(sessionsOverviewAtom);
|
const [result] = useQuery({ query: QUERY });
|
||||||
if (isErr(result)) return <GraphQLError error={unwrapErr(result)} />;
|
if (result.error) return <GraphQLError error={result.error} />;
|
||||||
|
if (!result.data) throw new Error(); // Suspense mode is enabled
|
||||||
|
|
||||||
const data = unwrapOk(result);
|
const data =
|
||||||
|
result.data.viewer.__typename === "User" ? result.data.viewer : null;
|
||||||
if (data === null) return <NotLoggedIn />;
|
if (data === null) return <NotLoggedIn />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -12,17 +12,13 @@
|
|||||||
// 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 { useAtomValue } from "jotai";
|
|
||||||
import { atomFamily } from "jotai/utils";
|
|
||||||
import { atomWithQuery } from "jotai-urql";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useQuery } from "urql";
|
||||||
|
|
||||||
import { mapQueryAtom } from "../atoms";
|
|
||||||
import ErrorBoundary from "../components/ErrorBoundary";
|
import ErrorBoundary from "../components/ErrorBoundary";
|
||||||
import GraphQLError from "../components/GraphQLError";
|
import GraphQLError from "../components/GraphQLError";
|
||||||
import VerifyEmailComponent from "../components/VerifyEmail";
|
import VerifyEmailComponent from "../components/VerifyEmail";
|
||||||
import { graphql } from "../gql";
|
import { graphql } from "../gql";
|
||||||
import { isErr, unwrapErr, unwrapOk } from "../result";
|
|
||||||
|
|
||||||
const QUERY = graphql(/* GraphQL */ `
|
const QUERY = graphql(/* GraphQL */ `
|
||||||
query VerifyEmailQuery($id: ID!) {
|
query VerifyEmailQuery($id: ID!) {
|
||||||
@@ -32,27 +28,14 @@ const QUERY = graphql(/* GraphQL */ `
|
|||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const verifyEmailFamily = atomFamily((id: string) => {
|
|
||||||
const verifyEmailQueryAtom = atomWithQuery({
|
|
||||||
query: QUERY,
|
|
||||||
getVariables: () => ({ id }),
|
|
||||||
});
|
|
||||||
|
|
||||||
const verifyEmailAtom = mapQueryAtom(
|
|
||||||
verifyEmailQueryAtom,
|
|
||||||
(data) => data?.userEmail,
|
|
||||||
);
|
|
||||||
|
|
||||||
return verifyEmailAtom;
|
|
||||||
});
|
|
||||||
|
|
||||||
const VerifyEmail: React.FC<{ id: string }> = ({ id }) => {
|
const VerifyEmail: React.FC<{ id: string }> = ({ id }) => {
|
||||||
const result = useAtomValue(verifyEmailFamily(id));
|
const [result] = useQuery({ query: QUERY, variables: { id } });
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
if (isErr(result)) return <GraphQLError error={unwrapErr(result)} />;
|
if (result.error) return <GraphQLError error={result.error} />;
|
||||||
|
if (!result.data) throw new Error(); // Suspense mode is enabled
|
||||||
|
|
||||||
const email = unwrapOk(result);
|
const email = result.data.userEmail;
|
||||||
if (email == null) return <>{t("frontend.verify_email.unknown_email")}</>;
|
if (email == null) return <>{t("frontend.verify_email.unknown_email")}</>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ const unknownRoute = (route: never): never => {
|
|||||||
throw new Error(`Invalid route: ${JSON.stringify(route)}`);
|
throw new Error(`Invalid route: ${JSON.stringify(route)}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Router: React.FC = () => {
|
const Router: React.FC<{ userId: string }> = ({ userId }) => {
|
||||||
const [route, redirecting] = useRouteWithRedirect();
|
const [route, redirecting] = useRouteWithRedirect();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -65,13 +65,13 @@ const Router: React.FC = () => {
|
|||||||
|
|
||||||
switch (route.type) {
|
switch (route.type) {
|
||||||
case "profile":
|
case "profile":
|
||||||
return <Profile />;
|
return <Profile userId={userId} />;
|
||||||
case "sessions-overview":
|
case "sessions-overview":
|
||||||
return <SessionsOverview />;
|
return <SessionsOverview />;
|
||||||
case "session":
|
case "session":
|
||||||
return <SessionDetail deviceId={route.id} />;
|
return <SessionDetail userId={userId} deviceId={route.id} />;
|
||||||
case "browser-session-list":
|
case "browser-session-list":
|
||||||
return <BrowserSessionList />;
|
return <BrowserSessionList userId={userId} />;
|
||||||
case "client":
|
case "client":
|
||||||
return <OAuth2Client id={route.id} />;
|
return <OAuth2Client id={route.id} />;
|
||||||
case "browser-session":
|
case "browser-session":
|
||||||
|
|||||||
@@ -12,11 +12,20 @@
|
|||||||
// 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 { CombinedError } from "@urql/core";
|
import { useQuery } from "urql";
|
||||||
import { useAtomValue } from "jotai";
|
|
||||||
|
|
||||||
import { currentBrowserSessionIdAtom } from "../../atoms";
|
import { graphql } from "../../gql";
|
||||||
import { isOk, unwrapOk, unwrapErr, isErr } from "../../result";
|
|
||||||
|
const CURRENT_VIEWER_SESSION_QUERY = graphql(/* GraphQL */ `
|
||||||
|
query CurrentViewerSessionQuery {
|
||||||
|
viewerSession {
|
||||||
|
__typename
|
||||||
|
... on BrowserSession {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query the current browser session id
|
* Query the current browser session id
|
||||||
@@ -24,16 +33,10 @@ import { isOk, unwrapOk, unwrapErr, isErr } from "../../result";
|
|||||||
* throws error when error result
|
* throws error when error result
|
||||||
*/
|
*/
|
||||||
export const useCurrentBrowserSessionId = (): string | null => {
|
export const useCurrentBrowserSessionId = (): string | null => {
|
||||||
const currentSessionIdResult = useAtomValue(currentBrowserSessionIdAtom);
|
const [result] = useQuery({ query: CURRENT_VIEWER_SESSION_QUERY });
|
||||||
|
if (result.error) throw result.error;
|
||||||
if (isErr(currentSessionIdResult)) {
|
if (!result.data) throw new Error(); // Suspense mode is enabled
|
||||||
// eslint-disable-next-line no-throw-literal
|
return result.data.viewer.__typename === "User"
|
||||||
throw unwrapErr<CombinedError>(currentSessionIdResult) as Error;
|
? result.data.viewer.id
|
||||||
}
|
: null;
|
||||||
|
|
||||||
if (isOk<string | null, unknown>(currentSessionIdResult)) {
|
|
||||||
return unwrapOk<string | null>(currentSessionIdResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user