Introduce local fallback for book creation and improve error handling

- Added support for creating books locally when the cloud limit is reached.
- Enhanced error handling in `AddNewBookForm` with `ApiError` and fallback logic for local book creation.
- Implemented `BookTypeLimit` to manage type-specific book limits with visual indicators in `BookList`.
- Refactored `TombstoneRecord` to standardize naming conventions for better API compatibility.
- Updated `useSyncSeries` and `useSyncBooks` to handle empty tombstones gracefully.
- Updated locales with new translations for fallback and error messaging.
This commit is contained in:
natreex
2026-03-31 09:18:11 -04:00
parent acacd95f38
commit b9bc024e91
10 changed files with 191 additions and 47 deletions

View File

@@ -13,10 +13,20 @@ interface ApiRequestConfig {
contentType?: ContentType;
}
export class ApiError extends Error {
statusCode: number;
constructor(message: string, statusCode: number) {
super(message);
this.statusCode = statusCode;
this.name = 'ApiError';
}
}
function handleApiError(error: unknown): never {
if (axios.isAxiosError(error)) {
const serverMessage: string = error.response?.data?.message || error.response?.data || error.message;
throw new Error(serverMessage);
const statusCode: number = error.response?.status ?? 500;
throw new ApiError(serverMessage, statusCode);
} else if (error instanceof Error) {
throw new Error(error.message);
}

View File

@@ -718,7 +718,10 @@
"titleTooShort": "Title is too short. Minimum 2 characters required.",
"titleTooLong": "Title is too long. Maximum 50 characters allowed.",
"typeMissing": "Select a genre.",
"addingBook": "An error occurred while adding the book."
"addingBook": "An error occurred while adding the book.",
"limitReached": "You have reached the book limit for this type on the cloud.",
"saveLocally": "Save locally",
"localFallbackDescription": "You can still save this book locally on your device."
},
"bookTypeHint": {
"title": "Type of work",

View File

@@ -717,7 +717,10 @@
"titleTooShort": "Le titre est trop court. Minimum 2 caractères requis",
"titleTooLong": "Le titre est trop long. Maximum 50 caractères autorisés",
"typeMissing": "Sélectionner un genre.",
"addingBook": "Une erreur est survenue lors de l'ajout du livre."
"addingBook": "Une erreur est survenue lors de l'ajout du livre.",
"limitReached": "Vous avez atteint la limite de livres pour ce type sur le cloud.",
"saveLocally": "Sauvegarder localement",
"localFallbackDescription": "Vous pouvez tout de même enregistrer ce livre localement sur votre appareil."
},
"bookTypeHint": {
"title": "Type d'oeuvre",

View File

@@ -44,10 +44,10 @@ export interface SyncCheckResult {
}
export interface TombstoneRecord {
tableName: string;
entityId: string;
bookId: string | null;
deletedAt: number;
table_name: string;
entity_id: string;
book_id: string | null;
deleted_at: number;
}
// ─── User & Auth ───────────────────────────────────────────

View File

@@ -106,3 +106,8 @@ export interface BookTags {
objects: Tag[];
worldElements: Tag[];
}
export interface BookTypeLimit {
current: number;
max: number;
}