feat(desktop): DevicePage routes to UserKeySetup or UserKeyUnlock
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user