feat(desktop): DevicePage routes to UserKeySetup or UserKeyUnlock

This commit is contained in:
byGalax
2026-05-15 23:03:02 +02:00
parent e292df82f0
commit 02e1af4517
3 changed files with 18 additions and 412 deletions
+18 -95
View File
@@ -1,109 +1,32 @@
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { DeviceRegistration } from '../components/DeviceRegistration';
import { DeviceRestore } from '../components/DeviceRestore';
import { useAuth } from '../context/AuthContext';
type Mode = 'register' | 'restore';
import { UserKeySetup } from '../components/UserKeySetup';
import { UserKeyUnlock } from '../components/UserKeyUnlock';
export function DevicePage() {
const { t } = useTranslation(['app']);
const { session, profile, setDevice } = useAuth();
const { session, userKeyState, refreshUserKeyState } = useAuth();
const navigate = useNavigate();
const [mode, setMode] = useState<Mode>('register');
if (!session) return null; // Guarded by RequireAuth, but be defensive.
const defaultName =
(profile?.displayName ?? profile?.username ?? 'Desktop') + ' Desktop';
if (!session) return null;
const onComplete = async () => {
await refreshUserKeyState();
navigate('/chats', { replace: true });
};
return (
<main className="relative flex min-h-screen items-center justify-center overflow-hidden bg-ink-950 px-5 py-10 text-neutral-100 sm:px-8">
<BackgroundStage />
<div className="relative z-10 w-full max-w-md space-y-3">
<div
role="tablist"
aria-label={t('app:backup.mode_label', { defaultValue: 'Gerätemodus' })}
className="flex gap-1 rounded-xl border border-white/10 bg-ink-900/60 p-1 backdrop-blur-xl"
>
<TabButton
active={mode === 'register'}
onClick={() => setMode('register')}
label={t('app:backup.mode_register', { defaultValue: 'Neu einrichten' })}
/>
<TabButton
active={mode === 'restore'}
onClick={() => setMode('restore')}
label={t('app:backup.mode_restore', { defaultValue: 'Backup wiederherstellen' })}
/>
</div>
{mode === 'register' ? (
<DeviceRegistration
<main className="flex min-h-screen items-center justify-center bg-ink-950 p-6">
<div className="w-full max-w-md">
{userKeyState.status === 'needs-setup' && (
<UserKeySetup userId={session.user.id} onComplete={onComplete} />
)}
{userKeyState.status === 'needs-unlock' && (
<UserKeyUnlock
userId={session.user.id}
defaultName={defaultName}
onRegistered={(device) => {
setDevice(device);
// Signal the chats page to open the post-registration backup
// prompt (AppShell reads this on mount).
try {
window.sessionStorage.setItem('chatapp.backup.prompt', '1');
} catch {
/* storage disabled — skip hint */
}
navigate('/chats', { replace: true });
}}
/>
) : (
<DeviceRestore
userId={session.user.id}
onRestored={(device) => {
setDevice(device);
navigate('/chats', { replace: true });
}}
hasRecovery={userKeyState.hasRecovery}
lockedUntil={userKeyState.lockedUntil}
onUnlocked={onComplete}
/>
)}
</div>
</main>
);
}
function TabButton({
active,
onClick,
label,
}: {
active: boolean;
onClick: () => void;
label: string;
}) {
return (
<button
type="button"
role="tab"
aria-selected={active}
onClick={onClick}
className={
'flex-1 cursor-pointer rounded-lg px-3 py-2 text-xs font-semibold transition focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-400/40 ' +
(active
? 'bg-brand-500/20 text-brand-100 ring-1 ring-brand-400/40'
: 'text-neutral-400 hover:bg-white/5 hover:text-neutral-100')
}
>
{label}
</button>
);
}
function BackgroundStage() {
return (
<div aria-hidden="true" className="pointer-events-none absolute inset-0">
<div className="bg-grid absolute inset-0 opacity-[0.28]" />
<div className="absolute -left-32 top-1/4 h-[460px] w-[460px] rounded-full bg-brand-500/25 blur-3xl" />
<div className="absolute -right-32 bottom-0 h-[460px] w-[460px] rounded-full bg-fuchsia-500/15 blur-3xl" />
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_center,transparent_45%,rgba(5,5,7,0.65)_100%)]" />
</div>
);
}