fix(account-settings): clear stale pw error on input + live header avatar update
Tessera CI/CD / Lint & Type Check (push) Successful in 44s
Tessera CI/CD / Tests (push) Successful in 37s
Tessera CI/CD / Build & Publish Images (push) Successful in 1m34s

- Password form errors (wrong pw, mismatch) now clear on first keystroke
  in any password field instead of persisting until next submit.
- Avatar upload now bumps avatarVersion in auth store and sets hasAvatar=true,
  so the header avatar switches to the uploaded image immediately without reload.
- Header img src uses ?v={avatarVersion} as cache-buster to force browser to
  fetch the new avatar when version increments.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-30 15:29:21 +02:00
parent 85cd17452c
commit 88db54fa7d
3 changed files with 14 additions and 3 deletions
+2 -2
View File
@@ -13,7 +13,7 @@ export function Header() {
const t = useTranslations(); const t = useTranslations();
const tHeader = useTranslations('header'); const tHeader = useTranslations('header');
const setMobileOpen = useSidebarStore((s) => s.setMobileOpen); const setMobileOpen = useSidebarStore((s) => s.setMobileOpen);
const { user, setUser } = useAuthStore(); const { user, setUser, avatarVersion } = useAuthStore();
const [dropdownOpen, setDropdownOpen] = useState(false); const [dropdownOpen, setDropdownOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null); const dropdownRef = useRef<HTMLDivElement>(null);
const pathname = usePathname(); const pathname = usePathname();
@@ -112,7 +112,7 @@ export function Header() {
> >
{user?.hasAvatar && !avatarError ? ( {user?.hasAvatar && !avatarError ? (
<img <img
src="/api-proxy/users/me/avatar" src={`/api-proxy/users/me/avatar?v=${avatarVersion}`}
alt="" alt=""
className="h-8 w-8 rounded-full object-cover" className="h-8 w-8 rounded-full object-cover"
onError={() => setAvatarError(true)} onError={() => setAvatarError(true)}
@@ -7,10 +7,12 @@ import {
changePasswordAction, changePasswordAction,
uploadAvatarAction, uploadAvatarAction,
} from '@/lib/auth-actions'; } from '@/lib/auth-actions';
import { useAuthStore } from '@/lib/stores/auth-store';
export function AccountSettingsForm() { export function AccountSettingsForm() {
const t = useTranslations('settings'); const t = useTranslations('settings');
const tAuth = useTranslations('auth'); const tAuth = useTranslations('auth');
const { user, setUser, bumpAvatarVersion } = useAuthStore();
const [isLocalUser, setIsLocalUser] = useState<boolean | null>(null); const [isLocalUser, setIsLocalUser] = useState<boolean | null>(null);
const [username, setUsername] = useState<string>(''); const [username, setUsername] = useState<string>('');
@@ -80,8 +82,10 @@ export function AccountSettingsForm() {
if (result.success) { if (result.success) {
setAvatarSuccess(true); setAvatarSuccess(true);
setHasAvatar(true); setHasAvatar(true);
// Cache-bust the avatar image to force reload
setAvatarKey((k) => k + 1); setAvatarKey((k) => k + 1);
// Sync to auth store so header avatar updates immediately
bumpAvatarVersion();
if (user) setUser({ ...user, hasAvatar: true });
} else { } else {
setAvatarError(result.error ?? 'uploadError'); setAvatarError(result.error ?? 'uploadError');
} }
@@ -185,6 +189,7 @@ export function AccountSettingsForm() {
type="password" type="password"
required required
autoComplete="current-password" autoComplete="current-password"
onChange={() => { setPwError(null); setPwMismatch(false); }}
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
/> />
</div> </div>
@@ -199,6 +204,7 @@ export function AccountSettingsForm() {
required required
minLength={8} minLength={8}
autoComplete="new-password" autoComplete="new-password"
onChange={() => { setPwError(null); setPwMismatch(false); }}
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
/> />
</div> </div>
@@ -213,6 +219,7 @@ export function AccountSettingsForm() {
required required
minLength={8} minLength={8}
autoComplete="new-password" autoComplete="new-password"
onChange={() => { setPwError(null); setPwMismatch(false); }}
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
/> />
</div> </div>
+4
View File
@@ -11,8 +11,10 @@ export interface AuthUser {
interface AuthState { interface AuthState {
user: AuthUser | null; user: AuthUser | null;
avatarVersion: number;
setUser: (user: AuthUser) => void; setUser: (user: AuthUser) => void;
clearUser: () => void; clearUser: () => void;
bumpAvatarVersion: () => void;
} }
/** /**
@@ -22,6 +24,8 @@ interface AuthState {
*/ */
export const useAuthStore = create<AuthState>()((set) => ({ export const useAuthStore = create<AuthState>()((set) => ({
user: null, user: null,
avatarVersion: 0,
setUser: (user) => set({ user }), setUser: (user) => set({ user }),
clearUser: () => set({ user: null }), clearUser: () => set({ user: null }),
bumpAvatarVersion: () => set((s) => ({ avatarVersion: s.avatarVersion + 1 })),
})); }));