Phase 1 & 2: full-stack family dashboard scaffold

- pnpm monorepo (apps/client + apps/server)
- Server: Express + node:sqlite with numbered migration runner,
  REST API for all 9 features (members, events, chores, shopping,
  meals, messages, countdowns, photos, settings)
- Client: React 18 + Vite + TypeScript + Tailwind + Framer Motion + Zustand
- Theme system: dark/light + 5 accent colors, CSS custom properties,
  anti-FOUC script, ThemeToggle on every surface
- AppShell: collapsible sidebar, animated route transitions, mobile drawer
- Phase 2 features: Calendar (custom month grid, event chips, add/edit modal),
  Chores (card grid, complete/reset, member filter, streaks),
  Shopping (multi-list tabs, animated check-off, quick-add bar, member assign)
- Family member CRUD with avatar, color picker
- Settings page: theme/accent, photo folder, slideshow, weather, date/time
- Docker: multi-stage Dockerfile, docker-compose.yml, entrypoint with PUID/PGID
- Unraid: CA XML template, CLI install script, UNRAID.md guide
- .gitignore covering node_modules, dist, db files, secrets, build artifacts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-29 21:56:30 -05:00
parent 6e44883365
commit 35ed5223a0
58 changed files with 6224 additions and 0 deletions
+38
View File
@@ -0,0 +1,38 @@
import { forwardRef, InputHTMLAttributes } from 'react';
import { clsx } from 'clsx';
interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
label?: string;
error?: string;
hint?: string;
}
export const Input = forwardRef<HTMLInputElement, InputProps>(
({ label, error, hint, className, id, ...props }, ref) => {
const inputId = id ?? label?.toLowerCase().replace(/\s+/g, '-');
return (
<div className="flex flex-col gap-1.5">
{label && (
<label htmlFor={inputId} className="text-sm font-medium text-secondary">
{label}
</label>
)}
<input
ref={ref}
id={inputId}
className={clsx(
'w-full rounded-lg border border-theme bg-surface-raised px-3 py-2 text-sm text-primary',
'placeholder:text-muted focus:outline-none focus:ring-2 ring-accent focus:border-transparent',
'transition-colors duration-150',
error && 'border-red-400 focus:ring-red-400',
className
)}
{...props}
/>
{hint && !error && <p className="text-xs text-muted">{hint}</p>}
{error && <p className="text-xs text-red-500">{error}</p>}
</div>
);
}
);
Input.displayName = 'Input';