Remove unused components and models for improved maintainability

- Deleted redundant components (`AddActionButton`, `AlertBox`, `AlertStack`, `BackButton`, `CancelButton`, and `CollapsableArea`) and related files.
- Removed unused models (`Book`, `BookSerie`, `BookTables`, `Character`, and `Chapter`) to reduce codebase clutter.
- Updated project structure and references to reflect these removals.
This commit is contained in:
natreex
2026-03-22 22:37:31 -04:00
parent e8aaef108b
commit 64ed90d993
229 changed files with 15091 additions and 21289 deletions

View File

@@ -1,38 +1,35 @@
'use client'
import * as tauri from '@/lib/tauri';
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faFeather, faTimes} from "@fortawesome/free-solid-svg-icons";
import {ChangeEvent, forwardRef, useContext, useImperativeHandle, useState} from "react";
import System from "@/lib/models/System";
import {X} from 'lucide-react';
import IconButton from "@/components/ui/IconButton";
import React, {ChangeEvent, forwardRef, useContext, useImperativeHandle, useState} from "react";
import {apiDelete, apiPost} from "@/lib/api/client";
import axios, {AxiosResponse} from "axios";
import {AlertContext} from "@/context/AlertContext";
import {BookContext} from "@/context/BookContext";
import {SessionContext} from "@/context/SessionContext";
import {AlertContext, AlertContextProps} from "@/context/AlertContext";
import {BookContext, BookContextProps} from "@/context/BookContext";
import {SessionContext, SessionContextProps} from "@/context/SessionContext";
import TextInput from "@/components/form/TextInput";
import TexteAreaInput from "@/components/form/TexteAreaInput";
import TextAreaInput from "@/components/form/TextAreaInput";
import InputField from "@/components/form/InputField";
import NumberInput from "@/components/form/NumberInput";
import DatePicker from "@/components/form/DatePicker";
import {configs} from "@/lib/configs";
import {useTranslations} from "next-intl";
import {configs, isDesktop} from "@/lib/configs";
import * as tauri from '@/lib/tauri';
import OfflineContext, {OfflineContextType} from '@/context/OfflineContext';
import {useTranslations} from '@/lib/i18n';
import {LangContext, LangContextProps} from "@/context/LangContext";
import {BookProps} from "@/lib/models/Book";
import OfflineContext, {OfflineContextType} from "@/context/OfflineContext";
import {LocalSyncQueueContext, LocalSyncQueueContextProps} from "@/context/SyncQueueContext";
import {BooksSyncContext, BooksSyncContextProps} from "@/context/BooksSyncContext";
import {SyncedBook} from "@/lib/models/SyncedBook";
import {BookProps} from "@/lib/types/book";
import {SettingRef} from "@/lib/types/settings";
import ImageDropZone from "@/components/form/ImageDropZone";
function BasicInformationSetting(props: any, ref: any) {
function BasicInformationSetting(_props: object, ref: React.ForwardedRef<SettingRef>): React.JSX.Element {
const t = useTranslations();
const {lang} = useContext<LangContextProps>(LangContext)
const {isCurrentlyOffline} = useContext<OfflineContextType>(OfflineContext);
const {addToQueue} = useContext<LocalSyncQueueContextProps>(LocalSyncQueueContext);
const {localSyncedBooks} = useContext<BooksSyncContextProps>(BooksSyncContext);
const {session} = useContext(SessionContext);
const {book, setBook} = useContext(BookContext);
const {lang}: LangContextProps = useContext<LangContextProps>(LangContext);
const {session}: SessionContextProps = useContext<SessionContextProps>(SessionContext);
const {book, setBook}: BookContextProps = useContext<BookContextProps>(BookContext);
const userToken: string = session?.accessToken ? session?.accessToken : '';
const {errorMessage, successMessage} = useContext(AlertContext);
const {errorMessage, successMessage}: AlertContextProps = useContext<AlertContextProps>(AlertContext);
const {isCurrentlyOffline}: OfflineContextType = useContext<OfflineContextType>(OfflineContext);
const bookId: string = book?.bookId ? book?.bookId.toString() : '';
const [currentImage, setCurrentImage] = useState<string>(book?.coverImage ?? '');
@@ -42,21 +39,14 @@ function BasicInformationSetting(props: any, ref: any) {
const [publicationDate, setPublicationDate] = useState<string>(book?.publicationDate ? book?.publicationDate : '');
const [wordCount, setWordCount] = useState<number>(book?.desiredWordCount ? book?.desiredWordCount : 0);
useImperativeHandle(ref, function () {
useImperativeHandle(ref, function (): SettingRef {
return {
handleSave: handleSave
};
});
async function handleCoverImageChange(e: ChangeEvent<HTMLInputElement>): Promise<void> {
const file: File | undefined = e.target.files?.[0];
if (!file) {
errorMessage(t('basicInformationSetting.error.noFileSelected'));
return;
}
const formData = new FormData();
async function handleCoverImageChange(file: File): Promise<void> {
const formData: FormData = new FormData();
formData.append('bookId', bookId);
formData.append('picture', file);
@@ -69,15 +59,15 @@ function BasicInformationSetting(props: any, ref: any) {
},
params: {
lang: lang,
plateforme: 'desktop',
plateforme: 'web',
},
data: formData,
responseType: 'arraybuffer'
});
const contentType: string = query.headers['content-type'] || 'image/jpeg';
const blob = new Blob([query.data], {type: contentType});
const reader = new FileReader();
const blob: Blob = new Blob([query.data], {type: contentType});
const reader: FileReader = new FileReader();
reader.onloadend = function (): void {
if (typeof reader.result === 'string') {
@@ -87,20 +77,17 @@ function BasicInformationSetting(props: any, ref: any) {
reader.readAsDataURL(blob);
} catch (e: unknown) {
if (axios.isAxiosError(e)) {
const serverMessage: string = e.response?.data?.message || e.response?.data || e.message;
throw new Error(serverMessage as string);
} else if (e instanceof Error) {
throw new Error(e.message);
if (e instanceof Error) {
errorMessage(e.message);
} else {
throw new Error('An unexpected error occurred');
errorMessage(t('basicInformationSetting.error.unknown'));
}
}
}
async function handleRemoveCurrentImage(): Promise<void> {
try {
const response: boolean = await System.authDeleteToServer<boolean>(`book/cover/delete`, {
const response: boolean = await apiDelete<boolean>(`book/cover/delete`, {
bookId: bookId
}, userToken, lang);
if (!response) {
@@ -115,7 +102,7 @@ function BasicInformationSetting(props: any, ref: any) {
}
}
}
async function handleSave(): Promise<void> {
if (!title) {
errorMessage(t('basicInformationSetting.error.titleRequired'));
@@ -123,22 +110,24 @@ function BasicInformationSetting(props: any, ref: any) {
}
try {
let response: boolean;
const basicInfoData = {
title: title,
subTitle: subTitle,
summary: summary,
publicationDate: publicationDate,
wordCount: wordCount,
bookId: bookId
};
if (isCurrentlyOffline() || book?.localBook) {
response = await tauri.updateBookBasicInfo(basicInfoData);
if (isDesktop && (isCurrentlyOffline() || book?.localBook)) {
response = await tauri.updateBookBasicInfo({
bookId: bookId,
title: title,
subTitle: subTitle,
summary: summary,
publicationDate: publicationDate,
wordCount: wordCount,
});
} else {
response = await System.authPostToServer<boolean>('book/basic-information', basicInfoData, userToken, lang);
if (localSyncedBooks.find((syncedBook: SyncedBook): boolean => syncedBook.id === bookId)) {
addToQueue('update_book_basic_info', {data: basicInfoData});
}
response = await apiPost<boolean>('book/basic-information', {
title: title,
subTitle: subTitle,
summary: summary,
publicationDate: publicationDate,
wordCount: wordCount,
bookId: bookId
}, userToken, lang);
}
if (!response) {
errorMessage(t('basicInformationSetting.error.update'));
@@ -156,7 +145,7 @@ function BasicInformationSetting(props: any, ref: any) {
publicationDate: publicationDate,
desiredWordCount: wordCount,
};
setBook!!(updatedBook);
setBook(updatedBook);
successMessage(t('basicInformationSetting.success.update'));
} catch (e: unknown) {
if (e instanceof Error) {
@@ -166,85 +155,62 @@ function BasicInformationSetting(props: any, ref: any) {
}
}
}
return (
<div className="space-y-6">
<div className="bg-tertiary/90 backdrop-blur-sm rounded-xl p-5 border border-secondary/50 shadow-md">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<InputField fieldName={t('basicInformationSetting.fields.title')} input={<TextInput
value={title}
setValue={(e: ChangeEvent<HTMLInputElement>) => setTitle(e.target.value)}
placeholder={t('basicInformationSetting.fields.titlePlaceholder')}
/>}/>
<InputField fieldName={t('basicInformationSetting.fields.subtitle')} input={<TextInput
value={subTitle}
setValue={(e: ChangeEvent<HTMLInputElement>) => setSubTitle(e.target.value)}
placeholder={t('basicInformationSetting.fields.subtitlePlaceholder')}
/>}/>
</div>
</div>
<div className="bg-tertiary/90 backdrop-blur-sm rounded-xl p-5 border border-secondary/50 shadow-md">
<InputField fieldName={t('basicInformationSetting.fields.summary')} input={<TexteAreaInput
value={summary}
setValue={(e: ChangeEvent<HTMLTextAreaElement>) => setSummary(e.target.value)}
placeholder={t('basicInformationSetting.fields.summaryPlaceholder')}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<InputField fieldName={t('basicInformationSetting.fields.title')} input={<TextInput
value={title}
setValue={(e: ChangeEvent<HTMLInputElement>): void => setTitle(e.target.value)}
placeholder={t('basicInformationSetting.fields.titlePlaceholder')}
/>}/>
<InputField fieldName={t('basicInformationSetting.fields.subtitle')} input={<TextInput
value={subTitle}
setValue={(e: ChangeEvent<HTMLInputElement>): void => setSubTitle(e.target.value)}
placeholder={t('basicInformationSetting.fields.subtitlePlaceholder')}
/>}/>
</div>
<div className="bg-tertiary/90 backdrop-blur-sm rounded-xl p-5 border border-secondary/50 shadow-md">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<InputField fieldName={t('basicInformationSetting.fields.publicationDate')} input={
<DatePicker
date={publicationDate}
setDate={(e: ChangeEvent<HTMLInputElement>) => setPublicationDate(e.target.value)}
/>
}/>
<InputField fieldName={t('basicInformationSetting.fields.wordCount')} input={
<NumberInput value={wordCount} setValue={setWordCount}
placeholder={t('basicInformationSetting.fields.wordCountPlaceholder')}/>
}/>
</div>
<InputField fieldName={t('basicInformationSetting.fields.summary')} input={<TextAreaInput
value={summary}
setValue={(e: ChangeEvent<HTMLTextAreaElement>): void => setSummary(e.target.value)}
placeholder={t('basicInformationSetting.fields.summaryPlaceholder')}
/>}/>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<InputField fieldName={t('basicInformationSetting.fields.publicationDate')} input={
<DatePicker
date={publicationDate}
setDate={(e: ChangeEvent<HTMLInputElement>): void => setPublicationDate(e.target.value)}
/>
}/>
<InputField fieldName={t('basicInformationSetting.fields.wordCount')} input={
<NumberInput value={wordCount} setValue={setWordCount}
placeholder={t('basicInformationSetting.fields.wordCountPlaceholder')}/>
}/>
</div>
<div className="bg-tertiary/90 backdrop-blur-sm rounded-xl p-5 border border-secondary/50 shadow-md">
<div>
{currentImage ? (
<div className="flex justify-center">
<div className="relative w-40">
<img src={currentImage} alt={t('basicInformationSetting.fields.coverImageAlt')}
className="rounded-lg border border-secondary shadow-md w-full h-auto"/>
<button
type="button"
className="absolute -top-2 -right-2 bg-error/90 hover:bg-error text-white rounded-full w-8 h-8 flex items-center justify-center hover:scale-110 transition-all duration-200 shadow-lg"
onClick={handleRemoveCurrentImage}
>
<FontAwesomeIcon icon={faTimes} className={'w-5 h-5'}/>
</button>
</div>
</div>
) : (
<div className="flex justify-center">
<div className="w-full max-w-lg">
<div
className="p-6 border-2 border-dashed border-secondary/50 rounded-xl bg-secondary/20 hover:border-primary/60 hover:bg-secondary/30 transition-all duration-200 shadow-inner">
<InputField fieldName={t('basicInformationSetting.fields.coverImage')}
actionIcon={faFeather}
actionLabel={t('basicInformationSetting.fields.generateWithQuillSense')}
action={async () => {
}} input={<input
type="file"
id="coverImage"
accept="image/png, image/jpeg"
onChange={handleCoverImageChange}
className="w-full text-text-secondary focus:outline-none file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-primary file:text-background hover:file:bg-primary-dark file:cursor-pointer"
/>}/>
className="rounded-lg border border-secondary w-full h-auto"/>
<div className="absolute -top-2 -right-2">
<IconButton icon={X} variant="danger" size="sm"
onClick={handleRemoveCurrentImage}/>
</div>
</div>
</div>
) : (
<ImageDropZone
onFileSelect={handleCoverImageChange}
label={t('basicInformationSetting.fields.coverImage')}
/>
)}
</div>
</div>
);
}
export default forwardRef(BasicInformationSetting);
export default forwardRef<SettingRef, object>(BasicInformationSetting);