Files
fullofempt 433b9e896c WIP API
2025-12-14 18:47:14 +05:00

337 lines
12 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
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 ProfileSettingsPage = () => {
const router = useRouter();
const [avatarUrl, setAvatarUrl] = useState("");
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [email, setEmail] = useState("");
const [phone, setPhone] = useState("");
const [address, setAddress] = useState("");
const [city, setCity] = useState("");
const [bio, setBio] = useState("");
const [loading, setLoading] = useState(true);
const [saveLoading, setSaveLoading] = useState(false);
const [error, setError] = useState("");
const [success, setSuccess] = useState("");
useEffect(() => {
const fetchProfile = async () => {
if (!API_BASE) {
setError("API_BASE_URL не задан");
setLoading(false);
return;
}
const saved =
typeof window !== "undefined"
? localStorage.getItem("authUser")
: null;
const authUser = saved ? JSON.parse(saved) : null;
const accessToken = authUser?.accessToken;
if (!accessToken) {
setError("Вы не авторизованы");
setLoading(false);
return;
}
try {
const res = await fetch(`${API_BASE}/users/me`, {
headers: {
Accept: "application/json",
Authorization: `Bearer ${accessToken}`,
},
});
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 || "");
setAddress(data.address || "");
setCity(data.city || "");
setBio(data.bio || "");
setAvatarUrl(data.avatar_url || "");
setLoading(false);
} catch (e) {
setError(e.message || "Ошибка сети");
setLoading(false);
}
};
fetchProfile();
}, []);
const handleSave = async (e) => {
e.preventDefault();
if (!API_BASE) return;
setError("");
setSuccess("");
setSaveLoading(true);
const saved =
typeof window !== "undefined"
? localStorage.getItem("authUser")
: null;
const authUser = saved ? JSON.parse(saved) : null;
const accessToken = authUser?.accessToken;
if (!accessToken) {
setError("Вы не авторизованы");
setSaveLoading(false);
return;
}
const body = {
first_name: firstName || undefined,
last_name: lastName || undefined,
phone: phone || undefined,
bio: bio || undefined,
address: address || undefined,
city: city || undefined,
};
try {
const res = await fetch(`${API_BASE}/users/me`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
Authorization: `Bearer ${accessToken}`,
},
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);
setSaveLoading(false);
return;
}
setSuccess("Профиль успешно сохранён");
setSaveLoading(false);
} catch (err) {
setError(err.message || "Ошибка сети");
setSaveLoading(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">
{/* 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">
{error && (
<p className="w-full text-center text-xs font-montserrat text-red-500">
{error}
</p>
)}
{success && (
<p className="w-full text-center text-xs font-montserrat text-green-600">
{success}
</p>
)}
{loading && !error && (
<p className="w-full text-center text-xs font-montserrat text-black">
Загрузка профиля...
</p>
)}
{/* Аватар (пока локально, без API) */}
<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={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="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}
disabled
className="w-full rounded-full bg-[#72B8E2] px-4 py-2 text-sm font-montserrat text-white opacity-70 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="text"
value={address}
onChange={(e) => setAddress(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="text"
value={city}
onChange={(e) => setCity(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>
<textarea
value={bio}
onChange={(e) => setBio(e.target.value)}
rows={3}
className="w-full rounded-3xl 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 resize-none"
placeholder="Расскажите о себе"
/>
</div>
{/* Кнопка сохранить */}
<button
type="submit"
disabled={saveLoading}
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">
{saveLoading ? "Сохранение..." : "Сохранить изменения"}
</span>
</button>
</form>
</main>
<TabBar />
</div>
</div>
);
};
export default ProfileSettingsPage;