> ## Documentation Index
> Fetch the complete documentation index at: https://opencals.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# VOLT — padel & squash club template

> Free, open-source multi-club padel and squash booking website template. Next.js 15, a live hall map and court grid per club, crew-and-kit checkout, match-ticket confirmations, coach-led training and passwordless accounts, built on the Opencals Storefront API.

A complete padel & squash club booking website you can clone and deploy today.
VOLT is the first template built around **staffless resources** rather than
staff-led appointments: courts are independent, exclusively-booked units, so
this is the one to start from if you are booking courts, bays, lanes, studios
or rooms rather than people. It runs on the same
[Storefront API](https://opencals.com/docs/api-reference/introduction.md) and typed
[TypeScript SDK](https://opencals.com/docs/guides/storefront-sdk.md) as [Haar](https://opencals.com/docs/open-source/haar.md),
[Frisor](https://opencals.com/docs/open-source/frisor.md) and [Clear Care](https://opencals.com/docs/open-source/clarity.md).

VOLT models a **network of clubs**: each club is a store location with its own
courts (padel doubles, padel singles, squash), and players book from one club
at a time, on a live **hall map** of its courts or a **courts × time grid**.
Coach-led lessons and group classes run per club too, and both go through the
same crew-and-kit checkout.

> **[Live demo](https://template-volt.vercel.app)**
>
> template-volt.vercel.app

> **[Source code](https://github.com/letsopencals/template-volt)**
>
> letsopencals/template-volt (MIT)

> **[Deploy to Vercel](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fletsopencals%2Ftemplate-volt\&env=OPENCALS_API_KEY,AUTH_SECRET\&envDescription=API%20key%20from%20your%20Opencals%20dashboard%20and%20a%20random%20secret%20for%20auth\&project-name=volt\&repository-name=template-volt)**
>
> One-click deploy

![VOLT padel and squash club booking website template — storefront and booking flow](https://raw.githubusercontent.com/letsopencals/template-volt/main/docs/cover.png)

VOLT — the storefront and booking flow, on desktop and mobile

![VOLT template homepage hero with a floodlit padel court, its four clubs and a live court map link](https://raw.githubusercontent.com/letsopencals/template-volt/main/docs/homepage-hero.png)

Homepage hero — full-bleed and sport-first

## Club-first court booking

Players don't look for a court in four cities at once, so `/book` starts from
**one club**: the one in the link, the one this browser remembers, or the
nearest one ("Near me" uses each location's coordinates). New visitors pick
from photo cards that show each club's soonest free court. The club's page is
full screen, with two views over the same live data:

* **Hall map.** Every court from above, doubles wide and singles narrow, lit for
  one start time: free, free within the hour, free later, or taken for the day.
  Drag the time bar (its bars count free courts per half hour) or tap a quick
  time. "Book 19:30" takes the best free court and falls back to the next if it
  goes; tapping a court lists its own free starts.
* **Grid.** Courts down the rows, half hours across, a now-line, and a duration
  popover pricing 30 / 60 / 90 / 120 minutes from the cell you tap.

Pick a day (14 days, sold-out days struck through), a sport (only the club's
sports appear) and a game length. Every choice is in the URL, so a link can
open a club, a day or a time.

![VOLT template hall map: a club's padel courts from above, free courts lit in orange for the chosen start time](https://raw.githubusercontent.com/letsopencals/template-volt/main/docs/book-a-court.png)

Book a court — every court and open slot on one grid, with a live-priced duration popover

### One request per club

A court is its own resource, so availability is per court. Instead of asking
for every court on every day from the browser, a server route
(`/api/clubs/[locationId]/availability`) reads each court's **merged free
ranges for the whole booking window once**
(`getCurrentAvailabilitiesMerged` with no date), caches each court for 60
seconds, and drops the cache after a booking, cancel or reschedule. The browser
computes the hall map, the grid, the pulse and every "free from" from those
spans, so switching day, length, sport or view makes no API calls.

## Crew, kit and the match ticket

Checkout is one flow for courts and training: numbered steps that fold up as
you go, beside a sticky match card with the running total and hold timer.
Courts go **court → crew → kit → details → payment**: name up to three players
(an email invites them as appointment guests, and the card shows each player's
share), add rackets or balls from the store's add-ons, then pay by card or at
the club.

After checkout, `/thank-you` shows the **match ticket**: the court from above
with a spot per player (doubles, singles or squash), day, time, length, booking
reference, kit and a countdown, plus a calendar file, directions, "Before you
play", "Getting there" and **"Same time next week?"**. The account keeps every
game at every club: stats, the next game as a ticket, past games with "Book
again", crew invites, change time and cancel.

## Coach-led training

Lessons and classes, per club. Filter the catalog by club, sport, level
(Starter → Advanced) and format (1-on-1 or group), then book through the same
flow: level, club, coach (or **"Any coach"**), day and start. Group classes show
live **"N left"** capacity and let you book more than one place.

![VOLT template training booking flow with coach selection, date strip and time slots showing spots left](https://raw.githubusercontent.com/letsopencals/template-volt/main/docs/training-booking.png)

Training booking — level, coach, date and time, with live remaining capacity

## Two resource models, one store

This is the part worth copying even if you never ship VOLT itself.

| Model | Rule | Capacity | Reads as |
| - | - | - | - |
| Courts (rentals) | `Self` — staffless resource, one club each | `maxAttendees: 1` | "Padel · Court 3 · 1h 30m" |
| Trainings (lessons) | `Staff` — assigned to a coach and clubs | `maxAttendees: 1` or more | "Padel Group Class · Intermediate · Coach Marta" |

A singles court is marked in its variant title (`Court 7 · Singles`), and sports
come from collections (`padel-courts`, `squash-courts`), so adding a sport or a
club is a data change.

Orders and appointment detail read naturally either way, so nothing downstream
has to decode a court that was faked as a staff member.

## What's inside

| Feature | Notes |
| - | - |
| Multi-club, club-first | Locations endpoints — clubs with coordinates for "Near me"; the chosen club is remembered |
| Hall map + grid | Merged availability per court, read once per club and cached; map, grid and pulse computed in the browser |
| Custom durations | 30-minute steps to two hours, priced live |
| Sports from collections | Product Collections endpoints — a third sport is a data change, not a code change |
| Crew-and-kit checkout | Appointment guests for crew emails, custom attributes for names, add-ons for kit |
| Match tickets | Confirmation and account tickets with countdown, calendar file and directions |
| Coach-led training | Staff Members endpoints — filter by club, sport, level and format; "Any coach" first |
| Group capacity | Live "N left" counts from `maxAttendees` |
| Add-ons at checkout | Racket and ball rental out of the box |
| Custom questions | Checkout questions configured in the dashboard, saved to the appointment |
| Cart & Stripe checkout | Cart + Checkout + Payment endpoints, deposits supported |
| Passwordless accounts | Self-Service endpoints — 6-digit email login (password fallback), auto-login after checkout, one-time email links via `/link/[token]` |
| Video-ready hero | Upgrades to a looping video when `public/videos/hero.mp4` exists, still image otherwise |
| SEO | Per-page metadata, Open Graph, `SportsActivityLocation` structured data, `robots.txt`, `sitemap.xml` |
| Stack | Next.js 15, TypeScript, Tailwind v4, Framer Motion, `@opencals/storefront-sdk` |

## Get started

**1. Clone and install**

```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
git clone https://github.com/letsopencals/template-volt.git
cd template-volt
npm install
```

**2. Add your API key**

Create a store in the [Opencals dashboard](https://opencals.com) — choose the
**VOLT Padel & Squash** dataset preset to seed four clubs with their courts,
coaches, training classes and add-ons — then grab a private storefront key (`spk_`, see
[API keys](https://opencals.com/docs/authentication.md#api-keys)) and set `OPENCALS_API_KEY` and
`AUTH_SECRET` in `.env`.

**3. Run it**

```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
npm run dev
```

Your real courts, coaches and availability render immediately.

**4. Deploy**

Push to your own repo and
[deploy to Vercel](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fletsopencals%2Ftemplate-volt\&env=OPENCALS_API_KEY,AUTH_SECRET\&envDescription=API%20key%20from%20your%20Opencals%20dashboard%20and%20a%20random%20secret%20for%20auth\&project-name=volt\&repository-name=template-volt),
or host anywhere Next.js runs.

> [!NOTE]
>
> Set **Storefront Base URL** in your dashboard (Settings → API) to your
> deployed domain. Every customer link — view, reschedule, cancel, leave
> feedback — is built as `{storefrontBaseUrl}/link/{token}`, so if it still
> points at localhost those emailed links go nowhere.

## Make it yours

MIT-licensed: rebrand it, restructure it, or use it as a starting point for
client work. All copy lives in `lib/site-config.ts`: brand name, sports and
their colours, club photos, taglines, hours and phones (matched to your
locations by slug or city), hero, coaches, gallery, stats, testimonials and
FAQs. Clubs, courts, prices and availability always come from your store. Every colour and font is a Tailwind v4
`@theme` token in `app/globals.css`, so components keep working when you change
values:

```css theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
@theme {
  --color-bg: #0A0A0A;          /* near-black page background */
  --color-primary: #FF4A1C;     /* electric-orange accent */
  --font-display: 'JetBrains Mono', ui-monospace, monospace;
}
```

Keep the accent aligned with your store's storefront primary colour so booking
emails match the site. Agencies can run one codebase against multiple stores —
each deployment points at a different API key. To go deeper than theming,
follow the [booking page tutorial](https://opencals.com/docs/guides/build-a-booking-page.md) to see the API
calls behind each screen.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.