A web app for managing an electricity-selling business: customers, monthly meter readings, bills, and payments. Built with Next.js, TypeScript, Tailwind CSS, Google Sheets (backend), and Google Drive (receipt storage).
- Roles: Manager and Employee
- Manager: Dashboard, view all data, manage pricing, edit customer discounts
- Employee: Add customers, record meter readings, record payments, upload receipts
- Billing: Monthly bills with ampere + kWh or either; fixed discounts; carry-over unpaid balance
- Data: Google Sheets for persistence; Google Drive for receipt images
- Next.js 14 (App Router)
- TypeScript
- Tailwind CSS
- iron-session (password-based auth)
- Google Sheets API
- Google Drive API
- Node.js 18+
- Google Cloud project with Sheets and Drive APIs enabled
- A Google Sheet and a Drive folder for receipts
cd electricity-mvp
npm installCopy the example file and fill in your values:
cp .env.example .env.localEdit .env.local with:
| Variable | Required | Description |
|---|---|---|
MANAGER_PASSWORD |
Yes | Manager login password |
EMPLOYEE_PASSWORD |
Yes | Employee login password |
SESSION_SECRET |
Yes | Random 32+ character string for session signing |
GOOGLE_SHEETS_ID |
Yes | Your Google Sheet ID (from the URL) |
GOOGLE_DRIVE_RECEIPTS_FOLDER_ID |
Yes | Drive folder ID for receipt images |
GOOGLE_APPLICATION_CREDENTIALS |
Yes* | Path to service account JSON (e.g. ./service-account.json) |
GOOGLE_CREDENTIALS_JSON |
Yes* | Alternative: inline JSON (for Vercel) |
* Use one of GOOGLE_APPLICATION_CREDENTIALS or GOOGLE_CREDENTIALS_JSON.
- Go to Google Cloud Console
- Create a new project or select an existing one
- APIs & Services → Library
- Enable Google Sheets API
- Enable Google Drive API
- APIs & Services → Credentials → Create Credentials → Service Account
- Name it (e.g.
electricity-mvp) - Create and download the JSON key
- Save as
service-account.jsonin the project root (or use inlineGOOGLE_CREDENTIALS_JSON)
- Open your Google Sheet
- Copy the Sheet ID from the URL:
https://docs.google.com/spreadsheets/d/YOUR_SHEET_ID/edit - Share the sheet with the service account email (e.g.
electricity-mvp@project.iam.gserviceaccount.com) as Editor
Create a Google Sheet with these tabs and headers (exact order matters):
Tab: Customers
- Row 1:
customerId,fullName,phone,area(box number),building,floor,apartmentNumber,subscribedAmpere,billingType,fixedDiscountAmount,status,notes,createdAt,freeReason,fixedDiscountPercent,isMonitor,linkedCustomerId
Tab: Bills
- Row 1:
billId,customerId,monthKey,previousCounter,currentCounter,usageKwh,amperePriceSnapshot,kwhPriceSnapshot,ampereCharge,consumptionCharge,discountApplied,previousUnpaidBalance,totalDue,totalPaid,remainingDue,paymentStatus,createdAt,updatedAt
Tab: Payments
- Row 1:
paymentId,billId,customerId,paymentDate,amountPaid,receiptImageUrl,paymentMethod,note,enteredByRole,createdAt
Tab: Settings
- Row 1:
kwhPrice,currency,updatedAt - Row 2 (optional initial values):
0,LBP, (leave updatedAt empty or use a timestamp)
Tab: AmperePrices (created automatically on first save, or add manually)
- Row 1:
amp,price - Data rows: one per amperage tier, e.g.
3,231000(3A = 231,000 LBP)
Receipt uploads require BLOB_READ_WRITE_TOKEN. Create a Blob store under Storage → Blob in Vercel and connect the read/write token to your project (and .env.local for dev). There is no Google Drive fallback in the app for receipts.
Images are resized and re-encoded as JPEG on the server (defaults: max edge 1600px, quality 82). Optional env: RECEIPT_MAX_PIXEL_EDGE (640–4096), RECEIPT_JPEG_QUALITY (60–95).
You do not need GOOGLE_DRIVE_RECEIPTS_FOLDER_ID for receipts. That variable is only relevant if you run npm run verify-receipt-folder to debug a Drive folder (optional).
npm run devOpen http://localhost:3000. You'll be redirected to the login page.
- Manager: Use the Manager role and
MANAGER_PASSWORD - Employee: Use the Employee role and
EMPLOYEE_PASSWORD
Import customers and bills from a CSV in the format:
Client Name, BOX NUMBER, BUILDING NAME, Subscription Type, Amps, Counter Previous, Counter Now, Paid till now, Total Price
- Total Price = amount due; Paid till now = amount paid; diff = 0 → PAID
- BOX NUMBER is stored as the "Box Number" field (shown instead of Area in the app)
npm run import-csv -- 2025-02 # FILE.csv, February 2025
npm run import-csv -- "FILE 2.csv" 2025-02 # custom file + monthgit init
git add .
git commit -m "Initial commit"
git remote add origin <your-repo-url>
git push -u origin main- Go to vercel.com
- Import your repository
- Add environment variables (from
.env.local). Receipts requireBLOB_READ_WRITE_TOKEN(Storage → Blob). Optional:RECEIPT_MAX_PIXEL_EDGE/RECEIPT_JPEG_QUALITY.
For Vercel, use GOOGLE_CREDENTIALS_JSON instead of a file path:
- Copy the entire contents of
service-account.json - Minify to one line (remove newlines)
- Paste as the value of
GOOGLE_CREDENTIALS_JSONin Vercel env vars
Vercel will build and deploy automatically on push.
src/
├── app/
│ ├── actions/ # Server actions
│ │ ├── bill.ts
│ │ ├── customer.ts
│ │ ├── payment.ts
│ │ ├── receipt.ts
│ │ └── settings.ts
│ ├── api/auth/ # Auth API routes
│ ├── employee/ # Employee pages
│ ├── manager/ # Manager pages
│ ├── login/
│ └── layout.tsx
├── components/
├── lib/
│ ├── billing.ts # Billing calculation logic
│ ├── receipt-image.ts # Resize / JPEG receipts (sharp)
│ ├── receipt-upload.ts # Vercel Blob upload
│ ├── google-sheets.ts # Sheets CRUD
│ ├── auth.ts
│ └── ...
└── types/
- Usage:
currentCounter - previousCounter(kWh) - Ampere charge: Applied only for
AMPERE_ONLYandBOTH - kWh charge: Applied only for
KWH_ONLYandBOTH - Discount: Fixed amount subtracted from total; total never goes negative
- Carry-over: Previous month's unpaid balance is added to the new bill
- Status: PAID / PARTIAL / UNPAID based on remaining amount
- Passwords are stored in env vars; use strong passwords
- Session uses HttpOnly cookies
- Manager-only actions are protected server-side
- Employee cannot edit old bills or change pricing
- Google credentials stay server-side
For production, consider migrating to a real database and a more robust auth system (e.g. OAuth, proper user tables).