diff --git a/app/api/invoices/[id]/route.ts b/app/api/invoices/[id]/route.ts new file mode 100644 index 0000000..5539f5f --- /dev/null +++ b/app/api/invoices/[id]/route.ts @@ -0,0 +1,127 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { db, schema } from '@/db'; +import { eq } from 'drizzle-orm'; + +interface Params { + id: string; +} + +export async function GET( + request: NextRequest, + { params }: { params: Params } +) { + try { + const { id } = params; + + // Validate ID format (should be UUID) + const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + if (!uuidRegex.test(id)) { + return NextResponse.json( + { error: 'Invalid invoice ID format' }, + { status: 400 } + ); + } + + // Get invoice details + const invoice = await db + .select() + .from(schema.invoice) + .where(eq(schema.invoice.id, id)) + .limit(1); + + if (invoice.length === 0) { + return NextResponse.json( + { error: 'Invoice not found' }, + { status: 404 } + ); + } + + // Get invoice line items + const lineItems = await db + .select() + .from(schema.invoiceItem) + .where(eq(schema.invoiceItem.invoiceId, id)) + .orderBy(schema.invoiceItem.createdAt); + + // Transform the response + const invoiceData = invoice[0]; + const transformedInvoice = { + id: invoiceData.id, + invoiceNumber: invoiceData.invoiceNumber, + date: invoiceData.date, + vendor: invoiceData.vendor, + totalAmount: parseFloat(invoiceData.totalAmount), + createdAt: invoiceData.createdAt, + updatedAt: invoiceData.updatedAt, + lineItems: lineItems.map(item => ({ + id: item.id, + description: item.description, + quantity: parseFloat(item.quantity), + unitPrice: parseFloat(item.unitPrice), + lineTotal: parseFloat(item.lineTotal), + createdAt: item.createdAt, + updatedAt: item.updatedAt + })) + }; + + return NextResponse.json({ + invoice: transformedInvoice + }); + + } catch (error) { + console.error('Error fetching invoice details:', error); + return NextResponse.json( + { error: 'Failed to fetch invoice details' }, + { status: 500 } + ); + } +} + +export async function DELETE( + request: NextRequest, + { params }: { params: Params } +) { + try { + const { id } = params; + + // Validate ID format (should be UUID) + const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + if (!uuidRegex.test(id)) { + return NextResponse.json( + { error: 'Invalid invoice ID format' }, + { status: 400 } + ); + } + + // Check if invoice exists + const existingInvoice = await db + .select({ id: schema.invoice.id }) + .from(schema.invoice) + .where(eq(schema.invoice.id, id)) + .limit(1); + + if (existingInvoice.length === 0) { + return NextResponse.json( + { error: 'Invoice not found' }, + { status: 404 } + ); + } + + // Delete invoice (line items will be deleted due to CASCADE) + await db + .delete(schema.invoice) + .where(eq(schema.invoice.id, id)); + + return NextResponse.json({ + message: 'Invoice deleted successfully', + deletedId: id + }); + + } catch (error) { + console.error('Error deleting invoice:', error); + return NextResponse.json( + { error: 'Failed to delete invoice' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/app/api/invoices/route.ts b/app/api/invoices/route.ts new file mode 100644 index 0000000..0473275 --- /dev/null +++ b/app/api/invoices/route.ts @@ -0,0 +1,129 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { db, schema } from '@/db'; +import { desc, gte, lte, ilike, and } from 'drizzle-orm'; + +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url); + + // Parse query parameters + const page = parseInt(searchParams.get('page') || '1'); + const limit = parseInt(searchParams.get('limit') || '10'); + const vendor = searchParams.get('vendor'); + const startDate = searchParams.get('startDate'); + const endDate = searchParams.get('endDate'); + const sortBy = searchParams.get('sortBy') || 'date'; + const sortOrder = searchParams.get('sortOrder') || 'desc'; + + // Validate pagination parameters + const validatedPage = Math.max(1, page); + const validatedLimit = Math.min(100, Math.max(1, limit)); // Max 100 items per page + const offset = (validatedPage - 1) * validatedLimit; + + // Build where conditions + const conditions = []; + + if (vendor) { + conditions.push(ilike(schema.invoice.vendor, `%${vendor}%`)); + } + + if (startDate) { + const start = new Date(startDate); + if (!isNaN(start.getTime())) { + conditions.push(gte(schema.invoice.date, start)); + } + } + + if (endDate) { + const end = new Date(endDate); + if (!isNaN(end.getTime())) { + // Set to end of day + end.setHours(23, 59, 59, 999); + conditions.push(lte(schema.invoice.date, end)); + } + } + + const whereClause = conditions.length > 0 ? and(...conditions) : undefined; + + // Build order clause + let orderClause; + switch (sortBy) { + case 'invoiceNumber': + orderClause = sortOrder === 'asc' + ? schema.invoice.invoiceNumber + : desc(schema.invoice.invoiceNumber); + break; + case 'vendor': + orderClause = sortOrder === 'asc' + ? schema.invoice.vendor + : desc(schema.invoice.vendor); + break; + case 'totalAmount': + orderClause = sortOrder === 'asc' + ? schema.invoice.totalAmount + : desc(schema.invoice.totalAmount); + break; + default: // date + orderClause = sortOrder === 'asc' + ? schema.invoice.date + : desc(schema.invoice.date); + } + + // Get total count for pagination + const [{ count }] = await db + .select({ count: schema.invoice.id }) + .from(schema.invoice) + .where(whereClause); + + // Get invoices with pagination + const invoices = await db + .select() + .from(schema.invoice) + .where(whereClause) + .orderBy(orderClause) + .limit(validatedLimit) + .offset(offset); + + // Transform the response to include proper numeric values + const transformedInvoices = invoices.map(invoice => ({ + id: invoice.id, + invoiceNumber: invoice.invoiceNumber, + date: invoice.date, + vendor: invoice.vendor, + totalAmount: parseFloat(invoice.totalAmount), + createdAt: invoice.createdAt, + updatedAt: invoice.updatedAt + })); + + // Calculate pagination metadata + const totalPages = Math.ceil(parseInt(count) / validatedLimit); + const hasNextPage = validatedPage < totalPages; + const hasPrevPage = validatedPage > 1; + + return NextResponse.json({ + invoices: transformedInvoices, + pagination: { + page: validatedPage, + limit: validatedLimit, + total: parseInt(count), + totalPages, + hasNextPage, + hasPrevPage + }, + filters: { + vendor, + startDate, + endDate, + sortBy, + sortOrder + } + }); + + } catch (error) { + console.error('Error fetching invoices:', error); + return NextResponse.json( + { error: 'Failed to fetch invoices' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/app/api/invoices/upload/route.ts b/app/api/invoices/upload/route.ts new file mode 100644 index 0000000..fbf3aa5 --- /dev/null +++ b/app/api/invoices/upload/route.ts @@ -0,0 +1,129 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { db, schema } from '@/db'; +import { processInvoiceImage } from '@/lib/services/ocrService'; + +export async function POST(request: NextRequest) { + try { + const formData = await request.formData(); + const file = formData.get('invoice') as File; + + if (!file) { + return NextResponse.json( + { error: 'No file provided' }, + { status: 400 } + ); + } + + // Validate file type + const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'application/pdf']; + if (!allowedTypes.includes(file.type)) { + return NextResponse.json( + { error: 'Invalid file type. Only images (JPEG, PNG, GIF) and PDF files are allowed.' }, + { status: 400 } + ); + } + + // Validate file size (5MB limit) + const maxSize = 5 * 1024 * 1024; // 5MB + if (file.size > maxSize) { + return NextResponse.json( + { error: 'File too large. Maximum size is 5MB.' }, + { status: 400 } + ); + } + + // Convert file to buffer + const bytes = await file.arrayBuffer(); + const buffer = Buffer.from(bytes); + + // Process image with OCR + let invoiceData; + try { + invoiceData = await processInvoiceImage(buffer); + } catch (ocrError) { + console.error('OCR processing failed:', ocrError); + return NextResponse.json( + { error: 'Failed to extract invoice data from the uploaded file. Please ensure the image is clear and contains valid invoice information.' }, + { status: 400 } + ); + } + + // Validate extracted data + if (!invoiceData.invoiceNumber || !invoiceData.vendor || invoiceData.totalAmount === null) { + return NextResponse.json( + { error: 'Could not extract essential invoice information (invoice number, vendor, or total amount). Please verify the image quality and try again.' }, + { status: 400 } + ); + } + + // Generate unique invoice number if extraction failed + let finalInvoiceNumber = invoiceData.invoiceNumber; + if (!finalInvoiceNumber) { + finalInvoiceNumber = `INV-${Date.now()}`; + } + + try { + // Insert invoice into database + const [invoice] = await db + .insert(schema.invoice) + .values({ + invoiceNumber: finalInvoiceNumber, + date: invoiceData.date || new Date(), + vendor: invoiceData.vendor!, + totalAmount: invoiceData.totalAmount!.toString(), + }) + .returning(); + + // Insert line items if any + if (invoiceData.lineItems.length > 0) { + await db + .insert(schema.invoiceItem) + .values( + invoiceData.lineItems.map(item => ({ + invoiceId: invoice.id, + description: item.description, + quantity: item.quantity.toString(), + unitPrice: item.unitPrice.toString(), + lineTotal: item.lineTotal.toString(), + })) + ); + } + + // Return success response with created invoice + return NextResponse.json({ + message: 'Invoice uploaded and processed successfully', + invoice: { + id: invoice.id, + invoiceNumber: invoice.invoiceNumber, + date: invoice.date, + vendor: invoice.vendor, + totalAmount: parseFloat(invoice.totalAmount), + lineItems: invoiceData.lineItems + } + }, { status: 201 }); + + } catch (dbError: any) { + console.error('Database error:', dbError); + + // Handle duplicate invoice number + if (dbError.code === '23505' && dbError.constraint?.includes('invoice_number_unique')) { + return NextResponse.json( + { error: 'An invoice with this number already exists.' }, + { status: 409 } + ); + } + + return NextResponse.json( + { error: 'Failed to save invoice to database' }, + { status: 500 } + ); + } + + } catch (error) { + console.error('Upload processing error:', error); + return NextResponse.json( + { error: 'Internal server error during file processing' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/app/invoices/page.tsx b/app/invoices/page.tsx new file mode 100644 index 0000000..36ecd50 --- /dev/null +++ b/app/invoices/page.tsx @@ -0,0 +1,5 @@ +import InvoiceDashboard from '@/components/InvoiceDashboard'; + +export default function InvoicesPage() { + return ; +} \ No newline at end of file diff --git a/components/InvoiceDashboard.tsx b/components/InvoiceDashboard.tsx new file mode 100644 index 0000000..d6557db --- /dev/null +++ b/components/InvoiceDashboard.tsx @@ -0,0 +1,675 @@ +'use client'; + +import React, { useState, useEffect, useCallback } from 'react'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Badge } from '@/components/ui/badge'; +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; +import { Alert, AlertDescription } from '@/components/ui/alert'; +import { Pagination, PaginationContent, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious } from '@/components/ui/pagination'; +import { Skeleton } from '@/components/ui/skeleton'; +import { + FileText, + Filter, + RefreshCw, + Calendar, + DollarSign, + Building, + Eye, + ChevronUp, + ChevronDown, + Search, + Plus +} from 'lucide-react'; +import { format } from 'date-fns'; +import { ChartContainer, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart'; +import { AreaChart, Area, BarChart, Bar, PieChart, Pie, Cell, ResponsiveContainer, XAxis, YAxis, CartesianGrid } from 'recharts'; +import UploadInvoiceForm from './UploadInvoiceForm'; + +interface Invoice { + id: string; + invoiceNumber: string; + date: string; + vendor: string; + totalAmount: number; + createdAt: string; + updatedAt: string; +} + +interface InvoiceDetails extends Invoice { + lineItems: { + id: string; + description: string; + quantity: number; + unitPrice: number; + lineTotal: number; + createdAt: string; + updatedAt: string; + }[]; +} + +interface FilterParams { + vendor?: string; + startDate?: string; + endDate?: string; + sortBy?: string; + sortOrder?: 'asc' | 'desc'; +} + +const ITEMS_PER_PAGE = 10; + +export default function InvoiceDashboard() { + const [invoices, setInvoices] = useState([]); + const [selectedInvoice, setSelectedInvoice] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [isLoadingDetails, setIsLoadingDetails] = useState(false); + const [error, setError] = useState(null); + const [currentPage, setCurrentPage] = useState(1); + const [totalPages, setTotalPages] = useState(1); + const [totalInvoices, setTotalInvoices] = useState(0); + const [showUploadForm, setShowUploadForm] = useState(false); + + // Filter states + const [filters, setFilters] = useState({ + sortBy: 'date', + sortOrder: 'desc' + }); + const [tempFilters, setTempFilters] = useState({}); + + const fetchInvoices = useCallback(async (page = 1, filterParams = filters) => { + try { + setIsLoading(true); + const params = new URLSearchParams({ + page: page.toString(), + limit: ITEMS_PER_PAGE.toString(), + ...(filterParams.vendor && { vendor: filterParams.vendor }), + ...(filterParams.startDate && { startDate: filterParams.startDate }), + ...(filterParams.endDate && { endDate: filterParams.endDate }), + ...(filterParams.sortBy && { sortBy: filterParams.sortBy }), + ...(filterParams.sortOrder && { sortOrder: filterParams.sortOrder }), + }); + + const response = await fetch(`/api/invoices?${params}`); + + if (!response.ok) { + throw new Error('Failed to fetch invoices'); + } + + const data = await response.json(); + setInvoices(data.invoices || []); + setCurrentPage(data.pagination?.page || 1); + setTotalPages(data.pagination?.totalPages || 1); + setTotalInvoices(data.pagination?.total || 0); + setError(null); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to fetch invoices'); + setInvoices([]); + } finally { + setIsLoading(false); + } + }, [filters]); + + const fetchInvoiceDetails = async (invoiceId: string) => { + try { + setIsLoadingDetails(true); + const response = await fetch(`/api/invoices/${invoiceId}`); + + if (!response.ok) { + throw new Error('Failed to fetch invoice details'); + } + + const data = await response.json(); + setSelectedInvoice(data.invoice); + } catch (err) { + console.error('Failed to fetch invoice details:', err); + } finally { + setIsLoadingDetails(false); + } + }; + + useEffect(() => { + fetchInvoices(currentPage, filters); + }, [fetchInvoices, currentPage, filters]); + + // Auto-refresh every 30 seconds + useEffect(() => { + const interval = setInterval(() => { + fetchInvoices(currentPage, filters); + }, 30000); + + return () => clearInterval(interval); + }, [fetchInvoices, currentPage, filters]); + + const handleFilterSubmit = (e: React.FormEvent) => { + e.preventDefault(); + setFilters(tempFilters); + setCurrentPage(1); + }; + + const handleFilterReset = () => { + const resetFilters = { sortBy: 'date', sortOrder: 'desc' as const }; + setTempFilters(resetFilters); + setFilters(resetFilters); + setCurrentPage(1); + }; + + const handleSort = (column: string) => { + const newOrder = filters.sortBy === column && filters.sortOrder === 'desc' ? 'asc' : 'desc'; + const newFilters = { ...filters, sortBy: column, sortOrder: newOrder }; + setFilters(newFilters); + setCurrentPage(1); + }; + + const handleUploadSuccess = (invoice: Invoice) => { + setShowUploadForm(false); + // Refresh the invoice list + fetchInvoices(1, filters); + }; + + const formatCurrency = (amount: number) => { + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD' + }).format(amount); + }; + + const getSortIcon = (column: string) => { + if (filters.sortBy !== column) return null; + return filters.sortOrder === 'desc' ? : ; + }; + + // Calculate summary statistics + const totalAmount = invoices.reduce((sum, invoice) => sum + invoice.totalAmount, 0); + const averageAmount = totalInvoices > 0 ? totalAmount / totalInvoices : 0; + + // Prepare chart data + const monthlyData = React.useMemo(() => { + const monthlyTotals: { [key: string]: number } = {}; + invoices.forEach(invoice => { + const month = format(new Date(invoice.date), 'MMM yyyy'); + monthlyTotals[month] = (monthlyTotals[month] || 0) + invoice.totalAmount; + }); + + return Object.entries(monthlyTotals).map(([month, total]) => ({ + month, + total + })); + }, [invoices]); + + const vendorData = React.useMemo(() => { + const vendorTotals: { [key: string]: number } = {}; + invoices.forEach(invoice => { + vendorTotals[invoice.vendor] = (vendorTotals[invoice.vendor] || 0) + invoice.totalAmount; + }); + + return Object.entries(vendorTotals) + .map(([vendor, total]) => ({ vendor, total })) + .sort((a, b) => b.total - a.total) + .slice(0, 5); // Top 5 vendors + }, [invoices]); + + const chartColors = ['#8884d8', '#82ca9d', '#ffc658', '#ff7c7c', '#8dd1e1']; + + return ( +
+ {/* Header */} +
+
+

Invoice Dashboard

+

Manage and analyze your invoices

+
+
+ + +
+
+ + {/* Summary Cards */} +
+ + + Total Invoices + + + +
{totalInvoices}
+
+
+ + + + Total Amount + + + +
{formatCurrency(totalAmount)}
+
+
+ + + + Average Amount + + + +
{formatCurrency(averageAmount)}
+
+
+ + + + This Month + + + +
+ {invoices.filter(inv => + new Date(inv.date).getMonth() === new Date().getMonth() + ).length} +
+
+
+
+ + {/* Charts */} +
+ + + Monthly Trends + Invoice amounts over time + + + + + + + + } + formatter={(value) => [formatCurrency(Number(value)), "Total"]} + /> + + + + + + + + + Top Vendors + Top 5 vendors by amount + + + + + + + + } + formatter={(value) => [formatCurrency(Number(value)), "Total"]} + /> + + + + + +
+ + {/* Filters */} + + + + + Filters + + + +
+
+
+ + setTempFilters({...tempFilters, vendor: e.target.value})} + /> +
+
+ + setTempFilters({...tempFilters, startDate: e.target.value})} + /> +
+
+ + setTempFilters({...tempFilters, endDate: e.target.value})} + /> +
+
+ + +
+
+
+ + +
+
+
+
+ + {/* Invoice List */} + + + Invoices + + {totalInvoices > 0 ? `Showing ${invoices.length} of ${totalInvoices} invoices` : 'No invoices found'} + + + + {error && ( + + {error} + + )} + + {isLoading ? ( +
+ {[...Array(5)].map((_, i) => ( + + ))} +
+ ) : invoices.length > 0 ? ( + <> + + + + handleSort('invoiceNumber')} + > +
+ Invoice # + {getSortIcon('invoiceNumber')} +
+
+ handleSort('date')} + > +
+ Date + {getSortIcon('date')} +
+
+ handleSort('vendor')} + > +
+ Vendor + {getSortIcon('vendor')} +
+
+ handleSort('totalAmount')} + > +
+ Amount + {getSortIcon('totalAmount')} +
+
+ Actions +
+
+ + {invoices.map((invoice) => ( + + + {invoice.invoiceNumber} + + + {format(new Date(invoice.date), 'MMM d, yyyy')} + + {invoice.vendor} + + {formatCurrency(invoice.totalAmount)} + + + + + + + + + Invoice Details + + Invoice #{invoice.invoiceNumber} + + + + {isLoadingDetails ? ( +
+ + + +
+ ) : selectedInvoice ? ( +
+ {/* Invoice Header */} +
+
+ +

{selectedInvoice.invoiceNumber}

+
+
+ +

+ {format(new Date(selectedInvoice.date), 'MMMM d, yyyy')} +

+
+
+ +

{selectedInvoice.vendor}

+
+
+ +

+ {formatCurrency(selectedInvoice.totalAmount)} +

+
+
+ + {/* Line Items */} + {selectedInvoice.lineItems.length > 0 && ( +
+ +
+
+ + + Description + Qty + Unit Price + Total + + + + {selectedInvoice.lineItems.map((item) => ( + + {item.description} + {item.quantity} + + {formatCurrency(item.unitPrice)} + + + {formatCurrency(item.lineTotal)} + + + ))} + +
+
+ + )} + + ) : null} + + + + + ))} + + + + {/* Pagination */} + {totalPages > 1 && ( +
+ + + + setCurrentPage(Math.max(1, currentPage - 1))} + className={currentPage === 1 ? 'pointer-events-none opacity-50' : 'cursor-pointer'} + /> + + + {[...Array(Math.min(5, totalPages))].map((_, i) => { + const pageNum = Math.max(1, currentPage - 2) + i; + if (pageNum > totalPages) return null; + + return ( + + setCurrentPage(pageNum)} + isActive={pageNum === currentPage} + className="cursor-pointer" + > + {pageNum} + + + ); + })} + + + setCurrentPage(Math.min(totalPages, currentPage + 1))} + className={currentPage === totalPages ? 'pointer-events-none opacity-50' : 'cursor-pointer'} + /> + + + +
+ )} + + ) : ( +
+ +

No invoices found

+

+ Get started by uploading your first invoice. +

+ +
+ )} + + + + {/* Upload Form Dialog */} + + + + Upload New Invoice + + Upload an invoice image or PDF for automatic processing + + + { + console.error('Upload error:', error); + }} + /> + + + + ); +} \ No newline at end of file diff --git a/components/UploadInvoiceForm.tsx b/components/UploadInvoiceForm.tsx new file mode 100644 index 0000000..4fb63b3 --- /dev/null +++ b/components/UploadInvoiceForm.tsx @@ -0,0 +1,291 @@ +'use client'; + +import React, { useState, useRef, useCallback } from 'react'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Progress } from '@/components/ui/progress'; +import { Alert, AlertDescription } from '@/components/ui/alert'; +import { Upload, FileText, CheckCircle, AlertCircle, X } from 'lucide-react'; +import { cn } from '@/lib/utils'; + +interface UploadInvoiceFormProps { + onUploadSuccess?: (invoice: any) => void; + onUploadError?: (error: string) => void; +} + +export default function UploadInvoiceForm({ + onUploadSuccess, + onUploadError +}: UploadInvoiceFormProps) { + const [isDragOver, setIsDragOver] = useState(false); + const [selectedFile, setSelectedFile] = useState(null); + const [isUploading, setIsUploading] = useState(false); + const [uploadProgress, setUploadProgress] = useState(0); + const [uploadStatus, setUploadStatus] = useState<'idle' | 'success' | 'error'>('idle'); + const [statusMessage, setStatusMessage] = useState(''); + const fileInputRef = useRef(null); + + const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'application/pdf']; + const maxSize = 5 * 1024 * 1024; // 5MB + + const validateFile = (file: File): string | null => { + if (!allowedTypes.includes(file.type)) { + return 'Invalid file type. Only images (JPEG, PNG, GIF) and PDF files are allowed.'; + } + if (file.size > maxSize) { + return 'File too large. Maximum size is 5MB.'; + } + return null; + }; + + const resetForm = () => { + setSelectedFile(null); + setUploadProgress(0); + setUploadStatus('idle'); + setStatusMessage(''); + if (fileInputRef.current) { + fileInputRef.current.value = ''; + } + }; + + const handleFileSelect = useCallback((file: File) => { + const validationError = validateFile(file); + if (validationError) { + setUploadStatus('error'); + setStatusMessage(validationError); + onUploadError?.(validationError); + return; + } + + setSelectedFile(file); + setUploadStatus('idle'); + setStatusMessage(''); + }, [onUploadError]); + + const handleDragOver = useCallback((e: React.DragEvent) => { + e.preventDefault(); + setIsDragOver(true); + }, []); + + const handleDragLeave = useCallback((e: React.DragEvent) => { + e.preventDefault(); + setIsDragOver(false); + }, []); + + const handleDrop = useCallback((e: React.DragEvent) => { + e.preventDefault(); + setIsDragOver(false); + + const files = Array.from(e.dataTransfer.files); + if (files.length > 0) { + handleFileSelect(files[0]); + } + }, [handleFileSelect]); + + const handleFileInputChange = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (file) { + handleFileSelect(file); + } + }; + + const simulateProgress = () => { + setUploadProgress(10); + + const interval = setInterval(() => { + setUploadProgress(prev => { + if (prev >= 90) { + clearInterval(interval); + return 90; + } + return prev + Math.random() * 15; + }); + }, 200); + + return interval; + }; + + const handleUpload = async () => { + if (!selectedFile) return; + + setIsUploading(true); + setUploadStatus('idle'); + setStatusMessage('Processing invoice...'); + + const progressInterval = simulateProgress(); + + try { + const formData = new FormData(); + formData.append('invoice', selectedFile); + + const response = await fetch('/api/invoices/upload', { + method: 'POST', + body: formData, + }); + + clearInterval(progressInterval); + setUploadProgress(100); + + const data = await response.json(); + + if (response.ok) { + setUploadStatus('success'); + setStatusMessage('Invoice uploaded and processed successfully!'); + onUploadSuccess?.(data.invoice); + + // Reset form after 3 seconds + setTimeout(() => { + resetForm(); + }, 3000); + } else { + throw new Error(data.error || 'Upload failed'); + } + } catch (error) { + clearInterval(progressInterval); + const errorMessage = error instanceof Error ? error.message : 'Upload failed'; + setUploadStatus('error'); + setStatusMessage(errorMessage); + setUploadProgress(0); + onUploadError?.(errorMessage); + } finally { + setIsUploading(false); + } + }; + + const getStatusIcon = () => { + switch (uploadStatus) { + case 'success': + return ; + case 'error': + return ; + default: + return ; + } + }; + + return ( + + + + + Upload Invoice + + + Upload an image or PDF of your invoice for automatic data extraction + + + + {/* Drag and Drop Zone */} +
fileInputRef.current?.click()} + > + + + {selectedFile ? ( +
+
+ {getStatusIcon()} + + {selectedFile.name} + + {!isUploading && ( + + )} +
+

+ {(selectedFile.size / 1024 / 1024).toFixed(2)} MB +

+
+ ) : ( +
+ +
+

+ Drag and drop your invoice here +

+

+ or click to browse files +

+
+

+ Supports: JPEG, PNG, GIF, PDF (max 5MB) +

+
+ )} +
+ + {/* Upload Progress */} + {isUploading && ( +
+
+ Uploading... + {Math.round(uploadProgress)}% +
+ +
+ )} + + {/* Status Message */} + {statusMessage && ( + + {getStatusIcon()} + + {statusMessage} + + + )} + + {/* Upload Button */} + +
+
+ ); +} \ No newline at end of file diff --git a/components/app-sidebar.tsx b/components/app-sidebar.tsx index 42d379d..6cf86c9 100644 --- a/components/app-sidebar.tsx +++ b/components/app-sidebar.tsx @@ -19,6 +19,7 @@ import { IconSearch, IconSettings, IconUsers, + IconReceipt, } from "@tabler/icons-react" import { NavDocuments } from "@/components/nav-documents" @@ -39,9 +40,14 @@ const staticData = { navMain: [ { title: "Dashboard", - url: "#", + url: "/dashboard", icon: IconDashboard, }, + { + title: "Invoices", + url: "/invoices", + icon: IconReceipt, + }, { title: "Lifecycle", url: "#", diff --git a/components/nav-main.tsx b/components/nav-main.tsx index 3759bb8..4142381 100644 --- a/components/nav-main.tsx +++ b/components/nav-main.tsx @@ -45,9 +45,11 @@ export function NavMain({ {items.map((item) => ( - - {item.icon && } - {item.title} + + + {item.icon && } + {item.title} + ))} diff --git a/db/index.ts b/db/index.ts index 51d837f..ffa3d57 100644 --- a/db/index.ts +++ b/db/index.ts @@ -1,4 +1,31 @@ import 'dotenv/config'; import { drizzle } from 'drizzle-orm/node-postgres'; +import { Pool } from 'pg'; +import * as schema from './schema/invoices'; +import * as authSchema from './schema/auth'; -export const db = drizzle(process.env.DATABASE_URL!); \ No newline at end of file +const pool = new Pool({ + connectionString: process.env.DATABASE_URL!, + max: 10, + idleTimeoutMillis: 30000, + connectionTimeoutMillis: 2000, +}); + +export const db = drizzle(pool, { + schema: { ...schema, ...authSchema } +}); + +export { schema, authSchema }; + +export async function testConnection() { + try { + const client = await pool.connect(); + await client.query('SELECT NOW()'); + client.release(); + console.log('Database connection successful'); + return true; + } catch (error) { + console.error('Database connection failed:', error); + return false; + } +} \ No newline at end of file diff --git a/db/schema/invoices.ts b/db/schema/invoices.ts new file mode 100644 index 0000000..b148fa9 --- /dev/null +++ b/db/schema/invoices.ts @@ -0,0 +1,32 @@ +import { pgTable, text, timestamp, decimal, integer, uuid } from "drizzle-orm/pg-core"; + +export const invoice = pgTable("invoice", { + id: uuid("id").defaultRandom().primaryKey(), + invoiceNumber: text("invoice_number").notNull().unique(), + date: timestamp("date").notNull(), + vendor: text("vendor").notNull(), + totalAmount: decimal("total_amount", { precision: 12, scale: 2 }).notNull(), + createdAt: timestamp("created_at") + .$defaultFn(() => new Date()) + .notNull(), + updatedAt: timestamp("updated_at") + .$defaultFn(() => new Date()) + .notNull(), +}); + +export const invoiceItem = pgTable("invoice_item", { + id: uuid("id").defaultRandom().primaryKey(), + invoiceId: uuid("invoice_id") + .notNull() + .references(() => invoice.id, { onDelete: "cascade" }), + description: text("description").notNull(), + quantity: decimal("quantity", { precision: 10, scale: 3 }).notNull(), + unitPrice: decimal("unit_price", { precision: 12, scale: 2 }).notNull(), + lineTotal: decimal("line_total", { precision: 12, scale: 2 }).notNull(), + createdAt: timestamp("created_at") + .$defaultFn(() => new Date()) + .notNull(), + updatedAt: timestamp("updated_at") + .$defaultFn(() => new Date()) + .notNull(), +}); \ No newline at end of file diff --git a/documentation/app_flow_document.md b/documentation/app_flow_document.md index 1916d04..518f485 100644 --- a/documentation/app_flow_document.md +++ b/documentation/app_flow_document.md @@ -2,40 +2,28 @@ ## Onboarding and Sign-In/Sign-Up -When a new visitor arrives at the application’s root URL, they land on a welcome page that offers clear buttons or links to either create an account or sign in. Clicking on the “Sign Up” link takes the visitor to a registration page where they enter their email address and choose a password. Once they submit the form, the application sends a POST request to the authentication API endpoint, which handles password hashing and user creation. If registration succeeds, the new user is automatically signed in and redirected to the dashboard. If there are validation errors, such as a password that is too short or an email already in use, the form reappears with inline messages explaining what must be corrected. +When a brand-new visitor opens the application in a browser, they arrive at the root URL, where the global layout displays the site name and navigation links labeled “Sign In” and “Sign Up.” Clicking “Sign Up” takes the user to the sign-up page. A simple form appears asking for an email address and a password. When the user fills in these fields and submits the form, the application sends the information to the authentication endpoint at `/api/auth/route`. If everything is valid, the user is automatically logged in and redirected to the dashboard. If there is an error, an inline message appears above the form explaining what went wrong, and the user can correct the input. -For returning users, clicking the “Sign In” link from the welcome page or from a persistent header link opens the login form. They input their email and password, and upon submission the app sends a request to the same authentication API with login credentials. A successful login leads directly to the dashboard. If the credentials are invalid, the page reloads with a clear error message prompting the user to try again. - -Signing out is available from within the dashboard via a logout button in the main navigation or header. When clicked, the application clears the user’s session or token, and then navigates back to the welcome page. Currently, there is no built-in password recovery or reset flow in this version, so users who forget their password are prompted to contact support for assistance. +Returning visitors click “Sign In” in the navigation. They are taken to a page with a login form that again asks for email and password. After entering credentials and submitting, the application sends a POST request to the same `/api/auth/route` endpoint. On success, the user session is established and the user is sent to the main dashboard. If the credentials are incorrect, an error message appears and the user stays on the sign-in page until valid details are entered. There is no separate forgot password flow in the current version, so users must contact support or retry with known credentials. Signing out is available after login through a link labeled “Sign Out” in the header, which clears the session and returns the user to the sign-in page. ## Main Dashboard or Home Page -After authentication, the user lands on the dashboard, which serves as the main home page. The dashboard is wrapped in a layout that displays a header bar and a sidebar navigation tailored for logged-in users. The header bar typically shows the application’s logo on the left and a Logout link on the right. The sidebar sits on the left side of the screen and may contain links back to the dashboard’s main panel or to future features. - -The central area of the dashboard page displays data pulled from a static JSON file. This content might appear in cards or tables to give users a quick overview of information. All styling for this section comes from a dedicated theme stylesheet to keep the look consistent. Users can click items or links here, but those actions are placeholders for future dynamic data features. - -From this dashboard view, users may revisit the welcome page or any other main area by selecting navigation items in the sidebar or header. The layout ensures that the logout link remains accessible at all times, and that the user cannot leave the authenticated area without signing out manually or having their session expire. +Once authenticated, the user lands on the dashboard at `/dashboard`. The global layout still wraps the view, providing the site header and the “Sign Out” link, and the dashboard layout adds a sidebar or top navigation bar specific to the dashboard. This dashboard interface shows data pulled from a local `data.json` file, such as lists of items or summary metrics. The header area may display the user’s email or name. From this main view, the user can click any sidebar label or header link to navigate to other parts of the dashboard or return to the sign-in or sign-up pages if they have been logged out. ## Detailed Feature Flows and Page Transitions -When a visitor lands on the root page, JavaScript on the client reads the route and displays either the welcome interface or automatically redirects them to the dashboard if a valid session exists. For new user registration, the user clicks the Sign Up link and is taken to the sign-up page. The sign-up form collects email and password fields, and on submission it triggers a client-side POST to the API route. Once the API responds with success, the client redirects the user to the dashboard page. +When the user chooses to sign up, the page transition goes from the root layout to the sign-up page. The sign-up form collects the user’s email and password and then calls the API to create a new account. After a successful response, the application programmatically navigates to the dashboard route. If the user instead goes to sign in, the transition is the same but the form action is interpreted as authentication rather than account creation. Once the user is on the dashboard, clicking the site logo or a “Home” link in the dashboard navigation returns them to the dashboard main view. Attempting to access `/dashboard` directly in the URL bar while not signed in triggers a redirect back to `/sign-in`, enforcing protection. -Returning users choose the Sign In link and arrive at the sign-in page, which offers the same fields as the sign-up page but is wired to authenticate rather than create a new account. On form submit, the user sees a loading indication until the API confirms valid credentials. If successful, the client pushes the dashboard route and loads the dashboard layout and content. - -All authenticated pages reside under the `/dashboard` path. When the user attempts to navigate directly to `/dashboard` without a valid session, server-side redirection logic intercepts the request and sends the user back to the sign-in page. This ensures that protected content never shows to unauthorized visitors. - -Signing out happens entirely on the client side by calling an API or clearing a cookie, then navigating back to the welcome page. The client code listens for the logout action, invalidates the current session, and then reloads or reroutes the application state to the landing interface. +On the dashboard itself, any link or button that takes the user outside of this section is handled by the root layout, which may navigate back to the sign-in or sign-up pages. When the user clicks “Sign Out,” the application calls a sign-out function that clears the session on the client and possibly invalidates a cookie, then routes the user to the sign-in page. Every page transition uses the framework’s file-based routing under the `app` directory, so the URL path always matches the folder and file where the code lives. ## Settings and Account Management -At present, users cannot change profile information, update their email, or configure notifications from within the interface. The only account management available is the ability to sign out from any dashboard view. In future iterations, a dedicated settings page could be added to let users update personal details or adjust preferences, but in this version, those capabilities are not provided. After signing out, users always return to the welcome page and must sign in again to regain access to the dashboard. +In the current version, there is no dedicated settings or profile page for managing personal details beyond email and password at sign-up. The primary account management action is signing out, which the user finds in the global header. Future versions may include a profile page under `/profile` or `/settings` where users can update their information, configure notification preferences, or manage subscriptions. At present, any change to preferences or account details would require updating code or database records externally, as there is no UI for it. ## Error States and Alternate Paths -If a user types an incorrect email or password on the sign-in page, the authentication API responds with an error status and a message. The form then displays an inline alert near the input fields explaining the issue, such as “Invalid email or password,” allowing the user to correct and resubmit. During sign up, validation errors like a missing field or weak password appear immediately under the relevant input. - -Network failures trigger a generic error notification at the top of the form, informing the user that the request could not be completed and advising them to check their connection. If the dashboard content fails to load due to a broken or missing static data file, a fallback message appears in the main panel stating that data could not be loaded and suggesting a page refresh. Trying to access a protected route without a session sends the user to the sign-in page automatically, making it clear that authentication is required. +If the user enters invalid data on sign-up or sign-in, the form validation runs on the client and the server. Errors such as missing fields, improperly formatted email, or weak password are caught and displayed above the form. If the network connection fails during an API call, a generic message prompts the user to check their connection and try again. When an unauthenticated user attempts to view a protected page, the application automatically redirects to the sign-in page with no warning popup, preserving security. Unexpected server errors return a generic error view or message, and the user can retry the action or refresh the page to recover. ## Conclusion and Overall App Journey -A typical user journey starts with visiting the application’s root URL, signing up with an email and password, then being welcomed in the dashboard area that displays sample data. Returning users go directly through the sign-in page to the dashboard. Throughout each step, clear messages guide the user in case of errors or invalid input. The layout remains consistent, with a header and navigation ensuring that users always know where they are and can sign out at any time. This flow lays the foundation for adding dynamic data, user profile management, and richer features in future releases. \ No newline at end of file +From the moment a new user discovers the application at its root URL, they can create an account or log in with just an email and password. After authentication, they land on a clean dashboard interface that uses mock data to illustrate how real content will appear. Navigation is intuitive through links in the global header and dashboard layout, and signing out is always one click away. Error messages guide the user through any mistakes, and unauthorized access is blocked automatically. In everyday use, the goal is to sign in, review or interact with dashboard data, and sign out when finished, providing a smooth and predictable journey from start to finish. \ No newline at end of file diff --git a/documentation/app_flowchart.md b/documentation/app_flowchart.md index 8460af1..fcff11c 100644 --- a/documentation/app_flowchart.md +++ b/documentation/app_flowchart.md @@ -1,14 +1,15 @@ flowchart TD - Start[Landing Page] - SignUpPage[Sign Up Page] - SignInPage[Sign In Page] - AuthAPI[Authentication API Endpoint] - DashboardPage[Dashboard Page] - Start -->|Select Sign Up| SignUpPage - Start -->|Select Sign In| SignInPage - SignUpPage -->|Submit Credentials| AuthAPI - SignInPage -->|Submit Credentials| AuthAPI - AuthAPI -->|Success| DashboardPage - AuthAPI -->|Error| SignUpPage - AuthAPI -->|Error| SignInPage - DashboardPage -->|Click Logout| Start \ No newline at end of file +A[Visit Root URL] --> B{User Authenticated} +B -->|No| C[Show Sign In and Sign Up Links] +B -->|Yes| D[Redirect to Dashboard] +C --> E[Sign In Page] +C --> F[Sign Up Page] +E --> G[Submit Credentials to Auth Endpoint] +F --> G +G --> H{Auth Success} +H -->|Yes| D +H -->|No| I[Show Error Message] +I --> C +D --> J[Dashboard Page] +J --> K[Click Sign Out] +K --> B \ No newline at end of file diff --git a/documentation/backend_structure_document.md b/documentation/backend_structure_document.md index 1ea043b..85231c8 100644 --- a/documentation/backend_structure_document.md +++ b/documentation/backend_structure_document.md @@ -1,179 +1,143 @@ # Backend Structure Document -This document outlines the backend architecture, hosting, and infrastructure for the **codeguide-starter** project. It uses plain language so anyone can understand how the backend is set up and how it supports the application. +This document explains how the backend of the `codeguide-starter-fullstack` project is organized, hosted, and maintained. It’s written in plain language so anyone can understand how the pieces fit together and why they were chosen. ## 1. Backend Architecture -- **Framework and Design Pattern** - - We use **Next.js API Routes** to handle all server-side logic. These routes live alongside the frontend code in the same repository, making development and deployment simpler. - - The backend follows a **layered pattern**: - 1. **API Layer**: Receives requests (login, registration, data fetch). - 2. **Service Layer**: Contains the core business logic (user validation, password hashing). - 3. **Data Access Layer**: Talks to the database via a simple ORM (e.g., Prisma or TypeORM). +### Overall Design +- We use Next.js’s built-in API Routes to handle server-side work. Each file under `app/api/` becomes a serverless function. +- The backend lives in the same codebase as the frontend, sharing TypeScript types and folder structure. +- File-based routing means the folder structure directly maps to URL paths, so there’s no separate routing config. -- **Scalability** - - Stateless API routes can scale horizontally—new instances can spin up on demand. - - We can add caching or a message queue (e.g., Redis or RabbitMQ) without changing the core code. +### Frameworks and Patterns +- **Next.js API Routes**: Server‐side endpoints written in TypeScript. +- **Node.js**: Under the hood, those API routes run on a Node.js environment (v16+). +- **Component-Driven**: Business logic is split into small functions that can be re-used by different routes. -- **Maintainability** - - Code for each feature is grouped by route (authentication, dashboard). - - A service layer separates complex logic from request handling. - -- **Performance** - - Lightweight Node.js handlers keep response times low. - - Future use of database connection pooling and Redis for caching repeated queries. +### Scalability, Maintainability, Performance +- **Scalability**: Serverless functions auto-scale with demand—no need to manage servers. +- **Maintainability**: Keeping backend code next to frontend code simplifies sharing types and utilities. +- **Performance**: Next.js pre-optimizes routes and cold starts are minimized on platforms like Vercel. ## 2. Database Management -- **Database Choice** - - We recommend **PostgreSQL** for structured data and reliable transactions. - - In-memory caching can be added later with **Redis** for session tokens or frequently read data. +### Current Setup (Mock Data) +- We use a local JSON file (`data.json`) as a stand-in for a real database during development. +- This file lives in the repo and contains an array of objects representing dashboard items. -- **Data Storage and Access** - - Use an ORM like **Prisma** or **TypeORM** to map JavaScript/TypeScript objects to database tables. - - Connection pooling ensures efficient use of database connections under load. - - Migrations track schema changes over time, keeping development, staging, and production in sync. +### Future Database Options +- When migrating to a real database, you could choose: + - **SQL (PostgreSQL, MySQL)** for structured, relational data. + - **NoSQL (MongoDB, DynamoDB)** for flexible, document-oriented storage. -- **Data Practices** - - Passwords are never stored in plain text—they are salted and hashed with **bcrypt** before saving. - - All outgoing data is typed and validated to prevent malformed records. +### Data Access +- In the mock version, API routes read `data.json` directly. +- In a real setup, you’d replace that read with a database query using an ORM like Prisma or a driver like `pg`. ## 3. Database Schema -### Human-Readable Format - -- **Users** - - **id**: Unique identifier - - **email**: User’s email address (unique) - - **password_hash**: Securely hashed password - - **created_at**: Account creation timestamp +### Mock Data (`data.json`) +The JSON file holds an array of dashboard items. Each item has: +- **id** (string): Unique identifier. +- **title** (string): Name of the item or metric. +- **value** (number): Numeric data to display. +- **details** (string): Optional description or extra info. -- **Sessions** - - **id**: Unique session record - - **user_id**: Links to a user - - **token**: Random string for authentication - - **expires_at**: When the token stops working - - **created_at**: When the session was created +Example in everyday terms: +- Item 1: id = "item1", title = "Total Users", value = 1024, details = "Count of all registered users" +- Item 2: id = "item2", title = "Active Sessions", value = 87, details = "Users currently online" -- **DashboardItems** *(optional for dynamic data)* - - **id**: Unique record - - **title**: Item title - - **content**: Item details - - **created_at**: When the item was added - -### SQL Schema (PostgreSQL) +### (Future) SQL Schema Example +If you switch to PostgreSQL, your `dashboard_items` table might look like: ```sql --- Users table -CREATE TABLE users ( - id SERIAL PRIMARY KEY, - email VARCHAR(255) UNIQUE NOT NULL, - password_hash VARCHAR(255) NOT NULL, - created_at TIMESTAMPTZ DEFAULT now() -); - --- Sessions table -CREATE TABLE sessions ( - id SERIAL PRIMARY KEY, - user_id INT REFERENCES users(id) ON DELETE CASCADE, - token VARCHAR(255) UNIQUE NOT NULL, - expires_at TIMESTAMPTZ NOT NULL, - created_at TIMESTAMPTZ DEFAULT now() -); - --- Dashboard items table CREATE TABLE dashboard_items ( - id SERIAL PRIMARY KEY, + id TEXT PRIMARY KEY, title TEXT NOT NULL, - content TEXT, - created_at TIMESTAMPTZ DEFAULT now() + value INTEGER NOT NULL, + details TEXT ); -``` +``` ## 4. API Design and Endpoints -- **Approach**: We follow a **RESTful** style, grouping related endpoints under `/api` directories. - -- **Key Endpoints** - - `POST /api/auth/register` - • Accepts `{ email, password }` - • Creates a new user and issues a session token - - `POST /api/auth/login` - • Accepts `{ email, password }` - • Verifies credentials and returns a session token - - `POST /api/auth/logout` - • Invalidates the session token on the server - - `GET /api/dashboard/data` - • Requires a valid session - • Returns user-specific data or dashboard items - -- **Communication** - - Frontend sends JSON requests; backend replies with JSON and appropriate HTTP status codes. - - Protected routes check for a valid session token (in cookies or Authorization header). +We follow a simple RESTful style using Next.js API Routes. -## 5. Hosting Solutions +### Key Endpoints +- **POST /api/auth** + - Purpose: Sign up or sign in a user. + - Input: `{ email: string, password: string }` + - Output: Success status, user session or error message. -- **Cloud Provider**: - - **Vercel** (recommended) offers seamless Next.js deployments, auto-scaling, and built-in CDN. - - Alternatively, **Netlify** or any Node.js-capable host will work. +- **GET /api/data** (optional) + - Purpose: Fetch mock dashboard items from `data.json`. + - Output: Array of items. -- **Benefits** - - **Reliability**: Global servers and failover across regions. - - **Scalability**: Auto-scale serverless functions based on traffic. - - **Cost-Effectiveness**: Pay-per-use model means low cost for small projects. +### How Frontend Calls These +1. Sign-in form submits credentials to `/api/auth`. +2. On success, Next.js sets a session cookie and redirects to `/dashboard`. +3. Dashboard page fetches `/api/data` to display items. -## 6. Infrastructure Components +## 5. Hosting Solutions -- **Load Balancer** - - Provided by the hosting platform—distributes API requests across function instances. +### Chosen Platform: Vercel +- **Why Vercel?** + - Built by the creators of Next.js—seamless integration. + - Automatic global CDN for static assets. + - Serverless functions run our API Routes without setup. -- **CDN (Content Delivery Network)** - - Vercel’s global edge network caches static assets (CSS, JS, images) for faster page loads. +### Benefits +- **Reliability**: Automatic health checks and rollbacks. +- **Scalability**: Instantly scales serverless functions with traffic. +- **Cost-Effective**: Free tier available; pay only for extra usage. -- **Caching** - - **Redis** (optional) for session storage or caching dashboard queries to reduce database load. +## 6. Infrastructure Components -- **Object Storage** - - For file uploads or backups, integrate with AWS S3 or similar services. +### Load Balancing and CDN +- Vercel uses its edge network to distribute traffic across regions. +- Static files (CSS, images) served from a CDN close to users. -- **Message Queue** - - In future, use **RabbitMQ** or **Kafka** for background tasks (e.g., email notifications). +### Caching Mechanisms +- **ISR (Incremental Static Regeneration)** in Next.js can cache pages and update them in the background. +- **HTTP Caching** headers can be configured for API responses. -## 7. Security Measures +### Content Delivery Networks +- All static assets are automatically uploaded and distributed by Vercel’s CDN. -- **Authentication & Authorization** - - Passwords hashed with **bcrypt** and salted. - - Session tokens stored in secure, HttpOnly cookies or Authorization headers. - - Protected endpoints verify tokens before proceeding. +## 7. Security Measures -- **Data Encryption** - - **HTTPS/TLS** encrypts data in transit. - - Database connections use SSL to encrypt data between the app and the database. +### Authentication & Authorization +- **Secure Cookies**: Session tokens are stored in HTTP-only cookies to prevent JavaScript access. +- **Password Handling**: Even in mock mode, follow best practice by hashing passwords with a library like `bcrypt`. -- **Input Validation** - - Every incoming request is validated (e.g., valid email format, password length) to prevent SQL injection or other attacks. +### Data Protection +- **Input Validation**: Check and sanitize all incoming data on the server. +- **Redirect Protection**: Unauthenticated users are sent back to `/sign-in` if they try to access protected routes. -- **Web Security Best Practices** - - Enable **CORS** policies to limit allowed origins. - - Use **CSRF tokens** or same-site cookies to prevent cross-site requests. - - Set secure headers with **Helmet** or a similar middleware. +### Compliance and Best Practices +- Plan for GDPR by ensuring user data can be erased or exported upon request. +- Prepare to add CSRF tokens if you introduce cookie-based sessions in production. ## 8. Monitoring and Maintenance -- **Performance Monitoring** - - Integrate **Sentry** or **LogRocket** for real-time crash reporting and performance tracing. - - Use Vercel’s built-in analytics to track request latencies and error rates. - -- **Logging** - - Structured logs (JSON) for all API requests and errors, shipped to a log management service like **Datadog** or **Logflare**. +### Performance Monitoring +- **Vercel Analytics**: Built-in metrics for response times, cold starts, and bandwidth. +- **Optional Tools**: Integrate Sentry or Datadog to track errors and performance events in more detail. -- **Health Checks** - - Define a `/health` endpoint that returns a 200 status if the service is up and the database is reachable. +### Logging +- Use `console.log` in development; in production, route logs to a service like Logflare or Datadog. -- **Maintenance Strategies** - - Automated migrations run on deploy to keep the database schema up to date. - - Scheduled dependency audits and security scans (e.g., `npm audit`). - - Regular backups of the database (daily or weekly depending on usage). +### Maintenance Strategies +- **Automated Tests**: Run linting and basic API tests via GitHub Actions on every pull request. +- **Dependency Updates**: Schedule regular checks (Dependabot) and use `npm audit` to identify vulnerabilities. +- **Backups**: If you adopt a real database, set up automated backups (e.g., daily snapshots on PostgreSQL). ## 9. Conclusion and Overall Backend Summary -The backend for **codeguide-starter** is built on Next.js API Routes and Node.js, paired with PostgreSQL for data and optional Redis for caching. It follows a clear layered architecture that keeps code easy to maintain and extend. With RESTful endpoints for authentication and data, secure practices like password hashing and HTTPS, and hosting on Vercel for scalability and global performance, this setup meets the project’s goals for a fast, secure, and developer-friendly foundation. Future enhancements—such as background job queues, advanced monitoring, or richer data models—can be added without disrupting the core structure. \ No newline at end of file +This backend uses Next.js API Routes and a local JSON file as a quick, zero-config starting point. It lives next to the frontend for shared types and easy deployment. Hosted on Vercel, it scales seamlessly without manual server management. Key highlights: + +- **Serverless Architecture**: No servers to maintain—functions scale automatically. +- **Local Mock Database**: Simplifies development; ready to swap to SQL or NoSQL. +- **Secure by Default**: HTTP-only cookies and input checks protect user data. +- **Easy Monitoring**: Vercel Analytics plus optional Sentry/Datadog integrations. + +As your project grows, you can replace the mock data with a real database, integrate third-party services, and expand the API surface—all without changing the underlying structure. This setup gives you a clear path from prototype to production. \ No newline at end of file diff --git a/documentation/frontend_guidelines_document.md b/documentation/frontend_guidelines_document.md index ffa3ea5..f71bd10 100644 --- a/documentation/frontend_guidelines_document.md +++ b/documentation/frontend_guidelines_document.md @@ -1,180 +1,168 @@ # Frontend Guideline Document -This document explains, in simple terms, how the frontend of the `codeguide-starter` project is structured, styled, and built. Anyone—technical or not—can read this and understand which tools are used, how components fit together, and what practices keep the app fast, reliable, and easy to maintain. +This document explains the frontend setup for the `codeguide-starter-fullstack` project in clear, everyday language. It covers architecture, design principles, styling, component organization, state handling, routing, performance, and testing practices. --- ## 1. Frontend Architecture -**Core Frameworks and Libraries** -- **Next.js (App Router)**: A React-based framework that provides file-based routing, server-side rendering (SSR), static site generation (SSG), and built-in API endpoints all in one project. -- **React 18**: The library for building user interfaces using components and hooks. -- **TypeScript**: A superset of JavaScript that adds static types, helping catch errors early and making the code easier to understand and refactor. - -**How It’s Organized** -- The `app/` folder holds all pages and layouts. Each URL path corresponds to a folder: - - `/app/sign-in` and `/app/sign-up` for authentication pages. - - `/app/dashboard` for the protected user area. - - API routes live under `/app/api/auth/route.ts`. -- Each route folder contains: - - `page.tsx` (the UI for that page) - - `layout.tsx` (wrapping structure, like headers or sidebars) - - Styles (e.g., `theme.css` in the dashboard). - -**Why This Works** -- **Scalability**: Adding new pages or features means creating new folders with their own layouts and pages. You don’t have to touch a central router file. -- **Maintainability**: Code is separated by feature. Backend logic (API routes) lives alongside the frontend code for that feature, reducing context-switching. -- **Performance**: Next.js pre-renders pages where possible and splits code by route, so users download only what’s needed. +**Frameworks and Libraries** +- **Next.js 13** (or Remix): Provides file-based routing, built-in API routes, and support for server-side rendering (SSR) and static site generation (SSG). +- **React 18**: The UI library that lets us build reusable components. +- **TypeScript**: Adds type safety to catch errors early and make the code easier to understand. +- **CSS**: We use plain CSS files (`globals.css` and `theme.css`) with CSS custom properties for theming. + +**How It Supports Scalability, Maintainability, and Performance** +- **File-based routing** means each folder and file under `app/` maps directly to a URL path. Adding a new page is as easy as creating a new file. +- **Nested layouts** (`app/layout.tsx` and `app/dashboard/layout.tsx`) allow us to share headers, footers, and sidebars without repeating code. +- **Component-based structure** (React) promotes reuse: build small, focused pieces that can be combined in different ways. +- **TypeScript and linting** (ESLint & Prettier) ensure consistent code style, fewer runtime errors, and easier onboarding for new developers. +- **SSR/SSG** speeds up initial page loads and improves SEO out of the box. --- ## 2. Design Principles -1. **Usability**: Forms give instant feedback. Buttons and links are clearly labeled. -2. **Accessibility**: Semantic HTML, proper color contrast, and focus outlines ensure people using screen readers or keyboards can navigate easily. -3. **Responsiveness**: Layouts adapt from mobile (320px) up to large desktop screens. CSS media queries ensure content resizes and stacks neatly. -4. **Consistency**: Shared global layout and styling mean pages look and feel like part of the same app. - -**How We Apply Them** -- Form fields use `aria-*` attributes and visible labels. -- Error messages appear inline under inputs. -- Navigation elements (header, sidebar) appear in every layout. -- Breakpoints at 480px, 768px, and 1024px guide responsive adjustments. +We follow these guiding principles to create a user-friendly interface: + +1. **Usability** + - Simple, predictable navigation. + - Clear calls to action (e.g., “Sign In,” “Sign Up,” “Sign Out”). +2. **Accessibility** + - Semantic HTML elements (forms, buttons, headings). + - ARIA attributes and keyboard support for screen readers. +3. **Responsiveness** + - Mobile-first CSS breakpoints. + - Flexible layouts that adapt to different screen sizes. +4. **Consistency** + - Uniform color scheme and typography across all pages. + - Reusable component styles to avoid visual drift. + +How We Apply These +- Forms on sign-in and sign-up pages include error messages and focus management for keyboard navigation. +- The dashboard layout uses clear headings and side navigation that collapse gracefully on smaller screens. +- All interactive elements (links, buttons) have hover/focus styles for better discoverability. --- ## 3. Styling and Theming -**Approach** -- **Global Styles (`globals.css`)**: Resets, base typography, and common utility classes. -- **Section Styles (`theme.css` in dashboard)**: Styles specific to the dashboard area (colors, layouts). -- We follow a **BEM-inspired naming** for classes when writing new CSS to avoid conflicts and keep selectors clear. - -**Visual Style**: Modern flat design with subtle shadows for depth. Clear spacing and large touch targets on mobile. - -**Color Palette** -- **Primary Blue**: #1E90FF (buttons, highlights) -- **Secondary Navy**: #2C3E50 (header, sidebar background) -- **Accent Cyan**: #00CEC9 (links, hover states) -- **Neutral Light**: #F8F9FA (page backgrounds) -- **Neutral Dark**: #2D3436 (text, icons) - -**Font** -- **Inter** (sans-serif): Clean, modern, highly legible on screens. Fallback to system fonts like `-apple-system, BlinkMacSystemFont, sans-serif`. +**Styling Approach** +- **CSS files**: We have two main files: + 1. `globals.css` – Base styles for typography, layout resets, and utility classes. + 2. `theme.css` – CSS custom properties (variables) defining the color palette and spacing scales. +- **CSS Methodology**: We adopt a simple, modular approach: + - Use descriptive class names in **BEM** style (e.g., `.button`, `.button--primary`). + - Keep component styles close to their markup in dedicated `.css` files if needed. **Theming** -- To keep a consistent look, all colors and font sizes are defined in CSS variables in `globals.css`: - ```css - :root { - --color-primary: #1E90FF; - --color-secondary: #2C3E50; - --color-accent: #00CEC9; - --color-bg: #F8F9FA; - --color-text: #2D3436; - --font-family: 'Inter', sans-serif; - } - ``` -- Components consume these variables for backgrounds, borders, and text. +- All colors and fonts are defined as CSS variables in `theme.css`. +- Switching themes (e.g., light/dark) is a matter of toggling a single class on `` or ``. + +**Visual Style** +- **Overall Style**: Flat, modern design with generous white space and clear typography. +- **Color Palette**: + - `--color-primary`: #4F46E5 (indigo) + - `--color-secondary`: #10B981 (emerald) + - `--color-accent`: #F59E0B (amber) + - `--color-background`: #F9FAFB (light gray) + - `--color-surface`: #FFFFFF (white) + - `--color-border`: #E5E7EB (gray) + - `--color-text`: #111827 (dark gray) + - `--color-text-muted`: #6B7280 (mid gray) +- **Typography**: + - Primary font: **Inter**, with system-font fallbacks (`-apple-system, BlinkMacSystemFont, sans-serif`). + - Base font size: 16px; line height: 1.5; clear hierarchy with `h1`–`h4` sizing. --- ## 4. Component Structure -**File Layout** -- `/app` (top-level folder) - - `layout.tsx`: Global wrapper (nav, footer). - - `page.tsx`: Landing or redirect logic. - - `/sign-in`, `/sign-up`, `/dashboard`, `/api/auth` - - Each has its own `layout.tsx` and `page.tsx`. -- **Common Components**: Put reusable UI pieces (buttons, inputs, cards) into a `/components` folder at the project root. +**Organization** +- **Feature-based folders** under `app/` for pages and layouts (`dashboard/`, `sign-in/`, `sign-up/`). +- A top-level **`components/`** directory for shared UI elements (e.g., Button, Input, Card). +- Each component has: + - A `.tsx` file for markup and logic. + - A `.module.css` or scoped CSS file for styles (if more CSS is needed beyond the global/theme). -**Reusability & Encapsulation** -- Components are self-contained: each has its own styles (class names scoped to BEM) and behavior. -- Shared logic (e.g., API calls) lives in `/lib` or `/hooks` so pages import only what they need. - -**Benefits** -- **Easier Maintenance**: Fix a bug in one button component, and it updates everywhere. -- **Better Team Collaboration**: Developers can own specific components or pages without stepping on each other’s code. +**Reusability and Maintainability** +- Components are **small and focused**: one responsibility each (e.g., a Button that accepts props for label and click handler). +- **Props** are well-typed with TypeScript interfaces for clarity. +- **Documentation** is encouraged: add brief comments or Storybook stories (if integrated) to show usage examples. --- ## 5. State Management -**Current Approach** -- **Local State**: React `useState` and `useEffect` for form values, loading flags, and error messages. -- **Server State**: Fetch data (e.g., dashboard JSON) directly in page components or using React Server Components. - -**Sharing State** -- **React Context**: A simple auth context (`AuthContext`) holds the user’s session info, login/logout methods, and makes it available to any component. - - Located in `/context/AuthContext.tsx`. +**Approach** +- **Local state** with `useState` for simple UI interactions. +- **Global state** (e.g., authenticated user info) via React **Context API**: + - Create an `AuthContext` to hold user data and helper methods (`signIn`, `signOut`). + - Wrap the app in an `AuthProvider` in `app/layout.tsx` so all pages can access it. -**Future Growth** -- If complexity grows (deeply nested data, multiple user roles), consider: - - **Redux Toolkit** or **Zustand** for centralized state. - - Query libraries like **React Query** or **SWR** for caching and re-fetch logic. +**Sharing State Across Components** +- Components subscribe to context values via `useContext(AuthContext)`. +- For data fetching (dashboard content), keep local state or introduce **React Query** (or SWR) in the next phase to handle caching, loading, and error states. --- ## 6. Routing and Navigation -**Routing Library** -- Built into **Next.js App Router**. Each folder under `/app` becomes a route automatically. -- Layouts (`layout.tsx`) and pages (`page.tsx`) are colocated for that route. - -**Protected Pages** -- The dashboard’s `layout.tsx` checks for a valid session (via cookie or context). If missing, it issues a server-side redirect to `/sign-in`. +**Routing** +- **File-based routing** in Next.js (`app/` folder): + - A file `app/dashboard/page.tsx` maps to `/dashboard`. + - API routes in `app/api/auth/route.ts` map to `/api/auth`. +- **Nested layouts**: + - `app/layout.tsx` – Global wrapper with header and footer. + - `app/dashboard/layout.tsx` – Dashboard-specific wrapper with sidebar. -**Navigation Structure** -- **Header**: Present in global layout with the app logo and conditional Sign In/Sign Out links. -- **Sidebar**: Included in `dashboard/layout.tsx` with links to dashboard sections (expandable in future). +**Navigation** +- Use Next.js’s `` component for client-side transitions without full page reloads. +- Protect routes by checking `AuthContext` in `middleware.ts` or inside `layout.tsx`. If the user isn’t signed in, redirect to `/sign-in`. --- ## 7. Performance Optimization -1. **Code Splitting**: Next.js automatically breaks code by route. Users only load JS needed for the current page. -2. **Lazy Loading**: For large components (charts, maps), wrap with `next/dynamic` to load them only when needed. -3. **Image Optimization**: Use Next.js `` component to serve responsive, compressed images. -4. **Caching**: - - Static assets (CSS, fonts) use long cache headers. - - API responses can be cached or ISR (Incremental Static Regeneration) applied. -5. **Minification & Compression**: Next.js production builds automatically minify JS and CSS, and enable Brotli/Gzip on the CDN. +**Built-in Next.js features** +- **SSR/SSG**: Pages are pre-rendered on the server or at build time for faster first paint. +- **Automatic code splitting**: Only the JavaScript needed for the current page is loaded. +- **Image optimization**: Use Next.js `` component to serve appropriately sized images. -These steps ensure fast page loads and smooth interactions. +**Additional Strategies** +- **Dynamic imports** (`next/dynamic`) for heavy components or charts in the dashboard. +- **Lazy-load non-critical assets** (e.g., third-party widgets) with `React.lazy` and `Suspense`. +- **Minify and compress** CSS/JS during the build (handled automatically by Next.js). --- ## 8. Testing and Quality Assurance +**Linting & Formatting** +- **ESLint** with `eslint-config-next` to enforce best practices. +- **Prettier** for consistent code formatting on save or pre-commit. + **Unit Tests** -- **Jest** + **React Testing Library** for components and utility functions. -- Example: test that the Sign In form shows an error message when fields are empty. +- **Jest** + **React Testing Library** for component logic and rendering tests. +- Aim for testing: + - Form components (validation, error messages). + - Button clicks and UI state changes. **Integration Tests** -- Combine multiple components and hooks; test API calls with **msw** (Mock Service Worker). +- Mock API routes using **MSW** (Mock Service Worker) to simulate `/api/auth` responses. +- Verify that pages fetch data correctly and render loading/error states. -**End-to-End (E2E) Tests** -- **Cypress** or **Playwright** to simulate real user flows: signing up, logging in, and viewing the dashboard. +**End-to-End Tests** +- **Cypress** or **Playwright** for full user flows: + - Sign-up ➔ Sign-in ➔ Dashboard access ➔ Sign-out. + - Redirects when not authenticated. -**Linting & Formatting** -- **ESLint** enforces code style and catches common bugs. -- **Prettier** applies consistent formatting. -- **Git Hooks** (via Husky) run linting/tests before each commit. - -**Continuous Integration (CI)** -- **GitHub Actions** runs tests and lint on each pull request, preventing regressions. +**Continuous Integration** +- Run lint, unit, and integration tests on every pull request using **GitHub Actions**. +- Block merges until all checks pass. --- ## 9. Conclusion and Overall Frontend Summary -The `codeguide-starter` frontend is built on modern, well-established tools—Next.js, React, and TypeScript—and follows clear principles around usability, accessibility, and maintainability. Its file-based structure, component-driven approach, and CSS-variable theming keep things organized and consistent. - -Key takeaways: -- **Scalable Structure**: Add new features by creating new folders under `app/` without touching a central router. -- **Component Reuse**: Shared UI pieces live in one place, making updates quick and error-free. -- **Simple Styling**: Global and section-specific CSS, underpinned by CSS variables, ensures a unified look. -- **Smooth Performance**: Next.js automatic optimizations plus best practices like lazy loading and caching. -- **Quality Assurance**: A testing plan that covers unit, integration, and E2E scenarios, enforced by CI. - -With these guidelines, any developer coming into the project can understand how the pieces fit together, how to follow existing patterns, and how to keep the app fast, reliable, and easy to grow. \ No newline at end of file +This guideline lays out a clear path for building, styling, and maintaining the frontend of our starter kit. By leveraging Next.js, React, and TypeScript, we ensure a scalable, high-performance foundation. Our design principles (usability, accessibility, responsiveness) keep the user experience front and center. A flat, modern visual style with a defined color palette and Inter font gives the app a clean look. Component-based structure and Context API state handling make the code easy to extend. File-based routing and nested layouts simplify navigation and consistency. Performance is optimized through SSR/SSG, code splitting, and image handling. Finally, a robust testing strategy—unit, integration, and end-to-end—backs up code quality. Together, these guidelines align with our goal: a reliable, developer-friendly starter that anyone can pick up and confidently build upon. \ No newline at end of file diff --git a/documentation/project_requirements_document.md b/documentation/project_requirements_document.md index 526d663..49bffc9 100644 --- a/documentation/project_requirements_document.md +++ b/documentation/project_requirements_document.md @@ -1,117 +1,87 @@ -# Project Requirements Document: codeguide-starter - ---- +# Project Requirements Document (PRD) ## 1. Project Overview -The **codeguide-starter** project is a boilerplate web application that provides a ready-made foundation for any web project requiring secure user authentication and a post-login dashboard. It sets up the common building blocks—sign-up and sign-in pages, API routes to handle registration and login, and a simple dashboard interface driven by static data. By delivering this skeleton, it accelerates development time and ensures best practices are in place from day one. +**Paragraph 1:** +The `codeguide-starter-fullstack` is a ready-to-use starter kit for modern full-stack web applications. It lays out a solid project structure using a React-based framework (Next.js or Remix) with file-based routing, serverless API routes, and TypeScript. Out of the box, it includes user authentication (sign-up and sign-in), a protected user dashboard area, nested layouts for consistent UI, global styling, and a mock data source to populate components during development. -This starter kit is being built to solve the friction developers face when setting up repeated common tasks: credential handling, session management, page routing, and theming. Key objectives include: 1) delivering a fully working authentication flow (registration & login), 2) providing a gated dashboard area upon successful login, 3) establishing a clear, maintainable project structure using Next.js and TypeScript, and 4) demonstrating a clean theming approach with global and section-specific CSS. Success is measured by having an end-to-end login journey in under 200 lines of code and zero runtime type errors. - ---- +**Paragraph 2:** +This starter aims to help developers save setup time and avoid repetitive boilerplate. By providing pre-configured authentication endpoints, layout components, and styling conventions, teams can focus on business logic and features instead of project scaffolding. Success is measured by how quickly a developer can clone the repo, run `npm install && npm run dev`, and see a functional sign-up/sign-in flow plus dashboard UI in under five minutes. ## 2. In-Scope vs. Out-of-Scope -### In-Scope (Version 1) -- User registration (sign-up) form with validation -- User login (sign-in) form with validation -- Next.js API routes under `/api/auth/route.ts` handling: - - Credential validation - - Password hashing (e.g., bcrypt) - - Session creation or JWT issuance -- Protected dashboard pages under `/dashboard`: - - `layout.tsx` wrapping dashboard content - - `page.tsx` rendering static data from `data.json` -- Global application layout in `/app/layout.tsx` -- Basic styling via `globals.css` and `dashboard/theme.css` -- TypeScript strict mode enabled - -### Out-of-Scope (Later Phases) -- Integration with a real database (PostgreSQL, MongoDB, etc.) -- Advanced authentication flows (password reset, email verification, MFA) -- Role-based access control (RBAC) -- Multi-tenant or white-label theming -- Unit, integration, or end-to-end testing suites -- CI/CD pipeline and production deployment scripts - ---- +**In-Scope (Version 1):** +- File-based routing under `app/` directory for pages and API routes. +- Authentication endpoints in `app/api/auth/route.ts` supporting user sign-up and sign-in flows. +- Sign-in page (`app/sign-in/page.tsx`) and sign-up page (`app/sign-up/page.tsx`) UIs. +- Global layout (`app/layout.tsx`) and nested dashboard layout (`app/dashboard/layout.tsx`). +- Dashboard page (`app/dashboard/page.tsx`) displaying mock data from `data.json`. +- Global styles (`globals.css`) and theme overrides (`theme.css`). +- Use of TypeScript (`.tsx` and `.ts` files) and React components. +- Local mock data source for development, no external database required. + +**Out-of-Scope (Phase 2+):** +- Integration with a real database (SQL, NoSQL) or ORM. +- Third-party authentication providers (Google, GitHub, etc.). +- Production-grade session management (JWT refresh tokens, secure cookies). +- Advanced state management libraries (Redux, Zustand) beyond React Context. +- Internationalization (i18n) and multi-locale support. +- Comprehensive automated testing (unit, integration, end-to-end). +- Deployment scripts or CI/CD pipelines. ## 3. User Flow -A new visitor lands on the root URL and sees a welcome page with options to **Sign Up** or **Sign In**. If they choose Sign Up, they fill in their email, password, and hit “Create Account.” The form submits to `/api/auth/route.ts`, which hashes the password, creates a new user session or token, and redirects them to the dashboard. If any input is invalid, an inline error message explains the issue (e.g., “Password too short”). - -Once authenticated, the user is taken to the `/dashboard` route. Here they see a sidebar or header defined by `dashboard/layout.tsx`, and the main panel pulls in static data from `data.json`. They can log out (if that control is present), but otherwise their entire session is managed by server-side cookies or tokens. Returning users go directly to Sign In, submit credentials, and upon success they land back on `/dashboard`. Any unauthorized access to `/dashboard` redirects back to Sign In. +**Paragraph 1:** +A new visitor lands on the homepage and sees options to "Sign Up" or "Sign In" in the navigation bar (rendered by `app/layout.tsx`). If they choose "Sign Up," they are taken to `/sign-up`, where `app/sign-up/page.tsx` shows a form asking for email and password. On submission, the form calls the API endpoint at `/api/auth/route.ts`. If registration succeeds, the user is automatically redirected to the dashboard; if it fails, an inline error message appears. ---- +**Paragraph 2:** +Returning users click "Sign In" and arrive at `/sign-in`. After entering credentials, the form POSTs to `/api/auth/route.ts`. On successful authentication, the user session is stored (in memory or via cookie), and the user is redirected to `/dashboard`. The dashboard page (`app/dashboard/page.tsx`) loads mock data from `data.json`, and its layout (`app/dashboard/layout.tsx`) adds a sidebar or header specific to dashboard pages. Users can navigate back to other pages or log out, which clears the session and returns them to the sign-in page. ## 4. Core Features -- **Sign-Up Page (`/app/sign-up/page.tsx`)**: Form fields for email & password, client-side validation, POST to `/api/auth`. -- **Sign-In Page (`/app/sign-in/page.tsx`)**: Form fields for email & password, client-side validation, POST to `/api/auth`. -- **Authentication API (`/app/api/auth/route.ts`)**: Handles both registration and login based on HTTP method, integrates password hashing (bcrypt) and session or JWT logic. -- **Global Layout (`/app/layout.tsx` + `globals.css`)**: Shared header, footer, and CSS resets across all pages. -- **Dashboard Layout (`/app/dashboard/layout.tsx` + `dashboard/theme.css`)**: Sidebar or top nav for authenticated flows, section-specific styling. -- **Dashboard Page (`/app/dashboard/page.tsx`)**: Reads `data.json`, renders it as cards or tables. -- **Static Data Source (`/app/dashboard/data.json`)**: Example dataset to demo dynamic rendering. -- **TypeScript Configuration**: `tsconfig.json` with strict mode and path aliases (if any). - ---- +- **Authentication Module**: Sign-up and sign-in endpoints in `app/api/auth/route.ts`. Handles POST requests for new users and login, performs basic validation. +- **Protected Dashboard**: A `/dashboard` route that checks authentication; unauthenticated requests redirect to `/sign-in`. +- **Nested Layouts**: `app/layout.tsx` for global elements (header, footer), `app/dashboard/layout.tsx` for dashboard-specific navigation. +- **File-Based Routing**: URL paths map to folders and files under `app/` (e.g., `app/sign-up/page.tsx` → `/sign-up`). +- **Mock Data Source**: `data.json` provides sample data for the dashboard to render charts or lists without a real backend. +- **Styling**: `globals.css` for site-wide styles, `theme.css` for color palettes and overrides. Supports future integration with CSS modules or Tailwind. +- **TypeScript & React**: All components and routes use `.tsx` and `.ts` for type safety and clarity. ## 5. Tech Stack & Tools -- **Framework**: Next.js (App Router) for file-based routing, SSR/SSG, and API routes. -- **Language**: TypeScript for type safety. -- **UI Library**: React 18 for component-based UI. -- **Styling**: Plain CSS via `globals.css` (global reset) and `theme.css` (sectional styling). Can easily migrate to CSS Modules or Tailwind in the future. -- **Backend**: Node.js runtime provided by Next.js API routes. -- **Password Hashing**: bcrypt (npm package). -- **Session/JWT**: NextAuth.js or custom JWT logic (to be decided in implementation). -- **IDE & Dev Tools**: VS Code with ESLint, Prettier extensions. Optionally, Cursor.ai for AI-assisted coding. - ---- +**Frontend**: Next.js 13 (or Remix), React 18, TypeScript 4.x. +**Backend/API**: Node.js 16+, built-in API routes of Next.js/Remix. +**Styling**: Plain CSS (`globals.css`, `theme.css`), ready for Tailwind or CSS modules. +**Linting & Formatting**: ESLint with `eslint-config-next` (or equivalent), Prettier. +**Mock Data**: Static JSON file (`data.json`) within repo. +**IDE Plugins**: Optional ESLint and Prettier integrations. +**Version Control**: Git, with a standard `.gitignore` for Node and VSCode. ## 6. Non-Functional Requirements -- **Performance**: Initial page load under 200 ms on a standard broadband connection. API responses under 300 ms. -- **Security**: - - HTTPS only in production. - - Proper CORS, CSRF protection for API routes. - - Secure password storage (bcrypt with salt). - - No credentials or secrets checked into version control. -- **Scalability**: Structure must support adding database integration, caching layers, and advanced auth flows without rewiring core app. -- **Usability**: Forms should give real-time feedback on invalid input. Layout must be responsive (mobile > 320 px). -- **Maintainability**: Code must adhere to TypeScript strict mode. Linting & formatting enforced by ESLint/Prettier. - ---- +- **Performance**: First meaningful paint under 2 seconds on a 3G network. API calls should return within 200ms in development mode. +- **Security**: Sanitize user inputs. Hash passwords before storage (even in mock). Set HTTP-only and Secure flags if cookies are used. +- **Scalability**: Clear separation of UI, API, and data layers to allow future growth. +- **Usability**: Responsive design, semantic HTML, basic ARIA roles for forms and navigation. Works on desktop and mobile screens. +- **Maintainability**: Strict TS config (`noImplicitAny`, `strictNullChecks`), consistent code style enforced by ESLint/Prettier. +- **Compliance**: Code organized to allow easy GDPR opt-in/out for user data. ## 7. Constraints & Assumptions -- **No Database**: Dashboard uses only `data.json`; real database integration is deferred. -- **Node Version**: Requires Node.js >= 14. -- **Next.js Version**: Built on Next.js 13+ App Router. -- **Authentication**: Assumes availability of bcrypt or NextAuth.js at implementation time. -- **Hosting**: Targets serverless or Node.js-capable hosting (e.g., Vercel, Netlify). -- **Browser Support**: Modern evergreen browsers; no IE11 support required. - ---- +- **Framework Choice**: Assumes Next.js 13 conventions—React Server Components, file-based routing. If Remix is chosen, adapt folder names to `routes/` instead of `app/`. +- **Mock Data Only**: No real database connection; data lives in `data.json`. Future phases must swap this out with real DB logic. +- **Node Environment**: Requires Node.js version 16 or higher. +- **Session Storage**: Assumes in-memory or cookie session for development. Production session store (Redis, etc.) is not included. +- **Single Language**: English UI only. ## 8. Known Issues & Potential Pitfalls -- **Static Data Limitation**: `data.json` is only for demo. A real API or database will be needed to avoid stale data. - *Mitigation*: Define a clear interface for data fetching so swapping to a live endpoint is trivial. - -- **Global CSS Conflicts**: Using global styles can lead to unintended overrides. - *Mitigation*: Plan to migrate to CSS Modules or utility-first CSS in Phase 2. - -- **API Route Ambiguity**: Single `/api/auth/route.ts` handling both sign-up and sign-in could get complex. - *Mitigation*: Clearly branch on HTTP method (`POST /register` vs. `POST /login`) or split into separate files. - -- **Lack of Testing**: No test suite means regressions can slip in. - *Mitigation*: Build a minimal Jest + React Testing Library setup in an early iteration. - -- **Error Handling Gaps**: Client and server must handle edge cases (network failures, malformed input). - *Mitigation*: Define a standard error response schema and show user-friendly messages. +- **Ambiguous Framework**: The code structure could fit Next.js or Remix. Pick one and align config (e.g., `next.config.js` vs `remix.config.js`). +- **Authentication Security**: Current route handler may lack robust hashing, salting, and CSRF protection. Plan to integrate `bcrypt` and CSRF tokens in Phase 2. +- **Hydration Mismatch**: Using React Server Components and client components improperly can cause hydration errors. Clearly mark client functions with `'use client'`. +- **Mock Data Limitations**: Relying on a static JSON file means no real state updates. For CRUD patterns, swap in a fake REST server (json-server) or stubbed GraphQL mock. +- **Global CSS Collisions**: Without CSS modules or namespacing, global styles can clash. Recommend migrating to CSS modules or utility classes for larger apps. --- -This PRD should serve as the single source of truth for the AI model or any developer generating the next set of technical documents: Tech Stack Doc, Frontend Guidelines, Backend Structure, App Flow, File Structure, and IDE Rules. It contains all functional and non-functional requirements with no ambiguity, enabling seamless downstream development. +This PRD should serve as the single source of truth for all subsequent technical docs—tech stack details, frontend and backend guidelines, file structures, and app flow definitions. Every requirement here is precise to avoid any guesswork during implementation. \ No newline at end of file diff --git a/documentation/security_guideline_document.md b/documentation/security_guideline_document.md index 683c23f..d4bfde5 100644 --- a/documentation/security_guideline_document.md +++ b/documentation/security_guideline_document.md @@ -1,116 +1,195 @@ -# Security Guidelines for codeguide-starter +# Security Guidelines for `codeguide-starter-fullstack` -This document defines mandatory security principles and implementation best practices tailored to the **codeguide-starter** repository. It aligns with Security-by-Design, Least Privilege, Defense-in-Depth, and other core security tenets. All sections reference specific areas of the codebase (e.g., `/app/api/auth/route.ts`, CSS files, environment configuration) to ensure practical guidance. +This document provides actionable security best practices for the `codeguide-starter-fullstack` starter kit. By following these guidelines, developers will embed security by design, ensure robust defenses, and maintain a secure default posture throughout development and deployment. --- -## 1. Security by Design +## 1. Security Principles +Embed these principles at every stage of development: -• Embed security from day one: review threat models whenever adding new features (e.g., new API routes, data fetching). -• Apply “secure defaults” in Next.js configuration (`next.config.js`), enabling strict mode and disabling debug flags in production builds. -• Maintain a security checklist in your PR template to confirm that each change has been reviewed against this guideline. +- **Security by Design:** Integrate security from the first line of code. +- **Least Privilege:** Grant the minimum access needed (file system, environment, database). +- **Defense in Depth:** Layer controls (validation, authentication, headers). +- **Fail Securely:** On error, do not leak data or leave an operation half-completed. +- **Keep Security Simple:** Use well-maintained libraries and patterns. +- **Secure Defaults:** Ensure all new features are locked down until explicitly opened. --- ## 2. Authentication & Access Control -### 2.1 Password Storage -- Use **bcrypt** (or Argon2) with a per-user salt to hash passwords in `/app/api/auth/route.ts`. -- Enforce a strong password policy on both client and server: minimum 12 characters, mixed case, numbers, and symbols. +- **Strong Password Policies:** + • Enforce minimum length (≥ 8 characters), complexity rules, and blacklists. + • Use `bcrypt` or `Argon2` with unique salts for hashing. -### 2.2 Session Management -- Issue sessions via Secure, HttpOnly, SameSite=strict cookies. Do **not** expose tokens to JavaScript. -- Implement absolute and idle timeouts. For example, invalidate sessions after 30 minutes of inactivity. -- Protect against session fixation by regenerating session IDs after authentication. +- **Secure Session Management:** + • Store session tokens in HTTP-only, Secure, `SameSite=Strict` cookies. + • Implement idle and absolute timeouts (e.g., 15 min idle, 24 h absolute). + • Regenerate session ID on login to prevent fixation. -### 2.3 Brute-Force & Rate Limiting -- Apply rate limiting at the API layer (e.g., using `express-rate-limit` or Next.js middleware) on `/api/auth` to throttle repeated login attempts. -- Introduce exponential backoff or temporary lockout after N failed attempts. +- **JWT Handling (if adopted):** + • Use strong signing algorithms (e.g., HS256 or RS256). + • Validate `alg`, `iss`, `aud`, and enforce short expirations (`exp`). + • Do not store sensitive user data in the token payload. -### 2.4 Role-Based Access Control (Future) -- Define user roles in your database model (e.g., `role = 'user' | 'admin'`). -- Enforce server-side authorization checks in every protected route (e.g., in `dashboard/layout.tsx` loader functions). +- **Multi-Factor Authentication (MFA):** + • Provide optional TOTP-based or SMS-based 2FA for sensitive flows. + +- **Role-Based Access Control (RBAC):** + • Define roles (e.g., `user`, `admin`) and enforce server-side checks in every API route and page. + • Never rely on client-side flags for authorization. --- ## 3. Input Handling & Processing -### 3.1 Validate & Sanitize All Inputs -- On **client** (`sign-up/page.tsx`, `sign-in/page.tsx`): perform basic format checks (email regex, password length). -- On **server** (`/app/api/auth/route.ts`): re-validate inputs with a schema validator (e.g., `zod`, `Joi`). -- Reject or sanitize any unexpected fields to prevent injection attacks. +- **Server-Side Validation:** + • Validate every request body, query, and parameter using schemas (e.g., Zod or Joi). + • Mirror client-side validation with stricter server rules. + +- **Prevent Injection:** + • Use parameterized queries or an ORM for any DB access. + • Never interpolate user input into command lines or file paths. + +- **Cross-Site Scripting (XSS) Mitigation:** + • Escape or sanitize all user-supplied content before rendering. + • Avoid `dangerouslySetInnerHTML`; if needed, sanitize with a library (e.g., DOMPurify). -### 3.2 Prevent Injection -- If you introduce a database later, always use parameterized queries or an ORM (e.g., Prisma) rather than string concatenation. -- Avoid dynamic `eval()` or template rendering with unsanitized user input. +- **Redirect and URL Validation:** + • Allow-list redirect targets. + • Reject or default to a safe path for invalid `next` parameters. -### 3.3 Safe Redirects -- When redirecting after login or logout, validate the target against an allow-list to prevent open redirects. +- **Secure File Uploads (Future Scope):** + • Validate file size, type (by magic bytes), and reject disallowed extensions. + • Store uploads outside the webroot or on a dedicated object store with least privilege. --- ## 4. Data Protection & Privacy -### 4.1 Encryption & Secrets -- Enforce HTTPS/TLS 1.2+ for all front-end ↔ back-end communications. -- Never commit secrets—use environment variables and a secrets manager (e.g., AWS Secrets Manager, Vault). +- **Encryption in Transit:** + • Enforce TLS 1.2+ on all endpoints (e.g., via Vercel defaults or custom NGINX). + +- **Encryption at Rest:** + • For persistent storage (databases, object stores), enable encryption features. + +- **Secrets Management:** + • Store secrets (DB credentials, JWT keys) in environment variables or a vault (AWS Secrets Manager, HashiCorp Vault). + • Avoid committing `.env` files to source control. + +- **Information Leakage:** + • In production, disable detailed stack traces. + • Return generic error messages (e.g., "An unexpected error occurred."). -### 4.2 Sensitive Data Handling -- Do ​not​ log raw passwords, tokens, or PII in server logs. Mask or redact any user identifiers. -- If storing PII in `data.json` or a future database, classify it and apply data retention policies. +- **Protect PII:** + • Mask or truncate sensitive fields in logs (emails, user IDs). + • Implement a data retention policy that aligns with GDPR/CCPA. --- ## 5. API & Service Security -### 5.1 HTTPS Enforcement -- In production, redirect all HTTP traffic to HTTPS (e.g., via Vercel’s redirect rules or custom middleware). +- **HTTPS Enforcement:** + • Redirect HTTP to HTTPS at the edge. -### 5.2 CORS -- Configure `next.config.js` or API middleware to allow **only** your front-end origin (e.g., `https://your-domain.com`). +- **Rate Limiting & Throttling:** + • Apply per-IP or per-user rate limits on authentication and write endpoints. -### 5.3 API Versioning & Minimal Exposure -- Version your API routes (e.g., `/api/v1/auth`) to handle future changes without breaking clients. -- Return only necessary fields in JSON responses; avoid leaking internal server paths or stack traces. +- **CORS Configuration:** + • Restrict `Access-Control-Allow-Origin` to trusted domains. + • Enable `Access-Control-Allow-Credentials` only when needed. + +- **Authentication & Authorization Checks:** + • Protect every API route under `app/api/` with middleware that verifies the session or token. + +- **Minimal Response Surface:** + • Only return necessary fields in JSON replies. + • Avoid verbosity that could leak internal implementation details. + +- **Proper HTTP Methods:** + • GET for read, POST for create, PUT/PATCH for update, DELETE for removal. + +- **API Versioning:** + • Use route prefixes like `/api/v1/auth` to allow safe iteration. --- ## 6. Web Application Security Hygiene -### 6.1 CSRF Protection -- Use anti-CSRF tokens for any state-changing API calls. Integrate Next.js CSRF middleware or implement synchronizer tokens stored in cookies. +- **Cross-Site Request Forgery (CSRF):** + • Implement CSRF tokens on state-changing requests (e.g., using `next-csrf` or `csurf`). + +- **Security Headers:** + • Content-Security-Policy: limit sources of scripts, styles, images. + • Strict-Transport-Security: `max-age=63072000; includeSubDomains; preload`. + • X-Content-Type-Options: `nosniff`. + • X-Frame-Options: `DENY`. + • Referrer-Policy: `no-referrer-when-downgrade` or stricter. + +- **Secure Cookies:** + • Set `HttpOnly`, `Secure`, `SameSite=Strict` on all session cookies. -### 6.2 Security Headers -- In `next.config.js` (or a custom server), add these headers: - - `Strict-Transport-Security: max-age=63072000; includeSubDomains; preload` - - `X-Content-Type-Options: nosniff` - - `X-Frame-Options: DENY` - - `Referrer-Policy: no-referrer-when-downgrade` - - `Content-Security-Policy`: restrict script/style/src to self and trusted CDNs. +- **Clickjacking Protection:** + • Enforce `X-Frame-Options: DENY` or CSP `frame-ancestors 'none'`. -### 6.3 Secure Cookies -- Set `Secure`, `HttpOnly`, `SameSite=Strict` on all cookies. Avoid storing sensitive data in `localStorage`. +- **Avoid Client-Side Storage of Secrets:** + • Never store tokens or passwords in `localStorage` or `sessionStorage`. -### 6.4 Prevent XSS -- Escape or encode all user-supplied data in React templates. Avoid `dangerouslySetInnerHTML` unless content is sanitized. +- **Subresource Integrity (SRI):** + • When including third-party scripts/styles, add integrity hashes. --- ## 7. Infrastructure & Configuration Management -- Harden your hosting environment (e.g., Vercel/Netlify) by disabling unnecessary endpoints (GraphQL/GraphiQL playgrounds in production). -- Rotate secrets and API keys regularly via your secrets manager. -- Maintain minimal privileges: e.g., database accounts should only have read/write on required tables. -- Keep Node.js, Next.js, and all system packages up to date. +- **Server Hardening:** + • Disable unused services and ports. + • Regularly apply OS and package updates. +- **Environment Segmentation:** + • Separate development, staging, and production configurations. + • Use distinct credentials and secrets per environment. + +- **TLS Configuration:** + • Disable weak ciphers and protocols (SSLv3, TLS 1.0/1.1). + • Test with tools like SSL Labs for compliance. + +- **File Permissions:** + • Restrict filesystem access—only necessary users should read/write code and logs. + +- **Disable Debug in Production:** + • Ensure `next.config.js` sets `reactStrictMode=false` only in development. + --- ## 8. Dependency Management -- Commit and maintain `package-lock.json` to guarantee reproducible builds. -- Integrate a vulnerability scanner (e.g., GitHub Dependabot, Snyk) to monitor and alert on CVEs in dependencies. -- Trim unused packages; each added library increases the attack surface. +- **Lockfiles:** + • Commit `package-lock.json` or `yarn.lock` for reproducible builds. + +- **Vulnerability Scanning:** + • Integrate SCA tools (e.g., GitHub Dependabot, Snyk) into CI. + +- **Update Regularly:** + • Schedule periodic dependency reviews and upgrades. + +- **Limit Footprint:** + • Only install essential packages to reduce the attack surface. + +--- + +## 9. Continuous Security Practices + +- **Automated CI/CD Checks:** + • Linting, type-checking, and vulnerability scanning on every pull request. + +- **Penetration Testing & Audits:** + • Conduct periodic security reviews or engage third-party auditors. + +- **Incident Response Plan:** + • Define steps for breach detection, containment, and notification. --- -Adherence to these guidelines will ensure that **codeguide-starter** remains secure, maintainable, and resilient as it evolves. Regularly review and update this document to reflect new threats and best practices. \ No newline at end of file +## Conclusion +By adopting these guidelines, the `codeguide-starter-fullstack` repository will maintain strong security hygiene, protect user data, and ensure a resilient foundation for future features. Always treat security as a continuous process and evolve controls as the application grows. diff --git a/documentation/tech_stack_document.md b/documentation/tech_stack_document.md index 6db3f9b..8d0fcdb 100644 --- a/documentation/tech_stack_document.md +++ b/documentation/tech_stack_document.md @@ -1,90 +1,120 @@ # Tech Stack Document -This document explains the key technologies chosen for the **codeguide-starter** project. It’s written in everyday language so anyone—technical or not—can understand why each tool was picked and how it supports the application. +This document explains the technology choices for the `codeguide-starter-fullstack` project in everyday language. Each section describes why we picked certain tools and how they work together to build a solid foundation for your web application. ## 1. Frontend Technologies -The frontend is everything the user sees and interacts with. For this project, we’ve used: -- **Next.js (App Router)** - - A React framework that makes page routing, server-side rendering, and API routes very simple. - - Enhances user experience by pre-rendering pages on the server or at build time, leading to faster initial load. +We use modern tools to build a fast, interactive user interface that’s easy to maintain: + +- **Next.js (React Framework)** + - Provides file-based routing: your folders and files under `app/` automatically become pages and API endpoints. + - Supports server-side rendering (SSR) and static site generation (SSG) out of the box, improving load times and SEO. - **React 18** - - The underlying library for building user interfaces with reusable components. - - Provides a smooth, interactive experience thanks to its virtual DOM and modern hooks. + - Lets us build UI as reusable components that update smoothly when data changes. - **TypeScript** - - A superset of JavaScript that adds types (labels for data). - - Helps catch errors early during development and makes the code easier to maintain. + - Adds type safety (checks for mistakes before you even run the code), making the codebase more reliable and easier to understand. - **CSS (globals.css & theme.css)** - - **globals.css** applies base styles (fonts, colors, resets) across the entire app. - - **dashboard/theme.css** defines the look and feel specific to the dashboard area. - - This separation keeps styles organized and avoids accidental style conflicts. + - `globals.css` sets site-wide styles (colors, fonts, spacing). + - `theme.css` defines color palettes and design tokens so you can change the look in one place. +- **ESLint & Prettier** + - Enforce consistent code style and catch potential errors early in development. + +How these choices enhance the user experience: -By combining these tools, we have a clear structure (Next.js folders for pages and layouts), safer code (TypeScript), and flexible styling with vanilla CSS. +- Fast initial load through SSR/SSG in Next.js. +- Component-based React keeps UI consistent and makes changes predictable. +- TypeScript helps avoid common mistakes, so features work as expected. +- Centralized styling files ensure a uniform look and make theme updates simple. ## 2. Backend Technologies -The backend handles data, user accounts, and the logic behind the scenes. Our choices here are: -- **Next.js API Routes** - - Allows us to write server-side code (`route.ts` files) alongside our frontend in the same project. - - Runs on Node.js, so we can handle requests like sign-up, sign-in, and data fetching in one place. -- **Node.js Runtime** - - The JavaScript environment on the server that executes our API routes. -- **bcrypt** (npm package) - - A library for hashing passwords securely before storing them. - - Ensures that even if someone got access to our data, raw passwords aren’t visible. -- **(Optional) NextAuth.js or JWT** - - While this starter kit shows a custom authentication flow, it can easily integrate services like NextAuth.js for email-based login or JWT (JSON Web Tokens) for stateless sessions. +Our backend is lightweight and lives in the same project, handling data and authentication: + +- **Next.js API Routes (`app/api/auth/route.ts`)** + - Let you create server-side endpoints (sign-up, sign-in) without a separate server. + - Keep frontend and backend logic together for faster iteration. +- **Node.js (v16+)** + - Runs JavaScript on the server side. It’s fast, widely supported, and works seamlessly with Next.js. +- **Local JSON Data (`data.json`)** + - Acts as a mock database during development. + - Lets you prototype UI and data views quickly without setting up a real database. -These components work together to receive user credentials, verify or store them securely, manage sessions or tokens, and deliver protected data back to the frontend. +How these pieces work together: + +1. A user submits credentials on the sign-in/sign-up page. +2. The form sends a request to an API route in the same codebase. +3. The API route processes the data (e.g., validate email, hash password). +4. On success, the route responds, and Next.js redirects the user to the dashboard. +5. The dashboard page reads from `data.json` to display sample content. ## 3. Infrastructure and Deployment -Infrastructure covers where and how we host the app, as well as how changes get delivered: -- **Git & GitHub** - - Version control system (Git) and remote hosting (GitHub) keep track of all code changes and allow team collaboration. -- **Vercel (or Netlify)** - - A popular hosting service optimized for Next.js, with one-click deployments and global content delivery. - - Automatically rebuilds and deploys the site whenever code is pushed to the main branch. -- **GitHub Actions (CI/CD)** - - Automates tasks like linting (ESLint), formatting (Prettier), and running any tests you add. - - Ensures that only clean, tested code goes live. +To keep deployments simple, reliable, and scalable, we recommend: + +- **Version Control: Git & GitHub** + - Store and track all code changes in Git. + - Collaborate easily through pull requests on GitHub. +- **Hosting Platform: Vercel** + - First-class support for Next.js applications. + - Automatic deployments when you push to the main branch. + - Free SSL certificates and global CDN for fast page loads. +- **CI/CD Pipeline: GitHub Actions** + - Automatically run linting and tests on every pull request. + - Deploy to Vercel (or another cloud) after checks pass. + +How these decisions help: -Together, these tools provide a reliable, scalable setup where every code change is tested and deployed quickly, with minimal manual work. +- **Reliability:** Automated checks catch errors before they reach production. +- **Scalability:** Vercel’s CDN scales your app globally without extra setup. +- **Ease of Deployment:** Push to GitHub, and your app goes live—no manual steps needed. ## 4. Third-Party Integrations -While this starter kit is minimal by design, it already includes or can easily add: -- **bcrypt** - - For secure password hashing (included as an npm dependency). -- **NextAuth.js** (optional) - - A full-featured authentication library supporting email/password, OAuth, and more. -- **Sentry or LogRocket** (optional) - - For real-time error tracking and performance monitoring in production. +In this starter kit, we keep integrations minimal to stay focused on core features: -These integrations help extend the app’s capabilities without building every feature from scratch. +- **None by default** + - No external APIs or services are tied in at this stage. + +Future integrations might include: + +- **Authentication Providers** (Google, GitHub) + - Let users sign in with existing accounts. +- **Analytics Tools** (Google Analytics, Plausible) + - Track user behavior and measure performance. +- **Payment Processors** (Stripe) + - Handle subscriptions or one-time payments. + +Adding these later is straightforward once the foundation is in place. ## 5. Security and Performance Considerations -We’ve baked in several measures to keep users safe and the app running smoothly: -Security: -- Passwords are never stored in plain text—bcrypt hashes them with a random salt. -- API routes can implement CSRF protection and input validation to block malicious requests. -- Session tokens or cookies are marked secure and HttpOnly to prevent theft via JavaScript. +We’ve built in basic safeguards and performance steps to keep users safe and happy: -Performance: -- Server-side rendering (SSR) and static site generation (SSG) in Next.js deliver pages faster. -- Code splitting and lazy-loaded components ensure users only download what they need. -- Global CSS and theme files are small and cached by the browser for quick repeat visits. +- **Security Measures:** + - **Password Handling:** Hash passwords (e.g., via `bcrypt`) before storing them (even in a mock scenario). + - **Secure Cookies:** Store sessions in HTTP-only cookies to prevent JavaScript access. + - **Input Validation:** Check user input on both client and server to avoid bad data. + - **Redirect Protection:** Unauthenticated users get sent back to the sign-in page if they try to access `/dashboard`. +- **Performance Optimizations:** + - **Server-Side Rendering:** Next.js pre-renders pages for faster first load. + - **Static Assets on CDN:** All CSS and images served from a content delivery network. + - **Code Splitting:** Only load the JavaScript needed for each page, keeping initial downloads small. -These strategies work together to give users a fast, secure experience every time. +These practices ensure a smooth, secure experience for end users. ## 6. Conclusion and Overall Tech Stack Summary -In building **codeguide-starter**, we chose technologies that: -- Align with modern web standards (Next.js, React, TypeScript). -- Provide a clear, file-based project structure for rapid onboarding. -- Offer built-in support for server-side rendering, API routes, and static assets. -- Emphasize security through password hashing, session management, and safe defaults. -- Enable easy scaling and future enhancements via modular code and optional integrations. +To recap, here’s why each major technology was chosen and how it aligns with our goals: + +- **Next.js + React + TypeScript**: Fast, SEO-friendly pages with type safety and component reusability. +- **API Routes & Node.js**: Simple, serverless backend logic lives in the same codebase for quick iteration. +- **CSS Styling**: Clear separation of global styles and theme variables makes design changes easy. +- **Git + GitHub + Vercel + GitHub Actions**: A streamlined workflow from code changes to production deployment. + +This combination delivers: + +- A **robust starter** that handles authentication, routing, and layouts right away. +- A **friendly developer experience**: clone the repo, install dependencies, and see a working app in minutes. +- A **scalable platform**: ready to add real databases, third-party services, and advanced features as your project grows. -This stack strikes a balance between simplicity for newcomers and flexibility for experienced teams. It accelerates development of a secure authentication flow and a polished dashboard, while leaving room to plug in databases, test suites, and advanced features as the project grows. \ No newline at end of file +With this tech stack, teams can focus on building features instead of wrestling with configuration. You have everything you need to start a modern full-stack web application with confidence. \ No newline at end of file diff --git a/drizzle/0001_invoice_schema.sql b/drizzle/0001_invoice_schema.sql new file mode 100644 index 0000000..faff9f5 --- /dev/null +++ b/drizzle/0001_invoice_schema.sql @@ -0,0 +1,29 @@ +CREATE TABLE "invoice" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "invoice_number" text NOT NULL, + "date" timestamp NOT NULL, + "vendor" text NOT NULL, + "total_amount" numeric(12,2) NOT NULL, + "created_at" timestamp NOT NULL DEFAULT NOW(), + "updated_at" timestamp NOT NULL DEFAULT NOW(), + CONSTRAINT "invoice_invoice_number_unique" UNIQUE("invoice_number") +); +--> statement-breakpoint +CREATE TABLE "invoice_item" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "invoice_id" uuid NOT NULL, + "description" text NOT NULL, + "quantity" numeric(10,3) NOT NULL, + "unit_price" numeric(12,2) NOT NULL, + "line_total" numeric(12,2) NOT NULL, + "created_at" timestamp NOT NULL DEFAULT NOW(), + "updated_at" timestamp NOT NULL DEFAULT NOW() +); +--> statement-breakpoint +ALTER TABLE "invoice_item" ADD CONSTRAINT "invoice_item_invoice_id_invoice_id_fk" FOREIGN KEY ("invoice_id") REFERENCES "public"."invoice"("id") ON DELETE cascade ON UPDATE no action; +--> statement-breakpoint +CREATE INDEX "idx_invoice_date" ON "invoice" USING btree ("date"); +--> statement-breakpoint +CREATE INDEX "idx_invoice_vendor" ON "invoice" USING btree ("vendor"); +--> statement-breakpoint +CREATE INDEX "idx_invoice_item_invoice_id" ON "invoice_item" USING btree ("invoice_id"); \ No newline at end of file diff --git a/lib/services/ocrService.ts b/lib/services/ocrService.ts new file mode 100644 index 0000000..a14e774 --- /dev/null +++ b/lib/services/ocrService.ts @@ -0,0 +1,148 @@ +import { createWorker, Worker } from 'tesseract.js'; + +export interface InvoiceData { + invoiceNumber: string | null; + date: Date | null; + vendor: string | null; + totalAmount: number | null; + lineItems: InvoiceLineItem[]; +} + +export interface InvoiceLineItem { + description: string; + quantity: number; + unitPrice: number; + lineTotal: number; +} + +let worker: Worker | null = null; + +async function initializeWorker() { + if (!worker) { + worker = await createWorker('eng', 1, { + logger: m => console.log(m) + }); + await worker.setParameters({ + tessedit_pageseg_mode: '6', // Uniform block of text + tessedit_char_whitelist: '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz .,$/()-:', + }); + } + return worker; +} + +export async function extractText(imageBuffer: Buffer): Promise { + try { + const workerInstance = await initializeWorker(); + const { data: { text } } = await workerInstance.recognize(imageBuffer); + return text.trim(); + } catch (error) { + console.error('OCR extraction failed:', error); + throw new Error('Failed to extract text from image'); + } +} + +export function parseInvoiceData(text: string): InvoiceData { + const result: InvoiceData = { + invoiceNumber: null, + date: null, + vendor: null, + totalAmount: null, + lineItems: [] + }; + + try { + // Extract invoice number - look for patterns like "Invoice #12345", "INV-12345", etc. + const invoiceNumberRegex = /(?:invoice\s*#?|inv[-#]?)\s*:?\s*([A-Z0-9-]+)/i; + const invoiceNumberMatch = text.match(invoiceNumberRegex); + if (invoiceNumberMatch) { + result.invoiceNumber = invoiceNumberMatch[1].trim(); + } + + // Extract date - look for various date formats + const dateRegex = /(?:date|invoice\s+date|date\s+issued)\s*:?\s*(\d{1,2}[\/\-\.]\d{1,2}[\/\-\.]\d{2,4}|\d{4}[\/\-\.]\d{1,2}[\/\-\.]\d{1,2}|\w+\s+\d{1,2},?\s+\d{4})/i; + const dateMatch = text.match(dateRegex); + if (dateMatch) { + const dateStr = dateMatch[1].trim(); + const parsedDate = new Date(dateStr); + if (!isNaN(parsedDate.getTime())) { + result.date = parsedDate; + } + } + + // Extract vendor/company name - usually at the top of the invoice + const vendorRegex = /^([A-Z][A-Z\s&.,'-]+(?:LLC|INC|CORP|LTD|CO|COMPANY)?)/im; + const vendorMatch = text.match(vendorRegex); + if (vendorMatch) { + result.vendor = vendorMatch[1].trim(); + } else { + // Fallback - look for "from" or "bill from" patterns + const billFromRegex = /(?:bill\s+from|from)\s*:?\s*([A-Z][A-Z\s&.,'-]+)/i; + const billFromMatch = text.match(billFromRegex); + if (billFromMatch) { + result.vendor = billFromMatch[1].trim(); + } + } + + // Extract total amount - look for patterns like "Total: $123.45", "Amount Due: $123.45" + const totalRegex = /(?:total|amount\s+due|grand\s+total|balance\s+due)\s*:?\s*\$?(\d+(?:[,\.]\d{3})*\.?\d{0,2})/i; + const totalMatch = text.match(totalRegex); + if (totalMatch) { + const amountStr = totalMatch[1].replace(/,/g, ''); + result.totalAmount = parseFloat(amountStr); + } + + // Extract line items - look for tabular data with description, quantity, price patterns + const lines = text.split('\n'); + const lineItems: InvoiceLineItem[] = []; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i].trim(); + + // Pattern for line items: description followed by quantity, unit price, and total + // Examples: "Product Name 2 $10.00 $20.00" or "Service Description 1.5 $50.00 $75.00" + const lineItemRegex = /^(.+?)\s+(\d*\.?\d+)\s+\$?(\d+(?:\.\d{2})?)\s+\$?(\d+(?:\.\d{2})?)$/; + const match = line.match(lineItemRegex); + + if (match) { + const description = match[1].trim(); + const quantity = parseFloat(match[2]); + const unitPrice = parseFloat(match[3]); + const lineTotal = parseFloat(match[4]); + + // Validate that the math checks out (with some tolerance for rounding) + if (Math.abs(quantity * unitPrice - lineTotal) < 0.01) { + lineItems.push({ + description, + quantity, + unitPrice, + lineTotal + }); + } + } + } + + result.lineItems = lineItems; + + // Validate that we have at least some essential information + if (!result.invoiceNumber && !result.vendor && !result.totalAmount) { + throw new Error('Could not extract essential invoice information'); + } + + return result; + } catch (error) { + console.error('Invoice parsing failed:', error); + throw new Error('Failed to parse invoice data from extracted text'); + } +} + +export async function processInvoiceImage(imageBuffer: Buffer): Promise { + const text = await extractText(imageBuffer); + return parseInvoiceData(text); +} + +export async function cleanup() { + if (worker) { + await worker.terminate(); + worker = null; + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 4de9436..8d9155a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,6 +41,7 @@ "@radix-ui/react-tooltip": "^1.2.8", "@tabler/icons-react": "^3.34.1", "@tanstack/react-table": "^8.21.3", + "@types/multer": "^2.0.0", "better-auth": "^1.3.7", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -51,6 +52,7 @@ "embla-carousel-react": "^8.6.0", "input-otp": "^1.4.2", "lucide-react": "^0.541.0", + "multer": "^2.0.2", "next": "15.5.0", "next-themes": "^0.4.6", "pg": "^8.16.3", @@ -62,6 +64,7 @@ "recharts": "^2.15.4", "sonner": "^2.0.7", "tailwind-merge": "^3.3.1", + "tesseract.js": "^6.0.1", "vaul": "^1.1.2", "zod": "^4.1.3" }, @@ -3861,6 +3864,25 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/d3-array": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", @@ -3931,6 +3953,35 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/express": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.3.tgz", + "integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==", + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz", + "integrity": "sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "license": "MIT" + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -3945,11 +3996,25 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "license": "MIT" + }, + "node_modules/@types/multer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-2.0.0.tgz", + "integrity": "sha512-C3Z9v9Evij2yST3RSBktxP9STm6OdMc5uR1xF1SGr98uv8dUlAL2hqwrZ3GVB3uyMyiegnscEK6PGtYvNrjTjw==", + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/node": { "version": "20.19.11", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.11.tgz", "integrity": "sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow==", - "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -3967,6 +4032,18 @@ "pg-types": "^2.2.0" } }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "license": "MIT" + }, "node_modules/@types/react": { "version": "19.1.11", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.11.tgz", @@ -3987,6 +4064,27 @@ "@types/react": "^19.0.0" } }, + "node_modules/@types/send": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", + "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", + "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.41.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.41.0.tgz", @@ -4600,6 +4698,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT" + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -4906,6 +5010,12 @@ "uncrypto": "^0.1.3" } }, + "node_modules/bmp-js": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz", + "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==", + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -4934,9 +5044,19 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, "license": "MIT" }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -5139,6 +5259,21 @@ "dev": true, "license": "MIT" }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -6813,6 +6948,12 @@ "node": ">= 0.4" } }, + "node_modules/idb-keyval": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.2.tgz", + "integrity": "sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg==", + "license": "Apache-2.0" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -6850,6 +6991,12 @@ "node": ">=0.8.19" } }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, "node_modules/input-otp": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.4.2.tgz", @@ -7246,6 +7393,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "license": "MIT" + }, "node_modules/is-weakmap": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", @@ -7774,6 +7927,15 @@ "node": ">= 0.4" } }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -7798,6 +7960,27 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -7815,7 +7998,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7867,6 +8049,36 @@ "dev": true, "license": "MIT" }, + "node_modules/multer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz", + "integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.6.0", + "concat-stream": "^2.0.0", + "mkdirp": "^0.5.6", + "object-assign": "^4.1.1", + "type-is": "^1.6.18", + "xtend": "^4.0.2" + }, + "engines": { + "node": ">= 10.16.0" + } + }, + "node_modules/multer/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -8013,6 +8225,26 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -8135,6 +8367,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/opencollective-postinstall": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz", + "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", + "license": "MIT", + "bin": { + "opencollective-postinstall": "index.js" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -8673,6 +8914,20 @@ "react-dom": ">=16.6.0" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/recharts": { "version": "2.15.4", "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz", @@ -8734,6 +8989,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT" + }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", @@ -8857,6 +9118,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/safe-push-apply": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", @@ -9188,6 +9469,23 @@ "node": ">= 0.4" } }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string.prototype.includes": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", @@ -9422,6 +9720,30 @@ "node": ">=18" } }, + "node_modules/tesseract.js": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tesseract.js/-/tesseract.js-6.0.1.tgz", + "integrity": "sha512-/sPvMvrCtgxnNRCjbTYbr7BRu0yfWDsMZQ2a/T5aN/L1t8wUQN6tTWv6p6FwzpoEBA0jrN2UD2SX4QQFRdoDbA==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "bmp-js": "^0.1.0", + "idb-keyval": "^6.2.0", + "is-url": "^1.2.4", + "node-fetch": "^2.6.9", + "opencollective-postinstall": "^2.0.3", + "regenerator-runtime": "^0.13.3", + "tesseract.js-core": "^6.0.0", + "wasm-feature-detect": "^1.2.11", + "zlibjs": "^0.3.1" + } + }, + "node_modules/tesseract.js-core": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tesseract.js-core/-/tesseract.js-core-6.0.0.tgz", + "integrity": "sha512-1Qncm/9oKM7xgrQXZXNB+NRh19qiXGhxlrR8EwFbK5SaUbPZnS5OMtP/ghtqfd23hsr1ZvZbZjeuAGcMxd/ooA==", + "license": "Apache-2.0" + }, "node_modules/tiny-invariant": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", @@ -9489,6 +9811,12 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -9564,6 +9892,19 @@ "node": ">= 0.8.0" } }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/typed-array-buffer": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", @@ -9642,6 +9983,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, "node_modules/typescript": { "version": "5.9.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", @@ -9685,7 +10032,6 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "devOptional": true, "license": "MIT" }, "node_modules/unrs-resolver": { @@ -9785,6 +10131,12 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, "node_modules/vaul": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vaul/-/vaul-1.1.2.tgz", @@ -9820,6 +10172,28 @@ "d3-timer": "^3.0.1" } }, + "node_modules/wasm-feature-detect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.8.0.tgz", + "integrity": "sha512-zksaLKM2fVlnB5jQQDqKXXwYHLQUVH9es+5TOOHwGOVJOCeRBCiPjwSg+3tN2AdTCzjgli4jijCH290kXb/zWQ==", + "license": "Apache-2.0" + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -9967,6 +10341,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zlibjs": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/zlibjs/-/zlibjs-0.3.1.tgz", + "integrity": "sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/zod": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.3.tgz", diff --git a/package.json b/package.json index e63ca9b..40ce95b 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "@radix-ui/react-tooltip": "^1.2.8", "@tabler/icons-react": "^3.34.1", "@tanstack/react-table": "^8.21.3", + "@types/multer": "^2.0.0", "better-auth": "^1.3.7", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -66,6 +67,7 @@ "embla-carousel-react": "^8.6.0", "input-otp": "^1.4.2", "lucide-react": "^0.541.0", + "multer": "^2.0.2", "next": "15.5.0", "next-themes": "^0.4.6", "pg": "^8.16.3", @@ -77,6 +79,7 @@ "recharts": "^2.15.4", "sonner": "^2.0.7", "tailwind-merge": "^3.3.1", + "tesseract.js": "^6.0.1", "vaul": "^1.1.2", "zod": "^4.1.3" }, @@ -95,4 +98,4 @@ "tw-animate-css": "^1.3.7", "typescript": "^5" } -} \ No newline at end of file +}