@burdenoff/microfe-ggnomad 2026.830.1 → 2026.1029.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 (78) hide show
  1. package/dist/ggnomad/GGNomadRoot.js +4 -2
  2. package/dist/ggnomad/GGNomadRoot.js.map +1 -1
  3. package/dist/ggnomad/GGNomadRoutes.js.map +1 -1
  4. package/dist/ggnomad/components/AuditTrailDialog.js.map +1 -1
  5. package/dist/ggnomad/components/AvailabilityPanel.js.map +1 -1
  6. package/dist/ggnomad/components/BookingPanel.js +6 -6
  7. package/dist/ggnomad/components/BookingPanel.js.map +1 -1
  8. package/dist/ggnomad/components/CreateProviderDialog.d.ts +22 -0
  9. package/dist/ggnomad/components/CreateProviderDialog.d.ts.map +1 -0
  10. package/dist/ggnomad/components/CreateProviderDialog.js +209 -0
  11. package/dist/ggnomad/components/CreateProviderDialog.js.map +1 -0
  12. package/dist/ggnomad/components/CreateTravelPolicyDialog.js.map +1 -1
  13. package/dist/ggnomad/components/GroupTripPanel.js.map +1 -1
  14. package/dist/ggnomad/components/ListingEditDialog.js.map +1 -1
  15. package/dist/ggnomad/components/ListingFilters.js.map +1 -1
  16. package/dist/ggnomad/components/PhotoGallery.js.map +1 -1
  17. package/dist/ggnomad/components/ProviderCard.js.map +1 -1
  18. package/dist/ggnomad/components/ProviderDashboardCard.d.ts +28 -0
  19. package/dist/ggnomad/components/ProviderDashboardCard.d.ts.map +1 -0
  20. package/dist/ggnomad/components/ProviderDashboardCard.js +222 -0
  21. package/dist/ggnomad/components/ProviderDashboardCard.js.map +1 -0
  22. package/dist/ggnomad/components/RatingSummary.js.map +1 -1
  23. package/dist/ggnomad/components/RedeemTicketPanel.js.map +1 -1
  24. package/dist/ggnomad/components/ReviewsSection.js.map +1 -1
  25. package/dist/ggnomad/components/StatusBadge.js.map +1 -1
  26. package/dist/ggnomad/components/TicketDialog.js +1 -1
  27. package/dist/ggnomad/components/TicketDialog.js.map +1 -1
  28. package/dist/ggnomad/components/index.d.ts +2 -0
  29. package/dist/ggnomad/components/index.d.ts.map +1 -1
  30. package/dist/ggnomad/components/index.js +26 -23
  31. package/dist/ggnomad/graphql.d.ts +1 -0
  32. package/dist/ggnomad/graphql.d.ts.map +1 -1
  33. package/dist/ggnomad/graphql.js +65 -47
  34. package/dist/ggnomad/graphql.js.map +1 -1
  35. package/dist/ggnomad/index.js +3 -2
  36. package/dist/ggnomad/pages/ActivityDetailsPage.js.map +1 -1
  37. package/dist/ggnomad/pages/AnalyticsPage.js.map +1 -1
  38. package/dist/ggnomad/pages/BookingsPage.js.map +1 -1
  39. package/dist/ggnomad/pages/DashboardPage.js.map +1 -1
  40. package/dist/ggnomad/pages/EntertainmentPage.js.map +1 -1
  41. package/dist/ggnomad/pages/EventDetailsPage.js.map +1 -1
  42. package/dist/ggnomad/pages/FavoritesPage.js.map +1 -1
  43. package/dist/ggnomad/pages/HospitalityPage.js.map +1 -1
  44. package/dist/ggnomad/pages/OperationsQueuePage.js.map +1 -1
  45. package/dist/ggnomad/pages/PageShell.js.map +1 -1
  46. package/dist/ggnomad/pages/PropertyDetailsPage.js.map +1 -1
  47. package/dist/ggnomad/pages/ProviderAnalyticsPage.js.map +1 -1
  48. package/dist/ggnomad/pages/ProviderPage.d.ts.map +1 -1
  49. package/dist/ggnomad/pages/ProviderPage.js +249 -134
  50. package/dist/ggnomad/pages/ProviderPage.js.map +1 -1
  51. package/dist/ggnomad/pages/SearchPage.js +1 -1
  52. package/dist/ggnomad/pages/SearchPage.js.map +1 -1
  53. package/dist/ggnomad/pages/StorefrontPage.js.map +1 -1
  54. package/dist/ggnomad/pages/StorefrontPublicPage.js.map +1 -1
  55. package/dist/ggnomad/pages/TourDetailsPage.js.map +1 -1
  56. package/dist/ggnomad/pages/TourismPage.js.map +1 -1
  57. package/dist/ggnomad/pages/TravelPage.js +1 -1
  58. package/dist/ggnomad/pages/TravelPage.js.map +1 -1
  59. package/dist/ggnomad/pages/TravelPolicyPage.js.map +1 -1
  60. package/dist/ggnomad/pages/TripPlanDetailPage.js.map +1 -1
  61. package/dist/ggnomad/pages/TripsPage.js.map +1 -1
  62. package/dist/ggnomad/providers/GGNomadProvider.js.map +1 -1
  63. package/dist/ggnomad/utils/format.d.ts +18 -0
  64. package/dist/ggnomad/utils/format.d.ts.map +1 -1
  65. package/dist/ggnomad/utils/format.js +23 -7
  66. package/dist/ggnomad/utils/format.js.map +1 -1
  67. package/dist/ggnomad/utils/notify.js +1 -1
  68. package/dist/ggnomad/utils/providerListControls.d.ts +53 -0
  69. package/dist/ggnomad/utils/providerListControls.d.ts.map +1 -0
  70. package/dist/ggnomad/utils/providerListControls.js +50 -0
  71. package/dist/ggnomad/utils/providerListControls.js.map +1 -0
  72. package/dist/ggnomad/utils/providerTypeMeta.d.ts +79 -0
  73. package/dist/ggnomad/utils/providerTypeMeta.d.ts.map +1 -0
  74. package/dist/ggnomad/utils/providerTypeMeta.js +81 -0
  75. package/dist/ggnomad/utils/providerTypeMeta.js.map +1 -0
  76. package/dist/utils/pageContext.js +1 -1
  77. package/dist/utils/pageContext.js.map +1 -1
  78. package/package.json +18 -17
@@ -1 +1 @@
1
- {"version":3,"file":"StorefrontPage.js","names":[],"sources":["../../../src/ggnomad/pages/StorefrontPage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { useParams } from 'react-router';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Globe, Palette, Store } from 'lucide-react';\nimport { Button, GlassCard, Input, PagePurpose, Spinner, Textarea } from '@burdenoff/fe-libs/ui';\n\nimport {\n CREATE_GGNOMAD_OPERATOR_STOREFRONT_MUTATION,\n GGNOMAD_OPERATOR_STOREFRONT_QUERY,\n PUBLISH_GGNOMAD_OPERATOR_STOREFRONT_MUTATION,\n UNPUBLISH_GGNOMAD_OPERATOR_STOREFRONT_MUTATION,\n UPDATE_GGNOMAD_OPERATOR_STOREFRONT_MUTATION,\n} from '../graphql';\nimport { PageShell } from './PageShell';\nimport { notify } from '../utils/notify';\n\ntype GgnomadOperatorStorefront = {\n id: string;\n providerId: string;\n slug: string;\n displayName: string;\n logoUrl: string | null;\n primaryColor: string | null;\n aboutText: string | null;\n isPublished: boolean;\n publishedAt: string | null;\n};\n\nconst HEX_COLOR_PATTERN = /^#[0-9a-fA-F]{6}$/;\nconst SLUG_PATTERN = /^[a-z0-9]([a-z0-9-]{1,78}[a-z0-9])?$/;\n\nconst fieldClass =\n 'mt-1 w-full rounded-md border border-border-seam bg-bg-canvas px-3 py-2 text-sm text-text-primary placeholder-text-secondary focus:border-action-primary-bg focus:outline-none focus:ring-1 focus:ring-action-primary-bg disabled:opacity-50';\nconst labelClass = 'block text-sm font-medium text-text-primary';\n\n/**\n * BOFF-4363 — operator storefront settings: create/edit the branding\n * (logo/color/slug/about) for one provider and publish/unpublish it. The\n * right-hand panel renders a live, themeable preview of the branded page a\n * traveler would see at `ggnomadStorefrontBySlug` once published.\n *\n * Custom-domain DNS/SSL provisioning and a drag-and-drop page builder are\n * explicitly out of scope for this pass — see Linear BOFF-4363.\n */\nexport function StorefrontPage() {\n const { t } = useI18n();\n const { providerId = '' } = useParams();\n\n const { data, loading, refetch } = useQuery(\n GGNOMAD_OPERATOR_STOREFRONT_QUERY as TypedDocumentNode<\n { ggnomadOperatorStorefront: GgnomadOperatorStorefront | null },\n { providerId: string }\n >,\n { variables: { providerId }, skip: !providerId }\n );\n const storefront = data?.ggnomadOperatorStorefront ?? null;\n\n const [slug, setSlug] = useState('');\n const [displayName, setDisplayName] = useState('');\n const [logoUrl, setLogoUrl] = useState('');\n const [primaryColor, setPrimaryColor] = useState('#1E88E5');\n const [aboutText, setAboutText] = useState('');\n const [hydrated, setHydrated] = useState(false);\n\n if (storefront && !hydrated) {\n setSlug(storefront.slug);\n setDisplayName(storefront.displayName);\n setLogoUrl(storefront.logoUrl ?? '');\n setPrimaryColor(storefront.primaryColor ?? '#1E88E5');\n setAboutText(storefront.aboutText ?? '');\n setHydrated(true);\n }\n\n const [createStorefront, { loading: creating }] = useMutation(\n CREATE_GGNOMAD_OPERATOR_STOREFRONT_MUTATION as TypedDocumentNode<\n { createGgnomadOperatorStorefront: GgnomadOperatorStorefront },\n {\n input: {\n providerId: string;\n slug: string;\n displayName: string;\n logoUrl?: string | null;\n primaryColor?: string | null;\n aboutText?: string | null;\n };\n }\n >\n );\n const [updateStorefront, { loading: updating }] = useMutation(\n UPDATE_GGNOMAD_OPERATOR_STOREFRONT_MUTATION as TypedDocumentNode<\n { updateGgnomadOperatorStorefront: GgnomadOperatorStorefront },\n {\n id: string;\n input: {\n slug?: string;\n displayName?: string;\n logoUrl?: string | null;\n primaryColor?: string | null;\n aboutText?: string | null;\n };\n }\n >\n );\n const [publishStorefront, { loading: publishing }] = useMutation(\n PUBLISH_GGNOMAD_OPERATOR_STOREFRONT_MUTATION as TypedDocumentNode<\n { publishGgnomadOperatorStorefront: GgnomadOperatorStorefront },\n { id: string }\n >\n );\n const [unpublishStorefront, { loading: unpublishing }] = useMutation(\n UNPUBLISH_GGNOMAD_OPERATOR_STOREFRONT_MUTATION as TypedDocumentNode<\n { unpublishGgnomadOperatorStorefront: GgnomadOperatorStorefront },\n { id: string }\n >\n );\n\n const handleSave = async () => {\n if (!slug.trim() || !SLUG_PATTERN.test(slug.trim())) {\n notify.warning(\n t('ggnomad.storefront.invalidSlugTitle', 'Invalid slug'),\n t(\n 'ggnomad.storefront.invalidSlugBody',\n '3-80 lowercase letters, digits, or hyphens; no leading/trailing hyphen.'\n )\n );\n return;\n }\n if (!displayName.trim()) {\n notify.warning(\n t('ggnomad.storefront.nameRequiredTitle', 'Display name is required'),\n t('ggnomad.storefront.nameRequiredBody', 'Give your storefront a public-facing name.')\n );\n return;\n }\n if (primaryColor.trim() && !HEX_COLOR_PATTERN.test(primaryColor.trim())) {\n notify.warning(\n t('ggnomad.storefront.invalidColorTitle', 'Invalid color'),\n t('ggnomad.storefront.invalidColorBody', 'Use a 6-digit hex color, e.g. #1E88E5.')\n );\n return;\n }\n\n try {\n if (storefront) {\n const result = await updateStorefront({\n variables: {\n id: storefront.id,\n input: {\n slug: slug.trim().toLowerCase(),\n displayName: displayName.trim(),\n logoUrl: logoUrl.trim() || null,\n primaryColor: primaryColor.trim() || null,\n aboutText: aboutText.trim() || null,\n },\n },\n });\n if (result.error) throw result.error;\n notify.success(t('ggnomad.storefront.savedTitle', 'Storefront updated'), displayName);\n } else {\n const result = await createStorefront({\n variables: {\n input: {\n providerId,\n slug: slug.trim().toLowerCase(),\n displayName: displayName.trim(),\n logoUrl: logoUrl.trim() || null,\n primaryColor: primaryColor.trim() || null,\n aboutText: aboutText.trim() || null,\n },\n },\n });\n if (result.error) throw result.error;\n notify.success(t('ggnomad.storefront.createdTitle', 'Storefront created'), displayName);\n }\n await refetch();\n } catch (err) {\n notify.error(\n t('ggnomad.storefront.errorTitle', 'Could not save storefront'),\n err instanceof Error ? err.message : String(err)\n );\n }\n };\n\n const handleTogglePublish = async () => {\n if (!storefront) return;\n try {\n if (storefront.isPublished) {\n await unpublishStorefront({ variables: { id: storefront.id } });\n notify.success(\n t('ggnomad.storefront.unpublishedTitle', 'Storefront unpublished'),\n t('ggnomad.storefront.unpublishedBody', 'It is no longer visible to the public.')\n );\n } else {\n await publishStorefront({ variables: { id: storefront.id } });\n notify.success(\n t('ggnomad.storefront.publishedTitle', 'Storefront published'),\n t('ggnomad.storefront.publishedBody', 'Your branded page is now publicly reachable.')\n );\n }\n await refetch();\n } catch (err) {\n notify.error(\n t('ggnomad.storefront.errorTitle', 'Could not update storefront'),\n err instanceof Error ? err.message : String(err)\n );\n }\n };\n\n const saving = creating || updating;\n const previewColor = HEX_COLOR_PATTERN.test(primaryColor.trim())\n ? primaryColor.trim()\n : '#1E88E5';\n\n if (!providerId) {\n return (\n <PageShell\n title={t('ggnomad.storefront.title', 'Operator Storefront')}\n subtitle={t('ggnomad.storefront.missingProvider', 'No provider selected.')}\n >\n <PagePurpose icon={<Store className=\"size-4\" />}>\n {t(\n 'ggnomad.storefront.missingProviderHelp',\n 'Open a provider from the Provider Dashboard and choose “Manage storefront”.'\n )}\n </PagePurpose>\n </PageShell>\n );\n }\n\n return (\n <PageShell\n title={t('ggnomad.storefront.title', 'Operator Storefront')}\n titleAccent={t('ggnomad.storefront.title.accent', 'Storefront')}\n subtitle={t(\n 'ggnomad.storefront.subtitle',\n 'White-label branding for your public listing page — logo, color, slug, and about text.'\n )}\n >\n <PagePurpose icon={<Store className=\"size-4\" />}>\n {t(\n 'ggnomad.storefront.purpose',\n 'Configure how travelers see your business when they visit your branded storefront URL. Publish when ready — draft branding stays private until then. Custom domains and a drag-and-drop builder are on the roadmap, not part of this core.'\n )}\n </PagePurpose>\n\n {loading && !hydrated ? (\n <div className=\"flex justify-center py-10\">\n <Spinner />\n </div>\n ) : (\n <div className=\"grid gap-5 lg:grid-cols-2\">\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam p-5\">\n <h2 className=\"mb-4 flex items-center gap-2 text-lg font-medium tracking-tight text-text-primary\">\n <Palette className=\"size-4\" />\n {t('ggnomad.storefront.brandingHeading', 'Branding')}\n </h2>\n <div className=\"space-y-4\">\n <div>\n <label className={labelClass} htmlFor=\"storefront-slug\">\n {t('ggnomad.storefront.slugLabel', 'Public slug')}\n </label>\n <Input\n id=\"storefront-slug\"\n className={fieldClass}\n value={slug}\n onChange={(e) => setSlug(e.target.value.toLowerCase())}\n placeholder=\"my-eco-stays\"\n disabled={saving}\n />\n <p className=\"mt-1 text-xs text-text-secondary\">\n {t(\n 'ggnomad.storefront.slugHelp',\n 'Lowercase letters, digits, hyphens — 3-80 chars.'\n )}\n </p>\n </div>\n <div>\n <label className={labelClass} htmlFor=\"storefront-name\">\n {t('ggnomad.storefront.nameLabel', 'Display name')}\n </label>\n <Input\n id=\"storefront-name\"\n className={fieldClass}\n value={displayName}\n onChange={(e) => setDisplayName(e.target.value)}\n placeholder={t('ggnomad.storefront.namePlaceholder', 'Sunset Eco Stays')}\n disabled={saving}\n />\n </div>\n <div>\n <label className={labelClass} htmlFor=\"storefront-logo\">\n {t('ggnomad.storefront.logoLabel', 'Logo URL')}\n </label>\n <Input\n id=\"storefront-logo\"\n className={fieldClass}\n value={logoUrl}\n onChange={(e) => setLogoUrl(e.target.value)}\n placeholder=\"https://example.com/logo.png\"\n disabled={saving}\n />\n </div>\n <div>\n <label className={labelClass} htmlFor=\"storefront-color\">\n {t('ggnomad.storefront.colorLabel', 'Primary color')}\n </label>\n <div className=\"mt-1 flex items-center gap-2\">\n <input\n id=\"storefront-color\"\n type=\"color\"\n className=\"h-9 w-12 rounded border border-border-seam bg-bg-canvas\"\n value={HEX_COLOR_PATTERN.test(primaryColor) ? primaryColor : '#1E88E5'}\n onChange={(e) => setPrimaryColor(e.target.value)}\n disabled={saving}\n />\n <Input\n className={fieldClass}\n value={primaryColor}\n onChange={(e) => setPrimaryColor(e.target.value)}\n placeholder=\"#1E88E5\"\n disabled={saving}\n />\n </div>\n </div>\n <div>\n <label className={labelClass} htmlFor=\"storefront-about\">\n {t('ggnomad.storefront.aboutLabel', 'About text')}\n </label>\n <Textarea\n id=\"storefront-about\"\n className={fieldClass}\n rows={4}\n value={aboutText}\n onChange={(e) => setAboutText(e.target.value)}\n placeholder={t(\n 'ggnomad.storefront.aboutPlaceholder',\n 'Tell travelers what makes your business worth booking.'\n )}\n disabled={saving}\n />\n </div>\n\n <div className=\"flex flex-wrap items-center gap-3 pt-2\">\n <Button onClick={handleSave} disabled={saving}>\n {storefront\n ? t('ggnomad.storefront.saveButton', 'Save changes')\n : t('ggnomad.storefront.createButton', 'Create storefront')}\n </Button>\n {storefront ? (\n <Button\n variant=\"secondary\"\n onClick={handleTogglePublish}\n disabled={publishing || unpublishing}\n >\n {storefront.isPublished\n ? t('ggnomad.storefront.unpublishButton', 'Unpublish')\n : t('ggnomad.storefront.publishButton', 'Publish')}\n </Button>\n ) : null}\n {storefront?.isPublished ? (\n <span className=\"inline-flex items-center gap-1 text-xs text-text-secondary\">\n <Globe className=\"size-3.5\" />\n {t('ggnomad.storefront.livePathLabel', 'Live at /store/{slug}').replace(\n '{slug}',\n storefront.slug\n )}\n </span>\n ) : null}\n </div>\n </div>\n </GlassCard>\n\n {/* Live preview — the same visual shape ggnomadStorefrontBySlug renders publicly. */}\n <div\n className=\"overflow-hidden rounded-lg border border-border-seam\"\n style={{ borderTopColor: previewColor, borderTopWidth: 4 }}\n >\n <div className=\"p-5\" style={{ background: `${previewColor}14` }}>\n <div className=\"flex items-center gap-3\">\n {logoUrl ? (\n <img\n src={logoUrl}\n alt=\"\"\n className=\"size-12 rounded object-contain\"\n onError={(e) => {\n (e.currentTarget as HTMLImageElement).style.display = 'none';\n }}\n />\n ) : (\n <div\n className=\"flex size-12 items-center justify-center rounded text-lg font-semibold text-white\"\n style={{ background: previewColor }}\n >\n {(displayName || 'S').slice(0, 1).toUpperCase()}\n </div>\n )}\n <div>\n <h3 className=\"text-lg font-semibold text-text-primary\">\n {displayName ||\n t('ggnomad.storefront.previewPlaceholderName', 'Your Storefront')}\n </h3>\n <p className=\"text-xs text-text-secondary\">\n {slug\n ? `/store/${slug}`\n : t('ggnomad.storefront.previewSlugPlaceholder', '/store/…')}\n </p>\n </div>\n </div>\n {aboutText ? (\n <p className=\"mt-4 text-sm text-text-primary\">{aboutText}</p>\n ) : (\n <p className=\"mt-4 text-sm text-text-secondary\">\n {t(\n 'ggnomad.storefront.previewNoAbout',\n 'Add an about text to introduce your business.'\n )}\n </p>\n )}\n <div\n className=\"mt-4 inline-block rounded px-3 py-1.5 text-xs font-medium text-white\"\n style={{ background: previewColor }}\n >\n {t('ggnomad.storefront.previewCta', 'Book with us')}\n </div>\n </div>\n <div className=\"border-t border-border-seam bg-bg-surface p-4 text-xs text-text-secondary\">\n {t(\n 'ggnomad.storefront.previewFooter',\n 'ACTIVE properties, tours, events, and activities from this provider render below this banner on the public page.'\n )}\n </div>\n </div>\n </div>\n )}\n </PageShell>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"StorefrontPage.js","names":[],"sources":["../../../src/ggnomad/pages/StorefrontPage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { useParams } from 'react-router';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Globe, Palette, Store } from 'lucide-react';\nimport { Button, GlassCard, Input, PagePurpose, Spinner, Textarea } from '@burdenoff/fe-libs/ui';\n\nimport {\n CREATE_GGNOMAD_OPERATOR_STOREFRONT_MUTATION,\n GGNOMAD_OPERATOR_STOREFRONT_QUERY,\n PUBLISH_GGNOMAD_OPERATOR_STOREFRONT_MUTATION,\n UNPUBLISH_GGNOMAD_OPERATOR_STOREFRONT_MUTATION,\n UPDATE_GGNOMAD_OPERATOR_STOREFRONT_MUTATION,\n} from '../graphql';\nimport { PageShell } from './PageShell';\nimport { notify } from '../utils/notify';\n\ntype GgnomadOperatorStorefront = {\n id: string;\n providerId: string;\n slug: string;\n displayName: string;\n logoUrl: string | null;\n primaryColor: string | null;\n aboutText: string | null;\n isPublished: boolean;\n publishedAt: string | null;\n};\n\nconst HEX_COLOR_PATTERN = /^#[0-9a-fA-F]{6}$/;\nconst SLUG_PATTERN = /^[a-z0-9]([a-z0-9-]{1,78}[a-z0-9])?$/;\n\nconst fieldClass =\n 'mt-1 w-full rounded-md border border-border-seam bg-bg-canvas px-3 py-2 text-sm text-text-primary placeholder-text-secondary focus:border-action-primary-bg focus:outline-none focus:ring-1 focus:ring-action-primary-bg disabled:opacity-50';\nconst labelClass = 'block text-sm font-medium text-text-primary';\n\n/**\n * BOFF-4363 — operator storefront settings: create/edit the branding\n * (logo/color/slug/about) for one provider and publish/unpublish it. The\n * right-hand panel renders a live, themeable preview of the branded page a\n * traveler would see at `ggnomadStorefrontBySlug` once published.\n *\n * Custom-domain DNS/SSL provisioning and a drag-and-drop page builder are\n * explicitly out of scope for this pass — see Linear BOFF-4363.\n */\nexport function StorefrontPage() {\n const { t } = useI18n();\n const { providerId = '' } = useParams();\n\n const { data, loading, refetch } = useQuery(\n GGNOMAD_OPERATOR_STOREFRONT_QUERY as TypedDocumentNode<\n { ggnomadOperatorStorefront: GgnomadOperatorStorefront | null },\n { providerId: string }\n >,\n { variables: { providerId }, skip: !providerId }\n );\n const storefront = data?.ggnomadOperatorStorefront ?? null;\n\n const [slug, setSlug] = useState('');\n const [displayName, setDisplayName] = useState('');\n const [logoUrl, setLogoUrl] = useState('');\n const [primaryColor, setPrimaryColor] = useState('#1E88E5');\n const [aboutText, setAboutText] = useState('');\n const [hydrated, setHydrated] = useState(false);\n\n if (storefront && !hydrated) {\n setSlug(storefront.slug);\n setDisplayName(storefront.displayName);\n setLogoUrl(storefront.logoUrl ?? '');\n setPrimaryColor(storefront.primaryColor ?? '#1E88E5');\n setAboutText(storefront.aboutText ?? '');\n setHydrated(true);\n }\n\n const [createStorefront, { loading: creating }] = useMutation(\n CREATE_GGNOMAD_OPERATOR_STOREFRONT_MUTATION as TypedDocumentNode<\n { createGgnomadOperatorStorefront: GgnomadOperatorStorefront },\n {\n input: {\n providerId: string;\n slug: string;\n displayName: string;\n logoUrl?: string | null;\n primaryColor?: string | null;\n aboutText?: string | null;\n };\n }\n >\n );\n const [updateStorefront, { loading: updating }] = useMutation(\n UPDATE_GGNOMAD_OPERATOR_STOREFRONT_MUTATION as TypedDocumentNode<\n { updateGgnomadOperatorStorefront: GgnomadOperatorStorefront },\n {\n id: string;\n input: {\n slug?: string;\n displayName?: string;\n logoUrl?: string | null;\n primaryColor?: string | null;\n aboutText?: string | null;\n };\n }\n >\n );\n const [publishStorefront, { loading: publishing }] = useMutation(\n PUBLISH_GGNOMAD_OPERATOR_STOREFRONT_MUTATION as TypedDocumentNode<\n { publishGgnomadOperatorStorefront: GgnomadOperatorStorefront },\n { id: string }\n >\n );\n const [unpublishStorefront, { loading: unpublishing }] = useMutation(\n UNPUBLISH_GGNOMAD_OPERATOR_STOREFRONT_MUTATION as TypedDocumentNode<\n { unpublishGgnomadOperatorStorefront: GgnomadOperatorStorefront },\n { id: string }\n >\n );\n\n const handleSave = async () => {\n if (!slug.trim() || !SLUG_PATTERN.test(slug.trim())) {\n notify.warning(\n t('ggnomad.storefront.invalidSlugTitle', 'Invalid slug'),\n t(\n 'ggnomad.storefront.invalidSlugBody',\n '3-80 lowercase letters, digits, or hyphens; no leading/trailing hyphen.'\n )\n );\n return;\n }\n if (!displayName.trim()) {\n notify.warning(\n t('ggnomad.storefront.nameRequiredTitle', 'Display name is required'),\n t('ggnomad.storefront.nameRequiredBody', 'Give your storefront a public-facing name.')\n );\n return;\n }\n if (primaryColor.trim() && !HEX_COLOR_PATTERN.test(primaryColor.trim())) {\n notify.warning(\n t('ggnomad.storefront.invalidColorTitle', 'Invalid color'),\n t('ggnomad.storefront.invalidColorBody', 'Use a 6-digit hex color, e.g. #1E88E5.')\n );\n return;\n }\n\n try {\n if (storefront) {\n const result = await updateStorefront({\n variables: {\n id: storefront.id,\n input: {\n slug: slug.trim().toLowerCase(),\n displayName: displayName.trim(),\n logoUrl: logoUrl.trim() || null,\n primaryColor: primaryColor.trim() || null,\n aboutText: aboutText.trim() || null,\n },\n },\n });\n if (result.error) throw result.error;\n notify.success(t('ggnomad.storefront.savedTitle', 'Storefront updated'), displayName);\n } else {\n const result = await createStorefront({\n variables: {\n input: {\n providerId,\n slug: slug.trim().toLowerCase(),\n displayName: displayName.trim(),\n logoUrl: logoUrl.trim() || null,\n primaryColor: primaryColor.trim() || null,\n aboutText: aboutText.trim() || null,\n },\n },\n });\n if (result.error) throw result.error;\n notify.success(t('ggnomad.storefront.createdTitle', 'Storefront created'), displayName);\n }\n await refetch();\n } catch (err) {\n notify.error(\n t('ggnomad.storefront.errorTitle', 'Could not save storefront'),\n err instanceof Error ? err.message : String(err)\n );\n }\n };\n\n const handleTogglePublish = async () => {\n if (!storefront) return;\n try {\n if (storefront.isPublished) {\n await unpublishStorefront({ variables: { id: storefront.id } });\n notify.success(\n t('ggnomad.storefront.unpublishedTitle', 'Storefront unpublished'),\n t('ggnomad.storefront.unpublishedBody', 'It is no longer visible to the public.')\n );\n } else {\n await publishStorefront({ variables: { id: storefront.id } });\n notify.success(\n t('ggnomad.storefront.publishedTitle', 'Storefront published'),\n t('ggnomad.storefront.publishedBody', 'Your branded page is now publicly reachable.')\n );\n }\n await refetch();\n } catch (err) {\n notify.error(\n t('ggnomad.storefront.errorTitle', 'Could not update storefront'),\n err instanceof Error ? err.message : String(err)\n );\n }\n };\n\n const saving = creating || updating;\n const previewColor = HEX_COLOR_PATTERN.test(primaryColor.trim())\n ? primaryColor.trim()\n : '#1E88E5';\n\n if (!providerId) {\n return (\n <PageShell\n title={t('ggnomad.storefront.title', 'Operator Storefront')}\n subtitle={t('ggnomad.storefront.missingProvider', 'No provider selected.')}\n >\n <PagePurpose icon={<Store className=\"size-4\" />}>\n {t(\n 'ggnomad.storefront.missingProviderHelp',\n 'Open a provider from the Provider Dashboard and choose “Manage storefront”.'\n )}\n </PagePurpose>\n </PageShell>\n );\n }\n\n return (\n <PageShell\n title={t('ggnomad.storefront.title', 'Operator Storefront')}\n titleAccent={t('ggnomad.storefront.title.accent', 'Storefront')}\n subtitle={t(\n 'ggnomad.storefront.subtitle',\n 'White-label branding for your public listing page — logo, color, slug, and about text.'\n )}\n >\n <PagePurpose icon={<Store className=\"size-4\" />}>\n {t(\n 'ggnomad.storefront.purpose',\n 'Configure how travelers see your business when they visit your branded storefront URL. Publish when ready — draft branding stays private until then. Custom domains and a drag-and-drop builder are on the roadmap, not part of this core.'\n )}\n </PagePurpose>\n\n {loading && !hydrated ? (\n <div className=\"flex justify-center py-10\">\n <Spinner />\n </div>\n ) : (\n <div className=\"grid gap-5 lg:grid-cols-2\">\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam p-5\">\n <h2 className=\"mb-4 flex items-center gap-2 text-lg font-medium tracking-tight text-text-primary\">\n <Palette className=\"size-4\" />\n {t('ggnomad.storefront.brandingHeading', 'Branding')}\n </h2>\n <div className=\"space-y-4\">\n <div>\n <label className={labelClass} htmlFor=\"storefront-slug\">\n {t('ggnomad.storefront.slugLabel', 'Public slug')}\n </label>\n <Input\n id=\"storefront-slug\"\n className={fieldClass}\n value={slug}\n onChange={(e) => setSlug(e.target.value.toLowerCase())}\n placeholder=\"my-eco-stays\"\n disabled={saving}\n />\n <p className=\"mt-1 text-xs text-text-secondary\">\n {t(\n 'ggnomad.storefront.slugHelp',\n 'Lowercase letters, digits, hyphens — 3-80 chars.'\n )}\n </p>\n </div>\n <div>\n <label className={labelClass} htmlFor=\"storefront-name\">\n {t('ggnomad.storefront.nameLabel', 'Display name')}\n </label>\n <Input\n id=\"storefront-name\"\n className={fieldClass}\n value={displayName}\n onChange={(e) => setDisplayName(e.target.value)}\n placeholder={t('ggnomad.storefront.namePlaceholder', 'Sunset Eco Stays')}\n disabled={saving}\n />\n </div>\n <div>\n <label className={labelClass} htmlFor=\"storefront-logo\">\n {t('ggnomad.storefront.logoLabel', 'Logo URL')}\n </label>\n <Input\n id=\"storefront-logo\"\n className={fieldClass}\n value={logoUrl}\n onChange={(e) => setLogoUrl(e.target.value)}\n placeholder=\"https://example.com/logo.png\"\n disabled={saving}\n />\n </div>\n <div>\n <label className={labelClass} htmlFor=\"storefront-color\">\n {t('ggnomad.storefront.colorLabel', 'Primary color')}\n </label>\n <div className=\"mt-1 flex items-center gap-2\">\n <input\n id=\"storefront-color\"\n type=\"color\"\n className=\"h-9 w-12 rounded border border-border-seam bg-bg-canvas\"\n value={HEX_COLOR_PATTERN.test(primaryColor) ? primaryColor : '#1E88E5'}\n onChange={(e) => setPrimaryColor(e.target.value)}\n disabled={saving}\n />\n <Input\n className={fieldClass}\n value={primaryColor}\n onChange={(e) => setPrimaryColor(e.target.value)}\n placeholder=\"#1E88E5\"\n disabled={saving}\n />\n </div>\n </div>\n <div>\n <label className={labelClass} htmlFor=\"storefront-about\">\n {t('ggnomad.storefront.aboutLabel', 'About text')}\n </label>\n <Textarea\n id=\"storefront-about\"\n className={fieldClass}\n rows={4}\n value={aboutText}\n onChange={(e) => setAboutText(e.target.value)}\n placeholder={t(\n 'ggnomad.storefront.aboutPlaceholder',\n 'Tell travelers what makes your business worth booking.'\n )}\n disabled={saving}\n />\n </div>\n\n <div className=\"flex flex-wrap items-center gap-3 pt-2\">\n <Button onClick={handleSave} disabled={saving}>\n {storefront\n ? t('ggnomad.storefront.saveButton', 'Save changes')\n : t('ggnomad.storefront.createButton', 'Create storefront')}\n </Button>\n {storefront ? (\n <Button\n variant=\"secondary\"\n onClick={handleTogglePublish}\n disabled={publishing || unpublishing}\n >\n {storefront.isPublished\n ? t('ggnomad.storefront.unpublishButton', 'Unpublish')\n : t('ggnomad.storefront.publishButton', 'Publish')}\n </Button>\n ) : null}\n {storefront?.isPublished ? (\n <span className=\"inline-flex items-center gap-1 text-xs text-text-secondary\">\n <Globe className=\"size-3.5\" />\n {t('ggnomad.storefront.livePathLabel', 'Live at /store/{slug}').replace(\n '{slug}',\n storefront.slug\n )}\n </span>\n ) : null}\n </div>\n </div>\n </GlassCard>\n\n {/* Live preview — the same visual shape ggnomadStorefrontBySlug renders publicly. */}\n <div\n className=\"overflow-hidden rounded-lg border border-border-seam\"\n style={{ borderTopColor: previewColor, borderTopWidth: 4 }}\n >\n <div className=\"p-5\" style={{ background: `${previewColor}14` }}>\n <div className=\"flex items-center gap-3\">\n {logoUrl ? (\n <img\n src={logoUrl}\n alt=\"\"\n className=\"size-12 rounded object-contain\"\n onError={(e) => {\n (e.currentTarget as HTMLImageElement).style.display = 'none';\n }}\n />\n ) : (\n <div\n className=\"flex size-12 items-center justify-center rounded text-lg font-semibold text-white\"\n style={{ background: previewColor }}\n >\n {(displayName || 'S').slice(0, 1).toUpperCase()}\n </div>\n )}\n <div>\n <h3 className=\"text-lg font-semibold text-text-primary\">\n {displayName ||\n t('ggnomad.storefront.previewPlaceholderName', 'Your Storefront')}\n </h3>\n <p className=\"text-xs text-text-secondary\">\n {slug\n ? `/store/${slug}`\n : t('ggnomad.storefront.previewSlugPlaceholder', '/store/…')}\n </p>\n </div>\n </div>\n {aboutText ? (\n <p className=\"mt-4 text-sm text-text-primary\">{aboutText}</p>\n ) : (\n <p className=\"mt-4 text-sm text-text-secondary\">\n {t(\n 'ggnomad.storefront.previewNoAbout',\n 'Add an about text to introduce your business.'\n )}\n </p>\n )}\n <div\n className=\"mt-4 inline-block rounded px-3 py-1.5 text-xs font-medium text-white\"\n style={{ background: previewColor }}\n >\n {t('ggnomad.storefront.previewCta', 'Book with us')}\n </div>\n </div>\n <div className=\"border-t border-border-seam bg-bg-surface p-4 text-xs text-text-secondary\">\n {t(\n 'ggnomad.storefront.previewFooter',\n 'ACTIVE properties, tours, events, and activities from this provider render below this banner on the public page.'\n )}\n </div>\n </div>\n </div>\n )}\n </PageShell>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"StorefrontPublicPage.js","names":[],"sources":["../../../src/ggnomad/pages/StorefrontPublicPage.tsx"],"sourcesContent":["import { useQuery } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { useParams } from 'react-router';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Leaf } from 'lucide-react';\nimport { GlassCard, IllustratedEmptyState, Spinner } from '@burdenoff/fe-libs/ui';\n\nimport { GGNOMAD_STOREFRONT_BY_SLUG_QUERY } from '../graphql';\nimport { PageShell } from './PageShell';\nimport { useGGNomad } from '../providers/GGNomadProvider';\n\ntype StorefrontListing = {\n id: string;\n name: string;\n price?: number;\n pricePerNight?: number;\n currency: string;\n images: string[];\n rating?: number | null;\n sustainabilityScore: number;\n};\n\ntype StorefrontPage = {\n storefront: {\n id: string;\n displayName: string;\n logoUrl: string | null;\n primaryColor: string | null;\n aboutText: string | null;\n publishedAt: string | null;\n };\n properties: StorefrontListing[];\n tours: StorefrontListing[];\n events: StorefrontListing[];\n activities: StorefrontListing[];\n};\n\n/**\n * BOFF-4363 — the branded storefront page a traveler sees, backed by the\n * genuinely public `ggnomadStorefrontBySlug` query. Note: this route still\n * renders inside the authenticated app shell today — making it reachable\n * without any login at all is a routing/CDN concern for the app shell, not\n * this MFE (tracked alongside the deferred custom-domain work in BOFF-4363).\n */\nexport function StorefrontPublicPage() {\n const { t } = useI18n();\n const { slug = '' } = useParams();\n const { workspaceId = '' } = useGGNomad();\n\n const { data, loading } = useQuery(\n GGNOMAD_STOREFRONT_BY_SLUG_QUERY as TypedDocumentNode<\n { ggnomadStorefrontBySlug: StorefrontPage | null },\n { workspaceId: string; slug: string }\n >,\n { variables: { workspaceId, slug }, skip: !workspaceId || !slug }\n );\n\n const page = data?.ggnomadStorefrontBySlug ?? null;\n const brandColor = page?.storefront.primaryColor ?? '#1E88E5';\n const allListings = page\n ? [...page.properties, ...page.tours, ...page.events, ...page.activities]\n : [];\n\n if (loading) {\n return (\n <PageShell title={t('ggnomad.storefrontPublic.title', 'Storefront')} subtitle=\"\">\n <div className=\"flex justify-center py-10\">\n <Spinner />\n </div>\n </PageShell>\n );\n }\n\n if (!page) {\n return (\n <PageShell\n title={t('ggnomad.storefrontPublic.title', 'Storefront')}\n subtitle={t(\n 'ggnomad.storefrontPublic.notFoundSubtitle',\n 'This storefront is not available.'\n )}\n >\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title={t('ggnomad.storefrontPublic.notFoundTitle', 'Storefront not found')}\n description={t(\n 'ggnomad.storefrontPublic.notFoundDescription',\n 'This link may be mistyped, or the operator has not published a storefront yet.'\n )}\n />\n </PageShell>\n );\n }\n\n return (\n <div>\n <div\n className=\"p-6\"\n style={{ background: `${brandColor}14`, borderBottom: `4px solid ${brandColor}` }}\n >\n <div className=\"mx-auto flex max-w-4xl items-center gap-4\">\n {page.storefront.logoUrl ? (\n <img src={page.storefront.logoUrl} alt=\"\" className=\"size-16 rounded object-contain\" />\n ) : (\n <div\n className=\"flex size-16 items-center justify-center rounded text-2xl font-semibold text-white\"\n style={{ background: brandColor }}\n >\n {page.storefront.displayName.slice(0, 1).toUpperCase()}\n </div>\n )}\n <div>\n <h1 className=\"text-2xl font-semibold text-text-primary\">\n {page.storefront.displayName}\n </h1>\n {page.storefront.aboutText ? (\n <p className=\"mt-1 max-w-2xl text-sm text-text-secondary\">\n {page.storefront.aboutText}\n </p>\n ) : null}\n </div>\n </div>\n </div>\n\n <div className=\"mx-auto max-w-4xl p-6\">\n {allListings.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title={t('ggnomad.storefrontPublic.emptyTitle', 'Nothing listed yet')}\n description={t(\n 'ggnomad.storefrontPublic.emptyDescription',\n 'This operator has not published any active listings.'\n )}\n />\n ) : (\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {allListings.map((listing) => (\n <GlassCard key={listing.id} treatment=\"glass\" className=\"border-border-seam p-4\">\n <h3 className=\"font-medium text-text-primary\">{listing.name}</h3>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {(listing.price ?? listing.pricePerNight ?? 0).toLocaleString(undefined, {\n style: 'currency',\n currency: listing.currency,\n })}\n </p>\n {listing.sustainabilityScore > 0 ? (\n <p className=\"mt-2 inline-flex items-center gap-1 text-xs text-text-secondary\">\n <Leaf className=\"size-3.5 text-emerald-500\" />\n {t(\n 'ggnomad.storefrontPublic.sustainabilityScore',\n '{score}% self-reported sustainable'\n ).replace('{score}', String(listing.sustainabilityScore))}\n </p>\n ) : null}\n </GlassCard>\n ))}\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;AA4CA,SAAgB,IAAuB;CACrC,IAAM,EAAE,SAAM,EAAQ,GAChB,EAAE,UAAO,OAAO,EAAU,GAC1B,EAAE,iBAAc,OAAO,EAAW,GAElC,EAAE,SAAM,eAAY,EACxB,GAIA;EAAE,WAAW;GAAE;GAAa;EAAK;EAAG,MAAM,CAAC,KAAe,CAAC;CAAK,CAClE,GAEM,IAAO,GAAM,2BAA2B,MACxC,IAAa,GAAM,WAAW,gBAAgB,WAC9C,IAAc,IAChB;EAAC,GAAG,EAAK;EAAY,GAAG,EAAK;EAAO,GAAG,EAAK;EAAQ,GAAG,EAAK;CAAU,IACtE,CAAC;CAiCL,OA/BI,IAEA,kBAAC,GAAD;EAAW,OAAO,EAAE,kCAAkC,YAAY;EAAG,UAAS;YAC5E,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,GAAD,CAAU,CAAA;EACP,CAAA;CACI,CAAA,IAIV,IAsBH,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,OAAD;EACE,WAAU;EACV,OAAO;GAAE,YAAY,GAAG,EAAW;GAAK,cAAc,aAAa;EAAa;YAEhF,kBAAC,OAAD;GAAK,WAAU;aAAf,CACG,EAAK,WAAW,UACf,kBAAC,OAAD;IAAK,KAAK,EAAK,WAAW;IAAS,KAAI;IAAG,WAAU;GAAkC,CAAA,IAEtF,kBAAC,OAAD;IACE,WAAU;IACV,OAAO,EAAE,YAAY,EAAW;cAE/B,EAAK,WAAW,YAAY,MAAM,GAAG,CAAC,EAAE,YAAY;GAClD,CAAA,GAEP,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;IAAI,WAAU;cACX,EAAK,WAAW;GACf,CAAA,GACH,EAAK,WAAW,YACf,kBAAC,KAAD;IAAG,WAAU;cACV,EAAK,WAAW;GAChB,CAAA,IACD,IACD,EAAA,CAAA,CACF;;CACF,CAAA,GAEL,kBAAC,OAAD;EAAK,WAAU;YACZ,EAAY,WAAW,IACtB,kBAAC,GAAD;GACE,cAAa;GACb,OAAO,EAAE,uCAAuC,oBAAoB;GACpE,aAAa,EACX,6CACA,sDACF;EACD,CAAA,IAED,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAY,KAAK,MAChB,kBAAC,GAAD;IAA4B,WAAU;IAAQ,WAAU;cAAxD;KACE,kBAAC,MAAD;MAAI,WAAU;gBAAiC,EAAQ;KAAS,CAAA;KAChE,kBAAC,KAAD;MAAG,WAAU;iBACT,EAAQ,SAAS,EAAQ,iBAAiB,GAAG,eAAe,KAAA,GAAW;OACvE,OAAO;OACP,UAAU,EAAQ;MACpB,CAAC;KACA,CAAA;KACF,EAAQ,sBAAsB,IAC7B,kBAAC,KAAD;MAAG,WAAU;gBAAb,CACE,kBAAC,GAAD,EAAM,WAAU,4BAA6B,CAAA,GAC5C,EACC,gDACA,oCACF,EAAE,QAAQ,WAAW,OAAO,EAAQ,mBAAmB,CAAC,CACvD;UACD;IACK;MAjBK,EAAQ,EAiBb,CACZ;EACE,CAAA;CAEJ,CAAA,CACF,EAAA,CAAA,IApFH,kBAAC,GAAD;EACE,OAAO,EAAE,kCAAkC,YAAY;EACvD,UAAU,EACR,6CACA,mCACF;YAEA,kBAAC,GAAD;GACE,cAAa;GACb,OAAO,EAAE,0CAA0C,sBAAsB;GACzE,aAAa,EACX,gDACA,gFACF;EACD,CAAA;CACQ,CAAA;AAuEjB"}
1
+ {"version":3,"file":"StorefrontPublicPage.js","names":[],"sources":["../../../src/ggnomad/pages/StorefrontPublicPage.tsx"],"sourcesContent":["import { useQuery } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { useParams } from 'react-router';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Leaf } from 'lucide-react';\nimport { GlassCard, IllustratedEmptyState, Spinner } from '@burdenoff/fe-libs/ui';\n\nimport { GGNOMAD_STOREFRONT_BY_SLUG_QUERY } from '../graphql';\nimport { PageShell } from './PageShell';\nimport { useGGNomad } from '../providers/GGNomadProvider';\n\ntype StorefrontListing = {\n id: string;\n name: string;\n price?: number;\n pricePerNight?: number;\n currency: string;\n images: string[];\n rating?: number | null;\n sustainabilityScore: number;\n};\n\ntype StorefrontPage = {\n storefront: {\n id: string;\n displayName: string;\n logoUrl: string | null;\n primaryColor: string | null;\n aboutText: string | null;\n publishedAt: string | null;\n };\n properties: StorefrontListing[];\n tours: StorefrontListing[];\n events: StorefrontListing[];\n activities: StorefrontListing[];\n};\n\n/**\n * BOFF-4363 — the branded storefront page a traveler sees, backed by the\n * genuinely public `ggnomadStorefrontBySlug` query. Note: this route still\n * renders inside the authenticated app shell today — making it reachable\n * without any login at all is a routing/CDN concern for the app shell, not\n * this MFE (tracked alongside the deferred custom-domain work in BOFF-4363).\n */\nexport function StorefrontPublicPage() {\n const { t } = useI18n();\n const { slug = '' } = useParams();\n const { workspaceId = '' } = useGGNomad();\n\n const { data, loading } = useQuery(\n GGNOMAD_STOREFRONT_BY_SLUG_QUERY as TypedDocumentNode<\n { ggnomadStorefrontBySlug: StorefrontPage | null },\n { workspaceId: string; slug: string }\n >,\n { variables: { workspaceId, slug }, skip: !workspaceId || !slug }\n );\n\n const page = data?.ggnomadStorefrontBySlug ?? null;\n const brandColor = page?.storefront.primaryColor ?? '#1E88E5';\n const allListings = page\n ? [...page.properties, ...page.tours, ...page.events, ...page.activities]\n : [];\n\n if (loading) {\n return (\n <PageShell title={t('ggnomad.storefrontPublic.title', 'Storefront')} subtitle=\"\">\n <div className=\"flex justify-center py-10\">\n <Spinner />\n </div>\n </PageShell>\n );\n }\n\n if (!page) {\n return (\n <PageShell\n title={t('ggnomad.storefrontPublic.title', 'Storefront')}\n subtitle={t(\n 'ggnomad.storefrontPublic.notFoundSubtitle',\n 'This storefront is not available.'\n )}\n >\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title={t('ggnomad.storefrontPublic.notFoundTitle', 'Storefront not found')}\n description={t(\n 'ggnomad.storefrontPublic.notFoundDescription',\n 'This link may be mistyped, or the operator has not published a storefront yet.'\n )}\n />\n </PageShell>\n );\n }\n\n return (\n <div>\n <div\n className=\"p-6\"\n style={{ background: `${brandColor}14`, borderBottom: `4px solid ${brandColor}` }}\n >\n <div className=\"mx-auto flex max-w-4xl items-center gap-4\">\n {page.storefront.logoUrl ? (\n <img src={page.storefront.logoUrl} alt=\"\" className=\"size-16 rounded object-contain\" />\n ) : (\n <div\n className=\"flex size-16 items-center justify-center rounded text-2xl font-semibold text-white\"\n style={{ background: brandColor }}\n >\n {page.storefront.displayName.slice(0, 1).toUpperCase()}\n </div>\n )}\n <div>\n <h1 className=\"text-2xl font-semibold text-text-primary\">\n {page.storefront.displayName}\n </h1>\n {page.storefront.aboutText ? (\n <p className=\"mt-1 max-w-2xl text-sm text-text-secondary\">\n {page.storefront.aboutText}\n </p>\n ) : null}\n </div>\n </div>\n </div>\n\n <div className=\"mx-auto max-w-4xl p-6\">\n {allListings.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title={t('ggnomad.storefrontPublic.emptyTitle', 'Nothing listed yet')}\n description={t(\n 'ggnomad.storefrontPublic.emptyDescription',\n 'This operator has not published any active listings.'\n )}\n />\n ) : (\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {allListings.map((listing) => (\n <GlassCard key={listing.id} treatment=\"glass\" className=\"border-border-seam p-4\">\n <h3 className=\"font-medium text-text-primary\">{listing.name}</h3>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {(listing.price ?? listing.pricePerNight ?? 0).toLocaleString(undefined, {\n style: 'currency',\n currency: listing.currency,\n })}\n </p>\n {listing.sustainabilityScore > 0 ? (\n <p className=\"mt-2 inline-flex items-center gap-1 text-xs text-text-secondary\">\n <Leaf className=\"size-3.5 text-emerald-500\" />\n {t(\n 'ggnomad.storefrontPublic.sustainabilityScore',\n '{score}% self-reported sustainable'\n ).replace('{score}', String(listing.sustainabilityScore))}\n </p>\n ) : null}\n </GlassCard>\n ))}\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;AA4CA,SAAgB,IAAuB;CACrC,IAAM,EAAE,SAAM,EAAQ,GAChB,EAAE,UAAO,OAAO,EAAU,GAC1B,EAAE,iBAAc,OAAO,EAAW,GAElC,EAAE,SAAM,eAAY,EACxB,GAIA;EAAE,WAAW;GAAE;GAAa;EAAK;EAAG,MAAM,CAAC,KAAe,CAAC;CAAK,CAClE,GAEM,IAAO,GAAM,2BAA2B,MACxC,IAAa,GAAM,WAAW,gBAAgB,WAC9C,IAAc,IAChB;EAAC,GAAG,EAAK;EAAY,GAAG,EAAK;EAAO,GAAG,EAAK;EAAQ,GAAG,EAAK;CAAU,IACtE,CAAC;CAiCL,OA/BI,IAEA,kBAAC,GAAD;EAAW,OAAO,EAAE,kCAAkC,YAAY;EAAG,UAAS;YAC5E,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,GAAD,CAAU,CAAA;EACP,CAAA;CACI,CAAA,IAIV,IAsBH,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,OAAD;EACE,WAAU;EACV,OAAO;GAAE,YAAY,GAAG,EAAW;GAAK,cAAc,aAAa;EAAa;YAEhF,kBAAC,OAAD;GAAK,WAAU;aAAf,CACG,EAAK,WAAW,UACf,kBAAC,OAAD;IAAK,KAAK,EAAK,WAAW;IAAS,KAAI;IAAG,WAAU;GAAkC,CAAA,IAEtF,kBAAC,OAAD;IACE,WAAU;IACV,OAAO,EAAE,YAAY,EAAW;cAE/B,EAAK,WAAW,YAAY,MAAM,GAAG,CAAC,CAAC,CAAC,YAAY;GAClD,CAAA,GAEP,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;IAAI,WAAU;cACX,EAAK,WAAW;GACf,CAAA,GACH,EAAK,WAAW,YACf,kBAAC,KAAD;IAAG,WAAU;cACV,EAAK,WAAW;GAChB,CAAA,IACD,IACD,EAAA,CAAA,CACF;;CACF,CAAA,GAEL,kBAAC,OAAD;EAAK,WAAU;YACZ,EAAY,WAAW,IACtB,kBAAC,GAAD;GACE,cAAa;GACb,OAAO,EAAE,uCAAuC,oBAAoB;GACpE,aAAa,EACX,6CACA,sDACF;EACD,CAAA,IAED,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAY,KAAK,MAChB,kBAAC,GAAD;IAA4B,WAAU;IAAQ,WAAU;cAAxD;KACE,kBAAC,MAAD;MAAI,WAAU;gBAAiC,EAAQ;KAAS,CAAA;KAChE,kBAAC,KAAD;MAAG,WAAU;iBACT,EAAQ,SAAS,EAAQ,iBAAiB,EAAA,CAAG,eAAe,KAAA,GAAW;OACvE,OAAO;OACP,UAAU,EAAQ;MACpB,CAAC;KACA,CAAA;KACF,EAAQ,sBAAsB,IAC7B,kBAAC,KAAD;MAAG,WAAU;gBAAb,CACE,kBAAC,GAAD,EAAM,WAAU,4BAA6B,CAAA,GAC5C,EACC,gDACA,oCACF,CAAC,CAAC,QAAQ,WAAW,OAAO,EAAQ,mBAAmB,CAAC,CACvD;UACD;IACK;MAjBK,EAAQ,EAiBb,CACZ;EACE,CAAA;CAEJ,CAAA,CACF,EAAA,CAAA,IApFH,kBAAC,GAAD;EACE,OAAO,EAAE,kCAAkC,YAAY;EACvD,UAAU,EACR,6CACA,mCACF;YAEA,kBAAC,GAAD;GACE,cAAa;GACb,OAAO,EAAE,0CAA0C,sBAAsB;GACzE,aAAa,EACX,gDACA,gFACF;EACD,CAAA;CACQ,CAAA;AAuEjB"}
@@ -1 +1 @@
1
- {"version":3,"file":"TourDetailsPage.js","names":[],"sources":["../../../src/ggnomad/pages/TourDetailsPage.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';\nimport {\n CheckCircle2,\n Clock,\n Compass,\n Gauge,\n Languages as LanguagesIcon,\n MapPin,\n Pencil,\n UserRound,\n Users,\n Zap,\n} from 'lucide-react';\nimport { GlassCard, IllustratedEmptyState, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';\n\nimport { GGNOMAD_TOUR_QUERY } from '../graphql';\nimport { useGGNomad } from '../providers/GGNomadProvider';\nimport { joinPath } from '../utils/navigation';\nimport { humanizeEnum, jsonLabel } from '../utils/format';\nimport { PageShell } from './PageShell';\nimport {\n AvailabilityPanel,\n BookingPanel,\n DetailHero,\n DetailRow,\n EcoAccessPanel,\n FactGrid,\n ListingEditDialog,\n PhotoGallery,\n ProviderCard,\n RatingSummary,\n ReviewsSection,\n SectionCard,\n StatusBadge,\n TagChips,\n} from '../components';\nimport type { Fact } from '../components';\n\ntype TourDetail = {\n id: string;\n name: string;\n description?: string | null;\n type: string;\n status?: string;\n duration: string;\n difficulty?: string | null;\n guideName?: string | null;\n currency: string;\n price: number;\n rating?: number | null;\n reviewCount: number;\n highlights: string[];\n images: string[];\n languages: string[];\n location?: unknown;\n maxGroupSize: number;\n instantBook: boolean;\n sustainabilityScore: number;\n sustainabilityPractices: string[];\n accessibilityFeatures: string[];\n provider?: {\n id: string;\n name: string;\n type?: string | null;\n rating?: number | null;\n reviewCount?: number | null;\n listingCount?: number | null;\n contactEmail?: string | null;\n website?: string | null;\n } | null;\n};\n\nexport function TourDetailsPage() {\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_TOUR_QUERY as TypedDocumentNode<{ ggnomadTour: TourDetail | null }, { id: string }>,\n { variables: { id }, skip: !id }\n );\n const tour = data?.ggnomadTour;\n const locationLabel = jsonLabel(tour?.location);\n\n const facts: Fact[] = [];\n if (tour) {\n facts.push({\n id: 'duration',\n label: t('ggnomad.tour.duration', 'Duration'),\n value: tour.duration,\n icon: <Clock />,\n });\n if (tour.difficulty) {\n facts.push({\n id: 'difficulty',\n label: t('ggnomad.tour.difficulty', 'Difficulty'),\n value: humanizeEnum(tour.difficulty),\n icon: <Gauge />,\n });\n }\n facts.push({\n id: 'group',\n label: t('ggnomad.tour.maxGroupSize', 'Max group size'),\n value: String(tour.maxGroupSize),\n icon: <Users />,\n });\n if (tour.languages.length > 0) {\n facts.push({\n id: 'languages',\n label: t('ggnomad.tour.languages', 'Languages'),\n value: tour.languages.join(', '),\n icon: <LanguagesIcon />,\n });\n }\n if (locationLabel) {\n facts.push({\n id: 'location',\n label: t('ggnomad.tour.location', 'Location'),\n value: locationLabel,\n icon: <MapPin />,\n });\n }\n if (tour.guideName) {\n facts.push({\n id: 'guide',\n label: t('ggnomad.tour.guide', 'Guide'),\n value: tour.guideName,\n icon: <UserRound />,\n });\n }\n }\n\n return (\n <PageShell\n title={tour?.name ?? t('ggnomad.tour.fallbackTitle', 'Tour details')}\n subtitle={t('ggnomad.tour.subtitle', 'Tour package details and booking context')}\n >\n {!tour ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={t('ggnomad.tour.notFoundTitle', 'No tour found')}\n description={t(\n 'ggnomad.tour.notFoundDescription',\n 'This tour is unavailable or may have been removed.'\n )}\n />\n ) : (\n <>\n {tour.images.length > 0 ? (\n <PhotoGallery images={tour.images} alt={tour.name} />\n ) : (\n <DetailHero alt={tour.name} />\n )}\n\n <div className=\"flex flex-wrap items-center gap-3\">\n <span className=\"rounded-full border border-border-seam bg-bg-sunken px-3 py-1 text-sm text-text-primary\">\n {humanizeEnum(tour.type)}\n </span>\n {tour.status ? <StatusBadge status={tour.status} /> : null}\n <RatingSummary rating={tour.rating} reviewCount={tour.reviewCount} />\n {tour.instantBook ? (\n <span className=\"inline-flex items-center gap-1.5 rounded-full bg-status-success-bg-subtle px-3 py-1 text-sm font-medium text-status-success-text\">\n <Zap className=\"size-3.5\" aria-hidden=\"true\" />\n {t('ggnomad.listing.instantBook', 'Instant book')}\n </span>\n ) : null}\n </div>\n\n <PagePurpose icon={<Compass className=\"size-4\" />}>\n {t(\n 'ggnomad.tour.purpose',\n 'See exactly what this tour involves — duration, difficulty, your guide, price and the highlights you’ll experience — so you can confirm it fits your trip before booking.'\n )}\n </PagePurpose>\n\n <FactGrid facts={facts} />\n\n <NextSteps\n storageKey=\"ggnomad-tour-detail\"\n steps={[\n {\n id: 'browse',\n label: t('ggnomad.tour.next.browse', 'Explore more experiences'),\n description: t(\n 'ggnomad.tour.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 {tour.description ? (\n <SectionCard title={t('ggnomad.tour.about', 'About this tour')}>\n <p className=\"text-text-primary\">{tour.description}</p>\n </SectionCard>\n ) : null}\n\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.tour.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.tour.editListing', 'Edit listing')}\n title={t('ggnomad.tour.editListing', 'Edit listing')}\n >\n <Pencil className=\"size-4\" />\n </button>\n </div>\n <dl className=\"space-y-2 text-sm\">\n <DetailRow label={t('ggnomad.tour.duration', 'Duration')} value={tour.duration} />\n <DetailRow\n label={t('ggnomad.tour.difficulty', 'Difficulty')}\n value={\n tour.difficulty ? humanizeEnum(tour.difficulty) : t('ggnomad.tour.na', 'N/A')\n }\n />\n <DetailRow\n label={t('ggnomad.tour.guide', 'Guide')}\n value={tour.guideName ?? t('ggnomad.tour.na', 'N/A')}\n />\n <DetailRow\n label={t('ggnomad.tour.price', 'Price per person')}\n value={`${tour.currency} ${Number(tour.price).toFixed(2)}`}\n />\n </dl>\n </GlassCard>\n\n {tour.highlights.length > 0 ? (\n <SectionCard title={t('ggnomad.tour.highlights', 'Highlights')}>\n <ul className=\"space-y-2 text-sm\">\n {tour.highlights.map((highlight) => (\n <li key={highlight} className=\"flex items-start gap-2 text-text-primary\">\n <CheckCircle2\n className=\"mt-0.5 size-4 flex-shrink-0 text-status-success-text\"\n aria-hidden=\"true\"\n />\n {highlight}\n </li>\n ))}\n </ul>\n </SectionCard>\n ) : null}\n\n {tour.languages.length > 0 ? (\n <SectionCard title={t('ggnomad.tour.languagesTitle', 'Guided in')}>\n <TagChips items={tour.languages} icon={<LanguagesIcon />} />\n </SectionCard>\n ) : null}\n\n <EcoAccessPanel\n sustainabilityScore={tour.sustainabilityScore}\n sustainabilityPractices={tour.sustainabilityPractices}\n accessibilityFeatures={tour.accessibilityFeatures}\n />\n\n <AvailabilityPanel listingId={tour.id ?? id} listingType=\"TOUR\" />\n\n {tour.provider ? <ProviderCard provider={tour.provider} /> : null}\n\n <ReviewsSection targetType=\"TOUR\" targetId={tour.id ?? id} />\n </div>\n <div>\n <BookingPanel\n bookingType=\"TOUR\"\n itemId={tour.id ?? id}\n itemName={tour.name}\n unitPrice={tour.price ?? 0}\n currency={tour.currency ?? 'USD'}\n />\n </div>\n </div>\n\n <ListingEditDialog\n open={editDialogOpen}\n onOpenChange={setEditDialogOpen}\n targetType=\"TOUR\"\n id={tour.id ?? id}\n initial={{\n name: tour.name,\n description: tour.description ?? undefined,\n price: tour.price,\n status: tour.status,\n images: tour.images,\n }}\n onSaved={() => refetch()}\n />\n </>\n )}\n </PageShell>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"TourDetailsPage.js","names":[],"sources":["../../../src/ggnomad/pages/TourDetailsPage.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';\nimport {\n CheckCircle2,\n Clock,\n Compass,\n Gauge,\n Languages as LanguagesIcon,\n MapPin,\n Pencil,\n UserRound,\n Users,\n Zap,\n} from 'lucide-react';\nimport { GlassCard, IllustratedEmptyState, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';\n\nimport { GGNOMAD_TOUR_QUERY } from '../graphql';\nimport { useGGNomad } from '../providers/GGNomadProvider';\nimport { joinPath } from '../utils/navigation';\nimport { humanizeEnum, jsonLabel } from '../utils/format';\nimport { PageShell } from './PageShell';\nimport {\n AvailabilityPanel,\n BookingPanel,\n DetailHero,\n DetailRow,\n EcoAccessPanel,\n FactGrid,\n ListingEditDialog,\n PhotoGallery,\n ProviderCard,\n RatingSummary,\n ReviewsSection,\n SectionCard,\n StatusBadge,\n TagChips,\n} from '../components';\nimport type { Fact } from '../components';\n\ntype TourDetail = {\n id: string;\n name: string;\n description?: string | null;\n type: string;\n status?: string;\n duration: string;\n difficulty?: string | null;\n guideName?: string | null;\n currency: string;\n price: number;\n rating?: number | null;\n reviewCount: number;\n highlights: string[];\n images: string[];\n languages: string[];\n location?: unknown;\n maxGroupSize: number;\n instantBook: boolean;\n sustainabilityScore: number;\n sustainabilityPractices: string[];\n accessibilityFeatures: string[];\n provider?: {\n id: string;\n name: string;\n type?: string | null;\n rating?: number | null;\n reviewCount?: number | null;\n listingCount?: number | null;\n contactEmail?: string | null;\n website?: string | null;\n } | null;\n};\n\nexport function TourDetailsPage() {\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_TOUR_QUERY as TypedDocumentNode<{ ggnomadTour: TourDetail | null }, { id: string }>,\n { variables: { id }, skip: !id }\n );\n const tour = data?.ggnomadTour;\n const locationLabel = jsonLabel(tour?.location);\n\n const facts: Fact[] = [];\n if (tour) {\n facts.push({\n id: 'duration',\n label: t('ggnomad.tour.duration', 'Duration'),\n value: tour.duration,\n icon: <Clock />,\n });\n if (tour.difficulty) {\n facts.push({\n id: 'difficulty',\n label: t('ggnomad.tour.difficulty', 'Difficulty'),\n value: humanizeEnum(tour.difficulty),\n icon: <Gauge />,\n });\n }\n facts.push({\n id: 'group',\n label: t('ggnomad.tour.maxGroupSize', 'Max group size'),\n value: String(tour.maxGroupSize),\n icon: <Users />,\n });\n if (tour.languages.length > 0) {\n facts.push({\n id: 'languages',\n label: t('ggnomad.tour.languages', 'Languages'),\n value: tour.languages.join(', '),\n icon: <LanguagesIcon />,\n });\n }\n if (locationLabel) {\n facts.push({\n id: 'location',\n label: t('ggnomad.tour.location', 'Location'),\n value: locationLabel,\n icon: <MapPin />,\n });\n }\n if (tour.guideName) {\n facts.push({\n id: 'guide',\n label: t('ggnomad.tour.guide', 'Guide'),\n value: tour.guideName,\n icon: <UserRound />,\n });\n }\n }\n\n return (\n <PageShell\n title={tour?.name ?? t('ggnomad.tour.fallbackTitle', 'Tour details')}\n subtitle={t('ggnomad.tour.subtitle', 'Tour package details and booking context')}\n >\n {!tour ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={t('ggnomad.tour.notFoundTitle', 'No tour found')}\n description={t(\n 'ggnomad.tour.notFoundDescription',\n 'This tour is unavailable or may have been removed.'\n )}\n />\n ) : (\n <>\n {tour.images.length > 0 ? (\n <PhotoGallery images={tour.images} alt={tour.name} />\n ) : (\n <DetailHero alt={tour.name} />\n )}\n\n <div className=\"flex flex-wrap items-center gap-3\">\n <span className=\"rounded-full border border-border-seam bg-bg-sunken px-3 py-1 text-sm text-text-primary\">\n {humanizeEnum(tour.type)}\n </span>\n {tour.status ? <StatusBadge status={tour.status} /> : null}\n <RatingSummary rating={tour.rating} reviewCount={tour.reviewCount} />\n {tour.instantBook ? (\n <span className=\"inline-flex items-center gap-1.5 rounded-full bg-status-success-bg-subtle px-3 py-1 text-sm font-medium text-status-success-text\">\n <Zap className=\"size-3.5\" aria-hidden=\"true\" />\n {t('ggnomad.listing.instantBook', 'Instant book')}\n </span>\n ) : null}\n </div>\n\n <PagePurpose icon={<Compass className=\"size-4\" />}>\n {t(\n 'ggnomad.tour.purpose',\n 'See exactly what this tour involves — duration, difficulty, your guide, price and the highlights you’ll experience — so you can confirm it fits your trip before booking.'\n )}\n </PagePurpose>\n\n <FactGrid facts={facts} />\n\n <NextSteps\n storageKey=\"ggnomad-tour-detail\"\n steps={[\n {\n id: 'browse',\n label: t('ggnomad.tour.next.browse', 'Explore more experiences'),\n description: t(\n 'ggnomad.tour.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 {tour.description ? (\n <SectionCard title={t('ggnomad.tour.about', 'About this tour')}>\n <p className=\"text-text-primary\">{tour.description}</p>\n </SectionCard>\n ) : null}\n\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.tour.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.tour.editListing', 'Edit listing')}\n title={t('ggnomad.tour.editListing', 'Edit listing')}\n >\n <Pencil className=\"size-4\" />\n </button>\n </div>\n <dl className=\"space-y-2 text-sm\">\n <DetailRow label={t('ggnomad.tour.duration', 'Duration')} value={tour.duration} />\n <DetailRow\n label={t('ggnomad.tour.difficulty', 'Difficulty')}\n value={\n tour.difficulty ? humanizeEnum(tour.difficulty) : t('ggnomad.tour.na', 'N/A')\n }\n />\n <DetailRow\n label={t('ggnomad.tour.guide', 'Guide')}\n value={tour.guideName ?? t('ggnomad.tour.na', 'N/A')}\n />\n <DetailRow\n label={t('ggnomad.tour.price', 'Price per person')}\n value={`${tour.currency} ${Number(tour.price).toFixed(2)}`}\n />\n </dl>\n </GlassCard>\n\n {tour.highlights.length > 0 ? (\n <SectionCard title={t('ggnomad.tour.highlights', 'Highlights')}>\n <ul className=\"space-y-2 text-sm\">\n {tour.highlights.map((highlight) => (\n <li key={highlight} className=\"flex items-start gap-2 text-text-primary\">\n <CheckCircle2\n className=\"mt-0.5 size-4 flex-shrink-0 text-status-success-text\"\n aria-hidden=\"true\"\n />\n {highlight}\n </li>\n ))}\n </ul>\n </SectionCard>\n ) : null}\n\n {tour.languages.length > 0 ? (\n <SectionCard title={t('ggnomad.tour.languagesTitle', 'Guided in')}>\n <TagChips items={tour.languages} icon={<LanguagesIcon />} />\n </SectionCard>\n ) : null}\n\n <EcoAccessPanel\n sustainabilityScore={tour.sustainabilityScore}\n sustainabilityPractices={tour.sustainabilityPractices}\n accessibilityFeatures={tour.accessibilityFeatures}\n />\n\n <AvailabilityPanel listingId={tour.id ?? id} listingType=\"TOUR\" />\n\n {tour.provider ? <ProviderCard provider={tour.provider} /> : null}\n\n <ReviewsSection targetType=\"TOUR\" targetId={tour.id ?? id} />\n </div>\n <div>\n <BookingPanel\n bookingType=\"TOUR\"\n itemId={tour.id ?? id}\n itemName={tour.name}\n unitPrice={tour.price ?? 0}\n currency={tour.currency ?? 'USD'}\n />\n </div>\n </div>\n\n <ListingEditDialog\n open={editDialogOpen}\n onOpenChange={setEditDialogOpen}\n targetType=\"TOUR\"\n id={tour.id ?? id}\n initial={{\n name: tour.name,\n description: tour.description ?? undefined,\n price: tour.price,\n status: tour.status,\n images: tour.images,\n }}\n onSaved={() => refetch()}\n />\n </>\n )}\n </PageShell>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"TourismPage.js","names":[],"sources":["../../../src/ggnomad/pages/TourismPage.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 { Clock, Compass, Gauge, Leaf, Zap } from 'lucide-react';\nimport { EmphasisPanel, IllustratedEmptyState, PagePurpose } from '@burdenoff/fe-libs/ui';\n\nimport { GGNOMAD_ACTIVITIES_QUERY, GGNOMAD_TOURS_QUERY } from '../graphql';\nimport { humanizeEnum } from '../utils/format';\nimport { PageShell } from './PageShell';\nimport {\n EntityCard,\n EMPTY_LISTING_FILTERS,\n ListingFilters,\n LoadMoreButton,\n RatingSummary,\n SectionCard,\n hasActiveListingFilters,\n} from '../components';\nimport type { ListingFilterState } from '../components';\n\ntype TourismItem = {\n id: string;\n name: string;\n /** Tours carry `type` (CULTURAL, ADVENTURE…), activities carry `category`. */\n type?: string;\n category?: string;\n duration: string;\n difficulty?: string | null;\n price: number;\n currency: string;\n rating?: number | null;\n reviewCount: number;\n images?: string[];\n instantBook: boolean;\n sustainabilityScore: number;\n};\n\ntype ListingVariables = {\n limit: number;\n offset: number;\n status?: string;\n minSustainabilityScore?: number;\n hasAccessibilityFeatures?: string[];\n};\n\nexport function TourismPage() {\n const { t } = useI18n();\n const [tourLimit, setTourLimit] = useState(12);\n const [activityLimit, setActivityLimit] = useState(12);\n const [filters, setFilters] = useState<ListingFilterState>(EMPTY_LISTING_FILTERS);\n\n const filterVariables = {\n status: filters.status,\n minSustainabilityScore: filters.minSustainabilityScore,\n hasAccessibilityFeatures:\n filters.accessibilityFeatures.length > 0 ? filters.accessibilityFeatures : undefined,\n };\n\n const { data: toursData } = useQuery(\n GGNOMAD_TOURS_QUERY as TypedDocumentNode<{ ggnomadTours: TourismItem[] }, ListingVariables>,\n { variables: { limit: tourLimit, offset: 0, ...filterVariables } }\n );\n const { data: activitiesData } = useQuery(\n GGNOMAD_ACTIVITIES_QUERY as TypedDocumentNode<\n { ggnomadActivities: TourismItem[] },\n ListingVariables\n >,\n { variables: { limit: activityLimit, offset: 0, ...filterVariables } }\n );\n const tours = toursData?.ggnomadTours ?? [];\n const activities = activitiesData?.ggnomadActivities ?? [];\n const filtered = hasActiveListingFilters(filters);\n\n const renderList = (items: TourismItem[], basePath: string, emptyLabel: string) => {\n if (items.length === 0) {\n return (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={\n filtered\n ? t('ggnomad.tourism.noMatchTitle', 'Nothing matches these filters')\n : emptyLabel\n }\n description={\n filtered\n ? t(\n 'ggnomad.tourism.noMatchDescription',\n 'Try clearing a filter — status, sustainability score and accessibility all narrow the catalogue.'\n )\n : t(\n 'ggnomad.tourism.emptyDescription',\n 'Curated experiences will appear here once published.'\n )\n }\n />\n );\n }\n return (\n <div className=\"grid gap-3\">\n {items.map((item) => (\n <EntityCard\n key={item.id}\n to={`${basePath}/${item.id}`}\n image={item.images?.[0]}\n imageAlt={item.name}\n title={item.name}\n meta={humanizeEnum(item.type ?? item.category ?? '')}\n badge={\n item.instantBook ? (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-status-success-bg-subtle px-2.5 py-0.5 text-xs font-medium text-status-success-text\">\n <Zap className=\"size-3\" aria-hidden=\"true\" />\n {t('ggnomad.listing.instantBook', 'Instant book')}\n </span>\n ) : null\n }\n price={`${item.currency} ${Number(item.price).toFixed(2)}`}\n footer={\n <div className=\"flex flex-wrap items-center gap-x-3 gap-y-1\">\n <span className=\"inline-flex items-center gap-1\">\n <Clock className=\"size-3.5\" aria-hidden=\"true\" />\n {item.duration}\n </span>\n {item.difficulty ? (\n <span className=\"inline-flex items-center gap-1\">\n <Gauge className=\"size-3.5\" aria-hidden=\"true\" />\n {humanizeEnum(item.difficulty)}\n </span>\n ) : null}\n <RatingSummary rating={item.rating} reviewCount={item.reviewCount} />\n {item.sustainabilityScore > 0 ? (\n <span className=\"inline-flex items-center gap-1 tabular-nums text-status-success-text\">\n <Leaf className=\"size-3.5\" aria-hidden=\"true\" />\n {item.sustainabilityScore}\n </span>\n ) : null}\n </div>\n }\n />\n ))}\n </div>\n );\n };\n\n return (\n <PageShell\n title={t('ggnomad.tourism.title', 'Tourism')}\n titleAccent={t('ggnomad.tourism.title.accent', 'Tourism')}\n subtitle={t('ggnomad.tourism.subtitle', 'Tours and curated activity experiences')}\n >\n <PagePurpose icon={<Compass className=\"size-4\" />}>\n {t(\n 'ggnomad.tourism.purpose',\n 'Plan what you’ll actually do at a destination — guided tours and bookable activities curated by local operators. Open any experience to review duration, price and what’s included, then add it to a booking.'\n )}\n </PagePurpose>\n\n <EmphasisPanel>\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <div>\n <p className=\"text-sm text-text-secondary\">{t('ggnomad.tourism.tours', 'Tours')}</p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums text-text-primary\">\n {tours.length}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">\n {t('ggnomad.tourism.activities', 'Activities')}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums text-text-primary\">\n {activities.length}\n </p>\n </div>\n </div>\n <p className=\"mt-4 text-sm text-text-secondary\">\n {t(\n 'ggnomad.tourism.emphasisNote',\n 'Curated, bookable experiences across your destinations — pick a tour or activity below to see full details.'\n )}\n </p>\n </EmphasisPanel>\n\n <ListingFilters\n idPrefix=\"ggnomad-tourism\"\n value={filters}\n onChange={(next) => {\n setTourLimit(12);\n setActivityLimit(12);\n setFilters(next);\n }}\n resultCount={tours.length + activities.length}\n />\n\n <div className=\"grid gap-6 lg:grid-cols-2\">\n <SectionCard title={t('ggnomad.tourism.tours', 'Tours')}>\n {renderList(tours, 'tours', t('ggnomad.tourism.toursEmpty', 'No tours yet'))}\n <LoadMoreButton\n visible={tours.length >= tourLimit}\n onLoadMore={() => setTourLimit((l) => l + 12)}\n />\n </SectionCard>\n <SectionCard title={t('ggnomad.tourism.activities', 'Activities')}>\n {renderList(\n activities,\n 'activities',\n t('ggnomad.tourism.activitiesEmpty', 'No activities yet')\n )}\n <LoadMoreButton\n visible={activities.length >= activityLimit}\n onLoadMore={() => setActivityLimit((l) => l + 12)}\n />\n </SectionCard>\n </div>\n </PageShell>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA8CA,SAAgB,IAAc;CAC5B,IAAM,EAAE,SAAM,EAAQ,GAChB,CAAC,GAAW,KAAgB,EAAS,EAAE,GACvC,CAAC,GAAe,KAAoB,EAAS,EAAE,GAC/C,CAAC,GAAS,KAAc,EAA6B,CAAqB,GAE1E,IAAkB;EACtB,QAAQ,EAAQ;EAChB,wBAAwB,EAAQ;EAChC,0BACE,EAAQ,sBAAsB,SAAS,IAAI,EAAQ,wBAAwB,KAAA;CAC/E,GAEM,EAAE,MAAM,MAAc,EAC1B,GACA,EAAE,WAAW;EAAE,OAAO;EAAW,QAAQ;EAAG,GAAG;CAAgB,EAAE,CACnE,GACM,EAAE,MAAM,MAAmB,EAC/B,GAIA,EAAE,WAAW;EAAE,OAAO;EAAe,QAAQ;EAAG,GAAG;CAAgB,EAAE,CACvE,GACM,IAAQ,GAAW,gBAAgB,CAAC,GACpC,IAAa,GAAgB,qBAAqB,CAAC,GACnD,IAAW,EAAwB,CAAO,GAE1C,KAAc,GAAsB,GAAkB,MACtD,EAAM,WAAW,IAEjB,kBAAC,GAAD;EACE,cAAa;EACb,OACE,IACI,EAAE,gCAAgC,+BAA+B,IACjE;EAEN,aACE,IACI,EACE,sCACA,kGACF,IACA,EACE,oCACA,sDACF;CAEP,CAAA,IAIH,kBAAC,OAAD;EAAK,WAAU;YACZ,EAAM,KAAK,MACV,kBAAC,GAAD;GAEE,IAAI,GAAG,EAAS,GAAG,EAAK;GACxB,OAAO,EAAK,SAAS;GACrB,UAAU,EAAK;GACf,OAAO,EAAK;GACZ,MAAM,EAAa,EAAK,QAAQ,EAAK,YAAY,EAAE;GACnD,OACE,EAAK,cACH,kBAAC,QAAD;IAAM,WAAU;cAAhB,CACE,kBAAC,GAAD;KAAK,WAAU;KAAS,eAAY;IAAQ,CAAA,GAC3C,EAAE,+BAA+B,cAAc,CAC5C;QACJ;GAEN,OAAO,GAAG,EAAK,SAAS,GAAG,OAAO,EAAK,KAAK,EAAE,QAAQ,CAAC;GACvD,QACE,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,QAAD;MAAM,WAAU;gBAAhB,CACE,kBAAC,GAAD;OAAO,WAAU;OAAW,eAAY;MAAQ,CAAA,GAC/C,EAAK,QACF;;KACL,EAAK,aACJ,kBAAC,QAAD;MAAM,WAAU;gBAAhB,CACE,kBAAC,GAAD;OAAO,WAAU;OAAW,eAAY;MAAQ,CAAA,GAC/C,EAAa,EAAK,UAAU,CACzB;UACJ;KACJ,kBAAC,GAAD;MAAe,QAAQ,EAAK;MAAQ,aAAa,EAAK;KAAc,CAAA;KACnE,EAAK,sBAAsB,IAC1B,kBAAC,QAAD;MAAM,WAAU;gBAAhB,CACE,kBAAC,GAAD;OAAM,WAAU;OAAW,eAAY;MAAQ,CAAA,GAC9C,EAAK,mBACF;UACJ;IACD;;EAER,GApCM,EAAK,EAoCX,CACF;CACE,CAAA;CAIT,OACE,kBAAC,GAAD;EACE,OAAO,EAAE,yBAAyB,SAAS;EAC3C,aAAa,EAAE,gCAAgC,SAAS;EACxD,UAAU,EAAE,4BAA4B,wCAAwC;YAHlF;GAKE,kBAAC,GAAD;IAAa,MAAM,kBAAC,GAAD,EAAS,WAAU,SAAU,CAAA;cAC7C,EACC,2BACA,+MACF;GACW,CAAA;GAEb,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;KAAG,WAAU;eAA+B,EAAE,yBAAyB,OAAO;IAAK,CAAA,GACnF,kBAAC,KAAD;KAAG,WAAU;eACV,EAAM;IACN,CAAA,CACA,EAAA,CAAA,GACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;KAAG,WAAU;eACV,EAAE,8BAA8B,YAAY;IAC5C,CAAA,GACH,kBAAC,KAAD;KAAG,WAAU;eACV,EAAW;IACX,CAAA,CACA,EAAA,CAAA,CACF;OACL,kBAAC,KAAD;IAAG,WAAU;cACV,EACC,gCACA,6GACF;GACC,CAAA,CACU,EAAA,CAAA;GAEf,kBAAC,GAAD;IACE,UAAS;IACT,OAAO;IACP,WAAW,MAAS;KAGlB,AAFA,EAAa,EAAE,GACf,EAAiB,EAAE,GACnB,EAAW,CAAI;IACjB;IACA,aAAa,EAAM,SAAS,EAAW;GACxC,CAAA;GAED,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KAAa,OAAO,EAAE,yBAAyB,OAAO;eAAtD,CACG,EAAW,GAAO,SAAS,EAAE,8BAA8B,cAAc,CAAC,GAC3E,kBAAC,GAAD;MACE,SAAS,EAAM,UAAU;MACzB,kBAAkB,GAAc,MAAM,IAAI,EAAE;KAC7C,CAAA,CACU;QACb,kBAAC,GAAD;KAAa,OAAO,EAAE,8BAA8B,YAAY;eAAhE,CACG,EACC,GACA,cACA,EAAE,mCAAmC,mBAAmB,CAC1D,GACA,kBAAC,GAAD;MACE,SAAS,EAAW,UAAU;MAC9B,kBAAkB,GAAkB,MAAM,IAAI,EAAE;KACjD,CAAA,CACU;MACV;;EACI;;AAEf"}
1
+ {"version":3,"file":"TourismPage.js","names":[],"sources":["../../../src/ggnomad/pages/TourismPage.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 { Clock, Compass, Gauge, Leaf, Zap } from 'lucide-react';\nimport { EmphasisPanel, IllustratedEmptyState, PagePurpose } from '@burdenoff/fe-libs/ui';\n\nimport { GGNOMAD_ACTIVITIES_QUERY, GGNOMAD_TOURS_QUERY } from '../graphql';\nimport { humanizeEnum } from '../utils/format';\nimport { PageShell } from './PageShell';\nimport {\n EntityCard,\n EMPTY_LISTING_FILTERS,\n ListingFilters,\n LoadMoreButton,\n RatingSummary,\n SectionCard,\n hasActiveListingFilters,\n} from '../components';\nimport type { ListingFilterState } from '../components';\n\ntype TourismItem = {\n id: string;\n name: string;\n /** Tours carry `type` (CULTURAL, ADVENTURE…), activities carry `category`. */\n type?: string;\n category?: string;\n duration: string;\n difficulty?: string | null;\n price: number;\n currency: string;\n rating?: number | null;\n reviewCount: number;\n images?: string[];\n instantBook: boolean;\n sustainabilityScore: number;\n};\n\ntype ListingVariables = {\n limit: number;\n offset: number;\n status?: string;\n minSustainabilityScore?: number;\n hasAccessibilityFeatures?: string[];\n};\n\nexport function TourismPage() {\n const { t } = useI18n();\n const [tourLimit, setTourLimit] = useState(12);\n const [activityLimit, setActivityLimit] = useState(12);\n const [filters, setFilters] = useState<ListingFilterState>(EMPTY_LISTING_FILTERS);\n\n const filterVariables = {\n status: filters.status,\n minSustainabilityScore: filters.minSustainabilityScore,\n hasAccessibilityFeatures:\n filters.accessibilityFeatures.length > 0 ? filters.accessibilityFeatures : undefined,\n };\n\n const { data: toursData } = useQuery(\n GGNOMAD_TOURS_QUERY as TypedDocumentNode<{ ggnomadTours: TourismItem[] }, ListingVariables>,\n { variables: { limit: tourLimit, offset: 0, ...filterVariables } }\n );\n const { data: activitiesData } = useQuery(\n GGNOMAD_ACTIVITIES_QUERY as TypedDocumentNode<\n { ggnomadActivities: TourismItem[] },\n ListingVariables\n >,\n { variables: { limit: activityLimit, offset: 0, ...filterVariables } }\n );\n const tours = toursData?.ggnomadTours ?? [];\n const activities = activitiesData?.ggnomadActivities ?? [];\n const filtered = hasActiveListingFilters(filters);\n\n const renderList = (items: TourismItem[], basePath: string, emptyLabel: string) => {\n if (items.length === 0) {\n return (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={\n filtered\n ? t('ggnomad.tourism.noMatchTitle', 'Nothing matches these filters')\n : emptyLabel\n }\n description={\n filtered\n ? t(\n 'ggnomad.tourism.noMatchDescription',\n 'Try clearing a filter — status, sustainability score and accessibility all narrow the catalogue.'\n )\n : t(\n 'ggnomad.tourism.emptyDescription',\n 'Curated experiences will appear here once published.'\n )\n }\n />\n );\n }\n return (\n <div className=\"grid gap-3\">\n {items.map((item) => (\n <EntityCard\n key={item.id}\n to={`${basePath}/${item.id}`}\n image={item.images?.[0]}\n imageAlt={item.name}\n title={item.name}\n meta={humanizeEnum(item.type ?? item.category ?? '')}\n badge={\n item.instantBook ? (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-status-success-bg-subtle px-2.5 py-0.5 text-xs font-medium text-status-success-text\">\n <Zap className=\"size-3\" aria-hidden=\"true\" />\n {t('ggnomad.listing.instantBook', 'Instant book')}\n </span>\n ) : null\n }\n price={`${item.currency} ${Number(item.price).toFixed(2)}`}\n footer={\n <div className=\"flex flex-wrap items-center gap-x-3 gap-y-1\">\n <span className=\"inline-flex items-center gap-1\">\n <Clock className=\"size-3.5\" aria-hidden=\"true\" />\n {item.duration}\n </span>\n {item.difficulty ? (\n <span className=\"inline-flex items-center gap-1\">\n <Gauge className=\"size-3.5\" aria-hidden=\"true\" />\n {humanizeEnum(item.difficulty)}\n </span>\n ) : null}\n <RatingSummary rating={item.rating} reviewCount={item.reviewCount} />\n {item.sustainabilityScore > 0 ? (\n <span className=\"inline-flex items-center gap-1 tabular-nums text-status-success-text\">\n <Leaf className=\"size-3.5\" aria-hidden=\"true\" />\n {item.sustainabilityScore}\n </span>\n ) : null}\n </div>\n }\n />\n ))}\n </div>\n );\n };\n\n return (\n <PageShell\n title={t('ggnomad.tourism.title', 'Tourism')}\n titleAccent={t('ggnomad.tourism.title.accent', 'Tourism')}\n subtitle={t('ggnomad.tourism.subtitle', 'Tours and curated activity experiences')}\n >\n <PagePurpose icon={<Compass className=\"size-4\" />}>\n {t(\n 'ggnomad.tourism.purpose',\n 'Plan what you’ll actually do at a destination — guided tours and bookable activities curated by local operators. Open any experience to review duration, price and what’s included, then add it to a booking.'\n )}\n </PagePurpose>\n\n <EmphasisPanel>\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <div>\n <p className=\"text-sm text-text-secondary\">{t('ggnomad.tourism.tours', 'Tours')}</p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums text-text-primary\">\n {tours.length}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">\n {t('ggnomad.tourism.activities', 'Activities')}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums text-text-primary\">\n {activities.length}\n </p>\n </div>\n </div>\n <p className=\"mt-4 text-sm text-text-secondary\">\n {t(\n 'ggnomad.tourism.emphasisNote',\n 'Curated, bookable experiences across your destinations — pick a tour or activity below to see full details.'\n )}\n </p>\n </EmphasisPanel>\n\n <ListingFilters\n idPrefix=\"ggnomad-tourism\"\n value={filters}\n onChange={(next) => {\n setTourLimit(12);\n setActivityLimit(12);\n setFilters(next);\n }}\n resultCount={tours.length + activities.length}\n />\n\n <div className=\"grid gap-6 lg:grid-cols-2\">\n <SectionCard title={t('ggnomad.tourism.tours', 'Tours')}>\n {renderList(tours, 'tours', t('ggnomad.tourism.toursEmpty', 'No tours yet'))}\n <LoadMoreButton\n visible={tours.length >= tourLimit}\n onLoadMore={() => setTourLimit((l) => l + 12)}\n />\n </SectionCard>\n <SectionCard title={t('ggnomad.tourism.activities', 'Activities')}>\n {renderList(\n activities,\n 'activities',\n t('ggnomad.tourism.activitiesEmpty', 'No activities yet')\n )}\n <LoadMoreButton\n visible={activities.length >= activityLimit}\n onLoadMore={() => setActivityLimit((l) => l + 12)}\n />\n </SectionCard>\n </div>\n </PageShell>\n );\n}\n"],"mappings":"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"}
@@ -17,7 +17,7 @@ var A = "mt-1 w-full rounded-md border border-border-seam bg-bg-canvas px-3 py-2
17
17
  "TRAIN",
18
18
  "BUS",
19
19
  "FERRY"
20
- ], M = new Set([
20
+ ], M = /* @__PURE__ */ new Set([
21
21
  "SEARCHING",
22
22
  "BOOKED",
23
23
  "CONFIRMED",
@@ -1 +1 @@
1
- {"version":3,"file":"TravelPage.js","names":[],"sources":["../../../src/ggnomad/pages/TravelPage.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 { ArrowRight, CalendarClock, Clock, Leaf, Plane, Plus, Search, Users } from 'lucide-react';\nimport { Button, GlassCard, IllustratedEmptyState, PagePurpose } from '@burdenoff/fe-libs/ui';\n\nimport {\n CREATE_GGNOMAD_TRIP_MUTATION,\n GGNOMAD_TRANSPORT_SEARCH_QUERY,\n GGNOMAD_TRIPS_QUERY,\n} from '../graphql';\nimport {\n formatDateTime,\n formatDurationMinutes,\n humanizeEnum,\n jsonArrayLength,\n jsonLabel,\n} from '../utils/format';\nimport { notify } from '../utils/notify';\nimport { PageShell } from './PageShell';\nimport { SectionCard, StatusBadge } from '../components';\n\ntype TripRow = {\n id: string;\n name: string;\n transportMode: string;\n status: string;\n origin?: unknown;\n destination?: unknown;\n departureAt: string;\n arrivalAt: string;\n isRoundTrip: boolean;\n carrier?: string | null;\n carrierCode?: string | null;\n class?: string | null;\n price: number;\n currency: string;\n passengers: number;\n ecoFriendly: boolean;\n co2Emissions?: number | null;\n stops?: unknown;\n returnDepartureAt?: string | null;\n returnArrivalAt?: string | null;\n};\n\ntype TransportOffer = {\n offerId: string;\n origin?: unknown;\n destination?: unknown;\n transportMode: string;\n departureAt: string;\n arrivalAt: string;\n returnDepartureAt?: string | null;\n returnArrivalAt?: string | null;\n isRoundTrip: boolean;\n carrier: string;\n carrierCode?: string | null;\n class?: string | null;\n price: number;\n currency: string;\n passengers: number;\n stops?: unknown;\n durationMinutes: number;\n ecoFriendly: boolean;\n co2Emissions?: number | null;\n providerName: string;\n};\n\ntype SearchCriteria = {\n origin: string;\n destination: string;\n departureDate: string;\n returnDate?: string;\n passengers?: number;\n transportMode?: string;\n};\n\nconst INPUT_CLASSES =\n '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';\n\n/** `transportMode` on the search input is free-text; these are the modes the service resolves. */\nconst TRANSPORT_MODES = ['FLIGHT', 'TRAIN', 'BUS', 'FERRY'];\n\nconst ACTIVE_TRIP_STATUSES = new Set(['SEARCHING', 'BOOKED', 'CONFIRMED', 'IN_PROGRESS']);\n\nfunction routeLabel(origin: unknown, destination: unknown, fallback: string): string {\n const from = jsonLabel(origin);\n const to = jsonLabel(destination);\n if (from && to) return `${from} → ${to}`;\n return fallback;\n}\n\nexport function TravelPage() {\n const { t } = useI18n();\n const [form, setForm] = useState({\n origin: '',\n destination: '',\n departureDate: '',\n returnDate: '',\n passengers: 1,\n transportMode: '',\n });\n const [criteria, setCriteria] = useState<SearchCriteria | null>(null);\n\n const { data, refetch } = useQuery(\n GGNOMAD_TRIPS_QUERY as TypedDocumentNode<\n { ggnomadTrips: TripRow[] },\n { limit: number; offset: number }\n >,\n { variables: { limit: 20, offset: 0 } }\n );\n const trips = data?.ggnomadTrips ?? [];\n\n const offersQuery = useQuery(\n GGNOMAD_TRANSPORT_SEARCH_QUERY as TypedDocumentNode<\n { searchGgnomadTransport: TransportOffer[] },\n { input: SearchCriteria }\n >,\n {\n variables: { input: criteria ?? { origin: '', destination: '', departureDate: '' } },\n skip: criteria === null,\n }\n );\n const offers = offersQuery.data?.searchGgnomadTransport ?? [];\n\n const [createTrip, { loading: adding }] = useMutation(\n CREATE_GGNOMAD_TRIP_MUTATION as TypedDocumentNode<\n { createGgnomadTrip: { id: string; name: string; status: string } },\n {\n input: {\n name: string;\n transportMode: string;\n origin: unknown;\n destination: unknown;\n departureAt: string;\n arrivalAt: string;\n returnDepartureAt?: string | null;\n returnArrivalAt?: string | null;\n isRoundTrip?: boolean;\n carrier?: string | null;\n carrierCode?: string | null;\n class?: string | null;\n price: number;\n currency?: string;\n passengers?: number;\n stops?: unknown;\n co2Emissions?: number | null;\n ecoFriendly?: boolean;\n };\n }\n >\n );\n\n const handleSearch = () => {\n if (!form.origin.trim() || !form.destination.trim() || !form.departureDate) {\n notify.warning(\n t('ggnomad.travel.search.missingTitle', 'Route and date required'),\n t(\n 'ggnomad.travel.search.missingBody',\n 'Enter an origin, a destination and a departure date to search transport inventory.'\n )\n );\n return;\n }\n setCriteria({\n origin: form.origin.trim(),\n destination: form.destination.trim(),\n departureDate: new Date(form.departureDate).toISOString(),\n returnDate: form.returnDate ? new Date(form.returnDate).toISOString() : undefined,\n passengers: Math.max(1, form.passengers),\n transportMode: form.transportMode || undefined,\n });\n };\n\n const handleAddOffer = async (offer: TransportOffer) => {\n const name = routeLabel(offer.origin, offer.destination, offer.carrier);\n try {\n const result = await createTrip({\n variables: {\n input: {\n name,\n transportMode: offer.transportMode,\n origin: offer.origin ?? {},\n destination: offer.destination ?? {},\n departureAt: offer.departureAt,\n arrivalAt: offer.arrivalAt,\n returnDepartureAt: offer.returnDepartureAt ?? undefined,\n returnArrivalAt: offer.returnArrivalAt ?? undefined,\n isRoundTrip: offer.isRoundTrip,\n carrier: offer.carrier,\n carrierCode: offer.carrierCode ?? undefined,\n class: offer.class ?? undefined,\n price: offer.price,\n currency: offer.currency,\n passengers: offer.passengers,\n stops: offer.stops ?? undefined,\n co2Emissions: offer.co2Emissions ?? undefined,\n ecoFriendly: offer.ecoFriendly,\n },\n },\n });\n if (result.error) {\n notify.error(\n t('ggnomad.travel.toast.addErrorTitle', 'Could not add this route'),\n result.error.message\n );\n return;\n }\n notify.success(\n t('ggnomad.travel.toast.addSuccessTitle', 'Route added to trip inventory'),\n name\n );\n await refetch();\n } catch (err) {\n notify.error(\n t('ggnomad.travel.toast.addErrorTitle', 'Could not add this route'),\n err instanceof Error ? err.message : String(err)\n );\n }\n };\n\n return (\n <PageShell\n title={t('ggnomad.travel.title', 'Travel')}\n titleAccent={t('ggnomad.travel.title.accent', 'Travel')}\n subtitle={t('ggnomad.travel.subtitle', 'Live trip inventory and route management')}\n >\n <PagePurpose icon={<Plane className=\"size-4\" />}>\n {t(\n 'ggnomad.travel.purpose',\n 'Browse the live trip catalogue — flights, rail and road routes published by providers — to compare transport modes, prices and availability before turning a trip into a booking.'\n )}\n </PagePurpose>\n\n <SectionCard title={t('ggnomad.travel.search.title', 'Search transport')}>\n <div className=\"grid gap-3 sm:grid-cols-2 xl:grid-cols-6\">\n <div className=\"xl:col-span-1\">\n <label\n htmlFor=\"ggnomad-travel-origin\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.travel.search.origin', 'Origin')}\n </label>\n <input\n id=\"ggnomad-travel-origin\"\n type=\"text\"\n value={form.origin}\n onChange={(event) => setForm({ ...form, origin: event.target.value })}\n placeholder={t('ggnomad.travel.search.originPlaceholder', 'City or station')}\n className={INPUT_CLASSES}\n />\n </div>\n <div className=\"xl:col-span-1\">\n <label\n htmlFor=\"ggnomad-travel-destination\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.travel.search.destination', 'Destination')}\n </label>\n <input\n id=\"ggnomad-travel-destination\"\n type=\"text\"\n value={form.destination}\n onChange={(event) => setForm({ ...form, destination: event.target.value })}\n placeholder={t('ggnomad.travel.search.destinationPlaceholder', 'City or station')}\n className={INPUT_CLASSES}\n />\n </div>\n <div>\n <label\n htmlFor=\"ggnomad-travel-departure\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.travel.search.departure', 'Departure')}\n </label>\n <input\n id=\"ggnomad-travel-departure\"\n type=\"date\"\n value={form.departureDate}\n onChange={(event) => setForm({ ...form, departureDate: event.target.value })}\n className={INPUT_CLASSES}\n />\n </div>\n <div>\n <label\n htmlFor=\"ggnomad-travel-return\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.travel.search.return', 'Return (optional)')}\n </label>\n <input\n id=\"ggnomad-travel-return\"\n type=\"date\"\n min={form.departureDate || undefined}\n value={form.returnDate}\n onChange={(event) => setForm({ ...form, returnDate: event.target.value })}\n className={INPUT_CLASSES}\n />\n </div>\n <div>\n <label\n htmlFor=\"ggnomad-travel-passengers\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.travel.search.passengers', 'Travellers')}\n </label>\n <input\n id=\"ggnomad-travel-passengers\"\n type=\"number\"\n min={1}\n value={form.passengers}\n onChange={(event) =>\n setForm({ ...form, passengers: Math.max(1, Number(event.target.value) || 1) })\n }\n className={INPUT_CLASSES}\n />\n </div>\n <div>\n <label\n htmlFor=\"ggnomad-travel-mode\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.travel.search.mode', 'Mode')}\n </label>\n <select\n id=\"ggnomad-travel-mode\"\n value={form.transportMode}\n onChange={(event) => setForm({ ...form, transportMode: event.target.value })}\n className={INPUT_CLASSES}\n >\n <option value=\"\">{t('ggnomad.travel.search.anyMode', 'Any')}</option>\n {TRANSPORT_MODES.map((mode) => (\n <option key={mode} value={mode}>\n {humanizeEnum(mode)}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div className=\"mt-4 flex flex-wrap items-center gap-3\">\n <Button onClick={handleSearch} disabled={offersQuery.loading}>\n <Search className=\"size-4\" aria-hidden=\"true\" />\n {offersQuery.loading\n ? t('ggnomad.travel.search.searching', 'Searching…')\n : t('ggnomad.travel.search.submit', 'Search routes')}\n </Button>\n <p className=\"text-xs text-text-secondary\">\n {t(\n 'ggnomad.travel.search.note',\n 'Returns live offers from the configured transport provider. Adding a route creates a trip in your inventory.'\n )}\n </p>\n </div>\n </SectionCard>\n\n {criteria !== null ? (\n <SectionCard title={t('ggnomad.travel.offers.title', 'Search results')}>\n {offersQuery.error ? (\n <p className=\"text-sm text-status-error-text\">\n {t('ggnomad.travel.offers.error', 'Could not search transport: {{message}}').replace(\n '{{message}}',\n offersQuery.error.message\n )}\n </p>\n ) : offersQuery.loading ? (\n <p className=\"text-sm text-text-secondary\">\n {t('ggnomad.travel.offers.loading', 'Searching transport inventory…')}\n </p>\n ) : offers.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n {t(\n 'ggnomad.travel.offers.empty',\n 'No routes came back for that origin, destination and date.'\n )}\n </p>\n ) : (\n <ul className=\"space-y-3\">\n {offers.map((offer) => {\n const stopCount = jsonArrayLength(offer.stops);\n return (\n <li\n key={offer.offerId}\n className=\"flex flex-wrap items-center justify-between gap-4 rounded-lg border border-border-seam bg-bg-surface/40 p-4\"\n >\n <div className=\"min-w-0 space-y-1\">\n <p className=\"flex flex-wrap items-center gap-2 font-medium text-text-primary\">\n <span>{jsonLabel(offer.origin) ?? '—'}</span>\n <ArrowRight className=\"size-4 text-text-secondary\" aria-hidden=\"true\" />\n <span>{jsonLabel(offer.destination) ?? '—'}</span>\n </p>\n <div className=\"flex flex-wrap items-center gap-x-3 gap-y-1 text-sm text-text-secondary\">\n <span>{humanizeEnum(offer.transportMode)}</span>\n <span className=\"inline-flex items-center gap-1\">\n <Clock className=\"size-3.5\" aria-hidden=\"true\" />\n {formatDurationMinutes(offer.durationMinutes)}\n </span>\n <span className=\"inline-flex items-center gap-1\">\n <CalendarClock className=\"size-3.5\" aria-hidden=\"true\" />\n {formatDateTime(offer.departureAt) ?? offer.departureAt}\n </span>\n <span>\n {offer.carrierCode\n ? `${offer.carrier} · ${offer.carrierCode}`\n : offer.carrier}\n </span>\n {offer.class ? <span>{humanizeEnum(offer.class)}</span> : null}\n {stopCount !== null ? (\n <span>\n {stopCount === 0\n ? t('ggnomad.travel.offers.nonStop', 'Non-stop')\n : t('ggnomad.travel.offers.stops', '{{n}} stops').replace(\n '{{n}}',\n String(stopCount)\n )}\n </span>\n ) : null}\n <span className=\"inline-flex items-center gap-1\">\n <Users className=\"size-3.5\" aria-hidden=\"true\" />\n {offer.passengers}\n </span>\n {offer.ecoFriendly ? (\n <span className=\"inline-flex items-center gap-1 text-status-success-text\">\n <Leaf className=\"size-3.5\" aria-hidden=\"true\" />\n {t('ggnomad.travel.ecoFriendly', 'Lower-emission mode')}\n </span>\n ) : null}\n {offer.co2Emissions !== null && offer.co2Emissions !== undefined ? (\n <span className=\"tabular-nums\">\n {t('ggnomad.travel.co2', '≈{{kg}} kg CO₂e').replace(\n '{{kg}}',\n offer.co2Emissions.toLocaleString()\n )}\n </span>\n ) : null}\n </div>\n </div>\n <div className=\"flex items-center gap-3\">\n <span className=\"text-lg font-semibold tabular-nums text-text-primary\">\n {offer.currency} {Number(offer.price).toFixed(2)}\n </span>\n <Button\n variant=\"outline\"\n size=\"sm\"\n disabled={adding}\n onClick={() => void handleAddOffer(offer)}\n >\n <Plus className=\"size-4\" aria-hidden=\"true\" />\n {t('ggnomad.travel.offers.add', 'Add to trips')}\n </Button>\n </div>\n </li>\n );\n })}\n </ul>\n )}\n </SectionCard>\n ) : null}\n\n {trips.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={t('ggnomad.travel.emptyTitle', 'No trips in inventory')}\n description={t(\n 'ggnomad.travel.emptyDescription',\n 'Routes and trips added by providers will appear here.'\n )}\n />\n ) : (\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.travel.availableTrips', 'Available trips')}\n </h2>\n <ul className=\"grid gap-3\">\n {trips.map((trip) => {\n const stopCount = jsonArrayLength(trip.stops);\n return (\n <li\n key={trip.id}\n className=\"flex flex-wrap items-start justify-between gap-4 rounded-lg border border-border-seam bg-bg-surface/40 p-4\"\n >\n <div className=\"min-w-0 space-y-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <p className=\"font-medium text-text-primary\">\n {routeLabel(trip.origin, trip.destination, trip.name)}\n </p>\n <StatusBadge\n status={trip.status}\n live={ACTIVE_TRIP_STATUSES.has(trip.status.toUpperCase())}\n />\n {trip.isRoundTrip ? (\n <span className=\"rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary\">\n {t('ggnomad.travel.roundTrip', 'Round trip')}\n </span>\n ) : null}\n </div>\n <div className=\"flex flex-wrap items-center gap-x-3 gap-y-1 text-sm text-text-secondary\">\n <span>{humanizeEnum(trip.transportMode)}</span>\n <span className=\"inline-flex items-center gap-1\">\n <CalendarClock className=\"size-3.5\" aria-hidden=\"true\" />\n {formatDateTime(trip.departureAt) ?? trip.departureAt}\n {' → '}\n {formatDateTime(trip.arrivalAt) ?? trip.arrivalAt}\n </span>\n {trip.carrier ? (\n <span>\n {trip.carrierCode\n ? `${trip.carrier} · ${trip.carrierCode}`\n : trip.carrier}\n </span>\n ) : null}\n {trip.class ? <span>{humanizeEnum(trip.class)}</span> : null}\n {stopCount !== null && stopCount > 0 ? (\n <span>\n {t('ggnomad.travel.offers.stops', '{{n}} stops').replace(\n '{{n}}',\n String(stopCount)\n )}\n </span>\n ) : null}\n <span className=\"inline-flex items-center gap-1\">\n <Users className=\"size-3.5\" aria-hidden=\"true\" />\n {trip.passengers}\n </span>\n {trip.ecoFriendly ? (\n <span className=\"inline-flex items-center gap-1 text-status-success-text\">\n <Leaf className=\"size-3.5\" aria-hidden=\"true\" />\n {t('ggnomad.travel.ecoFriendly', 'Lower-emission mode')}\n </span>\n ) : null}\n {trip.co2Emissions !== null && trip.co2Emissions !== undefined ? (\n <span className=\"tabular-nums\">\n {t('ggnomad.travel.co2', '≈{{kg}} kg CO₂e').replace(\n '{{kg}}',\n trip.co2Emissions.toLocaleString()\n )}\n </span>\n ) : null}\n </div>\n {trip.returnDepartureAt ? (\n <p className=\"text-sm text-text-secondary\">\n {t('ggnomad.travel.returnLeg', 'Return: {{depart}} → {{arrive}}')\n .replace(\n '{{depart}}',\n formatDateTime(trip.returnDepartureAt) ?? trip.returnDepartureAt\n )\n .replace('{{arrive}}', formatDateTime(trip.returnArrivalAt) ?? '—')}\n </p>\n ) : null}\n </div>\n <span className=\"text-lg font-semibold tabular-nums text-text-primary\">\n {trip.currency} {Number(trip.price).toFixed(2)}\n </span>\n </li>\n );\n })}\n </ul>\n </GlassCard>\n )}\n </PageShell>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"TravelPage.js","names":[],"sources":["../../../src/ggnomad/pages/TravelPage.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 { ArrowRight, CalendarClock, Clock, Leaf, Plane, Plus, Search, Users } from 'lucide-react';\nimport { Button, GlassCard, IllustratedEmptyState, PagePurpose } from '@burdenoff/fe-libs/ui';\n\nimport {\n CREATE_GGNOMAD_TRIP_MUTATION,\n GGNOMAD_TRANSPORT_SEARCH_QUERY,\n GGNOMAD_TRIPS_QUERY,\n} from '../graphql';\nimport {\n formatDateTime,\n formatDurationMinutes,\n humanizeEnum,\n jsonArrayLength,\n jsonLabel,\n} from '../utils/format';\nimport { notify } from '../utils/notify';\nimport { PageShell } from './PageShell';\nimport { SectionCard, StatusBadge } from '../components';\n\ntype TripRow = {\n id: string;\n name: string;\n transportMode: string;\n status: string;\n origin?: unknown;\n destination?: unknown;\n departureAt: string;\n arrivalAt: string;\n isRoundTrip: boolean;\n carrier?: string | null;\n carrierCode?: string | null;\n class?: string | null;\n price: number;\n currency: string;\n passengers: number;\n ecoFriendly: boolean;\n co2Emissions?: number | null;\n stops?: unknown;\n returnDepartureAt?: string | null;\n returnArrivalAt?: string | null;\n};\n\ntype TransportOffer = {\n offerId: string;\n origin?: unknown;\n destination?: unknown;\n transportMode: string;\n departureAt: string;\n arrivalAt: string;\n returnDepartureAt?: string | null;\n returnArrivalAt?: string | null;\n isRoundTrip: boolean;\n carrier: string;\n carrierCode?: string | null;\n class?: string | null;\n price: number;\n currency: string;\n passengers: number;\n stops?: unknown;\n durationMinutes: number;\n ecoFriendly: boolean;\n co2Emissions?: number | null;\n providerName: string;\n};\n\ntype SearchCriteria = {\n origin: string;\n destination: string;\n departureDate: string;\n returnDate?: string;\n passengers?: number;\n transportMode?: string;\n};\n\nconst INPUT_CLASSES =\n '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';\n\n/** `transportMode` on the search input is free-text; these are the modes the service resolves. */\nconst TRANSPORT_MODES = ['FLIGHT', 'TRAIN', 'BUS', 'FERRY'];\n\nconst ACTIVE_TRIP_STATUSES = new Set(['SEARCHING', 'BOOKED', 'CONFIRMED', 'IN_PROGRESS']);\n\nfunction routeLabel(origin: unknown, destination: unknown, fallback: string): string {\n const from = jsonLabel(origin);\n const to = jsonLabel(destination);\n if (from && to) return `${from} → ${to}`;\n return fallback;\n}\n\nexport function TravelPage() {\n const { t } = useI18n();\n const [form, setForm] = useState({\n origin: '',\n destination: '',\n departureDate: '',\n returnDate: '',\n passengers: 1,\n transportMode: '',\n });\n const [criteria, setCriteria] = useState<SearchCriteria | null>(null);\n\n const { data, refetch } = useQuery(\n GGNOMAD_TRIPS_QUERY as TypedDocumentNode<\n { ggnomadTrips: TripRow[] },\n { limit: number; offset: number }\n >,\n { variables: { limit: 20, offset: 0 } }\n );\n const trips = data?.ggnomadTrips ?? [];\n\n const offersQuery = useQuery(\n GGNOMAD_TRANSPORT_SEARCH_QUERY as TypedDocumentNode<\n { searchGgnomadTransport: TransportOffer[] },\n { input: SearchCriteria }\n >,\n {\n variables: { input: criteria ?? { origin: '', destination: '', departureDate: '' } },\n skip: criteria === null,\n }\n );\n const offers = offersQuery.data?.searchGgnomadTransport ?? [];\n\n const [createTrip, { loading: adding }] = useMutation(\n CREATE_GGNOMAD_TRIP_MUTATION as TypedDocumentNode<\n { createGgnomadTrip: { id: string; name: string; status: string } },\n {\n input: {\n name: string;\n transportMode: string;\n origin: unknown;\n destination: unknown;\n departureAt: string;\n arrivalAt: string;\n returnDepartureAt?: string | null;\n returnArrivalAt?: string | null;\n isRoundTrip?: boolean;\n carrier?: string | null;\n carrierCode?: string | null;\n class?: string | null;\n price: number;\n currency?: string;\n passengers?: number;\n stops?: unknown;\n co2Emissions?: number | null;\n ecoFriendly?: boolean;\n };\n }\n >\n );\n\n const handleSearch = () => {\n if (!form.origin.trim() || !form.destination.trim() || !form.departureDate) {\n notify.warning(\n t('ggnomad.travel.search.missingTitle', 'Route and date required'),\n t(\n 'ggnomad.travel.search.missingBody',\n 'Enter an origin, a destination and a departure date to search transport inventory.'\n )\n );\n return;\n }\n setCriteria({\n origin: form.origin.trim(),\n destination: form.destination.trim(),\n departureDate: new Date(form.departureDate).toISOString(),\n returnDate: form.returnDate ? new Date(form.returnDate).toISOString() : undefined,\n passengers: Math.max(1, form.passengers),\n transportMode: form.transportMode || undefined,\n });\n };\n\n const handleAddOffer = async (offer: TransportOffer) => {\n const name = routeLabel(offer.origin, offer.destination, offer.carrier);\n try {\n const result = await createTrip({\n variables: {\n input: {\n name,\n transportMode: offer.transportMode,\n origin: offer.origin ?? {},\n destination: offer.destination ?? {},\n departureAt: offer.departureAt,\n arrivalAt: offer.arrivalAt,\n returnDepartureAt: offer.returnDepartureAt ?? undefined,\n returnArrivalAt: offer.returnArrivalAt ?? undefined,\n isRoundTrip: offer.isRoundTrip,\n carrier: offer.carrier,\n carrierCode: offer.carrierCode ?? undefined,\n class: offer.class ?? undefined,\n price: offer.price,\n currency: offer.currency,\n passengers: offer.passengers,\n stops: offer.stops ?? undefined,\n co2Emissions: offer.co2Emissions ?? undefined,\n ecoFriendly: offer.ecoFriendly,\n },\n },\n });\n if (result.error) {\n notify.error(\n t('ggnomad.travel.toast.addErrorTitle', 'Could not add this route'),\n result.error.message\n );\n return;\n }\n notify.success(\n t('ggnomad.travel.toast.addSuccessTitle', 'Route added to trip inventory'),\n name\n );\n await refetch();\n } catch (err) {\n notify.error(\n t('ggnomad.travel.toast.addErrorTitle', 'Could not add this route'),\n err instanceof Error ? err.message : String(err)\n );\n }\n };\n\n return (\n <PageShell\n title={t('ggnomad.travel.title', 'Travel')}\n titleAccent={t('ggnomad.travel.title.accent', 'Travel')}\n subtitle={t('ggnomad.travel.subtitle', 'Live trip inventory and route management')}\n >\n <PagePurpose icon={<Plane className=\"size-4\" />}>\n {t(\n 'ggnomad.travel.purpose',\n 'Browse the live trip catalogue — flights, rail and road routes published by providers — to compare transport modes, prices and availability before turning a trip into a booking.'\n )}\n </PagePurpose>\n\n <SectionCard title={t('ggnomad.travel.search.title', 'Search transport')}>\n <div className=\"grid gap-3 sm:grid-cols-2 xl:grid-cols-6\">\n <div className=\"xl:col-span-1\">\n <label\n htmlFor=\"ggnomad-travel-origin\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.travel.search.origin', 'Origin')}\n </label>\n <input\n id=\"ggnomad-travel-origin\"\n type=\"text\"\n value={form.origin}\n onChange={(event) => setForm({ ...form, origin: event.target.value })}\n placeholder={t('ggnomad.travel.search.originPlaceholder', 'City or station')}\n className={INPUT_CLASSES}\n />\n </div>\n <div className=\"xl:col-span-1\">\n <label\n htmlFor=\"ggnomad-travel-destination\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.travel.search.destination', 'Destination')}\n </label>\n <input\n id=\"ggnomad-travel-destination\"\n type=\"text\"\n value={form.destination}\n onChange={(event) => setForm({ ...form, destination: event.target.value })}\n placeholder={t('ggnomad.travel.search.destinationPlaceholder', 'City or station')}\n className={INPUT_CLASSES}\n />\n </div>\n <div>\n <label\n htmlFor=\"ggnomad-travel-departure\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.travel.search.departure', 'Departure')}\n </label>\n <input\n id=\"ggnomad-travel-departure\"\n type=\"date\"\n value={form.departureDate}\n onChange={(event) => setForm({ ...form, departureDate: event.target.value })}\n className={INPUT_CLASSES}\n />\n </div>\n <div>\n <label\n htmlFor=\"ggnomad-travel-return\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.travel.search.return', 'Return (optional)')}\n </label>\n <input\n id=\"ggnomad-travel-return\"\n type=\"date\"\n min={form.departureDate || undefined}\n value={form.returnDate}\n onChange={(event) => setForm({ ...form, returnDate: event.target.value })}\n className={INPUT_CLASSES}\n />\n </div>\n <div>\n <label\n htmlFor=\"ggnomad-travel-passengers\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.travel.search.passengers', 'Travellers')}\n </label>\n <input\n id=\"ggnomad-travel-passengers\"\n type=\"number\"\n min={1}\n value={form.passengers}\n onChange={(event) =>\n setForm({ ...form, passengers: Math.max(1, Number(event.target.value) || 1) })\n }\n className={INPUT_CLASSES}\n />\n </div>\n <div>\n <label\n htmlFor=\"ggnomad-travel-mode\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.travel.search.mode', 'Mode')}\n </label>\n <select\n id=\"ggnomad-travel-mode\"\n value={form.transportMode}\n onChange={(event) => setForm({ ...form, transportMode: event.target.value })}\n className={INPUT_CLASSES}\n >\n <option value=\"\">{t('ggnomad.travel.search.anyMode', 'Any')}</option>\n {TRANSPORT_MODES.map((mode) => (\n <option key={mode} value={mode}>\n {humanizeEnum(mode)}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div className=\"mt-4 flex flex-wrap items-center gap-3\">\n <Button onClick={handleSearch} disabled={offersQuery.loading}>\n <Search className=\"size-4\" aria-hidden=\"true\" />\n {offersQuery.loading\n ? t('ggnomad.travel.search.searching', 'Searching…')\n : t('ggnomad.travel.search.submit', 'Search routes')}\n </Button>\n <p className=\"text-xs text-text-secondary\">\n {t(\n 'ggnomad.travel.search.note',\n 'Returns live offers from the configured transport provider. Adding a route creates a trip in your inventory.'\n )}\n </p>\n </div>\n </SectionCard>\n\n {criteria !== null ? (\n <SectionCard title={t('ggnomad.travel.offers.title', 'Search results')}>\n {offersQuery.error ? (\n <p className=\"text-sm text-status-error-text\">\n {t('ggnomad.travel.offers.error', 'Could not search transport: {{message}}').replace(\n '{{message}}',\n offersQuery.error.message\n )}\n </p>\n ) : offersQuery.loading ? (\n <p className=\"text-sm text-text-secondary\">\n {t('ggnomad.travel.offers.loading', 'Searching transport inventory…')}\n </p>\n ) : offers.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n {t(\n 'ggnomad.travel.offers.empty',\n 'No routes came back for that origin, destination and date.'\n )}\n </p>\n ) : (\n <ul className=\"space-y-3\">\n {offers.map((offer) => {\n const stopCount = jsonArrayLength(offer.stops);\n return (\n <li\n key={offer.offerId}\n className=\"flex flex-wrap items-center justify-between gap-4 rounded-lg border border-border-seam bg-bg-surface/40 p-4\"\n >\n <div className=\"min-w-0 space-y-1\">\n <p className=\"flex flex-wrap items-center gap-2 font-medium text-text-primary\">\n <span>{jsonLabel(offer.origin) ?? '—'}</span>\n <ArrowRight className=\"size-4 text-text-secondary\" aria-hidden=\"true\" />\n <span>{jsonLabel(offer.destination) ?? '—'}</span>\n </p>\n <div className=\"flex flex-wrap items-center gap-x-3 gap-y-1 text-sm text-text-secondary\">\n <span>{humanizeEnum(offer.transportMode)}</span>\n <span className=\"inline-flex items-center gap-1\">\n <Clock className=\"size-3.5\" aria-hidden=\"true\" />\n {formatDurationMinutes(offer.durationMinutes)}\n </span>\n <span className=\"inline-flex items-center gap-1\">\n <CalendarClock className=\"size-3.5\" aria-hidden=\"true\" />\n {formatDateTime(offer.departureAt) ?? offer.departureAt}\n </span>\n <span>\n {offer.carrierCode\n ? `${offer.carrier} · ${offer.carrierCode}`\n : offer.carrier}\n </span>\n {offer.class ? <span>{humanizeEnum(offer.class)}</span> : null}\n {stopCount !== null ? (\n <span>\n {stopCount === 0\n ? t('ggnomad.travel.offers.nonStop', 'Non-stop')\n : t('ggnomad.travel.offers.stops', '{{n}} stops').replace(\n '{{n}}',\n String(stopCount)\n )}\n </span>\n ) : null}\n <span className=\"inline-flex items-center gap-1\">\n <Users className=\"size-3.5\" aria-hidden=\"true\" />\n {offer.passengers}\n </span>\n {offer.ecoFriendly ? (\n <span className=\"inline-flex items-center gap-1 text-status-success-text\">\n <Leaf className=\"size-3.5\" aria-hidden=\"true\" />\n {t('ggnomad.travel.ecoFriendly', 'Lower-emission mode')}\n </span>\n ) : null}\n {offer.co2Emissions !== null && offer.co2Emissions !== undefined ? (\n <span className=\"tabular-nums\">\n {t('ggnomad.travel.co2', '≈{{kg}} kg CO₂e').replace(\n '{{kg}}',\n offer.co2Emissions.toLocaleString()\n )}\n </span>\n ) : null}\n </div>\n </div>\n <div className=\"flex items-center gap-3\">\n <span className=\"text-lg font-semibold tabular-nums text-text-primary\">\n {offer.currency} {Number(offer.price).toFixed(2)}\n </span>\n <Button\n variant=\"outline\"\n size=\"sm\"\n disabled={adding}\n onClick={() => void handleAddOffer(offer)}\n >\n <Plus className=\"size-4\" aria-hidden=\"true\" />\n {t('ggnomad.travel.offers.add', 'Add to trips')}\n </Button>\n </div>\n </li>\n );\n })}\n </ul>\n )}\n </SectionCard>\n ) : null}\n\n {trips.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={t('ggnomad.travel.emptyTitle', 'No trips in inventory')}\n description={t(\n 'ggnomad.travel.emptyDescription',\n 'Routes and trips added by providers will appear here.'\n )}\n />\n ) : (\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.travel.availableTrips', 'Available trips')}\n </h2>\n <ul className=\"grid gap-3\">\n {trips.map((trip) => {\n const stopCount = jsonArrayLength(trip.stops);\n return (\n <li\n key={trip.id}\n className=\"flex flex-wrap items-start justify-between gap-4 rounded-lg border border-border-seam bg-bg-surface/40 p-4\"\n >\n <div className=\"min-w-0 space-y-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <p className=\"font-medium text-text-primary\">\n {routeLabel(trip.origin, trip.destination, trip.name)}\n </p>\n <StatusBadge\n status={trip.status}\n live={ACTIVE_TRIP_STATUSES.has(trip.status.toUpperCase())}\n />\n {trip.isRoundTrip ? (\n <span className=\"rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary\">\n {t('ggnomad.travel.roundTrip', 'Round trip')}\n </span>\n ) : null}\n </div>\n <div className=\"flex flex-wrap items-center gap-x-3 gap-y-1 text-sm text-text-secondary\">\n <span>{humanizeEnum(trip.transportMode)}</span>\n <span className=\"inline-flex items-center gap-1\">\n <CalendarClock className=\"size-3.5\" aria-hidden=\"true\" />\n {formatDateTime(trip.departureAt) ?? trip.departureAt}\n {' → '}\n {formatDateTime(trip.arrivalAt) ?? trip.arrivalAt}\n </span>\n {trip.carrier ? (\n <span>\n {trip.carrierCode\n ? `${trip.carrier} · ${trip.carrierCode}`\n : trip.carrier}\n </span>\n ) : null}\n {trip.class ? <span>{humanizeEnum(trip.class)}</span> : null}\n {stopCount !== null && stopCount > 0 ? (\n <span>\n {t('ggnomad.travel.offers.stops', '{{n}} stops').replace(\n '{{n}}',\n String(stopCount)\n )}\n </span>\n ) : null}\n <span className=\"inline-flex items-center gap-1\">\n <Users className=\"size-3.5\" aria-hidden=\"true\" />\n {trip.passengers}\n </span>\n {trip.ecoFriendly ? (\n <span className=\"inline-flex items-center gap-1 text-status-success-text\">\n <Leaf className=\"size-3.5\" aria-hidden=\"true\" />\n {t('ggnomad.travel.ecoFriendly', 'Lower-emission mode')}\n </span>\n ) : null}\n {trip.co2Emissions !== null && trip.co2Emissions !== undefined ? (\n <span className=\"tabular-nums\">\n {t('ggnomad.travel.co2', '≈{{kg}} kg CO₂e').replace(\n '{{kg}}',\n trip.co2Emissions.toLocaleString()\n )}\n </span>\n ) : null}\n </div>\n {trip.returnDepartureAt ? (\n <p className=\"text-sm text-text-secondary\">\n {t('ggnomad.travel.returnLeg', 'Return: {{depart}} → {{arrive}}')\n .replace(\n '{{depart}}',\n formatDateTime(trip.returnDepartureAt) ?? trip.returnDepartureAt\n )\n .replace('{{arrive}}', formatDateTime(trip.returnArrivalAt) ?? '—')}\n </p>\n ) : null}\n </div>\n <span className=\"text-lg font-semibold tabular-nums text-text-primary\">\n {trip.currency} {Number(trip.price).toFixed(2)}\n </span>\n </li>\n );\n })}\n </ul>\n </GlassCard>\n )}\n </PageShell>\n );\n}\n"],"mappings":"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"}