@burdenoff/microfe-tours 2026.1014.1 → 2026.1109.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/constants/featureFlags.d.ts +24 -0
- package/dist/constants/featureFlags.js +1 -2
- package/dist/constants/featureFlags.js.map +1 -1
- package/dist/constants/permissions.d.ts +48 -0
- package/dist/constants/permissions.js +1 -2
- package/dist/constants/permissions.js.map +1 -1
- package/dist/dev/main.d.ts +0 -0
- package/dist/generated/global-operations.d.ts +1318 -0
- package/dist/generated/global-operations.js +2 -3
- package/dist/generated/global-operations.js.map +1 -1
- package/dist/generated/global-types.d.ts +26469 -0
- package/dist/generated/wspace-operations.d.ts +1 -0
- package/dist/generated/wspace-types.d.ts +108647 -0
- package/dist/index.d.ts +5 -0
- package/dist/test/happydom.d.ts +1 -0
- package/dist/test/testing-library.d.ts +1 -0
- package/dist/tours/ToursAdminRoot.d.ts +14 -0
- package/dist/tours/ToursAdminRoot.js +14 -13
- package/dist/tours/ToursAdminRoot.js.map +1 -1
- package/dist/tours/ToursAdminRoutes.d.ts +11 -0
- package/dist/tours/ToursAdminRoutes.js +12 -13
- package/dist/tours/ToursAdminRoutes.js.map +1 -1
- package/dist/tours/ToursRoot.d.ts +9 -0
- package/dist/tours/ToursRoot.js +1 -1
- package/dist/tours/ToursUserRoot.d.ts +13 -0
- package/dist/tours/ToursUserRoot.js +14 -13
- package/dist/tours/ToursUserRoot.js.map +1 -1
- package/dist/tours/ToursUserRoutes.d.ts +14 -0
- package/dist/tours/ToursUserRoutes.js +13 -13
- package/dist/tours/ToursUserRoutes.js.map +1 -1
- package/dist/tours/components/MarkdownEditor.d.ts +12 -0
- package/dist/tours/components/MarkdownEditor.js +1 -2
- package/dist/tours/components/MarkdownEditor.js.map +1 -1
- package/dist/tours/components/RouteHelpers.d.ts +31 -0
- package/dist/tours/components/RouteHelpers.js +1 -2
- package/dist/tours/components/RouteHelpers.js.map +1 -1
- package/dist/tours/components/ToursAdminNavRegistrar.d.ts +23 -0
- package/dist/tours/components/ToursAdminNavRegistrar.js +4 -3
- package/dist/tours/components/ToursAdminNavRegistrar.js.map +1 -1
- package/dist/tours/hooks/useMediaLibrary.d.ts +20 -0
- package/dist/tours/hooks/useMediaLibrary.js +1 -2
- package/dist/tours/hooks/useMediaLibrary.js.map +1 -1
- package/dist/tours/hooks/useStepSequencer.d.ts +18 -0
- package/dist/tours/hooks/useStepSequencer.js +16 -13
- package/dist/tours/hooks/useStepSequencer.js.map +1 -1
- package/dist/tours/hooks/useTourAnalytics.d.ts +30 -0
- package/dist/tours/hooks/useTourAnalytics.js +1 -2
- package/dist/tours/hooks/useTourAnalytics.js.map +1 -1
- package/dist/tours/hooks/useTourBuilder.d.ts +59 -0
- package/dist/tours/hooks/useTourBuilder.js +10 -6
- package/dist/tours/hooks/useTourBuilder.js.map +1 -1
- package/dist/tours/hooks/useTourLifecycle.d.ts +21 -0
- package/dist/tours/hooks/useTourLifecycle.js +4 -5
- package/dist/tours/hooks/useTourLifecycle.js.map +1 -1
- package/dist/tours/hooks/useTourPreview.d.ts +31 -0
- package/dist/tours/hooks/useTourPreview.js +5 -6
- package/dist/tours/hooks/useTourPreview.js.map +1 -1
- package/dist/tours/hooks/useToursList.d.ts +15 -0
- package/dist/tours/hooks/useToursList.js +1 -2
- package/dist/tours/hooks/useToursList.js.map +1 -1
- package/dist/tours/hooks/useToursPermissionAccess.d.ts +15 -0
- package/dist/tours/hooks/useToursPermissionAccess.js +1 -2
- package/dist/tours/hooks/useToursPermissionAccess.js.map +1 -1
- package/dist/tours/i18n.d.ts +413 -0
- package/dist/tours/i18n.js +1 -2
- package/dist/tours/i18n.js.map +1 -1
- package/dist/tours/index.d.ts +21 -0
- package/dist/tours/index.js +6 -5
- package/dist/tours/pages/AnalyticsDashboardPage.d.ts +5 -0
- package/dist/tours/pages/AnalyticsDashboardPage.js +6 -7
- package/dist/tours/pages/AnalyticsDashboardPage.js.map +1 -1
- package/dist/tours/pages/MediaLibraryPage.d.ts +5 -0
- package/dist/tours/pages/MediaLibraryPage.js +10 -11
- package/dist/tours/pages/MediaLibraryPage.js.map +1 -1
- package/dist/tours/pages/MyToursPage.d.ts +10 -0
- package/dist/tours/pages/MyToursPage.js +5 -6
- package/dist/tours/pages/MyToursPage.js.map +1 -1
- package/dist/tours/pages/MyToursProgressPage.d.ts +5 -0
- package/dist/tours/pages/MyToursProgressPage.js +5 -6
- package/dist/tours/pages/MyToursProgressPage.js.map +1 -1
- package/dist/tours/pages/SchedulerPage.d.ts +5 -0
- package/dist/tours/pages/SchedulerPage.js +10 -11
- package/dist/tours/pages/SchedulerPage.js.map +1 -1
- package/dist/tours/pages/StepEditorPage.d.ts +6 -0
- package/dist/tours/pages/StepEditorPage.js +302 -309
- package/dist/tours/pages/StepEditorPage.js.map +1 -1
- package/dist/tours/pages/SystemAnalyticsPage.d.ts +5 -0
- package/dist/tours/pages/SystemAnalyticsPage.js +1 -2
- package/dist/tours/pages/SystemAnalyticsPage.js.map +1 -1
- package/dist/tours/pages/TemplateLibraryPage.d.ts +5 -0
- package/dist/tours/pages/TemplateLibraryPage.js +15 -16
- package/dist/tours/pages/TemplateLibraryPage.js.map +1 -1
- package/dist/tours/pages/TourBuilderPage.d.ts +1 -0
- package/dist/tours/pages/TourBuilderPage.js +273 -300
- package/dist/tours/pages/TourBuilderPage.js.map +1 -1
- package/dist/tours/pages/TourPreviewPage.d.ts +5 -0
- package/dist/tours/pages/TourPreviewPage.js +9 -10
- package/dist/tours/pages/TourPreviewPage.js.map +1 -1
- package/dist/tours/pages/ToursListPage.d.ts +5 -0
- package/dist/tours/pages/ToursListPage.js +157 -157
- package/dist/tours/pages/ToursListPage.js.map +1 -1
- package/dist/tours/providers/ToursProvider.d.ts +25 -0
- package/dist/tours/providers/ToursProvider.js +1 -2
- package/dist/tours/providers/ToursProvider.js.map +1 -1
- package/dist/tours/store/toursStore.d.ts +40 -0
- package/dist/tours/store/toursStore.js +5 -6
- package/dist/tours/store/toursStore.js.map +1 -1
- package/dist/tours/telemetry.d.ts +7 -0
- package/dist/tours/telemetry.js +1 -2
- package/dist/tours/telemetry.js.map +1 -1
- package/dist/tours/types/index.d.ts +348 -0
- package/dist/tours/utils/index.d.ts +28 -0
- package/dist/tours/utils/index.js +3 -4
- package/dist/tours/utils/index.js.map +1 -1
- package/dist/tours/utils/joinPath.d.ts +8 -0
- package/dist/tours/utils/joinPath.js +1 -2
- package/dist/tours/utils/joinPath.js.map +1 -1
- package/dist/utils/nativeBridge.d.ts +2 -0
- package/dist/utils/nativeBridge.js +1 -2
- package/package.json +62 -51
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MyToursPage.js","names":[],"sources":["../../../src/tours/pages/MyToursPage.tsx"],"sourcesContent":["import { useEffect, useRef } from 'react';\nimport { useSearchParams } from 'react-router';\nimport { CheckCircle2, Circle, MinusCircle, Play } from 'lucide-react';\nimport {\n useGetToursForContextQuery,\n type GetToursForContextQuery,\n} from '../../generated/global-operations';\nimport { useToursContext } from '../providers/ToursProvider';\nimport { useToursI18n, type ToursTranslationKey } from '../i18n';\nimport { Markdown } from '@burdenoff/fe-libs/shared/components';\nimport { useEventBus } from '@burdenoff/fe-libs/shared/events';\nimport {\n EmphasisPanel,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\n\ntype TourSummary = GetToursForContextQuery['toursForContext'][number];\n\n// Plain slug validator for productId and moduleId.\nconst SLUG_PATTERN = /^[a-zA-Z0-9_-]+$/;\n\n// pageId is documented with path-style values like \"settings/team\" per\n// ToursRootProps — tighten to URL-safe characters only, but permit `/`.\nconst PAGE_PATH_PATTERN = /^[a-zA-Z0-9_\\-/]+$/;\n\nfunction safeSlug<T extends string | undefined>(\n raw: string | null | undefined,\n fallback: T\n): string | T {\n if (raw && SLUG_PATTERN.test(raw)) return raw;\n return fallback;\n}\n\nfunction safePageId<T extends string | undefined>(\n raw: string | null | undefined,\n fallback: T\n): string | T {\n if (raw && PAGE_PATH_PATTERN.test(raw)) return raw;\n return fallback;\n}\n\n/**\n * MyToursPage — user-facing browse for available tours.\n *\n * Lists tours returned by `toursForContext` (filtered to the current\n * product/module/page surface) and decorates each with the requesting\n * user's `TourUserProgress` status (Completed / In progress / Skipped /\n * Not started). Read-only — tours are actually played via the SDK overlay,\n * not via mutations on this page.\n */\nexport function MyToursPage() {\n const { tr } = useToursI18n();\n const {\n productId: shellProductId,\n moduleId: shellModuleId,\n pageId: shellPageId,\n workspaceId,\n currentUser,\n } = useToursContext();\n const [searchParams] = useSearchParams();\n const eventBus = useEventBus();\n\n // Emit page view event exactly once per mount. A ref guard keeps the effect\n // semantically mount-only while satisfying exhaustive-deps with the real\n // dependency list, so we don't have to disable the lint rule.\n const pageViewedRef = useRef(false);\n useEffect(() => {\n if (pageViewedRef.current) return;\n pageViewedRef.current = true;\n eventBus.emit('tours.page.viewed', {\n page: '/tours',\n productId: shellProductId,\n moduleId: shellModuleId,\n pageId: shellPageId,\n workspaceId,\n });\n }, [eventBus, shellProductId, shellModuleId, shellPageId, workspaceId]);\n\n // Preferred source is the shell's productId prop (the host app knows\n // which product it is — \"workspaces\", \"vibecontrols\", \"bigconsole\", etc.).\n // Fall back to a query-param override for deep-link scenarios, but only\n // after passing a plain-slug validator so we never forward arbitrary\n // strings into the GraphQL variables. No hardcoded product default —\n // if neither source provides one, skip the query entirely so non-\n // workspaces integrations don't get workspaces tours.\n const productId =\n safeSlug(shellProductId, undefined) ?? safeSlug(searchParams.get('productId'), undefined);\n const moduleId =\n safeSlug(shellModuleId, undefined) ?? safeSlug(searchParams.get('moduleId'), undefined);\n const pageId =\n safePageId(shellPageId, undefined) ?? safePageId(searchParams.get('pageId'), undefined);\n\n const {\n data: toursData,\n loading: toursLoading,\n error,\n refetch,\n } = useGetToursForContextQuery({\n variables: productId ? { context: { productId, moduleId, pageId, workspaceId } } : undefined,\n skip: !productId || !currentUser?.id,\n context: {\n requiredWorkspaceAuth: Boolean(workspaceId),\n requiredWorkspaceId: workspaceId,\n queryDeduplication: false,\n },\n fetchPolicy: 'no-cache',\n });\n\n // A pending scope change or failed request must never present old progress.\n const tours =\n toursLoading || error || !currentUser?.id || !productId\n ? []\n : (toursData?.toursForContext ?? []);\n const isLoading = toursLoading;\n\n // Surface a single, clearly-emphasised \"pick up where you left off / start\n // here\" tour: prefer one already in progress, then the first not-yet-started\n // tour, so a new user always has one obvious next walkthrough to take.\n const featuredTour =\n tours.find((tour: TourSummary) => tour.myProgress?.status === 'IN_PROGRESS') ??\n tours.find(\n (tour: TourSummary) =>\n !tour.myProgress?.status ||\n (tour.myProgress.status !== 'COMPLETED' &&\n tour.myProgress.status !== 'SKIPPED' &&\n tour.myProgress.status !== 'DISMISSED')\n );\n const restTours = tours.filter((tour: TourSummary) => tour.id !== featuredTour?.id);\n\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n <div>\n <h1 className=\"text-xl sm:text-2xl font-bold text-text-primary\">\n <GradientText>{tr('myTours.title')}</GradientText>\n </h1>\n <p className=\"text-sm sm:text-base text-text-secondary\">{tr('myTours.subtitle')}</p>\n </div>\n\n <PagePurpose>{tr('myTours.purpose')}</PagePurpose>\n\n {error ? (\n <div role=\"alert\" className=\"space-y-3 text-sm text-text-secondary\">\n <p>{tr('userTours.loadError')}</p>\n <button\n type=\"button\"\n onClick={() => void refetch().catch(() => undefined)}\n className=\"px-4 py-2 rounded-md bg-action-primary-bg text-action-primary-text\"\n >\n {tr('routeErrors.tryAgain')}\n </button>\n </div>\n ) : isLoading ? (\n <output className=\"text-sm text-text-secondary\">{tr('myTours.loading')}</output>\n ) : tours.length === 0 ? (\n <IllustratedEmptyState illustration=\"empty-generic\" title={tr('myTours.empty')} />\n ) : (\n <div className=\"space-y-4 sm:space-y-6\">\n {/* Featured \"start here\" tour — the single emphasis zone on this page. */}\n {featuredTour ? (\n <EmphasisPanel className=\"p-4 sm:p-5\">\n <div className=\"flex items-start justify-between gap-3\">\n <h2 className=\"text-lg font-semibold text-text-primary line-clamp-2\">\n {featuredTour.name}\n </h2>\n <ProgressBadge status={featuredTour.myProgress?.status} tr={tr} />\n </div>\n {featuredTour.description ? (\n <Markdown inline className=\"mt-2 text-sm text-text-secondary line-clamp-3\">\n {featuredTour.description}\n </Markdown>\n ) : null}\n </EmphasisPanel>\n ) : null}\n\n {restTours.length > 0 ? (\n <ul className=\"grid gap-3 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3\">\n {restTours.map((tour: TourSummary) => (\n <li key={tour.id}>\n <GlassCard\n treatment=\"plain\"\n className=\"h-full p-4 flex flex-col gap-2 border-border-seam shadow-[var(--shadow-pop)]\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <h2 className=\"font-semibold text-text-primary line-clamp-2\">{tour.name}</h2>\n <ProgressBadge status={tour.myProgress?.status} tr={tr} />\n </div>\n {tour.description ? (\n <Markdown inline className=\"text-sm text-text-secondary line-clamp-3\">\n {tour.description}\n </Markdown>\n ) : null}\n </GlassCard>\n </li>\n ))}\n </ul>\n ) : null}\n </div>\n )}\n </div>\n );\n}\n\nfunction ProgressBadge({\n status,\n tr,\n}: {\n status: string | undefined;\n tr: (key: ToursTranslationKey) => string;\n}) {\n switch (status) {\n case 'COMPLETED':\n return (\n <span className=\"inline-flex items-center gap-1 text-xs text-success\">\n <CheckCircle2 size={14} />\n {tr('myTours.status.completed')}\n </span>\n );\n case 'IN_PROGRESS':\n return (\n <span className=\"inline-flex items-center gap-1 text-xs text-warning motion-safe:animate-vc-glow-pulse\">\n <Play size={14} />\n {tr('myTours.status.inProgress')}\n </span>\n );\n case 'SKIPPED':\n case 'DISMISSED':\n return (\n <span className=\"inline-flex items-center gap-1 text-xs text-text-secondary\">\n <MinusCircle size={14} />\n {tr('myTours.status.skipped')}\n </span>\n );\n default:\n return (\n <span className=\"inline-flex items-center gap-1 text-xs text-text-secondary\">\n <Circle size={14} />\n {tr('myTours.status.notStarted')}\n </span>\n );\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MyToursPage.js","names":["SLUG_PATTERN","PAGE_PATH_PATTERN","safeSlug","raw","fallback","safePageId","MyToursPage","useToursI18n","shellProductId","shellModuleId","shellPageId","useToursContext","searchParams","useSearchParams","eventBus","useEventBus","pageViewedRef","useRef","workspaceId","productId","moduleId","pageId","toursData","toursLoading","useGetToursForContextQuery","currentUser","tours","error","isLoading","featuredTour","tour","restTours","GradientText","tr","PagePurpose","refetch","IllustratedEmptyState","EmphasisPanel","ProgressBadge","Markdown","GlassCard","status","CheckCircle2","Play","MinusCircle","Circle"],"sources":["../../../src/tours/pages/MyToursPage.tsx"],"sourcesContent":["import { useEffect, useRef } from 'react';\nimport { useSearchParams } from 'react-router';\nimport { CheckCircle2, Circle, MinusCircle, Play } from 'lucide-react';\nimport {\n useGetToursForContextQuery,\n type GetToursForContextQuery,\n} from '../../generated/global-operations';\nimport { useToursContext } from '../providers/ToursProvider';\nimport { useToursI18n, type ToursTranslationKey } from '../i18n';\nimport { Markdown } from '@burdenoff/fe-libs/shared/components';\nimport { useEventBus } from '@burdenoff/fe-libs/shared/events';\nimport {\n EmphasisPanel,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\n\ntype TourSummary = GetToursForContextQuery['toursForContext'][number];\n\n// Plain slug validator for productId and moduleId.\nconst SLUG_PATTERN = /^[a-zA-Z0-9_-]+$/;\n\n// pageId is documented with path-style values like \"settings/team\" per\n// ToursRootProps — tighten to URL-safe characters only, but permit `/`.\nconst PAGE_PATH_PATTERN = /^[a-zA-Z0-9_\\-/]+$/;\n\nfunction safeSlug<T extends string | undefined>(\n raw: string | null | undefined,\n fallback: T\n): string | T {\n if (raw && SLUG_PATTERN.test(raw)) return raw;\n return fallback;\n}\n\nfunction safePageId<T extends string | undefined>(\n raw: string | null | undefined,\n fallback: T\n): string | T {\n if (raw && PAGE_PATH_PATTERN.test(raw)) return raw;\n return fallback;\n}\n\n/**\n * MyToursPage — user-facing browse for available tours.\n *\n * Lists tours returned by `toursForContext` (filtered to the current\n * product/module/page surface) and decorates each with the requesting\n * user's `TourUserProgress` status (Completed / In progress / Skipped /\n * Not started). Read-only — tours are actually played via the SDK overlay,\n * not via mutations on this page.\n */\nexport function MyToursPage() {\n const { tr } = useToursI18n();\n const {\n productId: shellProductId,\n moduleId: shellModuleId,\n pageId: shellPageId,\n workspaceId,\n currentUser,\n } = useToursContext();\n const [searchParams] = useSearchParams();\n const eventBus = useEventBus();\n\n // Emit page view event exactly once per mount. A ref guard keeps the effect\n // semantically mount-only while satisfying exhaustive-deps with the real\n // dependency list, so we don't have to disable the lint rule.\n const pageViewedRef = useRef(false);\n useEffect(() => {\n if (pageViewedRef.current) return;\n pageViewedRef.current = true;\n eventBus.emit('tours.page.viewed', {\n page: '/tours',\n productId: shellProductId,\n moduleId: shellModuleId,\n pageId: shellPageId,\n workspaceId,\n });\n }, [eventBus, shellProductId, shellModuleId, shellPageId, workspaceId]);\n\n // Preferred source is the shell's productId prop (the host app knows\n // which product it is — \"workspaces\", \"vibecontrols\", \"bigconsole\", etc.).\n // Fall back to a query-param override for deep-link scenarios, but only\n // after passing a plain-slug validator so we never forward arbitrary\n // strings into the GraphQL variables. No hardcoded product default —\n // if neither source provides one, skip the query entirely so non-\n // workspaces integrations don't get workspaces tours.\n const productId =\n safeSlug(shellProductId, undefined) ?? safeSlug(searchParams.get('productId'), undefined);\n const moduleId =\n safeSlug(shellModuleId, undefined) ?? safeSlug(searchParams.get('moduleId'), undefined);\n const pageId =\n safePageId(shellPageId, undefined) ?? safePageId(searchParams.get('pageId'), undefined);\n\n const {\n data: toursData,\n loading: toursLoading,\n error,\n refetch,\n } = useGetToursForContextQuery({\n variables: productId ? { context: { productId, moduleId, pageId, workspaceId } } : undefined,\n skip: !productId || !currentUser?.id,\n context: {\n requiredWorkspaceAuth: Boolean(workspaceId),\n requiredWorkspaceId: workspaceId,\n queryDeduplication: false,\n },\n fetchPolicy: 'no-cache',\n });\n\n // A pending scope change or failed request must never present old progress.\n const tours =\n toursLoading || error || !currentUser?.id || !productId\n ? []\n : (toursData?.toursForContext ?? []);\n const isLoading = toursLoading;\n\n // Surface a single, clearly-emphasised \"pick up where you left off / start\n // here\" tour: prefer one already in progress, then the first not-yet-started\n // tour, so a new user always has one obvious next walkthrough to take.\n const featuredTour =\n tours.find((tour: TourSummary) => tour.myProgress?.status === 'IN_PROGRESS') ??\n tours.find(\n (tour: TourSummary) =>\n !tour.myProgress?.status ||\n (tour.myProgress.status !== 'COMPLETED' &&\n tour.myProgress.status !== 'SKIPPED' &&\n tour.myProgress.status !== 'DISMISSED')\n );\n const restTours = tours.filter((tour: TourSummary) => tour.id !== featuredTour?.id);\n\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n <div>\n <h1 className=\"text-xl sm:text-2xl font-bold text-text-primary\">\n <GradientText>{tr('myTours.title')}</GradientText>\n </h1>\n <p className=\"text-sm sm:text-base text-text-secondary\">{tr('myTours.subtitle')}</p>\n </div>\n\n <PagePurpose>{tr('myTours.purpose')}</PagePurpose>\n\n {error ? (\n <div role=\"alert\" className=\"space-y-3 text-sm text-text-secondary\">\n <p>{tr('userTours.loadError')}</p>\n <button\n type=\"button\"\n onClick={() => void refetch().catch(() => undefined)}\n className=\"px-4 py-2 rounded-md bg-action-primary-bg text-action-primary-text\"\n >\n {tr('routeErrors.tryAgain')}\n </button>\n </div>\n ) : isLoading ? (\n <output className=\"text-sm text-text-secondary\">{tr('myTours.loading')}</output>\n ) : tours.length === 0 ? (\n <IllustratedEmptyState illustration=\"empty-generic\" title={tr('myTours.empty')} />\n ) : (\n <div className=\"space-y-4 sm:space-y-6\">\n {/* Featured \"start here\" tour — the single emphasis zone on this page. */}\n {featuredTour ? (\n <EmphasisPanel className=\"p-4 sm:p-5\">\n <div className=\"flex items-start justify-between gap-3\">\n <h2 className=\"text-lg font-semibold text-text-primary line-clamp-2\">\n {featuredTour.name}\n </h2>\n <ProgressBadge status={featuredTour.myProgress?.status} tr={tr} />\n </div>\n {featuredTour.description ? (\n <Markdown inline className=\"mt-2 text-sm text-text-secondary line-clamp-3\">\n {featuredTour.description}\n </Markdown>\n ) : null}\n </EmphasisPanel>\n ) : null}\n\n {restTours.length > 0 ? (\n <ul className=\"grid gap-3 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3\">\n {restTours.map((tour: TourSummary) => (\n <li key={tour.id}>\n <GlassCard\n treatment=\"plain\"\n className=\"h-full p-4 flex flex-col gap-2 border-border-seam shadow-[var(--shadow-pop)]\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <h2 className=\"font-semibold text-text-primary line-clamp-2\">{tour.name}</h2>\n <ProgressBadge status={tour.myProgress?.status} tr={tr} />\n </div>\n {tour.description ? (\n <Markdown inline className=\"text-sm text-text-secondary line-clamp-3\">\n {tour.description}\n </Markdown>\n ) : null}\n </GlassCard>\n </li>\n ))}\n </ul>\n ) : null}\n </div>\n )}\n </div>\n );\n}\n\nfunction ProgressBadge({\n status,\n tr,\n}: {\n status: string | undefined;\n tr: (key: ToursTranslationKey) => string;\n}) {\n switch (status) {\n case 'COMPLETED':\n return (\n <span className=\"inline-flex items-center gap-1 text-xs text-success\">\n <CheckCircle2 size={14} />\n {tr('myTours.status.completed')}\n </span>\n );\n case 'IN_PROGRESS':\n return (\n <span className=\"inline-flex items-center gap-1 text-xs text-warning motion-safe:animate-vc-glow-pulse\">\n <Play size={14} />\n {tr('myTours.status.inProgress')}\n </span>\n );\n case 'SKIPPED':\n case 'DISMISSED':\n return (\n <span className=\"inline-flex items-center gap-1 text-xs text-text-secondary\">\n <MinusCircle size={14} />\n {tr('myTours.status.skipped')}\n </span>\n );\n default:\n return (\n <span className=\"inline-flex items-center gap-1 text-xs text-text-secondary\">\n <Circle size={14} />\n {tr('myTours.status.notStarted')}\n </span>\n );\n }\n}\n"],"mappings":";;;;;;;;;;;AAsBA,IAAMA,IAAe,oBAIfC,IAAoB;AAE1B,SAASC,EACP,GACA,GACY;CAEZ,OADIC,KAAOH,EAAa,KAAKG,CAAG,IAAUA,IACnCC;AACT;AAEA,SAASC,EACP,GACA,GACY;CAEZ,OADIF,KAAOF,EAAkB,KAAKE,CAAG,IAAUA,IACxCC;AACT;AAWA,SAAgBE,IAAc;CAC5B,IAAM,EAAE,UAAOC,EAAa,GACtB,EACJ,WAAWC,GACX,UAAUC,GACV,QAAQC,GACR,gBACA,mBACEC,EAAgB,GACd,CAACC,KAAgBC,EAAgB,GACjCC,IAAWC,EAAY,GAKvBC,IAAgBC,EAAO,EAAK;CAClC,QAAgB;EACd,AAAID,EAAc,YAClB,EAAc,UAAU,IACxBF,EAAS,KAAK,qBAAqB;GACjC,MAAM;GACN,WAAWN;GACX,UAAUC;GACV,QAAQC;GACR;EACF,CAAC;CACH,GAAG;EAACI;EAAUN;EAAgBC;EAAeC;EAAaQ;CAAW,CAAC;CAStE,IAAMC,IACJjB,EAASM,GAAgB,KAAA,CAAS,KAAKN,EAASU,EAAa,IAAI,WAAW,GAAG,KAAA,CAAS,GACpFQ,IACJlB,EAASO,GAAe,KAAA,CAAS,KAAKP,EAASU,EAAa,IAAI,UAAU,GAAG,KAAA,CAAS,GAClFS,IACJhB,EAAWK,GAAa,KAAA,CAAS,KAAKL,EAAWO,EAAa,IAAI,QAAQ,GAAG,KAAA,CAAS,GAElF,EACJ,MAAMU,GACN,SAASC,GACT,UACA,eACEC,EAA2B;EAC7B,WAAWL,IAAY,EAAE,SAAS;GAAE;GAAW;GAAU;GAAQ;EAAY,EAAE,IAAI,KAAA;EACnF,MAAM,CAACA,KAAa,CAACM,GAAa;EAClC,SAAS;GACP,uBAAuB,EAAQP;GAC/B,qBAAqBA;GACrB,oBAAoB;EACtB;EACA,aAAa;CACf,CAAC,GAGKQ,IACJH,KAAgBI,KAAS,CAACF,GAAa,MAAM,CAACN,IAC1C,CAAC,IACAG,GAAW,mBAAmB,CAAC,GAChCM,IAAYL,GAKZM,IACJH,EAAM,MAAM,MAAsBI,EAAK,YAAY,WAAW,aAAa,KAC3EJ,EAAM,MACH,MACC,CAACI,EAAK,YAAY,UACjBA,EAAK,WAAW,WAAW,eAC1BA,EAAK,WAAW,WAAW,aAC3BA,EAAK,WAAW,WAAW,WACjC,GACIC,IAAYL,EAAM,QAAQ,MAAsBI,EAAK,OAAOD,GAAc,EAAE;CAElF,OACE,kBAAC,OAAD;EAAK,WAAU;EAAf,UAAA;GACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;IAAI,WAAU;IACZ,UAAA,kBAACG,GAAD,EAAA,UAAeC,EAAG,eAAe,EAAgB,CAAA;GAC/C,CAAA,GACJ,kBAAC,KAAD;IAAG,WAAU;IAA4C,UAAAA,EAAG,kBAAkB;GAAK,CAAA,CAChF,EAAA,CAAA;GAEL,kBAACC,GAAD,EAAA,UAAcD,EAAG,iBAAiB,EAAe,CAAA;GAEhDN,IACC,kBAAC,OAAD;IAAK,MAAK;IAAQ,WAAU;IAA5B,UAAA,CACE,kBAAC,KAAD,EAAA,UAAIM,EAAG,qBAAqB,EAAK,CAAA,GACjC,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,KAAKE,EAAQ,CAAC,CAAC,YAAY,KAAA,CAAS;KACnD,WAAU;KAET,UAAAF,EAAG,sBAAsB;IACpB,CAAA,CACL;GACH,CAAA,IAAAL,IACF,kBAAC,UAAD;IAAQ,WAAU;IAA+B,UAAAK,EAAG,iBAAiB;GAAU,CAAA,IAC7EP,EAAM,WAAW,IACnB,kBAACU,GAAD;IAAuB,cAAa;IAAgB,OAAOH,EAAG,eAAe;GAAI,CAAA,IAEjF,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CAEGJ,IACC,kBAACQ,GAAD;KAAe,WAAU;KAAzB,UAAA,CACE,kBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACE,kBAAC,MAAD;OAAI,WAAU;OACX,UAAAR,EAAa;MACZ,CAAA,GACJ,kBAACS,GAAD;OAAe,QAAQT,EAAa,YAAY;OAAY;MAAK,CAAA,CAC9D;KACJ,CAAA,GAAAA,EAAa,cACZ,kBAACU,GAAD;MAAU,QAAA;MAAO,WAAU;MACxB,UAAAV,EAAa;KACN,CAAA,IACR,IACS;IACb,CAAA,IAAA,MAEHE,EAAU,SAAS,IAClB,kBAAC,MAAD;KAAI,WAAU;KACX,UAAAA,EAAU,KAAK,MACd,kBAAC,MAAD,EAAA,UACE,kBAACS,GAAD;MACE,WAAU;MACV,WAAU;MAFZ,UAAA,CAIE,kBAAC,OAAD;OAAK,WAAU;OAAf,UAAA,CACE,kBAAC,MAAD;QAAI,WAAU;QAAgD,UAAAV,EAAK;OAAS,CAAA,GAC5E,kBAACQ,GAAD;QAAe,QAAQR,EAAK,YAAY;QAAY;OAAK,CAAA,CACtD;MACJ,CAAA,GAAAA,EAAK,cACJ,kBAACS,GAAD;OAAU,QAAA;OAAO,WAAU;OACxB,UAAAT,EAAK;MACE,CAAA,IACR,IACK;KACT,CAAA,EAAA,GAfKA,EAAK,EAeV,CACL;IACC,CAAA,IACF,IACD;;EAEJ;;AAET;AAEA,SAASQ,EAAc,EACrB,WACA,SAIC;CACD,QAAQG,GAAR;EACE,KAAK,aACH,OACE,kBAAC,QAAD;GAAM,WAAU;GAAhB,UAAA,CACE,kBAACC,GAAD,EAAc,MAAM,GAAK,CAAA,GACxBT,EAAG,0BAA0B,CAC1B;;EAEV,KAAK,eACH,OACE,kBAAC,QAAD;GAAM,WAAU;GAAhB,UAAA,CACE,kBAACU,GAAD,EAAM,MAAM,GAAK,CAAA,GAChBV,EAAG,2BAA2B,CAC3B;;EAEV,KAAK;EACL,KAAK,aACH,OACE,kBAAC,QAAD;GAAM,WAAU;GAAhB,UAAA,CACE,kBAACW,GAAD,EAAa,MAAM,GAAK,CAAA,GACvBX,EAAG,wBAAwB,CACxB;;EAEV,SACE,OACE,kBAAC,QAAD;GAAM,WAAU;GAAhB,UAAA,CACE,kBAACY,GAAD,EAAQ,MAAM,GAAK,CAAA,GAClBZ,EAAG,2BAA2B,CAC3B;;CAEZ;AACF"}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useGetUserTourProgressesQuery as e } from "../../generated/global-operations.js";
|
|
2
2
|
import { useToursContext as t } from "../providers/ToursProvider.js";
|
|
3
|
-
import {
|
|
3
|
+
import { useToursI18n as n } from "../i18n.js";
|
|
4
4
|
import { useEffect as r, useMemo as i, useRef as a } from "react";
|
|
5
5
|
import { CheckCircle2 as o, HelpCircle as s, MinusCircle as c, Play as l } from "lucide-react";
|
|
6
6
|
import { jsx as u, jsxs as d } from "react/jsx-runtime";
|
|
7
7
|
import { EmphasisPanel as f, IllustratedEmptyState as p, PagePurpose as m } from "@burdenoff/fe-libs/ui";
|
|
8
8
|
import { useEventBus as h } from "@burdenoff/fe-libs/shared/events";
|
|
9
|
-
|
|
9
|
+
// src/tours/pages/MyToursProgressPage.tsx
|
|
10
10
|
function g() {
|
|
11
|
-
let { tr: g } =
|
|
11
|
+
let { tr: g } = n(), { currentUser: v, productId: y, moduleId: b, pageId: x, workspaceId: S } = t(), C = h(), w = a(!1);
|
|
12
12
|
r(() => {
|
|
13
13
|
w.current || (w.current = !0, C.emit("tours.page.viewed", {
|
|
14
14
|
page: "/tours/progress",
|
|
@@ -24,7 +24,7 @@ function g() {
|
|
|
24
24
|
x,
|
|
25
25
|
S
|
|
26
26
|
]);
|
|
27
|
-
let { data: T, loading: E, error: D, refetch: O } =
|
|
27
|
+
let { data: T, loading: E, error: D, refetch: O } = e({
|
|
28
28
|
variables: {
|
|
29
29
|
userId: v?.id,
|
|
30
30
|
pagination: {
|
|
@@ -168,7 +168,6 @@ function _({ title: e, icon: t, rows: n, emptyText: r }) {
|
|
|
168
168
|
})]
|
|
169
169
|
});
|
|
170
170
|
}
|
|
171
|
-
//#endregion
|
|
172
171
|
export { g as MyToursProgressPage };
|
|
173
172
|
|
|
174
173
|
//# sourceMappingURL=MyToursProgressPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MyToursProgressPage.js","names":[],"sources":["../../../src/tours/pages/MyToursProgressPage.tsx"],"sourcesContent":["import { useMemo, useEffect, useRef } from 'react';\nimport { CheckCircle2, HelpCircle, MinusCircle, Play } from 'lucide-react';\nimport { useGetUserTourProgressesQuery } from '../../generated/global-operations';\nimport { useToursContext } from '../providers/ToursProvider';\nimport { useToursI18n } from '../i18n';\nimport { useEventBus } from '@burdenoff/fe-libs/shared/events';\nimport { EmphasisPanel, IllustratedEmptyState, PagePurpose } from '@burdenoff/fe-libs/ui';\n\ntype ProgressGroup = 'IN_PROGRESS' | 'COMPLETED' | 'SKIPPED' | 'OTHER';\n\ntype ProgressRow = {\n id: string;\n status: string;\n tour: { id: string; name: string; productId?: string; moduleId?: string; pageId?: string };\n lastViewedAt?: string | null;\n};\n\n/**\n * MyToursProgressPage — user-facing read-only view of the current user's\n * tour interactions, grouped by status (In progress / Completed / Skipped).\n */\nexport function MyToursProgressPage() {\n const { tr } = useToursI18n();\n const {\n currentUser,\n productId: shellProductId,\n moduleId: shellModuleId,\n pageId: shellPageId,\n workspaceId,\n } = useToursContext();\n const eventBus = useEventBus();\n\n // Emit page view event exactly once per mount. A ref guard keeps the effect\n // semantically mount-only while satisfying exhaustive-deps with the real\n // dependency list, so we don't have to disable the lint rule.\n const pageViewedRef = useRef(false);\n useEffect(() => {\n if (pageViewedRef.current) return;\n pageViewedRef.current = true;\n eventBus.emit('tours.page.viewed', {\n page: '/tours/progress',\n productId: shellProductId,\n moduleId: shellModuleId,\n pageId: shellPageId,\n workspaceId,\n });\n }, [eventBus, shellProductId, shellModuleId, shellPageId, workspaceId]);\n\n const { data, loading, error, refetch } = useGetUserTourProgressesQuery({\n variables: { userId: currentUser?.id, pagination: { limit: 100, offset: 0 } },\n skip: !currentUser?.id,\n context: {\n requiredWorkspaceAuth: Boolean(workspaceId),\n requiredWorkspaceId: workspaceId,\n queryDeduplication: false,\n },\n fetchPolicy: 'no-cache',\n });\n\n const grouped = useMemo(() => {\n const result: Record<ProgressGroup, NonNullable<typeof data>['userTourProgresses']> = {\n IN_PROGRESS: [],\n COMPLETED: [],\n SKIPPED: [],\n OTHER: [],\n };\n // Filter the raw progress list to the shell's product/module/page context\n // so a user's vibecontrols session doesn't see workspaces / bigconsole\n // progress rows. `userTourProgresses` returns everything scoped by userId;\n // this MFE is mounted per-shell so we narrow to what the shell advertises.\n // When the shell didn't supply a filter, the corresponding level is not\n // checked (e.g., no shellModuleId → accept any moduleId).\n const rows = (\n loading || error || !currentUser?.id ? [] : (data?.userTourProgresses ?? [])\n ).filter((row: ProgressRow) => {\n if (shellProductId && row.tour.productId !== shellProductId) return false;\n if (shellModuleId && row.tour.moduleId && row.tour.moduleId !== shellModuleId) return false;\n if (shellPageId && row.tour.pageId && row.tour.pageId !== shellPageId) return false;\n return true;\n });\n for (const row of rows) {\n if (row.status === 'COMPLETED') result.COMPLETED.push(row);\n else if (row.status === 'IN_PROGRESS') result.IN_PROGRESS.push(row);\n else if (row.status === 'SKIPPED' || row.status === 'DISMISSED') result.SKIPPED.push(row);\n // Unknown / future status values fall through here so nothing is\n // silently dropped if the backend introduces a new state.\n else result.OTHER.push(row);\n }\n return result;\n }, [data, loading, error, currentUser?.id, shellProductId, shellModuleId, shellPageId]);\n\n const totalInContext =\n grouped.IN_PROGRESS.length +\n grouped.COMPLETED.length +\n grouped.SKIPPED.length +\n grouped.OTHER.length;\n\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n <div>\n <h1 className=\"text-xl sm:text-2xl font-bold text-text-primary\">\n {tr('myToursProgress.title')}\n </h1>\n <p className=\"text-sm sm:text-base text-text-secondary\">{tr('myToursProgress.subtitle')}</p>\n </div>\n\n <PagePurpose>{tr('myToursProgress.purpose')}</PagePurpose>\n\n {error ? (\n <div role=\"alert\" className=\"space-y-3 text-sm text-text-secondary\">\n <p>{tr('userTours.loadError')}</p>\n <button\n type=\"button\"\n onClick={() => void refetch().catch(() => undefined)}\n className=\"px-4 py-2 rounded-md bg-action-primary-bg text-action-primary-text\"\n >\n {tr('routeErrors.tryAgain')}\n </button>\n </div>\n ) : loading ? (\n <output className=\"text-sm text-text-secondary\">{tr('myToursProgress.loading')}</output>\n ) : totalInContext === 0 ? (\n <IllustratedEmptyState illustration=\"empty-data\" title={tr('myToursProgress.empty')} />\n ) : (\n <div className=\"space-y-6\">\n <EmphasisPanel className=\"p-4 sm:p-5\">\n <ProgressSection\n title={tr('myToursProgress.inProgress')}\n icon={<Play size={16} className=\"text-warning\" />}\n rows={grouped.IN_PROGRESS}\n emptyText={tr('myToursProgress.emptyInProgress')}\n />\n </EmphasisPanel>\n <ProgressSection\n title={tr('myToursProgress.completed')}\n icon={<CheckCircle2 size={16} className=\"text-success\" />}\n rows={grouped.COMPLETED}\n emptyText={tr('myToursProgress.emptyCompleted')}\n />\n <ProgressSection\n title={tr('myToursProgress.skipped')}\n icon={<MinusCircle size={16} className=\"text-text-secondary\" />}\n rows={grouped.SKIPPED}\n emptyText={tr('myToursProgress.emptySkipped')}\n />\n {grouped.OTHER.length > 0 ? (\n <ProgressSection\n title={tr('myToursProgress.other')}\n icon={<HelpCircle size={16} className=\"text-text-secondary\" />}\n rows={grouped.OTHER}\n emptyText={tr('myToursProgress.emptyOther')}\n />\n ) : null}\n </div>\n )}\n </div>\n );\n}\n\nfunction ProgressSection({\n title,\n icon,\n rows,\n emptyText,\n}: {\n title: string;\n icon: React.ReactNode;\n rows: Array<{ id: string; tour: { id: string; name: string }; lastViewedAt?: string | null }>;\n emptyText: string;\n}) {\n return (\n <section className=\"space-y-2\">\n <h2 className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\">\n {icon}\n {title}\n <span className=\"text-xs text-text-secondary font-normal\">({rows.length})</span>\n </h2>\n {rows.length === 0 ? (\n <p className=\"text-sm text-text-secondary\" role=\"status\" aria-live=\"polite\">\n {emptyText}\n </p>\n ) : (\n <ul className=\"divide-y divide-border-seam rounded-md border border-border-seam\">\n {rows.map((row) => (\n <li key={row.id} className=\"flex items-center justify-between gap-3 p-3\">\n <span className=\"text-sm text-text-primary line-clamp-1\">{row.tour.name}</span>\n {row.lastViewedAt ? (\n <time className=\"text-xs text-text-secondary\" dateTime={row.lastViewedAt}>\n {new Date(row.lastViewedAt).toLocaleDateString()}\n </time>\n ) : null}\n </li>\n ))}\n </ul>\n )}\n </section>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MyToursProgressPage.js","names":["MyToursProgressPage","useToursI18n","shellProductId","shellModuleId","shellPageId","useToursContext","eventBus","useEventBus","pageViewedRef","useRef","workspaceId","useGetUserTourProgressesQuery","currentUser","grouped","useMemo","result","rows","loading","error","data","row","totalInContext","tr","PagePurpose","refetch","IllustratedEmptyState","EmphasisPanel","ProgressSection","Play","CheckCircle2","MinusCircle","HelpCircle","icon","title","emptyText"],"sources":["../../../src/tours/pages/MyToursProgressPage.tsx"],"sourcesContent":["import { useMemo, useEffect, useRef } from 'react';\nimport { CheckCircle2, HelpCircle, MinusCircle, Play } from 'lucide-react';\nimport { useGetUserTourProgressesQuery } from '../../generated/global-operations';\nimport { useToursContext } from '../providers/ToursProvider';\nimport { useToursI18n } from '../i18n';\nimport { useEventBus } from '@burdenoff/fe-libs/shared/events';\nimport { EmphasisPanel, IllustratedEmptyState, PagePurpose } from '@burdenoff/fe-libs/ui';\n\ntype ProgressGroup = 'IN_PROGRESS' | 'COMPLETED' | 'SKIPPED' | 'OTHER';\n\ntype ProgressRow = {\n id: string;\n status: string;\n tour: { id: string; name: string; productId?: string; moduleId?: string; pageId?: string };\n lastViewedAt?: string | null;\n};\n\n/**\n * MyToursProgressPage — user-facing read-only view of the current user's\n * tour interactions, grouped by status (In progress / Completed / Skipped).\n */\nexport function MyToursProgressPage() {\n const { tr } = useToursI18n();\n const {\n currentUser,\n productId: shellProductId,\n moduleId: shellModuleId,\n pageId: shellPageId,\n workspaceId,\n } = useToursContext();\n const eventBus = useEventBus();\n\n // Emit page view event exactly once per mount. A ref guard keeps the effect\n // semantically mount-only while satisfying exhaustive-deps with the real\n // dependency list, so we don't have to disable the lint rule.\n const pageViewedRef = useRef(false);\n useEffect(() => {\n if (pageViewedRef.current) return;\n pageViewedRef.current = true;\n eventBus.emit('tours.page.viewed', {\n page: '/tours/progress',\n productId: shellProductId,\n moduleId: shellModuleId,\n pageId: shellPageId,\n workspaceId,\n });\n }, [eventBus, shellProductId, shellModuleId, shellPageId, workspaceId]);\n\n const { data, loading, error, refetch } = useGetUserTourProgressesQuery({\n variables: { userId: currentUser?.id, pagination: { limit: 100, offset: 0 } },\n skip: !currentUser?.id,\n context: {\n requiredWorkspaceAuth: Boolean(workspaceId),\n requiredWorkspaceId: workspaceId,\n queryDeduplication: false,\n },\n fetchPolicy: 'no-cache',\n });\n\n const grouped = useMemo(() => {\n const result: Record<ProgressGroup, NonNullable<typeof data>['userTourProgresses']> = {\n IN_PROGRESS: [],\n COMPLETED: [],\n SKIPPED: [],\n OTHER: [],\n };\n // Filter the raw progress list to the shell's product/module/page context\n // so a user's vibecontrols session doesn't see workspaces / bigconsole\n // progress rows. `userTourProgresses` returns everything scoped by userId;\n // this MFE is mounted per-shell so we narrow to what the shell advertises.\n // When the shell didn't supply a filter, the corresponding level is not\n // checked (e.g., no shellModuleId → accept any moduleId).\n const rows = (\n loading || error || !currentUser?.id ? [] : (data?.userTourProgresses ?? [])\n ).filter((row: ProgressRow) => {\n if (shellProductId && row.tour.productId !== shellProductId) return false;\n if (shellModuleId && row.tour.moduleId && row.tour.moduleId !== shellModuleId) return false;\n if (shellPageId && row.tour.pageId && row.tour.pageId !== shellPageId) return false;\n return true;\n });\n for (const row of rows) {\n if (row.status === 'COMPLETED') result.COMPLETED.push(row);\n else if (row.status === 'IN_PROGRESS') result.IN_PROGRESS.push(row);\n else if (row.status === 'SKIPPED' || row.status === 'DISMISSED') result.SKIPPED.push(row);\n // Unknown / future status values fall through here so nothing is\n // silently dropped if the backend introduces a new state.\n else result.OTHER.push(row);\n }\n return result;\n }, [data, loading, error, currentUser?.id, shellProductId, shellModuleId, shellPageId]);\n\n const totalInContext =\n grouped.IN_PROGRESS.length +\n grouped.COMPLETED.length +\n grouped.SKIPPED.length +\n grouped.OTHER.length;\n\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n <div>\n <h1 className=\"text-xl sm:text-2xl font-bold text-text-primary\">\n {tr('myToursProgress.title')}\n </h1>\n <p className=\"text-sm sm:text-base text-text-secondary\">{tr('myToursProgress.subtitle')}</p>\n </div>\n\n <PagePurpose>{tr('myToursProgress.purpose')}</PagePurpose>\n\n {error ? (\n <div role=\"alert\" className=\"space-y-3 text-sm text-text-secondary\">\n <p>{tr('userTours.loadError')}</p>\n <button\n type=\"button\"\n onClick={() => void refetch().catch(() => undefined)}\n className=\"px-4 py-2 rounded-md bg-action-primary-bg text-action-primary-text\"\n >\n {tr('routeErrors.tryAgain')}\n </button>\n </div>\n ) : loading ? (\n <output className=\"text-sm text-text-secondary\">{tr('myToursProgress.loading')}</output>\n ) : totalInContext === 0 ? (\n <IllustratedEmptyState illustration=\"empty-data\" title={tr('myToursProgress.empty')} />\n ) : (\n <div className=\"space-y-6\">\n <EmphasisPanel className=\"p-4 sm:p-5\">\n <ProgressSection\n title={tr('myToursProgress.inProgress')}\n icon={<Play size={16} className=\"text-warning\" />}\n rows={grouped.IN_PROGRESS}\n emptyText={tr('myToursProgress.emptyInProgress')}\n />\n </EmphasisPanel>\n <ProgressSection\n title={tr('myToursProgress.completed')}\n icon={<CheckCircle2 size={16} className=\"text-success\" />}\n rows={grouped.COMPLETED}\n emptyText={tr('myToursProgress.emptyCompleted')}\n />\n <ProgressSection\n title={tr('myToursProgress.skipped')}\n icon={<MinusCircle size={16} className=\"text-text-secondary\" />}\n rows={grouped.SKIPPED}\n emptyText={tr('myToursProgress.emptySkipped')}\n />\n {grouped.OTHER.length > 0 ? (\n <ProgressSection\n title={tr('myToursProgress.other')}\n icon={<HelpCircle size={16} className=\"text-text-secondary\" />}\n rows={grouped.OTHER}\n emptyText={tr('myToursProgress.emptyOther')}\n />\n ) : null}\n </div>\n )}\n </div>\n );\n}\n\nfunction ProgressSection({\n title,\n icon,\n rows,\n emptyText,\n}: {\n title: string;\n icon: React.ReactNode;\n rows: Array<{ id: string; tour: { id: string; name: string }; lastViewedAt?: string | null }>;\n emptyText: string;\n}) {\n return (\n <section className=\"space-y-2\">\n <h2 className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\">\n {icon}\n {title}\n <span className=\"text-xs text-text-secondary font-normal\">({rows.length})</span>\n </h2>\n {rows.length === 0 ? (\n <p className=\"text-sm text-text-secondary\" role=\"status\" aria-live=\"polite\">\n {emptyText}\n </p>\n ) : (\n <ul className=\"divide-y divide-border-seam rounded-md border border-border-seam\">\n {rows.map((row) => (\n <li key={row.id} className=\"flex items-center justify-between gap-3 p-3\">\n <span className=\"text-sm text-text-primary line-clamp-1\">{row.tour.name}</span>\n {row.lastViewedAt ? (\n <time className=\"text-xs text-text-secondary\" dateTime={row.lastViewedAt}>\n {new Date(row.lastViewedAt).toLocaleDateString()}\n </time>\n ) : null}\n </li>\n ))}\n </ul>\n )}\n </section>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useToursPermissionAccess as e } from "../hooks/useToursPermissionAccess.js";
|
|
2
2
|
import { useToursContext as t } from "../providers/ToursProvider.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
3
|
+
import { useToursI18n as n } from "../i18n.js";
|
|
4
|
+
import { TOUR_PERMISSIONS as r } from "../../constants/permissions.js";
|
|
5
|
+
import { emitToursApiFailed as i } from "../telemetry.js";
|
|
6
|
+
import { useCreateTourScheduleMutation as a, useGetTourSchedulesQuery as o } from "../../generated/global-operations.js";
|
|
7
7
|
import { useState as s } from "react";
|
|
8
8
|
import { PermissionGate as c } from "@burdenoff/fe-libs/shared/components";
|
|
9
9
|
import { ArrowLeft as l, Calendar as u, Clock as d, Save as f } from "lucide-react";
|
|
@@ -11,15 +11,15 @@ import { jsx as p, jsxs as m } from "react/jsx-runtime";
|
|
|
11
11
|
import { useNavigate as h, useParams as g } from "react-router";
|
|
12
12
|
import { Button as _, EmphasisPanel as v, GlassCard as y, PagePurpose as b } from "@burdenoff/fe-libs/ui";
|
|
13
13
|
import { useEventBus as x } from "@burdenoff/fe-libs/shared/events";
|
|
14
|
-
|
|
14
|
+
// src/tours/pages/SchedulerPage.tsx
|
|
15
15
|
function S() {
|
|
16
|
-
let { tr: S } =
|
|
16
|
+
let { tr: S } = n(), { tourId: C } = g(), w = h(), T = t(), { basePath: E = "/" } = T, D = x(), { hasPermission: O } = e(), k = O(r.TOUR_SCHEDULE_CREATE), A = O(r.TOUR_SCHEDULE_LIST), [j, M] = s({
|
|
17
17
|
startDate: "",
|
|
18
18
|
endDate: "",
|
|
19
19
|
recurrencePattern: "DAILY",
|
|
20
20
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
21
21
|
isActive: !0
|
|
22
|
-
}), [N, P] = s(!1), [F] =
|
|
22
|
+
}), [N, P] = s(!1), [F] = a(), { data: I } = o({
|
|
23
23
|
variables: { tourId: C },
|
|
24
24
|
skip: !C || !A
|
|
25
25
|
}), L = I?.tourSchedules ?? [], R = async (e) => {
|
|
@@ -38,7 +38,7 @@ function S() {
|
|
|
38
38
|
refetchQueries: ["GetTourSchedules"]
|
|
39
39
|
}), w(`${E}/${C}/edit`);
|
|
40
40
|
} catch {
|
|
41
|
-
|
|
41
|
+
i(D, "schedule.create", T);
|
|
42
42
|
} finally {
|
|
43
43
|
P(!1);
|
|
44
44
|
}
|
|
@@ -231,7 +231,7 @@ function S() {
|
|
|
231
231
|
className: "flex-1 min-h-[44px]",
|
|
232
232
|
children: S("tourScheduler.cancel", "Cancel")
|
|
233
233
|
}), /* @__PURE__ */ p(c, {
|
|
234
|
-
permission:
|
|
234
|
+
permission: r.TOUR_SCHEDULE_CREATE,
|
|
235
235
|
children: /* @__PURE__ */ m(_, {
|
|
236
236
|
type: "submit",
|
|
237
237
|
disabled: N || !k,
|
|
@@ -245,7 +245,6 @@ function S() {
|
|
|
245
245
|
]
|
|
246
246
|
});
|
|
247
247
|
}
|
|
248
|
-
//#endregion
|
|
249
248
|
export { S as SchedulerPage };
|
|
250
249
|
|
|
251
250
|
//# sourceMappingURL=SchedulerPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SchedulerPage.js","names":[],"sources":["../../../src/tours/pages/SchedulerPage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useParams, useNavigate } from 'react-router';\nimport { Calendar, Clock, ArrowLeft, Save } from 'lucide-react';\nimport { PermissionGate } from '@burdenoff/fe-libs/shared/components';\nimport { Button, EmphasisPanel, GlassCard, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { useEventBus } from '@burdenoff/fe-libs/shared/events';\nimport { useToursPermissionAccess } from '../hooks/useToursPermissionAccess';\nimport { useToursContext } from '../providers/ToursProvider';\nimport { useToursI18n } from '../i18n';\nimport { TOUR_PERMISSIONS } from '../../constants/permissions';\nimport type { RecurrencePattern } from '../types';\nimport { emitToursApiFailed } from '../telemetry';\nimport { useCreateTourScheduleMutation } from '../../generated/global-operations';\nimport { useGetTourSchedulesQuery } from '../../generated/global-operations';\n\n/**\n * Tour scheduler page\n * Configure tour scheduling and recurrence\n */\nexport function SchedulerPage() {\n const { tr } = useToursI18n();\n const { tourId } = useParams();\n const navigate = useNavigate();\n const toursContext = useToursContext();\n const { basePath = '/' } = toursContext;\n const eventBus = useEventBus();\n const { hasPermission } = useToursPermissionAccess();\n const canCreateSchedule = hasPermission(TOUR_PERMISSIONS.TOUR_SCHEDULE_CREATE);\n const canListSchedules = hasPermission(TOUR_PERMISSIONS.TOUR_SCHEDULE_LIST);\n\n const [scheduleData, setScheduleData] = useState({\n startDate: '',\n endDate: '',\n recurrencePattern: 'DAILY' as RecurrencePattern,\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n isActive: true,\n });\n\n const [saving, setSaving] = useState(false);\n\n const [createSchedule] = useCreateTourScheduleMutation();\n const { data: schedulesData } = useGetTourSchedulesQuery({\n variables: { tourId: tourId! },\n skip: !tourId || !canListSchedules,\n });\n const existingSchedules = schedulesData?.tourSchedules ?? [];\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!tourId) return;\n setSaving(true);\n try {\n // Convert recurrence pattern to RFC 5545 recurrence rule\n const recurrenceRule =\n scheduleData.recurrencePattern === 'DAILY'\n ? 'FREQ=DAILY'\n : scheduleData.recurrencePattern === 'WEEKLY'\n ? 'FREQ=WEEKLY'\n : scheduleData.recurrencePattern === 'MONTHLY'\n ? 'FREQ=MONTHLY'\n : undefined;\n\n await createSchedule({\n variables: {\n input: {\n tourId,\n startDate: new Date(scheduleData.startDate).toISOString(),\n endDate: scheduleData.endDate\n ? new Date(scheduleData.endDate).toISOString()\n : undefined,\n recurrenceRule,\n timezone: scheduleData.timezone,\n },\n },\n refetchQueries: ['GetTourSchedules'],\n });\n navigate(`${basePath}/${tourId}/edit`);\n } catch {\n emitToursApiFailed(eventBus, 'schedule.create', toursContext);\n } finally {\n setSaving(false);\n }\n };\n\n const handleBack = () => {\n navigate(`${basePath}/${tourId}/edit`);\n };\n\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6 max-w-3xl mx-auto\">\n {/* Header */}\n <div className=\"flex items-center gap-3 sm:gap-4\">\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"p-2 min-h-[44px] min-w-[44px] hover:bg-accent rounded-md\"\n aria-label={tr('tourScheduler.goBack', 'Go back')}\n >\n <ArrowLeft size={20} />\n </button>\n <div>\n <h1 className=\"text-xl sm:text-2xl font-bold\">\n {tr('tourScheduler.title', 'Schedule Tour')}\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {tr('tourScheduler.subtitle', 'Configure when this tour should be activated')}\n </p>\n </div>\n </div>\n\n <PagePurpose>\n {tr(\n 'tourScheduler.purpose',\n 'Decide exactly when this tour goes live and how often it repeats — for example, run an onboarding tour every Monday, or activate a feature-announcement tour for a single launch window. Set a start (and optional end) date plus a recurrence pattern, and the tour will surface to users automatically on schedule.'\n )}\n </PagePurpose>\n\n {/* Existing Schedules — emphasised so an editor sees current activation at a glance. */}\n {existingSchedules.length > 0 && (\n <EmphasisPanel className=\"p-4 sm:p-6\">\n <h2 className=\"text-lg font-semibold mb-4\">\n {tr('tourScheduler.existingSchedules', 'Existing Schedules')}\n </h2>\n <div className=\"space-y-2\">\n {existingSchedules.map((schedule: (typeof existingSchedules)[number]) => (\n <div\n key={schedule.id}\n className=\"flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between p-3 border border-border-seam rounded-md\"\n >\n <div>\n <div className=\"text-sm font-medium\">\n {new Date(schedule.startDate).toLocaleDateString()}\n {schedule.endDate\n ? ` — ${new Date(schedule.endDate).toLocaleDateString()}`\n : ` — ${tr('tourScheduler.noEndDate', 'No end date')}`}\n </div>\n <div className=\"text-xs text-text-secondary\">\n {schedule.recurrenceRule || tr('tourScheduler.oneTime', 'One-time')} ·{' '}\n {schedule.timezone}\n {schedule.isActive\n ? ` · ${tr('tourScheduler.active', 'Active')}`\n : ` · ${tr('tourScheduler.inactive', 'Inactive')}`}\n </div>\n </div>\n <div className=\"text-xs text-text-secondary tabular-nums\">\n {tr('tourScheduler.runs', 'Runs')}: {schedule.executionCount}\n </div>\n </div>\n ))}\n </div>\n </EmphasisPanel>\n )}\n\n {/* Form */}\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n {/* Date Range */}\n <GlassCard treatment=\"plain\" className=\"p-4 sm:p-6 border-border-seam\">\n <h2 className=\"text-lg font-semibold mb-4 flex items-center gap-2\">\n <Calendar size={20} />\n {tr('tourScheduler.sectionDateRange', 'Date Range')}\n </h2>\n\n <div className=\"space-y-4\">\n <div>\n <label htmlFor=\"scheduler-start-date\" className=\"block text-sm font-medium mb-2\">\n {tr('tourScheduler.labelStartDate', 'Start Date *')}\n </label>\n <input\n id=\"scheduler-start-date\"\n type=\"datetime-local\"\n value={scheduleData.startDate}\n onChange={(e) => setScheduleData({ ...scheduleData, startDate: e.target.value })}\n className=\"w-full px-3 py-2 min-h-[44px] border border-border-subtle rounded-md bg-bg-surface\"\n required\n aria-label={tr('tourScheduler.labelStartDate', 'Start Date *')}\n />\n </div>\n\n <div>\n <label htmlFor=\"scheduler-end-date\" className=\"block text-sm font-medium mb-2\">\n {tr('tourScheduler.labelEndDate', 'End Date (Optional)')}\n </label>\n <input\n id=\"scheduler-end-date\"\n type=\"datetime-local\"\n value={scheduleData.endDate}\n onChange={(e) => setScheduleData({ ...scheduleData, endDate: e.target.value })}\n className=\"w-full px-3 py-2 min-h-[44px] border border-border-subtle rounded-md bg-bg-surface\"\n aria-label={tr('tourScheduler.labelEndDate', 'End Date (Optional)')}\n />\n </div>\n </div>\n </GlassCard>\n\n {/* Recurrence */}\n <GlassCard treatment=\"plain\" className=\"p-4 sm:p-6 border-border-seam\">\n <h2 className=\"text-lg font-semibold mb-4 flex items-center gap-2\">\n <Clock size={20} />\n {tr('tourScheduler.sectionRecurrence', 'Recurrence')}\n </h2>\n\n <div className=\"space-y-4\">\n <div>\n <label htmlFor=\"scheduler-recurrence\" className=\"block text-sm font-medium mb-2\">\n {tr('tourScheduler.labelRecurrencePattern', 'Recurrence Pattern')}\n </label>\n <select\n id=\"scheduler-recurrence\"\n value={scheduleData.recurrencePattern}\n onChange={(e) =>\n setScheduleData({\n ...scheduleData,\n recurrencePattern: e.target.value as RecurrencePattern,\n })\n }\n className=\"w-full px-3 py-2 min-h-[44px] border border-border-subtle rounded-md bg-bg-surface scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n aria-label={tr('tourScheduler.labelRecurrencePattern', 'Recurrence Pattern')}\n >\n <option value=\"DAILY\">{tr('tourScheduler.recurrenceDaily', 'Daily')}</option>\n <option value=\"WEEKLY\">{tr('tourScheduler.recurrenceWeekly', 'Weekly')}</option>\n <option value=\"MONTHLY\">{tr('tourScheduler.recurrenceMonthly', 'Monthly')}</option>\n <option value=\"CUSTOM\">{tr('tourScheduler.recurrenceCustom', 'Custom')}</option>\n </select>\n </div>\n\n <div>\n <label htmlFor=\"scheduler-timezone\" className=\"block text-sm font-medium mb-2\">\n {tr('tourScheduler.labelTimezone', 'Timezone')}\n </label>\n <input\n id=\"scheduler-timezone\"\n type=\"text\"\n value={scheduleData.timezone}\n onChange={(e) => setScheduleData({ ...scheduleData, timezone: e.target.value })}\n className=\"w-full px-3 py-2 min-h-[44px] border border-border-subtle rounded-md bg-bg-surface\"\n readOnly\n aria-label={tr('tourScheduler.labelTimezone', 'Timezone')}\n />\n </div>\n </div>\n </GlassCard>\n\n {/* Status */}\n <GlassCard treatment=\"plain\" className=\"p-4 sm:p-6 border-border-seam\">\n <label className=\"flex items-center gap-3 cursor-pointer\">\n <input\n type=\"checkbox\"\n checked={scheduleData.isActive}\n onChange={(e) => setScheduleData({ ...scheduleData, isActive: e.target.checked })}\n className=\"size-5 rounded\"\n aria-label={tr('tourScheduler.statusActiveLabel', 'Active')}\n />\n <div>\n <div className=\"font-medium\">{tr('tourScheduler.statusActiveLabel', 'Active')}</div>\n <div className=\"text-sm text-text-secondary\">\n {tr(\n 'tourScheduler.statusActiveDescription',\n 'Enable this schedule immediately after creation'\n )}\n </div>\n </div>\n </label>\n </GlassCard>\n\n {/* Actions */}\n <div className=\"flex flex-col sm:flex-row gap-3\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={handleBack}\n className=\"flex-1 min-h-[44px]\"\n >\n {tr('tourScheduler.cancel', 'Cancel')}\n </Button>\n <PermissionGate permission={TOUR_PERMISSIONS.TOUR_SCHEDULE_CREATE}>\n <Button\n type=\"submit\"\n disabled={saving || !canCreateSchedule}\n className=\"flex-1 min-h-[44px]\"\n >\n <Save size={20} />\n {saving\n ? tr('tourScheduler.creating', 'Creating...')\n : tr('tourScheduler.createSchedule', 'Create Schedule')}\n </Button>\n </PermissionGate>\n </div>\n </form>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;AAmBA,SAAgB,IAAgB;CAC9B,IAAM,EAAE,UAAO,GAAc,EACvB,EAAE,cAAW,GAAW,EACxB,IAAW,GAAa,EACxB,IAAe,GAAiB,EAChC,EAAE,cAAW,QAAQ,GACrB,IAAW,GAAa,EACxB,EAAE,qBAAkB,GAA0B,EAC9C,IAAoB,EAAc,EAAiB,qBAAqB,EACxE,IAAmB,EAAc,EAAiB,mBAAmB,EAErE,CAAC,GAAc,KAAmB,EAAS;EAC/C,WAAW;EACX,SAAS;EACT,mBAAmB;EACnB,UAAU,KAAK,gBAAgB,CAAC,iBAAiB,CAAC;EAClD,UAAU;EACX,CAAC,EAEI,CAAC,GAAQ,KAAa,EAAS,GAAM,EAErC,CAAC,KAAkB,GAA+B,EAClD,EAAE,MAAM,MAAkB,EAAyB;EACvD,WAAW,EAAU,WAAS;EAC9B,MAAM,CAAC,KAAU,CAAC;EACnB,CAAC,EACI,IAAoB,GAAe,iBAAiB,EAAE,EAEtD,IAAe,OAAO,MAAuB;AACjD,QAAE,gBAAgB,EACb,GACL;KAAU,GAAK;AACf,OAAI;IAEF,IAAM,IACJ,EAAa,sBAAsB,UAC/B,eACA,EAAa,sBAAsB,WACjC,gBACA,EAAa,sBAAsB,YACjC,iBACA,KAAA;AAgBV,IAdA,MAAM,EAAe;KACnB,WAAW,EACT,OAAO;MACL;MACA,WAAW,IAAI,KAAK,EAAa,UAAU,CAAC,aAAa;MACzD,SAAS,EAAa,UAClB,IAAI,KAAK,EAAa,QAAQ,CAAC,aAAa,GAC5C,KAAA;MACJ;MACA,UAAU,EAAa;MACxB,EACF;KACD,gBAAgB,CAAC,mBAAmB;KACrC,CAAC,EACF,EAAS,GAAG,EAAS,GAAG,EAAO,OAAO;WAChC;AACN,MAAmB,GAAU,mBAAmB,EAAa;aACrD;AACR,MAAU,GAAM;;;IAId,UAAmB;AACvB,IAAS,GAAG,EAAS,GAAG,EAAO,OAAO;;AAGxC,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GAEE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,WAAU;KACV,cAAY,EAAG,wBAAwB,UAAU;eAEjD,kBAAC,GAAD,EAAW,MAAM,IAAM,CAAA;KAChB,CAAA,EACT,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,uBAAuB,gBAAgB;KACxC,CAAA,EACL,kBAAC,KAAD;KAAG,WAAU;eACV,EAAG,0BAA0B,+CAA+C;KAC3E,CAAA,CACA,EAAA,CAAA,CACF;;GAEN,kBAAC,GAAD,EAAA,UACG,EACC,yBACA,wTACD,EACW,CAAA;GAGb,EAAkB,SAAS,KAC1B,kBAAC,GAAD;IAAe,WAAU;cAAzB,CACE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,mCAAmC,qBAAqB;KACzD,CAAA,EACL,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAkB,KAAK,MACtB,kBAAC,OAAD;MAEE,WAAU;gBAFZ,CAIE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACG,IAAI,KAAK,EAAS,UAAU,CAAC,oBAAoB,EACjD,EAAS,UACN,MAAM,IAAI,KAAK,EAAS,QAAQ,CAAC,oBAAoB,KACrD,MAAM,EAAG,2BAA2B,cAAc,GAClD;UACN,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACG,EAAS,kBAAkB,EAAG,yBAAyB,WAAW;QAAC;QAAG;QACtE,EAAS;QACT,EAAS,WACN,MAAM,EAAG,wBAAwB,SAAS,KAC1C,MAAM,EAAG,0BAA0B,WAAW;QAC9C;SACF,EAAA,CAAA,EACN,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACG,EAAG,sBAAsB,OAAO;QAAC;QAAG,EAAS;QAC1C;SACF;QArBC,EAAS,GAqBV,CACN;KACE,CAAA,CACQ;;GAIlB,kBAAC,QAAD;IAAM,UAAU;IAAc,WAAU;cAAxC;KAEE,kBAAC,GAAD;MAAW,WAAU;MAAQ,WAAU;gBAAvC,CACE,kBAAC,MAAD;OAAI,WAAU;iBAAd,CACE,kBAAC,GAAD,EAAU,MAAM,IAAM,CAAA,EACrB,EAAG,kCAAkC,aAAa,CAChD;UAEL,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;QAAO,SAAQ;QAAuB,WAAU;kBAC7C,EAAG,gCAAgC,eAAe;QAC7C,CAAA,EACR,kBAAC,SAAD;QACE,IAAG;QACH,MAAK;QACL,OAAO,EAAa;QACpB,WAAW,MAAM,EAAgB;SAAE,GAAG;SAAc,WAAW,EAAE,OAAO;SAAO,CAAC;QAChF,WAAU;QACV,UAAA;QACA,cAAY,EAAG,gCAAgC,eAAe;QAC9D,CAAA,CACE,EAAA,CAAA,EAEN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;QAAO,SAAQ;QAAqB,WAAU;kBAC3C,EAAG,8BAA8B,sBAAsB;QAClD,CAAA,EACR,kBAAC,SAAD;QACE,IAAG;QACH,MAAK;QACL,OAAO,EAAa;QACpB,WAAW,MAAM,EAAgB;SAAE,GAAG;SAAc,SAAS,EAAE,OAAO;SAAO,CAAC;QAC9E,WAAU;QACV,cAAY,EAAG,8BAA8B,sBAAsB;QACnE,CAAA,CACE,EAAA,CAAA,CACF;SACI;;KAGZ,kBAAC,GAAD;MAAW,WAAU;MAAQ,WAAU;gBAAvC,CACE,kBAAC,MAAD;OAAI,WAAU;iBAAd,CACE,kBAAC,GAAD,EAAO,MAAM,IAAM,CAAA,EAClB,EAAG,mCAAmC,aAAa,CACjD;UAEL,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;QAAO,SAAQ;QAAuB,WAAU;kBAC7C,EAAG,wCAAwC,qBAAqB;QAC3D,CAAA,EACR,kBAAC,UAAD;QACE,IAAG;QACH,OAAO,EAAa;QACpB,WAAW,MACT,EAAgB;SACd,GAAG;SACH,mBAAmB,EAAE,OAAO;SAC7B,CAAC;QAEJ,WAAU;QACV,cAAY,EAAG,wCAAwC,qBAAqB;kBAV9E;SAYE,kBAAC,UAAD;UAAQ,OAAM;oBAAS,EAAG,iCAAiC,QAAQ;UAAU,CAAA;SAC7E,kBAAC,UAAD;UAAQ,OAAM;oBAAU,EAAG,kCAAkC,SAAS;UAAU,CAAA;SAChF,kBAAC,UAAD;UAAQ,OAAM;oBAAW,EAAG,mCAAmC,UAAU;UAAU,CAAA;SACnF,kBAAC,UAAD;UAAQ,OAAM;oBAAU,EAAG,kCAAkC,SAAS;UAAU,CAAA;SACzE;UACL,EAAA,CAAA,EAEN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;QAAO,SAAQ;QAAqB,WAAU;kBAC3C,EAAG,+BAA+B,WAAW;QACxC,CAAA,EACR,kBAAC,SAAD;QACE,IAAG;QACH,MAAK;QACL,OAAO,EAAa;QACpB,WAAW,MAAM,EAAgB;SAAE,GAAG;SAAc,UAAU,EAAE,OAAO;SAAO,CAAC;QAC/E,WAAU;QACV,UAAA;QACA,cAAY,EAAG,+BAA+B,WAAW;QACzD,CAAA,CACE,EAAA,CAAA,CACF;SACI;;KAGZ,kBAAC,GAAD;MAAW,WAAU;MAAQ,WAAU;gBACrC,kBAAC,SAAD;OAAO,WAAU;iBAAjB,CACE,kBAAC,SAAD;QACE,MAAK;QACL,SAAS,EAAa;QACtB,WAAW,MAAM,EAAgB;SAAE,GAAG;SAAc,UAAU,EAAE,OAAO;SAAS,CAAC;QACjF,WAAU;QACV,cAAY,EAAG,mCAAmC,SAAS;QAC3D,CAAA,EACF,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAe,EAAG,mCAAmC,SAAS;QAAO,CAAA,EACpF,kBAAC,OAAD;QAAK,WAAU;kBACZ,EACC,yCACA,kDACD;QACG,CAAA,CACF,EAAA,CAAA,CACA;;MACE,CAAA;KAGZ,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OACE,MAAK;OACL,SAAQ;OACR,SAAS;OACT,WAAU;iBAET,EAAG,wBAAwB,SAAS;OAC9B,CAAA,EACT,kBAAC,GAAD;OAAgB,YAAY,EAAiB;iBAC3C,kBAAC,GAAD;QACE,MAAK;QACL,UAAU,KAAU,CAAC;QACrB,WAAU;kBAHZ,CAKE,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA,EACjB,IACG,EAAG,0BAA0B,cAAc,GAC3C,EAAG,gCAAgC,kBAAkB,CAClD;;OACM,CAAA,CACb;;KACD;;GACH"}
|
|
1
|
+
{"version":3,"file":"SchedulerPage.js","names":["SchedulerPage","useToursI18n","useParams","navigate","useNavigate","toursContext","useToursContext","eventBus","useEventBus","useToursPermissionAccess","canCreateSchedule","hasPermission","TOUR_PERMISSIONS","canListSchedules","scheduleData","setScheduleData","useState","saving","setSaving","createSchedule","useCreateTourScheduleMutation","schedulesData","useGetTourSchedulesQuery","tourId","existingSchedules","handleSubmit","recurrenceRule","basePath","handleBack","tr","ArrowLeft","PagePurpose","EmphasisPanel","schedule","GlassCard","Calendar","Clock","Button","PermissionGate","Save"],"sources":["../../../src/tours/pages/SchedulerPage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useParams, useNavigate } from 'react-router';\nimport { Calendar, Clock, ArrowLeft, Save } from 'lucide-react';\nimport { PermissionGate } from '@burdenoff/fe-libs/shared/components';\nimport { Button, EmphasisPanel, GlassCard, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { useEventBus } from '@burdenoff/fe-libs/shared/events';\nimport { useToursPermissionAccess } from '../hooks/useToursPermissionAccess';\nimport { useToursContext } from '../providers/ToursProvider';\nimport { useToursI18n } from '../i18n';\nimport { TOUR_PERMISSIONS } from '../../constants/permissions';\nimport type { RecurrencePattern } from '../types';\nimport { emitToursApiFailed } from '../telemetry';\nimport { useCreateTourScheduleMutation } from '../../generated/global-operations';\nimport { useGetTourSchedulesQuery } from '../../generated/global-operations';\n\n/**\n * Tour scheduler page\n * Configure tour scheduling and recurrence\n */\nexport function SchedulerPage() {\n const { tr } = useToursI18n();\n const { tourId } = useParams();\n const navigate = useNavigate();\n const toursContext = useToursContext();\n const { basePath = '/' } = toursContext;\n const eventBus = useEventBus();\n const { hasPermission } = useToursPermissionAccess();\n const canCreateSchedule = hasPermission(TOUR_PERMISSIONS.TOUR_SCHEDULE_CREATE);\n const canListSchedules = hasPermission(TOUR_PERMISSIONS.TOUR_SCHEDULE_LIST);\n\n const [scheduleData, setScheduleData] = useState({\n startDate: '',\n endDate: '',\n recurrencePattern: 'DAILY' as RecurrencePattern,\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n isActive: true,\n });\n\n const [saving, setSaving] = useState(false);\n\n const [createSchedule] = useCreateTourScheduleMutation();\n const { data: schedulesData } = useGetTourSchedulesQuery({\n variables: { tourId: tourId! },\n skip: !tourId || !canListSchedules,\n });\n const existingSchedules = schedulesData?.tourSchedules ?? [];\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!tourId) return;\n setSaving(true);\n try {\n // Convert recurrence pattern to RFC 5545 recurrence rule\n const recurrenceRule =\n scheduleData.recurrencePattern === 'DAILY'\n ? 'FREQ=DAILY'\n : scheduleData.recurrencePattern === 'WEEKLY'\n ? 'FREQ=WEEKLY'\n : scheduleData.recurrencePattern === 'MONTHLY'\n ? 'FREQ=MONTHLY'\n : undefined;\n\n await createSchedule({\n variables: {\n input: {\n tourId,\n startDate: new Date(scheduleData.startDate).toISOString(),\n endDate: scheduleData.endDate\n ? new Date(scheduleData.endDate).toISOString()\n : undefined,\n recurrenceRule,\n timezone: scheduleData.timezone,\n },\n },\n refetchQueries: ['GetTourSchedules'],\n });\n navigate(`${basePath}/${tourId}/edit`);\n } catch {\n emitToursApiFailed(eventBus, 'schedule.create', toursContext);\n } finally {\n setSaving(false);\n }\n };\n\n const handleBack = () => {\n navigate(`${basePath}/${tourId}/edit`);\n };\n\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6 max-w-3xl mx-auto\">\n {/* Header */}\n <div className=\"flex items-center gap-3 sm:gap-4\">\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"p-2 min-h-[44px] min-w-[44px] hover:bg-accent rounded-md\"\n aria-label={tr('tourScheduler.goBack', 'Go back')}\n >\n <ArrowLeft size={20} />\n </button>\n <div>\n <h1 className=\"text-xl sm:text-2xl font-bold\">\n {tr('tourScheduler.title', 'Schedule Tour')}\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {tr('tourScheduler.subtitle', 'Configure when this tour should be activated')}\n </p>\n </div>\n </div>\n\n <PagePurpose>\n {tr(\n 'tourScheduler.purpose',\n 'Decide exactly when this tour goes live and how often it repeats — for example, run an onboarding tour every Monday, or activate a feature-announcement tour for a single launch window. Set a start (and optional end) date plus a recurrence pattern, and the tour will surface to users automatically on schedule.'\n )}\n </PagePurpose>\n\n {/* Existing Schedules — emphasised so an editor sees current activation at a glance. */}\n {existingSchedules.length > 0 && (\n <EmphasisPanel className=\"p-4 sm:p-6\">\n <h2 className=\"text-lg font-semibold mb-4\">\n {tr('tourScheduler.existingSchedules', 'Existing Schedules')}\n </h2>\n <div className=\"space-y-2\">\n {existingSchedules.map((schedule: (typeof existingSchedules)[number]) => (\n <div\n key={schedule.id}\n className=\"flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between p-3 border border-border-seam rounded-md\"\n >\n <div>\n <div className=\"text-sm font-medium\">\n {new Date(schedule.startDate).toLocaleDateString()}\n {schedule.endDate\n ? ` — ${new Date(schedule.endDate).toLocaleDateString()}`\n : ` — ${tr('tourScheduler.noEndDate', 'No end date')}`}\n </div>\n <div className=\"text-xs text-text-secondary\">\n {schedule.recurrenceRule || tr('tourScheduler.oneTime', 'One-time')} ·{' '}\n {schedule.timezone}\n {schedule.isActive\n ? ` · ${tr('tourScheduler.active', 'Active')}`\n : ` · ${tr('tourScheduler.inactive', 'Inactive')}`}\n </div>\n </div>\n <div className=\"text-xs text-text-secondary tabular-nums\">\n {tr('tourScheduler.runs', 'Runs')}: {schedule.executionCount}\n </div>\n </div>\n ))}\n </div>\n </EmphasisPanel>\n )}\n\n {/* Form */}\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n {/* Date Range */}\n <GlassCard treatment=\"plain\" className=\"p-4 sm:p-6 border-border-seam\">\n <h2 className=\"text-lg font-semibold mb-4 flex items-center gap-2\">\n <Calendar size={20} />\n {tr('tourScheduler.sectionDateRange', 'Date Range')}\n </h2>\n\n <div className=\"space-y-4\">\n <div>\n <label htmlFor=\"scheduler-start-date\" className=\"block text-sm font-medium mb-2\">\n {tr('tourScheduler.labelStartDate', 'Start Date *')}\n </label>\n <input\n id=\"scheduler-start-date\"\n type=\"datetime-local\"\n value={scheduleData.startDate}\n onChange={(e) => setScheduleData({ ...scheduleData, startDate: e.target.value })}\n className=\"w-full px-3 py-2 min-h-[44px] border border-border-subtle rounded-md bg-bg-surface\"\n required\n aria-label={tr('tourScheduler.labelStartDate', 'Start Date *')}\n />\n </div>\n\n <div>\n <label htmlFor=\"scheduler-end-date\" className=\"block text-sm font-medium mb-2\">\n {tr('tourScheduler.labelEndDate', 'End Date (Optional)')}\n </label>\n <input\n id=\"scheduler-end-date\"\n type=\"datetime-local\"\n value={scheduleData.endDate}\n onChange={(e) => setScheduleData({ ...scheduleData, endDate: e.target.value })}\n className=\"w-full px-3 py-2 min-h-[44px] border border-border-subtle rounded-md bg-bg-surface\"\n aria-label={tr('tourScheduler.labelEndDate', 'End Date (Optional)')}\n />\n </div>\n </div>\n </GlassCard>\n\n {/* Recurrence */}\n <GlassCard treatment=\"plain\" className=\"p-4 sm:p-6 border-border-seam\">\n <h2 className=\"text-lg font-semibold mb-4 flex items-center gap-2\">\n <Clock size={20} />\n {tr('tourScheduler.sectionRecurrence', 'Recurrence')}\n </h2>\n\n <div className=\"space-y-4\">\n <div>\n <label htmlFor=\"scheduler-recurrence\" className=\"block text-sm font-medium mb-2\">\n {tr('tourScheduler.labelRecurrencePattern', 'Recurrence Pattern')}\n </label>\n <select\n id=\"scheduler-recurrence\"\n value={scheduleData.recurrencePattern}\n onChange={(e) =>\n setScheduleData({\n ...scheduleData,\n recurrencePattern: e.target.value as RecurrencePattern,\n })\n }\n className=\"w-full px-3 py-2 min-h-[44px] border border-border-subtle rounded-md bg-bg-surface scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n aria-label={tr('tourScheduler.labelRecurrencePattern', 'Recurrence Pattern')}\n >\n <option value=\"DAILY\">{tr('tourScheduler.recurrenceDaily', 'Daily')}</option>\n <option value=\"WEEKLY\">{tr('tourScheduler.recurrenceWeekly', 'Weekly')}</option>\n <option value=\"MONTHLY\">{tr('tourScheduler.recurrenceMonthly', 'Monthly')}</option>\n <option value=\"CUSTOM\">{tr('tourScheduler.recurrenceCustom', 'Custom')}</option>\n </select>\n </div>\n\n <div>\n <label htmlFor=\"scheduler-timezone\" className=\"block text-sm font-medium mb-2\">\n {tr('tourScheduler.labelTimezone', 'Timezone')}\n </label>\n <input\n id=\"scheduler-timezone\"\n type=\"text\"\n value={scheduleData.timezone}\n onChange={(e) => setScheduleData({ ...scheduleData, timezone: e.target.value })}\n className=\"w-full px-3 py-2 min-h-[44px] border border-border-subtle rounded-md bg-bg-surface\"\n readOnly\n aria-label={tr('tourScheduler.labelTimezone', 'Timezone')}\n />\n </div>\n </div>\n </GlassCard>\n\n {/* Status */}\n <GlassCard treatment=\"plain\" className=\"p-4 sm:p-6 border-border-seam\">\n <label className=\"flex items-center gap-3 cursor-pointer\">\n <input\n type=\"checkbox\"\n checked={scheduleData.isActive}\n onChange={(e) => setScheduleData({ ...scheduleData, isActive: e.target.checked })}\n className=\"size-5 rounded\"\n aria-label={tr('tourScheduler.statusActiveLabel', 'Active')}\n />\n <div>\n <div className=\"font-medium\">{tr('tourScheduler.statusActiveLabel', 'Active')}</div>\n <div className=\"text-sm text-text-secondary\">\n {tr(\n 'tourScheduler.statusActiveDescription',\n 'Enable this schedule immediately after creation'\n )}\n </div>\n </div>\n </label>\n </GlassCard>\n\n {/* Actions */}\n <div className=\"flex flex-col sm:flex-row gap-3\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={handleBack}\n className=\"flex-1 min-h-[44px]\"\n >\n {tr('tourScheduler.cancel', 'Cancel')}\n </Button>\n <PermissionGate permission={TOUR_PERMISSIONS.TOUR_SCHEDULE_CREATE}>\n <Button\n type=\"submit\"\n disabled={saving || !canCreateSchedule}\n className=\"flex-1 min-h-[44px]\"\n >\n <Save size={20} />\n {saving\n ? tr('tourScheduler.creating', 'Creating...')\n : tr('tourScheduler.createSchedule', 'Create Schedule')}\n </Button>\n </PermissionGate>\n </div>\n </form>\n </div>\n );\n}\n"],"mappings":"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"}
|