WIP API
This commit is contained in:
@@ -1,31 +1,146 @@
|
||||
"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 API_BASE = process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
|
||||
const ValounterProfileSettingsPage = () => {
|
||||
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[file:519]
|
||||
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,
|
||||
// email обычно не меняют через этот эндпоинт, но если бек разрешает — можно добавить
|
||||
};
|
||||
|
||||
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 +161,124 @@ 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">
|
||||
{/* ФИО -> first_name + last_name */}
|
||||
<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 />
|
||||
|
||||
Reference in New Issue
Block a user