diff --git a/frontend/locales/en.json b/frontend/locales/en.json index 701fc158..cbdd3257 100644 --- a/frontend/locales/en.json +++ b/frontend/locales/en.json @@ -54,6 +54,10 @@ "body:one": "{{count}} active session", "body:other": "{{count}} active sessions", "heading": "Browsers", + "no_active_sessions": { + "default": "You are not signed in to any web browsers.", + "inactive_90_days": "All your sessions have been active in the last 90 days." + }, "view_all_button": "View all" }, "compat_session_detail": { @@ -230,9 +234,11 @@ "no_primary_email_alert": "No primary email address" }, "user_sessions_overview": { - "active_sessions:one": "{{count}} active session", - "active_sessions:other": "{{count}} active sessions", - "heading": "Where you're signed in" + "heading": "Where you're signed in", + "no_active_sessions": { + "default": "You are not signed in to any application.", + "inactive_90_days": "All your sessions have been active in the last 90 days." + } }, "verify_email": { "code_field_error": "Code not recognised", diff --git a/frontend/package.json b/frontend/package.json index 7123c4a9..8e7dc76d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -87,5 +87,6 @@ "vite-plugin-graphql-codegen": "^3.3.8", "vite-plugin-manifest-sri": "^0.2.0", "vitest": "^1.4.0" - } + }, + "packageManager": "pnpm@8.6.10+sha1.98fe2755061026799bfa30e7dc8d6d48e9c3edf0" } diff --git a/frontend/src/components/EmptyState/EmptyState.module.css b/frontend/src/components/EmptyState/EmptyState.module.css new file mode 100644 index 00000000..ec1800bd --- /dev/null +++ b/frontend/src/components/EmptyState/EmptyState.module.css @@ -0,0 +1,25 @@ +/* Copyright 2024 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.empty-state { + display: flex; + flex-direction: column; + gap: var(--cpd-space-2x); + padding: var(--cpd-space-4x); + background: var(--cpd-color-gray-200); + color: var(--cpd-color-text-secondary); + font: var(--cpd-font-body-sm-regular); + letter-spacing: var(--cpd-font-letter-spacing-body-sm); +} diff --git a/frontend/src/components/EmptyState/EmptyState.stories.tsx b/frontend/src/components/EmptyState/EmptyState.stories.tsx new file mode 100644 index 00000000..6428aa23 --- /dev/null +++ b/frontend/src/components/EmptyState/EmptyState.stories.tsx @@ -0,0 +1,35 @@ +// Copyright 2024 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Meta, StoryObj } from "@storybook/react"; + +import { EmptyState } from "./EmptyState"; + +const meta = { + title: "UI/EmptyState", + component: EmptyState, + tags: ["autodocs"], + args: { + children: "No results", + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Basic: Story = { + args: { + children: "No results", + }, +}; diff --git a/frontend/src/components/EmptyState/EmptyState.tsx b/frontend/src/components/EmptyState/EmptyState.tsx new file mode 100644 index 00000000..105473e2 --- /dev/null +++ b/frontend/src/components/EmptyState/EmptyState.tsx @@ -0,0 +1,33 @@ +// Copyright 2024 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import classNames from "classnames"; +import { forwardRef } from "react"; + +import styles from "./EmptyState.module.css"; + +/** + * A component to display a message when a list is empty + */ +export const EmptyState = forwardRef< + HTMLDivElement, + React.ComponentPropsWithoutRef<"div"> +>(function EmptyState({ children, ...props }, ref) { + const className = classNames(styles.emptyState, props.className); + return ( +
+ {children} +
+ ); +}); diff --git a/frontend/src/components/EmptyState/index.ts b/frontend/src/components/EmptyState/index.ts new file mode 100644 index 00000000..e0e9657b --- /dev/null +++ b/frontend/src/components/EmptyState/index.ts @@ -0,0 +1,15 @@ +// Copyright 2024 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +export { EmptyState as default } from "./EmptyState"; diff --git a/frontend/src/components/Filter/Filter.module.css b/frontend/src/components/Filter/Filter.module.css new file mode 100644 index 00000000..5a28e3b7 --- /dev/null +++ b/frontend/src/components/Filter/Filter.module.css @@ -0,0 +1,59 @@ +/* Copyright 2024 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.filter { + display: flex; + align-items: center; + gap: var(--cpd-space-2x); + font: var(--cpd-font-body-xs-regular); + letter-spacing: var(--cpd-font-letter-spacing-body-xs); + padding: var(--cpd-space-2x) var(--cpd-space-3x); + border-radius: var(--cpd-radius-pill-effect); +} + +.enabled-filter { + background: var(--cpd-color-bg-action-primary-rest); + color: var(--cpd-color-text-on-solid-primary); + + & > .close-icon { + height: var(--cpd-space-4x); + width: var(--cpd-space-4x); + opacity: 0.5; + } + + &:hover { + background: var(--cpd-color-bg-action-primary-hovered); + & > .close-icon { + opacity: 1; + } + } + + &:active { + background: var(--cpd-color-bg-action-primary-rest); + & > .close-icon { + opacity: 1; + } + } +} + +.disabled-filter { + color: var(--cpd-color-text-action-primary); + background: var(--cpd-color-bg-canvas-default); + outline: 1px solid var(--cpd-color-border-interactive-secondary); + + &:hover { + background: var(--cpd-color-bg-subtle-secondary); + } +} diff --git a/frontend/src/components/Filter/Filter.stories.tsx b/frontend/src/components/Filter/Filter.stories.tsx new file mode 100644 index 00000000..d1554e01 --- /dev/null +++ b/frontend/src/components/Filter/Filter.stories.tsx @@ -0,0 +1,53 @@ +// Copyright 2024 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Meta, StoryObj } from "@storybook/react"; + +import { DummyRouter } from "../../test-utils/router"; + +import { Filter } from "./Filter"; + +const meta = { + title: "UI/Filter", + component: Filter, + tags: ["autodocs"], + args: { + children: "Filter", + enabled: false, + }, + decorators: [ + (Story): React.ReactElement => ( + +
+ +
+
+ ), + ], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Disabled: Story = { + args: { + enabled: false, + }, +}; + +export const Enabled: Story = { + args: { + enabled: true, + }, +}; diff --git a/frontend/src/components/Filter/Filter.tsx b/frontend/src/components/Filter/Filter.tsx new file mode 100644 index 00000000..31ef0812 --- /dev/null +++ b/frontend/src/components/Filter/Filter.tsx @@ -0,0 +1,47 @@ +// Copyright 2024 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { createLink } from "@tanstack/react-router"; +import CloseIcon from "@vector-im/compound-design-tokens/assets/web/icons/close"; +import classNames from "classnames"; +import { forwardRef } from "react"; + +import styles from "./Filter.module.css"; + +type Props = React.ComponentPropsWithRef<"a"> & { + enabled?: boolean; +}; + +/** + * A link which looks like a chip used when filtering items + */ +export const Filter = createLink( + forwardRef(function Filter( + { children, enabled, ...props }, + ref, + ) { + const className = classNames( + styles.filter, + enabled ? styles.enabledFilter : styles.disabledFilter, + props.className, + ); + + return ( + + {children} + {enabled && } + + ); + }), +); diff --git a/frontend/src/components/Filter/index.ts b/frontend/src/components/Filter/index.ts new file mode 100644 index 00000000..1b215f9b --- /dev/null +++ b/frontend/src/components/Filter/index.ts @@ -0,0 +1,15 @@ +// Copyright 2024 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +export { Filter as default } from "./Filter"; diff --git a/frontend/src/components/UserSessionsOverview/BrowserSessionsOverview.module.css b/frontend/src/components/UserSessionsOverview/BrowserSessionsOverview.module.css index f8135776..5edd624c 100644 --- a/frontend/src/components/UserSessionsOverview/BrowserSessionsOverview.module.css +++ b/frontend/src/components/UserSessionsOverview/BrowserSessionsOverview.module.css @@ -1,4 +1,4 @@ -/* Copyright 2023 The Matrix.org Foundation C.I.C. +/* Copyright 2024 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,15 +13,13 @@ * limitations under the License. */ -.session-list-block { +.browser-sessions-overview { display: flex; - flex-direction: row; - justify-content: space-between; - align-items: flex-start; - gap: var(--cpd-space-1x); -} - -.session-list-block-info { - display: flex; - flex-direction: column; + align-items: center; + gap: var(--cpd-space-4x); + padding: var(--cpd-space-4x); + border-radius: var(--cpd-space-3x); + background-color: var(--cpd-color-bg-canvas-default); + outline: 1px solid var(--cpd-color-gray-400); + outline-offset: -1px; } diff --git a/frontend/src/components/UserSessionsOverview/BrowserSessionsOverview.tsx b/frontend/src/components/UserSessionsOverview/BrowserSessionsOverview.tsx index b9a4071a..3d99e0f1 100644 --- a/frontend/src/components/UserSessionsOverview/BrowserSessionsOverview.tsx +++ b/frontend/src/components/UserSessionsOverview/BrowserSessionsOverview.tsx @@ -12,11 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Body, H5 } from "@vector-im/compound-web"; +import { Text, H5 } from "@vector-im/compound-web"; import { useTranslation } from "react-i18next"; import { FragmentType, graphql, useFragment } from "../../gql"; -import Block from "../Block"; import { Link } from "../Link"; import styles from "./BrowserSessionsOverview.module.css"; @@ -38,19 +37,19 @@ const BrowserSessionsOverview: React.FC<{ const { t } = useTranslation(); return ( - -
+
+
{t("frontend.browser_sessions_overview.heading")}
- + {t("frontend.browser_sessions_overview.body", { count: data.browserSessions.totalCount, })} - +
{t("frontend.browser_sessions_overview.view_all_button")} - +
); }; diff --git a/frontend/src/components/UserSessionsOverview/__snapshots__/BrowserSessionsOverview.test.tsx.snap b/frontend/src/components/UserSessionsOverview/__snapshots__/BrowserSessionsOverview.test.tsx.snap index 16713dbe..f3f93036 100644 --- a/frontend/src/components/UserSessionsOverview/__snapshots__/BrowserSessionsOverview.test.tsx.snap +++ b/frontend/src/components/UserSessionsOverview/__snapshots__/BrowserSessionsOverview.test.tsx.snap @@ -3,10 +3,10 @@ exports[`BrowserSessionsOverview > renders with no browser sessions 1`] = `
renders with no browser sessions 1`] = ` exports[`BrowserSessionsOverview > renders with sessions 1`] = `
; last?: InputMaybe; before?: InputMaybe; + lastActive?: InputMaybe; }>; @@ -1587,6 +1588,7 @@ export type AppSessionsListQueryQueryVariables = Exact<{ after?: InputMaybe; first?: InputMaybe; last?: InputMaybe; + lastActive?: InputMaybe; }>; @@ -1694,9 +1696,9 @@ export const VerifyEmailDocument = {"kind":"Document","definitions":[{"kind":"Op export const ResendVerificationEmailDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ResendVerificationEmail"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"sendVerificationEmail"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"userEmailId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"primaryEmail"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"email"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserEmail_email"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserEmail_email"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"UserEmail"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"confirmedAt"}}]}}]} as unknown as DocumentNode; export const UserProfileQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UserProfileQuery"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"viewer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"primaryEmail"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserEmail_email"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserEmailList_user"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"siteConfig"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"emailChangeAllowed"}},{"kind":"Field","name":{"kind":"Name","value":"passwordLoginEnabled"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserEmailList_siteConfig"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserEmail_siteConfig"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"PasswordChange_siteConfig"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserEmail_siteConfig"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SiteConfig"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"emailChangeAllowed"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserEmail_email"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"UserEmail"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"confirmedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserEmailList_user"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"primaryEmail"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserEmailList_siteConfig"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SiteConfig"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserEmail_siteConfig"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PasswordChange_siteConfig"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SiteConfig"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"passwordChangeAllowed"}}]}}]} as unknown as DocumentNode; export const SessionDetailQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"SessionDetailQuery"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"viewerSession"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Node"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"node"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"CompatSession_detail"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"OAuth2Session_detail"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"BrowserSession_detail"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CompatSession_detail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"CompatSession"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"deviceId"}},{"kind":"Field","name":{"kind":"Name","value":"finishedAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveIp"}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveAt"}},{"kind":"Field","name":{"kind":"Name","value":"userAgent"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"os"}},{"kind":"Field","name":{"kind":"Name","value":"model"}}]}},{"kind":"Field","name":{"kind":"Name","value":"ssoLogin"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"redirectUri"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"OAuth2Session_detail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Oauth2Session"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"scope"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"finishedAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveIp"}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveAt"}},{"kind":"Field","name":{"kind":"Name","value":"client"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"clientId"}},{"kind":"Field","name":{"kind":"Name","value":"clientName"}},{"kind":"Field","name":{"kind":"Name","value":"clientUri"}},{"kind":"Field","name":{"kind":"Name","value":"logoUri"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BrowserSession_detail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BrowserSession"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"finishedAt"}},{"kind":"Field","name":{"kind":"Name","value":"userAgent"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"model"}},{"kind":"Field","name":{"kind":"Name","value":"os"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveIp"}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastAuthentication"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"username"}}]}}]}}]} as unknown as DocumentNode; -export const BrowserSessionListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"BrowserSessionList"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"first"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"after"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"last"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"before"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"viewerSession"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BrowserSession"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"browserSessions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"after"},"value":{"kind":"Variable","name":{"kind":"Name","value":"after"}}},{"kind":"Argument","name":{"kind":"Name","value":"last"},"value":{"kind":"Variable","name":{"kind":"Name","value":"last"}}},{"kind":"Argument","name":{"kind":"Name","value":"before"},"value":{"kind":"Variable","name":{"kind":"Name","value":"before"}}},{"kind":"Argument","name":{"kind":"Name","value":"state"},"value":{"kind":"EnumValue","value":"ACTIVE"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"BrowserSession_session"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasNextPage"}},{"kind":"Field","name":{"kind":"Name","value":"hasPreviousPage"}},{"kind":"Field","name":{"kind":"Name","value":"startCursor"}},{"kind":"Field","name":{"kind":"Name","value":"endCursor"}}]}}]}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BrowserSession_session"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BrowserSession"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"finishedAt"}},{"kind":"Field","name":{"kind":"Name","value":"userAgent"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"raw"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"os"}},{"kind":"Field","name":{"kind":"Name","value":"model"}},{"kind":"Field","name":{"kind":"Name","value":"deviceType"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveIp"}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastAuthentication"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}}]} as unknown as DocumentNode; +export const BrowserSessionListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"BrowserSessionList"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"first"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"after"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"last"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"before"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"lastActive"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"DateFilter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"viewerSession"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BrowserSession"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"browserSessions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"after"},"value":{"kind":"Variable","name":{"kind":"Name","value":"after"}}},{"kind":"Argument","name":{"kind":"Name","value":"last"},"value":{"kind":"Variable","name":{"kind":"Name","value":"last"}}},{"kind":"Argument","name":{"kind":"Name","value":"before"},"value":{"kind":"Variable","name":{"kind":"Name","value":"before"}}},{"kind":"Argument","name":{"kind":"Name","value":"lastActive"},"value":{"kind":"Variable","name":{"kind":"Name","value":"lastActive"}}},{"kind":"Argument","name":{"kind":"Name","value":"state"},"value":{"kind":"EnumValue","value":"ACTIVE"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"BrowserSession_session"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasNextPage"}},{"kind":"Field","name":{"kind":"Name","value":"hasPreviousPage"}},{"kind":"Field","name":{"kind":"Name","value":"startCursor"}},{"kind":"Field","name":{"kind":"Name","value":"endCursor"}}]}}]}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BrowserSession_session"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BrowserSession"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"finishedAt"}},{"kind":"Field","name":{"kind":"Name","value":"userAgent"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"raw"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"os"}},{"kind":"Field","name":{"kind":"Name","value":"model"}},{"kind":"Field","name":{"kind":"Name","value":"deviceType"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveIp"}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastAuthentication"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}}]} as unknown as DocumentNode; export const SessionsOverviewQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"SessionsOverviewQuery"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"viewer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"BrowserSessionsOverview_user"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BrowserSessionsOverview_user"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"browserSessions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"IntValue","value":"0"}},{"kind":"Argument","name":{"kind":"Name","value":"state"},"value":{"kind":"EnumValue","value":"ACTIVE"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]} as unknown as DocumentNode; -export const AppSessionsListQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AppSessionsListQuery"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"before"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"after"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"first"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"last"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"viewer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"appSessions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"before"},"value":{"kind":"Variable","name":{"kind":"Name","value":"before"}}},{"kind":"Argument","name":{"kind":"Name","value":"after"},"value":{"kind":"Variable","name":{"kind":"Name","value":"after"}}},{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"last"},"value":{"kind":"Variable","name":{"kind":"Name","value":"last"}}},{"kind":"Argument","name":{"kind":"Name","value":"state"},"value":{"kind":"EnumValue","value":"ACTIVE"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"CompatSession_session"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"OAuth2Session_session"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"startCursor"}},{"kind":"Field","name":{"kind":"Name","value":"endCursor"}},{"kind":"Field","name":{"kind":"Name","value":"hasNextPage"}},{"kind":"Field","name":{"kind":"Name","value":"hasPreviousPage"}}]}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CompatSession_session"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"CompatSession"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"deviceId"}},{"kind":"Field","name":{"kind":"Name","value":"finishedAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveIp"}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveAt"}},{"kind":"Field","name":{"kind":"Name","value":"userAgent"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"os"}},{"kind":"Field","name":{"kind":"Name","value":"model"}},{"kind":"Field","name":{"kind":"Name","value":"deviceType"}}]}},{"kind":"Field","name":{"kind":"Name","value":"ssoLogin"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"redirectUri"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"OAuth2Session_session"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Oauth2Session"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"scope"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"finishedAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveIp"}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveAt"}},{"kind":"Field","name":{"kind":"Name","value":"userAgent"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"model"}},{"kind":"Field","name":{"kind":"Name","value":"os"}},{"kind":"Field","name":{"kind":"Name","value":"deviceType"}}]}},{"kind":"Field","name":{"kind":"Name","value":"client"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"clientId"}},{"kind":"Field","name":{"kind":"Name","value":"clientName"}},{"kind":"Field","name":{"kind":"Name","value":"applicationType"}},{"kind":"Field","name":{"kind":"Name","value":"logoUri"}}]}}]}}]} as unknown as DocumentNode; +export const AppSessionsListQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AppSessionsListQuery"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"before"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"after"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"first"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"last"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"lastActive"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"DateFilter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"viewer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"appSessions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"before"},"value":{"kind":"Variable","name":{"kind":"Name","value":"before"}}},{"kind":"Argument","name":{"kind":"Name","value":"after"},"value":{"kind":"Variable","name":{"kind":"Name","value":"after"}}},{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"last"},"value":{"kind":"Variable","name":{"kind":"Name","value":"last"}}},{"kind":"Argument","name":{"kind":"Name","value":"lastActive"},"value":{"kind":"Variable","name":{"kind":"Name","value":"lastActive"}}},{"kind":"Argument","name":{"kind":"Name","value":"state"},"value":{"kind":"EnumValue","value":"ACTIVE"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"CompatSession_session"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"OAuth2Session_session"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"startCursor"}},{"kind":"Field","name":{"kind":"Name","value":"endCursor"}},{"kind":"Field","name":{"kind":"Name","value":"hasNextPage"}},{"kind":"Field","name":{"kind":"Name","value":"hasPreviousPage"}}]}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CompatSession_session"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"CompatSession"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"deviceId"}},{"kind":"Field","name":{"kind":"Name","value":"finishedAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveIp"}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveAt"}},{"kind":"Field","name":{"kind":"Name","value":"userAgent"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"os"}},{"kind":"Field","name":{"kind":"Name","value":"model"}},{"kind":"Field","name":{"kind":"Name","value":"deviceType"}}]}},{"kind":"Field","name":{"kind":"Name","value":"ssoLogin"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"redirectUri"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"OAuth2Session_session"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Oauth2Session"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"scope"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"finishedAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveIp"}},{"kind":"Field","name":{"kind":"Name","value":"lastActiveAt"}},{"kind":"Field","name":{"kind":"Name","value":"userAgent"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"model"}},{"kind":"Field","name":{"kind":"Name","value":"os"}},{"kind":"Field","name":{"kind":"Name","value":"deviceType"}}]}},{"kind":"Field","name":{"kind":"Name","value":"client"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"clientId"}},{"kind":"Field","name":{"kind":"Name","value":"clientName"}},{"kind":"Field","name":{"kind":"Name","value":"applicationType"}},{"kind":"Field","name":{"kind":"Name","value":"logoUri"}}]}}]}}]} as unknown as DocumentNode; export const CurrentUserGreetingDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"CurrentUserGreeting"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"viewerSession"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BrowserSession"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UnverifiedEmailAlert_user"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserGreeting_user"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"siteConfig"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserGreeting_siteConfig"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UnverifiedEmailAlert_user"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","alias":{"kind":"Name","value":"unverifiedEmails"},"name":{"kind":"Name","value":"emails"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"IntValue","value":"0"}},{"kind":"Argument","name":{"kind":"Name","value":"state"},"value":{"kind":"EnumValue","value":"PENDING"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserGreeting_user"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"matrix"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"mxid"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserGreeting_siteConfig"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SiteConfig"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"displayNameChangeAllowed"}}]}}]} as unknown as DocumentNode; export const OAuth2ClientQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"OAuth2ClientQuery"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"oauth2Client"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"OAuth2Client_detail"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"OAuth2Client_detail"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Oauth2Client"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"clientId"}},{"kind":"Field","name":{"kind":"Name","value":"clientName"}},{"kind":"Field","name":{"kind":"Name","value":"clientUri"}},{"kind":"Field","name":{"kind":"Name","value":"logoUri"}},{"kind":"Field","name":{"kind":"Name","value":"tosUri"}},{"kind":"Field","name":{"kind":"Name","value":"policyUri"}},{"kind":"Field","name":{"kind":"Name","value":"redirectUris"}}]}}]} as unknown as DocumentNode; export const CurrentViewerQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"CurrentViewerQuery"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"viewer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Node"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]} as unknown as DocumentNode; diff --git a/frontend/src/routes/_account.sessions.browsers.tsx b/frontend/src/routes/_account.sessions.browsers.tsx index 6a7fea2f..27a3132d 100644 --- a/frontend/src/routes/_account.sessions.browsers.tsx +++ b/frontend/src/routes/_account.sessions.browsers.tsx @@ -16,14 +16,23 @@ import { createFileRoute, notFound } from "@tanstack/react-router"; import { H5 } from "@vector-im/compound-web"; import { useTranslation } from "react-i18next"; import { useQuery } from "urql"; +import * as z from "zod"; import BlockList from "../components/BlockList"; import BrowserSession from "../components/BrowserSession"; import { ButtonLink } from "../components/ButtonLink"; +import EmptyState from "../components/EmptyState"; +import Filter from "../components/Filter"; import { graphql } from "../gql"; -import { Pagination, paginationSchema, usePages } from "../pagination"; +import { + BackwardPagination, + Pagination, + paginationSchema, + usePages, +} from "../pagination"; const PAGE_SIZE = 6; +const DEFAULT_PAGE: BackwardPagination = { last: PAGE_SIZE }; const QUERY = graphql(/* GraphQL */ ` query BrowserSessionList( @@ -31,6 +40,7 @@ const QUERY = graphql(/* GraphQL */ ` $after: String $last: Int $before: String + $lastActive: DateFilter ) { viewerSession { __typename @@ -45,6 +55,7 @@ const QUERY = graphql(/* GraphQL */ ` after: $after last: $last before: $before + lastActive: $lastActive state: ACTIVE ) { totalCount @@ -70,16 +81,37 @@ const QUERY = graphql(/* GraphQL */ ` } `); +const searchSchema = z.object({ + inactive: z.literal(true).optional().catch(undefined), +}); + +type Search = z.infer; + +const getNintyDaysAgo = (): string => { + const date = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000); + // Round down to the start of the day to avoid rerendering/requerying + date.setHours(0, 0, 0, 0); + return date.toISOString(); +}; + export const Route = createFileRoute("/_account/sessions/browsers")({ // We paginate backwards, so we need to validate the `last` parameter by default - validateSearch: paginationSchema.catch({ - last: PAGE_SIZE, - }), + validateSearch: paginationSchema.catch(DEFAULT_PAGE).and(searchSchema), - loaderDeps: ({ search }): Pagination => paginationSchema.parse(search), + loaderDeps: ({ search }): Pagination & Search => + paginationSchema.and(searchSchema).parse(search), - async loader({ context, deps: pagination, abortController: { signal } }) { - const result = await context.client.query(QUERY, pagination, { + async loader({ + context, + deps: { inactive, ...pagination }, + abortController: { signal }, + }) { + const variables = { + lastActive: inactive ? { before: getNintyDaysAgo() } : undefined, + ...pagination, + }; + + const result = await context.client.query(QUERY, variables, { fetchOptions: { signal }, }); if (result.error) throw result.error; @@ -92,8 +124,14 @@ export const Route = createFileRoute("/_account/sessions/browsers")({ function BrowserSessions(): React.ReactElement { const { t } = useTranslation(); - const pagination = Route.useLoaderDeps(); - const [list] = useQuery({ query: QUERY, variables: pagination }); + const { inactive, ...pagination } = Route.useLoaderDeps(); + + const variables = { + lastActive: inactive ? { before: getNintyDaysAgo() } : undefined, + ...pagination, + }; + + const [list] = useQuery({ query: QUERY, variables }); if (list.error) throw list.error; const currentSession = list.data?.viewerSession.__typename === "BrowserSession" @@ -113,6 +151,16 @@ function BrowserSessions(): React.ReactElement {
{t("frontend.browser_sessions_overview.heading")}
+
+ + {t("frontend.last_active.inactive_90_days")} + +
+ {edges.map((n) => ( ))} -
- - {t("common.previous")} - + {currentSession.user.browserSessions.totalCount === 0 && ( + + {inactive + ? t( + "frontend.browser_sessions_overview.no_active_sessions.inactive_90_days", + ) + : t( + "frontend.browser_sessions_overview.no_active_sessions.default", + )} + + )} - {/* Spacer */} -
+ {/* Only show the pagination buttons if there are pages to go to */} + {(forwardPage || backwardPage) && ( +
+ + {t("common.previous")} + - - {t("common.next")} - -
+ {/* Spacer */} +
+ + + {t("common.next")} + +
+ )} ); } diff --git a/frontend/src/routes/_account.sessions.index.tsx b/frontend/src/routes/_account.sessions.index.tsx index 5579aa9d..6a85e8c7 100644 --- a/frontend/src/routes/_account.sessions.index.tsx +++ b/frontend/src/routes/_account.sessions.index.tsx @@ -13,19 +13,28 @@ // limitations under the License. import { createFileRoute, notFound } from "@tanstack/react-router"; -import { H3, H5 } from "@vector-im/compound-web"; +import { H3, Separator } from "@vector-im/compound-web"; import { useTranslation } from "react-i18next"; import { useQuery } from "urql"; +import * as z from "zod"; import BlockList from "../components/BlockList"; import { ButtonLink } from "../components/ButtonLink"; import CompatSession from "../components/CompatSession"; +import EmptyState from "../components/EmptyState"; +import Filter from "../components/Filter"; import OAuth2Session from "../components/OAuth2Session"; import BrowserSessionsOverview from "../components/UserSessionsOverview/BrowserSessionsOverview"; import { graphql } from "../gql"; -import { Pagination, paginationSchema, usePages } from "../pagination"; +import { + BackwardPagination, + Pagination, + paginationSchema, + usePages, +} from "../pagination"; const PAGE_SIZE = 6; +const DEFAULT_PAGE: BackwardPagination = { last: PAGE_SIZE }; const QUERY = graphql(/* GraphQL */ ` query SessionsOverviewQuery { @@ -46,6 +55,7 @@ const LIST_QUERY = graphql(/* GraphQL */ ` $after: String $first: Int $last: Int + $lastActive: DateFilter ) { viewer { __typename @@ -57,6 +67,7 @@ const LIST_QUERY = graphql(/* GraphQL */ ` after: $after first: $first last: $last + lastActive: $lastActive state: ACTIVE ) { edges { @@ -86,16 +97,39 @@ const unknownSessionType = (type: never): never => { throw new Error(`Unknown session type: ${type}`); }; +const searchSchema = z.object({ + inactive: z.literal(true).optional().catch(undefined), +}); + +type Search = z.infer; + +const getNintyDaysAgo = (): string => { + const date = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000); + // Round down to the start of the day to avoid rerendering/requerying + date.setHours(0, 0, 0, 0); + return date.toISOString(); +}; + export const Route = createFileRoute("/_account/sessions/")({ // We paginate backwards, so we need to validate the `last` parameter by default - validateSearch: paginationSchema.catch({ last: PAGE_SIZE }), + validateSearch: paginationSchema.catch(DEFAULT_PAGE).and(searchSchema), - loaderDeps: ({ search }): Pagination => paginationSchema.parse(search), + loaderDeps: ({ search }): Pagination & Search => + paginationSchema.and(searchSchema).parse(search), + + async loader({ + context, + deps: { inactive, ...pagination }, + abortController: { signal }, + }) { + const variables = { + lastActive: inactive ? { before: getNintyDaysAgo() } : undefined, + ...pagination, + }; - async loader({ context, deps: pagination, abortController: { signal } }) { const [overview, list] = await Promise.all([ context.client.query(QUERY, {}, { fetchOptions: { signal } }), - context.client.query(LIST_QUERY, pagination, { + context.client.query(LIST_QUERY, variables, { fetchOptions: { signal }, }), ]); @@ -111,14 +145,19 @@ export const Route = createFileRoute("/_account/sessions/")({ function Sessions(): React.ReactElement { const { t } = useTranslation(); - const pagination = Route.useLoaderDeps(); + const { inactive, ...pagination } = Route.useLoaderDeps(); const [overview] = useQuery({ query: QUERY }); if (overview.error) throw overview.error; const user = overview.data?.viewer.__typename === "User" ? overview.data.viewer : null; if (user === null) throw notFound(); - const [list] = useQuery({ query: LIST_QUERY, variables: pagination }); + const variables = { + lastActive: inactive ? { before: getNintyDaysAgo() } : undefined, + ...pagination, + }; + + const [list] = useQuery({ query: LIST_QUERY, variables }); if (list.error) throw list.error; const appSessions = list.data?.viewer.__typename === "User" @@ -139,13 +178,16 @@ function Sessions(): React.ReactElement {

{t("frontend.user_sessions_overview.heading")}

- -
- {t("frontend.user_sessions_overview.active_sessions", { - count: appSessions.totalCount, - })} -
- + +
+ + {t("frontend.last_active.inactive_90_days")} + +
{edges.map((session) => { const type = session.node.__typename; switch (type) { @@ -162,30 +204,43 @@ function Sessions(): React.ReactElement { } })} -
- - {t("common.previous")} - + {appSessions.totalCount === 0 && ( + + {inactive + ? t( + "frontend.user_sessions_overview.no_active_sessions.inactive_90_days", + ) + : t("frontend.user_sessions_overview.no_active_sessions.default")} + + )} - {/* Spacer */} -
+ {/* Only show the pagination buttons if there are pages to go to */} + {(forwardPage || backwardPage) && ( +
+ + {t("common.previous")} + - - {t("common.next")} - -
+ {/* Spacer */} +
+ + + {t("common.next")} + +
+ )} ); } diff --git a/frontend/src/test-utils/dummy-router.tsx b/frontend/src/test-utils/dummy-router.tsx new file mode 100644 index 00000000..e69de29b diff --git a/frontend/src/test-utils/router.tsx b/frontend/src/test-utils/router.tsx index 3fff6ff2..4cf13dbd 100644 --- a/frontend/src/test-utils/router.tsx +++ b/frontend/src/test-utils/router.tsx @@ -19,7 +19,6 @@ import { createRoute, createRouter, } from "@tanstack/react-router"; -import { beforeAll } from "vitest"; const rootRoute = createRootRoute(); const index = createRoute({ getParentRoute: () => rootRoute, path: "/" }); @@ -31,8 +30,13 @@ const router = createRouter({ }); const routerReady = router.load(); -// Make sure the router is ready before running any tests -beforeAll(async () => await routerReady); +// Because we also use this in the stories, we need to make sure we call this only in tests +if (import.meta.vitest) { + // Make sure the router is ready before running any tests + import("vitest").then(({ beforeAll }) => + beforeAll(async () => await routerReady), + ); +} export const DummyRouter: React.FC = ({ children,