Backfill start-window indicator, account-delete spinner, app-password Help popup

- Backfill: show the sidebar spinner + "Preparing…" banner during the brief window
  after account creation when state=backfilling but total is not yet counted
  (total===0), so a freshly-added account shows progress immediately instead of
  looking done until a refresh. The stuck-at-100% guard is preserved.
- Settings: a centered spinner overlay ("Removing account…") covers the screen from
  confirm until the (slow) server cascade + accounts refetch complete, so the row
  no longer appears frozen before it disappears.
- Add-account wizard: the manual password hint now includes Outlook and gains a
  "Help" link opening a popup with per-provider direct links to enable 2-step
  verification and generate the app password (Gmail, Outlook, Yahoo, iCloud).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U72FZiF1W1pb8XxyWBTREA
This commit is contained in:
2026-08-07 07:59:26 +02:00
co-authored by Claude Opus 4.8
parent 12a67825cc
commit 719eeef378
5 changed files with 144 additions and 18 deletions
+23 -2
View File
@@ -251,9 +251,15 @@ function AccountsSection() {
const [editing, setEditing] = useState<{ id: string; mode: "account" | "signature" } | null>(
null,
);
// Which account is being deleted — drives the blocking spinner overlay until the
// (slow) server cascade finishes and the refetched list no longer includes it.
const [deletingId, setDeletingId] = useState<string | null>(null);
const remove = useMutation({
mutationFn: (id: string) => api.deleteAccount(id),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["accounts"] }),
onSettled: async () => {
await queryClient.invalidateQueries({ queryKey: ["accounts"] });
setDeletingId(null);
},
});
const downloadAll = useMutation({
mutationFn: (id: string) => api.updateAccount(id, { sync_window: "all" }),
@@ -325,7 +331,10 @@ function AccountsSection() {
<button
className="text-xs text-red-500"
onClick={() => {
if (confirm(t("settings.confirmRemoveAccount"))) remove.mutate(a.id);
if (confirm(t("settings.confirmRemoveAccount"))) {
setDeletingId(a.id);
remove.mutate(a.id);
}
}}
>
{t("common.remove")}
@@ -336,6 +345,18 @@ function AccountsSection() {
</li>
))}
</ul>
{deletingId && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
<div className="flex flex-col items-center gap-3 rounded-xl bg-app-elevated px-8 py-6 shadow-2xl">
<span
className="h-8 w-8 animate-spin rounded-full border-2 border-app-border border-t-app-accent"
role="status"
aria-label={t("settings.removingAccount")}
/>
<span className="text-sm text-app-muted">{t("settings.removingAccount")}</span>
</div>
</div>
)}
</section>
);
}