feat: initial smart home dashboard
All checks were successful
Deploy to Coolify / deploy (push) Successful in 44s

- Next.js 14 + TypeScript + Tailwind CSS
- Glassmorphism design with ambient orbs
- Cards: Light x2, Temperature, AirPurifier, Tasks, Weather, Savings
- Home Assistant integration (demo mode if no token)
- Vikunja tasks API
- Pulse savings API
- wttr.in weather
- Framer Motion animations
- Dark/light theme toggle
- Bottom navigation
- Dockerfile for deployment
This commit is contained in:
Cosmo
2026-04-22 10:00:41 +00:00
commit 9044869fa4
29 changed files with 2439 additions and 0 deletions

79
hooks/useHA.ts Normal file
View File

@@ -0,0 +1,79 @@
"use client";
import { useState, useEffect, useCallback } from "react";
import { HAStates } from "@/lib/ha";
export function useHA(refreshInterval = 10000) {
const [data, setData] = useState<HAStates | null>(null);
const [loading, setLoading] = useState(true);
const refresh = useCallback(async () => {
try {
const res = await fetch("/api/ha", { cache: "no-store" });
const json = await res.json();
setData(json);
} catch (e) {
console.error("HA fetch failed", e);
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
refresh();
const id = setInterval(refresh, refreshInterval);
return () => clearInterval(id);
}, [refresh, refreshInterval]);
return { data, loading, refresh };
}
export function useWeather() {
const [weather, setWeather] = useState<any>(null);
useEffect(() => {
fetch("/api/weather")
.then((r) => r.json())
.then(setWeather)
.catch(() => {});
}, []);
return weather;
}
export function useTasks() {
const [tasks, setTasks] = useState<any[]>([]);
const [demo, setDemo] = useState(false);
const refresh = useCallback(async () => {
try {
const res = await fetch("/api/tasks", { cache: "no-store" });
const json = await res.json();
setTasks(json.tasks || []);
setDemo(!!json.demo);
} catch (e) {}
}, []);
useEffect(() => {
refresh();
}, [refresh]);
return { tasks, setTasks, demo, refresh };
}
export function useSavings() {
const [savings, setSavings] = useState<any[]>([]);
const [demo, setDemo] = useState(false);
useEffect(() => {
fetch("/api/savings")
.then((r) => r.json())
.then((d) => {
setSavings(d.savings || []);
setDemo(!!d.demo);
})
.catch(() => {});
}, []);
return { savings, demo };
}