@burdenoff/microfe-ggnomad 2026.728.2 → 2026.730.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.
- package/dist/ggnomad/GGNomadRoutes.d.ts.map +1 -1
- package/dist/ggnomad/GGNomadRoutes.js +13 -5
- package/dist/ggnomad/GGNomadRoutes.js.map +1 -1
- package/dist/ggnomad/graphql.d.ts +6 -0
- package/dist/ggnomad/graphql.d.ts.map +1 -1
- package/dist/ggnomad/graphql.js +139 -29
- package/dist/ggnomad/graphql.js.map +1 -1
- package/dist/ggnomad/pages/ProviderAnalyticsPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/ProviderAnalyticsPage.js +2 -2
- package/dist/ggnomad/pages/ProviderAnalyticsPage.js.map +1 -1
- package/dist/ggnomad/pages/ProviderPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/ProviderPage.js +50 -41
- package/dist/ggnomad/pages/ProviderPage.js.map +1 -1
- package/dist/ggnomad/pages/StorefrontPage.d.ts +11 -0
- package/dist/ggnomad/pages/StorefrontPage.d.ts.map +1 -0
- package/dist/ggnomad/pages/StorefrontPage.js +252 -0
- package/dist/ggnomad/pages/StorefrontPage.js.map +1 -0
- package/dist/ggnomad/pages/StorefrontPublicPage.d.ts +9 -0
- package/dist/ggnomad/pages/StorefrontPublicPage.d.ts.map +1 -0
- package/dist/ggnomad/pages/StorefrontPublicPage.js +98 -0
- package/dist/ggnomad/pages/StorefrontPublicPage.js.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProviderAnalyticsPage.js","names":[],"sources":["../../../src/ggnomad/pages/ProviderAnalyticsPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useQuery } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { BarChart3, CalendarCheck, DollarSign, Gauge, TrendingUp, XCircle } from 'lucide-react';\nimport { IllustratedEmptyState, PagePurpose, Spinner } from '@burdenoff/fe-libs/ui';\n\nimport { GGNOMAD_MY_PROVIDERS_QUERY, GGNOMAD_MY_PROVIDER_ANALYTICS_QUERY } from '../graphql';\nimport { PageShell } from './PageShell';\nimport { SectionCard, StatCard } from '../components';\n\nconst CHART_COLORS = [\n 'var(--color-chart-1)',\n 'var(--color-chart-2)',\n 'var(--color-chart-3)',\n 'var(--color-chart-4)',\n 'var(--color-chart-5)',\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\ntype MyProvider = { id: string; name: string; type: string; status: string };\n\ntype AnalyticsPoint = { period: string; bookings: number; revenue: number };\n\ntype ListingAnalytics = {\n listingId: string;\n listingType: string;\n listingName: string;\n bookings: number;\n revenue: number;\n cancellationRate: number;\n capacityUtilizationRate: number;\n};\n\ntype ProviderAnalytics = {\n providerId: string;\n providerName: string;\n totalBookings: number;\n totalRevenue: number;\n cancelledBookings: number;\n cancellationRate: number;\n capacityUtilizationRate: number;\n bookingsOverTime: AnalyticsPoint[];\n topListings: ListingAnalytics[];\n};\n\nfunction toISODate(date: Date): string {\n return date.toISOString().slice(0, 10);\n}\n\nfunction daysAgo(days: number): Date {\n return new Date(Date.now() - days * 24 * 60 * 60 * 1000);\n}\n\n/**\n * BOFF-4390 — provider-facing analytics dashboard: bookings-over-time,\n * revenue, cancellation rate and capacity utilization, plus a per-listing\n * drill-down table. Self-scoped: only providers the calling actor created\n * are listed, and the analytics call is owner-verified server-side — a\n * provider can never see another provider's numbers. Forecasting and\n * custom-report building are explicitly out of scope for this core; see the\n * `myGgnomadProviderAnalytics` schema doc comment.\n */\nexport function ProviderAnalyticsPage() {\n const { t } = useI18n();\n\n const { data: providersData, loading: providersLoading } = useQuery(\n GGNOMAD_MY_PROVIDERS_QUERY as TypedDocumentNode<\n { myGgnomadProviders: MyProvider[] },\n Record<string, never>\n >\n );\n const providers = providersData?.myGgnomadProviders ?? [];\n\n const [selectedProviderId, setSelectedProviderId] = useState('');\n const [dateFrom, setDateFrom] = useState(() => toISODate(daysAgo(30)));\n const [dateTo, setDateTo] = useState(() => toISODate(new Date()));\n const [granularity, setGranularity] = useState<'day' | 'week' | 'month'>('day');\n\n const activeProviderId = selectedProviderId || providers[0]?.id || '';\n\n const { data: analyticsData, loading: analyticsLoading } = useQuery(\n GGNOMAD_MY_PROVIDER_ANALYTICS_QUERY as TypedDocumentNode<\n { myGgnomadProviderAnalytics: ProviderAnalytics },\n { providerId: string; dateFrom?: string; dateTo?: string; granularity?: string }\n >,\n {\n variables: { providerId: activeProviderId, dateFrom, dateTo, granularity },\n skip: !activeProviderId,\n }\n );\n const analytics = analyticsData?.myGgnomadProviderAnalytics;\n\n const maxTrend = useMemo(() => {\n const points = analytics?.bookingsOverTime ?? [];\n return points.length > 0 ? Math.max(...points.map((p) => p.bookings)) : 0;\n }, [analytics?.bookingsOverTime]);\n\n return (\n <PageShell\n title={t('ggnomad.providerAnalytics.title', 'Provider Analytics')}\n titleAccent={t('ggnomad.providerAnalytics.title.accent', 'Analytics')}\n subtitle={t(\n 'ggnomad.providerAnalytics.subtitle',\n 'Your bookings, revenue and listing performance'\n )}\n >\n <PagePurpose icon={<TrendingUp className=\"size-4\" />}>\n {t(\n 'ggnomad.providerAnalytics.purpose',\n 'A self-service dashboard for providers: bookings and revenue over time, cancellation rate, capacity utilization, and a per-listing breakdown — scoped to only the listings you own. Pick a date range and a listing granularity to drill in.'\n )}\n </PagePurpose>\n\n {!providersLoading && providers.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title={t('ggnomad.providerAnalytics.emptyTitle', 'No provider profile yet')}\n description={t(\n 'ggnomad.providerAnalytics.emptyDescription',\n 'Once you create a GGNomad provider profile, your bookings and revenue analytics will appear here.'\n )}\n />\n ) : (\n <>\n <SectionCard title={t('ggnomad.providerAnalytics.filters', 'Filters')}>\n <div className=\"grid gap-3 sm:grid-cols-4\">\n <div>\n <label\n htmlFor=\"provider-analytics-provider\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.providerAnalytics.provider', 'Provider')}\n </label>\n <select\n id=\"provider-analytics-provider\"\n value={activeProviderId}\n onChange={(e) => setSelectedProviderId(e.target.value)}\n className={INPUT_CLASSES}\n >\n {providers.map((provider) => (\n <option key={provider.id} value={provider.id}>\n {provider.name}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label\n htmlFor=\"provider-analytics-from\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.providerAnalytics.from', 'From')}\n </label>\n <input\n id=\"provider-analytics-from\"\n type=\"date\"\n value={dateFrom}\n max={dateTo}\n onChange={(e) => setDateFrom(e.target.value)}\n className={INPUT_CLASSES}\n />\n </div>\n <div>\n <label\n htmlFor=\"provider-analytics-to\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.providerAnalytics.to', 'To')}\n </label>\n <input\n id=\"provider-analytics-to\"\n type=\"date\"\n value={dateTo}\n min={dateFrom}\n max={toISODate(new Date())}\n onChange={(e) => setDateTo(e.target.value)}\n className={INPUT_CLASSES}\n />\n </div>\n <div>\n <label\n htmlFor=\"provider-analytics-granularity\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.providerAnalytics.granularity', 'Granularity')}\n </label>\n <select\n id=\"provider-analytics-granularity\"\n value={granularity}\n onChange={(e) => setGranularity(e.target.value as 'day' | 'week' | 'month')}\n className={INPUT_CLASSES}\n >\n <option value=\"day\">{t('ggnomad.providerAnalytics.day', 'Day')}</option>\n <option value=\"week\">{t('ggnomad.providerAnalytics.week', 'Week')}</option>\n <option value=\"month\">{t('ggnomad.providerAnalytics.month', 'Month')}</option>\n </select>\n </div>\n </div>\n </SectionCard>\n\n {analyticsLoading ? (\n <div className=\"flex justify-center py-12\">\n <Spinner size=\"lg\" />\n </div>\n ) : !analytics ? (\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title={t('ggnomad.providerAnalytics.noDataTitle', 'No bookings in this range')}\n description={t(\n 'ggnomad.providerAnalytics.noDataDescription',\n 'Try widening the date range — analytics appear here once bookings come in for your listings.'\n )}\n />\n ) : (\n <>\n <section className=\"grid gap-4 sm:grid-cols-2 xl:grid-cols-4\">\n <StatCard\n hero\n label={t('ggnomad.providerAnalytics.totalBookings', 'Bookings')}\n value={analytics.totalBookings.toLocaleString()}\n icon={<CalendarCheck />}\n />\n <StatCard\n label={t('ggnomad.providerAnalytics.totalRevenue', 'Revenue')}\n value={`$${Number(analytics.totalRevenue).toLocaleString()}`}\n icon={<DollarSign />}\n />\n <StatCard\n label={t('ggnomad.providerAnalytics.cancellationRate', 'Cancellation rate')}\n value={`${(analytics.cancellationRate * 100).toFixed(1)}%`}\n icon={<XCircle />}\n />\n <StatCard\n label={t('ggnomad.providerAnalytics.capacityUtilization', 'Capacity utilization')}\n value={`${(analytics.capacityUtilizationRate * 100).toFixed(1)}%`}\n icon={<Gauge />}\n />\n </section>\n\n <SectionCard\n title={t('ggnomad.providerAnalytics.bookingsOverTime', 'Bookings over time')}\n >\n {analytics.bookingsOverTime.length === 0 ? (\n <div className=\"flex flex-col items-center gap-2 py-8 text-center\">\n <span className=\"flex size-10 items-center justify-center rounded-full bg-[var(--color-accent-soft)] text-text-secondary\">\n <BarChart3 className=\"size-5\" />\n </span>\n <p className=\"text-sm font-medium text-text-primary\">\n {t('ggnomad.providerAnalytics.trendEmpty', 'No bookings in this window yet')}\n </p>\n </div>\n ) : (\n <div className=\"flex flex-col gap-4\">\n <div className=\"flex items-end gap-2 overflow-x-auto pb-2\">\n {analytics.bookingsOverTime.map((point, index) => {\n const pct =\n maxTrend > 0 ? Math.max((point.bookings / maxTrend) * 100, 5) : 5;\n return (\n <div key={point.period} className=\"flex flex-col items-center gap-1\">\n <div className=\"flex h-24 items-end\">\n <div\n className=\"w-8 rounded-t transition-all duration-500 ease-out\"\n style={{\n height: `${pct}%`,\n backgroundColor: CHART_COLORS[index % CHART_COLORS.length],\n }}\n title={`${point.period}: ${point.bookings} bookings, $${point.revenue.toLocaleString()} revenue`}\n />\n </div>\n <p className=\"truncate text-xs text-text-secondary\">{point.period}</p>\n </div>\n );\n })}\n </div>\n <p className=\"text-xs text-text-secondary\">\n {t(\n 'ggnomad.providerAnalytics.trendNote',\n 'Bar height is booking volume per period; hover a bar for its exact bookings and revenue.'\n )}\n </p>\n </div>\n )}\n </SectionCard>\n\n <SectionCard title={t('ggnomad.providerAnalytics.topListings', 'Top listings')}>\n {analytics.topListings.length === 0 ? (\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n {t(\n 'ggnomad.providerAnalytics.topListingsEmpty',\n 'No listing bookings in this range yet.'\n )}\n </p>\n ) : (\n <div className=\"overflow-x-auto\">\n <table className=\"w-full min-w-[560px] text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam text-left text-text-secondary\">\n <th className=\"py-2 pr-3 font-medium\">\n {t('ggnomad.providerAnalytics.listing', 'Listing')}\n </th>\n <th className=\"py-2 pr-3 font-medium\">\n {t('ggnomad.providerAnalytics.type', 'Type')}\n </th>\n <th className=\"py-2 pr-3 text-right font-medium\">\n {t('ggnomad.providerAnalytics.bookings', 'Bookings')}\n </th>\n <th className=\"py-2 pr-3 text-right font-medium\">\n {t('ggnomad.providerAnalytics.revenue', 'Revenue')}\n </th>\n <th className=\"py-2 pr-3 text-right font-medium\">\n {t('ggnomad.providerAnalytics.cancellation', 'Cancellation')}\n </th>\n <th className=\"py-2 text-right font-medium\">\n {t('ggnomad.providerAnalytics.utilization', 'Utilization')}\n </th>\n </tr>\n </thead>\n <tbody>\n {analytics.topListings.map((listing) => (\n <tr\n key={listing.listingId}\n className=\"border-b border-border-seam/50 last:border-0\"\n >\n <td className=\"py-2 pr-3 font-medium text-text-primary\">\n {listing.listingName}\n </td>\n <td className=\"py-2 pr-3 text-text-secondary\">{listing.listingType}</td>\n <td className=\"py-2 pr-3 text-right tabular-nums text-text-primary\">\n {listing.bookings}\n </td>\n <td className=\"py-2 pr-3 text-right tabular-nums text-text-primary\">\n ${listing.revenue.toLocaleString()}\n </td>\n <td className=\"py-2 pr-3 text-right tabular-nums text-text-primary\">\n {(listing.cancellationRate * 100).toFixed(0)}%\n </td>\n <td className=\"py-2 text-right tabular-nums text-text-primary\">\n {(listing.capacityUtilizationRate * 100).toFixed(0)}%\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </SectionCard>\n </>\n )}\n </>\n )}\n </PageShell>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ProviderAnalyticsPage.js","names":[],"sources":["../../../src/ggnomad/pages/ProviderAnalyticsPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useQuery } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { BarChart3, CalendarCheck, DollarSign, Gauge, TrendingUp, XCircle } from 'lucide-react';\nimport { IllustratedEmptyState, PagePurpose, Spinner } from '@burdenoff/fe-libs/ui';\n\nimport { GGNOMAD_MY_PROVIDERS_QUERY, GGNOMAD_MY_PROVIDER_ANALYTICS_QUERY } from '../graphql';\nimport { PageShell } from './PageShell';\nimport { SectionCard, StatCard } from '../components';\n\nconst CHART_COLORS = [\n 'var(--color-chart-1)',\n 'var(--color-chart-2)',\n 'var(--color-chart-3)',\n 'var(--color-chart-4)',\n 'var(--color-chart-5)',\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\ntype MyProvider = { id: string; name: string; type: string; status: string };\n\ntype AnalyticsPoint = { period: string; bookings: number; revenue: number };\n\ntype ListingAnalytics = {\n listingId: string;\n listingType: string;\n listingName: string;\n bookings: number;\n revenue: number;\n cancellationRate: number;\n capacityUtilizationRate: number;\n};\n\ntype ProviderAnalytics = {\n providerId: string;\n providerName: string;\n totalBookings: number;\n totalRevenue: number;\n cancelledBookings: number;\n cancellationRate: number;\n capacityUtilizationRate: number;\n bookingsOverTime: AnalyticsPoint[];\n topListings: ListingAnalytics[];\n};\n\nfunction toISODate(date: Date): string {\n return date.toISOString().slice(0, 10);\n}\n\nfunction daysAgo(days: number): Date {\n return new Date(Date.now() - days * 24 * 60 * 60 * 1000);\n}\n\n/**\n * BOFF-4390 — provider-facing analytics dashboard: bookings-over-time,\n * revenue, cancellation rate and capacity utilization, plus a per-listing\n * drill-down table. Self-scoped: only providers the calling actor created\n * are listed, and the analytics call is owner-verified server-side — a\n * provider can never see another provider's numbers. Forecasting and\n * custom-report building are explicitly out of scope for this core; see the\n * `myGgnomadProviderAnalytics` schema doc comment.\n */\nexport function ProviderAnalyticsPage() {\n const { t } = useI18n();\n\n const { data: providersData, loading: providersLoading } = useQuery(\n GGNOMAD_MY_PROVIDERS_QUERY as TypedDocumentNode<\n { myGgnomadProviders: MyProvider[] },\n Record<string, never>\n >\n );\n const providers = providersData?.myGgnomadProviders ?? [];\n\n const [selectedProviderId, setSelectedProviderId] = useState('');\n // `dateFrom`/`dateTo` are the plain YYYY-MM-DD strings bound to the <input\n // type=\"date\"> controls. They are NOT sent to GraphQL as-is: a bare date\n // string parses to UTC midnight, so using \"today\" as dateTo would exclude\n // every booking made later that same day. The query variables below\n // convert dateFrom to the start of that UTC day and dateTo to the end of\n // it, so \"today\" always includes today's activity.\n const [dateFrom, setDateFrom] = useState(() => toISODate(daysAgo(30)));\n const [dateTo, setDateTo] = useState(() => toISODate(new Date()));\n const [granularity, setGranularity] = useState<'day' | 'week' | 'month'>('day');\n\n const activeProviderId = selectedProviderId || providers[0]?.id || '';\n\n const { data: analyticsData, loading: analyticsLoading } = useQuery(\n GGNOMAD_MY_PROVIDER_ANALYTICS_QUERY as TypedDocumentNode<\n { myGgnomadProviderAnalytics: ProviderAnalytics },\n { providerId: string; dateFrom?: string; dateTo?: string; granularity?: string }\n >,\n {\n variables: {\n providerId: activeProviderId,\n dateFrom: `${dateFrom}T00:00:00.000Z`,\n dateTo: `${dateTo}T23:59:59.999Z`,\n granularity,\n },\n skip: !activeProviderId,\n }\n );\n const analytics = analyticsData?.myGgnomadProviderAnalytics;\n\n const maxTrend = useMemo(() => {\n const points = analytics?.bookingsOverTime ?? [];\n return points.length > 0 ? Math.max(...points.map((p) => p.bookings)) : 0;\n }, [analytics?.bookingsOverTime]);\n\n return (\n <PageShell\n title={t('ggnomad.providerAnalytics.title', 'Provider Analytics')}\n titleAccent={t('ggnomad.providerAnalytics.title.accent', 'Analytics')}\n subtitle={t(\n 'ggnomad.providerAnalytics.subtitle',\n 'Your bookings, revenue and listing performance'\n )}\n >\n <PagePurpose icon={<TrendingUp className=\"size-4\" />}>\n {t(\n 'ggnomad.providerAnalytics.purpose',\n 'A self-service dashboard for providers: bookings and revenue over time, cancellation rate, capacity utilization, and a per-listing breakdown — scoped to only the listings you own. Pick a date range and a listing granularity to drill in.'\n )}\n </PagePurpose>\n\n {!providersLoading && providers.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title={t('ggnomad.providerAnalytics.emptyTitle', 'No provider profile yet')}\n description={t(\n 'ggnomad.providerAnalytics.emptyDescription',\n 'Once you create a GGNomad provider profile, your bookings and revenue analytics will appear here.'\n )}\n />\n ) : (\n <>\n <SectionCard title={t('ggnomad.providerAnalytics.filters', 'Filters')}>\n <div className=\"grid gap-3 sm:grid-cols-4\">\n <div>\n <label\n htmlFor=\"provider-analytics-provider\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.providerAnalytics.provider', 'Provider')}\n </label>\n <select\n id=\"provider-analytics-provider\"\n value={activeProviderId}\n onChange={(e) => setSelectedProviderId(e.target.value)}\n className={INPUT_CLASSES}\n >\n {providers.map((provider) => (\n <option key={provider.id} value={provider.id}>\n {provider.name}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label\n htmlFor=\"provider-analytics-from\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.providerAnalytics.from', 'From')}\n </label>\n <input\n id=\"provider-analytics-from\"\n type=\"date\"\n value={dateFrom}\n max={dateTo}\n onChange={(e) => setDateFrom(e.target.value)}\n className={INPUT_CLASSES}\n />\n </div>\n <div>\n <label\n htmlFor=\"provider-analytics-to\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.providerAnalytics.to', 'To')}\n </label>\n <input\n id=\"provider-analytics-to\"\n type=\"date\"\n value={dateTo}\n min={dateFrom}\n max={toISODate(new Date())}\n onChange={(e) => setDateTo(e.target.value)}\n className={INPUT_CLASSES}\n />\n </div>\n <div>\n <label\n htmlFor=\"provider-analytics-granularity\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.providerAnalytics.granularity', 'Granularity')}\n </label>\n <select\n id=\"provider-analytics-granularity\"\n value={granularity}\n onChange={(e) => setGranularity(e.target.value as 'day' | 'week' | 'month')}\n className={INPUT_CLASSES}\n >\n <option value=\"day\">{t('ggnomad.providerAnalytics.day', 'Day')}</option>\n <option value=\"week\">{t('ggnomad.providerAnalytics.week', 'Week')}</option>\n <option value=\"month\">{t('ggnomad.providerAnalytics.month', 'Month')}</option>\n </select>\n </div>\n </div>\n </SectionCard>\n\n {analyticsLoading ? (\n <div className=\"flex justify-center py-12\">\n <Spinner size=\"lg\" />\n </div>\n ) : !analytics ? (\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title={t('ggnomad.providerAnalytics.noDataTitle', 'No bookings in this range')}\n description={t(\n 'ggnomad.providerAnalytics.noDataDescription',\n 'Try widening the date range — analytics appear here once bookings come in for your listings.'\n )}\n />\n ) : (\n <>\n <section className=\"grid gap-4 sm:grid-cols-2 xl:grid-cols-4\">\n <StatCard\n hero\n label={t('ggnomad.providerAnalytics.totalBookings', 'Bookings')}\n value={analytics.totalBookings.toLocaleString()}\n icon={<CalendarCheck />}\n />\n <StatCard\n label={t('ggnomad.providerAnalytics.totalRevenue', 'Revenue')}\n value={`$${Number(analytics.totalRevenue).toLocaleString()}`}\n icon={<DollarSign />}\n />\n <StatCard\n label={t('ggnomad.providerAnalytics.cancellationRate', 'Cancellation rate')}\n value={`${(analytics.cancellationRate * 100).toFixed(1)}%`}\n icon={<XCircle />}\n />\n <StatCard\n label={t('ggnomad.providerAnalytics.capacityUtilization', 'Capacity utilization')}\n value={`${(analytics.capacityUtilizationRate * 100).toFixed(1)}%`}\n icon={<Gauge />}\n />\n </section>\n\n <SectionCard\n title={t('ggnomad.providerAnalytics.bookingsOverTime', 'Bookings over time')}\n >\n {analytics.bookingsOverTime.length === 0 ? (\n <div className=\"flex flex-col items-center gap-2 py-8 text-center\">\n <span className=\"flex size-10 items-center justify-center rounded-full bg-[var(--color-accent-soft)] text-text-secondary\">\n <BarChart3 className=\"size-5\" />\n </span>\n <p className=\"text-sm font-medium text-text-primary\">\n {t('ggnomad.providerAnalytics.trendEmpty', 'No bookings in this window yet')}\n </p>\n </div>\n ) : (\n <div className=\"flex flex-col gap-4\">\n <div className=\"flex items-end gap-2 overflow-x-auto pb-2\">\n {analytics.bookingsOverTime.map((point, index) => {\n const pct =\n maxTrend > 0 ? Math.max((point.bookings / maxTrend) * 100, 5) : 5;\n return (\n <div key={point.period} className=\"flex flex-col items-center gap-1\">\n <div className=\"flex h-24 items-end\">\n <div\n className=\"w-8 rounded-t transition-all duration-500 ease-out\"\n style={{\n height: `${pct}%`,\n backgroundColor: CHART_COLORS[index % CHART_COLORS.length],\n }}\n title={`${point.period}: ${point.bookings} bookings, $${point.revenue.toLocaleString()} revenue`}\n />\n </div>\n <p className=\"truncate text-xs text-text-secondary\">{point.period}</p>\n </div>\n );\n })}\n </div>\n <p className=\"text-xs text-text-secondary\">\n {t(\n 'ggnomad.providerAnalytics.trendNote',\n 'Bar height is booking volume per period; hover a bar for its exact bookings and revenue.'\n )}\n </p>\n </div>\n )}\n </SectionCard>\n\n <SectionCard title={t('ggnomad.providerAnalytics.topListings', 'Top listings')}>\n {analytics.topListings.length === 0 ? (\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n {t(\n 'ggnomad.providerAnalytics.topListingsEmpty',\n 'No listing bookings in this range yet.'\n )}\n </p>\n ) : (\n <div className=\"overflow-x-auto\">\n <table className=\"w-full min-w-[560px] text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam text-left text-text-secondary\">\n <th className=\"py-2 pr-3 font-medium\">\n {t('ggnomad.providerAnalytics.listing', 'Listing')}\n </th>\n <th className=\"py-2 pr-3 font-medium\">\n {t('ggnomad.providerAnalytics.type', 'Type')}\n </th>\n <th className=\"py-2 pr-3 text-right font-medium\">\n {t('ggnomad.providerAnalytics.bookings', 'Bookings')}\n </th>\n <th className=\"py-2 pr-3 text-right font-medium\">\n {t('ggnomad.providerAnalytics.revenue', 'Revenue')}\n </th>\n <th className=\"py-2 pr-3 text-right font-medium\">\n {t('ggnomad.providerAnalytics.cancellation', 'Cancellation')}\n </th>\n <th className=\"py-2 text-right font-medium\">\n {t('ggnomad.providerAnalytics.utilization', 'Utilization')}\n </th>\n </tr>\n </thead>\n <tbody>\n {analytics.topListings.map((listing) => (\n <tr\n key={listing.listingId}\n className=\"border-b border-border-seam/50 last:border-0\"\n >\n <td className=\"py-2 pr-3 font-medium text-text-primary\">\n {listing.listingName}\n </td>\n <td className=\"py-2 pr-3 text-text-secondary\">{listing.listingType}</td>\n <td className=\"py-2 pr-3 text-right tabular-nums text-text-primary\">\n {listing.bookings}\n </td>\n <td className=\"py-2 pr-3 text-right tabular-nums text-text-primary\">\n ${listing.revenue.toLocaleString()}\n </td>\n <td className=\"py-2 pr-3 text-right tabular-nums text-text-primary\">\n {(listing.cancellationRate * 100).toFixed(0)}%\n </td>\n <td className=\"py-2 text-right tabular-nums text-text-primary\">\n {(listing.capacityUtilizationRate * 100).toFixed(0)}%\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </SectionCard>\n </>\n )}\n </>\n )}\n </PageShell>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProviderPage.d.ts","sourceRoot":"","sources":["../../../src/ggnomad/pages/ProviderPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ProviderPage.d.ts","sourceRoot":"","sources":["../../../src/ggnomad/pages/ProviderPage.tsx"],"names":[],"mappings":"AAYA,wBAAgB,YAAY,gCAwG3B"}
|
|
@@ -5,83 +5,92 @@ import { EntityCard as r } from "../components/EntityCard.js";
|
|
|
5
5
|
import { LoadMoreButton as i } from "../components/LoadMoreButton.js";
|
|
6
6
|
import "../components/index.js";
|
|
7
7
|
import { useState as a } from "react";
|
|
8
|
-
import { Briefcase as o } from "lucide-react";
|
|
9
|
-
import { useI18n as
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
8
|
+
import { Briefcase as o, Store as s } from "lucide-react";
|
|
9
|
+
import { useI18n as c } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
10
|
+
import { Link as l } from "react-router-dom";
|
|
11
|
+
import { useQuery as u } from "@apollo/client/react";
|
|
12
|
+
import { GlassCard as d, IllustratedEmptyState as f, PagePurpose as p } from "@burdenoff/fe-libs/ui";
|
|
13
|
+
import { jsx as m, jsxs as h } from "react/jsx-runtime";
|
|
13
14
|
//#region src/ggnomad/pages/ProviderPage.tsx
|
|
14
|
-
function
|
|
15
|
-
let { t:
|
|
16
|
-
limit:
|
|
15
|
+
function g() {
|
|
16
|
+
let { t: g } = c(), [_, v] = a(12), { data: y } = u(e, { variables: {
|
|
17
|
+
limit: _,
|
|
17
18
|
offset: 0
|
|
18
|
-
} }),
|
|
19
|
-
return /* @__PURE__ */
|
|
20
|
-
title:
|
|
21
|
-
titleAccent:
|
|
22
|
-
subtitle:
|
|
23
|
-
children: [/* @__PURE__ */
|
|
24
|
-
icon: /* @__PURE__ */
|
|
25
|
-
children:
|
|
26
|
-
}),
|
|
19
|
+
} }), b = y?.ggnomadProviders ?? [];
|
|
20
|
+
return /* @__PURE__ */ h(t, {
|
|
21
|
+
title: g("ggnomad.provider.title", "Provider Dashboard"),
|
|
22
|
+
titleAccent: g("ggnomad.provider.title.accent", "Provider"),
|
|
23
|
+
subtitle: g("ggnomad.provider.subtitle", "B2B provider operations and onboarding metrics"),
|
|
24
|
+
children: [/* @__PURE__ */ m(p, {
|
|
25
|
+
icon: /* @__PURE__ */ m(o, { className: "size-4" }),
|
|
26
|
+
children: g("ggnomad.provider.purpose", "Track the supply side of the marketplace — the hotels, tour operators and venues that publish inventory. Use it to monitor each provider’s status, listing count, revenue and rating, and to spot who needs onboarding attention.")
|
|
27
|
+
}), b.length === 0 ? /* @__PURE__ */ m(f, {
|
|
27
28
|
illustration: "empty-people",
|
|
28
|
-
title:
|
|
29
|
-
description:
|
|
30
|
-
}) : /* @__PURE__ */
|
|
29
|
+
title: g("ggnomad.provider.emptyTitle", "No providers onboarded"),
|
|
30
|
+
description: g("ggnomad.provider.emptyDescription", "Onboarded B2B providers and their performance will be tracked here.")
|
|
31
|
+
}) : /* @__PURE__ */ h(d, {
|
|
31
32
|
treatment: "glass",
|
|
32
33
|
glow: !0,
|
|
33
34
|
className: "border-border-seam p-5",
|
|
34
35
|
children: [
|
|
35
|
-
/* @__PURE__ */
|
|
36
|
+
/* @__PURE__ */ m("h2", {
|
|
36
37
|
className: "mb-4 text-lg font-medium tracking-tight text-text-primary",
|
|
37
|
-
children:
|
|
38
|
+
children: g("ggnomad.provider.onboardedProviders", "Onboarded providers")
|
|
38
39
|
}),
|
|
39
|
-
/* @__PURE__ */
|
|
40
|
+
/* @__PURE__ */ m("div", {
|
|
40
41
|
className: "grid gap-3 sm:grid-cols-2 xl:grid-cols-3",
|
|
41
|
-
children:
|
|
42
|
+
children: b.map((e) => /* @__PURE__ */ m(r, {
|
|
42
43
|
title: e.name,
|
|
43
44
|
meta: e.type,
|
|
44
|
-
badge: /* @__PURE__ */
|
|
45
|
+
badge: /* @__PURE__ */ m(n, {
|
|
45
46
|
status: e.status,
|
|
46
47
|
live: e.status === "ACTIVE"
|
|
47
48
|
}),
|
|
48
|
-
footer: /* @__PURE__ */
|
|
49
|
+
footer: /* @__PURE__ */ h("dl", {
|
|
49
50
|
className: "grid grid-cols-3 gap-2 text-xs",
|
|
50
51
|
children: [
|
|
51
|
-
/* @__PURE__ */
|
|
52
|
+
/* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("dt", {
|
|
52
53
|
className: "text-text-secondary",
|
|
53
|
-
children:
|
|
54
|
-
}), /* @__PURE__ */
|
|
54
|
+
children: g("ggnomad.provider.listings", "Listings")
|
|
55
|
+
}), /* @__PURE__ */ m("dd", {
|
|
55
56
|
className: "mt-0.5 font-medium tabular-nums text-text-primary",
|
|
56
57
|
children: e.listingCount
|
|
57
58
|
})] }),
|
|
58
|
-
/* @__PURE__ */
|
|
59
|
+
/* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("dt", {
|
|
59
60
|
className: "text-text-secondary",
|
|
60
|
-
children:
|
|
61
|
-
}), /* @__PURE__ */
|
|
61
|
+
children: g("ggnomad.provider.revenue", "Revenue")
|
|
62
|
+
}), /* @__PURE__ */ h("dd", {
|
|
62
63
|
className: "mt-0.5 font-medium tabular-nums text-text-primary",
|
|
63
64
|
children: ["$", Number(e.totalRevenue).toLocaleString()]
|
|
64
65
|
})] }),
|
|
65
|
-
/* @__PURE__ */
|
|
66
|
+
/* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("dt", {
|
|
66
67
|
className: "text-text-secondary",
|
|
67
|
-
children:
|
|
68
|
-
}), /* @__PURE__ */
|
|
68
|
+
children: g("ggnomad.provider.rating", "Rating")
|
|
69
|
+
}), /* @__PURE__ */ m("dd", {
|
|
69
70
|
className: "mt-0.5 font-medium tabular-nums text-text-primary",
|
|
70
|
-
children: e.rating ??
|
|
71
|
-
})] })
|
|
71
|
+
children: e.rating ?? g("ggnomad.provider.noRating", "N/A")
|
|
72
|
+
})] }),
|
|
73
|
+
/* @__PURE__ */ m("div", {
|
|
74
|
+
className: "col-span-3 mt-1 border-t border-border-seam pt-2",
|
|
75
|
+
children: /* @__PURE__ */ h(l, {
|
|
76
|
+
to: `provider/${e.id}/storefront`,
|
|
77
|
+
className: "inline-flex items-center gap-1 text-xs font-medium text-action-primary-bg hover:underline",
|
|
78
|
+
children: [/* @__PURE__ */ m(s, { className: "size-3.5" }), g("ggnomad.provider.manageStorefront", "Manage storefront")]
|
|
79
|
+
})
|
|
80
|
+
})
|
|
72
81
|
]
|
|
73
82
|
})
|
|
74
83
|
}, e.id))
|
|
75
84
|
}),
|
|
76
|
-
/* @__PURE__ */
|
|
77
|
-
visible:
|
|
78
|
-
onLoadMore: () =>
|
|
85
|
+
/* @__PURE__ */ m(i, {
|
|
86
|
+
visible: b.length >= _,
|
|
87
|
+
onLoadMore: () => v((e) => e + 12)
|
|
79
88
|
})
|
|
80
89
|
]
|
|
81
90
|
})]
|
|
82
91
|
});
|
|
83
92
|
}
|
|
84
93
|
//#endregion
|
|
85
|
-
export {
|
|
94
|
+
export { g as ProviderPage };
|
|
86
95
|
|
|
87
96
|
//# sourceMappingURL=ProviderPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProviderPage.js","names":[],"sources":["../../../src/ggnomad/pages/ProviderPage.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 { Briefcase } from 'lucide-react';\nimport { GlassCard, IllustratedEmptyState, PagePurpose } from '@burdenoff/fe-libs/ui';\n\nimport { GGNOMAD_PROVIDERS_QUERY } from '../graphql';\nimport { PageShell } from './PageShell';\nimport { EntityCard, LoadMoreButton, StatusBadge } from '../components';\n\nexport function ProviderPage() {\n const { t } = useI18n();\n const [limit, setLimit] = useState(12);\n const { data } = useQuery(\n GGNOMAD_PROVIDERS_QUERY as TypedDocumentNode<\n {\n ggnomadProviders: Array<{\n id: string;\n name: string;\n type: string;\n status: string;\n listingCount: number;\n totalRevenue: number;\n rating?: number;\n }>;\n },\n { limit: number; offset: number }\n >,\n { variables: { limit, offset: 0 } }\n );\n const providers = data?.ggnomadProviders ?? [];\n\n return (\n <PageShell\n title={t('ggnomad.provider.title', 'Provider Dashboard')}\n titleAccent={t('ggnomad.provider.title.accent', 'Provider')}\n subtitle={t('ggnomad.provider.subtitle', 'B2B provider operations and onboarding metrics')}\n >\n <PagePurpose icon={<Briefcase className=\"size-4\" />}>\n {t(\n 'ggnomad.provider.purpose',\n 'Track the supply side of the marketplace — the hotels, tour operators and venues that publish inventory. Use it to monitor each provider’s status, listing count, revenue and rating, and to spot who needs onboarding attention.'\n )}\n </PagePurpose>\n\n {providers.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title={t('ggnomad.provider.emptyTitle', 'No providers onboarded')}\n description={t(\n 'ggnomad.provider.emptyDescription',\n 'Onboarded B2B providers and their performance will be tracked 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.provider.onboardedProviders', 'Onboarded providers')}\n </h2>\n <div className=\"grid gap-3 sm:grid-cols-2 xl:grid-cols-3\">\n {providers.map((provider) => (\n <EntityCard\n key={provider.id}\n title={provider.name}\n meta={provider.type}\n badge={<StatusBadge status={provider.status} live={provider.status === 'ACTIVE'} />}\n footer={\n <dl className=\"grid grid-cols-3 gap-2 text-xs\">\n <div>\n <dt className=\"text-text-secondary\">\n {t('ggnomad.provider.listings', 'Listings')}\n </dt>\n <dd className=\"mt-0.5 font-medium tabular-nums text-text-primary\">\n {provider.listingCount}\n </dd>\n </div>\n <div>\n <dt className=\"text-text-secondary\">\n {t('ggnomad.provider.revenue', 'Revenue')}\n </dt>\n <dd className=\"mt-0.5 font-medium tabular-nums text-text-primary\">\n ${Number(provider.totalRevenue).toLocaleString()}\n </dd>\n </div>\n <div>\n <dt className=\"text-text-secondary\">\n {t('ggnomad.provider.rating', 'Rating')}\n </dt>\n <dd className=\"mt-0.5 font-medium tabular-nums text-text-primary\">\n {provider.rating ?? t('ggnomad.provider.noRating', 'N/A')}\n </dd>\n </div>\n </dl>\n }\n />\n ))}\n </div>\n <LoadMoreButton\n visible={providers.length >= limit}\n onLoadMore={() => setLimit((l) => l + 12)}\n />\n </GlassCard>\n )}\n </PageShell>\n );\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"ProviderPage.js","names":[],"sources":["../../../src/ggnomad/pages/ProviderPage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useQuery } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { Link } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Briefcase, Store } from 'lucide-react';\nimport { GlassCard, IllustratedEmptyState, PagePurpose } from '@burdenoff/fe-libs/ui';\n\nimport { GGNOMAD_PROVIDERS_QUERY } from '../graphql';\nimport { PageShell } from './PageShell';\nimport { EntityCard, LoadMoreButton, StatusBadge } from '../components';\n\nexport function ProviderPage() {\n const { t } = useI18n();\n const [limit, setLimit] = useState(12);\n const { data } = useQuery(\n GGNOMAD_PROVIDERS_QUERY as TypedDocumentNode<\n {\n ggnomadProviders: Array<{\n id: string;\n name: string;\n type: string;\n status: string;\n listingCount: number;\n totalRevenue: number;\n rating?: number;\n }>;\n },\n { limit: number; offset: number }\n >,\n { variables: { limit, offset: 0 } }\n );\n const providers = data?.ggnomadProviders ?? [];\n\n return (\n <PageShell\n title={t('ggnomad.provider.title', 'Provider Dashboard')}\n titleAccent={t('ggnomad.provider.title.accent', 'Provider')}\n subtitle={t('ggnomad.provider.subtitle', 'B2B provider operations and onboarding metrics')}\n >\n <PagePurpose icon={<Briefcase className=\"size-4\" />}>\n {t(\n 'ggnomad.provider.purpose',\n 'Track the supply side of the marketplace — the hotels, tour operators and venues that publish inventory. Use it to monitor each provider’s status, listing count, revenue and rating, and to spot who needs onboarding attention.'\n )}\n </PagePurpose>\n\n {providers.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title={t('ggnomad.provider.emptyTitle', 'No providers onboarded')}\n description={t(\n 'ggnomad.provider.emptyDescription',\n 'Onboarded B2B providers and their performance will be tracked 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.provider.onboardedProviders', 'Onboarded providers')}\n </h2>\n <div className=\"grid gap-3 sm:grid-cols-2 xl:grid-cols-3\">\n {providers.map((provider) => (\n <EntityCard\n key={provider.id}\n title={provider.name}\n meta={provider.type}\n badge={<StatusBadge status={provider.status} live={provider.status === 'ACTIVE'} />}\n footer={\n <dl className=\"grid grid-cols-3 gap-2 text-xs\">\n <div>\n <dt className=\"text-text-secondary\">\n {t('ggnomad.provider.listings', 'Listings')}\n </dt>\n <dd className=\"mt-0.5 font-medium tabular-nums text-text-primary\">\n {provider.listingCount}\n </dd>\n </div>\n <div>\n <dt className=\"text-text-secondary\">\n {t('ggnomad.provider.revenue', 'Revenue')}\n </dt>\n <dd className=\"mt-0.5 font-medium tabular-nums text-text-primary\">\n ${Number(provider.totalRevenue).toLocaleString()}\n </dd>\n </div>\n <div>\n <dt className=\"text-text-secondary\">\n {t('ggnomad.provider.rating', 'Rating')}\n </dt>\n <dd className=\"mt-0.5 font-medium tabular-nums text-text-primary\">\n {provider.rating ?? t('ggnomad.provider.noRating', 'N/A')}\n </dd>\n </div>\n <div className=\"col-span-3 mt-1 border-t border-border-seam pt-2\">\n <Link\n to={`provider/${provider.id}/storefront`}\n className=\"inline-flex items-center gap-1 text-xs font-medium text-action-primary-bg hover:underline\"\n >\n <Store className=\"size-3.5\" />\n {t('ggnomad.provider.manageStorefront', 'Manage storefront')}\n </Link>\n </div>\n </dl>\n }\n />\n ))}\n </div>\n <LoadMoreButton\n visible={providers.length >= limit}\n onLoadMore={() => setLimit((l) => l + 12)}\n />\n </GlassCard>\n )}\n </PageShell>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;AAYA,SAAgB,IAAe;CAC7B,IAAM,EAAE,SAAM,EAAQ,GAChB,CAAC,GAAO,KAAY,EAAS,EAAE,GAC/B,EAAE,YAAS,EACf,GAcA,EAAE,WAAW;EAAE;EAAO,QAAQ;CAAE,EAAE,CACpC,GACM,IAAY,GAAM,oBAAoB,CAAC;CAE7C,OACE,kBAAC,GAAD;EACE,OAAO,EAAE,0BAA0B,oBAAoB;EACvD,aAAa,EAAE,iCAAiC,UAAU;EAC1D,UAAU,EAAE,6BAA6B,gDAAgD;YAH3F,CAKE,kBAAC,GAAD;GAAa,MAAM,kBAAC,GAAD,EAAW,WAAU,SAAU,CAAA;aAC/C,EACC,4BACA,mOACF;EACW,CAAA,GAEZ,EAAU,WAAW,IACpB,kBAAC,GAAD;GACE,cAAa;GACb,OAAO,EAAE,+BAA+B,wBAAwB;GAChE,aAAa,EACX,qCACA,qEACF;EACD,CAAA,IAED,kBAAC,GAAD;GAAW,WAAU;GAAQ,MAAA;GAAK,WAAU;aAA5C;IACE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAE,uCAAuC,qBAAqB;IAC7D,CAAA;IACJ,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAU,KAAK,MACd,kBAAC,GAAD;MAEE,OAAO,EAAS;MAChB,MAAM,EAAS;MACf,OAAO,kBAAC,GAAD;OAAa,QAAQ,EAAS;OAAQ,MAAM,EAAS,WAAW;MAAW,CAAA;MAClF,QACE,kBAAC,MAAD;OAAI,WAAU;iBAAd;QACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAE,6BAA6B,UAAU;QACxC,CAAA,GACJ,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAS;QACR,CAAA,CACD,EAAA,CAAA;QACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAE,4BAA4B,SAAS;QACtC,CAAA,GACJ,kBAAC,MAAD;SAAI,WAAU;mBAAd,CAAkE,KAC9D,OAAO,EAAS,YAAY,EAAE,eAAe,CAC7C;UACD,EAAA,CAAA;QACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAE,2BAA2B,QAAQ;QACpC,CAAA,GACJ,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAS,UAAU,EAAE,6BAA6B,KAAK;QACtD,CAAA,CACD,EAAA,CAAA;QACL,kBAAC,OAAD;SAAK,WAAU;mBACb,kBAAC,GAAD;UACE,IAAI,YAAY,EAAS,GAAG;UAC5B,WAAU;oBAFZ,CAIE,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA,GAC5B,EAAE,qCAAqC,mBAAmB,CACvD;;QACH,CAAA;OACH;;KAEP,GAzCM,EAAS,EAyCf,CACF;IACE,CAAA;IACL,kBAAC,GAAD;KACE,SAAS,EAAU,UAAU;KAC7B,kBAAkB,GAAU,MAAM,IAAI,EAAE;IACzC,CAAA;GACQ;IAEJ;;AAEf"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BOFF-4363 — operator storefront settings: create/edit the branding
|
|
3
|
+
* (logo/color/slug/about) for one provider and publish/unpublish it. The
|
|
4
|
+
* right-hand panel renders a live, themeable preview of the branded page a
|
|
5
|
+
* traveler would see at `ggnomadStorefrontBySlug` once published.
|
|
6
|
+
*
|
|
7
|
+
* Custom-domain DNS/SSL provisioning and a drag-and-drop page builder are
|
|
8
|
+
* explicitly out of scope for this pass — see Linear BOFF-4363.
|
|
9
|
+
*/
|
|
10
|
+
export declare function StorefrontPage(): import("react").JSX.Element;
|
|
11
|
+
//# sourceMappingURL=StorefrontPage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StorefrontPage.d.ts","sourceRoot":"","sources":["../../../src/ggnomad/pages/StorefrontPage.tsx"],"names":[],"mappings":"AAqCA;;;;;;;;GAQG;AACH,wBAAgB,cAAc,gCAwY7B"}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { CREATE_GGNOMAD_OPERATOR_STOREFRONT_MUTATION as e, GGNOMAD_OPERATOR_STOREFRONT_QUERY as t, PUBLISH_GGNOMAD_OPERATOR_STOREFRONT_MUTATION as n, UNPUBLISH_GGNOMAD_OPERATOR_STOREFRONT_MUTATION as r, UPDATE_GGNOMAD_OPERATOR_STOREFRONT_MUTATION as ee } from "../graphql.js";
|
|
2
|
+
import { PageShell as i } from "./PageShell.js";
|
|
3
|
+
import { notify as a } from "../utils/notify.js";
|
|
4
|
+
import { useState as o } from "react";
|
|
5
|
+
import { Globe as s, Palette as c, Store as l } from "lucide-react";
|
|
6
|
+
import { useI18n as u } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
7
|
+
import { useParams as d } from "react-router-dom";
|
|
8
|
+
import { useMutation as f, useQuery as p } from "@apollo/client/react";
|
|
9
|
+
import { Button as m, GlassCard as te, Input as h, PagePurpose as g, Spinner as _, Textarea as v } from "@burdenoff/fe-libs/ui";
|
|
10
|
+
import { jsx as y, jsxs as b } from "react/jsx-runtime";
|
|
11
|
+
//#region src/ggnomad/pages/StorefrontPage.tsx
|
|
12
|
+
var x = /^#[0-9a-fA-F]{6}$/, S = /^[a-z0-9]([a-z0-9-]{1,78}[a-z0-9])?$/, C = "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", w = "block text-sm font-medium text-text-primary";
|
|
13
|
+
function T() {
|
|
14
|
+
let { t: T } = u(), { providerId: E = "" } = d(), { data: D, loading: O, refetch: k } = p(t, {
|
|
15
|
+
variables: { providerId: E },
|
|
16
|
+
skip: !E
|
|
17
|
+
}), A = D?.ggnomadOperatorStorefront ?? null, [j, M] = o(""), [N, P] = o(""), [F, I] = o(""), [L, R] = o("#1E88E5"), [z, B] = o(""), [V, H] = o(!1);
|
|
18
|
+
A && !V && (M(A.slug), P(A.displayName), I(A.logoUrl ?? ""), R(A.primaryColor ?? "#1E88E5"), B(A.aboutText ?? ""), H(!0));
|
|
19
|
+
let [U, { loading: W }] = f(e), [G, { loading: K }] = f(ee), [q, { loading: J }] = f(n), [Y, { loading: X }] = f(r), Z = async () => {
|
|
20
|
+
if (!j.trim() || !S.test(j.trim())) {
|
|
21
|
+
a.warning(T("ggnomad.storefront.invalidSlugTitle", "Invalid slug"), T("ggnomad.storefront.invalidSlugBody", "3-80 lowercase letters, digits, or hyphens; no leading/trailing hyphen."));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (!N.trim()) {
|
|
25
|
+
a.warning(T("ggnomad.storefront.nameRequiredTitle", "Display name is required"), T("ggnomad.storefront.nameRequiredBody", "Give your storefront a public-facing name."));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (L.trim() && !x.test(L.trim())) {
|
|
29
|
+
a.warning(T("ggnomad.storefront.invalidColorTitle", "Invalid color"), T("ggnomad.storefront.invalidColorBody", "Use a 6-digit hex color, e.g. #1E88E5."));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
if (A) {
|
|
34
|
+
let e = await G({ variables: {
|
|
35
|
+
id: A.id,
|
|
36
|
+
input: {
|
|
37
|
+
slug: j.trim().toLowerCase(),
|
|
38
|
+
displayName: N.trim(),
|
|
39
|
+
logoUrl: F.trim() || null,
|
|
40
|
+
primaryColor: L.trim() || null,
|
|
41
|
+
aboutText: z.trim() || null
|
|
42
|
+
}
|
|
43
|
+
} });
|
|
44
|
+
if (e.error) throw e.error;
|
|
45
|
+
a.success(T("ggnomad.storefront.savedTitle", "Storefront updated"), N);
|
|
46
|
+
} else {
|
|
47
|
+
let e = await U({ variables: { input: {
|
|
48
|
+
providerId: E,
|
|
49
|
+
slug: j.trim().toLowerCase(),
|
|
50
|
+
displayName: N.trim(),
|
|
51
|
+
logoUrl: F.trim() || null,
|
|
52
|
+
primaryColor: L.trim() || null,
|
|
53
|
+
aboutText: z.trim() || null
|
|
54
|
+
} } });
|
|
55
|
+
if (e.error) throw e.error;
|
|
56
|
+
a.success(T("ggnomad.storefront.createdTitle", "Storefront created"), N);
|
|
57
|
+
}
|
|
58
|
+
await k();
|
|
59
|
+
} catch (e) {
|
|
60
|
+
a.error(T("ggnomad.storefront.errorTitle", "Could not save storefront"), e instanceof Error ? e.message : String(e));
|
|
61
|
+
}
|
|
62
|
+
}, ne = async () => {
|
|
63
|
+
if (A) try {
|
|
64
|
+
A.isPublished ? (await Y({ variables: { id: A.id } }), a.success(T("ggnomad.storefront.unpublishedTitle", "Storefront unpublished"), T("ggnomad.storefront.unpublishedBody", "It is no longer visible to the public."))) : (await q({ variables: { id: A.id } }), a.success(T("ggnomad.storefront.publishedTitle", "Storefront published"), T("ggnomad.storefront.publishedBody", "Your branded page is now publicly reachable."))), await k();
|
|
65
|
+
} catch (e) {
|
|
66
|
+
a.error(T("ggnomad.storefront.errorTitle", "Could not update storefront"), e instanceof Error ? e.message : String(e));
|
|
67
|
+
}
|
|
68
|
+
}, Q = W || K, $ = x.test(L.trim()) ? L.trim() : "#1E88E5";
|
|
69
|
+
return E ? /* @__PURE__ */ b(i, {
|
|
70
|
+
title: T("ggnomad.storefront.title", "Operator Storefront"),
|
|
71
|
+
titleAccent: T("ggnomad.storefront.title.accent", "Storefront"),
|
|
72
|
+
subtitle: T("ggnomad.storefront.subtitle", "White-label branding for your public listing page — logo, color, slug, and about text."),
|
|
73
|
+
children: [/* @__PURE__ */ y(g, {
|
|
74
|
+
icon: /* @__PURE__ */ y(l, { className: "size-4" }),
|
|
75
|
+
children: T("ggnomad.storefront.purpose", "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.")
|
|
76
|
+
}), O && !V ? /* @__PURE__ */ y("div", {
|
|
77
|
+
className: "flex justify-center py-10",
|
|
78
|
+
children: /* @__PURE__ */ y(_, {})
|
|
79
|
+
}) : /* @__PURE__ */ b("div", {
|
|
80
|
+
className: "grid gap-5 lg:grid-cols-2",
|
|
81
|
+
children: [/* @__PURE__ */ b(te, {
|
|
82
|
+
treatment: "glass",
|
|
83
|
+
glow: !0,
|
|
84
|
+
className: "border-border-seam p-5",
|
|
85
|
+
children: [/* @__PURE__ */ b("h2", {
|
|
86
|
+
className: "mb-4 flex items-center gap-2 text-lg font-medium tracking-tight text-text-primary",
|
|
87
|
+
children: [/* @__PURE__ */ y(c, { className: "size-4" }), T("ggnomad.storefront.brandingHeading", "Branding")]
|
|
88
|
+
}), /* @__PURE__ */ b("div", {
|
|
89
|
+
className: "space-y-4",
|
|
90
|
+
children: [
|
|
91
|
+
/* @__PURE__ */ b("div", { children: [
|
|
92
|
+
/* @__PURE__ */ y("label", {
|
|
93
|
+
className: w,
|
|
94
|
+
htmlFor: "storefront-slug",
|
|
95
|
+
children: T("ggnomad.storefront.slugLabel", "Public slug")
|
|
96
|
+
}),
|
|
97
|
+
/* @__PURE__ */ y(h, {
|
|
98
|
+
id: "storefront-slug",
|
|
99
|
+
className: C,
|
|
100
|
+
value: j,
|
|
101
|
+
onChange: (e) => M(e.target.value.toLowerCase()),
|
|
102
|
+
placeholder: "my-eco-stays",
|
|
103
|
+
disabled: Q
|
|
104
|
+
}),
|
|
105
|
+
/* @__PURE__ */ y("p", {
|
|
106
|
+
className: "mt-1 text-xs text-text-secondary",
|
|
107
|
+
children: T("ggnomad.storefront.slugHelp", "Lowercase letters, digits, hyphens — 3-80 chars.")
|
|
108
|
+
})
|
|
109
|
+
] }),
|
|
110
|
+
/* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("label", {
|
|
111
|
+
className: w,
|
|
112
|
+
htmlFor: "storefront-name",
|
|
113
|
+
children: T("ggnomad.storefront.nameLabel", "Display name")
|
|
114
|
+
}), /* @__PURE__ */ y(h, {
|
|
115
|
+
id: "storefront-name",
|
|
116
|
+
className: C,
|
|
117
|
+
value: N,
|
|
118
|
+
onChange: (e) => P(e.target.value),
|
|
119
|
+
placeholder: T("ggnomad.storefront.namePlaceholder", "Sunset Eco Stays"),
|
|
120
|
+
disabled: Q
|
|
121
|
+
})] }),
|
|
122
|
+
/* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("label", {
|
|
123
|
+
className: w,
|
|
124
|
+
htmlFor: "storefront-logo",
|
|
125
|
+
children: T("ggnomad.storefront.logoLabel", "Logo URL")
|
|
126
|
+
}), /* @__PURE__ */ y(h, {
|
|
127
|
+
id: "storefront-logo",
|
|
128
|
+
className: C,
|
|
129
|
+
value: F,
|
|
130
|
+
onChange: (e) => I(e.target.value),
|
|
131
|
+
placeholder: "https://example.com/logo.png",
|
|
132
|
+
disabled: Q
|
|
133
|
+
})] }),
|
|
134
|
+
/* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("label", {
|
|
135
|
+
className: w,
|
|
136
|
+
htmlFor: "storefront-color",
|
|
137
|
+
children: T("ggnomad.storefront.colorLabel", "Primary color")
|
|
138
|
+
}), /* @__PURE__ */ b("div", {
|
|
139
|
+
className: "mt-1 flex items-center gap-2",
|
|
140
|
+
children: [/* @__PURE__ */ y("input", {
|
|
141
|
+
id: "storefront-color",
|
|
142
|
+
type: "color",
|
|
143
|
+
className: "h-9 w-12 rounded border border-border-seam bg-bg-canvas",
|
|
144
|
+
value: x.test(L) ? L : "#1E88E5",
|
|
145
|
+
onChange: (e) => R(e.target.value),
|
|
146
|
+
disabled: Q
|
|
147
|
+
}), /* @__PURE__ */ y(h, {
|
|
148
|
+
className: C,
|
|
149
|
+
value: L,
|
|
150
|
+
onChange: (e) => R(e.target.value),
|
|
151
|
+
placeholder: "#1E88E5",
|
|
152
|
+
disabled: Q
|
|
153
|
+
})]
|
|
154
|
+
})] }),
|
|
155
|
+
/* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("label", {
|
|
156
|
+
className: w,
|
|
157
|
+
htmlFor: "storefront-about",
|
|
158
|
+
children: T("ggnomad.storefront.aboutLabel", "About text")
|
|
159
|
+
}), /* @__PURE__ */ y(v, {
|
|
160
|
+
id: "storefront-about",
|
|
161
|
+
className: C,
|
|
162
|
+
rows: 4,
|
|
163
|
+
value: z,
|
|
164
|
+
onChange: (e) => B(e.target.value),
|
|
165
|
+
placeholder: T("ggnomad.storefront.aboutPlaceholder", "Tell travelers what makes your business worth booking."),
|
|
166
|
+
disabled: Q
|
|
167
|
+
})] }),
|
|
168
|
+
/* @__PURE__ */ b("div", {
|
|
169
|
+
className: "flex flex-wrap items-center gap-3 pt-2",
|
|
170
|
+
children: [
|
|
171
|
+
/* @__PURE__ */ y(m, {
|
|
172
|
+
onClick: Z,
|
|
173
|
+
disabled: Q,
|
|
174
|
+
children: A ? T("ggnomad.storefront.saveButton", "Save changes") : T("ggnomad.storefront.createButton", "Create storefront")
|
|
175
|
+
}),
|
|
176
|
+
A ? /* @__PURE__ */ y(m, {
|
|
177
|
+
variant: "secondary",
|
|
178
|
+
onClick: ne,
|
|
179
|
+
disabled: J || X,
|
|
180
|
+
children: A.isPublished ? T("ggnomad.storefront.unpublishButton", "Unpublish") : T("ggnomad.storefront.publishButton", "Publish")
|
|
181
|
+
}) : null,
|
|
182
|
+
A?.isPublished ? /* @__PURE__ */ b("span", {
|
|
183
|
+
className: "inline-flex items-center gap-1 text-xs text-text-secondary",
|
|
184
|
+
children: [/* @__PURE__ */ y(s, { className: "size-3.5" }), T("ggnomad.storefront.livePathLabel", "Live at /store/{slug}").replace("{slug}", A.slug)]
|
|
185
|
+
}) : null
|
|
186
|
+
]
|
|
187
|
+
})
|
|
188
|
+
]
|
|
189
|
+
})]
|
|
190
|
+
}), /* @__PURE__ */ b("div", {
|
|
191
|
+
className: "overflow-hidden rounded-lg border border-border-seam",
|
|
192
|
+
style: {
|
|
193
|
+
borderTopColor: $,
|
|
194
|
+
borderTopWidth: 4
|
|
195
|
+
},
|
|
196
|
+
children: [/* @__PURE__ */ b("div", {
|
|
197
|
+
className: "p-5",
|
|
198
|
+
style: { background: `${$}14` },
|
|
199
|
+
children: [
|
|
200
|
+
/* @__PURE__ */ b("div", {
|
|
201
|
+
className: "flex items-center gap-3",
|
|
202
|
+
children: [F ? /* @__PURE__ */ y("img", {
|
|
203
|
+
src: F,
|
|
204
|
+
alt: "",
|
|
205
|
+
className: "size-12 rounded object-contain",
|
|
206
|
+
onError: (e) => {
|
|
207
|
+
e.currentTarget.style.display = "none";
|
|
208
|
+
}
|
|
209
|
+
}) : /* @__PURE__ */ y("div", {
|
|
210
|
+
className: "flex size-12 items-center justify-center rounded text-lg font-semibold text-white",
|
|
211
|
+
style: { background: $ },
|
|
212
|
+
children: (N || "S").slice(0, 1).toUpperCase()
|
|
213
|
+
}), /* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("h3", {
|
|
214
|
+
className: "text-lg font-semibold text-text-primary",
|
|
215
|
+
children: N || T("ggnomad.storefront.previewPlaceholderName", "Your Storefront")
|
|
216
|
+
}), /* @__PURE__ */ y("p", {
|
|
217
|
+
className: "text-xs text-text-secondary",
|
|
218
|
+
children: j ? `/store/${j}` : T("ggnomad.storefront.previewSlugPlaceholder", "/store/…")
|
|
219
|
+
})] })]
|
|
220
|
+
}),
|
|
221
|
+
z ? /* @__PURE__ */ y("p", {
|
|
222
|
+
className: "mt-4 text-sm text-text-primary",
|
|
223
|
+
children: z
|
|
224
|
+
}) : /* @__PURE__ */ y("p", {
|
|
225
|
+
className: "mt-4 text-sm text-text-secondary",
|
|
226
|
+
children: T("ggnomad.storefront.previewNoAbout", "Add an about text to introduce your business.")
|
|
227
|
+
}),
|
|
228
|
+
/* @__PURE__ */ y("div", {
|
|
229
|
+
className: "mt-4 inline-block rounded px-3 py-1.5 text-xs font-medium text-white",
|
|
230
|
+
style: { background: $ },
|
|
231
|
+
children: T("ggnomad.storefront.previewCta", "Book with us")
|
|
232
|
+
})
|
|
233
|
+
]
|
|
234
|
+
}), /* @__PURE__ */ y("div", {
|
|
235
|
+
className: "border-t border-border-seam bg-bg-surface p-4 text-xs text-text-secondary",
|
|
236
|
+
children: T("ggnomad.storefront.previewFooter", "ACTIVE properties, tours, events, and activities from this provider render below this banner on the public page.")
|
|
237
|
+
})]
|
|
238
|
+
})]
|
|
239
|
+
})]
|
|
240
|
+
}) : /* @__PURE__ */ y(i, {
|
|
241
|
+
title: T("ggnomad.storefront.title", "Operator Storefront"),
|
|
242
|
+
subtitle: T("ggnomad.storefront.missingProvider", "No provider selected."),
|
|
243
|
+
children: /* @__PURE__ */ y(g, {
|
|
244
|
+
icon: /* @__PURE__ */ y(l, { className: "size-4" }),
|
|
245
|
+
children: T("ggnomad.storefront.missingProviderHelp", "Open a provider from the Provider Dashboard and choose “Manage storefront”.")
|
|
246
|
+
})
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
//#endregion
|
|
250
|
+
export { T as StorefrontPage };
|
|
251
|
+
|
|
252
|
+
//# sourceMappingURL=StorefrontPage.js.map
|
|
@@ -0,0 +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-dom';\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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"}
|