@burdenoff/microfe-tours 2026.830.1 → 2026.918.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/tours/ToursAdminRoot.js +28 -27
  2. package/dist/tours/ToursAdminRoot.js.map +1 -1
  3. package/dist/tours/ToursAdminRoutes.js +56 -55
  4. package/dist/tours/ToursAdminRoutes.js.map +1 -1
  5. package/dist/tours/ToursUserRoot.js +34 -29
  6. package/dist/tours/ToursUserRoot.js.map +1 -1
  7. package/dist/tours/ToursUserRoutes.js +33 -33
  8. package/dist/tours/ToursUserRoutes.js.map +1 -1
  9. package/dist/tours/components/MarkdownEditor.js +85 -80
  10. package/dist/tours/components/MarkdownEditor.js.map +1 -1
  11. package/dist/tours/components/RouteHelpers.js +47 -42
  12. package/dist/tours/components/RouteHelpers.js.map +1 -1
  13. package/dist/tours/components/ToursAdminNavRegistrar.js +32 -27
  14. package/dist/tours/components/ToursAdminNavRegistrar.js.map +1 -1
  15. package/dist/tours/hooks/useStepSequencer.js +45 -36
  16. package/dist/tours/hooks/useStepSequencer.js.map +1 -1
  17. package/dist/tours/hooks/useTourBuilder.js +106 -93
  18. package/dist/tours/hooks/useTourBuilder.js.map +1 -1
  19. package/dist/tours/hooks/useTourPreview.js +51 -41
  20. package/dist/tours/hooks/useTourPreview.js.map +1 -1
  21. package/dist/tours/hooks/useToursList.js +28 -27
  22. package/dist/tours/hooks/useToursList.js.map +1 -1
  23. package/dist/tours/i18n.js +187 -5
  24. package/dist/tours/i18n.js.map +1 -1
  25. package/dist/tours/pages/AnalyticsDashboardPage.js +108 -109
  26. package/dist/tours/pages/AnalyticsDashboardPage.js.map +1 -1
  27. package/dist/tours/pages/MediaLibraryPage.js +58 -61
  28. package/dist/tours/pages/MediaLibraryPage.js.map +1 -1
  29. package/dist/tours/pages/MyToursPage.js +5 -5
  30. package/dist/tours/pages/MyToursProgressPage.js +5 -5
  31. package/dist/tours/pages/SchedulerPage.js +78 -81
  32. package/dist/tours/pages/SchedulerPage.js.map +1 -1
  33. package/dist/tours/pages/StepEditorPage.js +32 -32
  34. package/dist/tours/pages/StepEditorPage.js.map +1 -1
  35. package/dist/tours/pages/SystemAnalyticsPage.js +75 -79
  36. package/dist/tours/pages/SystemAnalyticsPage.js.map +1 -1
  37. package/dist/tours/pages/TemplateLibraryPage.js +8 -8
  38. package/dist/tours/pages/TourBuilderPage.js +295 -279
  39. package/dist/tours/pages/TourBuilderPage.js.map +1 -1
  40. package/dist/tours/pages/TourPreviewPage.js +6 -6
  41. package/dist/tours/pages/ToursListPage.js +155 -155
  42. package/dist/tours/pages/ToursListPage.js.map +1 -1
  43. package/dist/tours/store/toursStore.js +11 -7
  44. package/dist/tours/store/toursStore.js.map +1 -1
  45. package/dist/tours/utils/index.js +32 -19
  46. package/dist/tours/utils/index.js.map +1 -1
  47. package/package.json +1 -1
@@ -1,10 +1,11 @@
1
- import { useCallback as e, useRef as t, useState as n } from "react";
2
- import { Markdown as r } from "@burdenoff/fe-libs/shared/components";
3
- import { Bold as i, Code as a, Italic as o, Link as s, List as c } from "lucide-react";
4
- import { jsx as l, jsxs as u } from "react/jsx-runtime";
1
+ import { useTr as e } from "../i18n.js";
2
+ import { useCallback as t, useRef as n, useState as r } from "react";
3
+ import { Markdown as i } from "@burdenoff/fe-libs/shared/components";
4
+ import { Bold as a, Code as o, Italic as s, Link as c, List as l } from "lucide-react";
5
+ import { jsx as u, jsxs as d } from "react/jsx-runtime";
5
6
  //#region src/tours/components/MarkdownEditor.tsx
6
- function d({ title: e, onClick: t, children: n }) {
7
- return /* @__PURE__ */ l("button", {
7
+ function f({ title: e, onClick: t, children: n }) {
8
+ return /* @__PURE__ */ u("button", {
8
9
  type: "button",
9
10
  onClick: t,
10
11
  title: e,
@@ -13,7 +14,7 @@ function d({ title: e, onClick: t, children: n }) {
13
14
  children: n
14
15
  });
15
16
  }
16
- function f(e, t, n = t) {
17
+ function p(e, t, n = t) {
17
18
  let r = e.selectionStart, i = e.selectionEnd, a = e.value, o = a.slice(r, i), s = `${t}${o}${n}`, c = a.slice(0, r) + s + a.slice(i);
18
19
  return requestAnimationFrame(() => {
19
20
  e.focus();
@@ -21,7 +22,7 @@ function f(e, t, n = t) {
21
22
  e.setSelectionRange(n, n);
22
23
  }), c;
23
24
  }
24
- function p(e, t) {
25
+ function m(e, t) {
25
26
  let n = e.selectionStart, r = e.value, i = r.lastIndexOf("\n", n - 1) + 1, a = r.slice(0, i), o = r.slice(i);
26
27
  return requestAnimationFrame(() => {
27
28
  e.focus();
@@ -29,109 +30,113 @@ function p(e, t) {
29
30
  e.setSelectionRange(n, n);
30
31
  }), `${a}${t}${o}`;
31
32
  }
32
- function m({ id: m, value: h, onChange: g, label: _, placeholder: v, rows: y = 6, required: b, disabled: x, "aria-label": S }) {
33
- let C = t(null), [w, T] = n("write"), E = e(() => {
34
- let e = C.current;
35
- e && g(f(e, "**"));
36
- }, [g]), D = e(() => {
37
- let e = C.current;
38
- e && g(f(e, "_"));
39
- }, [g]), O = e(() => {
40
- let e = C.current;
33
+ function h({ id: h, value: g, onChange: _, label: v, placeholder: y, rows: b = 6, required: x, disabled: S, "aria-label": C }) {
34
+ let w = e(), T = n(null), [E, D] = r("write"), O = t(() => {
35
+ let e = T.current;
36
+ e && _(p(e, "**"));
37
+ }, [_]), k = t(() => {
38
+ let e = T.current;
39
+ e && _(p(e, "_"));
40
+ }, [_]), A = t(() => {
41
+ let e = T.current;
41
42
  if (!e) return;
42
- let t = e.selectionStart, n = e.selectionEnd, r = h.slice(t, n) || "link text", i = `[${r}](url)`, a = h.slice(0, t) + i + h.slice(n);
43
+ let t = e.selectionStart, n = e.selectionEnd, r = g.slice(t, n) || w("markdownEditor.linkTextPlaceholder", "link text"), i = `[${r}](url)`, a = g.slice(0, t) + i + g.slice(n);
43
44
  requestAnimationFrame(() => {
44
45
  e.focus();
45
46
  let n = t + r.length + 3, i = n + 3;
46
47
  e.setSelectionRange(n, i);
47
- }), g(a);
48
- }, [g, h]), k = e(() => {
49
- let e = C.current;
50
- e && g(f(e, "`"));
51
- }, [g]), A = e(() => {
52
- let e = C.current;
53
- e && g(p(e, "- "));
54
- }, [g]);
55
- return /* @__PURE__ */ u("div", {
48
+ }), _(a);
49
+ }, [
50
+ _,
51
+ g,
52
+ w
53
+ ]), j = t(() => {
54
+ let e = T.current;
55
+ e && _(p(e, "`"));
56
+ }, [_]), M = t(() => {
57
+ let e = T.current;
58
+ e && _(m(e, "- "));
59
+ }, [_]);
60
+ return /* @__PURE__ */ d("div", {
56
61
  className: "space-y-2",
57
- children: [_ && /* @__PURE__ */ u("label", {
58
- htmlFor: m,
62
+ children: [v && /* @__PURE__ */ d("label", {
63
+ htmlFor: h,
59
64
  className: "block text-sm font-medium",
60
- children: [_, b && /* @__PURE__ */ l("span", {
65
+ children: [v, x && /* @__PURE__ */ u("span", {
61
66
  className: "text-status-error-text ml-1",
62
67
  children: "*"
63
68
  })]
64
- }), /* @__PURE__ */ u("div", {
69
+ }), /* @__PURE__ */ d("div", {
65
70
  className: "border border-border-subtle rounded-md bg-bg-surface overflow-hidden",
66
- children: [/* @__PURE__ */ u("div", {
71
+ children: [/* @__PURE__ */ d("div", {
67
72
  className: "flex items-center justify-between border-b border-border-subtle px-2 py-1",
68
- children: [/* @__PURE__ */ u("div", {
73
+ children: [/* @__PURE__ */ d("div", {
69
74
  className: "flex items-center gap-1",
70
75
  children: [
71
- /* @__PURE__ */ l(d, {
72
- title: "Bold",
73
- onClick: E,
74
- children: /* @__PURE__ */ l(i, { size: 16 })
75
- }),
76
- /* @__PURE__ */ l(d, {
77
- title: "Italic",
78
- onClick: D,
79
- children: /* @__PURE__ */ l(o, { size: 16 })
80
- }),
81
- /* @__PURE__ */ l(d, {
82
- title: "Link",
76
+ /* @__PURE__ */ u(f, {
77
+ title: w("markdownEditor.bold", "Bold"),
83
78
  onClick: O,
84
- children: /* @__PURE__ */ l(s, { size: 16 })
79
+ children: /* @__PURE__ */ u(a, { size: 16 })
85
80
  }),
86
- /* @__PURE__ */ l(d, {
87
- title: "Inline code",
81
+ /* @__PURE__ */ u(f, {
82
+ title: w("markdownEditor.italic", "Italic"),
88
83
  onClick: k,
89
- children: /* @__PURE__ */ l(a, { size: 16 })
84
+ children: /* @__PURE__ */ u(s, { size: 16 })
90
85
  }),
91
- /* @__PURE__ */ l(d, {
92
- title: "Bullet list",
86
+ /* @__PURE__ */ u(f, {
87
+ title: w("markdownEditor.link", "Link"),
93
88
  onClick: A,
94
- children: /* @__PURE__ */ l(c, { size: 16 })
89
+ children: /* @__PURE__ */ u(c, { size: 16 })
90
+ }),
91
+ /* @__PURE__ */ u(f, {
92
+ title: w("markdownEditor.inlineCode", "Inline code"),
93
+ onClick: j,
94
+ children: /* @__PURE__ */ u(o, { size: 16 })
95
+ }),
96
+ /* @__PURE__ */ u(f, {
97
+ title: w("markdownEditor.bulletList", "Bullet list"),
98
+ onClick: M,
99
+ children: /* @__PURE__ */ u(l, { size: 16 })
95
100
  })
96
101
  ]
97
- }), /* @__PURE__ */ u("div", {
102
+ }), /* @__PURE__ */ d("div", {
98
103
  className: "flex items-center gap-1",
99
104
  role: "tablist",
100
- "aria-label": "Markdown editor tabs",
101
- children: [/* @__PURE__ */ l("button", {
105
+ "aria-label": w("markdownEditor.tabsAriaLabel", "Markdown editor tabs"),
106
+ children: [/* @__PURE__ */ u("button", {
102
107
  type: "button",
103
108
  role: "tab",
104
- "aria-selected": w === "write",
105
- onClick: () => T("write"),
106
- className: `px-3 py-1.5 text-sm rounded-md ${w === "write" ? "bg-action-primary-bg text-action-primary-text" : "hover:bg-accent text-text-secondary"}`,
107
- children: "Write"
108
- }), /* @__PURE__ */ l("button", {
109
+ "aria-selected": E === "write",
110
+ onClick: () => D("write"),
111
+ className: `px-3 py-1.5 text-sm rounded-md ${E === "write" ? "bg-action-primary-bg text-action-primary-text" : "hover:bg-accent text-text-secondary"}`,
112
+ children: w("markdownEditor.writeTab", "Write")
113
+ }), /* @__PURE__ */ u("button", {
109
114
  type: "button",
110
115
  role: "tab",
111
- "aria-selected": w === "preview",
112
- onClick: () => T("preview"),
113
- className: `px-3 py-1.5 text-sm rounded-md ${w === "preview" ? "bg-action-primary-bg text-action-primary-text" : "hover:bg-accent text-text-secondary"}`,
114
- children: "Preview"
116
+ "aria-selected": E === "preview",
117
+ onClick: () => D("preview"),
118
+ className: `px-3 py-1.5 text-sm rounded-md ${E === "preview" ? "bg-action-primary-bg text-action-primary-text" : "hover:bg-accent text-text-secondary"}`,
119
+ children: w("markdownEditor.previewTab", "Preview")
115
120
  })]
116
121
  })]
117
- }), /* @__PURE__ */ u("div", {
122
+ }), /* @__PURE__ */ d("div", {
118
123
  className: "relative",
119
- children: [/* @__PURE__ */ l("textarea", {
120
- ref: C,
121
- id: m,
122
- value: h,
123
- onChange: (e) => g(e.target.value),
124
- rows: y,
125
- disabled: x,
126
- required: b,
127
- placeholder: v,
128
- "aria-label": S ?? _,
129
- className: `w-full px-3 py-2 bg-bg-surface text-text-primary text-sm sm:text-base resize-y min-h-[120px] focus:outline-none ${w === "write" ? "block" : "hidden"}`
130
- }), w === "preview" && /* @__PURE__ */ l("div", {
124
+ children: [/* @__PURE__ */ u("textarea", {
125
+ ref: T,
126
+ id: h,
127
+ value: g,
128
+ onChange: (e) => _(e.target.value),
129
+ rows: b,
130
+ disabled: S,
131
+ required: x,
132
+ placeholder: y,
133
+ "aria-label": C ?? v,
134
+ className: `w-full px-3 py-2 bg-bg-surface text-text-primary text-sm sm:text-base resize-y min-h-[120px] focus:outline-none ${E === "write" ? "block" : "hidden"}`
135
+ }), E === "preview" && /* @__PURE__ */ u("div", {
131
136
  className: "w-full px-3 py-2 min-h-[120px] max-h-[24rem] overflow-y-auto text-sm sm:text-base",
132
- children: h ? /* @__PURE__ */ l(r, { children: h }) : /* @__PURE__ */ l("p", {
137
+ children: g ? /* @__PURE__ */ u(i, { children: g }) : /* @__PURE__ */ u("p", {
133
138
  className: "text-text-secondary italic",
134
- children: "Nothing to preview"
139
+ children: w("markdownEditor.nothingToPreview", "Nothing to preview")
135
140
  })
136
141
  })]
137
142
  })]
@@ -139,6 +144,6 @@ function m({ id: m, value: h, onChange: g, label: _, placeholder: v, rows: y = 6
139
144
  });
140
145
  }
141
146
  //#endregion
142
- export { m as MarkdownEditor };
147
+ export { h as MarkdownEditor };
143
148
 
144
149
  //# sourceMappingURL=MarkdownEditor.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"MarkdownEditor.js","names":[],"sources":["../../../src/tours/components/MarkdownEditor.tsx"],"sourcesContent":["import { useCallback, useRef, useState } from 'react';\nimport { Bold, Italic, Link, Code, List } from 'lucide-react';\nimport { Markdown } from '@burdenoff/fe-libs/shared/components';\n\nexport interface MarkdownEditorProps {\n id?: string;\n value: string;\n onChange: (value: string) => void;\n label?: string;\n placeholder?: string;\n rows?: number;\n required?: boolean;\n disabled?: boolean;\n 'aria-label'?: string;\n}\n\ninterface ToolbarButtonProps {\n title: string;\n onClick: () => void;\n children: React.ReactNode;\n}\n\nfunction ToolbarButton({ title, onClick, children }: ToolbarButtonProps) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n title={title}\n aria-label={title}\n className=\"p-2 min-h-[36px] min-w-[36px] rounded-md border border-border-subtle hover:bg-accent text-text-secondary\"\n >\n {children}\n </button>\n );\n}\n\nfunction wrapSelection(\n textarea: HTMLTextAreaElement,\n prefix: string,\n suffix: string = prefix\n): string {\n const start = textarea.selectionStart;\n const end = textarea.selectionEnd;\n const value = textarea.value;\n const selected = value.slice(start, end);\n const replacement = `${prefix}${selected}${suffix}`;\n const newValue = value.slice(0, start) + replacement + value.slice(end);\n\n // Defer cursor positioning so React state flush does not reset it.\n requestAnimationFrame(() => {\n textarea.focus();\n const newCursor = start + prefix.length + selected.length;\n textarea.setSelectionRange(newCursor, newCursor);\n });\n\n return newValue;\n}\n\nfunction insertAtLineStart(textarea: HTMLTextAreaElement, marker: string): string {\n const start = textarea.selectionStart;\n const value = textarea.value;\n const lineStart = value.lastIndexOf('\\n', start - 1) + 1;\n const before = value.slice(0, lineStart);\n const after = value.slice(lineStart);\n\n requestAnimationFrame(() => {\n textarea.focus();\n const newCursor = lineStart + marker.length;\n textarea.setSelectionRange(newCursor, newCursor);\n });\n\n return `${before}${marker}${after}`;\n}\n\nexport function MarkdownEditor({\n id,\n value,\n onChange,\n label,\n placeholder,\n rows = 6,\n required,\n disabled,\n 'aria-label': ariaLabel,\n}: MarkdownEditorProps) {\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n const [activeTab, setActiveTab] = useState<'write' | 'preview'>('write');\n\n const handleBold = useCallback(() => {\n const textarea = textareaRef.current;\n if (!textarea) return;\n onChange(wrapSelection(textarea, '**'));\n }, [onChange]);\n\n const handleItalic = useCallback(() => {\n const textarea = textareaRef.current;\n if (!textarea) return;\n onChange(wrapSelection(textarea, '_'));\n }, [onChange]);\n\n const handleLink = useCallback(() => {\n const textarea = textareaRef.current;\n if (!textarea) return;\n const start = textarea.selectionStart;\n const end = textarea.selectionEnd;\n const selected = value.slice(start, end) || 'link text';\n const replacement = `[${selected}](url)`;\n const newValue = value.slice(0, start) + replacement + value.slice(end);\n\n requestAnimationFrame(() => {\n textarea.focus();\n const linkStart = start + selected.length + 3;\n const linkEnd = linkStart + 3;\n textarea.setSelectionRange(linkStart, linkEnd);\n });\n\n onChange(newValue);\n }, [onChange, value]);\n\n const handleCode = useCallback(() => {\n const textarea = textareaRef.current;\n if (!textarea) return;\n onChange(wrapSelection(textarea, '`'));\n }, [onChange]);\n\n const handleList = useCallback(() => {\n const textarea = textareaRef.current;\n if (!textarea) return;\n onChange(insertAtLineStart(textarea, '- '));\n }, [onChange]);\n\n return (\n <div className=\"space-y-2\">\n {label && (\n <label htmlFor={id} className=\"block text-sm font-medium\">\n {label}\n {required && <span className=\"text-status-error-text ml-1\">*</span>}\n </label>\n )}\n\n <div className=\"border border-border-subtle rounded-md bg-bg-surface overflow-hidden\">\n <div className=\"flex items-center justify-between border-b border-border-subtle px-2 py-1\">\n <div className=\"flex items-center gap-1\">\n <ToolbarButton title=\"Bold\" onClick={handleBold}>\n <Bold size={16} />\n </ToolbarButton>\n <ToolbarButton title=\"Italic\" onClick={handleItalic}>\n <Italic size={16} />\n </ToolbarButton>\n <ToolbarButton title=\"Link\" onClick={handleLink}>\n <Link size={16} />\n </ToolbarButton>\n <ToolbarButton title=\"Inline code\" onClick={handleCode}>\n <Code size={16} />\n </ToolbarButton>\n <ToolbarButton title=\"Bullet list\" onClick={handleList}>\n <List size={16} />\n </ToolbarButton>\n </div>\n\n <div className=\"flex items-center gap-1\" role=\"tablist\" aria-label=\"Markdown editor tabs\">\n <button\n type=\"button\"\n role=\"tab\"\n aria-selected={activeTab === 'write'}\n onClick={() => setActiveTab('write')}\n className={`px-3 py-1.5 text-sm rounded-md ${\n activeTab === 'write'\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'hover:bg-accent text-text-secondary'\n }`}\n >\n Write\n </button>\n <button\n type=\"button\"\n role=\"tab\"\n aria-selected={activeTab === 'preview'}\n onClick={() => setActiveTab('preview')}\n className={`px-3 py-1.5 text-sm rounded-md ${\n activeTab === 'preview'\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'hover:bg-accent text-text-secondary'\n }`}\n >\n Preview\n </button>\n </div>\n </div>\n\n <div className=\"relative\">\n <textarea\n ref={textareaRef}\n id={id}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n rows={rows}\n disabled={disabled}\n required={required}\n placeholder={placeholder}\n aria-label={ariaLabel ?? label}\n className={`w-full px-3 py-2 bg-bg-surface text-text-primary text-sm sm:text-base resize-y min-h-[120px] focus:outline-none ${\n activeTab === 'write' ? 'block' : 'hidden'\n }`}\n />\n {activeTab === 'preview' && (\n <div className=\"w-full px-3 py-2 min-h-[120px] max-h-[24rem] overflow-y-auto text-sm sm:text-base\">\n {value ? (\n <Markdown>{value}</Markdown>\n ) : (\n <p className=\"text-text-secondary italic\">Nothing to preview</p>\n )}\n </div>\n )}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"MarkdownEditor.js","names":[],"sources":["../../../src/tours/components/MarkdownEditor.tsx"],"sourcesContent":["import { useCallback, useRef, useState } from 'react';\nimport { Bold, Italic, Link, Code, List } from 'lucide-react';\nimport { Markdown } from '@burdenoff/fe-libs/shared/components';\nimport { useTr } from '../i18n';\n\nexport interface MarkdownEditorProps {\n id?: string;\n value: string;\n onChange: (value: string) => void;\n label?: string;\n placeholder?: string;\n rows?: number;\n required?: boolean;\n disabled?: boolean;\n 'aria-label'?: string;\n}\n\ninterface ToolbarButtonProps {\n title: string;\n onClick: () => void;\n children: React.ReactNode;\n}\n\nfunction ToolbarButton({ title, onClick, children }: ToolbarButtonProps) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n title={title}\n aria-label={title}\n className=\"p-2 min-h-[36px] min-w-[36px] rounded-md border border-border-subtle hover:bg-accent text-text-secondary\"\n >\n {children}\n </button>\n );\n}\n\nfunction wrapSelection(\n textarea: HTMLTextAreaElement,\n prefix: string,\n suffix: string = prefix\n): string {\n const start = textarea.selectionStart;\n const end = textarea.selectionEnd;\n const value = textarea.value;\n const selected = value.slice(start, end);\n const replacement = `${prefix}${selected}${suffix}`;\n const newValue = value.slice(0, start) + replacement + value.slice(end);\n\n // Defer cursor positioning so React state flush does not reset it.\n requestAnimationFrame(() => {\n textarea.focus();\n const newCursor = start + prefix.length + selected.length;\n textarea.setSelectionRange(newCursor, newCursor);\n });\n\n return newValue;\n}\n\nfunction insertAtLineStart(textarea: HTMLTextAreaElement, marker: string): string {\n const start = textarea.selectionStart;\n const value = textarea.value;\n const lineStart = value.lastIndexOf('\\n', start - 1) + 1;\n const before = value.slice(0, lineStart);\n const after = value.slice(lineStart);\n\n requestAnimationFrame(() => {\n textarea.focus();\n const newCursor = lineStart + marker.length;\n textarea.setSelectionRange(newCursor, newCursor);\n });\n\n return `${before}${marker}${after}`;\n}\n\nexport function MarkdownEditor({\n id,\n value,\n onChange,\n label,\n placeholder,\n rows = 6,\n required,\n disabled,\n 'aria-label': ariaLabel,\n}: MarkdownEditorProps) {\n const tr = useTr();\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n const [activeTab, setActiveTab] = useState<'write' | 'preview'>('write');\n\n const handleBold = useCallback(() => {\n const textarea = textareaRef.current;\n if (!textarea) return;\n onChange(wrapSelection(textarea, '**'));\n }, [onChange]);\n\n const handleItalic = useCallback(() => {\n const textarea = textareaRef.current;\n if (!textarea) return;\n onChange(wrapSelection(textarea, '_'));\n }, [onChange]);\n\n const handleLink = useCallback(() => {\n const textarea = textareaRef.current;\n if (!textarea) return;\n const start = textarea.selectionStart;\n const end = textarea.selectionEnd;\n const selected =\n value.slice(start, end) || tr('markdownEditor.linkTextPlaceholder', 'link text');\n const replacement = `[${selected}](url)`;\n const newValue = value.slice(0, start) + replacement + value.slice(end);\n\n requestAnimationFrame(() => {\n textarea.focus();\n const linkStart = start + selected.length + 3;\n const linkEnd = linkStart + 3;\n textarea.setSelectionRange(linkStart, linkEnd);\n });\n\n onChange(newValue);\n }, [onChange, value, tr]);\n\n const handleCode = useCallback(() => {\n const textarea = textareaRef.current;\n if (!textarea) return;\n onChange(wrapSelection(textarea, '`'));\n }, [onChange]);\n\n const handleList = useCallback(() => {\n const textarea = textareaRef.current;\n if (!textarea) return;\n onChange(insertAtLineStart(textarea, '- '));\n }, [onChange]);\n\n return (\n <div className=\"space-y-2\">\n {label && (\n <label htmlFor={id} className=\"block text-sm font-medium\">\n {label}\n {required && <span className=\"text-status-error-text ml-1\">*</span>}\n </label>\n )}\n\n <div className=\"border border-border-subtle rounded-md bg-bg-surface overflow-hidden\">\n <div className=\"flex items-center justify-between border-b border-border-subtle px-2 py-1\">\n <div className=\"flex items-center gap-1\">\n <ToolbarButton title={tr('markdownEditor.bold', 'Bold')} onClick={handleBold}>\n <Bold size={16} />\n </ToolbarButton>\n <ToolbarButton title={tr('markdownEditor.italic', 'Italic')} onClick={handleItalic}>\n <Italic size={16} />\n </ToolbarButton>\n <ToolbarButton title={tr('markdownEditor.link', 'Link')} onClick={handleLink}>\n <Link size={16} />\n </ToolbarButton>\n <ToolbarButton\n title={tr('markdownEditor.inlineCode', 'Inline code')}\n onClick={handleCode}\n >\n <Code size={16} />\n </ToolbarButton>\n <ToolbarButton\n title={tr('markdownEditor.bulletList', 'Bullet list')}\n onClick={handleList}\n >\n <List size={16} />\n </ToolbarButton>\n </div>\n\n <div\n className=\"flex items-center gap-1\"\n role=\"tablist\"\n aria-label={tr('markdownEditor.tabsAriaLabel', 'Markdown editor tabs')}\n >\n <button\n type=\"button\"\n role=\"tab\"\n aria-selected={activeTab === 'write'}\n onClick={() => setActiveTab('write')}\n className={`px-3 py-1.5 text-sm rounded-md ${\n activeTab === 'write'\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'hover:bg-accent text-text-secondary'\n }`}\n >\n {tr('markdownEditor.writeTab', 'Write')}\n </button>\n <button\n type=\"button\"\n role=\"tab\"\n aria-selected={activeTab === 'preview'}\n onClick={() => setActiveTab('preview')}\n className={`px-3 py-1.5 text-sm rounded-md ${\n activeTab === 'preview'\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'hover:bg-accent text-text-secondary'\n }`}\n >\n {tr('markdownEditor.previewTab', 'Preview')}\n </button>\n </div>\n </div>\n\n <div className=\"relative\">\n <textarea\n ref={textareaRef}\n id={id}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n rows={rows}\n disabled={disabled}\n required={required}\n placeholder={placeholder}\n aria-label={ariaLabel ?? label}\n className={`w-full px-3 py-2 bg-bg-surface text-text-primary text-sm sm:text-base resize-y min-h-[120px] focus:outline-none ${\n activeTab === 'write' ? 'block' : 'hidden'\n }`}\n />\n {activeTab === 'preview' && (\n <div className=\"w-full px-3 py-2 min-h-[120px] max-h-[24rem] overflow-y-auto text-sm sm:text-base\">\n {value ? (\n <Markdown>{value}</Markdown>\n ) : (\n <p className=\"text-text-secondary italic\">\n {tr('markdownEditor.nothingToPreview', 'Nothing to preview')}\n </p>\n )}\n </div>\n )}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1,14 +1,49 @@
1
- import { Component as e, Fragment as t } from "react";
2
- import { AlertTriangle as n, Loader2 as r, RefreshCw as i } from "lucide-react";
3
- import { jsx as a, jsxs as o } from "react/jsx-runtime";
1
+ import { useTr as e } from "../i18n.js";
2
+ import { Component as t, Fragment as n } from "react";
3
+ import { AlertTriangle as r, Loader2 as i, RefreshCw as a } from "lucide-react";
4
+ import { jsx as o, jsxs as s } from "react/jsx-runtime";
4
5
  //#region src/tours/components/RouteHelpers.tsx
5
- function s() {
6
- return /* @__PURE__ */ a("div", {
6
+ function c() {
7
+ return /* @__PURE__ */ o("div", {
7
8
  className: "flex items-center justify-center min-h-[200px]",
8
- children: /* @__PURE__ */ a(r, { className: "size-8 animate-spin text-primary" })
9
+ children: /* @__PURE__ */ o(i, { className: "size-8 animate-spin text-primary" })
9
10
  });
10
11
  }
11
- var c = 3, l = class extends e {
12
+ var l = 3;
13
+ function u({ retryCount: t, onRetry: n }) {
14
+ let i = e(), c = t < l;
15
+ return /* @__PURE__ */ s("div", {
16
+ className: "flex flex-col items-center justify-center min-h-[400px] gap-4 text-center p-6",
17
+ children: [
18
+ /* @__PURE__ */ o("div", {
19
+ className: "p-3 rounded-full bg-status-error-bg/10",
20
+ children: /* @__PURE__ */ o(r, { className: "size-8 text-status-error-text" })
21
+ }),
22
+ /* @__PURE__ */ s("div", {
23
+ className: "space-y-2",
24
+ children: [/* @__PURE__ */ o("h3", {
25
+ className: "text-lg font-semibold text-text-primary",
26
+ children: i("routeErrors.failedToLoadPage", "Failed to load page")
27
+ }), /* @__PURE__ */ o("p", {
28
+ className: "text-sm text-text-secondary max-w-md",
29
+ children: c ? i("routeErrors.chunkLoadRetry", "The page chunk could not be loaded. This may be due to a network issue.") : i("routeErrors.chunkLoadRefresh", "The page chunk could not be loaded after multiple attempts. Please refresh the page.")
30
+ })]
31
+ }),
32
+ c ? /* @__PURE__ */ s("button", {
33
+ type: "button",
34
+ onClick: n,
35
+ className: "inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90 transition-colors",
36
+ children: [/* @__PURE__ */ o(a, { className: "size-4" }), i("routeErrors.tryAgain", "Try Again")]
37
+ }) : /* @__PURE__ */ s("button", {
38
+ type: "button",
39
+ onClick: () => window.location.reload(),
40
+ className: "inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90 transition-colors",
41
+ children: [/* @__PURE__ */ o(a, { className: "size-4" }), i("routeErrors.refreshPage", "Refresh Page")]
42
+ })
43
+ ]
44
+ });
45
+ }
46
+ var d = class extends t {
12
47
  constructor(e) {
13
48
  super(e), this.state = {
14
49
  hasError: !1,
@@ -33,43 +68,13 @@ var c = 3, l = class extends e {
33
68
  }));
34
69
  };
35
70
  render() {
36
- if (this.state.hasError) {
37
- let e = this.state.retryCount < c;
38
- return /* @__PURE__ */ o("div", {
39
- className: "flex flex-col items-center justify-center min-h-[400px] gap-4 text-center p-6",
40
- children: [
41
- /* @__PURE__ */ a("div", {
42
- className: "p-3 rounded-full bg-status-error-bg/10",
43
- children: /* @__PURE__ */ a(n, { className: "size-8 text-status-error-text" })
44
- }),
45
- /* @__PURE__ */ o("div", {
46
- className: "space-y-2",
47
- children: [/* @__PURE__ */ a("h3", {
48
- className: "text-lg font-semibold text-text-primary",
49
- children: "Failed to load page"
50
- }), /* @__PURE__ */ a("p", {
51
- className: "text-sm text-text-secondary max-w-md",
52
- children: e ? "The page chunk could not be loaded. This may be due to a network issue." : "The page chunk could not be loaded after multiple attempts. Please refresh the page."
53
- })]
54
- }),
55
- e ? /* @__PURE__ */ o("button", {
56
- type: "button",
57
- onClick: this.handleRetry,
58
- className: "inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90 transition-colors",
59
- children: [/* @__PURE__ */ a(i, { className: "size-4" }), "Try Again"]
60
- }) : /* @__PURE__ */ o("button", {
61
- type: "button",
62
- onClick: () => window.location.reload(),
63
- className: "inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90 transition-colors",
64
- children: [/* @__PURE__ */ a(i, { className: "size-4" }), "Refresh Page"]
65
- })
66
- ]
67
- });
68
- }
69
- return /* @__PURE__ */ a(t, { children: this.props.children }, this.state.retryCount);
71
+ return this.state.hasError ? /* @__PURE__ */ o(u, {
72
+ retryCount: this.state.retryCount,
73
+ onRetry: this.handleRetry
74
+ }) : /* @__PURE__ */ o(n, { children: this.props.children }, this.state.retryCount);
70
75
  }
71
76
  };
72
77
  //#endregion
73
- export { l as LazyErrorBoundary, s as PageLoader };
78
+ export { d as LazyErrorBoundary, c as PageLoader };
74
79
 
75
80
  //# sourceMappingURL=RouteHelpers.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"RouteHelpers.js","names":[],"sources":["../../../src/tours/components/RouteHelpers.tsx"],"sourcesContent":["import { Component, Fragment, type ReactNode, type ErrorInfo } from 'react';\nimport { Loader2, AlertTriangle, RefreshCw } from 'lucide-react';\n\n/**\n * Loading fallback for lazy-loaded pages.\n * Shared between ToursAdminRoutes and ToursUserRoutes (Section D/E of BOFF-2391).\n */\nexport function PageLoader() {\n return (\n <div className=\"flex items-center justify-center min-h-[200px]\">\n <Loader2 className=\"size-8 animate-spin text-primary\" />\n </div>\n );\n}\n\n/** Max consecutive retries before the error screen suggests a full reload. */\nconst MAX_RETRY_COUNT = 3;\n\n/**\n * Error boundary for lazy-loaded page chunks.\n *\n * React.lazy() caches rejected promises, so resetting error state alone would\n * re-throw the same cached rejection. The retryCount key forces a full remount\n * of the subtree on retry, creating fresh lazy-component instances.\n */\nexport class LazyErrorBoundary extends Component<\n { children: ReactNode },\n { hasError: boolean; error: Error | null; retryCount: number }\n> {\n constructor(props: { children: ReactNode }) {\n super(props);\n this.state = { hasError: false, error: null, retryCount: 0 };\n }\n\n static getDerivedStateFromError(error: Error) {\n return { hasError: true, error };\n }\n\n componentDidCatch(error: Error, errorInfo: ErrorInfo) {\n // TODO: forward to error monitoring service (Sentry, etc.)\n // Log the full error server-side / in the console; do NOT surface the raw\n // message to users since chunk-load errors can include CDN paths, build\n // hashes, or deployment details that shouldn't appear in the UI.\n console.error('[Tours] Lazy page load failed:', error, errorInfo);\n }\n\n handleRetry = () => {\n this.setState((prev) => ({\n hasError: false,\n error: null,\n retryCount: prev.retryCount + 1,\n }));\n };\n\n render() {\n if (this.state.hasError) {\n const canRetry = this.state.retryCount < MAX_RETRY_COUNT;\n return (\n <div className=\"flex flex-col items-center justify-center min-h-[400px] gap-4 text-center p-6\">\n <div className=\"p-3 rounded-full bg-status-error-bg/10\">\n <AlertTriangle className=\"size-8 text-status-error-text\" />\n </div>\n <div className=\"space-y-2\">\n <h3 className=\"text-lg font-semibold text-text-primary\">Failed to load page</h3>\n <p className=\"text-sm text-text-secondary max-w-md\">\n {canRetry\n ? 'The page chunk could not be loaded. This may be due to a network issue.'\n : 'The page chunk could not be loaded after multiple attempts. Please refresh the page.'}\n </p>\n </div>\n {canRetry ? (\n <button\n type=\"button\"\n onClick={this.handleRetry}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90 transition-colors\"\n >\n <RefreshCw className=\"size-4\" />\n Try Again\n </button>\n ) : (\n <button\n type=\"button\"\n onClick={() => window.location.reload()}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90 transition-colors\"\n >\n <RefreshCw className=\"size-4\" />\n Refresh Page\n </button>\n )}\n </div>\n );\n }\n\n return <Fragment key={this.state.retryCount}>{this.props.children}</Fragment>;\n }\n}\n"],"mappings":";;;;AAOA,SAAgB,IAAa;AAC3B,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAS,WAAU,oCAAqC,CAAA;EACpD,CAAA;;AAKV,IAAM,IAAkB,GASX,IAAb,cAAuC,EAGrC;CACA,YAAY,GAAgC;AAE1C,EADA,MAAM,EAAM,EACZ,KAAK,QAAQ;GAAE,UAAU;GAAO,OAAO;GAAM,YAAY;GAAG;;CAG9D,OAAO,yBAAyB,GAAc;AAC5C,SAAO;GAAE,UAAU;GAAM;GAAO;;CAGlC,kBAAkB,GAAc,GAAsB;AAKpD,UAAQ,MAAM,kCAAkC,GAAO,EAAU;;CAGnE,oBAAoB;AAClB,OAAK,UAAU,OAAU;GACvB,UAAU;GACV,OAAO;GACP,YAAY,EAAK,aAAa;GAC/B,EAAE;;CAGL,SAAS;AACP,MAAI,KAAK,MAAM,UAAU;GACvB,IAAM,IAAW,KAAK,MAAM,aAAa;AACzC,UACE,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD,EAAe,WAAU,iCAAkC,CAAA;MACvD,CAAA;KACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,MAAD;OAAI,WAAU;iBAA0C;OAAwB,CAAA,EAChF,kBAAC,KAAD;OAAG,WAAU;iBACV,IACG,4EACA;OACF,CAAA,CACA;;KACL,IACC,kBAAC,UAAD;MACE,MAAK;MACL,SAAS,KAAK;MACd,WAAU;gBAHZ,CAKE,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA,EAAA,YAEzB;UAET,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,OAAO,SAAS,QAAQ;MACvC,WAAU;gBAHZ,CAKE,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA,EAAA,eAEzB;;KAEP;;;AAIV,SAAO,kBAAC,GAAD,EAAA,UAAuC,KAAK,MAAM,UAAoB,EAAvD,KAAK,MAAM,WAA4C"}
1
+ {"version":3,"file":"RouteHelpers.js","names":[],"sources":["../../../src/tours/components/RouteHelpers.tsx"],"sourcesContent":["import { Component, Fragment, type ReactNode, type ErrorInfo } from 'react';\nimport { Loader2, AlertTriangle, RefreshCw } from 'lucide-react';\nimport { useTr } from '../i18n';\n\n/**\n * Loading fallback for lazy-loaded pages.\n * Shared between ToursAdminRoutes and ToursUserRoutes (Section D/E of BOFF-2391).\n */\nexport function PageLoader() {\n return (\n <div className=\"flex items-center justify-center min-h-[200px]\">\n <Loader2 className=\"size-8 animate-spin text-primary\" />\n </div>\n );\n}\n\n/** Max consecutive retries before the error screen suggests a full reload. */\nconst MAX_RETRY_COUNT = 3;\n\ninterface LazyErrorScreenProps {\n retryCount: number;\n onRetry: () => void;\n}\n\nfunction LazyErrorScreen({ retryCount, onRetry }: LazyErrorScreenProps) {\n const tr = useTr();\n const canRetry = retryCount < MAX_RETRY_COUNT;\n\n return (\n <div className=\"flex flex-col items-center justify-center min-h-[400px] gap-4 text-center p-6\">\n <div className=\"p-3 rounded-full bg-status-error-bg/10\">\n <AlertTriangle className=\"size-8 text-status-error-text\" />\n </div>\n <div className=\"space-y-2\">\n <h3 className=\"text-lg font-semibold text-text-primary\">\n {tr('routeErrors.failedToLoadPage', 'Failed to load page')}\n </h3>\n <p className=\"text-sm text-text-secondary max-w-md\">\n {canRetry\n ? tr(\n 'routeErrors.chunkLoadRetry',\n 'The page chunk could not be loaded. This may be due to a network issue.'\n )\n : tr(\n 'routeErrors.chunkLoadRefresh',\n 'The page chunk could not be loaded after multiple attempts. Please refresh the page.'\n )}\n </p>\n </div>\n {canRetry ? (\n <button\n type=\"button\"\n onClick={onRetry}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90 transition-colors\"\n >\n <RefreshCw className=\"size-4\" />\n {tr('routeErrors.tryAgain', 'Try Again')}\n </button>\n ) : (\n <button\n type=\"button\"\n onClick={() => window.location.reload()}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90 transition-colors\"\n >\n <RefreshCw className=\"size-4\" />\n {tr('routeErrors.refreshPage', 'Refresh Page')}\n </button>\n )}\n </div>\n );\n}\n\n/**\n * Error boundary for lazy-loaded page chunks.\n *\n * React.lazy() caches rejected promises, so resetting error state alone would\n * re-throw the same cached rejection. The retryCount key forces a full remount\n * of the subtree on retry, creating fresh lazy-component instances.\n */\nexport class LazyErrorBoundary extends Component<\n { children: ReactNode },\n { hasError: boolean; error: Error | null; retryCount: number }\n> {\n constructor(props: { children: ReactNode }) {\n super(props);\n this.state = { hasError: false, error: null, retryCount: 0 };\n }\n\n static getDerivedStateFromError(error: Error) {\n return { hasError: true, error };\n }\n\n componentDidCatch(error: Error, errorInfo: ErrorInfo) {\n // TODO: forward to error monitoring service (Sentry, etc.)\n // Log the full error server-side / in the console; do NOT surface the raw\n // message to users since chunk-load errors can include CDN paths, build\n // hashes, or deployment details that shouldn't appear in the UI.\n console.error('[Tours] Lazy page load failed:', error, errorInfo);\n }\n\n handleRetry = () => {\n this.setState((prev) => ({\n hasError: false,\n error: null,\n retryCount: prev.retryCount + 1,\n }));\n };\n\n render() {\n if (this.state.hasError) {\n return <LazyErrorScreen retryCount={this.state.retryCount} onRetry={this.handleRetry} />;\n }\n\n return <Fragment key={this.state.retryCount}>{this.props.children}</Fragment>;\n }\n}\n"],"mappings":";;;;;AAQA,SAAgB,IAAa;AAC3B,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAS,WAAU,oCAAqC,CAAA;EACpD,CAAA;;AAKV,IAAM,IAAkB;AAOxB,SAAS,EAAgB,EAAE,eAAY,cAAiC;CACtE,IAAM,IAAK,GAAO,EACZ,IAAW,IAAa;AAE9B,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD,EAAe,WAAU,iCAAkC,CAAA;IACvD,CAAA;GACN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,gCAAgC,sBAAsB;KACvD,CAAA,EACL,kBAAC,KAAD;KAAG,WAAU;eACV,IACG,EACE,8BACA,0EACD,GACD,EACE,gCACA,uFACD;KACH,CAAA,CACA;;GACL,IACC,kBAAC,UAAD;IACE,MAAK;IACL,SAAS;IACT,WAAU;cAHZ,CAKE,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA,EAC/B,EAAG,wBAAwB,YAAY,CACjC;QAET,kBAAC,UAAD;IACE,MAAK;IACL,eAAe,OAAO,SAAS,QAAQ;IACvC,WAAU;cAHZ,CAKE,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA,EAC/B,EAAG,2BAA2B,eAAe,CACvC;;GAEP;;;AAWV,IAAa,IAAb,cAAuC,EAGrC;CACA,YAAY,GAAgC;AAE1C,EADA,MAAM,EAAM,EACZ,KAAK,QAAQ;GAAE,UAAU;GAAO,OAAO;GAAM,YAAY;GAAG;;CAG9D,OAAO,yBAAyB,GAAc;AAC5C,SAAO;GAAE,UAAU;GAAM;GAAO;;CAGlC,kBAAkB,GAAc,GAAsB;AAKpD,UAAQ,MAAM,kCAAkC,GAAO,EAAU;;CAGnE,oBAAoB;AAClB,OAAK,UAAU,OAAU;GACvB,UAAU;GACV,OAAO;GACP,YAAY,EAAK,aAAa;GAC/B,EAAE;;CAGL,SAAS;AAKP,SAJI,KAAK,MAAM,WACN,kBAAC,GAAD;GAAiB,YAAY,KAAK,MAAM;GAAY,SAAS,KAAK;GAAe,CAAA,GAGnF,kBAAC,GAAD,EAAA,UAAuC,KAAK,MAAM,UAAoB,EAAvD,KAAK,MAAM,WAA4C"}
@@ -1,60 +1,65 @@
1
1
  import { TOURS_FEATURES as e } from "../../constants/featureFlags.js";
2
2
  import { joinPath as t } from "../utils/joinPath.js";
3
- import { useEffect as n, useMemo as r, useRef as i } from "react";
4
- import { useFeatureFlag as a } from "@burdenoff/fe-libs/shared/feature-flags";
5
- import { BarChart3 as o, Image as s, LayoutTemplate as c, List as l, Plus as u } from "lucide-react";
6
- import { jsx as d } from "react/jsx-runtime";
3
+ import { useTr as n } from "../i18n.js";
4
+ import { useEffect as r, useMemo as i, useRef as a } from "react";
5
+ import { useFeatureFlag as o } from "@burdenoff/fe-libs/shared/feature-flags";
6
+ import { BarChart3 as s, Image as c, LayoutTemplate as l, List as u, Plus as d } from "lucide-react";
7
+ import { jsx as f } from "react/jsx-runtime";
7
8
  //#region src/tours/components/ToursAdminNavRegistrar.tsx
8
- function f({ basePath: f, registerNavItems: p }) {
9
- let m = a(e.MEDIA, !1), h = i(p);
10
- h.current = p;
11
- let g = r(() => {
9
+ function p({ basePath: p, registerNavItems: m }) {
10
+ let h = n(), g = o(e.MEDIA, !1), _ = a(m);
11
+ _.current = m;
12
+ let v = i(() => {
12
13
  let e = [
13
14
  {
14
15
  id: "tours-list",
15
- label: "All Tours",
16
- path: t(f, "list"),
17
- icon: /* @__PURE__ */ d(l, { size: 20 }),
16
+ label: h("toursNav.allTours", "All Tours"),
17
+ path: t(p, "list"),
18
+ icon: /* @__PURE__ */ f(u, { size: 20 }),
18
19
  order: 10,
19
20
  group: "tours"
20
21
  },
21
22
  {
22
23
  id: "tours-new",
23
- label: "Create Tour",
24
- path: t(f, "new"),
25
- icon: /* @__PURE__ */ d(u, { size: 20 }),
24
+ label: h("toursNav.createTour", "Create Tour"),
25
+ path: t(p, "new"),
26
+ icon: /* @__PURE__ */ f(d, { size: 20 }),
26
27
  order: 20,
27
28
  group: "tours"
28
29
  },
29
30
  {
30
31
  id: "tours-templates",
31
- label: "Templates",
32
- path: t(f, "templates"),
33
- icon: /* @__PURE__ */ d(c, { size: 20 }),
32
+ label: h("toursNav.templates", "Templates"),
33
+ path: t(p, "templates"),
34
+ icon: /* @__PURE__ */ f(l, { size: 20 }),
34
35
  order: 30,
35
36
  group: "tours"
36
37
  },
37
38
  {
38
39
  id: "tours-analytics",
39
- label: "Analytics",
40
- path: t(f, "analytics"),
41
- icon: /* @__PURE__ */ d(o, { size: 20 }),
40
+ label: h("toursNav.analytics", "Analytics"),
41
+ path: t(p, "analytics"),
42
+ icon: /* @__PURE__ */ f(s, { size: 20 }),
42
43
  order: 50,
43
44
  group: "tours"
44
45
  }
45
46
  ];
46
- return m && e.push({
47
+ return g && e.push({
47
48
  id: "tours-media",
48
- label: "Media Library",
49
- path: t(f, "media"),
50
- icon: /* @__PURE__ */ d(s, { size: 20 }),
49
+ label: h("toursNav.mediaLibrary", "Media Library"),
50
+ path: t(p, "media"),
51
+ icon: /* @__PURE__ */ f(c, { size: 20 }),
51
52
  order: 40,
52
53
  group: "tours"
53
54
  }), e;
54
- }, [f, m]);
55
- return n(() => h.current(g), [g]), null;
55
+ }, [
56
+ p,
57
+ g,
58
+ h
59
+ ]);
60
+ return r(() => _.current(v), [v]), null;
56
61
  }
57
62
  //#endregion
58
- export { f as ToursAdminNavRegistrar };
63
+ export { p as ToursAdminNavRegistrar };
59
64
 
60
65
  //# sourceMappingURL=ToursAdminNavRegistrar.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ToursAdminNavRegistrar.js","names":[],"sources":["../../../src/tours/components/ToursAdminNavRegistrar.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef } from 'react';\nimport { useFeatureFlag } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { List, Plus, LayoutTemplate, Image, BarChart3 } from 'lucide-react';\n\nimport { TOURS_FEATURES } from '../../constants/featureFlags';\nimport type { NavItem } from '../types';\nimport { joinPath } from '../utils/joinPath';\n\ninterface ToursAdminNavRegistrarProps {\n basePath: string;\n registerNavItems: (items: NavItem | NavItem[]) => () => void;\n}\n\n/**\n * Registers the admin tours nav items with the host shell.\n *\n * Rendered inside `<FeatureFlagProvider>` so it can consult `useFeatureFlag`\n * to conditionally include flag-gated items. Re-registers whenever flag state\n * changes so the host shell's nav reflects the current evaluation in real time.\n *\n * Baseline items (always registered): All Tours, Create Tour, Templates,\n * Analytics. Media Library is gated on `tours.media` because the upload\n * pipeline is still incomplete — the nav item appears only when the flag is\n * enabled for the workspace or the user has opted in via Feature Previews.\n *\n * `registerNavItems` is captured in a ref so a host shell that hands over a\n * fresh closure on every render can't trigger an unregister → re-register\n * storm. Only flag-state / `basePath` changes cause re-registration.\n */\nexport function ToursAdminNavRegistrar({\n basePath,\n registerNavItems,\n}: ToursAdminNavRegistrarProps) {\n const mediaEnabled = useFeatureFlag(TOURS_FEATURES.MEDIA, false);\n\n const registerNavItemsRef = useRef(registerNavItems);\n registerNavItemsRef.current = registerNavItems;\n\n const navItems = useMemo<NavItem[]>(() => {\n const items: NavItem[] = [\n {\n id: 'tours-list',\n label: 'All Tours',\n path: joinPath(basePath, 'list'),\n icon: <List size={20} />,\n order: 10,\n group: 'tours',\n },\n {\n id: 'tours-new',\n label: 'Create Tour',\n path: joinPath(basePath, 'new'),\n icon: <Plus size={20} />,\n order: 20,\n group: 'tours',\n },\n {\n id: 'tours-templates',\n label: 'Templates',\n path: joinPath(basePath, 'templates'),\n icon: <LayoutTemplate size={20} />,\n order: 30,\n group: 'tours',\n },\n {\n id: 'tours-analytics',\n label: 'Analytics',\n path: joinPath(basePath, 'analytics'),\n icon: <BarChart3 size={20} />,\n order: 50,\n group: 'tours',\n },\n ];\n\n if (mediaEnabled) {\n items.push({\n id: 'tours-media',\n label: 'Media Library',\n path: joinPath(basePath, 'media'),\n icon: <Image size={20} />,\n order: 40,\n group: 'tours',\n });\n }\n\n return items;\n }, [basePath, mediaEnabled]);\n\n useEffect(() => {\n const unregister = registerNavItemsRef.current(navItems);\n return unregister;\n }, [navItems]);\n\n return null;\n}\n"],"mappings":";;;;;;;AA6BA,SAAgB,EAAuB,EACrC,aACA,uBAC8B;CAC9B,IAAM,IAAe,EAAe,EAAe,OAAO,GAAM,EAE1D,IAAsB,EAAO,EAAiB;AACpD,GAAoB,UAAU;CAE9B,IAAM,IAAW,QAAyB;EACxC,IAAM,IAAmB;GACvB;IACE,IAAI;IACJ,OAAO;IACP,MAAM,EAAS,GAAU,OAAO;IAChC,MAAM,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA;IACxB,OAAO;IACP,OAAO;IACR;GACD;IACE,IAAI;IACJ,OAAO;IACP,MAAM,EAAS,GAAU,MAAM;IAC/B,MAAM,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA;IACxB,OAAO;IACP,OAAO;IACR;GACD;IACE,IAAI;IACJ,OAAO;IACP,MAAM,EAAS,GAAU,YAAY;IACrC,MAAM,kBAAC,GAAD,EAAgB,MAAM,IAAM,CAAA;IAClC,OAAO;IACP,OAAO;IACR;GACD;IACE,IAAI;IACJ,OAAO;IACP,MAAM,EAAS,GAAU,YAAY;IACrC,MAAM,kBAAC,GAAD,EAAW,MAAM,IAAM,CAAA;IAC7B,OAAO;IACP,OAAO;IACR;GACF;AAaD,SAXI,KACF,EAAM,KAAK;GACT,IAAI;GACJ,OAAO;GACP,MAAM,EAAS,GAAU,QAAQ;GACjC,MAAM,kBAAC,GAAD,EAAO,MAAM,IAAM,CAAA;GACzB,OAAO;GACP,OAAO;GACR,CAAC,EAGG;IACN,CAAC,GAAU,EAAa,CAAC;AAO5B,QALA,QACqB,EAAoB,QAAQ,EAAS,EAEvD,CAAC,EAAS,CAAC,EAEP"}
1
+ {"version":3,"file":"ToursAdminNavRegistrar.js","names":[],"sources":["../../../src/tours/components/ToursAdminNavRegistrar.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef } from 'react';\nimport { useFeatureFlag } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { List, Plus, LayoutTemplate, Image, BarChart3 } from 'lucide-react';\n\nimport { TOURS_FEATURES } from '../../constants/featureFlags';\nimport type { NavItem } from '../types';\nimport { joinPath } from '../utils/joinPath';\nimport { useTr } from '../i18n';\n\ninterface ToursAdminNavRegistrarProps {\n basePath: string;\n registerNavItems: (items: NavItem | NavItem[]) => () => void;\n}\n\n/**\n * Registers the admin tours nav items with the host shell.\n *\n * Rendered inside `<FeatureFlagProvider>` so it can consult `useFeatureFlag`\n * to conditionally include flag-gated items. Re-registers whenever flag state\n * changes so the host shell's nav reflects the current evaluation in real time.\n *\n * Baseline items (always registered): All Tours, Create Tour, Templates,\n * Analytics. Media Library is gated on `tours.media` because the upload\n * pipeline is still incomplete — the nav item appears only when the flag is\n * enabled for the workspace or the user has opted in via Feature Previews.\n *\n * `registerNavItems` is captured in a ref so a host shell that hands over a\n * fresh closure on every render can't trigger an unregister → re-register\n * storm. Only flag-state / `basePath` changes cause re-registration.\n */\nexport function ToursAdminNavRegistrar({\n basePath,\n registerNavItems,\n}: ToursAdminNavRegistrarProps) {\n const tr = useTr();\n const mediaEnabled = useFeatureFlag(TOURS_FEATURES.MEDIA, false);\n\n const registerNavItemsRef = useRef(registerNavItems);\n registerNavItemsRef.current = registerNavItems;\n\n const navItems = useMemo<NavItem[]>(() => {\n const items: NavItem[] = [\n {\n id: 'tours-list',\n label: tr('toursNav.allTours', 'All Tours'),\n path: joinPath(basePath, 'list'),\n icon: <List size={20} />,\n order: 10,\n group: 'tours',\n },\n {\n id: 'tours-new',\n label: tr('toursNav.createTour', 'Create Tour'),\n path: joinPath(basePath, 'new'),\n icon: <Plus size={20} />,\n order: 20,\n group: 'tours',\n },\n {\n id: 'tours-templates',\n label: tr('toursNav.templates', 'Templates'),\n path: joinPath(basePath, 'templates'),\n icon: <LayoutTemplate size={20} />,\n order: 30,\n group: 'tours',\n },\n {\n id: 'tours-analytics',\n label: tr('toursNav.analytics', 'Analytics'),\n path: joinPath(basePath, 'analytics'),\n icon: <BarChart3 size={20} />,\n order: 50,\n group: 'tours',\n },\n ];\n\n if (mediaEnabled) {\n items.push({\n id: 'tours-media',\n label: tr('toursNav.mediaLibrary', 'Media Library'),\n path: joinPath(basePath, 'media'),\n icon: <Image size={20} />,\n order: 40,\n group: 'tours',\n });\n }\n\n return items;\n }, [basePath, mediaEnabled, tr]);\n\n useEffect(() => {\n const unregister = registerNavItemsRef.current(navItems);\n return unregister;\n }, [navItems]);\n\n return null;\n}\n"],"mappings":";;;;;;;;AA8BA,SAAgB,EAAuB,EACrC,aACA,uBAC8B;CAC9B,IAAM,IAAK,GAAO,EACZ,IAAe,EAAe,EAAe,OAAO,GAAM,EAE1D,IAAsB,EAAO,EAAiB;AACpD,GAAoB,UAAU;CAE9B,IAAM,IAAW,QAAyB;EACxC,IAAM,IAAmB;GACvB;IACE,IAAI;IACJ,OAAO,EAAG,qBAAqB,YAAY;IAC3C,MAAM,EAAS,GAAU,OAAO;IAChC,MAAM,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA;IACxB,OAAO;IACP,OAAO;IACR;GACD;IACE,IAAI;IACJ,OAAO,EAAG,uBAAuB,cAAc;IAC/C,MAAM,EAAS,GAAU,MAAM;IAC/B,MAAM,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA;IACxB,OAAO;IACP,OAAO;IACR;GACD;IACE,IAAI;IACJ,OAAO,EAAG,sBAAsB,YAAY;IAC5C,MAAM,EAAS,GAAU,YAAY;IACrC,MAAM,kBAAC,GAAD,EAAgB,MAAM,IAAM,CAAA;IAClC,OAAO;IACP,OAAO;IACR;GACD;IACE,IAAI;IACJ,OAAO,EAAG,sBAAsB,YAAY;IAC5C,MAAM,EAAS,GAAU,YAAY;IACrC,MAAM,kBAAC,GAAD,EAAW,MAAM,IAAM,CAAA;IAC7B,OAAO;IACP,OAAO;IACR;GACF;AAaD,SAXI,KACF,EAAM,KAAK;GACT,IAAI;GACJ,OAAO,EAAG,yBAAyB,gBAAgB;GACnD,MAAM,EAAS,GAAU,QAAQ;GACjC,MAAM,kBAAC,GAAD,EAAO,MAAM,IAAM,CAAA;GACzB,OAAO;GACP,OAAO;GACR,CAAC,EAGG;IACN;EAAC;EAAU;EAAc;EAAG,CAAC;AAOhC,QALA,QACqB,EAAoB,QAAQ,EAAS,EAEvD,CAAC,EAAS,CAAC,EAEP"}