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

108
components/AddTaskModal.tsx Normal file
View File

@@ -0,0 +1,108 @@
"use client";
import { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { X, Plus } from "lucide-react";
interface Props {
open: boolean;
onClose: () => void;
onAdd: (title: string) => void;
}
export default function AddTaskModal({ open, onClose, onAdd }: Props) {
const [title, setTitle] = useState("");
const handleSubmit = () => {
if (!title.trim()) return;
onAdd(title.trim());
setTitle("");
onClose();
};
return (
<AnimatePresence>
{open && (
<motion.div
className="modal-backdrop"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={onClose}
>
<motion.div
className="glass-card p-6 w-96 mx-4"
style={{ border: "1px solid rgba(99,102,241,0.3)" }}
initial={{ opacity: 0, scale: 0.85, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.85, y: 20 }}
transition={{ type: "spring", stiffness: 400, damping: 30 }}
onClick={(e) => e.stopPropagation()}
>
<div className="flex items-center justify-between mb-5">
<h3
className="text-lg font-semibold"
style={{ color: "var(--text-primary)" }}
>
Новая задача
</h3>
<motion.button
onClick={onClose}
className="w-8 h-8 rounded-lg flex items-center justify-center"
style={{ background: "rgba(255,255,255,0.08)" }}
whileTap={{ scale: 0.9 }}
>
<X size={16} color="var(--text-secondary)" />
</motion.button>
</div>
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && handleSubmit()}
placeholder="Название задачи..."
autoFocus
className="w-full px-4 py-3 rounded-xl text-sm outline-none mb-4"
style={{
background: "rgba(255,255,255,0.06)",
border: "1px solid rgba(255,255,255,0.1)",
color: "var(--text-primary)",
}}
/>
<div className="flex gap-3">
<motion.button
onClick={onClose}
className="flex-1 py-3 rounded-xl text-sm font-medium"
style={{
background: "rgba(255,255,255,0.06)",
color: "var(--text-secondary)",
border: "1px solid rgba(255,255,255,0.08)",
}}
whileTap={{ scale: 0.95 }}
>
Отмена
</motion.button>
<motion.button
onClick={handleSubmit}
className="flex-1 py-3 rounded-xl text-sm font-semibold flex items-center justify-center gap-2"
style={{
background:
"linear-gradient(135deg, #6366f1, #8b5cf6)",
color: "white",
boxShadow: "0 0 20px rgba(99,102,241,0.4)",
}}
whileTap={{ scale: 0.95 }}
disabled={!title.trim()}
>
<Plus size={16} />
Добавить
</motion.button>
</div>
</motion.div>
</motion.div>
)}
</AnimatePresence>
);
}