updated name from lifemanager to umbra,
This commit is contained in:
parent
96c225f4f7
commit
e6387065ad
@ -1,8 +1,8 @@
|
|||||||
# Database
|
# Database
|
||||||
POSTGRES_USER=lifemanager
|
POSTGRES_USER=umbra
|
||||||
POSTGRES_PASSWORD=changeme_in_production
|
POSTGRES_PASSWORD=changeme_in_production
|
||||||
POSTGRES_DB=lifemanager
|
POSTGRES_DB=umbra
|
||||||
|
|
||||||
# Backend
|
# Backend
|
||||||
DATABASE_URL=postgresql+asyncpg://lifemanager:changeme_in_production@db:5432/lifemanager
|
DATABASE_URL=postgresql+asyncpg://umbra:changeme_in_production@db:5432/umbra
|
||||||
SECRET_KEY=change-this-to-a-random-secret-key-in-production
|
SECRET_KEY=change-this-to-a-random-secret-key-in-production
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
# CLAUDE.md - LifeManager
|
# CLAUDE.md - UMBRA
|
||||||
|
|
||||||
## Hard Rules
|
## Hard Rules
|
||||||
|
|
||||||
|
|||||||
10
README.md
10
README.md
@ -40,7 +40,7 @@ A self-hosted personal life administration app with a dark-themed UI. Manage you
|
|||||||
1. **Clone the repository**
|
1. **Clone the repository**
|
||||||
```bash
|
```bash
|
||||||
git clone https://your-gitea-instance/youruser/umbra.git
|
git clone https://your-gitea-instance/youruser/umbra.git
|
||||||
cd lifemanager
|
cd umbra
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Configure environment variables**
|
2. **Configure environment variables**
|
||||||
@ -49,10 +49,10 @@ A self-hosted personal life administration app with a dark-themed UI. Manage you
|
|||||||
```
|
```
|
||||||
Edit `.env` and set secure values:
|
Edit `.env` and set secure values:
|
||||||
```env
|
```env
|
||||||
POSTGRES_USER=lifemanager
|
POSTGRES_USER=umbra
|
||||||
POSTGRES_PASSWORD=your-secure-password
|
POSTGRES_PASSWORD=your-secure-password
|
||||||
POSTGRES_DB=lifemanager
|
POSTGRES_DB=umbra
|
||||||
DATABASE_URL=postgresql+asyncpg://lifemanager:your-secure-password@db:5432/lifemanager
|
DATABASE_URL=postgresql+asyncpg://umbra:your-secure-password@db:5432/umbra
|
||||||
SECRET_KEY=your-random-secret-key
|
SECRET_KEY=your-random-secret-key
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -150,7 +150,7 @@ docker-compose down
|
|||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
lifemanager/
|
umbra/
|
||||||
├── docker-compose.yaml
|
├── docker-compose.yaml
|
||||||
├── .env / .env.example
|
├── .env / .env.example
|
||||||
├── backend/
|
├── backend/
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/lifemanager
|
DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/umbra
|
||||||
SECRET_KEY=your-secret-key-change-in-production-use-a-long-random-string
|
SECRET_KEY=your-secret-key-change-in-production-use-a-long-random-string
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
# LifeManager Backend
|
# UMBRA Backend
|
||||||
|
|
||||||
A complete FastAPI backend for the LifeManager application with async SQLAlchemy, PostgreSQL, authentication, and comprehensive CRUD operations.
|
A complete FastAPI backend for the UMBRA application with async SQLAlchemy, PostgreSQL, authentication, and comprehensive CRUD operations.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@ -50,14 +50,14 @@ pip install -r requirements.txt
|
|||||||
Create a `.env` file:
|
Create a `.env` file:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/lifemanager
|
DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/umbra
|
||||||
SECRET_KEY=your-secret-key-change-in-production
|
SECRET_KEY=your-secret-key-change-in-production
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Create Database
|
### 3. Create Database
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
createdb lifemanager
|
createdb umbra
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. Run Migrations
|
### 4. Run Migrations
|
||||||
@ -167,8 +167,8 @@ The application uses the following tables:
|
|||||||
Build and run with Docker:
|
Build and run with Docker:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker build -t lifemanager-backend .
|
docker build -t umbra-backend .
|
||||||
docker run -p 8000:8000 -e DATABASE_URL=... -e SECRET_KEY=... lifemanager-backend
|
docker run -p 8000:8000 -e DATABASE_URL=... -e SECRET_KEY=... umbra-backend
|
||||||
```
|
```
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|||||||
@ -2,7 +2,7 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
|
|||||||
|
|
||||||
|
|
||||||
class Settings(BaseSettings):
|
class Settings(BaseSettings):
|
||||||
DATABASE_URL: str = "postgresql+asyncpg://postgres:postgres@localhost:5432/lifemanager"
|
DATABASE_URL: str = "postgresql+asyncpg://postgres:postgres@localhost:5432/umbra"
|
||||||
SECRET_KEY: str = "your-secret-key-change-in-production"
|
SECRET_KEY: str = "your-secret-key-change-in-production"
|
||||||
|
|
||||||
model_config = SettingsConfigDict(
|
model_config = SettingsConfigDict(
|
||||||
|
|||||||
@ -17,8 +17,8 @@ async def lifespan(app: FastAPI):
|
|||||||
|
|
||||||
|
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
title="LifeManager API",
|
title="UMBRA API",
|
||||||
description="Backend API for LifeManager application",
|
description="Backend API for UMBRA application",
|
||||||
version="1.0.0",
|
version="1.0.0",
|
||||||
lifespan=lifespan
|
lifespan=lifespan
|
||||||
)
|
)
|
||||||
@ -46,7 +46,7 @@ app.include_router(dashboard.router, prefix="/api", tags=["Dashboard"])
|
|||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
async def root():
|
async def root():
|
||||||
return {"message": "LifeManager API is running"}
|
return {"message": "UMBRA API is running"}
|
||||||
|
|
||||||
|
|
||||||
@app.get("/health")
|
@app.get("/health")
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
# LifeManager Frontend
|
# UMBRA Frontend
|
||||||
|
|
||||||
A modern, dark-themed React application for managing your life - todos, calendar events, reminders, projects, people, and locations.
|
A modern, dark-themed React application for managing your life - todos, calendar events, reminders, projects, people, and locations.
|
||||||
|
|
||||||
@ -90,10 +90,10 @@ Build and run with Docker:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Build image
|
# Build image
|
||||||
docker build -t lifemanager-frontend .
|
docker build -t umbra-frontend .
|
||||||
|
|
||||||
# Run container
|
# Run container
|
||||||
docker run -p 80:80 lifemanager-frontend
|
docker run -p 80:80 umbra-frontend
|
||||||
```
|
```
|
||||||
|
|
||||||
## Environment Variables
|
## Environment Variables
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>LifeManager</title>
|
<title>UMBRA</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "lifemanager",
|
"name": "umbra",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@ -54,7 +54,7 @@ export default function LockScreen() {
|
|||||||
<Lock className="h-8 w-8 text-accent" />
|
<Lock className="h-8 w-8 text-accent" />
|
||||||
</div>
|
</div>
|
||||||
<CardTitle className="text-2xl">
|
<CardTitle className="text-2xl">
|
||||||
{isSetup ? 'Welcome to LifeManager' : 'Enter PIN'}
|
{isSetup ? 'Welcome to UMBRA' : 'Enter PIN'}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
{isSetup
|
{isSetup
|
||||||
|
|||||||
@ -24,7 +24,7 @@ export default function AppLayout() {
|
|||||||
<Button variant="ghost" size="icon" onClick={() => setMobileOpen(true)}>
|
<Button variant="ghost" size="icon" onClick={() => setMobileOpen(true)}>
|
||||||
<Menu className="h-5 w-5" />
|
<Menu className="h-5 w-5" />
|
||||||
</Button>
|
</Button>
|
||||||
<h1 className="text-lg font-bold text-accent ml-3">LifeManager</h1>
|
<h1 className="text-lg font-bold text-accent ml-3">UMBRA</h1>
|
||||||
</div>
|
</div>
|
||||||
<main className="flex-1 overflow-y-auto">
|
<main className="flex-1 overflow-y-auto">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
|
|||||||
@ -44,7 +44,7 @@ export default function Sidebar({ collapsed, onToggle, mobileOpen, onMobileClose
|
|||||||
const sidebarContent = (
|
const sidebarContent = (
|
||||||
<>
|
<>
|
||||||
<div className="flex h-16 items-center justify-between border-b px-4">
|
<div className="flex h-16 items-center justify-between border-b px-4">
|
||||||
{!collapsed && <h1 className="text-xl font-bold text-accent">LifeManager</h1>}
|
{!collapsed && <h1 className="text-xl font-bold text-accent">UMBRA</h1>}
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
|
|||||||
@ -2,13 +2,15 @@ import { useState } from 'react';
|
|||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { ArrowLeft, Plus, Trash2 } from 'lucide-react';
|
import { ArrowLeft, Plus, Trash2, ListChecks } from 'lucide-react';
|
||||||
import api from '@/lib/api';
|
import api from '@/lib/api';
|
||||||
import type { Project, ProjectTask } from '@/types';
|
import type { Project, ProjectTask } from '@/types';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Card, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
import { Card, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
||||||
import { Checkbox } from '@/components/ui/checkbox';
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
|
import { ListSkeleton } from '@/components/ui/skeleton';
|
||||||
|
import { EmptyState } from '@/components/ui/empty-state';
|
||||||
import TaskForm from './TaskForm';
|
import TaskForm from './TaskForm';
|
||||||
import ProjectForm from './ProjectForm';
|
import ProjectForm from './ProjectForm';
|
||||||
|
|
||||||
@ -82,7 +84,21 @@ export default function ProjectDetail() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <div className="p-6 text-center text-muted-foreground">Loading project...</div>;
|
return (
|
||||||
|
<div className="flex flex-col h-full">
|
||||||
|
<div className="border-b bg-card px-6 py-4">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<Button variant="ghost" size="icon" onClick={() => navigate('/projects')}>
|
||||||
|
<ArrowLeft className="h-5 w-5" />
|
||||||
|
</Button>
|
||||||
|
<h1 className="text-3xl font-bold flex-1">Loading...</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 overflow-y-auto p-6">
|
||||||
|
<ListSkeleton rows={4} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!project) {
|
if (!project) {
|
||||||
@ -120,9 +136,13 @@ export default function ProjectDetail() {
|
|||||||
|
|
||||||
<div className="flex-1 overflow-y-auto p-6">
|
<div className="flex-1 overflow-y-auto p-6">
|
||||||
{!project.tasks || project.tasks.length === 0 ? (
|
{!project.tasks || project.tasks.length === 0 ? (
|
||||||
<div className="text-center py-12">
|
<EmptyState
|
||||||
<p className="text-muted-foreground">No tasks yet. Add one to get started!</p>
|
icon={ListChecks}
|
||||||
</div>
|
title="No tasks yet"
|
||||||
|
description="Break this project down into tasks to track your progress."
|
||||||
|
actionLabel="Add Task"
|
||||||
|
onAction={() => setShowTaskForm(true)}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{project.tasks.map((task) => (
|
{project.tasks.map((task) => (
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import type { Reminder } from '@/types';
|
|||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { EmptyState } from '@/components/ui/empty-state';
|
||||||
|
|
||||||
interface ReminderListProps {
|
interface ReminderListProps {
|
||||||
reminders: Reminder[];
|
reminders: Reminder[];
|
||||||
@ -45,9 +46,11 @@ export default function ReminderList({ reminders, onEdit }: ReminderListProps) {
|
|||||||
|
|
||||||
if (reminders.length === 0) {
|
if (reminders.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="text-center py-12">
|
<EmptyState
|
||||||
<p className="text-muted-foreground">No reminders found.</p>
|
icon={Bell}
|
||||||
</div>
|
title="No reminders"
|
||||||
|
description="Create a reminder so you never miss an important date or event."
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
|
import { CheckSquare } from 'lucide-react';
|
||||||
import type { Todo } from '@/types';
|
import type { Todo } from '@/types';
|
||||||
|
import { EmptyState } from '@/components/ui/empty-state';
|
||||||
import TodoItem from './TodoItem';
|
import TodoItem from './TodoItem';
|
||||||
|
|
||||||
interface TodoListProps {
|
interface TodoListProps {
|
||||||
@ -9,9 +11,11 @@ interface TodoListProps {
|
|||||||
export default function TodoList({ todos, onEdit }: TodoListProps) {
|
export default function TodoList({ todos, onEdit }: TodoListProps) {
|
||||||
if (todos.length === 0) {
|
if (todos.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="text-center py-12">
|
<EmptyState
|
||||||
<p className="text-muted-foreground">No todos found. Create one to get started!</p>
|
icon={CheckSquare}
|
||||||
</div>
|
title="No todos yet"
|
||||||
|
description="Create your first todo to start tracking tasks and staying organised."
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
13
progress.md
13
progress.md
@ -1,4 +1,4 @@
|
|||||||
# LifeManager - Project Progress
|
# UMBRA - Project Progress
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
Personal life administration web app with dark theme, accent color customization, and Docker deployment.
|
Personal life administration web app with dark theme, accent color customization, and Docker deployment.
|
||||||
@ -71,7 +71,7 @@ Personal life administration web app with dark theme, accent color customization
|
|||||||
- [x] Integration fixes: HTTP methods (PUT for updates, PATCH for toggle/dismiss)
|
- [x] Integration fixes: HTTP methods (PUT for updates, PATCH for toggle/dismiss)
|
||||||
- [x] Integration fixes: Type definitions match backend response schemas
|
- [x] Integration fixes: Type definitions match backend response schemas
|
||||||
- [ ] Integration testing (end-to-end CRUD verification) <-- POST-BUILD
|
- [ ] Integration testing (end-to-end CRUD verification) <-- POST-BUILD
|
||||||
- [ ] Final styling pass <-- POST-BUILD
|
- [x] Final styling pass (responsive sidebar, empty states, loading skeletons)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -110,6 +110,9 @@ These were found during first Docker build and integration testing:
|
|||||||
| All-day event dates empty when editing (datetime vs date format mismatch) | Added `formatForInput()` to normalize values for `date` vs `datetime-local` inputs |
|
| All-day event dates empty when editing (datetime vs date format mismatch) | Added `formatForInput()` to normalize values for `date` vs `datetime-local` inputs |
|
||||||
| Project create/update `MissingGreenlet` error (lazy load in async context) | Re-fetch with `selectinload(Project.tasks)` after commit in `projects.py` |
|
| Project create/update `MissingGreenlet` error (lazy load in async context) | Re-fetch with `selectinload(Project.tasks)` after commit in `projects.py` |
|
||||||
| Generic error toasts gave no useful information | Added `getErrorMessage()` helper to `api.ts`, updated all 8 form components |
|
| Generic error toasts gave no useful information | Added `getErrorMessage()` helper to `api.ts`, updated all 8 form components |
|
||||||
|
| Sidebar not responsive on mobile | Split into desktop sidebar + mobile overlay with hamburger menu in `AppLayout` |
|
||||||
|
| Plain "Loading..." text on all pages | Created `Skeleton`, `ListSkeleton`, `GridSkeleton`, `DashboardSkeleton` components |
|
||||||
|
| Basic empty states with no visual cue | Created `EmptyState` component with icon, message, and action button across all pages |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -125,10 +128,10 @@ These were found during first Docker build and integration testing:
|
|||||||
5. **End-to-end CRUD test** - Partially verified: Calendar (create/edit/drag/delete), Projects (create), Dashboard (today's events). Remaining: Todos, Reminders, People, Locations, Settings
|
5. **End-to-end CRUD test** - Partially verified: Calendar (create/edit/drag/delete), Projects (create), Dashboard (today's events). Remaining: Todos, Reminders, People, Locations, Settings
|
||||||
|
|
||||||
### Nice to have (polish):
|
### Nice to have (polish):
|
||||||
6. **Responsive sidebar** - Collapse behavior on mobile
|
6. ~~**Responsive sidebar**~~ - DONE: Mobile hamburger menu with overlay, desktop collapse/expand
|
||||||
7. ~~**Toast notifications**~~ - DONE: All forms now show meaningful error messages via `getErrorMessage()`
|
7. ~~**Toast notifications**~~ - DONE: All forms now show meaningful error messages via `getErrorMessage()`
|
||||||
8. **Empty states** - Good UX for pages with no data yet
|
8. ~~**Empty states**~~ - DONE: All pages show icon + message + action button when empty
|
||||||
9. **Loading skeletons** - Better loading states than plain text
|
9. ~~**Loading skeletons**~~ - DONE: Animated skeleton placeholders replace plain "Loading..." text
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user