@burdenoff/microfe-ggnomad 2026.1016.1 → 2026.1029.2
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/ggnomad/GGNomadRoot.js +4 -2
- package/dist/ggnomad/GGNomadRoot.js.map +1 -1
- package/dist/ggnomad/GGNomadRoutes.js.map +1 -1
- package/dist/ggnomad/components/AuditTrailDialog.js.map +1 -1
- package/dist/ggnomad/components/AvailabilityPanel.js.map +1 -1
- package/dist/ggnomad/components/BookingPanel.d.ts.map +1 -1
- package/dist/ggnomad/components/BookingPanel.js +53 -70
- package/dist/ggnomad/components/BookingPanel.js.map +1 -1
- package/dist/ggnomad/components/CreateProviderDialog.js.map +1 -1
- package/dist/ggnomad/components/CreateTravelPolicyDialog.js.map +1 -1
- package/dist/ggnomad/components/GroupTripPanel.js.map +1 -1
- package/dist/ggnomad/components/ListingEditDialog.js.map +1 -1
- package/dist/ggnomad/components/ListingFilters.js.map +1 -1
- package/dist/ggnomad/components/PhotoGallery.js.map +1 -1
- package/dist/ggnomad/components/ProviderCard.js.map +1 -1
- package/dist/ggnomad/components/ProviderDashboardCard.js.map +1 -1
- package/dist/ggnomad/components/RatingSummary.js.map +1 -1
- package/dist/ggnomad/components/RedeemTicketPanel.js.map +1 -1
- package/dist/ggnomad/components/ReviewsSection.js.map +1 -1
- package/dist/ggnomad/components/StatusBadge.js.map +1 -1
- package/dist/ggnomad/components/TicketDialog.js +1 -1
- package/dist/ggnomad/components/TicketDialog.js.map +1 -1
- package/dist/ggnomad/components/index.js +26 -25
- package/dist/ggnomad/index.js +3 -2
- package/dist/ggnomad/pages/ActivityDetailsPage.js.map +1 -1
- package/dist/ggnomad/pages/AnalyticsPage.js.map +1 -1
- package/dist/ggnomad/pages/BookingsPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/BookingsPage.js +54 -52
- package/dist/ggnomad/pages/BookingsPage.js.map +1 -1
- package/dist/ggnomad/pages/DashboardPage.js.map +1 -1
- package/dist/ggnomad/pages/EntertainmentPage.js.map +1 -1
- package/dist/ggnomad/pages/EventDetailsPage.js.map +1 -1
- package/dist/ggnomad/pages/FavoritesPage.js.map +1 -1
- package/dist/ggnomad/pages/HospitalityPage.js.map +1 -1
- package/dist/ggnomad/pages/OperationsQueuePage.js.map +1 -1
- package/dist/ggnomad/pages/PageShell.js.map +1 -1
- package/dist/ggnomad/pages/PropertyDetailsPage.js.map +1 -1
- package/dist/ggnomad/pages/ProviderAnalyticsPage.js.map +1 -1
- package/dist/ggnomad/pages/SearchPage.js +1 -1
- package/dist/ggnomad/pages/SearchPage.js.map +1 -1
- package/dist/ggnomad/pages/StorefrontPage.js.map +1 -1
- package/dist/ggnomad/pages/StorefrontPublicPage.js.map +1 -1
- package/dist/ggnomad/pages/TourDetailsPage.js.map +1 -1
- package/dist/ggnomad/pages/TourismPage.js.map +1 -1
- package/dist/ggnomad/pages/TravelPage.js +1 -1
- package/dist/ggnomad/pages/TravelPage.js.map +1 -1
- package/dist/ggnomad/pages/TravelPolicyPage.js.map +1 -1
- package/dist/ggnomad/pages/TripPlanDetailPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/TripPlanDetailPage.js +278 -273
- package/dist/ggnomad/pages/TripPlanDetailPage.js.map +1 -1
- package/dist/ggnomad/pages/TripsPage.js.map +1 -1
- package/dist/ggnomad/providers/GGNomadProvider.js.map +1 -1
- package/dist/ggnomad/utils/bookingDates.d.ts +21 -0
- package/dist/ggnomad/utils/bookingDates.d.ts.map +1 -0
- package/dist/ggnomad/utils/bookingDates.js +22 -0
- package/dist/ggnomad/utils/bookingDates.js.map +1 -0
- package/dist/ggnomad/utils/format.js.map +1 -1
- package/dist/ggnomad/utils/notify.js +1 -1
- package/dist/ggnomad/utils/providerListControls.js +1 -1
- package/dist/ggnomad/utils/providerListControls.js.map +1 -1
- package/dist/ggnomad/utils/providerTypeMeta.js.map +1 -1
- package/dist/utils/pageContext.js +1 -1
- package/dist/utils/pageContext.js.map +1 -1
- package/package.json +18 -17
|
@@ -10,7 +10,8 @@ import { FeatureFlagProvider as b } from "@burdenoff/fe-libs/shared/feature-flag
|
|
|
10
10
|
var x = (x) => {
|
|
11
11
|
let { registerNavItems: S, registerFooterItems: C, basePath: w = "/" } = x, { t: T } = v();
|
|
12
12
|
return r(() => {
|
|
13
|
-
if (S) return
|
|
13
|
+
if (!S) return;
|
|
14
|
+
let e = {
|
|
14
15
|
id: "ggnomad",
|
|
15
16
|
label: T("ggnomad.nav.root", "GGNomad"),
|
|
16
17
|
path: w,
|
|
@@ -117,7 +118,8 @@ var x = (x) => {
|
|
|
117
118
|
order: 80
|
|
118
119
|
}
|
|
119
120
|
]
|
|
120
|
-
}
|
|
121
|
+
};
|
|
122
|
+
return S(e);
|
|
121
123
|
}, [
|
|
122
124
|
S,
|
|
123
125
|
w,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GGNomadRoot.js","names":[],"sources":["../../src/ggnomad/GGNomadRoot.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useEffect } from 'react';\nimport {\n Activity,\n BarChart3,\n Building,\n CalendarCheck,\n ClipboardCheck,\n Compass,\n LayoutDashboard,\n MapPinned,\n Map,\n Search,\n ShieldCheck,\n Star,\n Tickets,\n TrendingUp,\n} from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\nimport { GGNomadRoutes } from './GGNomadRoutes';\nimport { FeatureFlagProvider } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { GGNomadProvider } from './providers/GGNomadProvider';\nimport type { GGNomadRootProps, NavItem } from './types';\nimport { joinPath } from './utils/navigation';\n\nexport const GGNomadRoot: FC<GGNomadRootProps> = (props) => {\n const { registerNavItems, registerFooterItems, basePath = '/' } = props;\n const { t } = useI18n();\n\n useEffect(() => {\n if (!registerNavItems) {\n return;\n }\n\n const navItem: NavItem = {\n id: 'ggnomad',\n label: t('ggnomad.nav.root', 'GGNomad'),\n path: basePath,\n icon: <LayoutDashboard size={18} />,\n order: 80,\n group: 'products',\n children: [\n {\n id: 'ggnomad-dashboard',\n label: t('ggnomad.nav.dashboard', 'Dashboard'),\n path: basePath,\n icon: <LayoutDashboard size={16} />,\n order: 0,\n },\n {\n id: 'ggnomad-search',\n label: t('ggnomad.nav.search', 'Search'),\n path: joinPath(basePath, 'search'),\n icon: <Search size={16} />,\n order: 5,\n },\n {\n id: 'ggnomad-travel',\n label: t('ggnomad.nav.travel', 'Travel'),\n path: joinPath(basePath, 'travel'),\n icon: <Compass size={16} />,\n order: 10,\n },\n {\n id: 'ggnomad-hospitality',\n label: t('ggnomad.nav.hospitality', 'Hospitality'),\n path: joinPath(basePath, 'hospitality'),\n icon: <Building size={16} />,\n order: 20,\n },\n {\n id: 'ggnomad-tourism',\n label: t('ggnomad.nav.tourism', 'Tourism'),\n path: joinPath(basePath, 'tourism'),\n icon: <MapPinned size={16} />,\n order: 30,\n },\n {\n id: 'ggnomad-entertainment',\n label: t('ggnomad.nav.entertainment', 'Entertainment'),\n path: joinPath(basePath, 'entertainment'),\n icon: <Activity size={16} />,\n order: 40,\n },\n {\n id: 'ggnomad-trips',\n label: t('ggnomad.nav.trips', 'Trips'),\n path: joinPath(basePath, 'trips'),\n icon: <Map size={16} />,\n order: 45,\n },\n {\n id: 'ggnomad-bookings',\n label: t('ggnomad.nav.bookings', 'Bookings'),\n path: joinPath(basePath, 'bookings'),\n icon: <CalendarCheck size={16} />,\n order: 50,\n },\n {\n id: 'ggnomad-operations',\n label: t('ggnomad.nav.operations', 'Operations Queue'),\n path: joinPath(basePath, 'operations'),\n icon: <ClipboardCheck size={16} />,\n order: 52,\n },\n {\n id: 'ggnomad-travel-policy',\n label: t('ggnomad.nav.travelPolicy', 'Travel Policy'),\n path: joinPath(basePath, 'travel-policy'),\n icon: <ShieldCheck size={16} />,\n order: 55,\n },\n {\n id: 'ggnomad-favorites',\n label: t('ggnomad.nav.favorites', 'Favorites'),\n path: joinPath(basePath, 'favorites'),\n icon: <Star size={16} />,\n order: 60,\n },\n {\n id: 'ggnomad-provider',\n label: t('ggnomad.nav.providers', 'Providers'),\n path: joinPath(basePath, 'provider'),\n icon: <Tickets size={16} />,\n order: 70,\n },\n {\n id: 'ggnomad-provider-analytics',\n label: t('ggnomad.nav.providerAnalytics', 'My Provider Analytics'),\n path: joinPath(basePath, 'provider/analytics'),\n icon: <TrendingUp size={16} />,\n order: 72,\n },\n {\n id: 'ggnomad-analytics',\n label: t('ggnomad.nav.analytics', 'Analytics'),\n path: joinPath(basePath, 'analytics'),\n icon: <BarChart3 size={16} />,\n order: 80,\n },\n ],\n };\n\n return registerNavItems(navItem);\n }, [registerNavItems, basePath, t]);\n\n useEffect(() => {\n if (!registerFooterItems) {\n return;\n }\n return registerFooterItems([\n {\n id: 'ggnomad-ready',\n label: t('ggnomad.footer.ready', 'GGNomad Ready'),\n type: 'status',\n section: 'left',\n order: 10,\n },\n ]);\n }, [registerFooterItems, t]);\n\n return (\n <FeatureFlagProvider workspaceId={null} gateway=\"global\" defaultEnabled={true}>\n <GGNomadProvider {...props}>\n <GGNomadRoutes />\n </GGNomadProvider>\n </FeatureFlagProvider>\n );\n};\n"],"mappings":";;;;;;;;;AA0BA,IAAa,KAAqC,MAAU;CAC1D,IAAM,EAAE,qBAAkB,wBAAqB,cAAW,QAAQ,GAC5D,EAAE,SAAM,EAAQ;CAsItB,OApIA,QAAgB;
|
|
1
|
+
{"version":3,"file":"GGNomadRoot.js","names":[],"sources":["../../src/ggnomad/GGNomadRoot.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useEffect } from 'react';\nimport {\n Activity,\n BarChart3,\n Building,\n CalendarCheck,\n ClipboardCheck,\n Compass,\n LayoutDashboard,\n MapPinned,\n Map,\n Search,\n ShieldCheck,\n Star,\n Tickets,\n TrendingUp,\n} from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\nimport { GGNomadRoutes } from './GGNomadRoutes';\nimport { FeatureFlagProvider } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { GGNomadProvider } from './providers/GGNomadProvider';\nimport type { GGNomadRootProps, NavItem } from './types';\nimport { joinPath } from './utils/navigation';\n\nexport const GGNomadRoot: FC<GGNomadRootProps> = (props) => {\n const { registerNavItems, registerFooterItems, basePath = '/' } = props;\n const { t } = useI18n();\n\n useEffect(() => {\n if (!registerNavItems) {\n return;\n }\n\n const navItem: NavItem = {\n id: 'ggnomad',\n label: t('ggnomad.nav.root', 'GGNomad'),\n path: basePath,\n icon: <LayoutDashboard size={18} />,\n order: 80,\n group: 'products',\n children: [\n {\n id: 'ggnomad-dashboard',\n label: t('ggnomad.nav.dashboard', 'Dashboard'),\n path: basePath,\n icon: <LayoutDashboard size={16} />,\n order: 0,\n },\n {\n id: 'ggnomad-search',\n label: t('ggnomad.nav.search', 'Search'),\n path: joinPath(basePath, 'search'),\n icon: <Search size={16} />,\n order: 5,\n },\n {\n id: 'ggnomad-travel',\n label: t('ggnomad.nav.travel', 'Travel'),\n path: joinPath(basePath, 'travel'),\n icon: <Compass size={16} />,\n order: 10,\n },\n {\n id: 'ggnomad-hospitality',\n label: t('ggnomad.nav.hospitality', 'Hospitality'),\n path: joinPath(basePath, 'hospitality'),\n icon: <Building size={16} />,\n order: 20,\n },\n {\n id: 'ggnomad-tourism',\n label: t('ggnomad.nav.tourism', 'Tourism'),\n path: joinPath(basePath, 'tourism'),\n icon: <MapPinned size={16} />,\n order: 30,\n },\n {\n id: 'ggnomad-entertainment',\n label: t('ggnomad.nav.entertainment', 'Entertainment'),\n path: joinPath(basePath, 'entertainment'),\n icon: <Activity size={16} />,\n order: 40,\n },\n {\n id: 'ggnomad-trips',\n label: t('ggnomad.nav.trips', 'Trips'),\n path: joinPath(basePath, 'trips'),\n icon: <Map size={16} />,\n order: 45,\n },\n {\n id: 'ggnomad-bookings',\n label: t('ggnomad.nav.bookings', 'Bookings'),\n path: joinPath(basePath, 'bookings'),\n icon: <CalendarCheck size={16} />,\n order: 50,\n },\n {\n id: 'ggnomad-operations',\n label: t('ggnomad.nav.operations', 'Operations Queue'),\n path: joinPath(basePath, 'operations'),\n icon: <ClipboardCheck size={16} />,\n order: 52,\n },\n {\n id: 'ggnomad-travel-policy',\n label: t('ggnomad.nav.travelPolicy', 'Travel Policy'),\n path: joinPath(basePath, 'travel-policy'),\n icon: <ShieldCheck size={16} />,\n order: 55,\n },\n {\n id: 'ggnomad-favorites',\n label: t('ggnomad.nav.favorites', 'Favorites'),\n path: joinPath(basePath, 'favorites'),\n icon: <Star size={16} />,\n order: 60,\n },\n {\n id: 'ggnomad-provider',\n label: t('ggnomad.nav.providers', 'Providers'),\n path: joinPath(basePath, 'provider'),\n icon: <Tickets size={16} />,\n order: 70,\n },\n {\n id: 'ggnomad-provider-analytics',\n label: t('ggnomad.nav.providerAnalytics', 'My Provider Analytics'),\n path: joinPath(basePath, 'provider/analytics'),\n icon: <TrendingUp size={16} />,\n order: 72,\n },\n {\n id: 'ggnomad-analytics',\n label: t('ggnomad.nav.analytics', 'Analytics'),\n path: joinPath(basePath, 'analytics'),\n icon: <BarChart3 size={16} />,\n order: 80,\n },\n ],\n };\n\n return registerNavItems(navItem);\n }, [registerNavItems, basePath, t]);\n\n useEffect(() => {\n if (!registerFooterItems) {\n return;\n }\n return registerFooterItems([\n {\n id: 'ggnomad-ready',\n label: t('ggnomad.footer.ready', 'GGNomad Ready'),\n type: 'status',\n section: 'left',\n order: 10,\n },\n ]);\n }, [registerFooterItems, t]);\n\n return (\n <FeatureFlagProvider workspaceId={null} gateway=\"global\" defaultEnabled={true}>\n <GGNomadProvider {...props}>\n <GGNomadRoutes />\n </GGNomadProvider>\n </FeatureFlagProvider>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GGNomadRoutes.js","names":[],"sources":["../../src/ggnomad/GGNomadRoutes.tsx"],"sourcesContent":["import { lazy, Suspense } from 'react';\nimport { Loader2 } from 'lucide-react';\nimport { Navigate, Route, Routes } from 'react-router';\n\nimport { DashboardPage } from './pages/DashboardPage';\n\nconst SearchPage = lazy(() =>\n import('./pages/SearchPage').then((m) => ({ default: m.SearchPage }))\n);\nconst TravelPage = lazy(() =>\n import('./pages/TravelPage').then((m) => ({ default: m.TravelPage }))\n);\nconst HospitalityPage = lazy(() =>\n import('./pages/HospitalityPage').then((m) => ({ default: m.HospitalityPage }))\n);\nconst PropertyDetailsPage = lazy(() =>\n import('./pages/PropertyDetailsPage').then((m) => ({ default: m.PropertyDetailsPage }))\n);\nconst TourismPage = lazy(() =>\n import('./pages/TourismPage').then((m) => ({ default: m.TourismPage }))\n);\nconst TourDetailsPage = lazy(() =>\n import('./pages/TourDetailsPage').then((m) => ({ default: m.TourDetailsPage }))\n);\nconst ActivityDetailsPage = lazy(() =>\n import('./pages/ActivityDetailsPage').then((m) => ({ default: m.ActivityDetailsPage }))\n);\nconst EntertainmentPage = lazy(() =>\n import('./pages/EntertainmentPage').then((m) => ({ default: m.EntertainmentPage }))\n);\nconst EventDetailsPage = lazy(() =>\n import('./pages/EventDetailsPage').then((m) => ({ default: m.EventDetailsPage }))\n);\nconst BookingsPage = lazy(() =>\n import('./pages/BookingsPage').then((m) => ({ default: m.BookingsPage }))\n);\nconst FavoritesPage = lazy(() =>\n import('./pages/FavoritesPage').then((m) => ({ default: m.FavoritesPage }))\n);\nconst ProviderPage = lazy(() =>\n import('./pages/ProviderPage').then((m) => ({ default: m.ProviderPage }))\n);\nconst AnalyticsPage = lazy(() =>\n import('./pages/AnalyticsPage').then((m) => ({ default: m.AnalyticsPage }))\n);\nconst ProviderAnalyticsPage = lazy(() =>\n import('./pages/ProviderAnalyticsPage').then((m) => ({ default: m.ProviderAnalyticsPage }))\n);\nconst TripsPage = lazy(() => import('./pages/TripsPage').then((m) => ({ default: m.TripsPage })));\nconst TripPlanDetailPage = lazy(() =>\n import('./pages/TripPlanDetailPage').then((m) => ({ default: m.TripPlanDetailPage }))\n);\nconst TravelPolicyPage = lazy(() =>\n import('./pages/TravelPolicyPage').then((m) => ({ default: m.TravelPolicyPage }))\n);\nconst OperationsQueuePage = lazy(() =>\n import('./pages/OperationsQueuePage').then((m) => ({ default: m.OperationsQueuePage }))\n);\nconst StorefrontPage = lazy(() =>\n import('./pages/StorefrontPage').then((m) => ({ default: m.StorefrontPage }))\n);\nconst StorefrontPublicPage = lazy(() =>\n import('./pages/StorefrontPublicPage').then((m) => ({ default: m.StorefrontPublicPage }))\n);\n\nfunction PageLoader() {\n return (\n <div className=\"flex min-h-[320px] items-center justify-center\">\n <Loader2 className=\"h-8 w-8 animate-spin text-primary\" />\n </div>\n );\n}\n\nexport function GGNomadRoutes() {\n return (\n <Suspense fallback={<PageLoader />}>\n <Routes>\n <Route index element={<DashboardPage />} />\n <Route path=\"search\" element={<SearchPage />} />\n <Route path=\"travel\" element={<TravelPage />} />\n <Route path=\"hospitality\" element={<HospitalityPage />} />\n <Route path=\"hospitality/:id\" element={<PropertyDetailsPage />} />\n <Route path=\"tourism\" element={<TourismPage />} />\n <Route path=\"tourism/tours/:id\" element={<TourDetailsPage />} />\n <Route path=\"tourism/activities/:id\" element={<ActivityDetailsPage />} />\n <Route path=\"entertainment\" element={<EntertainmentPage />} />\n <Route path=\"entertainment/:id\" element={<EventDetailsPage />} />\n <Route path=\"bookings\" element={<BookingsPage />} />\n <Route path=\"travel-policy\" element={<TravelPolicyPage />} />\n <Route path=\"operations\" element={<OperationsQueuePage />} />\n <Route path=\"favorites\" element={<FavoritesPage />} />\n <Route path=\"provider\" element={<ProviderPage />} />\n <Route path=\"provider/analytics\" element={<ProviderAnalyticsPage />} />\n <Route path=\"provider/:providerId/storefront\" element={<StorefrontPage />} />\n <Route path=\"store/:slug\" element={<StorefrontPublicPage />} />\n <Route path=\"analytics\" element={<AnalyticsPage />} />\n <Route path=\"trips\" element={<TripsPage />} />\n <Route path=\"trips/:id\" element={<TripPlanDetailPage />} />\n <Route path=\"*\" element={<Navigate to=\"\" replace />} />\n </Routes>\n </Suspense>\n );\n}\n"],"mappings":";;;;;;AAMA,IAAM,IAAa,QACjB,OAAO,
|
|
1
|
+
{"version":3,"file":"GGNomadRoutes.js","names":[],"sources":["../../src/ggnomad/GGNomadRoutes.tsx"],"sourcesContent":["import { lazy, Suspense } from 'react';\nimport { Loader2 } from 'lucide-react';\nimport { Navigate, Route, Routes } from 'react-router';\n\nimport { DashboardPage } from './pages/DashboardPage';\n\nconst SearchPage = lazy(() =>\n import('./pages/SearchPage').then((m) => ({ default: m.SearchPage }))\n);\nconst TravelPage = lazy(() =>\n import('./pages/TravelPage').then((m) => ({ default: m.TravelPage }))\n);\nconst HospitalityPage = lazy(() =>\n import('./pages/HospitalityPage').then((m) => ({ default: m.HospitalityPage }))\n);\nconst PropertyDetailsPage = lazy(() =>\n import('./pages/PropertyDetailsPage').then((m) => ({ default: m.PropertyDetailsPage }))\n);\nconst TourismPage = lazy(() =>\n import('./pages/TourismPage').then((m) => ({ default: m.TourismPage }))\n);\nconst TourDetailsPage = lazy(() =>\n import('./pages/TourDetailsPage').then((m) => ({ default: m.TourDetailsPage }))\n);\nconst ActivityDetailsPage = lazy(() =>\n import('./pages/ActivityDetailsPage').then((m) => ({ default: m.ActivityDetailsPage }))\n);\nconst EntertainmentPage = lazy(() =>\n import('./pages/EntertainmentPage').then((m) => ({ default: m.EntertainmentPage }))\n);\nconst EventDetailsPage = lazy(() =>\n import('./pages/EventDetailsPage').then((m) => ({ default: m.EventDetailsPage }))\n);\nconst BookingsPage = lazy(() =>\n import('./pages/BookingsPage').then((m) => ({ default: m.BookingsPage }))\n);\nconst FavoritesPage = lazy(() =>\n import('./pages/FavoritesPage').then((m) => ({ default: m.FavoritesPage }))\n);\nconst ProviderPage = lazy(() =>\n import('./pages/ProviderPage').then((m) => ({ default: m.ProviderPage }))\n);\nconst AnalyticsPage = lazy(() =>\n import('./pages/AnalyticsPage').then((m) => ({ default: m.AnalyticsPage }))\n);\nconst ProviderAnalyticsPage = lazy(() =>\n import('./pages/ProviderAnalyticsPage').then((m) => ({ default: m.ProviderAnalyticsPage }))\n);\nconst TripsPage = lazy(() => import('./pages/TripsPage').then((m) => ({ default: m.TripsPage })));\nconst TripPlanDetailPage = lazy(() =>\n import('./pages/TripPlanDetailPage').then((m) => ({ default: m.TripPlanDetailPage }))\n);\nconst TravelPolicyPage = lazy(() =>\n import('./pages/TravelPolicyPage').then((m) => ({ default: m.TravelPolicyPage }))\n);\nconst OperationsQueuePage = lazy(() =>\n import('./pages/OperationsQueuePage').then((m) => ({ default: m.OperationsQueuePage }))\n);\nconst StorefrontPage = lazy(() =>\n import('./pages/StorefrontPage').then((m) => ({ default: m.StorefrontPage }))\n);\nconst StorefrontPublicPage = lazy(() =>\n import('./pages/StorefrontPublicPage').then((m) => ({ default: m.StorefrontPublicPage }))\n);\n\nfunction PageLoader() {\n return (\n <div className=\"flex min-h-[320px] items-center justify-center\">\n <Loader2 className=\"h-8 w-8 animate-spin text-primary\" />\n </div>\n );\n}\n\nexport function GGNomadRoutes() {\n return (\n <Suspense fallback={<PageLoader />}>\n <Routes>\n <Route index element={<DashboardPage />} />\n <Route path=\"search\" element={<SearchPage />} />\n <Route path=\"travel\" element={<TravelPage />} />\n <Route path=\"hospitality\" element={<HospitalityPage />} />\n <Route path=\"hospitality/:id\" element={<PropertyDetailsPage />} />\n <Route path=\"tourism\" element={<TourismPage />} />\n <Route path=\"tourism/tours/:id\" element={<TourDetailsPage />} />\n <Route path=\"tourism/activities/:id\" element={<ActivityDetailsPage />} />\n <Route path=\"entertainment\" element={<EntertainmentPage />} />\n <Route path=\"entertainment/:id\" element={<EventDetailsPage />} />\n <Route path=\"bookings\" element={<BookingsPage />} />\n <Route path=\"travel-policy\" element={<TravelPolicyPage />} />\n <Route path=\"operations\" element={<OperationsQueuePage />} />\n <Route path=\"favorites\" element={<FavoritesPage />} />\n <Route path=\"provider\" element={<ProviderPage />} />\n <Route path=\"provider/analytics\" element={<ProviderAnalyticsPage />} />\n <Route path=\"provider/:providerId/storefront\" element={<StorefrontPage />} />\n <Route path=\"store/:slug\" element={<StorefrontPublicPage />} />\n <Route path=\"analytics\" element={<AnalyticsPage />} />\n <Route path=\"trips\" element={<TripsPage />} />\n <Route path=\"trips/:id\" element={<TripPlanDetailPage />} />\n <Route path=\"*\" element={<Navigate to=\"\" replace />} />\n </Routes>\n </Suspense>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuditTrailDialog.js","names":[],"sources":["../../../src/ggnomad/components/AuditTrailDialog.tsx"],"sourcesContent":["import { useQuery } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { History } from 'lucide-react';\nimport {\n AlertDialog,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\n\nimport { GGNOMAD_AUDIT_LOG_QUERY } from '../graphql';\n\ninterface AuditLogEntry {\n id: string;\n action: string;\n fromStatus: string | null;\n toStatus: string | null;\n reason: string | null;\n actorId: string;\n createdAt: string;\n}\n\ninterface AuditTrailDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n entityType: string;\n entityId: string;\n /** Shown in the dialog title, e.g. a reference code or listing name. */\n label: string;\n}\n\nfunction formatAction(action: string): string {\n return action\n .toLowerCase()\n .split('_')\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ');\n}\n\n/**\n * BOFF-4388 — read-only transition history for one listing or booking, backed\n * by GgnomadAuditLog. Every listing-verification and booking-lifecycle\n * mutation writes one row here; this dialog is the operator-facing view of\n * that append-only trail.\n */\nexport function AuditTrailDialog({\n open,\n onOpenChange,\n entityType,\n entityId,\n label,\n}: AuditTrailDialogProps) {\n const { t } = useI18n();\n\n const { data, loading } = useQuery(\n GGNOMAD_AUDIT_LOG_QUERY as TypedDocumentNode<\n { ggnomadAuditLog: AuditLogEntry[] },\n { entityType: string; entityId: string; limit?: number; offset?: number }\n >,\n {\n variables: { entityType, entityId, limit: 50, offset: 0 },\n skip: !open,\n fetchPolicy: 'network-only',\n }\n );\n\n const entries = data?.ggnomadAuditLog ?? [];\n\n return (\n <AlertDialog open={open} onOpenChange={onOpenChange}>\n <AlertDialogContent className=\"max-w-lg\">\n <AlertDialogHeader>\n <AlertDialogTitle className=\"flex items-center gap-2\">\n <History className=\"size-4\" />\n {t('ggnomad.auditTrail.title', 'Transition history')}\n </AlertDialogTitle>\n <AlertDialogDescription>\n {t(\n 'ggnomad.auditTrail.description',\n 'Every verification or lifecycle change for {{label}}.'\n ).replace('{{label}}', label)}\n </AlertDialogDescription>\n </AlertDialogHeader>\n\n <div className=\"max-h-[60vh] space-y-2 overflow-y-auto\">\n {loading ? (\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n {t('ggnomad.auditTrail.loading', 'Loading history…')}\n </p>\n ) : entries.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={t('ggnomad.auditTrail.emptyTitle', 'No transitions recorded yet')}\n description={t(\n 'ggnomad.auditTrail.emptyDescription',\n 'Approvals, rejections, check-ins and other lifecycle changes will show up here.'\n )}\n />\n ) : (\n <ol className=\"space-y-2\">\n {entries.map((entry) => (\n <li\n key={entry.id}\n className=\"rounded-lg border border-border-seam bg-bg-surface/40 p-3 text-sm\"\n >\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"font-medium text-text-primary\">\n {formatAction(entry.action)}\n </span>\n <span className=\"text-xs tabular-nums text-text-secondary\">\n {new Date(entry.createdAt).toLocaleString()}\n </span>\n </div>\n {entry.fromStatus || entry.toStatus ? (\n <p className=\"mt-1 text-xs text-text-secondary\">\n {entry.fromStatus ?? '—'} → {entry.toStatus ?? '—'}\n </p>\n ) : null}\n {entry.reason ? (\n <p className=\"mt-1 text-xs italic text-text-secondary\">\n “{entry.reason}”\n </p>\n ) : null}\n <p className=\"mt-1 text-xs text-text-secondary\">\n {t('ggnomad.auditTrail.by', 'by {{actor}}').replace('{{actor}}', entry.actorId)}\n </p>\n </li>\n ))}\n </ol>\n )}\n </div>\n\n <AlertDialogFooter>\n <AlertDialogCancel>{t('ggnomad.auditTrail.close', 'Close')}</AlertDialogCancel>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n );\n}\n"],"mappings":";;;;;;;AAoCA,SAAS,EAAa,GAAwB;CAC5C,OAAO,EACJ,YAAY,
|
|
1
|
+
{"version":3,"file":"AuditTrailDialog.js","names":[],"sources":["../../../src/ggnomad/components/AuditTrailDialog.tsx"],"sourcesContent":["import { useQuery } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { History } from 'lucide-react';\nimport {\n AlertDialog,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\n\nimport { GGNOMAD_AUDIT_LOG_QUERY } from '../graphql';\n\ninterface AuditLogEntry {\n id: string;\n action: string;\n fromStatus: string | null;\n toStatus: string | null;\n reason: string | null;\n actorId: string;\n createdAt: string;\n}\n\ninterface AuditTrailDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n entityType: string;\n entityId: string;\n /** Shown in the dialog title, e.g. a reference code or listing name. */\n label: string;\n}\n\nfunction formatAction(action: string): string {\n return action\n .toLowerCase()\n .split('_')\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ');\n}\n\n/**\n * BOFF-4388 — read-only transition history for one listing or booking, backed\n * by GgnomadAuditLog. Every listing-verification and booking-lifecycle\n * mutation writes one row here; this dialog is the operator-facing view of\n * that append-only trail.\n */\nexport function AuditTrailDialog({\n open,\n onOpenChange,\n entityType,\n entityId,\n label,\n}: AuditTrailDialogProps) {\n const { t } = useI18n();\n\n const { data, loading } = useQuery(\n GGNOMAD_AUDIT_LOG_QUERY as TypedDocumentNode<\n { ggnomadAuditLog: AuditLogEntry[] },\n { entityType: string; entityId: string; limit?: number; offset?: number }\n >,\n {\n variables: { entityType, entityId, limit: 50, offset: 0 },\n skip: !open,\n fetchPolicy: 'network-only',\n }\n );\n\n const entries = data?.ggnomadAuditLog ?? [];\n\n return (\n <AlertDialog open={open} onOpenChange={onOpenChange}>\n <AlertDialogContent className=\"max-w-lg\">\n <AlertDialogHeader>\n <AlertDialogTitle className=\"flex items-center gap-2\">\n <History className=\"size-4\" />\n {t('ggnomad.auditTrail.title', 'Transition history')}\n </AlertDialogTitle>\n <AlertDialogDescription>\n {t(\n 'ggnomad.auditTrail.description',\n 'Every verification or lifecycle change for {{label}}.'\n ).replace('{{label}}', label)}\n </AlertDialogDescription>\n </AlertDialogHeader>\n\n <div className=\"max-h-[60vh] space-y-2 overflow-y-auto\">\n {loading ? (\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n {t('ggnomad.auditTrail.loading', 'Loading history…')}\n </p>\n ) : entries.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={t('ggnomad.auditTrail.emptyTitle', 'No transitions recorded yet')}\n description={t(\n 'ggnomad.auditTrail.emptyDescription',\n 'Approvals, rejections, check-ins and other lifecycle changes will show up here.'\n )}\n />\n ) : (\n <ol className=\"space-y-2\">\n {entries.map((entry) => (\n <li\n key={entry.id}\n className=\"rounded-lg border border-border-seam bg-bg-surface/40 p-3 text-sm\"\n >\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"font-medium text-text-primary\">\n {formatAction(entry.action)}\n </span>\n <span className=\"text-xs tabular-nums text-text-secondary\">\n {new Date(entry.createdAt).toLocaleString()}\n </span>\n </div>\n {entry.fromStatus || entry.toStatus ? (\n <p className=\"mt-1 text-xs text-text-secondary\">\n {entry.fromStatus ?? '—'} → {entry.toStatus ?? '—'}\n </p>\n ) : null}\n {entry.reason ? (\n <p className=\"mt-1 text-xs italic text-text-secondary\">\n “{entry.reason}”\n </p>\n ) : null}\n <p className=\"mt-1 text-xs text-text-secondary\">\n {t('ggnomad.auditTrail.by', 'by {{actor}}').replace('{{actor}}', entry.actorId)}\n </p>\n </li>\n ))}\n </ol>\n )}\n </div>\n\n <AlertDialogFooter>\n <AlertDialogCancel>{t('ggnomad.auditTrail.close', 'Close')}</AlertDialogCancel>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n );\n}\n"],"mappings":";;;;;;;AAoCA,SAAS,EAAa,GAAwB;CAC5C,OAAO,EACJ,YAAY,CAAC,CACb,MAAM,GAAG,CAAC,CACV,KAAK,MAAS,EAAK,OAAO,CAAC,CAAC,CAAC,YAAY,IAAI,EAAK,MAAM,CAAC,CAAC,CAAC,CAC3D,KAAK,GAAG;AACb;AAQA,SAAgB,EAAiB,EAC/B,SACA,iBACA,eACA,aACA,YACwB;CACxB,IAAM,EAAE,SAAM,EAAQ,GAEhB,EAAE,SAAM,eAAY,EACxB,GAIA;EACE,WAAW;GAAE;GAAY;GAAU,OAAO;GAAI,QAAQ;EAAE;EACxD,MAAM,CAAC;EACP,aAAa;CACf,CACF,GAEM,IAAU,GAAM,mBAAmB,CAAC;CAE1C,OACE,kBAAC,GAAD;EAAmB;EAAoB;YACrC,kBAAC,GAAD;GAAoB,WAAU;aAA9B;IACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;KAAkB,WAAU;eAA5B,CACE,kBAAC,GAAD,EAAS,WAAU,SAAU,CAAA,GAC5B,EAAE,4BAA4B,oBAAoB,CACnC;QAClB,kBAAC,GAAD,EAAA,UACG,EACC,kCACA,uDACF,CAAC,CAAC,QAAQ,aAAa,CAAK,EACN,CAAA,CACP,EAAA,CAAA;IAEnB,kBAAC,OAAD;KAAK,WAAU;eACZ,IACC,kBAAC,KAAD;MAAG,WAAU;gBACV,EAAE,8BAA8B,kBAAkB;KAClD,CAAA,IACD,EAAQ,WAAW,IACrB,kBAAC,GAAD;MACE,cAAa;MACb,OAAO,EAAE,iCAAiC,6BAA6B;MACvE,aAAa,EACX,uCACA,iFACF;KACD,CAAA,IAED,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAQ,KAAK,MACZ,kBAAC,MAAD;OAEE,WAAU;iBAFZ;QAIE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,QAAD;UAAM,WAAU;oBACb,EAAa,EAAM,MAAM;SACtB,CAAA,GACN,kBAAC,QAAD;UAAM,WAAU;oBACb,IAAI,KAAK,EAAM,SAAS,CAAC,CAAC,eAAe;SACtC,CAAA,CACH;;QACJ,EAAM,cAAc,EAAM,WACzB,kBAAC,KAAD;SAAG,WAAU;mBAAb;UACG,EAAM,cAAc;UAAI;UAAI,EAAM,YAAY;SAC9C;aACD;QACH,EAAM,SACL,kBAAC,KAAD;SAAG,WAAU;mBAAb;UAAuD;UAC7C,EAAM;UAAO;SACpB;aACD;QACJ,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAE,yBAAyB,cAAc,CAAC,CAAC,QAAQ,aAAa,EAAM,OAAO;QAC7E,CAAA;OACD;SAxBG,EAAM,EAwBT,CACL;KACC,CAAA;IAEH,CAAA;IAEL,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAoB,EAAE,4BAA4B,OAAO,EAAqB,CAAA,EAC7D,CAAA;GACD;;CACT,CAAA;AAEjB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AvailabilityPanel.js","names":[],"sources":["../../../src/ggnomad/components/AvailabilityPanel.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useQuery } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { CalendarDays } from 'lucide-react';\n\nimport { GGNOMAD_BOOKING_SLOTS_QUERY } from '../graphql';\nimport { formatDate, formatTimeOnly } from '../utils/format';\nimport { SectionCard } from './SectionCard';\n\ntype GgnomadListingType = 'PROPERTY' | 'TOUR' | 'EVENT' | 'ACTIVITY';\n\ninterface BookingSlot {\n id: string;\n startAt: string;\n endAt?: string | null;\n capacity: number;\n bookedCount: number;\n remainingCapacity: number;\n isActive: boolean;\n}\n\ninterface AvailabilityPanelProps {\n listingId: string;\n listingType: GgnomadListingType;\n}\n\nconst SLOT_LIMIT = 30;\n\n/**\n * BOFF-4361 — the listing's published availability slots (`ggnomadBookingSlots`),\n * from today forward. Read-only: it reports the capacity the operator published and\n * how much of it is already taken; reserving a slot stays with the booking panel.\n * The toggle switches the query's own `onlyAvailable` argument, so \"sold out\" rows\n * are real records, never a placeholder.\n */\nexport function AvailabilityPanel({ listingId, listingType }: AvailabilityPanelProps) {\n const { t } = useI18n();\n const [onlyAvailable, setOnlyAvailable] = useState(true);\n const [touched, setTouched] = useState(false);\n const dateFrom = useMemo(() => new Date().toISOString(), []);\n\n const { data, loading, error } = useQuery(\n GGNOMAD_BOOKING_SLOTS_QUERY as TypedDocumentNode<\n { ggnomadBookingSlots: BookingSlot[] },\n {\n listingId: string;\n listingType: GgnomadListingType;\n dateFrom: string;\n onlyAvailable: boolean;\n limit: number;\n offset: number;\n }\n >,\n {\n variables: {\n listingId,\n listingType,\n dateFrom,\n onlyAvailable,\n limit: SLOT_LIMIT,\n offset: 0,\n },\n skip: !listingId,\n }\n );\n\n const slots = data?.ggnomadBookingSlots ?? [];\n\n // A listing that publishes no slots at all is booked by date, not by slot — the\n // panel stays out of the way rather than showing a permanently empty section.\n // Once the reader has used the toggle it stays put, so switching back to\n // \"available only\" cannot make the control it was just using disappear.\n if (!touched && !loading && !error && slots.length === 0 && onlyAvailable) {\n return null;\n }\n\n return (\n <SectionCard\n title={t('ggnomad.availability.title', 'Availability')}\n action={\n <label className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <input\n type=\"checkbox\"\n checked={!onlyAvailable}\n onChange={(event) => {\n setTouched(true);\n setOnlyAvailable(!event.target.checked);\n }}\n className=\"size-4 rounded border-border-seam accent-[var(--color-action-primary-bg)]\"\n />\n {t('ggnomad.availability.includeFull', 'Include full & closed slots')}\n </label>\n }\n >\n {error ? (\n <p className=\"text-sm text-status-error-text\">\n {t('ggnomad.availability.error', 'Could not load availability: {{message}}').replace(\n '{{message}}',\n error.message\n )}\n </p>\n ) : loading ? (\n <p className=\"text-sm text-text-secondary\">\n {t('ggnomad.availability.loading', 'Loading availability…')}\n </p>\n ) : slots.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n {t('ggnomad.availability.empty', 'No upcoming slots published for this listing.')}\n </p>\n ) : (\n <ul className=\"space-y-2\">\n {slots.map((slot) => {\n const day = formatDate(slot.startAt);\n const startTime = formatTimeOnly(slot.startAt);\n const endTime = formatTimeOnly(slot.endAt);\n const soldOut = slot.remainingCapacity <= 0;\n return (\n <li\n key={slot.id}\n className=\"flex flex-wrap items-center justify-between gap-3 rounded-lg border border-border-seam bg-bg-surface/40 p-3\"\n >\n <div className=\"flex min-w-0 items-center gap-3\">\n <span\n className=\"flex size-8 flex-shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary\"\n aria-hidden=\"true\"\n >\n <CalendarDays className=\"size-4\" />\n </span>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {day ?? slot.startAt}\n </p>\n {startTime ? (\n <p className=\"text-xs text-text-secondary\">\n {endTime ? `${startTime} – ${endTime}` : startTime}\n </p>\n ) : null}\n </div>\n </div>\n <div className=\"flex items-center gap-3 text-sm\">\n <span className=\"tabular-nums text-text-secondary\">\n {t('ggnomad.availability.remaining', '{{left}} of {{total}} left')\n .replace('{{left}}', String(Math.max(0, slot.remainingCapacity)))\n .replace('{{total}}', String(slot.capacity))}\n </span>\n {!slot.isActive ? (\n <span className=\"rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary\">\n {t('ggnomad.availability.closed', 'Closed')}\n </span>\n ) : soldOut ? (\n <span className=\"rounded-full bg-status-error-bg-subtle px-2.5 py-0.5 text-xs font-medium text-status-error-text\">\n {t('ggnomad.availability.soldOut', 'Sold out')}\n </span>\n ) : (\n <span className=\"rounded-full bg-status-success-bg-subtle px-2.5 py-0.5 text-xs font-medium text-status-success-text\">\n {t('ggnomad.availability.open', 'Available')}\n </span>\n )}\n </div>\n </li>\n );\n })}\n </ul>\n )}\n </SectionCard>\n );\n}\n"],"mappings":";;;;;;;;;AA2BA,IAAM,IAAa;AASnB,SAAgB,EAAkB,EAAE,cAAW,kBAAuC;CACpF,IAAM,EAAE,SAAM,EAAQ,GAChB,CAAC,GAAe,KAAoB,EAAS,EAAI,GACjD,CAAC,GAAS,KAAc,EAAS,EAAK,GAGtC,EAAE,SAAM,YAAS,aAAU,EAC/B,GAWA;EACE,WAAW;GACT;GACA;GACA,UAlBW,yBAAc,IAAI,KAAK,
|
|
1
|
+
{"version":3,"file":"AvailabilityPanel.js","names":[],"sources":["../../../src/ggnomad/components/AvailabilityPanel.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useQuery } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { CalendarDays } from 'lucide-react';\n\nimport { GGNOMAD_BOOKING_SLOTS_QUERY } from '../graphql';\nimport { formatDate, formatTimeOnly } from '../utils/format';\nimport { SectionCard } from './SectionCard';\n\ntype GgnomadListingType = 'PROPERTY' | 'TOUR' | 'EVENT' | 'ACTIVITY';\n\ninterface BookingSlot {\n id: string;\n startAt: string;\n endAt?: string | null;\n capacity: number;\n bookedCount: number;\n remainingCapacity: number;\n isActive: boolean;\n}\n\ninterface AvailabilityPanelProps {\n listingId: string;\n listingType: GgnomadListingType;\n}\n\nconst SLOT_LIMIT = 30;\n\n/**\n * BOFF-4361 — the listing's published availability slots (`ggnomadBookingSlots`),\n * from today forward. Read-only: it reports the capacity the operator published and\n * how much of it is already taken; reserving a slot stays with the booking panel.\n * The toggle switches the query's own `onlyAvailable` argument, so \"sold out\" rows\n * are real records, never a placeholder.\n */\nexport function AvailabilityPanel({ listingId, listingType }: AvailabilityPanelProps) {\n const { t } = useI18n();\n const [onlyAvailable, setOnlyAvailable] = useState(true);\n const [touched, setTouched] = useState(false);\n const dateFrom = useMemo(() => new Date().toISOString(), []);\n\n const { data, loading, error } = useQuery(\n GGNOMAD_BOOKING_SLOTS_QUERY as TypedDocumentNode<\n { ggnomadBookingSlots: BookingSlot[] },\n {\n listingId: string;\n listingType: GgnomadListingType;\n dateFrom: string;\n onlyAvailable: boolean;\n limit: number;\n offset: number;\n }\n >,\n {\n variables: {\n listingId,\n listingType,\n dateFrom,\n onlyAvailable,\n limit: SLOT_LIMIT,\n offset: 0,\n },\n skip: !listingId,\n }\n );\n\n const slots = data?.ggnomadBookingSlots ?? [];\n\n // A listing that publishes no slots at all is booked by date, not by slot — the\n // panel stays out of the way rather than showing a permanently empty section.\n // Once the reader has used the toggle it stays put, so switching back to\n // \"available only\" cannot make the control it was just using disappear.\n if (!touched && !loading && !error && slots.length === 0 && onlyAvailable) {\n return null;\n }\n\n return (\n <SectionCard\n title={t('ggnomad.availability.title', 'Availability')}\n action={\n <label className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <input\n type=\"checkbox\"\n checked={!onlyAvailable}\n onChange={(event) => {\n setTouched(true);\n setOnlyAvailable(!event.target.checked);\n }}\n className=\"size-4 rounded border-border-seam accent-[var(--color-action-primary-bg)]\"\n />\n {t('ggnomad.availability.includeFull', 'Include full & closed slots')}\n </label>\n }\n >\n {error ? (\n <p className=\"text-sm text-status-error-text\">\n {t('ggnomad.availability.error', 'Could not load availability: {{message}}').replace(\n '{{message}}',\n error.message\n )}\n </p>\n ) : loading ? (\n <p className=\"text-sm text-text-secondary\">\n {t('ggnomad.availability.loading', 'Loading availability…')}\n </p>\n ) : slots.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n {t('ggnomad.availability.empty', 'No upcoming slots published for this listing.')}\n </p>\n ) : (\n <ul className=\"space-y-2\">\n {slots.map((slot) => {\n const day = formatDate(slot.startAt);\n const startTime = formatTimeOnly(slot.startAt);\n const endTime = formatTimeOnly(slot.endAt);\n const soldOut = slot.remainingCapacity <= 0;\n return (\n <li\n key={slot.id}\n className=\"flex flex-wrap items-center justify-between gap-3 rounded-lg border border-border-seam bg-bg-surface/40 p-3\"\n >\n <div className=\"flex min-w-0 items-center gap-3\">\n <span\n className=\"flex size-8 flex-shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary\"\n aria-hidden=\"true\"\n >\n <CalendarDays className=\"size-4\" />\n </span>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {day ?? slot.startAt}\n </p>\n {startTime ? (\n <p className=\"text-xs text-text-secondary\">\n {endTime ? `${startTime} – ${endTime}` : startTime}\n </p>\n ) : null}\n </div>\n </div>\n <div className=\"flex items-center gap-3 text-sm\">\n <span className=\"tabular-nums text-text-secondary\">\n {t('ggnomad.availability.remaining', '{{left}} of {{total}} left')\n .replace('{{left}}', String(Math.max(0, slot.remainingCapacity)))\n .replace('{{total}}', String(slot.capacity))}\n </span>\n {!slot.isActive ? (\n <span className=\"rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary\">\n {t('ggnomad.availability.closed', 'Closed')}\n </span>\n ) : soldOut ? (\n <span className=\"rounded-full bg-status-error-bg-subtle px-2.5 py-0.5 text-xs font-medium text-status-error-text\">\n {t('ggnomad.availability.soldOut', 'Sold out')}\n </span>\n ) : (\n <span className=\"rounded-full bg-status-success-bg-subtle px-2.5 py-0.5 text-xs font-medium text-status-success-text\">\n {t('ggnomad.availability.open', 'Available')}\n </span>\n )}\n </div>\n </li>\n );\n })}\n </ul>\n )}\n </SectionCard>\n );\n}\n"],"mappings":";;;;;;;;;AA2BA,IAAM,IAAa;AASnB,SAAgB,EAAkB,EAAE,cAAW,kBAAuC;CACpF,IAAM,EAAE,SAAM,EAAQ,GAChB,CAAC,GAAe,KAAoB,EAAS,EAAI,GACjD,CAAC,GAAS,KAAc,EAAS,EAAK,GAGtC,EAAE,SAAM,YAAS,aAAU,EAC/B,GAWA;EACE,WAAW;GACT;GACA;GACA,UAlBW,yBAAc,IAAI,KAAK,EAAA,CAAE,YAAY,GAAG,CAAC,CAkBpD;GACA;GACA,OAAO;GACP,QAAQ;EACV;EACA,MAAM,CAAC;CACT,CACF,GAEM,IAAQ,GAAM,uBAAuB,CAAC;CAU5C,OAJI,CAAC,KAAW,CAAC,KAAW,CAAC,KAAS,EAAM,WAAW,KAAK,IACnD,OAIP,kBAAC,GAAD;EACE,OAAO,EAAE,8BAA8B,cAAc;EACrD,QACE,kBAAC,SAAD;GAAO,WAAU;aAAjB,CACE,kBAAC,SAAD;IACE,MAAK;IACL,SAAS,CAAC;IACV,WAAW,MAAU;KAEnB,AADA,EAAW,EAAI,GACf,EAAiB,CAAC,EAAM,OAAO,OAAO;IACxC;IACA,WAAU;GACX,CAAA,GACA,EAAE,oCAAoC,6BAA6B,CAC/D;;YAGR,IACC,kBAAC,KAAD;GAAG,WAAU;aACV,EAAE,8BAA8B,0CAA0C,CAAC,CAAC,QAC3E,eACA,EAAM,OACR;EACC,CAAA,IACD,IACF,kBAAC,KAAD;GAAG,WAAU;aACV,EAAE,gCAAgC,uBAAuB;EACzD,CAAA,IACD,EAAM,WAAW,IACnB,kBAAC,KAAD;GAAG,WAAU;aACV,EAAE,8BAA8B,+CAA+C;EAC/E,CAAA,IAEH,kBAAC,MAAD;GAAI,WAAU;aACX,EAAM,KAAK,MAAS;IACnB,IAAM,IAAM,EAAW,EAAK,OAAO,GAC7B,IAAY,EAAe,EAAK,OAAO,GACvC,IAAU,EAAe,EAAK,KAAK,GACnC,IAAU,EAAK,qBAAqB;IAC1C,OACE,kBAAC,MAAD;KAEE,WAAU;eAFZ,CAIE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,QAAD;OACE,WAAU;OACV,eAAY;iBAEZ,kBAAC,GAAD,EAAc,WAAU,SAAU,CAAA;MAC9B,CAAA,GACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,KAAD;QAAG,WAAU;kBACV,KAAO,EAAK;OACZ,CAAA,GACF,IACC,kBAAC,KAAD;QAAG,WAAU;kBACV,IAAU,GAAG,EAAU,KAAK,MAAY;OACxC,CAAA,IACD,IACD;QACF;SACL,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAE,kCAAkC,4BAA4B,CAAC,CAC/D,QAAQ,YAAY,OAAO,KAAK,IAAI,GAAG,EAAK,iBAAiB,CAAC,CAAC,CAAC,CAChE,QAAQ,aAAa,OAAO,EAAK,QAAQ,CAAC;MACzC,CAAA,GACJ,EAAK,WAIH,IACF,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAE,gCAAgC,UAAU;MACzC,CAAA,IAEN,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAE,6BAA6B,WAAW;MACvC,CAAA,IAVN,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAE,+BAA+B,QAAQ;MACtC,CAAA,CAUL;OACH;OAzCG,EAAK,EAyCR;GAER,CAAC;EACC,CAAA;CAEK,CAAA;AAEjB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BookingPanel.d.ts","sourceRoot":"","sources":["../../../src/ggnomad/components/BookingPanel.tsx"],"names":[],"mappings":"AAeA,KAAK,WAAW,GAAG,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC;AAE9D,KAAK,iBAAiB,GAAG;IACvB,WAAW,EAAE,WAAW,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,oGAAoG;AACpG,wBAAgB,YAAY,CAAC,EAC3B,WAAW,EACX,MAAM,EACN,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,SAAS,GACV,EAAE,iBAAiB,+
|
|
1
|
+
{"version":3,"file":"BookingPanel.d.ts","sourceRoot":"","sources":["../../../src/ggnomad/components/BookingPanel.tsx"],"names":[],"mappings":"AAeA,KAAK,WAAW,GAAG,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC;AAE9D,KAAK,iBAAiB,GAAG;IACvB,WAAW,EAAE,WAAW,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,oGAAoG;AACpG,wBAAgB,YAAY,CAAC,EAC3B,WAAW,EACX,MAAM,EACN,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,SAAS,GACV,EAAE,iBAAiB,+BAgUnB"}
|
|
@@ -13,7 +13,7 @@ import { Button as f, GlassCard as p, Input as m } from "@burdenoff/fe-libs/ui";
|
|
|
13
13
|
import { Fragment as h, jsx as g, jsxs as _ } from "react/jsx-runtime";
|
|
14
14
|
//#region src/ggnomad/components/BookingPanel.tsx
|
|
15
15
|
function v({ bookingType: v, itemId: y, itemName: b, unitPrice: x, currency: S, eventDate: C }) {
|
|
16
|
-
let { t: w } = l(), T = u(), { basePath: E = "/" } = r(), [D, O] = o(""), [k, A] = o(""), [j, M] = o(""), [N, P] = o(1), [F, { loading: I }] = d(e), L = v === "PROPERTY" ? t(k, j) : 0, R = x * (v === "PROPERTY" ? L : N), z = R
|
|
16
|
+
let { t: w } = l(), T = u(), { basePath: E = "/" } = r(), [D, O] = o(""), [k, A] = o(""), [j, M] = o(""), [N, P] = o(1), [F, { loading: I }] = d(e), L = v === "PROPERTY" ? t(k, j) : 0, R = x * (v === "PROPERTY" ? L : N), z = R, B = async () => {
|
|
17
17
|
if (v === "PROPERTY") {
|
|
18
18
|
if (!k || !j) {
|
|
19
19
|
a.warning(w("ggnomad.booking.validation.missingDates", "Missing dates"), w("ggnomad.booking.validation.propertyDatesRequired", "Please select both check-in and check-out dates."));
|
|
@@ -40,10 +40,10 @@ function v({ bookingType: v, itemId: y, itemName: b, unitPrice: x, currency: S,
|
|
|
40
40
|
guestName: w("ggnomad.booking.defaultGuestName", "Demo Guest"),
|
|
41
41
|
guestEmail: "demo@burdenoff.com",
|
|
42
42
|
guests: N,
|
|
43
|
-
totalPrice: Math.round(
|
|
43
|
+
totalPrice: Math.round(z * 100) / 100,
|
|
44
44
|
currency: S,
|
|
45
45
|
paymentMethod: "card"
|
|
46
|
-
}, t =
|
|
46
|
+
}, t = v === "PROPERTY" ? {
|
|
47
47
|
...e,
|
|
48
48
|
checkIn: k,
|
|
49
49
|
checkOut: j
|
|
@@ -53,17 +53,17 @@ function v({ bookingType: v, itemId: y, itemName: b, unitPrice: x, currency: S,
|
|
|
53
53
|
} : {
|
|
54
54
|
...e,
|
|
55
55
|
eventDate: D
|
|
56
|
-
} } });
|
|
57
|
-
if (
|
|
58
|
-
a.error(w("ggnomad.booking.toast.createErrorTitle", "Could not create booking"),
|
|
56
|
+
}, n = await F({ variables: { input: t } });
|
|
57
|
+
if (n.error) {
|
|
58
|
+
a.error(w("ggnomad.booking.toast.createErrorTitle", "Could not create booking"), n.error.message);
|
|
59
59
|
return;
|
|
60
60
|
}
|
|
61
|
-
let
|
|
62
|
-
a.success(w("ggnomad.booking.toast.createSuccessTitle", "Booking confirmed"),
|
|
61
|
+
let r = n.data?.createGgnomadBooking;
|
|
62
|
+
a.success(w("ggnomad.booking.toast.createSuccessTitle", "Booking confirmed"), r ? w("ggnomad.booking.toast.createSuccessBody", "{{ref}} · {{item}}").replace("{{ref}}", r.referenceCode).replace("{{item}}", r.itemName) : b), T(i(E, "bookings"));
|
|
63
63
|
} catch (e) {
|
|
64
64
|
a.error(w("ggnomad.booking.toast.createErrorTitle", "Could not create booking"), e instanceof Error ? e.message : String(e));
|
|
65
65
|
}
|
|
66
|
-
},
|
|
66
|
+
}, V = "text-sm font-medium text-text-primary", H = "w-full";
|
|
67
67
|
return /* @__PURE__ */ _(p, {
|
|
68
68
|
treatment: "glass",
|
|
69
69
|
glow: !0,
|
|
@@ -77,18 +77,18 @@ function v({ bookingType: v, itemId: y, itemName: b, unitPrice: x, currency: S,
|
|
|
77
77
|
className: "space-y-4 mb-6",
|
|
78
78
|
children: [v === "PROPERTY" ? /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
|
|
79
79
|
htmlFor: "check-in",
|
|
80
|
-
className:
|
|
80
|
+
className: V,
|
|
81
81
|
children: w("ggnomad.booking.checkIn", "Check-in")
|
|
82
82
|
}), /* @__PURE__ */ g(m, {
|
|
83
83
|
id: "check-in",
|
|
84
84
|
type: "date",
|
|
85
85
|
value: k,
|
|
86
86
|
onChange: (e) => A(e.target.value),
|
|
87
|
-
className: n(
|
|
87
|
+
className: n(H, "mt-2"),
|
|
88
88
|
"aria-label": w("ggnomad.booking.checkIn", "Check-in")
|
|
89
89
|
})] }), /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
|
|
90
90
|
htmlFor: "check-out",
|
|
91
|
-
className:
|
|
91
|
+
className: V,
|
|
92
92
|
children: w("ggnomad.booking.checkOut", "Check-out")
|
|
93
93
|
}), /* @__PURE__ */ g(m, {
|
|
94
94
|
id: "check-out",
|
|
@@ -96,32 +96,32 @@ function v({ bookingType: v, itemId: y, itemName: b, unitPrice: x, currency: S,
|
|
|
96
96
|
min: k || void 0,
|
|
97
97
|
value: j,
|
|
98
98
|
onChange: (e) => M(e.target.value),
|
|
99
|
-
className: n(
|
|
99
|
+
className: n(H, "mt-2"),
|
|
100
100
|
"aria-label": w("ggnomad.booking.checkOut", "Check-out")
|
|
101
101
|
})] })] }) : v === "EVENT" ? /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
|
|
102
102
|
htmlFor: "event-date",
|
|
103
|
-
className:
|
|
103
|
+
className: V,
|
|
104
104
|
children: w("ggnomad.booking.date", "Date")
|
|
105
105
|
}), /* @__PURE__ */ g(m, {
|
|
106
106
|
id: "event-date",
|
|
107
107
|
type: "date",
|
|
108
108
|
value: C ? C.split("T")[0] : "",
|
|
109
109
|
disabled: !0,
|
|
110
|
-
className: n(
|
|
110
|
+
className: n(H, "mt-2"),
|
|
111
111
|
"aria-label": w("ggnomad.booking.date", "Date")
|
|
112
112
|
})] }) : /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
|
|
113
113
|
htmlFor: "booking-date",
|
|
114
|
-
className:
|
|
114
|
+
className: V,
|
|
115
115
|
children: w("ggnomad.booking.date", "Date")
|
|
116
116
|
}), /* @__PURE__ */ g(m, {
|
|
117
117
|
id: "booking-date",
|
|
118
118
|
type: "date",
|
|
119
119
|
value: D,
|
|
120
120
|
onChange: (e) => O(e.target.value),
|
|
121
|
-
className: n(
|
|
121
|
+
className: n(H, "mt-2"),
|
|
122
122
|
"aria-label": w("ggnomad.booking.date", "Date")
|
|
123
123
|
})] }), /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
|
|
124
|
-
className:
|
|
124
|
+
className: V,
|
|
125
125
|
children: v === "PROPERTY" ? w("ggnomad.booking.guests", "Guests") : v === "EVENT" ? w("ggnomad.booking.tickets", "Tickets") : w("ggnomad.booking.participants", "Participants")
|
|
126
126
|
}), /* @__PURE__ */ _("div", {
|
|
127
127
|
className: "flex items-center gap-2 mt-2",
|
|
@@ -160,60 +160,43 @@ function v({ bookingType: v, itemId: y, itemName: b, unitPrice: x, currency: S,
|
|
|
160
160
|
children: w("ggnomad.booking.selectDatesForTotal", "Select check-in and check-out dates to see the total.")
|
|
161
161
|
}) : /* @__PURE__ */ _("div", {
|
|
162
162
|
className: "space-y-2 mb-6 p-3 rounded-md bg-bg-sunken",
|
|
163
|
-
children: [
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
/* @__PURE__ */
|
|
186
|
-
className: "
|
|
187
|
-
children:
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
})]
|
|
198
|
-
}),
|
|
199
|
-
/* @__PURE__ */ _("div", {
|
|
200
|
-
className: "border-t border-border-seam pt-2 flex justify-between",
|
|
201
|
-
children: [/* @__PURE__ */ g("span", {
|
|
202
|
-
className: "font-semibold text-text-primary",
|
|
203
|
-
children: w("ggnomad.booking.total", "Total")
|
|
204
|
-
}), /* @__PURE__ */ _("span", {
|
|
205
|
-
className: "font-bold text-lg text-text-primary",
|
|
206
|
-
children: [
|
|
207
|
-
S,
|
|
208
|
-
" ",
|
|
209
|
-
B.toFixed(2)
|
|
210
|
-
]
|
|
211
|
-
})]
|
|
212
|
-
})
|
|
213
|
-
]
|
|
163
|
+
children: [/* @__PURE__ */ _("div", {
|
|
164
|
+
className: "flex justify-between text-sm",
|
|
165
|
+
children: [/* @__PURE__ */ _("span", {
|
|
166
|
+
className: "text-text-secondary",
|
|
167
|
+
children: [
|
|
168
|
+
S,
|
|
169
|
+
" ",
|
|
170
|
+
x.toFixed(2),
|
|
171
|
+
" ×",
|
|
172
|
+
" ",
|
|
173
|
+
v === "PROPERTY" ? L === 1 ? `1 ${w("ggnomad.booking.night", "night")}` : `${L} ${w("ggnomad.booking.nights", "nights")}` : `${N} ${w("ggnomad.booking.item", "item")}`
|
|
174
|
+
]
|
|
175
|
+
}), /* @__PURE__ */ _("span", {
|
|
176
|
+
className: "text-text-primary font-medium",
|
|
177
|
+
children: [
|
|
178
|
+
S,
|
|
179
|
+
" ",
|
|
180
|
+
R.toFixed(2)
|
|
181
|
+
]
|
|
182
|
+
})]
|
|
183
|
+
}), /* @__PURE__ */ _("div", {
|
|
184
|
+
className: "border-t border-border-seam pt-2 flex justify-between",
|
|
185
|
+
children: [/* @__PURE__ */ g("span", {
|
|
186
|
+
className: "font-semibold text-text-primary",
|
|
187
|
+
children: w("ggnomad.booking.total", "Total")
|
|
188
|
+
}), /* @__PURE__ */ _("span", {
|
|
189
|
+
className: "font-bold text-lg text-text-primary",
|
|
190
|
+
children: [
|
|
191
|
+
S,
|
|
192
|
+
" ",
|
|
193
|
+
z.toFixed(2)
|
|
194
|
+
]
|
|
195
|
+
})]
|
|
196
|
+
})]
|
|
214
197
|
}),
|
|
215
198
|
/* @__PURE__ */ g(f, {
|
|
216
|
-
onClick: () => void
|
|
199
|
+
onClick: () => void B(),
|
|
217
200
|
disabled: I,
|
|
218
201
|
className: "w-full bg-gradient-to-r from-action-primary-bg to-action-success-bg text-white font-semibold",
|
|
219
202
|
size: "lg",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BookingPanel.js","names":[],"sources":["../../../src/ggnomad/components/BookingPanel.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useMutation } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useNavigate } from 'react-router';\nimport { Minus, Plus } from 'lucide-react';\nimport { Button, GlassCard, Input } from '@burdenoff/fe-libs/ui';\n\nimport { CREATE_GGNOMAD_BOOKING_MUTATION } from '../graphql';\nimport { useGGNomad } from '../providers/GGNomadProvider';\nimport { joinPath } from '../utils/navigation';\nimport { notify } from '../utils/notify';\nimport { nightsBetween } from '../utils/format';\nimport { cn } from '../utils/cn';\n\ntype BookingType = 'PROPERTY' | 'TOUR' | 'EVENT' | 'ACTIVITY';\n\ntype BookingPanelProps = {\n bookingType: BookingType;\n itemId: string;\n itemName: string;\n unitPrice: number;\n currency: string;\n /** For EVENT bookings, the event date (ISO string) */\n eventDate?: string;\n};\n\n/** Frosted glass booking panel with type-specific inputs and real createGgnomadBooking mutation. */\nexport function BookingPanel({\n bookingType,\n itemId,\n itemName,\n unitPrice,\n currency,\n eventDate,\n}: BookingPanelProps) {\n const { t } = useI18n();\n const navigate = useNavigate();\n const { basePath = '/' } = useGGNomad();\n\n // For properties: check-in and check-out dates\n // For events/tours/activities: a single date\n const [selectedDate, setSelectedDate] = useState<string>('');\n const [checkInDate, setCheckInDate] = useState<string>('');\n const [checkOutDate, setCheckOutDate] = useState<string>('');\n\n // Quantity: guests (property), participants (tour/activity), tickets (event)\n const [quantity, setQuantity] = useState(1);\n\n type BookingInput = {\n bookingType: BookingType;\n itemId: string;\n itemName: string;\n guestName: string;\n guestEmail: string;\n guests: number;\n totalPrice: number;\n currency: string;\n paymentMethod: string;\n checkIn?: string;\n checkOut?: string;\n eventDate?: string;\n };\n\n const [createBooking, { loading: creating }] = useMutation(\n CREATE_GGNOMAD_BOOKING_MUTATION as TypedDocumentNode<\n { createGgnomadBooking: { id: string; referenceCode: string; itemName: string } },\n { input: BookingInput }\n >\n );\n\n // A property is priced per night, so the multiplier is the length of the selected\n // stay — derived from the two date inputs above rather than assumed. Every other\n // listing type is priced per ticket / seat / participant.\n const nights = bookingType === 'PROPERTY' ? nightsBetween(checkInDate, checkOutDate) : 0;\n const multiplier = bookingType === 'PROPERTY' ? nights : quantity;\n const subtotal = unitPrice * multiplier;\n const taxAmount = subtotal * 0.1; // 10% tax\n const total = subtotal + taxAmount;\n\n const handleBooking = async () => {\n // Validate required fields\n if (bookingType === 'PROPERTY') {\n if (!checkInDate || !checkOutDate) {\n notify.warning(\n t('ggnomad.booking.validation.missingDates', 'Missing dates'),\n t(\n 'ggnomad.booking.validation.propertyDatesRequired',\n 'Please select both check-in and check-out dates.'\n )\n );\n return;\n }\n if (nights < 1) {\n notify.warning(\n t('ggnomad.booking.validation.invalidStayTitle', 'Check-out must be after check-in'),\n t(\n 'ggnomad.booking.validation.invalidStayBody',\n 'Pick a check-out date at least one night after check-in.'\n )\n );\n return;\n }\n } else if (bookingType === 'EVENT') {\n if (!eventDate) {\n notify.warning(\n t('ggnomad.booking.validation.missingDate', 'Missing date'),\n t('ggnomad.booking.validation.eventDateRequired', 'Event date is required for booking.')\n );\n return;\n }\n } else {\n if (!selectedDate) {\n notify.warning(\n t('ggnomad.booking.validation.missingDate', 'Missing date'),\n t('ggnomad.booking.validation.dateRequired', 'Please select a date for your booking.')\n );\n return;\n }\n }\n\n try {\n const baseInput = {\n bookingType,\n itemId,\n itemName,\n guestName: t('ggnomad.booking.defaultGuestName', 'Demo Guest'),\n guestEmail: 'demo@burdenoff.com',\n guests: quantity,\n totalPrice: Math.round(total * 100) / 100,\n currency,\n paymentMethod: 'card',\n };\n\n const input: BookingInput =\n bookingType === 'PROPERTY'\n ? { ...baseInput, checkIn: checkInDate, checkOut: checkOutDate }\n : bookingType === 'EVENT'\n ? { ...baseInput, eventDate: eventDate }\n : { ...baseInput, eventDate: selectedDate };\n\n const result = await createBooking({\n variables: { input },\n });\n\n if (result.error) {\n notify.error(\n t('ggnomad.booking.toast.createErrorTitle', 'Could not create booking'),\n result.error.message\n );\n return;\n }\n\n const created = result.data?.createGgnomadBooking;\n notify.success(\n t('ggnomad.booking.toast.createSuccessTitle', 'Booking confirmed'),\n created\n ? t('ggnomad.booking.toast.createSuccessBody', '{{ref}} · {{item}}')\n .replace('{{ref}}', created.referenceCode)\n .replace('{{item}}', created.itemName)\n : itemName\n );\n\n // Navigate to bookings list\n navigate(joinPath(basePath, 'bookings'));\n } catch (err) {\n notify.error(\n t('ggnomad.booking.toast.createErrorTitle', 'Could not create booking'),\n err instanceof Error ? err.message : String(err)\n );\n }\n };\n\n const labelClass = 'text-sm font-medium text-text-primary';\n const inputClass = 'w-full';\n\n return (\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam p-5 sticky top-6\">\n <h2 className=\"mb-4 text-lg font-medium tracking-tight text-text-primary\">\n {bookingType === 'PROPERTY'\n ? t('ggnomad.booking.title.property', 'Book this stay')\n : bookingType === 'EVENT'\n ? t('ggnomad.booking.title.event', 'Book tickets')\n : bookingType === 'TOUR'\n ? t('ggnomad.booking.title.tour', 'Book this tour')\n : t('ggnomad.booking.title.activity', 'Book this activity')}\n </h2>\n\n <div className=\"space-y-4 mb-6\">\n {/* Dates section */}\n {bookingType === 'PROPERTY' ? (\n <>\n <div>\n <label htmlFor=\"check-in\" className={labelClass}>\n {t('ggnomad.booking.checkIn', 'Check-in')}\n </label>\n <Input\n id=\"check-in\"\n type=\"date\"\n value={checkInDate}\n onChange={(e) => setCheckInDate(e.target.value)}\n className={cn(inputClass, 'mt-2')}\n aria-label={t('ggnomad.booking.checkIn', 'Check-in')}\n />\n </div>\n <div>\n <label htmlFor=\"check-out\" className={labelClass}>\n {t('ggnomad.booking.checkOut', 'Check-out')}\n </label>\n <Input\n id=\"check-out\"\n type=\"date\"\n min={checkInDate || undefined}\n value={checkOutDate}\n onChange={(e) => setCheckOutDate(e.target.value)}\n className={cn(inputClass, 'mt-2')}\n aria-label={t('ggnomad.booking.checkOut', 'Check-out')}\n />\n </div>\n </>\n ) : bookingType === 'EVENT' ? (\n <div>\n <label htmlFor=\"event-date\" className={labelClass}>\n {t('ggnomad.booking.date', 'Date')}\n </label>\n <Input\n id=\"event-date\"\n type=\"date\"\n value={eventDate ? eventDate.split('T')[0] : ''}\n disabled\n className={cn(inputClass, 'mt-2')}\n aria-label={t('ggnomad.booking.date', 'Date')}\n />\n </div>\n ) : (\n <div>\n <label htmlFor=\"booking-date\" className={labelClass}>\n {t('ggnomad.booking.date', 'Date')}\n </label>\n <Input\n id=\"booking-date\"\n type=\"date\"\n value={selectedDate}\n onChange={(e) => setSelectedDate(e.target.value)}\n className={cn(inputClass, 'mt-2')}\n aria-label={t('ggnomad.booking.date', 'Date')}\n />\n </div>\n )}\n\n {/* Quantity section */}\n <div>\n <label className={labelClass}>\n {bookingType === 'PROPERTY'\n ? t('ggnomad.booking.guests', 'Guests')\n : bookingType === 'EVENT'\n ? t('ggnomad.booking.tickets', 'Tickets')\n : t('ggnomad.booking.participants', 'Participants')}\n </label>\n <div className=\"flex items-center gap-2 mt-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setQuantity(Math.max(1, quantity - 1))}\n disabled={quantity <= 1 || creating}\n aria-label={t('ggnomad.booking.decreaseQuantity', 'Decrease quantity')}\n className=\"flex-shrink-0\"\n >\n <Minus className=\"size-4\" />\n </Button>\n <input\n type=\"number\"\n min={1}\n value={quantity}\n onChange={(e) => setQuantity(Math.max(1, parseInt(e.target.value) || 1))}\n className=\"flex-1 h-9 rounded-input border border-border-default bg-bg-surface px-3 py-1 text-center text-sm text-text-primary\"\n aria-label={\n bookingType === 'PROPERTY'\n ? t('ggnomad.booking.guests', 'Guests')\n : bookingType === 'EVENT'\n ? t('ggnomad.booking.tickets', 'Tickets')\n : t('ggnomad.booking.participants', 'Participants')\n }\n />\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setQuantity(quantity + 1)}\n disabled={creating}\n aria-label={t('ggnomad.booking.increaseQuantity', 'Increase quantity')}\n className=\"flex-shrink-0\"\n >\n <Plus className=\"size-4\" />\n </Button>\n </div>\n </div>\n </div>\n\n {/* Pricing breakdown — a per-night total is only meaningful once the stay\n length is known, so it is withheld until both dates are chosen. */}\n {bookingType === 'PROPERTY' && nights < 1 ? (\n <div className=\"mb-6 rounded-md bg-bg-sunken p-3 text-sm text-text-secondary\">\n {t(\n 'ggnomad.booking.selectDatesForTotal',\n 'Select check-in and check-out dates to see the total.'\n )}\n </div>\n ) : (\n <div className=\"space-y-2 mb-6 p-3 rounded-md bg-bg-sunken\">\n <div className=\"flex justify-between text-sm\">\n <span className=\"text-text-secondary\">\n {currency} {unitPrice.toFixed(2)} ×{' '}\n {bookingType === 'PROPERTY'\n ? nights === 1\n ? `1 ${t('ggnomad.booking.night', 'night')}`\n : `${nights} ${t('ggnomad.booking.nights', 'nights')}`\n : `${quantity} ${t('ggnomad.booking.item', 'item')}`}\n </span>\n <span className=\"text-text-primary font-medium\">\n {currency} {subtotal.toFixed(2)}\n </span>\n </div>\n <div className=\"flex justify-between text-sm\">\n <span className=\"text-text-secondary\">{t('ggnomad.booking.tax', 'Tax (10%)')}</span>\n <span className=\"text-text-primary\">\n {currency} {taxAmount.toFixed(2)}\n </span>\n </div>\n <div className=\"border-t border-border-seam pt-2 flex justify-between\">\n <span className=\"font-semibold text-text-primary\">\n {t('ggnomad.booking.total', 'Total')}\n </span>\n <span className=\"font-bold text-lg text-text-primary\">\n {currency} {total.toFixed(2)}\n </span>\n </div>\n </div>\n )}\n\n {/* Book button */}\n <Button\n onClick={() => void handleBooking()}\n disabled={creating}\n className=\"w-full bg-gradient-to-r from-action-primary-bg to-action-success-bg text-white font-semibold\"\n size=\"lg\"\n >\n {creating\n ? t('ggnomad.booking.button.creating', 'Creating booking…')\n : bookingType === 'PROPERTY'\n ? t('ggnomad.booking.button.property', 'Book this stay')\n : bookingType === 'EVENT'\n ? t('ggnomad.booking.button.event', 'Book tickets')\n : bookingType === 'TOUR'\n ? t('ggnomad.booking.button.tour', 'Book this tour')\n : t('ggnomad.booking.button.activity', 'Book this activity')}\n </Button>\n </GlassCard>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BookingPanel.js","names":[],"sources":["../../../src/ggnomad/components/BookingPanel.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useMutation } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useNavigate } from 'react-router';\nimport { Minus, Plus } from 'lucide-react';\nimport { Button, GlassCard, Input } from '@burdenoff/fe-libs/ui';\n\nimport { CREATE_GGNOMAD_BOOKING_MUTATION } from '../graphql';\nimport { useGGNomad } from '../providers/GGNomadProvider';\nimport { joinPath } from '../utils/navigation';\nimport { notify } from '../utils/notify';\nimport { nightsBetween } from '../utils/format';\nimport { cn } from '../utils/cn';\n\ntype BookingType = 'PROPERTY' | 'TOUR' | 'EVENT' | 'ACTIVITY';\n\ntype BookingPanelProps = {\n bookingType: BookingType;\n itemId: string;\n itemName: string;\n unitPrice: number;\n currency: string;\n /** For EVENT bookings, the event date (ISO string) */\n eventDate?: string;\n};\n\n/** Frosted glass booking panel with type-specific inputs and real createGgnomadBooking mutation. */\nexport function BookingPanel({\n bookingType,\n itemId,\n itemName,\n unitPrice,\n currency,\n eventDate,\n}: BookingPanelProps) {\n const { t } = useI18n();\n const navigate = useNavigate();\n const { basePath = '/' } = useGGNomad();\n\n // For properties: check-in and check-out dates\n // For events/tours/activities: a single date\n const [selectedDate, setSelectedDate] = useState<string>('');\n const [checkInDate, setCheckInDate] = useState<string>('');\n const [checkOutDate, setCheckOutDate] = useState<string>('');\n\n // Quantity: guests (property), participants (tour/activity), tickets (event)\n const [quantity, setQuantity] = useState(1);\n\n type BookingInput = {\n bookingType: BookingType;\n itemId: string;\n itemName: string;\n guestName: string;\n guestEmail: string;\n guests: number;\n totalPrice: number;\n currency: string;\n paymentMethod: string;\n checkIn?: string;\n checkOut?: string;\n eventDate?: string;\n };\n\n const [createBooking, { loading: creating }] = useMutation(\n CREATE_GGNOMAD_BOOKING_MUTATION as TypedDocumentNode<\n { createGgnomadBooking: { id: string; referenceCode: string; itemName: string } },\n { input: BookingInput }\n >\n );\n\n // A property is priced per night, so the multiplier is the length of the selected\n // stay — derived from the two date inputs above rather than assumed. Every other\n // listing type is priced per ticket / seat / participant.\n const nights = bookingType === 'PROPERTY' ? nightsBetween(checkInDate, checkOutDate) : 0;\n const multiplier = bookingType === 'PROPERTY' ? nights : quantity;\n const subtotal = unitPrice * multiplier;\n // BOFF-10193 — the service prices the booking from the listing (nightly or\n // per-person rate x the quantity, plus any demand surcharge on busy dates)\n // and does not add tax, so the panel shows that same amount. The 10% \"tax\"\n // line this panel used to add had no counterpart in the booking record.\n const total = subtotal;\n\n const handleBooking = async () => {\n // Validate required fields\n if (bookingType === 'PROPERTY') {\n if (!checkInDate || !checkOutDate) {\n notify.warning(\n t('ggnomad.booking.validation.missingDates', 'Missing dates'),\n t(\n 'ggnomad.booking.validation.propertyDatesRequired',\n 'Please select both check-in and check-out dates.'\n )\n );\n return;\n }\n if (nights < 1) {\n notify.warning(\n t('ggnomad.booking.validation.invalidStayTitle', 'Check-out must be after check-in'),\n t(\n 'ggnomad.booking.validation.invalidStayBody',\n 'Pick a check-out date at least one night after check-in.'\n )\n );\n return;\n }\n } else if (bookingType === 'EVENT') {\n if (!eventDate) {\n notify.warning(\n t('ggnomad.booking.validation.missingDate', 'Missing date'),\n t('ggnomad.booking.validation.eventDateRequired', 'Event date is required for booking.')\n );\n return;\n }\n } else {\n if (!selectedDate) {\n notify.warning(\n t('ggnomad.booking.validation.missingDate', 'Missing date'),\n t('ggnomad.booking.validation.dateRequired', 'Please select a date for your booking.')\n );\n return;\n }\n }\n\n try {\n const baseInput = {\n bookingType,\n itemId,\n itemName,\n guestName: t('ggnomad.booking.defaultGuestName', 'Demo Guest'),\n guestEmail: 'demo@burdenoff.com',\n guests: quantity,\n totalPrice: Math.round(total * 100) / 100,\n currency,\n paymentMethod: 'card',\n };\n\n const input: BookingInput =\n bookingType === 'PROPERTY'\n ? { ...baseInput, checkIn: checkInDate, checkOut: checkOutDate }\n : bookingType === 'EVENT'\n ? { ...baseInput, eventDate: eventDate }\n : { ...baseInput, eventDate: selectedDate };\n\n const result = await createBooking({\n variables: { input },\n });\n\n if (result.error) {\n notify.error(\n t('ggnomad.booking.toast.createErrorTitle', 'Could not create booking'),\n result.error.message\n );\n return;\n }\n\n const created = result.data?.createGgnomadBooking;\n notify.success(\n t('ggnomad.booking.toast.createSuccessTitle', 'Booking confirmed'),\n created\n ? t('ggnomad.booking.toast.createSuccessBody', '{{ref}} · {{item}}')\n .replace('{{ref}}', created.referenceCode)\n .replace('{{item}}', created.itemName)\n : itemName\n );\n\n // Navigate to bookings list\n navigate(joinPath(basePath, 'bookings'));\n } catch (err) {\n notify.error(\n t('ggnomad.booking.toast.createErrorTitle', 'Could not create booking'),\n err instanceof Error ? err.message : String(err)\n );\n }\n };\n\n const labelClass = 'text-sm font-medium text-text-primary';\n const inputClass = 'w-full';\n\n return (\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam p-5 sticky top-6\">\n <h2 className=\"mb-4 text-lg font-medium tracking-tight text-text-primary\">\n {bookingType === 'PROPERTY'\n ? t('ggnomad.booking.title.property', 'Book this stay')\n : bookingType === 'EVENT'\n ? t('ggnomad.booking.title.event', 'Book tickets')\n : bookingType === 'TOUR'\n ? t('ggnomad.booking.title.tour', 'Book this tour')\n : t('ggnomad.booking.title.activity', 'Book this activity')}\n </h2>\n\n <div className=\"space-y-4 mb-6\">\n {/* Dates section */}\n {bookingType === 'PROPERTY' ? (\n <>\n <div>\n <label htmlFor=\"check-in\" className={labelClass}>\n {t('ggnomad.booking.checkIn', 'Check-in')}\n </label>\n <Input\n id=\"check-in\"\n type=\"date\"\n value={checkInDate}\n onChange={(e) => setCheckInDate(e.target.value)}\n className={cn(inputClass, 'mt-2')}\n aria-label={t('ggnomad.booking.checkIn', 'Check-in')}\n />\n </div>\n <div>\n <label htmlFor=\"check-out\" className={labelClass}>\n {t('ggnomad.booking.checkOut', 'Check-out')}\n </label>\n <Input\n id=\"check-out\"\n type=\"date\"\n min={checkInDate || undefined}\n value={checkOutDate}\n onChange={(e) => setCheckOutDate(e.target.value)}\n className={cn(inputClass, 'mt-2')}\n aria-label={t('ggnomad.booking.checkOut', 'Check-out')}\n />\n </div>\n </>\n ) : bookingType === 'EVENT' ? (\n <div>\n <label htmlFor=\"event-date\" className={labelClass}>\n {t('ggnomad.booking.date', 'Date')}\n </label>\n <Input\n id=\"event-date\"\n type=\"date\"\n value={eventDate ? eventDate.split('T')[0] : ''}\n disabled\n className={cn(inputClass, 'mt-2')}\n aria-label={t('ggnomad.booking.date', 'Date')}\n />\n </div>\n ) : (\n <div>\n <label htmlFor=\"booking-date\" className={labelClass}>\n {t('ggnomad.booking.date', 'Date')}\n </label>\n <Input\n id=\"booking-date\"\n type=\"date\"\n value={selectedDate}\n onChange={(e) => setSelectedDate(e.target.value)}\n className={cn(inputClass, 'mt-2')}\n aria-label={t('ggnomad.booking.date', 'Date')}\n />\n </div>\n )}\n\n {/* Quantity section */}\n <div>\n <label className={labelClass}>\n {bookingType === 'PROPERTY'\n ? t('ggnomad.booking.guests', 'Guests')\n : bookingType === 'EVENT'\n ? t('ggnomad.booking.tickets', 'Tickets')\n : t('ggnomad.booking.participants', 'Participants')}\n </label>\n <div className=\"flex items-center gap-2 mt-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setQuantity(Math.max(1, quantity - 1))}\n disabled={quantity <= 1 || creating}\n aria-label={t('ggnomad.booking.decreaseQuantity', 'Decrease quantity')}\n className=\"flex-shrink-0\"\n >\n <Minus className=\"size-4\" />\n </Button>\n <input\n type=\"number\"\n min={1}\n value={quantity}\n onChange={(e) => setQuantity(Math.max(1, parseInt(e.target.value) || 1))}\n className=\"flex-1 h-9 rounded-input border border-border-default bg-bg-surface px-3 py-1 text-center text-sm text-text-primary\"\n aria-label={\n bookingType === 'PROPERTY'\n ? t('ggnomad.booking.guests', 'Guests')\n : bookingType === 'EVENT'\n ? t('ggnomad.booking.tickets', 'Tickets')\n : t('ggnomad.booking.participants', 'Participants')\n }\n />\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setQuantity(quantity + 1)}\n disabled={creating}\n aria-label={t('ggnomad.booking.increaseQuantity', 'Increase quantity')}\n className=\"flex-shrink-0\"\n >\n <Plus className=\"size-4\" />\n </Button>\n </div>\n </div>\n </div>\n\n {/* Pricing breakdown — a per-night total is only meaningful once the stay\n length is known, so it is withheld until both dates are chosen. */}\n {bookingType === 'PROPERTY' && nights < 1 ? (\n <div className=\"mb-6 rounded-md bg-bg-sunken p-3 text-sm text-text-secondary\">\n {t(\n 'ggnomad.booking.selectDatesForTotal',\n 'Select check-in and check-out dates to see the total.'\n )}\n </div>\n ) : (\n <div className=\"space-y-2 mb-6 p-3 rounded-md bg-bg-sunken\">\n <div className=\"flex justify-between text-sm\">\n <span className=\"text-text-secondary\">\n {currency} {unitPrice.toFixed(2)} ×{' '}\n {bookingType === 'PROPERTY'\n ? nights === 1\n ? `1 ${t('ggnomad.booking.night', 'night')}`\n : `${nights} ${t('ggnomad.booking.nights', 'nights')}`\n : `${quantity} ${t('ggnomad.booking.item', 'item')}`}\n </span>\n <span className=\"text-text-primary font-medium\">\n {currency} {subtotal.toFixed(2)}\n </span>\n </div>\n <div className=\"border-t border-border-seam pt-2 flex justify-between\">\n <span className=\"font-semibold text-text-primary\">\n {t('ggnomad.booking.total', 'Total')}\n </span>\n <span className=\"font-bold text-lg text-text-primary\">\n {currency} {total.toFixed(2)}\n </span>\n </div>\n </div>\n )}\n\n {/* Book button */}\n <Button\n onClick={() => void handleBooking()}\n disabled={creating}\n className=\"w-full bg-gradient-to-r from-action-primary-bg to-action-success-bg text-white font-semibold\"\n size=\"lg\"\n >\n {creating\n ? t('ggnomad.booking.button.creating', 'Creating booking…')\n : bookingType === 'PROPERTY'\n ? t('ggnomad.booking.button.property', 'Book this stay')\n : bookingType === 'EVENT'\n ? t('ggnomad.booking.button.event', 'Book tickets')\n : bookingType === 'TOUR'\n ? t('ggnomad.booking.button.tour', 'Book this tour')\n : t('ggnomad.booking.button.activity', 'Book this activity')}\n </Button>\n </GlassCard>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CreateProviderDialog.js","names":[],"sources":["../../../src/ggnomad/components/CreateProviderDialog.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useMutation } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Briefcase } from 'lucide-react';\nimport {\n ResponsiveDialog,\n ResponsiveDialogClose,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n} from '@burdenoff/fe-libs/ui';\n\nimport { CREATE_GGNOMAD_PROVIDER_MUTATION } from '../graphql';\nimport { notify } from '../utils/notify';\nimport { getProviderTypeOptions } from '../utils/providerTypeMeta';\n\ninterface CreateProviderDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n /** Called with the raw backend response once the mutation succeeds, so the\n * caller can decide whether to refetch, navigate, etc. */\n onCreated?: (provider: { id: string; name: string; type: string; status: string }) => void;\n}\n\ninterface CreateGgnomadProviderInput {\n name: string;\n type: string;\n contactEmail?: string | null;\n contactPhone?: string | null;\n website?: string | null;\n description?: string | null;\n}\n\nconst fieldClass =\n 'mt-1 w-full rounded-md border border-border-seam bg-bg-canvas px-3 py-2 text-sm text-text-primary placeholder-text-secondary focus:border-action-primary-bg focus:outline-none focus:ring-1 focus:ring-action-primary-bg disabled:opacity-50';\nconst labelClass = 'block text-sm font-medium text-text-primary';\n\n// Lightweight format checks — anything stricter belongs in the backend.\nconst EMAIL_RE = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\nconst URL_RE = /^(https?:\\/\\/)[^\\s]+$/i;\n\n// Phone inputs accept digits plus common dial/format punctuation only.\n// Letters are stripped as they are typed so invalid input never lands.\nconst PHONE_DISALLOWED_RE = /[^0-9+\\-().\\s]/g;\n// E.164: up to 15 digits; we require at least 7 to be a plausible number.\nconst PHONE_MIN_DIGITS = 7;\nconst PHONE_MAX_DIGITS = 15;\n\nfunction sanitizePhoneInput(value: string): string {\n return value.replace(PHONE_DISALLOWED_RE, '');\n}\n\n/**\n * Form for onboarding a new B2B provider (BOFF-8177). The backend\n * `createGgnomadProvider` mutation is gated by `ggnomad:create`, which\n * every managed GGNomad role holds by default. On submit, the new\n * provider appears at `status: ACTIVE` immediately (the backend hardcodes\n * this on create; see `service.createProvider`).\n */\nexport function CreateProviderDialog({ open, onOpenChange, onCreated }: CreateProviderDialogProps) {\n const { t } = useI18n();\n const providerTypeOptions = getProviderTypeOptions(t);\n const [name, setName] = useState('');\n const [type, setType] = useState('');\n const [contactEmail, setContactEmail] = useState('');\n const [contactPhone, setContactPhone] = useState('');\n const [website, setWebsite] = useState('');\n const [description, setDescription] = useState('');\n\n const [createProvider, { loading }] = useMutation(\n CREATE_GGNOMAD_PROVIDER_MUTATION as TypedDocumentNode<\n {\n createGgnomadProvider: {\n id: string;\n name: string;\n type: string;\n status: string;\n };\n },\n { input: CreateGgnomadProviderInput }\n >\n );\n\n const resetForm = () => {\n setName('');\n setType('');\n setContactEmail('');\n setContactPhone('');\n setWebsite('');\n setDescription('');\n };\n\n const handleCreate = async () => {\n if (!name.trim()) {\n notify.warning(\n t('ggnomad.provider.createDialog.nameRequiredTitle', 'Name is required'),\n t(\n 'ggnomad.provider.createDialog.nameRequiredBody',\n 'Give this provider a recognizable name.'\n )\n );\n return;\n }\n if (!type.trim()) {\n notify.warning(\n t('ggnomad.provider.createDialog.typeRequiredTitle', 'Type is required'),\n t(\n 'ggnomad.provider.createDialog.typeRequiredBody',\n 'Pick a provider type from the dropdown.'\n )\n );\n return;\n }\n\n const trimmedEmail = contactEmail.trim();\n if (trimmedEmail && !EMAIL_RE.test(trimmedEmail)) {\n notify.warning(\n t('ggnomad.provider.createDialog.invalidEmailTitle', 'Invalid email'),\n t(\n 'ggnomad.provider.createDialog.invalidEmailBody',\n 'Enter a valid email address or leave the field blank.'\n )\n );\n return;\n }\n const trimmedWebsite = website.trim();\n if (trimmedWebsite && !URL_RE.test(trimmedWebsite)) {\n notify.warning(\n t('ggnomad.provider.createDialog.invalidWebsiteTitle', 'Invalid website'),\n t(\n 'ggnomad.provider.createDialog.invalidWebsiteBody',\n 'Website must start with http:// or https://'\n )\n );\n return;\n }\n const trimmedPhone = contactPhone.trim();\n const phoneDigits = trimmedPhone.replace(/\\D/g, '').length;\n if (trimmedPhone && (phoneDigits < PHONE_MIN_DIGITS || phoneDigits > PHONE_MAX_DIGITS)) {\n notify.warning(\n t('ggnomad.provider.createDialog.invalidPhoneTitle', 'Invalid phone number'),\n t(\n 'ggnomad.provider.createDialog.invalidPhoneBody',\n 'Enter a valid phone number or leave the field blank.'\n )\n );\n return;\n }\n\n const input: CreateGgnomadProviderInput = {\n name: name.trim(),\n type: type as string,\n contactEmail: trimmedEmail || null,\n contactPhone: trimmedPhone || null,\n website: trimmedWebsite || null,\n description: description.trim() || null,\n };\n\n try {\n const result = await createProvider({ variables: { input } });\n // With errorPolicy: 'all' the mutation does not throw on failure; errors\n // surface on result.error. Without this check a failed create would\n // show \"Provider created\" and reset the form as if it had succeeded.\n if (result.error) {\n notify.error(\n t('ggnomad.provider.createDialog.errorTitle', 'Could not create provider'),\n result.error.message\n );\n return;\n }\n const created = result.data?.createGgnomadProvider;\n notify.success(\n t('ggnomad.provider.createDialog.successTitle', 'Provider created'),\n created?.name ?? name\n );\n resetForm();\n onOpenChange(false);\n if (created) onCreated?.(created);\n } catch (err) {\n notify.error(\n t('ggnomad.provider.createDialog.errorTitle', 'Could not create provider'),\n err instanceof Error ? err.message : String(err)\n );\n }\n };\n\n return (\n <ResponsiveDialog\n open={open}\n onOpenChange={(next) => {\n if (!next) resetForm();\n onOpenChange(next);\n }}\n >\n <ResponsiveDialogContent className=\"max-w-2xl\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle className=\"flex items-center gap-2\">\n <Briefcase className=\"size-4\" />\n {t('ggnomad.provider.createDialog.title', 'New provider')}\n </ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n {t(\n 'ggnomad.provider.createDialog.description',\n 'Onboard a new B2B supplier. They become ACTIVE immediately and can be selected as the host when you create listings.'\n )}\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <div className=\"sm:col-span-2\">\n <label htmlFor=\"provider-name\" className={labelClass}>\n {t('ggnomad.provider.createDialog.nameLabel', 'Provider name')}{' '}\n <span className=\"text-action-danger-text\">*</span>\n </label>\n <input\n id=\"provider-name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n disabled={loading}\n className={fieldClass}\n placeholder={t(\n 'ggnomad.provider.createDialog.namePlaceholder',\n 'e.g. Alpine Treks Co.'\n )}\n />\n </div>\n\n <div className=\"sm:col-span-2\">\n <label htmlFor=\"provider-type\" className={labelClass}>\n {t('ggnomad.provider.createDialog.typeLabel', 'Type')}{' '}\n <span className=\"text-action-danger-text\">*</span>\n </label>\n <select\n id=\"provider-type\"\n value={type}\n onChange={(e) => setType(e.target.value as string)}\n disabled={loading}\n className={fieldClass}\n >\n <option value=\"\">\n {t('ggnomad.provider.createDialog.typePlaceholder', 'Select a type…')}\n </option>\n {providerTypeOptions.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n </div>\n\n <div>\n <label htmlFor=\"provider-email\" className={labelClass}>\n {t('ggnomad.provider.createDialog.emailLabel', 'Contact email (optional)')}\n </label>\n <input\n id=\"provider-email\"\n type=\"email\"\n value={contactEmail}\n onChange={(e) => setContactEmail(e.target.value)}\n disabled={loading}\n className={fieldClass}\n placeholder=\"hello@alpinetreks.com\"\n />\n </div>\n\n <div>\n <label htmlFor=\"provider-phone\" className={labelClass}>\n {t('ggnomad.provider.createDialog.phoneLabel', 'Contact phone (optional)')}\n </label>\n <input\n id=\"provider-phone\"\n type=\"tel\"\n inputMode=\"tel\"\n autoComplete=\"tel\"\n value={contactPhone}\n onChange={(e) => setContactPhone(sanitizePhoneInput(e.target.value))}\n disabled={loading}\n className={fieldClass}\n placeholder=\"+91 98765 43210\"\n />\n </div>\n\n <div className=\"sm:col-span-2\">\n <label htmlFor=\"provider-website\" className={labelClass}>\n {t('ggnomad.provider.createDialog.websiteLabel', 'Website (optional)')}\n </label>\n <input\n id=\"provider-website\"\n type=\"url\"\n value={website}\n onChange={(e) => setWebsite(e.target.value)}\n disabled={loading}\n className={fieldClass}\n placeholder=\"https://alpinetreks.com\"\n />\n </div>\n\n <div className=\"sm:col-span-2\">\n <label htmlFor=\"provider-description\" className={labelClass}>\n {t('ggnomad.provider.createDialog.descriptionLabel', 'Description (optional)')}\n </label>\n <textarea\n id=\"provider-description\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n disabled={loading}\n rows={3}\n className={fieldClass}\n placeholder={t(\n 'ggnomad.provider.createDialog.descriptionPlaceholder',\n 'Short description shown to travelers on listings.'\n )}\n />\n </div>\n </div>\n\n <ResponsiveDialogFooter>\n <ResponsiveDialogClose disabled={loading}>\n {t('ggnomad.provider.createDialog.cancel', 'Cancel')}\n </ResponsiveDialogClose>\n <button\n type=\"button\"\n onClick={() => {\n void handleCreate();\n }}\n disabled={loading || !name.trim() || !type.trim()}\n className=\"bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover rounded-md px-4 py-2 text-sm font-medium disabled:opacity-50\"\n >\n {loading\n ? t('ggnomad.provider.createDialog.creating', 'Creating…')\n : t('ggnomad.provider.createDialog.create', 'Create provider')}\n </button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CreateProviderDialog.js","names":[],"sources":["../../../src/ggnomad/components/CreateProviderDialog.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useMutation } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Briefcase } from 'lucide-react';\nimport {\n ResponsiveDialog,\n ResponsiveDialogClose,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n} from '@burdenoff/fe-libs/ui';\n\nimport { CREATE_GGNOMAD_PROVIDER_MUTATION } from '../graphql';\nimport { notify } from '../utils/notify';\nimport { getProviderTypeOptions } from '../utils/providerTypeMeta';\n\ninterface CreateProviderDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n /** Called with the raw backend response once the mutation succeeds, so the\n * caller can decide whether to refetch, navigate, etc. */\n onCreated?: (provider: { id: string; name: string; type: string; status: string }) => void;\n}\n\ninterface CreateGgnomadProviderInput {\n name: string;\n type: string;\n contactEmail?: string | null;\n contactPhone?: string | null;\n website?: string | null;\n description?: string | null;\n}\n\nconst fieldClass =\n 'mt-1 w-full rounded-md border border-border-seam bg-bg-canvas px-3 py-2 text-sm text-text-primary placeholder-text-secondary focus:border-action-primary-bg focus:outline-none focus:ring-1 focus:ring-action-primary-bg disabled:opacity-50';\nconst labelClass = 'block text-sm font-medium text-text-primary';\n\n// Lightweight format checks — anything stricter belongs in the backend.\nconst EMAIL_RE = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\nconst URL_RE = /^(https?:\\/\\/)[^\\s]+$/i;\n\n// Phone inputs accept digits plus common dial/format punctuation only.\n// Letters are stripped as they are typed so invalid input never lands.\nconst PHONE_DISALLOWED_RE = /[^0-9+\\-().\\s]/g;\n// E.164: up to 15 digits; we require at least 7 to be a plausible number.\nconst PHONE_MIN_DIGITS = 7;\nconst PHONE_MAX_DIGITS = 15;\n\nfunction sanitizePhoneInput(value: string): string {\n return value.replace(PHONE_DISALLOWED_RE, '');\n}\n\n/**\n * Form for onboarding a new B2B provider (BOFF-8177). The backend\n * `createGgnomadProvider` mutation is gated by `ggnomad:create`, which\n * every managed GGNomad role holds by default. On submit, the new\n * provider appears at `status: ACTIVE` immediately (the backend hardcodes\n * this on create; see `service.createProvider`).\n */\nexport function CreateProviderDialog({ open, onOpenChange, onCreated }: CreateProviderDialogProps) {\n const { t } = useI18n();\n const providerTypeOptions = getProviderTypeOptions(t);\n const [name, setName] = useState('');\n const [type, setType] = useState('');\n const [contactEmail, setContactEmail] = useState('');\n const [contactPhone, setContactPhone] = useState('');\n const [website, setWebsite] = useState('');\n const [description, setDescription] = useState('');\n\n const [createProvider, { loading }] = useMutation(\n CREATE_GGNOMAD_PROVIDER_MUTATION as TypedDocumentNode<\n {\n createGgnomadProvider: {\n id: string;\n name: string;\n type: string;\n status: string;\n };\n },\n { input: CreateGgnomadProviderInput }\n >\n );\n\n const resetForm = () => {\n setName('');\n setType('');\n setContactEmail('');\n setContactPhone('');\n setWebsite('');\n setDescription('');\n };\n\n const handleCreate = async () => {\n if (!name.trim()) {\n notify.warning(\n t('ggnomad.provider.createDialog.nameRequiredTitle', 'Name is required'),\n t(\n 'ggnomad.provider.createDialog.nameRequiredBody',\n 'Give this provider a recognizable name.'\n )\n );\n return;\n }\n if (!type.trim()) {\n notify.warning(\n t('ggnomad.provider.createDialog.typeRequiredTitle', 'Type is required'),\n t(\n 'ggnomad.provider.createDialog.typeRequiredBody',\n 'Pick a provider type from the dropdown.'\n )\n );\n return;\n }\n\n const trimmedEmail = contactEmail.trim();\n if (trimmedEmail && !EMAIL_RE.test(trimmedEmail)) {\n notify.warning(\n t('ggnomad.provider.createDialog.invalidEmailTitle', 'Invalid email'),\n t(\n 'ggnomad.provider.createDialog.invalidEmailBody',\n 'Enter a valid email address or leave the field blank.'\n )\n );\n return;\n }\n const trimmedWebsite = website.trim();\n if (trimmedWebsite && !URL_RE.test(trimmedWebsite)) {\n notify.warning(\n t('ggnomad.provider.createDialog.invalidWebsiteTitle', 'Invalid website'),\n t(\n 'ggnomad.provider.createDialog.invalidWebsiteBody',\n 'Website must start with http:// or https://'\n )\n );\n return;\n }\n const trimmedPhone = contactPhone.trim();\n const phoneDigits = trimmedPhone.replace(/\\D/g, '').length;\n if (trimmedPhone && (phoneDigits < PHONE_MIN_DIGITS || phoneDigits > PHONE_MAX_DIGITS)) {\n notify.warning(\n t('ggnomad.provider.createDialog.invalidPhoneTitle', 'Invalid phone number'),\n t(\n 'ggnomad.provider.createDialog.invalidPhoneBody',\n 'Enter a valid phone number or leave the field blank.'\n )\n );\n return;\n }\n\n const input: CreateGgnomadProviderInput = {\n name: name.trim(),\n type: type as string,\n contactEmail: trimmedEmail || null,\n contactPhone: trimmedPhone || null,\n website: trimmedWebsite || null,\n description: description.trim() || null,\n };\n\n try {\n const result = await createProvider({ variables: { input } });\n // With errorPolicy: 'all' the mutation does not throw on failure; errors\n // surface on result.error. Without this check a failed create would\n // show \"Provider created\" and reset the form as if it had succeeded.\n if (result.error) {\n notify.error(\n t('ggnomad.provider.createDialog.errorTitle', 'Could not create provider'),\n result.error.message\n );\n return;\n }\n const created = result.data?.createGgnomadProvider;\n notify.success(\n t('ggnomad.provider.createDialog.successTitle', 'Provider created'),\n created?.name ?? name\n );\n resetForm();\n onOpenChange(false);\n if (created) onCreated?.(created);\n } catch (err) {\n notify.error(\n t('ggnomad.provider.createDialog.errorTitle', 'Could not create provider'),\n err instanceof Error ? err.message : String(err)\n );\n }\n };\n\n return (\n <ResponsiveDialog\n open={open}\n onOpenChange={(next) => {\n if (!next) resetForm();\n onOpenChange(next);\n }}\n >\n <ResponsiveDialogContent className=\"max-w-2xl\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle className=\"flex items-center gap-2\">\n <Briefcase className=\"size-4\" />\n {t('ggnomad.provider.createDialog.title', 'New provider')}\n </ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n {t(\n 'ggnomad.provider.createDialog.description',\n 'Onboard a new B2B supplier. They become ACTIVE immediately and can be selected as the host when you create listings.'\n )}\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <div className=\"sm:col-span-2\">\n <label htmlFor=\"provider-name\" className={labelClass}>\n {t('ggnomad.provider.createDialog.nameLabel', 'Provider name')}{' '}\n <span className=\"text-action-danger-text\">*</span>\n </label>\n <input\n id=\"provider-name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n disabled={loading}\n className={fieldClass}\n placeholder={t(\n 'ggnomad.provider.createDialog.namePlaceholder',\n 'e.g. Alpine Treks Co.'\n )}\n />\n </div>\n\n <div className=\"sm:col-span-2\">\n <label htmlFor=\"provider-type\" className={labelClass}>\n {t('ggnomad.provider.createDialog.typeLabel', 'Type')}{' '}\n <span className=\"text-action-danger-text\">*</span>\n </label>\n <select\n id=\"provider-type\"\n value={type}\n onChange={(e) => setType(e.target.value as string)}\n disabled={loading}\n className={fieldClass}\n >\n <option value=\"\">\n {t('ggnomad.provider.createDialog.typePlaceholder', 'Select a type…')}\n </option>\n {providerTypeOptions.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n </div>\n\n <div>\n <label htmlFor=\"provider-email\" className={labelClass}>\n {t('ggnomad.provider.createDialog.emailLabel', 'Contact email (optional)')}\n </label>\n <input\n id=\"provider-email\"\n type=\"email\"\n value={contactEmail}\n onChange={(e) => setContactEmail(e.target.value)}\n disabled={loading}\n className={fieldClass}\n placeholder=\"hello@alpinetreks.com\"\n />\n </div>\n\n <div>\n <label htmlFor=\"provider-phone\" className={labelClass}>\n {t('ggnomad.provider.createDialog.phoneLabel', 'Contact phone (optional)')}\n </label>\n <input\n id=\"provider-phone\"\n type=\"tel\"\n inputMode=\"tel\"\n autoComplete=\"tel\"\n value={contactPhone}\n onChange={(e) => setContactPhone(sanitizePhoneInput(e.target.value))}\n disabled={loading}\n className={fieldClass}\n placeholder=\"+91 98765 43210\"\n />\n </div>\n\n <div className=\"sm:col-span-2\">\n <label htmlFor=\"provider-website\" className={labelClass}>\n {t('ggnomad.provider.createDialog.websiteLabel', 'Website (optional)')}\n </label>\n <input\n id=\"provider-website\"\n type=\"url\"\n value={website}\n onChange={(e) => setWebsite(e.target.value)}\n disabled={loading}\n className={fieldClass}\n placeholder=\"https://alpinetreks.com\"\n />\n </div>\n\n <div className=\"sm:col-span-2\">\n <label htmlFor=\"provider-description\" className={labelClass}>\n {t('ggnomad.provider.createDialog.descriptionLabel', 'Description (optional)')}\n </label>\n <textarea\n id=\"provider-description\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n disabled={loading}\n rows={3}\n className={fieldClass}\n placeholder={t(\n 'ggnomad.provider.createDialog.descriptionPlaceholder',\n 'Short description shown to travelers on listings.'\n )}\n />\n </div>\n </div>\n\n <ResponsiveDialogFooter>\n <ResponsiveDialogClose disabled={loading}>\n {t('ggnomad.provider.createDialog.cancel', 'Cancel')}\n </ResponsiveDialogClose>\n <button\n type=\"button\"\n onClick={() => {\n void handleCreate();\n }}\n disabled={loading || !name.trim() || !type.trim()}\n className=\"bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover rounded-md px-4 py-2 text-sm font-medium disabled:opacity-50\"\n >\n {loading\n ? t('ggnomad.provider.createDialog.creating', 'Creating…')\n : t('ggnomad.provider.createDialog.create', 'Create provider')}\n </button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
|