diff --git a/frontend/src/components/layout/AppLayout.tsx b/frontend/src/components/layout/AppLayout.tsx index 60d4b7e..c532176 100644 --- a/frontend/src/components/layout/AppLayout.tsx +++ b/frontend/src/components/layout/AppLayout.tsx @@ -1,5 +1,5 @@ -import { useEffect, useRef, useState } from "react" -import { Outlet } from "react-router" +import { useEffect, useRef, useState, useCallback } from "react" +import { Outlet, useSearchParams } from "react-router" import { AppSidebar } from "./Sidebar" import { Header } from "./Header" import { SettingsDialog } from "@/components/SettingsDialog" @@ -11,13 +11,34 @@ import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar" function AppLayoutInner() { const [settingsOpen, setSettingsOpen] = useState(false) const [settingsTab, setSettingsTab] = useState("profile") + const [searchParams, setSearchParams] = useSearchParams() const commandPalette = useCommandPalette() const { openUpload, dialogOpen } = useUploadContext() - const openSettings = (tab = "profile") => { + const openSettings = useCallback((tab = "profile") => { setSettingsTab(tab) setSettingsOpen(true) - } + }, []) + + // Auto-open settings from URL params (e.g. OAuth redirect) + useEffect(() => { + const tab = searchParams.get("settings") + if (tab) { + openSettings(tab) + searchParams.delete("settings") + setSearchParams(searchParams, { replace: true }) + } + }, []) // eslint-disable-line react-hooks/exhaustive-deps + + // Listen for open-settings events from other components + useEffect(() => { + const handler = (e: Event) => { + const detail = (e as CustomEvent).detail + openSettings(detail?.tab || "profile") + } + window.addEventListener("open-settings", handler) + return () => window.removeEventListener("open-settings", handler) + }, [openSettings]) // Refs for dialog state — read inside keydown handler without re-subscribing const settingsOpenRef = useRef(settingsOpen) diff --git a/frontend/src/components/settings/YouTubeSection.tsx b/frontend/src/components/settings/YouTubeSection.tsx index 1cea53d..acfb82b 100644 --- a/frontend/src/components/settings/YouTubeSection.tsx +++ b/frontend/src/components/settings/YouTubeSection.tsx @@ -6,6 +6,7 @@ import { Input } from "@/components/ui/input" import { Button } from "@/components/ui/button" import { Label } from "@/components/ui/label" import { Skeleton } from "@/components/ui/skeleton" +import { Spinner } from "@/components/ui/spinner" interface YouTubeStatus { connected: boolean @@ -17,6 +18,7 @@ export function YouTubeSection() { const [searchParams, setSearchParams] = useSearchParams() const [clientId, setClientId] = useState("") const [clientSecret, setClientSecret] = useState("") + const [isFinalizing, setIsFinalizing] = useState(false) const { data: statusData, @@ -39,7 +41,7 @@ export function YouTubeSection() { ) const { call: callConnect, loading: connecting } = useFrappePostCall("vms.youtube.connect_youtube") - const { call: callFinalize, loading: finalizing } = useFrappePostCall("vms.youtube.finalize_youtube_connection") + const { call: callFinalize } = useFrappePostCall("vms.youtube.finalize_youtube_connection") const { call: callDisconnect, loading: disconnecting } = useFrappePostCall("vms.youtube.disconnect_youtube") const status = statusData?.message @@ -49,9 +51,9 @@ export function YouTubeSection() { useEffect(() => { if (searchParams.get("youtube_connected") === "1") { searchParams.delete("youtube_connected") - searchParams.delete("settings") setSearchParams(searchParams, { replace: true }) + setIsFinalizing(true) callFinalize({}) .then(() => { toast.success("YouTube connected successfully") @@ -60,6 +62,9 @@ export function YouTubeSection() { .catch(() => { toast.error("Failed to finalize YouTube connection") }) + .finally(() => { + setIsFinalizing(false) + }) } }, []) // eslint-disable-line react-hooks/exhaustive-deps @@ -94,19 +99,13 @@ export function YouTubeSection() { } } - if (statusLoading || redirectLoading) { + if (isFinalizing || statusLoading || redirectLoading) { return ( -
+ {isFinalizing ? "Connecting YouTube..." : "Loading..."} +