mirror of
https://github.com/soconnor0919/beenvoice.git
synced 2026-05-08 09:38:55 -04:00
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:
@@ -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)} · {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>
|
||||
);
|
||||
|
||||
@@ -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)} · {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>
|
||||
|
||||
Reference in New Issue
Block a user