import { useState, useRef } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; import { useForm } from "react-hook-form"; import { useQueryClient } from "@tanstack/react-query"; import { FiArrowLeft, FiUser, FiLock, FiCamera, FiImage } from "react-icons/fi"; import { useAuth } from "@/context/AuthContext"; import { privateApi } from "@/api/privateClient"; import { mediaUrl } from "@/utils/mediaUrl"; import Avatar from "@/components/ui/Avatar"; import Button from "@/components/ui/Button"; import Spinner from "@/components/ui/Spinner"; import FormErrorBanner from "@/components/ui/FormErrorBanner"; import { applyServerErrors } from "@/utils/formErrors"; type Tab = "profile" | "security"; interface ProfileForm { first_name: string; last_name: string; city: string; phone_number: string; } interface PasswordForm { current_password: string; new_password: string; confirm_password: string; } export default function AccountSettingsPage() { const { t } = useTranslation("social"); const { user, refreshUser } = useAuth() as any; const navigate = useNavigate(); const queryClient = useQueryClient(); const [tab, setTab] = useState("profile"); // ── Profile form ────────────────────────────────────────────── const [profileSuccess, setProfileSuccess] = useState(false); const [profileRootError, setProfileRootError] = useState(); const profileForm = useForm({ defaultValues: { first_name: user?.first_name ?? "", last_name: user?.last_name ?? "", city: user?.city ?? "", phone_number: user?.phone_number ?? "", }, }); const { register: regProfile, handleSubmit: handleProfile, formState: { isSubmitting: profileSubmitting }, setError: setProfileError } = profileForm; async function onProfileSubmit(values: ProfileForm) { setProfileRootError(undefined); setProfileSuccess(false); try { await privateApi.patch(`/api/account/users/${user.id}/`, values); setProfileSuccess(true); await queryClient.invalidateQueries({ queryKey: ["account"] }); if (refreshUser) await refreshUser(); } catch (err) { setProfileRootError(applyServerErrors(profileForm, err)); } } // ── Avatar upload ───────────────────────────────────────────── const avatarInputRef = useRef(null); const [avatarPreview, setAvatarPreview] = useState(null); const [avatarUploading, setAvatarUploading] = useState(false); async function handleAvatarChange(e: React.ChangeEvent) { const file = e.target.files?.[0]; if (!file) return; setAvatarPreview(URL.createObjectURL(file)); setAvatarUploading(true); try { const fd = new FormData(); fd.append("avatar", file); await privateApi.patch(`/api/account/users/${user.id}/`, fd); await queryClient.invalidateQueries({ queryKey: ["account"] }); if (refreshUser) await refreshUser(); } finally { setAvatarUploading(false); e.target.value = ""; } } // ── Banner upload ───────────────────────────────────────────── const bannerInputRef = useRef(null); const [bannerPreview, setBannerPreview] = useState(null); const [bannerUploading, setBannerUploading] = useState(false); async function handleBannerChange(e: React.ChangeEvent) { const file = e.target.files?.[0]; if (!file) return; setBannerPreview(URL.createObjectURL(file)); setBannerUploading(true); try { const fd = new FormData(); fd.append("banner", file); await privateApi.patch(`/api/account/users/${user.id}/`, fd); await queryClient.invalidateQueries({ queryKey: ["account"] }); if (refreshUser) await refreshUser(); } finally { setBannerUploading(false); e.target.value = ""; } } // ── Password form ───────────────────────────────────────────── const [passwordSuccess, setPasswordSuccess] = useState(false); const [passwordRootError, setPasswordRootError] = useState(); const passwordForm = useForm({ defaultValues: { current_password: "", new_password: "", confirm_password: "" }, }); const { register: regPassword, handleSubmit: handlePassword, formState: { isSubmitting: passwordSubmitting }, reset: resetPassword, setError: setPasswordError } = passwordForm; async function onPasswordSubmit(values: PasswordForm) { setPasswordRootError(undefined); setPasswordSuccess(false); if (values.new_password !== values.confirm_password) { setPasswordError("confirm_password", { message: "Hesla se neshodují." }); return; } try { await privateApi.post("/api/account/password-change/", { current_password: values.current_password, new_password: values.new_password, }); setPasswordSuccess(true); resetPassword(); } catch (err) { setPasswordRootError(applyServerErrors(passwordForm, err)); } } const displayName = [user?.first_name, user?.last_name].filter(Boolean).join(" ") || user?.username || "?"; const avatarSrc = avatarPreview ?? mediaUrl((user as any)?.avatar); const bannerSrc = bannerPreview ?? mediaUrl((user as any)?.banner); const tabClass = (active: boolean) => [ "flex items-center gap-2 rounded-xl px-3 py-2 text-sm font-medium transition-colors", active ? "bg-brand-lines/15 text-brand-text" : "text-brand-text/60 hover:bg-brand-lines/10 hover:text-brand-text", ].join(" "); const inputClass = "w-full rounded-xl border border-brand-lines/25 bg-brand-bgLight/40 px-3 py-2 text-sm text-brand-text placeholder:text-brand-text/30 focus:outline-none focus:border-brand-accent disabled:opacity-50"; return (

Nastavení účtu

{/* Sidebar tabs */} {/* Content */}
{/* ── Profile tab ── */} {tab === "profile" && (
{/* Appearance: banner + avatar */}
Vzhled
{/* Banner */}
bannerInputRef.current?.click()} > {bannerSrc && ( )}
{bannerUploading ? ( ) : (
Změnit banner
)}
{/* Avatar overlapping banner bottom-left */}
{avatarUploading && (
)}

JPG, PNG nebo WebP · Banner max. 5 MB · Avatar max. 5 MB

{/* Profile form */}
{profileSuccess && (
Profil byl uložen.
)}
)} {/* ── Security tab ── */} {tab === "security" && (
Změna hesla
{passwordSuccess && (
Heslo bylo změněno.
)}
{passwordForm.formState.errors.current_password && (

{passwordForm.formState.errors.current_password.message}

)}
{passwordForm.formState.errors.new_password && (

{passwordForm.formState.errors.new_password.message}

)}
{passwordForm.formState.errors.confirm_password && (

{passwordForm.formState.errors.confirm_password.message}

)}
)}
); }