diff --git a/src/App.js b/src/App.js index 3699b51..5c0a08f 100644 --- a/src/App.js +++ b/src/App.js @@ -1,12 +1,15 @@ import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'; import RegisterPage from './pages/AuthPage'; +import FeedPage from './pages/FeedPage'; // ← добавь импорт function App() { return ( - } /> + } /> + } /> + } /> ); diff --git a/src/assets/magnifier.png b/src/assets/magnifier.png new file mode 100644 index 0000000..760b08b Binary files /dev/null and b/src/assets/magnifier.png differ diff --git a/src/components/AdCard.jsx b/src/components/AdCard.jsx new file mode 100644 index 0000000..94f1577 --- /dev/null +++ b/src/components/AdCard.jsx @@ -0,0 +1,51 @@ +import PinIcon from "./icons/PinIcon"; +import CalendarIcon from "./icons/CalendarIcon"; +import PhoneIcon from "./icons/PhoneIcon"; +import MapIcon from "./icons/MapIcon"; +import HeartIcon from "./icons/HeartIcon"; + +export default function AdCard({ ad, liked, onLike }) { + return ( +
+
+ {ad.image + ? {ad.title} + : {ad.emoji || "📋"} + } + {ad.category &&
{ad.category}
} +
+ {ad.type === "loss" ? "Потеря" : "Находка"} +
+
+
+
{ad.title}
+
{ad.description}
+
+ {ad.location &&
{ad.location}
} + {ad.date &&
{ad.date}
} +
+ {ad.location && ( + + )} +
+ {ad.phone + ? e.stopPropagation()}> + {ad.phone} + + : + } + {ad.date || ""} + +
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/PostModal.jsx b/src/components/PostModal.jsx new file mode 100644 index 0000000..1b03ae4 --- /dev/null +++ b/src/components/PostModal.jsx @@ -0,0 +1,157 @@ +import {useCallback, useState} from "react"; +import {API_URL} from "../config/constants"; +import PinIcon from "./icons/PinIcon"; +import CloseIcon from "./icons/CloseIcon"; +import { EMPTY_FORM, CATEGORIES } from "../config/feedConstants"; + +export default function PostModal({ onClose, onSuccess }) { + const [form, setForm] = useState(EMPTY_FORM); + const [errors, setErrors] = useState({}); + const [loading, setLoading] = useState(false); + const [apiError, setApiError] = useState(""); + + const set = useCallback((k, v) => { + setForm(p => ({ ...p, [k]: v })); + setErrors(p => ({ ...p, [k]: "" })); + setApiError(""); + }, []); + + const validate = () => { + const e = {}; + if (!form.title.trim()) e.title = "Введите название"; + if (!form.description.trim()) e.description = "Введите описание"; + if (!form.category) e.category = "Выберите категорию"; + if (!form.phone.trim()) e.phone = "Введите номер телефона"; + setErrors(e); + return !Object.keys(e).length; + }; + + const handleSubmit = async () => { + if (!validate()) return; + setLoading(true); + setApiError(""); + try { + const res = await fetch(`${API_URL}/api/ads`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(form), + }); + if (!res.ok) { + const data = await res.json().catch(() => ({})); + throw new Error(data.message || `Ошибка ${res.status}`); + } + const newAd = await res.json(); + onSuccess(newAd); + onClose(); + } catch (err) { + setApiError(err.message || "Не удалось разместить объявление"); + } finally { + setLoading(false); + } + }; + + return ( +
e.target === e.currentTarget && onClose()}> +
+
+ Новое объявление + +
+ +
+ +
+ + +
+
+ +
+ + set("title", e.target.value)} + /> + {errors.title &&

{errors.title}

} +
+ +
+ +