Update book handling and improve offline/online sync logic
- Enhanced the `BookProps` struct with updated field mappings for better API compatibility. - Improved offline/online sync workflows in `BookList` by adding `localBook` property handling and new item count methods for segmented tracking of local/online items. - Updated click handlers in `BookList` to fetch data based on connectivity state and prioritize local data when offline. - Refactored the decryption and vault handling logic in Rust to remove obsolete legacy methods and standardize debug behavior. - Introduced `ScribeShell` layout component with foundational logic for book/chapter syncing and offline queue handling. - Added `init_panic_hook` to improve crash reporting during Rust app initialization.
This commit is contained in:
470
components/layout/ScribeShell.tsx
Normal file
470
components/layout/ScribeShell.tsx
Normal file
@@ -0,0 +1,470 @@
|
||||
import React, {ReactNode, useCallback, useContext, useEffect, useRef, useState} from 'react';
|
||||
import {BookContext} from '@/context/BookContext';
|
||||
import {ChapterProps} from '@/lib/types/chapter';
|
||||
import {ChapterContext} from '@/context/ChapterContext';
|
||||
import {EditorContext} from '@/context/EditorContext';
|
||||
import {Editor, useEditor} from '@tiptap/react';
|
||||
import StarterKit from '@tiptap/starter-kit';
|
||||
import Underline from '@tiptap/extension-underline';
|
||||
import TextAlign from '@tiptap/extension-text-align';
|
||||
import {AlertContext, AlertContextProps, AlertProvider} from '@/context/AlertContext';
|
||||
import {usePathname} from '@/lib/navigation';
|
||||
import {apiPost} from '@/lib/api/client';
|
||||
import {getCookie} from '@/lib/utils/cookies';
|
||||
import {SessionContext, SessionContextProps} from '@/context/SessionContext';
|
||||
import {BookProps} from '@/lib/types/book';
|
||||
import ScribeControllerBar from '@/components/layout/ScribeControllerBar';
|
||||
import ScribeLeftBar from '@/components/leftbar/ScribeLeftBar';
|
||||
import ComposerRightBar from '@/components/rightbar/ComposerRightBar';
|
||||
import ScribeFooterBar from '@/components/layout/ScribeFooterBar';
|
||||
import GuideTour from '@/components/GuideTour';
|
||||
import TermsOfUse from '@/components/TermsOfUse';
|
||||
import {useTranslations, changeLanguage} from '@/lib/i18n';
|
||||
import {isSupportedLocale, LangContext, LangContextProps, SupportedLocale} from '@/context/LangContext';
|
||||
import {ThemeContext} from '@/context/ThemeContext';
|
||||
import useTheme from '@/hooks/useTheme';
|
||||
import {AIUsageContext} from '@/context/AIUsageContext';
|
||||
import {BookSyncCompare, compareBookSyncs, SyncedBook} from '@/lib/types/synced-book';
|
||||
import {SeriesSyncCompare, compareSeriesSyncs, SyncedSeries} from '@/lib/types/synced-series';
|
||||
import {BooksSyncContext} from '@/context/BooksSyncContext';
|
||||
import {SeriesSyncContext} from '@/context/SeriesSyncContext';
|
||||
import {LocalSyncQueueContext, LocalSyncOperation} from '@/context/SyncQueueContext';
|
||||
import useAuthentication from '@/hooks/useAuthentication';
|
||||
import useOnboarding from '@/hooks/useOnboarding';
|
||||
import {SettingBookContext} from '@/context/SettingBookContext';
|
||||
import PulseLoader from '@/components/ui/PulseLoader';
|
||||
import {initCrashReporter} from '@/lib/crashReporter';
|
||||
import OfflineProvider from '@/context/OfflineProvider';
|
||||
import OfflineContext, {OfflineContextType} from '@/context/OfflineContext';
|
||||
import OfflinePinSetup from '@/components/offline/OfflinePinSetup';
|
||||
import OfflinePinVerify from '@/components/offline/OfflinePinVerify';
|
||||
import {isDesktop} from '@/lib/configs';
|
||||
import * as tauri from '@/lib/tauri';
|
||||
import useSyncBooks from '@/hooks/useSyncBooks';
|
||||
import useSyncSeries from '@/hooks/useSyncSeries';
|
||||
|
||||
function AutoSyncOnReconnect() {
|
||||
const {offlineMode}: OfflineContextType = useContext<OfflineContextType>(OfflineContext);
|
||||
const {session}: SessionContextProps = useContext<SessionContextProps>(SessionContext);
|
||||
const {syncAllToServer: syncAllBooksToServer, syncAllFromServer: syncAllBooksFromServer, refreshBooks, booksToSyncToServer, booksToSyncFromServer} = useSyncBooks();
|
||||
const {syncAllToServer: syncAllSeriesToServer, syncAllFromServer: syncAllSeriesFromServer, refreshSeries, seriesToSyncToServer, seriesToSyncFromServer} = useSyncSeries();
|
||||
const isSyncingRef = useRef<boolean>(false);
|
||||
const hasRefreshedRef = useRef<boolean>(false);
|
||||
|
||||
const saveLastOnlineTimestamp = useCallback((): void => {
|
||||
const timestamp: number = Math.floor(Date.now() / 1000);
|
||||
localStorage.setItem('lastOnlineTimestamp', timestamp.toString());
|
||||
}, []);
|
||||
|
||||
// Refresh sync data when online + authenticated + DB ready
|
||||
useEffect((): void => {
|
||||
if (!offlineMode.isOffline && session.isConnected && offlineMode.isDatabaseInitialized) {
|
||||
hasRefreshedRef.current = true;
|
||||
Promise.all([refreshBooks(), refreshSeries()]);
|
||||
}
|
||||
}, [offlineMode.isOffline, session.isConnected, offlineMode.isDatabaseInitialized]);
|
||||
|
||||
// Auto-sync when diffs become available
|
||||
useEffect((): void => {
|
||||
if (offlineMode.isOffline || !session.isConnected || isSyncingRef.current || !hasRefreshedRef.current) return;
|
||||
|
||||
const syncPromises: Promise<void>[] = [];
|
||||
|
||||
if (booksToSyncToServer.length > 0) syncPromises.push(syncAllBooksToServer());
|
||||
if (booksToSyncFromServer.length > 0) syncPromises.push(syncAllBooksFromServer());
|
||||
if (seriesToSyncToServer.length > 0) syncPromises.push(syncAllSeriesToServer());
|
||||
if (seriesToSyncFromServer.length > 0) syncPromises.push(syncAllSeriesFromServer());
|
||||
|
||||
if (syncPromises.length > 0) {
|
||||
isSyncingRef.current = true;
|
||||
Promise.all(syncPromises).then((): void => {
|
||||
saveLastOnlineTimestamp();
|
||||
isSyncingRef.current = false;
|
||||
}).catch((): void => {
|
||||
isSyncingRef.current = false;
|
||||
});
|
||||
}
|
||||
}, [booksToSyncToServer, booksToSyncFromServer, seriesToSyncToServer, seriesToSyncFromServer]);
|
||||
|
||||
// Update lastOnlineTimestamp every 5 minutes while online
|
||||
useEffect((): (() => void) | void => {
|
||||
if (!offlineMode.isOffline && session.isConnected) {
|
||||
const intervalId: ReturnType<typeof setInterval> = setInterval((): void => {
|
||||
saveLastOnlineTimestamp();
|
||||
}, 5 * 60 * 1000);
|
||||
return (): void => clearInterval(intervalId);
|
||||
}
|
||||
}, [offlineMode.isOffline, session.isConnected, saveLastOnlineTimestamp]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function ScribeContent({children}: { children: ReactNode }) {
|
||||
const t = useTranslations();
|
||||
const {lang: locale}: LangContextProps = useContext<LangContextProps>(LangContext);
|
||||
const {errorMessage}: AlertContextProps = useContext<AlertContextProps>(AlertContext);
|
||||
const {isCurrentlyOffline, offlineMode}: OfflineContextType = useContext<OfflineContextType>(OfflineContext);
|
||||
|
||||
const {
|
||||
session, setSession, isLoading,
|
||||
currentCredits, setCurrentCredits,
|
||||
amountSpent, setAmountSpent,
|
||||
showPinSetup, setShowPinSetup,
|
||||
showPinVerify, setShowPinVerify,
|
||||
handlePinVerifySuccess
|
||||
} = useAuthentication();
|
||||
|
||||
const {
|
||||
isTermsAccepted, homeStepsGuide, setHomeStepsGuide,
|
||||
handleTermsAcceptance, handleHomeTour, homeSteps
|
||||
} = useOnboarding({session, setSession});
|
||||
|
||||
const editor: Editor | null = useEditor({
|
||||
extensions: [
|
||||
StarterKit,
|
||||
Underline,
|
||||
TextAlign.configure({
|
||||
types: ['heading', 'paragraph'],
|
||||
}),
|
||||
],
|
||||
injectCSS: false,
|
||||
immediatelyRender: false,
|
||||
shouldRerenderOnTransaction: true,
|
||||
});
|
||||
|
||||
const pathname: string = usePathname();
|
||||
const [currentChapter, setCurrentChapter] = useState<ChapterProps | undefined>(undefined);
|
||||
const [currentBook, setCurrentBook] = useState<BookProps | null>(null);
|
||||
const [bookSettingId, setBookSettingId] = useState<string>('');
|
||||
|
||||
const [serverSyncedBooks, setServerSyncedBooks] = useState<SyncedBook[]>([]);
|
||||
const [localSyncedBooks, setLocalSyncedBooks] = useState<SyncedBook[]>([]);
|
||||
const [bookSyncDiffsFromServer, setBookSyncDiffsFromServer] = useState<BookSyncCompare[]>([]);
|
||||
const [bookSyncDiffsToServer, setBookSyncDiffsToServer] = useState<BookSyncCompare[]>([]);
|
||||
const [serverOnlyBooks, setServerOnlyBooks] = useState<SyncedBook[]>([]);
|
||||
const [localOnlyBooks, setLocalOnlyBooks] = useState<SyncedBook[]>([]);
|
||||
|
||||
const [serverSyncedSeries, setServerSyncedSeries] = useState<SyncedSeries[]>([]);
|
||||
const [localSyncedSeries, setLocalSyncedSeries] = useState<SyncedSeries[]>([]);
|
||||
const [seriesSyncDiffsFromServer, setSeriesSyncDiffsFromServer] = useState<SeriesSyncCompare[]>([]);
|
||||
const [seriesSyncDiffsToServer, setSeriesSyncDiffsToServer] = useState<SeriesSyncCompare[]>([]);
|
||||
const [serverOnlySeries, setServerOnlySeries] = useState<SyncedSeries[]>([]);
|
||||
const [localOnlySeries, setLocalOnlySeries] = useState<SyncedSeries[]>([]);
|
||||
|
||||
const [localSyncQueue, setLocalSyncQueue] = useState<LocalSyncOperation[]>([]);
|
||||
const [isQueueProcessing, setIsQueueProcessing] = useState<boolean>(false);
|
||||
|
||||
function addToLocalSyncQueue(channel: string, data: Record<string, unknown>): void {
|
||||
const operation: LocalSyncOperation = {
|
||||
id: `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`,
|
||||
channel,
|
||||
data,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
setLocalSyncQueue((prev: LocalSyncOperation[]): LocalSyncOperation[] => [...prev, operation]);
|
||||
}
|
||||
|
||||
useEffect((): void => {
|
||||
if (localSyncQueue.length === 0 || isQueueProcessing) return;
|
||||
|
||||
async function processQueue(): Promise<void> {
|
||||
setIsQueueProcessing(true);
|
||||
const queueCopy: LocalSyncOperation[] = [...localSyncQueue];
|
||||
for (const operation of queueCopy) {
|
||||
try {
|
||||
await tauri.invoke(operation.channel, {data: operation.data});
|
||||
setLocalSyncQueue((prev: LocalSyncOperation[]): LocalSyncOperation[] =>
|
||||
prev.filter((op: LocalSyncOperation): boolean => op.id !== operation.id)
|
||||
);
|
||||
} catch (_e) { /* queue retry on next cycle */ }
|
||||
}
|
||||
setIsQueueProcessing(false);
|
||||
}
|
||||
|
||||
processQueue().then();
|
||||
}, [localSyncQueue, isQueueProcessing]);
|
||||
|
||||
useEffect((): void => {
|
||||
if (pathname === '/') {
|
||||
setCurrentBook(null);
|
||||
setCurrentChapter(undefined);
|
||||
}
|
||||
}, [pathname]);
|
||||
|
||||
useEffect((): void => {
|
||||
if (!session.isConnected) return;
|
||||
// On desktop with DB initialized, AutoSyncOnReconnect handles fetching + tombstone exchange
|
||||
if (isDesktop && offlineMode.isDatabaseInitialized) return;
|
||||
getBooks().then();
|
||||
getSeries().then();
|
||||
}, [session, offlineMode.isDatabaseInitialized]);
|
||||
|
||||
useEffect((): void => {
|
||||
const diffsFromServer: BookSyncCompare[] = [];
|
||||
const diffsToServer: BookSyncCompare[] = [];
|
||||
|
||||
serverSyncedBooks.forEach((serverBook: SyncedBook): void => {
|
||||
const localBook: SyncedBook | undefined = localSyncedBooks.find((book: SyncedBook): boolean => book.id === serverBook.id);
|
||||
if (!localBook) return;
|
||||
const diff: BookSyncCompare | null = compareBookSyncs(serverBook, localBook);
|
||||
if (diff) diffsFromServer.push(diff);
|
||||
});
|
||||
|
||||
localSyncedBooks.forEach((localBook: SyncedBook): void => {
|
||||
const serverBook: SyncedBook | undefined = serverSyncedBooks.find((book: SyncedBook): boolean => book.id === localBook.id);
|
||||
if (!serverBook) return;
|
||||
const diff: BookSyncCompare | null = compareBookSyncs(localBook, serverBook);
|
||||
if (diff) diffsToServer.push(diff);
|
||||
});
|
||||
|
||||
setBookSyncDiffsFromServer(diffsFromServer);
|
||||
setBookSyncDiffsToServer(diffsToServer);
|
||||
setServerOnlyBooks(serverSyncedBooks.filter((serverBook: SyncedBook): boolean => !localSyncedBooks.find((localBook: SyncedBook): boolean => localBook.id === serverBook.id)));
|
||||
setLocalOnlyBooks(localSyncedBooks.filter((localBook: SyncedBook): boolean => !serverSyncedBooks.find((serverBook: SyncedBook): boolean => serverBook.id === localBook.id)));
|
||||
}, [localSyncedBooks, serverSyncedBooks]);
|
||||
|
||||
useEffect((): void => {
|
||||
const diffsFromServer: SeriesSyncCompare[] = [];
|
||||
const diffsToServer: SeriesSyncCompare[] = [];
|
||||
|
||||
serverSyncedSeries.forEach((serverSeries: SyncedSeries): void => {
|
||||
const localSeries: SyncedSeries | undefined = localSyncedSeries.find((series: SyncedSeries): boolean => series.id === serverSeries.id);
|
||||
if (!localSeries) return;
|
||||
const diff: SeriesSyncCompare | null = compareSeriesSyncs(serverSeries, localSeries);
|
||||
if (diff) diffsFromServer.push(diff);
|
||||
});
|
||||
|
||||
localSyncedSeries.forEach((localSeries: SyncedSeries): void => {
|
||||
const serverSeries: SyncedSeries | undefined = serverSyncedSeries.find((series: SyncedSeries): boolean => series.id === localSeries.id);
|
||||
if (!serverSeries) return;
|
||||
const diff: SeriesSyncCompare | null = compareSeriesSyncs(localSeries, serverSeries);
|
||||
if (diff) diffsToServer.push(diff);
|
||||
});
|
||||
|
||||
setSeriesSyncDiffsFromServer(diffsFromServer);
|
||||
setSeriesSyncDiffsToServer(diffsToServer);
|
||||
setServerOnlySeries(serverSyncedSeries.filter((s: SyncedSeries): boolean => !localSyncedSeries.find((l: SyncedSeries): boolean => l.id === s.id)));
|
||||
setLocalOnlySeries(localSyncedSeries.filter((l: SyncedSeries): boolean => !serverSyncedSeries.find((s: SyncedSeries): boolean => s.id === l.id)));
|
||||
}, [localSyncedSeries, serverSyncedSeries]);
|
||||
|
||||
async function getBooks(): Promise<void> {
|
||||
try {
|
||||
let localBooksResponse: SyncedBook[] = [];
|
||||
let serverBooksResponse: SyncedBook[] = [];
|
||||
|
||||
if (!isCurrentlyOffline()) {
|
||||
if (isDesktop && offlineMode.isDatabaseInitialized) {
|
||||
localBooksResponse = await tauri.getSyncedBooks() as SyncedBook[];
|
||||
|
||||
const lastOnlineStr: string | null = localStorage.getItem('lastOnlineTimestamp');
|
||||
const lastOnlineTimestamp: number = lastOnlineStr ? parseInt(lastOnlineStr, 10) : 0;
|
||||
|
||||
const localTombstones: tauri.TombstoneRecord[] = await tauri.getTombstonesSince(lastOnlineTimestamp) as tauri.TombstoneRecord[];
|
||||
|
||||
const serverResponse: { books: SyncedBook[]; tombstones: tauri.TombstoneRecord[] } = await apiPost<{
|
||||
books: SyncedBook[]; tombstones: tauri.TombstoneRecord[];
|
||||
}>('books/synced', { lastOnlineTimestamp, tombstones: localTombstones }, session.accessToken, locale);
|
||||
|
||||
serverBooksResponse = serverResponse.books;
|
||||
await tauri.applyBookTombstones(serverResponse.tombstones ?? []);
|
||||
} else {
|
||||
const serverResponse: { books: SyncedBook[] } = await apiPost<{
|
||||
books: SyncedBook[]
|
||||
}>('books/synced', { lastOnlineTimestamp: 0, tombstones: [] }, session.accessToken, locale);
|
||||
if (serverResponse) serverBooksResponse = serverResponse.books;
|
||||
}
|
||||
} else {
|
||||
if (isDesktop && offlineMode.isDatabaseInitialized) {
|
||||
localBooksResponse = await tauri.getSyncedBooks() as SyncedBook[];
|
||||
}
|
||||
}
|
||||
|
||||
setServerSyncedBooks(serverBooksResponse);
|
||||
setLocalSyncedBooks(localBooksResponse);
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error) {
|
||||
errorMessage(e.message);
|
||||
} else {
|
||||
errorMessage(t('homePage.errors.fetchBooksError'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getSeries(): Promise<void> {
|
||||
try {
|
||||
let localSeriesResponse: SyncedSeries[] = [];
|
||||
let serverSeriesResponse: SyncedSeries[] = [];
|
||||
|
||||
if (!isCurrentlyOffline()) {
|
||||
if (isDesktop && offlineMode.isDatabaseInitialized) {
|
||||
localSeriesResponse = await tauri.getSyncedSeries() as SyncedSeries[];
|
||||
|
||||
const lastOnlineStr: string | null = localStorage.getItem('lastOnlineTimestamp');
|
||||
const lastOnlineTimestamp: number = lastOnlineStr ? parseInt(lastOnlineStr, 10) : 0;
|
||||
|
||||
const localTombstones: tauri.TombstoneRecord[] = await tauri.getTombstonesSince(lastOnlineTimestamp) as tauri.TombstoneRecord[];
|
||||
|
||||
const serverResponse: { series: SyncedSeries[]; tombstones: tauri.TombstoneRecord[] } = await apiPost<{
|
||||
series: SyncedSeries[]; tombstones: tauri.TombstoneRecord[];
|
||||
}>('series/synced', { lastOnlineTimestamp, tombstones: localTombstones }, session.accessToken, locale);
|
||||
|
||||
serverSeriesResponse = serverResponse.series;
|
||||
await tauri.applySeriesTombstones(serverResponse.tombstones ?? []);
|
||||
} else {
|
||||
const serverResponse: { series: SyncedSeries[] } = await apiPost<{
|
||||
series: SyncedSeries[]
|
||||
}>('series/synced', { lastOnlineTimestamp: 0, tombstones: [] }, session.accessToken, locale);
|
||||
if (serverResponse) serverSeriesResponse = serverResponse.series;
|
||||
}
|
||||
} else {
|
||||
if (isDesktop && offlineMode.isDatabaseInitialized) {
|
||||
localSeriesResponse = await tauri.getSyncedSeries() as SyncedSeries[];
|
||||
}
|
||||
}
|
||||
|
||||
setServerSyncedSeries(serverSeriesResponse);
|
||||
setLocalSyncedSeries(localSeriesResponse);
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error) {
|
||||
errorMessage(e.message);
|
||||
} else {
|
||||
errorMessage(t('homePage.errors.fetchBooksError'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div
|
||||
className="bg-background text-text-primary h-screen flex flex-col items-center justify-center font-['Lora']">
|
||||
<div className="flex flex-col items-center space-y-6">
|
||||
<div className="animate-pulse">
|
||||
<img src="/logo.png" alt="ERitors Logo" width={400} height={400}/>
|
||||
</div>
|
||||
<PulseLoader text={t('homePage.loading')}/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SessionContext.Provider value={{session: session, setSession: setSession}}>
|
||||
<LocalSyncQueueContext.Provider value={{
|
||||
queue: localSyncQueue,
|
||||
setQueue: setLocalSyncQueue,
|
||||
addToQueue: addToLocalSyncQueue,
|
||||
isProcessing: isQueueProcessing,
|
||||
}}>
|
||||
<BooksSyncContext.Provider value={{
|
||||
serverSyncedBooks,
|
||||
setServerSyncedBooks,
|
||||
localSyncedBooks,
|
||||
setLocalSyncedBooks,
|
||||
booksToSyncFromServer: bookSyncDiffsFromServer,
|
||||
setBooksToSyncFromServer: setBookSyncDiffsFromServer,
|
||||
booksToSyncToServer: bookSyncDiffsToServer,
|
||||
setBooksToSyncToServer: setBookSyncDiffsToServer,
|
||||
setServerOnlyBooks,
|
||||
setLocalOnlyBooks,
|
||||
serverOnlyBooks,
|
||||
localOnlyBooks
|
||||
}}>
|
||||
<SeriesSyncContext.Provider value={{
|
||||
serverSyncedSeries,
|
||||
localSyncedSeries,
|
||||
seriesToSyncFromServer: seriesSyncDiffsFromServer,
|
||||
seriesToSyncToServer: seriesSyncDiffsToServer,
|
||||
setServerSyncedSeries,
|
||||
setLocalSyncedSeries,
|
||||
setServerOnlySeries,
|
||||
setLocalOnlySeries,
|
||||
setSeriesToSyncFromServer: setSeriesSyncDiffsFromServer,
|
||||
setSeriesToSyncToServer: setSeriesSyncDiffsToServer,
|
||||
serverOnlySeries,
|
||||
localOnlySeries
|
||||
}}>
|
||||
{isDesktop && <AutoSyncOnReconnect/>}
|
||||
<BookContext.Provider value={{book: currentBook, setBook: setCurrentBook}}>
|
||||
<ChapterContext.Provider value={{chapter: currentChapter, setChapter: setCurrentChapter}}>
|
||||
<AIUsageContext.Provider value={{
|
||||
totalCredits: currentCredits,
|
||||
setTotalCredits: setCurrentCredits,
|
||||
totalPrice: amountSpent,
|
||||
setTotalPrice: setAmountSpent
|
||||
}}>
|
||||
<SettingBookContext.Provider value={{bookSettingId, setBookSettingId}}>
|
||||
<div
|
||||
className="bg-tertiary text-text-primary h-screen flex flex-col font-['Lora']">
|
||||
<EditorContext.Provider value={{editor: editor}}>
|
||||
<ScribeControllerBar/>
|
||||
<div className="flex-1 flex overflow-hidden">
|
||||
<ScribeLeftBar/>
|
||||
<div
|
||||
className="flex-1 min-w-0 bg-darkest-background rounded-xl m-1 overflow-hidden">
|
||||
{children}
|
||||
</div>
|
||||
<ComposerRightBar/>
|
||||
</div>
|
||||
<ScribeFooterBar/>
|
||||
</EditorContext.Provider>
|
||||
</div>
|
||||
{homeStepsGuide &&
|
||||
<GuideTour stepId={0} steps={homeSteps} onComplete={handleHomeTour}
|
||||
onClose={(): void => setHomeStepsGuide(false)}/>
|
||||
}
|
||||
{!isCurrentlyOffline() && !isTermsAccepted && <TermsOfUse onAccept={handleTermsAcceptance}/>}
|
||||
{isDesktop && showPinSetup && (
|
||||
<OfflinePinSetup
|
||||
showOnFirstLogin={true}
|
||||
onClose={(): void => setShowPinSetup(false)}
|
||||
onSuccess={(): void => setShowPinSetup(false)}
|
||||
/>
|
||||
)}
|
||||
{isDesktop && showPinVerify && (
|
||||
<OfflinePinVerify
|
||||
onSuccess={handlePinVerifySuccess}
|
||||
onCancel={(): void => {}}
|
||||
/>
|
||||
)}
|
||||
</SettingBookContext.Provider>
|
||||
</AIUsageContext.Provider>
|
||||
</ChapterContext.Provider>
|
||||
</BookContext.Provider>
|
||||
</SeriesSyncContext.Provider>
|
||||
</BooksSyncContext.Provider>
|
||||
</LocalSyncQueueContext.Provider>
|
||||
</SessionContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ScribeShell({children}: { children: ReactNode }) {
|
||||
const [locale, setLocale] = useState<SupportedLocale>('fr');
|
||||
const {theme, setTheme} = useTheme();
|
||||
|
||||
useEffect((): void => {
|
||||
initCrashReporter();
|
||||
}, []);
|
||||
|
||||
useEffect((): void => {
|
||||
const lang: string | null = getCookie('lang');
|
||||
if (lang && isSupportedLocale(lang)) {
|
||||
setLocale(lang);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect((): void => {
|
||||
changeLanguage(locale);
|
||||
}, [locale]);
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{theme, setTheme}}>
|
||||
<LangContext.Provider value={{lang: locale, setLang: setLocale}}>
|
||||
<OfflineProvider>
|
||||
<AlertProvider>
|
||||
<ScribeContent>{children}</ScribeContent>
|
||||
</AlertProvider>
|
||||
</OfflineProvider>
|
||||
</LangContext.Provider>
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user