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
+128 -62
View File
@@ -8,23 +8,29 @@ A modern, professional invoicing application built for freelancers and small bus
## ✨ Features ## ✨ 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 - **👥 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 - **📄 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 - **💰 Flexible Pricing** - Set custom rates and calculate totals automatically
- **📱 Responsive Design** - Works seamlessly on desktop, tablet, and mobile - **📱 Responsive Design** - Works seamlessly on desktop, tablet, and mobile
- **🎨 Modern UI** - Clean, professional interface built with shadcn/ui - **🎨 Modern UI** - Clean, professional interface built with shadcn/ui
- **⚡ Type-Safe** - Full TypeScript support with tRPC for API calls - **⚡ 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 ## 🚀 Tech Stack
- **Frontend**: Next.js 15 with App Router - **Frontend**: Next.js 16 with App Router
- **Backend**: tRPC for type-safe API calls - **Backend**: tRPC for type-safe API calls
- **Database**: Drizzle ORM with LibSQL (SQLite) - **Database**: Drizzle ORM with PostgreSQL
- **Authentication**: NextAuth.js with email/password - **Authentication**: better-auth with email/password and Authentik OIDC SSO
- **UI Components**: shadcn/ui with Tailwind CSS - **UI Components**: shadcn/ui with Tailwind CSS v4
- **Styling**: Geist font family - **Email**: Resend for transactional email delivery
- **PDF**: @react-pdf/renderer for invoice PDF generation
- **Package Manager**: Bun - **Package Manager**: Bun
## 📦 Installation ## 📦 Installation
@@ -32,6 +38,7 @@ A modern, professional invoicing application built for freelancers and small bus
### Prerequisites ### Prerequisites
- Node.js 18+ or Bun - Node.js 18+ or Bun
- Docker & Docker Compose (for local PostgreSQL)
- Git - Git
### Quick Start ### Quick Start
@@ -43,7 +50,6 @@ A modern, professional invoicing application built for freelancers and small bus
``` ```
2. **Install dependencies** 2. **Install dependencies**
```bash
```bash ```bash
bun install bun install
``` ```
@@ -55,22 +61,39 @@ A modern, professional invoicing application built for freelancers and small bus
Edit `.env.local` and add your configuration: Edit `.env.local` and add your configuration:
```env ```env
DATABASE_URL="file:./db.sqlite" # Database
NEXTAUTH_SECRET="your-secret-key-here" DATABASE_URL="postgresql://postgres:password@localhost:5432/beenvoice"
NEXTAUTH_URL="http://localhost:3000" 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 ```bash
bun run db:push bun run db:push
``` ```
5. **Start the development server** 6. **Start the development server**
```bash ```bash
bun run dev bun run dev
``` ```
6. **Open your browser** 7. **Open your browser**
Navigate to [http://localhost:3000](http://localhost:3000) Navigate to [http://localhost:3000](http://localhost:3000)
## 🏗️ Project Structure ## 🏗️ Project Structure
@@ -79,21 +102,28 @@ A modern, professional invoicing application built for freelancers and small bus
beenvoice/ beenvoice/
├── src/ ├── src/
│ ├── app/ # Next.js App Router pages │ ├── app/ # Next.js App Router pages
│ │ ├── api/ # API routes (NextAuth, tRPC) │ │ ├── api/ # API routes (better-auth, tRPC)
│ │ ├── auth/ # Authentication pages │ │ ├── auth/ # Authentication pages
│ │ ├── clients/ # Client management pages │ │ ├── dashboard/ # Main app pages
│ │ ├── invoices/ # Invoice management pages │ │ │ ├── clients/ # Client management pages
│ │ │ ├── invoices/ # Invoice management pages
│ │ │ └── businesses/ # Business profile pages
│ │ └── _components/ # Page-specific components │ │ └── _components/ # Page-specific components
│ ├── components/ # Shared UI components │ ├── components/ # Shared UI components
│ │ ├── ui/ # shadcn/ui components
│ │ ├── data/ # Data display components
│ │ ├── forms/ # Form components
│ │ └── layout/ # Layout components
│ ├── server/ # Server-side code │ ├── server/ # Server-side code
│ │ ├── api/ # tRPC routers │ │ ├── api/ # tRPC routers
│ │ ├── auth/ # NextAuth configuration
│ │ └── db/ # Database schema and connection │ │ └── db/ # Database schema and connection
│ ├── lib/ # Utilities (auth, pdf export, etc.)
│ ├── styles/ # Global styles │ ├── styles/ # Global styles
│ └── trpc/ # tRPC client configuration │ └── trpc/ # tRPC client configuration
├── drizzle/ # Database migrations ├── drizzle/ # Database migrations
├── public/ # Static assets ├── public/ # Static assets
── docs/ # Documentation ── docs/ # Documentation
└── docker-compose.yml # Local PostgreSQL setup
``` ```
## 🎯 Usage ## 🎯 Usage
@@ -103,41 +133,53 @@ beenvoice/
1. **Register an Account** 1. **Register an Account**
- Visit the sign-up page - Visit the sign-up page
- Enter your name, email, and password - 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 - Navigate to the Clients page
- Click "Add New Client" - Click "Add New Client"
- Fill in client details (name, email, phone, address) - Fill in client details (name, email, phone, address)
3. **Create an Invoice** 4. **Create an Invoice**
- Go to the Invoices page - Go to the Invoices page
- Click "Create New Invoice" - Click "Create New Invoice"
- Select a client - Select a client and optionally a business profile
- Add line items with descriptions, dates, hours, and rates - 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 ### Features Overview
#### Client Management #### Client Management
- Create and edit client profiles - Create and edit client profiles
- Store contact information and addresses - Store contact information and addresses
- Set default hourly rates per client
- Search and filter client list - Search and filter client list
- View client history
#### Invoice Creation #### Invoice Creation
- Select from existing clients - Select from existing clients and business profiles
- Add multiple line items - Add multiple line items with drag-and-drop reordering
- Set custom rates per item - 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 - 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 #### User Interface
- Clean, modern design - Clean, modern design
- Responsive layout - Fully responsive — desktop, tablet, and mobile
- Intuitive navigation - Intuitive navigation with breadcrumbs
- Toast notifications for feedback - Toast notifications for feedback
- Modal dialogs for forms - Dark mode support
## 🔧 Development ## 🔧 Development
@@ -145,44 +187,53 @@ beenvoice/
```bash ```bash
# Development # Development
bun run dev # Start development server bun run dev # Start development server (Turbo)
bun run build # Build for production bun run build # Build for production
bun run start # Start production server bun run start # Start production server
# Database # Database
bun run db:push # Push schema changes to 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:studio # Open Drizzle Studio
bun run db:generate # Generate new migration 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 # Code Quality
bun run lint # Run ESLint bun run lint # Run ESLint
bun run format # Format code with Prettier bun run lint:fix # Fix ESLint issues
bun run type-check # Run TypeScript type checking bun run format:write # Format code with Prettier
bun run typecheck # Run TypeScript type checking
``` ```
### Database Schema ### Database Schema
The application uses four main tables: The application uses the following core tables:
- **users**: User accounts and authentication - **users** - User accounts and authentication
- **clients**: Client information and contact details - **sessions** - Active user sessions
- **invoices**: Invoice headers with client relationships - **clients** - Client information and contact details
- **invoice_items**: Individual line items with pricing - **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 ### API Development
All API endpoints are built with tRPC for type safety: 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 - **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 - **Validation**: Zod schemas for input validation
## 🎨 Customization ## 🎨 Customization
### Styling ### 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) - **Primary Color**: Green (#16a34a)
- **Font**: Geist for professional typography - **Font**: Geist for professional typography
@@ -198,38 +249,54 @@ Update the logo and colors in:
## 🚀 Deployment ## 🚀 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:**
1. Build the application:
```bash ```bash
bun run build bun run build
``` ```
2. Start the server: 2. **Set up production environment variables** (see `.env.local` example above, adjusting URLs and secrets for production)
3. **Run database migrations:**
```bash
bun run db:push
```
4. **Start the server:**
```bash ```bash
bun start bun start
``` ```
### 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
### Environment Variables ### Environment Variables
Required for production: Required for production:
```env ```env
DATABASE_URL="your-database-url" DATABASE_URL="postgresql://user:password@host:5432/dbname"
NEXTAUTH_SECRET="your-secret-key" AUTH_SECRET="your-long-random-secret"
NEXTAUTH_URL="https://your-domain.com" 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 ## 🤝 Contributing
1. Fork the repository 1. Fork the repository
@@ -243,8 +310,7 @@ NEXTAUTH_URL="https://your-domain.com"
- Follow TypeScript best practices - Follow TypeScript best practices
- Use shadcn/ui components for consistency - Use shadcn/ui components for consistency
- Implement proper error handling - Implement proper error handling
- Add tests for new features - Follow the existing code style (Prettier + ESLint configs provided)
- Follow the existing code style
## 📄 License ## 📄 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 - [T3 Stack](https://create.t3.gg/) for the excellent development stack
- [shadcn/ui](https://ui.shadcn.com/) for beautiful UI components - [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 - [Drizzle ORM](https://orm.drizzle.team/) for database management
- [Resend](https://resend.com/) for reliable email delivery
## 📞 Support ## 📞 Support
- **Issues**: [GitHub Issues](https://github.com/yourusername/beenvoice/issues) - **Issues**: [GitHub Issues](https://github.com/yourusername/beenvoice/issues)
- **Discussions**: [GitHub Discussions](https://github.com/yourusername/beenvoice/discussions) - **Discussions**: [GitHub Discussions](https://github.com/yourusername/beenvoice/discussions)
- **Email**: support@beenvoice.com
--- ---
@@ -40,13 +40,32 @@ const columns: ColumnDef<InvoiceItem>[] = [
accessorKey: "date", accessorKey: "date",
header: "Date", header: "Date",
cell: ({ row }) => formatDate(row.getValue("date")), cell: ({ row }) => formatDate(row.getValue("date")),
meta: {
headerClassName: "hidden sm:table-cell",
cellClassName: "hidden sm:table-cell",
},
}, },
{ {
accessorKey: "description", accessorKey: "description",
header: "Description", header: "Description",
cell: ({ row }) => ( cell: ({ row }) => {
<div className="font-medium">{row.getValue("description")}</div> 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", accessorKey: "hours",
@@ -54,6 +73,10 @@ const columns: ColumnDef<InvoiceItem>[] = [
cell: ({ row }) => ( cell: ({ row }) => (
<div className="text-right">{row.getValue("hours")}</div> <div className="text-right">{row.getValue("hours")}</div>
), ),
meta: {
headerClassName: "hidden sm:table-cell",
cellClassName: "hidden sm:table-cell",
},
}, },
{ {
accessorKey: "rate", accessorKey: "rate",
@@ -61,6 +84,10 @@ const columns: ColumnDef<InvoiceItem>[] = [
cell: ({ row }) => ( cell: ({ row }) => (
<div className="text-right">{formatCurrency(row.getValue("rate"))}</div> <div className="text-right">{formatCurrency(row.getValue("rate"))}</div>
), ),
meta: {
headerClassName: "hidden sm:table-cell",
cellClassName: "hidden sm:table-cell",
},
}, },
{ {
accessorKey: "amount", accessorKey: "amount",
@@ -134,13 +134,23 @@ export function InvoicesDataTable({ invoices }: InvoicesDataTableProps) {
<div className="bg-primary/10 hidden p-2 sm:flex"> <div className="bg-primary/10 hidden p-2 sm:flex">
<FileText className="text-primary h-4 w-4" /> <FileText className="text-primary h-4 w-4" />
</div> </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"> <p className="truncate font-medium">
{invoice.client?.name ?? "—"} {invoice.client?.name ?? "—"}
</p> </p>
<p className="text-muted-foreground truncate text-xs sm:text-sm"> <p className="text-muted-foreground truncate text-xs sm:text-sm">
{invoice.invoiceNumber} {invoice.invoiceNumber}
</p> </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>
</div> </div>
); );
+18 -15
View File
@@ -185,14 +185,14 @@ export function InvoiceView({ invoiceId }: InvoiceViewProps) {
{/* Invoice Header Card */} {/* Invoice Header Card */}
<Card className="bg-card border-border border"> <Card className="bg-card border-border border">
<CardContent> <CardContent>
<div className="flex items-start justify-between"> <div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
<div className="space-y-4"> <div className="min-w-0 flex-1 space-y-4">
<div className="flex items-center gap-3"> <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" /> <FileText className="text-primary h-6 w-6" />
</div> </div>
<div> <div className="min-w-0">
<h2 className="text-foreground text-2xl font-bold"> <h2 className="text-foreground truncate text-2xl font-bold">
{invoice.invoiceNumber} {invoice.invoiceNumber}
</h2> </h2>
<p className="text-muted-foreground"> <p className="text-muted-foreground">
@@ -217,21 +217,23 @@ export function InvoiceView({ invoiceId }: InvoiceViewProps) {
</div> </div>
</div> </div>
<div className="space-y-3 text-right"> <div className="flex flex-row items-center justify-between gap-3 sm:flex-col sm:items-end sm:text-right">
<div>
<StatusBadge <StatusBadge
status={invoice.status as StatusType} status={invoice.status as StatusType}
className="px-3 py-1 text-sm font-medium" className="px-3 py-1 text-sm font-medium"
> >
<StatusIcon className="mr-1 h-3 w-3" /> <StatusIcon className="mr-1 h-3 w-3" />
</StatusBadge> </StatusBadge>
<div className="text-primary text-3xl font-bold"> <div className="text-primary mt-1 text-2xl font-bold sm:text-3xl">
{formatCurrency(invoice.totalAmount)} {formatCurrency(invoice.totalAmount)}
</div> </div>
</div>
<Button <Button
onClick={handlePDFExport} onClick={handlePDFExport}
disabled={isExportingPDF} disabled={isExportingPDF}
variant="default" variant="default"
className="transform-none" className="transform-none flex-shrink-0"
> >
{isExportingPDF ? ( {isExportingPDF ? (
<> <>
@@ -326,17 +328,18 @@ export function InvoiceView({ invoiceId }: InvoiceViewProps) {
{invoice.items?.map((item, index) => ( {invoice.items?.map((item, index) => (
<div <div
key={item.id || index} 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="min-w-0 flex-1">
<div className="text-muted-foreground text-sm"> <div className="text-foreground font-medium break-words">
{formatDate(item.date)}
</div>
<div className="text-foreground font-medium">
{item.description} {item.description}
</div> </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>
<div className="text-foreground flex-shrink-0 font-medium sm:text-right">
{formatCurrency(item.amount)} {formatCurrency(item.amount)}
</div> </div>
</div> </div>