rework login page

This commit is contained in:
v.lagerev 2026-04-14 23:19:44 +03:00
parent 510cf87ec9
commit 50a57ffc2b
12 changed files with 92 additions and 107 deletions

View file

@ -1,12 +0,0 @@
import { style } from '@vanilla-extract/css';
import { color, config } from 'folds';
export const SplashScreen = style({
minHeight: '100%',
backgroundColor: color.Background.Container,
color: color.Background.OnContainer,
});
export const SplashScreenFooter = style({
padding: config.space.S400,
});

View file

@ -1,29 +0,0 @@
import { Box, Text } from 'folds';
import React, { ReactNode } from 'react';
import classNames from 'classnames';
import * as patternsCSS from '../../styles/Patterns.css';
import * as css from './SplashScreen.css';
type SplashScreenProps = {
children: ReactNode;
};
export function SplashScreen({ children }: SplashScreenProps) {
return (
<Box
className={classNames(css.SplashScreen, patternsCSS.BackgroundDotPattern)}
direction="Column"
>
{children}
<Box
className={css.SplashScreenFooter}
shrink="No"
alignItems="Center"
justifyContent="Center"
>
<Text size="H2" align="Center">
Vojo
</Text>
</Box>
</Box>
);
}

View file

@ -1 +0,0 @@
export * from './SplashScreen';

View file

@ -1,16 +1,9 @@
import { Box, Button, Dialog, Spinner, Text, color, config } from 'folds';
import { Box, Button, Dialog, Text, color, config } from 'folds';
import React from 'react';
import { SplashScreen } from '../components/splash-screen';
import { AuthSplashScreen } from './auth/AuthSplashScreen';
export function ConfigConfigLoading() {
return (
<SplashScreen>
<Box grow="Yes" direction="Column" gap="400" alignItems="Center" justifyContent="Center">
<Spinner variant="Secondary" size="600" />
<Text>Heating up</Text>
</Box>
</SplashScreen>
);
return <AuthSplashScreen />;
}
type ConfigConfigErrorProps = {
@ -20,7 +13,7 @@ type ConfigConfigErrorProps = {
};
export function ConfigConfigError({ error, retry, ignore }: ConfigConfigErrorProps) {
return (
<SplashScreen>
<AuthSplashScreen>
<Box grow="Yes" direction="Column" gap="400" alignItems="Center" justifyContent="Center">
<Dialog>
<Box style={{ padding: config.space.S400 }} direction="Column" gap="400">
@ -48,6 +41,6 @@ export function ConfigConfigError({ error, retry, ignore }: ConfigConfigErrorPro
</Box>
</Dialog>
</Box>
</SplashScreen>
</AuthSplashScreen>
);
}

View file

@ -2,7 +2,7 @@ import React, { ReactNode, useEffect } from 'react';
import { Box, Dialog, Text, config } from 'folds';
import { AsyncStatus, useAsyncCallback } from '../hooks/useAsyncCallback';
import { checkIndexedDBSupport } from '../utils/featureCheck';
import { SplashScreen } from '../components/splash-screen';
import { AuthSplashScreen } from './auth/AuthSplashScreen';
export function FeatureCheck({ children }: { children: ReactNode }) {
const [idbSupportState, checkIDBSupport] = useAsyncCallback(checkIndexedDBSupport);
@ -13,7 +13,7 @@ export function FeatureCheck({ children }: { children: ReactNode }) {
if (idbSupportState.status === AsyncStatus.Success && idbSupportState.data === false) {
return (
<SplashScreen>
<AuthSplashScreen>
<Box grow="Yes" alignItems="Center" justifyContent="Center">
<Dialog>
<Box style={{ padding: config.space.S400 }} direction="Column" gap="400">
@ -34,7 +34,7 @@ export function FeatureCheck({ children }: { children: ReactNode }) {
</Box>
</Dialog>
</Box>
</SplashScreen>
</AuthSplashScreen>
);
}

View file

@ -11,6 +11,8 @@ import {
import { useTranslation } from 'react-i18next';
import { AuthFooter } from './AuthFooter';
import { AuthMascot } from './AuthMascot';
import { authLayoutRootVars } from './layoutConfig';
import * as css from './styles.css';
import {
clientAllowedServer,
@ -27,8 +29,6 @@ import { AuthFlowsLoader } from '../../components/AuthFlowsLoader';
import { AuthFlowsProvider } from '../../hooks/useAuthFlows';
import { AuthServerProvider } from '../../hooks/useAuthServer';
import { tryDecodeURIComponent } from '../../utils/dom';
import mascotPoster from '../../../../public/res/img/mascot.png';
import mascotWebm from '../../../../public/res/img/mascot.webm';
const currentAuthPath = (pathname: string): string => {
if (matchPath(LOGIN_PATH, pathname)) return LOGIN_PATH;
@ -163,16 +163,6 @@ function calculateModalLayout(input: {
};
}
const rootVars: React.CSSProperties = {
'--vojo-mascot-size': 'clamp(28rem, 57dvh, 48rem)',
'--vojo-mascot-top': 'clamp(1.5rem, 4dvh, 3rem)',
'--vojo-stack-pad': '1.5rem',
'--vojo-anchor-ratio': '0.71',
'--vojo-modal-min-top': 'clamp(12rem, 26dvh, 18rem)',
'--vojo-modal-gap': 'clamp(0.75rem, 2dvh, 1.5rem)',
'--vojo-footer-space': 'clamp(4.5rem, 9dvh, 6.5rem)',
} as React.CSSProperties;
export function AuthLayout() {
const { t } = useTranslation();
const navigate = useNavigate();
@ -291,21 +281,9 @@ export function AuthLayout() {
}, []);
return (
<div className={css.AuthLayout} style={rootVars} ref={pageRef}>
<div className={css.AuthLayout} style={authLayoutRootVars} ref={pageRef}>
<div className={css.AuthStack}>
<div className={css.AuthMascot} aria-hidden="true" ref={mascotRef}>
<video
className={css.AuthMascotVideo}
autoPlay
loop
muted
playsInline
preload="auto"
poster={mascotPoster}
>
<source src={mascotWebm} type="video/webm" />
</video>
</div>
<AuthMascot mascotRef={mascotRef} />
<div className={css.AuthModalZone}>
<div className={css.AuthCard} ref={modalRef}>

View file

@ -0,0 +1,27 @@
import React, { Ref } from 'react';
import mascotPoster from '../../../../public/res/img/mascot.png';
import mascotWebm from '../../../../public/res/img/mascot.webm';
import * as css from './styles.css';
type AuthMascotProps = {
mascotRef?: Ref<HTMLDivElement>;
};
export function AuthMascot({ mascotRef }: AuthMascotProps) {
return (
<div className={css.AuthMascot} aria-hidden="true" ref={mascotRef}>
<video
className={css.AuthMascotVideo}
autoPlay
loop
muted
playsInline
preload="auto"
poster={mascotPoster}
>
<source src={mascotWebm} type="video/webm" />
</video>
</div>
);
}

View file

@ -0,0 +1,22 @@
import React, { ReactNode } from 'react';
import { AuthFooter } from './AuthFooter';
import { AuthMascot } from './AuthMascot';
import { authLayoutRootVars } from './layoutConfig';
import * as css from './styles.css';
type AuthSplashScreenProps = {
children?: ReactNode;
};
export function AuthSplashScreen({ children }: AuthSplashScreenProps) {
return (
<div className={css.AuthLayout} style={authLayoutRootVars}>
<div className={css.AuthStack}>
<AuthMascot />
{children && <div className={css.AuthSplashContent}>{children}</div>}
</div>
<AuthFooter />
</div>
);
}

View file

@ -0,0 +1,11 @@
import type { CSSProperties } from 'react';
export const authLayoutRootVars: CSSProperties = {
'--vojo-mascot-size': 'clamp(28rem, 57dvh, 48rem)',
'--vojo-mascot-top': 'clamp(1.5rem, 4dvh, 3rem)',
'--vojo-stack-pad': '1.5rem',
'--vojo-anchor-ratio': '0.71',
'--vojo-modal-min-top': 'clamp(12rem, 26dvh, 18rem)',
'--vojo-modal-gap': 'clamp(0.75rem, 2dvh, 1.5rem)',
'--vojo-footer-space': 'clamp(4.5rem, 9dvh, 6.5rem)',
} as CSSProperties;

View file

@ -87,6 +87,17 @@ export const AuthModalZone = style({
zIndex: 1,
});
export const AuthSplashContent = style({
position: 'relative',
zIndex: 1,
minHeight: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
paddingInline: 'var(--vojo-stack-pad)',
boxSizing: 'border-box',
});
/* ── Auth card (glassmorphism) ── */
export const AuthCard = style({
display: 'flex',

View file

@ -10,7 +10,6 @@ import {
MenuItem,
PopOut,
RectCords,
Spinner,
Text,
} from 'folds';
import { HttpApiEvent, HttpApiEventHandlerMap, MatrixClient } from 'matrix-js-sdk';
@ -23,7 +22,6 @@ import {
logoutClient,
startClient,
} from '../../../client/initMatrix';
import { SplashScreen } from '../../components/splash-screen';
import { ServerConfigsLoader } from '../../components/ServerConfigsLoader';
import { CapabilitiesProvider } from '../../hooks/useCapabilities';
import { MediaConfigProvider } from '../../hooks/useMediaConfig';
@ -36,16 +34,10 @@ import { SyncStatus } from './SyncStatus';
import { AuthMetadataProvider } from '../../hooks/useAuthMetadata';
import { getFallbackSession } from '../../state/sessions';
import { AutoDiscovery } from './AutoDiscovery';
import { AuthSplashScreen } from '../auth/AuthSplashScreen';
function ClientRootLoading() {
return (
<SplashScreen>
<Box direction="Column" grow="Yes" alignItems="Center" justifyContent="Center" gap="400">
<Spinner variant="Secondary" size="600" />
<Text>Heating up</Text>
</Box>
</SplashScreen>
);
return <AuthSplashScreen />;
}
function ClientRootOptions({ mx }: { mx?: MatrixClient }) {
@ -189,7 +181,7 @@ export function ClientRoot({ children }: ClientRootProps) {
{mx && <SyncStatus mx={mx} />}
{loading && <ClientRootOptions mx={mx} />}
{(loadState.status === AsyncStatus.Error || startState.status === AsyncStatus.Error) && (
<SplashScreen>
<AuthSplashScreen>
<Box
direction="Column"
grow="Yes"
@ -213,7 +205,7 @@ export function ClientRoot({ children }: ClientRootProps) {
</Box>
</Dialog>
</Box>
</SplashScreen>
</AuthSplashScreen>
)}
{loading || !mx ? (
<ClientRootLoading />

View file

@ -1,23 +1,16 @@
import React, { ReactNode } from 'react';
import { Box, Dialog, config, Text, Button, Spinner } from 'folds';
import { Box, Dialog, config, Text, Button } from 'folds';
import { SpecVersionsLoader } from '../../components/SpecVersionsLoader';
import { SpecVersionsProvider } from '../../hooks/useSpecVersions';
import { SplashScreen } from '../../components/splash-screen';
import { AuthSplashScreen } from '../auth/AuthSplashScreen';
export function SpecVersions({ baseUrl, children }: { baseUrl: string; children: ReactNode }) {
return (
<SpecVersionsLoader
baseUrl={baseUrl}
fallback={() => (
<SplashScreen>
<Box direction="Column" grow="Yes" alignItems="Center" justifyContent="Center" gap="400">
<Spinner variant="Secondary" size="600" />
<Text>Connecting to server</Text>
</Box>
</SplashScreen>
)}
fallback={() => <AuthSplashScreen />}
error={(err, retry, ignore) => (
<SplashScreen>
<AuthSplashScreen>
<Box direction="Column" grow="Yes" alignItems="Center" justifyContent="Center" gap="400">
<Dialog>
<Box direction="Column" gap="400" style={{ padding: config.space.S400 }}>
@ -37,7 +30,7 @@ export function SpecVersions({ baseUrl, children }: { baseUrl: string; children:
</Box>
</Dialog>
</Box>
</SplashScreen>
</AuthSplashScreen>
)}
>
{(versions) => <SpecVersionsProvider value={versions}>{children}</SpecVersionsProvider>}