Update README and improve mobile responsiveness for invoicing UI

- README: fix auth (better-auth), database (PostgreSQL), env vars,
  Docker setup, and feature list to reflect actual implementation
- InvoicesDataTable: show status badge + amount inline on mobile
  (previously hidden behind sm: breakpoint, leaving mobile users
  with no financial or status info at a glance)
- InvoiceItemsTable: hide Date/Hours/Rate columns on mobile and
  fold that info into the Description cell as secondary text
- invoice-view.tsx header card: wrap to column layout on mobile
  so status/amount/button don't overflow narrow screens; also
  improve item rows to show date, hours, and rate as subtext

https://claude.ai/code/session_012sqEgNQpx676isepeoX4Mi
This commit is contained in:
Claude
2026-04-05 01:53:15 +00:00
parent fb5ffc3195
commit 563d77ba65
4 changed files with 198 additions and 92 deletions
+132 -66
View File
@@ -8,23 +8,29 @@ A modern, professional invoicing application built for freelancers and small bus
## ✨ Features
- **🔐 Secure Authentication** - Email/password registration and sign-in with NextAuth.js
- **🔐 Secure Authentication** - Email/password registration and sign-in with better-auth, plus SSO via Authentik OIDC
- **👥 Client Management** - Create, edit, and manage client information
- **🏢 Business Profiles** - Manage your business details, logo, and email settings
- **📄 Professional Invoices** - Generate detailed invoices with line items
- **📅 Timesheet View** - Calendar-based time entry with month and week views
- **📧 Email Delivery** - Send invoices via email using Resend
- **📥 PDF Export** - Download invoices as professional PDFs
- **📊 CSV Import** - Bulk import invoice data from CSV files
- **💰 Flexible Pricing** - Set custom rates and calculate totals automatically
- **📱 Responsive Design** - Works seamlessly on desktop, tablet, and mobile
- **🎨 Modern UI** - Clean, professional interface built with shadcn/ui
- **⚡ Type-Safe** - Full TypeScript support with tRPC for API calls
- **💾 Local Database** - SQLite database with Drizzle ORM
- **💾 PostgreSQL Database** - Robust relational database with Drizzle ORM
## 🚀 Tech Stack
- **Frontend**: Next.js 15 with App Router
- **Frontend**: Next.js 16 with App Router
- **Backend**: tRPC for type-safe API calls
- **Database**: Drizzle ORM with LibSQL (SQLite)
- **Authentication**: NextAuth.js with email/password
- **UI Components**: shadcn/ui with Tailwind CSS
- **Styling**: Geist font family
- **Database**: Drizzle ORM with PostgreSQL
- **Authentication**: better-auth with email/password and Authentik OIDC SSO
- **UI Components**: shadcn/ui with Tailwind CSS v4
- **Email**: Resend for transactional email delivery
- **PDF**: @react-pdf/renderer for invoice PDF generation
- **Package Manager**: Bun
## 📦 Installation
@@ -32,6 +38,7 @@ A modern, professional invoicing application built for freelancers and small bus
### Prerequisites
- Node.js 18+ or Bun
- Docker & Docker Compose (for local PostgreSQL)
- Git
### Quick Start
@@ -43,7 +50,6 @@ A modern, professional invoicing application built for freelancers and small bus
```
2. **Install dependencies**
```bash
```bash
bun install
```
@@ -55,22 +61,39 @@ A modern, professional invoicing application built for freelancers and small bus
Edit `.env.local` and add your configuration:
```env
DATABASE_URL="file:./db.sqlite"
NEXTAUTH_SECRET="your-secret-key-here"
NEXTAUTH_URL="http://localhost:3000"
# Database
DATABASE_URL="postgresql://postgres:password@localhost:5432/beenvoice"
DB_DISABLE_SSL="true"
# Authentication
AUTH_SECRET="your-secret-key-here"
BETTER_AUTH_URL="http://localhost:3000"
# Application
NEXT_PUBLIC_APP_URL="http://localhost:3000"
NODE_ENV="development"
# Email (optional for local dev)
RESEND_API_KEY="your-resend-api-key"
RESEND_DOMAIN="yourdomain.com"
```
4. **Initialize the database**
4. **Start the database**
```bash
docker-compose up -d
```
5. **Push the database schema**
```bash
bun run db:push
```
5. **Start the development server**
6. **Start the development server**
```bash
bun run dev
```
6. **Open your browser**
7. **Open your browser**
Navigate to [http://localhost:3000](http://localhost:3000)
## 🏗️ Project Structure
@@ -79,21 +102,28 @@ A modern, professional invoicing application built for freelancers and small bus
beenvoice/
├── src/
│ ├── app/ # Next.js App Router pages
│ │ ├── api/ # API routes (NextAuth, tRPC)
│ │ ├── api/ # API routes (better-auth, tRPC)
│ │ ├── auth/ # Authentication pages
│ │ ├── clients/ # Client management pages
│ │ ├── invoices/ # Invoice management pages
│ │ ├── dashboard/ # Main app pages
│ │ │ ├── clients/ # Client management pages
│ │ │ ├── invoices/ # Invoice management pages
│ │ │ └── businesses/ # Business profile pages
│ │ └── _components/ # Page-specific components
│ ├── components/ # Shared UI components
│ │ ├── ui/ # shadcn/ui components
│ │ ├── data/ # Data display components
│ │ ├── forms/ # Form components
│ │ └── layout/ # Layout components
│ ├── server/ # Server-side code
│ │ ├── api/ # tRPC routers
│ │ ├── auth/ # NextAuth configuration
│ │ └── db/ # Database schema and connection
│ ├── lib/ # Utilities (auth, pdf export, etc.)
│ ├── styles/ # Global styles
│ └── trpc/ # tRPC client configuration
├── drizzle/ # Database migrations
├── public/ # Static assets
── docs/ # Documentation
── docs/ # Documentation
└── docker-compose.yml # Local PostgreSQL setup
```
## 🎯 Usage
@@ -103,41 +133,53 @@ beenvoice/
1. **Register an Account**
- Visit the sign-up page
- Enter your name, email, and password
- Verify your email (if configured)
2. **Add Your First Client**
2. **Set Up Your Business**
- Navigate to Business Settings
- Add your business name, contact info, and logo
- Configure email settings for invoice delivery (Resend API key + domain)
3. **Add Your First Client**
- Navigate to the Clients page
- Click "Add New Client"
- Fill in client details (name, email, phone, address)
3. **Create an Invoice**
4. **Create an Invoice**
- Go to the Invoices page
- Click "Create New Invoice"
- Select a client
- Select a client and optionally a business profile
- Add line items with descriptions, dates, hours, and rates
- Save and generate your invoice
- Use the Timesheet tab for calendar-based time entry
- Save and send or download as PDF
### Features Overview
#### Client Management
- Create and edit client profiles
- Store contact information and addresses
- Set default hourly rates per client
- Search and filter client list
- View client history
#### Invoice Creation
- Select from existing clients
- Add multiple line items
- Select from existing clients and business profiles
- Add multiple line items with drag-and-drop reordering
- Set custom rates per item
- Automatic total calculations
- Automatic total calculations with configurable tax rate
- Timesheet calendar view for date-based time tracking
- Professional invoice formatting
#### Invoice Delivery
- Send invoices via email directly from the app
- Rich text email composer with preview
- Resend and re-deliver sent invoices
- Track invoice status: Draft → Sent → Paid (+ Overdue)
#### User Interface
- Clean, modern design
- Responsive layout
- Intuitive navigation
- Fully responsive — desktop, tablet, and mobile
- Intuitive navigation with breadcrumbs
- Toast notifications for feedback
- Modal dialogs for forms
- Dark mode support
## 🔧 Development
@@ -145,44 +187,53 @@ beenvoice/
```bash
# Development
bun run dev # Start development server
bun run dev # Start development server (Turbo)
bun run build # Build for production
bun run start # Start production server
# Database
bun run db:push # Push schema changes to database
bun run db:migrate # Run migrations
bun run db:studio # Open Drizzle Studio
bun run db:generate # Generate new migration
# Docker
bun run docker:up # Start local PostgreSQL via Docker
bun run docker:down # Stop Docker services
# Code Quality
bun run lint # Run ESLint
bun run format # Format code with Prettier
bun run type-check # Run TypeScript type checking
bun run lint:fix # Fix ESLint issues
bun run format:write # Format code with Prettier
bun run typecheck # Run TypeScript type checking
```
### Database Schema
The application uses four main tables:
The application uses the following core tables:
- **users**: User accounts and authentication
- **clients**: Client information and contact details
- **invoices**: Invoice headers with client relationships
- **invoice_items**: Individual line items with pricing
- **users** - User accounts and authentication
- **sessions** - Active user sessions
- **clients** - Client information and contact details
- **businesses** - Business profiles with email/logo settings
- **invoices** - Invoice headers with client and business relationships
- **invoice_items** - Individual line items with pricing and position ordering
### API Development
All API endpoints are built with tRPC for type safety:
- **Authentication**: NextAuth.js integration
- **Authentication**: better-auth integration (email/password + OIDC)
- **Clients**: CRUD operations for client management
- **Invoices**: Invoice creation and management
- **Businesses**: Business profile management
- **Invoices**: Invoice creation, management, and status tracking
- **Validation**: Zod schemas for input validation
## 🎨 Customization
### Styling
The app uses Tailwind CSS with a custom design system:
The app uses Tailwind CSS v4 with a custom design system:
- **Primary Color**: Green (#16a34a)
- **Font**: Geist for professional typography
@@ -198,38 +249,54 @@ Update the logo and colors in:
## 🚀 Deployment
### Deployment
You can deploy this application to any platform that supports Next.js and PostgreSQL (Docker, Coolify, Railway, etc.).
You can deploy this application to any platform that supports Next.js (Docker, Coolify, Railway, etc.).
1. **Build the application:**
```bash
bun run build
```
1. Build the application:
```bash
bun run build
```
2. **Set up production environment variables** (see `.env.local` example above, adjusting URLs and secrets for production)
2. Start the server:
```bash
bun start
```
3. **Run database migrations:**
```bash
bun run db:push
```
### Other Platforms
The app can be deployed to any platform that supports Next.js:
- **Netlify**: Use the Next.js build command
- **Railway**: Connect your GitHub repository
- **DigitalOcean App Platform**: Deploy with automatic scaling
4. **Start the server:**
```bash
bun start
```
### Environment Variables
Required for production:
```env
DATABASE_URL="your-database-url"
NEXTAUTH_SECRET="your-secret-key"
NEXTAUTH_URL="https://your-domain.com"
DATABASE_URL="postgresql://user:password@host:5432/dbname"
AUTH_SECRET="your-long-random-secret"
BETTER_AUTH_URL="https://your-domain.com"
NEXT_PUBLIC_APP_URL="https://your-domain.com"
NODE_ENV="production"
# Email (required for invoice sending)
RESEND_API_KEY="re_xxxxxxxxxxxx"
RESEND_DOMAIN="yourdomain.com"
# Optional: Authentik SSO
AUTHENTIK_ISSUER="https://your-authentik-instance/application/o/beenvoice/"
AUTHENTIK_CLIENT_ID="your-client-id"
AUTHENTIK_CLIENT_SECRET="your-client-secret"
```
### Other Platforms
The app can be deployed to any platform that supports Next.js:
- **Coolify**: Deploy with Docker Compose support
- **Railway**: Connect your GitHub repository (includes managed PostgreSQL)
- **DigitalOcean App Platform**: Deploy with automatic scaling
## 🤝 Contributing
1. Fork the repository
@@ -243,8 +310,7 @@ NEXTAUTH_URL="https://your-domain.com"
- Follow TypeScript best practices
- Use shadcn/ui components for consistency
- Implement proper error handling
- Add tests for new features
- Follow the existing code style
- Follow the existing code style (Prettier + ESLint configs provided)
## 📄 License
@@ -254,14 +320,14 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
- [T3 Stack](https://create.t3.gg/) for the excellent development stack
- [shadcn/ui](https://ui.shadcn.com/) for beautiful UI components
- [NextAuth.js](https://next-auth.js.org/) for authentication
- [better-auth](https://www.better-auth.com/) for modern authentication
- [Drizzle ORM](https://orm.drizzle.team/) for database management
- [Resend](https://resend.com/) for reliable email delivery
## 📞 Support
- **Issues**: [GitHub Issues](https://github.com/yourusername/beenvoice/issues)
- **Discussions**: [GitHub Discussions](https://github.com/yourusername/beenvoice/discussions)
- **Email**: support@beenvoice.com
---
@@ -40,13 +40,32 @@ const columns: ColumnDef<InvoiceItem>[] = [
accessorKey: "date",
header: "Date",
cell: ({ row }) => formatDate(row.getValue("date")),
meta: {
headerClassName: "hidden sm:table-cell",
cellClassName: "hidden sm:table-cell",
},
},
{
accessorKey: "description",
header: "Description",
cell: ({ row }) => (
<div className="font-medium">{row.getValue("description")}</div>
),
cell: ({ row }) => {
const item = row.original;
return (
<>
{/* Desktop: plain description */}
<div className="hidden font-medium sm:block">
{item.description}
</div>
{/* Mobile: description + date + hours @ rate stacked */}
<div className="sm:hidden">
<p className="font-medium">{item.description}</p>
<p className="text-muted-foreground mt-0.5 text-xs">
{formatDate(item.date)} &middot; {item.hours}h @ {formatCurrency(item.rate)}/hr
</p>
</div>
</>
);
},
},
{
accessorKey: "hours",
@@ -54,6 +73,10 @@ const columns: ColumnDef<InvoiceItem>[] = [
cell: ({ row }) => (
<div className="text-right">{row.getValue("hours")}</div>
),
meta: {
headerClassName: "hidden sm:table-cell",
cellClassName: "hidden sm:table-cell",
},
},
{
accessorKey: "rate",
@@ -61,6 +84,10 @@ const columns: ColumnDef<InvoiceItem>[] = [
cell: ({ row }) => (
<div className="text-right">{formatCurrency(row.getValue("rate"))}</div>
),
meta: {
headerClassName: "hidden sm:table-cell",
cellClassName: "hidden sm:table-cell",
},
},
{
accessorKey: "amount",
@@ -134,13 +134,23 @@ export function InvoicesDataTable({ invoices }: InvoicesDataTableProps) {
<div className="bg-primary/10 hidden p-2 sm:flex">
<FileText className="text-primary h-4 w-4" />
</div>
<div className="max-w-[80px] min-w-0 sm:max-w-[200px] lg:max-w-[300px]">
<div className="min-w-0 flex-1">
<p className="truncate font-medium">
{invoice.client?.name ?? "—"}
</p>
<p className="text-muted-foreground truncate text-xs sm:text-sm">
{invoice.invoiceNumber}
</p>
{/* Show status + amount inline on mobile only */}
<div className="mt-1 flex items-center gap-2 sm:hidden">
<StatusBadge
status={getStatusType(invoice)}
className="text-xs"
/>
<span className="text-foreground text-xs font-semibold">
{formatCurrency(invoice.totalAmount)}
</span>
</div>
</div>
</div>
);
+25 -22
View File
@@ -185,14 +185,14 @@ export function InvoiceView({ invoiceId }: InvoiceViewProps) {
{/* Invoice Header Card */}
<Card className="bg-card border-border border">
<CardContent>
<div className="flex items-start justify-between">
<div className="space-y-4">
<div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
<div className="min-w-0 flex-1 space-y-4">
<div className="flex items-center gap-3">
<div className="bg-primary/10 p-2">
<div className="bg-primary/10 flex-shrink-0 p-2">
<FileText className="text-primary h-6 w-6" />
</div>
<div>
<h2 className="text-foreground text-2xl font-bold">
<div className="min-w-0">
<h2 className="text-foreground truncate text-2xl font-bold">
{invoice.invoiceNumber}
</h2>
<p className="text-muted-foreground">
@@ -217,21 +217,23 @@ export function InvoiceView({ invoiceId }: InvoiceViewProps) {
</div>
</div>
<div className="space-y-3 text-right">
<StatusBadge
status={invoice.status as StatusType}
className="px-3 py-1 text-sm font-medium"
>
<StatusIcon className="mr-1 h-3 w-3" />
</StatusBadge>
<div className="text-primary text-3xl font-bold">
{formatCurrency(invoice.totalAmount)}
<div className="flex flex-row items-center justify-between gap-3 sm:flex-col sm:items-end sm:text-right">
<div>
<StatusBadge
status={invoice.status as StatusType}
className="px-3 py-1 text-sm font-medium"
>
<StatusIcon className="mr-1 h-3 w-3" />
</StatusBadge>
<div className="text-primary mt-1 text-2xl font-bold sm:text-3xl">
{formatCurrency(invoice.totalAmount)}
</div>
</div>
<Button
onClick={handlePDFExport}
disabled={isExportingPDF}
variant="default"
className="transform-none"
className="transform-none flex-shrink-0"
>
{isExportingPDF ? (
<>
@@ -326,17 +328,18 @@ export function InvoiceView({ invoiceId }: InvoiceViewProps) {
{invoice.items?.map((item, index) => (
<div
key={item.id || index}
className="bg-background flex items-center justify-between rounded-lg p-4"
className="bg-background flex flex-col gap-1 rounded-lg p-4 sm:flex-row sm:items-center sm:justify-between"
>
<div className="flex items-center gap-4">
<div className="text-muted-foreground text-sm">
{formatDate(item.date)}
</div>
<div className="text-foreground font-medium">
<div className="min-w-0 flex-1">
<div className="text-foreground font-medium break-words">
{item.description}
</div>
<div className="text-muted-foreground mt-0.5 text-sm">
{formatDate(item.date)} &middot; {item.hours}h @{" "}
{formatCurrency(item.rate)}/hr
</div>
</div>
<div className="text-foreground text-right font-medium">
<div className="text-foreground flex-shrink-0 font-medium sm:text-right">
{formatCurrency(item.amount)}
</div>
</div>