diff --git a/backend/app/routes/me.py b/backend/app/routes/me.py index 6d05883..26498b4 100644 --- a/backend/app/routes/me.py +++ b/backend/app/routes/me.py @@ -2,7 +2,14 @@ from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Request from sqlalchemy import func, select from sqlalchemy.orm import Session -from app.auth import current_user, has_read_scope, has_write_scope, is_allowed, purge_user +from app.auth import ( + current_user, + google_enabled, + has_read_scope, + has_write_scope, + is_allowed, + purge_user, +) from app.db import get_db from app.models import Invite, User @@ -80,6 +87,9 @@ def get_me( "is_demo": user.is_demo, "has_google": user.google_sub is not None, "has_password": user.password_hash is not None, + # Instance-wide: whether Google sign-in / YouTube connect is available at all (the UI hides + # YouTube-access affordances and the onboarding nudge when it isn't configured). + "google_enabled": google_enabled(db), "can_read": has_read_scope(user), "can_write": has_write_scope(user), "pending_invites": pending_invites, diff --git a/frontend/src/components/SettingsPanel.tsx b/frontend/src/components/SettingsPanel.tsx index 465e988..156225f 100644 --- a/frontend/src/components/SettingsPanel.tsx +++ b/frontend/src/components/SettingsPanel.tsx @@ -389,34 +389,40 @@ function SignInMethods({ me }: { me: Me }) { const inputCls = "w-full bg-card border border-border rounded-xl px-3 py-2 text-sm outline-none focus:border-accent"; + // Offer Google only when this instance has Google OAuth configured (or the account is already + // linked — then we still show its "Connected" status even if the instance creds were removed). + const showGoogle = me.google_enabled || me.has_google; + return (
-
-
-
{t("settings.account.googleLink.title")}
-

- {me.has_google - ? t("settings.account.googleLink.connectedHint") - : t("settings.account.googleLink.connectHint")} -

+ {showGoogle && ( +
+
+
{t("settings.account.googleLink.title")}
+

+ {me.has_google + ? t("settings.account.googleLink.connectedHint") + : t("settings.account.googleLink.connectHint")} +

+
+ {me.has_google ? ( + + {t("settings.account.googleLink.connected")} + + ) : ( + + )}
- {me.has_google ? ( - - {t("settings.account.googleLink.connected")} - - ) : ( - - )} -
+ )} -
+
{t("settings.account.password.title")}
@@ -517,7 +523,8 @@ function Account({ me, onOpenWizard }: { me: Me; onOpenWizard: () => void }) { {t("settings.account.demoNotice")}

- ) : ( + ) : me.google_enabled ? ( + // YouTube access requires Google OAuth; hidden when this instance has no Google configured.

{t("settings.account.youtubeAccessIntro")} @@ -549,7 +556,7 @@ function Account({ me, onOpenWizard }: { me: Me; onOpenWizard: () => void }) { {t("settings.account.walkMeThrough")}

- )} + ) : null} {!me.is_demo && (
diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 2a0ca1e..2251e59 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -11,6 +11,7 @@ export interface Me { is_demo: boolean; has_google: boolean; has_password: boolean; + google_enabled: boolean; can_read: boolean; can_write: boolean; pending_invites: number; diff --git a/frontend/src/lib/onboarding.ts b/frontend/src/lib/onboarding.ts index 9575b3f..1becf83 100644 --- a/frontend/src/lib/onboarding.ts +++ b/frontend/src/lib/onboarding.ts @@ -15,9 +15,16 @@ export const ONBOARD_ACTIVE = "subfeed.onboarding.active"; export const ONBOARD_DISMISSED = "subfeed.onboarding.dismissed"; -export function shouldAutoOpenOnboarding(me: { can_read: boolean; is_demo?: boolean }): boolean { +export function shouldAutoOpenOnboarding(me: { + can_read: boolean; + is_demo?: boolean; + google_enabled?: boolean; +}): boolean { // The shared demo account can never connect YouTube, so never nudge it to. if (me.is_demo) return false; + // YouTube connect needs Google OAuth configured on this instance. A self-host instance may run + // email+password only — then there's nothing to connect, so don't nudge. + if (me.google_enabled === false) return false; if (sessionStorage.getItem(ONBOARD_ACTIVE)) return true; return !me.can_read && !localStorage.getItem(ONBOARD_DISMISSED); }