russ_react/src/components/ImageUpload/ImageUploaderModal.tsx

138 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { ChangeEvent, useCallback, useState } from 'react';
import Modal from '@/components/ui/Modal';
import Button from '@/components/ui/Button';
import api from '@/lib/api';
interface Props {
/** Управляет открытием/закрытием модального окна */
isOpen: boolean;
/** Колбэк, вызываемый, когда пользователь закрыл окно без сохранения */
onClose: () => void;
/** Колбэк, срабатывающий после успешной загрузки файла */
onSuccess: (uploaded: UploadedImage) => void;
}
/**
* Тип данных, которые реально уходят на бэкенд.
* Поля, которые не требуются при самой первой загрузке,
* объявлены с ?. Компилятор теперь не требует заполнять их
* каждый раз.
*/
export interface ImageBody {
file: File;
/** Если файл уже загружен, бэкенд может вернуть готовый URL */
url?: string;
title_ru?: string;
title_en?: string;
description_ru?: string;
description_en?: string;
}
/** Ответ от сервера после успешной загрузки */
export interface UploadedImage {
id: string;
url: string;
title_ru?: string;
title_en?: string;
description_ru?: string;
description_en?: string;
}
const ImageUploaderModal: React.FC<Props> = ({ isOpen, onClose, onSuccess }) => {
const [file, setFile] = useState<File | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const onFileChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
setError(null);
const chosen = e.target.files?.[0];
if (chosen) {
setFile(chosen);
}
}, []);
const resetState = () => {
setFile(null);
setError(null);
setLoading(false);
};
const handleClose = () => {
if (!loading) {
resetState();
onClose();
}
};
const upload = async () => {
if (!file) {
setError('Выберите изображение перед загрузкой.');
return;
}
setLoading(true);
try {
const body: ImageBody = { file }; // ничего лишнего не добавляем
// Формируем FormData, чтобы отправить файл «как есть»
const form = new FormData();
form.append('file', body.file);
// Если вам нужно сразу отправлять title/description,
// их можно добавить в form.append(...) при наличии
const { data } = await api.post<UploadedImage>('/images', form, {
headers: { 'Content-Type': 'multipart/form-data' }
});
onSuccess(data);
handleClose();
} catch (err) {
console.error(err);
setError('Не удалось загрузить файл. Попробуйте ещё раз.');
} finally {
setLoading(false);
}
};
return (
<Modal open={isOpen} onClose={handleClose} title="Загрузка изображения">
<div className="space-y-4">
<input
type="file"
accept="image/*"
onChange={onFileChange}
disabled={loading}
className="block w-full"
/>
{error && <p className="text-sm text-red-600">{error}</p>}
<div className="flex justify-end gap-2">
<Button
variant="secondary"
onClick={handleClose}
disabled={loading}
>
Отмена
</Button>
<Button
variant="primary"
onClick={upload}
loading={loading}
disabled={!file || loading}
>
Сохранить
</Button>
</div>
</div>
</Modal>
);
};
export default ImageUploaderModal;