JB logo

Command Palette

Search for a command to run...

yOUTUBE
Blog
Next

Next.js Code Snippets - Frequently Used Patterns

A collection of battle-tested code snippets for Next.js applications including CORS setup, Prisma configuration, file uploads, data export, and PDF generation.

CORS Configuration in Next.js

Setting up CORS in a Next.js application using middleware to handle cross-origin requests properly.

File: middleware.ts

import { NextRequest, NextResponse } from "next/server";
 
const allowedOrigins = [
  "https://newsletter-hub-teal.vercel.app",
  "http://localhost:3000",
];
 
const corsOptions = {
  "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
  "Access-Control-Allow-Headers": "Content-Type, Authorization",
};
 
export function middleware(request: NextRequest) {
  // Check the origin from the request
  const origin = request.headers.get("origin") ?? "";
  const isAllowedOrigin = allowedOrigins.includes(origin);
 
  // Handle preflighted requests
  const isPreflight = request.method === "OPTIONS";
 
  if (isPreflight) {
    const preflightHeaders = {
      ...(isAllowedOrigin && { "Access-Control-Allow-Origin": origin }),
      ...corsOptions,
    };
    return NextResponse.json({}, { headers: preflightHeaders });
  }
 
  // Handle simple requests
  const response = NextResponse.next();
 
  if (isAllowedOrigin) {
    response.headers.set("Access-Control-Allow-Origin", origin);
  }
 
  Object.entries(corsOptions).forEach(([key, value]) => {
    response.headers.set(key, value);
  });
 
  return response;
}
 
export const config = {
  matcher: "/api/:path*",
};

Key Benefits:

  • Handles both preflight and simple CORS requests
  • Configurable allowed origins
  • Applies only to API routes via matcher

Global Prisma Instance

Creating a singleton Prisma client instance that works properly in development and production environments.

File: lib/db.ts

import { PrismaClient } from "@prisma/client";
import { withAccelerate } from "@prisma/extension-accelerate";
 
export const db = new PrismaClient().$extends(withAccelerate());
 
const globalForPrisma = global as unknown as { prisma: typeof db };
 
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = db;

Key Benefits:

  • Prevents multiple Prisma instances in development
  • Includes Prisma Accelerate extension for caching
  • Memory efficient and prevents connection pool exhaustion

UploadThing File Router Configuration

Complete UploadThing setup for handling different types of file uploads with proper MIME type configuration.

File: app/api/uploadthing/core.ts

import { createUploadthing, type FileRouter } from "uploadthing/next";
import { UploadThingError } from "uploadthing/server";
 
const f = createUploadthing();
 
// FileRouter for your app, can contain multiple FileRoutes
export const ourFileRouter = {
  // Define as many FileRoutes as you like, each with a unique routeSlug
  categoryImage: f({ image: { maxFileSize: "1MB" } }).onUploadComplete(
    async ({ metadata, file }) => {
      console.log("file url", file.url);
      return { uploadedBy: "JB" };
    }
  ),
  blogImage: f({ image: { maxFileSize: "1MB" } }).onUploadComplete(
    async ({ metadata, file }) => {
      console.log("file url", file.url);
      return { uploadedBy: "JB" };
    }
  ),
  fileUploads: f({
    image: { maxFileSize: "1MB", maxFileCount: 4 },
    pdf: { maxFileSize: "1MB", maxFileCount: 4 },
    "application/msword": { maxFileSize: "1MB", maxFileCount: 4 }, // .doc
    "application/vnd.openxmlformats-officedocument.wordprocessingml.document": {
      maxFileSize: "1MB",
      maxFileCount: 4,
    }, // .docx
    "application/vnd.ms-excel": { maxFileSize: "1MB", maxFileCount: 4 }, // .xls
    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {
      maxFileSize: "1MB",
      maxFileCount: 4,
    }, // .xlsx
    "application/vnd.ms-powerpoint": { maxFileSize: "1MB", maxFileCount: 4 }, // .ppt
    "application/vnd.openxmlformats-officedocument.presentationml.presentation":
      { maxFileSize: "1MB", maxFileCount: 4 }, // .pptx
    "text/plain": { maxFileSize: "1MB", maxFileCount: 4 }, // .txt
 
    // Archive types
    "application/gzip": { maxFileSize: "1MB", maxFileCount: 4 },
    "application/zip": { maxFileSize: "1MB", maxFileCount: 4 },
  }).onUploadComplete(async ({ metadata, file }) => {
    console.log("file url", file.url);
    return { uploadedBy: "JB" };
  }),
  mailAttachments: f({
    image: { maxFileSize: "1MB", maxFileCount: 4 },
    pdf: { maxFileSize: "1MB", maxFileCount: 4 },
    "application/msword": { maxFileSize: "1MB", maxFileCount: 4 }, // .doc
    "application/vnd.openxmlformats-officedocument.wordprocessingml.document": {
      maxFileSize: "1MB",
      maxFileCount: 4,
    }, // .docx
    "application/vnd.ms-excel": { maxFileSize: "1MB", maxFileCount: 4 }, // .xls
    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {
      maxFileSize: "1MB",
      maxFileCount: 4,
    }, // .xlsx
    "application/vnd.ms-powerpoint": { maxFileSize: "1MB", maxFileCount: 4 }, // .ppt
    "application/vnd.openxmlformats-officedocument.presentationml.presentation":
      { maxFileSize: "1MB", maxFileCount: 4 }, // .pptx
    "text/plain": { maxFileSize: "1MB", maxFileCount: 4 }, // .txt
 
    // Archive types
    "application/gzip": { maxFileSize: "1MB", maxFileCount: 4 },
    "application/zip": { maxFileSize: "1MB", maxFileCount: 4 },
  }).onUploadComplete(async ({ metadata, file }) => {
    console.log("file url", file.url);
    return { uploadedBy: "JB" };
  }),
} satisfies FileRouter;
 
export type OurFileRouter = typeof ourFileRouter;

Key Benefits:

  • Supports multiple file types including Office documents
  • Separate routes for different use cases
  • Proper MIME type configuration for all common file formats

Excel Export with XLSX

Export data to Excel format with proper formatting and error handling.

const handleExport = async (filteredOrders: SalesOrder[]) => {
  setIsExporting(true);
  try {
    const exportData = filteredOrders.map((order) => ({
      "Order Number": order.orderNumber,
      "Order Date": formatDate(order.orderDate),
      "Buyer Type": order.buyerType.replace("_", " "),
      "Buyer Name": order.buyerName,
      Target: order.targetTitle,
      "Items Count": order.orderItems.length,
      "Number Plates": order.orderItems
        .map((item) => item.numberPlate)
        .join(", "),
      "Total Amount": formatCurrency(order.totalAmount),
      "Amount Paid": formatCurrency(order.amountPaid),
      Outstanding: formatCurrency(order.outstandingBalance),
      "Payment Status": order.paymentStatus,
      "Order Status": order.status,
      Created: formatDate(order.createdAt),
    }));
 
    const worksheet = XLSX.utils.json_to_sheet(exportData);
    const workbook = XLSX.utils.book_new();
    XLSX.utils.book_append_sheet(workbook, worksheet, "Sales Orders");
 
    const fileName = `Sales_Orders_${activeTab}_${format(
      new Date(),
      "yyyy-MM-dd"
    )}.xlsx`;
    XLSX.writeFile(workbook, fileName);
 
    toast.success("Export successful", {
      description: `Sales orders exported to ${fileName}`,
    });
  } catch (error) {
    toast.error("Export failed", {
      description:
        error instanceof Error ? error.message : "Unknown error occurred",
    });
  } finally {
    setIsExporting(false);
  }
};

Key Benefits:

  • Transforms complex nested data into flat Excel format
  • Automatic file naming with timestamps
  • Proper error handling and user feedback
  • Loading states for better UX

PDF Generation and Download

Complete PDF generation setup using React-PDF with download and print functionality.

Single Invoice PDF Component

// Usage in component for single invoice
const { data: order, isLoading, error, refetch } = useSalesOrder(orderId);
 
const preparePDFData = (order: any) => {
  return {
    id: order.id,
    orderNumber: order.orderNumber,
    orderDate: order.orderDate,
    status: order.status,
    paymentStatus: order.paymentStatus,
    buyerName: order.buyerName,
    buyerType: order.buyerType,
    targetTitle: order.targetTitle,
    subtotal: order.subtotal,
    taxAmount: order.taxAmount,
    totalAmount: order.totalAmount,
    amountPaid: order.amountPaid,
    outstandingBalance: order.outstandingBalance,
    notes: order.notes,
    salesAgent: order.salesAgent,
    salesPerson: order.salesPerson,
    orderItems: order.orderItems.map((item: any) => ({
      id: item.id,
      productName: item.productName,
      numberPlate: item.numberPlate,
      modelName: item.modelName,
      unitPrice: item.unitPrice,
      quantity: item.quantity,
      lineTotal: item.lineTotal,
      isPaid: item.isPaid,
      paidAt: item.paidAt,
    })),
    payments: order.payments.map((payment: any) => ({
      id: payment.id,
      amount: payment.amount,
      paymentDate: payment.paymentDate,
      paymentMethod: payment.paymentMethod,
      referenceNumber: payment.referenceNumber,
      notes: payment.notes,
    })),
  };
};
 
// Usage in component
<SalesOrderPrintComponent orderData={preparePDFData(order)} />

Sales Report PDF , SalesReportComponent, Hhooks and Usage

"use client";
 
import React from "react";
import {
  Document,
  Page,
  Text,
  View,
  StyleSheet,
  pdf,
} from "@react-pdf/renderer";
import { format } from "date-fns";
import { Download, Printer } from "lucide-react";
import { Button } from "@/components/ui/button";
import { toast } from "sonner";
import PDFHeader from "./PDFHeader";
import { DateFilterOption, SalesOrderFilters } from "@/actions/sales-orders-v2";
 
// Types for the sales order data
interface SalesOrderData {
  id: string;
  orderNumber: string;
  orderDate: string | Date;
  status: string;
  paymentStatus: string;
  buyerName: string;
  buyerType: "SALES_PERSON" | "SALES_AGENT";
  targetTitle: string;
  totalAmount: number;
  amountPaid: number;
  outstandingBalance: number;
  orderItems: Array<{
    id: string;
    productName: string;
    numberPlate: string;
    modelName: string;
    unitPrice: number;
    quantity: number;
    lineTotal: number;
    isPaid: boolean;
  }>;
}
 
interface SalesReportData {
  orders: SalesOrderData[];
  period: {
    filter: DateFilterOption;
    dateRange?: {
      from: Date;
      to: Date;
    };
    label: string;
  };
  summary: {
    totalOrders: number;
    totalRevenue: number;
    totalPaid: number;
    totalOutstanding: number;
    paidOrders: number;
    outstandingOrders: number;
    averageOrderValue: number;
  };
}
 
// PDF Styles
const styles = StyleSheet.create({
  page: {
    flexDirection: "column",
    backgroundColor: "#FFFFFF",
    padding: 30,
    fontSize: 10,
    fontFamily: "Helvetica",
  },
  section: {
    marginBottom: 15,
  },
  sectionTitle: {
    fontSize: 12,
    fontWeight: "bold",
    marginBottom: 8,
    color: "#1f2937",
    borderBottomWidth: 1,
    borderBottomColor: "#e5e7eb",
    paddingBottom: 3,
  },
  periodInfo: {
    backgroundColor: "#f3f4f6",
    padding: 15,
    marginBottom: 20,
    borderRadius: 5,
  },
  periodTitle: {
    fontSize: 14,
    fontWeight: "bold",
    marginBottom: 5,
    color: "#1f2937",
  },
  periodText: {
    fontSize: 10,
    color: "#4b5563",
  },
  summaryGrid: {
    flexDirection: "row",
    flexWrap: "wrap",
    marginBottom: 20,
  },
  summaryCard: {
    width: "48%",
    marginBottom: 10,
    marginRight: "2%",
    padding: 10,
    backgroundColor: "#f9fafb",
    borderWidth: 1,
    borderColor: "#e5e7eb",
    borderRadius: 3,
  },
  summaryLabel: {
    fontSize: 9,
    color: "#6b7280",
    marginBottom: 3,
  },
  summaryValue: {
    fontSize: 14,
    fontWeight: "bold",
    color: "#1f2937",
  },
  table: {
    width: "100%",
    borderStyle: "solid",
    borderWidth: 1,
    borderColor: "#e5e7eb",
    marginBottom: 15,
  },
  tableRow: {
    flexDirection: "row",
    borderBottomWidth: 1,
    borderBottomColor: "#e5e7eb",
    minHeight: 25,
    alignItems: "center",
  },
  tableHeader: {
    backgroundColor: "#1f2937",
    color: "#FFFFFF",
    fontWeight: "bold",
  },
  tableCol: {
    padding: 5,
    borderRightWidth: 1,
    borderRightColor: "#e5e7eb",
    fontSize: 9,
  },
  orderCol: { width: "15%" },
  dateCol: { width: "12%" },
  buyerCol: { width: "20%" },
  itemsCol: { width: "8%", textAlign: "center" },
  amountCol: { width: "15%", textAlign: "right" },
  paidCol: { width: "15%", textAlign: "right" },
  outstandingCol: { width: "15%", textAlign: "right" },
  badge: {
    fontSize: 7,
    padding: 2,
    borderRadius: 2,
    textAlign: "center",
    color: "#FFFFFF",
  },
  paidBadge: {
    fontSize: 7,
    padding: 2,
    borderRadius: 2,
    textAlign: "center",
    backgroundColor: "#10b981",
    color: "#FFFFFF",
  },
  partialBadge: {
    fontSize: 7,
    padding: 2,
    borderRadius: 2,
    textAlign: "center",
    backgroundColor: "#f59e0b",
    color: "#FFFFFF",
  },
  pendingBadge: {
    fontSize: 7,
    padding: 2,
    borderRadius: 2,
    textAlign: "center",
    backgroundColor: "#ef4444",
    color: "#FFFFFF",
  },
  footer: {
    marginTop: "auto",
    paddingTop: 20,
    borderTopWidth: 1,
    borderTopColor: "#e5e7eb",
    fontSize: 8,
    color: "#6b7280",
    textAlign: "center",
  },
  pageNumber: {
    position: "absolute",
    fontSize: 8,
    bottom: 20,
    left: 0,
    right: 0,
    textAlign: "center",
    color: "#6b7280",
  },
});
 
// PDF Document Component
const SalesReportPDF: React.FC<{ reportData: SalesReportData }> = ({
  reportData,
}) => {
  const formatCurrency = (amount: number) => {
    return new Intl.NumberFormat("en-UG", {
      style: "currency",
      currency: "UGX",
      minimumFractionDigits: 0,
    }).format(amount);
  };
 
  const formatDate = (date: string | Date) => {
    const dateObj = typeof date === "string" ? new Date(date) : date;
    return format(dateObj, "MMM dd, yyyy");
  };
 
  const getPaymentStatusBadge = (status: string) => {
    switch (status) {
      case "PAID":
        return styles.paidBadge;
      case "PARTIAL":
        return styles.partialBadge;
      default:
        return styles.pendingBadge;
    }
  };
 
  const getPeriodDescription = () => {
    const { filter, dateRange, label } = reportData.period;
 
    if (filter === "custom" && dateRange) {
      return `Custom Period: ${formatDate(dateRange.from)} - ${formatDate(
        dateRange.to
      )}`;
    }
 
    return `Report Period: ${label}`;
  };
 
  return (
    <Document>
      <Page size="A4" style={styles.page}>
        {/* Reusable Header */}
        <PDFHeader title="SALES REPORT" />
 
        {/* Period Information */}
        <View style={styles.periodInfo}>
          <Text style={styles.periodTitle}>Sales Report Summary</Text>
          <Text style={styles.periodText}>{getPeriodDescription()}</Text>
          <Text style={styles.periodText}>
            Generated on {format(new Date(), "PPP 'at' p")}
          </Text>
        </View>
 
        {/* Summary Cards */}
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>Performance Summary</Text>
          <View style={styles.summaryGrid}>
            <View style={styles.summaryCard}>
              <Text style={styles.summaryLabel}>Total Orders</Text>
              <Text style={styles.summaryValue}>
                {reportData.summary.totalOrders}
              </Text>
            </View>
            <View style={styles.summaryCard}>
              <Text style={styles.summaryLabel}>Total Revenue</Text>
              <Text style={styles.summaryValue}>
                {formatCurrency(reportData.summary.totalRevenue)}
              </Text>
            </View>
            <View style={styles.summaryCard}>
              <Text style={styles.summaryLabel}>Amount Collected</Text>
              <Text style={styles.summaryValue}>
                {formatCurrency(reportData.summary.totalPaid)}
              </Text>
            </View>
            <View style={styles.summaryCard}>
              <Text style={styles.summaryLabel}>Outstanding Balance</Text>
              <Text style={styles.summaryValue}>
                {formatCurrency(reportData.summary.totalOutstanding)}
              </Text>
            </View>
            <View style={styles.summaryCard}>
              <Text style={styles.summaryLabel}>Paid Orders</Text>
              <Text style={styles.summaryValue}>
                {reportData.summary.paidOrders}
              </Text>
            </View>
            <View style={styles.summaryCard}>
              <Text style={styles.summaryLabel}>Outstanding Orders</Text>
              <Text style={styles.summaryValue}>
                {reportData.summary.outstandingOrders}
              </Text>
            </View>
          </View>
        </View>
 
        {/* Orders Table */}
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>
            Detailed Order List ({reportData.orders.length} orders)
          </Text>
          <View style={styles.table}>
            {/* Table Header */}
            <View style={[styles.tableRow, styles.tableHeader]}>
              <View style={[styles.tableCol, styles.orderCol]}>
                <Text>Order #</Text>
              </View>
              <View style={[styles.tableCol, styles.dateCol]}>
                <Text>Date</Text>
              </View>
              <View style={[styles.tableCol, styles.buyerCol]}>
                <Text>Buyer</Text>
              </View>
              <View style={[styles.tableCol, styles.itemsCol]}>
                <Text>Items</Text>
              </View>
              <View style={[styles.tableCol, styles.amountCol]}>
                <Text>Total</Text>
              </View>
              <View style={[styles.tableCol, styles.paidCol]}>
                <Text>Paid</Text>
              </View>
              <View style={[styles.tableCol, styles.outstandingCol]}>
                <Text>Outstanding</Text>
              </View>
            </View>
 
            {/* Table Rows */}
            {reportData.orders.slice(0, 25).map((order, index) => (
              <View key={order.id} style={styles.tableRow}>
                <View style={[styles.tableCol, styles.orderCol]}>
                  <Text style={{ fontWeight: "bold", fontSize: 8 }}>
                    {order.orderNumber}
                  </Text>
                  <Text style={getPaymentStatusBadge(order.paymentStatus)}>
                    {order.paymentStatus}
                  </Text>
                </View>
                <View style={[styles.tableCol, styles.dateCol]}>
                  <Text>{formatDate(order.orderDate)}</Text>
                </View>
                <View style={[styles.tableCol, styles.buyerCol]}>
                  <Text style={{ fontWeight: "bold", fontSize: 8 }}>
                    {order.buyerName}
                  </Text>
                  <Text style={{ fontSize: 7, color: "#6b7280" }}>
                    {order.buyerType.replace("_", " ")}
                  </Text>
                </View>
                <View style={[styles.tableCol, styles.itemsCol]}>
                  <Text>{order.orderItems.length}</Text>
                </View>
                <View style={[styles.tableCol, styles.amountCol]}>
                  <Text style={{ fontWeight: "bold" }}>
                    {formatCurrency(order.totalAmount)}
                  </Text>
                </View>
                <View style={[styles.tableCol, styles.paidCol]}>
                  <Text style={{ color: "#10b981" }}>
                    {formatCurrency(order.amountPaid)}
                  </Text>
                </View>
                <View style={[styles.tableCol, styles.outstandingCol]}>
                  <Text style={{ color: "#ef4444" }}>
                    {formatCurrency(order.outstandingBalance)}
                  </Text>
                </View>
              </View>
            ))}
          </View>
 
          {reportData.orders.length > 25 && (
            <Text
              style={{ fontSize: 8, fontStyle: "italic", textAlign: "center" }}
            >
              Showing first 25 orders of {reportData.orders.length} total orders
            </Text>
          )}
        </View>
 
        {/* Footer */}
        <View style={styles.footer}>
          <Text>This report contains confidential business information.</Text>
          <Text>For inquiries, contact us at info@yourcompany.com</Text>
        </View>
 
        {/* Page Number */}
        <Text
          style={styles.pageNumber}
          render={({ pageNumber, totalPages }) =>
            `Page ${pageNumber} of ${totalPages}`
          }
          fixed
        />
      </Page>
    </Document>
  );
};
 
// Main Component Props
interface SalesReportComponentProps {
  orders: SalesOrderData[];
  currentFilter: SalesOrderFilters;
}
 
// Hook for sales report functionality
export const useSalesReport = ({
  orders,
  currentFilter,
}: SalesReportComponentProps) => {
  // Calculate summary data
  const summary = React.useMemo(() => {
    const totalOrders = orders.length;
    const totalRevenue = orders.reduce(
      (sum, order) => sum + order.totalAmount,
      0
    );
    const totalPaid = orders.reduce((sum, order) => sum + order.amountPaid, 0);
    const totalOutstanding = orders.reduce(
      (sum, order) => sum + order.outstandingBalance,
      0
    );
    const paidOrders = orders.filter(
      (order) => order.paymentStatus === "PAID"
    ).length;
    const outstandingOrders = orders.filter(
      (order) =>
        order.paymentStatus === "PENDING" ||
        order.paymentStatus === "PARTIAL" ||
        order.paymentStatus === "OVERDUE"
    ).length;
    const averageOrderValue = totalOrders > 0 ? totalRevenue / totalOrders : 0;
 
    return {
      totalOrders,
      totalRevenue,
      totalPaid,
      totalOutstanding,
      paidOrders,
      outstandingOrders,
      averageOrderValue,
    };
  }, [orders]);
 
  // Get period label
  const getPeriodLabel = () => {
    const filter = currentFilter.dateFilter || "last7days";
    switch (filter) {
      case "today":
        return "Today";
      case "last7days":
        return "Last 7 Days";
      case "thisMonth":
        return "This Month";
      case "thisYear":
        return "This Year";
      case "custom":
        return "Custom Period";
      case "lifetime":
        return "All Time";
      default:
        return "Last 7 Days";
    }
  };
 
  const reportData: SalesReportData = {
    orders,
    period: {
      filter: currentFilter.dateFilter || "last7days",
      dateRange: currentFilter.dateRange,
      label: getPeriodLabel(),
    },
    summary,
  };
 
  const handlePrint = () => {
    const generatePDF = async () => {
      try {
        const blob = await pdf(
          <SalesReportPDF reportData={reportData} />
        ).toBlob();
        const url = URL.createObjectURL(blob);
        const printWindow = window.open(url);
 
        if (printWindow) {
          printWindow.onload = () => {
            printWindow.print();
          };
        } else {
          toast.error("Popup blocked. Please allow popups for this site.");
        }
      } catch (error) {
        console.error("Error generating PDF for print:", error);
        toast.error("Failed to generate PDF for printing");
      }
    };
 
    generatePDF();
    toast.success("Preparing sales report for printing...");
  };
 
  const handleDownload = async () => {
    try {
      const blob = await pdf(
        <SalesReportPDF reportData={reportData} />
      ).toBlob();
      const url = URL.createObjectURL(blob);
      const link = document.createElement("a");
      link.href = url;
      link.download = `Sales-Report-${getPeriodLabel().replace(
        /\s+/g,
        "-"
      )}-${format(new Date(), "yyyy-MM-dd")}.pdf`;
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);
      URL.revokeObjectURL(url);
      toast.success("Sales report downloaded successfully!");
    } catch (error) {
      console.error("Error downloading PDF:", error);
      toast.error("Failed to download sales report");
    }
  };
 
  return {
    handlePrint,
    handleDownload,
    printButton: (
      <Button variant="outline" onClick={handlePrint} className="gap-2">
        <Printer className="h-4 w-4" />
        Print Report
      </Button>
    ),
    downloadButton: (
      <Button variant="outline" onClick={handleDownload} className="gap-2">
        <Download className="h-4 w-4" />
        Download Report
      </Button>
    ),
  };
};
 
// Component version (if you need to render the buttons directly)
const SalesReportComponent: React.FC<SalesReportComponentProps> = ({
  orders,
  currentFilter,
}) => {
  const { printButton, downloadButton } = useSalesReport({
    orders,
    currentFilter,
  });
 
  return (
    <div className="flex gap-2">
      {printButton}
      {downloadButton}
    </div>
  );
};
 
export default SalesReportComponent;
 

Reusable PDF Header Component

File: components/pdf/PDFHeader.tsx

import React from "react";
import { View, Text, StyleSheet } from "@react-pdf/renderer";
 
interface PDFHeaderProps {
  title: string;
  companyName?: string;
  phone?: string;
  email?: string;
  address?: string;
}
 
const styles = StyleSheet.create({
  header: {
    flexDirection: "row",
    justifyContent: "space-between",
    alignItems: "center",
    backgroundColor: "#1f2937",
    color: "#FFFFFF",
    padding: 20,
    marginBottom: 20,
  },
  companyInfo: {
    flex: 1,
  },
  companyName: {
    fontSize: 18,
    fontWeight: "bold",
    marginBottom: 5,
  },
  companyDetails: {
    fontSize: 10,
  },
  documentTitle: {
    fontSize: 28,
    fontWeight: "bold",
  },
});
 
const PDFHeader: React.FC<PDFHeaderProps> = ({
  title,
  companyName = "Limibooks (U) Ltd",
  phone = "+256 772514057",
  email = "info@limibooks.com",
  address = "Kampala, Uganda",
}) => {
  return (
    <View style={styles.header}>
      <View style={styles.companyInfo}>
        <Text style={styles.companyName}>{companyName}</Text>
        <Text style={styles.companyDetails}>Phone: {phone}</Text>
        <Text style={styles.companyDetails}>Email: {email}</Text>
        <Text style={styles.companyDetails}>Address: {address}</Text>
      </View>
      <Text style={styles.documentTitle}>{title}</Text>
    </View>
  );
};
 
export default PDFHeader;

Key Benefits:

  • Reusable header component for consistent branding
  • Configurable company information
  • Professional styling with React-PDF
  • Type-safe props interface

Installation Commands

For the snippets above, you'll need these dependencies:

# CORS and basic Next.js functionality (built-in)
npm install next
 
# Prisma with Accelerate
npm install prisma @prisma/client @prisma/extension-accelerate
npx prisma init
 
# UploadThing
npm install uploadthing @uploadthing/react
 
# Excel export
npm install xlsx
npm install --save-dev @types/xlsx
 
# PDF generation
npm install @react-pdf/renderer
npm install --save-dev @types/react
 
# Date formatting (if using date-fns)
npm install date-fns
 
# Toast notifications (if using sonner)
npm install sonner

Usage Tips

  1. CORS Middleware: Place the middleware.ts file in your project root, alongside your next.config.js
  2. Prisma Instance: Import db from your lib/db.ts file instead of creating new PrismaClient instances
  3. UploadThing: Remember to set up the API route in app/api/uploadthing/route.ts
  4. File Exports: Always handle errors gracefully and provide user feedback
  5. PDF Generation: Consider implementing lazy loading for PDF components to improve performance

These snippets cover the most common patterns I use in Next.js applications and should save significant development time.