Skip to content

Commit dc776c1

Browse files
committed
Refactor shared app state and feature utilities
1 parent 7475aa5 commit dc776c1

122 files changed

Lines changed: 1103 additions & 2650 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.typecheck-baseline.txt

Lines changed: 4 additions & 93 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ Navet (Swedish for "the hub") is a modern, responsive smart home dashboard built
6969
- **Local Config Backup** - Export and restore dashboard layout/preferences from a JSON file
7070
- **Tree-shakeable** - Only imports what's actually used
7171

72+
### 🧱 Architecture
73+
- **Direct Hook APIs** - Theme, navigation, search, and Home Assistant access now use hook modules directly instead of passthrough provider wrappers
74+
- **Shared Utilities** - Device room lookup, theme color resolution, wallpaper upload handling, notification styling, and vacuum status helpers are centralized to reduce fan-out edits
75+
- **Consistent Persistence** - Storage keys and session/config persistence are standardized behind shared helpers
76+
7277
## 🚀 Installation
7378

7479
### Prerequisites

src/app/App.tsx

Lines changed: 117 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,11 @@ import { RenderProfiler } from './components/shared/render-profiler';
2424
import { Toaster } from './components/ui/sonner';
2525
import { AuthProvider, useAuth } from './contexts/auth-context';
2626
import { ConfigProvider, useConfig } from './contexts/config-context';
27-
import { EditModeProvider } from './contexts/edit-mode-context';
2827
import { ErrorProvider } from './contexts/error-context';
29-
import { HomeAssistantProvider, useHomeAssistantContext } from './contexts/home-assistant-context';
3028
import { LoadingProvider } from './contexts/loading-context';
31-
import { NavigationProvider, useNavigation } from './contexts/navigation-context';
32-
import { SearchProvider } from './contexts/search-context';
33-
import { ThemeProvider } from './contexts/theme-context';
3429
import { LoginPage } from './features/auth/login-page';
3530
import { AllViewGrid } from './features/dashboard/all-view-grid';
36-
import type { CardType } from './features/dashboard/components/add-card-dialog';
31+
import type { CardType } from './features/dashboard/components/AddCardDialogContainer';
3732
import { DashboardLayout } from './features/dashboard/dashboard-layout';
3833
import { DeviceGrid } from './features/dashboard/device-grid';
3934
import {
@@ -42,16 +37,19 @@ import {
4237
useDashboardDevices,
4338
useDeviceMap,
4439
useEditMode,
40+
useHomeAssistant,
41+
useNavigation,
4542
useRoomNavigation,
4643
useRoomOrdering,
4744
} from './hooks';
4845
import { useCustomCards } from './hooks/use-custom-cards';
4946
import { useDevices, useRooms } from './hooks/use-devices';
5047
import { useDashboardEntitiesStore, useSettingsStore } from './stores';
48+
import { getDeviceRoom } from './utils/device-location';
5149

5250
const AddCardDialog = lazy(async () => {
53-
const module = await import('./features/dashboard/components/add-card-dialog');
54-
return { default: module.AddCardDialog };
51+
const module = await import('./features/dashboard/components/AddCardDialogContainer');
52+
return { default: module.AddCardDialogContainer };
5553
});
5654

5755
const AddEntityDialog = lazy(async () => {
@@ -70,7 +68,7 @@ const SettingsSection = lazy(async () => {
7068
*/
7169
function Dashboard() {
7270
const { activeSection } = useNavigation();
73-
const { connected, connecting, error } = useHomeAssistantContext();
71+
const { connected, connecting, error } = useHomeAssistant();
7472
const [devicesLoaded, setDevicesLoaded] = useState(false);
7573
const [showAddCardDialog, setShowAddCardDialog] = useState(false);
7674
const [showAddEntityDialog, setShowAddEntityDialog] = useState(false);
@@ -109,9 +107,7 @@ function Dashboard() {
109107
(cardId: string) => {
110108
const device = deviceMap.get(cardId);
111109
if (device) {
112-
return ('room' in device ? device.room : 'location' in device ? device.location : null) as
113-
| string
114-
| null;
110+
return getDeviceRoom(device);
115111
}
116112

117113
const customCard = allCustomCards.find((card) => card.id === cardId);
@@ -122,8 +118,9 @@ function Dashboard() {
122118
const lightRooms = useMemo(() => {
123119
const roomsWithLights = new Set<string>();
124120
lightDeviceMap.forEach((device) => {
125-
if ('room' in device && device.room) {
126-
roomsWithLights.add(device.room);
121+
const room = getDeviceRoom(device);
122+
if (room) {
123+
roomsWithLights.add(room);
127124
}
128125
});
129126
return roomOrder.filter((room) => roomsWithLights.has(room));
@@ -219,17 +216,6 @@ function Dashboard() {
219216
[updateCard]
220217
);
221218

222-
// Edit mode context value
223-
const editModeContextValue = useMemo(
224-
() => ({
225-
isEditMode,
226-
toggleEditMode,
227-
cardSizes,
228-
updateCardSize,
229-
}),
230-
[isEditMode, toggleEditMode, cardSizes, updateCardSize]
231-
);
232-
233219
// Show loading state during initial load
234220
if (!devicesLoaded) {
235221
const message = connecting ? 'Connecting to Home Assistant...' : 'Loading devices...';
@@ -269,11 +255,16 @@ function Dashboard() {
269255
return (
270256
<DashboardLayout>
271257
{lightDeviceMap.size > 0 ? (
272-
<EditModeProvider value={editModeContextValue}>
273-
<RenderProfiler id="LightsSection">
274-
<AllViewGrid deviceMap={lightDeviceMap} rooms={lightRooms} cardOrders={cardOrders} />
275-
</RenderProfiler>
276-
</EditModeProvider>
258+
<RenderProfiler id="LightsSection">
259+
<AllViewGrid
260+
deviceMap={lightDeviceMap}
261+
rooms={lightRooms}
262+
cardOrders={cardOrders}
263+
isEditMode={isEditMode}
264+
cardSizes={cardSizes}
265+
updateCardSize={updateCardSize}
266+
/>
267+
</RenderProfiler>
277268
) : (
278269
<EmptyState
279270
icon={Lightbulb}
@@ -313,93 +304,95 @@ function Dashboard() {
313304

314305
// Default home section
315306
return (
316-
<EditModeProvider value={editModeContextValue}>
317-
<DndContext
318-
sensors={sensors}
319-
collisionDetection={closestCenter}
320-
onDragOver={handleDragOver}
321-
onDragEnd={handleDragEnd}
322-
>
323-
<DashboardLayout>
324-
<RoomNav
325-
rooms={roomOrder}
326-
activeRoom={activeRoom}
327-
onRoomChange={changeRoom}
328-
isEditMode={isEditMode}
329-
onToggleEditMode={toggleEditMode}
330-
onMoveRoom={moveRoom}
331-
onAddCard={() => setShowAddCardDialog(true)}
332-
onAddEntity={
333-
dashboardMode === 'manual' ? () => setShowAddEntityDialog(true) : undefined
334-
}
335-
/>
307+
<DndContext
308+
sensors={sensors}
309+
collisionDetection={closestCenter}
310+
onDragOver={handleDragOver}
311+
onDragEnd={handleDragEnd}
312+
>
313+
<DashboardLayout>
314+
<RoomNav
315+
rooms={roomOrder}
316+
activeRoom={activeRoom}
317+
onRoomChange={changeRoom}
318+
isEditMode={isEditMode}
319+
onToggleEditMode={toggleEditMode}
320+
onMoveRoom={moveRoom}
321+
onAddCard={() => setShowAddCardDialog(true)}
322+
onAddEntity={dashboardMode === 'manual' ? () => setShowAddEntityDialog(true) : undefined}
323+
/>
324+
325+
{activeRoom === 'All' ? (
326+
<RenderProfiler id="AllViewGrid">
327+
<AllViewGrid
328+
deviceMap={deviceMap}
329+
rooms={roomOrder}
330+
cardOrders={cardOrders}
331+
isEditMode={isEditMode}
332+
cardSizes={cardSizes}
333+
updateCardSize={updateCardSize}
334+
customCards={customCards}
335+
onDeleteCard={handleDeleteCard}
336+
onUpdateCard={handleUpdateCard}
337+
onRemoveEntity={handleRemoveEntity}
338+
allowEntityRemoval={dashboardMode === 'manual'}
339+
/>
340+
</RenderProfiler>
341+
) : (
342+
<RenderProfiler id={`DeviceGrid:${activeRoom}`}>
343+
<DeviceGrid
344+
orderedCardIds={orderedCardIds}
345+
deviceMap={deviceMap}
346+
isEditMode={isEditMode}
347+
cardSizes={cardSizes}
348+
updateCardSize={updateCardSize}
349+
customCards={customCards}
350+
onDeleteCard={handleDeleteCard}
351+
onUpdateCard={handleUpdateCard}
352+
onRemoveEntity={handleRemoveEntity}
353+
allowEntityRemoval={dashboardMode === 'manual'}
354+
/>
355+
</RenderProfiler>
356+
)}
336357

337-
{activeRoom === 'All' ? (
338-
<RenderProfiler id="AllViewGrid">
339-
<AllViewGrid
340-
deviceMap={deviceMap}
341-
rooms={roomOrder}
342-
cardOrders={cardOrders}
343-
customCards={customCards}
344-
onDeleteCard={handleDeleteCard}
345-
onUpdateCard={handleUpdateCard}
346-
onRemoveEntity={handleRemoveEntity}
347-
allowEntityRemoval={dashboardMode === 'manual'}
348-
/>
349-
</RenderProfiler>
350-
) : (
351-
<RenderProfiler id={`DeviceGrid:${activeRoom}`}>
352-
<DeviceGrid
353-
orderedCardIds={orderedCardIds}
354-
deviceMap={deviceMap}
355-
customCards={customCards}
356-
onDeleteCard={handleDeleteCard}
357-
onUpdateCard={handleUpdateCard}
358-
onRemoveEntity={handleRemoveEntity}
359-
allowEntityRemoval={dashboardMode === 'manual'}
360-
/>
361-
</RenderProfiler>
358+
{dashboardMode === 'manual' &&
359+
deviceMap.size === 0 &&
360+
customCards.length === 0 &&
361+
activeRoom === 'All' && (
362+
<EmptyState
363+
icon={Lightbulb}
364+
title="No Entities Added"
365+
description="Switch to edit mode and add only the Home Assistant entities you want on the dashboard."
366+
actionLabel={isEditMode ? 'Add Entity' : undefined}
367+
onAction={isEditMode ? () => setShowAddEntityDialog(true) : undefined}
368+
/>
362369
)}
363370

364-
{dashboardMode === 'manual' &&
365-
deviceMap.size === 0 &&
366-
customCards.length === 0 &&
367-
activeRoom === 'All' && (
368-
<EmptyState
369-
icon={Lightbulb}
370-
title="No Entities Added"
371-
description="Switch to edit mode and add only the Home Assistant entities you want on the dashboard."
372-
actionLabel={isEditMode ? 'Add Entity' : undefined}
373-
onAction={isEditMode ? () => setShowAddEntityDialog(true) : undefined}
374-
/>
375-
)}
376-
377-
{showAddCardDialog && (
378-
<Suspense fallback={null}>
379-
<AddCardDialog
380-
open={showAddCardDialog}
381-
onClose={() => setShowAddCardDialog(false)}
382-
onAddCard={handleAddCard}
383-
currentRoom={activeRoom}
384-
/>
385-
</Suspense>
386-
)}
371+
{showAddCardDialog && (
372+
<Suspense fallback={null}>
373+
<AddCardDialog
374+
open={showAddCardDialog}
375+
onClose={() => setShowAddCardDialog(false)}
376+
onAddCard={handleAddCard}
377+
currentRoom={activeRoom}
378+
/>
379+
</Suspense>
380+
)}
387381

388-
{showAddEntityDialog && (
389-
<Suspense fallback={null}>
390-
<AddEntityDialog
391-
open={showAddEntityDialog}
392-
onClose={() => setShowAddEntityDialog(false)}
393-
onAddEntity={handleAddEntity}
394-
currentRoom={activeRoom}
395-
deviceMap={availableDeviceMap}
396-
addedEntityIds={manualEntityIds}
397-
/>
398-
</Suspense>
399-
)}
400-
</DashboardLayout>
401-
</DndContext>
402-
</EditModeProvider>
382+
{showAddEntityDialog && (
383+
<Suspense fallback={null}>
384+
<AddEntityDialog
385+
open={showAddEntityDialog}
386+
onClose={() => setShowAddEntityDialog(false)}
387+
onAddEntity={handleAddEntity}
388+
currentRoom={activeRoom}
389+
deviceMap={availableDeviceMap}
390+
addedEntityIds={manualEntityIds}
391+
/>
392+
</Suspense>
393+
)}
394+
</DashboardLayout>
395+
</DndContext>
403396
);
404397
}
405398

@@ -410,7 +403,7 @@ function Dashboard() {
410403
function AppContent() {
411404
const { isAuthenticated, config: authConfig } = useAuth();
412405
const { config: haConfig } = useConfig();
413-
const { connected, connecting, connect } = useHomeAssistantContext();
406+
const { connected, connecting, connect } = useHomeAssistant();
414407
const disableAnimations = useSettingsStore((state) => state.disableAnimations);
415408

416409
// Attempt to connect to Home Assistant when authenticated but not connected
@@ -444,26 +437,18 @@ function AppContent() {
444437

445438
/**
446439
* Main App Component
447-
* Provides all context providers
440+
* Provides app shell providers.
448441
*/
449442
export default function App() {
450443
return (
451-
<ThemeProvider>
452-
<ConfigProvider>
453-
<LoadingProvider>
454-
<ErrorProvider>
455-
<AuthProvider>
456-
<SearchProvider>
457-
<NavigationProvider>
458-
<HomeAssistantProvider>
459-
<AppContent />
460-
</HomeAssistantProvider>
461-
</NavigationProvider>
462-
</SearchProvider>
463-
</AuthProvider>
464-
</ErrorProvider>
465-
</LoadingProvider>
466-
</ConfigProvider>
467-
</ThemeProvider>
444+
<ConfigProvider>
445+
<LoadingProvider>
446+
<ErrorProvider>
447+
<AuthProvider>
448+
<AppContent />
449+
</AuthProvider>
450+
</ErrorProvider>
451+
</LoadingProvider>
452+
</ConfigProvider>
468453
);
469454
}

0 commit comments

Comments
 (0)