Reformat frontend code, fix a few new linting errors

This commit is contained in:
Quentin Gliech
2025-07-07 15:07:57 +02:00
parent f08f3bd786
commit 4cfd61c367
31 changed files with 146 additions and 132 deletions

View File

@@ -46,7 +46,8 @@
"noUnusedTemplateLiteral": "error",
"useNumberNamespace": "error",
"noInferrableTypes": "error",
"noUselessElse": "error"
"noUselessElse": "error",
"noDescendingSpecificity": "off"
}
}
}

View File

@@ -70,7 +70,10 @@ const AccountDeleteButton: React.FC<Props> = (props) => {
mutationFn: ({
password,
hsErase,
}: { password: string | null; hsErase: boolean }) =>
}: {
password: string | null;
hsErase: boolean;
}) =>
graphqlRequest({
query: MUTATION,
variables: { password, hsErase },

View File

@@ -7,7 +7,7 @@
import { createLink } from "@tanstack/react-router";
import { Button } from "@vector-im/compound-web";
import cx from "classnames";
import { type PropsWithChildren, forwardRef } from "react";
import { forwardRef, type PropsWithChildren } from "react";
import styles from "./ButtonLink.module.css";
type Props = {

View File

@@ -95,6 +95,7 @@
/* Cap the block size */
max-block-size: calc(100vh - var(--cpd-space-4x));
/* biome-ignore lint/suspicious/noDuplicateProperties: this isn't a real duplicate */
max-block-size: calc(100svh - var(--cpd-space-4x));
/* Drawer comes in the Android style by default */

View File

@@ -102,4 +102,4 @@ export const Title: React.FC<PropsWithChildren> = ({ children }) => (
<DialogTitle className={styles.title}>{children}</DialogTitle>
);
export { Description, Close } from "@radix-ui/react-dialog";
export { Close, Description } from "@radix-ui/react-dialog";

View File

@@ -4,4 +4,4 @@
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
export { Close, Dialog, Title, Description } from "./Dialog";
export { Close, Description, Dialog, Title } from "./Dialog";

View File

@@ -16,6 +16,7 @@
/* Fallback for browsers that do not support 100svh */
min-height: 100vh;
/* biome-ignore lint/suspicious/noDuplicateProperties: this isn't a real duplicate */
min-height: 100svh;
margin: 0 auto;

View File

@@ -10,6 +10,7 @@
/* Fallback for browsers that do not support 100svh */
min-height: 100vh;
/* biome-ignore lint/suspicious/noDuplicateProperties: this isn't a real duplicate */
min-height: 100svh;
justify-content: center;

View File

@@ -39,9 +39,9 @@ const PaginationControls: React.FC<Props> = ({
{t("common.previous")}
</Button>
<div className="text-center">
{count !== undefined ? (
<>{t("frontend.pagination_controls.total", { totalCount: count })}</>
) : null}
{count !== undefined
? t("frontend.pagination_controls.total", { totalCount: count })
: null}
</div>
<Button
kind="secondary"

View File

@@ -3,6 +3,9 @@
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
// biome-ignore-all lint/a11y/useFocusableInteractive: this is a false positive
// biome-ignore-all lint/a11y/useAriaPropsForRole: this is a false positive
import cx from "classnames";
import { forwardRef } from "react";
@@ -14,7 +17,6 @@ type Props = {
const Separator = forwardRef<HTMLDivElement, Props>(
({ kind, className, ...props }: Props, ref) => (
// biome-ignore lint/a11y/useFocusableInteractive: this is a false positive
<div
aria-orientation="horizontal"
role="separator"

View File

@@ -10,7 +10,7 @@ import { composeStory } from "@storybook/react-vite";
import { render } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import Meta, { Unknown, Pc, Mobile, Tablet } from "./DeviceTypeIcon.stories";
import Meta, { Mobile, Pc, Tablet, Unknown } from "./DeviceTypeIcon.stories";
describe("<DeviceTypeIcon />", () => {
it("renders unknown device type", () => {

View File

@@ -5,13 +5,13 @@
// Please see LICENSE files in the repository root for full details.
export {
Root,
LinkBody,
Body,
Header,
Name,
Client,
Metadata,
Info,
Action,
Body,
Client,
Header,
Info,
LinkBody,
Metadata,
Name,
Root,
} from "./SessionCard";

View File

@@ -3,6 +3,7 @@
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
import type { UseMutationResult } from "@tanstack/react-query";
import IconEdit from "@vector-im/compound-design-tokens/assets/web/icons/edit";
import { Button, Form, IconButton, Tooltip } from "@vector-im/compound-web";
import {
@@ -11,12 +12,10 @@ import {
useRef,
useState,
} from "react";
import { useTranslation } from "react-i18next";
import * as Dialog from "../Dialog";
import LoadingSpinner from "../LoadingSpinner";
import type { UseMutationResult } from "@tanstack/react-query";
import { useTranslation } from "react-i18next";
// This needs to be its own component because else props and refs aren't passed properly in the trigger
const EditButton = forwardRef<
HTMLButtonElement,

View File

@@ -6,12 +6,10 @@
// @vitest-environment happy-dom
import { TooltipProvider } from "@vector-im/compound-web";
import { beforeAll, describe, expect, it } from "vitest";
import { makeFragmentData } from "../../gql";
import { mockLocale } from "../../test-utils/mockLocale";
import { TooltipProvider } from "@vector-im/compound-web";
import render from "../../test-utils/render";
import OAuth2SessionDetail, { FRAGMENT } from "./OAuth2SessionDetail";

View File

@@ -105,7 +105,7 @@ const UserEmail: React.FC<{
const onRemoveClick = useCallback(
async (_e: React.MouseEvent<HTMLButtonElement>): Promise<void> => {
let password = undefined;
let password: string | undefined;
if (shouldPromptPassword) {
password = await promptPassword();
}

View File

@@ -32,11 +32,9 @@ export const CONFIG_FRAGMENT = graphql(/* GraphQL */ `
const ADD_EMAIL_MUTATION = graphql(/* GraphQL */ `
mutation AddEmail($email: String!, $password: String, $language: String!) {
startEmailAuthentication(input: {
email: $email,
password: $password,
language: $language
}) {
startEmailAuthentication(
input: { email: $email, password: $password, language: $language }
) {
status
violations
authentication {
@@ -64,7 +62,11 @@ const AddEmailForm: React.FC<{
email,
password,
language,
}: { email: string; password?: string; language: string }) =>
}: {
email: string;
password?: string;
language: string;
}) =>
graphqlRequest({
query: ADD_EMAIL_MUTATION,
variables: { email, password, language },
@@ -92,7 +94,7 @@ const AddEmailForm: React.FC<{
const formData = new FormData(e.currentTarget);
const email = formData.get("input") as string;
let password = undefined;
let password: string | undefined;
if (shouldPromptPassword) {
password = await promptPassword();
}

View File

@@ -44,7 +44,7 @@ type Documents = {
"\n mutation SetDisplayName($userId: ID!, $displayName: String) {\n setDisplayName(input: { userId: $userId, displayName: $displayName }) {\n status\n }\n }\n": typeof types.SetDisplayNameDocument,
"\n fragment AddEmailForm_user on User {\n hasPassword\n }\n": typeof types.AddEmailForm_UserFragmentDoc,
"\n fragment AddEmailForm_siteConfig on SiteConfig {\n passwordLoginEnabled\n }\n": typeof types.AddEmailForm_SiteConfigFragmentDoc,
"\n mutation AddEmail($email: String!, $password: String, $language: String!) {\n startEmailAuthentication(input: {\n email: $email,\n password: $password,\n language: $language\n }) {\n status\n violations\n authentication {\n id\n }\n }\n }\n": typeof types.AddEmailDocument,
"\n mutation AddEmail($email: String!, $password: String, $language: String!) {\n startEmailAuthentication(\n input: { email: $email, password: $password, language: $language }\n ) {\n status\n violations\n authentication {\n id\n }\n }\n }\n": typeof types.AddEmailDocument,
"\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_user on User {\n hasPassword\n }\n": typeof types.UserEmailList_UserFragmentDoc,
"\n fragment UserEmailList_siteConfig on SiteConfig {\n emailChangeAllowed\n passwordLoginEnabled\n }\n": typeof types.UserEmailList_SiteConfigFragmentDoc,
@@ -101,7 +101,7 @@ const documents: Documents = {
"\n mutation SetDisplayName($userId: ID!, $displayName: String) {\n setDisplayName(input: { userId: $userId, displayName: $displayName }) {\n status\n }\n }\n": types.SetDisplayNameDocument,
"\n fragment AddEmailForm_user on User {\n hasPassword\n }\n": types.AddEmailForm_UserFragmentDoc,
"\n fragment AddEmailForm_siteConfig on SiteConfig {\n passwordLoginEnabled\n }\n": types.AddEmailForm_SiteConfigFragmentDoc,
"\n mutation AddEmail($email: String!, $password: String, $language: String!) {\n startEmailAuthentication(input: {\n email: $email,\n password: $password,\n language: $language\n }) {\n status\n violations\n authentication {\n id\n }\n }\n }\n": types.AddEmailDocument,
"\n mutation AddEmail($email: String!, $password: String, $language: String!) {\n startEmailAuthentication(\n input: { email: $email, password: $password, language: $language }\n ) {\n status\n violations\n authentication {\n id\n }\n }\n }\n": types.AddEmailDocument,
"\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_user on User {\n hasPassword\n }\n": types.UserEmailList_UserFragmentDoc,
"\n fragment UserEmailList_siteConfig on SiteConfig {\n emailChangeAllowed\n passwordLoginEnabled\n }\n": types.UserEmailList_SiteConfigFragmentDoc,
@@ -248,7 +248,7 @@ export function graphql(source: "\n fragment AddEmailForm_siteConfig on SiteCon
/**
* 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 AddEmail($email: String!, $password: String, $language: String!) {\n startEmailAuthentication(input: {\n email: $email,\n password: $password,\n language: $language\n }) {\n status\n violations\n authentication {\n id\n }\n }\n }\n"): typeof import('./graphql').AddEmailDocument;
export function graphql(source: "\n mutation AddEmail($email: String!, $password: String, $language: String!) {\n startEmailAuthentication(\n input: { email: $email, password: $password, language: $language }\n ) {\n status\n violations\n authentication {\n id\n }\n }\n }\n"): typeof import('./graphql').AddEmailDocument;
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/

View File

@@ -7,10 +7,10 @@
import {
type BackendModule,
type InitOptions,
default as i18n,
type LanguageDetectorModule,
type ReadCallback,
type ResourceKey,
default as i18n,
} from "i18next";
import { initReactI18next } from "react-i18next";

View File

@@ -7,9 +7,9 @@
import type { QueryClient } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import {
createRootRouteWithContext,
type ErrorRouteComponent,
Outlet,
createRootRouteWithContext,
} from "@tanstack/react-router";
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
import GenericError from "../components/GenericError";

View File

@@ -188,7 +188,6 @@ function Index(): React.ReactElement {
};
return (
<>
<div className="flex flex-col gap-6">
{/* Only display this section if the user can add email addresses to their
account *or* if they have any existing email addresses */}
@@ -199,10 +198,7 @@ function Index(): React.ReactElement {
defaultOpen
title={t("frontend.account.contact_info")}
>
<UserEmailList
user={viewerSession.user}
siteConfig={siteConfig}
/>
<UserEmailList user={viewerSession.user} siteConfig={siteConfig} />
{siteConfig.emailChangeAllowed && (
<AddEmailForm
@@ -255,6 +251,5 @@ function Index(): React.ReactElement {
<Separator />
</div>
</>
);
}

View File

@@ -33,10 +33,10 @@ export const Route = createFileRoute({
preload(planManagementIframeUri, { as: "document" });
},
component: Plan,
component: RouteComponent,
});
function Plan(): React.ReactElement {
function RouteComponent(): React.ReactElement {
const result = useSuspenseQuery(query);
const { planManagementIframeUri } = result.data.siteConfig;
@@ -45,6 +45,14 @@ function Plan(): React.ReactElement {
return <Navigate to="/" replace />;
}
return <Plan planManagementIframeUri={planManagementIframeUri} />;
}
function Plan({
planManagementIframeUri,
}: {
planManagementIframeUri: string;
}): React.ReactElement {
const ref = useRef<HTMLIFrameElement>(null);
// Query the size of the iframe content and set the height
@@ -79,16 +87,8 @@ function Plan(): React.ReactElement {
[calculateHeight],
);
useEffect(() => {
const iframe = ref.current;
if (iframe) {
attachObserver(iframe);
}
// Cleanup observer when the component unmounts
return () => observer.disconnect();
}, [observer]);
const attachObserver = (iframe: HTMLIFrameElement) => {
const attachObserver = useCallback(
(iframe: HTMLIFrameElement) => {
const iframeBody = iframe.contentWindow?.document.body;
if (!iframeBody) {
return;
@@ -101,7 +101,18 @@ function Plan(): React.ReactElement {
subtree: true,
attributes: true,
});
};
},
[calculateHeight, observer],
);
useEffect(() => {
const iframe = ref.current;
if (iframe) {
attachObserver(iframe);
}
// Cleanup observer when the component unmounts
return () => observer.disconnect();
}, [observer, attachObserver]);
return (
<iframe

View File

@@ -4,8 +4,7 @@
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
import { useSuspenseQuery } from "@tanstack/react-query";
import { queryOptions } from "@tanstack/react-query";
import { queryOptions, useSuspenseQuery } from "@tanstack/react-query";
import { notFound } from "@tanstack/react-router";
import { H3 } from "@vector-im/compound-web";
import { useTranslation } from "react-i18next";
@@ -19,11 +18,11 @@ import Separator from "../components/Separator";
import BrowserSessionsOverview from "../components/UserSessionsOverview/BrowserSessionsOverview";
import { graphql } from "../gql";
import { graphqlRequest } from "../graphql";
import { usePages } from "../pagination";
import {
type AnyPagination,
anyPaginationSchema,
normalizePagination,
usePages,
} from "../pagination";
import { getNinetyDaysAgo } from "../utils/dates";

View File

@@ -5,7 +5,7 @@
// Please see LICENSE files in the repository root for full details.
import { queryOptions, useSuspenseQuery } from "@tanstack/react-query";
import { Outlet, notFound } from "@tanstack/react-router";
import { notFound, Outlet } from "@tanstack/react-router";
import { Heading } from "@vector-im/compound-web";
import { useTranslation } from "react-i18next";
import Layout from "../components/Layout";

View File

@@ -4,8 +4,7 @@
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
import { useSuspenseQuery } from "@tanstack/react-query";
import { queryOptions } from "@tanstack/react-query";
import { queryOptions, useSuspenseQuery } from "@tanstack/react-query";
import { notFound } from "@tanstack/react-router";
import OAuth2ClientDetail from "../components/Client/OAuth2ClientDetail";
import Layout from "../components/Layout";

View File

@@ -4,11 +4,10 @@
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
import { queryOptions } from "@tanstack/react-query";
import { notFound, redirect } from "@tanstack/react-router";
import { Alert } from "@vector-im/compound-web";
import { useTranslation } from "react-i18next";
import { queryOptions } from "@tanstack/react-query";
import Layout from "../components/Layout";
import { Link } from "../components/Link";
import { graphql } from "../gql";

View File

@@ -4,10 +4,12 @@
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
import { useMutation, useSuspenseQuery } from "@tanstack/react-query";
import { queryOptions } from "@tanstack/react-query";
import { useNavigate, useSearch } from "@tanstack/react-router";
import { notFound } from "@tanstack/react-router";
import {
queryOptions,
useMutation,
useSuspenseQuery,
} from "@tanstack/react-query";
import { notFound, useNavigate, useSearch } from "@tanstack/react-router";
import IconErrorSolid from "@vector-im/compound-design-tokens/assets/web/icons/error-solid";
import IconLockSolid from "@vector-im/compound-design-tokens/assets/web/icons/lock-solid";
import { Alert, Button, Form } from "@vector-im/compound-web";
@@ -19,8 +21,7 @@ import Layout from "../components/Layout";
import LoadingSpinner from "../components/LoadingSpinner";
import PageHeading from "../components/PageHeading";
import PasswordCreationDoubleInput from "../components/PasswordCreationDoubleInput";
import { type FragmentType, useFragment } from "../gql";
import { graphql } from "../gql";
import { type FragmentType, graphql, useFragment } from "../gql";
import { graphqlRequest } from "../graphql";
import { translateSetPasswordError } from "../i18n/password_changes";

View File

@@ -6,9 +6,8 @@
import { type ErrorComponentProps, Outlet } from "@tanstack/react-router";
import IconErrorSolid from "@vector-im/compound-design-tokens/assets/web/icons/error-solid";
import { Button, Text } from "@vector-im/compound-web";
import * as v from "valibot";
import { useTranslation } from "react-i18next";
import * as v from "valibot";
import Layout from "../components/Layout";
import PageHeading from "../components/PageHeading";

View File

@@ -5,12 +5,12 @@
// Please see LICENSE files in the repository root for full details.
import {
RouterContextProvider,
createMemoryHistory,
createRootRoute,
createRoute,
createRouter,
matchContext,
RouterContextProvider,
useRouterState,
} from "@tanstack/react-router";

View File

@@ -5,9 +5,9 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import {
RouterProvider,
createHashHistory,
createRouter,
RouterProvider,
} from "@tanstack/react-router";
import { TooltipProvider } from "@vector-im/compound-web";
import i18n from "i18next";

View File

@@ -4,7 +4,7 @@
// Please see LICENSE files in the repository root for full details.
import type { Meta, StoryObj } from "@storybook/react-vite";
import { HttpResponse, delay } from "msw";
import { delay, HttpResponse } from "msw";
import {
mockAllowCrossSigningResetMutation,
mockCurrentViewerQuery,

View File

@@ -4,8 +4,11 @@
// Please see LICENSE files in the repository root for full details.
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { RouterProvider, createMemoryHistory } from "@tanstack/react-router";
import { createRouter } from "@tanstack/react-router";
import {
createMemoryHistory,
createRouter,
RouterProvider,
} from "@tanstack/react-router";
import { type RenderResult, render } from "@testing-library/react";
import { TooltipProvider } from "@vector-im/compound-web";
import i18n from "i18next";