import { useEffect, useState, useCallback } from 'react';
import { Dashboard } from '@/components/layout';
import { ThermostatOverlay } from '@/components/climate';
import { LightsOverlay } from '@/components/lights';
import { LocksOverlay } from '@/components/locks';
import { ControlsOverlay } from '@/components/controls';
import { CalendarWidget } from '@/components/calendar';
import { TodoWidget } from '@/components/todo';
import { ChoreChart } from '@/components/chores';
import { SettingsPanel, ConnectionModal } from '@/components/settings';
import { CameraOverlay } from '@/components/cameras';
import { CameraFeed } from '@/components/cameras/CameraFeed';
import { JellyfinOverlay } from '@/components/media';
import { GlobalKeyboard } from '@/components/keyboard';
import { PhotoFrame } from '@/components/photoframe';
import { useHomeAssistant } from '@/hooks';
import { useIdle } from '@/hooks/useIdle';
import { useHAStore } from '@/stores/haStore';
import { useUIStore, useCameraOverlay } from '@/stores/uiStore';
import { useSettingsStore } from '@/stores/settingsStore';
import { env } from '@/config/environment';
// Person detection alert overlay - shows for 30 seconds when person detected on any configured camera
function PersonAlert({ cameraName, onClose }: { cameraName: string; onClose: () => void }) {
const cameras = useSettingsStore((state) => state.config.cameras);
const cameraLower = cameraName.toLowerCase();
const camera = cameras.find(
(c) => c.frigateCamera?.toLowerCase() === cameraLower || c.name.toLowerCase() === cameraLower,
);
useEffect(() => {
const timer = setTimeout(onClose, 30000);
return () => clearTimeout(timer);
}, [onClose]);
if (!camera) return null;
return (
Person Detected - {camera.displayName}
);
}
function ConnectionPrompt() {
const openSettings = useUIStore((state) => state.openSettings);
return (
Imperial Command Center
Enter your Home Assistant long-lived access token to connect.
);
}
function DashboardContent() {
const config = useSettingsStore((state) => state.config);
return (
);
}
export default function App() {
const { connectionState } = useHomeAssistant();
const accessToken = useHAStore((state) => state.accessToken);
const connect = useHAStore((state) => state.connect);
const settingsOpen = useUIStore((state) => state.settingsOpen);
const lightsOverlayOpen = useUIStore((state) => state.lightsOverlayOpen);
const locksOverlayOpen = useUIStore((state) => state.locksOverlayOpen);
const thermostatsOverlayOpen = useUIStore((state) => state.thermostatsOverlayOpen);
const mediaOverlayOpen = useUIStore((state) => state.mediaOverlayOpen);
const controlsOverlayOpen = useUIStore((state) => state.controlsOverlayOpen);
const { isOpen: cameraOverlayOpen } = useCameraOverlay();
const isIdle = useIdle(env.photoFrameIdleTimeout);
// Person detection alert state (via MQTT from Electron main process)
const [alertCamera, setAlertCamera] = useState(null);
// Report touch/click activity to main process for screen wake on Wayland
useEffect(() => {
const handleActivity = () => {
if (window.electronAPI?.screen?.activity) {
window.electronAPI.screen.activity();
}
};
// Listen for any touch or click events
document.addEventListener('touchstart', handleActivity, { passive: true });
document.addEventListener('mousedown', handleActivity, { passive: true });
return () => {
document.removeEventListener('touchstart', handleActivity);
document.removeEventListener('mousedown', handleActivity);
};
}, []);
// Auto-connect if token is stored (check localStorage first, then config file)
useEffect(() => {
const initConfig = async () => {
// Load HA token
let storedToken = localStorage.getItem('ha_access_token');
// If no token in localStorage, try to get from config file
if (!storedToken && window.electronAPI?.config?.getStoredToken) {
const fileToken = await window.electronAPI.config.getStoredToken();
if (fileToken) {
storedToken = fileToken;
localStorage.setItem('ha_access_token', fileToken);
}
}
if (storedToken && !accessToken) {
connect(storedToken);
}
// Load Jellyfin API key from config file
if (window.electronAPI?.config?.getJellyfinApiKey) {
const jellyfinKey = await window.electronAPI.config.getJellyfinApiKey();
if (jellyfinKey) {
useSettingsStore.getState().setJellyfinApiKey(jellyfinKey);
}
}
};
initConfig();
}, [accessToken, connect]);
// Listen for person detection via MQTT (from Electron main process)
useEffect(() => {
const api = window.electronAPI;
if (!api?.frigate?.onPersonDetected) return;
const unsub = api.frigate.onPersonDetected((camera: string) => {
setAlertCamera(camera);
useUIStore.getState().setIdle(false);
});
return unsub;
}, []);
const closePersonAlert = useCallback(() => {
setAlertCamera(null);
}, []);
// Set up screen idle timeout
useEffect(() => {
if (window.electronAPI) {
window.electronAPI.screen.setIdleTimeout(env.screenIdleTimeout);
}
}, []);
// Show connection prompt if no token
if (!accessToken) {
return (
<>
{settingsOpen && }
>
);
}
// Show loading state
if (connectionState === 'connecting') {
return (
Connecting to Home Assistant...
);
}
// Show error state
if (connectionState === 'error') {
return (
Connection Error
Failed to connect to Home Assistant. Please check your configuration.
);
}
return (
<>
{lightsOverlayOpen && }
{locksOverlayOpen && }
{thermostatsOverlayOpen && }
{mediaOverlayOpen && }
{controlsOverlayOpen && }
{cameraOverlayOpen && }
{settingsOpen && }
{isIdle && !alertCamera && }
{alertCamera && }
>
);
}