fix(settings): use useEffect for jira field init, fix token save UX
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled

Replace render-body setState with useEffect so field initialisation
is idiomatic React and never races with user input. Also clarifies
placeholder text: empty token field = keep current, not clear it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
kitos
2026-05-26 17:04:22 +02:00
parent 217c4c88b2
commit fd4a625760

View File

@@ -1,4 +1,4 @@
import { useState } from "react"; import { useState, useEffect } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { import {
Settings, Settings,
@@ -820,21 +820,24 @@ function ProfileSection() {
const [jiraApiToken, setJiraApiToken] = useState<string>(""); const [jiraApiToken, setJiraApiToken] = useState<string>("");
const [showToken, setShowToken] = useState(false); const [showToken, setShowToken] = useState(false);
const [dirty, setDirty] = useState(false); const [dirty, setDirty] = useState(false);
const [initialised, setInitialised] = useState(false);
// Sync from server on first load // Initialise editable fields from server on first successful load
if (me && !initialised) { useEffect(() => {
if (me) {
setJiraAccountId(me.jira_account_id ?? ""); setJiraAccountId(me.jira_account_id ?? "");
setJiraEmail(me.jira_email ?? ""); setJiraEmail(me.jira_email ?? "");
setInitialised(true); // Never pre-fill the token — we only know whether it is set, not its value
} }
// Only run when `me` transitions from undefined → data (i.e., first load)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [!!me]);
const saveMut = useMutation({ const saveMut = useMutation({
mutationFn: updateMyPreferences, mutationFn: updateMyPreferences,
onSuccess: () => { onSuccess: () => {
qc.invalidateQueries({ queryKey: ["me-prefs"] }); qc.invalidateQueries({ queryKey: ["me-prefs"] });
setDirty(false); setDirty(false);
setJiraApiToken(""); setJiraApiToken(""); // clear token field after save — it's now persisted
setToast({ msg: "Profile settings saved", type: "success" }); setToast({ msg: "Profile settings saved", type: "success" });
}, },
onError: () => setToast({ msg: "Failed to save", type: "error" }), onError: () => setToast({ msg: "Failed to save", type: "error" }),
@@ -845,9 +848,10 @@ function ProfileSection() {
jira_account_id: jiraAccountId || null, jira_account_id: jiraAccountId || null,
jira_email: jiraEmail || null, jira_email: jiraEmail || null,
}; };
// Only send token if user typed something (empty string clears it) // Only send token when the user has typed something new
if (jiraApiToken !== "") { // (empty field = "keep current token unchanged")
payload.jira_api_token = jiraApiToken; if (jiraApiToken.trim() !== "") {
payload.jira_api_token = jiraApiToken.trim();
} }
saveMut.mutate(payload); saveMut.mutate(payload);
}; };
@@ -936,7 +940,7 @@ function ProfileSection() {
setJiraApiToken(e.target.value); setJiraApiToken(e.target.value);
setDirty(true); setDirty(true);
}} }}
placeholder={me?.jira_token_set ? "•••••••••••• (leave blank to keep current)" : "Paste your Atlassian API token"} placeholder={me?.jira_token_set ? "Leave blank to keep current token" : "Paste your Atlassian API token here"}
className="w-full rounded-lg border border-gray-700 bg-gray-800 px-3 py-2 pr-10 text-sm text-gray-200 placeholder-gray-600 focus:border-cyan-500 focus:outline-none" className="w-full rounded-lg border border-gray-700 bg-gray-800 px-3 py-2 pr-10 text-sm text-gray-200 placeholder-gray-600 focus:border-cyan-500 focus:outline-none"
/> />
<button <button