From 880b17facbf5a28ef19c39b7e1952f0c4c366f34 Mon Sep 17 00:00:00 2001 From: alpha Date: Mon, 10 Aug 2026 10:39:56 -0500 Subject: [PATCH] Open configuration dialog when Start checks fail on missing username/profile The dialog was owned solely by TitlebarMenu's local state, so ControlPanel's existing pre-start validation could only toast an error, not surface a way to fix it. Lift the dialog into a context provided by MainFrame so both places can open it. Fixes #90 Co-Authored-By: Claude Sonnet 5 --- .../components/custom/control-panel/index.tsx | 9 +++++- src/renderer/components/custom/main-frame.tsx | 32 +++++++++++++------ .../components/custom/titlebar-menu.tsx | 8 ++--- .../hooks/use-configuration-dialog.tsx | 18 +++++++++++ 4 files changed, 52 insertions(+), 15 deletions(-) create mode 100644 src/renderer/hooks/use-configuration-dialog.tsx diff --git a/src/renderer/components/custom/control-panel/index.tsx b/src/renderer/components/custom/control-panel/index.tsx index 45fcdcb..929bf10 100644 --- a/src/renderer/components/custom/control-panel/index.tsx +++ b/src/renderer/components/custom/control-panel/index.tsx @@ -6,6 +6,7 @@ import { useAppState } from '@/hooks/use-app-state'; import { useAssistantService } from '@/hooks/use-assistant-service'; import { useAudioInputDevices } from '@/hooks/use-audio-devices'; import { useConfigStore } from '@/hooks/use-config-store'; +import { useConfigurationDialog } from '@/hooks/use-configuration-dialog'; import useIsStealthMode from '@/hooks/use-is-stealth-mode'; import { isMac } from '@/lib/consts'; import { getElectron } from '@/lib/utils'; @@ -30,6 +31,7 @@ export default function ControlPanel() { const { startAssistant, stopAssistant } = useAssistantService(); const { runningState, appState } = useAppState(); const { config } = useConfigStore(); + const { openConfigurationDialog } = useConfigurationDialog(); const [permGateOpen, setPermGateOpen] = useState(false); const audioInputDevices = useAudioInputDevices(); @@ -47,10 +49,15 @@ export default function ControlPanel() { // retry: without this the same toast repeats forever however often Start is pressed. onFail: () => void getElectron()?.account?.refresh(), }, - { ok: !!appState?.interviewConfig?.fullName, message: 'Full name is not set' }, + { + ok: !!appState?.interviewConfig?.fullName, + message: 'Full name is not set', + onFail: openConfigurationDialog, + }, { ok: appState?.interviewConfig?.hasProfileData ?? false, message: 'Profile data is not set', + onFail: openConfigurationDialog, }, { ok: !audioInputDeviceNotFound, diff --git a/src/renderer/components/custom/main-frame.tsx b/src/renderer/components/custom/main-frame.tsx index 86d9872..b9b9fc7 100644 --- a/src/renderer/components/custom/main-frame.tsx +++ b/src/renderer/components/custom/main-frame.tsx @@ -1,10 +1,12 @@ import React, { useEffect } from 'react'; import { toast } from 'sonner'; +import { ConfigurationDialogContext } from '@/hooks/use-configuration-dialog'; import { MainContainerContext } from '@/hooks/use-main-container'; import usePointerLockGuard from '@/hooks/use-pointer-lock-guard'; import type { PushNotification } from '@/types/push-notification'; +import ConfigurationDialog from './configuration-dialog'; import Titlebar from './titlebar'; import { UpdateNotification } from './update-notification'; @@ -16,6 +18,14 @@ export default function MainFrame({ children }: { children: React.ReactNode }) { setContainer(el); }, []); + // Owned here (rather than by the menu that used to be its only opener) so the start-checks + // in ControlPanel can also open it when username/profile turn out to be unconfigured. + const [isConfigOpen, setIsConfigOpen] = React.useState(false); + const configurationDialogValue = React.useMemo( + () => ({ openConfigurationDialog: () => setIsConfigOpen(true) }), + [] + ); + useEffect(() => { const api = window.electronAPI; if (!api?.onPushNotification) return; @@ -39,14 +49,18 @@ export default function MainFrame({ children }: { children: React.ReactNode }) { }, []); return ( - -
-
- -
{children}
-
- -
-
+ + +
+
+ +
{children}
+
+ +
+ + +
+
); } diff --git a/src/renderer/components/custom/titlebar-menu.tsx b/src/renderer/components/custom/titlebar-menu.tsx index f6e572d..683e56d 100644 --- a/src/renderer/components/custom/titlebar-menu.tsx +++ b/src/renderer/components/custom/titlebar-menu.tsx @@ -14,7 +14,6 @@ import React, { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { toast } from 'sonner'; -import ConfigurationDialog from '@/components/custom/configuration-dialog'; import DocumentationDialog from '@/components/custom/documentation-dialog'; import { DropdownMenu, @@ -28,6 +27,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip import { useAppState } from '@/hooks/use-app-state'; import useAuth from '@/hooks/use-auth'; import { useConfigStore } from '@/hooks/use-config-store'; +import { useConfigurationDialog } from '@/hooks/use-configuration-dialog'; import { useThemeStore } from '@/hooks/use-theme-store'; import { Hotkey, HOTKEYS } from '@/lib/hotkeys'; import { getElectron } from '@/lib/utils'; @@ -41,8 +41,8 @@ export default function TitlebarMenu({ style }: { style?: React.CSSProperties }) const { config } = useConfigStore(); const { isDark, toggleTheme } = useThemeStore(); const { logout, changePassword, loading, error, setError } = useAuth(); + const { openConfigurationDialog } = useConfigurationDialog(); const [isDocsOpen, setIsDocsOpen] = useState(false); - const [isConfigOpen, setIsConfigOpen] = useState(false); const [isChangePasswordOpen, setIsChangePasswordOpen] = useState(false); const isLoggedIn = appState?.isLoggedIn ?? false; @@ -116,7 +116,7 @@ export default function TitlebarMenu({ style }: { style?: React.CSSProperties }) !disabled && setIsConfigOpen(true)} + onClick={() => !disabled && openConfigurationDialog()} disabled={disabled} > @@ -173,8 +173,6 @@ export default function TitlebarMenu({ style }: { style?: React.CSSProperties }) moment a session ends, stranding the pointer-events lock it holds. */} - - void; +} + +export const ConfigurationDialogContext = + React.createContext(null); + +export function useConfigurationDialog(): ConfigurationDialogContextValue { + const ctx = React.useContext(ConfigurationDialogContext); + if (!ctx) { + throw new Error('useConfigurationDialog must be used within MainFrame'); + } + return ctx; +}