Compare commits

..

3 Commits

Author SHA1 Message Date
Lotus Bot 85d556a2a4 fix(security): upgrade i18next-http-backend 2.5.2→3.0.6 (path traversal CVE)
Fixes GHSA-q89c-q3h5-w34g: path traversal & URL injection via unsanitised
lng/ns parameters. Remaining open issues are all in devDependencies
(commitizen/lodash/tmp) or dev-server-only tools (esbuild/vite), with no
runtime impact on the production build.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 16:17:08 -04:00
Lotus Bot 528e2a48fc perf(router): lazy-load Lobby/Explore/Inbox routes; fix spoiler aria-pressed initial state
Lobby, Explore/FeaturedRooms/PublicRooms, Inbox/Notifications/Invites are
now lazy-loaded via React.lazy so they only enter the bundle when navigated
to. Main bundle: 2547 kB → 2472 kB (gzip 637 → 618 kB).

Spoiler aria-pressed was initialised to false (revealed); changed to true
so the spoiler starts hidden, matching CSS logic (aria-pressed=true →
color:transparent).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 16:14:08 -04:00
Lotus Bot 0d3eabb884 fix(a11y): semantic headings, htmlFor/id associations, remove duplicate aria-labels
H-tag: add as=h1/h2 to dialog/UIA/auth headings (21 components)
Label: add htmlFor/id to PasswordRegisterForm (5 pairs) and PasswordResetForm (3 pairs)
Dupe: remove duplicate aria-label from Controls.tsx screenshare button, MembersDrawer, Members, RoomInput

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 15:36:59 -04:00
28 changed files with 71 additions and 58 deletions
+15 -13
View File
@@ -40,7 +40,7 @@
"html-react-parser": "4.2.0", "html-react-parser": "4.2.0",
"i18next": "23.12.2", "i18next": "23.12.2",
"i18next-browser-languagedetector": "8.0.0", "i18next-browser-languagedetector": "8.0.0",
"i18next-http-backend": "2.5.2", "i18next-http-backend": "3.0.6",
"immer": "9.0.16", "immer": "9.0.16",
"is-hotkey": "0.2.0", "is-hotkey": "0.2.0",
"jotai": "2.6.0", "jotai": "2.6.0",
@@ -7733,6 +7733,15 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/cross-fetch": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz",
"integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==",
"license": "MIT",
"dependencies": {
"node-fetch": "^2.7.0"
}
},
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"version": "7.0.6", "version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -10670,19 +10679,12 @@
} }
}, },
"node_modules/i18next-http-backend": { "node_modules/i18next-http-backend": {
"version": "2.5.2", "version": "3.0.6",
"resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-2.5.2.tgz", "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-3.0.6.tgz",
"integrity": "sha512-+K8HbDfrvc1/2X8jpb7RLhI9ZxBDpx3xogYkQwGKlWAUXLSEGXzgdt3EcUjLlBCdMwdQY+K+EUF6oh8oB6rwHw==", "integrity": "sha512-mBOqy8993jtqAoj6XaI1XeC/8/9v6EPS+681ziegrPvTB0DoaCY7PpTS0SpY56qLMoS4OI1TZEM2Zf59zNh05w==",
"license": "MIT",
"dependencies": { "dependencies": {
"cross-fetch": "4.0.0" "cross-fetch": "4.1.0"
}
},
"node_modules/i18next-http-backend/node_modules/cross-fetch": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz",
"integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==",
"dependencies": {
"node-fetch": "^2.6.12"
} }
}, },
"node_modules/iconv-lite": { "node_modules/iconv-lite": {
+1 -1
View File
@@ -93,7 +93,7 @@
"html-react-parser": "4.2.0", "html-react-parser": "4.2.0",
"i18next": "23.12.2", "i18next": "23.12.2",
"i18next-browser-languagedetector": "8.0.0", "i18next-browser-languagedetector": "8.0.0",
"i18next-http-backend": "2.5.2", "i18next-http-backend": "3.0.6",
"immer": "9.0.16", "immer": "9.0.16",
"is-hotkey": "0.2.0", "is-hotkey": "0.2.0",
"jotai": "2.6.0", "jotai": "2.6.0",
+1 -1
View File
@@ -259,7 +259,7 @@ export function DeviceVerification({ request, onExit }: DeviceVerificationProps)
<Dialog variant="Surface"> <Dialog variant="Surface">
<Header style={DialogHeaderStyles} variant="Surface" size="500"> <Header style={DialogHeaderStyles} variant="Surface" size="500">
<Box grow="Yes"> <Box grow="Yes">
<Text size="H4">Device Verification</Text> <Text as="h2" size="H4">Device Verification</Text>
</Box> </Box>
<IconButton size="300" radii="300" onClick={handleCancel} aria-label="Cancel verification"> <IconButton size="300" radii="300" onClick={handleCancel} aria-label="Cancel verification">
<Icon src={Icons.Cross} /> <Icon src={Icons.Cross} />
@@ -299,7 +299,7 @@ export const DeviceVerificationSetup = forwardRef<HTMLDivElement, DeviceVerifica
size="500" size="500"
> >
<Box grow="Yes"> <Box grow="Yes">
<Text size="H4">Setup Device Verification</Text> <Text as="h2" size="H4">Setup Device Verification</Text>
</Box> </Box>
<IconButton size="300" radii="300" onClick={onCancel} aria-label="Cancel"> <IconButton size="300" radii="300" onClick={onCancel} aria-label="Cancel">
<Icon src={Icons.Cross} /> <Icon src={Icons.Cross} />
@@ -334,7 +334,7 @@ export const DeviceVerificationReset = forwardRef<HTMLDivElement, DeviceVerifica
size="500" size="500"
> >
<Box grow="Yes"> <Box grow="Yes">
<Text size="H4">Reset Device Verification</Text> <Text as="h2" size="H4">Reset Device Verification</Text>
</Box> </Box>
<IconButton size="300" radii="300" onClick={onCancel} aria-label="Cancel"> <IconButton size="300" radii="300" onClick={onCancel} aria-label="Cancel">
<Icon src={Icons.Cross} /> <Icon src={Icons.Cross} />
+1 -1
View File
@@ -43,7 +43,7 @@ export const LogoutDialog = forwardRef<HTMLDivElement, LogoutDialogProps>(
size="500" size="500"
> >
<Box grow="Yes"> <Box grow="Yes">
<Text size="H4">Logout</Text> <Text as="h2" size="H4">Logout</Text>
</Box> </Box>
</Header> </Header>
<Box style={{ padding: config.space.S400 }} direction="Column" gap="400"> <Box style={{ padding: config.space.S400 }} direction="Column" gap="400">
@@ -80,7 +80,7 @@ export function JoinAddressPrompt({ onOpen, onCancel }: JoinAddressProps) {
size="500" size="500"
> >
<Box grow="Yes"> <Box grow="Yes">
<Text size="H4">Join with Address</Text> <Text as="h2" size="H4">Join with Address</Text>
</Box> </Box>
<IconButton size="300" onClick={onCancel} radii="300" aria-label="Cancel"> <IconButton size="300" onClick={onCancel} radii="300" aria-label="Cancel">
<Icon src={Icons.Cross} /> <Icon src={Icons.Cross} />
@@ -66,7 +66,7 @@ export function LeaveRoomPrompt({ roomId, onDone, onCancel }: LeaveRoomPromptPro
size="500" size="500"
> >
<Box grow="Yes"> <Box grow="Yes">
<Text size="H4" id="leave-room-dialog-title">Leave Room</Text> <Text as="h2" size="H4" id="leave-room-dialog-title">Leave Room</Text>
</Box> </Box>
<IconButton size="300" onClick={onCancel} radii="300" aria-label="Cancel"> <IconButton size="300" onClick={onCancel} radii="300" aria-label="Cancel">
<Icon src={Icons.Cross} /> <Icon src={Icons.Cross} />
@@ -66,7 +66,7 @@ export function LeaveSpacePrompt({ roomId, onDone, onCancel }: LeaveSpacePromptP
size="500" size="500"
> >
<Box grow="Yes"> <Box grow="Yes">
<Text size="H4">Leave Space</Text> <Text as="h2" size="H4">Leave Space</Text>
</Box> </Box>
<IconButton size="300" onClick={onCancel} radii="300" aria-label="Cancel"> <IconButton size="300" onClick={onCancel} radii="300" aria-label="Cancel">
<Icon src={Icons.Cross} /> <Icon src={Icons.Cross} />
@@ -23,7 +23,7 @@ export const RoomTopicViewer = as<
> >
<Header className={css.ModalHeader} variant="Surface" size="500"> <Header className={css.ModalHeader} variant="Surface" size="500">
<Box grow="Yes"> <Box grow="Yes">
<Text size="H4" truncate id="room-topic-title"> <Text as="h2" size="H4" truncate id="room-topic-title">
{name} {name}
</Text> </Text>
</Box> </Box>
+1 -1
View File
@@ -18,7 +18,7 @@ function DummyErrorDialog({
<Dialog> <Dialog>
<Box style={{ padding: config.space.S400 }} direction="Column" gap="400"> <Box style={{ padding: config.space.S400 }} direction="Column" gap="400">
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text size="H4">{title}</Text> <Text as="h2" size="H4">{title}</Text>
<Text>{message}</Text> <Text>{message}</Text>
</Box> </Box>
<Button variant="Critical" onClick={onRetry}> <Button variant="Critical" onClick={onRetry}>
+2 -2
View File
@@ -37,7 +37,7 @@ function EmailErrorDialog({
gap="400" gap="400"
> >
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text size="H4">{title}</Text> <Text as="h2" size="H4">{title}</Text>
<Text>{message}</Text> <Text>{message}</Text>
<Text as="label" htmlFor="retryEmailInput" size="L400" style={{ paddingTop: config.space.S400 }}> <Text as="label" htmlFor="retryEmailInput" size="L400" style={{ paddingTop: config.space.S400 }}>
Email Email
@@ -141,7 +141,7 @@ export function EmailStageDialog({
<Dialog> <Dialog>
<Box style={{ padding: config.space.S400 }} direction="Column" gap="400"> <Box style={{ padding: config.space.S400 }} direction="Column" gap="400">
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text size="H4">Verification Request Sent</Text> <Text as="h2" size="H4">Verification Request Sent</Text>
<Text>{`Please check your email "${emailTokenState.data.email}" and validate before continuing further.`}</Text> <Text>{`Please check your email "${emailTokenState.data.email}" and validate before continuing further.`}</Text>
{errorCode && ( {errorCode && (
@@ -43,7 +43,7 @@ export function PasswordStage({
size="500" size="500"
> >
<Box grow="Yes"> <Box grow="Yes">
<Text size="H4">Account Password</Text> <Text as="h2" size="H4">Account Password</Text>
</Box> </Box>
<IconButton size="300" onClick={onCancel} radii="300" aria-label="Cancel"> <IconButton size="300" onClick={onCancel} radii="300" aria-label="Cancel">
<Icon src={Icons.Cross} /> <Icon src={Icons.Cross} />
@@ -35,7 +35,7 @@ function RegistrationTokenErrorDialog({
gap="400" gap="400"
> >
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text size="H4">{title}</Text> <Text as="h2" size="H4">{title}</Text>
<Text>{message}</Text> <Text>{message}</Text>
<Text as="label" htmlFor="retryTokenInput" size="L400" style={{ paddingTop: config.space.S400 }}> <Text as="label" htmlFor="retryTokenInput" size="L400" style={{ paddingTop: config.space.S400 }}>
Registration Token Registration Token
+1 -1
View File
@@ -54,7 +54,7 @@ export function SSOStage({
size="500" size="500"
> >
<Box grow="Yes"> <Box grow="Yes">
<Text size="H4">SSO Login</Text> <Text as="h2" size="H4">SSO Login</Text>
</Box> </Box>
<IconButton size="300" onClick={onCancel} radii="300" aria-label="Cancel"> <IconButton size="300" onClick={onCancel} radii="300" aria-label="Cancel">
<Icon src={Icons.Cross} /> <Icon src={Icons.Cross} />
+1 -1
View File
@@ -18,7 +18,7 @@ function TermsErrorDialog({
<Dialog> <Dialog>
<Box style={{ padding: config.space.S400 }} direction="Column" gap="400"> <Box style={{ padding: config.space.S400 }} direction="Column" gap="400">
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text size="H4">{title}</Text> <Text as="h2" size="H4">{title}</Text>
<Text>{message}</Text> <Text>{message}</Text>
</Box> </Box>
<Button variant="Critical" onClick={onRetry}> <Button variant="Critical" onClick={onRetry}>
@@ -68,7 +68,7 @@ function SelfDemoteAlert({ power, onCancel, onChange }: SelfDemoteAlertProps) {
size="500" size="500"
> >
<Box grow="Yes"> <Box grow="Yes">
<Text size="H4">Self Demotion</Text> <Text as="h2" size="H4">Self Demotion</Text>
</Box> </Box>
<IconButton size="300" onClick={onCancel} radii="300" aria-label="Cancel"> <IconButton size="300" onClick={onCancel} radii="300" aria-label="Cancel">
<Icon src={Icons.Cross} /> <Icon src={Icons.Cross} />
@@ -118,7 +118,7 @@ function SharedPowerAlert({ power, onCancel, onChange }: SharedPowerAlertProps)
size="500" size="500"
> >
<Box grow="Yes"> <Box grow="Yes">
<Text size="H4">Shared Power</Text> <Text as="h2" size="H4">Shared Power</Text>
</Box> </Box>
<IconButton size="300" onClick={onCancel} radii="300" aria-label="Cancel"> <IconButton size="300" onClick={onCancel} radii="300" aria-label="Cancel">
<Icon src={Icons.Cross} /> <Icon src={Icons.Cross} />
@@ -198,7 +198,7 @@ export function AddExistingModal({ parentId, space, requestClose }: AddExistingM
}} }}
> >
<Box grow="Yes"> <Box grow="Yes">
<Text size="H4">Add Existing</Text> <Text as="h2" size="H4">Add Existing</Text>
</Box> </Box>
<Box shrink="No"> <Box shrink="No">
<IconButton size="300" radii="300" onClick={requestClose} aria-label="Close"> <IconButton size="300" radii="300" onClick={requestClose} aria-label="Close">
-1
View File
@@ -146,7 +146,6 @@ export function ScreenShareButton({ enabled, onToggle }: ScreenShareButtonProps)
radii="400" radii="400"
size="400" size="400"
onClick={() => onToggle()} onClick={() => onToggle()}
aria-label={enabled ? 'Stop Video' : 'Start Video'}
aria-label={enabled ? 'Stop Screenshare' : 'Start Screenshare'} aria-label={enabled ? 'Stop Screenshare' : 'Start Screenshare'}
outlined outlined
> >
@@ -121,7 +121,7 @@ export function RoomEncryption({ permissions }: RoomEncryptionProps) {
size="500" size="500"
> >
<Box grow="Yes"> <Box grow="Yes">
<Text size="H4">Enable Encryption</Text> <Text as="h2" size="H4">Enable Encryption</Text>
</Box> </Box>
<IconButton size="300" onClick={() => setPrompt(false)} radii="300" aria-label="Cancel"> <IconButton size="300" onClick={() => setPrompt(false)} radii="300" aria-label="Cancel">
<Icon src={Icons.Cross} /> <Icon src={Icons.Cross} />
@@ -103,7 +103,7 @@ function RoomUpgradeDialog({ requestClose }: { requestClose: () => void }) {
size="500" size="500"
> >
<Box grow="Yes"> <Box grow="Yes">
<Text size="H4">{room.isSpaceRoom() ? 'Space Upgrade' : 'Room Upgrade'}</Text> <Text as="h2" size="H4">{room.isSpaceRoom() ? 'Space Upgrade' : 'Room Upgrade'}</Text>
</Box> </Box>
<IconButton size="300" onClick={requestClose} radii="300" aria-label="Close"> <IconButton size="300" onClick={requestClose} radii="300" aria-label="Close">
<Icon src={Icons.Cross} /> <Icon src={Icons.Cross} />
@@ -283,7 +283,6 @@ export function Members({ requestClose }: MembersProps) {
radii="Pill" radii="Pill"
outlined outlined
size="300" size="300"
aria-label="Scroll to Top"
> >
<Icon src={Icons.ChevronTop} size="300" /> <Icon src={Icons.ChevronTop} size="300" />
</IconButton> </IconButton>
-1
View File
@@ -366,7 +366,6 @@ export function MembersDrawer({ room, members }: MembersDrawerProps) {
radii="Pill" radii="Pill"
outlined outlined
size="300" size="300"
aria-label="Scroll to Top"
> >
<Icon src={Icons.ChevronTop} size="300" /> <Icon src={Icons.ChevronTop} size="300" />
</IconButton> </IconButton>
-1
View File
@@ -808,7 +808,6 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
variant="SurfaceVariant" variant="SurfaceVariant"
size="300" size="300"
radii="300" radii="300"
aria-label="Share location"
title="Share location" title="Share location"
> >
{locating ? ( {locating ? (
+17 -10
View File
@@ -46,11 +46,11 @@ import { ClientBindAtoms, ClientLayout, ClientRoot } from './client';
import { Home, HomeRouteRoomProvider, HomeSearch } from './client/home'; import { Home, HomeRouteRoomProvider, HomeSearch } from './client/home';
import { Direct, DirectCreate, DirectRouteRoomProvider } from './client/direct'; import { Direct, DirectCreate, DirectRouteRoomProvider } from './client/direct';
import { RouteSpaceProvider, Space, SpaceRouteRoomProvider, SpaceSearch } from './client/space'; import { RouteSpaceProvider, Space, SpaceRouteRoomProvider, SpaceSearch } from './client/space';
import { Explore, FeaturedRooms, PublicRooms } from './client/explore';
import { Notifications, Inbox, Invites } from './client/inbox';
import { setAfterLoginRedirectPath } from './afterLoginRedirectPath'; import { setAfterLoginRedirectPath } from './afterLoginRedirectPath';
import { Room } from '../features/room'; import { Room } from '../features/room';
import { Lobby } from '../features/lobby';
import { WelcomePage } from './client/WelcomePage'; import { WelcomePage } from './client/WelcomePage';
import { SidebarNav } from './client/SidebarNav'; import { SidebarNav } from './client/SidebarNav';
import { PageRoot } from '../components/page'; import { PageRoot } from '../components/page';
@@ -70,6 +70,13 @@ import { HomeCreateRoom } from './client/home/CreateRoom';
import { Create } from './client/create'; import { Create } from './client/create';
const CreateSpaceModalRenderer = React.lazy(() => import('../features/create-space').then(m => ({ default: m.CreateSpaceModalRenderer }))); const CreateSpaceModalRenderer = React.lazy(() => import('../features/create-space').then(m => ({ default: m.CreateSpaceModalRenderer })));
const SearchModalRenderer = React.lazy(() => import('../features/search').then(m => ({ default: m.SearchModalRenderer }))); const SearchModalRenderer = React.lazy(() => import('../features/search').then(m => ({ default: m.SearchModalRenderer })));
const Explore = React.lazy(() => import('./client/explore').then(m => ({ default: m.Explore })));
const FeaturedRooms = React.lazy(() => import('./client/explore').then(m => ({ default: m.FeaturedRooms })));
const PublicRooms = React.lazy(() => import('./client/explore').then(m => ({ default: m.PublicRooms })));
const Notifications = React.lazy(() => import('./client/inbox').then(m => ({ default: m.Notifications })));
const Inbox = React.lazy(() => import('./client/inbox').then(m => ({ default: m.Inbox })));
const Invites = React.lazy(() => import('./client/inbox').then(m => ({ default: m.Invites })));
const Lobby = React.lazy(() => import('../features/lobby').then(m => ({ default: m.Lobby })));
import { getFallbackSession } from '../state/sessions'; import { getFallbackSession } from '../state/sessions';
import { CallStatusRenderer } from './CallStatusRenderer'; import { CallStatusRenderer } from './CallStatusRenderer';
import { CallEmbedProvider } from '../components/CallEmbedProvider'; import { CallEmbedProvider } from '../components/CallEmbedProvider';
@@ -238,7 +245,7 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize)
element={<WelcomePage />} element={<WelcomePage />}
/> />
)} )}
<Route path={_LOBBY_PATH} element={<Lobby />} /> <Route path={_LOBBY_PATH} element={<React.Suspense fallback={null}><Lobby /></React.Suspense>} />
<Route path={_SEARCH_PATH} element={<SpaceSearch />} /> <Route path={_SEARCH_PATH} element={<SpaceSearch />} />
<Route <Route
path={_ROOM_PATH} path={_ROOM_PATH}
@@ -255,7 +262,7 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize)
<PageRoot <PageRoot
nav={ nav={
<MobileFriendlyPageNav path={EXPLORE_PATH}> <MobileFriendlyPageNav path={EXPLORE_PATH}>
<Explore /> <React.Suspense fallback={null}><Explore /></React.Suspense>
</MobileFriendlyPageNav> </MobileFriendlyPageNav>
} }
> >
@@ -270,8 +277,8 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize)
element={<WelcomePage />} element={<WelcomePage />}
/> />
)} )}
<Route path={_FEATURED_PATH} element={<FeaturedRooms />} /> <Route path={_FEATURED_PATH} element={<React.Suspense fallback={null}><FeaturedRooms /></React.Suspense>} />
<Route path={_SERVER_PATH} element={<PublicRooms />} /> <Route path={_SERVER_PATH} element={<React.Suspense fallback={null}><PublicRooms /></React.Suspense>} />
</Route> </Route>
<Route path={CREATE_PATH} element={<Create />} /> <Route path={CREATE_PATH} element={<Create />} />
<Route <Route
@@ -280,7 +287,7 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize)
<PageRoot <PageRoot
nav={ nav={
<MobileFriendlyPageNav path={INBOX_PATH}> <MobileFriendlyPageNav path={INBOX_PATH}>
<Inbox /> <React.Suspense fallback={null}><Inbox /></React.Suspense>
</MobileFriendlyPageNav> </MobileFriendlyPageNav>
} }
> >
@@ -295,8 +302,8 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize)
element={<WelcomePage />} element={<WelcomePage />}
/> />
)} )}
<Route path={_NOTIFICATIONS_PATH} element={<Notifications />} /> <Route path={_NOTIFICATIONS_PATH} element={<React.Suspense fallback={null}><Notifications /></React.Suspense>} />
<Route path={_INVITES_PATH} element={<Invites />} /> <Route path={_INVITES_PATH} element={<React.Suspense fallback={null}><Invites /></React.Suspense>} />
</Route> </Route>
</Route> </Route>
<Route path="/*" element={<p>Page not found</p>} /> <Route path="/*" element={<p>Page not found</p>} />
+1 -1
View File
@@ -56,7 +56,7 @@ export function Login() {
return ( return (
<Box direction="Column" gap="500"> <Box direction="Column" gap="500">
<Text size="H2" priority="400"> <Text as="h1" size="H2" priority="400">
Login Login
</Text> </Text>
{parsedFlows.token && loginSearchParams.loginToken && ( {parsedFlows.token && loginSearchParams.loginToken && (
@@ -258,12 +258,13 @@ export function PasswordRegisterForm({
<> <>
<Box as="form" onSubmit={handleSubmit} direction="Inherit" gap="400"> <Box as="form" onSubmit={handleSubmit} direction="Inherit" gap="400">
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text as="label" size="L400" priority="300"> <Text as="label" size="L400" priority="300" htmlFor="usernameInput">
Username Username
</Text> </Text>
<Input <Input
variant="Background" variant="Background"
defaultValue={defaultUsername} defaultValue={defaultUsername}
id="usernameInput"
name="usernameInput" name="usernameInput"
aria-label="Username" aria-label="Username"
size="500" size="500"
@@ -284,12 +285,13 @@ export function PasswordRegisterForm({
{(match, doMatch, passRef, confPassRef) => ( {(match, doMatch, passRef, confPassRef) => (
<> <>
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text as="label" size="L400" priority="300"> <Text as="label" size="L400" priority="300" htmlFor="passwordInput">
Password Password
</Text> </Text>
<PasswordInput <PasswordInput
ref={passRef} ref={passRef}
onChange={doMatch} onChange={doMatch}
id="passwordInput"
name="passwordInput" name="passwordInput"
aria-label="Password" aria-label="Password"
variant="Background" variant="Background"
@@ -315,12 +317,13 @@ export function PasswordRegisterForm({
)} )}
</Box> </Box>
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text as="label" size="L400" priority="300"> <Text as="label" size="L400" priority="300" htmlFor="confirmPasswordInput">
Confirm Password Confirm Password
</Text> </Text>
<PasswordInput <PasswordInput
ref={confPassRef} ref={confPassRef}
onChange={doMatch} onChange={doMatch}
id="confirmPasswordInput"
name="confirmPasswordInput" name="confirmPasswordInput"
aria-label="Confirm password" aria-label="Confirm password"
variant="Background" variant="Background"
@@ -335,7 +338,7 @@ export function PasswordRegisterForm({
</ConfirmPasswordMatch> </ConfirmPasswordMatch>
{hasStageInFlows(uiaFlows, AuthType.RegistrationToken) && ( {hasStageInFlows(uiaFlows, AuthType.RegistrationToken) && (
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text as="label" size="L400" priority="300"> <Text as="label" size="L400" priority="300" htmlFor="tokenInput">
{requiredStageInFlows(uiaFlows, AuthType.RegistrationToken) {requiredStageInFlows(uiaFlows, AuthType.RegistrationToken)
? 'Registration Token' ? 'Registration Token'
: 'Registration Token (Optional)'} : 'Registration Token (Optional)'}
@@ -343,6 +346,7 @@ export function PasswordRegisterForm({
<Input <Input
variant="Background" variant="Background"
defaultValue={defaultRegisterToken} defaultValue={defaultRegisterToken}
id="tokenInput"
name="tokenInput" name="tokenInput"
aria-label="Registration token" aria-label="Registration token"
size="500" size="500"
@@ -353,12 +357,13 @@ export function PasswordRegisterForm({
)} )}
{hasStageInFlows(uiaFlows, AuthType.Email) && ( {hasStageInFlows(uiaFlows, AuthType.Email) && (
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text as="label" size="L400" priority="300"> <Text as="label" size="L400" priority="300" htmlFor="emailInput">
{requiredStageInFlows(uiaFlows, AuthType.Email) ? 'Email' : 'Email (Optional)'} {requiredStageInFlows(uiaFlows, AuthType.Email) ? 'Email' : 'Email (Optional)'}
</Text> </Text>
<Input <Input
variant="Background" variant="Background"
defaultValue={defaultEmail} defaultValue={defaultEmail}
id="emailInput"
name="emailInput" name="emailInput"
aria-label="Email address" aria-label="Email address"
type="email" type="email"
@@ -170,12 +170,13 @@ export function PasswordResetForm({ defaultEmail }: PasswordResetFormProps) {
Homeserver <strong>{server}</strong> will send you an email to let you reset your password. Homeserver <strong>{server}</strong> will send you an email to let you reset your password.
</Text> </Text>
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text as="label" size="L400" priority="300"> <Text as="label" size="L400" priority="300" htmlFor="emailInput">
Email Email
</Text> </Text>
<Input <Input
defaultValue={defaultEmail} defaultValue={defaultEmail}
type="email" type="email"
id="emailInput"
name="emailInput" name="emailInput"
aria-label="Email address" aria-label="Email address"
variant="Background" variant="Background"
@@ -193,12 +194,13 @@ export function PasswordResetForm({ defaultEmail }: PasswordResetFormProps) {
{(match, doMatch, passRef, confPassRef) => ( {(match, doMatch, passRef, confPassRef) => (
<> <>
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text as="label" size="L400" priority="300"> <Text as="label" size="L400" priority="300" htmlFor="passwordInput">
New Password New Password
</Text> </Text>
<PasswordInput <PasswordInput
ref={passRef} ref={passRef}
onChange={doMatch} onChange={doMatch}
id="passwordInput"
name="passwordInput" name="passwordInput"
aria-label="New password" aria-label="New password"
variant="Background" variant="Background"
@@ -208,12 +210,13 @@ export function PasswordResetForm({ defaultEmail }: PasswordResetFormProps) {
/> />
</Box> </Box>
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text as="label" size="L400" priority="300"> <Text as="label" size="L400" priority="300" htmlFor="confirmPasswordInput">
Confirm Password Confirm Password
</Text> </Text>
<PasswordInput <PasswordInput
ref={confPassRef} ref={confPassRef}
onChange={doMatch} onChange={doMatch}
id="confirmPasswordInput"
name="confirmPasswordInput" name="confirmPasswordInput"
aria-label="Confirm new password" aria-label="Confirm new password"
variant="Background" variant="Background"
+1 -1
View File
@@ -466,7 +466,7 @@ export const getReactCustomHtmlParser = (
onClick={params.handleSpoilerClick} onClick={params.handleSpoilerClick}
className={css.Spoiler()} className={css.Spoiler()}
aria-label="Spoiler — click to reveal" aria-label="Spoiler — click to reveal"
aria-pressed={false} aria-pressed={true}
style={{ cursor: 'pointer' }} style={{ cursor: 'pointer' }}
> >
{domToReact(children, opts)} {domToReact(children, opts)}