From 0361ecbd50fd3bae18c647be884d9ba0e2ec80a5 Mon Sep 17 00:00:00 2001
From: TheSawkit <23707419+TheSawkit@users.noreply.github.com>
Date: Thu, 11 Jun 2026 03:45:29 +0200
Subject: [PATCH 1/5] Fix : Resolve avatar update and onboarding google auth
issues Closed #17
---
app/[lang]/(protected)/layout.tsx | 24 +++-
.../(protected)/profile/[username]/page.tsx | 6 +-
app/[lang]/(protected)/settings/actions.ts | 73 +++++++----
app/[lang]/(protected)/settings/page.tsx | 2 +
app/[lang]/onboarding/actions.ts | 56 +++++++++
app/[lang]/onboarding/loading.tsx | 24 ++++
app/[lang]/onboarding/page.tsx | 53 ++++++++
app/actions/friends.ts | 19 +--
app/actions/playlists.ts | 11 +-
app/api/search/route.ts | 11 +-
components/navigation/Navbar.tsx | 20 ++-
components/navigation/NavbarClient.tsx | 3 +
components/onboarding/OnboardingForm.tsx | 118 ++++++++++++++++++
components/settings/DangerZone.tsx | 37 ++++--
components/settings/PasswordSettings.tsx | 20 ++-
components/settings/ProfileSettings.tsx | 15 ++-
components/settings/SettingsContent.tsx | 13 +-
lib/avatar.ts | 13 ++
lib/i18n/translations.ts | 40 ++++++
lib/onboarding.ts | 75 +++++++++++
lib/supabase/auth-helpers.ts | 10 ++
lib/supabase/columns.ts | 2 +-
tests/unit/avatar.test.ts | 24 ++++
tests/unit/onboarding.test.ts | 104 +++++++++++++++
types/database.ts | 6 +
types/profile.ts | 2 +
26 files changed, 701 insertions(+), 80 deletions(-)
create mode 100644 app/[lang]/onboarding/actions.ts
create mode 100644 app/[lang]/onboarding/loading.tsx
create mode 100644 app/[lang]/onboarding/page.tsx
create mode 100644 components/onboarding/OnboardingForm.tsx
create mode 100644 lib/avatar.ts
create mode 100644 lib/onboarding.ts
create mode 100644 tests/unit/avatar.test.ts
create mode 100644 tests/unit/onboarding.test.ts
diff --git a/app/[lang]/(protected)/layout.tsx b/app/[lang]/(protected)/layout.tsx
index fbf0c54..fb91a5f 100644
--- a/app/[lang]/(protected)/layout.tsx
+++ b/app/[lang]/(protected)/layout.tsx
@@ -1,11 +1,31 @@
+import { redirect } from 'next/navigation';
import { requireAuth } from '@/lib/auth';
+import { createClient } from '@/lib/supabase/server';
+import { getServerLanguage } from '@/lib/i18n/server';
+import { localizedHref } from '@/lib/i18n/utils';
+import { needsOnboarding } from '@/lib/onboarding';
-/** Auth boundary for every route in the (protected) group — redirects to /login when unauthenticated. */
+/**
+ * Auth boundary for every route in the (protected) group — redirects to /login when
+ * unauthenticated, or to /onboarding while the profile (username + region) is incomplete.
+ */
export default async function ProtectedLayout({
children,
}: {
children: React.ReactNode;
}) {
- await requireAuth();
+ const user = await requireAuth();
+ const supabase = await createClient();
+
+ const { data: profile } = await supabase
+ .from('user_profiles')
+ .select('onboarding_completed')
+ .eq('user_id', user.id)
+ .maybeSingle();
+
+ if (needsOnboarding(user.user_metadata, profile?.onboarding_completed)) {
+ redirect(localizedHref(await getServerLanguage(), '/onboarding'));
+ }
+
return <>{children}>;
}
diff --git a/app/[lang]/(protected)/profile/[username]/page.tsx b/app/[lang]/(protected)/profile/[username]/page.tsx
index 3bd3b6b..aac71ce 100644
--- a/app/[lang]/(protected)/profile/[username]/page.tsx
+++ b/app/[lang]/(protected)/profile/[username]/page.tsx
@@ -2,6 +2,7 @@ import { notFound } from 'next/navigation';
import { Suspense } from 'react';
import { requireAuth } from '@/lib/auth';
import { createClient, createAdminClient } from '@/lib/supabase/server';
+import { resolveAvatarUrl } from '@/lib/avatar';
import { getTranslations } from '@/lib/i18n/server';
import { PageLayout } from '@/components/layout/PageLayout';
import { ProfileHero } from '@/components/profile/ProfileHero';
@@ -169,9 +170,8 @@ export default async function ProfilePage({ params }: Props) {
const ownerMeta = ownerAuth.data.user?.user_metadata;
const avatarUrl =
- typeof ownerMeta?.avatar_url === 'string'
- ? ownerMeta.avatar_url
- : undefined;
+ resolveAvatarUrl(profile.avatar_url, ownerMeta?.avatar_url) ??
+ undefined;
const fullName =
typeof ownerMeta?.full_name === 'string'
? ownerMeta.full_name
diff --git a/app/[lang]/(protected)/settings/actions.ts b/app/[lang]/(protected)/settings/actions.ts
index c4b0235..fd4846d 100644
--- a/app/[lang]/(protected)/settings/actions.ts
+++ b/app/[lang]/(protected)/settings/actions.ts
@@ -3,6 +3,7 @@
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
import { createClient, createAdminClient } from '@/lib/supabase/server';
+import { isOAuthOnly } from '@/lib/supabase/auth-helpers';
import { getTranslations, getServerLanguage } from '@/lib/i18n/server';
import { localizedHref } from '@/lib/i18n/utils';
import {
@@ -44,6 +45,10 @@ export async function updateEmail(prevState: unknown, formData: FormData) {
return { error: t.auth.notAuthenticated, success: false };
}
+ if (isOAuthOnly(user)) {
+ return { error: t.settings.profile.warningEmail, success: false };
+ }
+
const newEmail = validateEmail(formData.get('email'));
if (!newEmail) {
@@ -183,18 +188,24 @@ export async function updateAvatar(prevState: unknown, formData: FormData) {
const fileName = `${user.id}-${Date.now()}.${fileExt}`;
const buffer = await avatarFile.arrayBuffer();
-
- const oldAvatarUrl = user.user_metadata?.avatar_url as
- | string
- | undefined;
+ const adminClient = createAdminClient();
+
+ const { data: currentProfile } = await supabase
+ .from('user_profiles')
+ .select('avatar_url')
+ .eq('user_id', user.id)
+ .maybeSingle();
+ const oldAvatarUrl =
+ currentProfile?.avatar_url ??
+ (user.user_metadata?.avatar_url as string | undefined);
if (oldAvatarUrl?.includes('/avatars/')) {
const oldPath = oldAvatarUrl.split('/avatars/')[1]?.split('?')[0];
if (oldPath) {
- await supabase.storage.from('avatars').remove([oldPath]);
+ await adminClient.storage.from('avatars').remove([oldPath]);
}
}
- const { error: uploadError } = await supabase.storage
+ const { error: uploadError } = await adminClient.storage
.from('avatars')
.upload(fileName, buffer, {
contentType: avatarFile.type,
@@ -205,18 +216,26 @@ export async function updateAvatar(prevState: unknown, formData: FormData) {
return { error: uploadError.message, success: false };
}
- const { data: publicUrlData } = supabase.storage
+ const { data: publicUrlData } = adminClient.storage
.from('avatars')
.getPublicUrl(fileName);
finalAvatarUrl = `${publicUrlData.publicUrl}?t=${Date.now()}`;
}
- const { error } = await supabase.auth.updateUser({
- data: {
+ const username = user.user_metadata?.username as string | undefined;
+ if (!username) {
+ return { error: t.settings.missingFields, success: false };
+ }
+
+ const { error } = await supabase.from('user_profiles').upsert(
+ {
+ user_id: user.id,
+ username,
avatar_url: finalAvatarUrl,
- picture: finalAvatarUrl,
+ updated_at: new Date().toISOString(),
},
- });
+ { onConflict: 'user_id' }
+ );
if (error) {
return { error: error.message, success: false };
@@ -255,23 +274,25 @@ export async function deleteAccount(prevState: unknown, formData: FormData) {
};
}
- if (typeof password !== 'string' || !password) {
- return {
- error: t.settings.dangerZone.passwordRequired,
- success: false,
- };
- }
+ if (!isOAuthOnly(user)) {
+ if (typeof password !== 'string' || !password) {
+ return {
+ error: t.settings.dangerZone.passwordRequired,
+ success: false,
+ };
+ }
- const { error: signInError } = await supabase.auth.signInWithPassword({
- email: user.email!,
- password,
- });
+ const { error: signInError } = await supabase.auth.signInWithPassword({
+ email: user.email!,
+ password,
+ });
- if (signInError) {
- return {
- error: t.settings.dangerZone.incorrectPassword,
- success: false,
- };
+ if (signInError) {
+ return {
+ error: t.settings.dangerZone.incorrectPassword,
+ success: false,
+ };
+ }
}
await supabase.from('episode_watches').delete().eq('user_id', user.id);
diff --git a/app/[lang]/(protected)/settings/page.tsx b/app/[lang]/(protected)/settings/page.tsx
index 10bce3d..9bbd405 100644
--- a/app/[lang]/(protected)/settings/page.tsx
+++ b/app/[lang]/(protected)/settings/page.tsx
@@ -1,6 +1,7 @@
import { Suspense } from 'react';
import { requireAuth } from '@/lib/auth';
import { createClient } from '@/lib/supabase/server';
+import { isOAuthOnly } from '@/lib/supabase/auth-helpers';
import { SettingsContent } from '@/components/settings/SettingsContent';
import { SettingsContentSkeleton } from '@/components/settings/SettingsContentSkeleton';
import { PageLayout, PageHeader } from '@/components/layout/PageLayout';
@@ -47,6 +48,7 @@ async function SettingsSection({ user }: { user: User }) {
user={user}
userProfile={userProfile}
privacySettings={privacySettings}
+ isOAuthOnly={isOAuthOnly(user)}
/>
);
}
diff --git a/app/[lang]/onboarding/actions.ts b/app/[lang]/onboarding/actions.ts
new file mode 100644
index 0000000..bb5d889
--- /dev/null
+++ b/app/[lang]/onboarding/actions.ts
@@ -0,0 +1,56 @@
+'use server';
+
+import { revalidatePath } from 'next/cache';
+import { redirect } from 'next/navigation';
+import { getAuthenticatedUser } from '@/lib/supabase/auth-helpers';
+import { getServerLanguage, getTranslations } from '@/lib/i18n/server';
+import { localizedHref } from '@/lib/i18n/utils';
+import { validateUsername, validateRegion } from '@/lib/validators';
+
+export async function completeOnboarding(
+ prevState: unknown,
+ formData: FormData
+) {
+ const t = await getTranslations();
+ const { supabase, userId, user } = await getAuthenticatedUser();
+
+ const username = validateUsername(formData.get('username'));
+ const region = validateRegion(formData.get('region'));
+
+ if (!username || !region) return { error: t.settings.missingFields };
+
+ const { data: existing } = await supabase
+ .from('user_profiles')
+ .select('user_id')
+ .ilike('username', username)
+ .maybeSingle();
+
+ if (existing && existing.user_id !== userId)
+ return { error: t.settings.usernameTaken };
+
+ const meta = user.user_metadata ?? {};
+ const fullName =
+ (typeof meta.full_name === 'string' && meta.full_name) ||
+ (typeof meta.name === 'string' ? meta.name : username);
+
+ const { error: metaError } = await supabase.auth.updateUser({
+ data: { username, region, full_name: fullName },
+ });
+ if (metaError) return { error: metaError.message };
+
+ const { error: profileError } = await supabase.from('user_profiles').upsert(
+ {
+ user_id: userId,
+ username,
+ onboarding_completed: true,
+ updated_at: new Date().toISOString(),
+ },
+ { onConflict: 'user_id' }
+ );
+ if (profileError?.code === '23505')
+ return { error: t.settings.usernameTaken };
+ if (profileError) return { error: profileError.message };
+
+ revalidatePath('/', 'layout');
+ redirect(localizedHref(await getServerLanguage(), '/dashboard'));
+}
diff --git a/app/[lang]/onboarding/loading.tsx b/app/[lang]/onboarding/loading.tsx
new file mode 100644
index 0000000..447b191
--- /dev/null
+++ b/app/[lang]/onboarding/loading.tsx
@@ -0,0 +1,24 @@
+import {
+ Card,
+ CardContent,
+ CardHeader,
+} from '@/components/ui/card';
+import { Skeleton } from '@/components/ui/skeleton';
+
+export default function OnboardingLoading() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/app/[lang]/onboarding/page.tsx b/app/[lang]/onboarding/page.tsx
new file mode 100644
index 0000000..f7d4d35
--- /dev/null
+++ b/app/[lang]/onboarding/page.tsx
@@ -0,0 +1,53 @@
+import { redirect } from 'next/navigation';
+import { requireAuth } from '@/lib/auth';
+import { createClient } from '@/lib/supabase/server';
+import { getServerLanguage, getTranslations } from '@/lib/i18n/server';
+import { localizedHref } from '@/lib/i18n/utils';
+import {
+ ensureUniqueUsername,
+ needsOnboarding,
+ suggestUsernameFromMetadata,
+} from '@/lib/onboarding';
+import { OnboardingForm } from '@/components/onboarding/OnboardingForm';
+
+export async function generateMetadata() {
+ const t = await getTranslations();
+ return {
+ title: t.onboarding.title,
+ robots: {
+ index: false,
+ follow: false,
+ googleBot: { index: false, follow: false },
+ },
+ };
+}
+
+export default async function OnboardingPage() {
+ const user = await requireAuth();
+ const supabase = await createClient();
+
+ const { data: profile } = await supabase
+ .from('user_profiles')
+ .select('username, onboarding_completed')
+ .eq('user_id', user.id)
+ .maybeSingle();
+
+ if (!needsOnboarding(user.user_metadata, profile?.onboarding_completed)) {
+ redirect(localizedHref(await getServerLanguage(), '/dashboard'));
+ }
+
+ const existingUsername =
+ (typeof user.user_metadata?.username === 'string' &&
+ user.user_metadata.username) ||
+ profile?.username ||
+ null;
+
+ const initialUsername = existingUsername
+ ? existingUsername
+ : await ensureUniqueUsername(
+ supabase,
+ suggestUsernameFromMetadata(user.user_metadata, user.email)
+ );
+
+ return ;
+}
diff --git a/app/actions/friends.ts b/app/actions/friends.ts
index a036e06..9e4a2b4 100644
--- a/app/actions/friends.ts
+++ b/app/actions/friends.ts
@@ -3,6 +3,7 @@
import { getAuthenticatedUser } from '@/lib/supabase/auth-helpers';
import { createAdminClient } from '@/lib/supabase/server';
import { FRIENDSHIP_COLUMNS } from '@/lib/supabase/columns';
+import { resolveAvatarUrl } from '@/lib/avatar';
import { revalidateProfile } from '@/app/actions/_helpers';
import type {
Friendship,
@@ -57,7 +58,7 @@ export async function getPendingRequestsWithProfiles(): Promise<
const [{ data: profiles }, adminResults] = await Promise.all([
supabase
.from('user_profiles')
- .select('user_id, username')
+ .select('user_id, username, avatar_url')
.in('user_id', requesterIds),
Promise.all(
requesterIds.map((id) => adminClient.auth.admin.getUserById(id))
@@ -80,9 +81,10 @@ export async function getPendingRequestsWithProfiles(): Promise<
friendship: f,
username: profile.username,
avatarUrl:
- typeof adminUser?.user_metadata?.avatar_url === 'string'
- ? adminUser.user_metadata.avatar_url
- : undefined,
+ resolveAvatarUrl(
+ profile.avatar_url,
+ adminUser?.user_metadata?.avatar_url
+ ) ?? undefined,
fullName:
typeof adminUser?.user_metadata?.full_name === 'string'
? adminUser.user_metadata.full_name
@@ -123,7 +125,7 @@ export async function getFriendsWithProfiles(
const [{ data: profiles }, adminResults] = await Promise.all([
supabase
.from('user_profiles')
- .select('user_id, username')
+ .select('user_id, username, avatar_url')
.in('user_id', friendUserIds),
Promise.all(
friendUserIds.map((id) => adminClient.auth.admin.getUserById(id))
@@ -148,9 +150,10 @@ export async function getFriendsWithProfiles(
friendship: f,
username: profile.username,
avatarUrl:
- typeof adminUser?.user_metadata?.avatar_url === 'string'
- ? adminUser.user_metadata.avatar_url
- : undefined,
+ resolveAvatarUrl(
+ profile.avatar_url,
+ adminUser?.user_metadata?.avatar_url
+ ) ?? undefined,
fullName:
typeof adminUser?.user_metadata?.full_name === 'string'
? adminUser.user_metadata.full_name
diff --git a/app/actions/playlists.ts b/app/actions/playlists.ts
index 63852a7..87b0bc2 100644
--- a/app/actions/playlists.ts
+++ b/app/actions/playlists.ts
@@ -9,6 +9,7 @@ import { getTranslations } from '@/lib/i18n/server';
import { revalidateProfile } from '@/app/actions/_helpers';
import { parseVisibility, isVisibility } from '@/lib/privacy';
import { getListMediaMetadata } from '@/lib/tmdb';
+import { resolveAvatarUrl } from '@/lib/avatar';
import type { MediaType } from '@/types/tmdb';
import type { Playlist, PrivacyVisibility } from '@/types/profile';
@@ -59,17 +60,17 @@ export async function getPlaylistById(id: string): Promise<{
const [profileResult, ownerAuth] = await Promise.all([
supabase
.from('user_profiles')
- .select('username')
+ .select('username, avatar_url')
.eq('user_id', data.user_id)
.maybeSingle(),
adminClient.auth.admin.getUserById(data.user_id),
]);
const ownerUsername = profileResult.data?.username ?? null;
- const ownerAvatarUrl =
- typeof ownerAuth.data.user?.user_metadata?.avatar_url === 'string'
- ? ownerAuth.data.user.user_metadata.avatar_url
- : null;
+ const ownerAvatarUrl = resolveAvatarUrl(
+ profileResult.data?.avatar_url,
+ ownerAuth.data.user?.user_metadata?.avatar_url
+ );
return {
playlist: data as Playlist,
diff --git a/app/api/search/route.ts b/app/api/search/route.ts
index dc54b9a..d5e9aa8 100644
--- a/app/api/search/route.ts
+++ b/app/api/search/route.ts
@@ -3,6 +3,7 @@ import { searchMulti } from '@/lib/tmdb';
import { rankMedia } from '@/lib/search/score';
import { getMediaKey } from '@/lib/media';
import { createClient, createAdminClient } from '@/lib/supabase/server';
+import { resolveAvatarUrl } from '@/lib/avatar';
import type { MediaItem } from '@/types/tmdb';
const MAX_RESULTS = 6;
@@ -46,7 +47,7 @@ export async function GET(req: NextRequest) {
const supabase = await createClient();
const { data: profiles } = await supabase
.from('user_profiles')
- .select('user_id, username, bio')
+ .select('user_id, username, bio, avatar_url')
.ilike('username', `%${username}%`)
.limit(MAX_USER_RESULTS);
@@ -58,10 +59,10 @@ export async function GET(req: NextRequest) {
const { data } = await adminClient.auth.admin.getUserById(
profile.user_id
);
- const avatarUrl =
- typeof data.user?.user_metadata?.avatar_url === 'string'
- ? data.user.user_metadata.avatar_url
- : null;
+ const avatarUrl = resolveAvatarUrl(
+ profile.avatar_url,
+ data.user?.user_metadata?.avatar_url
+ );
return { ...profile, avatar_url: avatarUrl };
})
);
diff --git a/components/navigation/Navbar.tsx b/components/navigation/Navbar.tsx
index 809c01b..32b7b91 100644
--- a/components/navigation/Navbar.tsx
+++ b/components/navigation/Navbar.tsx
@@ -7,13 +7,22 @@ export default async function Navbar() {
const t = await getTranslations();
let initialUnreadCount = 0;
+ let avatarUrl: string | null = null;
if (user) {
- const { count } = await supabase
- .from('notifications')
- .select('id', { count: 'exact', head: true })
- .eq('user_id', user.id)
- .is('read_at', null);
+ const [{ count }, { data: profile }] = await Promise.all([
+ supabase
+ .from('notifications')
+ .select('id', { count: 'exact', head: true })
+ .eq('user_id', user.id)
+ .is('read_at', null),
+ supabase
+ .from('user_profiles')
+ .select('avatar_url')
+ .eq('user_id', user.id)
+ .maybeSingle(),
+ ]);
initialUnreadCount = count ?? 0;
+ avatarUrl = profile?.avatar_url ?? null;
}
return (
@@ -21,6 +30,7 @@ export default async function Navbar() {
user={user}
t={t}
initialUnreadCount={initialUnreadCount}
+ avatarUrl={avatarUrl}
/>
);
}
diff --git a/components/navigation/NavbarClient.tsx b/components/navigation/NavbarClient.tsx
index 0716256..b10d080 100644
--- a/components/navigation/NavbarClient.tsx
+++ b/components/navigation/NavbarClient.tsx
@@ -41,12 +41,14 @@ interface NavbarClientProps {
user: NavbarUser | null;
t: NavbarTranslations;
initialUnreadCount: number;
+ avatarUrl?: string | null;
}
export function NavbarClient({
user,
t,
initialUnreadCount,
+ avatarUrl,
}: NavbarClientProps) {
const { title, scrolled } = useMediaHeader();
const { lang } = useTranslation();
@@ -173,6 +175,7 @@ export function NavbarClient({
>
+
+
+
+ {t.onboarding.title}
+
+ {t.onboarding.subtitle}
+
+
+
+
+
+
+ );
+}
diff --git a/components/settings/DangerZone.tsx b/components/settings/DangerZone.tsx
index e39161c..9f30d47 100644
--- a/components/settings/DangerZone.tsx
+++ b/components/settings/DangerZone.tsx
@@ -24,7 +24,11 @@ const initialState = {
success: false,
};
-export function DangerZone() {
+interface DangerZoneProps {
+ isOAuthOnly: boolean;
+}
+
+export function DangerZone({ isOAuthOnly }: DangerZoneProps) {
const [state, formAction, isPending] = useActionState(
deleteAccount,
initialState
@@ -93,17 +97,26 @@ export function DangerZone() {
{t.danger.additionalWarning}
-
-
- {t.settings.dangerZone.confirmPassword}
-
-
-
+ {isOAuthOnly ? (
+
+ {t.oauth.deleteNoPassword}
+
+ ) : (
+
+
+ {
+ t.settings.dangerZone
+ .confirmPassword
+ }
+
+
+
+ )}
{state.error && (
diff --git a/components/settings/PasswordSettings.tsx b/components/settings/PasswordSettings.tsx
index e5dc723..4b0ef3f 100644
--- a/components/settings/PasswordSettings.tsx
+++ b/components/settings/PasswordSettings.tsx
@@ -25,7 +25,11 @@ const initialState = {
message: '',
};
-export function PasswordSettings() {
+interface PasswordSettingsProps {
+ isOAuthOnly: boolean;
+}
+
+export function PasswordSettings({ isOAuthOnly }: PasswordSettingsProps) {
const { t } = useTranslation();
const [state, formAction, isPending] = useActionState(
updatePassword,
@@ -35,9 +39,15 @@ export function PasswordSettings() {
return (
- {t.settings.password.title}
+
+ {isOAuthOnly
+ ? t.oauth.createPasswordTitle
+ : t.settings.password.title}
+
- {t.settings.password.description}
+ {isOAuthOnly
+ ? t.oauth.createPasswordDescription
+ : t.settings.password.description}
@@ -87,7 +97,9 @@ export function PasswordSettings() {
diff --git a/components/settings/ProfileSettings.tsx b/components/settings/ProfileSettings.tsx
index 047b42f..c71efe8 100644
--- a/components/settings/ProfileSettings.tsx
+++ b/components/settings/ProfileSettings.tsx
@@ -33,9 +33,13 @@ const initialState = {
interface ProfileSettingsProps {
user: User | null;
+ profileAvatarUrl?: string | null;
}
-export function ProfileSettings({ user }: ProfileSettingsProps) {
+export function ProfileSettings({
+ user,
+ profileAvatarUrl,
+}: ProfileSettingsProps) {
const { t } = useTranslation();
const [state, formAction, isPending] = useActionState(
updateProfile,
@@ -47,9 +51,9 @@ export function ProfileSettings({ user }: ProfileSettingsProps) {
);
const fileInputRef = useRef(null);
const [avatarFile, setAvatarFile] = useState(null);
- const [avatarPreview, setAvatarPreview] = useState(
- user?.user_metadata?.avatar_url || ''
- );
+ const currentAvatarUrl =
+ profileAvatarUrl || user?.user_metadata?.avatar_url || '';
+ const [avatarPreview, setAvatarPreview] = useState(currentAvatarUrl);
const [fullNameStr, setFullNameStr] = useState(
user?.user_metadata?.full_name || ''
);
@@ -146,8 +150,7 @@ export function ProfileSettings({ user }: ProfileSettingsProps) {
{avatarPreview &&
avatarPreview !==
- user?.user_metadata
- ?.avatar_url && (
+ currentAvatarUrl && (
<>