Skip to content

Repository files navigation

FleetGuard

Minimal SaaS for small trucking and logistics companies: track fleet compliance documents (insurance, inspections, driver licenses, permits) and get email reminders 30/15/7/1 days before anything expires.

Stack

  • Next.js 15 (App Router, server actions) + TypeScript + Tailwind CSS
  • Prisma ORM — SQLite for local dev, switch to Postgres for production
  • Auth: email + password with signed JWT session cookie (no third-party service)
  • Payments: Lemon Squeezy subscription ($29/month, 14-day free trial without card)
  • Emails: Resend (falls back to console logging when not configured)
  • Reminders: internal daily cron (node-cron) or external cron hitting an endpoint

Local development

npm install
cp .env.example .env    # then edit values (AUTH_SECRET at minimum)
npm run db:push         # creates dev.db (SQLite)
npm run dev

Open http://localhost:3000, create an account, and you get a 14-day trial with full access. Without RESEND_API_KEY, reminder emails are printed to the console.

Environment variables

See .env.example. Minimum for production:

Variable Purpose
DATABASE_URL Postgres connection string
AUTH_SECRET Long random string for session cookies
APP_URL Public URL, e.g. https://fleetguard.app
LEMONSQUEEZY_API_KEY / LEMONSQUEEZY_STORE_ID / LEMONSQUEEZY_VARIANT_ID / LEMONSQUEEZY_WEBHOOK_SECRET Payments
RESEND_API_KEY / EMAIL_FROM Reminder emails
CRON_SECRET Protects /api/cron/reminders
ENABLE_INTERNAL_CRON 1 to run the daily 08:00 sweep in-process (VPS/Railway/Render)

Lemon Squeezy setup (10 minutes)

  1. Create a store, then a Product "Fleet plan" with a $29/month subscription variant. Copy the store ID into LEMONSQUEEZY_STORE_ID and the variant ID into LEMONSQUEEZY_VARIANT_ID.
  2. Create an API key (Settings → API) → LEMONSQUEEZY_API_KEY.
  3. Add a webhook (Settings → Webhooks) pointing to <APP_URL>/api/lemonsqueezy/webhook with events subscription_created, subscription_updated, subscription_expired. The signing secret you choose goes into LEMONSQUEEZY_WEBHOOK_SECRET.
  4. For local testing enable "Test mode" in Lemon Squeezy and use a tunnel (e.g. ngrok http 3000) so webhooks can reach your machine.

Reminders in production

  • VPS / Railway / Render: set ENABLE_INTERNAL_CRON=1 — a daily sweep runs at 08:00 server time inside the app.
  • Vercel / serverless: leave it unset and schedule a daily request to GET /api/cron/reminders with header Authorization: Bearer <CRON_SECRET> (Vercel Cron or cron-job.org).

Production notes

  • Switch prisma/schema.prisma datasource provider to postgresql and point DATABASE_URL at a managed Postgres (Neon and Supabase have free tiers), then run npm run db:push.
  • File uploads: on Vercel, create a Blob store (Storage tab) and the BLOB_READ_WRITE_TOKEN env var it provides makes uploads go to Vercel Blob automatically. Locally (or on a VPS) files land in uploads/ on disk.
  • Reminders on Vercel: vercel.json schedules a daily cron that calls /api/cron/reminders. Set the CRON_SECRET env var in Vercel — its cron sends it automatically as the Authorization bearer token.
  • Other deploy targets that work out of the box: Railway, Render, Fly.io, any VPS (npm run build && npm start) — set ENABLE_INTERNAL_CRON=1 there.

Project structure

prisma/schema.prisma        Data model (Company, Vehicle, Driver, Document)
src/lib/                    db, auth, lemonsqueezy, email, storage, expiry, reminders
src/app/page.tsx            Landing page with pricing
src/app/(auth)/             Signup / login
src/app/app/                Authenticated app: dashboard, documents, vehicles,
                            drivers, billing
src/app/api/lemonsqueezy/webhook  Lemon Squeezy webhook
src/app/api/cron/reminders  External cron endpoint
src/app/api/files/[name]    Authenticated file downloads
src/instrumentation.ts      Internal daily cron

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages