fixed post create response
This commit is contained in:
@@ -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>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user