diff --git a/backend/app/auth.py b/backend/app/auth.py index b4fe775..8ebf762 100644 --- a/backend/app/auth.py +++ b/backend/app/auth.py @@ -320,13 +320,17 @@ def require_human(user: User = Depends(current_user)) -> User: @router.get("/upgrade") async def upgrade( - request: Request, access: str = "read", user: User = Depends(require_human) + request: Request, access: str = "read", user: User = Depends(current_user) ): """Incremental consent for the onboarding wizard. `access=read` grants YouTube read-only (enough to build the feed); `access=write` additionally grants management (unsubscribe). The shared callback stores whatever Google returns, so `can_read` / `can_write` flip on once granted. Anything other than an explicit `write` defaults to the least-privileged read scope.""" + # This is the browser-facing redirect entry point, so the shared demo account is bounced + # straight home (no YouTube link is ever attached to it) rather than shown a raw 403. + if user.is_demo: + return RedirectResponse(url="/") scope = WRITE_SCOPES if access == "write" else READ_SCOPES return await oauth.google.authorize_redirect( request, diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 91fdfee..5658722 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -291,13 +291,14 @@ export default function App() { setFilters={setFilters} view={view} canRead={meQuery.data!.can_read} + isDemo={meQuery.data!.is_demo} onOpenWizard={() => setWizardOpen(true)} /> )} - {wizardOpen && meQuery.data && ( + {wizardOpen && meQuery.data && !meQuery.data.is_demo && ( setWizardOpen(false)} /> )} {aboutOpen && ( diff --git a/frontend/src/components/Feed.tsx b/frontend/src/components/Feed.tsx index 7741586..ff4c09d 100644 --- a/frontend/src/components/Feed.tsx +++ b/frontend/src/components/Feed.tsx @@ -68,12 +68,14 @@ export default function Feed({ setFilters, view, canRead, + isDemo = false, onOpenWizard, }: { filters: FeedFilters; setFilters: (f: FeedFilters) => void; view: "grid" | "list"; canRead: boolean; + isDemo?: boolean; onOpenWizard: () => void; }) { const { t } = useTranslation(); @@ -204,26 +206,42 @@ export default function Feed({ if (query.isLoading) return
{t("feed.loading")}
; if (query.isError) return
{t("feed.loadError")}
; - // Onboarding empty state (no YouTube + own subscriptions): a dedicated full-page prompt, - // with no toolbar (content-type/sort would be meaningless here). + // Empty "my" feed with no YouTube access. The shared demo account can never connect + // YouTube, so it gets a gentle nudge into the shared library instead of the connect + // wizard; real users get the onboarding prompt. No toolbar (sort/type are meaningless). if (items.length === 0 && !canRead && filters.scope === "my") return (
-

{t("feed.emptyTitle")}

-

{t("feed.emptyBody")}

- - +

+ {isDemo ? t("feed.demoEmptyTitle") : t("feed.emptyTitle")} +

+

+ {isDemo ? t("feed.demoEmptyBody") : t("feed.emptyBody")} +

+ {isDemo ? ( + + ) : ( + <> + + + + )}
); diff --git a/frontend/src/i18n/locales/de/feed.json b/frontend/src/i18n/locales/de/feed.json index 9378c5e..a948419 100644 --- a/frontend/src/i18n/locales/de/feed.json +++ b/frontend/src/i18n/locales/de/feed.json @@ -5,6 +5,9 @@ "emptyBody": "Verbinde dein YouTube-Konto, um deine Abos zu importieren und deinen Feed aufzubauen.", "setUp": "Meinen Feed einrichten", "browseShared": "Oder einfach die gemeinsame Bibliothek durchsuchen", + "demoEmptyTitle": "Hier in der Demo ist nichts", + "demoEmptyBody": "Das Demo-Konto hat keine eigenen Abos — aber die gesamte gemeinsame Bibliothek steht dir offen: durchsuchen, filtern und sortieren.", + "browseLibrary": "Bibliothek durchsuchen", "noMatches": "Keine Videos entsprechen diesen Filtern.", "videoCount_one": "{{formattedCount}} Video", "videoCount_other": "{{formattedCount}} Videos", diff --git a/frontend/src/i18n/locales/en/feed.json b/frontend/src/i18n/locales/en/feed.json index 4332daa..070975c 100644 --- a/frontend/src/i18n/locales/en/feed.json +++ b/frontend/src/i18n/locales/en/feed.json @@ -5,6 +5,9 @@ "emptyBody": "Connect your YouTube account to import your subscriptions and build your feed.", "setUp": "Set up my feed", "browseShared": "Or just browse the shared library", + "demoEmptyTitle": "Nothing here in the demo", + "demoEmptyBody": "The demo account has no subscriptions of its own — but the whole shared library is yours to explore, filter and sort.", + "browseLibrary": "Browse the library", "noMatches": "No videos match these filters.", "videoCount_one": "{{formattedCount}} video", "videoCount_other": "{{formattedCount}} videos", diff --git a/frontend/src/i18n/locales/hu/feed.json b/frontend/src/i18n/locales/hu/feed.json index fc6b100..430f98f 100644 --- a/frontend/src/i18n/locales/hu/feed.json +++ b/frontend/src/i18n/locales/hu/feed.json @@ -5,6 +5,9 @@ "emptyBody": "Kösd össze a YouTube-fiókodat, hogy importáld a feliratkozásaidat, és felépítsd a hírfolyamodat.", "setUp": "Hírfolyam beállítása", "browseShared": "Vagy csak böngészd a közös könyvtárat", + "demoEmptyTitle": "Itt a demóban nincs semmi", + "demoEmptyBody": "A demo fióknak nincsenek saját feliratkozásai — de a teljes közös könyvtár a tiéd: böngészd, szűrd és rendezd kedvedre.", + "browseLibrary": "Könyvtár böngészése", "noMatches": "Egy videó sem felel meg ezeknek a szűrőknek.", "videoCount_one": "{{formattedCount}} videó", "videoCount_other": "{{formattedCount}} videó",