uidudevelopers
Domains

Bookings & Calendars

Public calendars, calendar events, and bookings (appointments).

  • Use it for: Public website · Server integration
  • Reads: public token — works in the browser; Bearer for getBooking, listBookings
  • Writes: none — read-only today
  • CLI: uidu bookings
  • Auth: what each token can do and where it may run — Authentication

Calendars and their events are the workspace's public schedule — opening hours, classes, appointments on offer — and bookings are the reservations people made. Render the first on a public site with the public token; read the second from your server.

Mental model

Calendar            name, description, color ("#22c55e"), timeZone ("Europe/Rome")
  └─→ CalendarEvent name, startTime / endTime, allDay, timeZone, url,
                    primaryAddress, body (rich text — on getCalendarEvent only)

Booking             kind, number, status, currency, itemsCount, itemsTotal (cents),
                    paidAt / refundedAt / expiredAt / willExpireAt, contact,
                    fieldValuesByShortname (on getBooking only)
  • A CalendarEvent is lighter than an Event: no campaign, no attendance, no CMS body to build — a dated entry on a calendar. Use Events when people RSVP.
  • Two auth levels. Calendars and calendar events are public reads: the public token works, including in the browser. Bookings are private data and need a Bearer (apiKey) client on the server — with a public token listBookings returns an empty list and getBooking returns null, no error. Nothing here writes: the domain is read-only today.
  • Times (startTime, endTime) are ISO strings or null; an allDay event has no meaningful time of day. Each event and calendar carries its timeZone — format with it rather than the server's zone.
  • Money is in cents. A booking's itemsTotal is an integer in the minor unit of its currency: divide by 100 to display it. status and kind are labels (strings); number is the human-readable booking number.
  • Filters. listCalendarEvents narrows by calendarIds and by named scopes (e.g. upcoming). List items carry the event's calendar (id, name, color) and a short primaryAddress (formattedAddress, city); the full event, with body and the address's name and countryCode, comes from getCalendarEvent. listCalendars and listBookings page with first/after or last/before.

Use cases

Public calendar grid

Goal: a statically rendered page with a legend of calendars and the upcoming events grouped by month.

// app/calendar/page.tsx — Server Component
import { listCalendars, listCalendarEvents } from '@uidu/client';
import { uidu } from '@/lib/uidu'; // public token is enough

export default async function CalendarPage() {
  const [calendars, events] = await Promise.all([
    listCalendars(uidu, { first: 50 }),
    listCalendarEvents(uidu, { first: 100, scopes: ['upcoming'] }),
  ]);

  const byMonth = events.reduce<Record<string, typeof events>>((acc, e) => {
    if (!e.startTime) return acc;
    const month = e.startTime.slice(0, 7); // YYYY-MM
    (acc[month] ??= []).push(e);
    return acc;
  }, {});

  return (
    <div>
      <ul>
        {calendars.map((c) => (
          <li key={c.id} style={{ color: c.color ?? undefined }}>
            {c.name}
          </li>
        ))}
      </ul>
      {Object.entries(byMonth).map(([month, list]) => (
        <section key={month}>
          <h2>{month}</h2>
          <ul>
            {list.map((e) => (
              <li key={e.id} style={{ borderColor: e.calendar.color ?? undefined }}>
                <a href={`/calendar/${e.id}`}>{e.name}</a>{' '}
                {new Date(e.startTime!).toLocaleString(undefined, {
                  timeZone: e.timeZone ?? undefined,
                  dateStyle: 'medium',
                  timeStyle: e.allDay ? undefined : 'short',
                })}
                {e.primaryAddress?.city && ` · ${e.primaryAddress.city}`}
              </li>
            ))}
          </ul>
        </section>
      ))}
    </div>
  );
}
  • To show one calendar only, pass calendarIds: [calendarId]; getCalendar(uidu, { id }) returns its name, color and time zone for the page header.
  • One page of events is usually enough for "upcoming"; for a full archive walk every page with paginate and ListCalendarEventsDocument — see Pagination & field values.

Calendar event page

Goal: the detail page each grid item links to, with the event's rich-text description.

// app/calendar/[id]/page.tsx
import { notFound } from 'next/navigation';
import { getCalendarEvent } from '@uidu/client';
import { RichText } from '@uidu/react';
import { uidu } from '@/lib/uidu';

export default async function CalendarEventPage({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params;
  const event = await getCalendarEvent(uidu, { id });
  if (!event) notFound();

  return (
    <article>
      <p>{event.calendar.name}</p>
      <h1>{event.name}</h1>
      {event.primaryAddress && <p>{event.primaryAddress.formattedAddress}</p>}
      {event.body && <RichText doc={event.body} />} {/* Tiptap JSON */}
      {event.url && <a href={event.url}>More information</a>}
    </article>
  );
}

Bookings report on the server

Goal: an internal page or export listing recent bookings with their customer and amount.

// app/admin/bookings/data.ts — server-only
import { listBookings, getBooking } from '@uidu/client';
import { uiduAdmin } from '@/lib/uidu-admin'; // createClient({ workspace, apiKey })

const money = (cents: number | null | undefined, currency: string | null | undefined) =>
  cents == null
    ? '—'
    : new Intl.NumberFormat(undefined, { style: 'currency', currency: currency ?? 'EUR' }).format(
        cents / 100, // itemsTotal is in cents
      );

export async function recentBookings() {
  const bookings = await listBookings(uiduAdmin, { first: 25 }); // [] with a public token

  return bookings.map((b) => ({
    number: b.number,
    kind: b.kind,
    status: b.status,
    paid: Boolean(b.paidAt),
    items: b.itemsCount,
    total: money(b.itemsTotal, b.currency),
    customer: b.contact?.name ?? b.contact?.email,
  }));
}

export async function bookingDetail(id: string) {
  const booking = await getBooking(uiduAdmin, { id }); // null with a public token
  return booking && {
    ...booking,
    answers: booking.fieldValuesByShortname, // custom fields, keyed by shortname
  };
}
  • getBooking adds paymentIntentId, refundedAt, updatedAt and the custom-field map fieldValuesByShortname to what the list returns.
  • From a terminal: uidu bookings list --json, uidu bookings get <id> --json.

API at a glance

Every @uidu/client function in Bookings & calendars, generated from the source — the full reference has the rest of the SDK.

FunctionKindAuthCLIDescription
getBookingreadBearerbookings getOne booking with its contact, totals in cents and custom fields (fieldValuesByShortname), or null; a public token sees none.
getCalendarreadpublic token—One calendar (name, color, time zone), or null.
getCalendarEventreadpublic token—One calendar event with its rich-text body and its calendar, or null.
listBookingsreadBearerbookings listThe workspace's bookings as a flat array; page with first/after (or last/before); a public token gets an empty list.
listCalendarEventsreadpublic token—Calendar events as a flat array, optionally narrowed by calendarIds and named scopes (e.g. upcoming); page with first/after.
listCalendarsreadpublic token—The workspace's calendars as a flat array; page with first/after (or last/before).

Types: Booking, Calendar, CalendarEvent

CLI:

Entitylistgetcreateupdatedelete
bookings✓✓———

On this page