HFU Digital Docs
RoomKitExamples

Next.js App

Build a room booking UI with Next.js and RoomKit React

Overview

This example shows how to build a room booking frontend with Next.js 15 and @hfu.digital/roomkit-react.

Provider Setup

app/providers.tsx
'use client';

import { RoomKitProvider } from '@hfu.digital/roomkit-react';

export function Providers({ children }: { children: React.ReactNode }) {
    return (
        <RoomKitProvider
            config={{
                apiUrl: '/api/roomkit',
                fetchOptions: {
                    credentials: 'include',
                },
            }}
        >
            {children}
        </RoomKitProvider>
    );
}
app/layout.tsx
import { Providers } from './providers';

export default function RootLayout({ children }: { children: React.ReactNode }) {
    return (
        <html>
            <body>
                <Providers>{children}</Providers>
            </body>
        </html>
    );
}

Room Finder Page

app/rooms/page.tsx
'use client';

import { useState } from 'react';
import {
    useAvailability,
    AvailabilitySearch,
    RoomCard,
} from '@hfu.digital/roomkit-react';
import type { AvailabilityFilter } from '@hfu.digital/roomkit-react';

export default function RoomsPage() {
    const [filters, setFilters] = useState<AvailabilityFilter>({
        timeRange: {
            startsAt: new Date(),
            endsAt: new Date(Date.now() + 2 * 60 * 60 * 1000),
        },
    });

    const { data, isLoading, error } = useAvailability({
        filters,
        pagination: { limit: 12 },
    });

    return (
        <div style={{ display: 'flex', gap: '2rem', padding: '2rem' }}>
            <aside style={{ width: '300px' }}>
                <h2>Search</h2>
                <AvailabilitySearch
                    equipmentOptions={['projector', 'whiteboard', 'microphone']}
                    accessibilityOptions={['wheelchair', 'hearing_loop']}
                    onChange={setFilters}
                />
            </aside>

            <main style={{ flex: 1 }}>
                <h1>Available Rooms</h1>
                {isLoading && <p>Searching...</p>}
                {error && <p>Error: {error.message}</p>}

                <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))', gap: '1rem' }}>
                    {data?.items.map((item) => (
                        <RoomCard
                            key={item.room.id}
                            room={item.room}
                            score={item.score}
                            onClick={() => window.location.href = `/rooms/${item.room.id}`}
                        />
                    ))}
                </div>

                {data && (
                    <p>{data.totalMatching} rooms found</p>
                )}
            </main>
        </div>
    );
}

Booking Detail Page

app/bookings/[id]/page.tsx
'use client';

import { use } from 'react';
import {
    useBooking,
    useCancelBooking,
    BookingStatusBadge,
} from '@hfu.digital/roomkit-react';

export default function BookingPage({ params }: { params: Promise<{ id: string }> }) {
    const { id } = use(params);
    const { data: booking, isLoading, refetch } = useBooking({ bookingId: id });
    const cancelBooking = useCancelBooking({
        bookingId: id,
        onSuccess: () => refetch(),
    });

    if (isLoading) return <p>Loading...</p>;
    if (!booking) return <p>Booking not found</p>;

    return (
        <div style={{ padding: '2rem' }}>
            <h1>{booking.title}</h1>
            <BookingStatusBadge status={booking.status} />

            <dl>
                <dt>Room</dt><dd>{booking.roomId}</dd>
                <dt>Start</dt><dd>{booking.startsAt}</dd>
                <dt>End</dt><dd>{booking.endsAt}</dd>
                <dt>Purpose</dt><dd>{booking.purposeType}</dd>
                <dt>Priority</dt><dd>{booking.priority}</dd>
            </dl>

            {booking.status !== 'cancelled' && booking.status !== 'completed' && (
                <button
                    onClick={() => cancelBooking.mutate({ reason: 'Cancelled by user' })}
                    disabled={cancelBooking.isLoading}
                >
                    Cancel Booking
                </button>
            )}
        </div>
    );
}

Location Browser Page

app/locations/page.tsx
'use client';

import { useLocationTree, LocationBrowser } from '@hfu.digital/roomkit-react';

export default function LocationsPage() {
    const { data, isLoading } = useLocationTree();

    if (isLoading) return <p>Loading locations...</p>;

    return (
        <div style={{ padding: '2rem' }}>
            <h1>Locations</h1>
            {data && (
                <LocationBrowser
                    tree={data}
                    onSelect={(node) => {
                        if (node.type === 'room') {
                            window.location.href = `/rooms/${node.id}`;
                        }
                    }}
                />
            )}
        </div>
    );
}

Booking Form Page

app/rooms/[id]/book/page.tsx
'use client';

import { use } from 'react';
import { useCreateBooking, BookingForm } from '@hfu.digital/roomkit-react';

export default function BookRoomPage({ params }: { params: Promise<{ id: string }> }) {
    const { id: roomId } = use(params);
    const createBooking = useCreateBooking({
        onSuccess: (booking) => {
            window.location.href = `/bookings/${booking.id}`;
        },
    });

    return (
        <div style={{ padding: '2rem', maxWidth: '600px' }}>
            <h1>Book Room</h1>
            <BookingForm
                roomId={roomId}
                onSubmit={(data) => createBooking.mutate(data)}
                isSubmitting={createBooking.isLoading}
            />
            {createBooking.error && (
                <p style={{ color: 'red' }}>Error: {createBooking.error.message}</p>
            )}
        </div>
    );
}

Running the Example

git clone https://github.com/hfu-digital/RoomKit.git
cd RoomKit/examples/nextjs-app
bun install
bun run dev

On this page