Basefloor Dev
Frontend

Frontend Overview

Structure and conventions of the Basefloor TanStack Start web application.

Frontend Overview

The Basefloor web application is built with TanStack Start — a full-stack React framework from the TanStack ecosystem. It runs inside the basefloor-UI monorepo.

Repository Structure

basefloor-UI/
├── apps/
│   ├── web/              ← TanStack Start hotel staff app
│   │   ├── src/
│   │   │   ├── routes/       ← File-based routing
│   │   │   ├── components/   ← App-specific components
│   │   │   ├── services/     ← API communication layer
│   │   │   ├── hooks/        ← Custom React hooks
│   │   │   ├── store/        ← Global state (Zustand)
│   │   │   ├── types/        ← TypeScript types
│   │   │   └── utils/        ← Utilities (subdomain, etc.)
│   │   └── .env.example
│   └── marketing/        ← Next.js 15 marketing site
├── packages/
│   ├── ui/               ← @hms/ui shared component library
│   └── shared/           ← @hms/shared utilities and types
└── pnpm-workspace.yaml

Routing

TanStack Start uses file-based routing. Routes are defined in src/routes/:

routes/
├── __root.tsx          ← Root layout (auth check, providers)
├── index.tsx           ← Landing / redirect
├── _auth/              ← Authenticated route group
│   └── $workspace/     ← Dynamic tenant workspace
│       ├── bookings/
│       ├── guests/
│       └── properties/
└── properties/         ← Public property browse

The $workspace segment corresponds to the tenant subdomain slug.

Subdomain Tenant Detection

On app load, the useSubdomain hook extracts the tenant from the current URL:

import { useSubdomain } from '../hooks/useSubdomain'

function App() {
  const { subdomain, client, isLoading, isValid } = useSubdomain()

  if (isLoading) return <Spinner />
  if (!isValid) return <InvalidTenantPage />

  return <Dashboard chain={client} />
}

The hook:

  1. Reads window.location.hostname
  2. Extracts the subdomain (e.g., khyber from khyber.hms.com)
  3. Fetches chain data from the API
  4. Returns the chain context to the component tree

API Layer

All API calls go through src/services/. Each resource has its own service file:

// src/services/bookings.ts
import { apiClient } from '../lib/api'

export const bookingsService = {
  list: (params?: BookingFilters) =>
    apiClient.get<Booking[]>('/bookings', { params }),

  create: (data: CreateBookingInput) =>
    apiClient.post<Booking>('/bookings', data),

  checkIn: (id: number) =>
    apiClient.post(`/bookings/${id}/check_in`),
}

The apiClient is a configured Axios instance that automatically:

  • Adds the Authorization: Bearer <token> header from the auth store
  • Sets the base URL from VITE_API_BASE_URL
  • Handles 401 errors by clearing the session and redirecting to login

State Management

Global state uses Zustand:

// src/store/auth.ts
export const useAuthStore = create<AuthState>((set) => ({
  token: localStorage.getItem('token'),
  user: null,
  login: (token, user) => {
    localStorage.setItem('token', token)
    set({ token, user })
  },
  logout: () => {
    localStorage.removeItem('token')
    set({ token: null, user: null })
  }
}))

Shared UI Components — @hms/ui

All visual components come from the shared @hms/ui package, which wraps shadcn/ui:

import { Button, Card, DataTable } from '@hms/ui'

Each app can have a different color theme while using the same components. The theme is applied via CSS custom properties in the app's globals.css.

To add a new shadcn/ui component to @hms/ui:

cd packages/ui
pnpm dlx shadcn@latest add dialog

Then re-export it from src/index.ts.

Adding a New Route

  1. Create a file in src/routes/ following the naming convention
  2. Export a component as the default export
  3. Export a Route using TanStack's createFileRoute:
// src/routes/_auth/$workspace/reports/index.tsx
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/_auth/$workspace/reports/')({
  component: ReportsPage,
})

function ReportsPage() {
  return <div>Reports</div>
}

TanStack Router will automatically generate the route tree on the next dev server start.

Environment Variables

VariableDescription
VITE_API_BASE_URLBase URL for the hms-core API

In development: http://localhost:4000/api/v1 In production: https://hms-api.kaisersakhi.com/api/v1 (set in Cloudflare dashboard)

On this page