@burdenoff/microfe-ggnomad 2026.1016.1 → 2026.1029.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ggnomad/GGNomadRoot.js +4 -2
- package/dist/ggnomad/GGNomadRoot.js.map +1 -1
- package/dist/ggnomad/GGNomadRoutes.js.map +1 -1
- package/dist/ggnomad/components/AuditTrailDialog.js.map +1 -1
- package/dist/ggnomad/components/AvailabilityPanel.js.map +1 -1
- package/dist/ggnomad/components/BookingPanel.d.ts.map +1 -1
- package/dist/ggnomad/components/BookingPanel.js +53 -70
- package/dist/ggnomad/components/BookingPanel.js.map +1 -1
- package/dist/ggnomad/components/CreateProviderDialog.js.map +1 -1
- package/dist/ggnomad/components/CreateTravelPolicyDialog.js.map +1 -1
- package/dist/ggnomad/components/GroupTripPanel.js.map +1 -1
- package/dist/ggnomad/components/ListingEditDialog.js.map +1 -1
- package/dist/ggnomad/components/ListingFilters.js.map +1 -1
- package/dist/ggnomad/components/PhotoGallery.js.map +1 -1
- package/dist/ggnomad/components/ProviderCard.js.map +1 -1
- package/dist/ggnomad/components/ProviderDashboardCard.js.map +1 -1
- package/dist/ggnomad/components/RatingSummary.js.map +1 -1
- package/dist/ggnomad/components/RedeemTicketPanel.js.map +1 -1
- package/dist/ggnomad/components/ReviewsSection.js.map +1 -1
- package/dist/ggnomad/components/StatusBadge.js.map +1 -1
- package/dist/ggnomad/components/TicketDialog.js +1 -1
- package/dist/ggnomad/components/TicketDialog.js.map +1 -1
- package/dist/ggnomad/components/index.js +26 -25
- package/dist/ggnomad/index.js +3 -2
- package/dist/ggnomad/pages/ActivityDetailsPage.js.map +1 -1
- package/dist/ggnomad/pages/AnalyticsPage.js.map +1 -1
- package/dist/ggnomad/pages/BookingsPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/BookingsPage.js +54 -52
- package/dist/ggnomad/pages/BookingsPage.js.map +1 -1
- package/dist/ggnomad/pages/DashboardPage.js.map +1 -1
- package/dist/ggnomad/pages/EntertainmentPage.js.map +1 -1
- package/dist/ggnomad/pages/EventDetailsPage.js.map +1 -1
- package/dist/ggnomad/pages/FavoritesPage.js.map +1 -1
- package/dist/ggnomad/pages/HospitalityPage.js.map +1 -1
- package/dist/ggnomad/pages/OperationsQueuePage.js.map +1 -1
- package/dist/ggnomad/pages/PageShell.js.map +1 -1
- package/dist/ggnomad/pages/PropertyDetailsPage.js.map +1 -1
- package/dist/ggnomad/pages/ProviderAnalyticsPage.js.map +1 -1
- package/dist/ggnomad/pages/SearchPage.js +1 -1
- package/dist/ggnomad/pages/SearchPage.js.map +1 -1
- package/dist/ggnomad/pages/StorefrontPage.js.map +1 -1
- package/dist/ggnomad/pages/StorefrontPublicPage.js.map +1 -1
- package/dist/ggnomad/pages/TourDetailsPage.js.map +1 -1
- package/dist/ggnomad/pages/TourismPage.js.map +1 -1
- package/dist/ggnomad/pages/TravelPage.js +1 -1
- package/dist/ggnomad/pages/TravelPage.js.map +1 -1
- package/dist/ggnomad/pages/TravelPolicyPage.js.map +1 -1
- package/dist/ggnomad/pages/TripPlanDetailPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/TripPlanDetailPage.js +278 -273
- package/dist/ggnomad/pages/TripPlanDetailPage.js.map +1 -1
- package/dist/ggnomad/pages/TripsPage.js.map +1 -1
- package/dist/ggnomad/providers/GGNomadProvider.js.map +1 -1
- package/dist/ggnomad/utils/bookingDates.d.ts +21 -0
- package/dist/ggnomad/utils/bookingDates.d.ts.map +1 -0
- package/dist/ggnomad/utils/bookingDates.js +22 -0
- package/dist/ggnomad/utils/bookingDates.js.map +1 -0
- package/dist/ggnomad/utils/format.js.map +1 -1
- package/dist/ggnomad/utils/notify.js +1 -1
- package/dist/ggnomad/utils/providerListControls.js +1 -1
- package/dist/ggnomad/utils/providerListControls.js.map +1 -1
- package/dist/ggnomad/utils/providerTypeMeta.js.map +1 -1
- package/dist/utils/pageContext.js +1 -1
- package/dist/utils/pageContext.js.map +1 -1
- package/package.json +18 -17
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"providerTypeMeta.js","names":[],"sources":["../../../src/ggnomad/utils/providerTypeMeta.ts"],"sourcesContent":["/**\n * Shared metadata for the GgnomadProviderType enum + provider visual\n * helpers.\n *\n * The single source of truth for the translation key + English fallback\n * for each provider type label, reused by both ProviderDashboardCard.tsx\n * (rendering) and CreateProviderDialog.tsx (form dropdown) so the wording\n * cannot drift between the two surfaces.\n *\n * If the backend ever adds a new value to GgnomadProviderType, add a\n * row here — anything unlisted falls back to a sentence-cased version of\n * the enum (\"HOTEL_OPERATOR\" -> \"Hotel operator\").\n *\n * Type-specific icons were intentionally removed: a single type\n * (\"Transport provider\") can cover buses, taxis, ferries and airlines,\n * so one icon per type is wrong. The card's banner is tinted by type\n * instead (see bannerTintFor) so the tint works as a type indicator.\n */\n\n/**\n * Local enum declaration. The published supergraph on Hive CDN still\n * declares `GgnomadProvider.type` as `String`, so the codegen-derived\n * types in src/generated/wspace-types.ts don't carry this enum yet. The\n * backend change (BOFF-8207) adds the enum to ggnomad.gql; once it ships\n * to alpha, the next codegen run will export this enum from\n * wspace-types and the local fallback below becomes a no-op. Keep the\n * two declarations in sync.\n */\nexport type GgnomadProviderType =\n | 'HOTEL_OPERATOR'\n | 'TOUR_OPERATOR'\n | 'ACTIVITY_PROVIDER'\n | 'TRANSPORT_PROVIDER'\n | 'EVENT_ORGANIZER';\n\n/**\n * Per-type translation keys + English fallbacks. The dropdown/filter chips\n * show these labels, and the same keys are reused by CreateProviderDialog.\n * If you add a value to GgnomadProviderType, add a key here and (eventually)\n * a translation in the locale files; the fallback covers English-only\n * locales.\n */\nconst KNOWN_LABEL_KEYS: Record<GgnomadProviderType, { key: string; fallback: string }> = {\n HOTEL_OPERATOR: { key: 'ggnomad.provider.type.hotelOperator', fallback: 'Hotel operator' },\n TOUR_OPERATOR: { key: 'ggnomad.provider.type.tourOperator', fallback: 'Tour operator' },\n ACTIVITY_PROVIDER: {\n key: 'ggnomad.provider.type.activityProvider',\n fallback: 'Activity provider',\n },\n TRANSPORT_PROVIDER: {\n key: 'ggnomad.provider.type.transportProvider',\n fallback: 'Transport provider',\n },\n EVENT_ORGANIZER: { key: 'ggnomad.provider.type.eventOrganizer', fallback: 'Event organizer' },\n};\n\nconst ORDER: GgnomadProviderType[] = [\n 'HOTEL_OPERATOR',\n 'TOUR_OPERATOR',\n 'ACTIVITY_PROVIDER',\n 'TRANSPORT_PROVIDER',\n 'EVENT_ORGANIZER',\n];\n\n/**\n * Build the dropdown options. `translate` should be the `t` from `useI18n()` in\n * the calling component so each label resolves in the active locale; the\n * fallback string is used otherwise (or when the translator is omitted).\n */\nexport function getProviderTypeOptions(\n translate?: (key: string, fallback: string) => string\n): Array<{\n value: GgnomadProviderType;\n label: string;\n}> {\n return ORDER.map((value) => {\n const { key, fallback } = KNOWN_LABEL_KEYS[value];\n return { value, label: translate ? translate(key, fallback) : fallback };\n });\n}\n\n/** Human-facing label for a provider type, with a safe fallback. */\nexport function labelForProviderType(\n type: string,\n translate?: (key: string, fallback: string) => string\n): string {\n if (type in KNOWN_LABEL_KEYS) {\n const { key, fallback } = KNOWN_LABEL_KEYS[type as GgnomadProviderType];\n return translate ? translate(key, fallback) : fallback;\n }\n return enumToSentence(type);\n}\n\n/** \"HOTEL_OPERATOR\" -> \"Hotel operator\", \"OTHER\" -> \"Other\". */\nfunction enumToSentence(value: string): string {\n const spaced = value.replace(/_+/g, ' ').trim().toLowerCase();\n if (!spaced) return value;\n return spaced.charAt(0).toUpperCase() + spaced.slice(1);\n}\n\n/**\n * Two-letter initials for the avatar fallback.\n *\n * - \"Indigo\" -> \"IN\" (first two letters of the single word)\n * - \"Blue Sky\" -> \"BS\" (first letter of each of the first two words)\n * - \"A\" -> \"A\" (single character preserved as-is)\n * - \"\" -> \"??\" (sentinel for empty names)\n */\nexport function initialsFor(name: string): string {\n const cleaned = name.trim();\n if (!cleaned) return '??';\n\n const words = cleaned.split(/\\s+/).filter(Boolean);\n let result: string;\n if (words.length >= 2) {\n result = (words[0].charAt(0) + words[1].charAt(0)).toUpperCase();\n } else {\n result = words[0].slice(0, 2).toUpperCase();\n }\n return result || '??';\n}\n\n/** \"alpine\" -> \"Alpine\", \"SUSPENDED\" -> \"Suspended\". Lowercases the input\n * first so the function normalizes backend enum values (which arrive\n * in UPPER_SNAKE_CASE) to a sentence-case display label. */\nexport function sentenceCase(value: string): string {\n const trimmed = value.trim();\n if (!trimmed) return '';\n const lower = trimmed.toLowerCase();\n return lower.charAt(0).toUpperCase() + lower.slice(1);\n}\n\n/** \"blue sky adventures\" -> \"Blue Sky Adventures\". Title-cases each word,\n * leaving small connector words (\"of\", \"the\", \"and\", \"a\", \"an\", \"in\", \"on\",\n * \"for\", \"to\") lowercase unless they are the first word. Used to display\n * user-entered provider names. */\nconst TITLE_CASE_SMALL_WORDS = new Set([\n 'a',\n 'an',\n 'and',\n 'as',\n 'at',\n 'but',\n 'by',\n 'for',\n 'in',\n 'nor',\n 'of',\n 'on',\n 'or',\n 'so',\n 'the',\n 'to',\n 'up',\n 'yet',\n]);\n\nexport function titleCase(value: string): string {\n const trimmed = value.trim();\n if (!trimmed) return '';\n // Preserve acronyms and proper nouns the user has already chosen the\n // casing for (\"McDonald's\", \"DHL Express\"): only short-circuit on\n // *mixed-case* input. Pure all-caps input like \"ALPINE TREKS CO\" is\n // still title-cased so the user gets \"Alpine Treks Co\".\n if (/[A-Z]/.test(trimmed.slice(1)) && /[a-z]/.test(trimmed)) return trimmed;\n const words = trimmed.split(/\\s+/);\n return words\n .map((word, index) => {\n const lower = word.toLowerCase();\n if (index > 0 && TITLE_CASE_SMALL_WORDS.has(lower)) return lower;\n if (word.length === 0) return word;\n return lower.charAt(0).toUpperCase() + lower.slice(1);\n })\n .join(' ');\n}\n\n/**\n * Banner tint as a CSS `style` object. The banner band uses the hue\n * at 16% alpha over the card surface so the band reads as colored in\n * both light and dark mode; the avatar initials use\n * `var(--color-text-primary)` so contrast holds against the card\n * surface in both themes. (Previously the per-type hue was used\n * directly for the initials, which was too low-contrast for some\n * chart-N slots in light mode.)\n *\n * Structurally compatible with React.CSSProperties — we don't import\n * React here to keep this util free of UI dependencies.\n *\n * Implementation note: colors are applied via inline `style` (not\n * Tailwind classes) because the host shell's Tailwind v4 build does\n * not emit utility classes for the theme's custom palette\n * (`bg-chart-N`, `bg-accent-*`, ...). The tokens resolve against the\n * active theme so contrast holds in light and dark mode without us\n * hard-coding hex values per theme.\n */\nexport interface BannerTintStyle {\n backgroundColor: string;\n color: string;\n}\n\n/** Build the style object for a hue token. */\nfunction tint(token: string): BannerTintStyle {\n return {\n backgroundColor: `color-mix(in srgb, ${token} 16%, transparent)`,\n color: 'var(--color-text-primary)',\n };\n}\n\n/**\n * Fixed banner hue per provider type. Color is keyed by type (not by\n * id), so every provider of a given type reads the same — the tint\n * becomes a type indicator at a glance. No green (reserved for the\n * \"Active\" status) and no red (reserved for errors).\n */\n/**\n * Chart-token slot per provider type. The actual hue is theme-dependent\n * (Forest/Ocean/Sunset/HC themes ship different chart-1..12 palettes), so\n * the card's banner reads as a type indicator via the design system rather\n * than a fixed hex. Light/dark contrast is the theme tokens' responsibility.\n *\n * Slots 3..7 are used so slots 1/2 stay free for brand accents and chart-\n * positive stays free for status success. If you add a new provider type,\n * pick the next free slot from this range; chart-8..12 stay reserved for\n * later additions.\n */\nconst TYPE_TINT_VAR: Record<GgnomadProviderType, string> = {\n HOTEL_OPERATOR: 'var(--color-chart-3)',\n TOUR_OPERATOR: 'var(--color-chart-4)',\n ACTIVITY_PROVIDER: 'var(--color-chart-5)',\n TRANSPORT_PROVIDER: 'var(--color-chart-6)',\n EVENT_ORGANIZER: 'var(--color-chart-7)',\n};\n\n/** Fallback for types outside the known enum (incl. future additions). */\nconst FALLBACK_TINT_VAR = 'var(--color-border-seam)';\n\n/** Banner tint for a provider type; unknown types get neutral slate. */\nexport function bannerTintFor(type: string): BannerTintStyle {\n // `in` guard mirrors labelForProviderType so an arbitrary string never\n // indexes the enum-keyed record without a check.\n const token = type in TYPE_TINT_VAR ? TYPE_TINT_VAR[type as GgnomadProviderType] : undefined;\n return tint(token ?? FALLBACK_TINT_VAR);\n}\n"],"mappings":";AA0CA,IAAM,IAAmF;CACvF,gBAAgB;EAAE,KAAK;EAAuC,UAAU;CAAiB;CACzF,eAAe;EAAE,KAAK;EAAsC,UAAU;CAAgB;CACtF,mBAAmB;EACjB,KAAK;EACL,UAAU;CACZ;CACA,oBAAoB;EAClB,KAAK;EACL,UAAU;CACZ;CACA,iBAAiB;EAAE,KAAK;EAAwC,UAAU;CAAkB;AAC9F,GAEM,IAA+B;CACnC;CACA;CACA;CACA;CACA;AACF;AAOA,SAAgB,EACd,GAIC;CACD,OAAO,EAAM,KAAK,MAAU;EAC1B,IAAM,EAAE,QAAK,gBAAa,EAAiB;EAC3C,OAAO;GAAE;GAAO,OAAO,IAAY,EAAU,GAAK,CAAQ,IAAI;EAAS;CACzE,CAAC;AACH;AAGA,SAAgB,EACd,GACA,GACQ;CACR,IAAI,KAAQ,GAAkB;EAC5B,IAAM,EAAE,QAAK,gBAAa,EAAiB;EAC3C,OAAO,IAAY,EAAU,GAAK,CAAQ,IAAI;CAChD;CACA,OAAO,EAAe,CAAI;AAC5B;AAGA,SAAS,EAAe,GAAuB;CAC7C,IAAM,IAAS,EAAM,QAAQ,OAAO,GAAG,EAAE,KAAK,EAAE,YAAY;CAE5D,OADK,IACE,EAAO,OAAO,CAAC,EAAE,YAAY,IAAI,EAAO,MAAM,CAAC,IADlC;AAEtB;AAUA,SAAgB,EAAY,GAAsB;CAChD,IAAM,IAAU,EAAK,KAAK;CAC1B,IAAI,CAAC,GAAS,OAAO;CAErB,IAAM,IAAQ,EAAQ,MAAM,KAAK,EAAE,OAAO,OAAO,GAC7C;CAMJ,OALA,AAGE,IAHE,EAAM,UAAU,KACR,EAAM,GAAG,OAAO,CAAC,IAAI,EAAM,GAAG,OAAO,CAAC,GAAG,YAAY,IAEtD,EAAM,GAAG,MAAM,GAAG,CAAC,EAAE,YAAY,GAErC,KAAU;AACnB;AAKA,SAAgB,EAAa,GAAuB;CAClD,IAAM,IAAU,EAAM,KAAK;CAC3B,IAAI,CAAC,GAAS,OAAO;CACrB,IAAM,IAAQ,EAAQ,YAAY;CAClC,OAAO,EAAM,OAAO,CAAC,EAAE,YAAY,IAAI,EAAM,MAAM,CAAC;AACtD;AAuEA,SAAS,EAAK,GAAgC;CAC5C,OAAO;EACL,iBAAiB,sBAAsB,EAAM;EAC7C,OAAO;CACT;AACF;AAmBA,IAAM,IAAqD;CACzD,gBAAgB;CAChB,eAAe;CACf,mBAAmB;CACnB,oBAAoB;CACpB,iBAAiB;AACnB,GAGM,IAAoB;AAG1B,SAAgB,EAAc,GAA+B;CAI3D,OAAO,GADO,KAAQ,IAAgB,EAAc,KAA+B,KAAA,MAC9D,CAAiB;AACxC"}
|
|
1
|
+
{"version":3,"file":"providerTypeMeta.js","names":[],"sources":["../../../src/ggnomad/utils/providerTypeMeta.ts"],"sourcesContent":["/**\n * Shared metadata for the GgnomadProviderType enum + provider visual\n * helpers.\n *\n * The single source of truth for the translation key + English fallback\n * for each provider type label, reused by both ProviderDashboardCard.tsx\n * (rendering) and CreateProviderDialog.tsx (form dropdown) so the wording\n * cannot drift between the two surfaces.\n *\n * If the backend ever adds a new value to GgnomadProviderType, add a\n * row here — anything unlisted falls back to a sentence-cased version of\n * the enum (\"HOTEL_OPERATOR\" -> \"Hotel operator\").\n *\n * Type-specific icons were intentionally removed: a single type\n * (\"Transport provider\") can cover buses, taxis, ferries and airlines,\n * so one icon per type is wrong. The card's banner is tinted by type\n * instead (see bannerTintFor) so the tint works as a type indicator.\n */\n\n/**\n * Local enum declaration. The published supergraph on Hive CDN still\n * declares `GgnomadProvider.type` as `String`, so the codegen-derived\n * types in src/generated/wspace-types.ts don't carry this enum yet. The\n * backend change (BOFF-8207) adds the enum to ggnomad.gql; once it ships\n * to alpha, the next codegen run will export this enum from\n * wspace-types and the local fallback below becomes a no-op. Keep the\n * two declarations in sync.\n */\nexport type GgnomadProviderType =\n | 'HOTEL_OPERATOR'\n | 'TOUR_OPERATOR'\n | 'ACTIVITY_PROVIDER'\n | 'TRANSPORT_PROVIDER'\n | 'EVENT_ORGANIZER';\n\n/**\n * Per-type translation keys + English fallbacks. The dropdown/filter chips\n * show these labels, and the same keys are reused by CreateProviderDialog.\n * If you add a value to GgnomadProviderType, add a key here and (eventually)\n * a translation in the locale files; the fallback covers English-only\n * locales.\n */\nconst KNOWN_LABEL_KEYS: Record<GgnomadProviderType, { key: string; fallback: string }> = {\n HOTEL_OPERATOR: { key: 'ggnomad.provider.type.hotelOperator', fallback: 'Hotel operator' },\n TOUR_OPERATOR: { key: 'ggnomad.provider.type.tourOperator', fallback: 'Tour operator' },\n ACTIVITY_PROVIDER: {\n key: 'ggnomad.provider.type.activityProvider',\n fallback: 'Activity provider',\n },\n TRANSPORT_PROVIDER: {\n key: 'ggnomad.provider.type.transportProvider',\n fallback: 'Transport provider',\n },\n EVENT_ORGANIZER: { key: 'ggnomad.provider.type.eventOrganizer', fallback: 'Event organizer' },\n};\n\nconst ORDER: GgnomadProviderType[] = [\n 'HOTEL_OPERATOR',\n 'TOUR_OPERATOR',\n 'ACTIVITY_PROVIDER',\n 'TRANSPORT_PROVIDER',\n 'EVENT_ORGANIZER',\n];\n\n/**\n * Build the dropdown options. `translate` should be the `t` from `useI18n()` in\n * the calling component so each label resolves in the active locale; the\n * fallback string is used otherwise (or when the translator is omitted).\n */\nexport function getProviderTypeOptions(\n translate?: (key: string, fallback: string) => string\n): Array<{\n value: GgnomadProviderType;\n label: string;\n}> {\n return ORDER.map((value) => {\n const { key, fallback } = KNOWN_LABEL_KEYS[value];\n return { value, label: translate ? translate(key, fallback) : fallback };\n });\n}\n\n/** Human-facing label for a provider type, with a safe fallback. */\nexport function labelForProviderType(\n type: string,\n translate?: (key: string, fallback: string) => string\n): string {\n if (type in KNOWN_LABEL_KEYS) {\n const { key, fallback } = KNOWN_LABEL_KEYS[type as GgnomadProviderType];\n return translate ? translate(key, fallback) : fallback;\n }\n return enumToSentence(type);\n}\n\n/** \"HOTEL_OPERATOR\" -> \"Hotel operator\", \"OTHER\" -> \"Other\". */\nfunction enumToSentence(value: string): string {\n const spaced = value.replace(/_+/g, ' ').trim().toLowerCase();\n if (!spaced) return value;\n return spaced.charAt(0).toUpperCase() + spaced.slice(1);\n}\n\n/**\n * Two-letter initials for the avatar fallback.\n *\n * - \"Indigo\" -> \"IN\" (first two letters of the single word)\n * - \"Blue Sky\" -> \"BS\" (first letter of each of the first two words)\n * - \"A\" -> \"A\" (single character preserved as-is)\n * - \"\" -> \"??\" (sentinel for empty names)\n */\nexport function initialsFor(name: string): string {\n const cleaned = name.trim();\n if (!cleaned) return '??';\n\n const words = cleaned.split(/\\s+/).filter(Boolean);\n let result: string;\n if (words.length >= 2) {\n result = (words[0].charAt(0) + words[1].charAt(0)).toUpperCase();\n } else {\n result = words[0].slice(0, 2).toUpperCase();\n }\n return result || '??';\n}\n\n/** \"alpine\" -> \"Alpine\", \"SUSPENDED\" -> \"Suspended\". Lowercases the input\n * first so the function normalizes backend enum values (which arrive\n * in UPPER_SNAKE_CASE) to a sentence-case display label. */\nexport function sentenceCase(value: string): string {\n const trimmed = value.trim();\n if (!trimmed) return '';\n const lower = trimmed.toLowerCase();\n return lower.charAt(0).toUpperCase() + lower.slice(1);\n}\n\n/** \"blue sky adventures\" -> \"Blue Sky Adventures\". Title-cases each word,\n * leaving small connector words (\"of\", \"the\", \"and\", \"a\", \"an\", \"in\", \"on\",\n * \"for\", \"to\") lowercase unless they are the first word. Used to display\n * user-entered provider names. */\nconst TITLE_CASE_SMALL_WORDS = new Set([\n 'a',\n 'an',\n 'and',\n 'as',\n 'at',\n 'but',\n 'by',\n 'for',\n 'in',\n 'nor',\n 'of',\n 'on',\n 'or',\n 'so',\n 'the',\n 'to',\n 'up',\n 'yet',\n]);\n\nexport function titleCase(value: string): string {\n const trimmed = value.trim();\n if (!trimmed) return '';\n // Preserve acronyms and proper nouns the user has already chosen the\n // casing for (\"McDonald's\", \"DHL Express\"): only short-circuit on\n // *mixed-case* input. Pure all-caps input like \"ALPINE TREKS CO\" is\n // still title-cased so the user gets \"Alpine Treks Co\".\n if (/[A-Z]/.test(trimmed.slice(1)) && /[a-z]/.test(trimmed)) return trimmed;\n const words = trimmed.split(/\\s+/);\n return words\n .map((word, index) => {\n const lower = word.toLowerCase();\n if (index > 0 && TITLE_CASE_SMALL_WORDS.has(lower)) return lower;\n if (word.length === 0) return word;\n return lower.charAt(0).toUpperCase() + lower.slice(1);\n })\n .join(' ');\n}\n\n/**\n * Banner tint as a CSS `style` object. The banner band uses the hue\n * at 16% alpha over the card surface so the band reads as colored in\n * both light and dark mode; the avatar initials use\n * `var(--color-text-primary)` so contrast holds against the card\n * surface in both themes. (Previously the per-type hue was used\n * directly for the initials, which was too low-contrast for some\n * chart-N slots in light mode.)\n *\n * Structurally compatible with React.CSSProperties — we don't import\n * React here to keep this util free of UI dependencies.\n *\n * Implementation note: colors are applied via inline `style` (not\n * Tailwind classes) because the host shell's Tailwind v4 build does\n * not emit utility classes for the theme's custom palette\n * (`bg-chart-N`, `bg-accent-*`, ...). The tokens resolve against the\n * active theme so contrast holds in light and dark mode without us\n * hard-coding hex values per theme.\n */\nexport interface BannerTintStyle {\n backgroundColor: string;\n color: string;\n}\n\n/** Build the style object for a hue token. */\nfunction tint(token: string): BannerTintStyle {\n return {\n backgroundColor: `color-mix(in srgb, ${token} 16%, transparent)`,\n color: 'var(--color-text-primary)',\n };\n}\n\n/**\n * Fixed banner hue per provider type. Color is keyed by type (not by\n * id), so every provider of a given type reads the same — the tint\n * becomes a type indicator at a glance. No green (reserved for the\n * \"Active\" status) and no red (reserved for errors).\n */\n/**\n * Chart-token slot per provider type. The actual hue is theme-dependent\n * (Forest/Ocean/Sunset/HC themes ship different chart-1..12 palettes), so\n * the card's banner reads as a type indicator via the design system rather\n * than a fixed hex. Light/dark contrast is the theme tokens' responsibility.\n *\n * Slots 3..7 are used so slots 1/2 stay free for brand accents and chart-\n * positive stays free for status success. If you add a new provider type,\n * pick the next free slot from this range; chart-8..12 stay reserved for\n * later additions.\n */\nconst TYPE_TINT_VAR: Record<GgnomadProviderType, string> = {\n HOTEL_OPERATOR: 'var(--color-chart-3)',\n TOUR_OPERATOR: 'var(--color-chart-4)',\n ACTIVITY_PROVIDER: 'var(--color-chart-5)',\n TRANSPORT_PROVIDER: 'var(--color-chart-6)',\n EVENT_ORGANIZER: 'var(--color-chart-7)',\n};\n\n/** Fallback for types outside the known enum (incl. future additions). */\nconst FALLBACK_TINT_VAR = 'var(--color-border-seam)';\n\n/** Banner tint for a provider type; unknown types get neutral slate. */\nexport function bannerTintFor(type: string): BannerTintStyle {\n // `in` guard mirrors labelForProviderType so an arbitrary string never\n // indexes the enum-keyed record without a check.\n const token = type in TYPE_TINT_VAR ? TYPE_TINT_VAR[type as GgnomadProviderType] : undefined;\n return tint(token ?? FALLBACK_TINT_VAR);\n}\n"],"mappings":";AA0CA,IAAM,IAAmF;CACvF,gBAAgB;EAAE,KAAK;EAAuC,UAAU;CAAiB;CACzF,eAAe;EAAE,KAAK;EAAsC,UAAU;CAAgB;CACtF,mBAAmB;EACjB,KAAK;EACL,UAAU;CACZ;CACA,oBAAoB;EAClB,KAAK;EACL,UAAU;CACZ;CACA,iBAAiB;EAAE,KAAK;EAAwC,UAAU;CAAkB;AAC9F,GAEM,IAA+B;CACnC;CACA;CACA;CACA;CACA;AACF;AAOA,SAAgB,EACd,GAIC;CACD,OAAO,EAAM,KAAK,MAAU;EAC1B,IAAM,EAAE,QAAK,gBAAa,EAAiB;EAC3C,OAAO;GAAE;GAAO,OAAO,IAAY,EAAU,GAAK,CAAQ,IAAI;EAAS;CACzE,CAAC;AACH;AAGA,SAAgB,EACd,GACA,GACQ;CACR,IAAI,KAAQ,GAAkB;EAC5B,IAAM,EAAE,QAAK,gBAAa,EAAiB;EAC3C,OAAO,IAAY,EAAU,GAAK,CAAQ,IAAI;CAChD;CACA,OAAO,EAAe,CAAI;AAC5B;AAGA,SAAS,EAAe,GAAuB;CAC7C,IAAM,IAAS,EAAM,QAAQ,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,YAAY;CAE5D,OADK,IACE,EAAO,OAAO,CAAC,CAAC,CAAC,YAAY,IAAI,EAAO,MAAM,CAAC,IADlC;AAEtB;AAUA,SAAgB,EAAY,GAAsB;CAChD,IAAM,IAAU,EAAK,KAAK;CAC1B,IAAI,CAAC,GAAS,OAAO;CAErB,IAAM,IAAQ,EAAQ,MAAM,KAAK,CAAC,CAAC,OAAO,OAAO,GAC7C;CAMJ,OALA,AAGE,IAHE,EAAM,UAAU,KACR,EAAM,EAAE,CAAC,OAAO,CAAC,IAAI,EAAM,EAAE,CAAC,OAAO,CAAC,EAAA,CAAG,YAAY,IAEtD,EAAM,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,YAAY,GAErC,KAAU;AACnB;AAKA,SAAgB,EAAa,GAAuB;CAClD,IAAM,IAAU,EAAM,KAAK;CAC3B,IAAI,CAAC,GAAS,OAAO;CACrB,IAAM,IAAQ,EAAQ,YAAY;CAClC,OAAO,EAAM,OAAO,CAAC,CAAC,CAAC,YAAY,IAAI,EAAM,MAAM,CAAC;AACtD;AAuEA,SAAS,EAAK,GAAgC;CAC5C,OAAO;EACL,iBAAiB,sBAAsB,EAAM;EAC7C,OAAO;CACT;AACF;AAmBA,IAAM,IAAqD;CACzD,gBAAgB;CAChB,eAAe;CACf,mBAAmB;CACnB,oBAAoB;CACpB,iBAAiB;AACnB,GAGM,IAAoB;AAG1B,SAAgB,EAAc,GAA+B;CAI3D,OAAO,GADO,KAAQ,IAAgB,EAAc,KAA+B,KAAA,MAC9D,CAAiB;AACxC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pageContext.js","names":[],"sources":["../../src/utils/pageContext.ts"],"sourcesContent":["/**\n * Page context gathering for the GGNomad AI assistant.\n *\n * Captures the user's current browser context (URL, route, entity) so the\n * floating assistant can answer \"this page\" / \"current\" questions and run\n * operations scoped to the screen the user is looking at.\n */\n\nexport interface PageContext {\n currentUrl: string;\n currentPage: string;\n entityType?: string;\n entityId?: string;\n platform: string;\n version: string;\n viewportSize: string;\n userAgent: string;\n timestamp: string;\n}\n\ninterface ConversationContextShape {\n entityType?: string;\n entityId?: string;\n activeTab?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport function gatherPageContext(): PageContext {\n const url = window.location.href;\n const pathSegments = window.location.pathname.split('/').filter(Boolean);\n const currentPage = pathSegments.slice(0, 3).join('/') || 'home';\n const entity = resolveGGNomadConversationContext(\n window.location.pathname,\n window.location.search\n );\n\n return {\n currentUrl: url,\n currentPage,\n entityType: entity.entityType,\n entityId: entity.entityId,\n platform: 'ggnomad',\n version: import.meta.env.VITE_APP_VERSION ?? '2026.420.95',\n viewportSize: `${window.innerWidth}x${window.innerHeight}`,\n userAgent: navigator.userAgent,\n timestamp: new Date().toISOString(),\n };\n}\n\nfunction matchPath(pattern: RegExp, pathname: string): RegExpMatchArray | null {\n return pathname.match(pattern);\n}\n\nexport function resolveGGNomadConversationContext(\n pathname: string,\n search: string\n): ConversationContextShape {\n const params = new URLSearchParams(search);\n\n const directMatches: Array<[RegExp, string]> = [\n [/^\\/ggnomad\\/hospitality\\/([^/]+)$/, 'GGNOMAD_PROPERTY'],\n [/^\\/ggnomad\\/tourism\\/tours\\/([^/]+)$/, 'GGNOMAD_TOUR'],\n [/^\\/ggnomad\\/tourism\\/activities\\/([^/]+)$/, 'GGNOMAD_ACTIVITY'],\n [/^\\/ggnomad\\/entertainment\\/([^/]+)$/, 'GGNOMAD_EVENT'],\n [/^\\/ggnomad\\/trips\\/([^/]+)$/, 'GGNOMAD_TRIP_PLAN'],\n ];\n\n for (const [pattern, entityType] of directMatches) {\n const match = matchPath(pattern, pathname);\n if (match) {\n return {\n entityType,\n entityId: match[1],\n metadata: { routePattern: pattern.source },\n };\n }\n }\n\n const activeTab =\n params.get('tab') ??\n (() => {\n const segments = pathname.split('/').filter(Boolean);\n return segments.length > 2 ? segments[segments.length - 1] : undefined;\n })();\n\n return {\n activeTab: activeTab ?? undefined,\n metadata: {\n ...(activeTab ? { activeTab } : {}),\n ...(params.get('q') ? { searchQuery: params.get('q') } : {}),\n },\n };\n}\n"],"mappings":";AA2BA,SAAgB,IAAiC;CAC/C,IAAM,IAAM,OAAO,SAAS,MAEtB,IADe,OAAO,SAAS,SAAS,MAAM,GAAG,
|
|
1
|
+
{"version":3,"file":"pageContext.js","names":[],"sources":["../../src/utils/pageContext.ts"],"sourcesContent":["/**\n * Page context gathering for the GGNomad AI assistant.\n *\n * Captures the user's current browser context (URL, route, entity) so the\n * floating assistant can answer \"this page\" / \"current\" questions and run\n * operations scoped to the screen the user is looking at.\n */\n\nexport interface PageContext {\n currentUrl: string;\n currentPage: string;\n entityType?: string;\n entityId?: string;\n platform: string;\n version: string;\n viewportSize: string;\n userAgent: string;\n timestamp: string;\n}\n\ninterface ConversationContextShape {\n entityType?: string;\n entityId?: string;\n activeTab?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport function gatherPageContext(): PageContext {\n const url = window.location.href;\n const pathSegments = window.location.pathname.split('/').filter(Boolean);\n const currentPage = pathSegments.slice(0, 3).join('/') || 'home';\n const entity = resolveGGNomadConversationContext(\n window.location.pathname,\n window.location.search\n );\n\n return {\n currentUrl: url,\n currentPage,\n entityType: entity.entityType,\n entityId: entity.entityId,\n platform: 'ggnomad',\n version: import.meta.env.VITE_APP_VERSION ?? '2026.420.95',\n viewportSize: `${window.innerWidth}x${window.innerHeight}`,\n userAgent: navigator.userAgent,\n timestamp: new Date().toISOString(),\n };\n}\n\nfunction matchPath(pattern: RegExp, pathname: string): RegExpMatchArray | null {\n return pathname.match(pattern);\n}\n\nexport function resolveGGNomadConversationContext(\n pathname: string,\n search: string\n): ConversationContextShape {\n const params = new URLSearchParams(search);\n\n const directMatches: Array<[RegExp, string]> = [\n [/^\\/ggnomad\\/hospitality\\/([^/]+)$/, 'GGNOMAD_PROPERTY'],\n [/^\\/ggnomad\\/tourism\\/tours\\/([^/]+)$/, 'GGNOMAD_TOUR'],\n [/^\\/ggnomad\\/tourism\\/activities\\/([^/]+)$/, 'GGNOMAD_ACTIVITY'],\n [/^\\/ggnomad\\/entertainment\\/([^/]+)$/, 'GGNOMAD_EVENT'],\n [/^\\/ggnomad\\/trips\\/([^/]+)$/, 'GGNOMAD_TRIP_PLAN'],\n ];\n\n for (const [pattern, entityType] of directMatches) {\n const match = matchPath(pattern, pathname);\n if (match) {\n return {\n entityType,\n entityId: match[1],\n metadata: { routePattern: pattern.source },\n };\n }\n }\n\n const activeTab =\n params.get('tab') ??\n (() => {\n const segments = pathname.split('/').filter(Boolean);\n return segments.length > 2 ? segments[segments.length - 1] : undefined;\n })();\n\n return {\n activeTab: activeTab ?? undefined,\n metadata: {\n ...(activeTab ? { activeTab } : {}),\n ...(params.get('q') ? { searchQuery: params.get('q') } : {}),\n },\n };\n}\n"],"mappings":";AA2BA,SAAgB,IAAiC;CAC/C,IAAM,IAAM,OAAO,SAAS,MAEtB,IADe,OAAO,SAAS,SAAS,MAAM,GAAG,CAAC,CAAC,OAAO,OAC5C,CAAA,CAAa,MAAM,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,KAAK,QACpD,IAAS,EACb,OAAO,SAAS,UAChB,OAAO,SAAS,MAClB;CAEA,OAAO;EACL,YAAY;EACZ;EACA,YAAY,EAAO;EACnB,UAAU,EAAO;EACjB,UAAU;EACV,SAA6C;EAC7C,cAAc,GAAG,OAAO,WAAW,GAAG,OAAO;EAC7C,WAAW,UAAU;EACrB,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY;CACpC;AACF;AAEA,SAAS,EAAU,GAAiB,GAA2C;CAC7E,OAAO,EAAS,MAAM,CAAO;AAC/B;AAEA,SAAgB,EACd,GACA,GAC0B;CAC1B,IAAM,IAAS,IAAI,gBAAgB,CAAM;CAUzC,KAAK,IAAM,CAAC,GAAS,MAAe;EAPlC,CAAC,qCAAqC,kBAAkB;EACxD,CAAC,wCAAwC,cAAc;EACvD,CAAC,6CAA6C,kBAAkB;EAChE,CAAC,uCAAuC,eAAe;EACvD,CAAC,+BAA+B,mBAAmB;CAGjB,GAAe;EACjD,IAAM,IAAQ,EAAU,GAAS,CAAQ;EACzC,IAAI,GACF,OAAO;GACL;GACA,UAAU,EAAM;GAChB,UAAU,EAAE,cAAc,EAAQ,OAAO;EAC3C;CAEJ;CAEA,IAAM,IACJ,EAAO,IAAI,KAAK,YACT;EACL,IAAM,IAAW,EAAS,MAAM,GAAG,CAAC,CAAC,OAAO,OAAO;EACnD,OAAO,EAAS,SAAS,IAAI,EAAS,EAAS,SAAS,KAAK,KAAA;CAC/D,EAAA,CAAG;CAEL,OAAO;EACL,WAAW,KAAa,KAAA;EACxB,UAAU;GACR,GAAI,IAAY,EAAE,aAAU,IAAI,CAAC;GACjC,GAAI,EAAO,IAAI,GAAG,IAAI,EAAE,aAAa,EAAO,IAAI,GAAG,EAAE,IAAI,CAAC;EAC5D;CACF;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@burdenoff/microfe-ggnomad",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.1029.2",
|
|
4
4
|
"description": "Burdenoff GGNomad Microfrontend",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -56,11 +56,11 @@
|
|
|
56
56
|
"@graphql-codegen/typescript": "^4.0.0",
|
|
57
57
|
"@graphql-codegen/typescript-operations": "^4.0.0",
|
|
58
58
|
"@graphql-codegen/typescript-react-apollo": "^4.0.0",
|
|
59
|
-
"@storybook/addon-essentials": "8.6.
|
|
60
|
-
"@storybook/addon-interactions": "8.6.
|
|
61
|
-
"@storybook/react": "8.6.
|
|
62
|
-
"@storybook/react-vite": "8.6.
|
|
63
|
-
"@storybook/test": "8.6.
|
|
59
|
+
"@storybook/addon-essentials": "8.6.19",
|
|
60
|
+
"@storybook/addon-interactions": "8.6.19",
|
|
61
|
+
"@storybook/react": "8.6.19",
|
|
62
|
+
"@storybook/react-vite": "8.6.19",
|
|
63
|
+
"@storybook/test": "8.6.19",
|
|
64
64
|
"@storybook/test-runner": "0.24.4",
|
|
65
65
|
"@tailwindcss/vite": "^4.3.2",
|
|
66
66
|
"@types/react": "^19.0.2",
|
|
@@ -76,11 +76,11 @@
|
|
|
76
76
|
"lefthook": "^2.1.1",
|
|
77
77
|
"postcss": "^8.4.49",
|
|
78
78
|
"prettier": "^3.4.2",
|
|
79
|
-
"storybook": "8.6.
|
|
79
|
+
"storybook": "8.6.19",
|
|
80
80
|
"tailwindcss": "^4.0.0",
|
|
81
81
|
"typescript": "^5.9.0",
|
|
82
82
|
"typescript-eslint": "^8.54.0",
|
|
83
|
-
"vite": "8.
|
|
83
|
+
"vite": "8.1.6",
|
|
84
84
|
"vite-plugin-dts": "3.9.1",
|
|
85
85
|
"wait-on": "^8.0.4"
|
|
86
86
|
},
|
|
@@ -104,14 +104,14 @@
|
|
|
104
104
|
"access": "public"
|
|
105
105
|
},
|
|
106
106
|
"overrides": {
|
|
107
|
-
"@storybook/addon-essentials": "8.6.
|
|
108
|
-
"@storybook/addon-interactions": "8.6.
|
|
109
|
-
"@storybook/core": "8.6.
|
|
110
|
-
"@storybook/instrumenter": "8.6.
|
|
111
|
-
"@storybook/react": "8.6.
|
|
112
|
-
"@storybook/react-vite": "8.6.
|
|
113
|
-
"@storybook/test": "8.6.
|
|
114
|
-
"storybook": "8.6.
|
|
107
|
+
"@storybook/addon-essentials": "8.6.19",
|
|
108
|
+
"@storybook/addon-interactions": "8.6.19",
|
|
109
|
+
"@storybook/core": "8.6.19",
|
|
110
|
+
"@storybook/instrumenter": "8.6.19",
|
|
111
|
+
"@storybook/react": "8.6.19",
|
|
112
|
+
"@storybook/react-vite": "8.6.19",
|
|
113
|
+
"@storybook/test": "8.6.19",
|
|
114
|
+
"storybook": "8.6.19",
|
|
115
115
|
"lodash": ">=4.17.21",
|
|
116
116
|
"lodash-es": ">=4.17.21",
|
|
117
117
|
"dompurify": ">=3.3.4",
|
|
@@ -126,6 +126,7 @@
|
|
|
126
126
|
"yaml": ">=2.8.3",
|
|
127
127
|
"picomatch": ">=2.3.2",
|
|
128
128
|
"immutable": ">=3.8.3",
|
|
129
|
-
"minimatch": ">=3.1.3"
|
|
129
|
+
"minimatch": ">=3.1.3",
|
|
130
|
+
"zod": "^4.6.7"
|
|
130
131
|
}
|
|
131
132
|
}
|