Polish the session details

This commit is contained in:
Quentin Gliech
2025-02-13 15:48:19 +01:00
parent 8c98287d65
commit 8da8c95573
62 changed files with 2449 additions and 2229 deletions

View File

@@ -10,6 +10,7 @@
"expand": "Expand",
"save": "Save",
"save_and_continue": "Save and continue",
"sign_out": "Sign out",
"start_over": "Start over"
},
"branding": {
@@ -69,8 +70,7 @@
},
"compat_session_detail": {
"client_details_title": "Client info",
"name": "Name",
"session_details_title": "Session"
"name": "Name"
},
"device_type_icon_label": {
"mobile": "Mobile",
@@ -83,7 +83,7 @@
},
"end_session_button": {
"confirmation_modal_title": "Are you sure you want to end this session?",
"text": "Sign out"
"text": "Remove device"
},
"error": {
"hideDetails": "Hide details",
@@ -233,6 +233,7 @@
"current": "Current",
"device_id_label": "Device ID",
"finished_label": "Finished",
"generic_browser_session": "Browser session",
"ip_label": "IP Address",
"last_active_label": "Last Active",
"name_for_platform": "{{name}} for {{platform}}",

View File

@@ -1,25 +0,0 @@
/* Copyright 2024 New Vector Ltd.
* Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
.block {
width: 100%;
color: var(--cpd-color-text-primary);
padding-bottom: var(--cpd-space-5x);
&:last-child {
border-bottom: none;
}
}
.title {
padding-bottom: var(--cpd-space-2x);
border-bottom: var(--cpd-border-width-2) solid var(--cpd-color-gray-400);
/* Workaround compound design tokens heading style being broken */
font-weight: var(--cpd-font-weight-semibold) !important;
font-size: var(--cpd-font-size-heading-sm) !important;
}

View File

@@ -1,41 +0,0 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
import type { Meta, StoryObj } from "@storybook/react";
import { Body, H1, H5 } from "@vector-im/compound-web";
import Block from "./Block";
const meta = {
title: "UI/Block",
component: Block,
tags: ["autodocs"],
} satisfies Meta<typeof Block>;
export default meta;
type Story = StoryObj<typeof Block>;
export const Basic: Story = {
render: (args) => (
<Block {...args}>
<H1>Title</H1>
<H5>Subtitle</H5>
<Body justified>
Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit
enim labore culpa sint ad nisi Lorem pariatur mollit ex esse
exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit
nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor
minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure
elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor
Lorem duis laboris cupidatat officia voluptate. Culpa proident
adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod.
Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim.
Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa
et culpa duis.
</Body>
</Block>
),
};

View File

@@ -1,39 +0,0 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// @vitest-environment happy-dom
import { describe, expect, it } from "vitest";
import render from "../../test-utils/render";
import Block from "./Block";
describe("Block", () => {
it("render <Block />", () => {
const { asFragment } = render(<Block />);
expect(asFragment()).toMatchSnapshot();
});
it("render <Block /> with children", () => {
const { asFragment } = render(
<Block>
<h1>Title</h1>
<p>Body</p>
</Block>,
);
expect(asFragment()).toMatchSnapshot();
});
it("passes down the className prop", () => {
const { asFragment } = render(<Block className="test" />);
expect(asFragment()).toMatchSnapshot();
});
it("renders with highlight", () => {
const { asFragment } = render(<Block highlight />);
expect(asFragment()).toMatchSnapshot();
});
});

View File

@@ -1,33 +0,0 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
import { Heading } from "@vector-im/compound-web";
import cx from "classnames";
import type { ReactNode } from "react";
import styles from "./Block.module.css";
type Props = React.PropsWithChildren<{
title?: ReactNode;
className?: string;
highlight?: boolean;
}>;
const Block: React.FC<Props> = ({ children, className, highlight, title }) => {
return (
<div className={cx(styles.block, className)} data-active={highlight}>
{title && (
<Heading as="h4" size="sm" weight="semibold" className={styles.title}>
{title}
</Heading>
)}
{children}
</div>
);
};
export default Block;

View File

@@ -1,41 +0,0 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Block > passes down the className prop 1`] = `
<DocumentFragment>
<div
class="_block_17898c test"
/>
</DocumentFragment>
`;
exports[`Block > render <Block /> 1`] = `
<DocumentFragment>
<div
class="_block_17898c"
/>
</DocumentFragment>
`;
exports[`Block > render <Block /> with children 1`] = `
<DocumentFragment>
<div
class="_block_17898c"
>
<h1>
Title
</h1>
<p>
Body
</p>
</div>
</DocumentFragment>
`;
exports[`Block > renders with highlight 1`] = `
<DocumentFragment>
<div
class="_block_17898c"
data-active="true"
/>
</DocumentFragment>
`;

View File

@@ -1,7 +0,0 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
export { default } from "./Block";

View File

@@ -1,13 +0,0 @@
/* Copyright 2024 New Vector Ltd.
* Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
.block-list {
display: flex;
flex-direction: column;
align-content: flex-start;
gap: var(--cpd-space-6x);
}

View File

@@ -1,36 +0,0 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
import type { Meta, StoryObj } from "@storybook/react";
import { H2, Text } from "@vector-im/compound-web";
import Block from "../Block";
import BlockList from "./BlockList";
const meta = {
title: "UI/Block List",
component: BlockList,
} satisfies Meta<typeof BlockList>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Basic: Story = {
render: (args) => (
<BlockList {...args}>
<Block>
<H2>Block 1</H2>
<Text>Body 1</Text>
</Block>
<Block>
<H2>Block 2</H2>
<Text>Body 2</Text>
</Block>
</BlockList>
),
};

View File

@@ -1,34 +0,0 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
// @vitest-environment happy-dom
import { describe, expect, it } from "vitest";
import render from "../../test-utils/render";
import Block from "../Block";
import BlockList from "./BlockList";
describe("BlockList", () => {
it("render an empty <BlockList />", () => {
const { asFragment } = render(<BlockList />);
expect(asFragment()).toMatchSnapshot();
});
it("render <BlockList /> with children", () => {
const { asFragment } = render(
<BlockList>
<Block>Block 1</Block>
<Block>Block 2</Block>
</BlockList>,
);
expect(asFragment()).toMatchSnapshot();
});
it("passes down the className prop", () => {
const { asFragment } = render(<BlockList className="foo" />);
expect(asFragment()).toMatchSnapshot();
});
});

View File

@@ -1,19 +0,0 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
import cx from "classnames";
import styles from "./BlockList.module.css";
type Props = React.PropsWithChildren<{
className?: string;
}>;
const BlockList: React.FC<Props> = ({ className, children }) => {
return <div className={cx(styles.blockList, className)}>{children}</div>;
};
export default BlockList;

View File

@@ -1,36 +0,0 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`BlockList > passes down the className prop 1`] = `
<DocumentFragment>
<div
class="_blockList_f8cc7f foo"
/>
</DocumentFragment>
`;
exports[`BlockList > render <BlockList /> with children 1`] = `
<DocumentFragment>
<div
class="_blockList_f8cc7f"
>
<div
class="_block_17898c"
>
Block 1
</div>
<div
class="_block_17898c"
>
Block 2
</div>
</div>
</DocumentFragment>
`;
exports[`BlockList > render an empty <BlockList /> 1`] = `
<DocumentFragment>
<div
class="_blockList_f8cc7f"
/>
</DocumentFragment>
`;

View File

@@ -1,7 +0,0 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
export { default } from "./BlockList";

View File

@@ -7,15 +7,12 @@
import IconChrome from "@browser-logos/chrome/chrome_64x64.png?url";
import IconFirefox from "@browser-logos/firefox/firefox_64x64.png?url";
import IconSafari from "@browser-logos/safari/safari_64x64.png?url";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { Badge } from "@vector-im/compound-web";
import { parseISO } from "date-fns";
import { useCallback } from "react";
import { useTranslation } from "react-i18next";
import { type FragmentType, graphql, useFragment } from "../gql";
import { graphqlRequest } from "../graphql";
import DateTime from "./DateTime";
import EndSessionButton from "./Session/EndSessionButton";
import EndBrowserSessionButton from "./Session/EndBrowserSessionButton";
import LastActive from "./Session/LastActive";
import * as Card from "./SessionCard";
@@ -24,62 +21,17 @@ const FRAGMENT = graphql(/* GraphQL */ `
id
createdAt
finishedAt
...EndBrowserSessionButton_session
userAgent {
raw
deviceType
name
os
model
deviceType
}
lastActiveIp
lastActiveAt
lastAuthentication {
id
createdAt
}
}
`);
const END_SESSION_MUTATION = graphql(/* GraphQL */ `
mutation EndBrowserSession($id: ID!) {
endBrowserSession(input: { browserSessionId: $id }) {
status
browserSession {
id
...BrowserSession_session
}
}
}
`);
export const useEndBrowserSession = (
sessionId: string,
isCurrent: boolean,
): (() => Promise<void>) => {
const queryClient = useQueryClient();
const endSession = useMutation({
mutationFn: (id: string) =>
graphqlRequest({ query: END_SESSION_MUTATION, variables: { id } }),
onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: ["sessionsOverview"] });
queryClient.invalidateQueries({ queryKey: ["browserSessionList"] });
queryClient.invalidateQueries({
queryKey: ["sessionDetail", data.endBrowserSession.browserSession?.id],
});
if (isCurrent) {
window.location.reload();
}
},
});
const onSessionEnd = useCallback(async (): Promise<void> => {
await endSession.mutateAsync(sessionId);
}, [endSession.mutateAsync, sessionId]);
return onSessionEnd;
};
export const browserLogoUri = (browser?: string): string | undefined => {
const lcBrowser = browser?.toLowerCase();
@@ -105,8 +57,6 @@ const BrowserSession: React.FC<Props> = ({ session, isCurrent }) => {
const data = useFragment(FRAGMENT, session);
const { t } = useTranslation();
const onSessionEnd = useEndBrowserSession(data.id, isCurrent);
const deviceType = data.userAgent?.deviceType ?? "UNKNOWN";
let deviceName: string | null = null;
@@ -175,14 +125,7 @@ const BrowserSession: React.FC<Props> = ({ session, isCurrent }) => {
{!data.finishedAt && (
<Card.Action>
<EndSessionButton endSession={onSessionEnd}>
<Card.Body compact>
<Card.Header type={deviceType}>
<Card.Name name={deviceName} />
{clientName && <Card.Client name={clientName} />}
</Card.Header>
</Card.Body>
</EndSessionButton>
<EndBrowserSessionButton session={data} size="sm" />
</Card.Action>
)}
</Card.Root>

View File

@@ -1,14 +0,0 @@
/* Copyright 2024 New Vector Ltd.
* Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
.header {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
gap: var(--cpd-space-2x);
}

View File

@@ -1,4 +1,4 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
@@ -9,12 +9,9 @@ import { useTranslation } from "react-i18next";
import { type FragmentType, useFragment } from "../../gql";
import { graphql } from "../../gql/gql";
import BlockList from "../BlockList/BlockList";
import ExternalLink from "../ExternalLink/ExternalLink";
import ClientAvatar from "../Session/ClientAvatar";
import SessionDetails from "../SessionDetail/SessionDetails";
import styles from "./OAuth2ClientDetail.module.css";
import * as Info from "../SessionDetail/SessionInfo";
export const OAUTH2_CLIENT_FRAGMENT = graphql(/* GraphQL */ `
fragment OAuth2Client_detail on Oauth2Client {
@@ -47,21 +44,9 @@ const OAuth2ClientDetail: React.FC<Props> = ({ client }) => {
const data = useFragment(OAUTH2_CLIENT_FRAGMENT, client);
const { t } = useTranslation();
const details = [
{ label: t("frontend.oauth2_client_detail.name"), value: data.clientName },
{
label: t("frontend.oauth2_client_detail.terms"),
value: data.tosUri && <FriendlyExternalLink uri={data.tosUri} />,
},
{
label: t("frontend.oauth2_client_detail.policy"),
value: data.policyUri && <FriendlyExternalLink uri={data.policyUri} />,
},
].filter(({ value }) => !!value);
return (
<BlockList>
<header className={styles.header}>
<div className="flex flex-col gap-10">
<header className="flex flex-row gap-2 justify-start items-center">
<ClientAvatar
logoUri={data.logoUri || undefined}
name={data.clientName || data.clientId}
@@ -69,11 +54,42 @@ const OAuth2ClientDetail: React.FC<Props> = ({ client }) => {
/>
<H3>{data.clientName}</H3>
</header>
<SessionDetails
title={t("frontend.oauth2_client_detail.details_title")}
details={details}
/>
</BlockList>
<Info.DataSection>
<Info.DataSectionHeader>
{t("frontend.oauth2_client_detail.details_title")}
</Info.DataSectionHeader>
<Info.DataList>
{data.clientName && (
<Info.Data>
<Info.DataLabel>
{t("frontend.oauth2_client_detail.name")}
</Info.DataLabel>
<Info.DataValue>{data.clientName}</Info.DataValue>
</Info.Data>
)}
{data.tosUri && (
<Info.Data>
<Info.DataLabel>
{t("frontend.oauth2_client_detail.terms")}
</Info.DataLabel>
<Info.DataValue>
<FriendlyExternalLink uri={data.tosUri} />
</Info.DataValue>
</Info.Data>
)}
{data.policyUri && (
<Info.Data>
<Info.DataLabel>
{t("frontend.oauth2_client_detail.policy")}
</Info.DataLabel>
<Info.DataValue>
<FriendlyExternalLink uri={data.policyUri} />
</Info.DataValue>
</Info.Data>
)}
</Info.DataList>
</Info.DataSection>
</div>
);
};

View File

@@ -3,10 +3,10 @@
exports[`<OAuth2ClientDetail> > renders client details 1`] = `
<div>
<div
class="_blockList_f8cc7f"
class="flex flex-col gap-10"
>
<header
class="_header_477219"
class="flex flex-row gap-2 justify-start items-center"
>
<h3
class="_typography_yh5dq_162 _font-heading-md-semibold_yh5dq_121"
@@ -14,71 +14,85 @@ exports[`<OAuth2ClientDetail> > renders client details 1`] = `
Test Client
</h3>
</header>
<div
class="_block_17898c"
<section
class="flex flex-col gap-6"
>
<h4
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102 _title_17898c"
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
>
Client info
</h4>
<div
class="_wrapper_040867"
>
<div
class="_datum_040867"
class="_separator_144s5_17"
data-kind="section"
data-orientation="horizontal"
role="separator"
/>
</h4>
<ul
class="flex flex-wrap gap-x-10 gap-y-6"
>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Name
</h5>
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 _datumValue_040867"
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
>
Test Client
</p>
</div>
<div
class="_datum_040867"
</li>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Terms of service
</h5>
<a
class="_link_ue21z_17 _externalLink_a97355"
data-kind="primary"
data-size="medium"
href="https://client.org/tos"
rel="noreferrer noopener"
target="_blank"
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
>
client.org/tos
</a>
</div>
<div
class="_datum_040867"
<a
class="_link_ue21z_17 _externalLink_a97355"
data-kind="primary"
data-size="medium"
href="https://client.org/tos"
rel="noreferrer noopener"
target="_blank"
>
client.org/tos
</a>
</p>
</li>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Policy
</h5>
<a
class="_link_ue21z_17 _externalLink_a97355"
data-kind="primary"
data-size="medium"
href="https://client.org/policy"
rel="noreferrer noopener"
target="_blank"
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
>
client.org/policy
</a>
</div>
</div>
</div>
<a
class="_link_ue21z_17 _externalLink_a97355"
data-kind="primary"
data-size="medium"
href="https://client.org/policy"
rel="noreferrer noopener"
target="_blank"
>
client.org/policy
</a>
</p>
</li>
</ul>
</section>
</div>
</div>
`;

View File

@@ -1,17 +1,16 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { parseISO } from "date-fns";
import { useTranslation } from "react-i18next";
import { type FragmentType, graphql, useFragment } from "../gql";
import { graphqlRequest } from "../graphql";
import simplifyUrl from "../utils/simplifyUrl";
import { browserLogoUri } from "./BrowserSession";
import DateTime from "./DateTime";
import EndSessionButton from "./Session/EndSessionButton";
import EndCompatSessionButton from "./Session/EndCompatSessionButton";
import LastActive from "./Session/LastActive";
import * as Card from "./SessionCard";
@@ -23,6 +22,7 @@ export const FRAGMENT = graphql(/* GraphQL */ `
finishedAt
lastActiveIp
lastActiveAt
...EndCompatSessionButton_session
userAgent {
name
os
@@ -36,59 +36,11 @@ export const FRAGMENT = graphql(/* GraphQL */ `
}
`);
export const END_SESSION_MUTATION = graphql(/* GraphQL */ `
mutation EndCompatSession($id: ID!) {
endCompatSession(input: { compatSessionId: $id }) {
status
compatSession {
id
}
}
}
`);
export const simplifyUrl = (url: string): string => {
let parsed: URL;
try {
parsed = new URL(url);
} catch (_e) {
// Not a valid URL, return the original
return url;
}
// Clear out the search params and hash
parsed.search = "";
parsed.hash = "";
if (parsed.protocol === "https:") {
return parsed.hostname;
}
// Return the simplified URL
return parsed.toString();
};
const CompatSession: React.FC<{
session: FragmentType<typeof FRAGMENT>;
}> = ({ session }) => {
const { t } = useTranslation();
const data = useFragment(FRAGMENT, session);
const queryClient = useQueryClient();
const endSession = useMutation({
mutationFn: (id: string) =>
graphqlRequest({ query: END_SESSION_MUTATION, variables: { id } }),
onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: ["sessionsOverview"] });
queryClient.invalidateQueries({ queryKey: ["appSessionList"] });
queryClient.invalidateQueries({
queryKey: ["sessionDetail", data.endCompatSession.compatSession?.id],
});
},
});
const onSessionEnd = async (): Promise<void> => {
await endSession.mutateAsync(data.id);
};
const clientName = data.ssoLogin?.redirectUri
? simplifyUrl(data.ssoLogin.redirectUri)
@@ -146,14 +98,7 @@ const CompatSession: React.FC<{
{!data.finishedAt && (
<Card.Action>
<EndSessionButton endSession={onSessionEnd}>
<Card.Body compact>
<Card.Header type={deviceType}>
<Card.Name name={deviceName} />
{clientName && <Card.Client name={clientName} />}
</Card.Header>
</Card.Body>
</EndSessionButton>
<EndCompatSessionButton session={data} size="sm" />
</Card.Action>
)}
</Card.Root>

View File

@@ -8,8 +8,6 @@ import IconError from "@vector-im/compound-design-tokens/assets/web/icons/error"
import { Button } from "@vector-im/compound-web";
import { useState } from "react";
import { Translation } from "react-i18next";
import BlockList from "./BlockList";
import styles from "./GenericError.module.css";
import PageHeading from "./PageHeading";
@@ -21,7 +19,7 @@ const GenericError: React.FC<{ error: unknown; dontSuspend?: boolean }> = ({
return (
<Translation useSuspense={!dontSuspend}>
{(t) => (
<BlockList>
<div className="flex flex-col gap-10">
<PageHeading
invalid
Icon={IconError}
@@ -46,7 +44,7 @@ const GenericError: React.FC<{ error: unknown; dontSuspend?: boolean }> = ({
<code>{String(error)}</code>
</pre>
)}
</BlockList>
</div>
)}
</Translation>
);

View File

@@ -1,18 +1,10 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { parseISO } from "date-fns";
import { useTranslation } from "react-i18next";
import { type FragmentType, graphql, useFragment } from "../gql";
import type { DeviceType, Oauth2ApplicationType } from "../gql/graphql";
import { graphqlRequest } from "../graphql";
import { getDeviceIdFromScope } from "../utils/deviceIdFromScope";
import DateTime from "./DateTime";
import EndSessionButton from "./Session/EndSessionButton";
import EndOAuth2SessionButton from "./Session/EndOAuth2SessionButton";
import LastActive from "./Session/LastActive";
import * as Card from "./SessionCard";
@@ -25,6 +17,8 @@ export const FRAGMENT = graphql(/* GraphQL */ `
lastActiveIp
lastActiveAt
...EndOAuth2SessionButton_session
userAgent {
name
model
@@ -42,17 +36,6 @@ export const FRAGMENT = graphql(/* GraphQL */ `
}
`);
export const END_SESSION_MUTATION = graphql(/* GraphQL */ `
mutation EndOAuth2Session($id: ID!) {
endOauth2Session(input: { oauth2SessionId: $id }) {
status
oauth2Session {
id
}
}
}
`);
const getDeviceTypeFromClientAppType = (
appType?: Oauth2ApplicationType | null,
): DeviceType => {
@@ -72,22 +55,6 @@ type Props = {
const OAuth2Session: React.FC<Props> = ({ session }) => {
const { t } = useTranslation();
const data = useFragment(FRAGMENT, session);
const queryClient = useQueryClient();
const endSession = useMutation({
mutationFn: (id: string) =>
graphqlRequest({ query: END_SESSION_MUTATION, variables: { id } }),
onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: ["sessionsOverview"] });
queryClient.invalidateQueries({ queryKey: ["appSessionList"] });
queryClient.invalidateQueries({
queryKey: ["sessionDetail", data.endOauth2Session.oauth2Session?.id],
});
},
});
const onSessionEnd = async (): Promise<void> => {
await endSession.mutateAsync(data.id);
};
const deviceId = getDeviceIdFromScope(data.scope);
@@ -149,17 +116,7 @@ const OAuth2Session: React.FC<Props> = ({ session }) => {
{!data.finishedAt && (
<Card.Action>
<EndSessionButton endSession={onSessionEnd}>
<Card.Body compact>
<Card.Header type={deviceType}>
<Card.Name name={deviceName} />
<Card.Client
name={clientName}
logoUri={data.client.logoUri ?? undefined}
/>
</Card.Header>
</Card.Body>
</EndSessionButton>
<EndOAuth2SessionButton session={data} size="sm" />
</Card.Action>
)}
</Card.Root>

View File

@@ -0,0 +1,128 @@
// Copyright 2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
import {
type UseMutationResult,
useMutation,
useQueryClient,
} from "@tanstack/react-query";
import { useTranslation } from "react-i18next";
import { type FragmentType, graphql, useFragment } from "../../gql";
import { graphqlRequest } from "../../graphql";
import * as Card from "../SessionCard";
import EndSessionButton from "./EndSessionButton";
const FRAGMENT = graphql(/* GraphQL */ `
fragment EndBrowserSessionButton_session on BrowserSession {
id
userAgent {
name
os
model
deviceType
}
}
`);
const END_SESSION_MUTATION = graphql(/* GraphQL */ `
mutation EndBrowserSession($id: ID!) {
endBrowserSession(input: { browserSessionId: $id }) {
status
browserSession {
id
}
}
}
`);
export const useEndBrowserSession = (
sessionId: string,
isCurrent: boolean,
): UseMutationResult<unknown, unknown, void> => {
const queryClient = useQueryClient();
const endSession = useMutation({
mutationFn: () =>
graphqlRequest({
query: END_SESSION_MUTATION,
variables: { id: sessionId },
}),
onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: ["sessionsOverview"] });
queryClient.invalidateQueries({ queryKey: ["browserSessionList"] });
queryClient.invalidateQueries({
queryKey: ["sessionDetail", data.endBrowserSession.browserSession?.id],
});
if (isCurrent) {
window.location.reload();
}
},
});
return endSession;
};
type Props = {
session: FragmentType<typeof FRAGMENT>;
size: "sm" | "lg";
};
const EndBrowserSessionButton: React.FC<Props> = ({ session, size }) => {
const { t } = useTranslation();
const data = useFragment(FRAGMENT, session);
const queryClient = useQueryClient();
const endSession = useMutation({
mutationFn: () =>
graphqlRequest({
query: END_SESSION_MUTATION,
variables: { id: data.id },
}),
onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: ["sessionsOverview"] });
queryClient.invalidateQueries({ queryKey: ["appSessionList"] });
queryClient.invalidateQueries({
queryKey: ["sessionDetail", data.endBrowserSession.browserSession?.id],
});
},
});
const deviceType = data.userAgent?.deviceType ?? "UNKNOWN";
let deviceName: string | null = null;
let clientName: string | null = null;
// If we have a model, use that as the device name, and the browser (+ OS) as the client name
if (data.userAgent?.model) {
deviceName = data.userAgent.model;
if (data.userAgent?.name) {
if (data.userAgent?.os) {
clientName = t("frontend.session.name_for_platform", {
name: data.userAgent.name,
platform: data.userAgent.os,
});
} else {
clientName = data.userAgent.name;
}
}
} else {
// Else use the browser as the device name
deviceName = data.userAgent?.name ?? t("frontend.session.unknown_browser");
// and if we have an OS, use that as the client name
clientName = data.userAgent?.os ?? null;
}
return (
<EndSessionButton mutation={endSession} size={size}>
<Card.Body compact>
<Card.Header type={deviceType}>
<Card.Name name={deviceName} />
{clientName && <Card.Client name={clientName} />}
</Card.Header>
</Card.Body>
</EndSessionButton>
);
};
export default EndBrowserSessionButton;

View File

@@ -0,0 +1,89 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useTranslation } from "react-i18next";
import { type FragmentType, graphql, useFragment } from "../../gql";
import { graphqlRequest } from "../../graphql";
import simplifyUrl from "../../utils/simplifyUrl";
import * as Card from "../SessionCard";
import EndSessionButton from "./EndSessionButton";
const FRAGMENT = graphql(/* GraphQL */ `
fragment EndCompatSessionButton_session on CompatSession {
id
userAgent {
name
os
model
deviceType
}
ssoLogin {
id
redirectUri
}
}
`);
const END_SESSION_MUTATION = graphql(/* GraphQL */ `
mutation EndCompatSession($id: ID!) {
endCompatSession(input: { compatSessionId: $id }) {
status
compatSession {
id
}
}
}
`);
type Props = {
session: FragmentType<typeof FRAGMENT>;
size: "sm" | "lg";
};
const EndCompatSessionButton: React.FC<Props> = ({ session, size }) => {
const { t } = useTranslation();
const data = useFragment(FRAGMENT, session);
const queryClient = useQueryClient();
const endSession = useMutation({
mutationFn: () =>
graphqlRequest({
query: END_SESSION_MUTATION,
variables: { id: data.id },
}),
onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: ["sessionsOverview"] });
queryClient.invalidateQueries({ queryKey: ["appSessionList"] });
queryClient.invalidateQueries({
queryKey: ["sessionDetail", data.endCompatSession.compatSession?.id],
});
},
});
const clientName = data.ssoLogin?.redirectUri
? simplifyUrl(data.ssoLogin.redirectUri)
: undefined;
const deviceType = data.userAgent?.deviceType ?? "UNKNOWN";
const deviceName =
data.userAgent?.model ??
(data.userAgent?.name
? data.userAgent?.os
? t("frontend.session.name_for_platform", {
name: data.userAgent.name,
platform: data.userAgent.os,
})
: data.userAgent.name
: t("frontend.session.unknown_device"));
return (
<EndSessionButton mutation={endSession} size={size}>
<Card.Body compact>
<Card.Header type={deviceType}>
<Card.Name name={deviceName} />
{clientName && <Card.Client name={clientName} />}
</Card.Header>
</Card.Body>
</EndSessionButton>
);
};
export default EndCompatSessionButton;

View File

@@ -0,0 +1,115 @@
// Copyright 2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useTranslation } from "react-i18next";
import { type FragmentType, graphql, useFragment } from "../../gql";
import type { DeviceType, Oauth2ApplicationType } from "../../gql/graphql";
import { graphqlRequest } from "../../graphql";
import * as Card from "../SessionCard";
import EndSessionButton from "./EndSessionButton";
const FRAGMENT = graphql(/* GraphQL */ `
fragment EndOAuth2SessionButton_session on Oauth2Session {
id
userAgent {
name
model
os
deviceType
}
client {
clientId
clientName
applicationType
logoUri
}
}
`);
const END_SESSION_MUTATION = graphql(/* GraphQL */ `
mutation EndOAuth2Session($id: ID!) {
endOauth2Session(input: { oauth2SessionId: $id }) {
status
oauth2Session {
id
}
}
}
`);
const getDeviceTypeFromClientAppType = (
appType?: Oauth2ApplicationType | null,
): DeviceType => {
if (appType === "WEB") {
return "PC";
}
if (appType === "NATIVE") {
return "MOBILE";
}
return "UNKNOWN";
};
type Props = {
session: FragmentType<typeof FRAGMENT>;
size: "sm" | "lg";
};
const EndOAuth2SessionButton: React.FC<Props> = ({ session, size }) => {
const { t } = useTranslation();
const data = useFragment(FRAGMENT, session);
const queryClient = useQueryClient();
const endSession = useMutation({
mutationFn: () =>
graphqlRequest({
query: END_SESSION_MUTATION,
variables: { id: data.id },
}),
onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: ["sessionsOverview"] });
queryClient.invalidateQueries({ queryKey: ["appSessionList"] });
queryClient.invalidateQueries({
queryKey: ["sessionDetail", data.endOauth2Session.oauth2Session?.id],
});
},
});
const deviceType =
(data.userAgent?.deviceType === "UNKNOWN"
? null
: data.userAgent?.deviceType) ??
getDeviceTypeFromClientAppType(data.client.applicationType);
const clientName = data.client.clientName || data.client.clientId;
const deviceName =
data.userAgent?.model ??
(data.userAgent?.name
? data.userAgent?.os
? t("frontend.session.name_for_platform", {
name: data.userAgent.name,
platform: data.userAgent.os,
})
: data.userAgent.name
: t("frontend.session.unknown_device"));
return (
<EndSessionButton mutation={endSession} size={size}>
<Card.Body compact>
<Card.Header type={deviceType}>
<Card.Name name={deviceName} />
<Card.Client
name={clientName}
logoUri={data.client.logoUri ?? undefined}
/>
</Card.Header>
</Card.Body>
</EndSessionButton>
);
};
export default EndOAuth2SessionButton;

View File

@@ -1,39 +0,0 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
import { action } from "@storybook/addon-actions";
import type { Meta, StoryObj } from "@storybook/react";
import EndSessionButton from "./EndSessionButton";
const endSession = action("end-session");
const meta = {
title: "UI/Session/End Session Button",
component: EndSessionButton,
tags: ["autodocs"],
args: {
endSession: async (): Promise<void> => {
await new Promise((resolve) => setTimeout(resolve, 300));
endSession();
},
},
argTypes: {
children: { control: "text" },
},
} satisfies Meta<typeof EndSessionButton>;
export default meta;
type Story = StoryObj<typeof EndSessionButton>;
export const Basic: Story = {};
export const WithChildren: Story = {
args: {
children:
"Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.",
},
};

View File

@@ -1,14 +1,14 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
import IconSignOut from "@vector-im/compound-design-tokens/assets/web/icons/sign-out";
import type { UseMutationResult } from "@tanstack/react-query";
import IconDelete from "@vector-im/compound-design-tokens/assets/web/icons/delete";
import { Button } from "@vector-im/compound-web";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import * as Dialog from "../Dialog";
import LoadingSpinner from "../LoadingSpinner/LoadingSpinner";
@@ -17,25 +17,17 @@ import LoadingSpinner from "../LoadingSpinner/LoadingSpinner";
* Handles loading state while endSession is in progress
*/
const EndSessionButton: React.FC<
React.PropsWithChildren<{ endSession: () => Promise<void> }>
> = ({ children, endSession }) => {
const [inProgress, setInProgress] = useState(false);
React.PropsWithChildren<{
mutation: UseMutationResult<unknown, unknown, void>;
size: "sm" | "lg";
}>
> = ({ children, mutation, size }) => {
const [open, setOpen] = useState(false);
const { t } = useTranslation();
const onConfirm = async (
e: React.MouseEvent<HTMLButtonElement>,
): Promise<void> => {
const onConfirm = (e: React.MouseEvent<HTMLButtonElement>): void => {
e.preventDefault();
setInProgress(true);
try {
await endSession();
setOpen(false);
} catch (error) {
console.error("Failed to end session", error);
}
setInProgress(false);
mutation.mutate(void 0, { onSuccess: () => setOpen(false) });
};
return (
@@ -43,7 +35,7 @@ const EndSessionButton: React.FC<
open={open}
onOpenChange={setOpen}
trigger={
<Button kind="secondary" destructive size="sm" Icon={IconSignOut}>
<Button kind="secondary" destructive size={size} Icon={IconDelete}>
{t("frontend.end_session_button.text")}
</Button>
}
@@ -59,10 +51,10 @@ const EndSessionButton: React.FC<
kind="primary"
destructive
onClick={onConfirm}
disabled={inProgress}
Icon={inProgress ? undefined : IconSignOut}
disabled={mutation.isPending}
Icon={mutation.isPending ? undefined : IconDelete}
>
{inProgress && <LoadingSpinner inline />}
{mutation.isPending && <LoadingSpinner inline />}
{t("frontend.end_session_button.text")}
</Button>

View File

@@ -111,6 +111,10 @@
flex-wrap: wrap;
gap: var(--cpd-space-4x) var(--cpd-space-10x);
& > * {
min-width: 0;
}
& .key {
font: var(--cpd-font-body-sm-regular);
letter-spacing: var(--cpd-font-letter-spacing-body-sm);
@@ -121,6 +125,8 @@
font: var(--cpd-font-body-md-regular);
letter-spacing: var(--cpd-font-letter-spacing-body-md);
color: var(--cpd-color-text-primary);
overflow: hidden;
text-overflow: ellipsis;
}
}
}

View File

@@ -1,10 +0,0 @@
/* Copyright 2024 New Vector Ltd.
* Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
.current-badge {
align-self: flex-start;
}

View File

@@ -1,4 +1,4 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
@@ -7,22 +7,19 @@
import { Badge } from "@vector-im/compound-web";
import { parseISO } from "date-fns";
import { useTranslation } from "react-i18next";
import { type FragmentType, graphql, useFragment } from "../../gql";
import BlockList from "../BlockList/BlockList";
import { useEndBrowserSession } from "../BrowserSession";
import DateTime from "../DateTime";
import EndSessionButton from "../Session/EndSessionButton";
import styles from "./BrowserSessionDetail.module.css";
import SessionDetails from "./SessionDetails";
import EndBrowserSessionButton from "../Session/EndBrowserSessionButton";
import LastActive from "../Session/LastActive";
import SessionHeader from "./SessionHeader";
import * as Info from "./SessionInfo";
const FRAGMENT = graphql(/* GraphQL */ `
fragment BrowserSession_detail on BrowserSession {
id
createdAt
finishedAt
...EndBrowserSessionButton_session
userAgent {
name
model
@@ -50,51 +47,79 @@ const BrowserSessionDetail: React.FC<Props> = ({ session, isCurrent }) => {
const data = useFragment(FRAGMENT, session);
const { t } = useTranslation();
const onSessionEnd = useEndBrowserSession(data.id, isCurrent);
let sessionName = "Browser session";
let sessionName = t("frontend.session.generic_browser_session");
if (data.userAgent) {
if (data.userAgent.model && data.userAgent.name) {
sessionName = `${data.userAgent.name} on ${data.userAgent.model}`;
sessionName = t("frontend.session.name_for_platform", {
name: data.userAgent.name,
platform: data.userAgent.model,
});
} else if (data.userAgent.name && data.userAgent.os) {
sessionName = `${data.userAgent.name} on ${data.userAgent.os}`;
sessionName = t("frontend.session.name_for_platform", {
name: data.userAgent.name,
platform: data.userAgent.os,
});
} else if (data.userAgent.name) {
sessionName = data.userAgent.name;
}
}
const finishedAt = data.finishedAt
? [
{
label: t("frontend.session.finished_label"),
value: <DateTime datetime={parseISO(data.finishedAt)} />,
},
]
: [];
const sessionDetails = [...finishedAt];
return (
<BlockList>
<div className="flex flex-col gap-10">
{isCurrent && (
<Badge className={styles.currentBadge} kind="green">
<Badge className="self-start" kind="green">
{t("frontend.browser_session_details.current_badge")}
</Badge>
)}
<SessionHeader to="/sessions/browsers">{sessionName}</SessionHeader>
<SessionDetails
title={t("frontend.session.title")}
lastActive={data.lastActiveAt ? parseISO(data.lastActiveAt) : undefined}
signedIn={
data.lastAuthentication
? parseISO(data.lastAuthentication.createdAt)
: undefined
}
ipAddress={data.lastActiveIp ?? undefined}
details={sessionDetails}
/>
{!data.finishedAt && <EndSessionButton endSession={onSessionEnd} />}
</BlockList>
<Info.DataSection>
<Info.DataSectionHeader>
{t("frontend.session.title")}
</Info.DataSectionHeader>
<Info.DataList>
{data.lastActiveAt && (
<Info.Data>
<Info.DataLabel>
{t("frontend.session.last_active_label")}
</Info.DataLabel>
<Info.DataValue>
<LastActive lastActive={parseISO(data.lastActiveAt)} />
</Info.DataValue>
</Info.Data>
)}
<Info.Data>
<Info.DataLabel>
{t("frontend.session.signed_in_label")}
</Info.DataLabel>
<Info.DataValue>
<DateTime datetime={data.createdAt} />
</Info.DataValue>
</Info.Data>
{data.finishedAt && (
<Info.Data>
<Info.DataLabel>
{t("frontend.session.finished_label")}
</Info.DataLabel>
<Info.DataValue>
<DateTime datetime={data.finishedAt} />
</Info.DataValue>
</Info.Data>
)}
{data.lastActiveIp && (
<Info.Data>
<Info.DataLabel>{t("frontend.session.ip_label")}</Info.DataLabel>
<Info.DataValue>
<code>{data.lastActiveIp}</code>
</Info.DataValue>
</Info.Data>
)}
</Info.DataList>
</Info.DataSection>
{!data.finishedAt && <EndBrowserSessionButton session={data} size="lg" />}
</div>
);
};

View File

@@ -38,7 +38,7 @@ describe("<CompatSessionDetail>", () => {
expect(container).toMatchSnapshot();
expect(queryByText("Finished")).toBeFalsy();
expect(getByText("Sign out")).toBeTruthy();
expect(getByText("Remove device")).toBeTruthy();
});
it("renders a compatability session without an ssoLogin", () => {
@@ -56,7 +56,7 @@ describe("<CompatSessionDetail>", () => {
expect(container).toMatchSnapshot();
expect(queryByText("Finished")).toBeFalsy();
expect(getByText("Sign out")).toBeTruthy();
expect(getByText("Remove device")).toBeTruthy();
});
it("renders a finished compatability session details", () => {
@@ -74,6 +74,6 @@ describe("<CompatSessionDetail>", () => {
expect(container).toMatchSnapshot();
expect(getByText("Finished")).toBeTruthy();
expect(queryByText("Sign out")).toBeFalsy();
expect(queryByText("Remove device")).toBeFalsy();
});
});

View File

@@ -1,21 +1,19 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { VisualList } from "@vector-im/compound-web";
import { parseISO } from "date-fns";
import { useTranslation } from "react-i18next";
import { type FragmentType, graphql, useFragment } from "../../gql";
import { graphqlRequest } from "../../graphql";
import BlockList from "../BlockList/BlockList";
import { END_SESSION_MUTATION, simplifyUrl } from "../CompatSession";
import simplifyUrl from "../../utils/simplifyUrl";
import DateTime from "../DateTime";
import ExternalLink from "../ExternalLink/ExternalLink";
import EndSessionButton from "../Session/EndSessionButton";
import SessionDetails from "./SessionDetails";
import EndCompatSessionButton from "../Session/EndCompatSessionButton";
import LastActive from "../Session/LastActive";
import SessionHeader from "./SessionHeader";
import * as Info from "./SessionInfo";
export const FRAGMENT = graphql(/* GraphQL */ `
fragment CompatSession_detail on CompatSession {
@@ -25,11 +23,15 @@ export const FRAGMENT = graphql(/* GraphQL */ `
finishedAt
lastActiveIp
lastActiveAt
...EndCompatSessionButton_session
userAgent {
name
os
model
}
ssoLogin {
id
redirectUri
@@ -43,74 +45,111 @@ type Props = {
const CompatSessionDetail: React.FC<Props> = ({ session }) => {
const data = useFragment(FRAGMENT, session);
const queryClient = useQueryClient();
const endSession = useMutation({
mutationFn: (id: string) =>
graphqlRequest({ query: END_SESSION_MUTATION, variables: { id } }),
onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: ["sessionsOverview"] });
queryClient.invalidateQueries({ queryKey: ["appSessionList"] });
queryClient.invalidateQueries({
queryKey: ["sessionDetail", data.endCompatSession.compatSession?.id],
});
},
});
const { t } = useTranslation();
const onSessionEnd = async (): Promise<void> => {
await endSession.mutateAsync(data.id);
};
const deviceName =
data.userAgent?.model ??
(data.userAgent?.name
? data.userAgent?.os
? t("frontend.session.name_for_platform", {
name: data.userAgent.name,
platform: data.userAgent.os,
})
: data.userAgent.name
: t("frontend.session.unknown_device"));
const finishedAt = data.finishedAt
? [
{
label: t("frontend.session.finished_label"),
value: <DateTime datetime={parseISO(data.finishedAt)} />,
},
]
: [];
const sessionDetails = [...finishedAt];
const clientDetails: { label: string; value: string | React.ReactElement }[] =
[];
if (data.ssoLogin?.redirectUri) {
clientDetails.push({
label: t("frontend.compat_session_detail.name"),
value: data.userAgent?.name ?? simplifyUrl(data.ssoLogin.redirectUri),
});
clientDetails.push({
label: t("frontend.session.uri_label"),
value: (
<ExternalLink target="_blank" href={data.ssoLogin?.redirectUri}>
{data.ssoLogin?.redirectUri}
</ExternalLink>
),
});
}
const clientName = data.ssoLogin?.redirectUri
? simplifyUrl(data.ssoLogin.redirectUri)
: data.deviceId || data.id;
return (
<BlockList>
<SessionHeader to="/sessions">{data.deviceId || data.id}</SessionHeader>
<SessionDetails
title={t("frontend.compat_session_detail.session_details_title")}
deviceId={data.deviceId}
signedIn={parseISO(data.createdAt)}
lastActive={data.lastActiveAt ? parseISO(data.lastActiveAt) : undefined}
ipAddress={data.lastActiveIp ?? undefined}
details={sessionDetails}
// These scopes need to be kept in sync with `templates/pages/sso.html`
scopes={["openid", "urn:matrix:org.matrix.msc2967.client:api:*"]}
/>
{clientDetails.length > 0 ? (
<SessionDetails
title={t("frontend.compat_session_detail.client_details_title")}
details={clientDetails}
/>
) : null}
{!data.finishedAt && <EndSessionButton endSession={onSessionEnd} />}
</BlockList>
<div className="flex flex-col gap-10">
<SessionHeader to="/sessions">
{clientName}: {deviceName}
</SessionHeader>
<Info.DataSection>
<Info.DataSectionHeader>
{t("frontend.session.title")}
</Info.DataSectionHeader>
<Info.DataList>
{data.lastActiveAt && (
<Info.Data>
<Info.DataLabel>
{t("frontend.session.last_active_label")}
</Info.DataLabel>
<Info.DataValue>
<LastActive lastActive={parseISO(data.lastActiveAt)} />
</Info.DataValue>
</Info.Data>
)}
<Info.Data>
<Info.DataLabel>
{t("frontend.session.signed_in_label")}
</Info.DataLabel>
<Info.DataValue>
<DateTime datetime={data.createdAt} />
</Info.DataValue>
</Info.Data>
{data.finishedAt && (
<Info.Data>
<Info.DataLabel>
{t("frontend.session.finished_label")}
</Info.DataLabel>
<Info.DataValue>
<DateTime datetime={data.finishedAt} />
</Info.DataValue>
</Info.Data>
)}
<Info.Data>
<Info.DataLabel>
{t("frontend.session.device_id_label")}
</Info.DataLabel>
<Info.DataValue>{data.deviceId}</Info.DataValue>
</Info.Data>
{data.lastActiveIp && (
<Info.Data>
<Info.DataLabel>{t("frontend.session.ip_label")}</Info.DataLabel>
<Info.DataValue>
<code>{data.lastActiveIp}</code>
</Info.DataValue>
</Info.Data>
)}
</Info.DataList>
<Info.Data>
<Info.DataLabel>{t("frontend.session.scopes_label")}</Info.DataLabel>
<VisualList className="mt-1">
<Info.ScopeViewProfile />
<Info.ScopeViewMessages />
<Info.ScopeSendMessages />
</VisualList>
</Info.Data>
</Info.DataSection>
<Info.DataSection>
<Info.DataSectionHeader>
{t("frontend.compat_session_detail.client_details_title")}
</Info.DataSectionHeader>
<Info.DataList>
<Info.Data>
<Info.DataLabel>
{t("frontend.compat_session_detail.name")}
</Info.DataLabel>
<Info.DataValue>{deviceName}</Info.DataValue>
</Info.Data>
<Info.Data>
<Info.DataLabel>{t("frontend.session.uri_label")}</Info.DataLabel>
<Info.DataValue>{data.ssoLogin?.redirectUri}</Info.DataValue>
</Info.Data>
</Info.DataList>
</Info.DataSection>
{!data.finishedAt && <EndCompatSessionButton session={data} size="lg" />}
</div>
);
};

View File

@@ -44,7 +44,7 @@ describe("<OAuth2SessionDetail>", () => {
expect(asFragment()).toMatchSnapshot();
expect(queryByText("Finished")).toBeFalsy();
expect(getByText("Sign out")).toBeTruthy();
expect(getByText("Remove device")).toBeTruthy();
});
it("renders a finished session details", () => {
@@ -62,6 +62,6 @@ describe("<OAuth2SessionDetail>", () => {
expect(asFragment()).toMatchSnapshot();
expect(getByText("Finished")).toBeTruthy();
expect(queryByText("Sign out")).toBeFalsy();
expect(queryByText("Remove device")).toBeFalsy();
});
});

View File

@@ -1,23 +1,19 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { parseISO } from "date-fns";
import { useTranslation } from "react-i18next";
import { type FragmentType, graphql, useFragment } from "../../gql";
import { graphqlRequest } from "../../graphql";
import { getDeviceIdFromScope } from "../../utils/deviceIdFromScope";
import BlockList from "../BlockList/BlockList";
import DateTime from "../DateTime";
import { Link } from "../Link";
import { END_SESSION_MUTATION } from "../OAuth2Session";
import ClientAvatar from "../Session/ClientAvatar";
import EndSessionButton from "../Session/EndSessionButton";
import SessionDetails from "./SessionDetails";
import EndOAuth2SessionButton from "../Session/EndOAuth2SessionButton";
import LastActive from "../Session/LastActive";
import SessionHeader from "./SessionHeader";
import * as Info from "./SessionInfo";
export const FRAGMENT = graphql(/* GraphQL */ `
fragment OAuth2Session_detail on Oauth2Session {
@@ -27,6 +23,15 @@ export const FRAGMENT = graphql(/* GraphQL */ `
finishedAt
lastActiveIp
lastActiveAt
...EndOAuth2SessionButton_session
userAgent {
name
model
os
}
client {
id
clientId
@@ -43,90 +48,129 @@ type Props = {
const OAuth2SessionDetail: React.FC<Props> = ({ session }) => {
const data = useFragment(FRAGMENT, session);
const queryClient = useQueryClient();
const endSession = useMutation({
mutationFn: (id: string) =>
graphqlRequest({ query: END_SESSION_MUTATION, variables: { id } }),
onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: ["sessionsOverview"] });
queryClient.invalidateQueries({ queryKey: ["appSessionList"] });
queryClient.invalidateQueries({
queryKey: ["sessionDetail", data.endOauth2Session.oauth2Session?.id],
});
},
});
const { t } = useTranslation();
const onSessionEnd = async (): Promise<void> => {
await endSession.mutateAsync(data.id);
};
const deviceId = getDeviceIdFromScope(data.scope);
const clientName = data.client.clientName || data.client.clientId;
const finishedAt = data.finishedAt
? [
{
label: t("frontend.session.finished_label"),
value: <DateTime datetime={parseISO(data.finishedAt)} />,
},
]
: [];
const sessionDetails = [...finishedAt];
const clientTitle = (
<Link to="/clients/$id" params={{ id: data.client.id }}>
{t("frontend.oauth2_session_detail.client_title")}
</Link>
);
const clientDetails = [
{
label: t("frontend.oauth2_session_detail.client_details_name"),
value: (
<>
<ClientAvatar
name={data.client.clientName || data.client.clientId}
logoUri={data.client.logoUri || undefined}
size="var(--cpd-space-4x)"
/>
{data.client.clientName}
</>
),
},
{
label: t("frontend.session.client_id_label"),
value: <code>{data.client.clientId}</code>,
},
{
label: t("frontend.session.uri_label"),
value: (
<a
target="_blank"
rel="noreferrer"
href={data.client.clientUri || undefined}
>
{data.client.clientUri}
</a>
),
},
];
const deviceName =
data.userAgent?.model ??
(data.userAgent?.name
? data.userAgent?.os
? t("frontend.session.name_for_platform", {
name: data.userAgent.name,
platform: data.userAgent.os,
})
: data.userAgent.name
: t("frontend.session.unknown_device"));
return (
<BlockList>
<SessionHeader to="/sessions">{deviceId || data.id}</SessionHeader>
<SessionDetails
title={t("frontend.session.title")}
lastActive={data.lastActiveAt ? parseISO(data.lastActiveAt) : undefined}
signedIn={parseISO(data.createdAt)}
deviceId={deviceId}
ipAddress={data.lastActiveIp ?? undefined}
scopes={data.scope.split(" ")}
details={sessionDetails}
/>
<SessionDetails title={clientTitle} details={clientDetails} />
{!data.finishedAt && <EndSessionButton endSession={onSessionEnd} />}
</BlockList>
<div className="flex flex-col gap-10">
<SessionHeader to="/sessions">
{clientName}: {deviceName}
</SessionHeader>
<Info.DataSection>
<Info.DataSectionHeader>
{t("frontend.session.title")}
</Info.DataSectionHeader>
<Info.DataList>
{data.lastActiveAt && (
<Info.Data>
<Info.DataLabel>
{t("frontend.session.last_active_label")}
</Info.DataLabel>
<Info.DataValue>
<LastActive lastActive={parseISO(data.lastActiveAt)} />
</Info.DataValue>
</Info.Data>
)}
<Info.Data>
<Info.DataLabel>
{t("frontend.session.signed_in_label")}
</Info.DataLabel>
<Info.DataValue>
<DateTime datetime={data.createdAt} />
</Info.DataValue>
</Info.Data>
{data.finishedAt && (
<Info.Data>
<Info.DataLabel>
{t("frontend.session.finished_label")}
</Info.DataLabel>
<Info.DataValue>
<DateTime datetime={data.finishedAt} />
</Info.DataValue>
</Info.Data>
)}
<Info.Data>
<Info.DataLabel>
{t("frontend.session.device_id_label")}
</Info.DataLabel>
<Info.DataValue>{deviceId}</Info.DataValue>
</Info.Data>
{data.lastActiveIp && (
<Info.Data>
<Info.DataLabel>{t("frontend.session.ip_label")}</Info.DataLabel>
<Info.DataValue>
<code>{data.lastActiveIp}</code>
</Info.DataValue>
</Info.Data>
)}
</Info.DataList>
<Info.Data>
<Info.DataLabel>{t("frontend.session.scopes_label")}</Info.DataLabel>
<Info.ScopeList scope={data.scope} />
</Info.Data>
</Info.DataSection>
<Info.DataSection>
<Info.DataSectionHeader>
{t("frontend.oauth2_session_detail.client_title")}
</Info.DataSectionHeader>
<Info.DataList>
<Info.Data>
<Info.DataLabel>
{t("frontend.oauth2_session_detail.client_details_name")}
</Info.DataLabel>
<Info.DataValue>
<ClientAvatar
name={data.client.clientName || data.client.clientId}
logoUri={data.client.logoUri || undefined}
size="var(--cpd-space-4x)"
/>
{data.client.clientName}
</Info.DataValue>
</Info.Data>
<Info.Data>
<Info.DataLabel>
{t("frontend.session.client_id_label")}
</Info.DataLabel>
<Info.DataValue>
<code>{data.client.clientId}</code>
</Info.DataValue>
</Info.Data>
<Info.Data>
<Info.DataLabel>{t("frontend.session.uri_label")}</Info.DataLabel>
<Info.DataValue>
<a
target="_blank"
rel="noreferrer"
href={data.client.clientUri || undefined}
>
{data.client.clientUri}
</a>
</Info.DataValue>
</Info.Data>
</Info.DataList>
</Info.DataSection>
{!data.finishedAt && <EndOAuth2SessionButton session={data} size="lg" />}
</div>
);
};

View File

@@ -1,33 +0,0 @@
/* Copyright 2024 New Vector Ltd.
* Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
.wrapper {
display: flex;
flex-wrap: wrap;
gap: var(--cpd-space-4x);
margin-bottom: var(--cpd-space-4x);
margin-top: var(--cpd-space-8x);
}
.wrapper h5 {
color: var(--cpd-color-text-secondary);
}
.wrapper .datum {
width: max-content;
}
.datum {
flex-grow: 1;
max-width: 100%;
}
.datum-value {
font-size: var(--cpd-font-size-body-md);
text-overflow: ellipsis;
overflow: hidden;
}

View File

@@ -1,156 +0,0 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
import IconChat from "@vector-im/compound-design-tokens/assets/web/icons/chat";
import IconComputer from "@vector-im/compound-design-tokens/assets/web/icons/computer";
import IconError from "@vector-im/compound-design-tokens/assets/web/icons/error";
import IconInfo from "@vector-im/compound-design-tokens/assets/web/icons/info";
import IconSend from "@vector-im/compound-design-tokens/assets/web/icons/send";
import IconUserProfile from "@vector-im/compound-design-tokens/assets/web/icons/user-profile";
import { Text } from "@vector-im/compound-web";
import type { ReactNode } from "react";
import { useTranslation } from "react-i18next";
import Block from "../Block/Block";
import DateTime from "../DateTime";
import LastActive from "../Session/LastActive";
import { VisualList, VisualListItem } from "../VisualList/VisualList";
import styles from "./SessionDetails.module.css";
type Detail = { label: string; value: ReactNode };
type Props = {
title: string | ReactNode;
lastActive?: Date;
signedIn?: Date;
deviceId?: string | null;
ipAddress?: string;
scopes?: string[];
details?: Detail[];
};
const Scope: React.FC<{ scope: string }> = ({ scope }) => {
const { t } = useTranslation();
// Filter out "urn:matrix:org.matrix.msc2967.client:device:"
if (scope.startsWith("urn:matrix:org.matrix.msc2967.client:device:")) {
return null;
}
// Needs to be manually kept in sync with /templates/components/scope.html
const scopeMap: Record<string, [number, typeof IconInfo, string][]> = {
openid: [[0, IconUserProfile, t("mas.scope.view_profile")]],
"urn:mas:graphql:*": [
[1, IconInfo, t("mas.scope.edit_profile")],
[2, IconComputer, t("mas.scope.manage_sessions")],
],
"urn:matrix:org.matrix.msc2967.client:api:*": [
[3, IconChat, t("mas.scope.view_messages")],
[4, IconSend, t("mas.scope.send_messages")],
],
"urn:synapse:admin:*": [[5, IconError, t("mas.scope.synapse_admin")]],
"urn:mas:admin": [[6, IconError, t("mas.scope.mas_admin")]],
} as const;
const mappedScopes: [number | string, typeof IconInfo, string][] = scopeMap[
scope
] ?? [[scope, IconInfo, scope]];
return (
<>
{mappedScopes.map(([key, Icon, text]) => (
<VisualListItem key={key} Icon={Icon} label={text} />
))}
</>
);
};
const Datum: React.FC<{ label: string; value: ReactNode }> = ({
label,
value,
}) => {
return (
<div className={styles.datum}>
<Text size="sm" weight="regular" as="h5">
{label}
</Text>
{typeof value === "string" ? (
<Text size="md" className={styles.datumValue}>
{value}
</Text>
) : (
value
)}
</div>
);
};
const SessionDetails: React.FC<Props> = ({
title,
lastActive,
signedIn,
deviceId,
ipAddress,
details,
scopes,
}) => {
const { t } = useTranslation();
return (
<Block title={title}>
<div className={styles.wrapper}>
{lastActive && (
<Datum
label={t("frontend.session.last_active_label")}
value={
<LastActive
className={styles.datumValue}
lastActive={lastActive}
/>
}
/>
)}
{signedIn && (
<Datum
label={t("frontend.session.signed_in_label")}
value={
<DateTime className={styles.datumValue} datetime={signedIn} />
}
/>
)}
{deviceId && (
<Datum
label={t("frontend.session.device_id_label")}
value={deviceId}
/>
)}
{ipAddress && (
<Datum
label={t("frontend.session.ip_label")}
value={<code className={styles.datumValue}>{ipAddress}</code>}
/>
)}
{details?.map(({ label, value }) => (
<Datum key={label} label={label} value={value} />
))}
</div>
{scopes?.length && (
<Datum
label={t("frontend.session.scopes_label")}
value={
<VisualList>
{scopes.map((scope) => (
<Scope key={scope} scope={scope} />
))}
</VisualList>
}
/>
)}
</Block>
);
};
export default SessionDetails;

View File

@@ -0,0 +1,183 @@
// Copyright 2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
import IconChat from "@vector-im/compound-design-tokens/assets/web/icons/chat";
import IconComputer from "@vector-im/compound-design-tokens/assets/web/icons/computer";
import IconError from "@vector-im/compound-design-tokens/assets/web/icons/error";
import IconInfo from "@vector-im/compound-design-tokens/assets/web/icons/info";
import IconSend from "@vector-im/compound-design-tokens/assets/web/icons/send";
import IconUserProfile from "@vector-im/compound-design-tokens/assets/web/icons/user-profile";
import {
Heading,
Separator,
Text,
VisualList,
VisualListItem,
} from "@vector-im/compound-web";
import cx from "classnames";
import type * as React from "react";
import { useTranslation } from "react-i18next";
export const ScopeViewProfile: React.FC = () => {
const { t } = useTranslation();
return (
<VisualListItem Icon={IconUserProfile}>
{t("mas.scope.view_profile")}
</VisualListItem>
);
};
const ScopeEditProfile: React.FC = () => {
const { t } = useTranslation();
return (
<VisualListItem Icon={IconInfo}>
{t("mas.scope.edit_profile")}
</VisualListItem>
);
};
const ScopeManageSessions: React.FC = () => {
const { t } = useTranslation();
return (
<VisualListItem Icon={IconComputer}>
{t("mas.scope.manage_sessions")}
</VisualListItem>
);
};
export const ScopeViewMessages: React.FC = () => {
const { t } = useTranslation();
return (
<VisualListItem Icon={IconChat}>
{t("mas.scope.view_messages")}
</VisualListItem>
);
};
export const ScopeSendMessages: React.FC = () => {
const { t } = useTranslation();
return (
<VisualListItem Icon={IconSend}>
{t("mas.scope.send_messages")}
</VisualListItem>
);
};
const ScopeSynapseAdmin: React.FC = () => {
const { t } = useTranslation();
return (
<VisualListItem Icon={IconError}>
{t("mas.scope.synapse_admin")}
</VisualListItem>
);
};
const ScopeMasAdmin: React.FC = () => {
const { t } = useTranslation();
return (
<VisualListItem Icon={IconError}>{t("mas.scope.mas_admin")}</VisualListItem>
);
};
const ScopeOther: React.FC<{ scope: string }> = ({ scope }) => {
return <VisualListItem Icon={IconInfo}>{scope}</VisualListItem>;
};
const Scope: React.FC<{ scope: string }> = ({ scope }) => {
// Filter out "urn:matrix:org.matrix.msc2967.client:device:"
if (scope.startsWith("urn:matrix:org.matrix.msc2967.client:device:")) {
return null;
}
switch (scope) {
case "openid":
return <ScopeViewProfile />;
case "urn:mas:graphql:*":
return (
<>
<ScopeEditProfile />
<ScopeManageSessions />
</>
);
case "urn:matrix:org.matrix.msc2967.client:api:*":
return (
<>
<ScopeViewMessages />
<ScopeSendMessages />
</>
);
case "urn:synapse:admin:*":
return <ScopeSynapseAdmin />;
case "urn:mas:admin":
return <ScopeMasAdmin />;
default:
return <ScopeOther scope={scope} />;
}
};
export const ScopeList: React.FC<{ scope: string }> = ({ scope }) => {
const scopes = scope.split(" ");
return (
<VisualList className="mt-1">
{scopes.map((scope) => (
<Scope key={scope} scope={scope} />
))}
</VisualList>
);
};
export const Data: React.FC<
React.PropsWithChildren<{ className?: string }>
> = ({ children, className }) => (
<li className={cx("flex flex-col min-w-0", className)}>{children}</li>
);
export const DataLabel: React.FC<
React.PropsWithChildren<{ className?: string }>
> = ({ children, className }) => (
<Text
size="sm"
weight="regular"
as="h5"
className={cx("text-secondary", className)}
>
{children}
</Text>
);
export const DataValue: React.FC<
React.PropsWithChildren<{ className?: string }>
> = ({ children, className }) => (
<Text
size="md"
weight="regular"
className={cx("text-ellipsis overflow-hidden", className)}
>
{children}
</Text>
);
export const DataList: React.FC<
React.PropsWithChildren<{ className?: string }>
> = ({ children, className }) => (
<ul className={cx("flex flex-wrap gap-x-10 gap-y-6", className)}>
{children}
</ul>
);
export const DataSection: React.FC<
React.PropsWithChildren<{ className?: string }>
> = ({ children, className }) => (
<section className={cx("flex flex-col gap-6", className)}>{children}</section>
);
export const DataSectionHeader: React.FC<
React.PropsWithChildren<{ className?: string }>
> = ({ children, className }) => (
<Heading as="h4" size="sm" weight="semibold" className={className}>
{children}
<Separator kind="section" />
</Heading>
);

View File

@@ -3,7 +3,7 @@
exports[`<CompatSessionDetail> > renders a compatability session details 1`] = `
<div>
<div
class="_blockList_f8cc7f"
class="flex flex-col gap-10"
>
<header
class="_header_92353c"
@@ -27,99 +27,116 @@ exports[`<CompatSessionDetail> > renders a compatability session details 1`] = `
<h3
class="_typography_yh5dq_162 _font-heading-md-semibold_yh5dq_121"
>
abcd1234
element.io
:
Unknown device
</h3>
</header>
<div
class="_block_17898c"
<section
class="flex flex-col gap-6"
>
<h4
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102 _title_17898c"
>
Session
</h4>
<div
class="_wrapper_040867"
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
>
Device details
<div
class="_datum_040867"
class="_separator_144s5_17"
data-kind="section"
data-orientation="horizontal"
role="separator"
/>
</h4>
<ul
class="flex flex-wrap gap-x-10 gap-y-6"
>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Last Active
</h5>
<span
class="_datumValue_040867"
title="Sat, 29 Jul 2023, 03:35"
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
>
Inactive for 90+ days
</span>
</div>
<div
class="_datum_040867"
<span
title="Sat, 29 Jul 2023, 03:35"
>
Inactive for 90+ days
</span>
</p>
</li>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Signed in
</h5>
<time
class="_datumValue_040867"
datetime="2023-06-29T03:35:17Z"
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
>
Thu, 29 Jun 2023, 03:35
</time>
</div>
<div
class="_datum_040867"
<time
datetime="2023-06-29T03:35:17Z"
>
Thu, 29 Jun 2023, 03:35
</time>
</p>
</li>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Device ID
</h5>
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 _datumValue_040867"
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
>
abcd1234
</p>
</div>
<div
class="_datum_040867"
</li>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
IP Address
</h5>
<code
class="_datumValue_040867"
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
>
1.2.3.4
</code>
</div>
</div>
<div
class="_datum_040867"
<code>
1.2.3.4
</code>
</p>
</li>
</ul>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Scopes
</h5>
<ul
class="_list_7f22f8"
class="_visual-list_4dcf8_17 mt-1"
>
<li
class="_item_7f22f8"
class="_visual-list-item_bqeu7_17"
>
<svg
color="var(--cpd-color-icon-tertiary)"
aria-hidden="true"
class="_visual-list-item-icon_bqeu7_26"
fill="currentColor"
height="1em"
height="24px"
viewBox="0 0 24 24"
width="1em"
width="24px"
xmlns="http://www.w3.org/2000/svg"
>
<path
@@ -132,42 +149,36 @@ exports[`<CompatSessionDetail> > renders a compatability session details 1`] = `
d="M16.23 18.792a12.47 12.47 0 0 0-1.455-.455 11.6 11.6 0 0 0-5.55 0c-.487.12-.972.271-1.455.455a8.04 8.04 0 0 1-1.729-1.454c.89-.412 1.794-.729 2.709-.95A13.76 13.76 0 0 1 12 16c1.1 0 2.183.13 3.25.387a14.78 14.78 0 0 1 2.709.95 8.042 8.042 0 0 1-1.73 1.455Z"
/>
</svg>
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59"
>
See your profile info and contact details
</p>
See your profile info and contact details
</li>
<li
class="_item_7f22f8"
class="_visual-list-item_bqeu7_17"
>
<svg
color="var(--cpd-color-icon-tertiary)"
aria-hidden="true"
class="_visual-list-item-icon_bqeu7_26"
fill="currentColor"
height="1em"
height="24px"
viewBox="0 0 24 24"
width="1em"
width="24px"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="m1.5 21.25 1.45-4.95a10.232 10.232 0 0 1-.712-2.1A10.167 10.167 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.737 9.737 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.737 9.737 0 0 1 12 22c-.75 0-1.483-.08-2.2-.238a10.23 10.23 0 0 1-2.1-.712L2.75 22.5a.936.936 0 0 1-1-.25.936.936 0 0 1-.25-1Zm2.45-1.2 3.2-.95a.949.949 0 0 1 .275-.063c.1-.008.192-.012.275-.012.15 0 .296.013.438.038.141.024.279.07.412.137a7.435 7.435 0 0 0 1.675.6c.583.133 1.175.2 1.775.2 2.233 0 4.125-.775 5.675-2.325C19.225 16.125 20 14.233 20 12c0-2.233-.775-4.125-2.325-5.675C16.125 4.775 14.233 4 12 4c-2.233 0-4.125.775-5.675 2.325C4.775 7.875 4 9.767 4 12c0 .6.067 1.192.2 1.775s.333 1.142.6 1.675c.117.217.18.446.188.688a2.29 2.29 0 0 1-.088.712l-.95 3.2Z"
/>
</svg>
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59"
>
View your existing messages and data
</p>
View your existing messages and data
</li>
<li
class="_item_7f22f8"
class="_visual-list-item_bqeu7_17"
>
<svg
color="var(--cpd-color-icon-tertiary)"
aria-hidden="true"
class="_visual-list-item-icon_bqeu7_26"
fill="currentColor"
height="1em"
height="24px"
viewBox="0 0 24 24"
width="1em"
width="24px"
xmlns="http://www.w3.org/2000/svg"
>
<path
@@ -176,68 +187,65 @@ exports[`<CompatSessionDetail> > renders a compatability session details 1`] = `
fill-rule="evenodd"
/>
</svg>
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59"
>
Send new messages on your behalf
</p>
Send new messages on your behalf
</li>
</ul>
</div>
</div>
<div
class="_block_17898c"
</li>
</section>
<section
class="flex flex-col gap-6"
>
<h4
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102 _title_17898c"
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
>
Client info
</h4>
<div
class="_wrapper_040867"
>
<div
class="_datum_040867"
class="_separator_144s5_17"
data-kind="section"
data-orientation="horizontal"
role="separator"
/>
</h4>
<ul
class="flex flex-wrap gap-x-10 gap-y-6"
>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Name
</h5>
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 _datumValue_040867"
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
>
element.io
Unknown device
</p>
</div>
<div
class="_datum_040867"
</li>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Uri
</h5>
<a
class="_link_ue21z_17 _externalLink_a97355"
data-kind="primary"
data-size="medium"
href="https://element.io"
rel="noreferrer noopener"
target="_blank"
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
>
https://element.io
</a>
</div>
</div>
</div>
</p>
</li>
</ul>
</section>
<button
aria-controls="radix-:r0:"
aria-expanded="false"
aria-haspopup="dialog"
class="_button_i91xf_17 _has-icon_i91xf_66 _destructive_i91xf_116"
data-kind="secondary"
data-size="sm"
data-size="lg"
data-state="closed"
role="button"
tabindex="0"
@@ -252,10 +260,10 @@ exports[`<CompatSessionDetail> > renders a compatability session details 1`] = `
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9 12.031c0-.283.096-.52.288-.712A.968.968 0 0 1 10 11.03h7.15l-1.875-1.875a.96.96 0 0 1-.3-.7c0-.266.108-.508.325-.725a.93.93 0 0 1 .712-.287.977.977 0 0 1 .688.287l3.6 3.6c.1.1.17.209.212.325.042.117.063.242.063.375 0 .134-.02.259-.063.375a.877.877 0 0 1-.212.325l-3.6 3.6a.93.93 0 0 1-.712.288.977.977 0 0 1-.688-.288 1.02 1.02 0 0 1-.313-.712.931.931 0 0 1 .288-.713l1.875-1.875H10a.968.968 0 0 1-.712-.287A.968.968 0 0 1 9 12.03Zm-6-7c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 5 3.03h6a.97.97 0 0 1 .713.288.968.968 0 0 1 .287.712.97.97 0 0 1-.287.713.968.968 0 0 1-.713.287H5v14h6a.97.97 0 0 1 .713.288.968.968 0 0 1 .287.712.97.97 0 0 1-.287.713.968.968 0 0 1-.713.287H5c-.55 0-1.02-.196-1.412-.587A1.926 1.926 0 0 1 3 19.03v-14Z"
d="M7 21c-.55 0-1.02-.196-1.412-.587A1.926 1.926 0 0 1 5 19V6a.968.968 0 0 1-.713-.287A.968.968 0 0 1 4 5c0-.283.096-.52.287-.713A.968.968 0 0 1 5 4h4a.97.97 0 0 1 .287-.712A.968.968 0 0 1 10 3h4a.97.97 0 0 1 .713.288A.968.968 0 0 1 15 4h4a.97.97 0 0 1 .712.287c.192.192.288.43.288.713s-.096.52-.288.713A.968.968 0 0 1 19 6v13c0 .55-.196 1.02-.587 1.413A1.926 1.926 0 0 1 17 21H7ZM7 6v13h10V6H7Zm2 10c0 .283.096.52.287.712.192.192.43.288.713.288s.52-.096.713-.288A.968.968 0 0 0 11 16V9a.967.967 0 0 0-.287-.713A.968.968 0 0 0 10 8a.968.968 0 0 0-.713.287A.968.968 0 0 0 9 9v7Zm4 0c0 .283.096.52.287.712.192.192.43.288.713.288s.52-.096.713-.288A.968.968 0 0 0 15 16V9a.967.967 0 0 0-.287-.713A.968.968 0 0 0 14 8a.968.968 0 0 0-.713.287A.967.967 0 0 0 13 9v7Z"
/>
</svg>
Sign out
Remove device
</button>
</div>
</div>
@@ -264,7 +272,7 @@ exports[`<CompatSessionDetail> > renders a compatability session details 1`] = `
exports[`<CompatSessionDetail> > renders a compatability session without an ssoLogin 1`] = `
<div>
<div
class="_blockList_f8cc7f"
class="flex flex-col gap-10"
>
<header
class="_header_92353c"
@@ -289,98 +297,115 @@ exports[`<CompatSessionDetail> > renders a compatability session without an ssoL
class="_typography_yh5dq_162 _font-heading-md-semibold_yh5dq_121"
>
abcd1234
:
Unknown device
</h3>
</header>
<div
class="_block_17898c"
<section
class="flex flex-col gap-6"
>
<h4
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102 _title_17898c"
>
Session
</h4>
<div
class="_wrapper_040867"
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
>
Device details
<div
class="_datum_040867"
class="_separator_144s5_17"
data-kind="section"
data-orientation="horizontal"
role="separator"
/>
</h4>
<ul
class="flex flex-wrap gap-x-10 gap-y-6"
>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Last Active
</h5>
<span
class="_datumValue_040867"
title="Sat, 29 Jul 2023, 03:35"
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
>
Inactive for 90+ days
</span>
</div>
<div
class="_datum_040867"
<span
title="Sat, 29 Jul 2023, 03:35"
>
Inactive for 90+ days
</span>
</p>
</li>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Signed in
</h5>
<time
class="_datumValue_040867"
datetime="2023-06-29T03:35:17Z"
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
>
Thu, 29 Jun 2023, 03:35
</time>
</div>
<div
class="_datum_040867"
<time
datetime="2023-06-29T03:35:17Z"
>
Thu, 29 Jun 2023, 03:35
</time>
</p>
</li>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Device ID
</h5>
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 _datumValue_040867"
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
>
abcd1234
</p>
</div>
<div
class="_datum_040867"
</li>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
IP Address
</h5>
<code
class="_datumValue_040867"
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
>
1.2.3.4
</code>
</div>
</div>
<div
class="_datum_040867"
<code>
1.2.3.4
</code>
</p>
</li>
</ul>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Scopes
</h5>
<ul
class="_list_7f22f8"
class="_visual-list_4dcf8_17 mt-1"
>
<li
class="_item_7f22f8"
class="_visual-list-item_bqeu7_17"
>
<svg
color="var(--cpd-color-icon-tertiary)"
aria-hidden="true"
class="_visual-list-item-icon_bqeu7_26"
fill="currentColor"
height="1em"
height="24px"
viewBox="0 0 24 24"
width="1em"
width="24px"
xmlns="http://www.w3.org/2000/svg"
>
<path
@@ -393,42 +418,36 @@ exports[`<CompatSessionDetail> > renders a compatability session without an ssoL
d="M16.23 18.792a12.47 12.47 0 0 0-1.455-.455 11.6 11.6 0 0 0-5.55 0c-.487.12-.972.271-1.455.455a8.04 8.04 0 0 1-1.729-1.454c.89-.412 1.794-.729 2.709-.95A13.76 13.76 0 0 1 12 16c1.1 0 2.183.13 3.25.387a14.78 14.78 0 0 1 2.709.95 8.042 8.042 0 0 1-1.73 1.455Z"
/>
</svg>
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59"
>
See your profile info and contact details
</p>
See your profile info and contact details
</li>
<li
class="_item_7f22f8"
class="_visual-list-item_bqeu7_17"
>
<svg
color="var(--cpd-color-icon-tertiary)"
aria-hidden="true"
class="_visual-list-item-icon_bqeu7_26"
fill="currentColor"
height="1em"
height="24px"
viewBox="0 0 24 24"
width="1em"
width="24px"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="m1.5 21.25 1.45-4.95a10.232 10.232 0 0 1-.712-2.1A10.167 10.167 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.737 9.737 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.737 9.737 0 0 1 12 22c-.75 0-1.483-.08-2.2-.238a10.23 10.23 0 0 1-2.1-.712L2.75 22.5a.936.936 0 0 1-1-.25.936.936 0 0 1-.25-1Zm2.45-1.2 3.2-.95a.949.949 0 0 1 .275-.063c.1-.008.192-.012.275-.012.15 0 .296.013.438.038.141.024.279.07.412.137a7.435 7.435 0 0 0 1.675.6c.583.133 1.175.2 1.775.2 2.233 0 4.125-.775 5.675-2.325C19.225 16.125 20 14.233 20 12c0-2.233-.775-4.125-2.325-5.675C16.125 4.775 14.233 4 12 4c-2.233 0-4.125.775-5.675 2.325C4.775 7.875 4 9.767 4 12c0 .6.067 1.192.2 1.775s.333 1.142.6 1.675c.117.217.18.446.188.688a2.29 2.29 0 0 1-.088.712l-.95 3.2Z"
/>
</svg>
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59"
>
View your existing messages and data
</p>
View your existing messages and data
</li>
<li
class="_item_7f22f8"
class="_visual-list-item_bqeu7_17"
>
<svg
color="var(--cpd-color-icon-tertiary)"
aria-hidden="true"
class="_visual-list-item-icon_bqeu7_26"
fill="currentColor"
height="1em"
height="24px"
viewBox="0 0 24 24"
width="1em"
width="24px"
xmlns="http://www.w3.org/2000/svg"
>
<path
@@ -437,22 +456,63 @@ exports[`<CompatSessionDetail> > renders a compatability session without an ssoL
fill-rule="evenodd"
/>
</svg>
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59"
>
Send new messages on your behalf
</p>
Send new messages on your behalf
</li>
</ul>
</div>
</div>
</li>
</section>
<section
class="flex flex-col gap-6"
>
<h4
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
>
Client info
<div
class="_separator_144s5_17"
data-kind="section"
data-orientation="horizontal"
role="separator"
/>
</h4>
<ul
class="flex flex-wrap gap-x-10 gap-y-6"
>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Name
</h5>
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
>
Unknown device
</p>
</li>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Uri
</h5>
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
/>
</li>
</ul>
</section>
<button
aria-controls="radix-:r3:"
aria-expanded="false"
aria-haspopup="dialog"
class="_button_i91xf_17 _has-icon_i91xf_66 _destructive_i91xf_116"
data-kind="secondary"
data-size="sm"
data-size="lg"
data-state="closed"
role="button"
tabindex="0"
@@ -467,10 +527,10 @@ exports[`<CompatSessionDetail> > renders a compatability session without an ssoL
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9 12.031c0-.283.096-.52.288-.712A.968.968 0 0 1 10 11.03h7.15l-1.875-1.875a.96.96 0 0 1-.3-.7c0-.266.108-.508.325-.725a.93.93 0 0 1 .712-.287.977.977 0 0 1 .688.287l3.6 3.6c.1.1.17.209.212.325.042.117.063.242.063.375 0 .134-.02.259-.063.375a.877.877 0 0 1-.212.325l-3.6 3.6a.93.93 0 0 1-.712.288.977.977 0 0 1-.688-.288 1.02 1.02 0 0 1-.313-.712.931.931 0 0 1 .288-.713l1.875-1.875H10a.968.968 0 0 1-.712-.287A.968.968 0 0 1 9 12.03Zm-6-7c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 5 3.03h6a.97.97 0 0 1 .713.288.968.968 0 0 1 .287.712.97.97 0 0 1-.287.713.968.968 0 0 1-.713.287H5v14h6a.97.97 0 0 1 .713.288.968.968 0 0 1 .287.712.97.97 0 0 1-.287.713.968.968 0 0 1-.713.287H5c-.55 0-1.02-.196-1.412-.587A1.926 1.926 0 0 1 3 19.03v-14Z"
d="M7 21c-.55 0-1.02-.196-1.412-.587A1.926 1.926 0 0 1 5 19V6a.968.968 0 0 1-.713-.287A.968.968 0 0 1 4 5c0-.283.096-.52.287-.713A.968.968 0 0 1 5 4h4a.97.97 0 0 1 .287-.712A.968.968 0 0 1 10 3h4a.97.97 0 0 1 .713.288A.968.968 0 0 1 15 4h4a.97.97 0 0 1 .712.287c.192.192.288.43.288.713s-.096.52-.288.713A.968.968 0 0 1 19 6v13c0 .55-.196 1.02-.587 1.413A1.926 1.926 0 0 1 17 21H7ZM7 6v13h10V6H7Zm2 10c0 .283.096.52.287.712.192.192.43.288.713.288s.52-.096.713-.288A.968.968 0 0 0 11 16V9a.967.967 0 0 0-.287-.713A.968.968 0 0 0 10 8a.968.968 0 0 0-.713.287A.968.968 0 0 0 9 9v7Zm4 0c0 .283.096.52.287.712.192.192.43.288.713.288s.52-.096.713-.288A.968.968 0 0 0 15 16V9a.967.967 0 0 0-.287-.713A.968.968 0 0 0 14 8a.968.968 0 0 0-.713.287A.967.967 0 0 0 13 9v7Z"
/>
</svg>
Sign out
Remove device
</button>
</div>
</div>
@@ -479,7 +539,7 @@ exports[`<CompatSessionDetail> > renders a compatability session without an ssoL
exports[`<CompatSessionDetail> > renders a finished compatability session details 1`] = `
<div>
<div
class="_blockList_f8cc7f"
class="flex flex-col gap-10"
>
<header
class="_header_92353c"
@@ -503,113 +563,134 @@ exports[`<CompatSessionDetail> > renders a finished compatability session detail
<h3
class="_typography_yh5dq_162 _font-heading-md-semibold_yh5dq_121"
>
abcd1234
element.io
:
Unknown device
</h3>
</header>
<div
class="_block_17898c"
<section
class="flex flex-col gap-6"
>
<h4
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102 _title_17898c"
>
Session
</h4>
<div
class="_wrapper_040867"
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
>
Device details
<div
class="_datum_040867"
class="_separator_144s5_17"
data-kind="section"
data-orientation="horizontal"
role="separator"
/>
</h4>
<ul
class="flex flex-wrap gap-x-10 gap-y-6"
>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Last Active
</h5>
<span
class="_datumValue_040867"
title="Sat, 29 Jul 2023, 03:35"
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
>
Inactive for 90+ days
</span>
</div>
<div
class="_datum_040867"
<span
title="Sat, 29 Jul 2023, 03:35"
>
Inactive for 90+ days
</span>
</p>
</li>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Signed in
</h5>
<time
class="_datumValue_040867"
datetime="2023-06-29T03:35:17Z"
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
>
Thu, 29 Jun 2023, 03:35
</time>
</div>
<div
class="_datum_040867"
<time
datetime="2023-06-29T03:35:17Z"
>
Thu, 29 Jun 2023, 03:35
</time>
</p>
</li>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Finished
</h5>
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
>
<time
datetime="2023-07-29T03:35:17Z"
>
Sat, 29 Jul 2023, 03:35
</time>
</p>
</li>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Device ID
</h5>
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 _datumValue_040867"
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
>
abcd1234
</p>
</div>
<div
class="_datum_040867"
</li>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
IP Address
</h5>
<code
class="_datumValue_040867"
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
>
1.2.3.4
</code>
</div>
<div
class="_datum_040867"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
>
Finished
</h5>
<time
datetime="2023-07-29T03:35:17Z"
>
Sat, 29 Jul 2023, 03:35
</time>
</div>
</div>
<div
class="_datum_040867"
<code>
1.2.3.4
</code>
</p>
</li>
</ul>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Scopes
</h5>
<ul
class="_list_7f22f8"
class="_visual-list_4dcf8_17 mt-1"
>
<li
class="_item_7f22f8"
class="_visual-list-item_bqeu7_17"
>
<svg
color="var(--cpd-color-icon-tertiary)"
aria-hidden="true"
class="_visual-list-item-icon_bqeu7_26"
fill="currentColor"
height="1em"
height="24px"
viewBox="0 0 24 24"
width="1em"
width="24px"
xmlns="http://www.w3.org/2000/svg"
>
<path
@@ -622,42 +703,36 @@ exports[`<CompatSessionDetail> > renders a finished compatability session detail
d="M16.23 18.792a12.47 12.47 0 0 0-1.455-.455 11.6 11.6 0 0 0-5.55 0c-.487.12-.972.271-1.455.455a8.04 8.04 0 0 1-1.729-1.454c.89-.412 1.794-.729 2.709-.95A13.76 13.76 0 0 1 12 16c1.1 0 2.183.13 3.25.387a14.78 14.78 0 0 1 2.709.95 8.042 8.042 0 0 1-1.73 1.455Z"
/>
</svg>
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59"
>
See your profile info and contact details
</p>
See your profile info and contact details
</li>
<li
class="_item_7f22f8"
class="_visual-list-item_bqeu7_17"
>
<svg
color="var(--cpd-color-icon-tertiary)"
aria-hidden="true"
class="_visual-list-item-icon_bqeu7_26"
fill="currentColor"
height="1em"
height="24px"
viewBox="0 0 24 24"
width="1em"
width="24px"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="m1.5 21.25 1.45-4.95a10.232 10.232 0 0 1-.712-2.1A10.167 10.167 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.737 9.737 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.737 9.737 0 0 1 12 22c-.75 0-1.483-.08-2.2-.238a10.23 10.23 0 0 1-2.1-.712L2.75 22.5a.936.936 0 0 1-1-.25.936.936 0 0 1-.25-1Zm2.45-1.2 3.2-.95a.949.949 0 0 1 .275-.063c.1-.008.192-.012.275-.012.15 0 .296.013.438.038.141.024.279.07.412.137a7.435 7.435 0 0 0 1.675.6c.583.133 1.175.2 1.775.2 2.233 0 4.125-.775 5.675-2.325C19.225 16.125 20 14.233 20 12c0-2.233-.775-4.125-2.325-5.675C16.125 4.775 14.233 4 12 4c-2.233 0-4.125.775-5.675 2.325C4.775 7.875 4 9.767 4 12c0 .6.067 1.192.2 1.775s.333 1.142.6 1.675c.117.217.18.446.188.688a2.29 2.29 0 0 1-.088.712l-.95 3.2Z"
/>
</svg>
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59"
>
View your existing messages and data
</p>
View your existing messages and data
</li>
<li
class="_item_7f22f8"
class="_visual-list-item_bqeu7_17"
>
<svg
color="var(--cpd-color-icon-tertiary)"
aria-hidden="true"
class="_visual-list-item-icon_bqeu7_26"
fill="currentColor"
height="1em"
height="24px"
viewBox="0 0 24 24"
width="1em"
width="24px"
xmlns="http://www.w3.org/2000/svg"
>
<path
@@ -666,61 +741,58 @@ exports[`<CompatSessionDetail> > renders a finished compatability session detail
fill-rule="evenodd"
/>
</svg>
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59"
>
Send new messages on your behalf
</p>
Send new messages on your behalf
</li>
</ul>
</div>
</div>
<div
class="_block_17898c"
</li>
</section>
<section
class="flex flex-col gap-6"
>
<h4
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102 _title_17898c"
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
>
Client info
</h4>
<div
class="_wrapper_040867"
>
<div
class="_datum_040867"
class="_separator_144s5_17"
data-kind="section"
data-orientation="horizontal"
role="separator"
/>
</h4>
<ul
class="flex flex-wrap gap-x-10 gap-y-6"
>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Name
</h5>
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 _datumValue_040867"
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
>
element.io
Unknown device
</p>
</div>
<div
class="_datum_040867"
</li>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Uri
</h5>
<a
class="_link_ue21z_17 _externalLink_a97355"
data-kind="primary"
data-size="medium"
href="https://element.io"
rel="noreferrer noopener"
target="_blank"
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
>
https://element.io
</a>
</div>
</div>
</div>
</p>
</li>
</ul>
</section>
</div>
</div>
`;

View File

@@ -3,7 +3,7 @@
exports[`<OAuth2SessionDetail> > renders a finished session details 1`] = `
<DocumentFragment>
<div
class="_blockList_f8cc7f"
class="flex flex-col gap-10"
>
<header
class="_header_92353c"
@@ -27,113 +27,132 @@ exports[`<OAuth2SessionDetail> > renders a finished session details 1`] = `
<h3
class="_typography_yh5dq_162 _font-heading-md-semibold_yh5dq_121"
>
abcd1234
Element: Unknown device
</h3>
</header>
<div
class="_block_17898c"
<section
class="flex flex-col gap-6"
>
<h4
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102 _title_17898c"
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
>
Device details
</h4>
<div
class="_wrapper_040867"
>
<div
class="_datum_040867"
class="_separator_144s5_17"
data-kind="section"
data-orientation="horizontal"
role="separator"
/>
</h4>
<ul
class="flex flex-wrap gap-x-10 gap-y-6"
>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Last Active
</h5>
<span
class="_datumValue_040867"
title="Sat, 29 Jul 2023, 03:35"
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
>
Inactive for 90+ days
</span>
</div>
<div
class="_datum_040867"
<span
title="Sat, 29 Jul 2023, 03:35"
>
Inactive for 90+ days
</span>
</p>
</li>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Signed in
</h5>
<time
class="_datumValue_040867"
datetime="2023-06-29T03:35:17Z"
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
>
Thu, 29 Jun 2023, 03:35
</time>
</div>
<div
class="_datum_040867"
<time
datetime="2023-06-29T03:35:17Z"
>
Thu, 29 Jun 2023, 03:35
</time>
</p>
</li>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Finished
</h5>
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
>
<time
datetime="2023-07-29T03:35:17Z"
>
Sat, 29 Jul 2023, 03:35
</time>
</p>
</li>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Device ID
</h5>
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 _datumValue_040867"
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
>
abcd1234
</p>
</div>
<div
class="_datum_040867"
</li>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
IP Address
</h5>
<code
class="_datumValue_040867"
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
>
1.2.3.4
</code>
</div>
<div
class="_datum_040867"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
>
Finished
</h5>
<time
datetime="2023-07-29T03:35:17Z"
>
Sat, 29 Jul 2023, 03:35
</time>
</div>
</div>
<div
class="_datum_040867"
<code>
1.2.3.4
</code>
</p>
</li>
</ul>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Scopes
</h5>
<ul
class="_list_7f22f8"
class="_visual-list_4dcf8_17 mt-1"
>
<li
class="_item_7f22f8"
class="_visual-list-item_bqeu7_17"
>
<svg
color="var(--cpd-color-icon-tertiary)"
aria-hidden="true"
class="_visual-list-item-icon_bqeu7_26"
fill="currentColor"
height="1em"
height="24px"
viewBox="0 0 24 24"
width="1em"
width="24px"
xmlns="http://www.w3.org/2000/svg"
>
<path
@@ -146,42 +165,36 @@ exports[`<OAuth2SessionDetail> > renders a finished session details 1`] = `
d="M16.23 18.792a12.47 12.47 0 0 0-1.455-.455 11.6 11.6 0 0 0-5.55 0c-.487.12-.972.271-1.455.455a8.04 8.04 0 0 1-1.729-1.454c.89-.412 1.794-.729 2.709-.95A13.76 13.76 0 0 1 12 16c1.1 0 2.183.13 3.25.387a14.78 14.78 0 0 1 2.709.95 8.042 8.042 0 0 1-1.73 1.455Z"
/>
</svg>
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59"
>
See your profile info and contact details
</p>
See your profile info and contact details
</li>
<li
class="_item_7f22f8"
class="_visual-list-item_bqeu7_17"
>
<svg
color="var(--cpd-color-icon-tertiary)"
aria-hidden="true"
class="_visual-list-item-icon_bqeu7_26"
fill="currentColor"
height="1em"
height="24px"
viewBox="0 0 24 24"
width="1em"
width="24px"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="m1.5 21.25 1.45-4.95a10.232 10.232 0 0 1-.712-2.1A10.167 10.167 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.737 9.737 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.737 9.737 0 0 1 12 22c-.75 0-1.483-.08-2.2-.238a10.23 10.23 0 0 1-2.1-.712L2.75 22.5a.936.936 0 0 1-1-.25.936.936 0 0 1-.25-1Zm2.45-1.2 3.2-.95a.949.949 0 0 1 .275-.063c.1-.008.192-.012.275-.012.15 0 .296.013.438.038.141.024.279.07.412.137a7.435 7.435 0 0 0 1.675.6c.583.133 1.175.2 1.775.2 2.233 0 4.125-.775 5.675-2.325C19.225 16.125 20 14.233 20 12c0-2.233-.775-4.125-2.325-5.675C16.125 4.775 14.233 4 12 4c-2.233 0-4.125.775-5.675 2.325C4.775 7.875 4 9.767 4 12c0 .6.067 1.192.2 1.775s.333 1.142.6 1.675c.117.217.18.446.188.688a2.29 2.29 0 0 1-.088.712l-.95 3.2Z"
/>
</svg>
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59"
>
View your existing messages and data
</p>
View your existing messages and data
</li>
<li
class="_item_7f22f8"
class="_visual-list-item_bqeu7_17"
>
<svg
color="var(--cpd-color-icon-tertiary)"
aria-hidden="true"
class="_visual-list-item-icon_bqeu7_26"
fill="currentColor"
height="1em"
height="24px"
viewBox="0 0 24 24"
width="1em"
width="24px"
xmlns="http://www.w3.org/2000/svg"
>
<path
@@ -190,74 +203,80 @@ exports[`<OAuth2SessionDetail> > renders a finished session details 1`] = `
fill-rule="evenodd"
/>
</svg>
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59"
>
Send new messages on your behalf
</p>
Send new messages on your behalf
</li>
</ul>
</div>
</div>
<div
class="_block_17898c"
</li>
</section>
<section
class="flex flex-col gap-6"
>
<h4
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102 _title_17898c"
>
<a
class="_link_ue21z_17"
data-kind="primary"
data-size="medium"
href="/clients/test-id"
rel="noreferrer noopener"
>
Client info
</a>
</h4>
<div
class="_wrapper_040867"
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
>
Client info
<div
class="_datum_040867"
class="_separator_144s5_17"
data-kind="section"
data-orientation="horizontal"
role="separator"
/>
</h4>
<ul
class="flex flex-wrap gap-x-10 gap-y-6"
>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Name
</h5>
Element
</div>
<div
class="_datum_040867"
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
>
Element
</p>
</li>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Client ID
</h5>
<code>
test-client-id
</code>
</div>
<div
class="_datum_040867"
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
>
<code>
test-client-id
</code>
</p>
</li>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Uri
</h5>
<a
href="https://element.io"
rel="noreferrer"
target="_blank"
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
>
https://element.io
</a>
</div>
</div>
</div>
<a
href="https://element.io"
rel="noreferrer"
target="_blank"
>
https://element.io
</a>
</p>
</li>
</ul>
</section>
</div>
</DocumentFragment>
`;
@@ -265,7 +284,7 @@ exports[`<OAuth2SessionDetail> > renders a finished session details 1`] = `
exports[`<OAuth2SessionDetail> > renders session details 1`] = `
<DocumentFragment>
<div
class="_blockList_f8cc7f"
class="flex flex-col gap-10"
>
<header
class="_header_92353c"
@@ -289,99 +308,114 @@ exports[`<OAuth2SessionDetail> > renders session details 1`] = `
<h3
class="_typography_yh5dq_162 _font-heading-md-semibold_yh5dq_121"
>
abcd1234
Element: Unknown device
</h3>
</header>
<div
class="_block_17898c"
<section
class="flex flex-col gap-6"
>
<h4
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102 _title_17898c"
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
>
Device details
</h4>
<div
class="_wrapper_040867"
>
<div
class="_datum_040867"
class="_separator_144s5_17"
data-kind="section"
data-orientation="horizontal"
role="separator"
/>
</h4>
<ul
class="flex flex-wrap gap-x-10 gap-y-6"
>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Last Active
</h5>
<span
class="_datumValue_040867"
title="Sat, 29 Jul 2023, 03:35"
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
>
Inactive for 90+ days
</span>
</div>
<div
class="_datum_040867"
<span
title="Sat, 29 Jul 2023, 03:35"
>
Inactive for 90+ days
</span>
</p>
</li>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Signed in
</h5>
<time
class="_datumValue_040867"
datetime="2023-06-29T03:35:17Z"
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
>
Thu, 29 Jun 2023, 03:35
</time>
</div>
<div
class="_datum_040867"
<time
datetime="2023-06-29T03:35:17Z"
>
Thu, 29 Jun 2023, 03:35
</time>
</p>
</li>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Device ID
</h5>
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 _datumValue_040867"
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
>
abcd1234
</p>
</div>
<div
class="_datum_040867"
</li>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
IP Address
</h5>
<code
class="_datumValue_040867"
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
>
1.2.3.4
</code>
</div>
</div>
<div
class="_datum_040867"
<code>
1.2.3.4
</code>
</p>
</li>
</ul>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Scopes
</h5>
<ul
class="_list_7f22f8"
class="_visual-list_4dcf8_17 mt-1"
>
<li
class="_item_7f22f8"
class="_visual-list-item_bqeu7_17"
>
<svg
color="var(--cpd-color-icon-tertiary)"
aria-hidden="true"
class="_visual-list-item-icon_bqeu7_26"
fill="currentColor"
height="1em"
height="24px"
viewBox="0 0 24 24"
width="1em"
width="24px"
xmlns="http://www.w3.org/2000/svg"
>
<path
@@ -394,42 +428,36 @@ exports[`<OAuth2SessionDetail> > renders session details 1`] = `
d="M16.23 18.792a12.47 12.47 0 0 0-1.455-.455 11.6 11.6 0 0 0-5.55 0c-.487.12-.972.271-1.455.455a8.04 8.04 0 0 1-1.729-1.454c.89-.412 1.794-.729 2.709-.95A13.76 13.76 0 0 1 12 16c1.1 0 2.183.13 3.25.387a14.78 14.78 0 0 1 2.709.95 8.042 8.042 0 0 1-1.73 1.455Z"
/>
</svg>
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59"
>
See your profile info and contact details
</p>
See your profile info and contact details
</li>
<li
class="_item_7f22f8"
class="_visual-list-item_bqeu7_17"
>
<svg
color="var(--cpd-color-icon-tertiary)"
aria-hidden="true"
class="_visual-list-item-icon_bqeu7_26"
fill="currentColor"
height="1em"
height="24px"
viewBox="0 0 24 24"
width="1em"
width="24px"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="m1.5 21.25 1.45-4.95a10.232 10.232 0 0 1-.712-2.1A10.167 10.167 0 0 1 2 12a9.74 9.74 0 0 1 .788-3.9 10.099 10.099 0 0 1 2.137-3.175c.9-.9 1.958-1.612 3.175-2.137A9.737 9.737 0 0 1 12 2a9.74 9.74 0 0 1 3.9.788 10.098 10.098 0 0 1 3.175 2.137c.9.9 1.613 1.958 2.137 3.175A9.738 9.738 0 0 1 22 12a9.738 9.738 0 0 1-.788 3.9 10.098 10.098 0 0 1-2.137 3.175c-.9.9-1.958 1.613-3.175 2.137A9.737 9.737 0 0 1 12 22c-.75 0-1.483-.08-2.2-.238a10.23 10.23 0 0 1-2.1-.712L2.75 22.5a.936.936 0 0 1-1-.25.936.936 0 0 1-.25-1Zm2.45-1.2 3.2-.95a.949.949 0 0 1 .275-.063c.1-.008.192-.012.275-.012.15 0 .296.013.438.038.141.024.279.07.412.137a7.435 7.435 0 0 0 1.675.6c.583.133 1.175.2 1.775.2 2.233 0 4.125-.775 5.675-2.325C19.225 16.125 20 14.233 20 12c0-2.233-.775-4.125-2.325-5.675C16.125 4.775 14.233 4 12 4c-2.233 0-4.125.775-5.675 2.325C4.775 7.875 4 9.767 4 12c0 .6.067 1.192.2 1.775s.333 1.142.6 1.675c.117.217.18.446.188.688a2.29 2.29 0 0 1-.088.712l-.95 3.2Z"
/>
</svg>
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59"
>
View your existing messages and data
</p>
View your existing messages and data
</li>
<li
class="_item_7f22f8"
class="_visual-list-item_bqeu7_17"
>
<svg
color="var(--cpd-color-icon-tertiary)"
aria-hidden="true"
class="_visual-list-item-icon_bqeu7_26"
fill="currentColor"
height="1em"
height="24px"
viewBox="0 0 24 24"
width="1em"
width="24px"
xmlns="http://www.w3.org/2000/svg"
>
<path
@@ -438,81 +466,87 @@ exports[`<OAuth2SessionDetail> > renders session details 1`] = `
fill-rule="evenodd"
/>
</svg>
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59"
>
Send new messages on your behalf
</p>
Send new messages on your behalf
</li>
</ul>
</div>
</div>
<div
class="_block_17898c"
</li>
</section>
<section
class="flex flex-col gap-6"
>
<h4
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102 _title_17898c"
>
<a
class="_link_ue21z_17"
data-kind="primary"
data-size="medium"
href="/clients/test-id"
rel="noreferrer noopener"
>
Client info
</a>
</h4>
<div
class="_wrapper_040867"
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102"
>
Client info
<div
class="_datum_040867"
class="_separator_144s5_17"
data-kind="section"
data-orientation="horizontal"
role="separator"
/>
</h4>
<ul
class="flex flex-wrap gap-x-10 gap-y-6"
>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Name
</h5>
Element
</div>
<div
class="_datum_040867"
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
>
Element
</p>
</li>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Client ID
</h5>
<code>
test-client-id
</code>
</div>
<div
class="_datum_040867"
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
>
<code>
test-client-id
</code>
</p>
</li>
<li
class="flex flex-col min-w-0"
>
<h5
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40"
class="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 text-secondary"
>
Uri
</h5>
<a
href="https://element.io"
rel="noreferrer"
target="_blank"
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 text-ellipsis overflow-hidden"
>
https://element.io
</a>
</div>
</div>
</div>
<a
href="https://element.io"
rel="noreferrer"
target="_blank"
>
https://element.io
</a>
</p>
</li>
</ul>
</section>
<button
aria-controls="radix-:r0:"
aria-expanded="false"
aria-haspopup="dialog"
class="_button_i91xf_17 _has-icon_i91xf_66 _destructive_i91xf_116"
data-kind="secondary"
data-size="sm"
data-size="lg"
data-state="closed"
role="button"
tabindex="0"
@@ -527,10 +561,10 @@ exports[`<OAuth2SessionDetail> > renders session details 1`] = `
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9 12.031c0-.283.096-.52.288-.712A.968.968 0 0 1 10 11.03h7.15l-1.875-1.875a.96.96 0 0 1-.3-.7c0-.266.108-.508.325-.725a.93.93 0 0 1 .712-.287.977.977 0 0 1 .688.287l3.6 3.6c.1.1.17.209.212.325.042.117.063.242.063.375 0 .134-.02.259-.063.375a.877.877 0 0 1-.212.325l-3.6 3.6a.93.93 0 0 1-.712.288.977.977 0 0 1-.688-.288 1.02 1.02 0 0 1-.313-.712.931.931 0 0 1 .288-.713l1.875-1.875H10a.968.968 0 0 1-.712-.287A.968.968 0 0 1 9 12.03Zm-6-7c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 5 3.03h6a.97.97 0 0 1 .713.288.968.968 0 0 1 .287.712.97.97 0 0 1-.287.713.968.968 0 0 1-.713.287H5v14h6a.97.97 0 0 1 .713.288.968.968 0 0 1 .287.712.97.97 0 0 1-.287.713.968.968 0 0 1-.713.287H5c-.55 0-1.02-.196-1.412-.587A1.926 1.926 0 0 1 3 19.03v-14Z"
d="M7 21c-.55 0-1.02-.196-1.412-.587A1.926 1.926 0 0 1 5 19V6a.968.968 0 0 1-.713-.287A.968.968 0 0 1 4 5c0-.283.096-.52.287-.713A.968.968 0 0 1 5 4h4a.97.97 0 0 1 .287-.712A.968.968 0 0 1 10 3h4a.97.97 0 0 1 .713.288A.968.968 0 0 1 15 4h4a.97.97 0 0 1 .712.287c.192.192.288.43.288.713s-.096.52-.288.713A.968.968 0 0 1 19 6v13c0 .55-.196 1.02-.587 1.413A1.926 1.926 0 0 1 17 21H7ZM7 6v13h10V6H7Zm2 10c0 .283.096.52.287.712.192.192.43.288.713.288s.52-.096.713-.288A.968.968 0 0 0 11 16V9a.967.967 0 0 0-.287-.713A.968.968 0 0 0 10 8a.968.968 0 0 0-.713.287A.968.968 0 0 0 9 9v7Zm4 0c0 .283.096.52.287.712.192.192.43.288.713.288s.52-.096.713-.288A.968.968 0 0 0 15 16V9a.967.967 0 0 0-.287-.713A.968.968 0 0 0 14 8a.968.968 0 0 0-.713.287A.967.967 0 0 0 13 9v7Z"
/>
</svg>
Sign out
Remove device
</button>
</div>
</DocumentFragment>

View File

@@ -1,28 +0,0 @@
/* Copyright 2024 New Vector Ltd.
* Copyright 2024 The Matrix.org Foundation C.I.C.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
.list {
display: flex;
flex-direction: column;
gap: var(--cpd-space-scale);
border-radius: var(--cpd-space-5x);
overflow: hidden;
}
.item {
background: var(--cpd-color-bg-action-secondary-hovered);
padding: var(--cpd-space-3x) var(--cpd-space-5x);
display: flex;
align-items: center;
gap: var(--cpd-space-3x);
}
.item svg {
inline-size: var(--cpd-space-6x);
block-size: var(--cpd-space-6x);
flex-shrink: 0;
}

View File

@@ -1,40 +0,0 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
import { Text } from "@vector-im/compound-web";
import type {
FC,
ForwardRefExoticComponent,
ReactNode,
RefAttributes,
SVGProps,
} from "react";
import styles from "./VisualList.module.css";
type Props = {
children: ReactNode;
};
export const VisualListItem: FC<{
Icon: ForwardRefExoticComponent<
Omit<SVGProps<SVGSVGElement>, "ref" | "children"> &
RefAttributes<SVGSVGElement>
>;
iconColor?: string;
label: string;
}> = ({ Icon, iconColor, label }) => {
return (
<li className={styles.item}>
<Icon color={iconColor ?? "var(--cpd-color-icon-tertiary)"} />
<Text size="md">{label}</Text>
</li>
);
};
export const VisualList: React.FC<Props> = ({ children }) => {
return <ul className={styles.list}>{children}</ul>;
};

View File

@@ -182,10 +182,10 @@ exports[`<CompatSession /> > renders an active session 1`] = `
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9 12.031c0-.283.096-.52.288-.712A.968.968 0 0 1 10 11.03h7.15l-1.875-1.875a.96.96 0 0 1-.3-.7c0-.266.108-.508.325-.725a.93.93 0 0 1 .712-.287.977.977 0 0 1 .688.287l3.6 3.6c.1.1.17.209.212.325.042.117.063.242.063.375 0 .134-.02.259-.063.375a.877.877 0 0 1-.212.325l-3.6 3.6a.93.93 0 0 1-.712.288.977.977 0 0 1-.688-.288 1.02 1.02 0 0 1-.313-.712.931.931 0 0 1 .288-.713l1.875-1.875H10a.968.968 0 0 1-.712-.287A.968.968 0 0 1 9 12.03Zm-6-7c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 5 3.03h6a.97.97 0 0 1 .713.288.968.968 0 0 1 .287.712.97.97 0 0 1-.287.713.968.968 0 0 1-.713.287H5v14h6a.97.97 0 0 1 .713.288.968.968 0 0 1 .287.712.97.97 0 0 1-.287.713.968.968 0 0 1-.713.287H5c-.55 0-1.02-.196-1.412-.587A1.926 1.926 0 0 1 3 19.03v-14Z"
d="M7 21c-.55 0-1.02-.196-1.412-.587A1.926 1.926 0 0 1 5 19V6a.968.968 0 0 1-.713-.287A.968.968 0 0 1 4 5c0-.283.096-.52.287-.713A.968.968 0 0 1 5 4h4a.97.97 0 0 1 .287-.712A.968.968 0 0 1 10 3h4a.97.97 0 0 1 .713.288A.968.968 0 0 1 15 4h4a.97.97 0 0 1 .712.287c.192.192.288.43.288.713s-.096.52-.288.713A.968.968 0 0 1 19 6v13c0 .55-.196 1.02-.587 1.413A1.926 1.926 0 0 1 17 21H7ZM7 6v13h10V6H7Zm2 10c0 .283.096.52.287.712.192.192.43.288.713.288s.52-.096.713-.288A.968.968 0 0 0 11 16V9a.967.967 0 0 0-.287-.713A.968.968 0 0 0 10 8a.968.968 0 0 0-.713.287A.968.968 0 0 0 9 9v7Zm4 0c0 .283.096.52.287.712.192.192.43.288.713.288s.52-.096.713-.288A.968.968 0 0 0 15 16V9a.967.967 0 0 0-.287-.713A.968.968 0 0 0 14 8a.968.968 0 0 0-.713.287A.967.967 0 0 0 13 9v7Z"
/>
</svg>
Sign out
Remove device
</button>
</div>
</section>

View File

@@ -176,10 +176,10 @@ exports[`<OAuth2Session /> > renders an active session 1`] = `
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9 12.031c0-.283.096-.52.288-.712A.968.968 0 0 1 10 11.03h7.15l-1.875-1.875a.96.96 0 0 1-.3-.7c0-.266.108-.508.325-.725a.93.93 0 0 1 .712-.287.977.977 0 0 1 .688.287l3.6 3.6c.1.1.17.209.212.325.042.117.063.242.063.375 0 .134-.02.259-.063.375a.877.877 0 0 1-.212.325l-3.6 3.6a.93.93 0 0 1-.712.288.977.977 0 0 1-.688-.288 1.02 1.02 0 0 1-.313-.712.931.931 0 0 1 .288-.713l1.875-1.875H10a.968.968 0 0 1-.712-.287A.968.968 0 0 1 9 12.03Zm-6-7c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 5 3.03h6a.97.97 0 0 1 .713.288.968.968 0 0 1 .287.712.97.97 0 0 1-.287.713.968.968 0 0 1-.713.287H5v14h6a.97.97 0 0 1 .713.288.968.968 0 0 1 .287.712.97.97 0 0 1-.287.713.968.968 0 0 1-.713.287H5c-.55 0-1.02-.196-1.412-.587A1.926 1.926 0 0 1 3 19.03v-14Z"
d="M7 21c-.55 0-1.02-.196-1.412-.587A1.926 1.926 0 0 1 5 19V6a.968.968 0 0 1-.713-.287A.968.968 0 0 1 4 5c0-.283.096-.52.287-.713A.968.968 0 0 1 5 4h4a.97.97 0 0 1 .287-.712A.968.968 0 0 1 10 3h4a.97.97 0 0 1 .713.288A.968.968 0 0 1 15 4h4a.97.97 0 0 1 .712.287c.192.192.288.43.288.713s-.096.52-.288.713A.968.968 0 0 1 19 6v13c0 .55-.196 1.02-.587 1.413A1.926 1.926 0 0 1 17 21H7ZM7 6v13h10V6H7Zm2 10c0 .283.096.52.287.712.192.192.43.288.713.288s.52-.096.713-.288A.968.968 0 0 0 11 16V9a.967.967 0 0 0-.287-.713A.968.968 0 0 0 10 8a.968.968 0 0 0-.713.287A.968.968 0 0 0 9 9v7Zm4 0c0 .283.096.52.287.712.192.192.43.288.713.288s.52-.096.713-.288A.968.968 0 0 0 15 16V9a.967.967 0 0 0-.287-.713A.968.968 0 0 0 14 8a.968.968 0 0 0-.713.287A.967.967 0 0 0 13 9v7Z"
/>
</svg>
Sign out
Remove device
</button>
</div>
</section>

View File

@@ -16,19 +16,22 @@ import * as types from './graphql';
*/
type Documents = {
"\n fragment PasswordChange_siteConfig on SiteConfig {\n passwordChangeAllowed\n }\n": typeof types.PasswordChange_SiteConfigFragmentDoc,
"\n fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n finishedAt\n userAgent {\n raw\n name\n os\n model\n deviceType\n }\n lastActiveIp\n lastActiveAt\n lastAuthentication {\n id\n createdAt\n }\n }\n": typeof 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": typeof types.EndBrowserSessionDocument,
"\n fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n finishedAt\n ...EndBrowserSessionButton_session\n userAgent {\n deviceType\n name\n os\n model\n }\n lastActiveAt\n }\n": typeof types.BrowserSession_SessionFragmentDoc,
"\n fragment OAuth2Client_detail on Oauth2Client {\n id\n clientId\n clientName\n clientUri\n logoUri\n tosUri\n policyUri\n redirectUris\n }\n": typeof types.OAuth2Client_DetailFragmentDoc,
"\n fragment CompatSession_session on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\n lastActiveIp\n lastActiveAt\n userAgent {\n name\n os\n model\n deviceType\n }\n ssoLogin {\n id\n redirectUri\n }\n }\n": typeof types.CompatSession_SessionFragmentDoc,
"\n mutation EndCompatSession($id: ID!) {\n endCompatSession(input: { compatSessionId: $id }) {\n status\n compatSession {\n id\n }\n }\n }\n": typeof types.EndCompatSessionDocument,
"\n fragment CompatSession_session on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\n lastActiveIp\n lastActiveAt\n ...EndCompatSessionButton_session\n userAgent {\n name\n os\n model\n deviceType\n }\n ssoLogin {\n id\n redirectUri\n }\n }\n": typeof types.CompatSession_SessionFragmentDoc,
"\n fragment Footer_siteConfig on SiteConfig {\n id\n imprint\n tosUri\n policyUri\n }\n": typeof types.Footer_SiteConfigFragmentDoc,
"\n query Footer {\n siteConfig {\n id\n ...Footer_siteConfig\n }\n }\n": typeof types.FooterDocument,
"\n fragment OAuth2Session_session on Oauth2Session {\n id\n scope\n createdAt\n finishedAt\n lastActiveIp\n lastActiveAt\n\n userAgent {\n name\n model\n os\n deviceType\n }\n\n client {\n id\n clientId\n clientName\n applicationType\n logoUri\n }\n }\n": typeof types.OAuth2Session_SessionFragmentDoc,
"\n mutation EndOAuth2Session($id: ID!) {\n endOauth2Session(input: { oauth2SessionId: $id }) {\n status\n oauth2Session {\n id\n }\n }\n }\n": typeof types.EndOAuth2SessionDocument,
"\n fragment OAuth2Session_session on Oauth2Session {\n id\n scope\n createdAt\n finishedAt\n lastActiveIp\n lastActiveAt\n\n ...EndOAuth2SessionButton_session\n\n userAgent {\n name\n model\n os\n deviceType\n }\n\n client {\n id\n clientId\n clientName\n applicationType\n logoUri\n }\n }\n": typeof types.OAuth2Session_SessionFragmentDoc,
"\n fragment PasswordCreationDoubleInput_siteConfig on SiteConfig {\n id\n minimumPasswordComplexity\n }\n": typeof types.PasswordCreationDoubleInput_SiteConfigFragmentDoc,
"\n fragment BrowserSession_detail on BrowserSession {\n id\n createdAt\n finishedAt\n userAgent {\n name\n model\n os\n }\n lastActiveIp\n lastActiveAt\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n": typeof types.BrowserSession_DetailFragmentDoc,
"\n fragment CompatSession_detail on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\n lastActiveIp\n lastActiveAt\n userAgent {\n name\n os\n model\n }\n ssoLogin {\n id\n redirectUri\n }\n }\n": typeof types.CompatSession_DetailFragmentDoc,
"\n fragment OAuth2Session_detail on Oauth2Session {\n id\n scope\n createdAt\n finishedAt\n lastActiveIp\n lastActiveAt\n client {\n id\n clientId\n clientName\n clientUri\n logoUri\n }\n }\n": typeof types.OAuth2Session_DetailFragmentDoc,
"\n fragment EndBrowserSessionButton_session on BrowserSession {\n id\n userAgent {\n name\n os\n model\n deviceType\n }\n }\n": typeof types.EndBrowserSessionButton_SessionFragmentDoc,
"\n mutation EndBrowserSession($id: ID!) {\n endBrowserSession(input: { browserSessionId: $id }) {\n status\n browserSession {\n id\n }\n }\n }\n": typeof types.EndBrowserSessionDocument,
"\n fragment EndCompatSessionButton_session on CompatSession {\n id\n userAgent {\n name\n os\n model\n deviceType\n }\n ssoLogin {\n id\n redirectUri\n }\n }\n": typeof types.EndCompatSessionButton_SessionFragmentDoc,
"\n mutation EndCompatSession($id: ID!) {\n endCompatSession(input: { compatSessionId: $id }) {\n status\n compatSession {\n id\n }\n }\n }\n": typeof types.EndCompatSessionDocument,
"\n fragment EndOAuth2SessionButton_session on Oauth2Session {\n id\n\n userAgent {\n name\n model\n os\n deviceType\n }\n\n client {\n clientId\n clientName\n applicationType\n logoUri\n }\n }\n": typeof types.EndOAuth2SessionButton_SessionFragmentDoc,
"\n mutation EndOAuth2Session($id: ID!) {\n endOauth2Session(input: { oauth2SessionId: $id }) {\n status\n oauth2Session {\n id\n }\n }\n }\n": typeof types.EndOAuth2SessionDocument,
"\n fragment BrowserSession_detail on BrowserSession {\n id\n createdAt\n finishedAt\n ...EndBrowserSessionButton_session\n userAgent {\n name\n model\n os\n }\n lastActiveIp\n lastActiveAt\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n": typeof types.BrowserSession_DetailFragmentDoc,
"\n fragment CompatSession_detail on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\n lastActiveIp\n lastActiveAt\n\n ...EndCompatSessionButton_session\n\n userAgent {\n name\n os\n model\n }\n\n ssoLogin {\n id\n redirectUri\n }\n }\n": typeof types.CompatSession_DetailFragmentDoc,
"\n fragment OAuth2Session_detail on Oauth2Session {\n id\n scope\n createdAt\n finishedAt\n lastActiveIp\n lastActiveAt\n\n ...EndOAuth2SessionButton_session\n\n userAgent {\n name\n model\n os\n }\n\n client {\n id\n clientId\n clientName\n clientUri\n logoUri\n }\n }\n": typeof types.OAuth2Session_DetailFragmentDoc,
"\n fragment UserEmail_email on UserEmail {\n id\n email\n }\n": typeof types.UserEmail_EmailFragmentDoc,
"\n fragment UserEmail_siteConfig on SiteConfig {\n emailChangeAllowed\n }\n": typeof types.UserEmail_SiteConfigFragmentDoc,
"\n mutation RemoveEmail($id: ID!) {\n removeEmail(input: { userEmailId: $id }) {\n status\n\n user {\n id\n }\n }\n }\n": typeof types.RemoveEmailDocument,
@@ -39,12 +42,11 @@ type Documents = {
"\n query UserEmailList(\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n viewer {\n __typename\n ... on User {\n emails(first: $first, after: $after, last: $last, before: $before) {\n edges {\n cursor\n node {\n ...UserEmail_email\n }\n }\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n }\n": typeof types.UserEmailListDocument,
"\n fragment UserEmailList_siteConfig on SiteConfig {\n emailChangeAllowed\n }\n": typeof types.UserEmailList_SiteConfigFragmentDoc,
"\n fragment BrowserSessionsOverview_user on User {\n id\n\n browserSessions(first: 0, state: ACTIVE) {\n totalCount\n }\n }\n": typeof types.BrowserSessionsOverview_UserFragmentDoc,
"\n query UserProfile {\n viewer {\n __typename\n ... on User {\n emails(first: 0) {\n totalCount\n }\n }\n }\n\n siteConfig {\n emailChangeAllowed\n passwordLoginEnabled\n ...UserEmailList_siteConfig\n ...UserEmail_siteConfig\n ...PasswordChange_siteConfig\n }\n }\n": typeof types.UserProfileDocument,
"\n query SessionDetail($id: ID!) {\n viewerSession {\n ... on Node {\n id\n }\n }\n\n node(id: $id) {\n __typename\n id\n ...CompatSession_detail\n ...OAuth2Session_detail\n ...BrowserSession_detail\n }\n }\n": typeof types.SessionDetailDocument,
"\n query UserProfile {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n user {\n emails(first: 0) {\n totalCount\n }\n }\n }\n }\n\n siteConfig {\n emailChangeAllowed\n passwordLoginEnabled\n ...UserEmailList_siteConfig\n ...UserEmail_siteConfig\n ...PasswordChange_siteConfig\n }\n }\n": typeof types.UserProfileDocument,
"\n query BrowserSessionList(\n $first: Int\n $after: String\n $last: Int\n $before: String\n $lastActive: DateFilter\n ) {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n\n user {\n id\n\n browserSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n lastActive: $lastActive\n state: ACTIVE\n ) {\n totalCount\n\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n }\n }\n": typeof types.BrowserSessionListDocument,
"\n query SessionsOverview {\n viewer {\n __typename\n\n ... on User {\n id\n ...BrowserSessionsOverview_user\n }\n }\n }\n": typeof types.SessionsOverviewDocument,
"\n query AppSessionsList(\n $before: String\n $after: String\n $first: Int\n $last: Int\n $lastActive: DateFilter\n ) {\n viewer {\n __typename\n\n ... on User {\n id\n appSessions(\n before: $before\n after: $after\n first: $first\n last: $last\n lastActive: $lastActive\n state: ACTIVE\n ) {\n edges {\n cursor\n node {\n __typename\n ...CompatSession_session\n ...OAuth2Session_session\n }\n }\n\n totalCount\n pageInfo {\n startCursor\n endCursor\n hasNextPage\n hasPreviousPage\n }\n }\n }\n }\n }\n": typeof types.AppSessionsListDocument,
"\n query CurrentUserGreeting {\n viewerSession {\n __typename\n\n ... on BrowserSession {\n id\n\n user {\n ...UserGreeting_user\n }\n }\n }\n\n siteConfig {\n ...UserGreeting_siteConfig\n }\n }\n": typeof types.CurrentUserGreetingDocument,
"\n query CurrentUserGreeting {\n viewer {\n __typename\n ... on User {\n ...UserGreeting_user\n }\n }\n\n siteConfig {\n ...UserGreeting_siteConfig\n }\n }\n": typeof types.CurrentUserGreetingDocument,
"\n query OAuth2Client($id: ID!) {\n oauth2Client(id: $id) {\n ...OAuth2Client_detail\n }\n }\n": typeof types.OAuth2ClientDocument,
"\n query CurrentViewer {\n viewer {\n __typename\n ... on Node {\n id\n }\n }\n }\n": typeof types.CurrentViewerDocument,
"\n query DeviceRedirect($deviceId: String!, $userId: ID!) {\n session(deviceId: $deviceId, userId: $userId) {\n __typename\n ... on Node {\n id\n }\n }\n }\n": typeof types.DeviceRedirectDocument,
@@ -59,22 +61,26 @@ type Documents = {
"\n fragment RecoverPassword_siteConfig on SiteConfig {\n ...PasswordCreationDoubleInput_siteConfig\n }\n": typeof types.RecoverPassword_SiteConfigFragmentDoc,
"\n query PasswordRecovery($ticket: String!) {\n siteConfig {\n ...RecoverPassword_siteConfig\n }\n\n userRecoveryTicket(ticket: $ticket) {\n status\n ...RecoverPassword_userRecoveryTicket\n }\n }\n": typeof types.PasswordRecoveryDocument,
"\n mutation AllowCrossSigningReset($userId: ID!) {\n allowUserCrossSigningReset(input: { userId: $userId }) {\n user {\n id\n }\n }\n }\n": typeof types.AllowCrossSigningResetDocument,
"\n query SessionDetail($id: ID!) {\n viewerSession {\n ... on Node {\n id\n }\n }\n\n node(id: $id) {\n __typename\n id\n ...CompatSession_detail\n ...OAuth2Session_detail\n ...BrowserSession_detail\n }\n }\n": typeof types.SessionDetailDocument,
};
const documents: Documents = {
"\n fragment PasswordChange_siteConfig on SiteConfig {\n passwordChangeAllowed\n }\n": types.PasswordChange_SiteConfigFragmentDoc,
"\n fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n finishedAt\n userAgent {\n raw\n name\n os\n model\n deviceType\n }\n lastActiveIp\n lastActiveAt\n lastAuthentication {\n id\n createdAt\n }\n }\n": 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": types.EndBrowserSessionDocument,
"\n fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n finishedAt\n ...EndBrowserSessionButton_session\n userAgent {\n deviceType\n name\n os\n model\n }\n lastActiveAt\n }\n": types.BrowserSession_SessionFragmentDoc,
"\n fragment OAuth2Client_detail on Oauth2Client {\n id\n clientId\n clientName\n clientUri\n logoUri\n tosUri\n policyUri\n redirectUris\n }\n": types.OAuth2Client_DetailFragmentDoc,
"\n fragment CompatSession_session on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\n lastActiveIp\n lastActiveAt\n userAgent {\n name\n os\n model\n deviceType\n }\n ssoLogin {\n id\n redirectUri\n }\n }\n": types.CompatSession_SessionFragmentDoc,
"\n mutation EndCompatSession($id: ID!) {\n endCompatSession(input: { compatSessionId: $id }) {\n status\n compatSession {\n id\n }\n }\n }\n": types.EndCompatSessionDocument,
"\n fragment CompatSession_session on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\n lastActiveIp\n lastActiveAt\n ...EndCompatSessionButton_session\n userAgent {\n name\n os\n model\n deviceType\n }\n ssoLogin {\n id\n redirectUri\n }\n }\n": types.CompatSession_SessionFragmentDoc,
"\n fragment Footer_siteConfig on SiteConfig {\n id\n imprint\n tosUri\n policyUri\n }\n": types.Footer_SiteConfigFragmentDoc,
"\n query Footer {\n siteConfig {\n id\n ...Footer_siteConfig\n }\n }\n": types.FooterDocument,
"\n fragment OAuth2Session_session on Oauth2Session {\n id\n scope\n createdAt\n finishedAt\n lastActiveIp\n lastActiveAt\n\n userAgent {\n name\n model\n os\n deviceType\n }\n\n client {\n id\n clientId\n clientName\n applicationType\n logoUri\n }\n }\n": types.OAuth2Session_SessionFragmentDoc,
"\n mutation EndOAuth2Session($id: ID!) {\n endOauth2Session(input: { oauth2SessionId: $id }) {\n status\n oauth2Session {\n id\n }\n }\n }\n": types.EndOAuth2SessionDocument,
"\n fragment OAuth2Session_session on Oauth2Session {\n id\n scope\n createdAt\n finishedAt\n lastActiveIp\n lastActiveAt\n\n ...EndOAuth2SessionButton_session\n\n userAgent {\n name\n model\n os\n deviceType\n }\n\n client {\n id\n clientId\n clientName\n applicationType\n logoUri\n }\n }\n": types.OAuth2Session_SessionFragmentDoc,
"\n fragment PasswordCreationDoubleInput_siteConfig on SiteConfig {\n id\n minimumPasswordComplexity\n }\n": types.PasswordCreationDoubleInput_SiteConfigFragmentDoc,
"\n fragment BrowserSession_detail on BrowserSession {\n id\n createdAt\n finishedAt\n userAgent {\n name\n model\n os\n }\n lastActiveIp\n lastActiveAt\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n": types.BrowserSession_DetailFragmentDoc,
"\n fragment CompatSession_detail on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\n lastActiveIp\n lastActiveAt\n userAgent {\n name\n os\n model\n }\n ssoLogin {\n id\n redirectUri\n }\n }\n": types.CompatSession_DetailFragmentDoc,
"\n fragment OAuth2Session_detail on Oauth2Session {\n id\n scope\n createdAt\n finishedAt\n lastActiveIp\n lastActiveAt\n client {\n id\n clientId\n clientName\n clientUri\n logoUri\n }\n }\n": types.OAuth2Session_DetailFragmentDoc,
"\n fragment EndBrowserSessionButton_session on BrowserSession {\n id\n userAgent {\n name\n os\n model\n deviceType\n }\n }\n": types.EndBrowserSessionButton_SessionFragmentDoc,
"\n mutation EndBrowserSession($id: ID!) {\n endBrowserSession(input: { browserSessionId: $id }) {\n status\n browserSession {\n id\n }\n }\n }\n": types.EndBrowserSessionDocument,
"\n fragment EndCompatSessionButton_session on CompatSession {\n id\n userAgent {\n name\n os\n model\n deviceType\n }\n ssoLogin {\n id\n redirectUri\n }\n }\n": types.EndCompatSessionButton_SessionFragmentDoc,
"\n mutation EndCompatSession($id: ID!) {\n endCompatSession(input: { compatSessionId: $id }) {\n status\n compatSession {\n id\n }\n }\n }\n": types.EndCompatSessionDocument,
"\n fragment EndOAuth2SessionButton_session on Oauth2Session {\n id\n\n userAgent {\n name\n model\n os\n deviceType\n }\n\n client {\n clientId\n clientName\n applicationType\n logoUri\n }\n }\n": types.EndOAuth2SessionButton_SessionFragmentDoc,
"\n mutation EndOAuth2Session($id: ID!) {\n endOauth2Session(input: { oauth2SessionId: $id }) {\n status\n oauth2Session {\n id\n }\n }\n }\n": types.EndOAuth2SessionDocument,
"\n fragment BrowserSession_detail on BrowserSession {\n id\n createdAt\n finishedAt\n ...EndBrowserSessionButton_session\n userAgent {\n name\n model\n os\n }\n lastActiveIp\n lastActiveAt\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n": types.BrowserSession_DetailFragmentDoc,
"\n fragment CompatSession_detail on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\n lastActiveIp\n lastActiveAt\n\n ...EndCompatSessionButton_session\n\n userAgent {\n name\n os\n model\n }\n\n ssoLogin {\n id\n redirectUri\n }\n }\n": types.CompatSession_DetailFragmentDoc,
"\n fragment OAuth2Session_detail on Oauth2Session {\n id\n scope\n createdAt\n finishedAt\n lastActiveIp\n lastActiveAt\n\n ...EndOAuth2SessionButton_session\n\n userAgent {\n name\n model\n os\n }\n\n client {\n id\n clientId\n clientName\n clientUri\n logoUri\n }\n }\n": types.OAuth2Session_DetailFragmentDoc,
"\n fragment UserEmail_email on UserEmail {\n id\n email\n }\n": types.UserEmail_EmailFragmentDoc,
"\n fragment UserEmail_siteConfig on SiteConfig {\n emailChangeAllowed\n }\n": types.UserEmail_SiteConfigFragmentDoc,
"\n mutation RemoveEmail($id: ID!) {\n removeEmail(input: { userEmailId: $id }) {\n status\n\n user {\n id\n }\n }\n }\n": types.RemoveEmailDocument,
@@ -85,12 +91,11 @@ const documents: Documents = {
"\n query UserEmailList(\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n viewer {\n __typename\n ... on User {\n emails(first: $first, after: $after, last: $last, before: $before) {\n edges {\n cursor\n node {\n ...UserEmail_email\n }\n }\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n }\n": types.UserEmailListDocument,
"\n fragment UserEmailList_siteConfig on SiteConfig {\n emailChangeAllowed\n }\n": types.UserEmailList_SiteConfigFragmentDoc,
"\n fragment BrowserSessionsOverview_user on User {\n id\n\n browserSessions(first: 0, state: ACTIVE) {\n totalCount\n }\n }\n": types.BrowserSessionsOverview_UserFragmentDoc,
"\n query UserProfile {\n viewer {\n __typename\n ... on User {\n emails(first: 0) {\n totalCount\n }\n }\n }\n\n siteConfig {\n emailChangeAllowed\n passwordLoginEnabled\n ...UserEmailList_siteConfig\n ...UserEmail_siteConfig\n ...PasswordChange_siteConfig\n }\n }\n": types.UserProfileDocument,
"\n query SessionDetail($id: ID!) {\n viewerSession {\n ... on Node {\n id\n }\n }\n\n node(id: $id) {\n __typename\n id\n ...CompatSession_detail\n ...OAuth2Session_detail\n ...BrowserSession_detail\n }\n }\n": types.SessionDetailDocument,
"\n query UserProfile {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n user {\n emails(first: 0) {\n totalCount\n }\n }\n }\n }\n\n siteConfig {\n emailChangeAllowed\n passwordLoginEnabled\n ...UserEmailList_siteConfig\n ...UserEmail_siteConfig\n ...PasswordChange_siteConfig\n }\n }\n": types.UserProfileDocument,
"\n query BrowserSessionList(\n $first: Int\n $after: String\n $last: Int\n $before: String\n $lastActive: DateFilter\n ) {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n\n user {\n id\n\n browserSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n lastActive: $lastActive\n state: ACTIVE\n ) {\n totalCount\n\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n }\n }\n": types.BrowserSessionListDocument,
"\n query SessionsOverview {\n viewer {\n __typename\n\n ... on User {\n id\n ...BrowserSessionsOverview_user\n }\n }\n }\n": types.SessionsOverviewDocument,
"\n query AppSessionsList(\n $before: String\n $after: String\n $first: Int\n $last: Int\n $lastActive: DateFilter\n ) {\n viewer {\n __typename\n\n ... on User {\n id\n appSessions(\n before: $before\n after: $after\n first: $first\n last: $last\n lastActive: $lastActive\n state: ACTIVE\n ) {\n edges {\n cursor\n node {\n __typename\n ...CompatSession_session\n ...OAuth2Session_session\n }\n }\n\n totalCount\n pageInfo {\n startCursor\n endCursor\n hasNextPage\n hasPreviousPage\n }\n }\n }\n }\n }\n": types.AppSessionsListDocument,
"\n query CurrentUserGreeting {\n viewerSession {\n __typename\n\n ... on BrowserSession {\n id\n\n user {\n ...UserGreeting_user\n }\n }\n }\n\n siteConfig {\n ...UserGreeting_siteConfig\n }\n }\n": types.CurrentUserGreetingDocument,
"\n query CurrentUserGreeting {\n viewer {\n __typename\n ... on User {\n ...UserGreeting_user\n }\n }\n\n siteConfig {\n ...UserGreeting_siteConfig\n }\n }\n": types.CurrentUserGreetingDocument,
"\n query OAuth2Client($id: ID!) {\n oauth2Client(id: $id) {\n ...OAuth2Client_detail\n }\n }\n": types.OAuth2ClientDocument,
"\n query CurrentViewer {\n viewer {\n __typename\n ... on Node {\n id\n }\n }\n }\n": types.CurrentViewerDocument,
"\n query DeviceRedirect($deviceId: String!, $userId: ID!) {\n session(deviceId: $deviceId, userId: $userId) {\n __typename\n ... on Node {\n id\n }\n }\n }\n": types.DeviceRedirectDocument,
@@ -105,6 +110,7 @@ const documents: Documents = {
"\n fragment RecoverPassword_siteConfig on SiteConfig {\n ...PasswordCreationDoubleInput_siteConfig\n }\n": types.RecoverPassword_SiteConfigFragmentDoc,
"\n query PasswordRecovery($ticket: String!) {\n siteConfig {\n ...RecoverPassword_siteConfig\n }\n\n userRecoveryTicket(ticket: $ticket) {\n status\n ...RecoverPassword_userRecoveryTicket\n }\n }\n": types.PasswordRecoveryDocument,
"\n mutation AllowCrossSigningReset($userId: ID!) {\n allowUserCrossSigningReset(input: { userId: $userId }) {\n user {\n id\n }\n }\n }\n": types.AllowCrossSigningResetDocument,
"\n query SessionDetail($id: ID!) {\n viewerSession {\n ... on Node {\n id\n }\n }\n\n node(id: $id) {\n __typename\n id\n ...CompatSession_detail\n ...OAuth2Session_detail\n ...BrowserSession_detail\n }\n }\n": types.SessionDetailDocument,
};
/**
@@ -114,11 +120,7 @@ export function graphql(source: "\n fragment PasswordChange_siteConfig on SiteC
/**
* 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 finishedAt\n userAgent {\n raw\n name\n os\n model\n deviceType\n }\n lastActiveIp\n lastActiveAt\n lastAuthentication {\n id\n createdAt\n }\n }\n"): typeof import('./graphql').BrowserSession_SessionFragmentDoc;
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n mutation EndBrowserSession($id: ID!) {\n endBrowserSession(input: { browserSessionId: $id }) {\n status\n browserSession {\n id\n ...BrowserSession_session\n }\n }\n }\n"): typeof import('./graphql').EndBrowserSessionDocument;
export function graphql(source: "\n fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n finishedAt\n ...EndBrowserSessionButton_session\n userAgent {\n deviceType\n name\n os\n model\n }\n lastActiveAt\n }\n"): typeof import('./graphql').BrowserSession_SessionFragmentDoc;
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
@@ -126,11 +128,7 @@ export function graphql(source: "\n fragment OAuth2Client_detail on Oauth2Clien
/**
* 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 CompatSession_session on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\n lastActiveIp\n lastActiveAt\n userAgent {\n name\n os\n model\n deviceType\n }\n ssoLogin {\n id\n redirectUri\n }\n }\n"): typeof import('./graphql').CompatSession_SessionFragmentDoc;
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n mutation EndCompatSession($id: ID!) {\n endCompatSession(input: { compatSessionId: $id }) {\n status\n compatSession {\n id\n }\n }\n }\n"): typeof import('./graphql').EndCompatSessionDocument;
export function graphql(source: "\n fragment CompatSession_session on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\n lastActiveIp\n lastActiveAt\n ...EndCompatSessionButton_session\n userAgent {\n name\n os\n model\n deviceType\n }\n ssoLogin {\n id\n redirectUri\n }\n }\n"): typeof import('./graphql').CompatSession_SessionFragmentDoc;
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
@@ -142,11 +140,7 @@ export function graphql(source: "\n query Footer {\n siteConfig {\n id\
/**
* 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 createdAt\n finishedAt\n lastActiveIp\n lastActiveAt\n\n userAgent {\n name\n model\n os\n deviceType\n }\n\n client {\n id\n clientId\n clientName\n applicationType\n logoUri\n }\n }\n"): typeof import('./graphql').OAuth2Session_SessionFragmentDoc;
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n mutation EndOAuth2Session($id: ID!) {\n endOauth2Session(input: { oauth2SessionId: $id }) {\n status\n oauth2Session {\n id\n }\n }\n }\n"): typeof import('./graphql').EndOAuth2SessionDocument;
export function graphql(source: "\n fragment OAuth2Session_session on Oauth2Session {\n id\n scope\n createdAt\n finishedAt\n lastActiveIp\n lastActiveAt\n\n ...EndOAuth2SessionButton_session\n\n userAgent {\n name\n model\n os\n deviceType\n }\n\n client {\n id\n clientId\n clientName\n applicationType\n logoUri\n }\n }\n"): typeof import('./graphql').OAuth2Session_SessionFragmentDoc;
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
@@ -154,15 +148,39 @@ export function graphql(source: "\n fragment PasswordCreationDoubleInput_siteCo
/**
* 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_detail on BrowserSession {\n id\n createdAt\n finishedAt\n userAgent {\n name\n model\n os\n }\n lastActiveIp\n lastActiveAt\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n"): typeof import('./graphql').BrowserSession_DetailFragmentDoc;
export function graphql(source: "\n fragment EndBrowserSessionButton_session on BrowserSession {\n id\n userAgent {\n name\n os\n model\n deviceType\n }\n }\n"): typeof import('./graphql').EndBrowserSessionButton_SessionFragmentDoc;
/**
* 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 CompatSession_detail on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\n lastActiveIp\n lastActiveAt\n userAgent {\n name\n os\n model\n }\n ssoLogin {\n id\n redirectUri\n }\n }\n"): typeof import('./graphql').CompatSession_DetailFragmentDoc;
export function graphql(source: "\n mutation EndBrowserSession($id: ID!) {\n endBrowserSession(input: { browserSessionId: $id }) {\n status\n browserSession {\n id\n }\n }\n }\n"): typeof import('./graphql').EndBrowserSessionDocument;
/**
* 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_detail on Oauth2Session {\n id\n scope\n createdAt\n finishedAt\n lastActiveIp\n lastActiveAt\n client {\n id\n clientId\n clientName\n clientUri\n logoUri\n }\n }\n"): typeof import('./graphql').OAuth2Session_DetailFragmentDoc;
export function graphql(source: "\n fragment EndCompatSessionButton_session on CompatSession {\n id\n userAgent {\n name\n os\n model\n deviceType\n }\n ssoLogin {\n id\n redirectUri\n }\n }\n"): typeof import('./graphql').EndCompatSessionButton_SessionFragmentDoc;
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n mutation EndCompatSession($id: ID!) {\n endCompatSession(input: { compatSessionId: $id }) {\n status\n compatSession {\n id\n }\n }\n }\n"): typeof import('./graphql').EndCompatSessionDocument;
/**
* 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 EndOAuth2SessionButton_session on Oauth2Session {\n id\n\n userAgent {\n name\n model\n os\n deviceType\n }\n\n client {\n clientId\n clientName\n applicationType\n logoUri\n }\n }\n"): typeof import('./graphql').EndOAuth2SessionButton_SessionFragmentDoc;
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n mutation EndOAuth2Session($id: ID!) {\n endOauth2Session(input: { oauth2SessionId: $id }) {\n status\n oauth2Session {\n id\n }\n }\n }\n"): typeof import('./graphql').EndOAuth2SessionDocument;
/**
* 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_detail on BrowserSession {\n id\n createdAt\n finishedAt\n ...EndBrowserSessionButton_session\n userAgent {\n name\n model\n os\n }\n lastActiveIp\n lastActiveAt\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n"): typeof import('./graphql').BrowserSession_DetailFragmentDoc;
/**
* 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 CompatSession_detail on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\n lastActiveIp\n lastActiveAt\n\n ...EndCompatSessionButton_session\n\n userAgent {\n name\n os\n model\n }\n\n ssoLogin {\n id\n redirectUri\n }\n }\n"): typeof import('./graphql').CompatSession_DetailFragmentDoc;
/**
* 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_detail on Oauth2Session {\n id\n scope\n createdAt\n finishedAt\n lastActiveIp\n lastActiveAt\n\n ...EndOAuth2SessionButton_session\n\n userAgent {\n name\n model\n os\n }\n\n client {\n id\n clientId\n clientName\n clientUri\n logoUri\n }\n }\n"): typeof import('./graphql').OAuth2Session_DetailFragmentDoc;
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
@@ -206,11 +224,7 @@ export function graphql(source: "\n fragment BrowserSessionsOverview_user on Us
/**
* 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 UserProfile {\n viewer {\n __typename\n ... on User {\n emails(first: 0) {\n totalCount\n }\n }\n }\n\n siteConfig {\n emailChangeAllowed\n passwordLoginEnabled\n ...UserEmailList_siteConfig\n ...UserEmail_siteConfig\n ...PasswordChange_siteConfig\n }\n }\n"): typeof import('./graphql').UserProfileDocument;
/**
* 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 SessionDetail($id: ID!) {\n viewerSession {\n ... on Node {\n id\n }\n }\n\n node(id: $id) {\n __typename\n id\n ...CompatSession_detail\n ...OAuth2Session_detail\n ...BrowserSession_detail\n }\n }\n"): typeof import('./graphql').SessionDetailDocument;
export function graphql(source: "\n query UserProfile {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n user {\n emails(first: 0) {\n totalCount\n }\n }\n }\n }\n\n siteConfig {\n emailChangeAllowed\n passwordLoginEnabled\n ...UserEmailList_siteConfig\n ...UserEmail_siteConfig\n ...PasswordChange_siteConfig\n }\n }\n"): typeof import('./graphql').UserProfileDocument;
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
@@ -226,7 +240,7 @@ export function graphql(source: "\n query AppSessionsList(\n $before: String
/**
* 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 CurrentUserGreeting {\n viewerSession {\n __typename\n\n ... on BrowserSession {\n id\n\n user {\n ...UserGreeting_user\n }\n }\n }\n\n siteConfig {\n ...UserGreeting_siteConfig\n }\n }\n"): typeof import('./graphql').CurrentUserGreetingDocument;
export function graphql(source: "\n query CurrentUserGreeting {\n viewer {\n __typename\n ... on User {\n ...UserGreeting_user\n }\n }\n\n siteConfig {\n ...UserGreeting_siteConfig\n }\n }\n"): typeof import('./graphql').CurrentUserGreetingDocument;
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
@@ -283,6 +297,10 @@ export function graphql(source: "\n query PasswordRecovery($ticket: String!) {\
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n mutation AllowCrossSigningReset($userId: ID!) {\n allowUserCrossSigningReset(input: { userId: $userId }) {\n user {\n id\n }\n }\n }\n"): typeof import('./graphql').AllowCrossSigningResetDocument;
/**
* 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 SessionDetail($id: ID!) {\n viewerSession {\n ... on Node {\n id\n }\n }\n\n node(id: $id) {\n __typename\n id\n ...CompatSession_detail\n ...OAuth2Session_detail\n ...BrowserSession_detail\n }\n }\n"): typeof import('./graphql').SessionDetailDocument;
export function graphql(source: string) {

View File

@@ -1565,28 +1565,17 @@ export type ViewerSession = Anonymous | BrowserSession | Oauth2Session;
export type PasswordChange_SiteConfigFragment = { __typename?: 'SiteConfig', passwordChangeAllowed: boolean } & { ' $fragmentName'?: 'PasswordChange_SiteConfigFragment' };
export type BrowserSession_SessionFragment = { __typename?: 'BrowserSession', id: string, createdAt: string, finishedAt?: string | null, lastActiveIp?: string | null, lastActiveAt?: string | null, userAgent?: { __typename?: 'UserAgent', raw: string, name?: string | null, os?: string | null, model?: string | null, deviceType: DeviceType } | null, lastAuthentication?: { __typename?: 'Authentication', id: string, createdAt: string } | null } & { ' $fragmentName'?: 'BrowserSession_SessionFragment' };
export type EndBrowserSessionMutationVariables = Exact<{
id: Scalars['ID']['input'];
}>;
export type EndBrowserSessionMutation = { __typename?: 'Mutation', endBrowserSession: { __typename?: 'EndBrowserSessionPayload', status: EndBrowserSessionStatus, browserSession?: (
{ __typename?: 'BrowserSession', id: string }
& { ' $fragmentRefs'?: { 'BrowserSession_SessionFragment': BrowserSession_SessionFragment } }
) | null } };
export type BrowserSession_SessionFragment = (
{ __typename?: 'BrowserSession', id: string, createdAt: string, finishedAt?: string | null, lastActiveAt?: string | null, userAgent?: { __typename?: 'UserAgent', deviceType: DeviceType, name?: string | null, os?: string | null, model?: string | null } | null }
& { ' $fragmentRefs'?: { 'EndBrowserSessionButton_SessionFragment': EndBrowserSessionButton_SessionFragment } }
) & { ' $fragmentName'?: 'BrowserSession_SessionFragment' };
export type OAuth2Client_DetailFragment = { __typename?: 'Oauth2Client', id: string, clientId: string, clientName?: string | null, clientUri?: string | null, logoUri?: string | null, tosUri?: string | null, policyUri?: string | null, redirectUris: Array<string> } & { ' $fragmentName'?: 'OAuth2Client_DetailFragment' };
export type CompatSession_SessionFragment = { __typename?: 'CompatSession', id: string, createdAt: string, deviceId?: string | null, finishedAt?: string | null, lastActiveIp?: string | null, lastActiveAt?: string | null, userAgent?: { __typename?: 'UserAgent', name?: string | null, os?: string | null, model?: string | null, deviceType: DeviceType } | null, ssoLogin?: { __typename?: 'CompatSsoLogin', id: string, redirectUri: string } | null } & { ' $fragmentName'?: 'CompatSession_SessionFragment' };
export type EndCompatSessionMutationVariables = Exact<{
id: Scalars['ID']['input'];
}>;
export type EndCompatSessionMutation = { __typename?: 'Mutation', endCompatSession: { __typename?: 'EndCompatSessionPayload', status: EndCompatSessionStatus, compatSession?: { __typename?: 'CompatSession', id: string } | null } };
export type CompatSession_SessionFragment = (
{ __typename?: 'CompatSession', id: string, createdAt: string, deviceId?: string | null, finishedAt?: string | null, lastActiveIp?: string | null, lastActiveAt?: string | null, userAgent?: { __typename?: 'UserAgent', name?: string | null, os?: string | null, model?: string | null, deviceType: DeviceType } | null, ssoLogin?: { __typename?: 'CompatSsoLogin', id: string, redirectUri: string } | null }
& { ' $fragmentRefs'?: { 'EndCompatSessionButton_SessionFragment': EndCompatSessionButton_SessionFragment } }
) & { ' $fragmentName'?: 'CompatSession_SessionFragment' };
export type Footer_SiteConfigFragment = { __typename?: 'SiteConfig', id: string, imprint?: string | null, tosUri?: string | null, policyUri?: string | null } & { ' $fragmentName'?: 'Footer_SiteConfigFragment' };
@@ -1598,7 +1587,32 @@ export type FooterQuery = { __typename?: 'Query', siteConfig: (
& { ' $fragmentRefs'?: { 'Footer_SiteConfigFragment': Footer_SiteConfigFragment } }
) };
export type OAuth2Session_SessionFragment = { __typename?: 'Oauth2Session', id: string, scope: string, createdAt: string, finishedAt?: string | null, lastActiveIp?: string | null, lastActiveAt?: string | null, userAgent?: { __typename?: 'UserAgent', name?: string | null, model?: string | null, os?: string | null, deviceType: DeviceType } | null, client: { __typename?: 'Oauth2Client', id: string, clientId: string, clientName?: string | null, applicationType?: Oauth2ApplicationType | null, logoUri?: string | null } } & { ' $fragmentName'?: 'OAuth2Session_SessionFragment' };
export type OAuth2Session_SessionFragment = (
{ __typename?: 'Oauth2Session', id: string, scope: string, createdAt: string, finishedAt?: string | null, lastActiveIp?: string | null, lastActiveAt?: string | null, userAgent?: { __typename?: 'UserAgent', name?: string | null, model?: string | null, os?: string | null, deviceType: DeviceType } | null, client: { __typename?: 'Oauth2Client', id: string, clientId: string, clientName?: string | null, applicationType?: Oauth2ApplicationType | null, logoUri?: string | null } }
& { ' $fragmentRefs'?: { 'EndOAuth2SessionButton_SessionFragment': EndOAuth2SessionButton_SessionFragment } }
) & { ' $fragmentName'?: 'OAuth2Session_SessionFragment' };
export type PasswordCreationDoubleInput_SiteConfigFragment = { __typename?: 'SiteConfig', id: string, minimumPasswordComplexity: number } & { ' $fragmentName'?: 'PasswordCreationDoubleInput_SiteConfigFragment' };
export type EndBrowserSessionButton_SessionFragment = { __typename?: 'BrowserSession', id: string, userAgent?: { __typename?: 'UserAgent', name?: string | null, os?: string | null, model?: string | null, deviceType: DeviceType } | null } & { ' $fragmentName'?: 'EndBrowserSessionButton_SessionFragment' };
export type EndBrowserSessionMutationVariables = Exact<{
id: Scalars['ID']['input'];
}>;
export type EndBrowserSessionMutation = { __typename?: 'Mutation', endBrowserSession: { __typename?: 'EndBrowserSessionPayload', status: EndBrowserSessionStatus, browserSession?: { __typename?: 'BrowserSession', id: string } | null } };
export type EndCompatSessionButton_SessionFragment = { __typename?: 'CompatSession', id: string, userAgent?: { __typename?: 'UserAgent', name?: string | null, os?: string | null, model?: string | null, deviceType: DeviceType } | null, ssoLogin?: { __typename?: 'CompatSsoLogin', id: string, redirectUri: string } | null } & { ' $fragmentName'?: 'EndCompatSessionButton_SessionFragment' };
export type EndCompatSessionMutationVariables = Exact<{
id: Scalars['ID']['input'];
}>;
export type EndCompatSessionMutation = { __typename?: 'Mutation', endCompatSession: { __typename?: 'EndCompatSessionPayload', status: EndCompatSessionStatus, compatSession?: { __typename?: 'CompatSession', id: string } | null } };
export type EndOAuth2SessionButton_SessionFragment = { __typename?: 'Oauth2Session', id: string, userAgent?: { __typename?: 'UserAgent', name?: string | null, model?: string | null, os?: string | null, deviceType: DeviceType } | null, client: { __typename?: 'Oauth2Client', clientId: string, clientName?: string | null, applicationType?: Oauth2ApplicationType | null, logoUri?: string | null } } & { ' $fragmentName'?: 'EndOAuth2SessionButton_SessionFragment' };
export type EndOAuth2SessionMutationVariables = Exact<{
id: Scalars['ID']['input'];
@@ -1607,13 +1621,20 @@ export type EndOAuth2SessionMutationVariables = Exact<{
export type EndOAuth2SessionMutation = { __typename?: 'Mutation', endOauth2Session: { __typename?: 'EndOAuth2SessionPayload', status: EndOAuth2SessionStatus, oauth2Session?: { __typename?: 'Oauth2Session', id: string } | null } };
export type PasswordCreationDoubleInput_SiteConfigFragment = { __typename?: 'SiteConfig', id: string, minimumPasswordComplexity: number } & { ' $fragmentName'?: 'PasswordCreationDoubleInput_SiteConfigFragment' };
export type BrowserSession_DetailFragment = (
{ __typename?: 'BrowserSession', id: string, createdAt: string, finishedAt?: string | null, lastActiveIp?: string | null, lastActiveAt?: string | null, userAgent?: { __typename?: 'UserAgent', name?: string | null, model?: string | null, os?: string | null } | null, lastAuthentication?: { __typename?: 'Authentication', id: string, createdAt: string } | null, user: { __typename?: 'User', id: string, username: string } }
& { ' $fragmentRefs'?: { 'EndBrowserSessionButton_SessionFragment': EndBrowserSessionButton_SessionFragment } }
) & { ' $fragmentName'?: 'BrowserSession_DetailFragment' };
export type BrowserSession_DetailFragment = { __typename?: 'BrowserSession', id: string, createdAt: string, finishedAt?: string | null, lastActiveIp?: string | null, lastActiveAt?: string | null, userAgent?: { __typename?: 'UserAgent', name?: string | null, model?: string | null, os?: string | null } | null, lastAuthentication?: { __typename?: 'Authentication', id: string, createdAt: string } | null, user: { __typename?: 'User', id: string, username: string } } & { ' $fragmentName'?: 'BrowserSession_DetailFragment' };
export type CompatSession_DetailFragment = (
{ __typename?: 'CompatSession', id: string, createdAt: string, deviceId?: string | null, finishedAt?: string | null, lastActiveIp?: string | null, lastActiveAt?: string | null, userAgent?: { __typename?: 'UserAgent', name?: string | null, os?: string | null, model?: string | null } | null, ssoLogin?: { __typename?: 'CompatSsoLogin', id: string, redirectUri: string } | null }
& { ' $fragmentRefs'?: { 'EndCompatSessionButton_SessionFragment': EndCompatSessionButton_SessionFragment } }
) & { ' $fragmentName'?: 'CompatSession_DetailFragment' };
export type CompatSession_DetailFragment = { __typename?: 'CompatSession', id: string, createdAt: string, deviceId?: string | null, finishedAt?: string | null, lastActiveIp?: string | null, lastActiveAt?: string | null, userAgent?: { __typename?: 'UserAgent', name?: string | null, os?: string | null, model?: string | null } | null, ssoLogin?: { __typename?: 'CompatSsoLogin', id: string, redirectUri: string } | null } & { ' $fragmentName'?: 'CompatSession_DetailFragment' };
export type OAuth2Session_DetailFragment = { __typename?: 'Oauth2Session', id: string, scope: string, createdAt: string, finishedAt?: string | null, lastActiveIp?: string | null, lastActiveAt?: string | null, client: { __typename?: 'Oauth2Client', id: string, clientId: string, clientName?: string | null, clientUri?: string | null, logoUri?: string | null } } & { ' $fragmentName'?: 'OAuth2Session_DetailFragment' };
export type OAuth2Session_DetailFragment = (
{ __typename?: 'Oauth2Session', id: string, scope: string, createdAt: string, finishedAt?: string | null, lastActiveIp?: string | null, lastActiveAt?: string | null, userAgent?: { __typename?: 'UserAgent', name?: string | null, model?: string | null, os?: string | null } | null, client: { __typename?: 'Oauth2Client', id: string, clientId: string, clientName?: string | null, clientUri?: string | null, logoUri?: string | null } }
& { ' $fragmentRefs'?: { 'EndOAuth2SessionButton_SessionFragment': EndOAuth2SessionButton_SessionFragment } }
) & { ' $fragmentName'?: 'OAuth2Session_DetailFragment' };
export type UserEmail_EmailFragment = { __typename?: 'UserEmail', id: string, email: string } & { ' $fragmentName'?: 'UserEmail_EmailFragment' };
@@ -1666,27 +1687,11 @@ export type BrowserSessionsOverview_UserFragment = { __typename?: 'User', id: st
export type UserProfileQueryVariables = Exact<{ [key: string]: never; }>;
export type UserProfileQuery = { __typename?: 'Query', viewer: { __typename: 'Anonymous' } | { __typename: 'User', emails: { __typename?: 'UserEmailConnection', totalCount: number } }, siteConfig: (
export type UserProfileQuery = { __typename?: 'Query', viewerSession: { __typename: 'Anonymous' } | { __typename: 'BrowserSession', id: string, user: { __typename?: 'User', emails: { __typename?: 'UserEmailConnection', totalCount: number } } } | { __typename: 'Oauth2Session' }, siteConfig: (
{ __typename?: 'SiteConfig', emailChangeAllowed: boolean, passwordLoginEnabled: boolean }
& { ' $fragmentRefs'?: { 'UserEmailList_SiteConfigFragment': UserEmailList_SiteConfigFragment;'UserEmail_SiteConfigFragment': UserEmail_SiteConfigFragment;'PasswordChange_SiteConfigFragment': PasswordChange_SiteConfigFragment } }
) };
export type SessionDetailQueryVariables = Exact<{
id: Scalars['ID']['input'];
}>;
export type SessionDetailQuery = { __typename?: 'Query', viewerSession: { __typename?: 'Anonymous', id: string } | { __typename?: 'BrowserSession', id: string } | { __typename?: 'Oauth2Session', id: string }, node?: { __typename: 'Anonymous', id: string } | { __typename: 'Authentication', id: string } | (
{ __typename: 'BrowserSession', id: string }
& { ' $fragmentRefs'?: { 'BrowserSession_DetailFragment': BrowserSession_DetailFragment } }
) | (
{ __typename: 'CompatSession', id: string }
& { ' $fragmentRefs'?: { 'CompatSession_DetailFragment': CompatSession_DetailFragment } }
) | { __typename: 'CompatSsoLogin', id: string } | { __typename: 'Oauth2Client', id: string } | (
{ __typename: 'Oauth2Session', id: string }
& { ' $fragmentRefs'?: { 'OAuth2Session_DetailFragment': OAuth2Session_DetailFragment } }
) | { __typename: 'SiteConfig', id: string } | { __typename: 'UpstreamOAuth2Link', id: string } | { __typename: 'UpstreamOAuth2Provider', id: string } | { __typename: 'User', id: string } | { __typename: 'UserEmail', id: string } | { __typename: 'UserEmailAuthentication', id: string } | { __typename: 'UserRecoveryTicket', id: string } | null };
export type BrowserSessionListQueryVariables = Exact<{
first?: InputMaybe<Scalars['Int']['input']>;
after?: InputMaybe<Scalars['String']['input']>;
@@ -1729,10 +1734,10 @@ export type AppSessionsListQuery = { __typename?: 'Query', viewer: { __typename:
export type CurrentUserGreetingQueryVariables = Exact<{ [key: string]: never; }>;
export type CurrentUserGreetingQuery = { __typename?: 'Query', viewerSession: { __typename: 'Anonymous' } | { __typename: 'BrowserSession', id: string, user: (
{ __typename?: 'User' }
& { ' $fragmentRefs'?: { 'UserGreeting_UserFragment': UserGreeting_UserFragment } }
) } | { __typename: 'Oauth2Session' }, siteConfig: (
export type CurrentUserGreetingQuery = { __typename?: 'Query', viewer: { __typename: 'Anonymous' } | (
{ __typename: 'User' }
& { ' $fragmentRefs'?: { 'UserGreeting_UserFragment': UserGreeting_UserFragment } }
), siteConfig: (
{ __typename?: 'SiteConfig' }
& { ' $fragmentRefs'?: { 'UserGreeting_SiteConfigFragment': UserGreeting_SiteConfigFragment } }
) };
@@ -1842,6 +1847,22 @@ export type AllowCrossSigningResetMutationVariables = Exact<{
export type AllowCrossSigningResetMutation = { __typename?: 'Mutation', allowUserCrossSigningReset: { __typename?: 'AllowUserCrossSigningResetPayload', user?: { __typename?: 'User', id: string } | null } };
export type SessionDetailQueryVariables = Exact<{
id: Scalars['ID']['input'];
}>;
export type SessionDetailQuery = { __typename?: 'Query', viewerSession: { __typename?: 'Anonymous', id: string } | { __typename?: 'BrowserSession', id: string } | { __typename?: 'Oauth2Session', id: string }, node?: { __typename: 'Anonymous', id: string } | { __typename: 'Authentication', id: string } | (
{ __typename: 'BrowserSession', id: string }
& { ' $fragmentRefs'?: { 'BrowserSession_DetailFragment': BrowserSession_DetailFragment } }
) | (
{ __typename: 'CompatSession', id: string }
& { ' $fragmentRefs'?: { 'CompatSession_DetailFragment': CompatSession_DetailFragment } }
) | { __typename: 'CompatSsoLogin', id: string } | { __typename: 'Oauth2Client', id: string } | (
{ __typename: 'Oauth2Session', id: string }
& { ' $fragmentRefs'?: { 'OAuth2Session_DetailFragment': OAuth2Session_DetailFragment } }
) | { __typename: 'SiteConfig', id: string } | { __typename: 'UpstreamOAuth2Link', id: string } | { __typename: 'UpstreamOAuth2Provider', id: string } | { __typename: 'User', id: string } | { __typename: 'UserEmail', id: string } | { __typename: 'UserEmailAuthentication', id: string } | { __typename: 'UserRecoveryTicket', id: string } | null };
export class TypedDocumentString<TResult, TVariables>
extends String
implements DocumentTypeDecoration<TResult, TVariables>
@@ -1861,26 +1882,40 @@ export const PasswordChange_SiteConfigFragmentDoc = new TypedDocumentString(`
passwordChangeAllowed
}
`, {"fragmentName":"PasswordChange_siteConfig"}) as unknown as TypedDocumentString<PasswordChange_SiteConfigFragment, unknown>;
export const BrowserSession_SessionFragmentDoc = new TypedDocumentString(`
fragment BrowserSession_session on BrowserSession {
export const EndBrowserSessionButton_SessionFragmentDoc = new TypedDocumentString(`
fragment EndBrowserSessionButton_session on BrowserSession {
id
createdAt
finishedAt
userAgent {
raw
name
os
model
deviceType
}
lastActiveIp
lastActiveAt
lastAuthentication {
id
createdAt
}
}
`, {"fragmentName":"BrowserSession_session"}) as unknown as TypedDocumentString<BrowserSession_SessionFragment, unknown>;
`, {"fragmentName":"EndBrowserSessionButton_session"}) as unknown as TypedDocumentString<EndBrowserSessionButton_SessionFragment, unknown>;
export const BrowserSession_SessionFragmentDoc = new TypedDocumentString(`
fragment BrowserSession_session on BrowserSession {
id
createdAt
finishedAt
...EndBrowserSessionButton_session
userAgent {
deviceType
name
os
model
}
lastActiveAt
}
fragment EndBrowserSessionButton_session on BrowserSession {
id
userAgent {
name
os
model
deviceType
}
}`, {"fragmentName":"BrowserSession_session"}) as unknown as TypedDocumentString<BrowserSession_SessionFragment, unknown>;
export const OAuth2Client_DetailFragmentDoc = new TypedDocumentString(`
fragment OAuth2Client_detail on Oauth2Client {
id
@@ -1893,14 +1928,9 @@ export const OAuth2Client_DetailFragmentDoc = new TypedDocumentString(`
redirectUris
}
`, {"fragmentName":"OAuth2Client_detail"}) as unknown as TypedDocumentString<OAuth2Client_DetailFragment, unknown>;
export const CompatSession_SessionFragmentDoc = new TypedDocumentString(`
fragment CompatSession_session on CompatSession {
export const EndCompatSessionButton_SessionFragmentDoc = new TypedDocumentString(`
fragment EndCompatSessionButton_session on CompatSession {
id
createdAt
deviceId
finishedAt
lastActiveIp
lastActiveAt
userAgent {
name
os
@@ -1912,7 +1942,40 @@ export const CompatSession_SessionFragmentDoc = new TypedDocumentString(`
redirectUri
}
}
`, {"fragmentName":"CompatSession_session"}) as unknown as TypedDocumentString<CompatSession_SessionFragment, unknown>;
`, {"fragmentName":"EndCompatSessionButton_session"}) as unknown as TypedDocumentString<EndCompatSessionButton_SessionFragment, unknown>;
export const CompatSession_SessionFragmentDoc = new TypedDocumentString(`
fragment CompatSession_session on CompatSession {
id
createdAt
deviceId
finishedAt
lastActiveIp
lastActiveAt
...EndCompatSessionButton_session
userAgent {
name
os
model
deviceType
}
ssoLogin {
id
redirectUri
}
}
fragment EndCompatSessionButton_session on CompatSession {
id
userAgent {
name
os
model
deviceType
}
ssoLogin {
id
redirectUri
}
}`, {"fragmentName":"CompatSession_session"}) as unknown as TypedDocumentString<CompatSession_SessionFragment, unknown>;
export const Footer_SiteConfigFragmentDoc = new TypedDocumentString(`
fragment Footer_siteConfig on SiteConfig {
id
@@ -1921,6 +1984,23 @@ export const Footer_SiteConfigFragmentDoc = new TypedDocumentString(`
policyUri
}
`, {"fragmentName":"Footer_siteConfig"}) as unknown as TypedDocumentString<Footer_SiteConfigFragment, unknown>;
export const EndOAuth2SessionButton_SessionFragmentDoc = new TypedDocumentString(`
fragment EndOAuth2SessionButton_session on Oauth2Session {
id
userAgent {
name
model
os
deviceType
}
client {
clientId
clientName
applicationType
logoUri
}
}
`, {"fragmentName":"EndOAuth2SessionButton_session"}) as unknown as TypedDocumentString<EndOAuth2SessionButton_SessionFragment, unknown>;
export const OAuth2Session_SessionFragmentDoc = new TypedDocumentString(`
fragment OAuth2Session_session on Oauth2Session {
id
@@ -1929,6 +2009,7 @@ export const OAuth2Session_SessionFragmentDoc = new TypedDocumentString(`
finishedAt
lastActiveIp
lastActiveAt
...EndOAuth2SessionButton_session
userAgent {
name
model
@@ -1943,12 +2024,27 @@ export const OAuth2Session_SessionFragmentDoc = new TypedDocumentString(`
logoUri
}
}
`, {"fragmentName":"OAuth2Session_session"}) as unknown as TypedDocumentString<OAuth2Session_SessionFragment, unknown>;
fragment EndOAuth2SessionButton_session on Oauth2Session {
id
userAgent {
name
model
os
deviceType
}
client {
clientId
clientName
applicationType
logoUri
}
}`, {"fragmentName":"OAuth2Session_session"}) as unknown as TypedDocumentString<OAuth2Session_SessionFragment, unknown>;
export const BrowserSession_DetailFragmentDoc = new TypedDocumentString(`
fragment BrowserSession_detail on BrowserSession {
id
createdAt
finishedAt
...EndBrowserSessionButton_session
userAgent {
name
model
@@ -1965,7 +2061,15 @@ export const BrowserSession_DetailFragmentDoc = new TypedDocumentString(`
username
}
}
`, {"fragmentName":"BrowserSession_detail"}) as unknown as TypedDocumentString<BrowserSession_DetailFragment, unknown>;
fragment EndBrowserSessionButton_session on BrowserSession {
id
userAgent {
name
os
model
deviceType
}
}`, {"fragmentName":"BrowserSession_detail"}) as unknown as TypedDocumentString<BrowserSession_DetailFragment, unknown>;
export const CompatSession_DetailFragmentDoc = new TypedDocumentString(`
fragment CompatSession_detail on CompatSession {
id
@@ -1974,6 +2078,7 @@ export const CompatSession_DetailFragmentDoc = new TypedDocumentString(`
finishedAt
lastActiveIp
lastActiveAt
...EndCompatSessionButton_session
userAgent {
name
os
@@ -1984,7 +2089,19 @@ export const CompatSession_DetailFragmentDoc = new TypedDocumentString(`
redirectUri
}
}
`, {"fragmentName":"CompatSession_detail"}) as unknown as TypedDocumentString<CompatSession_DetailFragment, unknown>;
fragment EndCompatSessionButton_session on CompatSession {
id
userAgent {
name
os
model
deviceType
}
ssoLogin {
id
redirectUri
}
}`, {"fragmentName":"CompatSession_detail"}) as unknown as TypedDocumentString<CompatSession_DetailFragment, unknown>;
export const OAuth2Session_DetailFragmentDoc = new TypedDocumentString(`
fragment OAuth2Session_detail on Oauth2Session {
id
@@ -1993,6 +2110,12 @@ export const OAuth2Session_DetailFragmentDoc = new TypedDocumentString(`
finishedAt
lastActiveIp
lastActiveAt
...EndOAuth2SessionButton_session
userAgent {
name
model
os
}
client {
id
clientId
@@ -2001,7 +2124,21 @@ export const OAuth2Session_DetailFragmentDoc = new TypedDocumentString(`
logoUri
}
}
`, {"fragmentName":"OAuth2Session_detail"}) as unknown as TypedDocumentString<OAuth2Session_DetailFragment, unknown>;
fragment EndOAuth2SessionButton_session on Oauth2Session {
id
userAgent {
name
model
os
deviceType
}
client {
clientId
clientName
applicationType
logoUri
}
}`, {"fragmentName":"OAuth2Session_detail"}) as unknown as TypedDocumentString<OAuth2Session_DetailFragment, unknown>;
export const UserEmail_EmailFragmentDoc = new TypedDocumentString(`
fragment UserEmail_email on UserEmail {
id
@@ -2060,44 +2197,6 @@ export const RecoverPassword_SiteConfigFragmentDoc = new TypedDocumentString(`
id
minimumPasswordComplexity
}`, {"fragmentName":"RecoverPassword_siteConfig"}) as unknown as TypedDocumentString<RecoverPassword_SiteConfigFragment, unknown>;
export const EndBrowserSessionDocument = new TypedDocumentString(`
mutation EndBrowserSession($id: ID!) {
endBrowserSession(input: {browserSessionId: $id}) {
status
browserSession {
id
...BrowserSession_session
}
}
}
fragment BrowserSession_session on BrowserSession {
id
createdAt
finishedAt
userAgent {
raw
name
os
model
deviceType
}
lastActiveIp
lastActiveAt
lastAuthentication {
id
createdAt
}
}`) as unknown as TypedDocumentString<EndBrowserSessionMutation, EndBrowserSessionMutationVariables>;
export const EndCompatSessionDocument = new TypedDocumentString(`
mutation EndCompatSession($id: ID!) {
endCompatSession(input: {compatSessionId: $id}) {
status
compatSession {
id
}
}
}
`) as unknown as TypedDocumentString<EndCompatSessionMutation, EndCompatSessionMutationVariables>;
export const FooterDocument = new TypedDocumentString(`
query Footer {
siteConfig {
@@ -2111,6 +2210,26 @@ export const FooterDocument = new TypedDocumentString(`
tosUri
policyUri
}`) as unknown as TypedDocumentString<FooterQuery, FooterQueryVariables>;
export const EndBrowserSessionDocument = new TypedDocumentString(`
mutation EndBrowserSession($id: ID!) {
endBrowserSession(input: {browserSessionId: $id}) {
status
browserSession {
id
}
}
}
`) as unknown as TypedDocumentString<EndBrowserSessionMutation, EndBrowserSessionMutationVariables>;
export const EndCompatSessionDocument = new TypedDocumentString(`
mutation EndCompatSession($id: ID!) {
endCompatSession(input: {compatSessionId: $id}) {
status
compatSession {
id
}
}
}
`) as unknown as TypedDocumentString<EndCompatSessionMutation, EndCompatSessionMutationVariables>;
export const EndOAuth2SessionDocument = new TypedDocumentString(`
mutation EndOAuth2Session($id: ID!) {
endOauth2Session(input: {oauth2SessionId: $id}) {
@@ -2178,11 +2297,14 @@ export const UserEmailListDocument = new TypedDocumentString(`
}`) as unknown as TypedDocumentString<UserEmailListQuery, UserEmailListQueryVariables>;
export const UserProfileDocument = new TypedDocumentString(`
query UserProfile {
viewer {
viewerSession {
__typename
... on User {
emails(first: 0) {
totalCount
... on BrowserSession {
id
user {
emails(first: 0) {
totalCount
}
}
}
}
@@ -2203,73 +2325,6 @@ fragment UserEmail_siteConfig on SiteConfig {
fragment UserEmailList_siteConfig on SiteConfig {
emailChangeAllowed
}`) as unknown as TypedDocumentString<UserProfileQuery, UserProfileQueryVariables>;
export const SessionDetailDocument = new TypedDocumentString(`
query SessionDetail($id: ID!) {
viewerSession {
... on Node {
id
}
}
node(id: $id) {
__typename
id
...CompatSession_detail
...OAuth2Session_detail
...BrowserSession_detail
}
}
fragment BrowserSession_detail on BrowserSession {
id
createdAt
finishedAt
userAgent {
name
model
os
}
lastActiveIp
lastActiveAt
lastAuthentication {
id
createdAt
}
user {
id
username
}
}
fragment CompatSession_detail on CompatSession {
id
createdAt
deviceId
finishedAt
lastActiveIp
lastActiveAt
userAgent {
name
os
model
}
ssoLogin {
id
redirectUri
}
}
fragment OAuth2Session_detail on Oauth2Session {
id
scope
createdAt
finishedAt
lastActiveIp
lastActiveAt
client {
id
clientId
clientName
clientUri
logoUri
}
}`) as unknown as TypedDocumentString<SessionDetailQuery, SessionDetailQueryVariables>;
export const BrowserSessionListDocument = new TypedDocumentString(`
query BrowserSessionList($first: Int, $after: String, $last: Int, $before: String, $lastActive: DateFilter) {
viewerSession {
@@ -2309,19 +2364,23 @@ export const BrowserSessionListDocument = new TypedDocumentString(`
id
createdAt
finishedAt
...EndBrowserSessionButton_session
userAgent {
deviceType
name
os
model
}
lastActiveAt
}
fragment EndBrowserSessionButton_session on BrowserSession {
id
userAgent {
raw
name
os
model
deviceType
}
lastActiveIp
lastActiveAt
lastAuthentication {
id
createdAt
}
}`) as unknown as TypedDocumentString<BrowserSessionListQuery, BrowserSessionListQueryVariables>;
export const SessionsOverviewDocument = new TypedDocumentString(`
query SessionsOverview {
@@ -2379,6 +2438,7 @@ export const AppSessionsListDocument = new TypedDocumentString(`
finishedAt
lastActiveIp
lastActiveAt
...EndCompatSessionButton_session
userAgent {
name
os
@@ -2397,6 +2457,7 @@ fragment OAuth2Session_session on Oauth2Session {
finishedAt
lastActiveIp
lastActiveAt
...EndOAuth2SessionButton_session
userAgent {
name
model
@@ -2410,16 +2471,41 @@ fragment OAuth2Session_session on Oauth2Session {
applicationType
logoUri
}
}
fragment EndCompatSessionButton_session on CompatSession {
id
userAgent {
name
os
model
deviceType
}
ssoLogin {
id
redirectUri
}
}
fragment EndOAuth2SessionButton_session on Oauth2Session {
id
userAgent {
name
model
os
deviceType
}
client {
clientId
clientName
applicationType
logoUri
}
}`) as unknown as TypedDocumentString<AppSessionsListQuery, AppSessionsListQueryVariables>;
export const CurrentUserGreetingDocument = new TypedDocumentString(`
query CurrentUserGreeting {
viewerSession {
viewer {
__typename
... on BrowserSession {
id
user {
...UserGreeting_user
}
... on User {
...UserGreeting_user
}
}
siteConfig {
@@ -2565,6 +2651,139 @@ export const AllowCrossSigningResetDocument = new TypedDocumentString(`
}
}
`) as unknown as TypedDocumentString<AllowCrossSigningResetMutation, AllowCrossSigningResetMutationVariables>;
export const SessionDetailDocument = new TypedDocumentString(`
query SessionDetail($id: ID!) {
viewerSession {
... on Node {
id
}
}
node(id: $id) {
__typename
id
...CompatSession_detail
...OAuth2Session_detail
...BrowserSession_detail
}
}
fragment EndBrowserSessionButton_session on BrowserSession {
id
userAgent {
name
os
model
deviceType
}
}
fragment EndCompatSessionButton_session on CompatSession {
id
userAgent {
name
os
model
deviceType
}
ssoLogin {
id
redirectUri
}
}
fragment EndOAuth2SessionButton_session on Oauth2Session {
id
userAgent {
name
model
os
deviceType
}
client {
clientId
clientName
applicationType
logoUri
}
}
fragment BrowserSession_detail on BrowserSession {
id
createdAt
finishedAt
...EndBrowserSessionButton_session
userAgent {
name
model
os
}
lastActiveIp
lastActiveAt
lastAuthentication {
id
createdAt
}
user {
id
username
}
}
fragment CompatSession_detail on CompatSession {
id
createdAt
deviceId
finishedAt
lastActiveIp
lastActiveAt
...EndCompatSessionButton_session
userAgent {
name
os
model
}
ssoLogin {
id
redirectUri
}
}
fragment OAuth2Session_detail on Oauth2Session {
id
scope
createdAt
finishedAt
lastActiveIp
lastActiveAt
...EndOAuth2SessionButton_session
userAgent {
name
model
os
}
client {
id
clientId
clientName
clientUri
logoUri
}
}`) as unknown as TypedDocumentString<SessionDetailQuery, SessionDetailQueryVariables>;
/**
* @param resolver A function that accepts [resolver arguments](https://mswjs.io/docs/api/graphql#resolver-argument) and must always return the instruction on what to do with the intercepted request. ([see more](https://mswjs.io/docs/concepts/response-resolver#resolver-instructions))
* @param options Options object to customize the behavior of the mock. ([see more](https://mswjs.io/docs/api/graphql#handler-options))
* @see https://mswjs.io/docs/basics/response-resolver
* @example
* mockFooterQuery(
* ({ query, variables }) => {
* return HttpResponse.json({
* data: { siteConfig }
* })
* },
* requestOptions
* )
*/
export const mockFooterQuery = (resolver: GraphQLResponseResolver<FooterQuery, FooterQueryVariables>, options?: RequestHandlerOptions) =>
graphql.query<FooterQuery, FooterQueryVariables>(
'Footer',
resolver,
options
)
/**
* @param resolver A function that accepts [resolver arguments](https://mswjs.io/docs/api/graphql#resolver-argument) and must always return the instruction on what to do with the intercepted request. ([see more](https://mswjs.io/docs/concepts/response-resolver#resolver-instructions))
@@ -2610,27 +2829,6 @@ export const mockEndCompatSessionMutation = (resolver: GraphQLResponseResolver<E
options
)
/**
* @param resolver A function that accepts [resolver arguments](https://mswjs.io/docs/api/graphql#resolver-argument) and must always return the instruction on what to do with the intercepted request. ([see more](https://mswjs.io/docs/concepts/response-resolver#resolver-instructions))
* @param options Options object to customize the behavior of the mock. ([see more](https://mswjs.io/docs/api/graphql#handler-options))
* @see https://mswjs.io/docs/basics/response-resolver
* @example
* mockFooterQuery(
* ({ query, variables }) => {
* return HttpResponse.json({
* data: { siteConfig }
* })
* },
* requestOptions
* )
*/
export const mockFooterQuery = (resolver: GraphQLResponseResolver<FooterQuery, FooterQueryVariables>, options?: RequestHandlerOptions) =>
graphql.query<FooterQuery, FooterQueryVariables>(
'Footer',
resolver,
options
)
/**
* @param resolver A function that accepts [resolver arguments](https://mswjs.io/docs/api/graphql#resolver-argument) and must always return the instruction on what to do with the intercepted request. ([see more](https://mswjs.io/docs/concepts/response-resolver#resolver-instructions))
* @param options Options object to customize the behavior of the mock. ([see more](https://mswjs.io/docs/api/graphql#handler-options))
@@ -2749,7 +2947,7 @@ export const mockUserEmailListQuery = (resolver: GraphQLResponseResolver<UserEma
* mockUserProfileQuery(
* ({ query, variables }) => {
* return HttpResponse.json({
* data: { viewer, siteConfig }
* data: { viewerSession, siteConfig }
* })
* },
* requestOptions
@@ -2762,28 +2960,6 @@ export const mockUserProfileQuery = (resolver: GraphQLResponseResolver<UserProfi
options
)
/**
* @param resolver A function that accepts [resolver arguments](https://mswjs.io/docs/api/graphql#resolver-argument) and must always return the instruction on what to do with the intercepted request. ([see more](https://mswjs.io/docs/concepts/response-resolver#resolver-instructions))
* @param options Options object to customize the behavior of the mock. ([see more](https://mswjs.io/docs/api/graphql#handler-options))
* @see https://mswjs.io/docs/basics/response-resolver
* @example
* mockSessionDetailQuery(
* ({ query, variables }) => {
* const { id } = variables;
* return HttpResponse.json({
* data: { viewerSession, node }
* })
* },
* requestOptions
* )
*/
export const mockSessionDetailQuery = (resolver: GraphQLResponseResolver<SessionDetailQuery, SessionDetailQueryVariables>, options?: RequestHandlerOptions) =>
graphql.query<SessionDetailQuery, SessionDetailQueryVariables>(
'SessionDetail',
resolver,
options
)
/**
* @param resolver A function that accepts [resolver arguments](https://mswjs.io/docs/api/graphql#resolver-argument) and must always return the instruction on what to do with the intercepted request. ([see more](https://mswjs.io/docs/concepts/response-resolver#resolver-instructions))
* @param options Options object to customize the behavior of the mock. ([see more](https://mswjs.io/docs/api/graphql#handler-options))
@@ -2857,7 +3033,7 @@ export const mockAppSessionsListQuery = (resolver: GraphQLResponseResolver<AppSe
* mockCurrentUserGreetingQuery(
* ({ query, variables }) => {
* return HttpResponse.json({
* data: { viewerSession, siteConfig }
* data: { viewer, siteConfig }
* })
* },
* requestOptions
@@ -3131,3 +3307,25 @@ export const mockAllowCrossSigningResetMutation = (resolver: GraphQLResponseReso
resolver,
options
)
/**
* @param resolver A function that accepts [resolver arguments](https://mswjs.io/docs/api/graphql#resolver-argument) and must always return the instruction on what to do with the intercepted request. ([see more](https://mswjs.io/docs/concepts/response-resolver#resolver-instructions))
* @param options Options object to customize the behavior of the mock. ([see more](https://mswjs.io/docs/api/graphql#handler-options))
* @see https://mswjs.io/docs/basics/response-resolver
* @example
* mockSessionDetailQuery(
* ({ query, variables }) => {
* const { id } = variables;
* return HttpResponse.json({
* data: { viewerSession, node }
* })
* },
* requestOptions
* )
*/
export const mockSessionDetailQuery = (resolver: GraphQLResponseResolver<SessionDetailQuery, SessionDetailQueryVariables>, options?: RequestHandlerOptions) =>
graphql.query<SessionDetailQuery, SessionDetailQueryVariables>(
'SessionDetail',
resolver,
options
)

View File

@@ -17,6 +17,7 @@ import { Route as ResetCrossSigningImport } from './routes/reset-cross-signing'
import { Route as AccountImport } from './routes/_account'
import { Route as ResetCrossSigningIndexImport } from './routes/reset-cross-signing.index'
import { Route as AccountIndexImport } from './routes/_account.index'
import { Route as SessionsIdImport } from './routes/sessions.$id'
import { Route as ResetCrossSigningSuccessImport } from './routes/reset-cross-signing.success'
import { Route as ResetCrossSigningCancelledImport } from './routes/reset-cross-signing.cancelled'
import { Route as DevicesSplatImport } from './routes/devices.$'
@@ -27,7 +28,6 @@ import { Route as AccountSessionsIndexImport } from './routes/_account.sessions.
import { Route as EmailsIdVerifyImport } from './routes/emails.$id.verify'
import { Route as EmailsIdInUseImport } from './routes/emails.$id.in-use'
import { Route as AccountSessionsBrowsersImport } from './routes/_account.sessions.browsers'
import { Route as AccountSessionsIdImport } from './routes/_account.sessions.$id'
// Create Virtual Routes
@@ -62,6 +62,12 @@ const AccountIndexRoute = AccountIndexImport.update({
import('./routes/_account.index.lazy').then((d) => d.Route),
)
const SessionsIdRoute = SessionsIdImport.update({
id: '/sessions/$id',
path: '/sessions/$id',
getParentRoute: () => rootRoute,
} as any).lazy(() => import('./routes/sessions.$id.lazy').then((d) => d.Route))
const ResetCrossSigningSuccessRoute = ResetCrossSigningSuccessImport.update({
id: '/success',
path: '/success',
@@ -142,14 +148,6 @@ const AccountSessionsBrowsersRoute = AccountSessionsBrowsersImport.update({
import('./routes/_account.sessions.browsers.lazy').then((d) => d.Route),
)
const AccountSessionsIdRoute = AccountSessionsIdImport.update({
id: '/sessions/$id',
path: '/sessions/$id',
getParentRoute: () => AccountRoute,
} as any).lazy(() =>
import('./routes/_account.sessions.$id.lazy').then((d) => d.Route),
)
// Populate the FileRoutesByPath interface
declare module '@tanstack/react-router' {
@@ -196,6 +194,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ResetCrossSigningSuccessImport
parentRoute: typeof ResetCrossSigningImport
}
'/sessions/$id': {
id: '/sessions/$id'
path: '/sessions/$id'
fullPath: '/sessions/$id'
preLoaderRoute: typeof SessionsIdImport
parentRoute: typeof rootRoute
}
'/_account/': {
id: '/_account/'
path: '/'
@@ -210,13 +215,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ResetCrossSigningIndexImport
parentRoute: typeof ResetCrossSigningImport
}
'/_account/sessions/$id': {
id: '/_account/sessions/$id'
path: '/sessions/$id'
fullPath: '/sessions/$id'
preLoaderRoute: typeof AccountSessionsIdImport
parentRoute: typeof AccountImport
}
'/_account/sessions/browsers': {
id: '/_account/sessions/browsers'
path: '/sessions/browsers'
@@ -273,14 +271,12 @@ declare module '@tanstack/react-router' {
interface AccountRouteChildren {
AccountIndexRoute: typeof AccountIndexRoute
AccountSessionsIdRoute: typeof AccountSessionsIdRoute
AccountSessionsBrowsersRoute: typeof AccountSessionsBrowsersRoute
AccountSessionsIndexRoute: typeof AccountSessionsIndexRoute
}
const AccountRouteChildren: AccountRouteChildren = {
AccountIndexRoute: AccountIndexRoute,
AccountSessionsIdRoute: AccountSessionsIdRoute,
AccountSessionsBrowsersRoute: AccountSessionsBrowsersRoute,
AccountSessionsIndexRoute: AccountSessionsIndexRoute,
}
@@ -310,9 +306,9 @@ export interface FileRoutesByFullPath {
'/devices/$': typeof DevicesSplatRoute
'/reset-cross-signing/cancelled': typeof ResetCrossSigningCancelledRoute
'/reset-cross-signing/success': typeof ResetCrossSigningSuccessRoute
'/sessions/$id': typeof SessionsIdRoute
'/': typeof AccountIndexRoute
'/reset-cross-signing/': typeof ResetCrossSigningIndexRoute
'/sessions/$id': typeof AccountSessionsIdRoute
'/sessions/browsers': typeof AccountSessionsBrowsersRoute
'/emails/$id/in-use': typeof EmailsIdInUseRoute
'/emails/$id/verify': typeof EmailsIdVerifyRoute
@@ -327,9 +323,9 @@ export interface FileRoutesByTo {
'/devices/$': typeof DevicesSplatRoute
'/reset-cross-signing/cancelled': typeof ResetCrossSigningCancelledRoute
'/reset-cross-signing/success': typeof ResetCrossSigningSuccessRoute
'/sessions/$id': typeof SessionsIdRoute
'/': typeof AccountIndexRoute
'/reset-cross-signing': typeof ResetCrossSigningIndexRoute
'/sessions/$id': typeof AccountSessionsIdRoute
'/sessions/browsers': typeof AccountSessionsBrowsersRoute
'/emails/$id/in-use': typeof EmailsIdInUseRoute
'/emails/$id/verify': typeof EmailsIdVerifyRoute
@@ -347,9 +343,9 @@ export interface FileRoutesById {
'/devices/$': typeof DevicesSplatRoute
'/reset-cross-signing/cancelled': typeof ResetCrossSigningCancelledRoute
'/reset-cross-signing/success': typeof ResetCrossSigningSuccessRoute
'/sessions/$id': typeof SessionsIdRoute
'/_account/': typeof AccountIndexRoute
'/reset-cross-signing/': typeof ResetCrossSigningIndexRoute
'/_account/sessions/$id': typeof AccountSessionsIdRoute
'/_account/sessions/browsers': typeof AccountSessionsBrowsersRoute
'/emails/$id/in-use': typeof EmailsIdInUseRoute
'/emails/$id/verify': typeof EmailsIdVerifyRoute
@@ -368,9 +364,9 @@ export interface FileRouteTypes {
| '/devices/$'
| '/reset-cross-signing/cancelled'
| '/reset-cross-signing/success'
| '/sessions/$id'
| '/'
| '/reset-cross-signing/'
| '/sessions/$id'
| '/sessions/browsers'
| '/emails/$id/in-use'
| '/emails/$id/verify'
@@ -384,9 +380,9 @@ export interface FileRouteTypes {
| '/devices/$'
| '/reset-cross-signing/cancelled'
| '/reset-cross-signing/success'
| '/sessions/$id'
| '/'
| '/reset-cross-signing'
| '/sessions/$id'
| '/sessions/browsers'
| '/emails/$id/in-use'
| '/emails/$id/verify'
@@ -402,9 +398,9 @@ export interface FileRouteTypes {
| '/devices/$'
| '/reset-cross-signing/cancelled'
| '/reset-cross-signing/success'
| '/sessions/$id'
| '/_account/'
| '/reset-cross-signing/'
| '/_account/sessions/$id'
| '/_account/sessions/browsers'
| '/emails/$id/in-use'
| '/emails/$id/verify'
@@ -420,6 +416,7 @@ export interface RootRouteChildren {
ResetCrossSigningRoute: typeof ResetCrossSigningRouteWithChildren
ClientsIdRoute: typeof ClientsIdRoute
DevicesSplatRoute: typeof DevicesSplatRoute
SessionsIdRoute: typeof SessionsIdRoute
EmailsIdInUseRoute: typeof EmailsIdInUseRoute
EmailsIdVerifyRoute: typeof EmailsIdVerifyRoute
PasswordChangeSuccessLazyRoute: typeof PasswordChangeSuccessLazyRoute
@@ -432,6 +429,7 @@ const rootRouteChildren: RootRouteChildren = {
ResetCrossSigningRoute: ResetCrossSigningRouteWithChildren,
ClientsIdRoute: ClientsIdRoute,
DevicesSplatRoute: DevicesSplatRoute,
SessionsIdRoute: SessionsIdRoute,
EmailsIdInUseRoute: EmailsIdInUseRoute,
EmailsIdVerifyRoute: EmailsIdVerifyRoute,
PasswordChangeSuccessLazyRoute: PasswordChangeSuccessLazyRoute,
@@ -453,6 +451,7 @@ export const routeTree = rootRoute
"/reset-cross-signing",
"/clients/$id",
"/devices/$",
"/sessions/$id",
"/emails/$id/in-use",
"/emails/$id/verify",
"/password/change/success",
@@ -464,7 +463,6 @@ export const routeTree = rootRoute
"filePath": "_account.tsx",
"children": [
"/_account/",
"/_account/sessions/$id",
"/_account/sessions/browsers",
"/_account/sessions/"
]
@@ -491,6 +489,9 @@ export const routeTree = rootRoute
"filePath": "reset-cross-signing.success.tsx",
"parent": "/reset-cross-signing"
},
"/sessions/$id": {
"filePath": "sessions.$id.tsx"
},
"/_account/": {
"filePath": "_account.index.tsx",
"parent": "/_account"
@@ -499,10 +500,6 @@ export const routeTree = rootRoute
"filePath": "reset-cross-signing.index.tsx",
"parent": "/reset-cross-signing"
},
"/_account/sessions/$id": {
"filePath": "_account.sessions.$id.tsx",
"parent": "/_account"
},
"/_account/sessions/browsers": {
"filePath": "_account.sessions.browsers.tsx",
"parent": "/_account"

View File

@@ -10,28 +10,63 @@ import {
notFound,
useNavigate,
} from "@tanstack/react-router";
import { Separator, Text } from "@vector-im/compound-web";
import IconSignOut from "@vector-im/compound-design-tokens/assets/web/icons/sign-out";
import { Button, Separator, Text } from "@vector-im/compound-web";
import { useTranslation } from "react-i18next";
import AccountManagementPasswordPreview from "../components/AccountManagementPasswordPreview";
import { ButtonLink } from "../components/ButtonLink";
import * as Collapsible from "../components/Collapsible";
import * as Dialog from "../components/Dialog";
import LoadingSpinner from "../components/LoadingSpinner";
import { useEndBrowserSession } from "../components/Session/EndBrowserSessionButton";
import AddEmailForm from "../components/UserProfile/AddEmailForm";
import UserEmailList from "../components/UserProfile/UserEmailList";
import { query } from "./_account.index";
export const Route = createLazyFileRoute("/_account/")({
component: Index,
});
const SignOutButton: React.FC<{ id: string }> = ({ id }) => {
const { t } = useTranslation();
const mutation = useEndBrowserSession(id, true);
return (
<Dialog.Dialog
trigger={
<Button kind="primary" destructive size="lg" Icon={IconSignOut}>
Sign out of account
</Button>
}
>
<Dialog.Title>Sign out of your account?</Dialog.Title>
<Button
type="button"
kind="primary"
destructive
onClick={() => mutation.mutate()}
disabled={mutation.isPending}
Icon={mutation.isPending ? undefined : IconSignOut}
>
{mutation.isPending && <LoadingSpinner inline />}
{t("action.sign_out")}
</Button>
<Dialog.Close asChild>
<Button kind="tertiary">{t("action.cancel")}</Button>
</Dialog.Close>
</Dialog.Dialog>
);
};
function Index(): React.ReactElement {
const navigate = useNavigate();
const { t } = useTranslation();
const {
data: { viewer, siteConfig },
data: { viewerSession, siteConfig },
} = useSuspenseQuery(query);
if (viewer?.__typename !== "User") throw notFound();
if (viewerSession?.__typename !== "BrowserSession") throw notFound();
// When adding an email, we want to go to the email verification form
const onAdd = async (id: string): Promise<void> => {
@@ -39,45 +74,52 @@ function Index(): React.ReactElement {
};
return (
<div className="flex flex-col gap-4 mb-4">
{/* Only display this section if the user can add email addresses to their
<>
<div className="flex flex-col gap-4">
{/* Only display this section if the user can add email addresses to their
account *or* if they have any existing email addresses */}
{(siteConfig.emailChangeAllowed || viewer.emails.totalCount > 0) && (
<>
<Collapsible.Section
defaultOpen
title={t("frontend.account.contact_info")}
>
<UserEmailList siteConfig={siteConfig} />
{(siteConfig.emailChangeAllowed ||
viewerSession.user.emails.totalCount > 0) && (
<>
<Collapsible.Section
defaultOpen
title={t("frontend.account.contact_info")}
>
<UserEmailList siteConfig={siteConfig} />
{siteConfig.emailChangeAllowed && <AddEmailForm onAdd={onAdd} />}
</Collapsible.Section>
{siteConfig.emailChangeAllowed && <AddEmailForm onAdd={onAdd} />}
</Collapsible.Section>
<Separator kind="section" />
</>
)}
<Separator kind="section" />
</>
)}
{siteConfig.passwordLoginEnabled && (
<>
<Collapsible.Section
defaultOpen
title={t("frontend.account.account_password")}
>
<AccountManagementPasswordPreview siteConfig={siteConfig} />
</Collapsible.Section>
{siteConfig.passwordLoginEnabled && (
<>
<Collapsible.Section
defaultOpen
title={t("frontend.account.account_password")}
>
<AccountManagementPasswordPreview siteConfig={siteConfig} />
</Collapsible.Section>
<Separator kind="section" />
</>
)}
<Separator kind="section" />
</>
)}
<Collapsible.Section title={t("common.e2ee")}>
<Text className="text-secondary" size="md">
{t("frontend.reset_cross_signing.description")}
</Text>
<ButtonLink to="/reset-cross-signing" kind="secondary" destructive>
{t("frontend.reset_cross_signing.start_reset")}
</ButtonLink>
</Collapsible.Section>
</div>
<Collapsible.Section title={t("common.e2ee")}>
<Text className="text-secondary" size="md">
{t("frontend.reset_cross_signing.description")}
</Text>
<ButtonLink to="/reset-cross-signing" kind="secondary" destructive>
{t("frontend.reset_cross_signing.start_reset")}
</ButtonLink>
</Collapsible.Section>
<Separator kind="section" />
</div>
<SignOutButton id={viewerSession.id} />
</>
);
}

View File

@@ -13,11 +13,14 @@ import { graphqlRequest } from "../graphql";
const QUERY = graphql(/* GraphQL */ `
query UserProfile {
viewer {
viewerSession {
__typename
... on User {
emails(first: 0) {
totalCount
... on BrowserSession {
id
user {
emails(first: 0) {
totalCount
}
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2024 New Vector Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
@@ -7,12 +7,9 @@
import { Outlet, createLazyFileRoute, notFound } from "@tanstack/react-router";
import { Heading } from "@vector-im/compound-web";
import { useTranslation } from "react-i18next";
import { useEndBrowserSession } from "../components/BrowserSession";
import Layout from "../components/Layout";
import NavBar from "../components/NavBar";
import NavItem from "../components/NavItem";
import EndSessionButton from "../components/Session/EndSessionButton";
import UserGreeting from "../components/UserGreeting";
import { useSuspenseQuery } from "@tanstack/react-query";
@@ -25,10 +22,9 @@ export const Route = createLazyFileRoute("/_account")({
function Account(): React.ReactElement {
const { t } = useTranslation();
const result = useSuspenseQuery(query);
const session = result.data.viewerSession;
if (session?.__typename !== "BrowserSession") throw notFound();
const viewer = result.data.viewer;
if (viewer?.__typename !== "User") throw notFound();
const siteConfig = result.data.siteConfig;
const onSessionEnd = useEndBrowserSession(session.id, true);
return (
<Layout wide>
@@ -37,12 +33,10 @@ function Account(): React.ReactElement {
<Heading size="md" weight="semibold">
{t("frontend.account.title")}
</Heading>
<EndSessionButton endSession={onSessionEnd} />
</header>
<div className="flex flex-col gap-4">
<UserGreeting user={session.user} siteConfig={siteConfig} />
<UserGreeting user={viewer} siteConfig={siteConfig} />
<NavBar>
<NavItem to="/">{t("frontend.nav.settings")}</NavItem>

View File

@@ -8,7 +8,6 @@ import { createLazyFileRoute, notFound } from "@tanstack/react-router";
import { H5 } from "@vector-im/compound-web";
import { useTranslation } from "react-i18next";
import BlockList from "../components/BlockList";
import BrowserSession from "../components/BrowserSession";
import { ButtonLink } from "../components/ButtonLink";
import EmptyState from "../components/EmptyState";
@@ -42,7 +41,7 @@ function BrowserSessions(): React.ReactElement {
// We reverse the list as we are paginating backwards
const edges = [...viewerSession.user.browserSessions.edges].reverse();
return (
<BlockList>
<div className="flex flex-col gap-10">
<H5>{t("frontend.browser_sessions_overview.heading")}</H5>
<div className="flex gap-2 items-start">
@@ -104,6 +103,6 @@ function BrowserSessions(): React.ReactElement {
</ButtonLink>
</div>
)}
</BlockList>
</div>
);
}

View File

@@ -8,7 +8,6 @@ import { createLazyFileRoute, notFound } from "@tanstack/react-router";
import { H3, Separator } from "@vector-im/compound-web";
import { useTranslation } from "react-i18next";
import BlockList from "../components/BlockList";
import { ButtonLink } from "../components/ButtonLink";
import CompatSession from "../components/CompatSession";
import EmptyState from "../components/EmptyState";
@@ -53,7 +52,7 @@ function Sessions(): React.ReactElement {
const edges = [...appSessions.edges].reverse();
return (
<BlockList>
<div className="flex flex-col gap-10">
<H3>{t("frontend.user_sessions_overview.heading")}</H3>
<BrowserSessionsOverview user={viewer} />
<Separator />
@@ -121,6 +120,6 @@ function Sessions(): React.ReactElement {
</ButtonLink>
</div>
)}
</BlockList>
</div>
);
}

View File

@@ -11,15 +11,10 @@ import { graphqlRequest } from "../graphql";
const QUERY = graphql(/* GraphQL */ `
query CurrentUserGreeting {
viewerSession {
viewer {
__typename
... on BrowserSession {
id
user {
...UserGreeting_user
}
... on User {
...UserGreeting_user
}
}

View File

@@ -15,7 +15,6 @@ import { Alert, Form, Separator } from "@vector-im/compound-web";
import { type FormEvent, useRef } from "react";
import { useTranslation } from "react-i18next";
import BlockList from "../components/BlockList";
import { ButtonLink } from "../components/ButtonLink";
import Layout from "../components/Layout";
import LoadingSpinner from "../components/LoadingSpinner";
@@ -103,7 +102,7 @@ function ChangePassword(): React.ReactNode {
return (
<Layout>
<BlockList>
<div className="flex flex-col gap-10">
<PageHeading
Icon={IconLockSolid}
title={t("frontend.password_change.title")}
@@ -181,7 +180,7 @@ function ChangePassword(): React.ReactNode {
{t("action.cancel")}
</ButtonLink>
</Form.Root>
</BlockList>
</div>
</Layout>
);
}

View File

@@ -7,8 +7,6 @@
import { createLazyFileRoute } from "@tanstack/react-router";
import IconCheckCircle from "@vector-im/compound-design-tokens/assets/web/icons/check-circle-solid";
import { useTranslation } from "react-i18next";
import BlockList from "../components/BlockList";
import { ButtonLink } from "../components/ButtonLink";
import Layout from "../components/Layout";
import PageHeading from "../components/PageHeading";
@@ -22,7 +20,7 @@ function ChangePasswordSuccess(): React.ReactNode {
return (
<Layout>
<BlockList>
<div className="flex flex-col gap-10">
<PageHeading
Icon={IconCheckCircle}
title={t("frontend.password_change.success.title")}
@@ -33,7 +31,7 @@ function ChangePasswordSuccess(): React.ReactNode {
<ButtonLink to="/" kind="tertiary">
{t("action.back")}
</ButtonLink>
</BlockList>
</div>
</Layout>
);
}

View File

@@ -16,8 +16,6 @@ import IconLockSolid from "@vector-im/compound-design-tokens/assets/web/icons/lo
import { Alert, Button, Form } from "@vector-im/compound-web";
import type { FormEvent } from "react";
import { useTranslation } from "react-i18next";
import BlockList from "../components/BlockList";
import { ButtonLink } from "../components/ButtonLink";
import Layout from "../components/Layout";
import LoadingSpinner from "../components/LoadingSpinner";
@@ -206,7 +204,7 @@ const EmailRecovery: React.FC<{
return (
<Layout>
<BlockList>
<div className="flex flex-col gap-10">
<PageHeading
Icon={IconLockSolid}
title={t("frontend.password_reset.title")}
@@ -256,7 +254,7 @@ const EmailRecovery: React.FC<{
{t("action.save_and_continue")}
</Form.Submit>
</Form.Root>
</BlockList>
</div>
</Layout>
);
};

View File

@@ -13,15 +13,16 @@ import { createFileRoute, notFound } from "@tanstack/react-router";
import IconCheck from "@vector-im/compound-design-tokens/assets/web/icons/check";
import IconError from "@vector-im/compound-design-tokens/assets/web/icons/error";
import IconInfo from "@vector-im/compound-design-tokens/assets/web/icons/info";
import { Button, Text } from "@vector-im/compound-web";
import {
Button,
Text,
VisualList,
VisualListItem,
} from "@vector-im/compound-web";
import { useTranslation } from "react-i18next";
import { ButtonLink } from "../components/ButtonLink";
import LoadingSpinner from "../components/LoadingSpinner";
import PageHeading from "../components/PageHeading";
import {
VisualList,
VisualListItem,
} from "../components/VisualList/VisualList";
import { graphql } from "../gql";
import { graphqlRequest } from "../graphql";
@@ -123,19 +124,15 @@ function ResetCrossSigning(): React.ReactNode {
</Text>
<VisualList>
<VisualListItem
Icon={IconCheck}
iconColor="var(--cpd-color-icon-success-primary)"
label={t("frontend.reset_cross_signing.effect_list.positive_1")}
/>
<VisualListItem
Icon={IconInfo}
label={t("frontend.reset_cross_signing.effect_list.neutral_1")}
/>
<VisualListItem
Icon={IconInfo}
label={t("frontend.reset_cross_signing.effect_list.neutral_2")}
/>
<VisualListItem Icon={IconCheck} success>
{t("frontend.reset_cross_signing.effect_list.positive_1")}
</VisualListItem>
<VisualListItem Icon={IconInfo}>
{t("frontend.reset_cross_signing.effect_list.neutral_1")}
</VisualListItem>
<VisualListItem Icon={IconInfo}>
{t("frontend.reset_cross_signing.effect_list.neutral_2")}
</VisualListItem>
</VisualList>
<Text className="text-center" size="md" weight="semibold">

View File

@@ -13,7 +13,6 @@ import { Button, Text } from "@vector-im/compound-web";
import * as v from "valibot";
import { useTranslation } from "react-i18next";
import BlockList from "../components/BlockList";
import Layout from "../components/Layout";
import PageHeading from "../components/PageHeading";
@@ -25,9 +24,9 @@ export const Route = createFileRoute("/reset-cross-signing")({
validateSearch: searchSchema,
component: () => (
<Layout>
<BlockList>
<div className="flex flex-col gap-10">
<Outlet />
</BlockList>
</div>
</Layout>
),
errorComponent: ResetCrossSigningError,

View File

@@ -4,19 +4,18 @@
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
import { useSuspenseQuery } from "@tanstack/react-query";
import { createLazyFileRoute, notFound } from "@tanstack/react-router";
import { Alert } from "@vector-im/compound-web";
import { useTranslation } from "react-i18next";
import Layout from "../components/Layout";
import { Link } from "../components/Link";
import BrowserSessionDetail from "../components/SessionDetail/BrowserSessionDetail";
import CompatSessionDetail from "../components/SessionDetail/CompatSessionDetail";
import OAuth2SessionDetail from "../components/SessionDetail/OAuth2SessionDetail";
import { query } from "./sessions.$id";
import { useSuspenseQuery } from "@tanstack/react-query";
import { query } from "./_account.sessions.$id";
export const Route = createLazyFileRoute("/_account/sessions/$id")({
export const Route = createLazyFileRoute("/sessions/$id")({
notFoundComponent: NotFound,
component: SessionDetail,
});
@@ -26,13 +25,15 @@ function NotFound(): React.ReactElement {
const { t } = useTranslation();
return (
<Alert
type="critical"
title={t("frontend.session_detail.alert.title", { deviceId: id })}
>
{t("frontend.session_detail.alert.text")}
<Link to="/sessions">{t("frontend.session_detail.alert.button")}</Link>
</Alert>
<Layout>
<Alert
type="critical"
title={t("frontend.session_detail.alert.title", { deviceId: id })}
>
{t("frontend.session_detail.alert.text")}
<Link to="/sessions">{t("frontend.session_detail.alert.button")}</Link>
</Alert>
</Layout>
);
}
@@ -45,15 +46,25 @@ function SessionDetail(): React.ReactElement {
switch (node.__typename) {
case "CompatSession":
return <CompatSessionDetail session={node} />;
return (
<Layout wide>
<CompatSessionDetail session={node} />
</Layout>
);
case "Oauth2Session":
return <OAuth2SessionDetail session={node} />;
return (
<Layout wide>
<OAuth2SessionDetail session={node} />
</Layout>
);
case "BrowserSession":
return (
<BrowserSessionDetail
session={node}
isCurrent={node.id === viewerSession.id}
/>
<Layout wide>
<BrowserSessionDetail
session={node}
isCurrent={node.id === viewerSession.id}
/>
</Layout>
);
default:
throw new Error("Unknown session type");

View File

@@ -34,7 +34,7 @@ export const query = (id: string) =>
graphqlRequest({ query: QUERY, signal, variables: { id } }),
});
export const Route = createFileRoute("/_account/sessions/$id")({
export const Route = createFileRoute("/sessions/$id")({
loader: ({ context, params }) =>
context.queryClient.ensureQueryData(query(params.id)),
});

View File

@@ -0,0 +1,33 @@
// Copyright 2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
/**
* Simplify a URL by removing the protocol, search params and hash.
*
* @param url The URL to simplify
* @returns The simplified URL
*/
const simplifyUrl = (url: string): string => {
let parsed: URL;
try {
parsed = new URL(url);
} catch (_e) {
// Not a valid URL, return the original
return url;
}
// Clear out the search params and hash
parsed.search = "";
parsed.hash = "";
if (parsed.protocol === "https:") {
return parsed.hostname;
}
// Return the simplified URL
return parsed.toString();
};
export default simplifyUrl;

View File

@@ -43,10 +43,13 @@ const userProfileHandler = ({
mockUserProfileQuery(() =>
HttpResponse.json({
data: {
viewer: {
__typename: "User",
emails: {
totalCount: emailTotalCount,
viewerSession: {
__typename: "BrowserSession",
id: "session-id",
user: {
emails: {
totalCount: emailTotalCount,
},
},
},

View File

@@ -60,23 +60,19 @@ export const handlers = [
mockCurrentUserGreetingQuery(() =>
HttpResponse.json({
data: {
viewerSession: {
__typename: "BrowserSession",
id: "session-id",
user: Object.assign(
makeFragmentData(
{
id: "user-id",
matrix: {
mxid: "@alice:example.com",
displayName: "Alice",
},
viewer: Object.assign(
makeFragmentData(
{
__typename: "User",
id: "user-id",
matrix: {
mxid: "@alice:example.com",
displayName: "Alice",
},
USER_GREETING_FRAGMENT,
),
},
USER_GREETING_FRAGMENT,
),
},
),
siteConfig: makeFragmentData(
{
@@ -91,10 +87,13 @@ export const handlers = [
mockUserProfileQuery(() =>
HttpResponse.json({
data: {
viewer: {
__typename: "User",
emails: {
totalCount: 1,
viewerSession: {
__typename: "BrowserSession",
id: "browser-session-id",
user: {
emails: {
totalCount: 1,
},
},
},

View File

@@ -6,7 +6,7 @@ exports[`Reset cross signing > renders the cancelled page 1`] = `
class="_layoutContainer_0c8bf9"
>
<div
class="_blockList_f8cc7f"
class="flex flex-col gap-10"
>
<header
class="_pageHeading_c10486"
@@ -60,7 +60,7 @@ exports[`Reset cross signing > renders the deep link page 1`] = `
class="_layoutContainer_0c8bf9"
>
<div
class="_blockList_f8cc7f"
class="flex flex-col gap-10"
>
<header
class="_pageHeading_c10486"
@@ -96,38 +96,36 @@ exports[`Reset cross signing > renders the deep link page 1`] = `
If you're not signed in to any other devices and you've lost your recovery key, then you'll need to reset your identity to continue using the app.
</p>
<ul
class="_list_7f22f8"
class="_visual-list_4dcf8_17"
>
<li
class="_item_7f22f8"
class="_visual-list-item_bqeu7_17"
>
<svg
color="var(--cpd-color-icon-success-primary)"
aria-hidden="true"
class="_visual-list-item-icon_bqeu7_26 _visual-list-item-icon-success_bqeu7_31"
fill="currentColor"
height="1em"
height="24px"
viewBox="0 0 24 24"
width="1em"
width="24px"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.55 17.575c-.133 0-.258-.02-.375-.063a.876.876 0 0 1-.325-.212L4.55 13c-.183-.183-.27-.42-.263-.713.009-.291.105-.529.288-.712a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275L9.55 15.15l8.475-8.475c.183-.183.42-.275.713-.275.291 0 .529.092.712.275.183.183.275.42.275.713 0 .291-.092.529-.275.712l-9.2 9.2c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063Z"
/>
</svg>
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59"
>
Your account details, contacts, preferences, and chat list will be kept
</p>
Your account details, contacts, preferences, and chat list will be kept
</li>
<li
class="_item_7f22f8"
class="_visual-list-item_bqeu7_17"
>
<svg
color="var(--cpd-color-icon-tertiary)"
aria-hidden="true"
class="_visual-list-item-icon_bqeu7_26"
fill="currentColor"
height="1em"
height="24px"
viewBox="0 0 24 24"
width="1em"
width="24px"
xmlns="http://www.w3.org/2000/svg"
>
<path
@@ -139,21 +137,18 @@ exports[`Reset cross signing > renders the deep link page 1`] = `
fill-rule="evenodd"
/>
</svg>
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59"
>
You will lose any message history that's stored only on the server
</p>
You will lose any message history that's stored only on the server
</li>
<li
class="_item_7f22f8"
class="_visual-list-item_bqeu7_17"
>
<svg
color="var(--cpd-color-icon-tertiary)"
aria-hidden="true"
class="_visual-list-item-icon_bqeu7_26"
fill="currentColor"
height="1em"
height="24px"
viewBox="0 0 24 24"
width="1em"
width="24px"
xmlns="http://www.w3.org/2000/svg"
>
<path
@@ -165,11 +160,7 @@ exports[`Reset cross signing > renders the deep link page 1`] = `
fill-rule="evenodd"
/>
</svg>
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59"
>
You will need to verify all your existing devices and contacts again
</p>
You will need to verify all your existing devices and contacts again
</li>
</ul>
<p
@@ -340,7 +331,7 @@ exports[`Reset cross signing > renders the page 1`] = `
class="_layoutContainer_0c8bf9"
>
<div
class="_blockList_f8cc7f"
class="flex flex-col gap-10"
>
<header
class="_pageHeading_c10486"
@@ -376,38 +367,36 @@ exports[`Reset cross signing > renders the page 1`] = `
If you're not signed in to any other devices and you've lost your recovery key, then you'll need to reset your identity to continue using the app.
</p>
<ul
class="_list_7f22f8"
class="_visual-list_4dcf8_17"
>
<li
class="_item_7f22f8"
class="_visual-list-item_bqeu7_17"
>
<svg
color="var(--cpd-color-icon-success-primary)"
aria-hidden="true"
class="_visual-list-item-icon_bqeu7_26 _visual-list-item-icon-success_bqeu7_31"
fill="currentColor"
height="1em"
height="24px"
viewBox="0 0 24 24"
width="1em"
width="24px"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.55 17.575c-.133 0-.258-.02-.375-.063a.876.876 0 0 1-.325-.212L4.55 13c-.183-.183-.27-.42-.263-.713.009-.291.105-.529.288-.712a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275L9.55 15.15l8.475-8.475c.183-.183.42-.275.713-.275.291 0 .529.092.712.275.183.183.275.42.275.713 0 .291-.092.529-.275.712l-9.2 9.2c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063Z"
/>
</svg>
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59"
>
Your account details, contacts, preferences, and chat list will be kept
</p>
Your account details, contacts, preferences, and chat list will be kept
</li>
<li
class="_item_7f22f8"
class="_visual-list-item_bqeu7_17"
>
<svg
color="var(--cpd-color-icon-tertiary)"
aria-hidden="true"
class="_visual-list-item-icon_bqeu7_26"
fill="currentColor"
height="1em"
height="24px"
viewBox="0 0 24 24"
width="1em"
width="24px"
xmlns="http://www.w3.org/2000/svg"
>
<path
@@ -419,21 +408,18 @@ exports[`Reset cross signing > renders the page 1`] = `
fill-rule="evenodd"
/>
</svg>
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59"
>
You will lose any message history that's stored only on the server
</p>
You will lose any message history that's stored only on the server
</li>
<li
class="_item_7f22f8"
class="_visual-list-item_bqeu7_17"
>
<svg
color="var(--cpd-color-icon-tertiary)"
aria-hidden="true"
class="_visual-list-item-icon_bqeu7_26"
fill="currentColor"
height="1em"
height="24px"
viewBox="0 0 24 24"
width="1em"
width="24px"
xmlns="http://www.w3.org/2000/svg"
>
<path
@@ -445,11 +431,7 @@ exports[`Reset cross signing > renders the page 1`] = `
fill-rule="evenodd"
/>
</svg>
<p
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59"
>
You will need to verify all your existing devices and contacts again
</p>
You will need to verify all your existing devices and contacts again
</li>
</ul>
<p
@@ -529,7 +511,7 @@ exports[`Reset cross signing > renders the success page 1`] = `
class="_layoutContainer_0c8bf9"
>
<div
class="_blockList_f8cc7f"
class="flex flex-col gap-10"
>
<header
class="_pageHeading_c10486"
@@ -578,7 +560,7 @@ exports[`Reset cross signing > renders the success page 2`] = `
class="_layoutContainer_0c8bf9"
>
<div
class="_blockList_f8cc7f"
class="flex flex-col gap-10"
>
<header
class="_pageHeading_c10486"

View File

@@ -2,18 +2,18 @@
exports[`Account home page > display name edit box > displays an error if the display name is invalid 1`] = `
<div
aria-describedby="radix-:r75:"
aria-labelledby="radix-:r74:"
aria-describedby="radix-:r72:"
aria-labelledby="radix-:r71:"
class="_body_9cf7b0"
data-state="open"
id="radix-:r73:"
id="radix-:r70:"
role="dialog"
style="pointer-events: auto;"
tabindex="-1"
>
<h2
class="_title_9cf7b0"
id="radix-:r74:"
id="radix-:r71:"
>
Edit profile
</h2>
@@ -150,18 +150,18 @@ exports[`Account home page > display name edit box > displays an error if the di
exports[`Account home page > display name edit box > lets edit the display name 1`] = `
<div
aria-describedby="radix-:r1h:"
aria-labelledby="radix-:r1g:"
aria-describedby="radix-:r1e:"
aria-labelledby="radix-:r1d:"
class="_body_9cf7b0"
data-state="open"
id="radix-:r1f:"
id="radix-:r1c:"
role="dialog"
style="pointer-events: auto;"
tabindex="-1"
>
<h2
class="_title_9cf7b0"
id="radix-:r1g:"
id="radix-:r1d:"
>
Edit profile
</h2>
@@ -308,32 +308,6 @@ exports[`Account home page > renders the page 1`] = `
>
Your account
</h1>
<button
aria-controls="radix-:r0:"
aria-expanded="false"
aria-haspopup="dialog"
class="_button_i91xf_17 _has-icon_i91xf_66 _destructive_i91xf_116"
data-kind="secondary"
data-size="sm"
data-state="closed"
role="button"
tabindex="0"
type="button"
>
<svg
aria-hidden="true"
fill="currentColor"
height="20"
viewBox="0 0 24 24"
width="20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9 12.031c0-.283.096-.52.288-.712A.968.968 0 0 1 10 11.03h7.15l-1.875-1.875a.96.96 0 0 1-.3-.7c0-.266.108-.508.325-.725a.93.93 0 0 1 .712-.287.977.977 0 0 1 .688.287l3.6 3.6c.1.1.17.209.212.325.042.117.063.242.063.375 0 .134-.02.259-.063.375a.877.877 0 0 1-.212.325l-3.6 3.6a.93.93 0 0 1-.712.288.977.977 0 0 1-.688-.288 1.02 1.02 0 0 1-.313-.712.931.931 0 0 1 .288-.713l1.875-1.875H10a.968.968 0 0 1-.712-.287A.968.968 0 0 1 9 12.03Zm-6-7c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 5 3.03h6a.97.97 0 0 1 .713.288.968.968 0 0 1 .287.712.97.97 0 0 1-.287.713.968.968 0 0 1-.713.287H5v14h6a.97.97 0 0 1 .713.288.968.968 0 0 1 .287.712.97.97 0 0 1-.287.713.968.968 0 0 1-.713.287H5c-.55 0-1.02-.196-1.412-.587A1.926 1.926 0 0 1 3 19.03v-14Z"
/>
</svg>
Sign out
</button>
</header>
<div
class="flex flex-col gap-4"
@@ -366,10 +340,10 @@ exports[`Account home page > renders the page 1`] = `
</p>
</div>
<button
aria-controls="radix-:r3:"
aria-controls="radix-:r0:"
aria-expanded="false"
aria-haspopup="dialog"
aria-labelledby=":r6:"
aria-labelledby=":r3:"
class="_icon-button_bh2qc_17 _editButton_66f22a"
data-state="closed"
role="button"
@@ -430,10 +404,10 @@ exports[`Account home page > renders the page 1`] = `
</div>
</div>
<div
class="flex flex-col gap-4 mb-4"
class="flex flex-col gap-4"
>
<section
aria-labelledby=":rb:"
aria-labelledby=":r8:"
class="_root_f1daaa"
data-state="open"
>
@@ -445,14 +419,14 @@ exports[`Account home page > renders the page 1`] = `
>
<h4
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102 _triggerTitle_f1daaa"
id=":rb:"
id=":r8:"
>
Contact info
</h4>
<button
aria-controls="radix-:rd:"
aria-controls="radix-:ra:"
aria-expanded="true"
aria-labelledby=":re:"
aria-labelledby=":rb:"
class="_icon-button_bh2qc_17 _triggerIcon_f1daaa"
data-state="open"
role="button"
@@ -482,7 +456,7 @@ exports[`Account home page > renders the page 1`] = `
<article
class="_content_f1daaa"
data-state="open"
id="radix-:rd:"
id="radix-:ra:"
style="transition-duration: 0s; animation-name: none;"
>
<form
@@ -493,7 +467,7 @@ exports[`Account home page > renders the page 1`] = `
>
<label
class="_label_ssths_67"
for="radix-:rj:"
for="radix-:rg:"
>
Email
</label>
@@ -502,7 +476,7 @@ exports[`Account home page > renders the page 1`] = `
>
<input
class="_control_9gon8_18 _userEmailField_e2a518"
id="radix-:rj:"
id="radix-:rg:"
name="email"
readonly=""
title=""
@@ -520,7 +494,7 @@ exports[`Account home page > renders the page 1`] = `
>
<label
class="_label_ssths_67"
for="radix-:rk:"
for="radix-:rh:"
>
Add email
</label>
@@ -528,9 +502,9 @@ exports[`Account home page > renders the page 1`] = `
class="_controls_1h4nb_17"
>
<input
aria-describedby="radix-:rl:"
aria-describedby="radix-:ri:"
class="_control_9gon8_18"
id="radix-:rk:"
id="radix-:rh:"
name="input"
required=""
title=""
@@ -539,7 +513,7 @@ exports[`Account home page > renders the page 1`] = `
</div>
<span
class="_message_ssths_93 _help-message_ssths_99"
id="radix-:rl:"
id="radix-:ri:"
>
Add an alternative email you can use to access this account.
</span>
@@ -554,7 +528,7 @@ exports[`Account home page > renders the page 1`] = `
role="separator"
/>
<section
aria-labelledby=":rm:"
aria-labelledby=":rj:"
class="_root_f1daaa"
data-state="open"
>
@@ -566,14 +540,14 @@ exports[`Account home page > renders the page 1`] = `
>
<h4
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102 _triggerTitle_f1daaa"
id=":rm:"
id=":rj:"
>
Account password
</h4>
<button
aria-controls="radix-:ro:"
aria-controls="radix-:rl:"
aria-expanded="true"
aria-labelledby=":rp:"
aria-labelledby=":rm:"
class="_icon-button_bh2qc_17 _triggerIcon_f1daaa"
data-state="open"
role="button"
@@ -603,7 +577,7 @@ exports[`Account home page > renders the page 1`] = `
<article
class="_content_f1daaa"
data-state="open"
id="radix-:ro:"
id="radix-:rl:"
style="transition-duration: 0s; animation-name: none;"
>
<form
@@ -614,14 +588,14 @@ exports[`Account home page > renders the page 1`] = `
>
<label
class="_label_ssths_67"
for="radix-:ru:"
for="radix-:rr:"
>
Password
</label>
<input
aria-describedby="radix-:rv:"
aria-describedby="radix-:rs:"
class="_control_9gon8_18"
id="radix-:ru:"
id="radix-:rr:"
name="password_preview"
readonly=""
title=""
@@ -630,7 +604,7 @@ exports[`Account home page > renders the page 1`] = `
/>
<span
class="_message_ssths_93 _help-message_ssths_99"
id="radix-:rv:"
id="radix-:rs:"
>
<a
class="_link_7634c3"
@@ -650,7 +624,7 @@ exports[`Account home page > renders the page 1`] = `
role="separator"
/>
<section
aria-labelledby=":r10:"
aria-labelledby=":rt:"
class="_root_f1daaa"
data-state="closed"
>
@@ -662,14 +636,14 @@ exports[`Account home page > renders the page 1`] = `
>
<h4
class="_typography_yh5dq_162 _font-heading-sm-semibold_yh5dq_102 _triggerTitle_f1daaa"
id=":r10:"
id=":rt:"
>
End-to-end encryption
</h4>
<button
aria-controls="radix-:r12:"
aria-controls="radix-:rv:"
aria-expanded="false"
aria-labelledby=":r13:"
aria-labelledby=":r10:"
class="_icon-button_bh2qc_17 _triggerIcon_f1daaa"
data-state="closed"
role="button"
@@ -697,7 +671,39 @@ exports[`Account home page > renders the page 1`] = `
</div>
</header>
</section>
<div
class="_separator_144s5_17"
data-kind="section"
data-orientation="horizontal"
role="separator"
/>
</div>
<button
aria-controls="radix-:r15:"
aria-expanded="false"
aria-haspopup="dialog"
class="_button_i91xf_17 _has-icon_i91xf_66 _destructive_i91xf_116"
data-kind="primary"
data-size="lg"
data-state="closed"
role="button"
tabindex="0"
type="button"
>
<svg
aria-hidden="true"
fill="currentColor"
height="20"
viewBox="0 0 24 24"
width="20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9 12.031c0-.283.096-.52.288-.712A.968.968 0 0 1 10 11.03h7.15l-1.875-1.875a.96.96 0 0 1-.3-.7c0-.266.108-.508.325-.725a.93.93 0 0 1 .712-.287.977.977 0 0 1 .688.287l3.6 3.6c.1.1.17.209.212.325.042.117.063.242.063.375 0 .134-.02.259-.063.375a.877.877 0 0 1-.212.325l-3.6 3.6a.93.93 0 0 1-.712.288.977.977 0 0 1-.688-.288 1.02 1.02 0 0 1-.313-.712.931.931 0 0 1 .288-.713l1.875-1.875H10a.968.968 0 0 1-.712-.287A.968.968 0 0 1 9 12.03Zm-6-7c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 5 3.03h6a.97.97 0 0 1 .713.288.968.968 0 0 1 .287.712.97.97 0 0 1-.287.713.968.968 0 0 1-.713.287H5v14h6a.97.97 0 0 1 .713.288.968.968 0 0 1 .287.712.97.97 0 0 1-.287.713.968.968 0 0 1-.713.287H5c-.55 0-1.02-.196-1.412-.587A1.926 1.926 0 0 1 3 19.03v-14Z"
/>
</svg>
Sign out of account
</button>
<footer
class="_legalFooter_eb428f"
>