v3: added user profile description

This commit is contained in:
khannurien
2026-03-22 21:07:17 +00:00
parent c5051e3485
commit d94a319d96
10 changed files with 227 additions and 26 deletions

View File

@@ -34,6 +34,8 @@ import { DumpCreateModal } from "../components/DumpCreateModal.tsx";
import { FollowUserButton } from "../components/FollowButton.tsx";
import { ErrorCard } from "../components/ErrorCard.tsx";
import { friendlyFetchError } from "../utils/apiError.ts";
import { TextEditor } from "../components/TextEditor.tsx";
import { Markdown } from "../components/Markdown.tsx";
const PAGE_SIZE = 20;
@@ -246,6 +248,11 @@ export function UserPublicProfile() {
const [uploading, setUploading] = useState(false);
const [avatarError, setAvatarError] = useState<string | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
const [descEditing, setDescEditing] = useState(false);
const [descDraft, setDescDraft] = useState("");
const [descSaving, setDescSaving] = useState(false);
const [descError, setDescError] = useState<string | null>(null);
const prevMyVotesRef = useRef<Set<string> | null>(null);
useEffect(() => {
@@ -515,6 +522,36 @@ export function UserPublicProfile() {
}
};
const handleDescSave = async () => {
if (state.status !== "loaded") return;
setDescSaving(true);
setDescError(null);
try {
const res = await authFetch(`${API_URL}/api/users/me`, {
method: "PATCH",
body: JSON.stringify({ description: descDraft.trim() }),
});
const body = await res.json();
if (!res.ok || !body.success) {
setDescError(body.error?.message ?? "Failed to save");
return;
}
setState((s) =>
s.status === "loaded"
? {
...s,
user: { ...s.user, description: descDraft.trim() || undefined },
}
: s
);
setDescEditing(false);
} catch {
setDescError("Failed to save");
} finally {
setDescSaving(false);
}
};
if (state.status === "loading") {
return (
<PageShell>
@@ -613,6 +650,76 @@ export function UserPublicProfile() {
</div>
</div>
{(profileUser.description || isOwnProfile) && (
<div className="profile-description">
{descEditing
? (
<div className="profile-description-editor">
<TextEditor
className="comment-reply-textarea"
value={descDraft}
onChange={setDescDraft}
onSubmit={handleDescSave}
placeholder="Tell people about yourself…"
autoResize
/>
<div className="profile-description-actions">
<button
type="button"
className="btn-primary"
onClick={handleDescSave}
disabled={descSaving}
>
{descSaving ? "Saving…" : "Save"}
</button>
<button
type="button"
className="btn-border"
onClick={() => setDescEditing(false)}
disabled={descSaving}
>
Cancel
</button>
{descError && (
<ErrorCard title="Failed to save" message={descError} />
)}
</div>
</div>
)
: (
<div
className={`profile-description-view${
isOwnProfile ? " profile-description-view--editable" : ""
}`}
onClick={isOwnProfile
? () => {
setDescDraft(profileUser.description ?? "");
setDescError(null);
setDescEditing(true);
}
: undefined}
>
{profileUser.description
? (
<Markdown className="profile-description-text">
{profileUser.description}
</Markdown>
)
: (
<div className="profile-description-empty">
Add a bio
</div>
)}
{isOwnProfile && (
<span className="profile-description-edit-btn" aria-hidden>
</span>
)}
</div>
)}
</div>
)}
<div className="profile-columns">
<DumpList
title={`Dumps (${dumps.items.length}${dumps.hasMore ? "+" : ""})`}