- Deleted obsolete icons, layout files, and SVG assets. - Added `useAutoUpdate` hook for streamlined update logic. - Introduced `auto_migrate_electron` and migration commands for Electron-to-Tauri data migration. - Implemented `init_panic_hook` for improved crash reporting. - Updated `.gitignore` for Tauri build output and IDE-specific files.
138 lines
5.0 KiB
TypeScript
138 lines
5.0 KiB
TypeScript
import React, {useEffect, useState} from 'react';
|
|
import ReactDOM from 'react-dom/client';
|
|
import {BrowserRouter, Routes, Route, Outlet} from 'react-router-dom';
|
|
import '@/app/globals.css';
|
|
import '@/lib/i18n';
|
|
import {listen} from '@tauri-apps/api/event';
|
|
import * as tauri from '@/lib/tauri';
|
|
import PulseLoader from '@/components/ui/PulseLoader';
|
|
import {useTranslations} from '@/lib/i18n';
|
|
|
|
listen('auth-success', () => window.location.reload()).then();
|
|
|
|
type MigrationState = 'pending' | 'error' | 'done';
|
|
|
|
function AppInitializer({children}: { children: React.ReactNode }) {
|
|
const t = useTranslations();
|
|
const [state, setState] = useState<MigrationState>('pending');
|
|
const [retryCount, setRetryCount] = useState<number>(0);
|
|
|
|
useEffect(function (): void {
|
|
setState('pending');
|
|
tauri.autoMigrateElectron().then(function (result): void {
|
|
if (result.migrated) {
|
|
window.location.reload();
|
|
} else if (result.error) {
|
|
setState('error');
|
|
} else {
|
|
setState('done');
|
|
}
|
|
}).catch(function (): void {
|
|
setState('error');
|
|
});
|
|
}, [retryCount]);
|
|
|
|
if (state === 'pending') {
|
|
return (
|
|
<div className="bg-background text-text-primary h-screen flex flex-col items-center justify-center gap-4 font-['Lora']">
|
|
<PulseLoader text={t('migration.autoMigrating')}/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (state === 'error') {
|
|
return (
|
|
<div className="bg-background text-text-primary h-screen flex flex-col items-center justify-center gap-6 font-['Lora'] p-8">
|
|
<div className="flex flex-col items-center gap-3 max-w-md text-center">
|
|
<p className="text-lg font-semibold">{t('migration.autoErrorTitle')}</p>
|
|
<p className="text-sm text-text-secondary">{t('migration.autoErrorText')}</p>
|
|
</div>
|
|
<div className="flex flex-col items-center gap-3">
|
|
<button
|
|
className="px-6 py-2 bg-primary text-white rounded-lg hover:opacity-90 transition-opacity"
|
|
onClick={function (): void { setRetryCount(function (n): number { return n + 1; }); }}
|
|
>
|
|
{t('migration.autoErrorRetry')}
|
|
</button>
|
|
<div className="flex flex-col items-center gap-1">
|
|
<button
|
|
className="px-6 py-2 text-sm text-text-secondary hover:text-text-primary underline transition-colors"
|
|
onClick={function (): void { setState('done'); }}
|
|
>
|
|
{t('migration.autoErrorContinue')}
|
|
</button>
|
|
<p className="text-xs text-text-secondary opacity-70">{t('migration.autoErrorContinueWarning')}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return <>{children}</>;
|
|
}
|
|
|
|
import ScribeShell from '@/components/layout/ScribeShell';
|
|
import LoginWrapper from '@/app/login/LoginWrapper';
|
|
|
|
import HomePage from '@/app/HomePage';
|
|
import BookPage from '@/app/book/BookPage';
|
|
import ChapterPage from '@/app/book/ChapterPage';
|
|
import BookLayout from '@/app/book/BookLayout';
|
|
|
|
import LoginPage from '@/app/login/login/page';
|
|
import RegisterPage from '@/app/login/register/page';
|
|
import ResetPasswordPage from '@/app/login/reset-password/page';
|
|
import OfflineLoginPage from '@/app/login/offline/page';
|
|
|
|
function LoginShell() {
|
|
return (
|
|
<LoginWrapper>
|
|
<Outlet/>
|
|
</LoginWrapper>
|
|
);
|
|
}
|
|
|
|
function MainShell() {
|
|
return (
|
|
<ScribeShell>
|
|
<Outlet/>
|
|
</ScribeShell>
|
|
);
|
|
}
|
|
|
|
function BookShell() {
|
|
return (
|
|
<BookLayout>
|
|
<Outlet/>
|
|
</BookLayout>
|
|
);
|
|
}
|
|
|
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
<React.StrictMode>
|
|
<AppInitializer>
|
|
<BrowserRouter>
|
|
<Routes>
|
|
{/* Login routes — dedicated window, no ScribeShell */}
|
|
<Route element={<LoginShell/>}>
|
|
<Route path="/login" element={<LoginPage/>}/>
|
|
<Route path="/login/login" element={<LoginPage/>}/>
|
|
<Route path="/login/register" element={<RegisterPage/>}/>
|
|
<Route path="/login/reset-password" element={<ResetPasswordPage/>}/>
|
|
<Route path="/login/offline" element={<OfflineLoginPage/>}/>
|
|
</Route>
|
|
|
|
{/* Main app routes — with ScribeShell */}
|
|
<Route element={<MainShell/>}>
|
|
<Route path="/" element={<HomePage/>}/>
|
|
<Route element={<BookShell/>}>
|
|
<Route path="/book/:bookId" element={<BookPage/>}/>
|
|
<Route path="/book/:bookId/chapter/:chapterId" element={<ChapterPage/>}/>
|
|
</Route>
|
|
</Route>
|
|
</Routes>
|
|
</BrowserRouter>
|
|
</AppInitializer>
|
|
</React.StrictMode>
|
|
);
|