eb8f9857ff
Backup / restore flow: - deviceBackup.ts: v2 format wrapping userId + deviceId + privateKey in an encrypted JSON payload so restore can re-seed localStorage, vault, and reattach to the existing server-side device row without provisioning a new one (conv-key bundles stay valid, no "awaiting key" state) - shared/auth: restoreDeviceFromServerRecord — verifies session.user.id matches the backup's userId, confirms the server device row still exists, then writes the private key into the local secret store - BackupExportDialog — passphrase + confirm, generates portable string, copy + download .txt - DeviceRestore — textarea + passphrase → seeds vault + writes deviceId cache, treats this install as the original device - DevicePage now has tabs "Neu einrichten" / "Backup wiederherstellen" - BackupPromptBanner — post-registration nudge, reads sessionStorage signal from fresh provisions and persists "never-ask-again" in localStorage so it stops nagging - SettingsPage backup section: uses the new dialog; removes the dangerous in-place key import (restore now lives in the device flow) Username casing: - Migration 20260420000002 drops lower() from the handle_new_user trigger and widens the regex to [A-Za-z0-9_]. profiles.username is citext so uniqueness + lookups stay case-insensitive regardless of stored casing - Shared auth: trim() only, no toLowerCase on signup/lookups/search. ilike handles CI anyway and citext makes client normalisation redundant - AuthPage regex + input preserve case, FriendsPage search preserves case - i18n (de+en): updated username_hint / username_invalid / ERR_USERNAME_INVALID to reflect the new rule Quick wins: - React Router v7 future flags (v7_startTransition + v7_relativeSplatPath) set on BrowserRouter — silences the upgrade warning - appUpdates.checkForUpdate: swallow benign network/fetch/"could not fetch valid release JSON" cases silently instead of console spam - osNotify: persist an "asked" marker in localStorage so the permission prompt only fires once per install (OS already persists the answer, but the plugin re-queries loudly otherwise)
103 lines
4.7 KiB
TypeScript
103 lines
4.7 KiB
TypeScript
import { BrowserRouter, Navigate, Outlet, Route, Routes } from 'react-router-dom';
|
|
|
|
import { AppShell } from './components/AppShell';
|
|
import { ErrorBoundary } from './components/ErrorBoundary';
|
|
import { UpdateToast } from './components/UpdateToast';
|
|
import { RequireAdmin, RequireAuth, RequireDevice } from './components/guards';
|
|
import { AuthProvider } from './context/AuthContext';
|
|
import { CallProvider } from './context/CallContext';
|
|
import { ConversationsProvider } from './context/ConversationsContext';
|
|
import { FriendshipsProvider } from './context/FriendshipsContext';
|
|
import { ThemeProvider } from './context/ThemeContext';
|
|
import { AdminPage } from './pages/AdminPage';
|
|
import { AuthCallbackPage } from './pages/AuthCallbackPage';
|
|
import { AuthPage } from './pages/AuthPage';
|
|
import { ChatsEmptyState, ChatsPage } from './pages/ChatsPage';
|
|
import { ConversationPage } from './pages/ConversationPage';
|
|
import { DevicePage } from './pages/DevicePage';
|
|
import { FriendsPage } from './pages/FriendsPage';
|
|
import { SettingsPage } from './pages/SettingsPage';
|
|
|
|
// Isolates each top-level route so a crash in one page doesn't take the whole
|
|
// shell down. The boundary auto-retries via `ErrorBoundary`'s backoff schedule.
|
|
function RouteBoundary({ scope }: { scope: string }) {
|
|
return (
|
|
<ErrorBoundary scope={scope}>
|
|
<Outlet />
|
|
</ErrorBoundary>
|
|
);
|
|
}
|
|
|
|
export function App() {
|
|
return (
|
|
<ErrorBoundary scope="root">
|
|
<ThemeProvider>
|
|
<AuthProvider>
|
|
<FriendshipsProvider>
|
|
<ConversationsProvider>
|
|
<CallProvider>
|
|
<BrowserRouter
|
|
future={{
|
|
// Opt into v7 behaviour early so the upgrade is a no-op:
|
|
// - `v7_startTransition` wraps navigations in startTransition
|
|
// so Suspense / concurrent rendering deal with the new tree
|
|
// - `v7_relativeSplatPath` matches relative paths inside
|
|
// splat routes against the parent splat segment (not the
|
|
// full matched path). Our tree has no splat routes today
|
|
// but this kills the runtime warning and future-proofs.
|
|
v7_startTransition: true,
|
|
v7_relativeSplatPath: true,
|
|
}}
|
|
>
|
|
<Routes>
|
|
<Route element={<RouteBoundary scope="auth" />}>
|
|
<Route path="/auth" element={<AuthPage />} />
|
|
<Route path="/auth/callback" element={<AuthCallbackPage />} />
|
|
</Route>
|
|
<Route element={<RequireAuth />}>
|
|
<Route element={<RouteBoundary scope="device" />}>
|
|
<Route path="/device" element={<DevicePage />} />
|
|
</Route>
|
|
<Route element={<RequireDevice />}>
|
|
<Route element={<AppShell />}>
|
|
<Route index element={<Navigate to="/chats" replace />} />
|
|
<Route element={<RouteBoundary scope="chats" />}>
|
|
<Route path="/chats" element={<ChatsPage />}>
|
|
<Route index element={<ChatsEmptyState />} />
|
|
<Route
|
|
path=":id"
|
|
element={
|
|
<ErrorBoundary scope="conversation">
|
|
<ConversationPage />
|
|
</ErrorBoundary>
|
|
}
|
|
/>
|
|
</Route>
|
|
</Route>
|
|
<Route element={<RouteBoundary scope="friends" />}>
|
|
<Route path="/friends" element={<FriendsPage />} />
|
|
</Route>
|
|
<Route element={<RouteBoundary scope="settings" />}>
|
|
<Route path="/settings" element={<SettingsPage />} />
|
|
</Route>
|
|
<Route element={<RequireAdmin />}>
|
|
<Route element={<RouteBoundary scope="admin" />}>
|
|
<Route path="/admin" element={<AdminPage />} />
|
|
</Route>
|
|
</Route>
|
|
</Route>
|
|
</Route>
|
|
</Route>
|
|
<Route path="*" element={<Navigate to="/chats" replace />} />
|
|
</Routes>
|
|
<UpdateToast />
|
|
</BrowserRouter>
|
|
</CallProvider>
|
|
</ConversationsProvider>
|
|
</FriendshipsProvider>
|
|
</AuthProvider>
|
|
</ThemeProvider>
|
|
</ErrorBoundary>
|
|
);
|
|
}
|