Modern self-service terminal for a library of things built with Next.js, React and PocketBase.
- Frontend: Next.js 15 (App Router), React 19, TypeScript
- UI: Shadcn/ui + Tailwind CSS
- Animations: Framer Motion
- Backend: PocketBase
- PWA: next-pwa
- Browse available items with search and shuffle functionality
- Select up to 3 items for rental
- Customer identification (existing or new customer)
- Automated rental creation with deposit calculation
- Receipt display with return date
- OTP-based reservation verification (6-digit code from email)
- Large, accessible OTP input with on-screen keypad
- Reservation details display (customer info, items, deposit)
- One-tap confirmation to mark reservation as picked up
- Deposit reminder screen
- Simple form for new customer signup
- Newsletter opt-in
- Integrated with rental flow
llka-selfservice/
βββ app/ # Next.js app directory
β βββ page.tsx # Home page (main menu)
β βββ new-rental/ # New rental flow
β β βββ page.tsx # Item selection
β β βββ customer/page.tsx # Customer identification
β β βββ success/page.tsx # Rental receipt
β βββ pickup-reservation/ # Reservation pickup flow
β β βββ page.tsx # OTP entry and confirmation
β βββ new-customer/ # New customer registration
β β βββ page.tsx # Registration form
β βββ layout.tsx # Root layout
β βββ globals.css # Global styles + animations
βββ components/ # React components
β βββ ui/ # Base UI components
β β βββ animated-dot-grid.tsx # Background animation
β β βββ card.tsx # Card component
β β βββ button.tsx # Button component
β β βββ ... # Other UI components
β βββ otp-input.tsx # OTP input with on-screen keypad
β βββ item-card.tsx # Item display card
β βββ selection-bar.tsx # Selected items bar
β βββ header.tsx # App header
β βββ ... # Other components
βββ lib/ # Utilities and helpers
β βββ pocketbase/ # PocketBase integration
β β βββ client.ts # PocketBase client
β β βββ collections.ts # Collection helpers
β β βββ queries.ts # Common queries
β β βββ auth.ts # Authentication
β βββ utils/ # Utility functions
β β βββ formatting.ts # Date/currency formatting
β β βββ get-item-image-url.ts # Image URL helper
β βββ constants/ # App constants
β βββ ui-labels.ts # UI text labels
β βββ categories.ts # Item categories
βββ types/ # TypeScript type definitions
β βββ index.ts # Shared types
βββ public/ # Static assets
βββ ausleihen.png # "New Rental" button image
βββ abholen.png # "Pickup" button image
βββ leihlokal.svg # Logo
Create a .env.local file in the root directory:
NEXT_PUBLIC_POCKETBASE_URL=your_pocketbase_url
NEXT_PUBLIC_TERMINAL_NAME=Terminal_Name# Install dependencies
npm install
# Run development server
npm run dev
# Build for production
npm run build
# Start production server
npm startThe app will be available at http://localhost:3000
Customer records with fields:
iid(number) - Customer IDfirstname,lastname(text)email,phone(text, optional)street,postal_code,city(text, optional)registered_on(date)newsletter(boolean)
Inventory items with fields:
iid(number) - Item IDname(text)category(select, multiple)deposit(number)copies(number) - Available copiesstatus(select) - instock, outofstock, reserved, etc.images(file, multiple)description,brand,model(text, optional)
Active and completed rentals with fields:
customer(relation to customer)items(relation to item, multiple)requested_copies(json) - Copy counts per itemdeposit,deposit_back(number)rented_on,returned_on,expected_on(date)employee(text)
Pickup reservations with fields:
customer_iid(text, optional)customer_name,customer_phone,customer_email(text)is_new_customer(boolean)items(relation to item, multiple)pickup(date)otp(text) - 6-digit pickup codeon_premises(boolean) - Set when customer arrivesdone(boolean) - Marks reservation as completed
- Large touch targets (minimum 56px)
- High contrast colors
- Clear visual hierarchy
- Touch-optimized interactions
- Monospace digits for OTP input
- Playful, cinema-style OTP entry with bounce effects
- Smooth page transitions with Framer Motion
- Satisfying micro-interactions on button press
- Animated dot grid background
- Portrait-optimized layouts
- Auto-redirect timers on success screens
- No scrolling on main screens
- Touch-first design with large buttons
- Full-screen OTP entry without card backgrounds
This app is designed to run in portrait mode on a large touch screen display and is intended to be used in a self-service kiosk environment.
Recommended specs:
- Touch screen display (portrait orientation)
- Minimum resolution: 1080x1920
- Chrome/Chromium browser in kiosk mode
- Stable network connection to PocketBase instance
Update labels and text in lib/constants/ui-labels.ts
Configure item categories in lib/constants/categories.ts
Set via NEXT_PUBLIC_TERMINAL_NAME environment variable - appears in rental records
The pickup reservation flow includes a commented-out date filter for easier testing:
// TESTING VERSION (no date filter):
const filter = `otp = "${otp}" && done = false`;
// PRODUCTION VERSION (uncomment for live use):
// const today = new Date().toISOString().split('T')[0];
// const filter = `otp = "${otp}" && pickup = "${today}" && done = false`;Located in: app/pickup-reservation/page.tsx:39
- The
on_premisesflag update may error if the field doesn't exist in PocketBase yet - this is expected and handled gracefully - The app will continue to the confirmation screen even if the update fails
- Auto-redirect timers (30 seconds on success screens) help reset the kiosk for the next user
Global CSS prevents iOS from zooming on input focus by setting font-size: 16px on all inputs. OTP inputs override this with inline styles for the large digit display.
- Physical keyboard input supported on OTP entry
- Paste support for 6-digit codes
- Number keys work on OTP screen
- Backspace/Delete work as expected
Optimized for:
- Chrome/Chromium (recommended for kiosk mode)
- Safari (iOS tablets)
- Firefox
- Edge
Copyright Β© 2025 BΓΌrgerstiftung Karlsruhe
Ein Projekt der BΓΌrgerstiftung Karlsruhe
