Add terms of use translations, sync detection, and refactor book components

- Introduced new translations for terms of use in French and English locales.
- Added sync status detection logic for books in `BookList` and `BookCard` components.
- Refactored `BookCard` to handle additional props and improve layout flexibility.
- Enhanced `TermsOfUse` component with complete localization support and refuse functionality.
- Updated data decryption logic in Rust services to handle optional fields and additional metadata consistently.
- Improved offline/online synchronization workflows with extended context properties.
This commit is contained in:
natreex
2026-03-23 11:56:35 -04:00
parent 64ed90d993
commit a114592ac9
23 changed files with 588 additions and 438 deletions

View File

@@ -17,7 +17,8 @@ import GuideTour, {GuideStep} from "@/components/GuideTour";
import {guideTourDone, setNewGuideTour} from "@/lib/utils/user";
import {useTranslations} from '@/lib/i18n';
import {LangContext, LangContextProps} from "@/context/LangContext";
import {BooksSyncContext, BooksSyncContextProps} from "@/context/BooksSyncContext";
import {BooksSyncContext, BooksSyncContextProps, SyncType} from "@/context/BooksSyncContext";
import {BookSyncCompare, SyncedBook} from "@/lib/types/synced-book";
import {SeriesListItemProps} from "@/lib/types/series";
import SeriesCard, {SeriesCardProps} from "@/components/series/SeriesCard";
import SeriesSetting from "@/components/series/SeriesSetting";
@@ -35,8 +36,11 @@ export default function BookList() {
const router = useRouter();
const t = useTranslations();
const {lang}: LangContextProps = useContext<LangContextProps>(LangContext)
const {serverSyncedBooks}: BooksSyncContextProps = useContext<BooksSyncContextProps>(BooksSyncContext)
const {isCurrentlyOffline}: OfflineContextType = useContext<OfflineContextType>(OfflineContext);
const {
serverSyncedBooks, booksToSyncFromServer, booksToSyncToServer,
serverOnlyBooks, localOnlyBooks
}: BooksSyncContextProps = useContext<BooksSyncContextProps>(BooksSyncContext);
const {isCurrentlyOffline, offlineMode}: OfflineContextType = useContext<OfflineContextType>(OfflineContext);
const [searchQuery, setSearchQuery] = useState<string>('');
const [groupedItems, setGroupedItems] = useState<Record<string, CategoryItem[]>>({});
@@ -95,27 +99,38 @@ export default function BookList() {
]
useEffect((): void => {
if (groupedItems && Object.keys(groupedItems).length > 0 && guideTourDone(session.user?.guideTour || [], 'new-first-book')) {
setBookGuide(true);
if (groupedItems && Object.keys(groupedItems).length > 0) {
const notDone: boolean = isCurrentlyOffline()
? localStorage.getItem('guide-tour-new-first-book') !== 'true'
: guideTourDone(session.user?.guideTour || [], 'new-first-book');
if (notDone) setBookGuide(true);
}
}, [groupedItems]);
useEffect((): void => {
loadBooksAndSeries().then()
}, [serverSyncedBooks]);
const canLoad: boolean = !isDesktop ||
(!isCurrentlyOffline() || offlineMode.isDatabaseInitialized);
if (canLoad) loadBooksAndSeries().then();
}, [serverSyncedBooks, offlineMode.isDatabaseInitialized, booksToSyncFromServer, booksToSyncToServer, serverOnlyBooks, localOnlyBooks]);
useEffect((): void => {
if (accessToken) loadBooksAndSeries().then();
}, [accessToken]);
async function handleFirstBookGuide(): Promise<void> {
if (isCurrentlyOffline()) {
localStorage.setItem('guide-tour-new-first-book', 'true');
setBookGuide(false);
return;
}
try {
const response: boolean = await apiPost<boolean>(
'logs/tour',
{plateforme: 'web', tour: 'new-first-book'},
{plateforme: 'desktop', tour: 'new-first-book'},
session.accessToken, lang
);
if (response) {
localStorage.setItem('guide-tour-new-first-book', 'true');
setSession(setNewGuideTour(session, 'new-first-book'));
setBookGuide(false);
}
@@ -291,6 +306,23 @@ export default function BookList() {
}, 0);
}
function detectBookSyncStatus(bookId: string): SyncType {
if (!isDesktop || isCurrentlyOffline()) return 'synced';
if (serverOnlyBooks.find((book: SyncedBook): boolean => book.id === bookId)) {
return 'server-only';
}
if (localOnlyBooks.find((book: SyncedBook): boolean => book.id === bookId)) {
return 'local-only';
}
if (booksToSyncFromServer.find((book: BookSyncCompare): boolean => book.id === bookId)) {
return 'to-sync-from-server';
}
if (booksToSyncToServer.find((book: BookSyncCompare): boolean => book.id === bookId)) {
return 'to-sync-to-server';
}
return 'synced';
}
function handleBookClick(bookId: string): void {
router.push(`/book/${bookId}`);
}
@@ -388,11 +420,12 @@ export default function BookList() {
return (
<div key={item.book.bookId}
{...(idx === 0 && {'data-guide': 'book-card'})}
className={`flex-shrink-0 w-64 sm:w-52 md:w-48 lg:w-56 xl:w-64 p-2 box-border ${guideTourDone(session.user?.guideTour || [], 'new-first-book') && 'mb-[200px]'}`}>
className={`flex-shrink-0 w-64 sm:w-52 md:w-48 lg:w-56 xl:w-64 p-2 box-border ${bookGuide && 'mb-[200px]'}`}>
<BookCard
book={item.book}
onClickCallback={handleBookClick}
index={idx}
syncStatus={detectBookSyncStatus(item.book.bookId)}
/>
</div>
);