updated name from lifemanager to umbra,
This commit is contained in:
parent
96c225f4f7
commit
e6387065ad
@ -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
|
||||
|
||||
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**
|
||||
```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/
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "lifemanager",
|
||||
"name": "umbra",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 />
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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) => (
|
||||
|
||||
@ -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."
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -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."
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
13
progress.md
13
progress.md
@ -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
|
||||
|
||||
---
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user