Skip to content

Latest commit

 

History

121 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Reparaturbonus Zürich

Connecting Zurich residents with certified repair shops through government-subsidized bonus codes. Repair instead of replace.

License: MIT TypeScript Next.js Live

What It Does

  • Bonus codes: Stadt Zürich issues CHF 100 repair bonuses -- 8-character codes, one-month expiry, one per repair
  • Shop directory: Searchable by category, text, postal code, or radius (haversine distance calculation)
  • Three categories: Elektro/Elektronik, Kleidung, Schuhe -- only these qualify for the city's bonus program
  • Admin dashboard: User management, shop approval, bonus tracking, platform statistics
  • Graceful degradation: API endpoints fall back to mock data if the database is unavailable -- the shop directory keeps working

Architecture

Bonus Code System

src/lib/bonus-codes.ts -- centralized logic for the core business operation:

  • generateBonusCode(): 8-character alphanumeric, unique validation with retry loop (up to 10 attempts)
  • calculateBonusAmount(): Fixed CHF 100 per code
  • getBonusExpiryDate(): one month from creation
  • isValidBonusCode(): Regex validation before database lookup

The bonus amount was a design decision -- percentage-based (20% of repair cost) was considered and rejected. A flat CHF 100 is simpler to communicate, simpler to implement, and eliminates disputes over repair cost valuation.

Constants as SSOT

Two files prevent hardcoded strings from scattering across the codebase:

  • src/lib/constants/categories.ts -- shop category enum + Swiss German labels
  • src/lib/constants/routes.ts -- all application routes in one place

API Resilience

Every shop-related API endpoint has a fallback path. If the database is down, the endpoint returns mock data from src/lib/demo/ instead of a 500 error. The shop directory continues working. Users see real data when available, demo data when not -- no error pages.

Role-Based Access

Customer  -->  Admin  -->  Super Admin
  • Credentials provider with bcrypt password hashing
  • JWT session strategy with role embedded in token
  • Protected routes check session?.user?.role server-side
  • Admin dashboard at /admin is role-gated

Database Schema

Drizzle schema as SSOT (src/lib/db/schema.ts) -- types derived from schema (migrated from Prisma 2026-09, keeping the exact table shapes):

User ──── has many ──> BonusCode
  │                       │
  └── has many ──> Order ─┘ (1:1 with BonusCode)
                     │
Shop ── has many ────┘

Three enums enforce domain constraints at the database level:

  • UserRole: CUSTOMER, ADMIN, SUPER_ADMIN
  • ShopCategory: ELECTRONICS, CLOTHING, SHOES
  • OrderStatus: PENDING, CONFIRMED, IN_PROGRESS, COMPLETED, CANCELLED

Tech Stack

Layer Technology
Framework Next.js 16 (App Router, Turbopack)
Language TypeScript 6 (strict mode)
Database PostgreSQL + Drizzle ORM
Auth NextAuth.js, JWT, bcryptjs
Styling Tailwind CSS 4
Deployment Self-hosted (Hetzner, Caddy, Next.js standalone)

Quick Start

Prerequisites

  • Node.js 20+ (CI runs on 24)
  • PostgreSQL

Setup

git clone https://github.com/bitbaum/reparaturbonus-zh.git
cd reparaturbonus-zh
# create .env.local with DATABASE_URL, NEXTAUTH_SECRET, NEXTAUTH_URL (see below)
pnpm install
pnpm run setup                  # apply Drizzle migrations + seed
pnpm run dev

Environment Variables

Variable Purpose
DATABASE_URL PostgreSQL connection string
NEXTAUTH_SECRET Session signing key
NEXTAUTH_URL Application URL

Project Structure

src/
  app/
    api/              # API routes (shops, bonus-codes, admin, auth)
    admin/            # Admin dashboard (role-gated)
    dashboard/        # Customer dashboard
    shops/            # Shop directory + detail pages
    how-it-works/     # Platform explanation
  components/
    admin/            # Auth guard
    forms/            # Reusable form inputs
    layout/           # Header, Footer
    shop/             # Shop-specific components
  lib/
    auth.ts           # NextAuth config + JWT strategy
    bonus-codes.ts    # Code generation, validation, expiry
    db/               # Drizzle client (lazy singleton) + schema (SSOT for types)
    constants/        # Routes + categories (SSOT)
    demo/             # Mock data for graceful fallback
drizzle/              # Generated SQL migrations
scripts/db/           # Seed + prod-safe shop upsert

License

MIT


Reparaturbonus Zürich -- repair culture for a circular economy.

About

City of Zurich repair bonus platform. Connecting residents with certified repair shops instead of landfills.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages