@burdenoff/microfe-mybodyshield 2026.821.2 → 2026.830.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 (90) hide show
  1. package/dist/mybodyshield/MyBodyShieldRoot.d.ts.map +1 -1
  2. package/dist/mybodyshield/MyBodyShieldRoot.js +54 -56
  3. package/dist/mybodyshield/MyBodyShieldRoot.js.map +1 -1
  4. package/dist/mybodyshield/MyBodyShieldRoutes.d.ts.map +1 -1
  5. package/dist/mybodyshield/MyBodyShieldRoutes.js +121 -62
  6. package/dist/mybodyshield/MyBodyShieldRoutes.js.map +1 -1
  7. package/dist/mybodyshield/components/EvidenceUpload.d.ts.map +1 -1
  8. package/dist/mybodyshield/components/EvidenceUpload.js +46 -45
  9. package/dist/mybodyshield/components/EvidenceUpload.js.map +1 -1
  10. package/dist/mybodyshield/components/FileLink.d.ts.map +1 -1
  11. package/dist/mybodyshield/components/FileLink.js +27 -18
  12. package/dist/mybodyshield/components/FileLink.js.map +1 -1
  13. package/dist/mybodyshield/components/MybDiscussion.d.ts +1 -1
  14. package/dist/mybodyshield/components/MybDiscussion.d.ts.map +1 -1
  15. package/dist/mybodyshield/components/MybDiscussion.js +59 -58
  16. package/dist/mybodyshield/components/MybDiscussion.js.map +1 -1
  17. package/dist/mybodyshield/components/MybTags.d.ts +1 -1
  18. package/dist/mybodyshield/components/MybTags.d.ts.map +1 -1
  19. package/dist/mybodyshield/components/MybTags.js +83 -74
  20. package/dist/mybodyshield/components/MybTags.js.map +1 -1
  21. package/dist/mybodyshield/components/PageDisabled.d.ts.map +1 -1
  22. package/dist/mybodyshield/components/PageDisabled.js +16 -11
  23. package/dist/mybodyshield/components/PageDisabled.js.map +1 -1
  24. package/dist/mybodyshield/components/Pagination.d.ts.map +1 -1
  25. package/dist/mybodyshield/components/Pagination.js +36 -27
  26. package/dist/mybodyshield/components/Pagination.js.map +1 -1
  27. package/dist/mybodyshield/components/StateViews.d.ts.map +1 -1
  28. package/dist/mybodyshield/components/StateViews.js +42 -34
  29. package/dist/mybodyshield/components/StateViews.js.map +1 -1
  30. package/dist/mybodyshield/components/UserSearchSelect.d.ts.map +1 -1
  31. package/dist/mybodyshield/components/UserSearchSelect.js +66 -68
  32. package/dist/mybodyshield/components/UserSearchSelect.js.map +1 -1
  33. package/dist/mybodyshield/hooks/index.d.ts +1 -0
  34. package/dist/mybodyshield/hooks/index.d.ts.map +1 -1
  35. package/dist/mybodyshield/hooks/index.js +1 -0
  36. package/dist/mybodyshield/hooks/useTr.d.ts +5 -0
  37. package/dist/mybodyshield/hooks/useTr.d.ts.map +1 -0
  38. package/dist/mybodyshield/hooks/useTr.js +12 -0
  39. package/dist/mybodyshield/hooks/useTr.js.map +1 -0
  40. package/dist/mybodyshield/pages/DashboardPage.d.ts.map +1 -1
  41. package/dist/mybodyshield/pages/DashboardPage.js +157 -136
  42. package/dist/mybodyshield/pages/DashboardPage.js.map +1 -1
  43. package/dist/mybodyshield/pages/SettingsPage.d.ts.map +1 -1
  44. package/dist/mybodyshield/pages/SettingsPage.js +50 -59
  45. package/dist/mybodyshield/pages/SettingsPage.js.map +1 -1
  46. package/dist/mybodyshield/pages/_shared/visuals.d.ts +2 -1
  47. package/dist/mybodyshield/pages/_shared/visuals.d.ts.map +1 -1
  48. package/dist/mybodyshield/pages/_shared/visuals.js +91 -81
  49. package/dist/mybodyshield/pages/_shared/visuals.js.map +1 -1
  50. package/dist/mybodyshield/pages/ai/AiControlsPage.d.ts.map +1 -1
  51. package/dist/mybodyshield/pages/ai/AiControlsPage.js +279 -231
  52. package/dist/mybodyshield/pages/ai/AiControlsPage.js.map +1 -1
  53. package/dist/mybodyshield/pages/coaching/CoachingPage.d.ts.map +1 -1
  54. package/dist/mybodyshield/pages/coaching/CoachingPage.js +349 -258
  55. package/dist/mybodyshield/pages/coaching/CoachingPage.js.map +1 -1
  56. package/dist/mybodyshield/pages/goals/GoalsPage.d.ts.map +1 -1
  57. package/dist/mybodyshield/pages/goals/GoalsPage.js +247 -251
  58. package/dist/mybodyshield/pages/goals/GoalsPage.js.map +1 -1
  59. package/dist/mybodyshield/pages/insights/InsightsCharts.d.ts.map +1 -1
  60. package/dist/mybodyshield/pages/insights/InsightsCharts.js +216 -213
  61. package/dist/mybodyshield/pages/insights/InsightsCharts.js.map +1 -1
  62. package/dist/mybodyshield/pages/insights/InsightsPage.d.ts.map +1 -1
  63. package/dist/mybodyshield/pages/insights/InsightsPage.js +327 -220
  64. package/dist/mybodyshield/pages/insights/InsightsPage.js.map +1 -1
  65. package/dist/mybodyshield/pages/nutrition/NutritionPage.d.ts.map +1 -1
  66. package/dist/mybodyshield/pages/nutrition/NutritionPage.js +575 -529
  67. package/dist/mybodyshield/pages/nutrition/NutritionPage.js.map +1 -1
  68. package/dist/mybodyshield/pages/programs/ProgramDetailPage.d.ts.map +1 -1
  69. package/dist/mybodyshield/pages/programs/ProgramDetailPage.js +333 -275
  70. package/dist/mybodyshield/pages/programs/ProgramDetailPage.js.map +1 -1
  71. package/dist/mybodyshield/pages/programs/ProgramsPage.d.ts.map +1 -1
  72. package/dist/mybodyshield/pages/programs/ProgramsPage.js +258 -253
  73. package/dist/mybodyshield/pages/programs/ProgramsPage.js.map +1 -1
  74. package/dist/mybodyshield/pages/progress/ProgressPage.d.ts.map +1 -1
  75. package/dist/mybodyshield/pages/progress/ProgressPage.js +271 -254
  76. package/dist/mybodyshield/pages/progress/ProgressPage.js.map +1 -1
  77. package/dist/mybodyshield/pages/recovery/RecoveryPage.d.ts.map +1 -1
  78. package/dist/mybodyshield/pages/recovery/RecoveryPage.js +206 -200
  79. package/dist/mybodyshield/pages/recovery/RecoveryPage.js.map +1 -1
  80. package/dist/mybodyshield/pages/train/SessionDetailPage.d.ts.map +1 -1
  81. package/dist/mybodyshield/pages/train/SessionDetailPage.js +327 -294
  82. package/dist/mybodyshield/pages/train/SessionDetailPage.js.map +1 -1
  83. package/dist/mybodyshield/pages/train/SessionsPage.d.ts.map +1 -1
  84. package/dist/mybodyshield/pages/train/SessionsPage.js +180 -163
  85. package/dist/mybodyshield/pages/train/SessionsPage.js.map +1 -1
  86. package/dist/mybodyshield/utils/i18n.d.ts +1 -2
  87. package/dist/mybodyshield/utils/i18n.d.ts.map +1 -1
  88. package/dist/mybodyshield/utils/i18n.js +15 -0
  89. package/dist/mybodyshield/utils/i18n.js.map +1 -0
  90. package/package.json +1 -1
@@ -1,148 +1,146 @@
1
1
  import { directoryUserName as e, useMybUserSearch as t } from "../hooks/useMybUsers.js";
2
- import { useState as n } from "react";
3
- import { Check as r, Loader2 as i, Search as a, User as o, X as s } from "lucide-react";
4
- import { jsx as c, jsxs as l } from "react/jsx-runtime";
5
- import { Input as u, Label as d } from "@burdenoff/fe-libs/ui";
2
+ import { useTr as n } from "../hooks/useTr.js";
3
+ import "../hooks/index.js";
4
+ import { useState as r } from "react";
5
+ import { Check as i, Loader2 as a, Search as o, User as s, X as c } from "lucide-react";
6
+ import { jsx as l, jsxs as u } from "react/jsx-runtime";
7
+ import { Input as d, Label as f } from "@burdenoff/fe-libs/ui";
6
8
  //#region src/mybodyshield/components/UserSearchSelect.tsx
7
- function f({ label: f, userId: p, displayName: m, onSelect: h, required: g = !1, htmlId: _ = "myb-user-id" }) {
8
- let [v, y] = n(""), [b, x] = n(!1), S = t(v), C = S.data ?? [], w = v.trim().length >= 2, T = S.isError, E = (t) => {
9
- h({
9
+ function p({ label: p, userId: m, displayName: h, onSelect: g, required: _ = !1, htmlId: v = "myb-user-id" }) {
10
+ let y = n(), [b, x] = r(""), [S, C] = r(!1), w = t(b), T = w.data ?? [], E = b.trim().length >= 2, D = w.isError, O = (t) => {
11
+ g({
10
12
  userId: t.id,
11
13
  displayName: e(t)
12
- }), y("");
14
+ }), x("");
13
15
  };
14
- return b || T ? /* @__PURE__ */ l("div", {
16
+ return S || D ? /* @__PURE__ */ u("div", {
15
17
  className: "grid gap-1.5",
16
18
  children: [
17
- /* @__PURE__ */ l(d, {
18
- htmlFor: _,
19
+ /* @__PURE__ */ u(f, {
20
+ htmlFor: v,
19
21
  children: [
20
- f,
22
+ p,
21
23
  " ",
22
- g && /* @__PURE__ */ c("span", {
24
+ _ && /* @__PURE__ */ l("span", {
23
25
  className: "text-status-error-text",
24
26
  children: "*"
25
27
  })
26
28
  ]
27
29
  }),
28
- /* @__PURE__ */ c(u, {
29
- id: _,
30
- value: p,
31
- onChange: (e) => h({
30
+ /* @__PURE__ */ l(d, {
31
+ id: v,
32
+ value: m,
33
+ onChange: (e) => g({
32
34
  userId: e.target.value,
33
- displayName: m
35
+ displayName: h
34
36
  }),
35
- placeholder: "user-uuid"
37
+ placeholder: y("mybodyshield.components.userSearch.manualPlaceholder", "user-uuid")
36
38
  }),
37
- /* @__PURE__ */ c("p", {
39
+ /* @__PURE__ */ l("p", {
38
40
  className: "text-xs text-text-tertiary",
39
- children: T ? "User search is unavailable here — paste the user id." : "Paste the user id."
41
+ children: D ? y("mybodyshield.components.userSearch.searchUnavailable", "User search is unavailable here — paste the user id.") : y("mybodyshield.components.userSearch.pasteHint", "Paste the user id.")
40
42
  }),
41
- !T && /* @__PURE__ */ c("button", {
43
+ !D && /* @__PURE__ */ l("button", {
42
44
  type: "button",
43
- onClick: () => x(!1),
45
+ onClick: () => C(!1),
44
46
  className: "self-start text-xs font-medium text-accent hover:underline",
45
- children: "Search the directory instead"
47
+ children: y("mybodyshield.components.userSearch.searchDirectory", "Search the directory instead")
46
48
  })
47
49
  ]
48
- }) : /* @__PURE__ */ l("div", {
50
+ }) : /* @__PURE__ */ u("div", {
49
51
  className: "grid gap-1.5",
50
- children: [/* @__PURE__ */ l(d, { children: [
51
- f,
52
+ children: [/* @__PURE__ */ u(f, { children: [
53
+ p,
52
54
  " ",
53
- g && /* @__PURE__ */ c("span", {
55
+ _ && /* @__PURE__ */ l("span", {
54
56
  className: "text-status-error-text",
55
57
  children: "*"
56
58
  })
57
- ] }), p ? /* @__PURE__ */ l("div", {
59
+ ] }), m ? /* @__PURE__ */ u("div", {
58
60
  className: "flex items-center gap-2 rounded-lg border border-border-seam bg-bg-surface px-3 py-2",
59
61
  children: [
60
- /* @__PURE__ */ c("span", {
62
+ /* @__PURE__ */ l("span", {
61
63
  className: "flex size-7 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-text-secondary",
62
- children: /* @__PURE__ */ c(o, { className: "h-4 w-4" })
64
+ children: /* @__PURE__ */ l(s, { className: "h-4 w-4" })
63
65
  }),
64
- /* @__PURE__ */ l("div", {
66
+ /* @__PURE__ */ u("div", {
65
67
  className: "min-w-0 flex-1",
66
- children: [/* @__PURE__ */ c("p", {
68
+ children: [/* @__PURE__ */ l("p", {
67
69
  className: "truncate text-sm font-medium text-text-primary",
68
- children: m || "Selected user"
69
- }), /* @__PURE__ */ c("p", {
70
+ children: h || y("mybodyshield.components.userSearch.selectedUser", "Selected user")
71
+ }), /* @__PURE__ */ l("p", {
70
72
  className: "truncate text-xs text-text-tertiary",
71
- children: p
73
+ children: m
72
74
  })]
73
75
  }),
74
- /* @__PURE__ */ c("button", {
76
+ /* @__PURE__ */ l("button", {
75
77
  type: "button",
76
- "aria-label": `Clear selected ${f.toLowerCase()}`,
77
- onClick: () => h({
78
+ "aria-label": `Clear selected ${p.toLowerCase()}`,
79
+ onClick: () => g({
78
80
  userId: "",
79
81
  displayName: ""
80
82
  }),
81
83
  className: "rounded-md p-1 text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary",
82
- children: /* @__PURE__ */ c(s, { className: "h-4 w-4" })
84
+ children: /* @__PURE__ */ l(c, { className: "h-4 w-4" })
83
85
  })
84
86
  ]
85
- }) : /* @__PURE__ */ l("div", {
87
+ }) : /* @__PURE__ */ u("div", {
86
88
  className: "space-y-2",
87
89
  children: [
88
- /* @__PURE__ */ l("div", {
90
+ /* @__PURE__ */ u("div", {
89
91
  className: "relative",
90
92
  children: [
91
- /* @__PURE__ */ c(a, { className: "pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-tertiary" }),
92
- /* @__PURE__ */ c(u, {
93
- value: v,
94
- onChange: (e) => y(e.target.value),
95
- placeholder: "Search by name or email…",
96
- "aria-label": `Search ${f.toLowerCase()} by name or email`,
93
+ /* @__PURE__ */ l(o, { className: "pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-tertiary" }),
94
+ /* @__PURE__ */ l(d, {
95
+ value: b,
96
+ onChange: (e) => x(e.target.value),
97
+ placeholder: y("mybodyshield.components.userSearch.searchPlaceholder", "Search by name or email…"),
98
+ "aria-label": y("mybodyshield.components.userSearch.searchAria", "Search {{label}} by name or email", { label: p.toLowerCase() }),
97
99
  className: "pl-9"
98
100
  }),
99
- S.isFetching && /* @__PURE__ */ c(i, { className: "absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 animate-spin text-text-tertiary" })
101
+ w.isFetching && /* @__PURE__ */ l(a, { className: "absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 animate-spin text-text-tertiary" })
100
102
  ]
101
103
  }),
102
- w && !S.isFetching && C.length === 0 && /* @__PURE__ */ l("p", {
104
+ E && !w.isFetching && T.length === 0 && /* @__PURE__ */ l("p", {
103
105
  className: "text-xs text-text-tertiary",
104
- children: [
105
- "No users match “",
106
- v.trim(),
107
- "”."
108
- ]
106
+ children: y("mybodyshield.components.userSearch.noResults", "No users match “{{term}}”.", { term: b.trim() })
109
107
  }),
110
- C.length > 0 && /* @__PURE__ */ c("ul", {
108
+ T.length > 0 && /* @__PURE__ */ l("ul", {
111
109
  className: "max-h-56 divide-y divide-border-seam overflow-auto rounded-lg border border-border-seam bg-bg-surface",
112
- children: C.map((t) => /* @__PURE__ */ c("li", { children: /* @__PURE__ */ l("button", {
110
+ children: T.map((t) => /* @__PURE__ */ l("li", { children: /* @__PURE__ */ u("button", {
113
111
  type: "button",
114
- onClick: () => E(t),
112
+ onClick: () => O(t),
115
113
  className: "flex w-full items-center gap-2 px-3 py-2 text-left transition-colors hover:bg-bg-sunken",
116
114
  children: [
117
- /* @__PURE__ */ c("span", {
115
+ /* @__PURE__ */ l("span", {
118
116
  className: "flex size-7 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-text-secondary",
119
- children: /* @__PURE__ */ c(o, { className: "h-4 w-4" })
117
+ children: /* @__PURE__ */ l(s, { className: "h-4 w-4" })
120
118
  }),
121
- /* @__PURE__ */ l("span", {
119
+ /* @__PURE__ */ u("span", {
122
120
  className: "min-w-0 flex-1",
123
- children: [/* @__PURE__ */ c("span", {
121
+ children: [/* @__PURE__ */ l("span", {
124
122
  className: "block truncate text-sm font-medium text-text-primary",
125
123
  children: e(t)
126
- }), /* @__PURE__ */ c("span", {
124
+ }), /* @__PURE__ */ l("span", {
127
125
  className: "block truncate text-xs text-text-tertiary",
128
126
  children: t.email
129
127
  })]
130
128
  }),
131
- /* @__PURE__ */ c(r, { className: "h-4 w-4 shrink-0 text-text-tertiary opacity-0" })
129
+ /* @__PURE__ */ l(i, { className: "h-4 w-4 shrink-0 text-text-tertiary opacity-0" })
132
130
  ]
133
131
  }) }, t.id))
134
132
  }),
135
- /* @__PURE__ */ c("button", {
133
+ /* @__PURE__ */ l("button", {
136
134
  type: "button",
137
- onClick: () => x(!0),
135
+ onClick: () => C(!0),
138
136
  className: "text-xs font-medium text-accent hover:underline",
139
- children: "Enter a user ID manually"
137
+ children: y("mybodyshield.components.userSearch.enterManually", "Enter a user ID manually")
140
138
  })
141
139
  ]
142
140
  })]
143
141
  });
144
142
  }
145
143
  //#endregion
146
- export { f as UserSearchSelect };
144
+ export { p as UserSearchSelect };
147
145
 
148
146
  //# sourceMappingURL=UserSearchSelect.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"UserSearchSelect.js","names":[],"sources":["../../../src/mybodyshield/components/UserSearchSelect.tsx"],"sourcesContent":["/**\n * User picker (identity reuse)\n * @module mybodyshield/components\n *\n * Resolves a real user by name or email from the global user directory so a\n * coach/member can be chosen without pasting a raw UUID. Degrades gracefully:\n * if the directory is unavailable (RBAC), the user can still paste an id.\n * Semantic tokens only.\n */\n\nimport { useState } from 'react';\nimport { Check, Loader2, Search, User as UserIcon, X } from 'lucide-react';\nimport { Input, Label } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\nimport { directoryUserName, useMybUserSearch, type DirectoryUser } from '../hooks/useMybUsers';\n\nexport interface UserSelectValue {\n userId: string;\n displayName: string;\n}\n\ninterface UserSearchSelectProps {\n /** Field label (e.g. \"Coach\", \"Member\"). */\n label: string;\n /** Currently selected user id. */\n userId: string;\n /** Display name currently chosen (echoed back so the parent can prefill). */\n displayName: string;\n onSelect: (value: UserSelectValue) => void;\n required?: boolean;\n /** Optional id for the label/input association in manual mode. */\n htmlId?: string;\n}\n\nexport function UserSearchSelect({\n label,\n userId,\n displayName,\n onSelect,\n required = false,\n htmlId = 'myb-user-id',\n}: UserSearchSelectProps) {\n const [term, setTerm] = useState('');\n const [manual, setManual] = useState(false);\n\n const search = useMybUserSearch(term);\n const results = search.data ?? [];\n const active = term.trim().length >= 2;\n const searchUnavailable = search.isError;\n\n const pick = (user: DirectoryUser) => {\n onSelect({ userId: user.id, displayName: directoryUserName(user) });\n setTerm('');\n };\n\n if (manual || searchUnavailable) {\n return (\n <div className=\"grid gap-1.5\">\n <Label htmlFor={htmlId}>\n {label} {required && <span className=\"text-status-error-text\">*</span>}\n </Label>\n <Input\n id={htmlId}\n value={userId}\n onChange={(e) => onSelect({ userId: e.target.value, displayName })}\n placeholder=\"user-uuid\"\n />\n <p className=\"text-xs text-text-tertiary\">\n {searchUnavailable\n ? 'User search is unavailable here — paste the user id.'\n : 'Paste the user id.'}\n </p>\n {!searchUnavailable && (\n <button\n type=\"button\"\n onClick={() => setManual(false)}\n className=\"self-start text-xs font-medium text-accent hover:underline\"\n >\n Search the directory instead\n </button>\n )}\n </div>\n );\n }\n\n return (\n <div className=\"grid gap-1.5\">\n <Label>\n {label} {required && <span className=\"text-status-error-text\">*</span>}\n </Label>\n {userId ? (\n <div className=\"flex items-center gap-2 rounded-lg border border-border-seam bg-bg-surface px-3 py-2\">\n <span className=\"flex size-7 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-text-secondary\">\n <UserIcon className=\"h-4 w-4\" />\n </span>\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {displayName || 'Selected user'}\n </p>\n <p className=\"truncate text-xs text-text-tertiary\">{userId}</p>\n </div>\n <button\n type=\"button\"\n aria-label={`Clear selected ${label.toLowerCase()}`}\n onClick={() => onSelect({ userId: '', displayName: '' })}\n className=\"rounded-md p-1 text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n </div>\n ) : (\n <div className=\"space-y-2\">\n <div className=\"relative\">\n <Search className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-tertiary\" />\n <Input\n value={term}\n onChange={(e) => setTerm(e.target.value)}\n placeholder=\"Search by name or email…\"\n aria-label={`Search ${label.toLowerCase()} by name or email`}\n className=\"pl-9\"\n />\n {search.isFetching && (\n <Loader2 className=\"absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 animate-spin text-text-tertiary\" />\n )}\n </div>\n\n {active && !search.isFetching && results.length === 0 && (\n <p className=\"text-xs text-text-tertiary\">No users match “{term.trim()}”.</p>\n )}\n\n {results.length > 0 && (\n <ul className=\"max-h-56 divide-y divide-border-seam overflow-auto rounded-lg border border-border-seam bg-bg-surface\">\n {results.map((user) => (\n <li key={user.id}>\n <button\n type=\"button\"\n onClick={() => pick(user)}\n className=\"flex w-full items-center gap-2 px-3 py-2 text-left transition-colors hover:bg-bg-sunken\"\n >\n <span className=\"flex size-7 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-text-secondary\">\n <UserIcon className=\"h-4 w-4\" />\n </span>\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate text-sm font-medium text-text-primary\">\n {directoryUserName(user)}\n </span>\n <span className=\"block truncate text-xs text-text-tertiary\">\n {user.email}\n </span>\n </span>\n <Check className=\"h-4 w-4 shrink-0 text-text-tertiary opacity-0\" />\n </button>\n </li>\n ))}\n </ul>\n )}\n\n <button\n type=\"button\"\n onClick={() => setManual(true)}\n className=\"text-xs font-medium text-accent hover:underline\"\n >\n Enter a user ID manually\n </button>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;AAkCA,SAAgB,EAAiB,EAC/B,UACA,WACA,gBACA,aACA,cAAW,IACX,YAAS,iBACe;CACxB,IAAM,CAAC,GAAM,KAAW,EAAS,GAAG,EAC9B,CAAC,GAAQ,KAAa,EAAS,GAAM,EAErC,IAAS,EAAiB,EAAK,EAC/B,IAAU,EAAO,QAAQ,EAAE,EAC3B,IAAS,EAAK,MAAM,CAAC,UAAU,GAC/B,IAAoB,EAAO,SAE3B,KAAQ,MAAwB;AAEpC,EADA,EAAS;GAAE,QAAQ,EAAK;GAAI,aAAa,EAAkB,EAAK;GAAE,CAAC,EACnE,EAAQ,GAAG;;AAiCb,QA9BI,KAAU,IAEV,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD;IAAO,SAAS;cAAhB;KACG;KAAM;KAAE,KAAY,kBAAC,QAAD;MAAM,WAAU;gBAAyB;MAAQ,CAAA;KAChE;;GACR,kBAAC,GAAD;IACE,IAAI;IACJ,OAAO;IACP,WAAW,MAAM,EAAS;KAAE,QAAQ,EAAE,OAAO;KAAO;KAAa,CAAC;IAClE,aAAY;IACZ,CAAA;GACF,kBAAC,KAAD;IAAG,WAAU;cACV,IACG,yDACA;IACF,CAAA;GACH,CAAC,KACA,kBAAC,UAAD;IACE,MAAK;IACL,eAAe,EAAU,GAAM;IAC/B,WAAU;cACX;IAEQ,CAAA;GAEP;MAKR,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,GAAD,EAAA,UAAA;GACG;GAAM;GAAE,KAAY,kBAAC,QAAD;IAAM,WAAU;cAAyB;IAAQ,CAAA;GAChE,EAAA,CAAA,EACP,IACC,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,QAAD;KAAM,WAAU;eACd,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;KAC3B,CAAA;IACP,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,KAAD;MAAG,WAAU;gBACV,KAAe;MACd,CAAA,EACJ,kBAAC,KAAD;MAAG,WAAU;gBAAuC;MAAW,CAAA,CAC3D;;IACN,kBAAC,UAAD;KACE,MAAK;KACL,cAAY,kBAAkB,EAAM,aAAa;KACjD,eAAe,EAAS;MAAE,QAAQ;MAAI,aAAa;MAAI,CAAC;KACxD,WAAU;eAEV,kBAAC,GAAD,EAAG,WAAU,WAAY,CAAA;KAClB,CAAA;IACL;OAEN,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD,EAAQ,WAAU,2FAA4F,CAAA;MAC9G,kBAAC,GAAD;OACE,OAAO;OACP,WAAW,MAAM,EAAQ,EAAE,OAAO,MAAM;OACxC,aAAY;OACZ,cAAY,UAAU,EAAM,aAAa,CAAC;OAC1C,WAAU;OACV,CAAA;MACD,EAAO,cACN,kBAAC,GAAD,EAAS,WAAU,qFAAsF,CAAA;MAEvG;;IAEL,KAAU,CAAC,EAAO,cAAc,EAAQ,WAAW,KAClD,kBAAC,KAAD;KAAG,WAAU;eAAb;MAA0C;MAAiB,EAAK,MAAM;MAAC;MAAM;;IAG9E,EAAQ,SAAS,KAChB,kBAAC,MAAD;KAAI,WAAU;eACX,EAAQ,KAAK,MACZ,kBAAC,MAAD,EAAA,UACE,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAK,EAAK;MACzB,WAAU;gBAHZ;OAKE,kBAAC,QAAD;QAAM,WAAU;kBACd,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;QAC3B,CAAA;OACP,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CACE,kBAAC,QAAD;SAAM,WAAU;mBACb,EAAkB,EAAK;SACnB,CAAA,EACP,kBAAC,QAAD;SAAM,WAAU;mBACb,EAAK;SACD,CAAA,CACF;;OACP,kBAAC,GAAD,EAAO,WAAU,iDAAkD,CAAA;OAC5D;SACN,EAnBI,EAAK,GAmBT,CACL;KACC,CAAA;IAGP,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAU,GAAK;KAC9B,WAAU;eACX;KAEQ,CAAA;IACL;KAEJ"}
1
+ {"version":3,"file":"UserSearchSelect.js","names":[],"sources":["../../../src/mybodyshield/components/UserSearchSelect.tsx"],"sourcesContent":["/**\n * User picker (identity reuse)\n * @module mybodyshield/components\n *\n * Resolves a real user by name or email from the global user directory so a\n * coach/member can be chosen without pasting a raw UUID. Degrades gracefully:\n * if the directory is unavailable (RBAC), the user can still paste an id.\n * Semantic tokens only.\n */\n\nimport { useState } from 'react';\nimport { Check, Loader2, Search, User as UserIcon, X } from 'lucide-react';\nimport { Input, Label } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\nimport { directoryUserName, useMybUserSearch, type DirectoryUser } from '../hooks/useMybUsers';\nimport { useTr } from '../hooks';\n\nexport interface UserSelectValue {\n userId: string;\n displayName: string;\n}\n\ninterface UserSearchSelectProps {\n /** Field label (e.g. \"Coach\", \"Member\"). */\n label: string;\n /** Currently selected user id. */\n userId: string;\n /** Display name currently chosen (echoed back so the parent can prefill). */\n displayName: string;\n onSelect: (value: UserSelectValue) => void;\n required?: boolean;\n /** Optional id for the label/input association in manual mode. */\n htmlId?: string;\n}\n\nexport function UserSearchSelect({\n label,\n userId,\n displayName,\n onSelect,\n required = false,\n htmlId = 'myb-user-id',\n}: UserSearchSelectProps) {\n const t = useTr();\n const [term, setTerm] = useState('');\n const [manual, setManual] = useState(false);\n\n const search = useMybUserSearch(term);\n const results = search.data ?? [];\n const active = term.trim().length >= 2;\n const searchUnavailable = search.isError;\n\n const pick = (user: DirectoryUser) => {\n onSelect({ userId: user.id, displayName: directoryUserName(user) });\n setTerm('');\n };\n\n if (manual || searchUnavailable) {\n return (\n <div className=\"grid gap-1.5\">\n <Label htmlFor={htmlId}>\n {label} {required && <span className=\"text-status-error-text\">*</span>}\n </Label>\n <Input\n id={htmlId}\n value={userId}\n onChange={(e) => onSelect({ userId: e.target.value, displayName })}\n placeholder={t('mybodyshield.components.userSearch.manualPlaceholder', 'user-uuid')}\n />\n <p className=\"text-xs text-text-tertiary\">\n {searchUnavailable\n ? t(\n 'mybodyshield.components.userSearch.searchUnavailable',\n 'User search is unavailable here — paste the user id.'\n )\n : t('mybodyshield.components.userSearch.pasteHint', 'Paste the user id.')}\n </p>\n {!searchUnavailable && (\n <button\n type=\"button\"\n onClick={() => setManual(false)}\n className=\"self-start text-xs font-medium text-accent hover:underline\"\n >\n {t(\n 'mybodyshield.components.userSearch.searchDirectory',\n 'Search the directory instead'\n )}\n </button>\n )}\n </div>\n );\n }\n\n return (\n <div className=\"grid gap-1.5\">\n <Label>\n {label} {required && <span className=\"text-status-error-text\">*</span>}\n </Label>\n {userId ? (\n <div className=\"flex items-center gap-2 rounded-lg border border-border-seam bg-bg-surface px-3 py-2\">\n <span className=\"flex size-7 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-text-secondary\">\n <UserIcon className=\"h-4 w-4\" />\n </span>\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {displayName || t('mybodyshield.components.userSearch.selectedUser', 'Selected user')}\n </p>\n <p className=\"truncate text-xs text-text-tertiary\">{userId}</p>\n </div>\n <button\n type=\"button\"\n aria-label={`Clear selected ${label.toLowerCase()}`}\n onClick={() => onSelect({ userId: '', displayName: '' })}\n className=\"rounded-md p-1 text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n </div>\n ) : (\n <div className=\"space-y-2\">\n <div className=\"relative\">\n <Search className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-tertiary\" />\n <Input\n value={term}\n onChange={(e) => setTerm(e.target.value)}\n placeholder={t(\n 'mybodyshield.components.userSearch.searchPlaceholder',\n 'Search by name or email…'\n )}\n aria-label={t(\n 'mybodyshield.components.userSearch.searchAria',\n 'Search {{label}} by name or email',\n { label: label.toLowerCase() }\n )}\n className=\"pl-9\"\n />\n {search.isFetching && (\n <Loader2 className=\"absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 animate-spin text-text-tertiary\" />\n )}\n </div>\n\n {active && !search.isFetching && results.length === 0 && (\n <p className=\"text-xs text-text-tertiary\">\n {t('mybodyshield.components.userSearch.noResults', 'No users match “{{term}}”.', {\n term: term.trim(),\n })}\n </p>\n )}\n\n {results.length > 0 && (\n <ul className=\"max-h-56 divide-y divide-border-seam overflow-auto rounded-lg border border-border-seam bg-bg-surface\">\n {results.map((user) => (\n <li key={user.id}>\n <button\n type=\"button\"\n onClick={() => pick(user)}\n className=\"flex w-full items-center gap-2 px-3 py-2 text-left transition-colors hover:bg-bg-sunken\"\n >\n <span className=\"flex size-7 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-text-secondary\">\n <UserIcon className=\"h-4 w-4\" />\n </span>\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate text-sm font-medium text-text-primary\">\n {directoryUserName(user)}\n </span>\n <span className=\"block truncate text-xs text-text-tertiary\">\n {user.email}\n </span>\n </span>\n <Check className=\"h-4 w-4 shrink-0 text-text-tertiary opacity-0\" />\n </button>\n </li>\n ))}\n </ul>\n )}\n\n <button\n type=\"button\"\n onClick={() => setManual(true)}\n className=\"text-xs font-medium text-accent hover:underline\"\n >\n {t('mybodyshield.components.userSearch.enterManually', 'Enter a user ID manually')}\n </button>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -9,4 +9,5 @@ export * from './useMybComments';
9
9
  export * from './useMybTags';
10
10
  export * from './useMybUsers';
11
11
  export * from './useMyBodyShieldAnalytics';
12
+ export * from './useTr';
12
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/mybodyshield/hooks/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,sBAAsB,CAAC;AACrC,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,4BAA4B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/mybodyshield/hooks/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,sBAAsB,CAAC;AACrC,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,4BAA4B,CAAC;AAC3C,cAAc,SAAS,CAAC"}
@@ -5,3 +5,4 @@ import "./useMybComments.js";
5
5
  import "./useMybTags.js";
6
6
  import "./useMybUsers.js";
7
7
  import "./useMyBodyShieldAnalytics.js";
8
+ import "./useTr.js";
@@ -0,0 +1,5 @@
1
+ import { TranslationParams } from '../utils/i18n';
2
+
3
+ export type Tr = (key: string, fallback: string, params?: TranslationParams) => string;
4
+ export declare function useTr(): Tr;
5
+ //# sourceMappingURL=useTr.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTr.d.ts","sourceRoot":"","sources":["../../../src/mybodyshield/hooks/useTr.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEvD,MAAM,MAAM,EAAE,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,iBAAiB,KAAK,MAAM,CAAC;AAEvF,wBAAgB,KAAK,IAAI,EAAE,CAQ1B"}
@@ -0,0 +1,12 @@
1
+ import { tWithFallback as e } from "../utils/i18n.js";
2
+ import { useCallback as t } from "react";
3
+ import { useI18n as n } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
4
+ //#region src/mybodyshield/hooks/useTr.ts
5
+ function r() {
6
+ let { t: r } = n();
7
+ return t((t, n, i) => e(r, t, n, i), [r]);
8
+ }
9
+ //#endregion
10
+ export { r as useTr };
11
+
12
+ //# sourceMappingURL=useTr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTr.js","names":[],"sources":["../../../src/mybodyshield/hooks/useTr.ts"],"sourcesContent":["/**\n * MyBodyShield i18n hook\n * @module mybodyshield/hooks\n *\n * Thin wrapper around the shell I18nProvider that returns a `t` helper with\n * fallback-string and interpolation support. Keeps MFE components free of\n * `useI18n` boilerplate and guarantees a stable API even if the shell provider\n * changes shape.\n */\n\nimport { useCallback } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { tWithFallback } from '../utils/i18n';\nimport type { TranslationParams } from '../utils/i18n';\n\nexport type Tr = (key: string, fallback: string, params?: TranslationParams) => string;\n\nexport function useTr(): Tr {\n const { t: translate } = useI18n();\n\n return useCallback(\n (key: string, fallback: string, params?: TranslationParams): string =>\n tWithFallback(translate, key, fallback, params),\n [translate]\n );\n}\n"],"mappings":";;;;AAiBA,SAAgB,IAAY;CAC1B,IAAM,EAAE,GAAG,MAAc,GAAS;AAElC,QAAO,GACJ,GAAa,GAAkB,MAC9B,EAAc,GAAW,GAAK,GAAU,EAAO,EACjD,CAAC,EAAU,CACZ"}
@@ -1 +1 @@
1
- {"version":3,"file":"DashboardPage.d.ts","sourceRoot":"","sources":["../../../src/mybodyshield/pages/DashboardPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAmGH,wBAAgB,aAAa,gCAgmB5B"}
1
+ {"version":3,"file":"DashboardPage.d.ts","sourceRoot":"","sources":["../../../src/mybodyshield/pages/DashboardPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAyGH,wBAAgB,aAAa,gCAsqB5B"}