From fc829c8c7a891222286f52fd5ba76c3071c008e8 Mon Sep 17 00:00:00 2001 From: SlimusMinus Date: Wed, 6 May 2026 01:51:01 +0300 Subject: [PATCH] added feed --- src/App.js | 5 +- src/assets/magnifier.png | Bin 0 -> 7517 bytes src/components/AdCard.jsx | 51 ++++ src/components/PostModal.jsx | 157 +++++++++++ src/components/RightSidebar.jsx | 14 + src/components/Sidebar.jsx | 46 +++ src/components/icons/CalendarIcon.jsx | 10 + src/components/icons/CloseIcon.jsx | 9 + src/components/icons/HeartIcon.jsx | 10 + src/components/icons/LogoIcon.jsx | 13 + src/components/icons/MapIcon.jsx | 10 + src/components/icons/MenuIcon.jsx | 9 + src/components/icons/NavIcons.jsx | 39 +++ src/components/icons/PhoneIcon.jsx | 9 + src/components/icons/PinIcon.jsx | 8 + src/config/feedConstants.js | 29 ++ src/pages/FeedPage.jsx | 141 ++++++++++ src/styles/feed.css | 388 ++++++++++++++++++++++++++ 18 files changed, 947 insertions(+), 1 deletion(-) create mode 100644 src/assets/magnifier.png create mode 100644 src/components/AdCard.jsx create mode 100644 src/components/PostModal.jsx create mode 100644 src/components/RightSidebar.jsx create mode 100644 src/components/Sidebar.jsx create mode 100644 src/components/icons/CalendarIcon.jsx create mode 100644 src/components/icons/CloseIcon.jsx create mode 100644 src/components/icons/HeartIcon.jsx create mode 100644 src/components/icons/LogoIcon.jsx create mode 100644 src/components/icons/MapIcon.jsx create mode 100644 src/components/icons/MenuIcon.jsx create mode 100644 src/components/icons/NavIcons.jsx create mode 100644 src/components/icons/PhoneIcon.jsx create mode 100644 src/components/icons/PinIcon.jsx create mode 100644 src/config/feedConstants.js create mode 100644 src/pages/FeedPage.jsx create mode 100644 src/styles/feed.css 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 0000000000000000000000000000000000000000..760b08b920cf509f2d2e964e30f41db55a5344a8 GIT binary patch literal 7517 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 literal 0 HcmV?d00001 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}

} +
+ +
+ +