mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-14 10:34:06 +02:00
fix: show user avatar on the home page settings button
The bottom-left settings button and the settings page both read profilePictureUrl, but only the settings page had a fallback (initials avatar) - the button silently fell back to a gear. The URL itself was usually null because fetchProfilePicture raced the background OAuth avatar sync with a fixed 500ms delay and never retried, and a missing bucket object simply resolved to null. - useConfigButtonIcon: fall back to the same initials avatar as the settings page instead of the gear when no picture URL is available. - UseSession: fetch the profile picture when syncOAuthAvatar settles (init and SIGNED_IN) instead of after an arbitrary 500ms. - fetchProfilePicture: when the bucket copy is missing, fall back to the OAuth provider's own photo URL so the picture shows immediately on first login - unless the user explicitly uploaded/removed a picture (metadata source 'upload'), preserving the remove flow.
This commit is contained in:
@@ -27,6 +27,7 @@ import { synchronizeUserUpgrade } from "@app/services/userService";
|
||||
import {
|
||||
syncOAuthAvatar,
|
||||
getProfilePictureMetadata,
|
||||
getProviderAvatarUrl,
|
||||
type ProfilePictureMetadata,
|
||||
} from "@app/services/avatarSyncService";
|
||||
|
||||
@@ -321,6 +322,21 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
await fetchTrialStatus();
|
||||
}, [fetchTrialStatus]);
|
||||
|
||||
// Provider photo as interim fallback when the bucket copy is missing —
|
||||
// skipped when the user explicitly chose upload/removal (source "upload").
|
||||
const providerAvatarFallback = useCallback(
|
||||
async (user: SupabaseUser): Promise<string | null> => {
|
||||
try {
|
||||
const metadata = await getProfilePictureMetadata(user.id);
|
||||
if (metadata?.source === "upload") return null;
|
||||
return getProviderAvatarUrl(user);
|
||||
} catch {
|
||||
return getProviderAvatarUrl(user);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const fetchProfilePicture = useCallback(
|
||||
async (sessionToUse?: Session | null) => {
|
||||
const currentSession = sessionToUse ?? session;
|
||||
@@ -351,7 +367,9 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
"[Auth Debug] Profile picture not available:",
|
||||
error.message,
|
||||
);
|
||||
setProfilePictureUrl(null);
|
||||
setProfilePictureUrl(
|
||||
await providerAvatarFallback(currentSession.user),
|
||||
);
|
||||
} else {
|
||||
setProfilePictureUrl(data.signedUrl);
|
||||
console.debug(
|
||||
@@ -360,10 +378,10 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
console.debug("[Auth Debug] Failed to fetch profile picture:", error);
|
||||
setProfilePictureUrl(null);
|
||||
setProfilePictureUrl(await providerAvatarFallback(currentSession.user));
|
||||
}
|
||||
},
|
||||
[session],
|
||||
[session, providerAvatarFallback],
|
||||
);
|
||||
|
||||
const refreshProfilePicture = useCallback(async () => {
|
||||
@@ -522,23 +540,22 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
|
||||
// Fetch credits, pro status, trial status, profile picture metadata, and profile picture using the session from the response
|
||||
if (data.session?.user) {
|
||||
// Sync OAuth avatar in background
|
||||
syncOAuthAvatar(data.session.user).catch((err) => {
|
||||
console.debug(
|
||||
"[Auth Debug] Failed to sync OAuth avatar on init:",
|
||||
err,
|
||||
);
|
||||
});
|
||||
// Sync OAuth avatar in background; fetch the picture once the
|
||||
// sync settles instead of guessing with a fixed delay.
|
||||
syncOAuthAvatar(data.session.user)
|
||||
.catch((err) => {
|
||||
console.debug(
|
||||
"[Auth Debug] Failed to sync OAuth avatar on init:",
|
||||
err,
|
||||
);
|
||||
return false;
|
||||
})
|
||||
.then(() => fetchProfilePicture(data.session));
|
||||
|
||||
await fetchCredits(data.session);
|
||||
await fetchProStatus(data.session);
|
||||
await fetchTrialStatus(data.session);
|
||||
await fetchProfilePictureMetadata(data.session);
|
||||
|
||||
// Small delay to allow avatar sync to complete if quick
|
||||
setTimeout(() => {
|
||||
fetchProfilePicture(data.session);
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -602,9 +619,15 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
// null/loading states.
|
||||
|
||||
// Sync OAuth avatar in background (don't block other fetches)
|
||||
syncOAuthAvatar(newSession.user).catch((err) => {
|
||||
console.debug("[Auth Debug] Failed to sync OAuth avatar:", err);
|
||||
});
|
||||
const avatarSync = syncOAuthAvatar(newSession.user).catch(
|
||||
(err) => {
|
||||
console.debug(
|
||||
"[Auth Debug] Failed to sync OAuth avatar:",
|
||||
err,
|
||||
);
|
||||
return false;
|
||||
},
|
||||
);
|
||||
|
||||
// Fetch user data in parallel
|
||||
Promise.all([
|
||||
@@ -613,15 +636,15 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
fetchTrialStatus(newSession),
|
||||
fetchProfilePictureMetadata(newSession),
|
||||
]).then(() => {
|
||||
// Fetch profile picture AFTER sync has had time to complete
|
||||
// Use a small delay to allow avatar sync to finish if it's quick
|
||||
setTimeout(() => {
|
||||
// Fetch the picture once the avatar sync settles rather than
|
||||
// after a fixed 500ms the first-ever sync usually loses.
|
||||
avatarSync.then(() => {
|
||||
fetchProfilePicture(newSession).finally(() => {
|
||||
console.debug(
|
||||
"[Auth Debug] User data fully loaded after sign in",
|
||||
);
|
||||
});
|
||||
}, 500);
|
||||
});
|
||||
});
|
||||
}
|
||||
} else if (event === "TOKEN_REFRESHED") {
|
||||
|
||||
@@ -2,8 +2,16 @@ import { Avatar } from "@mantine/core";
|
||||
import { useAuth } from "@app/auth/UseSession";
|
||||
|
||||
export function useConfigButtonIcon(): React.ReactNode {
|
||||
const { profilePictureUrl } = useAuth();
|
||||
return profilePictureUrl ? (
|
||||
<Avatar src={profilePictureUrl} radius="xl" size={24} />
|
||||
const { profilePictureUrl, user } = useAuth();
|
||||
if (profilePictureUrl) {
|
||||
return <Avatar src={profilePictureUrl} radius="xl" size={24} />;
|
||||
}
|
||||
// Mirror the settings page fallback (initials avatar) so both spots show
|
||||
// the same identity badge while the picture URL is unavailable.
|
||||
const initial = user?.email?.trim()?.charAt(0)?.toUpperCase();
|
||||
return initial ? (
|
||||
<Avatar radius="xl" size={24} color="blue">
|
||||
{initial}
|
||||
</Avatar>
|
||||
) : null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user