v3: added onboarding email on account creation

This commit is contained in:
khannurien
2026-03-30 14:55:30 +00:00
parent cbb3505139
commit 378b3ffa46
27 changed files with 404 additions and 59 deletions

View File

@@ -233,6 +233,11 @@ export function UserPublicProfile() {
const [descDraft, setDescDraft] = useState("");
const [descSaving, setDescSaving] = useState(false);
const [descError, setDescError] = useState<string | null>(null);
const [emailEditing, setEmailEditing] = useState(false);
const [emailDraft, setEmailDraft] = useState("");
const [emailSaving, setEmailSaving] = useState(false);
const [emailError, setEmailError] = useState<string | null>(null);
const prevMyVotesRef = useRef<Set<string> | null>(null);
useEffect(() => {
@@ -507,6 +512,36 @@ export function UserPublicProfile() {
}
};
const handleEmailSave = async () => {
if (state.status !== "loaded") return;
setEmailSaving(true);
setEmailError(null);
try {
const res = await authFetch(`${API_URL}/api/users/me`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(
{ email: emailDraft.trim() } satisfies UpdateUserRequest,
),
});
const body = parseAPIResponse<RawPublicUser>(await res.json());
if (!body.success) {
setEmailError(body.error.message);
return;
}
const storedRaw = localStorage.getItem("authResponse");
if (storedRaw) {
const prev = deserializeAuthResponse(JSON.parse(storedRaw));
login({ ...prev, user: { ...prev.user, email: emailDraft.trim() } });
}
setEmailEditing(false);
} catch {
setEmailError("Failed to save");
} finally {
setEmailSaving(false);
}
};
const handleDescSave = async () => {
if (state.status !== "loaded") return;
setDescSaving(true);
@@ -584,7 +619,7 @@ export function UserPublicProfile() {
userId={profileUser.id}
username={profileUser.username}
hasAvatar={!!profileUser.avatarMime}
size={72}
size={128}
version={profileUser.updatedAt?.getTime()}
/>
{isOwnProfile && (
@@ -620,6 +655,66 @@ export function UserPublicProfile() {
O.G.
</p>
)}
{isOwnProfile && (
emailEditing
? (
<form
className="profile-email-editor"
onSubmit={(e) => {
e.preventDefault();
handleEmailSave();
}}
>
<input
type="email"
className="profile-email-input"
value={emailDraft}
onChange={(e) => setEmailDraft(e.currentTarget.value)}
onKeyDown={(e) => {
if (e.key === "Escape") setEmailEditing(false);
}}
disabled={emailSaving}
autoFocus
/>
<div className="profile-email-actions">
<button
type="submit"
className="profile-email-btn profile-email-btn--save"
disabled={emailSaving || !emailDraft.trim()}
>
{emailSaving ? "Saving…" : "Save"}
</button>
<button
type="button"
className="profile-email-btn profile-email-btn--cancel"
onClick={() => setEmailEditing(false)}
disabled={emailSaving}
>
Cancel
</button>
</div>
{emailError && (
<ErrorCard title="Failed to save" message={emailError} />
)}
</form>
)
: (
<p
className="profile-email-display"
onClick={() => {
setEmailDraft(me?.email ?? "");
setEmailError(null);
setEmailEditing(true);
}}
title="Edit email"
>
{me?.email ?? "Add email…"}
<span className="profile-description-edit-btn" aria-hidden>
</span>
</p>
)
)}
{avatarError && (
<ErrorCard title="Failed to update avatar" message={avatarError} />
)}