Add components for Act management and integrate Electron setup
This commit is contained in:
232
components/book/settings/BasicInformationSetting.tsx
Normal file
232
components/book/settings/BasicInformationSetting.tsx
Normal file
@@ -0,0 +1,232 @@
|
||||
'use client'
|
||||
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 axios, {AxiosResponse} from "axios";
|
||||
import {AlertContext} from "@/context/AlertContext";
|
||||
import {BookContext} from "@/context/BookContext";
|
||||
import {SessionContext} from "@/context/SessionContext";
|
||||
import TextInput from "@/components/form/TextInput";
|
||||
import TexteAreaInput from "@/components/form/TexteAreaInput";
|
||||
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 {LangContext, LangContextProps} from "@/context/LangContext";
|
||||
import {BookProps} from "@/lib/models/Book";
|
||||
|
||||
function BasicInformationSetting(props: any, ref: any) {
|
||||
const t = useTranslations();
|
||||
const {lang} = useContext<LangContextProps>(LangContext)
|
||||
|
||||
const {session} = useContext(SessionContext);
|
||||
const {book, setBook} = useContext(BookContext);
|
||||
const userToken: string = session?.accessToken ? session?.accessToken : '';
|
||||
const {errorMessage, successMessage} = useContext(AlertContext);
|
||||
const bookId: string = book?.bookId ? book?.bookId.toString() : '';
|
||||
|
||||
const [currentImage, setCurrentImage] = useState<string>(book?.coverImage ?? '');
|
||||
const [title, setTitle] = useState<string>(book?.title ? book?.title : '');
|
||||
const [subTitle, setSubTitle] = useState<string>(book?.subTitle ? book?.subTitle : '');
|
||||
const [summary, setSummary] = useState<string>(book?.summary ? book?.summary : '');
|
||||
const [publicationDate, setPublicationDate] = useState<string>(book?.publicationDate ? book?.publicationDate : '');
|
||||
const [wordCount, setWordCount] = useState<number>(book?.desiredWordCount ? book?.desiredWordCount : 0);
|
||||
|
||||
useImperativeHandle(ref, function () {
|
||||
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();
|
||||
formData.append('bookId', bookId);
|
||||
formData.append('picture', file);
|
||||
|
||||
try {
|
||||
const query: AxiosResponse<ArrayBuffer> = await axios({
|
||||
method: "POST",
|
||||
url: configs.apiUrl + `book/cover?bookid=${bookId}`,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${userToken}`,
|
||||
},
|
||||
params: {
|
||||
lang: lang,
|
||||
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();
|
||||
|
||||
reader.onloadend = function (): void {
|
||||
if (typeof reader.result === 'string') {
|
||||
setCurrentImage(reader.result);
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
} else {
|
||||
throw new Error('An unexpected error occurred');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRemoveCurrentImage(): Promise<void> {
|
||||
try {
|
||||
const response: boolean = await System.authDeleteToServer<boolean>(`book/cover/delete`, {
|
||||
bookId: bookId
|
||||
}, userToken, lang);
|
||||
if (!response) {
|
||||
errorMessage(t('basicInformationSetting.error.removeCover'));
|
||||
}
|
||||
setCurrentImage('');
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error) {
|
||||
errorMessage(e.message);
|
||||
} else {
|
||||
errorMessage(t('basicInformationSetting.error.unknown'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSave(): Promise<void> {
|
||||
if (!title) {
|
||||
errorMessage(t('basicInformationSetting.error.titleRequired'));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response: boolean = await System.authPostToServer<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'));
|
||||
return;
|
||||
}
|
||||
if (!book) {
|
||||
errorMessage(t('basicInformationSetting.error.unknown'));
|
||||
return;
|
||||
}
|
||||
const updatedBook: BookProps = {
|
||||
...book,
|
||||
title: title,
|
||||
subTitle: subTitle,
|
||||
summary: summary,
|
||||
publicationDate: publicationDate,
|
||||
desiredWordCount: wordCount,
|
||||
};
|
||||
setBook!!(updatedBook);
|
||||
successMessage(t('basicInformationSetting.success.update'));
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error) {
|
||||
errorMessage(e.message);
|
||||
} else {
|
||||
errorMessage(t('basicInformationSetting.error.unknown'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div className="bg-tertiary/90 backdrop-blur-sm rounded-xl p-5 border border-secondary/50 shadow-md">
|
||||
{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"
|
||||
/>}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default forwardRef(BasicInformationSetting);
|
||||
Reference in New Issue
Block a user