A web-based equipment loan management system for organizations. Browse inventory, request loans, and manage returns with automated email notifications.
- Browse equipment catalog with search and filtering
- Request loans with flexible date ranges
- Automated email notifications for loan reminders and updates
- Admin dashboard for managing inventory, locations, and loans
- Multi-user support with role-based permissions (Admin, User, Kiosk)
- Support for normal and temporary items
- Organized inventory with categories, locations, and boxes
- Browse available equipment in the catalog
- Request a loan by selecting dates and items
- Receive an email confirmation right away — requests are accepted automatically, there is no approval queue to wait for
- Get automated reminders before pickup, and when a loan runs late
- Return items and view loan history
- Manage equipment catalog (add, edit, remove items)
- Organize items by categories, locations, and boxes
- Track all active and past loans; reject or cancel ones that shouldn't run (rejecting is after the fact — see the note on automatic acceptance above)
- Handle returns, including items dropped into a return box
- Manage user accounts and permissions
- Self-service stations for quick item checkout and returns
- Simplified interface for public access points
- Next.js 16 (App Router) — React framework
- React 19 with TypeScript (strict)
- Prisma 7 — Database ORM
- PostgreSQL — Database
- NextAuth.js — Authentication
- Tailwind CSS 4 — Utility-first styling (CSS-first config)
- shadcn/ui — Component primitives (owned in
components/ui/) - Radix UI — Unstyled accessible primitives (Dialog, Switch, Tooltip, Label)
- next-themes — Dark mode (class-based)
- sonner — Toast notifications
- react-select — Creatable/multi selects (shadcn-styled wrapper)
- lucide-react + react-icons — Icons
- SWR — Client-side data fetching
- AWS SES — Email notifications
- All UI primitives live in
components/ui/— they're source files you own and edit freely (shadcn pattern, not an installed library). - Design tokens are CSS variables in
styles/globals.css. Colors map to HSL vars (--primary,--background,--card,--destructive,--success,--warning, etc.) with.darkoverrides. - There is no
tailwind.config.ts— Tailwind 4 is configured CSS-first.styles/globals.cssdoes@import 'tailwindcss'and exposes the tokens through an@themeblock, which is what makesbg-primary,text-muted-foregroundetc. work. PostCSS wires it up via@tailwindcss/postcssinpostcss.config.js. Dark mode is class-based — toggled bynext-themesvia theclassattribute on<html>. - Toasts use
sonner. Importtoastfromsonnerand calltoast.success(...),toast.error(...),toast.warning(...). - Creatable selects use the
CreatableSelectwrapper incomponents/ui/creatable-select.tsx— it stylesreact-select's creatable via theclassNamesAPI so it respects dark mode and tokens without runtime theme juggling.
- Install dependencies:
pnpm install- Set up environment variables:
cp .env.example .envThe defaults in .env.example match docker-compose.yml, so the database
works out of the box; the AWS and Google values are only needed for real email,
photo uploads, and Google sign-in.
- Start local database:
docker-compose up -d- Run migrations and seed data:
pnpm prisma migrate dev
pnpm prisma db seed- Start development server (also boots the local SES mock automatically):
pnpm devVisit http://localhost:3000.
pnpm dev— Next dev server + local SES mockpnpm build—prisma migrate deploy+next buildpnpm start— Production serverpnpm type-check—tsc --noEmitpnpm lint— ESLint (Next config)pnpm test— Vitest against a disposable Postgres (docker-compose)pnpm test:ci— Vitest without docker (expectsDATABASE_URLalready set)
The dev script starts aws-ses-v2-local in parallel. All emails are captured instead of being sent.
Open the email viewer at http://localhost:8005 to see sent emails.
To review the templates without triggering the flows that send them:
pnpm tsx scripts/preview-emails.tsIt renders every template with sample data — no app or database needed — to
/tmp/klapi-emails/, with an index.html linking them all.
Schema is defined in prisma/schema.prisma. After schema changes:
pnpm prisma migrate dev --name description_of_changeGenerate test data:
pnpm prisma db seedERD.pdf is generated by prisma-erd-generator. The generator block
in prisma/schema.prisma is commented out on purpose —
it launches puppeteer on every prisma generate, which is slow and pointless
for a schema that rarely changes. To refresh the diagram, uncomment the block,
run pnpm prisma generate, then comment it out again.
Supports Google OAuth and username/password authentication via NextAuth.js. Configure providers in the NextAuth API route.
User Roles:
- Admin: Full access to manage catalog, users, and loans
- User: Browse catalog, request loans, view own history
- Kiosk: Simplified interface for self-service stations
Admins can elevate a kiosk session to ADMIN temporarily via a 4-digit PIN (set in /admin). The elevated session auto-expires after 30 minutes.
Klapi is deployed automatically when a new commit lands on main.
See .env.example for the full list with comments. The ones a deployment cannot run without:
DATABASE_URL: PostgreSQL connection stringNEXTAUTH_SECRET: Random secret for NextAuth (generate withopenssl rand -base64 32)NEXTAUTH_URL: Public URL of your deploymentGOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRET: Google OAuth credentialsKLAPI_AWS_REGION,KLAPI_AWS_ACCESS_KEY_ID,KLAPI_AWS_SECRET_ACCESS_KEY: AWS credentials for SES and S3 (prefixed because Vercel reservesAWS_*)AWS_SES_FROM_EMAIL: Sender address for all notification emailAWS_BUCKET_NAME,NEXT_PUBLIC_AWS_ITEM_PHOTOS_URL: S3 bucket for item photos, and its public URLCRON_SECRET: Shared secret the/api/cron/*routes require asAuthorization: Bearer …. Without it the nightly reminder/overdue/auto-start jobs return 401 and silently stop working.
app/ App Router — pages (server components by default) and
app/api/ route handlers (`route.ts`), including the cron jobs
components/ App-specific React components
components/ui/ shadcn/ui primitives (owned source, edit freely)
contexts/ React contexts (cart, dates)
hooks/ Custom hooks
lib/ `cn()` className merger and the NextAuth config
styles/globals.css Tailwind import + `@theme` design tokens (light/dark)
types/ Shared types and the next-auth session augmentation
utils/ Server and shared helpers (Prisma client, loan helpers, etc.)
prisma/ Schema, migrations, seed
scripts/ Operator scripts run with `tsx` (kiosk user, email previews)
__tests__/ Vitest suites — mostly API integration tests