import { useState, type FormEvent } from "react"; import { useNavigate, useParams } from "react-router"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { api, errMsg } from "../api"; import { useAuthChanged, useMe } from "../auth"; import { avatarUrl } from "../format"; import { Feed } from "../components/Feed"; import type { Profile } from "../types"; // Beim Wechsel zu einem anderen Profil frisch anfangen (Meldungen, Formulare). export function UserPage() { const { username = "" } = useParams(); return ; } function ProfileView({ username }: { username: string }) { const profile = useQuery({ queryKey: ["profile", username], queryFn: () => api.get(`/u/${encodeURIComponent(username)}/info`), }); const me = useMe().data; // Nach einem Upload das neue Bild sofort zeigen: Zeitstempel am URL // umgeht den Browser-Cache. const [avatarBust, setAvatarBust] = useState(""); const info = profile.data; let name = ""; if (profile.isError) name = errMsg(profile.error, "Profil konnte nicht geladen werden."); else if (info) name = info.username; const isOwner = !!me && !!info && me.username === info.username; return ( {name} {info && `Mitglied seit ${new Date(info.created_at * 1000).toLocaleDateString("de-DE")}`} {isOwner && setAvatarBust(`?t=${Date.now()}`)} />} {info && ( `/u/${encodeURIComponent(username)}/feed/${page}`} /> )} ); } // useFormAction verdrahtet ein Einstellungsformular: POST auf path, bei Erfolg // onSuccess mit Formular und Antwort, bei Fehler die Meldung unter dem Formular. function useFormAction(path: string, fallback: string, onSuccess: (form: HTMLFormElement, data: T) => void) { const m = useMutation({ mutationFn: (fd: FormData) => api.post(path, fd) }); const onSubmit = (e: FormEvent) => { e.preventDefault(); const form = e.currentTarget; m.mutate(new FormData(form), { onSuccess: (data) => onSuccess(form, data) }); }; return { onSubmit, busy: m.isPending, msg: m.isError ? errMsg(m.error, fallback) : "" }; } // Settings: nur für den Eigentümer. Die {" "} bilden die Leerzeichen des alten // Markups nach -- Inputs und Knöpfe sind inline. function Settings({ onAvatar }: { onAvatar: () => void }) { const qc = useQueryClient(); const navigate = useNavigate(); const authChanged = useAuthChanged(); // Neues Bild und neuer Name tauchen auch in den Beitragskarten auf -> die // Feeds und das eigene Konto neu laden. const refresh = () => { qc.invalidateQueries({ queryKey: ["feed"] }); qc.invalidateQueries({ queryKey: ["thread"] }); qc.invalidateQueries({ queryKey: ["profile"] }); return authChanged(); }; const avatar = useFormAction<{ avatar: string }>("/user/avatar", "Hochladen fehlgeschlagen.", (form) => { form.reset(); onAvatar(); refresh(); }); const rename = useFormAction<{ username: string }>("/user/rename", "Umbenennen fehlgeschlagen.", async (_form, data) => { await refresh(); navigate(`/u/${encodeURIComponent(data.username)}`); }); const del = useFormAction("/user/delete", "Löschen fehlgeschlagen.", async () => { await refresh(); navigate("/"); }); return ( <> Einstellungen Profilbild ändern {" "} Bild hochladen {avatar.msg} Name ändern {" "} Namen ändern {rename.msg} Konto löschen { if (confirm("Konto wirklich unwiderruflich löschen?")) del.onSubmit(e); else e.preventDefault(); }} > Löscht dein Konto samt Beiträgen und Votes unwiderruflich.{" "} {" "} Konto endgültig löschen {del.msg} > ); }
{info && `Mitglied seit ${new Date(info.created_at * 1000).toLocaleDateString("de-DE")}`}
{avatar.msg}
{rename.msg}
Löscht dein Konto samt Beiträgen und Votes unwiderruflich.
{del.msg}