295 lines
15 KiB
TypeScript
295 lines
15 KiB
TypeScript
'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,
|
|
faBookOpen,
|
|
faCalendarAlt,
|
|
faFileWord,
|
|
faInfo,
|
|
faPencilAlt,
|
|
faX
|
|
} from "@fortawesome/free-solid-svg-icons";
|
|
import {SelectBoxProps} from "@/shared/interface";
|
|
import {BookProps, bookTypes} from "@/lib/models/Book";
|
|
import InputField from "@/components/form/InputField";
|
|
import TextInput from "@/components/form/TextInput";
|
|
import SelectBox from "@/components/form/SelectBox";
|
|
import DatePicker from "@/components/form/DatePicker";
|
|
import NumberInput from "@/components/form/NumberInput";
|
|
import TexteAreaInput from "@/components/form/TexteAreaInput";
|
|
import CancelButton from "@/components/form/CancelButton";
|
|
import SubmitButtonWLoading from "@/components/form/SubmitButtonWLoading";
|
|
import GuideTour, {GuideStep} from "@/components/GuideTour";
|
|
import {UserProps} from "@/lib/models/User";
|
|
import {useTranslations} from "next-intl";
|
|
import {LangContext, LangContextProps} from "@/context/LangContext";
|
|
|
|
interface MinMax {
|
|
min: number;
|
|
max: number;
|
|
}
|
|
|
|
export default function AddNewBookForm({setCloseForm}: { setCloseForm: Dispatch<SetStateAction<boolean>> }) {
|
|
const t = useTranslations();
|
|
const {lang} = useContext<LangContextProps>(LangContext);
|
|
const {session, setSession} = useContext(SessionContext);
|
|
const {errorMessage} = useContext(AlertContext);
|
|
const modalRef: React.RefObject<HTMLDivElement | null> = useRef<HTMLDivElement>(null);
|
|
|
|
const [title, setTitle] = useState<string>('');
|
|
const [subtitle, setSubtitle] = useState<string>('');
|
|
const [summary, setSummary] = useState<string>('');
|
|
const [publicationDate, setPublicationDate] = useState<string>('');
|
|
const [wordCount, setWordCount] = useState<number>(0);
|
|
const [selectedBookType, setSelectedBookType] = useState<string>('');
|
|
|
|
const [isAddingBook, setIsAddingBook] = useState<boolean>(false);
|
|
const [bookTypeHint, setBookTypeHint] = useState<boolean>(false);
|
|
|
|
const token: string = session?.accessToken ?? '';
|
|
|
|
const bookTypesHint: GuideStep[] = [{
|
|
id: 0,
|
|
x: 80,
|
|
y: 50,
|
|
title: t("addNewBookForm.bookTypeHint.title"),
|
|
content: (
|
|
<div className="space-y-4 max-h-96 overflow-y-auto custom-scrollbar">
|
|
<div className="space-y-3">
|
|
<div className="border-l-4 border-primary pl-4 bg-secondary/10 p-3 rounded-r-xl">
|
|
<h4 className="font-semibold text-lg text-text-primary mb-1">{t("addNewBookForm.bookTypeHint.nouvelle.title")}</h4>
|
|
<p className="text-sm text-muted mb-2">{t("addNewBookForm.bookTypeHint.nouvelle.range")}</p>
|
|
<p className="text-sm text-text-secondary">{t("addNewBookForm.bookTypeHint.nouvelle.description")}</p>
|
|
</div>
|
|
<div className="border-l-4 border-primary pl-4 bg-secondary/10 p-3 rounded-r-xl">
|
|
<h4 className="font-semibold text-lg text-text-primary mb-1">{t("addNewBookForm.bookTypeHint.novelette.title")}</h4>
|
|
<p className="text-sm text-muted mb-2">{t("addNewBookForm.bookTypeHint.novelette.range")}</p>
|
|
<p className="text-sm text-text-secondary">{t("addNewBookForm.bookTypeHint.novelette.description")}</p>
|
|
</div>
|
|
<div className="border-l-4 border-primary pl-4 bg-secondary/10 p-3 rounded-r-xl">
|
|
<h4 className="font-semibold text-lg text-text-primary mb-1">{t("addNewBookForm.bookTypeHint.novella.title")}</h4>
|
|
<p className="text-sm text-muted mb-2">{t("addNewBookForm.bookTypeHint.novella.range")}</p>
|
|
<p className="text-sm text-text-secondary">{t("addNewBookForm.bookTypeHint.novella.description")}</p>
|
|
</div>
|
|
<div className="border-l-4 border-primary pl-4 bg-secondary/10 p-3 rounded-r-xl">
|
|
<h4 className="font-semibold text-lg text-text-primary mb-1">{t("addNewBookForm.bookTypeHint.chapbook.title")}</h4>
|
|
<p className="text-sm text-muted mb-2">{t("addNewBookForm.bookTypeHint.chapbook.range")}</p>
|
|
<p className="text-sm text-text-secondary">{t("addNewBookForm.bookTypeHint.chapbook.description")}</p>
|
|
</div>
|
|
<div className="border-l-4 border-primary pl-4 bg-secondary/10 p-3 rounded-r-xl">
|
|
<h4 className="font-semibold text-lg text-text-primary mb-1">{t("addNewBookForm.bookTypeHint.roman.title")}</h4>
|
|
<p className="text-sm text-muted mb-2">{t("addNewBookForm.bookTypeHint.roman.range")}</p>
|
|
<p className="text-sm text-text-secondary">{t("addNewBookForm.bookTypeHint.roman.description")}</p>
|
|
</div>
|
|
</div>
|
|
<div className="bg-primary/10 border border-primary/30 p-4 rounded-xl">
|
|
<p className="text-sm text-text-primary font-medium">
|
|
{t("addNewBookForm.bookTypeHint.tip")}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
),
|
|
}]
|
|
|
|
useEffect((): () => void => {
|
|
document.body.style.overflow = 'hidden';
|
|
return (): void => {
|
|
document.body.style.overflow = 'auto';
|
|
};
|
|
}, []);
|
|
|
|
async function handleAddBook(): Promise<void> {
|
|
if (!title) {
|
|
errorMessage(t('addNewBookForm.error.titleMissing'));
|
|
return;
|
|
} else {
|
|
if (title.length < 2) {
|
|
errorMessage(t('addNewBookForm.error.titleTooShort'));
|
|
return;
|
|
}
|
|
if (title.length > 50) {
|
|
errorMessage(t('addNewBookForm.error.titleTooLong'));
|
|
return;
|
|
}
|
|
}
|
|
if (selectedBookType === '') {
|
|
errorMessage(t('addNewBookForm.error.typeMissing'));
|
|
return;
|
|
}
|
|
setIsAddingBook(true);
|
|
try {
|
|
const bookId: string = await System.authPostToServer<string>('book/add', {
|
|
title: title,
|
|
subTitle: subtitle,
|
|
type: selectedBookType,
|
|
summary: summary,
|
|
serie: 0,
|
|
publicationDate: publicationDate,
|
|
desiredWordCount: wordCount,
|
|
}, token, lang)
|
|
if (!bookId) {
|
|
errorMessage(t('addNewBookForm.error.addingBook'));
|
|
setIsAddingBook(false);
|
|
return;
|
|
}
|
|
const book: BookProps = {
|
|
bookId: bookId,
|
|
title,
|
|
subTitle: subtitle,
|
|
type: selectedBookType,
|
|
summary, serie: 0,
|
|
publicationDate,
|
|
desiredWordCount: wordCount
|
|
};
|
|
setSession({
|
|
...session,
|
|
user: {
|
|
...session.user as UserProps,
|
|
books: [...((session.user as UserProps)?.books ?? []), book]
|
|
}
|
|
});
|
|
setIsAddingBook(false);
|
|
setCloseForm(false)
|
|
} catch (e: unknown) {
|
|
if (e instanceof Error) {
|
|
errorMessage(e.message);
|
|
} else {
|
|
errorMessage(t('addNewBookForm.error.addingBook'));
|
|
}
|
|
setIsAddingBook(false);
|
|
}
|
|
}
|
|
|
|
function maxWordsCountHint(): MinMax {
|
|
switch (selectedBookType) {
|
|
case 'short':
|
|
return {
|
|
min: 1000,
|
|
max: 7500,
|
|
};
|
|
case 'chapbook':
|
|
return {
|
|
min: 1000,
|
|
max: 10000,
|
|
};
|
|
case 'novelette' :
|
|
return {
|
|
min: 7500,
|
|
max: 17500,
|
|
};
|
|
case 'long' :
|
|
return {
|
|
min: 17500,
|
|
max: 40000,
|
|
};
|
|
case 'novel' :
|
|
return {
|
|
min: 40000,
|
|
max: 0,
|
|
};
|
|
default :
|
|
return {
|
|
min: 0,
|
|
max: 0
|
|
}
|
|
}
|
|
}
|
|
|
|
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-1/4 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={faBook} className="w-6 h-6"/>
|
|
{t("addNewBookForm.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>
|
|
</div>
|
|
|
|
<div className="p-5 overflow-y-auto flex-grow custom-scrollbar">
|
|
<div className="space-y-6">
|
|
<InputField icon={faBookOpen} fieldName={t("addNewBookForm.type")} input={
|
|
<SelectBox
|
|
onChangeCallBack={(e: ChangeEvent<HTMLSelectElement>): void => setSelectedBookType(e.target.value)}
|
|
data={bookTypes.map((types: SelectBoxProps): SelectBoxProps => {
|
|
return {
|
|
value: types.value,
|
|
label: t(types.label)
|
|
}
|
|
})} defaultValue={selectedBookType}
|
|
placeholder={t("addNewBookForm.typePlaceholder")}/>
|
|
} action={async (): Promise<void> => setBookTypeHint(true)} actionIcon={faInfo}/>
|
|
<InputField icon={faPencilAlt} fieldName={t("addNewBookForm.bookTitle")} input={
|
|
<TextInput value={title}
|
|
setValue={(e: ChangeEvent<HTMLInputElement>): void => setTitle(e.target.value)}
|
|
placeholder={t("addNewBookForm.bookTitlePlaceholder")}/>
|
|
}/>
|
|
{
|
|
selectedBookType !== 'lyric' && (
|
|
<InputField icon={faPencilAlt} fieldName={t("addNewBookForm.subtitle")} input={
|
|
<TextInput value={subtitle}
|
|
setValue={(e: ChangeEvent<HTMLInputElement>): void => setSubtitle(e.target.value)}
|
|
placeholder={t("addNewBookForm.subtitlePlaceholder")}/>
|
|
}/>
|
|
)
|
|
}
|
|
|
|
<InputField icon={faCalendarAlt} fieldName={t("addNewBookForm.publicationDate")} input={
|
|
<DatePicker date={publicationDate}
|
|
setDate={(e: React.ChangeEvent<HTMLInputElement>): void => setPublicationDate(e.target.value)}/>
|
|
}/>
|
|
|
|
{
|
|
selectedBookType !== 'lyric' && (
|
|
<>
|
|
<InputField icon={faFileWord} fieldName={t("addNewBookForm.wordGoal")}
|
|
hint={selectedBookType && `${maxWordsCountHint().min.toLocaleString('fr-FR')} - ${maxWordsCountHint().max > 0 ? maxWordsCountHint().max.toLocaleString('fr-FR') : '∞'} ${t("addNewBookForm.words")}`}
|
|
input={
|
|
<NumberInput value={wordCount} setValue={setWordCount}
|
|
placeholder={t("addNewBookForm.wordGoalPlaceholder")}/>
|
|
}/>
|
|
|
|
<InputField
|
|
icon={faFileWord}
|
|
fieldName={t("addNewBookForm.summary")}
|
|
input={
|
|
<TexteAreaInput
|
|
value={summary}
|
|
setValue={(e: ChangeEvent<HTMLTextAreaElement>): void => setSummary(e.target.value)}
|
|
placeholder={t("addNewBookForm.summaryPlaceholder")}
|
|
/>
|
|
}
|
|
/>
|
|
</>
|
|
)
|
|
}
|
|
</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={handleAddBook} isLoading={isAddingBook}
|
|
text={t("addNewBookForm.add")}
|
|
loadingText={t("addNewBookForm.adding")} icon={faBook}/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{bookTypeHint && <GuideTour stepId={0} steps={bookTypesHint} onClose={(): void => setBookTypeHint(false)}
|
|
onComplete={async (): Promise<void> => setBookTypeHint(false)}/>}
|
|
</div>
|
|
);
|
|
} |