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:
@@ -1,23 +1,18 @@
|
||||
'use client';
|
||||
import React, {ChangeEvent, Dispatch, SetStateAction, useContext, useEffect, useRef, useState} from "react";
|
||||
import {AlertContext} from "@/context/AlertContext";
|
||||
import System from "@/lib/models/System";
|
||||
import {SessionContext} from "@/context/SessionContext";
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import {faBook, faCheck, faLayerGroup, faPencilAlt, faX} from "@fortawesome/free-solid-svg-icons";
|
||||
import React, {ChangeEvent, Dispatch, SetStateAction, useContext, useState} from "react";
|
||||
import {AlertContext, AlertContextProps} from "@/context/AlertContext";
|
||||
import {apiPost} from '@/lib/api/client';
|
||||
import {SessionContext, SessionContextProps} from "@/context/SessionContext";
|
||||
import {Book, Check, Layers, Pencil} from 'lucide-react';
|
||||
import InputField from "@/components/form/InputField";
|
||||
import TextInput from "@/components/form/TextInput";
|
||||
import TexteAreaInput from "@/components/form/TexteAreaInput";
|
||||
import CancelButton from "@/components/form/CancelButton";
|
||||
import SubmitButtonWLoading from "@/components/form/SubmitButtonWLoading";
|
||||
import {useTranslations} from "next-intl";
|
||||
import TextAreaInput from "@/components/form/TextAreaInput";
|
||||
import Button from "@/components/ui/Button";
|
||||
import Modal from "@/components/ui/Modal";
|
||||
import {useTranslations} from '@/lib/i18n';
|
||||
import {LangContext, LangContextProps} from "@/context/LangContext";
|
||||
import {BooksSyncContext, BooksSyncContextProps} from "@/context/BooksSyncContext";
|
||||
import {SyncedBook} from "@/lib/models/SyncedBook";
|
||||
import OfflineContext, {OfflineContextType} from "@/context/OfflineContext";
|
||||
import {SeriesSyncContext, SeriesSyncContextProps} from "@/context/SeriesSyncContext";
|
||||
import {SyncedSeries, SyncedSeriesBook} from "@/lib/models/SyncedSeries";
|
||||
import * as tauri from '@/lib/tauri';
|
||||
import {SyncedBook} from "@/lib/types/synced-book";
|
||||
|
||||
interface AddNewSeriesFormProps {
|
||||
setCloseForm: Dispatch<SetStateAction<boolean>>;
|
||||
@@ -26,36 +21,20 @@ interface AddNewSeriesFormProps {
|
||||
|
||||
export default function AddNewSeriesForm({setCloseForm, onSeriesCreated}: AddNewSeriesFormProps) {
|
||||
const t = useTranslations();
|
||||
const {lang} = useContext<LangContextProps>(LangContext);
|
||||
const {session} = useContext(SessionContext);
|
||||
const {errorMessage, successMessage} = useContext(AlertContext);
|
||||
const {serverSyncedBooks, setServerSyncedBooks, localSyncedBooks, setLocalSyncedBooks} = useContext<BooksSyncContextProps>(BooksSyncContext);
|
||||
const {isCurrentlyOffline} = useContext<OfflineContextType>(OfflineContext);
|
||||
const {serverSyncedSeries, localSyncedSeries} = useContext<SeriesSyncContextProps>(SeriesSyncContext);
|
||||
|
||||
// Get all bookIds already in a series
|
||||
const booksAlreadyInSeries: Set<string> = new Set(
|
||||
(isCurrentlyOffline() ? localSyncedSeries : serverSyncedSeries)
|
||||
.flatMap((series: SyncedSeries): string[] =>
|
||||
series.books.map((book: SyncedSeriesBook): string => book.bookId)
|
||||
)
|
||||
);
|
||||
const modalRef: React.RefObject<HTMLDivElement | null> = useRef<HTMLDivElement>(null);
|
||||
|
||||
const {lang}: LangContextProps = useContext<LangContextProps>(LangContext);
|
||||
const {session}: SessionContextProps = useContext<SessionContextProps>(SessionContext);
|
||||
const {errorMessage, successMessage}: AlertContextProps = useContext<AlertContextProps>(AlertContext);
|
||||
const {
|
||||
serverSyncedBooks,
|
||||
setServerSyncedBooks
|
||||
}: BooksSyncContextProps = useContext<BooksSyncContextProps>(BooksSyncContext);
|
||||
const [name, setName] = useState<string>('');
|
||||
const [description, setDescription] = useState<string>('');
|
||||
const [selectedBookIds, setSelectedBookIds] = useState<string[]>([]);
|
||||
const [isAddingSeries, setIsAddingSeries] = useState<boolean>(false);
|
||||
|
||||
|
||||
const token: string = session?.accessToken ?? '';
|
||||
|
||||
useEffect((): () => void => {
|
||||
document.body.style.overflow = 'hidden';
|
||||
return (): void => {
|
||||
document.body.style.overflow = 'auto';
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
function toggleBookSelection(bookId: string): void {
|
||||
setSelectedBookIds((prev: string[]): string[] => {
|
||||
if (prev.includes(bookId)) {
|
||||
@@ -64,7 +43,7 @@ export default function AddNewSeriesForm({setCloseForm, onSeriesCreated}: AddNew
|
||||
return [...prev, bookId];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async function handleAddSeries(): Promise<void> {
|
||||
if (!name) {
|
||||
errorMessage(t('addNewSeriesForm.error.nameMissing'));
|
||||
@@ -78,54 +57,36 @@ export default function AddNewSeriesForm({setCloseForm, onSeriesCreated}: AddNew
|
||||
errorMessage(t('addNewSeriesForm.error.nameTooLong'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
setIsAddingSeries(true);
|
||||
try {
|
||||
const createData = {
|
||||
name: name,
|
||||
description: description || null,
|
||||
bookIds: selectedBookIds,
|
||||
};
|
||||
let response: string;
|
||||
|
||||
if (isCurrentlyOffline()) {
|
||||
response = await tauri.createSeries(createData);
|
||||
} else {
|
||||
response = await System.authPostToServer<string>(
|
||||
'series/add',
|
||||
createData,
|
||||
token,
|
||||
lang
|
||||
);
|
||||
}
|
||||
|
||||
const response: string = await apiPost<string>(
|
||||
'series/add',
|
||||
{
|
||||
name: name,
|
||||
description: description || null,
|
||||
bookIds: selectedBookIds,
|
||||
},
|
||||
token,
|
||||
lang
|
||||
);
|
||||
if (!response) {
|
||||
errorMessage(t('addNewSeriesForm.error.addingSeries'));
|
||||
setIsAddingSeries(false);
|
||||
return;
|
||||
}
|
||||
successMessage(t('addNewSeriesForm.success'));
|
||||
|
||||
|
||||
if (selectedBookIds.length > 0) {
|
||||
if (isCurrentlyOffline()) {
|
||||
setLocalSyncedBooks((prev: SyncedBook[]): SyncedBook[] =>
|
||||
prev.map((book: SyncedBook): SyncedBook =>
|
||||
selectedBookIds.includes(book.id)
|
||||
? {...book, seriesId: response}
|
||||
: book
|
||||
)
|
||||
);
|
||||
} else {
|
||||
setServerSyncedBooks((prev: SyncedBook[]): SyncedBook[] =>
|
||||
prev.map((book: SyncedBook): SyncedBook =>
|
||||
selectedBookIds.includes(book.id)
|
||||
? {...book, seriesId: response}
|
||||
: book
|
||||
)
|
||||
);
|
||||
}
|
||||
setServerSyncedBooks((prev: SyncedBook[]): SyncedBook[] =>
|
||||
prev.map((book: SyncedBook): SyncedBook =>
|
||||
selectedBookIds.includes(book.id)
|
||||
? {...book, seriesId: response}
|
||||
: book
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if (onSeriesCreated) {
|
||||
onSeriesCreated(response, name);
|
||||
}
|
||||
@@ -140,120 +101,100 @@ export default function AddNewSeriesForm({setCloseForm, onSeriesCreated}: AddNew
|
||||
setIsAddingSeries(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 flex items-center justify-center bg-black/60 z-50 backdrop-blur-md animate-fadeIn">
|
||||
<div ref={modalRef}
|
||||
className="bg-tertiary/95 backdrop-blur-sm text-text-primary rounded-2xl border border-secondary/50 shadow-2xl md:w-3/4 xl:w-2/5 lg:w-2/4 sm:w-11/12 max-h-[85vh] flex flex-col">
|
||||
<div className="flex justify-between items-center bg-primary px-6 py-4 rounded-t-2xl shadow-lg">
|
||||
<h2 className="flex items-center gap-3 font-['ADLaM_Display'] text-2xl text-text-primary">
|
||||
<FontAwesomeIcon icon={faLayerGroup} className="w-6 h-6"/>
|
||||
{t("addNewSeriesForm.title")}
|
||||
</h2>
|
||||
<button
|
||||
className="text-background hover:text-background w-10 h-10 rounded-xl hover:bg-white/20 transition-all duration-200 flex items-center justify-center hover:scale-110"
|
||||
onClick={(): void => setCloseForm(false)}
|
||||
>
|
||||
<FontAwesomeIcon icon={faX} className={'w-5 h-5'}/>
|
||||
</button>
|
||||
<Modal
|
||||
icon={Layers}
|
||||
title={t("addNewSeriesForm.title")}
|
||||
onClose={(): void => setCloseForm(false)}
|
||||
size="md"
|
||||
footer={
|
||||
<>
|
||||
<Button variant="secondary" onClick={() => setCloseForm(false)}>{t("common.cancel")}</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={handleAddSeries}
|
||||
isLoading={isAddingSeries}
|
||||
loadingText={t("addNewSeriesForm.adding")}
|
||||
icon={Layers}
|
||||
>{t("addNewSeriesForm.add")}</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<InputField icon={Pencil} fieldName={t("addNewSeriesForm.name")} input={
|
||||
<TextInput
|
||||
value={name}
|
||||
setValue={(e: ChangeEvent<HTMLInputElement>): void => setName(e.target.value)}
|
||||
placeholder={t("addNewSeriesForm.namePlaceholder")}
|
||||
/>
|
||||
}/>
|
||||
|
||||
<InputField
|
||||
icon={Pencil}
|
||||
fieldName={`${t("addNewSeriesForm.description")} (${t("addNewSeriesForm.optional")})`}
|
||||
input={
|
||||
<TextAreaInput
|
||||
value={description}
|
||||
setValue={(e: ChangeEvent<HTMLTextAreaElement>): void => setDescription(e.target.value)}
|
||||
placeholder={t("addNewSeriesForm.descriptionPlaceholder")}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2 text-text-primary">
|
||||
<Book className="w-4 h-4 text-primary" strokeWidth={1.75}/>
|
||||
<span className="font-medium">{t("addNewSeriesForm.selectBooks")}</span>
|
||||
{selectedBookIds.length > 0 && (
|
||||
<span className="text-sm text-muted">
|
||||
({selectedBookIds.length} {t("addNewSeriesForm.selected")})
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="p-5 overflow-y-auto flex-grow custom-scrollbar">
|
||||
<div className="space-y-6">
|
||||
<InputField icon={faPencilAlt} fieldName={t("addNewSeriesForm.name")} input={
|
||||
<TextInput
|
||||
value={name}
|
||||
setValue={(e: ChangeEvent<HTMLInputElement>): void => setName(e.target.value)}
|
||||
placeholder={t("addNewSeriesForm.namePlaceholder")}
|
||||
/>
|
||||
}/>
|
||||
|
||||
<InputField
|
||||
icon={faPencilAlt}
|
||||
fieldName={`${t("addNewSeriesForm.description")} (${t("addNewSeriesForm.optional")})`}
|
||||
input={
|
||||
<TexteAreaInput
|
||||
value={description}
|
||||
setValue={(e: ChangeEvent<HTMLTextAreaElement>): void => setDescription(e.target.value)}
|
||||
placeholder={t("addNewSeriesForm.descriptionPlaceholder")}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2 text-text-primary">
|
||||
<FontAwesomeIcon icon={faBook} className="w-4 h-4 text-primary"/>
|
||||
<span className="font-medium">{t("addNewSeriesForm.selectBooks")}</span>
|
||||
{selectedBookIds.length > 0 && (
|
||||
<span className="text-sm text-muted">
|
||||
({selectedBookIds.length} {t("addNewSeriesForm.selected")})
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{(isCurrentlyOffline() ? localSyncedBooks : serverSyncedBooks)
|
||||
.filter((book: SyncedBook): boolean => !booksAlreadyInSeries.has(book.id)).length === 0 ? (
|
||||
<div className="text-center py-6 text-muted">
|
||||
<FontAwesomeIcon icon={faBook} className="w-8 h-8 mb-2 opacity-50"/>
|
||||
<p>{t("addNewSeriesForm.noBooks")}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="max-h-48 overflow-y-auto rounded-xl border border-secondary/50 bg-secondary/20">
|
||||
{(isCurrentlyOffline() ? localSyncedBooks : serverSyncedBooks)
|
||||
.filter((book: SyncedBook): boolean => !booksAlreadyInSeries.has(book.id))
|
||||
.map((book: SyncedBook) => {
|
||||
const isSelected: boolean = selectedBookIds.includes(book.id);
|
||||
return (
|
||||
<button
|
||||
key={book.id}
|
||||
type="button"
|
||||
onClick={(): void => toggleBookSelection(book.id)}
|
||||
className={`w-full flex items-center gap-3 px-4 py-3 transition-all duration-200 border-b border-secondary/30 last:border-b-0 ${
|
||||
isSelected
|
||||
? 'bg-primary/20 hover:bg-primary/30'
|
||||
: 'hover:bg-secondary/50'
|
||||
}`}
|
||||
>
|
||||
<div className={`w-5 h-5 rounded border-2 flex items-center justify-center transition-all ${
|
||||
isSelected
|
||||
? 'bg-primary border-primary'
|
||||
: 'border-secondary/50'
|
||||
}`}>
|
||||
{isSelected && (
|
||||
<FontAwesomeIcon icon={faCheck} className="w-3 h-3 text-white"/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 text-left">
|
||||
<div className="text-text-primary font-medium">{book.title}</div>
|
||||
{book.subTitle && (
|
||||
<div className="text-sm text-muted">{book.subTitle}</div>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{serverSyncedBooks.length === 0 ? (
|
||||
<div className="text-center py-6 text-muted">
|
||||
<Book className="w-8 h-8 mb-2 opacity-50" strokeWidth={1.75}/>
|
||||
<p>{t("addNewSeriesForm.noBooks")}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="flex justify-between items-center p-5 border-t border-secondary/50 bg-secondary/20 rounded-b-2xl">
|
||||
<div></div>
|
||||
<div className="flex gap-3">
|
||||
<CancelButton callBackFunction={() => setCloseForm(false)}/>
|
||||
<SubmitButtonWLoading
|
||||
callBackAction={handleAddSeries}
|
||||
isLoading={isAddingSeries}
|
||||
text={t("addNewSeriesForm.add")}
|
||||
loadingText={t("addNewSeriesForm.adding")}
|
||||
icon={faLayerGroup}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className="max-h-48 overflow-y-auto rounded-xl border border-secondary bg-tertiary">
|
||||
{serverSyncedBooks.map((book: SyncedBook) => {
|
||||
const isSelected: boolean = selectedBookIds.includes(book.id);
|
||||
return (
|
||||
<button
|
||||
key={book.id}
|
||||
type="button"
|
||||
onClick={(): void => toggleBookSelection(book.id)}
|
||||
className={`w-full flex items-center gap-3 px-4 py-3 transition-colors duration-150 border-b border-secondary last:border-b-0 ${
|
||||
isSelected
|
||||
? 'bg-secondary'
|
||||
: 'hover:bg-secondary/50'
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`w-5 h-5 rounded border-2 flex items-center justify-center transition-colors ${
|
||||
isSelected
|
||||
? 'bg-primary/20 border-primary'
|
||||
: 'border-secondary'
|
||||
}`}>
|
||||
{isSelected && (
|
||||
<Check className="w-3 h-3 text-text-primary" strokeWidth={1.75}/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 text-left">
|
||||
<div className="text-text-primary font-medium">{book.title}</div>
|
||||
{book.subTitle && (
|
||||
<div className="text-sm text-muted">{book.subTitle}</div>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user