@burdenoff/microfe-tours 2026.527.1 → 2026.527.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/tours/ToursUserRoutes.js +2 -4
  2. package/dist/tours/ToursUserRoutes.js.map +1 -1
  3. package/dist/tours/components/RouteHelpers.js +4 -4
  4. package/dist/tours/components/RouteHelpers.js.map +1 -1
  5. package/dist/tours/hooks/useTourPreview.js +39 -39
  6. package/dist/tours/hooks/useTourPreview.js.map +1 -1
  7. package/dist/tours/hooks/useToursList.js.map +1 -1
  8. package/dist/tours/i18n.js +4 -0
  9. package/dist/tours/i18n.js.map +1 -1
  10. package/dist/tours/pages/MediaLibraryPage.js +11 -8
  11. package/dist/tours/pages/MediaLibraryPage.js.map +1 -1
  12. package/dist/tours/pages/MyToursPage.js +59 -57
  13. package/dist/tours/pages/MyToursPage.js.map +1 -1
  14. package/dist/tours/pages/MyToursProgressPage.js +65 -61
  15. package/dist/tours/pages/MyToursProgressPage.js.map +1 -1
  16. package/dist/tours/pages/SchedulerPage.js +18 -4
  17. package/dist/tours/pages/SchedulerPage.js.map +1 -1
  18. package/dist/tours/pages/StepEditorPage.js +330 -276
  19. package/dist/tours/pages/StepEditorPage.js.map +1 -1
  20. package/dist/tours/pages/TemplateLibraryPage.js +7 -2
  21. package/dist/tours/pages/TemplateLibraryPage.js.map +1 -1
  22. package/dist/tours/pages/TourBuilderPage.js +144 -101
  23. package/dist/tours/pages/TourBuilderPage.js.map +1 -1
  24. package/dist/tours/pages/TourPreviewPage.js +12 -4
  25. package/dist/tours/pages/TourPreviewPage.js.map +1 -1
  26. package/dist/tours/pages/ToursListPage.js +165 -149
  27. package/dist/tours/pages/ToursListPage.js.map +1 -1
  28. package/dist/tours/providers/ToursProvider.js +1 -1
  29. package/dist/tours/providers/ToursProvider.js.map +1 -1
  30. package/dist/tours/utils/index.js.map +1 -1
  31. package/package.json +1 -1
@@ -1,74 +1,76 @@
1
1
  import { useToursContext as e } from "../providers/ToursProvider.js";
2
2
  import { useGetToursForContextQuery as t } from "../../generated/global-operations.js";
3
3
  import { useToursI18n as n } from "../i18n.js";
4
- import { useEffect as r } from "react";
5
- import { CheckCircle2 as i, Circle as a, MinusCircle as o, Play as s } from "lucide-react";
6
- import { jsx as c, jsxs as l } from "react/jsx-runtime";
7
- import { useSearchParams as u } from "react-router-dom";
8
- import { useEventBus as d } from "@burdenoff/fe-libs/shared/events";
4
+ import { useEffect as r, useRef as i } from "react";
5
+ import { CheckCircle2 as a, Circle as o, MinusCircle as s, Play as c } from "lucide-react";
6
+ import { jsx as l, jsxs as u } from "react/jsx-runtime";
7
+ import { useSearchParams as d } from "react-router-dom";
8
+ import { useEventBus as f } from "@burdenoff/fe-libs/shared/events";
9
9
  //#region src/tours/pages/MyToursPage.tsx
10
- var f = /^[a-zA-Z0-9_-]+$/, p = /^[a-zA-Z0-9_\-/]+$/;
11
- function m(e, t) {
12
- return e && f.test(e) ? e : t;
13
- }
10
+ var p = /^[a-zA-Z0-9_-]+$/, m = /^[a-zA-Z0-9_\-/]+$/;
14
11
  function h(e, t) {
15
12
  return e && p.test(e) ? e : t;
16
13
  }
17
- function g() {
18
- let { tr: i } = n(), { productId: a, moduleId: o, pageId: s, workspaceId: f } = e(), [p] = u(), g = d();
14
+ function g(e, t) {
15
+ return e && m.test(e) ? e : t;
16
+ }
17
+ function _() {
18
+ let { tr: a } = n(), { productId: o, moduleId: s, pageId: c, workspaceId: p } = e(), [m] = d(), _ = f(), y = i(!1);
19
19
  r(() => {
20
- g.emit("tours.page.viewed", {
20
+ y.current || (y.current = !0, _.emit("tours.page.viewed", {
21
21
  page: "/tours",
22
- productId: a,
23
- moduleId: o,
24
- pageId: s,
25
- workspaceId: f
26
- });
27
- }, []);
28
- let v = m(a, void 0) ?? m(p.get("productId"), void 0), y = m(o, void 0) ?? m(p.get("moduleId"), void 0), b = h(s, void 0) ?? h(p.get("pageId"), void 0), { data: x, loading: S } = t({
29
- variables: v ? { context: {
30
- productId: v,
31
- moduleId: y,
32
- pageId: b
22
+ productId: o,
23
+ moduleId: s,
24
+ pageId: c,
25
+ workspaceId: p
26
+ }));
27
+ }, [
28
+ _,
29
+ o,
30
+ s,
31
+ c,
32
+ p
33
+ ]);
34
+ let b = h(o, void 0) ?? h(m.get("productId"), void 0), x = h(s, void 0) ?? h(m.get("moduleId"), void 0), S = g(c, void 0) ?? g(m.get("pageId"), void 0), { data: C, loading: w } = t({
35
+ variables: b ? { context: {
36
+ productId: b,
37
+ moduleId: x,
38
+ pageId: S
33
39
  } } : void 0,
34
- skip: !v,
40
+ skip: !b,
35
41
  fetchPolicy: "cache-and-network"
36
- }), C = x?.toursForContext ?? [], w = S;
37
- return /* @__PURE__ */ l("div", {
42
+ }), T = C?.toursForContext ?? [], E = w;
43
+ return /* @__PURE__ */ u("div", {
38
44
  className: "space-y-4 sm:space-y-6 p-3 sm:p-6",
39
- children: [/* @__PURE__ */ l("div", { children: [/* @__PURE__ */ c("h1", {
45
+ children: [/* @__PURE__ */ u("div", { children: [/* @__PURE__ */ l("h1", {
40
46
  className: "text-xl sm:text-2xl font-bold text-foreground",
41
- children: i("myTours.title")
42
- }), /* @__PURE__ */ c("p", {
47
+ children: a("myTours.title")
48
+ }), /* @__PURE__ */ l("p", {
43
49
  className: "text-sm sm:text-base text-muted-foreground",
44
- children: i("myTours.subtitle")
45
- })] }), w && C.length === 0 ? /* @__PURE__ */ c("div", {
50
+ children: a("myTours.subtitle")
51
+ })] }), E && T.length === 0 ? /* @__PURE__ */ l("output", {
46
52
  className: "text-sm text-muted-foreground",
47
- role: "status",
48
- "aria-live": "polite",
49
- children: i("myTours.loading")
50
- }) : C.length === 0 ? /* @__PURE__ */ c("div", {
53
+ children: a("myTours.loading")
54
+ }) : T.length === 0 ? /* @__PURE__ */ l("div", {
51
55
  className: "rounded-lg border border-dashed border-border p-8 text-center",
52
- children: /* @__PURE__ */ c("p", {
53
- className: "text-sm text-muted-foreground",
54
- role: "status",
55
- "aria-live": "polite",
56
- children: i("myTours.empty")
56
+ children: /* @__PURE__ */ l("output", {
57
+ className: "block text-sm text-muted-foreground",
58
+ children: a("myTours.empty")
57
59
  })
58
- }) : /* @__PURE__ */ c("ul", {
60
+ }) : /* @__PURE__ */ l("ul", {
59
61
  className: "grid gap-3 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3",
60
- children: C.map((e) => /* @__PURE__ */ l("li", {
62
+ children: T.map((e) => /* @__PURE__ */ u("li", {
61
63
  className: "rounded-lg border border-border bg-card p-4 flex flex-col gap-2",
62
- children: [/* @__PURE__ */ l("div", {
64
+ children: [/* @__PURE__ */ u("div", {
63
65
  className: "flex items-start justify-between gap-2",
64
- children: [/* @__PURE__ */ c("h2", {
66
+ children: [/* @__PURE__ */ l("h2", {
65
67
  className: "font-semibold text-foreground line-clamp-2",
66
68
  children: e.name
67
- }), /* @__PURE__ */ c(_, {
69
+ }), /* @__PURE__ */ l(v, {
68
70
  status: e.myProgress?.status,
69
- tr: i
71
+ tr: a
70
72
  })]
71
- }), e.description ? /* @__PURE__ */ c("p", {
73
+ }), e.description ? /* @__PURE__ */ l("p", {
72
74
  className: "text-sm text-muted-foreground line-clamp-3",
73
75
  children: e.description
74
76
  }) : null]
@@ -76,28 +78,28 @@ function g() {
76
78
  })]
77
79
  });
78
80
  }
79
- function _({ status: e, tr: t }) {
81
+ function v({ status: e, tr: t }) {
80
82
  switch (e) {
81
- case "COMPLETED": return /* @__PURE__ */ l("span", {
83
+ case "COMPLETED": return /* @__PURE__ */ u("span", {
82
84
  className: "inline-flex items-center gap-1 text-xs text-success",
83
- children: [/* @__PURE__ */ c(i, { size: 14 }), t("myTours.status.completed")]
85
+ children: [/* @__PURE__ */ l(a, { size: 14 }), t("myTours.status.completed")]
84
86
  });
85
- case "IN_PROGRESS": return /* @__PURE__ */ l("span", {
87
+ case "IN_PROGRESS": return /* @__PURE__ */ u("span", {
86
88
  className: "inline-flex items-center gap-1 text-xs text-warning",
87
- children: [/* @__PURE__ */ c(s, { size: 14 }), t("myTours.status.inProgress")]
89
+ children: [/* @__PURE__ */ l(c, { size: 14 }), t("myTours.status.inProgress")]
88
90
  });
89
91
  case "SKIPPED":
90
- case "DISMISSED": return /* @__PURE__ */ l("span", {
92
+ case "DISMISSED": return /* @__PURE__ */ u("span", {
91
93
  className: "inline-flex items-center gap-1 text-xs text-muted-foreground",
92
- children: [/* @__PURE__ */ c(o, { size: 14 }), t("myTours.status.skipped")]
94
+ children: [/* @__PURE__ */ l(s, { size: 14 }), t("myTours.status.skipped")]
93
95
  });
94
- default: return /* @__PURE__ */ l("span", {
96
+ default: return /* @__PURE__ */ u("span", {
95
97
  className: "inline-flex items-center gap-1 text-xs text-muted-foreground",
96
- children: [/* @__PURE__ */ c(a, { size: 14 }), t("myTours.status.notStarted")]
98
+ children: [/* @__PURE__ */ l(o, { size: 14 }), t("myTours.status.notStarted")]
97
99
  });
98
100
  }
99
101
  }
100
102
  //#endregion
101
- export { g as MyToursPage };
103
+ export { _ as MyToursPage };
102
104
 
103
105
  //# sourceMappingURL=MyToursPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"MyToursPage.js","names":[],"sources":["../../../src/tours/pages/MyToursPage.tsx"],"sourcesContent":["import { useEffect } from 'react';\nimport { useSearchParams } from 'react-router-dom';\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 { useEventBus } from '@burdenoff/fe-libs/shared/events';\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 } = useToursContext();\n const [searchParams] = useSearchParams();\n const eventBus = useEventBus();\n\n // Emit page view event on mount\n useEffect(() => {\n eventBus.emit('tours.page.viewed', {\n page: '/tours',\n productId: shellProductId,\n moduleId: shellModuleId,\n pageId: shellPageId,\n workspaceId,\n });\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\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 { data: toursData, loading: toursLoading } = useGetToursForContextQuery({\n variables: productId ? { context: { productId, moduleId, pageId } } : undefined,\n skip: !productId,\n fetchPolicy: 'cache-and-network',\n });\n\n const tours = toursData?.toursForContext ?? [];\n const isLoading = toursLoading;\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-foreground\">{tr('myTours.title')}</h1>\n <p className=\"text-sm sm:text-base text-muted-foreground\">{tr('myTours.subtitle')}</p>\n </div>\n\n {isLoading && tours.length === 0 ? (\n <div className=\"text-sm text-muted-foreground\" role=\"status\" aria-live=\"polite\">\n {tr('myTours.loading')}\n </div>\n ) : tours.length === 0 ? (\n <div className=\"rounded-lg border border-dashed border-border p-8 text-center\">\n <p className=\"text-sm text-muted-foreground\" role=\"status\" aria-live=\"polite\">\n {tr('myTours.empty')}\n </p>\n </div>\n ) : (\n <ul className=\"grid gap-3 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3\">\n {tours.map((tour: TourSummary) => (\n <li\n key={tour.id}\n className=\"rounded-lg border border-border bg-card p-4 flex flex-col gap-2\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <h2 className=\"font-semibold text-foreground line-clamp-2\">{tour.name}</h2>\n <ProgressBadge status={tour.myProgress?.status} tr={tr} />\n </div>\n {tour.description ? (\n <p className=\"text-sm text-muted-foreground line-clamp-3\">{tour.description}</p>\n ) : null}\n </li>\n ))}\n </ul>\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\">\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-muted-foreground\">\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-muted-foreground\">\n <Circle size={14} />\n {tr('myTours.status.notStarted')}\n </span>\n );\n }\n}\n"],"mappings":";;;;;;;;;AAcA,IAAM,IAAe,oBAIf,IAAoB;AAE1B,SAAS,EACP,GACA,GACY;AAEZ,QADI,KAAO,EAAa,KAAK,EAAI,GAAS,IACnC;;AAGT,SAAS,EACP,GACA,GACY;AAEZ,QADI,KAAO,EAAkB,KAAK,EAAI,GAAS,IACxC;;AAYT,SAAgB,IAAc;CAC5B,IAAM,EAAE,UAAO,GAAc,EACvB,EACJ,WAAW,GACX,UAAU,GACV,QAAQ,GACR,mBACE,GAAiB,EACf,CAAC,KAAgB,GAAiB,EAClC,IAAW,GAAa;AAG9B,SAAgB;AACd,IAAS,KAAK,qBAAqB;GACjC,MAAM;GACN,WAAW;GACX,UAAU;GACV,QAAQ;GACR;GACD,CAAC;IACD,EAAE,CAAC;CASN,IAAM,IACJ,EAAS,GAAgB,KAAA,EAAU,IAAI,EAAS,EAAa,IAAI,YAAY,EAAE,KAAA,EAAU,EACrF,IACJ,EAAS,GAAe,KAAA,EAAU,IAAI,EAAS,EAAa,IAAI,WAAW,EAAE,KAAA,EAAU,EACnF,IACJ,EAAW,GAAa,KAAA,EAAU,IAAI,EAAW,EAAa,IAAI,SAAS,EAAE,KAAA,EAAU,EAEnF,EAAE,MAAM,GAAW,SAAS,MAAiB,EAA2B;EAC5E,WAAW,IAAY,EAAE,SAAS;GAAE;GAAW;GAAU;GAAQ,EAAE,GAAG,KAAA;EACtE,MAAM,CAAC;EACP,aAAa;EACd,CAAC,EAEI,IAAQ,GAAW,mBAAmB,EAAE,EACxC,IAAY;AAElB,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;GAAI,WAAU;aAAiD,EAAG,gBAAgB;GAAM,CAAA,EACxF,kBAAC,KAAD;GAAG,WAAU;aAA8C,EAAG,mBAAmB;GAAK,CAAA,CAClF,EAAA,CAAA,EAEL,KAAa,EAAM,WAAW,IAC7B,kBAAC,OAAD;GAAK,WAAU;GAAgC,MAAK;GAAS,aAAU;aACpE,EAAG,kBAAkB;GAClB,CAAA,GACJ,EAAM,WAAW,IACnB,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,KAAD;IAAG,WAAU;IAAgC,MAAK;IAAS,aAAU;cAClE,EAAG,gBAAgB;IAClB,CAAA;GACA,CAAA,GAEN,kBAAC,MAAD;GAAI,WAAU;aACX,EAAM,KAAK,MACV,kBAAC,MAAD;IAEE,WAAU;cAFZ,CAIE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,MAAD;MAAI,WAAU;gBAA8C,EAAK;MAAU,CAAA,EAC3E,kBAAC,GAAD;MAAe,QAAQ,EAAK,YAAY;MAAY;MAAM,CAAA,CACtD;QACL,EAAK,cACJ,kBAAC,KAAD;KAAG,WAAU;eAA8C,EAAK;KAAgB,CAAA,GAC9E,KACD;MAVE,EAAK,GAUP,CACL;GACC,CAAA,CAEH;;;AAIV,SAAS,EAAc,EACrB,WACA,SAIC;AACD,SAAQ,GAAR;EACE,KAAK,YACH,QACE,kBAAC,QAAD;GAAM,WAAU;aAAhB,CACE,kBAAC,GAAD,EAAc,MAAM,IAAM,CAAA,EACzB,EAAG,2BAA2B,CAC1B;;EAEX,KAAK,cACH,QACE,kBAAC,QAAD;GAAM,WAAU;aAAhB,CACE,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA,EACjB,EAAG,4BAA4B,CAC3B;;EAEX,KAAK;EACL,KAAK,YACH,QACE,kBAAC,QAAD;GAAM,WAAU;aAAhB,CACE,kBAAC,GAAD,EAAa,MAAM,IAAM,CAAA,EACxB,EAAG,yBAAyB,CACxB;;EAEX,QACE,QACE,kBAAC,QAAD;GAAM,WAAU;aAAhB,CACE,kBAAC,GAAD,EAAQ,MAAM,IAAM,CAAA,EACnB,EAAG,4BAA4B,CAC3B"}
1
+ {"version":3,"file":"MyToursPage.js","names":[],"sources":["../../../src/tours/pages/MyToursPage.tsx"],"sourcesContent":["import { useEffect, useRef } from 'react';\nimport { useSearchParams } from 'react-router-dom';\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 { useEventBus } from '@burdenoff/fe-libs/shared/events';\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 } = 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 { data: toursData, loading: toursLoading } = useGetToursForContextQuery({\n variables: productId ? { context: { productId, moduleId, pageId } } : undefined,\n skip: !productId,\n fetchPolicy: 'cache-and-network',\n });\n\n const tours = toursData?.toursForContext ?? [];\n const isLoading = toursLoading;\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-foreground\">{tr('myTours.title')}</h1>\n <p className=\"text-sm sm:text-base text-muted-foreground\">{tr('myTours.subtitle')}</p>\n </div>\n\n {isLoading && tours.length === 0 ? (\n <output className=\"text-sm text-muted-foreground\">{tr('myTours.loading')}</output>\n ) : tours.length === 0 ? (\n <div className=\"rounded-lg border border-dashed border-border p-8 text-center\">\n <output className=\"block text-sm text-muted-foreground\">{tr('myTours.empty')}</output>\n </div>\n ) : (\n <ul className=\"grid gap-3 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3\">\n {tours.map((tour: TourSummary) => (\n <li\n key={tour.id}\n className=\"rounded-lg border border-border bg-card p-4 flex flex-col gap-2\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <h2 className=\"font-semibold text-foreground line-clamp-2\">{tour.name}</h2>\n <ProgressBadge status={tour.myProgress?.status} tr={tr} />\n </div>\n {tour.description ? (\n <p className=\"text-sm text-muted-foreground line-clamp-3\">{tour.description}</p>\n ) : null}\n </li>\n ))}\n </ul>\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\">\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-muted-foreground\">\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-muted-foreground\">\n <Circle size={14} />\n {tr('myTours.status.notStarted')}\n </span>\n );\n }\n}\n"],"mappings":"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"}
@@ -1,112 +1,116 @@
1
1
  import { useToursContext as e } from "../providers/ToursProvider.js";
2
2
  import { useGetUserTourProgressesQuery as t } from "../../generated/global-operations.js";
3
3
  import { useToursI18n as n } from "../i18n.js";
4
- import { useEffect as r, useMemo as i } from "react";
5
- import { CheckCircle2 as a, HelpCircle as o, MinusCircle as s, Play as c } from "lucide-react";
6
- import { jsx as l, jsxs as u } from "react/jsx-runtime";
7
- import { useEventBus as d } from "@burdenoff/fe-libs/shared/events";
4
+ import { useEffect as r, useMemo as i, useRef as a } from "react";
5
+ import { CheckCircle2 as o, HelpCircle as s, MinusCircle as c, Play as l } from "lucide-react";
6
+ import { jsx as u, jsxs as d } from "react/jsx-runtime";
7
+ import { useEventBus as f } from "@burdenoff/fe-libs/shared/events";
8
8
  //#region src/tours/pages/MyToursProgressPage.tsx
9
- function f() {
10
- let { tr: f } = n(), { currentUser: m, productId: h, moduleId: g, pageId: _, workspaceId: v } = e(), y = d();
9
+ function p() {
10
+ let { tr: p } = n(), { currentUser: h, productId: g, moduleId: _, pageId: v, workspaceId: y } = e(), b = f(), x = a(!1);
11
11
  r(() => {
12
- y.emit("tours.page.viewed", {
12
+ x.current || (x.current = !0, b.emit("tours.page.viewed", {
13
13
  page: "/tours/progress",
14
- productId: h,
15
- moduleId: g,
16
- pageId: _,
17
- workspaceId: v
18
- });
19
- }, []);
20
- let { data: b, loading: x } = t({
14
+ productId: g,
15
+ moduleId: _,
16
+ pageId: v,
17
+ workspaceId: y
18
+ }));
19
+ }, [
20
+ b,
21
+ g,
22
+ _,
23
+ v,
24
+ y
25
+ ]);
26
+ let { data: S, loading: C } = t({
21
27
  variables: {
22
- userId: m?.id,
28
+ userId: h?.id,
23
29
  pagination: {
24
30
  limit: 100,
25
31
  offset: 0
26
32
  }
27
33
  },
28
- skip: !m?.id,
34
+ skip: !h?.id,
29
35
  fetchPolicy: "cache-and-network"
30
- }), S = i(() => {
36
+ }), w = i(() => {
31
37
  let e = {
32
38
  IN_PROGRESS: [],
33
39
  COMPLETED: [],
34
40
  SKIPPED: [],
35
41
  OTHER: []
36
- }, t = (b?.userTourProgresses ?? []).filter((e) => !(h && e.tour.productId !== h || g && e.tour.moduleId && e.tour.moduleId !== g || _ && e.tour.pageId && e.tour.pageId !== _));
42
+ }, t = (S?.userTourProgresses ?? []).filter((e) => !(g && e.tour.productId !== g || _ && e.tour.moduleId && e.tour.moduleId !== _ || v && e.tour.pageId && e.tour.pageId !== v));
37
43
  for (let n of t) n.status === "COMPLETED" ? e.COMPLETED.push(n) : n.status === "IN_PROGRESS" ? e.IN_PROGRESS.push(n) : n.status === "SKIPPED" || n.status === "DISMISSED" ? e.SKIPPED.push(n) : e.OTHER.push(n);
38
44
  return e;
39
45
  }, [
40
- b,
41
- h,
46
+ S,
42
47
  g,
43
- _
48
+ _,
49
+ v
44
50
  ]);
45
- return /* @__PURE__ */ u("div", {
51
+ return /* @__PURE__ */ d("div", {
46
52
  className: "space-y-4 sm:space-y-6 p-3 sm:p-6",
47
- children: [/* @__PURE__ */ u("div", { children: [/* @__PURE__ */ l("h1", {
53
+ children: [/* @__PURE__ */ d("div", { children: [/* @__PURE__ */ u("h1", {
48
54
  className: "text-xl sm:text-2xl font-bold text-foreground",
49
- children: f("myToursProgress.title")
50
- }), /* @__PURE__ */ l("p", {
55
+ children: p("myToursProgress.title")
56
+ }), /* @__PURE__ */ u("p", {
51
57
  className: "text-sm sm:text-base text-muted-foreground",
52
- children: f("myToursProgress.subtitle")
53
- })] }), x && (b?.userTourProgresses?.length ?? 0) === 0 ? /* @__PURE__ */ l("div", {
58
+ children: p("myToursProgress.subtitle")
59
+ })] }), C && (S?.userTourProgresses?.length ?? 0) === 0 ? /* @__PURE__ */ u("output", {
54
60
  className: "text-sm text-muted-foreground",
55
- role: "status",
56
- "aria-live": "polite",
57
- children: f("myToursProgress.loading")
58
- }) : /* @__PURE__ */ u("div", {
61
+ children: p("myToursProgress.loading")
62
+ }) : /* @__PURE__ */ d("div", {
59
63
  className: "space-y-6",
60
64
  children: [
61
- /* @__PURE__ */ l(p, {
62
- title: f("myToursProgress.inProgress"),
63
- icon: /* @__PURE__ */ l(c, {
65
+ /* @__PURE__ */ u(m, {
66
+ title: p("myToursProgress.inProgress"),
67
+ icon: /* @__PURE__ */ u(l, {
64
68
  size: 16,
65
69
  className: "text-warning"
66
70
  }),
67
- rows: S.IN_PROGRESS,
68
- emptyText: f("myToursProgress.emptyInProgress")
71
+ rows: w.IN_PROGRESS,
72
+ emptyText: p("myToursProgress.emptyInProgress")
69
73
  }),
70
- /* @__PURE__ */ l(p, {
71
- title: f("myToursProgress.completed"),
72
- icon: /* @__PURE__ */ l(a, {
74
+ /* @__PURE__ */ u(m, {
75
+ title: p("myToursProgress.completed"),
76
+ icon: /* @__PURE__ */ u(o, {
73
77
  size: 16,
74
78
  className: "text-success"
75
79
  }),
76
- rows: S.COMPLETED,
77
- emptyText: f("myToursProgress.emptyCompleted")
80
+ rows: w.COMPLETED,
81
+ emptyText: p("myToursProgress.emptyCompleted")
78
82
  }),
79
- /* @__PURE__ */ l(p, {
80
- title: f("myToursProgress.skipped"),
81
- icon: /* @__PURE__ */ l(s, {
83
+ /* @__PURE__ */ u(m, {
84
+ title: p("myToursProgress.skipped"),
85
+ icon: /* @__PURE__ */ u(c, {
82
86
  size: 16,
83
87
  className: "text-muted-foreground"
84
88
  }),
85
- rows: S.SKIPPED,
86
- emptyText: f("myToursProgress.emptySkipped")
89
+ rows: w.SKIPPED,
90
+ emptyText: p("myToursProgress.emptySkipped")
87
91
  }),
88
- S.OTHER.length > 0 ? /* @__PURE__ */ l(p, {
89
- title: f("myToursProgress.other"),
90
- icon: /* @__PURE__ */ l(o, {
92
+ w.OTHER.length > 0 ? /* @__PURE__ */ u(m, {
93
+ title: p("myToursProgress.other"),
94
+ icon: /* @__PURE__ */ u(s, {
91
95
  size: 16,
92
96
  className: "text-muted-foreground"
93
97
  }),
94
- rows: S.OTHER,
95
- emptyText: f("myToursProgress.emptyOther")
98
+ rows: w.OTHER,
99
+ emptyText: p("myToursProgress.emptyOther")
96
100
  }) : null
97
101
  ]
98
102
  })]
99
103
  });
100
104
  }
101
- function p({ title: e, icon: t, rows: n, emptyText: r }) {
102
- return /* @__PURE__ */ u("section", {
105
+ function m({ title: e, icon: t, rows: n, emptyText: r }) {
106
+ return /* @__PURE__ */ d("section", {
103
107
  className: "space-y-2",
104
- children: [/* @__PURE__ */ u("h2", {
108
+ children: [/* @__PURE__ */ d("h2", {
105
109
  className: "flex items-center gap-2 text-sm font-semibold text-foreground",
106
110
  children: [
107
111
  t,
108
112
  e,
109
- /* @__PURE__ */ u("span", {
113
+ /* @__PURE__ */ d("span", {
110
114
  className: "text-xs text-muted-foreground font-normal",
111
115
  children: [
112
116
  "(",
@@ -115,19 +119,19 @@ function p({ title: e, icon: t, rows: n, emptyText: r }) {
115
119
  ]
116
120
  })
117
121
  ]
118
- }), n.length === 0 ? /* @__PURE__ */ l("p", {
122
+ }), n.length === 0 ? /* @__PURE__ */ u("p", {
119
123
  className: "text-sm text-muted-foreground",
120
124
  role: "status",
121
125
  "aria-live": "polite",
122
126
  children: r
123
- }) : /* @__PURE__ */ l("ul", {
127
+ }) : /* @__PURE__ */ u("ul", {
124
128
  className: "divide-y divide-border rounded-md border border-border",
125
- children: n.map((e) => /* @__PURE__ */ u("li", {
129
+ children: n.map((e) => /* @__PURE__ */ d("li", {
126
130
  className: "flex items-center justify-between gap-3 p-3",
127
- children: [/* @__PURE__ */ l("span", {
131
+ children: [/* @__PURE__ */ u("span", {
128
132
  className: "text-sm text-foreground line-clamp-1",
129
133
  children: e.tour.name
130
- }), e.lastViewedAt ? /* @__PURE__ */ l("time", {
134
+ }), e.lastViewedAt ? /* @__PURE__ */ u("time", {
131
135
  className: "text-xs text-muted-foreground",
132
136
  dateTime: e.lastViewedAt,
133
137
  children: new Date(e.lastViewedAt).toLocaleDateString()
@@ -137,6 +141,6 @@ function p({ title: e, icon: t, rows: n, emptyText: r }) {
137
141
  });
138
142
  }
139
143
  //#endregion
140
- export { f as MyToursProgressPage };
144
+ export { p as MyToursProgressPage };
141
145
 
142
146
  //# sourceMappingURL=MyToursProgressPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"MyToursProgressPage.js","names":[],"sources":["../../../src/tours/pages/MyToursProgressPage.tsx"],"sourcesContent":["import { useMemo, useEffect } 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';\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 on mount\n useEffect(() => {\n eventBus.emit('tours.page.viewed', {\n page: '/tours/progress',\n productId: shellProductId,\n moduleId: shellModuleId,\n pageId: shellPageId,\n workspaceId,\n });\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n const { data, loading } = useGetUserTourProgressesQuery({\n variables: { userId: currentUser?.id, pagination: { limit: 100, offset: 0 } },\n skip: !currentUser?.id,\n fetchPolicy: 'cache-and-network',\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 = (data?.userTourProgresses ?? []).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, shellProductId, shellModuleId, shellPageId]);\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-foreground\">\n {tr('myToursProgress.title')}\n </h1>\n <p className=\"text-sm sm:text-base text-muted-foreground\">\n {tr('myToursProgress.subtitle')}\n </p>\n </div>\n\n {loading && (data?.userTourProgresses?.length ?? 0) === 0 ? (\n <div className=\"text-sm text-muted-foreground\" role=\"status\" aria-live=\"polite\">\n {tr('myToursProgress.loading')}\n </div>\n ) : (\n <div className=\"space-y-6\">\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 <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-muted-foreground\" />}\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-muted-foreground\" />}\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-foreground\">\n {icon}\n {title}\n <span className=\"text-xs text-muted-foreground font-normal\">({rows.length})</span>\n </h2>\n {rows.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\" role=\"status\" aria-live=\"polite\">\n {emptyText}\n </p>\n ) : (\n <ul className=\"divide-y divide-border rounded-md border border-border\">\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-foreground line-clamp-1\">{row.tour.name}</span>\n {row.lastViewedAt ? (\n <time className=\"text-xs text-muted-foreground\" 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":[],"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';\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 } = useGetUserTourProgressesQuery({\n variables: { userId: currentUser?.id, pagination: { limit: 100, offset: 0 } },\n skip: !currentUser?.id,\n fetchPolicy: 'cache-and-network',\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 = (data?.userTourProgresses ?? []).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, shellProductId, shellModuleId, shellPageId]);\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-foreground\">\n {tr('myToursProgress.title')}\n </h1>\n <p className=\"text-sm sm:text-base text-muted-foreground\">\n {tr('myToursProgress.subtitle')}\n </p>\n </div>\n\n {loading && (data?.userTourProgresses?.length ?? 0) === 0 ? (\n <output className=\"text-sm text-muted-foreground\">{tr('myToursProgress.loading')}</output>\n ) : (\n <div className=\"space-y-6\">\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 <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-muted-foreground\" />}\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-muted-foreground\" />}\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-foreground\">\n {icon}\n {title}\n <span className=\"text-xs text-muted-foreground font-normal\">({rows.length})</span>\n </h2>\n {rows.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\" role=\"status\" aria-live=\"polite\">\n {emptyText}\n </p>\n ) : (\n <ul className=\"divide-y divide-border rounded-md border border-border\">\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-foreground line-clamp-1\">{row.tour.name}</span>\n {row.lastViewedAt ? (\n <time className=\"text-xs text-muted-foreground\" 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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"}
@@ -54,6 +54,7 @@ function y() {
54
54
  /* @__PURE__ */ m("div", {
55
55
  className: "flex items-center gap-3 sm:gap-4",
56
56
  children: [/* @__PURE__ */ p("button", {
57
+ type: "button",
57
58
  onClick: L,
58
59
  className: "p-2 min-h-[44px] min-w-[44px] hover:bg-accent rounded-md",
59
60
  "aria-label": b("tourScheduler.goBack", "Go back"),
@@ -110,9 +111,11 @@ function y() {
110
111
  }), /* @__PURE__ */ m("div", {
111
112
  className: "space-y-4",
112
113
  children: [/* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
114
+ htmlFor: "scheduler-start-date",
113
115
  className: "block text-sm font-medium mb-2",
114
116
  children: b("tourScheduler.labelStartDate", "Start Date *")
115
117
  }), /* @__PURE__ */ p("input", {
118
+ id: "scheduler-start-date",
116
119
  type: "datetime-local",
117
120
  value: k.startDate,
118
121
  onChange: (e) => A({
@@ -120,18 +123,22 @@ function y() {
120
123
  startDate: e.target.value
121
124
  }),
122
125
  className: "w-full px-3 py-2 min-h-[44px] border border-border rounded-md bg-background",
123
- required: !0
126
+ required: !0,
127
+ "aria-label": b("tourScheduler.labelStartDate", "Start Date *")
124
128
  })] }), /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
129
+ htmlFor: "scheduler-end-date",
125
130
  className: "block text-sm font-medium mb-2",
126
131
  children: b("tourScheduler.labelEndDate", "End Date (Optional)")
127
132
  }), /* @__PURE__ */ p("input", {
133
+ id: "scheduler-end-date",
128
134
  type: "datetime-local",
129
135
  value: k.endDate,
130
136
  onChange: (e) => A({
131
137
  ...k,
132
138
  endDate: e.target.value
133
139
  }),
134
- className: "w-full px-3 py-2 min-h-[44px] border border-border rounded-md bg-background"
140
+ className: "w-full px-3 py-2 min-h-[44px] border border-border rounded-md bg-background",
141
+ "aria-label": b("tourScheduler.labelEndDate", "End Date (Optional)")
135
142
  })] })]
136
143
  })]
137
144
  }),
@@ -143,15 +150,18 @@ function y() {
143
150
  }), /* @__PURE__ */ m("div", {
144
151
  className: "space-y-4",
145
152
  children: [/* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
153
+ htmlFor: "scheduler-recurrence",
146
154
  className: "block text-sm font-medium mb-2",
147
155
  children: b("tourScheduler.labelRecurrencePattern", "Recurrence Pattern")
148
156
  }), /* @__PURE__ */ m("select", {
157
+ id: "scheduler-recurrence",
149
158
  value: k.recurrencePattern,
150
159
  onChange: (e) => A({
151
160
  ...k,
152
161
  recurrencePattern: e.target.value
153
162
  }),
154
163
  className: "w-full px-3 py-2 min-h-[44px] border border-border rounded-md bg-background",
164
+ "aria-label": b("tourScheduler.labelRecurrencePattern", "Recurrence Pattern"),
155
165
  children: [
156
166
  /* @__PURE__ */ p("option", {
157
167
  value: "DAILY",
@@ -171,9 +181,11 @@ function y() {
171
181
  })
172
182
  ]
173
183
  })] }), /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
184
+ htmlFor: "scheduler-timezone",
174
185
  className: "block text-sm font-medium mb-2",
175
186
  children: b("tourScheduler.labelTimezone", "Timezone")
176
187
  }), /* @__PURE__ */ p("input", {
188
+ id: "scheduler-timezone",
177
189
  type: "text",
178
190
  value: k.timezone,
179
191
  onChange: (e) => A({
@@ -181,7 +193,8 @@ function y() {
181
193
  timezone: e.target.value
182
194
  }),
183
195
  className: "w-full px-3 py-2 min-h-[44px] border border-border rounded-md bg-background",
184
- readOnly: !0
196
+ readOnly: !0,
197
+ "aria-label": b("tourScheduler.labelTimezone", "Timezone")
185
198
  })] })]
186
199
  })]
187
200
  }),
@@ -196,7 +209,8 @@ function y() {
196
209
  ...k,
197
210
  isActive: e.target.checked
198
211
  }),
199
- className: "w-5 h-5 rounded"
212
+ className: "size-5 rounded",
213
+ "aria-label": b("tourScheduler.statusActiveLabel", "Active")
200
214
  }), /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("div", {
201
215
  className: "font-medium",
202
216
  children: b("tourScheduler.statusActiveLabel", "Active")