global refactoring
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { API_URL } from "../config/constants";
|
||||
import { API_URL } from "../config/authConstants";
|
||||
|
||||
export function useAuth() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState } from "react";
|
||||
import { API_URL } from "../config/constants";
|
||||
import { API_URL } from "../config/authConstants";
|
||||
|
||||
export function useCreatePost() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
87
src/hooks/useImageUpload.js
Normal file
87
src/hooks/useImageUpload.js
Normal file
@@ -0,0 +1,87 @@
|
||||
import { useState, useRef } from "react";
|
||||
import { compressImage } from "../utils/compressImage";
|
||||
|
||||
const MAX_SIZE_MB = 5;
|
||||
export const ACCEPT = "image/jpeg,image/png,image/webp";
|
||||
|
||||
/**
|
||||
* Управляет выбором, сжатием и удалением фотографии в форме объявления.
|
||||
*
|
||||
* @param {string|null} existingUrl — URL существующей картинки с сервера
|
||||
* (передаётся при редактировании)
|
||||
* @returns {{
|
||||
* imageFile: File|null,
|
||||
* displayImage: string|null,
|
||||
* imgError: string,
|
||||
* compressing: boolean,
|
||||
* fileInputRef: React.RefObject,
|
||||
* handleFileChange: function,
|
||||
* removeImage: function,
|
||||
* }}
|
||||
*/
|
||||
export function useImageUpload(existingUrl = null) {
|
||||
// Сжатый файл, готовый к отправке в FormData
|
||||
const [imageFile, setImageFile] = useState(null);
|
||||
// ObjectURL для предпросмотра нового файла
|
||||
const [preview, setPreview] = useState(null);
|
||||
// true — пользователь явно удалил фото, existingUrl больше не показываем
|
||||
const [removed, setRemoved] = useState(false);
|
||||
const [imgError, setImgError] = useState("");
|
||||
// true пока идёт сжатие — нужен чтобы заблокировать сабмит
|
||||
const [compressing, setCompressing] = useState(false);
|
||||
// ref на <input type="file"> — нужен для сброса значения после removeImage
|
||||
const fileInputRef = useRef(null);
|
||||
|
||||
// Что показывать в блоке превью:
|
||||
// новый preview имеет приоритет, иначе existingUrl (если не удалён)
|
||||
const displayImage = preview || (!removed ? existingUrl : null);
|
||||
|
||||
/** Проверяет файл, сжимает его и создаёт превью. */
|
||||
const handleFileChange = async (e) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
setImgError("");
|
||||
|
||||
if (!file.type.startsWith("image/")) {
|
||||
setImgError("Только изображения (JPEG, PNG, WebP)");
|
||||
return;
|
||||
}
|
||||
if (file.size > MAX_SIZE_MB * 1024 * 1024) {
|
||||
setImgError(`Файл слишком большой — максимум ${MAX_SIZE_MB} МБ`);
|
||||
return;
|
||||
}
|
||||
|
||||
setCompressing(true);
|
||||
try {
|
||||
const compressed = await compressImage(file);
|
||||
setImageFile(compressed);
|
||||
setPreview(URL.createObjectURL(compressed));
|
||||
setRemoved(false);
|
||||
} catch {
|
||||
setImgError("Не удалось обработать изображение");
|
||||
} finally {
|
||||
setCompressing(false);
|
||||
}
|
||||
};
|
||||
|
||||
/** Удаляет фото, освобождает ObjectURL и сбрасывает input. */
|
||||
const removeImage = () => {
|
||||
setImageFile(null);
|
||||
if (preview) URL.revokeObjectURL(preview); // предотвращаем утечку памяти
|
||||
setPreview(null);
|
||||
setRemoved(true);
|
||||
setImgError("");
|
||||
// Сброс нужен, иначе повторный выбор того же файла не вызовет onChange
|
||||
if (fileInputRef.current) fileInputRef.current.value = "";
|
||||
};
|
||||
|
||||
return {
|
||||
imageFile,
|
||||
displayImage,
|
||||
imgError,
|
||||
compressing,
|
||||
fileInputRef,
|
||||
handleFileChange,
|
||||
removeImage,
|
||||
};
|
||||
}
|
||||
45
src/hooks/usePostMutation.js
Normal file
45
src/hooks/usePostMutation.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import { useState } from "react";
|
||||
import { API_URL } from "../config/authConstants";
|
||||
|
||||
/**
|
||||
* Универсальная мутация для создания и обновления объявления.
|
||||
* Заменяет отдельные хуки useCreatePost и useUpdatePost.
|
||||
*
|
||||
* Логика выбора метода:
|
||||
* id передан → PUT /api/v1/posts/:id (обновление)
|
||||
* id не передан → POST /api/v1/posts (создание)
|
||||
*
|
||||
* @returns {{
|
||||
* save: (id: string|null, data: FormData) => Promise<object|null>,
|
||||
* loading: boolean,
|
||||
* error: string,
|
||||
* }}
|
||||
*/
|
||||
export function usePostMutation() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
/**
|
||||
* @param {string|null} id — id объявления при обновлении, null при создании
|
||||
* @param {FormData} data — данные формы
|
||||
* @returns {object|null} — ответ сервера или null при ошибке
|
||||
*/
|
||||
const save = async (id, data) => {
|
||||
setLoading(true);
|
||||
setError("");
|
||||
try {
|
||||
const url = id ? `${API_URL}/api/v1/posts/${id}` : `${API_URL}/api/v1/posts`;
|
||||
const method = id ? "PUT" : "POST";
|
||||
const res = await fetch(url, { method, body: data });
|
||||
if (!res.ok) throw new Error(`Ошибка сервера: ${res.status}`);
|
||||
return await res.json();
|
||||
} catch (e) {
|
||||
setError(e.message || "Неизвестная ошибка");
|
||||
return null;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return { save, loading, error };
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import {API_URL} from "../config/constants";
|
||||
import {API_URL} from "../config/authConstants";
|
||||
|
||||
|
||||
export function useCheckLogin(login) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState } from "react";
|
||||
import { API_URL } from "../config/constants";
|
||||
import { API_URL } from "../config/authConstants";
|
||||
|
||||
export function useUpdatePost() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -14,7 +14,6 @@ export function useUpdatePost() {
|
||||
const res = await fetch(`${API_URL}/api/v1/posts/${id}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
// Content-Type НЕ указываем — браузер сам выставит multipart/form-data
|
||||
"Authorization": `Bearer ${token}`,
|
||||
},
|
||||
body: formData, // FormData
|
||||
|
||||
Reference in New Issue
Block a user