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

82
app/api/tasks/route.ts Normal file
View File

@@ -0,0 +1,82 @@
import { NextRequest, NextResponse } from "next/server";
const VIKUNJA_URL = process.env.VIKUNJA_URL || "https://tasks.digital-home.site";
const VIKUNJA_TOKEN = process.env.VIKUNJA_TOKEN || "tk_03787e3778789fd5bfaff0542a8dd9390aae0f82";
const MOCK_TASKS = [
{ id: 1, title: "Записаться на ТО", done: false, priority: 2 },
{ id: 2, title: "Оплатить аренду", done: true, priority: 3 },
{ id: 3, title: "Купить продукты", done: false, priority: 1 },
];
export async function GET() {
try {
const today = new Date().toISOString().split("T")[0];
const res = await fetch(
`${VIKUNJA_URL}/api/v1/tasks/all?filter_by=due_date&filter_value=${today}&filter_comparator=equals&per_page=20`,
{
headers: {
Authorization: `Bearer ${VIKUNJA_TOKEN}`,
"Content-Type": "application/json",
},
next: { revalidate: 0 },
}
);
if (!res.ok) throw new Error(`Vikunja responded ${res.status}`);
const data = await res.json();
return NextResponse.json({ tasks: Array.isArray(data) ? data : [] });
} catch (e) {
return NextResponse.json({ tasks: MOCK_TASKS, demo: true });
}
}
export async function POST(req: NextRequest) {
const { title } = await req.json();
if (!title) return NextResponse.json({ error: "Title required" }, { status: 400 });
const today = new Date();
today.setHours(23, 59, 59, 0);
try {
const res = await fetch(`${VIKUNJA_URL}/api/v1/projects/3/tasks`, {
method: "PUT",
headers: {
Authorization: `Bearer ${VIKUNJA_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
title,
due_date: today.toISOString(),
}),
});
if (!res.ok) throw new Error(`Vikunja responded ${res.status}`);
const task = await res.json();
return NextResponse.json({ task });
} catch (e) {
return NextResponse.json(
{ task: { id: Date.now(), title, done: false }, demo: true }
);
}
}
export async function PATCH(req: NextRequest) {
const { id, done } = await req.json();
try {
const res = await fetch(`${VIKUNJA_URL}/api/v1/tasks/${id}`, {
method: "POST",
headers: {
Authorization: `Bearer ${VIKUNJA_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ done }),
});
if (!res.ok) throw new Error(`Vikunja responded ${res.status}`);
return NextResponse.json({ success: true });
} catch (e) {
return NextResponse.json({ success: true, demo: true });
}
}