Integrate offline support and improve error handling across app
- Add `OfflineContext` to manage offline state and interactions within components. - Refactor session logic in `ScribeControllerBar` and `page.tsx` to handle offline scenarios (e.g., check connectivity before enabling GPT features). - Enhance offline PIN setup and verification with better flow and error messaging. - Optimize database IPC handlers to initialize and sync data in offline mode. - Refactor code to clean up redundant logs and ensure stricter typings. - Improve consistency and structure in handling online and offline operations for smoother user experience.
This commit is contained in:
94
app/page.tsx
94
app/page.tsx
@@ -14,7 +14,6 @@ import {SessionContext} from '@/context/SessionContext';
|
||||
import {SessionProps} from "@/lib/models/Session";
|
||||
import User, {UserProps} from "@/lib/models/User";
|
||||
import {BookProps} from "@/lib/models/Book";
|
||||
// Removed Next.js router imports for Electron
|
||||
import ScribeTopBar from "@/components/ScribeTopBar";
|
||||
import ScribeControllerBar from "@/components/ScribeControllerBar";
|
||||
import ScribeLeftBar from "@/components/leftbar/ScribeLeftBar";
|
||||
@@ -27,7 +26,6 @@ import {faBookMedical, faFeather} from "@fortawesome/free-solid-svg-icons";
|
||||
import TermsOfUse from "@/components/TermsOfUse";
|
||||
import frMessages from '@/lib/locales/fr.json';
|
||||
import enMessages from '@/lib/locales/en.json';
|
||||
// Removed Next.js Image import for Electron
|
||||
import {NextIntlClientProvider, useTranslations} from "next-intl";
|
||||
import {LangContext} from "@/context/LangContext";
|
||||
import {AIUsageContext} from "@/context/AIUsageContext";
|
||||
@@ -45,7 +43,7 @@ function ScribeContent() {
|
||||
const t = useTranslations();
|
||||
const {lang: locale} = useContext(LangContext);
|
||||
const {errorMessage} = useContext(AlertContext);
|
||||
const {initializeDatabase} = useContext(OfflineContext);
|
||||
const {initializeDatabase, setOfflineMode, isCurrentlyOffline} = useContext(OfflineContext);
|
||||
const editor: Editor | null = useEditor({
|
||||
extensions: [
|
||||
StarterKit,
|
||||
@@ -57,8 +55,7 @@ function ScribeContent() {
|
||||
injectCSS: false,
|
||||
immediatelyRender: false,
|
||||
});
|
||||
|
||||
// Router removed for Electron - using window.location instead
|
||||
|
||||
const [session, setSession] = useState<SessionProps>({user: null, accessToken: '', isConnected: false});
|
||||
const [currentChapter, setCurrentChapter] = useState<ChapterProps | undefined>(undefined);
|
||||
const [currentBook, setCurrentBook] = useState<BookProps | null>(null);
|
||||
@@ -68,8 +65,6 @@ function ScribeContent() {
|
||||
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
|
||||
const [sessionAttempts, setSessionAttempts] = useState<number>(0)
|
||||
|
||||
const [isTermsAccepted, setIsTermsAccepted] = useState<boolean>(false);
|
||||
const [homeStepsGuide, setHomeStepsGuide] = useState<boolean>(false);
|
||||
const [showPinSetup, setShowPinSetup] = useState<boolean>(false);
|
||||
@@ -151,12 +146,7 @@ function ScribeContent() {
|
||||
setIsTermsAccepted(session.user?.termsAccepted ?? false);
|
||||
setHomeStepsGuide(User.guideTourDone(session.user?.guideTour ?? [], 'home-basic'));
|
||||
setIsLoading(false);
|
||||
} else {
|
||||
if (sessionAttempts > 2) {
|
||||
// Redirect handled by checkAuthentification
|
||||
}
|
||||
}
|
||||
setSessionAttempts(sessionAttempts + 1);
|
||||
}, [session]);
|
||||
|
||||
useEffect((): void => {
|
||||
@@ -166,54 +156,50 @@ function ScribeContent() {
|
||||
}, [currentBook]);
|
||||
|
||||
// Check for PIN setup after successful connection
|
||||
useEffect(() => {
|
||||
useEffect(():void => {
|
||||
async function checkPinSetup() {
|
||||
if (session.isConnected && window.electron) {
|
||||
try {
|
||||
const offlineStatus = await window.electron.offlineModeGet();
|
||||
console.log('[Page] Session connected, offline status:', offlineStatus);
|
||||
|
||||
if (!offlineStatus.hasPin) {
|
||||
console.log('[Page] No PIN configured, will show setup dialog');
|
||||
// Show PIN setup dialog after a short delay
|
||||
setTimeout(() => {
|
||||
setTimeout(():void => {
|
||||
console.log('[Page] Showing PIN setup dialog');
|
||||
setShowPinSetup(true);
|
||||
}, 2000); // 2 seconds delay after page load
|
||||
}, 2000);
|
||||
}
|
||||
} catch (e:unknown) {
|
||||
if (e instanceof Error) {
|
||||
errorMessage(e.message);
|
||||
} else {
|
||||
errorMessage('Unknown error occurred while checking offline mode')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Page] Error checking offline mode:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkPinSetup();
|
||||
}, [session.isConnected]); // Run when session connection status changes
|
||||
checkPinSetup().then();
|
||||
}, [session.isConnected]);
|
||||
|
||||
async function handlePinVerifySuccess(userId: string): Promise<void> {
|
||||
console.log('[OfflinePin] PIN verified successfully for user:', userId);
|
||||
|
||||
try {
|
||||
// Initialize database with user's encryption key
|
||||
if (window.electron) {
|
||||
const encryptionKey:string|null = await window.electron.getUserEncryptionKey(userId);
|
||||
if (encryptionKey) {
|
||||
await window.electron.dbInitialize(userId, encryptionKey);
|
||||
|
||||
// Load user from local DB
|
||||
const localUser = await window.electron.invoke('db:user:info');
|
||||
if (localUser && localUser.success) {
|
||||
// Use local data and continue in offline mode
|
||||
|
||||
const localUser:UserProps = await window.electron.invoke('db:user:info');
|
||||
if (localUser && localUser.id) {
|
||||
setSession({
|
||||
isConnected: true,
|
||||
user: localUser.data,
|
||||
user: localUser,
|
||||
accessToken: 'offline', // Special offline token
|
||||
});
|
||||
setShowPinVerify(false);
|
||||
setCurrentCredits(localUser.data.creditsBalance || 0);
|
||||
setAmountSpent(localUser.data.aiUsage || 0);
|
||||
|
||||
console.log('[OfflinePin] Running in offline mode');
|
||||
setCurrentCredits(localUser.creditsBalance || 0);
|
||||
setAmountSpent(localUser.aiUsage || 0);
|
||||
} else {
|
||||
errorMessage(t("homePage.errors.localDataError"));
|
||||
if (window.electron) {
|
||||
@@ -282,29 +268,18 @@ function ScribeContent() {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('user: ' , user);
|
||||
|
||||
// Initialize user in Electron (sets userId and creates/gets encryption key)
|
||||
if (window.electron && user.id) {
|
||||
try {
|
||||
const initResult = await window.electron.initUser(user.id);
|
||||
if (!initResult.success) {
|
||||
console.error('[Page] Failed to initialize user:', initResult.error);
|
||||
} else {
|
||||
console.log('[Page] User initialized successfully, key created:', initResult.keyCreated);
|
||||
|
||||
// Check if PIN is configured for offline mode
|
||||
try {
|
||||
const offlineStatus = await window.electron.offlineModeGet();
|
||||
console.log('[Page] Offline status:', offlineStatus);
|
||||
if (!offlineStatus.hasPin) {
|
||||
// First login or no PIN configured yet
|
||||
// Show PIN setup dialog after a short delay
|
||||
console.log('[Page] No PIN configured, will show setup dialog');
|
||||
setTimeout(() => {
|
||||
console.log('[Page] Showing PIN setup dialog');
|
||||
setTimeout(():void => {
|
||||
setShowPinSetup(true);
|
||||
}, 2000); // 2 seconds delay after successful login
|
||||
}, 2000);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Page] Error checking offline mode:', error);
|
||||
@@ -321,15 +296,10 @@ function ScribeContent() {
|
||||
});
|
||||
setCurrentCredits(user.creditsBalance)
|
||||
setAmountSpent(user.aiUsage)
|
||||
|
||||
// Initialiser la DB locale en Electron
|
||||
if (window.electron && user.id) {
|
||||
try {
|
||||
const dbInitialized = await initializeDatabase(user.id);
|
||||
const dbInitialized:boolean = await initializeDatabase(user.id);
|
||||
if (dbInitialized) {
|
||||
console.log('Database initialized successfully');
|
||||
|
||||
// Sync user to local DB (only if not exists)
|
||||
try {
|
||||
await window.electron.invoke('db:user:sync', {
|
||||
userId: user.id,
|
||||
@@ -341,7 +311,6 @@ function ScribeContent() {
|
||||
console.log('User synced to local DB');
|
||||
} catch (syncError) {
|
||||
console.error('Failed to sync user to local DB:', syncError);
|
||||
// Non-blocking error, continue anyway
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -349,13 +318,12 @@ function ScribeContent() {
|
||||
}
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
console.log('[Auth] Server error, checking offline mode...');
|
||||
if (window.electron) {
|
||||
try {
|
||||
const offlineStatus = await window.electron.offlineModeGet();
|
||||
|
||||
if (offlineStatus.hasPin && offlineStatus.lastUserId) {
|
||||
console.log('[Auth] Server unreachable but PIN configured, showing PIN verification');
|
||||
setOfflineMode(prev => ({...prev, isOffline: true, isNetworkOnline: false}));
|
||||
setShowPinVerify(true);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
@@ -369,8 +337,7 @@ function ScribeContent() {
|
||||
console.error('[Auth] Error checking offline mode:', offlineError);
|
||||
}
|
||||
}
|
||||
|
||||
// If not in offline mode or no PIN configured, show error and logout
|
||||
|
||||
if (e instanceof Error) {
|
||||
errorMessage(e.message);
|
||||
} else {
|
||||
@@ -383,7 +350,7 @@ function ScribeContent() {
|
||||
const offlineStatus = await window.electron.offlineModeGet();
|
||||
|
||||
if (offlineStatus.hasPin && offlineStatus.lastUserId) {
|
||||
console.log('[Auth] No token but PIN configured, showing PIN verification for offline mode');
|
||||
setOfflineMode(prev => ({...prev, isOffline: true, isNetworkOnline: false}));
|
||||
setShowPinVerify(true);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
@@ -428,7 +395,12 @@ function ScribeContent() {
|
||||
async function getLastChapter(): Promise<void> {
|
||||
if (session?.accessToken) {
|
||||
try {
|
||||
const response: ChapterProps | null = await System.authGetQueryToServer<ChapterProps | null>(`chapter/last-chapter`, session.accessToken, locale, {bookid: currentBook?.bookId});
|
||||
let response: ChapterProps | null
|
||||
if (isCurrentlyOffline()){
|
||||
response = await window.electron.invoke('db:chapter:last', currentBook?.bookId)
|
||||
} else {
|
||||
response = await System.authGetQueryToServer<ChapterProps | null>(`chapter/last-chapter`, session.accessToken, locale, {bookid: currentBook?.bookId});
|
||||
}
|
||||
if (response) {
|
||||
setCurrentChapter(response)
|
||||
} else {
|
||||
@@ -489,12 +461,12 @@ function ScribeContent() {
|
||||
</EditorContext.Provider>
|
||||
</div>
|
||||
{
|
||||
homeStepsGuide &&
|
||||
homeStepsGuide && !isCurrentlyOffline() &&
|
||||
<GuideTour stepId={0} steps={homeSteps} onComplete={handleHomeTour}
|
||||
onClose={(): void => setHomeStepsGuide(false)}/>
|
||||
}
|
||||
{
|
||||
!isTermsAccepted && <TermsOfUse onAccept={handleTermsAcceptance}/>
|
||||
!isTermsAccepted && !isCurrentlyOffline() && <TermsOfUse onAccept={handleTermsAcceptance}/>
|
||||
}
|
||||
{
|
||||
showPinSetup && window.electron && (
|
||||
|
||||
Reference in New Issue
Block a user