{dropdownOpen && (
-
-
setDropdownOpen(false)}>Profile
-
+
+
setDropdownOpen(false)}
+ >
+ Profile
+
+
+ {/* КНОПКА SETTINGS */}
+
setDropdownOpen(false)}
+ >
+ Settings
+
+
+
+
+
)}
@@ -107,11 +133,16 @@ export const Header: React.FC = () => {
setMenuOpen(false)}>Titles
setMenuOpen(false)}>Users
setMenuOpen(false)}>About
+
{username ? (
- <>
-
setMenuOpen(false)}>Profile
-
- >
+
+ setMenuOpen(false)}>Profile
+
+ {/* SETTINGS (Mobile) */}
+ setMenuOpen(false)}>Settings
+
+
+
) : (
setMenuOpen(false)}>Login
)}
diff --git a/modules/frontend/src/pages/SettingsPage/SettingsPage.tsx b/modules/frontend/src/pages/SettingsPage/SettingsPage.tsx
index 16c7e9e..f525baf 100644
--- a/modules/frontend/src/pages/SettingsPage/SettingsPage.tsx
+++ b/modules/frontend/src/pages/SettingsPage/SettingsPage.tsx
@@ -1,154 +1,235 @@
-// import React, { useEffect, useState } from "react";
-// import { updateUser, getUsersId } from "../../api";
-// import { useNavigate } from "react-router-dom";
+import React, { useEffect, useState, useRef } from "react";
+import { updateUser, getUsersId } from "../../api";
+import { useNavigate } from "react-router-dom";
+import { useCookies } from 'react-cookie';
-// export const SettingsPage: React.FC = () => {
-// const navigate = useNavigate();
+export const SettingsPage: React.FC = () => {
+ const [cookies] = useCookies(['xsrf_token']);
+ const xsrfToken = cookies['xsrf_token'] || null;
-// const userId = Number(localStorage.getItem("user_id"));
-// const initialNickname = localStorage.getItem("user_name") || "";
-// const [mail, setMail] = useState("");
-// const [nickname, setNickname] = useState(initialNickname);
-// const [dispName, setDispName] = useState("");
-// const [userDesc, setUserDesc] = useState("");
-// const [avatarId, setAvatarId] = useState
(null);
+ const navigate = useNavigate();
+ const fileInputRef = useRef(null);
-// const [loading, setLoading] = useState(false);
-// const [success, setSuccess] = useState(null);
-// const [error, setError] = useState(null);
+ const userId = Number(localStorage.getItem("user_id"));
+
+ // Состояния для полей формы
+ const [mail, setMail] = useState("");
+ const [nickname, setNickname] = useState("");
+ const [dispName, setDispName] = useState("");
+ const [userDesc, setUserDesc] = useState("");
+ const [avatarId, setAvatarId] = useState(null);
+ const [avatarUrl, setAvatarUrl] = useState(null);
-// useEffect(() => {
-// const fetch = async () => {
-// const res = await getUsersId({
-// path: { user_id: String(userId) },
-// });
+ const [loading, setLoading] = useState(false);
+ const [uploading, setUploading] = useState(false);
+ const [success, setSuccess] = useState(null);
+ const [error, setError] = useState(null);
-// setProfile(res.data);
-// };
+ // Загружаем текущие данные пользователя при входе
+ useEffect(() => {
+ const fetchUserData = async () => {
+ try {
+ const res = await getUsersId({
+ path: { user_id: String(userId) },
+ });
+ if (res.data) {
+ setMail(res.data.mail || "");
+ setNickname(res.data.nickname || "");
+ setDispName(res.data.disp_name || "");
+ setUserDesc(res.data.user_desc || "");
+ setAvatarId(res.data.image?.id ?? null);
+ const path = res.data.image?.image_path;
+ setAvatarUrl(path ? (path.startsWith('http') ? path : `/media/${path}`) : null);
+ }
+ } catch (err) {
+ console.error("Failed to fetch user:", err);
+ }
+ };
+ fetchUserData();
+ }, [userId]);
-// fetch();
-// }, [userId]);
+ // Обработка загрузки файла
+const handleFileChange = async (e: React.ChangeEvent) => {
+ const file = e.target.files?.[0];
+ if (!file) return;
-// const saveSettings = async (e: React.FormEvent) => {
-// e.preventDefault();
-// setLoading(true);
-// setSuccess(null);
-// setError(null);
+ setUploading(true);
+ setError(null);
+ setSuccess(null);
-// try {
-// const res = await updateUser({
-// path: { user_id: userId },
-// body: {
-// ...(mail ? { mail } : {}),
-// ...(nickname ? { nickname } : {}),
-// ...(dispName ? { disp_name: dispName } : {}),
-// ...(userDesc ? { user_desc: userDesc } : {}),
-// ...(avatarId !== undefined ? { avatar_id: avatarId } : {}),
-// }
-// });
+ const formData = new FormData();
+ formData.append("image", file);
-// // Обновляем локальное отображение username
-// if (nickname) {
-// localStorage.setItem("user_name", nickname);
-// window.dispatchEvent(new Event("storage")); // чтобы Header обновился
-// }
+ try {
+ // 1. Загружаем файл на сервер (POST)
+ const uploadRes = await fetch("/api/v1/media/upload", {
+ method: "POST",
+ body: formData,
+ headers: {
+ "X-XSRF-TOKEN": xsrfToken || "",
+ },
+ });
-// setSuccess("Settings updated!");
-// setTimeout(() => navigate("/profile"), 800);
+ if (!uploadRes.ok) throw new Error("Failed to upload image to storage");
-// } catch (err: any) {
-// console.error(err);
-// setError(err?.message || "Failed to update settings");
-// } finally {
-// setLoading(false);
-// }
-// };
+ const uploadData = await uploadRes.json();
+ const newAvatarId = uploadData.id;
-// return (
-//
-//
User Settings
+ if (newAvatarId) {
+ // 2. СРАЗУ отправляем PATCH запрос для обновления профиля пользователя
+ await updateUser({
+ path: { user_id: userId },
+ body: {
+ avatar_id: newAvatarId, // Привязываем новый ID к юзеру
+ },
+ headers: { "X-XSRF-TOKEN": xsrfToken },
+ });
-// {success &&
{success}
}
-// {error &&
{error}
}
+ // 3. Обновляем локальный стейт для отображения
+ setAvatarId(newAvatarId);
+ const path = uploadData.image_path;
+ setAvatarUrl(path ? (path.startsWith("/") ? path : `/media/${path}`) : null);
+
+ setSuccess("Avatar updated successfully!");
+ }
+ } catch (err: any) {
+ console.error("Upload & Patch error:", err);
+ setError(err.message || "Failed to update avatar");
+ } finally {
+ setUploading(false);
+ }
+};
-//
+
+ );
+};
\ No newline at end of file
diff --git a/modules/frontend/src/pages/TitlesPage/TitlesPage.tsx b/modules/frontend/src/pages/TitlesPage/TitlesPage.tsx
index 727e072..b5fb266 100644
--- a/modules/frontend/src/pages/TitlesPage/TitlesPage.tsx
+++ b/modules/frontend/src/pages/TitlesPage/TitlesPage.tsx
@@ -9,6 +9,7 @@ import { getTitles, type CursorObj, type Title, type TitleSort } from "../../api
import { LayoutSwitch } from "../../components/LayoutSwitch/LayoutSwitch";
import { Link } from "react-router-dom";
import { type TitlesFilter, TitlesFilterPanel } from "../../components/TitlesFilterPanel/TitlesFilterPanel";
+import { authClient } from "../../api/AuthClient/AuthClient";
const PAGE_SIZE = 10;
@@ -51,7 +52,12 @@ export default function TitlesPage() {
offset: PAGE_SIZE,
fields: "all",
},
- });
+ client: authClient,
+ },);
+
+ if (response.response.status === 403) {
+
+ }
return {
items: response.data?.data ?? [],
diff --git a/modules/frontend/src/pages/UserPage/UserPage.tsx b/modules/frontend/src/pages/UserPage/UserPage.tsx
index 1a8ba1e..91c263f 100644
--- a/modules/frontend/src/pages/UserPage/UserPage.tsx
+++ b/modules/frontend/src/pages/UserPage/UserPage.tsx
@@ -155,7 +155,7 @@ export default function UserPage({ userId }: UserPageProps) {
{errorUser && {errorUser}
}
{user && (
-

+
{user.disp_name || user.nickname}
{user.mail &&
{user.mail}
}
{user.user_desc &&
{user.user_desc}
}