fix(account-settings): clear stale pw error on input + live header avatar update
- 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:
@@ -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>
|
||||||
|
|||||||
@@ -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 })),
|
||||||
}));
|
}));
|
||||||
|
|||||||
Reference in New Issue
Block a user