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
POSTGRES_USER=lifemanager
POSTGRES_USER=umbra
POSTGRES_PASSWORD=changeme_in_production
POSTGRES_DB=lifemanager
POSTGRES_DB=umbra
# 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

View File

@ -1,4 +1,4 @@
# CLAUDE.md - LifeManager
# CLAUDE.md - UMBRA
## 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**
```bash
git clone https://your-gitea-instance/youruser/umbra.git
cd lifemanager
cd umbra
```
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:
```env
POSTGRES_USER=lifemanager
POSTGRES_USER=umbra
POSTGRES_PASSWORD=your-secure-password
POSTGRES_DB=lifemanager
DATABASE_URL=postgresql+asyncpg://lifemanager:your-secure-password@db:5432/lifemanager
POSTGRES_DB=umbra
DATABASE_URL=postgresql+asyncpg://umbra:your-secure-password@db:5432/umbra
SECRET_KEY=your-random-secret-key
```
@ -150,7 +150,7 @@ docker-compose down
## Project Structure
```
lifemanager/
umbra/
├── docker-compose.yaml
├── .env / .env.example
├── 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

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
@ -50,14 +50,14 @@ pip install -r requirements.txt
Create a `.env` file:
```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
```
### 3. Create Database
```bash
createdb lifemanager
createdb umbra
```
### 4. Run Migrations
@ -167,8 +167,8 @@ The application uses the following tables:
Build and run with Docker:
```bash
docker build -t lifemanager-backend .
docker run -p 8000:8000 -e DATABASE_URL=... -e SECRET_KEY=... lifemanager-backend
docker build -t umbra-backend .
docker run -p 8000:8000 -e DATABASE_URL=... -e SECRET_KEY=... umbra-backend
```
## Development

View File

@ -2,7 +2,7 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
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"
model_config = SettingsConfigDict(

View File

@ -17,8 +17,8 @@ async def lifespan(app: FastAPI):
app = FastAPI(
title="LifeManager API",
description="Backend API for LifeManager application",
title="UMBRA API",
description="Backend API for UMBRA application",
version="1.0.0",
lifespan=lifespan
)
@ -46,7 +46,7 @@ app.include_router(dashboard.router, prefix="/api", tags=["Dashboard"])
@app.get("/")
async def root():
return {"message": "LifeManager API is running"}
return {"message": "UMBRA API is running"}
@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.
@ -90,10 +90,10 @@ Build and run with Docker:
```bash
# Build image
docker build -t lifemanager-frontend .
docker build -t umbra-frontend .
# Run container
docker run -p 80:80 lifemanager-frontend
docker run -p 80:80 umbra-frontend
```
## Environment Variables

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>LifeManager</title>
<title>UMBRA</title>
</head>
<body>
<div id="root"></div>

View File

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

View File

@ -54,7 +54,7 @@ export default function LockScreen() {
<Lock className="h-8 w-8 text-accent" />
</div>
<CardTitle className="text-2xl">
{isSetup ? 'Welcome to LifeManager' : 'Enter PIN'}
{isSetup ? 'Welcome to UMBRA' : 'Enter PIN'}
</CardTitle>
<CardDescription>
{isSetup

View File

@ -24,7 +24,7 @@ export default function AppLayout() {
<Button variant="ghost" size="icon" onClick={() => setMobileOpen(true)}>
<Menu className="h-5 w-5" />
</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>
<main className="flex-1 overflow-y-auto">
<Outlet />

View File

@ -44,7 +44,7 @@ export default function Sidebar({ collapsed, onToggle, mobileOpen, onMobileClose
const sidebarContent = (
<>
<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
variant="ghost"
size="icon"

View File

@ -2,13 +2,15 @@ import { useState } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
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 type { Project, ProjectTask } from '@/types';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Card, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
import { Checkbox } from '@/components/ui/checkbox';
import { ListSkeleton } from '@/components/ui/skeleton';
import { EmptyState } from '@/components/ui/empty-state';
import TaskForm from './TaskForm';
import ProjectForm from './ProjectForm';
@ -82,7 +84,21 @@ export default function ProjectDetail() {
});
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) {
@ -120,9 +136,13 @@ export default function ProjectDetail() {
<div className="flex-1 overflow-y-auto p-6">
{!project.tasks || project.tasks.length === 0 ? (
<div className="text-center py-12">
<p className="text-muted-foreground">No tasks yet. Add one to get started!</p>
</div>
<EmptyState
icon={ListChecks}
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">
{project.tasks.map((task) => (

View File

@ -7,6 +7,7 @@ import type { Reminder } from '@/types';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { EmptyState } from '@/components/ui/empty-state';
interface ReminderListProps {
reminders: Reminder[];
@ -45,9 +46,11 @@ export default function ReminderList({ reminders, onEdit }: ReminderListProps) {
if (reminders.length === 0) {
return (
<div className="text-center py-12">
<p className="text-muted-foreground">No reminders found.</p>
</div>
<EmptyState
icon={Bell}
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 { EmptyState } from '@/components/ui/empty-state';
import TodoItem from './TodoItem';
interface TodoListProps {
@ -9,9 +11,11 @@ interface TodoListProps {
export default function TodoList({ todos, onEdit }: TodoListProps) {
if (todos.length === 0) {
return (
<div className="text-center py-12">
<p className="text-muted-foreground">No todos found. Create one to get started!</p>
</div>
<EmptyState
icon={CheckSquare}
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
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: Type definitions match backend response schemas
- [ ] 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 |
| 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 |
| 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
### 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()`
8. **Empty states** - Good UX for pages with no data yet
9. **Loading skeletons** - Better loading states than plain text
8. ~~**Empty states**~~ - DONE: All pages show icon + message + action button when empty
9. ~~**Loading skeletons**~~ - DONE: Animated skeleton placeholders replace plain "Loading..." text
---