Refactor ScribeChapterComponent and offline handlers for seamless local and server operations

- Add stricter typings (`RefObject`) and improve type safety.
- Introduce conditional logic for `localBook` to
This commit is contained in:
natreex
2025-12-19 10:39:59 -05:00
parent f5e66f8983
commit 43c7ef375c
4 changed files with 81 additions and 42 deletions

View File

@@ -205,13 +205,11 @@ function ScribeContent() {
let serverBooksResponse: SyncedBook[] = [];
if (!isCurrentlyOffline()){
// Mode online: récupérer les livres du serveur ET de la DB locale
if (offlineMode.isDatabaseInitialized) {
localBooksResponse = await window.electron.invoke<SyncedBook[]>('db:books:synced');
}
serverBooksResponse = await System.authGetQueryToServer<SyncedBook[]>('books/synced', session.accessToken, locale);
} else {
// Mode offline: récupérer uniquement depuis la DB locale
if (offlineMode.isDatabaseInitialized) {
localBooksResponse = await window.electron.invoke<SyncedBook[]>('db:books:synced');
}
@@ -364,13 +362,6 @@ function ScribeContent() {
console.error('[Page] Error initializing user:', error);
}
}
setSession({
isConnected: true,
user: user,
accessToken: token,
});
setCurrentCredits(user.creditsBalance)
setAmountSpent(user.aiUsage)
if (window.electron && user.id) {
try {
const dbInitialized:boolean = await initializeDatabase(user.id);
@@ -392,6 +383,13 @@ function ScribeContent() {
console.error('Failed to initialize database:', error);
}
}
setSession({
isConnected: true,
user: user,
accessToken: token,
});
setCurrentCredits(user.creditsBalance)
setAmountSpent(user.aiUsage)
} catch (e: unknown) {
if (window.electron) {
try {

View File

@@ -86,10 +86,9 @@ export default function BookList() {
setBookGuide(true);
}
}, [groupedBooks]);
// Charger les livres quand les conditions sont remplies
useEffect((): void => {
const shouldFetchBooks =
const shouldFetchBooks:boolean|"" =
(session.isConnected || accessToken) &&
(!isCurrentlyOffline() || offlineMode.isDatabaseInitialized);
@@ -119,7 +118,6 @@ export default function BookList() {
setBookGuide(false);
}
} else {
// Mode offline: stocker dans localStorage
const completedGuides = JSON.parse(localStorage.getItem('completedGuides') || '[]');
if (!completedGuides.includes('new-first-book')) {
completedGuides.push('new-first-book');
@@ -229,6 +227,7 @@ export default function BookList() {
async function getBook(bookId: string): Promise<void> {
try {
let localBookOnly: boolean = false;
let bookResponse: BookListProps|null = null;
if (isCurrentlyOffline()){
if (!offlineMode.isDatabaseInitialized) {
@@ -236,10 +235,18 @@ export default function BookList() {
return;
}
bookResponse = await window.electron.invoke('db:book:bookBasicInformation', bookId)
if (bookResponse) {
localBookOnly = true;
}
} else {
bookResponse = await System.authGetQueryToServer<BookListProps>(`book/basic-information`, accessToken, lang, {
id: bookId
});
const isOfflineBook: SyncedBook | undefined = localOnlyBooks.find((book: SyncedBook):boolean => book.id === bookId);
if (isOfflineBook) {
bookResponse = await window.electron.invoke('db:book:bookBasicInformation', bookId)
localBookOnly = true;
}
if (!bookResponse) {
bookResponse = await System.authGetQueryToServer<BookListProps>(`book/basic-information`, accessToken, lang, {id: bookId});
}
}
if (!bookResponse) {
errorMessage(t("bookList.errorBookDetails"));
@@ -256,6 +263,7 @@ export default function BookList() {
publicationDate: bookResponse?.desiredReleaseDate || '',
desiredWordCount: bookResponse?.desiredWordCount || 0,
totalWordCount: 0,
localBook: localBookOnly,
coverImage: bookResponse?.coverImage ? 'data:image/jpeg;base64,' + bookResponse.coverImage : '',
});
}

View File

@@ -1,5 +1,5 @@
import {ChapterListProps, ChapterProps} from "@/lib/models/Chapter";
import {useContext, useEffect, useRef, useState} from "react";
import {RefObject, useContext, useEffect, useRef, useState} from "react";
import System from "@/lib/models/System";
import {BookContext} from "@/context/BookContext";
import {AlertContext} from "@/context/AlertContext";
@@ -32,8 +32,8 @@ export default function ScribeChapterComponent() {
const [deleteConfirmationMessage, setDeleteConfirmationMessage] = useState<boolean>(false);
const [removeChapterId, setRemoveChapterId] = useState<string>('');
const chapterRefs = useRef<Map<string, HTMLDivElement>>(new Map());
const scrollContainerRef = useRef<HTMLUListElement>(null);
const chapterRefs: RefObject<Map<string, HTMLDivElement>> = useRef<Map<string, HTMLDivElement>>(new Map());
const scrollContainerRef: RefObject<HTMLUListElement | null> = useRef<HTMLUListElement>(null);
useEffect((): void => {
if (book)
@@ -46,9 +46,9 @@ export default function ScribeChapterComponent() {
useEffect((): void => {
if (chapter?.chapterId && scrollContainerRef.current) {
setTimeout(() => {
const element = chapterRefs.current.get(chapter.chapterId);
const container = scrollContainerRef.current;
setTimeout(():void => {
const element: HTMLDivElement | undefined = chapterRefs.current.get(chapter.chapterId);
const container: HTMLUListElement | null = scrollContainerRef.current;
if (element && container) {
const containerRect:DOMRect = container.getBoundingClientRect();
@@ -77,7 +77,11 @@ export default function ScribeChapterComponent() {
if (isCurrentlyOffline()){
response = await window.electron.invoke<ChapterListProps[]>('db:book:chapters', book?.bookId)
} else {
response = await System.authGetQueryToServer<ChapterListProps[]>(`book/chapters?id=${book?.bookId}`, userToken, lang);
if (book?.localBook){
response = await window.electron.invoke<ChapterListProps[]>('db:book:chapters', book?.bookId)
} else {
response = await System.authGetQueryToServer<ChapterListProps[]>(`book/chapters?id=${book?.bookId}`, userToken, lang);
}
}
if (response) {
setChapters(response);
@@ -94,7 +98,7 @@ export default function ScribeChapterComponent() {
async function getChapter(chapterId: string): Promise<void> {
const version: number = chapter?.chapterContent.version ? chapter?.chapterContent.version : 2;
try {
let response: ChapterProps | null = null
let response: ChapterProps | null
if (isCurrentlyOffline()) {
response = await window.electron.invoke<ChapterProps>('db:chapter:whole', {
bookid: book?.bookId,
@@ -102,11 +106,19 @@ export default function ScribeChapterComponent() {
version: version,
})
} else {
response = await System.authGetQueryToServer<ChapterProps>(`chapter/whole`, userToken, lang, {
bookid: book?.bookId,
id: chapterId,
version: version,
});
if (book?.localBook){
response = await window.electron.invoke<ChapterProps>('db:chapter:whole', {
bookid: book?.bookId,
id: chapterId,
version: version,
})
} else {
response = await System.authGetQueryToServer<ChapterProps>(`chapter/whole`, userToken, lang, {
bookid: book?.bookId,
id: chapterId,
version: version,
});
}
}
if (!response) {
errorMessage(t("scribeChapterComponent.errorFetchChapter"));
@@ -132,11 +144,19 @@ export default function ScribeChapterComponent() {
title: title,
})
} else {
response = await System.authPostToServer<boolean>('chapter/update', {
chapterId: chapterId,
chapterOrder: chapterOrder,
title: title,
}, userToken, lang);
if (book?.localBook){
response = await window.electron.invoke<boolean>('db:chapter:update',{
chapterId: chapterId,
chapterOrder: chapterOrder,
title: title,
})
} else {
response = await System.authPostToServer<boolean>('chapter/update', {
chapterId: chapterId,
chapterOrder: chapterOrder,
title: title,
}, userToken, lang);
}
}
if (!response) {
errorMessage(t("scribeChapterComponent.errorChapterUpdate"));
@@ -173,9 +193,13 @@ export default function ScribeChapterComponent() {
if (isCurrentlyOffline()) {
response = await window.electron.invoke<boolean>('db:chapter:remove', removeChapterId)
} else {
response = await System.authDeleteToServer<boolean>('chapter/remove', {
chapterId: removeChapterId,
}, userToken, lang);
if (book?.localBook){
response = await window.electron.invoke<boolean>('db:chapter:remove', removeChapterId)
} else {
response = await System.authDeleteToServer<boolean>('chapter/remove', {
chapterId: removeChapterId,
}, userToken, lang);
}
}
if (!response) {
errorMessage(t("scribeChapterComponent.errorChapterDelete"));
@@ -209,11 +233,19 @@ export default function ScribeChapterComponent() {
title: chapterTitle
})
} else {
chapterId = await System.authPostToServer<string>('chapter/add', {
bookId: book?.bookId,
chapterOrder: chapterOrder,
title: chapterTitle
}, userToken, lang);
if (book?.localBook){
chapterId = await window.electron.invoke<string>('db:chapter:add', {
bookId: book?.bookId,
chapterOrder: chapterOrder,
title: chapterTitle
})
} else {
chapterId = await System.authPostToServer<string>('chapter/add', {
bookId: book?.bookId,
chapterOrder: chapterOrder,
title: chapterTitle
}, userToken, lang);
}
}
if (!chapterId) {
errorMessage(t("scribeChapterComponent.errorChapterSubmit", {chapterName: newChapterName}));

View File

@@ -69,6 +69,7 @@ export interface BookProps {
desiredWordCount?: number;
totalWordCount?: number;
coverImage?: string;
localBook?: boolean;
chapters?: ChapterProps[];
}