1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-11-20 12:02:22 +03:00

add app sessions list

This commit is contained in:
Kerry Archibald
2023-09-21 12:22:43 +12:00
committed by Quentin Gliech
parent e646319f5a
commit 1567dbb6d6
12 changed files with 121 additions and 571 deletions

View File

@@ -12,7 +12,7 @@
// 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 { H6, Text } from "@vector-im/compound-web"; import { H5 } from "@vector-im/compound-web";
import { atom, useAtomValue, useSetAtom } from "jotai"; import { atom, useAtomValue, useSetAtom } from "jotai";
import { atomFamily } from "jotai/utils"; import { atomFamily } from "jotai/utils";
import { atomWithQuery } from "jotai-urql"; import { atomWithQuery } from "jotai-urql";
@@ -28,6 +28,8 @@ import {
} from "../../pagination"; } from "../../pagination";
import { isOk, unwrap, unwrapOk } from "../../result"; import { isOk, unwrap, unwrapOk } from "../../result";
import BlockList from "../BlockList"; import BlockList from "../BlockList";
import CompatSession from "../CompatSession";
import OAuth2Session from "../OAuth2Session";
import PaginationControls from "../PaginationControls"; import PaginationControls from "../PaginationControls";
const QUERY = graphql(/* GraphQL */ ` const QUERY = graphql(/* GraphQL */ `
@@ -53,6 +55,7 @@ const QUERY = graphql(/* GraphQL */ `
edges { edges {
cursor cursor
node { node {
__typename
...CompatSession_session ...CompatSession_session
...OAuth2Session_session ...OAuth2Session_session
} }
@@ -72,7 +75,7 @@ const QUERY = graphql(/* GraphQL */ `
const filterAtom = atom<SessionState | null>(SessionState.Active); const filterAtom = atom<SessionState | null>(SessionState.Active);
const currentPaginationAtom = atomForCurrentPagination(); const currentPaginationAtom = atomForCurrentPagination();
const appSessionListFamily = atomFamily((userId: string) => { export const appSessionListFamily = atomFamily((userId: string) => {
const appSessionListQuery = atomWithQuery({ const appSessionListQuery = atomWithQuery({
query: QUERY, query: QUERY,
getVariables: (get) => ({ getVariables: (get) => ({
@@ -124,8 +127,23 @@ const AppSessionsList: React.FC<{ userId: string }> = ({ userId }) => {
return ( return (
<BlockList> <BlockList>
<H6>Apps</H6> <header>
<Text>{`${appSessions.totalCount} active sessions`}</Text> <H5>Apps</H5>
</header>
{appSessions.edges.map((session) => {
switch (session.node.__typename) {
case "Oauth2Session":
return (
<OAuth2Session key={session.cursor} session={session.node} />
);
case "CompatSession":
return (
<CompatSession key={session.cursor} session={session.node} />
);
default:
throw new Error("Unexpected session type.");
}
})}
<PaginationControls <PaginationControls
onPrev={prevPage ? (): void => paginate(prevPage) : null} onPrev={prevPage ? (): void => paginate(prevPage) : null}
onNext={nextPage ? (): void => paginate(nextPage) : null} onNext={nextPage ? (): void => paginate(nextPage) : null}

View File

@@ -1,27 +0,0 @@
/* 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.
*/
.session-list-block {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: flex-start;
gap: var(--cpd-space-1x);
}
.session-list-block-info {
display: flex;
flex-direction: column;
}

View File

@@ -1,135 +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 { Provider } from "jotai";
import { useHydrateAtoms } from "jotai/utils";
import { makeFragmentData } from "../../gql";
import { appConfigAtom, locationAtom } from "../../routing";
import { FRAGMENT as EMAIL_FRAGMENT } from "../UserEmail";
import UserSessionsOverview, { FRAGMENT } from "./UserSessionsOverview";
type Props = {
primaryEmail: string | null;
unverifiedEmails: number;
confirmedEmails: number;
oauth2Sessions: number;
browserSessions: number;
compatSessions: number;
};
const WithHomePage: React.FC<React.PropsWithChildren<{}>> = ({ children }) => {
useHydrateAtoms([
[appConfigAtom, { root: "/" }],
[locationAtom, { pathname: "/" }],
]);
return <>{children}</>;
};
const Template: React.FC<Props> = ({
primaryEmail: email,
unverifiedEmails,
confirmedEmails,
oauth2Sessions,
browserSessions,
compatSessions,
}) => {
let primaryEmail = null;
if (email) {
primaryEmail = {
id: "email:123",
...makeFragmentData(
{
id: "email:123",
email,
confirmedAt: new Date(),
},
EMAIL_FRAGMENT,
),
};
}
const data = makeFragmentData(
{
id: "user:123",
primaryEmail,
unverifiedEmails: {
totalCount: unverifiedEmails,
},
confirmedEmails: {
totalCount: confirmedEmails,
},
oauth2Sessions: {
totalCount: oauth2Sessions,
},
browserSessions: {
totalCount: browserSessions,
},
compatSessions: {
totalCount: compatSessions,
},
},
FRAGMENT,
);
return (
<Provider>
<WithHomePage>
<UserSessionsOverview user={data} />
</WithHomePage>
</Provider>
);
};
const meta = {
title: "Pages/User Sessions Overview",
component: Template,
tags: ["autodocs"],
} satisfies Meta<typeof Template>;
export default meta;
type Story = StoryObj<typeof Template>;
export const Basic: Story = {
args: {
primaryEmail: "hello@example.com",
unverifiedEmails: 0,
confirmedEmails: 5,
oauth2Sessions: 3,
compatSessions: 1,
browserSessions: 2,
},
};
export const Empty: Story = {
args: {
primaryEmail: "hello@example.com",
unverifiedEmails: 0,
confirmedEmails: 1,
oauth2Sessions: 0,
compatSessions: 0,
browserSessions: 0,
},
};
export const UnverifiedEmails: Story = {
args: {
primaryEmail: "hello@example.com",
unverifiedEmails: 1,
confirmedEmails: 1,
oauth2Sessions: 0,
compatSessions: 0,
browserSessions: 0,
},
};

View File

@@ -1,103 +0,0 @@
// 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 { create } from "react-test-renderer";
import { describe, expect, it } from "vitest";
import { makeFragmentData } from "../../gql";
import { FRAGMENT as EMAIL_FRAGMENT } from "../UserEmail";
import UserSessionsOverview, { FRAGMENT } from "./";
describe("UserSessionsOverview", () => {
it("render an simple <UserSessionsOverview />", () => {
const primaryEmail = makeFragmentData(
{
id: "email:123",
email: "hello@example.com",
confirmedAt: new Date(),
},
EMAIL_FRAGMENT,
);
const user = makeFragmentData(
{
id: "user:123",
primaryEmail: {
id: "email:123",
...primaryEmail,
},
compatSessions: {
totalCount: 0,
},
browserSessions: {
totalCount: 0,
},
oauth2Sessions: {
totalCount: 0,
},
unverifiedEmails: {
totalCount: 0,
},
confirmedEmails: {
totalCount: 1,
},
},
FRAGMENT,
);
const component = create(<UserSessionsOverview user={user} />);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
it("render a <UserSessionsOverview /> with sessions", () => {
const primaryEmail = makeFragmentData(
{
id: "email:123",
email: "hello@example.com",
confirmedAt: new Date(),
},
EMAIL_FRAGMENT,
);
const user = makeFragmentData(
{
id: "user:123",
primaryEmail: {
id: "email:123",
...primaryEmail,
},
compatSessions: {
totalCount: 1,
},
browserSessions: {
totalCount: 2,
},
oauth2Sessions: {
totalCount: 3,
},
unverifiedEmails: {
totalCount: 0,
},
confirmedEmails: {
totalCount: 1,
},
},
FRAGMENT,
);
const component = create(<UserSessionsOverview user={user} />);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
});

View File

@@ -12,90 +12,23 @@
// 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 { Body, H3, H6 } from "@vector-im/compound-web"; import { H3 } from "@vector-im/compound-web";
import { FragmentType, graphql, useFragment } from "../../gql"; import { FragmentType, useFragment } from "../../gql";
import { Link } from "../../routing";
import Block from "../Block";
import BlockList from "../BlockList"; import BlockList from "../BlockList";
import AppSessionsList from "./AppSessionsList"; import AppSessionsList from "./AppSessionsList";
import styles from "./UserSessionsOverview.module.css"; import BrowserSessionsOverview, { FRAGMENT } from "./BrowserSessionsOverview";
export const FRAGMENT = graphql(/* GraphQL */ `
fragment UserSessionsOverview_user on User {
id
primaryEmail {
id
...UserEmail_email
}
confirmedEmails: emails(first: 0, state: CONFIRMED) {
totalCount
}
browserSessions(first: 0, state: ACTIVE) {
totalCount
}
oauth2Sessions(first: 0, state: ACTIVE) {
totalCount
}
compatSessions(first: 0, state: ACTIVE) {
totalCount
}
}
`);
const UserSessionsOverview: React.FC<{ const UserSessionsOverview: React.FC<{
user: FragmentType<typeof FRAGMENT>; user: FragmentType<typeof FRAGMENT>;
}> = ({ user }) => { }> = ({ user }) => {
const data = useFragment(FRAGMENT, user); const data = useFragment(FRAGMENT, user);
// allow this until we get i18n
const pluraliseSession = (count: number): string =>
count === 1 ? "session" : "sessions";
// user friendly description of sessions is:
// browser -> browser
// oauth2 sessions -> New apps
// compatibility sessions -> Regular apps
return ( return (
<BlockList> <BlockList>
{/* This is a short term solution, so I won't bother extracting these blocks into components */}
<H3>Where you're signed in</H3> <H3>Where you're signed in</H3>
<Block className={styles.sessionListBlock}> <BrowserSessionsOverview user={user} />
<div className={styles.sessionListBlockInfo}>
<H6>Browser</H6>
<Body>
{data.browserSessions.totalCount} active{" "}
{pluraliseSession(data.browserSessions.totalCount)}
</Body>
</div>
<Link kind="button" route={{ type: "browser-session-list" }}>
View all
</Link>
</Block>
<div className={styles.sessionListBlockInfo}>
<H6>compatSessions</H6>
<Body>
{data.compatSessions.totalCount} active{" "}
{pluraliseSession(data.compatSessions.totalCount)}
</Body>
</div>
<div className={styles.sessionListBlockInfo}>
<H6>oauth2Sessions</H6>
<Body>
{data.oauth2Sessions.totalCount} active{" "}
{pluraliseSession(data.oauth2Sessions.totalCount)}
</Body>
<Link kind="button" route={{ type: "oauth2-session-list" }}>
View all
</Link>
</div>
<AppSessionsList userId={data.id} /> <AppSessionsList userId={data.id} />
</BlockList> </BlockList>
); );

View File

@@ -0,0 +1,65 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`BrowserSessionsOverview > renders with no browser sessions 1`] = `
<div>
<div
class="_block_17898c _sessionListBlock_c5c17e"
>
<div
class="_sessionListBlockInfo_c5c17e"
>
<h5
class="_font-body-lg-semibold_1jx6b_83"
>
Browsers
</h5>
<p
class="_font-body-md-regular_1jx6b_59"
>
0
active
sessions
</p>
</div>
<a
class="_linkButton_b80ad8"
href="/browser-sessions"
>
View all
</a>
</div>
</div>
`;
exports[`BrowserSessionsOverview > renders with sessions 1`] = `
<div>
<div
class="_block_17898c _sessionListBlock_c5c17e"
>
<div
class="_sessionListBlockInfo_c5c17e"
>
<h5
class="_font-body-lg-semibold_1jx6b_83"
>
Browsers
</h5>
<p
class="_font-body-md-regular_1jx6b_59"
>
2
active
sessions
</p>
</div>
<a
class="_linkButton_b80ad8"
href="/browser-sessions"
>
View all
</a>
</div>
</div>
`;

View File

@@ -0,0 +1,3 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`UserSessionsOverview > render an simple <UserSessionsOverview /> 1`] = `<div />`;

View File

@@ -12,4 +12,4 @@
// 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.
export { default, FRAGMENT } from "./UserSessionsOverview"; export { default } from "./UserSessionsOverview";

View File

@@ -57,10 +57,10 @@ const documents = {
types.UserPrimaryEmailDocument, types.UserPrimaryEmailDocument,
"\n mutation SetDisplayName($userId: ID!, $displayName: String) {\n setDisplayName(input: { userId: $userId, displayName: $displayName }) {\n status\n user {\n id\n matrix {\n displayName\n }\n }\n }\n }\n": "\n mutation SetDisplayName($userId: ID!, $displayName: String) {\n setDisplayName(input: { userId: $userId, displayName: $displayName }) {\n status\n user {\n id\n matrix {\n displayName\n }\n }\n }\n }\n":
types.SetDisplayNameDocument, types.SetDisplayNameDocument,
"\n query AppSessionList(\n $userId: ID!\n $state: SessionState\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n appSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n state: $state\n ) {\n totalCount\n\n edges {\n cursor\n node {\n ...CompatSession_session\n ...OAuth2Session_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n": "\n query AppSessionList(\n $userId: ID!\n $state: SessionState\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n appSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n state: $state\n ) {\n totalCount\n\n edges {\n cursor\n node {\n __typename\n ...CompatSession_session\n ...OAuth2Session_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n":
types.AppSessionListDocument, types.AppSessionListDocument,
"\n fragment UserSessionsOverview_user on User {\n id\n\n primaryEmail {\n id\n ...UserEmail_email\n }\n\n confirmedEmails: emails(first: 0, state: CONFIRMED) {\n totalCount\n }\n\n browserSessions(first: 0, state: ACTIVE) {\n totalCount\n }\n\n oauth2Sessions(first: 0, state: ACTIVE) {\n totalCount\n }\n\n compatSessions(first: 0, state: ACTIVE) {\n totalCount\n }\n }\n": "\n fragment BrowserSessionsOverview_user on User {\n id\n\n browserSessions(first: 0, state: ACTIVE) {\n totalCount\n }\n }\n":
types.UserSessionsOverview_UserFragmentDoc, types.BrowserSessionsOverview_UserFragmentDoc,
"\n fragment UserEmail_verifyEmail on UserEmail {\n id\n email\n }\n": "\n fragment UserEmail_verifyEmail on UserEmail {\n id\n email\n }\n":
types.UserEmail_VerifyEmailFragmentDoc, types.UserEmail_VerifyEmailFragmentDoc,
"\n mutation VerifyEmail($id: ID!, $code: String!) {\n verifyEmail(input: { userEmailId: $id, code: $code }) {\n status\n\n user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\n }\n }\n }\n": "\n mutation VerifyEmail($id: ID!, $code: String!) {\n verifyEmail(input: { userEmailId: $id, code: $code }) {\n status\n\n user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\n }\n }\n }\n":
@@ -73,7 +73,7 @@ const documents = {
types.BrowserSessionQueryDocument, types.BrowserSessionQueryDocument,
"\n query OAuth2ClientQuery($id: ID!) {\n oauth2Client(id: $id) {\n ...OAuth2Client_detail\n }\n }\n": "\n query OAuth2ClientQuery($id: ID!) {\n oauth2Client(id: $id) {\n ...OAuth2Client_detail\n }\n }\n":
types.OAuth2ClientQueryDocument, types.OAuth2ClientQueryDocument,
"\n query SessionsOverviewQuery {\n viewer {\n __typename\n\n ... on User {\n id\n ...UserSessionsOverview_user\n }\n }\n }\n": "\n query SessionsOverviewQuery {\n viewer {\n __typename\n\n ... on User {\n id\n ...BrowserSessionsOverview_user\n }\n }\n }\n":
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,
@@ -229,14 +229,14 @@ export function graphql(
* 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 AppSessionList(\n $userId: ID!\n $state: SessionState\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n appSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n state: $state\n ) {\n totalCount\n\n edges {\n cursor\n node {\n ...CompatSession_session\n ...OAuth2Session_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n", source: "\n query AppSessionList(\n $userId: ID!\n $state: SessionState\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n appSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n state: $state\n ) {\n totalCount\n\n edges {\n cursor\n node {\n __typename\n ...CompatSession_session\n ...OAuth2Session_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n",
): (typeof documents)["\n query AppSessionList(\n $userId: ID!\n $state: SessionState\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n appSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n state: $state\n ) {\n totalCount\n\n edges {\n cursor\n node {\n ...CompatSession_session\n ...OAuth2Session_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n"]; ): (typeof documents)["\n query AppSessionList(\n $userId: ID!\n $state: SessionState\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n appSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n state: $state\n ) {\n totalCount\n\n edges {\n cursor\n node {\n __typename\n ...CompatSession_session\n ...OAuth2Session_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\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.
*/ */
export function graphql( export function graphql(
source: "\n fragment UserSessionsOverview_user on User {\n id\n\n primaryEmail {\n id\n ...UserEmail_email\n }\n\n confirmedEmails: emails(first: 0, state: CONFIRMED) {\n totalCount\n }\n\n browserSessions(first: 0, state: ACTIVE) {\n totalCount\n }\n\n oauth2Sessions(first: 0, state: ACTIVE) {\n totalCount\n }\n\n compatSessions(first: 0, state: ACTIVE) {\n totalCount\n }\n }\n", source: "\n fragment BrowserSessionsOverview_user on User {\n id\n\n browserSessions(first: 0, state: ACTIVE) {\n totalCount\n }\n }\n",
): (typeof documents)["\n fragment UserSessionsOverview_user on User {\n id\n\n primaryEmail {\n id\n ...UserEmail_email\n }\n\n confirmedEmails: emails(first: 0, state: CONFIRMED) {\n totalCount\n }\n\n browserSessions(first: 0, state: ACTIVE) {\n totalCount\n }\n\n oauth2Sessions(first: 0, state: ACTIVE) {\n totalCount\n }\n\n compatSessions(first: 0, state: ACTIVE) {\n totalCount\n }\n }\n"]; ): (typeof documents)["\n fragment BrowserSessionsOverview_user on User {\n id\n\n browserSessions(first: 0, state: ACTIVE) {\n totalCount\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.
*/ */
@@ -277,8 +277,8 @@ export function graphql(
* 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 SessionsOverviewQuery {\n viewer {\n __typename\n\n ... on User {\n id\n ...UserSessionsOverview_user\n }\n }\n }\n", source: "\n query SessionsOverviewQuery {\n viewer {\n __typename\n\n ... on User {\n id\n ...BrowserSessionsOverview_user\n }\n }\n }\n",
): (typeof documents)["\n query SessionsOverviewQuery {\n viewer {\n __typename\n\n ... on User {\n id\n ...UserSessionsOverview_user\n }\n }\n }\n"]; ): (typeof documents)["\n query SessionsOverviewQuery {\n viewer {\n __typename\n\n ... on User {\n id\n ...BrowserSessionsOverview_user\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.
*/ */

View File

@@ -1496,12 +1496,12 @@ export type AppSessionListQuery = {
__typename?: "AppSessionEdge"; __typename?: "AppSessionEdge";
cursor: string; cursor: string;
node: node:
| ({ __typename?: "CompatSession" } & { | ({ __typename: "CompatSession" } & {
" $fragmentRefs"?: { " $fragmentRefs"?: {
CompatSession_SessionFragment: CompatSession_SessionFragment; CompatSession_SessionFragment: CompatSession_SessionFragment;
}; };
}) })
| ({ __typename?: "Oauth2Session" } & { | ({ __typename: "Oauth2Session" } & {
" $fragmentRefs"?: { " $fragmentRefs"?: {
OAuth2Session_SessionFragment: OAuth2Session_SessionFragment; OAuth2Session_SessionFragment: OAuth2Session_SessionFragment;
}; };
@@ -1518,28 +1518,14 @@ export type AppSessionListQuery = {
} | null; } | null;
}; };
export type UserSessionsOverview_UserFragment = { export type BrowserSessionsOverview_UserFragment = {
__typename?: "User"; __typename?: "User";
id: string; id: string;
primaryEmail?:
| ({ __typename?: "UserEmail"; id: string } & {
" $fragmentRefs"?: { UserEmail_EmailFragment: UserEmail_EmailFragment };
})
| null;
confirmedEmails: { __typename?: "UserEmailConnection"; totalCount: number };
browserSessions: { browserSessions: {
__typename?: "BrowserSessionConnection"; __typename?: "BrowserSessionConnection";
totalCount: number; totalCount: number;
}; };
oauth2Sessions: { } & { " $fragmentName"?: "BrowserSessionsOverview_UserFragment" };
__typename?: "Oauth2SessionConnection";
totalCount: number;
};
compatSessions: {
__typename?: "CompatSessionConnection";
totalCount: number;
};
} & { " $fragmentName"?: "UserSessionsOverview_UserFragment" };
export type UserEmail_VerifyEmailFragment = { export type UserEmail_VerifyEmailFragment = {
__typename?: "UserEmail"; __typename?: "UserEmail";
@@ -1646,7 +1632,7 @@ export type SessionsOverviewQueryQuery = {
| { __typename: "Anonymous" } | { __typename: "Anonymous" }
| ({ __typename: "User"; id: string } & { | ({ __typename: "User"; id: string } & {
" $fragmentRefs"?: { " $fragmentRefs"?: {
UserSessionsOverview_UserFragment: UserSessionsOverview_UserFragment; BrowserSessionsOverview_UserFragment: BrowserSessionsOverview_UserFragment;
}; };
}); });
}; };
@@ -1857,12 +1843,12 @@ export const UserEmail_EmailFragmentDoc = {
}, },
], ],
} as unknown as DocumentNode<UserEmail_EmailFragment, unknown>; } as unknown as DocumentNode<UserEmail_EmailFragment, unknown>;
export const UserSessionsOverview_UserFragmentDoc = { export const BrowserSessionsOverview_UserFragmentDoc = {
kind: "Document", kind: "Document",
definitions: [ definitions: [
{ {
kind: "FragmentDefinition", kind: "FragmentDefinition",
name: { kind: "Name", value: "UserSessionsOverview_user" }, name: { kind: "Name", value: "BrowserSessionsOverview_user" },
typeCondition: { typeCondition: {
kind: "NamedType", kind: "NamedType",
name: { kind: "Name", value: "User" }, name: { kind: "Name", value: "User" },
@@ -1871,43 +1857,6 @@ export const UserSessionsOverview_UserFragmentDoc = {
kind: "SelectionSet", kind: "SelectionSet",
selections: [ selections: [
{ kind: "Field", name: { kind: "Name", value: "id" } }, { kind: "Field", name: { kind: "Name", value: "id" } },
{
kind: "Field",
name: { kind: "Name", value: "primaryEmail" },
selectionSet: {
kind: "SelectionSet",
selections: [
{ kind: "Field", name: { kind: "Name", value: "id" } },
{
kind: "FragmentSpread",
name: { kind: "Name", value: "UserEmail_email" },
},
],
},
},
{
kind: "Field",
alias: { kind: "Name", value: "confirmedEmails" },
name: { kind: "Name", value: "emails" },
arguments: [
{
kind: "Argument",
name: { kind: "Name", value: "first" },
value: { kind: "IntValue", value: "0" },
},
{
kind: "Argument",
name: { kind: "Name", value: "state" },
value: { kind: "EnumValue", value: "CONFIRMED" },
},
],
selectionSet: {
kind: "SelectionSet",
selections: [
{ kind: "Field", name: { kind: "Name", value: "totalCount" } },
],
},
},
{ {
kind: "Field", kind: "Field",
name: { kind: "Name", value: "browserSessions" }, name: { kind: "Name", value: "browserSessions" },
@@ -1930,71 +1879,11 @@ export const UserSessionsOverview_UserFragmentDoc = {
], ],
}, },
}, },
{
kind: "Field",
name: { kind: "Name", value: "oauth2Sessions" },
arguments: [
{
kind: "Argument",
name: { kind: "Name", value: "first" },
value: { kind: "IntValue", value: "0" },
},
{
kind: "Argument",
name: { kind: "Name", value: "state" },
value: { kind: "EnumValue", value: "ACTIVE" },
},
],
selectionSet: {
kind: "SelectionSet",
selections: [
{ kind: "Field", name: { kind: "Name", value: "totalCount" } },
],
},
},
{
kind: "Field",
name: { kind: "Name", value: "compatSessions" },
arguments: [
{
kind: "Argument",
name: { kind: "Name", value: "first" },
value: { kind: "IntValue", value: "0" },
},
{
kind: "Argument",
name: { kind: "Name", value: "state" },
value: { kind: "EnumValue", value: "ACTIVE" },
},
],
selectionSet: {
kind: "SelectionSet",
selections: [
{ kind: "Field", name: { kind: "Name", value: "totalCount" } },
],
},
},
],
},
},
{
kind: "FragmentDefinition",
name: { kind: "Name", value: "UserEmail_email" },
typeCondition: {
kind: "NamedType",
name: { kind: "Name", value: "UserEmail" },
},
selectionSet: {
kind: "SelectionSet",
selections: [
{ kind: "Field", name: { kind: "Name", value: "id" } },
{ kind: "Field", name: { kind: "Name", value: "email" } },
{ kind: "Field", name: { kind: "Name", value: "confirmedAt" } },
], ],
}, },
}, },
], ],
} as unknown as DocumentNode<UserSessionsOverview_UserFragment, unknown>; } as unknown as DocumentNode<BrowserSessionsOverview_UserFragment, unknown>;
export const UserEmail_VerifyEmailFragmentDoc = { export const UserEmail_VerifyEmailFragmentDoc = {
kind: "Document", kind: "Document",
definitions: [ definitions: [
@@ -4138,6 +4027,10 @@ export const AppSessionListDocument = {
selectionSet: { selectionSet: {
kind: "SelectionSet", kind: "SelectionSet",
selections: [ selections: [
{
kind: "Field",
name: { kind: "Name", value: "__typename" },
},
{ {
kind: "FragmentSpread", kind: "FragmentSpread",
name: { name: {
@@ -4683,7 +4576,7 @@ export const SessionsOverviewQueryDocument = {
kind: "FragmentSpread", kind: "FragmentSpread",
name: { name: {
kind: "Name", kind: "Name",
value: "UserSessionsOverview_user", value: "BrowserSessionsOverview_user",
}, },
}, },
], ],
@@ -4697,23 +4590,7 @@ export const SessionsOverviewQueryDocument = {
}, },
{ {
kind: "FragmentDefinition", kind: "FragmentDefinition",
name: { kind: "Name", value: "UserEmail_email" }, name: { kind: "Name", value: "BrowserSessionsOverview_user" },
typeCondition: {
kind: "NamedType",
name: { kind: "Name", value: "UserEmail" },
},
selectionSet: {
kind: "SelectionSet",
selections: [
{ kind: "Field", name: { kind: "Name", value: "id" } },
{ kind: "Field", name: { kind: "Name", value: "email" } },
{ kind: "Field", name: { kind: "Name", value: "confirmedAt" } },
],
},
},
{
kind: "FragmentDefinition",
name: { kind: "Name", value: "UserSessionsOverview_user" },
typeCondition: { typeCondition: {
kind: "NamedType", kind: "NamedType",
name: { kind: "Name", value: "User" }, name: { kind: "Name", value: "User" },
@@ -4722,43 +4599,6 @@ export const SessionsOverviewQueryDocument = {
kind: "SelectionSet", kind: "SelectionSet",
selections: [ selections: [
{ kind: "Field", name: { kind: "Name", value: "id" } }, { kind: "Field", name: { kind: "Name", value: "id" } },
{
kind: "Field",
name: { kind: "Name", value: "primaryEmail" },
selectionSet: {
kind: "SelectionSet",
selections: [
{ kind: "Field", name: { kind: "Name", value: "id" } },
{
kind: "FragmentSpread",
name: { kind: "Name", value: "UserEmail_email" },
},
],
},
},
{
kind: "Field",
alias: { kind: "Name", value: "confirmedEmails" },
name: { kind: "Name", value: "emails" },
arguments: [
{
kind: "Argument",
name: { kind: "Name", value: "first" },
value: { kind: "IntValue", value: "0" },
},
{
kind: "Argument",
name: { kind: "Name", value: "state" },
value: { kind: "EnumValue", value: "CONFIRMED" },
},
],
selectionSet: {
kind: "SelectionSet",
selections: [
{ kind: "Field", name: { kind: "Name", value: "totalCount" } },
],
},
},
{ {
kind: "Field", kind: "Field",
name: { kind: "Name", value: "browserSessions" }, name: { kind: "Name", value: "browserSessions" },
@@ -4781,50 +4621,6 @@ export const SessionsOverviewQueryDocument = {
], ],
}, },
}, },
{
kind: "Field",
name: { kind: "Name", value: "oauth2Sessions" },
arguments: [
{
kind: "Argument",
name: { kind: "Name", value: "first" },
value: { kind: "IntValue", value: "0" },
},
{
kind: "Argument",
name: { kind: "Name", value: "state" },
value: { kind: "EnumValue", value: "ACTIVE" },
},
],
selectionSet: {
kind: "SelectionSet",
selections: [
{ kind: "Field", name: { kind: "Name", value: "totalCount" } },
],
},
},
{
kind: "Field",
name: { kind: "Name", value: "compatSessions" },
arguments: [
{
kind: "Argument",
name: { kind: "Name", value: "first" },
value: { kind: "IntValue", value: "0" },
},
{
kind: "Argument",
name: { kind: "Name", value: "state" },
value: { kind: "EnumValue", value: "ACTIVE" },
},
],
selectionSet: {
kind: "SelectionSet",
selections: [
{ kind: "Field", name: { kind: "Name", value: "totalCount" } },
],
},
},
], ],
}, },
}, },

View File

@@ -130,7 +130,7 @@ const exchanges = [
]; ];
export const client = createClient({ export const client = createClient({
url: "/graphql", url: "/graphql?testtest",
// 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,
}); });

View File

@@ -30,7 +30,7 @@ const QUERY = graphql(/* GraphQL */ `
... on User { ... on User {
id id
...UserSessionsOverview_user ...BrowserSessionsOverview_user
} }
} }
} }