fixed post create response

This commit is contained in:
SlimusMinus
2026-07-29 00:59:15 +03:00
parent eb1c444c94
commit 0b169633d2
4 changed files with 124 additions and 13 deletions

View File

@@ -54,6 +54,9 @@ export default function CreateUpdatePost({ onClose, onSuccess, initialData }) {
// Единый хук для создания и обновления // Единый хук для создания и обновления
const { save, loading: saving, error: apiError } = usePostMutation(); const { save, loading: saving, error: apiError } = usePostMutation();
// Состояние для модалки-результата (успех / блокировка контента)
const [resultModal, setResultModal] = useState(null); // null | "success" | "blocked"
// Внутри компонента, рядом с другими хуками // Внутри компонента, рядом с другими хуками
const initialCoords = isEdit && initialData.latitude && initialData.longitude const initialCoords = isEdit && initialData.latitude && initialData.longitude
? { latitude: initialData.latitude, longitude: initialData.longitude } ? { latitude: initialData.latitude, longitude: initialData.longitude }
@@ -124,14 +127,22 @@ export default function CreateUpdatePost({ onClose, onSuccess, initialData }) {
// save(id, data): id = null → POST (создание), id = string → PUT (обновление) // save(id, data): id = null → POST (создание), id = string → PUT (обновление)
const result = await save(isEdit ? initialData.id : null, data); const result = await save(isEdit ? initialData.id : null, data);
if (result) { if (!result) {
onSuccess(result); // Ошибка уже записана в apiError, либо null если неизвестная
onClose(); return;
}
if (result.blocked) {
setResultModal("blocked");
return;
}
if (result.data) {
setResultModal("success");
} }
}; };
return ( return (
// Клик по фону (не по самой модалке) закрывает её <>
{/* Клик по фону (не по самой модалке) закрывает её */}
<div className="f-modal-bg" onClick={e => e.target === e.currentTarget && onClose()}> <div className="f-modal-bg" onClick={e => e.target === e.currentTarget && onClose()}>
<div className="f-modal"> <div className="f-modal">
@@ -309,5 +320,45 @@ export default function CreateUpdatePost({ onClose, onSuccess, initialData }) {
</div> </div>
</div> </div>
{/* ── Модалка-результат после отправки ── */}
{resultModal && (
<div className="f-modal-bg" onClick={e => {
if (e.target === e.currentTarget) {
if (resultModal === "success") {
onSuccess?.();
}
onClose();
}
}}>
<div className="f-modal f-modal-result">
<div className={`f-result-icon ${resultModal === "blocked" ? "blocked" : "success"}`}>
{resultModal === "blocked" ? "✕" : "✓"}
</div>
<h3 className="f-result-title">
{resultModal === "blocked"
? t('post.blockedTitle')
: t('post.successTitle')}
</h3>
<p className="f-result-text">
{resultModal === "blocked"
? t('post.blockedText')
: t('post.successText')}
</p>
<button
className={`f-result-btn ${resultModal === "blocked" ? "err" : "ok"}`}
onClick={() => {
if (resultModal === "success") {
onSuccess?.();
}
onClose();
}}
>
{t('post.resultClose')}
</button>
</div>
</div>
)}
</>
); );
} }

View File

@@ -9,7 +9,7 @@ import headphonesIcon from '../assets/headphones.png';
import watchIcon from '../assets/watch.png'; import watchIcon from '../assets/watch.png';
import glassesIcon from '../assets/glasses.png'; import glassesIcon from '../assets/glasses.png';
export const API_URL = process.env.REACT_APP_API_URL || "http://192.168.1.68:8080"; export const API_URL = process.env.REACT_APP_API_URL || "http://localhost:8080";
export function getImageUrl(filename) { export function getImageUrl(filename) {
if (!filename) return null; if (!filename) return null;

View File

@@ -11,11 +11,10 @@ import { API_URL } from "../config/authConstants";
* *
* Автоматически добавляет заголовок Authorization с JWT-токеном. * Автоматически добавляет заголовок Authorization с JWT-токеном.
* *
* @returns {{ * save() возвращает:
* save: (id: string|null, data: FormData) => Promise<object|null>, * { data } — успешный ответ сервера
* loading: boolean, * { blocked: true } — контент заблокирован модерацией
* error: string, * null — серверная ошибка (свойство error содержит текст)
* }}
*/ */
export function usePostMutation() { export function usePostMutation() {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -24,7 +23,7 @@ export function usePostMutation() {
/** /**
* @param {string|null} id — id объявления при обновлении, null при создании * @param {string|null} id — id объявления при обновлении, null при создании
* @param {FormData} data — данные формы * @param {FormData} data — данные формы
* @returns {object|null} — ответ сервера или null при ошибке * @returns {Promise<{data:object}|{blocked:boolean, reason:string}|null>}
*/ */
const save = async (id, data) => { const save = async (id, data) => {
setLoading(true); setLoading(true);
@@ -45,8 +44,18 @@ export function usePostMutation() {
throw new Error("Требуется авторизация. Войдите в аккаунт."); throw new Error("Требуется авторизация. Войдите в аккаунт.");
} }
if (!res.ok) throw new Error(`Ошибка сервера: ${res.status}`); if (!res.ok) {
return await res.json(); let body;
try { body = await res.json(); } catch { body = null; }
if (body?.error === "CONTENT_BLOCKED") {
return { blocked: true, reason: body.reason || "" };
}
throw new Error(body?.message || `Ошибка сервера: ${res.status}`);
}
return { data: await res.json() };
} catch (e) { } catch (e) {
setError(e.message || "Неизвестная ошибка"); setError(e.message || "Неизвестная ошибка");
return null; return null;

View File

@@ -252,3 +252,54 @@
padding: 20px 16px 32px; /* 32px снизу — отступ от home indicator */ padding: 20px 16px 32px; /* 32px снизу — отступ от home indicator */
} }
} }
/* ── Result Modal (success / blocked) ── */
.f-modal-result {
text-align: center;
padding: 36px 28px 28px;
max-width: 380px;
}
.f-result-icon {
width: 56px; height: 56px;
border-radius: 50%;
display: flex; align-items: center; justify-content: center;
margin: 0 auto 16px;
font-size: 1.5rem; font-weight: 900;
}
.f-result-icon.success {
background: rgba(var(--teal-rgb), .15);
color: var(--teal);
}
.f-result-icon.blocked {
background: rgba(255, 107, 107, .15);
color: #ff6b6b;
}
.f-result-title {
margin: 0 0 8px;
font-size: 1.05rem; font-weight: 800;
color: var(--text-primary);
}
.f-result-text {
margin: 0 0 22px;
font-size: .85rem; line-height: 1.5;
color: var(--muted);
}
.f-result-btn {
width: 100%; border: none; border-radius: 12px;
padding: 13px; font-family: var(--font);
font-size: .9rem; font-weight: 800; cursor: pointer;
transition: opacity .15s, transform .15s;
}
.f-result-btn:hover { opacity: .88; transform: translateY(-1px); }
.f-result-btn:active { transform: translateY(0); }
.f-result-btn.ok {
background: linear-gradient(90deg, var(--teal2), var(--teal), var(--teal2));
background-size: 200% 100%;
color: #082320;
animation: shimmer 3s linear infinite;
}
.f-result-btn.err {
background: rgba(255, 107, 107, .12);
border: 1.5px solid rgba(255, 107, 107, .35);
color: #ff6b6b;
}