WIPVOLONT

This commit is contained in:
fullofempt
2025-12-14 21:14:55 +05:00
parent 433b9e896c
commit 0df52352a8
12 changed files with 893 additions and 440 deletions

View File

@@ -1,31 +1,145 @@
"use client";
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { FaUserCircle } from "react-icons/fa";
import TabBar from "../components/TabBar";
const ValounterProfileSettingsPage = () => {
const API_BASE = process.env.NEXT_PUBLIC_API_BASE_URL;
const ModeratorProfileSettingsPage = () => {
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 [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [email, setEmail] = useState("");
const [phone, setPhone] = useState("");
const handleSave = (e) => {
e.preventDefault();
console.log("Сохранить профиль:", {
avatarUrl,
fullName,
birthDate,
email,
phone,
});
// здесь будет запрос на бэк
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [error, setError] = useState("");
const [saveMessage, setSaveMessage] = useState("");
const getAccessToken = () => {
if (typeof window === "undefined") return null;
const saved = localStorage.getItem("authUser");
const authUser = saved ? JSON.parse(saved) : null;
return authUser?.accessToken || null;
};
// загрузить профиль
useEffect(() => {
const fetchProfile = async () => {
if (!API_BASE) {
setError("API_BASE_URL не задан");
setLoading(false);
return;
}
const token = getAccessToken();
if (!token) {
setError("Вы не авторизованы");
setLoading(false);
return;
}
try {
const res = await fetch(`${API_BASE}/users/me`, {
headers: {
Accept: "application/json",
Authorization: `Bearer ${token}`,
},
});
if (!res.ok) {
let msg = "Не удалось загрузить профиль";
try {
const data = await res.json();
if (data.error) msg = data.error;
} catch {
const text = await res.text();
if (text) msg = text;
}
setError(msg);
setLoading(false);
return;
}
const data = await res.json(); // UserProfile[web:598]
setFirstName(data.first_name || "");
setLastName(data.last_name || "");
setEmail(data.email || "");
setPhone(data.phone || "");
setAvatarUrl(data.avatar_url || "");
setLoading(false);
} catch (e) {
setError(e.message || "Ошибка сети");
setLoading(false);
}
};
fetchProfile();
}, []);
const handleSave = async (e) => {
e.preventDefault();
if (!API_BASE) return;
const token = getAccessToken();
if (!token) {
setError("Вы не авторизованы");
return;
}
try {
setSaving(true);
setError("");
setSaveMessage("");
const body = {
first_name: firstName || null,
last_name: lastName || null,
phone: phone || null,
}; // UpdateProfileInput[web:598]
const res = await fetch(`${API_BASE}/users/me`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(body),
});
if (!res.ok) {
let msg = "Не удалось сохранить профиль";
try {
const data = await res.json();
if (data.error) msg = data.error;
} catch {
const text = await res.text();
if (text) msg = text;
}
setError(msg);
setSaving(false);
return;
}
const updated = await res.json();
setSaveMessage("Изменения сохранены");
setSaving(false);
setFirstName(updated.first_name || "");
setLastName(updated.last_name || "");
setPhone(updated.phone || "");
} catch (e) {
setError(e.message || "Ошибка сети");
setSaving(false);
}
};
const fullName = [firstName, lastName].filter(Boolean).join(" ");
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">
@@ -46,102 +160,128 @@ const ValounterProfileSettingsPage = () => {
{/* Карточка настроек */}
<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" />
{loading && (
<p className="font-montserrat text-[14px] text-black">
Загрузка профиля...
</p>
)}
{!loading && (
<>
{/* Аватар */}
<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>
{error && (
<p className="font-montserrat text-[12px] text-red-500">
{error}
</p>
)}
{saveMessage && (
<p className="font-montserrat text-[12px] text-green-600">
{saveMessage}
</p>
)}
</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>
<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={firstName}
onChange={(e) => setFirstName(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="text"
value={lastName}
onChange={(e) => setLastName(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="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="email"
value={email}
disabled
className="w-full rounded-full bg-[#72B8E2]/60 px-4 py-2 text-sm font-montserrat text-white placeholder:text-white/70 outline-none border border-transparent cursor-not-allowed"
/>
</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>
{/* Телефон */}
<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>
{/* Кнопка сохранить */}
<button
type="submit"
disabled={saving}
className="mt-2 w-full bg-[#94E067] rounded-full py-2.5 flex items-center justify-center disabled:opacity-60"
>
<span className="font-montserrat font-extrabold text-[14px] text-white">
{saving ? "Сохранение..." : "Сохранить изменения"}
</span>
</button>
</form>
</>
)}
</main>
<TabBar />
@@ -150,4 +290,4 @@ const ValounterProfileSettingsPage = () => {
);
};
export default ValounterProfileSettingsPage;
export default ModeratorProfileSettingsPage;