@burdenoff/microfe-ggnomad 2026.716.1 → 2026.719.1

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.
Files changed (36) hide show
  1. package/dist/ggnomad/GGNomadRoot.js.map +1 -1
  2. package/dist/ggnomad/GGNomadRoutes.js.map +1 -1
  3. package/dist/ggnomad/components/BookingPanel.js.map +1 -1
  4. package/dist/ggnomad/components/DetailHero.js.map +1 -1
  5. package/dist/ggnomad/components/DetailRow.js.map +1 -1
  6. package/dist/ggnomad/components/EntityCard.js.map +1 -1
  7. package/dist/ggnomad/components/ListingEditDialog.js.map +1 -1
  8. package/dist/ggnomad/components/LoadMoreButton.js.map +1 -1
  9. package/dist/ggnomad/components/ReviewsSection.js.map +1 -1
  10. package/dist/ggnomad/components/SectionCard.js.map +1 -1
  11. package/dist/ggnomad/components/StatCard.js.map +1 -1
  12. package/dist/ggnomad/components/StatusBadge.js.map +1 -1
  13. package/dist/ggnomad/pages/ActivityDetailsPage.js.map +1 -1
  14. package/dist/ggnomad/pages/AnalyticsPage.js.map +1 -1
  15. package/dist/ggnomad/pages/BookingsPage.js.map +1 -1
  16. package/dist/ggnomad/pages/DashboardPage.js.map +1 -1
  17. package/dist/ggnomad/pages/EntertainmentPage.js.map +1 -1
  18. package/dist/ggnomad/pages/EventDetailsPage.js.map +1 -1
  19. package/dist/ggnomad/pages/FavoritesPage.js.map +1 -1
  20. package/dist/ggnomad/pages/HospitalityPage.js.map +1 -1
  21. package/dist/ggnomad/pages/PageShell.js.map +1 -1
  22. package/dist/ggnomad/pages/PropertyDetailsPage.js.map +1 -1
  23. package/dist/ggnomad/pages/ProviderPage.js.map +1 -1
  24. package/dist/ggnomad/pages/SearchPage.js.map +1 -1
  25. package/dist/ggnomad/pages/TourDetailsPage.js.map +1 -1
  26. package/dist/ggnomad/pages/TourismPage.js.map +1 -1
  27. package/dist/ggnomad/pages/TravelPage.js.map +1 -1
  28. package/dist/ggnomad/pages/TripPlanDetailPage.js.map +1 -1
  29. package/dist/ggnomad/pages/TripsPage.js.map +1 -1
  30. package/dist/ggnomad/providers/GGNomadProvider.js.map +1 -1
  31. package/dist/ggnomad/utils/cn.js.map +1 -1
  32. package/dist/ggnomad/utils/navigation.js.map +1 -1
  33. package/dist/ggnomad/utils/notify.js.map +1 -1
  34. package/dist/services/createSandboxAssistantTransport.js.map +1 -1
  35. package/dist/utils/pageContext.js.map +1 -1
  36. package/package.json +2 -2
@@ -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 Compass,\n LayoutDashboard,\n MapPinned,\n Map,\n Search,\n Star,\n Tickets,\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-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-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":";;;;;;;;;AAuBA,IAAa,KAAqC,MAAU;CAC1D,IAAM,EAAE,qBAAkB,wBAAqB,cAAW,QAAQ,GAC5D,EAAE,SAAM,GAAS;AAiHvB,QA/GA,QAAgB;AACT,QA4FL,QAAO,EAxFkB;GACvB,IAAI;GACJ,OAAO,EAAE,oBAAoB,UAAU;GACvC,MAAM;GACN,MAAM,kBAAC,GAAD,EAAiB,MAAM,IAAM,CAAA;GACnC,OAAO;GACP,OAAO;GACP,UAAU;IACR;KACE,IAAI;KACJ,OAAO,EAAE,yBAAyB,YAAY;KAC9C,MAAM;KACN,MAAM,kBAAC,GAAD,EAAiB,MAAM,IAAM,CAAA;KACnC,OAAO;KACR;IACD;KACE,IAAI;KACJ,OAAO,EAAE,sBAAsB,SAAS;KACxC,MAAM,EAAS,GAAU,SAAS;KAClC,MAAM,kBAAC,GAAD,EAAQ,MAAM,IAAM,CAAA;KAC1B,OAAO;KACR;IACD;KACE,IAAI;KACJ,OAAO,EAAE,sBAAsB,SAAS;KACxC,MAAM,EAAS,GAAU,SAAS;KAClC,MAAM,kBAAC,GAAD,EAAS,MAAM,IAAM,CAAA;KAC3B,OAAO;KACR;IACD;KACE,IAAI;KACJ,OAAO,EAAE,2BAA2B,cAAc;KAClD,MAAM,EAAS,GAAU,cAAc;KACvC,MAAM,kBAAC,GAAD,EAAU,MAAM,IAAM,CAAA;KAC5B,OAAO;KACR;IACD;KACE,IAAI;KACJ,OAAO,EAAE,uBAAuB,UAAU;KAC1C,MAAM,EAAS,GAAU,UAAU;KACnC,MAAM,kBAAC,GAAD,EAAW,MAAM,IAAM,CAAA;KAC7B,OAAO;KACR;IACD;KACE,IAAI;KACJ,OAAO,EAAE,6BAA6B,gBAAgB;KACtD,MAAM,EAAS,GAAU,gBAAgB;KACzC,MAAM,kBAAC,GAAD,EAAU,MAAM,IAAM,CAAA;KAC5B,OAAO;KACR;IACD;KACE,IAAI;KACJ,OAAO,EAAE,qBAAqB,QAAQ;KACtC,MAAM,EAAS,GAAU,QAAQ;KACjC,MAAM,kBAAC,GAAD,EAAK,MAAM,IAAM,CAAA;KACvB,OAAO;KACR;IACD;KACE,IAAI;KACJ,OAAO,EAAE,wBAAwB,WAAW;KAC5C,MAAM,EAAS,GAAU,WAAW;KACpC,MAAM,kBAAC,GAAD,EAAe,MAAM,IAAM,CAAA;KACjC,OAAO;KACR;IACD;KACE,IAAI;KACJ,OAAO,EAAE,yBAAyB,YAAY;KAC9C,MAAM,EAAS,GAAU,YAAY;KACrC,MAAM,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA;KACxB,OAAO;KACR;IACD;KACE,IAAI;KACJ,OAAO,EAAE,yBAAyB,YAAY;KAC9C,MAAM,EAAS,GAAU,WAAW;KACpC,MAAM,kBAAC,GAAD,EAAS,MAAM,IAAM,CAAA;KAC3B,OAAO;KACR;IACD;KACE,IAAI;KACJ,OAAO,EAAE,yBAAyB,YAAY;KAC9C,MAAM,EAAS,GAAU,YAAY;KACrC,MAAM,kBAAC,GAAD,EAAW,MAAM,IAAM,CAAA;KAC7B,OAAO;KACR;IACF;GACF,CAE+B;IAC/B;EAAC;EAAkB;EAAU;EAAE,CAAC,EAEnC,QAAgB;AACT,QAGL,QAAO,EAAoB,CACzB;GACE,IAAI;GACJ,OAAO,EAAE,wBAAwB,gBAAgB;GACjD,MAAM;GACN,SAAS;GACT,OAAO;GACR,CACF,CAAC;IACD,CAAC,GAAqB,EAAE,CAAC,EAG1B,kBAAC,GAAD;EAAqB,aAAa;EAAM,SAAQ;EAAS,gBAAgB;YACvE,kBAAC,GAAD;GAAiB,GAAI;aACnB,kBAAC,GAAD,EAAiB,CAAA;GACD,CAAA;EACE,CAAA"}
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 Compass,\n LayoutDashboard,\n MapPinned,\n Map,\n Search,\n Star,\n Tickets,\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-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-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":";;;;;;;;;AAuBA,IAAa,KAAqC,MAAU;CAC1D,IAAM,EAAE,qBAAkB,wBAAqB,cAAW,QAAQ,GAC5D,EAAE,SAAM,EAAQ;CAiHtB,OA/GA,QAAgB;EACT,OA4FL,OAAO,EAAiB;GAvFtB,IAAI;GACJ,OAAO,EAAE,oBAAoB,SAAS;GACtC,MAAM;GACN,MAAM,kBAAC,GAAD,EAAiB,MAAM,GAAK,CAAA;GAClC,OAAO;GACP,OAAO;GACP,UAAU;IACR;KACE,IAAI;KACJ,OAAO,EAAE,yBAAyB,WAAW;KAC7C,MAAM;KACN,MAAM,kBAAC,GAAD,EAAiB,MAAM,GAAK,CAAA;KAClC,OAAO;IACT;IACA;KACE,IAAI;KACJ,OAAO,EAAE,sBAAsB,QAAQ;KACvC,MAAM,EAAS,GAAU,QAAQ;KACjC,MAAM,kBAAC,GAAD,EAAQ,MAAM,GAAK,CAAA;KACzB,OAAO;IACT;IACA;KACE,IAAI;KACJ,OAAO,EAAE,sBAAsB,QAAQ;KACvC,MAAM,EAAS,GAAU,QAAQ;KACjC,MAAM,kBAAC,GAAD,EAAS,MAAM,GAAK,CAAA;KAC1B,OAAO;IACT;IACA;KACE,IAAI;KACJ,OAAO,EAAE,2BAA2B,aAAa;KACjD,MAAM,EAAS,GAAU,aAAa;KACtC,MAAM,kBAAC,GAAD,EAAU,MAAM,GAAK,CAAA;KAC3B,OAAO;IACT;IACA;KACE,IAAI;KACJ,OAAO,EAAE,uBAAuB,SAAS;KACzC,MAAM,EAAS,GAAU,SAAS;KAClC,MAAM,kBAAC,GAAD,EAAW,MAAM,GAAK,CAAA;KAC5B,OAAO;IACT;IACA;KACE,IAAI;KACJ,OAAO,EAAE,6BAA6B,eAAe;KACrD,MAAM,EAAS,GAAU,eAAe;KACxC,MAAM,kBAAC,GAAD,EAAU,MAAM,GAAK,CAAA;KAC3B,OAAO;IACT;IACA;KACE,IAAI;KACJ,OAAO,EAAE,qBAAqB,OAAO;KACrC,MAAM,EAAS,GAAU,OAAO;KAChC,MAAM,kBAAC,GAAD,EAAK,MAAM,GAAK,CAAA;KACtB,OAAO;IACT;IACA;KACE,IAAI;KACJ,OAAO,EAAE,wBAAwB,UAAU;KAC3C,MAAM,EAAS,GAAU,UAAU;KACnC,MAAM,kBAAC,GAAD,EAAe,MAAM,GAAK,CAAA;KAChC,OAAO;IACT;IACA;KACE,IAAI;KACJ,OAAO,EAAE,yBAAyB,WAAW;KAC7C,MAAM,EAAS,GAAU,WAAW;KACpC,MAAM,kBAAC,GAAD,EAAM,MAAM,GAAK,CAAA;KACvB,OAAO;IACT;IACA;KACE,IAAI;KACJ,OAAO,EAAE,yBAAyB,WAAW;KAC7C,MAAM,EAAS,GAAU,UAAU;KACnC,MAAM,kBAAC,GAAD,EAAS,MAAM,GAAK,CAAA;KAC1B,OAAO;IACT;IACA;KACE,IAAI;KACJ,OAAO,EAAE,yBAAyB,WAAW;KAC7C,MAAM,EAAS,GAAU,WAAW;KACpC,MAAM,kBAAC,GAAD,EAAW,MAAM,GAAK,CAAA;KAC5B,OAAO;IACT;GACF;EAGsB,CAAO;CACjC,GAAG;EAAC;EAAkB;EAAU;CAAC,CAAC,GAElC,QAAgB;EACT,OAGL,OAAO,EAAoB,CACzB;GACE,IAAI;GACJ,OAAO,EAAE,wBAAwB,eAAe;GAChD,MAAM;GACN,SAAS;GACT,OAAO;EACT,CACF,CAAC;CACH,GAAG,CAAC,GAAqB,CAAC,CAAC,GAGzB,kBAAC,GAAD;EAAqB,aAAa;EAAM,SAAQ;EAAS,gBAAgB;YACvE,kBAAC,GAAD;GAAiB,GAAI;aACnB,kBAAC,GAAD,CAAgB,CAAA;EACD,CAAA;CACE,CAAA;AAEzB"}
@@ -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-dom';\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 TripsPage = lazy(() => import('./pages/TripsPage').then((m) => ({ default: m.TripsPage })));\nconst TripPlanDetailPage = lazy(() =>\n import('./pages/TripPlanDetailPage').then((m) => ({ default: m.TripPlanDetailPage }))\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=\"favorites\" element={<FavoritesPage />} />\n <Route path=\"provider\" element={<ProviderPage />} />\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
+ {"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-dom';\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 TripsPage = lazy(() => import('./pages/TripsPage').then((m) => ({ default: m.TripsPage })));\nconst TripPlanDetailPage = lazy(() =>\n import('./pages/TripPlanDetailPage').then((m) => ({ default: m.TripPlanDetailPage }))\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=\"favorites\" element={<FavoritesPage />} />\n <Route path=\"provider\" element={<ProviderPage />} />\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":"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-dom';\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 { 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 PROPERTY bookings, the number of nights to calculate price */\n nights?: number;\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 nights,\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 // Calculate total: for properties, multiply by nights; for others, multiply by quantity\n const multiplier = bookingType === 'PROPERTY' && nights ? 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 } 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 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 */}\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} ${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 {/* 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-dom';\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 { 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 PROPERTY bookings, the number of nights to calculate price */\n nights?: number;\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 nights,\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 // Calculate total: for properties, multiply by nights; for others, multiply by quantity\n const multiplier = bookingType === 'PROPERTY' && nights ? 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 } 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 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 */}\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} ${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 {/* 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":"DetailHero.js","names":[],"sources":["../../../src/ggnomad/components/DetailHero.tsx"],"sourcesContent":["import { cn } from '../utils/cn';\n\ninterface DetailHeroProps {\n /** Primary cover image URL; a branded gradient is shown when absent. */\n image?: string | null;\n alt?: string;\n className?: string;\n}\n\n/**\n * Full-width hero cover for a listing detail page — a 21:9 photo with a bottom\n * gradient scrim, or a branded gradient fallback when the listing has no media.\n */\nexport function DetailHero({ image, alt, className }: DetailHeroProps) {\n return (\n <div\n className={cn(\n 'relative mb-5 aspect-[21/9] w-full overflow-hidden rounded-card border border-border-seam',\n className\n )}\n >\n {image ? (\n <img\n src={image}\n alt={alt ?? ''}\n loading=\"eager\"\n decoding=\"async\"\n className=\"size-full object-cover\"\n />\n ) : (\n <div\n aria-hidden=\"true\"\n className=\"size-full bg-gradient-to-br from-action-primary-bg/30 via-bg-sunken to-bg-sunken\"\n />\n )}\n <div className=\"pointer-events-none absolute inset-0 bg-gradient-to-t from-black/50 via-transparent to-transparent\" />\n </div>\n );\n}\n"],"mappings":";;;AAaA,SAAgB,EAAW,EAAE,UAAO,QAAK,gBAA8B;AACrE,QACE,kBAAC,OAAD;EACE,WAAW,EACT,6FACA,EACD;YAJH,CAMG,IACC,kBAAC,OAAD;GACE,KAAK;GACL,KAAK,KAAO;GACZ,SAAQ;GACR,UAAS;GACT,WAAU;GACV,CAAA,GAEF,kBAAC,OAAD;GACE,eAAY;GACZ,WAAU;GACV,CAAA,EAEJ,kBAAC,OAAD,EAAK,WAAU,sGAAuG,CAAA,CAClH"}
1
+ {"version":3,"file":"DetailHero.js","names":[],"sources":["../../../src/ggnomad/components/DetailHero.tsx"],"sourcesContent":["import { cn } from '../utils/cn';\n\ninterface DetailHeroProps {\n /** Primary cover image URL; a branded gradient is shown when absent. */\n image?: string | null;\n alt?: string;\n className?: string;\n}\n\n/**\n * Full-width hero cover for a listing detail page — a 21:9 photo with a bottom\n * gradient scrim, or a branded gradient fallback when the listing has no media.\n */\nexport function DetailHero({ image, alt, className }: DetailHeroProps) {\n return (\n <div\n className={cn(\n 'relative mb-5 aspect-[21/9] w-full overflow-hidden rounded-card border border-border-seam',\n className\n )}\n >\n {image ? (\n <img\n src={image}\n alt={alt ?? ''}\n loading=\"eager\"\n decoding=\"async\"\n className=\"size-full object-cover\"\n />\n ) : (\n <div\n aria-hidden=\"true\"\n className=\"size-full bg-gradient-to-br from-action-primary-bg/30 via-bg-sunken to-bg-sunken\"\n />\n )}\n <div className=\"pointer-events-none absolute inset-0 bg-gradient-to-t from-black/50 via-transparent to-transparent\" />\n </div>\n );\n}\n"],"mappings":";;;AAaA,SAAgB,EAAW,EAAE,UAAO,QAAK,gBAA8B;CACrE,OACE,kBAAC,OAAD;EACE,WAAW,EACT,6FACA,CACF;YAJF,CAMG,IACC,kBAAC,OAAD;GACE,KAAK;GACL,KAAK,KAAO;GACZ,SAAQ;GACR,UAAS;GACT,WAAU;EACX,CAAA,IAED,kBAAC,OAAD;GACE,eAAY;GACZ,WAAU;EACX,CAAA,GAEH,kBAAC,OAAD,EAAK,WAAU,qGAAsG,CAAA,CAClH;;AAET"}
@@ -1 +1 @@
1
- {"version":3,"file":"DetailRow.js","names":[],"sources":["../../../src/ggnomad/components/DetailRow.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\n\n/** A single label/value pair inside a details list (low-contrast, tabular). */\nexport function DetailRow({ label, value }: { label: string; value: ReactNode }) {\n return (\n <div className=\"flex items-center justify-between gap-3\">\n <dt className=\"text-text-secondary\">{label}</dt>\n <dd className=\"font-medium tabular-nums text-text-primary\">{value}</dd>\n </div>\n );\n}\n"],"mappings":";;AAGA,SAAgB,EAAU,EAAE,UAAO,YAA8C;AAC/E,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,MAAD;GAAI,WAAU;aAAuB;GAAW,CAAA,EAChD,kBAAC,MAAD;GAAI,WAAU;aAA8C;GAAW,CAAA,CACnE"}
1
+ {"version":3,"file":"DetailRow.js","names":[],"sources":["../../../src/ggnomad/components/DetailRow.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\n\n/** A single label/value pair inside a details list (low-contrast, tabular). */\nexport function DetailRow({ label, value }: { label: string; value: ReactNode }) {\n return (\n <div className=\"flex items-center justify-between gap-3\">\n <dt className=\"text-text-secondary\">{label}</dt>\n <dd className=\"font-medium tabular-nums text-text-primary\">{value}</dd>\n </div>\n );\n}\n"],"mappings":";;AAGA,SAAgB,EAAU,EAAE,UAAO,YAA8C;CAC/E,OACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,MAAD;GAAI,WAAU;aAAuB;EAAU,CAAA,GAC/C,kBAAC,MAAD;GAAI,WAAU;aAA8C;EAAU,CAAA,CACnE;;AAET"}
@@ -1 +1 @@
1
- {"version":3,"file":"EntityCard.js","names":[],"sources":["../../../src/ggnomad/components/EntityCard.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { Link } from 'react-router-dom';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\n\ntype EntityCardProps = {\n title: ReactNode;\n /** Optional secondary line (category / type / duration). */\n meta?: ReactNode;\n /** Right-aligned price or primary figure. */\n price?: ReactNode;\n badge?: ReactNode;\n footer?: ReactNode;\n /** Optional cover photo URL. Falls back to a branded gradient tile. */\n image?: string | null;\n /** Accessible description for the cover photo. */\n imageAlt?: string;\n /** When provided, the whole card becomes a router Link. */\n to?: string;\n className?: string;\n};\n\n/**\n * An inventory tile for a property / tour / event / activity / provider. When an\n * `image` is supplied it renders a 16:10 cover photo (with a branded gradient\n * fallback otherwise) above the title / meta / price. Hover lifts the shadow and\n * gently zooms the photo. When `to` is set the whole surface is one accessible link.\n */\nexport function EntityCard({\n title,\n meta,\n price,\n badge,\n footer,\n image,\n imageAlt,\n to,\n className,\n}: EntityCardProps) {\n const cover = (\n <div className=\"relative -mx-4 -mt-4 mb-3 aspect-[16/10] overflow-hidden\">\n {image ? (\n <img\n src={image}\n alt={imageAlt ?? ''}\n loading=\"lazy\"\n decoding=\"async\"\n className=\"size-full object-cover transition-transform duration-300 group-hover:scale-[1.04]\"\n />\n ) : (\n <div\n aria-hidden=\"true\"\n className=\"size-full bg-gradient-to-br from-action-primary-bg/25 via-bg-sunken to-bg-sunken\"\n />\n )}\n <div className=\"pointer-events-none absolute inset-0 bg-gradient-to-t from-black/45 via-transparent to-transparent\" />\n {badge ? <div className=\"absolute left-2 top-2\">{badge}</div> : null}\n </div>\n );\n\n const body = (\n <>\n {cover}\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium text-text-primary\">{title}</p>\n {meta ? <p className=\"mt-0.5 text-sm text-text-secondary\">{meta}</p> : null}\n </div>\n {price ? (\n <p className=\"mt-2 text-sm font-medium tabular-nums text-text-primary\">{price}</p>\n ) : null}\n {footer ? <div className=\"mt-2 text-sm text-text-secondary\">{footer}</div> : null}\n </>\n );\n\n if (to) {\n return (\n <Link\n to={to}\n className=\"group block rounded-card focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2 focus-visible:ring-offset-bg-canvas\"\n >\n <GlassCard\n treatment=\"plain\"\n className={cn('overflow-hidden border-border-seam p-4', className)}\n >\n {body}\n </GlassCard>\n </Link>\n );\n }\n\n return (\n <GlassCard\n treatment=\"plain\"\n className={cn('group overflow-hidden border-border-seam p-4', className)}\n >\n {body}\n </GlassCard>\n );\n}\n"],"mappings":";;;;;AA4BA,SAAgB,EAAW,EACzB,UACA,SACA,UACA,UACA,WACA,UACA,aACA,OACA,gBACkB;CAsBlB,IAAM,IACJ,kBAAA,GAAA,EAAA,UAAA;EArBA,kBAAC,OAAD;GAAK,WAAU;aAAf;IACG,IACC,kBAAC,OAAD;KACE,KAAK;KACL,KAAK,KAAY;KACjB,SAAQ;KACR,UAAS;KACT,WAAU;KACV,CAAA,GAEF,kBAAC,OAAD;KACE,eAAY;KACZ,WAAU;KACV,CAAA;IAEJ,kBAAC,OAAD,EAAK,WAAU,sGAAuG,CAAA;IACrH,IAAQ,kBAAC,OAAD;KAAK,WAAU;eAAyB;KAAY,CAAA,GAAG;IAC5D;;EAMJ,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,KAAD;IAAG,WAAU;cAA0C;IAAU,CAAA,EAChE,IAAO,kBAAC,KAAD;IAAG,WAAU;cAAsC;IAAS,CAAA,GAAG,KACnE;;EACL,IACC,kBAAC,KAAD;GAAG,WAAU;aAA2D;GAAU,CAAA,GAChF;EACH,IAAS,kBAAC,OAAD;GAAK,WAAU;aAAoC;GAAa,CAAA,GAAG;EAC5E,EAAA,CAAA;AAmBL,QAhBI,IAEA,kBAAC,GAAD;EACM;EACJ,WAAU;YAEV,kBAAC,GAAD;GACE,WAAU;GACV,WAAW,EAAG,0CAA0C,EAAU;aAEjE;GACS,CAAA;EACP,CAAA,GAKT,kBAAC,GAAD;EACE,WAAU;EACV,WAAW,EAAG,gDAAgD,EAAU;YAEvE;EACS,CAAA"}
1
+ {"version":3,"file":"EntityCard.js","names":[],"sources":["../../../src/ggnomad/components/EntityCard.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { Link } from 'react-router-dom';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\n\ntype EntityCardProps = {\n title: ReactNode;\n /** Optional secondary line (category / type / duration). */\n meta?: ReactNode;\n /** Right-aligned price or primary figure. */\n price?: ReactNode;\n badge?: ReactNode;\n footer?: ReactNode;\n /** Optional cover photo URL. Falls back to a branded gradient tile. */\n image?: string | null;\n /** Accessible description for the cover photo. */\n imageAlt?: string;\n /** When provided, the whole card becomes a router Link. */\n to?: string;\n className?: string;\n};\n\n/**\n * An inventory tile for a property / tour / event / activity / provider. When an\n * `image` is supplied it renders a 16:10 cover photo (with a branded gradient\n * fallback otherwise) above the title / meta / price. Hover lifts the shadow and\n * gently zooms the photo. When `to` is set the whole surface is one accessible link.\n */\nexport function EntityCard({\n title,\n meta,\n price,\n badge,\n footer,\n image,\n imageAlt,\n to,\n className,\n}: EntityCardProps) {\n const cover = (\n <div className=\"relative -mx-4 -mt-4 mb-3 aspect-[16/10] overflow-hidden\">\n {image ? (\n <img\n src={image}\n alt={imageAlt ?? ''}\n loading=\"lazy\"\n decoding=\"async\"\n className=\"size-full object-cover transition-transform duration-300 group-hover:scale-[1.04]\"\n />\n ) : (\n <div\n aria-hidden=\"true\"\n className=\"size-full bg-gradient-to-br from-action-primary-bg/25 via-bg-sunken to-bg-sunken\"\n />\n )}\n <div className=\"pointer-events-none absolute inset-0 bg-gradient-to-t from-black/45 via-transparent to-transparent\" />\n {badge ? <div className=\"absolute left-2 top-2\">{badge}</div> : null}\n </div>\n );\n\n const body = (\n <>\n {cover}\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium text-text-primary\">{title}</p>\n {meta ? <p className=\"mt-0.5 text-sm text-text-secondary\">{meta}</p> : null}\n </div>\n {price ? (\n <p className=\"mt-2 text-sm font-medium tabular-nums text-text-primary\">{price}</p>\n ) : null}\n {footer ? <div className=\"mt-2 text-sm text-text-secondary\">{footer}</div> : null}\n </>\n );\n\n if (to) {\n return (\n <Link\n to={to}\n className=\"group block rounded-card focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2 focus-visible:ring-offset-bg-canvas\"\n >\n <GlassCard\n treatment=\"plain\"\n className={cn('overflow-hidden border-border-seam p-4', className)}\n >\n {body}\n </GlassCard>\n </Link>\n );\n }\n\n return (\n <GlassCard\n treatment=\"plain\"\n className={cn('group overflow-hidden border-border-seam p-4', className)}\n >\n {body}\n </GlassCard>\n );\n}\n"],"mappings":";;;;;AA4BA,SAAgB,EAAW,EACzB,UACA,SACA,UACA,UACA,WACA,UACA,aACA,OACA,gBACkB;CAsBlB,IAAM,IACJ,kBAAA,GAAA,EAAA,UAAA;EArBA,kBAAC,OAAD;GAAK,WAAU;aAAf;IACG,IACC,kBAAC,OAAD;KACE,KAAK;KACL,KAAK,KAAY;KACjB,SAAQ;KACR,UAAS;KACT,WAAU;IACX,CAAA,IAED,kBAAC,OAAD;KACE,eAAY;KACZ,WAAU;IACX,CAAA;IAEH,kBAAC,OAAD,EAAK,WAAU,qGAAsG,CAAA;IACpH,IAAQ,kBAAC,OAAD;KAAK,WAAU;eAAyB;IAAW,CAAA,IAAI;GAC7D;GAKF;EACD,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,KAAD;IAAG,WAAU;cAA0C;GAAS,CAAA,GAC/D,IAAO,kBAAC,KAAD;IAAG,WAAU;cAAsC;GAAQ,CAAA,IAAI,IACpE;;EACJ,IACC,kBAAC,KAAD;GAAG,WAAU;aAA2D;EAAS,CAAA,IAC/E;EACH,IAAS,kBAAC,OAAD;GAAK,WAAU;aAAoC;EAAY,CAAA,IAAI;CAC7E,EAAA,CAAA;CAmBJ,OAhBI,IAEA,kBAAC,GAAD;EACM;EACJ,WAAU;YAEV,kBAAC,GAAD;GACE,WAAU;GACV,WAAW,EAAG,0CAA0C,CAAS;aAEhE;EACQ,CAAA;CACP,CAAA,IAKR,kBAAC,GAAD;EACE,WAAU;EACV,WAAW,EAAG,gDAAgD,CAAS;YAEtE;CACQ,CAAA;AAEf"}
@@ -1 +1 @@
1
- {"version":3,"file":"ListingEditDialog.js","names":[],"sources":["../../../src/ggnomad/components/ListingEditDialog.tsx"],"sourcesContent":["import { useState, useMemo } 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 { Pencil } from 'lucide-react';\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n UPDATE_GGNOMAD_PROPERTY_MUTATION,\n UPDATE_GGNOMAD_TOUR_MUTATION,\n UPDATE_GGNOMAD_EVENT_MUTATION,\n UPDATE_GGNOMAD_ACTIVITY_MUTATION,\n} from '../graphql';\nimport { notify } from '../utils/notify';\n\ntype TargetType = 'PROPERTY' | 'TOUR' | 'EVENT' | 'ACTIVITY';\n\ninterface ListingEditDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n targetType: TargetType;\n id: string;\n initial: {\n name?: string;\n description?: string;\n price?: number;\n pricePerNight?: number;\n status?: string;\n images?: string[];\n };\n onSaved?: () => void;\n}\n\n/** Edit dialog for listing details (property, tour, event, activity). */\nexport function ListingEditDialog({\n open,\n onOpenChange,\n targetType,\n id,\n initial,\n onSaved,\n}: ListingEditDialogProps) {\n const { t } = useI18n();\n const [name, setName] = useState(initial.name ?? '');\n const [description, setDescription] = useState(initial.description ?? '');\n const priceField = initial.pricePerNight ?? initial.price ?? 0;\n const [price, setPrice] = useState(String(priceField));\n const [status, setStatus] = useState(initial.status ?? 'ACTIVE');\n const [imagesText, setImagesText] = useState((initial.images ?? []).join('\\n'));\n\n const [updateProperty, { loading: updatingProperty }] = useMutation(\n UPDATE_GGNOMAD_PROPERTY_MUTATION as TypedDocumentNode<\n { updateGgnomadProperty: { id: string; name: string } },\n {\n id: string;\n input: {\n name?: string;\n description?: string;\n pricePerNight?: number;\n status?: string;\n images?: string[];\n };\n }\n >\n );\n\n const [updateTour, { loading: updatingTour }] = useMutation(\n UPDATE_GGNOMAD_TOUR_MUTATION as TypedDocumentNode<\n { updateGgnomadTour: { id: string; name: string } },\n {\n id: string;\n input: {\n name?: string;\n description?: string;\n price?: number;\n status?: string;\n images?: string[];\n };\n }\n >\n );\n\n const [updateEvent, { loading: updatingEvent }] = useMutation(\n UPDATE_GGNOMAD_EVENT_MUTATION as TypedDocumentNode<\n { updateGgnomadEvent: { id: string; name: string } },\n {\n id: string;\n input: {\n name?: string;\n description?: string;\n price?: number;\n status?: string;\n images?: string[];\n };\n }\n >\n );\n\n const [updateActivity, { loading: updatingActivity }] = useMutation(\n UPDATE_GGNOMAD_ACTIVITY_MUTATION as TypedDocumentNode<\n { updateGgnomadActivity: { id: string; name: string } },\n {\n id: string;\n input: {\n name?: string;\n description?: string;\n price?: number;\n status?: string;\n images?: string[];\n };\n }\n >\n );\n\n const loading = useMemo(\n () => updatingProperty || updatingTour || updatingEvent || updatingActivity,\n [updatingProperty, updatingTour, updatingEvent, updatingActivity]\n );\n\n const handleSave = async () => {\n try {\n // Build input with only changed fields\n const input: Record<string, unknown> = {};\n\n if (name !== (initial.name ?? '')) {\n input.name = name;\n }\n if (description !== (initial.description ?? '')) {\n input.description = description;\n }\n\n const priceValue = parseFloat(price);\n const initialPrice = initial.pricePerNight ?? initial.price ?? 0;\n if (priceValue !== initialPrice) {\n if (targetType === 'PROPERTY') {\n input.pricePerNight = priceValue;\n } else {\n input.price = priceValue;\n }\n }\n\n if (status !== (initial.status ?? 'ACTIVE')) {\n input.status = status;\n }\n\n const newImages = imagesText\n .split('\\n')\n .map((line) => line.trim())\n .filter(Boolean);\n const initialImages = initial.images ?? [];\n if (JSON.stringify(newImages) !== JSON.stringify(initialImages)) {\n input.images = newImages;\n }\n\n // If nothing changed, just close\n if (Object.keys(input).length === 0) {\n notify.info(\n t('ggnomad.listingEdit.noChangesTitle', 'No changes'),\n t('ggnomad.listingEdit.noChangesBody', 'Nothing was modified.')\n );\n onOpenChange(false);\n return;\n }\n\n let result;\n if (targetType === 'PROPERTY') {\n result = await updateProperty({\n variables: { id, input },\n });\n } else if (targetType === 'TOUR') {\n result = await updateTour({\n variables: { id, input },\n });\n } else if (targetType === 'EVENT') {\n result = await updateEvent({\n variables: { id, input },\n });\n } else {\n result = await updateActivity({\n variables: { id, input },\n });\n }\n\n if (result?.error) {\n notify.error(\n t('ggnomad.listingEdit.errorTitle', 'Could not update listing'),\n result.error.message\n );\n return;\n }\n\n notify.success(\n t('ggnomad.listingEdit.successTitle', 'Listing updated'),\n t('ggnomad.listingEdit.successBody', 'Your changes have been saved.')\n );\n\n onOpenChange(false);\n onSaved?.();\n } catch (err) {\n notify.error(\n t('ggnomad.listingEdit.errorTitle', 'Could not update listing'),\n err instanceof Error ? err.message : String(err)\n );\n }\n };\n\n const priceLabel = targetType === 'PROPERTY' ? 'Price per night' : 'Price';\n\n return (\n <AlertDialog open={open} onOpenChange={onOpenChange}>\n <AlertDialogContent className=\"max-w-2xl\">\n <AlertDialogHeader>\n <AlertDialogTitle className=\"flex items-center gap-2\">\n <Pencil className=\"size-4\" />\n {t('ggnomad.listingEdit.title', 'Edit listing')}\n </AlertDialogTitle>\n <AlertDialogDescription>\n {t('ggnomad.listingEdit.description', 'Update the details for this listing.')}\n </AlertDialogDescription>\n </AlertDialogHeader>\n\n <div className=\"space-y-4\">\n {/* Name */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n {t('ggnomad.listingEdit.nameLabel', 'Name')}\n </label>\n <input\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n disabled={loading}\n className=\"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\"\n placeholder={t('ggnomad.listingEdit.namePlaceholder', 'Enter name')}\n />\n </div>\n\n {/* Description */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n {t('ggnomad.listingEdit.descriptionLabel', 'Description')}\n </label>\n <textarea\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n disabled={loading}\n rows={3}\n className=\"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\"\n placeholder={t('ggnomad.listingEdit.descriptionPlaceholder', 'Enter description')}\n />\n </div>\n\n {/* Price */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n {t(`ggnomad.listingEdit.priceLabel`, priceLabel)}\n </label>\n <input\n type=\"number\"\n step=\"0.01\"\n min=\"0\"\n value={price}\n onChange={(e) => setPrice(e.target.value)}\n disabled={loading}\n className=\"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\"\n placeholder=\"0.00\"\n />\n </div>\n\n {/* Status */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n {t('ggnomad.listingEdit.statusLabel', 'Status')}\n </label>\n <select\n value={status}\n onChange={(e) => setStatus(e.target.value)}\n disabled={loading}\n className=\"mt-1 w-full rounded-md border border-border-seam bg-bg-canvas px-3 py-2 text-sm text-text-primary focus:border-action-primary-bg focus:outline-none focus:ring-1 focus:ring-action-primary-bg disabled:opacity-50\"\n >\n <option value=\"ACTIVE\">{t('ggnomad.listingEdit.statusActive', 'Active')}</option>\n <option value=\"PAUSED\">{t('ggnomad.listingEdit.statusPaused', 'Paused')}</option>\n <option value=\"DRAFT\">{t('ggnomad.listingEdit.statusDraft', 'Draft')}</option>\n </select>\n </div>\n\n {/* Images */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n {t('ggnomad.listingEdit.imagesLabel', 'Images (one URL per line)')}\n </label>\n <textarea\n value={imagesText}\n onChange={(e) => setImagesText(e.target.value)}\n disabled={loading}\n rows={3}\n className=\"mt-1 w-full rounded-md border border-border-seam bg-bg-canvas px-3 py-2 text-sm font-mono 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\"\n placeholder={t(\n 'ggnomad.listingEdit.imagesPlaceholder',\n 'https://example.com/image1.jpg\\nhttps://example.com/image2.jpg'\n )}\n />\n </div>\n </div>\n\n <AlertDialogFooter>\n <AlertDialogCancel disabled={loading}>\n {t('ggnomad.listingEdit.cancel', 'Cancel')}\n </AlertDialogCancel>\n <AlertDialogAction\n onClick={(event) => {\n event.preventDefault();\n void handleSave();\n }}\n disabled={loading}\n className=\"bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover\"\n >\n {loading\n ? t('ggnomad.listingEdit.saving', 'Saving…')\n : t('ggnomad.listingEdit.save', 'Save changes')}\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ListingEditDialog.js","names":[],"sources":["../../../src/ggnomad/components/ListingEditDialog.tsx"],"sourcesContent":["import { useState, useMemo } 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 { Pencil } from 'lucide-react';\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n UPDATE_GGNOMAD_PROPERTY_MUTATION,\n UPDATE_GGNOMAD_TOUR_MUTATION,\n UPDATE_GGNOMAD_EVENT_MUTATION,\n UPDATE_GGNOMAD_ACTIVITY_MUTATION,\n} from '../graphql';\nimport { notify } from '../utils/notify';\n\ntype TargetType = 'PROPERTY' | 'TOUR' | 'EVENT' | 'ACTIVITY';\n\ninterface ListingEditDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n targetType: TargetType;\n id: string;\n initial: {\n name?: string;\n description?: string;\n price?: number;\n pricePerNight?: number;\n status?: string;\n images?: string[];\n };\n onSaved?: () => void;\n}\n\n/** Edit dialog for listing details (property, tour, event, activity). */\nexport function ListingEditDialog({\n open,\n onOpenChange,\n targetType,\n id,\n initial,\n onSaved,\n}: ListingEditDialogProps) {\n const { t } = useI18n();\n const [name, setName] = useState(initial.name ?? '');\n const [description, setDescription] = useState(initial.description ?? '');\n const priceField = initial.pricePerNight ?? initial.price ?? 0;\n const [price, setPrice] = useState(String(priceField));\n const [status, setStatus] = useState(initial.status ?? 'ACTIVE');\n const [imagesText, setImagesText] = useState((initial.images ?? []).join('\\n'));\n\n const [updateProperty, { loading: updatingProperty }] = useMutation(\n UPDATE_GGNOMAD_PROPERTY_MUTATION as TypedDocumentNode<\n { updateGgnomadProperty: { id: string; name: string } },\n {\n id: string;\n input: {\n name?: string;\n description?: string;\n pricePerNight?: number;\n status?: string;\n images?: string[];\n };\n }\n >\n );\n\n const [updateTour, { loading: updatingTour }] = useMutation(\n UPDATE_GGNOMAD_TOUR_MUTATION as TypedDocumentNode<\n { updateGgnomadTour: { id: string; name: string } },\n {\n id: string;\n input: {\n name?: string;\n description?: string;\n price?: number;\n status?: string;\n images?: string[];\n };\n }\n >\n );\n\n const [updateEvent, { loading: updatingEvent }] = useMutation(\n UPDATE_GGNOMAD_EVENT_MUTATION as TypedDocumentNode<\n { updateGgnomadEvent: { id: string; name: string } },\n {\n id: string;\n input: {\n name?: string;\n description?: string;\n price?: number;\n status?: string;\n images?: string[];\n };\n }\n >\n );\n\n const [updateActivity, { loading: updatingActivity }] = useMutation(\n UPDATE_GGNOMAD_ACTIVITY_MUTATION as TypedDocumentNode<\n { updateGgnomadActivity: { id: string; name: string } },\n {\n id: string;\n input: {\n name?: string;\n description?: string;\n price?: number;\n status?: string;\n images?: string[];\n };\n }\n >\n );\n\n const loading = useMemo(\n () => updatingProperty || updatingTour || updatingEvent || updatingActivity,\n [updatingProperty, updatingTour, updatingEvent, updatingActivity]\n );\n\n const handleSave = async () => {\n try {\n // Build input with only changed fields\n const input: Record<string, unknown> = {};\n\n if (name !== (initial.name ?? '')) {\n input.name = name;\n }\n if (description !== (initial.description ?? '')) {\n input.description = description;\n }\n\n const priceValue = parseFloat(price);\n const initialPrice = initial.pricePerNight ?? initial.price ?? 0;\n if (priceValue !== initialPrice) {\n if (targetType === 'PROPERTY') {\n input.pricePerNight = priceValue;\n } else {\n input.price = priceValue;\n }\n }\n\n if (status !== (initial.status ?? 'ACTIVE')) {\n input.status = status;\n }\n\n const newImages = imagesText\n .split('\\n')\n .map((line) => line.trim())\n .filter(Boolean);\n const initialImages = initial.images ?? [];\n if (JSON.stringify(newImages) !== JSON.stringify(initialImages)) {\n input.images = newImages;\n }\n\n // If nothing changed, just close\n if (Object.keys(input).length === 0) {\n notify.info(\n t('ggnomad.listingEdit.noChangesTitle', 'No changes'),\n t('ggnomad.listingEdit.noChangesBody', 'Nothing was modified.')\n );\n onOpenChange(false);\n return;\n }\n\n let result;\n if (targetType === 'PROPERTY') {\n result = await updateProperty({\n variables: { id, input },\n });\n } else if (targetType === 'TOUR') {\n result = await updateTour({\n variables: { id, input },\n });\n } else if (targetType === 'EVENT') {\n result = await updateEvent({\n variables: { id, input },\n });\n } else {\n result = await updateActivity({\n variables: { id, input },\n });\n }\n\n if (result?.error) {\n notify.error(\n t('ggnomad.listingEdit.errorTitle', 'Could not update listing'),\n result.error.message\n );\n return;\n }\n\n notify.success(\n t('ggnomad.listingEdit.successTitle', 'Listing updated'),\n t('ggnomad.listingEdit.successBody', 'Your changes have been saved.')\n );\n\n onOpenChange(false);\n onSaved?.();\n } catch (err) {\n notify.error(\n t('ggnomad.listingEdit.errorTitle', 'Could not update listing'),\n err instanceof Error ? err.message : String(err)\n );\n }\n };\n\n const priceLabel = targetType === 'PROPERTY' ? 'Price per night' : 'Price';\n\n return (\n <AlertDialog open={open} onOpenChange={onOpenChange}>\n <AlertDialogContent className=\"max-w-2xl\">\n <AlertDialogHeader>\n <AlertDialogTitle className=\"flex items-center gap-2\">\n <Pencil className=\"size-4\" />\n {t('ggnomad.listingEdit.title', 'Edit listing')}\n </AlertDialogTitle>\n <AlertDialogDescription>\n {t('ggnomad.listingEdit.description', 'Update the details for this listing.')}\n </AlertDialogDescription>\n </AlertDialogHeader>\n\n <div className=\"space-y-4\">\n {/* Name */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n {t('ggnomad.listingEdit.nameLabel', 'Name')}\n </label>\n <input\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n disabled={loading}\n className=\"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\"\n placeholder={t('ggnomad.listingEdit.namePlaceholder', 'Enter name')}\n />\n </div>\n\n {/* Description */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n {t('ggnomad.listingEdit.descriptionLabel', 'Description')}\n </label>\n <textarea\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n disabled={loading}\n rows={3}\n className=\"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\"\n placeholder={t('ggnomad.listingEdit.descriptionPlaceholder', 'Enter description')}\n />\n </div>\n\n {/* Price */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n {t(`ggnomad.listingEdit.priceLabel`, priceLabel)}\n </label>\n <input\n type=\"number\"\n step=\"0.01\"\n min=\"0\"\n value={price}\n onChange={(e) => setPrice(e.target.value)}\n disabled={loading}\n className=\"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\"\n placeholder=\"0.00\"\n />\n </div>\n\n {/* Status */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n {t('ggnomad.listingEdit.statusLabel', 'Status')}\n </label>\n <select\n value={status}\n onChange={(e) => setStatus(e.target.value)}\n disabled={loading}\n className=\"mt-1 w-full rounded-md border border-border-seam bg-bg-canvas px-3 py-2 text-sm text-text-primary focus:border-action-primary-bg focus:outline-none focus:ring-1 focus:ring-action-primary-bg disabled:opacity-50\"\n >\n <option value=\"ACTIVE\">{t('ggnomad.listingEdit.statusActive', 'Active')}</option>\n <option value=\"PAUSED\">{t('ggnomad.listingEdit.statusPaused', 'Paused')}</option>\n <option value=\"DRAFT\">{t('ggnomad.listingEdit.statusDraft', 'Draft')}</option>\n </select>\n </div>\n\n {/* Images */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n {t('ggnomad.listingEdit.imagesLabel', 'Images (one URL per line)')}\n </label>\n <textarea\n value={imagesText}\n onChange={(e) => setImagesText(e.target.value)}\n disabled={loading}\n rows={3}\n className=\"mt-1 w-full rounded-md border border-border-seam bg-bg-canvas px-3 py-2 text-sm font-mono 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\"\n placeholder={t(\n 'ggnomad.listingEdit.imagesPlaceholder',\n 'https://example.com/image1.jpg\\nhttps://example.com/image2.jpg'\n )}\n />\n </div>\n </div>\n\n <AlertDialogFooter>\n <AlertDialogCancel disabled={loading}>\n {t('ggnomad.listingEdit.cancel', 'Cancel')}\n </AlertDialogCancel>\n <AlertDialogAction\n onClick={(event) => {\n event.preventDefault();\n void handleSave();\n }}\n disabled={loading}\n className=\"bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover\"\n >\n {loading\n ? t('ggnomad.listingEdit.saving', 'Saving…')\n : t('ggnomad.listingEdit.save', 'Save changes')}\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"LoadMoreButton.js","names":[],"sources":["../../../src/ggnomad/components/LoadMoreButton.tsx"],"sourcesContent":["import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface LoadMoreButtonProps {\n /** Whether more items may exist (typically items.length >= current limit). */\n visible: boolean;\n /** Increase the page limit to fetch the next batch. */\n onLoadMore: () => void;\n}\n\n/**\n * Offset \"Load more\" control shown under a list grid when the current page is\n * full and more results may exist. Tokenized + responsive; no fixed widths.\n */\nexport function LoadMoreButton({ visible, onLoadMore }: LoadMoreButtonProps) {\n const { t } = useI18n();\n if (!visible) return null;\n return (\n <div className=\"mt-6 flex justify-center\">\n <button\n type=\"button\"\n onClick={onLoadMore}\n className=\"rounded-lg border border-border-seam bg-bg-surface px-5 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-action-primary-bg\"\n >\n {t('ggnomad.common.loadMore', 'Load more')}\n </button>\n </div>\n );\n}\n"],"mappings":";;;AAaA,SAAgB,EAAe,EAAE,YAAS,iBAAmC;CAC3E,IAAM,EAAE,SAAM,GAAS;AAEvB,QADK,IAEH,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,UAAD;GACE,MAAK;GACL,SAAS;GACT,WAAU;aAET,EAAE,2BAA2B,YAAY;GACnC,CAAA;EACL,CAAA,GAVa"}
1
+ {"version":3,"file":"LoadMoreButton.js","names":[],"sources":["../../../src/ggnomad/components/LoadMoreButton.tsx"],"sourcesContent":["import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface LoadMoreButtonProps {\n /** Whether more items may exist (typically items.length >= current limit). */\n visible: boolean;\n /** Increase the page limit to fetch the next batch. */\n onLoadMore: () => void;\n}\n\n/**\n * Offset \"Load more\" control shown under a list grid when the current page is\n * full and more results may exist. Tokenized + responsive; no fixed widths.\n */\nexport function LoadMoreButton({ visible, onLoadMore }: LoadMoreButtonProps) {\n const { t } = useI18n();\n if (!visible) return null;\n return (\n <div className=\"mt-6 flex justify-center\">\n <button\n type=\"button\"\n onClick={onLoadMore}\n className=\"rounded-lg border border-border-seam bg-bg-surface px-5 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-action-primary-bg\"\n >\n {t('ggnomad.common.loadMore', 'Load more')}\n </button>\n </div>\n );\n}\n"],"mappings":";;;AAaA,SAAgB,EAAe,EAAE,YAAS,iBAAmC;CAC3E,IAAM,EAAE,SAAM,EAAQ;CAEtB,OADK,IAEH,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,UAAD;GACE,MAAK;GACL,SAAS;GACT,WAAU;aAET,EAAE,2BAA2B,WAAW;EACnC,CAAA;CACL,CAAA,IAVc;AAYvB"}
@@ -1 +1 @@
1
- {"version":3,"file":"ReviewsSection.js","names":[],"sources":["../../../src/ggnomad/components/ReviewsSection.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Star, MessageSquare, AlertCircle } from 'lucide-react';\nimport { Button, GlassCard, Input, Textarea } from '@burdenoff/fe-libs/ui';\n\nimport {\n GGNOMAD_REVIEW_SUMMARY_QUERY,\n GGNOMAD_REVIEWS_QUERY,\n CREATE_GGNOMAD_REVIEW_MUTATION,\n} from '../graphql';\nimport { notify } from '../utils/notify';\nimport { cn } from '../utils/cn';\n\ntype GgnomadBookingType = 'PROPERTY' | 'TOUR' | 'EVENT' | 'ACTIVITY';\n\ntype ReviewsSectionProps = {\n targetType: GgnomadBookingType;\n targetId: string;\n};\n\ntype ReviewSummaryData = {\n ggnomadReviewSummary: {\n average: number;\n count: number;\n distribution: number[];\n };\n};\n\ntype ReviewsData = {\n ggnomadReviews: Array<{\n id: string;\n rating: number;\n title?: string;\n content?: string;\n isVerified: boolean;\n helpfulCount: number;\n response?: string;\n respondedAt?: string;\n createdAt: string;\n }>;\n};\n\ntype CreateReviewInput = {\n targetType: GgnomadBookingType;\n targetId: string;\n rating: number;\n title?: string;\n content?: string;\n images?: string[];\n};\n\n/**\n * Star distribution bar chart component.\n * Displays a 5-row breakdown of rating distribution.\n */\nfunction StarDistributionChart({ distribution }: { distribution: number[] }) {\n const total = distribution.reduce((a, b) => a + b, 0);\n\n return (\n <div className=\"space-y-2\">\n {[5, 4, 3, 2, 1].map((stars, idx) => {\n const count = distribution[4 - idx] ?? 0;\n const percentage = total > 0 ? (count / total) * 100 : 0;\n\n return (\n <div key={stars} className=\"flex items-center gap-2\">\n <div className=\"flex items-center gap-1 whitespace-nowrap text-xs text-text-secondary\">\n <span>{stars}</span>\n <Star className=\"size-3 text-status-warning-text\" aria-hidden=\"true\" />\n </div>\n <div className=\"flex-1 h-2 rounded-full bg-bg-sunken overflow-hidden\">\n <div\n className=\"h-full bg-status-warning-bg transition-all\"\n style={{ width: `${percentage}%` }}\n role=\"progressbar\"\n aria-label={`${count} reviews with ${stars} stars`}\n aria-valuenow={percentage}\n aria-valuemin={0}\n aria-valuemax={100}\n />\n </div>\n <span className=\"text-xs text-text-secondary tabular-nums w-8 text-right\">{count}</span>\n </div>\n );\n })}\n </div>\n );\n}\n\n/**\n * Individual review card component.\n */\nfunction ReviewCard({ review }: { review: ReviewsData['ggnomadReviews'][0] }) {\n const { t } = useI18n();\n const createdDate = new Date(review.createdAt);\n const formattedDate = createdDate.toLocaleDateString(undefined, {\n month: 'short',\n day: 'numeric',\n year: createdDate.getFullYear() !== new Date().getFullYear() ? 'numeric' : undefined,\n });\n\n return (\n <div className=\"rounded-lg border border-border-seam bg-bg-surface/40 p-4 space-y-3\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex items-center gap-2\">\n <div className=\"flex gap-0.5\">\n {[...Array(5)].map((_, i) => (\n <Star\n key={i}\n className={cn(\n 'size-3.5',\n i < review.rating\n ? 'fill-status-warning-text text-status-warning-text'\n : 'text-border-seam'\n )}\n aria-hidden=\"true\"\n />\n ))}\n </div>\n <span className=\"text-xs text-text-secondary font-medium\">{review.rating}/5</span>\n </div>\n {review.isVerified && (\n <span className=\"inline-flex items-center gap-1 text-xs font-medium text-status-success-text bg-status-success-bg-subtle px-2 py-1 rounded-full\">\n <span className=\"size-1.5 bg-status-success-text rounded-full\" aria-hidden=\"true\" />\n {t('ggnomad.reviews.verified', 'Verified')}\n </span>\n )}\n </div>\n\n {review.title && <h4 className=\"font-medium text-text-primary text-sm\">{review.title}</h4>}\n\n {review.content && (\n <p className=\"text-sm text-text-secondary leading-relaxed\">{review.content}</p>\n )}\n\n <div className=\"flex items-center justify-between pt-2 border-t border-border-seam\">\n <span className=\"text-xs text-text-secondary\">{formattedDate}</span>\n {review.helpfulCount > 0 && (\n <span className=\"text-xs text-text-secondary flex items-center gap-1\">\n <MessageSquare className=\"size-3\" aria-hidden=\"true\" />\n {review.helpfulCount}\n </span>\n )}\n </div>\n\n {review.response && (\n <div className=\"mt-3 pt-3 border-t border-border-seam bg-bg-sunken/50 rounded p-3 text-sm\">\n <p className=\"font-medium text-text-primary mb-1\">\n {t('ggnomad.reviews.hostResponse', 'Host response')}\n </p>\n <p className=\"text-text-secondary\">{review.response}</p>\n {review.respondedAt && (\n <p className=\"text-xs text-text-secondary mt-2\">\n {new Date(review.respondedAt).toLocaleDateString()}\n </p>\n )}\n </div>\n )}\n </div>\n );\n}\n\n/**\n * Review composer component.\n */\nfunction ReviewComposer({\n targetType,\n targetId,\n onReviewCreated,\n}: {\n targetType: GgnomadBookingType;\n targetId: string;\n onReviewCreated: () => void;\n}) {\n const { t } = useI18n();\n const [rating, setRating] = useState(5);\n const [title, setTitle] = useState('');\n const [content, setContent] = useState('');\n\n const [createReview, { loading: creating }] = useMutation(\n CREATE_GGNOMAD_REVIEW_MUTATION as TypedDocumentNode<\n {\n createGgnomadReview: {\n id: string;\n rating: number;\n title?: string;\n content?: string;\n };\n },\n { input: CreateReviewInput }\n >\n );\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n\n if (rating === 0) {\n notify.warning(\n t('ggnomad.reviews.composer.ratingRequired', 'Rating required'),\n t('ggnomad.reviews.composer.selectRating', 'Please select a rating before submitting.')\n );\n return;\n }\n\n try {\n const result = await createReview({\n variables: {\n input: {\n targetType,\n targetId,\n rating,\n title: title.trim() || undefined,\n content: content.trim() || undefined,\n },\n },\n });\n\n if (result.error) {\n notify.error(\n t('ggnomad.reviews.composer.error', 'Could not post review'),\n result.error.message\n );\n return;\n }\n\n notify.success(\n t('ggnomad.reviews.composer.success', 'Review posted'),\n t('ggnomad.reviews.composer.successDesc', 'Thank you for sharing your experience!')\n );\n\n // Reset form\n setRating(5);\n setTitle('');\n setContent('');\n\n // Trigger refresh\n onReviewCreated();\n } catch (err) {\n notify.error(\n t('ggnomad.reviews.composer.error', 'Could not post review'),\n err instanceof Error ? err.message : String(err)\n );\n }\n };\n\n return (\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam p-5\">\n <h3 className=\"mb-4 text-lg font-medium tracking-tight text-text-primary\">\n {t('ggnomad.reviews.composer.title', 'Share your experience')}\n </h3>\n\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {/* Rating selector */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-2\">\n {t('ggnomad.reviews.composer.rating', 'Your rating')}\n </label>\n <div className=\"flex gap-2\">\n {[1, 2, 3, 4, 5].map((star) => (\n <button\n key={star}\n type=\"button\"\n onClick={() => setRating(star)}\n className={cn(\n 'p-1.5 rounded-lg transition-all',\n rating >= star\n ? 'bg-status-warning-bg-subtle'\n : 'bg-bg-sunken hover:bg-bg-surface'\n )}\n aria-label={t(\n 'ggnomad.reviews.composer.ratingLabel',\n `Rate {{stars}} out of 5 stars`\n ).replace('{{stars}}', String(star))}\n aria-pressed={rating >= star}\n >\n <Star\n className={cn(\n 'size-5 transition-colors',\n rating >= star\n ? 'fill-status-warning-text text-status-warning-text'\n : 'text-border-seam'\n )}\n aria-hidden=\"true\"\n />\n </button>\n ))}\n </div>\n </div>\n\n {/* Title input */}\n <div>\n <label\n htmlFor=\"review-title\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n {t('ggnomad.reviews.composer.titleLabel', 'Title (optional)')}\n </label>\n <Input\n id=\"review-title\"\n placeholder={t(\n 'ggnomad.reviews.composer.titlePlaceholder',\n 'e.g., \"Great location, friendly host\"'\n )}\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n maxLength={100}\n disabled={creating}\n className=\"bg-bg-surface border-border-seam\"\n />\n <p className=\"text-xs text-text-secondary mt-1\">{title.length}/100</p>\n </div>\n\n {/* Content textarea */}\n <div>\n <label\n htmlFor=\"review-content\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n {t('ggnomad.reviews.composer.contentLabel', 'Your review (optional)')}\n </label>\n <Textarea\n id=\"review-content\"\n placeholder={t(\n 'ggnomad.reviews.composer.contentPlaceholder',\n 'Share the details of your experience...'\n )}\n value={content}\n onChange={(e) => setContent(e.target.value)}\n maxLength={1000}\n rows={4}\n disabled={creating}\n className=\"bg-bg-surface border-border-seam resize-none\"\n />\n <p className=\"text-xs text-text-secondary mt-1\">{content.length}/1000</p>\n </div>\n\n {/* Submit button */}\n <Button type=\"submit\" disabled={creating || rating === 0} className=\"w-full\">\n {creating\n ? t('ggnomad.reviews.composer.submitting', 'Posting…')\n : t('ggnomad.reviews.composer.submit', 'Post review')}\n </Button>\n </form>\n </GlassCard>\n );\n}\n\n/**\n * ReviewsSection component.\n * Displays review summary, list, and composition UI for a bookable item.\n */\nexport function ReviewsSection({ targetType, targetId }: ReviewsSectionProps) {\n const { t } = useI18n();\n const [showComposer, setShowComposer] = useState(false);\n\n // Fetch review summary\n const { data: summaryData, refetch: refetchSummary } = useQuery(\n GGNOMAD_REVIEW_SUMMARY_QUERY as TypedDocumentNode<\n ReviewSummaryData,\n { targetType: GgnomadBookingType; targetId: string }\n >,\n { variables: { targetType, targetId } }\n );\n\n // Fetch reviews list\n const { data: reviewsData, refetch: refetchReviews } = useQuery(\n GGNOMAD_REVIEWS_QUERY as TypedDocumentNode<\n ReviewsData,\n { targetType: GgnomadBookingType; targetId: string; first?: number; skip?: number }\n >,\n {\n variables: { targetType, targetId, first: 10, skip: 0 },\n }\n );\n\n const summary = summaryData?.ggnomadReviewSummary;\n const reviews = reviewsData?.ggnomadReviews ?? [];\n\n const handleReviewCreated = () => {\n setShowComposer(false);\n void refetchSummary();\n void refetchReviews();\n };\n\n return (\n <div className=\"space-y-6\">\n {/* Summary Section */}\n {summary && summary.count > 0 && (\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam p-5\">\n <h2 className=\"mb-4 text-lg font-medium tracking-tight text-text-primary\">\n {t('ggnomad.reviews.summary', 'Ratings & reviews')}\n </h2>\n\n <div className=\"grid gap-6 sm:grid-cols-2\">\n {/* Average Rating */}\n <div className=\"flex flex-col items-center justify-center py-4\">\n <div className=\"text-5xl font-bold text-text-primary tabular-nums\">\n {summary.average.toFixed(1)}\n </div>\n <div className=\"flex gap-0.5 mt-2\">\n {[...Array(5)].map((_, i) => (\n <Star\n key={i}\n className={cn(\n 'size-4',\n i < Math.round(summary.average)\n ? 'fill-status-warning-text text-status-warning-text'\n : 'text-border-seam'\n )}\n aria-hidden=\"true\"\n />\n ))}\n </div>\n <p className=\"text-sm text-text-secondary mt-2\">\n {t('ggnomad.reviews.totalCount', '{{count}} {{plural}}')\n .replace('{{count}}', String(summary.count))\n .replace(\n '{{plural}}',\n summary.count === 1\n ? t('ggnomad.reviews.review', 'review')\n : t('ggnomad.reviews.reviews', 'reviews')\n )}\n </p>\n </div>\n\n {/* Distribution Chart */}\n <div>\n <StarDistributionChart distribution={summary.distribution} />\n </div>\n </div>\n </GlassCard>\n )}\n\n {/* Write Review Composer */}\n {!showComposer && (\n <Button onClick={() => setShowComposer(true)} variant=\"secondary\" className=\"w-full\">\n {t('ggnomad.reviews.writeReview', 'Write a review')}\n </Button>\n )}\n\n {showComposer && (\n <ReviewComposer\n targetType={targetType}\n targetId={targetId}\n onReviewCreated={handleReviewCreated}\n />\n )}\n\n {/* Reviews List */}\n {reviews.length > 0 ? (\n <div>\n <h3 className=\"mb-4 text-lg font-medium tracking-tight text-text-primary\">\n {t('ggnomad.reviews.allReviews', 'All reviews')}\n </h3>\n <div className=\"grid gap-3\">\n {reviews.map((review) => (\n <ReviewCard key={review.id} review={review} />\n ))}\n </div>\n </div>\n ) : summary && summary.count === 0 ? (\n <div className=\"text-center py-8 rounded-lg border border-border-seam bg-bg-surface/40\">\n <AlertCircle className=\"size-6 text-text-secondary mx-auto mb-2\" aria-hidden=\"true\" />\n <p className=\"text-sm text-text-secondary\">\n {t('ggnomad.reviews.noReviewsYet', 'Be the first to review')}\n </p>\n </div>\n ) : null}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ReviewsSection.js","names":[],"sources":["../../../src/ggnomad/components/ReviewsSection.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Star, MessageSquare, AlertCircle } from 'lucide-react';\nimport { Button, GlassCard, Input, Textarea } from '@burdenoff/fe-libs/ui';\n\nimport {\n GGNOMAD_REVIEW_SUMMARY_QUERY,\n GGNOMAD_REVIEWS_QUERY,\n CREATE_GGNOMAD_REVIEW_MUTATION,\n} from '../graphql';\nimport { notify } from '../utils/notify';\nimport { cn } from '../utils/cn';\n\ntype GgnomadBookingType = 'PROPERTY' | 'TOUR' | 'EVENT' | 'ACTIVITY';\n\ntype ReviewsSectionProps = {\n targetType: GgnomadBookingType;\n targetId: string;\n};\n\ntype ReviewSummaryData = {\n ggnomadReviewSummary: {\n average: number;\n count: number;\n distribution: number[];\n };\n};\n\ntype ReviewsData = {\n ggnomadReviews: Array<{\n id: string;\n rating: number;\n title?: string;\n content?: string;\n isVerified: boolean;\n helpfulCount: number;\n response?: string;\n respondedAt?: string;\n createdAt: string;\n }>;\n};\n\ntype CreateReviewInput = {\n targetType: GgnomadBookingType;\n targetId: string;\n rating: number;\n title?: string;\n content?: string;\n images?: string[];\n};\n\n/**\n * Star distribution bar chart component.\n * Displays a 5-row breakdown of rating distribution.\n */\nfunction StarDistributionChart({ distribution }: { distribution: number[] }) {\n const total = distribution.reduce((a, b) => a + b, 0);\n\n return (\n <div className=\"space-y-2\">\n {[5, 4, 3, 2, 1].map((stars, idx) => {\n const count = distribution[4 - idx] ?? 0;\n const percentage = total > 0 ? (count / total) * 100 : 0;\n\n return (\n <div key={stars} className=\"flex items-center gap-2\">\n <div className=\"flex items-center gap-1 whitespace-nowrap text-xs text-text-secondary\">\n <span>{stars}</span>\n <Star className=\"size-3 text-status-warning-text\" aria-hidden=\"true\" />\n </div>\n <div className=\"flex-1 h-2 rounded-full bg-bg-sunken overflow-hidden\">\n <div\n className=\"h-full bg-status-warning-bg transition-all\"\n style={{ width: `${percentage}%` }}\n role=\"progressbar\"\n aria-label={`${count} reviews with ${stars} stars`}\n aria-valuenow={percentage}\n aria-valuemin={0}\n aria-valuemax={100}\n />\n </div>\n <span className=\"text-xs text-text-secondary tabular-nums w-8 text-right\">{count}</span>\n </div>\n );\n })}\n </div>\n );\n}\n\n/**\n * Individual review card component.\n */\nfunction ReviewCard({ review }: { review: ReviewsData['ggnomadReviews'][0] }) {\n const { t } = useI18n();\n const createdDate = new Date(review.createdAt);\n const formattedDate = createdDate.toLocaleDateString(undefined, {\n month: 'short',\n day: 'numeric',\n year: createdDate.getFullYear() !== new Date().getFullYear() ? 'numeric' : undefined,\n });\n\n return (\n <div className=\"rounded-lg border border-border-seam bg-bg-surface/40 p-4 space-y-3\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex items-center gap-2\">\n <div className=\"flex gap-0.5\">\n {[...Array(5)].map((_, i) => (\n <Star\n key={i}\n className={cn(\n 'size-3.5',\n i < review.rating\n ? 'fill-status-warning-text text-status-warning-text'\n : 'text-border-seam'\n )}\n aria-hidden=\"true\"\n />\n ))}\n </div>\n <span className=\"text-xs text-text-secondary font-medium\">{review.rating}/5</span>\n </div>\n {review.isVerified && (\n <span className=\"inline-flex items-center gap-1 text-xs font-medium text-status-success-text bg-status-success-bg-subtle px-2 py-1 rounded-full\">\n <span className=\"size-1.5 bg-status-success-text rounded-full\" aria-hidden=\"true\" />\n {t('ggnomad.reviews.verified', 'Verified')}\n </span>\n )}\n </div>\n\n {review.title && <h4 className=\"font-medium text-text-primary text-sm\">{review.title}</h4>}\n\n {review.content && (\n <p className=\"text-sm text-text-secondary leading-relaxed\">{review.content}</p>\n )}\n\n <div className=\"flex items-center justify-between pt-2 border-t border-border-seam\">\n <span className=\"text-xs text-text-secondary\">{formattedDate}</span>\n {review.helpfulCount > 0 && (\n <span className=\"text-xs text-text-secondary flex items-center gap-1\">\n <MessageSquare className=\"size-3\" aria-hidden=\"true\" />\n {review.helpfulCount}\n </span>\n )}\n </div>\n\n {review.response && (\n <div className=\"mt-3 pt-3 border-t border-border-seam bg-bg-sunken/50 rounded p-3 text-sm\">\n <p className=\"font-medium text-text-primary mb-1\">\n {t('ggnomad.reviews.hostResponse', 'Host response')}\n </p>\n <p className=\"text-text-secondary\">{review.response}</p>\n {review.respondedAt && (\n <p className=\"text-xs text-text-secondary mt-2\">\n {new Date(review.respondedAt).toLocaleDateString()}\n </p>\n )}\n </div>\n )}\n </div>\n );\n}\n\n/**\n * Review composer component.\n */\nfunction ReviewComposer({\n targetType,\n targetId,\n onReviewCreated,\n}: {\n targetType: GgnomadBookingType;\n targetId: string;\n onReviewCreated: () => void;\n}) {\n const { t } = useI18n();\n const [rating, setRating] = useState(5);\n const [title, setTitle] = useState('');\n const [content, setContent] = useState('');\n\n const [createReview, { loading: creating }] = useMutation(\n CREATE_GGNOMAD_REVIEW_MUTATION as TypedDocumentNode<\n {\n createGgnomadReview: {\n id: string;\n rating: number;\n title?: string;\n content?: string;\n };\n },\n { input: CreateReviewInput }\n >\n );\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n\n if (rating === 0) {\n notify.warning(\n t('ggnomad.reviews.composer.ratingRequired', 'Rating required'),\n t('ggnomad.reviews.composer.selectRating', 'Please select a rating before submitting.')\n );\n return;\n }\n\n try {\n const result = await createReview({\n variables: {\n input: {\n targetType,\n targetId,\n rating,\n title: title.trim() || undefined,\n content: content.trim() || undefined,\n },\n },\n });\n\n if (result.error) {\n notify.error(\n t('ggnomad.reviews.composer.error', 'Could not post review'),\n result.error.message\n );\n return;\n }\n\n notify.success(\n t('ggnomad.reviews.composer.success', 'Review posted'),\n t('ggnomad.reviews.composer.successDesc', 'Thank you for sharing your experience!')\n );\n\n // Reset form\n setRating(5);\n setTitle('');\n setContent('');\n\n // Trigger refresh\n onReviewCreated();\n } catch (err) {\n notify.error(\n t('ggnomad.reviews.composer.error', 'Could not post review'),\n err instanceof Error ? err.message : String(err)\n );\n }\n };\n\n return (\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam p-5\">\n <h3 className=\"mb-4 text-lg font-medium tracking-tight text-text-primary\">\n {t('ggnomad.reviews.composer.title', 'Share your experience')}\n </h3>\n\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {/* Rating selector */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-2\">\n {t('ggnomad.reviews.composer.rating', 'Your rating')}\n </label>\n <div className=\"flex gap-2\">\n {[1, 2, 3, 4, 5].map((star) => (\n <button\n key={star}\n type=\"button\"\n onClick={() => setRating(star)}\n className={cn(\n 'p-1.5 rounded-lg transition-all',\n rating >= star\n ? 'bg-status-warning-bg-subtle'\n : 'bg-bg-sunken hover:bg-bg-surface'\n )}\n aria-label={t(\n 'ggnomad.reviews.composer.ratingLabel',\n `Rate {{stars}} out of 5 stars`\n ).replace('{{stars}}', String(star))}\n aria-pressed={rating >= star}\n >\n <Star\n className={cn(\n 'size-5 transition-colors',\n rating >= star\n ? 'fill-status-warning-text text-status-warning-text'\n : 'text-border-seam'\n )}\n aria-hidden=\"true\"\n />\n </button>\n ))}\n </div>\n </div>\n\n {/* Title input */}\n <div>\n <label\n htmlFor=\"review-title\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n {t('ggnomad.reviews.composer.titleLabel', 'Title (optional)')}\n </label>\n <Input\n id=\"review-title\"\n placeholder={t(\n 'ggnomad.reviews.composer.titlePlaceholder',\n 'e.g., \"Great location, friendly host\"'\n )}\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n maxLength={100}\n disabled={creating}\n className=\"bg-bg-surface border-border-seam\"\n />\n <p className=\"text-xs text-text-secondary mt-1\">{title.length}/100</p>\n </div>\n\n {/* Content textarea */}\n <div>\n <label\n htmlFor=\"review-content\"\n className=\"block text-sm font-medium text-text-primary mb-2\"\n >\n {t('ggnomad.reviews.composer.contentLabel', 'Your review (optional)')}\n </label>\n <Textarea\n id=\"review-content\"\n placeholder={t(\n 'ggnomad.reviews.composer.contentPlaceholder',\n 'Share the details of your experience...'\n )}\n value={content}\n onChange={(e) => setContent(e.target.value)}\n maxLength={1000}\n rows={4}\n disabled={creating}\n className=\"bg-bg-surface border-border-seam resize-none\"\n />\n <p className=\"text-xs text-text-secondary mt-1\">{content.length}/1000</p>\n </div>\n\n {/* Submit button */}\n <Button type=\"submit\" disabled={creating || rating === 0} className=\"w-full\">\n {creating\n ? t('ggnomad.reviews.composer.submitting', 'Posting…')\n : t('ggnomad.reviews.composer.submit', 'Post review')}\n </Button>\n </form>\n </GlassCard>\n );\n}\n\n/**\n * ReviewsSection component.\n * Displays review summary, list, and composition UI for a bookable item.\n */\nexport function ReviewsSection({ targetType, targetId }: ReviewsSectionProps) {\n const { t } = useI18n();\n const [showComposer, setShowComposer] = useState(false);\n\n // Fetch review summary\n const { data: summaryData, refetch: refetchSummary } = useQuery(\n GGNOMAD_REVIEW_SUMMARY_QUERY as TypedDocumentNode<\n ReviewSummaryData,\n { targetType: GgnomadBookingType; targetId: string }\n >,\n { variables: { targetType, targetId } }\n );\n\n // Fetch reviews list\n const { data: reviewsData, refetch: refetchReviews } = useQuery(\n GGNOMAD_REVIEWS_QUERY as TypedDocumentNode<\n ReviewsData,\n { targetType: GgnomadBookingType; targetId: string; first?: number; skip?: number }\n >,\n {\n variables: { targetType, targetId, first: 10, skip: 0 },\n }\n );\n\n const summary = summaryData?.ggnomadReviewSummary;\n const reviews = reviewsData?.ggnomadReviews ?? [];\n\n const handleReviewCreated = () => {\n setShowComposer(false);\n void refetchSummary();\n void refetchReviews();\n };\n\n return (\n <div className=\"space-y-6\">\n {/* Summary Section */}\n {summary && summary.count > 0 && (\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam p-5\">\n <h2 className=\"mb-4 text-lg font-medium tracking-tight text-text-primary\">\n {t('ggnomad.reviews.summary', 'Ratings & reviews')}\n </h2>\n\n <div className=\"grid gap-6 sm:grid-cols-2\">\n {/* Average Rating */}\n <div className=\"flex flex-col items-center justify-center py-4\">\n <div className=\"text-5xl font-bold text-text-primary tabular-nums\">\n {summary.average.toFixed(1)}\n </div>\n <div className=\"flex gap-0.5 mt-2\">\n {[...Array(5)].map((_, i) => (\n <Star\n key={i}\n className={cn(\n 'size-4',\n i < Math.round(summary.average)\n ? 'fill-status-warning-text text-status-warning-text'\n : 'text-border-seam'\n )}\n aria-hidden=\"true\"\n />\n ))}\n </div>\n <p className=\"text-sm text-text-secondary mt-2\">\n {t('ggnomad.reviews.totalCount', '{{count}} {{plural}}')\n .replace('{{count}}', String(summary.count))\n .replace(\n '{{plural}}',\n summary.count === 1\n ? t('ggnomad.reviews.review', 'review')\n : t('ggnomad.reviews.reviews', 'reviews')\n )}\n </p>\n </div>\n\n {/* Distribution Chart */}\n <div>\n <StarDistributionChart distribution={summary.distribution} />\n </div>\n </div>\n </GlassCard>\n )}\n\n {/* Write Review Composer */}\n {!showComposer && (\n <Button onClick={() => setShowComposer(true)} variant=\"secondary\" className=\"w-full\">\n {t('ggnomad.reviews.writeReview', 'Write a review')}\n </Button>\n )}\n\n {showComposer && (\n <ReviewComposer\n targetType={targetType}\n targetId={targetId}\n onReviewCreated={handleReviewCreated}\n />\n )}\n\n {/* Reviews List */}\n {reviews.length > 0 ? (\n <div>\n <h3 className=\"mb-4 text-lg font-medium tracking-tight text-text-primary\">\n {t('ggnomad.reviews.allReviews', 'All reviews')}\n </h3>\n <div className=\"grid gap-3\">\n {reviews.map((review) => (\n <ReviewCard key={review.id} review={review} />\n ))}\n </div>\n </div>\n ) : summary && summary.count === 0 ? (\n <div className=\"text-center py-8 rounded-lg border border-border-seam bg-bg-surface/40\">\n <AlertCircle className=\"size-6 text-text-secondary mx-auto mb-2\" aria-hidden=\"true\" />\n <p className=\"text-sm text-text-secondary\">\n {t('ggnomad.reviews.noReviewsYet', 'Be the first to review')}\n </p>\n </div>\n ) : null}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"SectionCard.js","names":[],"sources":["../../../src/ggnomad/components/SectionCard.tsx"],"sourcesContent":["import type { PropsWithChildren, ReactNode } from 'react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\n\ntype SectionCardProps = PropsWithChildren<{\n title?: ReactNode;\n action?: ReactNode;\n className?: string;\n treatment?: 'plain' | 'glass' | 'metric' | 'insight';\n}>;\n\n/** A titled glass surface used for grouped content blocks. */\nexport function SectionCard({\n title,\n action,\n className,\n treatment = 'plain',\n children,\n}: SectionCardProps) {\n return (\n <GlassCard treatment={treatment} className={cn('border-border-seam p-5', className)}>\n {title || action ? (\n <div className=\"mb-4 flex items-center justify-between gap-3\">\n {title ? (\n <h2 className=\"text-lg font-medium tracking-tight text-text-primary\">{title}</h2>\n ) : (\n <span />\n )}\n {action}\n </div>\n ) : null}\n {children}\n </GlassCard>\n );\n}\n"],"mappings":";;;;AAYA,SAAgB,EAAY,EAC1B,UACA,WACA,cACA,eAAY,SACZ,eACmB;AACnB,QACE,kBAAC,GAAD;EAAsB;EAAW,WAAW,EAAG,0BAA0B,EAAU;YAAnF,CACG,KAAS,IACR,kBAAC,OAAD;GAAK,WAAU;aAAf,CACG,IACC,kBAAC,MAAD;IAAI,WAAU;cAAwD;IAAW,CAAA,GAEjF,kBAAC,QAAD,EAAQ,CAAA,EAET,EACG;OACJ,MACH,EACS"}
1
+ {"version":3,"file":"SectionCard.js","names":[],"sources":["../../../src/ggnomad/components/SectionCard.tsx"],"sourcesContent":["import type { PropsWithChildren, ReactNode } from 'react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\n\ntype SectionCardProps = PropsWithChildren<{\n title?: ReactNode;\n action?: ReactNode;\n className?: string;\n treatment?: 'plain' | 'glass' | 'metric' | 'insight';\n}>;\n\n/** A titled glass surface used for grouped content blocks. */\nexport function SectionCard({\n title,\n action,\n className,\n treatment = 'plain',\n children,\n}: SectionCardProps) {\n return (\n <GlassCard treatment={treatment} className={cn('border-border-seam p-5', className)}>\n {title || action ? (\n <div className=\"mb-4 flex items-center justify-between gap-3\">\n {title ? (\n <h2 className=\"text-lg font-medium tracking-tight text-text-primary\">{title}</h2>\n ) : (\n <span />\n )}\n {action}\n </div>\n ) : null}\n {children}\n </GlassCard>\n );\n}\n"],"mappings":";;;;AAYA,SAAgB,EAAY,EAC1B,UACA,WACA,cACA,eAAY,SACZ,eACmB;CACnB,OACE,kBAAC,GAAD;EAAsB;EAAW,WAAW,EAAG,0BAA0B,CAAS;YAAlF,CACG,KAAS,IACR,kBAAC,OAAD;GAAK,WAAU;aAAf,CACG,IACC,kBAAC,MAAD;IAAI,WAAU;cAAwD;GAAU,CAAA,IAEhF,kBAAC,QAAD,CAAO,CAAA,GAER,CACE;OACH,MACH,CACQ;;AAEf"}
@@ -1 +1 @@
1
- {"version":3,"file":"StatCard.js","names":[],"sources":["../../../src/ggnomad/components/StatCard.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { GlassCard, GradientText } from '@burdenoff/fe-libs/ui';\n\ntype StatCardProps = {\n label: string;\n value: ReactNode;\n icon?: ReactNode;\n /** The single most important metric on a page reads as the glass hero tile. */\n hero?: boolean;\n};\n\n/**\n * KPI / metric tile. Neutral metric tiles use the GlassCard \"metric\" treatment;\n * the page's hero metric uses the \"glass\" treatment with a soft accent glow and\n * a gradient numeral. Numbers are tabular for calm vertical alignment.\n */\nexport function StatCard({ label, value, icon, hero = false }: StatCardProps) {\n return (\n <GlassCard treatment={hero ? 'glass' : 'metric'} glow={hero} className=\"border-border-seam p-4\">\n <div className=\"flex items-start justify-between gap-3\">\n <p className=\"text-sm font-medium text-text-secondary\">{label}</p>\n {icon ? (\n <span className=\"flex size-9 flex-shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary [&>svg]:size-4\">\n {icon}\n </span>\n ) : null}\n </div>\n <p className=\"mt-2 text-2xl font-semibold tabular-nums text-text-primary\">\n {hero ? <GradientText>{value}</GradientText> : value}\n </p>\n </GlassCard>\n );\n}\n"],"mappings":";;;AAgBA,SAAgB,EAAS,EAAE,UAAO,UAAO,SAAM,UAAO,MAAwB;AAC5E,QACE,kBAAC,GAAD;EAAW,WAAW,IAAO,UAAU;EAAU,MAAM;EAAM,WAAU;YAAvE,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,KAAD;IAAG,WAAU;cAA2C;IAAU,CAAA,EACjE,IACC,kBAAC,QAAD;IAAM,WAAU;cACb;IACI,CAAA,GACL,KACA;MACN,kBAAC,KAAD;GAAG,WAAU;aACV,IAAO,kBAAC,GAAD,EAAA,UAAe,GAAqB,CAAA,GAAG;GAC7C,CAAA,CACM"}
1
+ {"version":3,"file":"StatCard.js","names":[],"sources":["../../../src/ggnomad/components/StatCard.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { GlassCard, GradientText } from '@burdenoff/fe-libs/ui';\n\ntype StatCardProps = {\n label: string;\n value: ReactNode;\n icon?: ReactNode;\n /** The single most important metric on a page reads as the glass hero tile. */\n hero?: boolean;\n};\n\n/**\n * KPI / metric tile. Neutral metric tiles use the GlassCard \"metric\" treatment;\n * the page's hero metric uses the \"glass\" treatment with a soft accent glow and\n * a gradient numeral. Numbers are tabular for calm vertical alignment.\n */\nexport function StatCard({ label, value, icon, hero = false }: StatCardProps) {\n return (\n <GlassCard treatment={hero ? 'glass' : 'metric'} glow={hero} className=\"border-border-seam p-4\">\n <div className=\"flex items-start justify-between gap-3\">\n <p className=\"text-sm font-medium text-text-secondary\">{label}</p>\n {icon ? (\n <span className=\"flex size-9 flex-shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary [&>svg]:size-4\">\n {icon}\n </span>\n ) : null}\n </div>\n <p className=\"mt-2 text-2xl font-semibold tabular-nums text-text-primary\">\n {hero ? <GradientText>{value}</GradientText> : value}\n </p>\n </GlassCard>\n );\n}\n"],"mappings":";;;AAgBA,SAAgB,EAAS,EAAE,UAAO,UAAO,SAAM,UAAO,MAAwB;CAC5E,OACE,kBAAC,GAAD;EAAW,WAAW,IAAO,UAAU;EAAU,MAAM;EAAM,WAAU;YAAvE,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,KAAD;IAAG,WAAU;cAA2C;GAAS,CAAA,GAChE,IACC,kBAAC,QAAD;IAAM,WAAU;cACb;GACG,CAAA,IACJ,IACD;MACL,kBAAC,KAAD;GAAG,WAAU;aACV,IAAO,kBAAC,GAAD,EAAA,UAAe,EAAoB,CAAA,IAAI;EAC9C,CAAA,CACM;;AAEf"}
@@ -1 +1 @@
1
- {"version":3,"file":"StatusBadge.js","names":[],"sources":["../../../src/ggnomad/components/StatusBadge.tsx"],"sourcesContent":["import { cn } from '../utils/cn';\n\ntype Tone = 'success' | 'warning' | 'danger' | 'info' | 'neutral';\n\nconst TONE: Record<Tone, { dot: string; text: string; bg: string }> = {\n success: {\n dot: 'bg-status-success-text',\n text: 'text-status-success-text',\n bg: 'bg-status-success-bg-subtle',\n },\n warning: {\n dot: 'bg-status-warning-text',\n text: 'text-status-warning-text',\n bg: 'bg-status-warning-bg-subtle',\n },\n danger: {\n dot: 'bg-status-error-text',\n text: 'text-status-error-text',\n bg: 'bg-status-error-bg-subtle',\n },\n info: {\n dot: 'bg-status-info-text',\n text: 'text-status-info-text',\n bg: 'bg-status-info-bg-subtle',\n },\n neutral: {\n dot: 'bg-text-secondary',\n text: 'text-text-secondary',\n bg: 'bg-bg-sunken',\n },\n};\n\n/** Maps a free-form status string to a semantic tone. */\nexport function toneForStatus(status: string): Tone {\n const s = status.toUpperCase();\n if (['CONFIRMED', 'ACTIVE', 'COMPLETED', 'PAID', 'APPROVED'].includes(s)) {\n return 'success';\n }\n if (['PENDING', 'PROCESSING', 'AWAITING', 'IN_PROGRESS', 'ONBOARDING'].includes(s)) {\n return 'warning';\n }\n if (['CANCELLED', 'FAILED', 'REJECTED', 'SUSPENDED'].includes(s)) {\n return 'danger';\n }\n return 'info';\n}\n\ntype StatusBadgeProps = {\n status: string;\n /** Pulses the dot for live/active states (opacity only, reduced-motion-safe). */\n live?: boolean;\n className?: string;\n};\n\nexport function StatusBadge({ status, live = false, className }: StatusBadgeProps) {\n const tone = TONE[toneForStatus(status)];\n return (\n <span\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium',\n tone.bg,\n tone.text,\n className\n )}\n >\n <span\n className={cn(\n 'size-1.5 rounded-full',\n tone.dot,\n live ? 'motion-safe:animate-vc-glow-pulse' : ''\n )}\n aria-hidden=\"true\"\n />\n {status}\n </span>\n );\n}\n"],"mappings":";;;AAIA,IAAM,IAAgE;CACpE,SAAS;EACP,KAAK;EACL,MAAM;EACN,IAAI;EACL;CACD,SAAS;EACP,KAAK;EACL,MAAM;EACN,IAAI;EACL;CACD,QAAQ;EACN,KAAK;EACL,MAAM;EACN,IAAI;EACL;CACD,MAAM;EACJ,KAAK;EACL,MAAM;EACN,IAAI;EACL;CACD,SAAS;EACP,KAAK;EACL,MAAM;EACN,IAAI;EACL;CACF;AAGD,SAAgB,EAAc,GAAsB;CAClD,IAAM,IAAI,EAAO,aAAa;AAU9B,QATI;EAAC;EAAa;EAAU;EAAa;EAAQ;EAAW,CAAC,SAAS,EAAE,GAC/D,YAEL;EAAC;EAAW;EAAc;EAAY;EAAe;EAAa,CAAC,SAAS,EAAE,GACzE,YAEL;EAAC;EAAa;EAAU;EAAY;EAAY,CAAC,SAAS,EAAE,GACvD,WAEF;;AAUT,SAAgB,EAAY,EAAE,WAAQ,UAAO,IAAO,gBAA+B;CACjF,IAAM,IAAO,EAAK,EAAc,EAAO;AACvC,QACE,kBAAC,QAAD;EACE,WAAW,EACT,mFACA,EAAK,IACL,EAAK,MACL,EACD;YANH,CAQE,kBAAC,QAAD;GACE,WAAW,EACT,yBACA,EAAK,KACL,IAAO,sCAAsC,GAC9C;GACD,eAAY;GACZ,CAAA,EACD,EACI"}
1
+ {"version":3,"file":"StatusBadge.js","names":[],"sources":["../../../src/ggnomad/components/StatusBadge.tsx"],"sourcesContent":["import { cn } from '../utils/cn';\n\ntype Tone = 'success' | 'warning' | 'danger' | 'info' | 'neutral';\n\nconst TONE: Record<Tone, { dot: string; text: string; bg: string }> = {\n success: {\n dot: 'bg-status-success-text',\n text: 'text-status-success-text',\n bg: 'bg-status-success-bg-subtle',\n },\n warning: {\n dot: 'bg-status-warning-text',\n text: 'text-status-warning-text',\n bg: 'bg-status-warning-bg-subtle',\n },\n danger: {\n dot: 'bg-status-error-text',\n text: 'text-status-error-text',\n bg: 'bg-status-error-bg-subtle',\n },\n info: {\n dot: 'bg-status-info-text',\n text: 'text-status-info-text',\n bg: 'bg-status-info-bg-subtle',\n },\n neutral: {\n dot: 'bg-text-secondary',\n text: 'text-text-secondary',\n bg: 'bg-bg-sunken',\n },\n};\n\n/** Maps a free-form status string to a semantic tone. */\nexport function toneForStatus(status: string): Tone {\n const s = status.toUpperCase();\n if (['CONFIRMED', 'ACTIVE', 'COMPLETED', 'PAID', 'APPROVED'].includes(s)) {\n return 'success';\n }\n if (['PENDING', 'PROCESSING', 'AWAITING', 'IN_PROGRESS', 'ONBOARDING'].includes(s)) {\n return 'warning';\n }\n if (['CANCELLED', 'FAILED', 'REJECTED', 'SUSPENDED'].includes(s)) {\n return 'danger';\n }\n return 'info';\n}\n\ntype StatusBadgeProps = {\n status: string;\n /** Pulses the dot for live/active states (opacity only, reduced-motion-safe). */\n live?: boolean;\n className?: string;\n};\n\nexport function StatusBadge({ status, live = false, className }: StatusBadgeProps) {\n const tone = TONE[toneForStatus(status)];\n return (\n <span\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium',\n tone.bg,\n tone.text,\n className\n )}\n >\n <span\n className={cn(\n 'size-1.5 rounded-full',\n tone.dot,\n live ? 'motion-safe:animate-vc-glow-pulse' : ''\n )}\n aria-hidden=\"true\"\n />\n {status}\n </span>\n );\n}\n"],"mappings":";;;AAIA,IAAM,IAAgE;CACpE,SAAS;EACP,KAAK;EACL,MAAM;EACN,IAAI;CACN;CACA,SAAS;EACP,KAAK;EACL,MAAM;EACN,IAAI;CACN;CACA,QAAQ;EACN,KAAK;EACL,MAAM;EACN,IAAI;CACN;CACA,MAAM;EACJ,KAAK;EACL,MAAM;EACN,IAAI;CACN;CACA,SAAS;EACP,KAAK;EACL,MAAM;EACN,IAAI;CACN;AACF;AAGA,SAAgB,EAAc,GAAsB;CAClD,IAAM,IAAI,EAAO,YAAY;CAU7B,OATI;EAAC;EAAa;EAAU;EAAa;EAAQ;CAAU,EAAE,SAAS,CAAC,IAC9D,YAEL;EAAC;EAAW;EAAc;EAAY;EAAe;CAAY,EAAE,SAAS,CAAC,IACxE,YAEL;EAAC;EAAa;EAAU;EAAY;CAAW,EAAE,SAAS,CAAC,IACtD,WAEF;AACT;AASA,SAAgB,EAAY,EAAE,WAAQ,UAAO,IAAO,gBAA+B;CACjF,IAAM,IAAO,EAAK,EAAc,CAAM;CACtC,OACE,kBAAC,QAAD;EACE,WAAW,EACT,mFACA,EAAK,IACL,EAAK,MACL,CACF;YANF,CAQE,kBAAC,QAAD;GACE,WAAW,EACT,yBACA,EAAK,KACL,IAAO,sCAAsC,EAC/C;GACA,eAAY;EACb,CAAA,GACA,CACG;;AAEV"}
@@ -1 +1 @@
1
- {"version":3,"file":"ActivityDetailsPage.js","names":[],"sources":["../../../src/ggnomad/pages/ActivityDetailsPage.tsx"],"sourcesContent":["import { 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 { useNavigate, useParams } from 'react-router-dom';\nimport { Activity, Compass, Pencil, ShieldCheck } from 'lucide-react';\nimport { GlassCard, IllustratedEmptyState, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';\n\nimport { GGNOMAD_ACTIVITY_QUERY } from '../graphql';\nimport { useGGNomad } from '../providers/GGNomadProvider';\nimport { joinPath } from '../utils/navigation';\nimport { PageShell } from './PageShell';\nimport {\n DetailRow,\n SectionCard,\n DetailHero,\n BookingPanel,\n ReviewsSection,\n ListingEditDialog,\n} from '../components';\n\nexport function ActivityDetailsPage() {\n const { t } = useI18n();\n const navigate = useNavigate();\n const { basePath = '/' } = useGGNomad();\n const { id = '' } = useParams();\n const [editDialogOpen, setEditDialogOpen] = useState(false);\n const { data, refetch } = useQuery(\n GGNOMAD_ACTIVITY_QUERY as TypedDocumentNode<\n {\n ggnomadActivity: {\n id: string;\n name: string;\n description?: string;\n category: string;\n duration: string;\n maxParticipants: number;\n currency: string;\n price: number;\n status?: string;\n safetyInfo?: string;\n images?: string[];\n } | null;\n },\n { id: string }\n >,\n { variables: { id }, skip: !id }\n );\n const activity = data?.ggnomadActivity;\n\n return (\n <PageShell\n title={activity?.name ?? t('ggnomad.activity.fallbackTitle', 'Activity details')}\n subtitle={t('ggnomad.activity.subtitle', 'Safety, inclusions and participation details')}\n >\n {!activity ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={t('ggnomad.activity.notFoundTitle', 'No activity found')}\n description={t(\n 'ggnomad.activity.notFoundDescription',\n 'This activity is unavailable or may have been removed.'\n )}\n />\n ) : (\n <>\n <DetailHero image={activity.images?.[0]} alt={activity.name} />\n <PagePurpose icon={<Activity className=\"size-4\" />}>\n {t(\n 'ggnomad.activity.purpose',\n 'Everything you need to know before joining this activity — its category, duration, group size, price and the safety policy that applies — so you can be confident it fits your group before you book.'\n )}\n </PagePurpose>\n\n <NextSteps\n storageKey=\"ggnomad-activity-detail\"\n steps={[\n {\n id: 'browse',\n label: t('ggnomad.activity.next.browse', 'Explore more experiences'),\n description: t(\n 'ggnomad.activity.next.browseDesc',\n 'Return to Tourism to compare tours and activities.'\n ),\n icon: <Compass className=\"size-4\" />,\n onClick: () => navigate(joinPath(basePath, 'tourism')),\n },\n ]}\n />\n\n <div className=\"grid gap-4 lg:grid-cols-3\">\n <div className=\"lg:col-span-2 space-y-4\">\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam p-5\">\n <div className=\"mb-4 flex items-center justify-between gap-3\">\n <h2 className=\"text-lg font-medium tracking-tight text-text-primary\">\n {t('ggnomad.activity.details', 'Details')}\n </h2>\n <button\n onClick={() => setEditDialogOpen(true)}\n className=\"rounded-md p-2 text-text-secondary hover:bg-action-primary-bg hover:text-action-primary-text\"\n aria-label={t('ggnomad.activity.editListing', 'Edit listing')}\n title={t('ggnomad.activity.editListing', 'Edit listing')}\n >\n <Pencil className=\"size-4\" />\n </button>\n </div>\n <dl className=\"space-y-2 text-sm\">\n <DetailRow\n label={t('ggnomad.activity.category', 'Category')}\n value={activity.category}\n />\n <DetailRow\n label={t('ggnomad.activity.duration', 'Duration')}\n value={activity.duration}\n />\n <DetailRow\n label={t('ggnomad.activity.maxParticipants', 'Max participants')}\n value={String(activity.maxParticipants)}\n />\n <DetailRow\n label={t('ggnomad.activity.price', 'Price')}\n value={`${activity.currency} ${Number(activity.price).toFixed(2)}`}\n />\n </dl>\n </GlassCard>\n <SectionCard title={t('ggnomad.activity.safety', 'Safety information')}>\n <div className=\"flex items-start gap-2 text-sm text-text-primary\">\n <ShieldCheck\n className=\"mt-0.5 size-4 flex-shrink-0 text-status-success-text\"\n aria-hidden=\"true\"\n />\n <p>\n {activity.safetyInfo ??\n t(\n 'ggnomad.activity.defaultSafety',\n 'Standard provider safety policy applies.'\n )}\n </p>\n </div>\n </SectionCard>\n <ReviewsSection targetType=\"ACTIVITY\" targetId={activity.id ?? id} />\n </div>\n <div>\n <BookingPanel\n bookingType=\"ACTIVITY\"\n itemId={activity.id ?? id}\n itemName={activity.name}\n unitPrice={activity.price ?? 0}\n currency={activity.currency ?? 'USD'}\n />\n </div>\n </div>\n\n <ListingEditDialog\n open={editDialogOpen}\n onOpenChange={setEditDialogOpen}\n targetType=\"ACTIVITY\"\n id={activity.id ?? id}\n initial={{\n name: activity.name,\n description: activity.description,\n price: activity.price,\n status: activity.status,\n images: activity.images,\n }}\n onSaved={() => refetch()}\n />\n </>\n )}\n </PageShell>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ActivityDetailsPage.js","names":[],"sources":["../../../src/ggnomad/pages/ActivityDetailsPage.tsx"],"sourcesContent":["import { 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 { useNavigate, useParams } from 'react-router-dom';\nimport { Activity, Compass, Pencil, ShieldCheck } from 'lucide-react';\nimport { GlassCard, IllustratedEmptyState, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';\n\nimport { GGNOMAD_ACTIVITY_QUERY } from '../graphql';\nimport { useGGNomad } from '../providers/GGNomadProvider';\nimport { joinPath } from '../utils/navigation';\nimport { PageShell } from './PageShell';\nimport {\n DetailRow,\n SectionCard,\n DetailHero,\n BookingPanel,\n ReviewsSection,\n ListingEditDialog,\n} from '../components';\n\nexport function ActivityDetailsPage() {\n const { t } = useI18n();\n const navigate = useNavigate();\n const { basePath = '/' } = useGGNomad();\n const { id = '' } = useParams();\n const [editDialogOpen, setEditDialogOpen] = useState(false);\n const { data, refetch } = useQuery(\n GGNOMAD_ACTIVITY_QUERY as TypedDocumentNode<\n {\n ggnomadActivity: {\n id: string;\n name: string;\n description?: string;\n category: string;\n duration: string;\n maxParticipants: number;\n currency: string;\n price: number;\n status?: string;\n safetyInfo?: string;\n images?: string[];\n } | null;\n },\n { id: string }\n >,\n { variables: { id }, skip: !id }\n );\n const activity = data?.ggnomadActivity;\n\n return (\n <PageShell\n title={activity?.name ?? t('ggnomad.activity.fallbackTitle', 'Activity details')}\n subtitle={t('ggnomad.activity.subtitle', 'Safety, inclusions and participation details')}\n >\n {!activity ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={t('ggnomad.activity.notFoundTitle', 'No activity found')}\n description={t(\n 'ggnomad.activity.notFoundDescription',\n 'This activity is unavailable or may have been removed.'\n )}\n />\n ) : (\n <>\n <DetailHero image={activity.images?.[0]} alt={activity.name} />\n <PagePurpose icon={<Activity className=\"size-4\" />}>\n {t(\n 'ggnomad.activity.purpose',\n 'Everything you need to know before joining this activity — its category, duration, group size, price and the safety policy that applies — so you can be confident it fits your group before you book.'\n )}\n </PagePurpose>\n\n <NextSteps\n storageKey=\"ggnomad-activity-detail\"\n steps={[\n {\n id: 'browse',\n label: t('ggnomad.activity.next.browse', 'Explore more experiences'),\n description: t(\n 'ggnomad.activity.next.browseDesc',\n 'Return to Tourism to compare tours and activities.'\n ),\n icon: <Compass className=\"size-4\" />,\n onClick: () => navigate(joinPath(basePath, 'tourism')),\n },\n ]}\n />\n\n <div className=\"grid gap-4 lg:grid-cols-3\">\n <div className=\"lg:col-span-2 space-y-4\">\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam p-5\">\n <div className=\"mb-4 flex items-center justify-between gap-3\">\n <h2 className=\"text-lg font-medium tracking-tight text-text-primary\">\n {t('ggnomad.activity.details', 'Details')}\n </h2>\n <button\n onClick={() => setEditDialogOpen(true)}\n className=\"rounded-md p-2 text-text-secondary hover:bg-action-primary-bg hover:text-action-primary-text\"\n aria-label={t('ggnomad.activity.editListing', 'Edit listing')}\n title={t('ggnomad.activity.editListing', 'Edit listing')}\n >\n <Pencil className=\"size-4\" />\n </button>\n </div>\n <dl className=\"space-y-2 text-sm\">\n <DetailRow\n label={t('ggnomad.activity.category', 'Category')}\n value={activity.category}\n />\n <DetailRow\n label={t('ggnomad.activity.duration', 'Duration')}\n value={activity.duration}\n />\n <DetailRow\n label={t('ggnomad.activity.maxParticipants', 'Max participants')}\n value={String(activity.maxParticipants)}\n />\n <DetailRow\n label={t('ggnomad.activity.price', 'Price')}\n value={`${activity.currency} ${Number(activity.price).toFixed(2)}`}\n />\n </dl>\n </GlassCard>\n <SectionCard title={t('ggnomad.activity.safety', 'Safety information')}>\n <div className=\"flex items-start gap-2 text-sm text-text-primary\">\n <ShieldCheck\n className=\"mt-0.5 size-4 flex-shrink-0 text-status-success-text\"\n aria-hidden=\"true\"\n />\n <p>\n {activity.safetyInfo ??\n t(\n 'ggnomad.activity.defaultSafety',\n 'Standard provider safety policy applies.'\n )}\n </p>\n </div>\n </SectionCard>\n <ReviewsSection targetType=\"ACTIVITY\" targetId={activity.id ?? id} />\n </div>\n <div>\n <BookingPanel\n bookingType=\"ACTIVITY\"\n itemId={activity.id ?? id}\n itemName={activity.name}\n unitPrice={activity.price ?? 0}\n currency={activity.currency ?? 'USD'}\n />\n </div>\n </div>\n\n <ListingEditDialog\n open={editDialogOpen}\n onOpenChange={setEditDialogOpen}\n targetType=\"ACTIVITY\"\n id={activity.id ?? id}\n initial={{\n name: activity.name,\n description: activity.description,\n price: activity.price,\n status: activity.status,\n images: activity.images,\n }}\n onSaved={() => refetch()}\n />\n </>\n )}\n </PageShell>\n );\n}\n"],"mappings":"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"}