updated name from lifemanager to umbra,

This commit is contained in:
Kyle 2026-02-15 20:21:55 +08:00
parent 96c225f4f7
commit e6387065ad
17 changed files with 74 additions and 44 deletions

View File

@ -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

View File

@ -1,4 +1,4 @@
# CLAUDE.md - LifeManager # CLAUDE.md - UMBRA
## Hard Rules ## Hard Rules

View File

@ -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/

View File

@ -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

View File

@ -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

View File

@ -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(

View File

@ -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")

View File

@ -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

View File

@ -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>

View File

@ -1,5 +1,5 @@
{ {
"name": "lifemanager", "name": "umbra",
"private": true, "private": true,
"version": "1.0.0", "version": "1.0.0",
"type": "module", "type": "module",

View File

@ -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

View File

@ -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 />

View File

@ -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"

View File

@ -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) => (

View File

@ -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."
/>
); );
} }

View File

@ -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."
/>
); );
} }

View File

@ -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
--- ---