@withpotter/pages-bookings 0.13.0-next.101
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dashboard/(bookings)/attendees/page.js +8 -0
- package/dist/dashboard/(bookings)/attendees/page.js.map +1 -0
- package/dist/dashboard/(bookings)/availability/_components/availability-screen.js +714 -0
- package/dist/dashboard/(bookings)/availability/_components/availability-screen.js.map +1 -0
- package/dist/dashboard/(bookings)/availability/_components/month-availability.js +130 -0
- package/dist/dashboard/(bookings)/availability/_components/month-availability.js.map +1 -0
- package/dist/dashboard/(bookings)/availability/page.js +10 -0
- package/dist/dashboard/(bookings)/availability/page.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/assign-resource-modal.js +133 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/assign-resource-modal.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/booking-constants.js +26 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/booking-constants.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/booking-desktop-layout.js +194 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/booking-desktop-layout.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/booking-layout-props.js +1 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/booking-layout-props.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/booking-lede.js +189 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/booking-lede.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/booking-meta.js +38 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/booking-meta.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/booking-mobile-layout.js +315 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/booking-mobile-layout.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/booking-mobile-screen.js +94 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/booking-mobile-screen.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/booking-money.js +54 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/booking-money.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/booking-split.js +71 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/booking-split.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/cancel-booking-modal.js +102 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/cancel-booking-modal.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/cancellation-notice.js +33 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/cancellation-notice.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/customer-info-card.js +65 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/customer-info-card.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/index.js +23 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/index.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/intake-answers-card.js +19 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/intake-answers-card.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/refund-booking-modal.js +156 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/refund-booking-modal.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/reschedule-booking-modal.js +120 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/reschedule-booking-modal.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/security-deposit-card.js +63 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/security-deposit-card.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/settle-deposit-modal.js +141 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/settle-deposit-modal.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/ticket-row-actions.js +49 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/ticket-row-actions.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/waiting-queue-notice.js +42 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components/waiting-queue-notice.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_components.js +1 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_hooks/index.js +19 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_hooks/index.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_hooks/use-booking-detail-data.js +352 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_hooks/use-booking-detail-data.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/_hooks.js +1 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/layout.js +33 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/layout.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/page.js +356 -0
- package/dist/dashboard/(bookings)/bookings/[bookingId]/page.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/_components/appointment-calendar-layout.js +34 -0
- package/dist/dashboard/(bookings)/bookings/_components/appointment-calendar-layout.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/_components/booking-constants.js +92 -0
- package/dist/dashboard/(bookings)/bookings/_components/booking-constants.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/_components/booking-tab-hints.js +32 -0
- package/dist/dashboard/(bookings)/bookings/_components/booking-tab-hints.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/_components/bookings-filter-bar.js +131 -0
- package/dist/dashboard/(bookings)/bookings/_components/bookings-filter-bar.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/_components/bookings-mobile-controls.js +119 -0
- package/dist/dashboard/(bookings)/bookings/_components/bookings-mobile-controls.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/_components/bookings-screen.js +414 -0
- package/dist/dashboard/(bookings)/bookings/_components/bookings-screen.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/_components/bookings-table.js +192 -0
- package/dist/dashboard/(bookings)/bookings/_components/bookings-table.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/_components/cannot-take-bookings.js +34 -0
- package/dist/dashboard/(bookings)/bookings/_components/cannot-take-bookings.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/_components/demo-data.js +176 -0
- package/dist/dashboard/(bookings)/bookings/_components/demo-data.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/_components/index.js +9 -0
- package/dist/dashboard/(bookings)/bookings/_components/index.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/_components/today/booking-card.js +126 -0
- package/dist/dashboard/(bookings)/bookings/_components/today/booking-card.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/_components/today/demo-data.js +142 -0
- package/dist/dashboard/(bookings)/bookings/_components/today/demo-data.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/_components/today/handover.js +128 -0
- package/dist/dashboard/(bookings)/bookings/_components/today/handover.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/_components/today/index.js +17 -0
- package/dist/dashboard/(bookings)/bookings/_components/today/index.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/_components/today/past-band.js +69 -0
- package/dist/dashboard/(bookings)/bookings/_components/today/past-band.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/_components/today/resource-board.js +111 -0
- package/dist/dashboard/(bookings)/bookings/_components/today/resource-board.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/_components/today/today-band.js +61 -0
- package/dist/dashboard/(bookings)/bookings/_components/today/today-band.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/_components/today/upcoming-band.js +113 -0
- package/dist/dashboard/(bookings)/bookings/_components/today/upcoming-band.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/_components/today/waiting-queue.js +62 -0
- package/dist/dashboard/(bookings)/bookings/_components/today/waiting-queue.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/_components/today-board.js +192 -0
- package/dist/dashboard/(bookings)/bookings/_components/today-board.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/_components/today.js +1 -0
- package/dist/dashboard/(bookings)/bookings/_components.js +1 -0
- package/dist/dashboard/(bookings)/bookings/_hooks/index.js +5 -0
- package/dist/dashboard/(bookings)/bookings/_hooks/index.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/_hooks/use-bookings-data.js +331 -0
- package/dist/dashboard/(bookings)/bookings/_hooks/use-bookings-data.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/_hooks/use-today-data.js +309 -0
- package/dist/dashboard/(bookings)/bookings/_hooks/use-today-data.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/_hooks.js +1 -0
- package/dist/dashboard/(bookings)/bookings/_lib/booking-link.js +13 -0
- package/dist/dashboard/(bookings)/bookings/_lib/booking-link.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/attendees/_components/attendee-cards.js +60 -0
- package/dist/dashboard/(bookings)/bookings/attendees/_components/attendee-cards.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/attendees/_components/qr-scanner-modal.js +225 -0
- package/dist/dashboard/(bookings)/bookings/attendees/_components/qr-scanner-modal.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/attendees/demo-data.js +195 -0
- package/dist/dashboard/(bookings)/bookings/attendees/demo-data.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/attendees/page.js +341 -0
- package/dist/dashboard/(bookings)/bookings/attendees/page.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/new/page.js +129 -0
- package/dist/dashboard/(bookings)/bookings/new/page.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/page.js +10 -0
- package/dist/dashboard/(bookings)/bookings/page.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/sessions/new/_components/save-buttons.js +36 -0
- package/dist/dashboard/(bookings)/bookings/sessions/new/_components/save-buttons.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/sessions/new/_components/session-creator.js +386 -0
- package/dist/dashboard/(bookings)/bookings/sessions/new/_components/session-creator.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/sessions/new/_lib/session-payload.js +62 -0
- package/dist/dashboard/(bookings)/bookings/sessions/new/_lib/session-payload.js.map +1 -0
- package/dist/dashboard/(bookings)/bookings/sessions/new/page.js +10 -0
- package/dist/dashboard/(bookings)/bookings/sessions/new/page.js.map +1 -0
- package/dist/dashboard/(bookings)/calendar/_components/calendar-nav.js +84 -0
- package/dist/dashboard/(bookings)/calendar/_components/calendar-nav.js.map +1 -0
- package/dist/dashboard/(bookings)/calendar/_components/calendar-range.js +84 -0
- package/dist/dashboard/(bookings)/calendar/_components/calendar-range.js.map +1 -0
- package/dist/dashboard/(bookings)/calendar/_components/day-board-view.js +279 -0
- package/dist/dashboard/(bookings)/calendar/_components/day-board-view.js.map +1 -0
- package/dist/dashboard/(bookings)/calendar/_components/event-detail-modal.js +113 -0
- package/dist/dashboard/(bookings)/calendar/_components/event-detail-modal.js.map +1 -0
- package/dist/dashboard/(bookings)/calendar/_components/format.js +22 -0
- package/dist/dashboard/(bookings)/calendar/_components/format.js.map +1 -0
- package/dist/dashboard/(bookings)/calendar/_components/month-grid.js +109 -0
- package/dist/dashboard/(bookings)/calendar/_components/month-grid.js.map +1 -0
- package/dist/dashboard/(bookings)/calendar/_components/resource-day-grid.js +325 -0
- package/dist/dashboard/(bookings)/calendar/_components/resource-day-grid.js.map +1 -0
- package/dist/dashboard/(bookings)/calendar/_components/slot-action-sheet.js +209 -0
- package/dist/dashboard/(bookings)/calendar/_components/slot-action-sheet.js.map +1 -0
- package/dist/dashboard/(bookings)/calendar/_components/use-block-drag.js +77 -0
- package/dist/dashboard/(bookings)/calendar/_components/use-block-drag.js.map +1 -0
- package/dist/dashboard/(bookings)/calendar/_components/use-calendar-nav.js +69 -0
- package/dist/dashboard/(bookings)/calendar/_components/use-calendar-nav.js.map +1 -0
- package/dist/dashboard/(bookings)/calendar/_components/week-grid.js +202 -0
- package/dist/dashboard/(bookings)/calendar/_components/week-grid.js.map +1 -0
- package/dist/dashboard/(bookings)/calendar/_components/year-grid.js +111 -0
- package/dist/dashboard/(bookings)/calendar/_components/year-grid.js.map +1 -0
- package/dist/dashboard/(bookings)/calendar/page.js +8 -0
- package/dist/dashboard/(bookings)/calendar/page.js.map +1 -0
- package/dist/dashboard/(bookings)/calendar-sync/_components/channel-feed-modal.js +126 -0
- package/dist/dashboard/(bookings)/calendar-sync/_components/channel-feed-modal.js.map +1 -0
- package/dist/dashboard/(bookings)/calendar-sync/_components/channel-feeds-section.js +99 -0
- package/dist/dashboard/(bookings)/calendar-sync/_components/channel-feeds-section.js.map +1 -0
- package/dist/dashboard/(bookings)/calendar-sync/demo-data.js +64 -0
- package/dist/dashboard/(bookings)/calendar-sync/demo-data.js.map +1 -0
- package/dist/dashboard/(bookings)/calendar-sync/page.js +15 -0
- package/dist/dashboard/(bookings)/calendar-sync/page.js.map +1 -0
- package/dist/dashboard/(bookings)/check-in/page.js +252 -0
- package/dist/dashboard/(bookings)/check-in/page.js.map +1 -0
- package/dist/dashboard/(bookings)/events/[eventId]/_components/event-attendees.js +108 -0
- package/dist/dashboard/(bookings)/events/[eventId]/_components/event-attendees.js.map +1 -0
- package/dist/dashboard/(bookings)/events/[eventId]/_components/event-share.js +73 -0
- package/dist/dashboard/(bookings)/events/[eventId]/_components/event-share.js.map +1 -0
- package/dist/dashboard/(bookings)/events/[eventId]/layout.js +10 -0
- package/dist/dashboard/(bookings)/events/[eventId]/layout.js.map +1 -0
- package/dist/dashboard/(bookings)/events/[eventId]/page.js +247 -0
- package/dist/dashboard/(bookings)/events/[eventId]/page.js.map +1 -0
- package/dist/dashboard/(bookings)/events/_components/event-grid.js +44 -0
- package/dist/dashboard/(bookings)/events/_components/event-grid.js.map +1 -0
- package/dist/dashboard/(bookings)/events/_components/event-poster.js +17 -0
- package/dist/dashboard/(bookings)/events/_components/event-poster.js.map +1 -0
- package/dist/dashboard/(bookings)/events/_hooks/use-events-data.js +55 -0
- package/dist/dashboard/(bookings)/events/_hooks/use-events-data.js.map +1 -0
- package/dist/dashboard/(bookings)/events/_lib/event-facts.js +68 -0
- package/dist/dashboard/(bookings)/events/_lib/event-facts.js.map +1 -0
- package/dist/dashboard/(bookings)/events/new/_components/event-composer.js +295 -0
- package/dist/dashboard/(bookings)/events/new/_components/event-composer.js.map +1 -0
- package/dist/dashboard/(bookings)/events/new/_components/event-poster.js +65 -0
- package/dist/dashboard/(bookings)/events/new/_components/event-poster.js.map +1 -0
- package/dist/dashboard/(bookings)/events/new/_components/ticket-tiers.js +75 -0
- package/dist/dashboard/(bookings)/events/new/_components/ticket-tiers.js.map +1 -0
- package/dist/dashboard/(bookings)/events/new/page.js +10 -0
- package/dist/dashboard/(bookings)/events/new/page.js.map +1 -0
- package/dist/dashboard/(bookings)/events/page.js +214 -0
- package/dist/dashboard/(bookings)/events/page.js.map +1 -0
- package/dist/dashboard/(bookings)/resources/[resourceId]/layout.js +36 -0
- package/dist/dashboard/(bookings)/resources/[resourceId]/layout.js.map +1 -0
- package/dist/dashboard/(bookings)/resources/[resourceId]/page.js +351 -0
- package/dist/dashboard/(bookings)/resources/[resourceId]/page.js.map +1 -0
- package/dist/dashboard/(bookings)/resources/_components/block-time-modal.js +130 -0
- package/dist/dashboard/(bookings)/resources/_components/block-time-modal.js.map +1 -0
- package/dist/dashboard/(bookings)/resources/_components/category-card.js +74 -0
- package/dist/dashboard/(bookings)/resources/_components/category-card.js.map +1 -0
- package/dist/dashboard/(bookings)/resources/_components/category-form-dialog.js +197 -0
- package/dist/dashboard/(bookings)/resources/_components/category-form-dialog.js.map +1 -0
- package/dist/dashboard/(bookings)/resources/_components/day-editor.js +194 -0
- package/dist/dashboard/(bookings)/resources/_components/day-editor.js.map +1 -0
- package/dist/dashboard/(bookings)/resources/_components/group-dialog.js +170 -0
- package/dist/dashboard/(bookings)/resources/_components/group-dialog.js.map +1 -0
- package/dist/dashboard/(bookings)/resources/_components/group-header.js +90 -0
- package/dist/dashboard/(bookings)/resources/_components/group-header.js.map +1 -0
- package/dist/dashboard/(bookings)/resources/_components/index.js +22 -0
- package/dist/dashboard/(bookings)/resources/_components/index.js.map +1 -0
- package/dist/dashboard/(bookings)/resources/_components/mobile-category-form-sheet.js +256 -0
- package/dist/dashboard/(bookings)/resources/_components/mobile-category-form-sheet.js.map +1 -0
- package/dist/dashboard/(bookings)/resources/_components/resource-form.js +420 -0
- package/dist/dashboard/(bookings)/resources/_components/resource-form.js.map +1 -0
- package/dist/dashboard/(bookings)/resources/_components/resource-picker.js +144 -0
- package/dist/dashboard/(bookings)/resources/_components/resource-picker.js.map +1 -0
- package/dist/dashboard/(bookings)/resources/_components/resource-type-picker-empty.js +109 -0
- package/dist/dashboard/(bookings)/resources/_components/resource-type-picker-empty.js.map +1 -0
- package/dist/dashboard/(bookings)/resources/_components/roster-empty.js +103 -0
- package/dist/dashboard/(bookings)/resources/_components/roster-empty.js.map +1 -0
- package/dist/dashboard/(bookings)/resources/_components/roster-grid.js +160 -0
- package/dist/dashboard/(bookings)/resources/_components/roster-grid.js.map +1 -0
- package/dist/dashboard/(bookings)/resources/_components/schedule-editor.js +244 -0
- package/dist/dashboard/(bookings)/resources/_components/schedule-editor.js.map +1 -0
- package/dist/dashboard/(bookings)/resources/_components/schedule-timezone-field.js +25 -0
- package/dist/dashboard/(bookings)/resources/_components/schedule-timezone-field.js.map +1 -0
- package/dist/dashboard/(bookings)/resources/_components/solo-schedule-view.js +146 -0
- package/dist/dashboard/(bookings)/resources/_components/solo-schedule-view.js.map +1 -0
- package/dist/dashboard/(bookings)/resources/_components/team-onboarding-dialog.js +161 -0
- package/dist/dashboard/(bookings)/resources/_components/team-onboarding-dialog.js.map +1 -0
- package/dist/dashboard/(bookings)/resources/_components/week-grid.js +175 -0
- package/dist/dashboard/(bookings)/resources/_components/week-grid.js.map +1 -0
- package/dist/dashboard/(bookings)/resources/_components.js +1 -0
- package/dist/dashboard/(bookings)/resources/_hooks/index.js +8 -0
- package/dist/dashboard/(bookings)/resources/_hooks/index.js.map +1 -0
- package/dist/dashboard/(bookings)/resources/_hooks/use-resource-categories-data.js +86 -0
- package/dist/dashboard/(bookings)/resources/_hooks/use-resource-categories-data.js.map +1 -0
- package/dist/dashboard/(bookings)/resources/_hooks/use-roster-page.js +138 -0
- package/dist/dashboard/(bookings)/resources/_hooks/use-roster-page.js.map +1 -0
- package/dist/dashboard/(bookings)/resources/_hooks/use-roster.js +182 -0
- package/dist/dashboard/(bookings)/resources/_hooks/use-roster.js.map +1 -0
- package/dist/dashboard/(bookings)/resources/_hooks.js +1 -0
- package/dist/dashboard/(bookings)/resources/_lib/presets.js +147 -0
- package/dist/dashboard/(bookings)/resources/_lib/presets.js.map +1 -0
- package/dist/dashboard/(bookings)/resources/_lib/schedule-timezone.js +17 -0
- package/dist/dashboard/(bookings)/resources/_lib/schedule-timezone.js.map +1 -0
- package/dist/dashboard/(bookings)/resources/_lib/shapes.js +105 -0
- package/dist/dashboard/(bookings)/resources/_lib/shapes.js.map +1 -0
- package/dist/dashboard/(bookings)/resources/_lib/week.js +122 -0
- package/dist/dashboard/(bookings)/resources/_lib/week.js.map +1 -0
- package/dist/dashboard/(bookings)/resources/page.js +391 -0
- package/dist/dashboard/(bookings)/resources/page.js.map +1 -0
- package/dist/dashboard/(bookings)/schedule/page.js +8 -0
- package/dist/dashboard/(bookings)/schedule/page.js.map +1 -0
- package/dist/dashboard/(bookings)/today/page.js +14 -0
- package/dist/dashboard/(bookings)/today/page.js.map +1 -0
- package/dist/dashboard/(bookings)/waitlist/_components/demo-data.js +99 -0
- package/dist/dashboard/(bookings)/waitlist/_components/demo-data.js.map +1 -0
- package/dist/dashboard/(bookings)/waitlist/_components/offer-spot-modal.js +71 -0
- package/dist/dashboard/(bookings)/waitlist/_components/offer-spot-modal.js.map +1 -0
- package/dist/dashboard/(bookings)/waitlist/_components/waitlist-entry-editor.js +150 -0
- package/dist/dashboard/(bookings)/waitlist/_components/waitlist-entry-editor.js.map +1 -0
- package/dist/dashboard/(bookings)/waitlist/new/page.js +59 -0
- package/dist/dashboard/(bookings)/waitlist/new/page.js.map +1 -0
- package/dist/dashboard/(bookings)/waitlist/page.js +317 -0
- package/dist/dashboard/(bookings)/waitlist/page.js.map +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -0
- package/package.json +60 -0
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useId, useMemo, useState } from "react";
|
|
4
|
+
import { useRouter } from "next/navigation";
|
|
5
|
+
import { BadgeCheck, CalendarClock, Tag } from "lucide-react";
|
|
6
|
+
import { DetailPage } from "@withpotter/ui-kit/detail-page";
|
|
7
|
+
import { Card } from "@withpotter/ui-kit/card";
|
|
8
|
+
import { Button } from "@withpotter/ui-kit/button";
|
|
9
|
+
import { SaveButtons } from "./save-buttons";
|
|
10
|
+
import {
|
|
11
|
+
Input,
|
|
12
|
+
Textarea,
|
|
13
|
+
Select,
|
|
14
|
+
CurrencyInput,
|
|
15
|
+
Dialog,
|
|
16
|
+
DialogContent,
|
|
17
|
+
DialogDescription,
|
|
18
|
+
DialogFooter,
|
|
19
|
+
DialogHeader,
|
|
20
|
+
DialogTitle,
|
|
21
|
+
NumberField,
|
|
22
|
+
OptionButton
|
|
23
|
+
} from "@withpotter/ui-kit";
|
|
24
|
+
import { BookingLocationField } from "@withpotter/components/behaviors";
|
|
25
|
+
import { ServiceStaffingSection } from "@withpotter/components/behaviors/service-staffing-section";
|
|
26
|
+
import { WeeklyHoursEditor } from "@withpotter/components/features/weekly-hours-editor";
|
|
27
|
+
import { useHoursData } from "@withpotter/hooks/use-hours-data";
|
|
28
|
+
import { summariseHours } from "@withpotter/pages-bookings/dashboard/(bookings)/resources/_lib/week";
|
|
29
|
+
import { useToast } from "@withpotter/ui-kit/use-toast";
|
|
30
|
+
import { useAuth } from "@withpotter/lib/auth-context";
|
|
31
|
+
import { useTenantCurrency } from "@withpotter/hooks/use-tenant-currency";
|
|
32
|
+
import { inventoryApi } from "@withpotter/lib/api";
|
|
33
|
+
import { resourceCategoriesApi } from "@withpotter/lib/api/resource-categories";
|
|
34
|
+
import { useApiQuery } from "@withpotter/hooks/use-api-query";
|
|
35
|
+
import {
|
|
36
|
+
buildSessionPayload,
|
|
37
|
+
sessionIsPublishable
|
|
38
|
+
} from "../_lib/session-payload";
|
|
39
|
+
const DURATION_OPTIONS = [10, 15, 20, 30, 45, 60, 90, 120];
|
|
40
|
+
function durationLabel(minutes) {
|
|
41
|
+
if (minutes < 60) return `${minutes} minutes`;
|
|
42
|
+
if (minutes === 60) return "1 hour";
|
|
43
|
+
return `${minutes / 60} hours`;
|
|
44
|
+
}
|
|
45
|
+
function SessionCreator() {
|
|
46
|
+
const router = useRouter();
|
|
47
|
+
const { tenant } = useAuth();
|
|
48
|
+
const { toast } = useToast();
|
|
49
|
+
const currency = useTenantCurrency();
|
|
50
|
+
const hours = useHoursData();
|
|
51
|
+
const priceIndicatorId = useId();
|
|
52
|
+
const [name, setName] = useState("");
|
|
53
|
+
const [description, setDescription] = useState("");
|
|
54
|
+
const [durationMinutes, setDurationMinutes] = useState(30);
|
|
55
|
+
const [paid, setPaid] = useState(false);
|
|
56
|
+
const [price, setPrice] = useState(null);
|
|
57
|
+
const [cancellationHours, setCancellationHours] = useState(
|
|
58
|
+
null
|
|
59
|
+
);
|
|
60
|
+
const [location, setLocation] = useState();
|
|
61
|
+
const [bookableConfig, setBookableConfig] = useState({
|
|
62
|
+
enabled: true,
|
|
63
|
+
bookingMode: "duration",
|
|
64
|
+
durationType: "fixed",
|
|
65
|
+
assignmentStrategy: "auto-first"
|
|
66
|
+
});
|
|
67
|
+
const [resourceRequirements, setResourceRequirements] = useState([]);
|
|
68
|
+
const [editingHours, setEditingHours] = useState(false);
|
|
69
|
+
const [showBookingRules, setShowBookingRules] = useState(false);
|
|
70
|
+
const [showBottomActions, setShowBottomActions] = useState(false);
|
|
71
|
+
const [saving, setSaving] = useState(false);
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
const handleScroll = (event) => {
|
|
74
|
+
const target = event.target;
|
|
75
|
+
const scrollTop = target instanceof HTMLElement ? target.scrollTop : window.scrollY;
|
|
76
|
+
setShowBottomActions(scrollTop > 120);
|
|
77
|
+
};
|
|
78
|
+
document.addEventListener("scroll", handleScroll, {
|
|
79
|
+
passive: true,
|
|
80
|
+
capture: true
|
|
81
|
+
});
|
|
82
|
+
return () => document.removeEventListener("scroll", handleScroll, { capture: true });
|
|
83
|
+
}, []);
|
|
84
|
+
const { data: resourceCategories = [] } = useApiQuery(
|
|
85
|
+
["session-resource-categories"],
|
|
86
|
+
(slug) => resourceCategoriesApi.list(slug)
|
|
87
|
+
);
|
|
88
|
+
const staffingCategories = useMemo(
|
|
89
|
+
() => resourceCategories.map((category) => ({
|
|
90
|
+
id: category.id,
|
|
91
|
+
label: category.label || category.name || "",
|
|
92
|
+
labelPlural: category.labelPlural || "",
|
|
93
|
+
kind: category.kind || "person"
|
|
94
|
+
})),
|
|
95
|
+
[resourceCategories]
|
|
96
|
+
);
|
|
97
|
+
const answers = {
|
|
98
|
+
name,
|
|
99
|
+
description,
|
|
100
|
+
durationMinutes,
|
|
101
|
+
price: paid ? price : 0,
|
|
102
|
+
bufferAfter: null,
|
|
103
|
+
advanceBookingDays: null,
|
|
104
|
+
cancellationHours,
|
|
105
|
+
location,
|
|
106
|
+
currency,
|
|
107
|
+
assignmentStrategy: bookableConfig.assignmentStrategy ?? "auto-first"
|
|
108
|
+
};
|
|
109
|
+
const hasValidPrice = !paid || price !== null && Number.isFinite(price) && price >= 0;
|
|
110
|
+
const publishable = sessionIsPublishable(answers) && hasValidPrice;
|
|
111
|
+
const canPublish = !saving;
|
|
112
|
+
const [showErrors, setShowErrors] = useState(false);
|
|
113
|
+
const nameMissing = showErrors && name.trim().length === 0;
|
|
114
|
+
const publish = async (asDraft = false) => {
|
|
115
|
+
if (!tenant?.slug || !canPublish) return;
|
|
116
|
+
if (!publishable) {
|
|
117
|
+
setShowErrors(true);
|
|
118
|
+
if (name.trim().length === 0) {
|
|
119
|
+
document.getElementById("service-name")?.focus();
|
|
120
|
+
}
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
setSaving(true);
|
|
124
|
+
try {
|
|
125
|
+
if (hours.dirty) await hours.save();
|
|
126
|
+
const created = await inventoryApi.create(
|
|
127
|
+
tenant.slug,
|
|
128
|
+
{
|
|
129
|
+
...buildSessionPayload(answers, !asDraft),
|
|
130
|
+
// Without its model named, an engine that does not infer one saves
|
|
131
|
+
// the service with none, and the Bookings list, which asks for this
|
|
132
|
+
// model's items, never shows what was just made.
|
|
133
|
+
intent: "take-bookings"
|
|
134
|
+
}
|
|
135
|
+
);
|
|
136
|
+
const staffCategory = staffingCategories.find(
|
|
137
|
+
(category) => category.kind === "person"
|
|
138
|
+
);
|
|
139
|
+
const requirements = staffCategory ? [
|
|
140
|
+
...resourceRequirements.filter(
|
|
141
|
+
(requirement) => requirement.categoryId !== staffCategory.id
|
|
142
|
+
),
|
|
143
|
+
{
|
|
144
|
+
categoryId: staffCategory.id,
|
|
145
|
+
isRequired: true,
|
|
146
|
+
eligibleResourceIds: resourceRequirements.find(
|
|
147
|
+
(requirement) => requirement.categoryId === staffCategory.id
|
|
148
|
+
)?.eligibleResourceIds ?? null
|
|
149
|
+
}
|
|
150
|
+
] : resourceRequirements;
|
|
151
|
+
if (requirements.length > 0) {
|
|
152
|
+
await resourceCategoriesApi.setContentRequirements(
|
|
153
|
+
tenant.slug,
|
|
154
|
+
created.id,
|
|
155
|
+
{ requirements }
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
toast({
|
|
159
|
+
title: asDraft ? "Draft saved" : `${name.trim()} is live`,
|
|
160
|
+
description: asDraft ? "You can finish the setup and publish it later." : "People can book it on your site."
|
|
161
|
+
});
|
|
162
|
+
router.push(`/dashboard/booking-services/${created.id}`);
|
|
163
|
+
} catch (err) {
|
|
164
|
+
toast({
|
|
165
|
+
title: "Couldn't publish",
|
|
166
|
+
description: err instanceof Error ? err.message : "Please try again in a moment.",
|
|
167
|
+
variant: "destructive"
|
|
168
|
+
});
|
|
169
|
+
setSaving(false);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
return /* @__PURE__ */ jsx(
|
|
173
|
+
DetailPage,
|
|
174
|
+
{
|
|
175
|
+
breadcrumbs: [
|
|
176
|
+
{
|
|
177
|
+
label: "Services",
|
|
178
|
+
href: "/dashboard/catalog",
|
|
179
|
+
icon: /* @__PURE__ */ jsx(CalendarClock, {})
|
|
180
|
+
}
|
|
181
|
+
],
|
|
182
|
+
title: "New service",
|
|
183
|
+
mobileActions: !showBottomActions ? /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1", children: /* @__PURE__ */ jsx(
|
|
184
|
+
SaveButtons,
|
|
185
|
+
{
|
|
186
|
+
saving,
|
|
187
|
+
size: "sm",
|
|
188
|
+
onSave: (asDraft) => void publish(asDraft)
|
|
189
|
+
}
|
|
190
|
+
) }) : void 0,
|
|
191
|
+
actions: /* @__PURE__ */ jsx(
|
|
192
|
+
SaveButtons,
|
|
193
|
+
{
|
|
194
|
+
saving,
|
|
195
|
+
buttonClassName: "flex-1",
|
|
196
|
+
onSave: (asDraft) => void publish(asDraft)
|
|
197
|
+
}
|
|
198
|
+
),
|
|
199
|
+
maxWidth: "max-w-4xl",
|
|
200
|
+
children: /* @__PURE__ */ jsxs("div", { className: "space-y-4 pb-24 lg:pb-0", children: [
|
|
201
|
+
/* @__PURE__ */ jsxs(Card, { className: "p-4", children: [
|
|
202
|
+
/* @__PURE__ */ jsx("h2", { className: "mb-3 text-base font-semibold text-[var(--text-primary)]", children: "What are people booking?" }),
|
|
203
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
204
|
+
/* @__PURE__ */ jsx(
|
|
205
|
+
Input,
|
|
206
|
+
{
|
|
207
|
+
id: "service-name",
|
|
208
|
+
label: "Name",
|
|
209
|
+
value: name,
|
|
210
|
+
onChange: (e) => setName(e.target.value),
|
|
211
|
+
error: nameMissing ? "Give this service a name." : void 0,
|
|
212
|
+
placeholder: "30-minute consultation"
|
|
213
|
+
}
|
|
214
|
+
),
|
|
215
|
+
/* @__PURE__ */ jsx(
|
|
216
|
+
Textarea,
|
|
217
|
+
{
|
|
218
|
+
label: "Description",
|
|
219
|
+
rows: 2,
|
|
220
|
+
value: description,
|
|
221
|
+
onChange: (e) => setDescription(e.target.value),
|
|
222
|
+
placeholder: "What the session covers"
|
|
223
|
+
}
|
|
224
|
+
),
|
|
225
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
226
|
+
/* @__PURE__ */ jsx("label", { className: "mb-1 block text-sm font-medium text-[var(--text-secondary)]", children: "Duration" }),
|
|
227
|
+
/* @__PURE__ */ jsx(
|
|
228
|
+
Select,
|
|
229
|
+
{
|
|
230
|
+
value: String(durationMinutes ?? 30),
|
|
231
|
+
onChange: (value) => setDurationMinutes(Number(value)),
|
|
232
|
+
options: DURATION_OPTIONS.map((minutes) => ({
|
|
233
|
+
value: String(minutes),
|
|
234
|
+
label: durationLabel(minutes)
|
|
235
|
+
})),
|
|
236
|
+
ariaLabel: "Duration"
|
|
237
|
+
}
|
|
238
|
+
)
|
|
239
|
+
] })
|
|
240
|
+
] })
|
|
241
|
+
] }),
|
|
242
|
+
/* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsx(
|
|
243
|
+
ServiceStaffingSection,
|
|
244
|
+
{
|
|
245
|
+
config: bookableConfig,
|
|
246
|
+
resourceCategories: staffingCategories,
|
|
247
|
+
resourceRequirements,
|
|
248
|
+
onConfigChange: setBookableConfig,
|
|
249
|
+
onResourceRequirementsChange: setResourceRequirements
|
|
250
|
+
}
|
|
251
|
+
) }),
|
|
252
|
+
/* @__PURE__ */ jsxs(Card, { className: "p-4", children: [
|
|
253
|
+
/* @__PURE__ */ jsx("h2", { className: "mb-3 text-base font-semibold text-[var(--text-primary)]", children: "Availability" }),
|
|
254
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
|
|
255
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm text-[var(--text-secondary)]", children: [
|
|
256
|
+
summariseHours(hours.hours),
|
|
257
|
+
/* @__PURE__ */ jsxs("span", { className: "text-[var(--text-muted)]", children: [
|
|
258
|
+
" \xB7 ",
|
|
259
|
+
hours.hours.timezone
|
|
260
|
+
] })
|
|
261
|
+
] }),
|
|
262
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => setEditingHours(true), children: "Edit hours" })
|
|
263
|
+
] }),
|
|
264
|
+
/* @__PURE__ */ jsx(Dialog, { open: editingHours, onOpenChange: setEditingHours, children: /* @__PURE__ */ jsxs(DialogContent, { className: "flex max-h-[min(680px,calc(100dvh-2rem))] max-w-2xl flex-col overflow-hidden", children: [
|
|
265
|
+
/* @__PURE__ */ jsxs(DialogHeader, { children: [
|
|
266
|
+
/* @__PURE__ */ jsx(DialogTitle, { children: "Edit weekly hours" }),
|
|
267
|
+
/* @__PURE__ */ jsx(DialogDescription, { children: "Choose when customers can book this service." })
|
|
268
|
+
] }),
|
|
269
|
+
/* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto", children: /* @__PURE__ */ jsx(
|
|
270
|
+
WeeklyHoursEditor,
|
|
271
|
+
{
|
|
272
|
+
hours: hours.hours,
|
|
273
|
+
onDayToggle: hours.setDayEnabled,
|
|
274
|
+
onSlotChange: hours.updateSlot,
|
|
275
|
+
onAddSlot: hours.addSlot,
|
|
276
|
+
onRemoveSlot: hours.removeSlot,
|
|
277
|
+
onTimezoneChange: hours.setTimezone
|
|
278
|
+
}
|
|
279
|
+
) }),
|
|
280
|
+
/* @__PURE__ */ jsx(DialogFooter, { className: "shrink-0 border-t border-[var(--border-subtle)] bg-[var(--bg-base)] pt-4", children: /* @__PURE__ */ jsx(Button, { onClick: () => setEditingHours(false), children: "Done" }) })
|
|
281
|
+
] }) })
|
|
282
|
+
] }),
|
|
283
|
+
/* @__PURE__ */ jsx(Card, { className: "p-4", children: /* @__PURE__ */ jsx(BookingLocationField, { value: location, onChange: setLocation }) }),
|
|
284
|
+
/* @__PURE__ */ jsxs(Card, { className: "p-4", children: [
|
|
285
|
+
/* @__PURE__ */ jsx("h2", { className: "mb-3 text-base font-semibold text-[var(--text-primary)]", children: "Price" }),
|
|
286
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
|
|
287
|
+
/* @__PURE__ */ jsx(
|
|
288
|
+
OptionButton,
|
|
289
|
+
{
|
|
290
|
+
shape: "card",
|
|
291
|
+
indicatorId: priceIndicatorId,
|
|
292
|
+
selected: !paid,
|
|
293
|
+
onClick: () => {
|
|
294
|
+
setPaid(false);
|
|
295
|
+
setPrice(null);
|
|
296
|
+
},
|
|
297
|
+
className: !paid ? void 0 : "bg-[var(--bg-base)]",
|
|
298
|
+
children: /* @__PURE__ */ jsxs("span", { className: "flex flex-col items-start gap-1 text-left", children: [
|
|
299
|
+
/* @__PURE__ */ jsx(BadgeCheck, { className: "size-4 text-[var(--text-muted)]" }),
|
|
300
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: "Free" }),
|
|
301
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-[var(--text-muted)]", children: "No payment required" })
|
|
302
|
+
] })
|
|
303
|
+
}
|
|
304
|
+
),
|
|
305
|
+
/* @__PURE__ */ jsx(
|
|
306
|
+
OptionButton,
|
|
307
|
+
{
|
|
308
|
+
shape: "card",
|
|
309
|
+
indicatorId: priceIndicatorId,
|
|
310
|
+
selected: paid,
|
|
311
|
+
onClick: () => setPaid(true),
|
|
312
|
+
className: paid ? void 0 : "bg-[var(--bg-base)]",
|
|
313
|
+
children: /* @__PURE__ */ jsxs("span", { className: "flex flex-col items-start gap-1 text-left", children: [
|
|
314
|
+
/* @__PURE__ */ jsx(Tag, { className: "size-4 text-[var(--text-muted)]" }),
|
|
315
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: "Paid" }),
|
|
316
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-[var(--text-muted)]", children: "Customers pay a fixed amount" })
|
|
317
|
+
] })
|
|
318
|
+
}
|
|
319
|
+
)
|
|
320
|
+
] }),
|
|
321
|
+
paid && /* @__PURE__ */ jsxs("div", { className: "surface-inset mt-3 rounded-lg p-3", children: [
|
|
322
|
+
/* @__PURE__ */ jsx(
|
|
323
|
+
CurrencyInput,
|
|
324
|
+
{
|
|
325
|
+
label: "Enter amount",
|
|
326
|
+
placeholder: "0.00",
|
|
327
|
+
currencyCode: currency,
|
|
328
|
+
value: price ?? void 0,
|
|
329
|
+
onChange: (value) => setPrice(value ?? null),
|
|
330
|
+
className: "w-full max-w-md !text-lg font-medium"
|
|
331
|
+
}
|
|
332
|
+
),
|
|
333
|
+
showErrors && !hasValidPrice && /* @__PURE__ */ jsx("p", { role: "alert", className: "mt-2 text-sm text-[var(--error)]", children: "Enter the price, or choose Free." })
|
|
334
|
+
] })
|
|
335
|
+
] }),
|
|
336
|
+
/* @__PURE__ */ jsxs(Card, { className: "p-4", children: [
|
|
337
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
|
|
338
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
339
|
+
/* @__PURE__ */ jsx("h2", { className: "text-base font-semibold text-[var(--text-primary)]", children: "Booking policies" }),
|
|
340
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-[var(--text-muted)]", children: cancellationHours != null ? `${cancellationHours} hrs before appointment` : "No cancellation deadline set" })
|
|
341
|
+
] }),
|
|
342
|
+
/* @__PURE__ */ jsx(
|
|
343
|
+
Button,
|
|
344
|
+
{
|
|
345
|
+
variant: "secondary",
|
|
346
|
+
size: "sm",
|
|
347
|
+
onClick: () => setShowBookingRules(true),
|
|
348
|
+
children: "Edit"
|
|
349
|
+
}
|
|
350
|
+
)
|
|
351
|
+
] }),
|
|
352
|
+
/* @__PURE__ */ jsx(Dialog, { open: showBookingRules, onOpenChange: setShowBookingRules, children: /* @__PURE__ */ jsxs(DialogContent, { className: "max-w-sm", children: [
|
|
353
|
+
/* @__PURE__ */ jsxs(DialogHeader, { children: [
|
|
354
|
+
/* @__PURE__ */ jsx(DialogTitle, { children: "Booking policies" }),
|
|
355
|
+
/* @__PURE__ */ jsx(DialogDescription, { children: "Set the rules customers see when booking this service." })
|
|
356
|
+
] }),
|
|
357
|
+
/* @__PURE__ */ jsx(
|
|
358
|
+
NumberField,
|
|
359
|
+
{
|
|
360
|
+
label: "Cancellation deadline",
|
|
361
|
+
value: cancellationHours,
|
|
362
|
+
onChange: setCancellationHours,
|
|
363
|
+
min: 0,
|
|
364
|
+
decimal: false,
|
|
365
|
+
suffix: "hrs before appointment"
|
|
366
|
+
}
|
|
367
|
+
),
|
|
368
|
+
/* @__PURE__ */ jsx(DialogFooter, { children: /* @__PURE__ */ jsx(Button, { onClick: () => setShowBookingRules(false), children: "Done" }) })
|
|
369
|
+
] }) })
|
|
370
|
+
] }),
|
|
371
|
+
showBottomActions && /* @__PURE__ */ jsx("div", { className: "fixed bottom-20 left-1/2 z-20 flex w-[calc(100%-2rem)] max-w-[240px] -translate-x-1/2 justify-center gap-2 rounded-lg border border-[var(--border-subtle)] bg-[var(--bg-base)] p-2 shadow-lg lg:hidden", children: /* @__PURE__ */ jsx(
|
|
372
|
+
SaveButtons,
|
|
373
|
+
{
|
|
374
|
+
saving,
|
|
375
|
+
buttonClassName: "w-28",
|
|
376
|
+
onSave: (asDraft) => void publish(asDraft)
|
|
377
|
+
}
|
|
378
|
+
) })
|
|
379
|
+
] })
|
|
380
|
+
}
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
export {
|
|
384
|
+
SessionCreator
|
|
385
|
+
};
|
|
386
|
+
//# sourceMappingURL=session-creator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../src/dashboard/%28bookings%29/bookings/sessions/new/_components/session-creator.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useId, useMemo, useState } from 'react';\nimport { useRouter } from 'next/navigation';\nimport { BadgeCheck, CalendarClock, Tag } from 'lucide-react';\nimport { DetailPage } from '@withpotter/ui-kit/detail-page';\nimport { Card } from '@withpotter/ui-kit/card';\nimport { Button } from '@withpotter/ui-kit/button';\nimport { SaveButtons } from './save-buttons';\nimport {\n Input,\n Textarea,\n Select,\n CurrencyInput,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n NumberField,\n OptionButton,\n} from '@withpotter/ui-kit';\nimport { BookingLocationField } from '@withpotter/components/behaviors';\nimport { ServiceStaffingSection } from '@withpotter/components/behaviors/service-staffing-section';\nimport { WeeklyHoursEditor } from '@withpotter/components/features/weekly-hours-editor';\nimport { useHoursData } from '@withpotter/hooks/use-hours-data';\nimport { summariseHours } from '@withpotter/pages-bookings/dashboard/(bookings)/resources/_lib/week';\nimport { useToast } from '@withpotter/ui-kit/use-toast';\nimport { useAuth } from '@withpotter/lib/auth-context';\nimport { useTenantCurrency } from '@withpotter/hooks/use-tenant-currency';\nimport { inventoryApi } from '@withpotter/lib/api';\nimport { resourceCategoriesApi } from '@withpotter/lib/api/resource-categories';\nimport { useApiQuery } from '@withpotter/hooks/use-api-query';\nimport type { BookingLocation } from '@withpotter/lib/product-types';\nimport type { BookableConfig } from '@withpotter/lib/product-types';\nimport type { ContentResourceRequirementItem } from '@withpotter/lib/resource-types';\nimport {\n buildSessionPayload,\n sessionIsPublishable,\n type SessionAnswers,\n} from '../_lib/session-payload';\n\nconst DURATION_OPTIONS = [10, 15, 20, 30, 45, 60, 90, 120];\n\nfunction durationLabel(minutes: number): string {\n if (minutes < 60) return `${minutes} minutes`;\n if (minutes === 60) return '1 hour';\n return `${minutes / 60} hours`;\n}\n\n/**\n * Making something bookable, as four questions.\n *\n * The full product editor can already do this and asks it in the wrong order:\n * a service's identity is its LENGTH and when it can be had, and that sat\n * collapsed under a heading called \"How it sells\", below a cover-image\n * dropzone and a compare-at price. A merchant writing \"30-minute\n * consultation\" met a shop form and had to know that the booking part was\n * folded away at the bottom.\n *\n * So: what is it, when can people book it, where does it happen. In that\n * order, on one screen, with nothing on it that a session does not have.\n * Everything else about a listing — media, collections, add-ons, variants —\n * stays in the full editor, reachable from the record this creates.\n *\n * It writes an ordinary catalogue item. There is no second entity for a\n * bookable thing and there must not be: the storefront, the availability\n * engine and the order line all read products, and a parallel record would\n * be a second source of truth about the same session.\n */\nexport function SessionCreator() {\n const router = useRouter();\n const { tenant } = useAuth();\n const { toast } = useToast();\n const currency = useTenantCurrency();\n const hours = useHoursData();\n const priceIndicatorId = useId();\n\n const [name, setName] = useState('');\n const [description, setDescription] = useState('');\n const [durationMinutes, setDurationMinutes] = useState<number | null>(30);\n const [paid, setPaid] = useState(false);\n const [price, setPrice] = useState<number | null>(null);\n const [cancellationHours, setCancellationHours] = useState<number | null>(\n null,\n );\n /**\n * Free is a choice, not an empty price box.\n *\n * A consultation, a discovery call and a viewing are free far more often\n * than they are ₦0.00, and a merchant who leaves the amount blank means\n * free rather than unpriced. Asking it as two options says so, and stops\n * the price field being the thing you have to leave empty correctly.\n */\n const [location, setLocation] = useState<BookingLocation | undefined>();\n const [bookableConfig, setBookableConfig] = useState<BookableConfig>({\n enabled: true,\n bookingMode: 'duration',\n durationType: 'fixed',\n assignmentStrategy: 'auto-first',\n });\n const [resourceRequirements, setResourceRequirements] = useState<\n ContentResourceRequirementItem[]\n >([]);\n const [editingHours, setEditingHours] = useState(false);\n const [showBookingRules, setShowBookingRules] = useState(false);\n const [showBottomActions, setShowBottomActions] = useState(false);\n const [saving, setSaving] = useState(false);\n\n useEffect(() => {\n const handleScroll = (event: Event) => {\n const target = event.target;\n const scrollTop =\n target instanceof HTMLElement ? target.scrollTop : window.scrollY;\n setShowBottomActions(scrollTop > 120);\n };\n\n document.addEventListener('scroll', handleScroll, {\n passive: true,\n capture: true,\n });\n return () =>\n document.removeEventListener('scroll', handleScroll, { capture: true });\n }, []);\n const { data: resourceCategories = [] } = useApiQuery<\n Array<{\n id: string;\n label?: string;\n name?: string;\n labelPlural?: string;\n kind?: string;\n }>\n >(['session-resource-categories'], (slug) =>\n resourceCategoriesApi.list(slug),\n );\n const staffingCategories = useMemo(\n () =>\n resourceCategories.map((category) => ({\n id: category.id,\n label: category.label || category.name || '',\n labelPlural: category.labelPlural || '',\n kind: category.kind || 'person',\n })),\n [resourceCategories],\n );\n\n const answers: SessionAnswers = {\n name,\n description,\n durationMinutes,\n price: paid ? price : 0,\n bufferAfter: null,\n advanceBookingDays: null,\n cancellationHours,\n location,\n currency,\n assignmentStrategy: bookableConfig.assignmentStrategy ?? 'auto-first',\n };\n\n const hasValidPrice =\n !paid || (price !== null && Number.isFinite(price) && price >= 0);\n const publishable = sessionIsPublishable(answers) && hasValidPrice;\n // A greyed-out button that never says why reads as broken. The buttons stay\n // live until a save is under way, and a click that is missing something\n // says what and takes the merchant to it.\n const canPublish = !saving;\n const [showErrors, setShowErrors] = useState(false);\n const nameMissing = showErrors && name.trim().length === 0;\n\n const publish = async (asDraft = false) => {\n if (!tenant?.slug || !canPublish) return;\n if (!publishable) {\n setShowErrors(true);\n if (name.trim().length === 0) {\n document.getElementById('service-name')?.focus();\n }\n return;\n }\n setSaving(true);\n try {\n /**\n * Hours first. They are the business's own opening times rather than\n * this session's, so an unsaved edit here would otherwise be lost the\n * moment the screen navigates to the record it just made.\n */\n if (hours.dirty) await hours.save();\n const created = await inventoryApi.create(\n tenant.slug,\n {\n ...(buildSessionPayload(answers, !asDraft) as unknown as Record<\n string,\n unknown\n >),\n // Without its model named, an engine that does not infer one saves\n // the service with none, and the Bookings list, which asks for this\n // model's items, never shows what was just made.\n intent: 'take-bookings',\n },\n );\n const staffCategory = staffingCategories.find(\n (category) => category.kind === 'person',\n );\n const requirements = staffCategory\n ? [\n ...resourceRequirements.filter(\n (requirement) => requirement.categoryId !== staffCategory.id,\n ),\n {\n categoryId: staffCategory.id,\n isRequired: true,\n eligibleResourceIds:\n resourceRequirements.find(\n (requirement) => requirement.categoryId === staffCategory.id,\n )?.eligibleResourceIds ?? null,\n },\n ]\n : resourceRequirements;\n if (requirements.length > 0) {\n await resourceCategoriesApi.setContentRequirements(\n tenant.slug,\n created.id,\n { requirements },\n );\n }\n toast({\n title: asDraft ? 'Draft saved' : `${name.trim()} is live`,\n description: asDraft\n ? 'You can finish the setup and publish it later.'\n : 'People can book it on your site.',\n });\n router.push(`/dashboard/booking-services/${created.id}`);\n } catch (err) {\n toast({\n title: \"Couldn't publish\",\n description:\n err instanceof Error ? err.message : 'Please try again in a moment.',\n variant: 'destructive',\n });\n setSaving(false);\n }\n };\n\n return (\n <DetailPage\n breadcrumbs={[\n {\n label: 'Services',\n href: '/dashboard/catalog',\n icon: <CalendarClock />,\n },\n ]}\n title=\"New service\"\n mobileActions={\n !showBottomActions ? (\n <div className=\"flex items-center gap-1\">\n <SaveButtons\n saving={saving}\n size=\"sm\"\n onSave={(asDraft) => void publish(asDraft)}\n />\n </div>\n ) : undefined\n }\n actions={\n <SaveButtons\n saving={saving}\n buttonClassName=\"flex-1\"\n onSave={(asDraft) => void publish(asDraft)}\n />\n }\n maxWidth=\"max-w-4xl\"\n >\n <div className=\"space-y-4 pb-24 lg:pb-0\">\n <Card className=\"p-4\">\n <h2 className=\"mb-3 text-base font-semibold text-[var(--text-primary)]\">\n What are people booking?\n </h2>\n <div className=\"space-y-3\">\n <Input\n id=\"service-name\"\n label=\"Name\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n error={nameMissing ? 'Give this service a name.' : undefined}\n placeholder=\"30-minute consultation\"\n />\n <Textarea\n label=\"Description\"\n rows={2}\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n placeholder=\"What the session covers\"\n />\n <div>\n <label className=\"mb-1 block text-sm font-medium text-[var(--text-secondary)]\">\n Duration\n </label>\n <Select\n value={String(durationMinutes ?? 30)}\n onChange={(value) => setDurationMinutes(Number(value))}\n options={DURATION_OPTIONS.map((minutes) => ({\n value: String(minutes),\n label: durationLabel(minutes),\n }))}\n ariaLabel=\"Duration\"\n />\n </div>\n </div>\n </Card>\n\n <Card className=\"p-4\">\n <ServiceStaffingSection\n config={bookableConfig}\n resourceCategories={staffingCategories}\n resourceRequirements={resourceRequirements}\n onConfigChange={setBookableConfig}\n onResourceRequirementsChange={setResourceRequirements}\n />\n </Card>\n\n <Card className=\"p-4\">\n <h2 className=\"mb-3 text-base font-semibold text-[var(--text-primary)]\">\n Availability\n </h2>\n {/* The business's opening times, said as a sentence and edited in\n place. A merchant setting up their first session should not have\n to know that hours live on a different screen — and the editor\n behind this is the same one Settings opens, so the two cannot\n drift. */}\n <div className=\"flex items-center justify-between gap-3\">\n <p className=\"text-sm text-[var(--text-secondary)]\">\n {summariseHours(hours.hours)}\n <span className=\"text-[var(--text-muted)]\">\n {' · '}\n {hours.hours.timezone}\n </span>\n </p>\n <Button variant=\"secondary\" onClick={() => setEditingHours(true)}>\n Edit hours\n </Button>\n </div>\n\n <Dialog open={editingHours} onOpenChange={setEditingHours}>\n <DialogContent className=\"flex max-h-[min(680px,calc(100dvh-2rem))] max-w-2xl flex-col overflow-hidden\">\n <DialogHeader>\n <DialogTitle>Edit weekly hours</DialogTitle>\n <DialogDescription>\n Choose when customers can book this service.\n </DialogDescription>\n </DialogHeader>\n <div className=\"min-h-0 flex-1 overflow-y-auto\">\n <WeeklyHoursEditor\n hours={hours.hours}\n onDayToggle={hours.setDayEnabled}\n onSlotChange={hours.updateSlot}\n onAddSlot={hours.addSlot}\n onRemoveSlot={hours.removeSlot}\n onTimezoneChange={hours.setTimezone}\n />\n </div>\n <DialogFooter className=\"shrink-0 border-t border-[var(--border-subtle)] bg-[var(--bg-base)] pt-4\">\n <Button onClick={() => setEditingHours(false)}>Done</Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </Card>\n\n <Card className=\"p-4\">\n <BookingLocationField value={location} onChange={setLocation} />\n </Card>\n\n <Card className=\"p-4\">\n <h2 className=\"mb-3 text-base font-semibold text-[var(--text-primary)]\">\n Price\n </h2>\n <div className=\"grid grid-cols-2 gap-2\">\n <OptionButton\n shape=\"card\"\n indicatorId={priceIndicatorId}\n selected={!paid}\n onClick={() => {\n setPaid(false);\n setPrice(null);\n }}\n className={!paid ? undefined : 'bg-[var(--bg-base)]'}\n >\n <span className=\"flex flex-col items-start gap-1 text-left\">\n <BadgeCheck className=\"size-4 text-[var(--text-muted)]\" />\n <span className=\"font-medium\">Free</span>\n <span className=\"text-xs text-[var(--text-muted)]\">\n No payment required\n </span>\n </span>\n </OptionButton>\n <OptionButton\n shape=\"card\"\n indicatorId={priceIndicatorId}\n selected={paid}\n onClick={() => setPaid(true)}\n className={paid ? undefined : 'bg-[var(--bg-base)]'}\n >\n <span className=\"flex flex-col items-start gap-1 text-left\">\n <Tag className=\"size-4 text-[var(--text-muted)]\" />\n <span className=\"font-medium\">Paid</span>\n <span className=\"text-xs text-[var(--text-muted)]\">\n Customers pay a fixed amount\n </span>\n </span>\n </OptionButton>\n </div>\n {paid && (\n <div className=\"surface-inset mt-3 rounded-lg p-3\">\n <CurrencyInput\n label=\"Enter amount\"\n placeholder=\"0.00\"\n currencyCode={currency}\n value={price ?? undefined}\n onChange={(value) => setPrice(value ?? null)}\n className=\"w-full max-w-md !text-lg font-medium\"\n />\n {showErrors && !hasValidPrice && (\n <p role=\"alert\" className=\"mt-2 text-sm text-[var(--error)]\">\n Enter the price, or choose Free.\n </p>\n )}\n </div>\n )}\n </Card>\n\n <Card className=\"p-4\">\n <div className=\"flex items-start justify-between gap-3\">\n <div>\n <h2 className=\"text-base font-semibold text-[var(--text-primary)]\">\n Booking policies\n </h2>\n <p className=\"mt-1 text-sm text-[var(--text-muted)]\">\n {cancellationHours != null\n ? `${cancellationHours} hrs before appointment`\n : 'No cancellation deadline set'}\n </p>\n </div>\n <Button\n variant=\"secondary\"\n size=\"sm\"\n onClick={() => setShowBookingRules(true)}\n >\n Edit\n </Button>\n </div>\n <Dialog open={showBookingRules} onOpenChange={setShowBookingRules}>\n <DialogContent className=\"max-w-sm\">\n <DialogHeader>\n <DialogTitle>Booking policies</DialogTitle>\n <DialogDescription>\n Set the rules customers see when booking this service.\n </DialogDescription>\n </DialogHeader>\n <NumberField\n label=\"Cancellation deadline\"\n value={cancellationHours}\n onChange={setCancellationHours}\n min={0}\n decimal={false}\n suffix=\"hrs before appointment\"\n />\n <DialogFooter>\n <Button onClick={() => setShowBookingRules(false)}>Done</Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </Card>\n\n {showBottomActions && (\n <div className=\"fixed bottom-20 left-1/2 z-20 flex w-[calc(100%-2rem)] max-w-[240px] -translate-x-1/2 justify-center gap-2 rounded-lg border border-[var(--border-subtle)] bg-[var(--bg-base)] p-2 shadow-lg lg:hidden\">\n <SaveButtons\n saving={saving}\n buttonClassName=\"w-28\"\n onSave={(asDraft) => void publish(asDraft)}\n />\n </div>\n )}\n </div>\n </DetailPage>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
function slugify(name) {
|
|
2
|
+
return name.trim().toLowerCase().replace(/['’]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
3
|
+
}
|
|
4
|
+
function sessionIsPublishable(answers) {
|
|
5
|
+
return answers.name.trim().length > 0 && typeof answers.durationMinutes === "number" && Number.isFinite(answers.durationMinutes) && answers.durationMinutes > 0;
|
|
6
|
+
}
|
|
7
|
+
function sessionValidationMessage(answers, { paid, draft = false }) {
|
|
8
|
+
if (!answers.name.trim()) return "Enter a service name.";
|
|
9
|
+
if (draft) return null;
|
|
10
|
+
if (!sessionIsPublishable(answers)) return "Choose a valid service duration.";
|
|
11
|
+
if (paid && (answers.price == null || !Number.isFinite(answers.price) || answers.price < 0)) {
|
|
12
|
+
return "Enter a price for this paid service, or choose Free.";
|
|
13
|
+
}
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
function buildSessionPayload(answers, isActive = true) {
|
|
17
|
+
const bookable = {
|
|
18
|
+
enabled: true,
|
|
19
|
+
bookingMode: "duration",
|
|
20
|
+
durationType: "fixed",
|
|
21
|
+
durationMinutes: answers.durationMinutes ?? 30,
|
|
22
|
+
/**
|
|
23
|
+
* One at a time unless the merchant says otherwise. A session created
|
|
24
|
+
* through this screen is the Calendly shape — one person, one slot — and
|
|
25
|
+
* a capacity left undefined reads downstream as unlimited.
|
|
26
|
+
*/
|
|
27
|
+
maxCapacity: 1,
|
|
28
|
+
pricingUnit: "flat",
|
|
29
|
+
/**
|
|
30
|
+
* Nobody assigns a session to themselves. `sole-provider` is what the
|
|
31
|
+
* bookable panel already forces for a business with no second person, and
|
|
32
|
+
* saying it here means the storefront never draws a "choose a stylist"
|
|
33
|
+
* step with one name in it.
|
|
34
|
+
*/
|
|
35
|
+
assignmentStrategy: answers.assignmentStrategy ?? "sole-provider"
|
|
36
|
+
};
|
|
37
|
+
if (typeof answers.bufferAfter === "number")
|
|
38
|
+
bookable.bufferAfter = answers.bufferAfter;
|
|
39
|
+
if (typeof answers.advanceBookingDays === "number")
|
|
40
|
+
bookable.advanceBookingDays = answers.advanceBookingDays;
|
|
41
|
+
if (typeof answers.cancellationHours === "number")
|
|
42
|
+
bookable.cancellationHours = answers.cancellationHours;
|
|
43
|
+
if (answers.location) bookable.location = answers.location;
|
|
44
|
+
return {
|
|
45
|
+
type: "service",
|
|
46
|
+
title: answers.name.trim(),
|
|
47
|
+
slug: slugify(answers.name),
|
|
48
|
+
description: answers.description.trim() || void 0,
|
|
49
|
+
price: answers.price ?? 0,
|
|
50
|
+
currency: answers.currency,
|
|
51
|
+
isActive,
|
|
52
|
+
trackInventory: false,
|
|
53
|
+
behaviors: { bookable }
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export {
|
|
57
|
+
buildSessionPayload,
|
|
58
|
+
sessionIsPublishable,
|
|
59
|
+
sessionValidationMessage,
|
|
60
|
+
slugify
|
|
61
|
+
};
|
|
62
|
+
//# sourceMappingURL=session-payload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../src/dashboard/%28bookings%29/bookings/sessions/new/_lib/session-payload.ts"],"sourcesContent":["import type { BookingLocation } from '@withpotter/lib/product-types';\n\n/**\n * What a merchant answered, turned into the product the engine already reads.\n *\n * A bookable session IS a catalogue item — one record, not a second entity —\n * so this builds exactly the payload `POST /products` already takes, with the\n * booking answers in `behaviors.bookable`. Kept pure and apart from the screen\n * because the interesting part is not the form, it is the shape: three of\n * these fields have a wrong-looking default that is load-bearing.\n */\n\nexport interface SessionAnswers {\n name: string;\n description: string;\n durationMinutes: number | null;\n /** Null is free. Zero is also free; both are a price of nothing. */\n price: number | null;\n bufferAfter: number | null;\n advanceBookingDays: number | null;\n cancellationHours: number | null;\n location?: BookingLocation;\n currency: string;\n assignmentStrategy: NonNullable<\n import('@withpotter/lib/product-types').BookableConfig['assignmentStrategy']\n >;\n}\n\nexport interface SessionPayload {\n type: string;\n title: string;\n slug: string;\n description?: string;\n price: number;\n currency: string;\n isActive: boolean;\n /**\n * A session has no stock. The product model tracks inventory for everything\n * by default, which is how a consultation's record page comes to announce\n * that it is out of stock.\n */\n trackInventory: boolean;\n behaviors: Record<string, unknown>;\n}\n\n/**\n * A URL for the session, from its name.\n *\n * Not `encodeURIComponent(name)`: a slug with a space in it round-trips\n * through three systems and comes back as `%2520` in the customer's address\n * bar. Letters, digits and single hyphens only.\n */\nexport function slugify(name: string): string {\n return name\n .trim()\n .toLowerCase()\n .replace(/['’]/g, '')\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 80);\n}\n\n/** Everything a session needs before it can be offered to anybody. */\nexport function sessionIsPublishable(answers: SessionAnswers): boolean {\n return (\n answers.name.trim().length > 0 &&\n typeof answers.durationMinutes === 'number' &&\n Number.isFinite(answers.durationMinutes) &&\n answers.durationMinutes > 0\n );\n}\n\nexport function sessionValidationMessage(\n answers: SessionAnswers,\n { paid, draft = false }: { paid: boolean; draft?: boolean },\n): string | null {\n if (!answers.name.trim()) return 'Enter a service name.';\n if (draft) return null;\n if (!sessionIsPublishable(answers)) return 'Choose a valid service duration.';\n if (\n paid &&\n (answers.price == null ||\n !Number.isFinite(answers.price) ||\n answers.price < 0)\n ) {\n return 'Enter a price for this paid service, or choose Free.';\n }\n return null;\n}\n\nexport function buildSessionPayload(\n answers: SessionAnswers,\n isActive = true,\n): SessionPayload {\n const bookable: Record<string, unknown> = {\n enabled: true,\n bookingMode: 'duration',\n durationType: 'fixed',\n durationMinutes: answers.durationMinutes ?? 30,\n /**\n * One at a time unless the merchant says otherwise. A session created\n * through this screen is the Calendly shape — one person, one slot — and\n * a capacity left undefined reads downstream as unlimited.\n */\n maxCapacity: 1,\n pricingUnit: 'flat',\n /**\n * Nobody assigns a session to themselves. `sole-provider` is what the\n * bookable panel already forces for a business with no second person, and\n * saying it here means the storefront never draws a \"choose a stylist\"\n * step with one name in it.\n */\n assignmentStrategy: answers.assignmentStrategy ?? 'sole-provider',\n };\n\n if (typeof answers.bufferAfter === 'number')\n bookable.bufferAfter = answers.bufferAfter;\n if (typeof answers.advanceBookingDays === 'number')\n bookable.advanceBookingDays = answers.advanceBookingDays;\n if (typeof answers.cancellationHours === 'number')\n bookable.cancellationHours = answers.cancellationHours;\n if (answers.location) bookable.location = answers.location;\n\n return {\n type: 'service',\n title: answers.name.trim(),\n slug: slugify(answers.name),\n description: answers.description.trim() || undefined,\n price: answers.price ?? 0,\n currency: answers.currency,\n isActive,\n trackInventory: false,\n behaviors: { bookable },\n };\n}\n"],"mappings":"AAoDO,SAAS,QAAQ,MAAsB;AAC5C,SAAO,KACJ,KAAK,EACL,YAAY,EACZ,QAAQ,SAAS,EAAE,EACnB,QAAQ,eAAe,GAAG,EAC1B,QAAQ,YAAY,EAAE,EACtB,MAAM,GAAG,EAAE;AAChB;AAGO,SAAS,qBAAqB,SAAkC;AACrE,SACE,QAAQ,KAAK,KAAK,EAAE,SAAS,KAC7B,OAAO,QAAQ,oBAAoB,YACnC,OAAO,SAAS,QAAQ,eAAe,KACvC,QAAQ,kBAAkB;AAE9B;AAEO,SAAS,yBACd,SACA,EAAE,MAAM,QAAQ,MAAM,GACP;AACf,MAAI,CAAC,QAAQ,KAAK,KAAK,EAAG,QAAO;AACjC,MAAI,MAAO,QAAO;AAClB,MAAI,CAAC,qBAAqB,OAAO,EAAG,QAAO;AAC3C,MACE,SACC,QAAQ,SAAS,QAChB,CAAC,OAAO,SAAS,QAAQ,KAAK,KAC9B,QAAQ,QAAQ,IAClB;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEO,SAAS,oBACd,SACA,WAAW,MACK;AAChB,QAAM,WAAoC;AAAA,IACxC,SAAS;AAAA,IACT,aAAa;AAAA,IACb,cAAc;AAAA,IACd,iBAAiB,QAAQ,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAM5C,aAAa;AAAA,IACb,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOb,oBAAoB,QAAQ,sBAAsB;AAAA,EACpD;AAEA,MAAI,OAAO,QAAQ,gBAAgB;AACjC,aAAS,cAAc,QAAQ;AACjC,MAAI,OAAO,QAAQ,uBAAuB;AACxC,aAAS,qBAAqB,QAAQ;AACxC,MAAI,OAAO,QAAQ,sBAAsB;AACvC,aAAS,oBAAoB,QAAQ;AACvC,MAAI,QAAQ,SAAU,UAAS,WAAW,QAAQ;AAElD,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO,QAAQ,KAAK,KAAK;AAAA,IACzB,MAAM,QAAQ,QAAQ,IAAI;AAAA,IAC1B,aAAa,QAAQ,YAAY,KAAK,KAAK;AAAA,IAC3C,OAAO,QAAQ,SAAS;AAAA,IACxB,UAAU,QAAQ;AAAA,IAClB;AAAA,IACA,gBAAgB;AAAA,IAChB,WAAW,EAAE,SAAS;AAAA,EACxB;AACF;","names":[]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { redirect } from "next/navigation";
|
|
2
|
+
const metadata = { title: "New session" };
|
|
3
|
+
function NewSessionPage() {
|
|
4
|
+
redirect("/dashboard/catalog/new?intent=take-bookings");
|
|
5
|
+
}
|
|
6
|
+
export {
|
|
7
|
+
NewSessionPage as default,
|
|
8
|
+
metadata
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=page.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../src/dashboard/%28bookings%29/bookings/sessions/new/page.tsx"],"sourcesContent":["import { redirect } from 'next/navigation';\n\nexport const metadata = { title: 'New session' };\n\n/** Preserve the old deep link while keeping service creation canonical. */\nexport default function NewSessionPage() {\n redirect('/dashboard/catalog/new?intent=take-bookings');\n}\n"],"mappings":"AAAA,SAAS,gBAAgB;AAElB,MAAM,WAAW,EAAE,OAAO,cAAc;AAGhC,SAAR,iBAAkC;AACvC,WAAS,6CAA6C;AACxD;","names":[]}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { ChevronLeft, ChevronRight, CornerUpLeft } from "lucide-react";
|
|
4
|
+
import { cn } from "@withpotter/lib/utils";
|
|
5
|
+
import { RANGE_OPTIONS } from "./calendar-range";
|
|
6
|
+
function CalendarTail({ nav }) {
|
|
7
|
+
const { range, setRange, step, periodLabel, openDay, today, atNow } = nav;
|
|
8
|
+
const stepping = range !== "day";
|
|
9
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex h-full items-center gap-0.5 pr-0.5", children: [
|
|
10
|
+
/* @__PURE__ */ jsx(
|
|
11
|
+
"div",
|
|
12
|
+
{
|
|
13
|
+
role: "radiogroup",
|
|
14
|
+
"aria-label": "Calendar range",
|
|
15
|
+
className: "flex h-full items-center gap-0.5",
|
|
16
|
+
children: RANGE_OPTIONS.map((opt) => {
|
|
17
|
+
const active = opt.value === range;
|
|
18
|
+
return /* @__PURE__ */ jsx(
|
|
19
|
+
"button",
|
|
20
|
+
{
|
|
21
|
+
type: "button",
|
|
22
|
+
role: "radio",
|
|
23
|
+
"aria-checked": active,
|
|
24
|
+
onClick: () => setRange(opt.value),
|
|
25
|
+
className: cn(
|
|
26
|
+
"rounded-[7px] px-2 py-1 text-xs font-medium transition-colors duration-[var(--motion-fast)]",
|
|
27
|
+
active ? "text-[var(--text-primary)]" : "text-[var(--text-tertiary)] hover:text-[var(--text-secondary)]"
|
|
28
|
+
),
|
|
29
|
+
children: opt.label
|
|
30
|
+
},
|
|
31
|
+
opt.value
|
|
32
|
+
);
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
),
|
|
36
|
+
/* @__PURE__ */ jsx(
|
|
37
|
+
"span",
|
|
38
|
+
{
|
|
39
|
+
"aria-hidden": true,
|
|
40
|
+
className: "mx-1 h-3.5 w-px shrink-0 bg-[var(--border-default)]"
|
|
41
|
+
}
|
|
42
|
+
),
|
|
43
|
+
stepping && /* @__PURE__ */ jsx(
|
|
44
|
+
"button",
|
|
45
|
+
{
|
|
46
|
+
type: "button",
|
|
47
|
+
onClick: () => step(-1),
|
|
48
|
+
"aria-label": `Previous ${range}`,
|
|
49
|
+
className: "grid size-6 place-items-center rounded-[7px] text-[var(--text-tertiary)] transition-colors hover:bg-[var(--interactive-hover)] hover:text-[var(--text-primary)]",
|
|
50
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-4" })
|
|
51
|
+
}
|
|
52
|
+
),
|
|
53
|
+
/* @__PURE__ */ jsxs(
|
|
54
|
+
"button",
|
|
55
|
+
{
|
|
56
|
+
type: "button",
|
|
57
|
+
onClick: () => openDay(today),
|
|
58
|
+
"aria-label": atNow ? periodLabel : `${periodLabel} \u2014 back to today`,
|
|
59
|
+
className: cn(
|
|
60
|
+
"group inline-flex min-w-[7.5rem] items-center justify-center gap-1 rounded-[7px] px-2 py-1 text-xs tabular-nums transition-colors hover:bg-[var(--interactive-hover)]",
|
|
61
|
+
atNow ? "text-[var(--text-secondary)]" : "text-[var(--text-primary)]"
|
|
62
|
+
),
|
|
63
|
+
children: [
|
|
64
|
+
periodLabel,
|
|
65
|
+
!atNow && /* @__PURE__ */ jsx(CornerUpLeft, { className: "text-[var(--text-tertiary)] transition-colors group-hover:text-[var(--text-primary)]" })
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
),
|
|
69
|
+
stepping && /* @__PURE__ */ jsx(
|
|
70
|
+
"button",
|
|
71
|
+
{
|
|
72
|
+
type: "button",
|
|
73
|
+
onClick: () => step(1),
|
|
74
|
+
"aria-label": `Next ${range}`,
|
|
75
|
+
className: "grid size-6 place-items-center rounded-[7px] text-[var(--text-tertiary)] transition-colors hover:bg-[var(--interactive-hover)] hover:text-[var(--text-primary)]",
|
|
76
|
+
children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-4" })
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
] });
|
|
80
|
+
}
|
|
81
|
+
export {
|
|
82
|
+
CalendarTail
|
|
83
|
+
};
|
|
84
|
+
//# sourceMappingURL=calendar-nav.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../src/dashboard/%28bookings%29/calendar/_components/calendar-nav.tsx"],"sourcesContent":["'use client';\n\nimport { ChevronLeft, ChevronRight, CornerUpLeft } from 'lucide-react';\nimport { cn } from '@withpotter/lib/utils';\nimport { RANGE_OPTIONS } from './calendar-range';\nimport type { CalendarNav as Nav } from './use-calendar-nav';\n\n/**\n * The calendar's range AND where it is pointed, as the tail of the view switch.\n *\n * THE PROBLEM THIS SOLVES. Today, List and Calendar are three ways to read the\n * same bookings, and two of them need nothing else — a desk board is today and\n * a table is a table. The calendar alone has a second question (a day, a week, a\n * month, a year) and a third (which one), and every attempt to give those their\n * own controls made the header read as a row of peers: first two identically\n * boxed segmented controls side by side, which is one eight-option control split\n * by a gap; then a switch plus a loose stepper, which is a control and a\n * fragment. Nothing in either said the extra pieces only exist because of a\n * choice made in the first.\n *\n * So it is ONE OBJECT: `Today · List · [Calendar] | Day Week Month Year | ‹ Aug\n * 23 to 29 ›`, inside the switch's own track, opening when the calendar is\n * chosen and closing when it is not. John's call, and it is the right one — the\n * frame is what says these belong together.\n *\n * THE RANGE HAS NO THUMB. The thumb is on Calendar, where the VIEW is chosen;\n * a second one inside the same track would be two controls again. Weight of ink\n * carries it instead.\n *\n * THE LABEL IS A BUTTON, not a caption between two arrows — pressing \"Aug 23 to\n * 29\" comes back to this week, which is the thing you want from it and the\n * reason there is no separate Today button. (There was one. It sat six pixels\n * from a view switch whose first segment also reads Today: the same word twice,\n * one a verb and one a place.) The ⟲ appears only when you are away from now,\n * so the affordance shows up exactly when it does something.\n */\nexport function CalendarTail({ nav }: { nav: Nav }) {\n const { range, setRange, step, periodLabel, openDay, today, atNow } = nav;\n /* No arrows in day range: the week strip under the board is the date control\n there, and a second pair here would be two navigators for one date. */\n const stepping = range !== 'day';\n\n return (\n <div className=\"flex h-full items-center gap-0.5 pr-0.5\">\n <div\n role=\"radiogroup\"\n aria-label=\"Calendar range\"\n className=\"flex h-full items-center gap-0.5\"\n >\n {RANGE_OPTIONS.map((opt) => {\n const active = opt.value === range;\n return (\n <button\n key={opt.value}\n type=\"button\"\n role=\"radio\"\n aria-checked={active}\n onClick={() => setRange(opt.value)}\n className={cn(\n 'rounded-[7px] px-2 py-1 text-xs font-medium transition-colors duration-[var(--motion-fast)]',\n active\n ? 'text-[var(--text-primary)]'\n : 'text-[var(--text-tertiary)] hover:text-[var(--text-secondary)]',\n )}\n >\n {opt.label}\n </button>\n );\n })}\n </div>\n\n <span\n aria-hidden\n className=\"mx-1 h-3.5 w-px shrink-0 bg-[var(--border-default)]\"\n />\n\n {stepping && (\n <button\n type=\"button\"\n onClick={() => step(-1)}\n aria-label={`Previous ${range}`}\n className=\"grid size-6 place-items-center rounded-[7px] text-[var(--text-tertiary)] transition-colors hover:bg-[var(--interactive-hover)] hover:text-[var(--text-primary)]\"\n >\n <ChevronLeft className=\"size-4\" />\n </button>\n )}\n\n {/* `tabular-nums` and a floor on the width, so stepping through months\n does not shunt the arrows sideways on every press. */}\n <button\n type=\"button\"\n onClick={() => openDay(today)}\n aria-label={atNow ? periodLabel : `${periodLabel} — back to today`}\n className={cn(\n 'group inline-flex min-w-[7.5rem] items-center justify-center gap-1 rounded-[7px] px-2 py-1 text-xs tabular-nums transition-colors hover:bg-[var(--interactive-hover)]',\n atNow ? 'text-[var(--text-secondary)]' : 'text-[var(--text-primary)]',\n )}\n >\n {periodLabel}\n {!atNow && (\n <CornerUpLeft className=\"text-[var(--text-tertiary)] transition-colors group-hover:text-[var(--text-primary)]\" />\n )}\n </button>\n\n {stepping && (\n <button\n type=\"button\"\n onClick={() => step(1)}\n aria-label={`Next ${range}`}\n className=\"grid size-6 place-items-center rounded-[7px] text-[var(--text-tertiary)] transition-colors hover:bg-[var(--interactive-hover)] hover:text-[var(--text-primary)]\"\n >\n <ChevronRight className=\"size-4\" />\n </button>\n )}\n </div>\n );\n}\n"],"mappings":";AAoDY,cAqCN,YArCM;AAlDZ,SAAS,aAAa,cAAc,oBAAoB;AACxD,SAAS,UAAU;AACnB,SAAS,qBAAqB;AAgCvB,SAAS,aAAa,EAAE,IAAI,GAAiB;AAClD,QAAM,EAAE,OAAO,UAAU,MAAM,aAAa,SAAS,OAAO,MAAM,IAAI;AAGtE,QAAM,WAAW,UAAU;AAE3B,SACE,qBAAC,SAAI,WAAU,2CACb;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,cAAW;AAAA,QACX,WAAU;AAAA,QAET,wBAAc,IAAI,CAAC,QAAQ;AAC1B,gBAAM,SAAS,IAAI,UAAU;AAC7B,iBACE;AAAA,YAAC;AAAA;AAAA,cAEC,MAAK;AAAA,cACL,MAAK;AAAA,cACL,gBAAc;AAAA,cACd,SAAS,MAAM,SAAS,IAAI,KAAK;AAAA,cACjC,WAAW;AAAA,gBACT;AAAA,gBACA,SACI,+BACA;AAAA,cACN;AAAA,cAEC,cAAI;AAAA;AAAA,YAZA,IAAI;AAAA,UAaX;AAAA,QAEJ,CAAC;AAAA;AAAA,IACH;AAAA,IAEA;AAAA,MAAC;AAAA;AAAA,QACC,eAAW;AAAA,QACX,WAAU;AAAA;AAAA,IACZ;AAAA,IAEC,YACC;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAS,MAAM,KAAK,EAAE;AAAA,QACtB,cAAY,YAAY,KAAK;AAAA,QAC7B,WAAU;AAAA,QAEV,8BAAC,eAAY,WAAU,UAAS;AAAA;AAAA,IAClC;AAAA,IAKF;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAS,MAAM,QAAQ,KAAK;AAAA,QAC5B,cAAY,QAAQ,cAAc,GAAG,WAAW;AAAA,QAChD,WAAW;AAAA,UACT;AAAA,UACA,QAAQ,iCAAiC;AAAA,QAC3C;AAAA,QAEC;AAAA;AAAA,UACA,CAAC,SACA,oBAAC,gBAAa,WAAU,wFAAuF;AAAA;AAAA;AAAA,IAEnH;AAAA,IAEC,YACC;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAS,MAAM,KAAK,CAAC;AAAA,QACrB,cAAY,QAAQ,KAAK;AAAA,QACzB,WAAU;AAAA,QAEV,8BAAC,gBAAa,WAAU,UAAS;AAAA;AAAA,IACnC;AAAA,KAEJ;AAEJ;","names":[]}
|