Messenger/packages/web/src/features/auth/ui/AuthForm.tsx
Заид Омар Медхат | Zaid Omar Medhat 7ab6b72866 Redesign web client: modern Telegram-shaped chat UI
Full presentation-layer rebuild of packages/web (no core/API/model changes).

- Design system: indigo light+dark token sets, 25 inline SVG icons, CSS
  foundation (spacing/radius/type/motion scales, per-theme elevation, reset,
  scrollbars).
- Shell: two-pane rail (brand, icon theme switch, live search, rich
  conversation rows, account footer, group/contacts compose) + auth card.
- Chat pane: sticky header with presence/typing, grouped bubbles with date
  separators, icon read-ticks, hover toolbar, inline edit, reaction pills,
  auto-growing composer with Enter-to-send, near-bottom-aware autoscroll.
- Media renderers: waveform voice player (WebAudio decode + pseudo-waveform
  fallback), circular video notes (rendered bare), image lightbox with
  zoom/pan, framed video->lightbox player, file cards.
- Restore + modernize notification toast styles.

Emoji kept only as reaction content; all chrome is icons.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFRyKxkKEjfAgpXygzoNiD
2026-07-11 00:30:49 +05:00

106 lines
3.4 KiB
TypeScript

import { useState } from 'react';
import type { ReactElement, SyntheticEvent } from 'react';
import { ApiError } from '@altricade/core/api';
import { useSession } from '../../../entities/session';
type Mode = 'login' | 'register';
export const AuthForm = (): ReactElement => {
const { login, register } = useSession();
const [mode, setMode] = useState<Mode>('login');
const [username, setUsername] = useState('');
const [displayName, setDisplayName] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState<string | null>(null);
const [busy, setBusy] = useState(false);
const submit = async (event: SyntheticEvent): Promise<void> => {
event.preventDefault();
setBusy(true);
setError(null);
try {
if (mode === 'login') {
await login({ username, password });
} else {
await register({ username, displayName, password });
}
} catch (caught) {
setError(caught instanceof ApiError ? caught.message : 'Something went wrong');
} finally {
setBusy(false);
}
};
return (
<main className="auth">
<div className="auth-card">
<div className="auth-brand">
<span className="auth-mark" aria-hidden="true">
A
</span>
<h1 className="auth-wordmark">Altricade</h1>
<p className="auth-tagline">
{mode === 'login' ? 'Welcome back.' : 'Create your account.'}
</p>
</div>
<form className="auth-form" onSubmit={(event) => void submit(event)}>
<label className="field-label">
Username
<input
className="field"
placeholder="username"
autoComplete="username"
value={username}
onChange={(event) => {
setUsername(event.target.value);
}}
/>
</label>
{mode === 'register' ? (
<label className="field-label">
Display name
<input
className="field"
placeholder="Your name"
value={displayName}
onChange={(event) => {
setDisplayName(event.target.value);
}}
/>
</label>
) : null}
<label className="field-label">
Password
<input
className="field"
type="password"
placeholder="••••••••"
autoComplete={mode === 'login' ? 'current-password' : 'new-password'}
value={password}
onChange={(event) => {
setPassword(event.target.value);
}}
/>
</label>
{error !== null ? <p className="form-error">{error}</p> : null}
<button type="submit" className="btn-primary" disabled={busy}>
{busy ? 'Please wait…' : mode === 'login' ? 'Log in' : 'Create account'}
</button>
</form>
<p className="auth-switch">
{mode === 'login' ? "Don't have an account?" : 'Already have an account?'}{' '}
<button
type="button"
className="link"
onClick={() => {
setMode(mode === 'login' ? 'register' : 'login');
setError(null);
}}
>
{mode === 'login' ? 'Sign up' : 'Log in'}
</button>
</p>
</div>
</main>
);
};