Component Inventory — Frontend

The frontend is built with Leptos 0.8 (CSR + nightly). Components fall into two layers:

  1. Primitives in frontend/src/ui/ — copied (with attribution + license preserved at frontend/src/ui/LICENSE.rust-ui) from the rust-ui MIT-licensed component library plus our own additions.

  2. Feature components in frontend/src/{admin,employee,customer}/ — page-level Leptos components for each surface.


Primitives — frontend/src/ui/

File Component Status

alert.rs

Alert

Inline, signals only

alert_dialog.rs

AlertDialog

⚠️ Needs JS shim (Dialog overlay)

avatar.rs

Avatar

Inline

badge.rs

Badge

Inline

breadcrumb.rs

Breadcrumb

Inline

button.rs

Button

Inline

callout.rs

Callout

Inline (used as toast replacement until Sonner shim lands)

card.rs

Card, CardHeader, CardContent

Inline

checkbox.rs

Checkbox

Inline; exposes on_checked_change (used in Settings page since Switch doesn’t)

dialog.rs

Dialog

⚠️ Compiles + renders, throws at runtime when opened (needs window.ScrollLock JS shim)

dropdown_menu.rs

DropdownMenu

⚠️ Same JS-shim issue as Dialog

input.rs

Input

Inline

label.rs

Label

Inline

popover.rs

Popover

⚠️ Same JS-shim issue

separator.rs

Separator

Inline

sidebar.rs

Sidebar

Hand-rolled (not from rust-ui). Icon-free; compact prop shrinks the drawer width

sonner.rs

Sonner

⚠️ Markup-only — toast logic lives in upstream JS we didn’t pull

switch.rs

Switch

Internal-state only; no on_checked_change callback (use Checkbox for parent-driven toggles)

tabs.rs

Tabs, TabList, Tab, TabPanel

Inline

textarea.rs

Textarea

Inline

tooltip.rs

Tooltip

Inline

hooks/

rust-ui hooks

One hook (see source)

⚠️ items are tracked in docs/follow-ups.md under "Frontend — UI follow-ups".

Tailwind v4

Trunk pulls the v4 standalone CLI (pinned in Trunk.toml). rust-ui components target v4-only syntax (@theme, oklch() colors, data-[state=…] variants, size-N, field-sizing-content) — downgrading to v3 silently breaks the visual design.


Surface 1 — Admin (frontend/src/admin/)

10 routed pages under /admin/*, all wrapped by AdminShell (sidebar + header).

Route Component File

/admin/dashboard

Dashboard

dashboard.rs

/admin/events

EventList

events/list.rs

/admin/events/:id

EventDetail

events/detail.rs

/admin/shifts

ShiftCalendar

shifts/calendar.rs (placeholder pointing at /shifts/list)

/admin/shifts/list

ShiftList

shifts/list.rs

/admin/shifts/:id

ShiftDetail

shifts/detail.rs (slots tab live, assignments tab is "coming soon" Callout)

/admin/employees

EmployeeList

employees/list.rs

/admin/employees/:id

EmployeeDetail

employees/detail.rs (profile + qualifications tabs)

/admin/forms

FormList

forms/list.rs

/admin/forms/new and /admin/forms/:id

FormBuilder

forms/builder.rs

/admin/forms/:id/submissions

FormSubmissions

forms/submissions.rs

/admin/locations

Locations

locations.rs

/admin/roles

Roles

roles.rs

/admin/settings

Settings

settings.rs

/admin/ redirects to /admin/dashboard. Shell provides the navigation chrome via Sidebar.

Surface 2 — Employee (frontend/src/employee/)

5 routed pages under /app/*, wrapped by EmployeeShell.

Route Component File

/app/schedule

Schedule

schedule.rs

/app/shifts

AvailableShifts

available.rs (claim button still passes shift_id as placeholder slot id — slot-picker UI is the second P0 follow-up)

/app/overtime

Overtime

overtime.rs

/app/notifications

Notifications

notifications.rs (browser-permission request works; PushManager.subscribe() flow stubbed pending VAPID key)

/app/ redirects to /app/schedule.

Surface 3 — Customer (frontend/src/customer/)

Single route /f/:slug, no shell — the page is the form.

  • form_page.rsCustomerFormPage — fetches the form by slug, renders via form_renderer.rs.

  • form_renderer.rsFormRenderer — drives the field render loop.

  • fields/ — per-field-type components:

    • text.rs — TextField

    • number.rs — NumberField

    • date_field.rs — DateField

    • select.rs — SelectField (pre-selected option not yet visually reflected — see follow-up)

    • location.rs — LocationField (uses default_location_id as initial value; cleared selection sticks — see follow-up)


Cross-cutting modules

  • frontend/src/api/ — backend client. client.rs exposes api_get, api_post, api_patch, api_delete, all attaching the JWT from local storage. types.rs mirrors backend DTOs. mod.rs re-exports.

  • frontend/src/auth/ — provide_auth_context puts a Resource<Option<MeResponse>> into Leptos context. get_token reads shiftit_token from local storage via gloo-storage.

  • frontend/src/app.rs — App component: provides auth context, declares the router. The router lives entirely in this one file (~60 lines).

  • frontend/src/main.rs / lib.rs — wasm-bindgen entry points.

Patterns the next session should know

  • Fetching: LocalResource::new(fetcher). gloo-net futures are !Send on wasm32 so the local variant is mandatory.

  • Mutating: Action::new_local. Same !Send reason.

  • Refetch after mutate: version signal incremented in an Effect, used in the LocalResource's key. Already used by EmployeeDetail and ShiftDetail (Phase 2). Roles, Locations, FormList still need this — see follow-up "Optimistic UI / refetch after mutation".

  • Iteration in views: <For each=… key=… children=…> (the view= prop was renamed in 0.8).

  • Anchor classes: <A attr:class="…"> (not class=).

  • view! macro limits: does not accept turbofish or tuple destructuring in closure args.