VolonteurMainPage

This commit is contained in:
fullofempt
2025-12-13 17:58:31 +05:00
parent 55c42a115d
commit 48d4db0e77
9 changed files with 609 additions and 7 deletions

109
app/ProfilePage/page.jsx Normal file
View File

@@ -0,0 +1,109 @@
"use client";
import React from "react";
import { useRouter } from "next/navigation";
import { FaUserCircle, FaStar } from "react-icons/fa";
import TabBar from "../components/TabBar";
const ProfilePage = () => {
const router = useRouter();
const fullName = "Иванов Александр Сергеевич";
const birthDate = "12.03.1990";
const rating = 4.8;
return (
<div className="min-h-screen w-full bg-[#90D2F9] flex justify-center px-4">
<div className="relative w-full max-w-md flex flex-col pb-20 pt-4">
{/* Header с кнопкой назад и заголовком по центру */}
<header className="flex items-center mb-4">
<button
type="button"
onClick={() => router.back()}
className="text-white w-8 h-8 rounded-full flex items-center justify-center text-lg"
>
</button>
<h1 className="flex-1 text-center font-montserrat font-extrabold text-[20px] leading-[24px] text-white">
Профиль
</h1>
<span className="w-8" />
</header>
{/* Карточка профиля */}
<main className="bg-white rounded-3xl p-4 flex flex-col items-center gap-4 shadow-lg">
{/* Аватар */}
<FaUserCircle className="text-[#72B8E2] w-20 h-20" />
{/* ФИО и рейтинг */}
<div className="text-center space-y-1">
{/* <p className="font-montserrat font-extrabold text-[16px] text-black">
ФИО
</p> */}
<p className="font-montserrat font-bold text-[20px] text-black">
{fullName}
</p>
{/* Рейтинг + звезды */}
<div className="mt-2 flex items-center justify-center gap-2">
<span className="font-montserrat font-semibold text-[14px] text-black">
Рейтинг: {rating.toFixed(1)}
</span>
<div className="flex gap-1">
{[1, 2, 3, 4, 5].map((star) => (
<FaStar
key={star}
size={18}
className={
star <= Math.round(rating)
? "text-[#F6E168] fill-[#F6E168]"
: "text-[#F6E168] fill-[#F6E168]/30"
}
/>
))}
</div>
</div>
</div>
{/* Контакты и день рождения */}
<div className="w-full bg-[#72B8E2] rounded-2xl p-3 text-white space-y-1">
<p className="font-montserrat text-[12px]">
Дата рождения: {birthDate}
</p>
<p className="font-montserrat text-[12px]">
Почта: example@mail.com
</p>
<p className="font-montserrat text-[12px]">
Телефон: +7 (900) 000-00-00
</p>
</div>
{/* Кнопки */}
<div className="w-full flex flex-col gap-2 mt-2">
<button
type="button"
onClick={() => router.push("/profileSettings")}
className="w-full bg-[#E0B267] rounded-full py-2 flex items-center justify-center"
>
<span className="font-montserrat font-extrabold text-[14px] text-white">
Редактировать профиль
</span>
</button>
<button
type="button"
className="w-full bg-[#E07567] rounded-full py-2 flex items-center justify-center"
>
<span className="font-montserrat font-extrabold text-[14px] text-white">
Выйти из аккаунта
</span>
</button>
</div>
</main>
<TabBar />
</div>
</div>
);
};
export default ProfilePage;

View File

@@ -43,7 +43,7 @@ const RequestDetailsModal = ({ request, onClose }) => {
{/* Белая карточка как на макете */}
<div className="flex-1 flex items-start justify-center">
<div className="w-full max-w-[360px] bg-white rounded-3xl p-4 flex flex-col gap-4 shadow-lg">
<div className="w-full max-w-[360px] bg-white rounded-2xl p-4 flex flex-col gap-4 shadow-lg">
{/* Статус + срок (берём цвет и текст из заявки) */}
<div className="flex items-start justify-between">
<span
@@ -87,7 +87,7 @@ const RequestDetailsModal = ({ request, onClose }) => {
{isRejected && (
<>
{request.rejectReason && (
<div className="bg-[#FF8282] rounded-3xl p-3">
<div className="bg-[#FF8282] rounded-2xl p-3">
<p className="font-montserrat font-bold text-[12px] text-white mb-1">
Причина отказа
</p>
@@ -147,7 +147,7 @@ const RequestDetailsModal = ({ request, onClose }) => {
<button
type="button"
onClick={handleSubmit}
className="mt-4 w-full max-w-[360px] mx-auto bg-[#94E067] rounded-full py-3 flex items-center justify-center"
className="mt-4 w-full max-w-[360px] mx-auto bg-[#94E067] rounded-2xl py-3 flex items-center justify-center"
>
<span className="font-montserrat font-extrabold text-[16px] text-white">
{isRejected ? "Отправить комментарий" : "Оставить отзыв"}

View File

@@ -32,6 +32,7 @@ const TabBar = () => {
</button>
<button
type="button"
onClick={() => router.push("/ProfilePage")}
className="flex flex-col items-center gap-1 text-[#90D2F9]"
>
<FaCog size={20} />

View File

@@ -0,0 +1,102 @@
"use client";
import React from "react";
import { FaTimesCircle } from "react-icons/fa";
const AcceptPopup = ({ request, isOpen, onClose, onAccept }) => {
if (!isOpen || !request) return null;
return (
<div className="fixed inset-0 z-50 flex items-center justify-center">
{/* затемнение */}
<div
className="absolute inset-0 bg-black/40"
onClick={onClose}
/>
{/* карточка на всю страницу */}
<div className="relative z-10 w-full h-250px bg-white rounded-2xl px-4 pt-4 pb-6 flex flex-col">
{/* крестик */}
<button
type="button"
onClick={onClose}
className="absolute top-4 right-4 text-[#FF9494]"
>
<FaTimesCircle className="w-5 h-5" />
</button>
{/* Заголовок */}
<h2 className="font-montserrat font-extrabold text-[20px] leading-[22px] text-[#90D2F9] mb-1">
Задача
</h2>
<p className="text-[20px] leading-[14px] mt-5 font-montserrat mb-5">
{request.title}
</p>
{/* Сумма и время */}
<div className="flex items-center gap-3 mb-3">
<div className="w-full h-[40px] bg-[#90D2F9] rounded-full flex flex-col items-center justify-center">
<span className="text-[12px] leading-[11px] text-white font-semibold mb-2">
Сумма
</span>
<span className="text-[15px] leading-[13px] text-white font-semibold">
{request.amount || "2000 ₽"}
</span>
</div>
<div className="w-full h-[40px] bg-[#90D2F9] rounded-full flex flex-col items-center justify-center">
<span className="text-[12px] leading-[11px] text-white font-semibold mb-2">
Выполнить до
</span>
<span className="text-[15px] leading-[13px] text-white font-semibold">
{request.deadline || "17:00"}
</span>
</div>
</div>
{/* Список покупок / описание */}
<div className="w-full bg-[#E4E4E4] rounded-[20px] px-3 py-3 mb-3 max-h-[40vh] overflow-y-auto">
<p className="text-[15px] leading-[20px] font-montserrat text-black whitespace-pre-line">
{request.description ||
"Необходимо приобрести:\n1. Белый хлеб\n2. Молоко\n3. Колбаса\n4. Фрукты"}
</p>
</div>
{/* Данные человека */}
<div className="w-full flex flex-col gap-3 mb-4">
<p className="font-montserrat text-[20px] leading-[19px] font-medium">
Данные:
</p>
<p className="text-[20px] leading-[12px] font-montserrat">
ФИО: {request.fullName || "Клавдия Березова"}
</p>
<p className="text-[15px] leading-[12px] font-montserrat">
Место: {request.address}
</p>
{request.flat && (
<p className="text-[10px] leading-[12px] font-montserrat">
кв: {request.flat}
</p>
)}
{request.floor && (
<p className="text-[10px] leading-[12px] font-montserrat">
Этаж: {request.floor}
</p>
)}
</div>
{/* Кнопка отклика внизу */}
<button
type="button"
onClick={() => onAccept(request)}
className="mt-auto w-full h-[40px] bg-[#94E067] rounded-[10px] flex items-center justify-center"
>
<span className="font-montserrat font-bold text-[16px] leading-[19px] text-white">
Откликнуться
</span>
</button>
</div>
</div>
);
};
export default AcceptPopup;

View File

@@ -99,7 +99,7 @@ const HistoryRequestPage = () => {
<div className="w-8 h-8 rounded-full border border-white flex items-center justify-center">
<FaUser className="text-white text-sm" />
</div>
<p className="font-montserrat font-extrabold text-[11px] leading-[11px] text-white">
<p className="font-montserrat font-extrabold text-[20px] leading-[11px] text-white">
Александр
</p>
</div>
@@ -111,7 +111,7 @@ const HistoryRequestPage = () => {
</button>
</header>
<h1 className="font-montserrat font-extrabold text-[18px] leading-[22px] text-white mb-3">
<h1 className="font-montserrat font-extrabold text-[20px] leading-[22px] text-white mb-3">
История заявок
</h1>

202
app/mainValounter/page.jsx Normal file
View File

@@ -0,0 +1,202 @@
"use client";
import React, { useEffect, useState } from "react";
import dynamic from "next/dynamic";
import { FaUser, FaCog } from "react-icons/fa";
import TabBar from "../components/TabBar";
import AcceptPopup from "../components/acceptPopUp";
// динамический импорт карты, чтобы не падало на сервере
const MapContainer = dynamic(
() => import("react-leaflet").then((m) => m.MapContainer),
{ ssr: false }
);
const TileLayer = dynamic(
() => import("react-leaflet").then((m) => m.TileLayer),
{ ssr: false }
);
const Marker = dynamic(
() => import("react-leaflet").then((m) => m.Marker),
{ ssr: false }
);
const Popup = dynamic(
() => import("react-leaflet").then((m) => m.Popup),
{ ssr: false }
);
// центр Перми
const DEFAULT_POSITION = [58.0105, 56.2294];
const requests = [
{
id: 1,
title: "Приобрести продукты пенсионерке",
address: "г. Пермь, ул. Ленина 50, кв. 24, этаж 3",
coords: [58.0109, 56.2478], // район ул. Ленина
distance: "1.2 км",
},
{
id: 2,
title: "Приобрести медикаменты бабушке",
address: "г. Пермь, ул. Пушкина 24, кв. 12, этаж 1",
coords: [58.0135, 56.2320], // район ул. Пушкина
distance: "2.0 км",
},
{
id: 3,
title: "Сопроводить до поликлиники",
address: "г. Пермь, ул. Куйбышева 95, кв. 7, этаж 2",
coords: [58.0068, 56.2265], // район ул. Куйбышева
distance: "3.4 км",
},
{
id: 4,
title: "Сопроводить до поликлиники",
address: "г. Пермь, ул. Куйбышева 95, кв. 7, этаж 2",
coords: [58.0068, 56.2265], // район ул. Куйбышева
distance: "3.4 км",
},
{
id: 5,
title: "Сопроводить до поликлиники",
address: "г. Пермь, ул. Куйбышева 95, кв. 7, этаж 2",
coords: [58.0068, 56.2265], // район ул. Куйбышева
distance: "3.4 км",
},
];
const MainVolunteerPage = () => {
const [position, setPosition] = useState(DEFAULT_POSITION);
const [hasLocation, setHasLocation] = useState(false);
const [selectedRequest, setSelectedRequest] = useState(null);
const [isPopupOpen, setIsPopupOpen] = useState(false);
const openPopup = (req) => {
setSelectedRequest(req);
setIsPopupOpen(true);
};
const closePopup = () => {
setIsPopupOpen(false);
setSelectedRequest(null);
};
useEffect(() => {
if (!navigator.geolocation) return;
navigator.geolocation.getCurrentPosition(
(pos) => {
setPosition([pos.coords.latitude, pos.coords.longitude]);
setHasLocation(true);
},
() => {
setHasLocation(false);
}
);
}, []);
const handleAccept = (req) => {
console.log("Откликнуться на заявку:", req.id);
// TODO: запрос на бэк
};
return (
<div className="min-h-screen w-full bg-[#90D2F9] flex justify-center px-4">
<div className="relative w-full max-w-md flex flex-col pb-20 pt-4">
{/* Header */}
<header className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<div className="w-8 h-8 rounded-full border border-white flex items-center justify-center">
<FaUser className="text-white text-sm" />
</div>
<p className="font-montserrat font-extrabold text-[11px] leading-[11px] text-white">
Александр
</p>
</div>
<button
type="button"
className="w-8 h-8 rounded-full border border-white flex items-center justify-center"
>
<FaCog className="text-white text-sm" />
</button>
</header>
<h1 className="font-montserrat font-extrabold text-[16px] leading-[20px] text-white mb-2">
Кому нужна помощь
</h1>
{/* Карта */}
<div className="w-full bg-transparent mb-3">
<div className="w-full h-[250px] bg-[#D9D9D9] rounded-2xl overflow-hidden">
<MapContainer
center={position}
zoom={13}
style={{ width: "100%", height: "100%" }}
>
<TileLayer
attribution='&copy; OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{/* Маркер волонтёра */}
{hasLocation && (
<Marker position={position}>
<Popup>Вы здесь</Popup>
</Marker>
)}
{/* Маркеры заявок */}
{requests.map((req) => (
<Marker key={req.id} position={req.coords}>
<Popup>{req.title}</Popup>
</Marker>
))}
</MapContainer>
</div>
</div>
{/* Заявки ниже карты */}
<main className="space-y-3">
{requests.map((req) => (
<div
key={req.id}
className="bg-white rounded-xl px-3 py-2 flex flex-col gap-1"
onClick={() => openPopup(req)}
>
<p className="font-montserrat font-semibold text-[12px] leading-[14px] text-black">
{req.title}
</p>
<p className="font-montserrat text-[10px] text-black">
{req.address}
</p>
{req.distance && (
<p className="font-montserrat text-[9px] text-gray-500">
Расстояние: {req.distance}
</p>
)}
<button
type="button"
onClick={() => handleAccept(req)}
className="mt-2 w-full bg-[#94E067] rounded-lg py-2 flex items-center justify-center"
>
<span className="font-montserrat font-bold text-[14px] text-white">
Откликнуться
</span>
</button>
</div>
))}
</main>
<TabBar />
</div>
<AcceptPopup
request={selectedRequest}
isOpen={isPopupOpen}
onClose={closePopup}
onAccept={handleAccept}
/>
</div>
);
};
export default MainVolunteerPage;

View File

@@ -0,0 +1,153 @@
"use client";
import React, { useState } from "react";
import { useRouter } from "next/navigation";
import { FaUserCircle } from "react-icons/fa";
import TabBar from "../components/TabBar";
const ProfileSettingsPage = () => {
const router = useRouter();
const [avatarUrl, setAvatarUrl] = useState("");
const [fullName, setFullName] = useState("Иванов Александр Сергеевич");
const [birthDate, setBirthDate] = useState("1990-03-12");
const [email, setEmail] = useState("example@mail.com");
const [phone, setPhone] = useState("+7 (900) 000-00-00");
const handleSave = (e) => {
e.preventDefault();
console.log("Сохранить профиль:", {
avatarUrl,
fullName,
birthDate,
email,
phone,
});
// здесь будет запрос на бэк
};
return (
<div className="min-h-screen w-full bg-[#90D2F9] flex justify-center px-4">
<div className="relative w-full max-w-md flex flex-col pb-20 pt-4">
{/* Header */}
<header className="flex items-center mb-4">
<button
type="button"
onClick={() => router.back()}
className="text-white w-8 h-8 rounded-full flex items-center justify-center text-lg"
>
</button>
<h1 className="flex-1 text-center font-montserrat font-extrabold text-[20px] leading-[24px] text-white">
Настройки профиля
</h1>
<span className="w-8" />
</header>
{/* Карточка настроек */}
<main className="bg-white rounded-3xl p-4 flex flex-col items-center gap-4 shadow-lg">
{/* Аватар */}
<div className="flex flex-col items-center gap-2">
<div className="w-24 h-24 rounded-full bg-[#E5F3FB] flex items-center justify-center overflow-hidden">
{avatarUrl ? (
// eslint-disable-next-line @next/next/no-img-element
<img
src={avatarUrl}
alt="Аватар"
className="w-full h-full object-cover"
/>
) : (
<FaUserCircle className="text-[#72B8E2] w-20 h-20" />
)}
</div>
<label className="font-montserrat text-[12px] text-[#72B8E2] underline cursor-pointer">
Загрузить аватар
<input
type="file"
accept="image/*"
className="hidden"
onChange={(e) => {
const file = e.target.files?.[0];
if (!file) return;
const url = URL.createObjectURL(file);
setAvatarUrl(url);
}}
/>
</label>
</div>
<form onSubmit={handleSave} className="w-full flex flex-col gap-3">
{/* ФИО */}
<div className="flex flex-col gap-1">
<label className="font-montserrat text-[12px] text-black">
ФИО
</label>
<input
type="text"
value={fullName}
onChange={(e) => setFullName(e.target.value)}
className="w-full rounded-full bg-[#72B8E2] px-4 py-2 text-sm font-montserrat text-white placeholder:text-white/70 outline-none border border-transparent focus:border-white/70"
placeholder="Введите ФИО"
/>
</div>
{/* Дата рождения */}
<div className="flex flex-col gap-1">
<label className="font-montserrat text-[12px] text-black">
Дата рождения
</label>
<input
type="date"
value={birthDate}
onChange={(e) => setBirthDate(e.target.value)}
className="w-full rounded-full bg-[#72B8E2] px-4 py-2 text-sm font-montserrat text-white outline-none border border-transparent focus:border-white/70"
/>
</div>
{/* Почта */}
<div className="flex flex-col gap-1">
<label className="font-montserrat text-[12px] text-black">
Почта
</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full rounded-full bg-[#72B8E2] px-4 py-2 text-sm font-montserrat text-white placeholder:text-white/70 outline-none border border-transparent focus:border-white/70"
placeholder="example@mail.com"
/>
</div>
{/* Телефон */}
<div className="flex flex-col gap-1">
<label className="font-montserrat text-[12px] text-black">
Телефон
</label>
<input
type="tel"
value={phone}
onChange={(e) => setPhone(e.target.value)}
className="w-full rounded-full bg-[#72B8E2] px-4 py-2 text-sm font-montserrat text:white.placeholder:text-white/70 outline-none border border-transparent focus:border-white/70"
placeholder="+7 (900) 000-00-00"
/>
</div>
{/* Кнопка сохранить */}
<button
type="submit"
className="mt-2 w-full bg-[#94E067] rounded-full py-2.5 flex items-center justify-center"
>
<span className="font-montserrat font-extrabold text-[14px] text-white">
Сохранить изменения
</span>
</button>
</form>
</main>
<TabBar />
</div>
</div>
);
};
export default ProfileSettingsPage;