Skip to content

js-soft/Frook

Repository files navigation

Frook

A desk booking web app for a single-floor office space. Users can view an interactive floor plan, book desks by day or full week, and manage their reservations. Admins upload the floor plan and configure desk equipment.


Table of contents


Requirements

Tool Minimum version
Node.js 18.x
npm 9.x

No external services are required — the database is a local SQLite file.


Quick start

# 1. Install dependencies
npm install

# 2. Push the database schema
npm run db:push

# 3. Seed demo users and desks
npm run db:seed

# 4. Start the development server
npm run dev

Open http://localhost:3000 and sign in with one of the seed accounts.


Environment variables

Create a .env file at the project root (already provided for local development):

# SQLite database path (relative to project root)
DATABASE_URL="file:./dev.db"

# Secret used to sign NextAuth JWTs — change this in production
NEXTAUTH_SECRET="deskly-super-secret-key-change-in-production"

# Public URL of the app (used by NextAuth for redirects)
NEXTAUTH_URL="http://localhost:3000"

Production note: Generate a strong NEXTAUTH_SECRET with openssl rand -base64 32 and set NEXTAUTH_URL to your public domain.


Available scripts

Command Description
npm run dev Start the Next.js development server on port 3000
npm run build Build the app for production
npm run start Start the production server (requires build first)
npm run lint Run ESLint across the project
npm run db:push Apply the Prisma schema to the database (creates dev.db if absent)
npm run db:seed Populate the database with demo users and desks
npm run db:studio Open Prisma Studio — a visual database browser at port 5555

Project structure

deskly/
├── prisma/
│   ├── schema.prisma       # Database schema (SQLite)
│   └── seed.ts             # Demo data seed script
├── public/
│   └── uploads/            # Floor plan images (created at runtime, gitignored)
├── src/
│   ├── app/
│   │   ├── (auth)/
│   │   │   ├── login/      # /login — sign-in page
│   │   │   └── register/   # /register — account creation page
│   │   ├── admin/          # /admin — floor plan editor (admin only)
│   │   ├── bookings/       # /bookings — bookings list with filter
│   │   ├── api/
│   │   │   ├── auth/
│   │   │   │   ├── [...nextauth]/  # NextAuth handler (GET + POST)
│   │   │   │   └── register/       # POST — create account
│   │   │   ├── bookings/
│   │   │   │   ├── route.ts        # GET (list) + POST (create booking)
│   │   │   │   └── [id]/route.ts   # DELETE (cancel booking)
│   │   │   ├── desks/
│   │   │   │   ├── route.ts        # GET (list) + POST (create desk, admin)
│   │   │   │   └── [id]/route.ts   # PUT (update) + DELETE (admin)
│   │   │   ├── floor-plan/route.ts # GET — current floor plan record
│   │   │   └── upload/route.ts     # POST — upload floor plan image (admin)
│   │   ├── globals.css
│   │   ├── layout.tsx      # Root layout with SessionProvider and Header
│   │   ├── page.tsx        # / — interactive floor plan (booking view)
│   │   └── providers.tsx   # Client-side NextAuth SessionProvider wrapper
│   ├── components/
│   │   ├── BookingModal.tsx     # Day / week booking form modal
│   │   ├── BookingsList.tsx     # Filterable bookings table with cancel action
│   │   ├── DeskMarker.tsx       # Coloured desk pin on the floor plan
│   │   ├── FloorPlanEditor.tsx  # Admin: upload image + place / edit desks
│   │   ├── FloorPlanViewer.tsx  # User: view plan + trigger booking
│   │   └── Header.tsx           # Top navigation bar
│   ├── lib/
│   │   ├── auth.ts         # NextAuth options (credentials provider + JWT callbacks)
│   │   ├── db.ts           # Prisma client singleton
│   │   └── utils.ts        # cn(), date helpers, parseEquipment()
│   ├── middleware.ts        # Protects /admin and /bookings from unauthenticated access
│   └── types/
│       ├── index.ts         # Shared TypeScript interfaces
│       └── next-auth.d.ts   # NextAuth session/JWT type augmentation
├── .env                    # Local environment variables (gitignored)
├── .gitignore
├── next.config.mjs
├── postcss.config.js
├── tailwind.config.ts
└── tsconfig.json

Architecture

Tech stack

Layer Technology
Framework Next.js 14 (App Router)
Language TypeScript
Styling Tailwind CSS
Database SQLite via Prisma ORM
Authentication NextAuth v4 — credentials strategy, JWT sessions
Password hashing bcryptjs (cost factor 12)
Validation Zod
Date handling date-fns
Icons lucide-react

Session strategy

NextAuth is configured with strategy: "jwt" — no database session table is needed. The JWT payload carries id and role, which are exposed on the session.user object.

Floor plan positioning

Desk coordinates (x, y) are stored as percentages (0–100) of the floor plan image dimensions. This makes the layout resolution-independent — desks scale correctly with any image size and screen width.

Equipment storage

The equipment column on the Desk table is a JSON-serialised string (e.g. '["Standing desk","Dual monitors"]'). The parseEquipment() utility in src/lib/utils.ts handles deserialisation, and all API responses return equipment as a proper string[].

Booking conflict detection

The POST /api/bookings handler queries for any existing booking on the same desk where the date ranges overlap (startDate <= newEnd AND endDate >= newStart). If a conflict is found the request returns 409 Conflict.


API reference

All endpoints require a valid session cookie except where noted.

Authentication

Method Path Auth Description
POST /api/auth/register Public Create a new user account
GET/POST /api/auth/[...nextauth] Public NextAuth sign-in / sign-out / session

Register body

{ "email": "user@example.com", "password": "min6chars", "name": "Optional Name" }

Desks

Method Path Auth Description
GET /api/desks Any user List all desks
POST /api/desks Admin Create a desk
PUT /api/desks/:id Admin Update desk name / position / equipment
DELETE /api/desks/:id Admin Delete a desk (cascades bookings)

Create / update desk body

{
  "name": "Desk A1",
  "x": 20.5,
  "y": 34.1,
  "equipment": ["Standing desk", "Dual monitors"]
}

Bookings

Method Path Auth Description
GET /api/bookings Any user List all bookings
GET /api/bookings?mine=true Any user List only the caller's bookings
POST /api/bookings Any user Create a booking
DELETE /api/bookings/:id Owner or Admin Cancel a booking

Create booking body

{
  "deskId": "desk-1",
  "date": "2026-06-16",
  "type": "DAY"
}

type is "DAY" or "WEEK". For WEEK, date can be any day within the target week — the API calculates Monday–Sunday automatically.

Floor plan

Method Path Auth Description
GET /api/floor-plan Any user Get the current floor plan record
POST /api/upload Admin Upload a new floor plan image (multipart/form-data, field: file)

Authentication

The app uses email + password authentication via NextAuth's credentials provider.

  • Passwords are hashed with bcrypt (cost factor 12) before storage.
  • Sessions are stateless JWTs stored in an HTTP-only cookie.
  • The role field (USER or ADMIN) is embedded in the token and validated server-side on every protected request.
  • The NextAuth middleware (src/middleware.ts) guards /admin/* and /bookings/* — unauthenticated requests are redirected to /login.

Roles and permissions

Action USER ADMIN
View floor plan
Book a desk
Cancel own booking
Cancel any booking
Upload floor plan
Create / edit / delete desks
Access /admin

To promote a user to admin, update their role column directly via Prisma Studio (npm run db:studio) or a SQL query:

UPDATE User SET role = 'ADMIN' WHERE email = 'user@example.com';

Database

The Prisma schema defines four models:

User        — id, email (unique), name, password (bcrypt), role, timestamps
FloorPlan   — id, imageUrl, timestamps  (only one record is used; updated in-place)
Desk        — id, name, x, y, equipment (JSON string), timestamps
Booking     — id, userId → User, deskId → Desk, startDate, endDate, timestamps

Useful Prisma commands

# Apply schema changes to the database without generating a migration file
npm run db:push

# Open the visual database browser
npm run db:studio

# Re-seed (safe — uses upsert, will not duplicate records)
npm run db:seed

# Generate the Prisma client after schema changes
npx prisma generate

Testing

The project currently has no automated test suite. Below are the manual test scenarios that cover all key behaviours.

Test accounts

Email Password Role
admin@deskly.com admin123 Admin
alice@deskly.com user123 User
bob@deskly.com user123 User

Manual test scenarios

1. Registration and login

# Steps Expected result
1.1 Open /register, fill in name + email + password (≥ 6 chars), submit Account created, redirected to /
1.2 Open /register with an already-used email Error: "Email already in use"
1.3 Open /login, enter correct credentials Signed in, redirected to /
1.4 Open /login, enter wrong password Error: "Invalid email or password"
1.5 Navigate to /bookings while signed out Redirected to /login
1.6 Navigate to /admin while signed in as a regular user Redirected to /

2. Floor plan — admin

# Steps Expected result
2.1 Sign in as admin, go to /admin Upload button visible, empty plan area
2.2 Upload a PNG/JPG/WebP/SVG image Image rendered as the floor plan
2.3 Click anywhere on the image Dashed circle appears at click point, "New desk" form opens
2.4 Enter desk name, check equipment, click "Create desk" Coloured marker appears on the plan
2.5 Click an existing marker, edit name or equipment, save Marker label and sidebar list update
2.6 Hover a marker, click the red × Confirmation dialog → desk and all its bookings are deleted
2.7 Upload a non-image file Error: "Invalid file type"

3. Floor plan — user booking

# Steps Expected result
3.1 Sign in as Alice, go to / Floor plan shows green (available) markers
3.2 Click a green marker Booking modal opens with desk name and equipment
3.3 Select "Single day", pick a date, confirm Modal closes; marker turns blue (mine)
3.4 Click the same desk again on the same date Modal shows the desk as unavailable / booking fails with conflict error
3.5 Sign in as Bob, go to / The desk Alice booked shows as red (taken)
3.6 Select "Full week", pick any day in the week, confirm Desk is booked Mon–Sun; adjacent weeks remain green
3.7 Switch the date filter to a day outside a booking period Previously booked desk appears green again

4. Bookings list

# Steps Expected result
4.1 Go to /bookings All bookings from all users shown under "Upcoming"
4.2 Click "My bookings" filter Only the signed-in user's bookings remain
4.3 Own upcoming booking has a "Cancel" button Clicking it removes the booking from the list
4.4 Another user's booking has no cancel button No cancel button visible
4.5 Sign in as admin, go to /bookings Admin sees all bookings; "Cancel" button is visible only on their own entries

5. API authorization (manual with curl or Postman)

# Attempt to create a desk without auth — expect 403
curl -X POST http://localhost:3000/api/desks \
  -H "Content-Type: application/json" \
  -d '{"name":"X","x":50,"y":50,"equipment":[]}'

# Attempt to delete another user's booking — expect 403
curl -X DELETE http://localhost:3000/api/bookings/<booking-id>

Resetting test data

To return the database to a clean seeded state:

rm prisma/dev.db
npm run db:push
npm run db:seed

Deployment

Production build

npm run build
npm run start

Environment checklist before deploying

  • Set a strong NEXTAUTH_SECRET (openssl rand -base64 32)
  • Set NEXTAUTH_URL to the public URL of the app
  • Set DATABASE_URL — for a hosted environment consider replacing SQLite with PostgreSQL by changing the Prisma provider and running npx prisma migrate deploy
  • Ensure the public/uploads/ directory is writable and persisted across deployments (use a CDN or object storage for production)
  • Create the initial admin account manually (register normally, then set role = 'ADMIN' in the database)

About

a desk booking software for shared office spaces

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages