@burdenoff/microfe-buildmyiq 2026.525.1 → 2026.705.2

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 (169) hide show
  1. package/dist/buildmyiq/BuildmyiqRoot.js +123 -0
  2. package/dist/buildmyiq/BuildmyiqRoot.js.map +1 -0
  3. package/dist/buildmyiq/BuildmyiqRoutes.js +84 -0
  4. package/dist/buildmyiq/BuildmyiqRoutes.js.map +1 -0
  5. package/dist/buildmyiq/components/BuiDiscussion.js +109 -0
  6. package/dist/buildmyiq/components/BuiDiscussion.js.map +1 -0
  7. package/dist/buildmyiq/components/BuiTags.js +149 -0
  8. package/dist/buildmyiq/components/BuiTags.js.map +1 -0
  9. package/dist/buildmyiq/components/DataTable.js +71 -0
  10. package/dist/buildmyiq/components/DataTable.js.map +1 -0
  11. package/dist/buildmyiq/components/EmptyState.js +35 -0
  12. package/dist/buildmyiq/components/EmptyState.js.map +1 -0
  13. package/dist/buildmyiq/components/EvidenceUpload.js +93 -0
  14. package/dist/buildmyiq/components/EvidenceUpload.js.map +1 -0
  15. package/dist/buildmyiq/components/FileLink.js +36 -0
  16. package/dist/buildmyiq/components/FileLink.js.map +1 -0
  17. package/dist/buildmyiq/components/FilterTabs.js +27 -0
  18. package/dist/buildmyiq/components/FilterTabs.js.map +1 -0
  19. package/dist/buildmyiq/components/MetricsCard.js +64 -0
  20. package/dist/buildmyiq/components/MetricsCard.js.map +1 -0
  21. package/dist/buildmyiq/components/MiniBar.js +22 -0
  22. package/dist/buildmyiq/components/MiniBar.js.map +1 -0
  23. package/dist/buildmyiq/components/PageLayout.js +38 -0
  24. package/dist/buildmyiq/components/PageLayout.js.map +1 -0
  25. package/dist/buildmyiq/components/Pagination.js +50 -0
  26. package/dist/buildmyiq/components/Pagination.js.map +1 -0
  27. package/dist/buildmyiq/components/StateViews.js +49 -0
  28. package/dist/buildmyiq/components/StateViews.js.map +1 -0
  29. package/dist/buildmyiq/components/StatusBadge.js +60 -0
  30. package/dist/buildmyiq/components/StatusBadge.js.map +1 -0
  31. package/dist/buildmyiq/components/Tabs.js +31 -0
  32. package/dist/buildmyiq/components/Tabs.js.map +1 -0
  33. package/dist/buildmyiq/components/index.js +14 -0
  34. package/dist/buildmyiq/context/BuildmyiqContext.js +85 -0
  35. package/dist/buildmyiq/context/BuildmyiqContext.js.map +1 -0
  36. package/dist/buildmyiq/context/index.js +1 -0
  37. package/dist/buildmyiq/hooks/index.js +5 -0
  38. package/dist/buildmyiq/hooks/useBuiComments.js +45 -0
  39. package/dist/buildmyiq/hooks/useBuiComments.js.map +1 -0
  40. package/dist/buildmyiq/hooks/useBuiFiles.js +81 -0
  41. package/dist/buildmyiq/hooks/useBuiFiles.js.map +1 -0
  42. package/dist/buildmyiq/hooks/useBuiTags.js +89 -0
  43. package/dist/buildmyiq/hooks/useBuiTags.js.map +1 -0
  44. package/dist/buildmyiq/hooks/useBuildmyiqApi.js +412 -0
  45. package/dist/buildmyiq/hooks/useBuildmyiqApi.js.map +1 -0
  46. package/dist/buildmyiq/hooks/useCursorPager.js +25 -0
  47. package/dist/buildmyiq/hooks/useCursorPager.js.map +1 -0
  48. package/dist/buildmyiq/index.js +6 -3
  49. package/dist/buildmyiq/pages/DashboardPage.js +134 -247
  50. package/dist/buildmyiq/pages/DashboardPage.js.map +1 -1
  51. package/dist/buildmyiq/pages/_shared/CourseCard.js +74 -0
  52. package/dist/buildmyiq/pages/_shared/CourseCard.js.map +1 -0
  53. package/dist/buildmyiq/pages/_shared/index.js +1 -0
  54. package/dist/buildmyiq/pages/assessments/AssessmentDetailPage.js +499 -0
  55. package/dist/buildmyiq/pages/assessments/AssessmentDetailPage.js.map +1 -0
  56. package/dist/buildmyiq/pages/assessments/AssessmentsPage.js +390 -0
  57. package/dist/buildmyiq/pages/assessments/AssessmentsPage.js.map +1 -0
  58. package/dist/buildmyiq/pages/authoring/AuthoringPage.js +252 -0
  59. package/dist/buildmyiq/pages/authoring/AuthoringPage.js.map +1 -0
  60. package/dist/buildmyiq/pages/authoring/CourseBuilderPage.js +680 -0
  61. package/dist/buildmyiq/pages/authoring/CourseBuilderPage.js.map +1 -0
  62. package/dist/buildmyiq/pages/authoring/_forms.js +54 -0
  63. package/dist/buildmyiq/pages/authoring/_forms.js.map +1 -0
  64. package/dist/buildmyiq/pages/catalog/CatalogPage.js +97 -0
  65. package/dist/buildmyiq/pages/catalog/CatalogPage.js.map +1 -0
  66. package/dist/buildmyiq/pages/catalog/CourseDetailPage.js +354 -0
  67. package/dist/buildmyiq/pages/catalog/CourseDetailPage.js.map +1 -0
  68. package/dist/buildmyiq/pages/certificates/CertificatesPage.js +363 -0
  69. package/dist/buildmyiq/pages/certificates/CertificatesPage.js.map +1 -0
  70. package/dist/buildmyiq/pages/cohorts/CohortsPage.js +339 -0
  71. package/dist/buildmyiq/pages/cohorts/CohortsPage.js.map +1 -0
  72. package/dist/buildmyiq/pages/learning/LessonPlayerPage.js +303 -0
  73. package/dist/buildmyiq/pages/learning/LessonPlayerPage.js.map +1 -0
  74. package/dist/buildmyiq/pages/learning/MyLearningPage.js +187 -0
  75. package/dist/buildmyiq/pages/learning/MyLearningPage.js.map +1 -0
  76. package/dist/buildmyiq/pages/skills/SkillsPage.js +230 -0
  77. package/dist/buildmyiq/pages/skills/SkillsPage.js.map +1 -0
  78. package/dist/buildmyiq/pages/submissions/SubmissionsPage.js +433 -0
  79. package/dist/buildmyiq/pages/submissions/SubmissionsPage.js.map +1 -0
  80. package/dist/buildmyiq/types/index.js +44 -0
  81. package/dist/buildmyiq/types/index.js.map +1 -0
  82. package/dist/buildmyiq/utils/cn.js +10 -0
  83. package/dist/buildmyiq/utils/cn.js.map +1 -0
  84. package/dist/buildmyiq/utils/formatters.js +62 -0
  85. package/dist/buildmyiq/utils/formatters.js.map +1 -0
  86. package/dist/buildmyiq/utils/navigation.js +2 -1
  87. package/dist/buildmyiq/utils/navigation.js.map +1 -1
  88. package/dist/index.js +6 -4
  89. package/dist/node_modules/clsx/dist/clsx.js +18 -0
  90. package/dist/node_modules/clsx/dist/clsx.js.map +1 -0
  91. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +1716 -0
  92. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -0
  93. package/package.json +3 -3
  94. package/dist/buildmyiq/BuildMyIQRoot.js +0 -329
  95. package/dist/buildmyiq/BuildMyIQRoot.js.map +0 -1
  96. package/dist/buildmyiq/BuildMyIQRoutes.js +0 -171
  97. package/dist/buildmyiq/BuildMyIQRoutes.js.map +0 -1
  98. package/dist/buildmyiq/pages/AdminAdminAnalyticsPagePage.js +0 -271
  99. package/dist/buildmyiq/pages/AdminAdminAnalyticsPagePage.js.map +0 -1
  100. package/dist/buildmyiq/pages/AdminAdminCoursesPagePage.js +0 -212
  101. package/dist/buildmyiq/pages/AdminAdminCoursesPagePage.js.map +0 -1
  102. package/dist/buildmyiq/pages/AdminAdminUsersPagePage.js +0 -235
  103. package/dist/buildmyiq/pages/AdminAdminUsersPagePage.js.map +0 -1
  104. package/dist/buildmyiq/pages/AdminIndexPage.js +0 -274
  105. package/dist/buildmyiq/pages/AdminIndexPage.js.map +0 -1
  106. package/dist/buildmyiq/pages/AiAichatPagePage.js +0 -209
  107. package/dist/buildmyiq/pages/AiAichatPagePage.js.map +0 -1
  108. package/dist/buildmyiq/pages/AiAicontentGenerationPagePage.js +0 -318
  109. package/dist/buildmyiq/pages/AiAicontentGenerationPagePage.js.map +0 -1
  110. package/dist/buildmyiq/pages/AiAitutoringPagePage.js +0 -290
  111. package/dist/buildmyiq/pages/AiAitutoringPagePage.js.map +0 -1
  112. package/dist/buildmyiq/pages/AiIndexPage.js +0 -191
  113. package/dist/buildmyiq/pages/AiIndexPage.js.map +0 -1
  114. package/dist/buildmyiq/pages/AssessmentsAssessmentBuilderPagePage.js +0 -290
  115. package/dist/buildmyiq/pages/AssessmentsAssessmentBuilderPagePage.js.map +0 -1
  116. package/dist/buildmyiq/pages/AssessmentsAssessmentGradingPagePage.js +0 -215
  117. package/dist/buildmyiq/pages/AssessmentsAssessmentGradingPagePage.js.map +0 -1
  118. package/dist/buildmyiq/pages/AssessmentsAssessmentResultsPagePage.js +0 -224
  119. package/dist/buildmyiq/pages/AssessmentsAssessmentResultsPagePage.js.map +0 -1
  120. package/dist/buildmyiq/pages/AssessmentsAssessmentTakePagePage.js +0 -174
  121. package/dist/buildmyiq/pages/AssessmentsAssessmentTakePagePage.js.map +0 -1
  122. package/dist/buildmyiq/pages/AssessmentsIndexPage.js +0 -174
  123. package/dist/buildmyiq/pages/AssessmentsIndexPage.js.map +0 -1
  124. package/dist/buildmyiq/pages/CalendarPage.js +0 -242
  125. package/dist/buildmyiq/pages/CalendarPage.js.map +0 -1
  126. package/dist/buildmyiq/pages/CommunicationAnnouncementsPagePage.js +0 -180
  127. package/dist/buildmyiq/pages/CommunicationAnnouncementsPagePage.js.map +0 -1
  128. package/dist/buildmyiq/pages/CommunicationForumsPagePage.js +0 -154
  129. package/dist/buildmyiq/pages/CommunicationForumsPagePage.js.map +0 -1
  130. package/dist/buildmyiq/pages/CommunicationIndexPage.js +0 -191
  131. package/dist/buildmyiq/pages/CommunicationIndexPage.js.map +0 -1
  132. package/dist/buildmyiq/pages/CommunicationMessagesPagePage.js +0 -261
  133. package/dist/buildmyiq/pages/CommunicationMessagesPagePage.js.map +0 -1
  134. package/dist/buildmyiq/pages/CoursesCourseContentPagePage.js +0 -164
  135. package/dist/buildmyiq/pages/CoursesCourseContentPagePage.js.map +0 -1
  136. package/dist/buildmyiq/pages/CoursesCourseCreatePagePage.js +0 -239
  137. package/dist/buildmyiq/pages/CoursesCourseCreatePagePage.js.map +0 -1
  138. package/dist/buildmyiq/pages/CoursesCourseDashboardPagePage.js +0 -368
  139. package/dist/buildmyiq/pages/CoursesCourseDashboardPagePage.js.map +0 -1
  140. package/dist/buildmyiq/pages/CoursesCourseDetailPagePage.js +0 -219
  141. package/dist/buildmyiq/pages/CoursesCourseDetailPagePage.js.map +0 -1
  142. package/dist/buildmyiq/pages/CoursesCourseListPagePage.js +0 -153
  143. package/dist/buildmyiq/pages/CoursesCourseListPagePage.js.map +0 -1
  144. package/dist/buildmyiq/pages/CoursesIndexPage.js +0 -138
  145. package/dist/buildmyiq/pages/CoursesIndexPage.js.map +0 -1
  146. package/dist/buildmyiq/pages/MessagesPage.js +0 -261
  147. package/dist/buildmyiq/pages/MessagesPage.js.map +0 -1
  148. package/dist/buildmyiq/pages/ProfileProgressPage.js +0 -194
  149. package/dist/buildmyiq/pages/ProfileProgressPage.js.map +0 -1
  150. package/dist/buildmyiq/pages/TeachingAnalyticsPage.js +0 -243
  151. package/dist/buildmyiq/pages/TeachingAnalyticsPage.js.map +0 -1
  152. package/dist/buildmyiq/pages/TeachingContentPage.js +0 -165
  153. package/dist/buildmyiq/pages/TeachingContentPage.js.map +0 -1
  154. package/dist/buildmyiq/pages/TeachingCoursesPage.js +0 -151
  155. package/dist/buildmyiq/pages/TeachingCoursesPage.js.map +0 -1
  156. package/dist/buildmyiq/pages/TeachingGradingPage.js +0 -204
  157. package/dist/buildmyiq/pages/TeachingGradingPage.js.map +0 -1
  158. package/dist/buildmyiq/pages/TeachingIndexPage.js +0 -220
  159. package/dist/buildmyiq/pages/TeachingIndexPage.js.map +0 -1
  160. package/dist/buildmyiq/pages/TeachingLessonPlansPage.js +0 -157
  161. package/dist/buildmyiq/pages/TeachingLessonPlansPage.js.map +0 -1
  162. package/dist/buildmyiq/pages/TeachingQuestionsPage.js +0 -184
  163. package/dist/buildmyiq/pages/TeachingQuestionsPage.js.map +0 -1
  164. package/dist/buildmyiq/pages/TeachingStudentsPage.js +0 -204
  165. package/dist/buildmyiq/pages/TeachingStudentsPage.js.map +0 -1
  166. package/dist/buildmyiq/providers/BuildMyIQProvider.js +0 -27
  167. package/dist/buildmyiq/providers/BuildMyIQProvider.js.map +0 -1
  168. package/dist/buildmyiq/store/buildmyiqStore.js +0 -1160
  169. package/dist/buildmyiq/store/buildmyiqStore.js.map +0 -1
@@ -0,0 +1,85 @@
1
+ import { joinPath as e } from "../utils/navigation.js";
2
+ import { createContext as t, useCallback as n, useContext as r, useMemo as i } from "react";
3
+ import { MemoryRouter as a, useNavigate as o } from "react-router-dom";
4
+ import { jsx as s } from "react/jsx-runtime";
5
+ //#region src/buildmyiq/context/BuildmyiqContext.tsx
6
+ var c = t(null);
7
+ function l() {
8
+ let e = r(c);
9
+ if (!e) throw Error("useBuildmyiq must be used within an BuildmyiqProvider");
10
+ return e;
11
+ }
12
+ var u = ({ children: t, basePath: r, currentUser: a, tenantId: l, workspaceId: u, apiGatewayUrl: d, authToken: f }) => {
13
+ let p = o(), m = r || "/", h = n((t) => e(m, t), [m]), g = n((t) => {
14
+ p(e(m, t));
15
+ }, [m, p]), _ = i(() => ({
16
+ basePath: m,
17
+ navigate: void 0,
18
+ currentUser: a,
19
+ tenantId: l,
20
+ workspaceId: u,
21
+ apiGatewayUrl: d,
22
+ authToken: f,
23
+ isStandalone: !0,
24
+ navigateTo: g,
25
+ getPath: h
26
+ }), [
27
+ m,
28
+ a,
29
+ l,
30
+ u,
31
+ d,
32
+ f,
33
+ g,
34
+ h
35
+ ]);
36
+ return /* @__PURE__ */ s(c.Provider, {
37
+ value: _,
38
+ children: t
39
+ });
40
+ }, d = ({ children: t, basePath: r, navigate: a, currentUser: o, tenantId: l, workspaceId: u, apiGatewayUrl: d, authToken: f }) => {
41
+ let p = r || "/", m = n((t) => e(p, t), [p]), h = n((t) => {
42
+ a(e(p, t));
43
+ }, [p, a]), g = i(() => ({
44
+ basePath: p,
45
+ navigate: a,
46
+ currentUser: o,
47
+ tenantId: l,
48
+ workspaceId: u,
49
+ apiGatewayUrl: d,
50
+ authToken: f,
51
+ isStandalone: !1,
52
+ navigateTo: h,
53
+ getPath: m
54
+ }), [
55
+ p,
56
+ a,
57
+ o,
58
+ l,
59
+ u,
60
+ d,
61
+ f,
62
+ h,
63
+ m
64
+ ]);
65
+ return /* @__PURE__ */ s(c.Provider, {
66
+ value: g,
67
+ children: t
68
+ });
69
+ }, f = (e) => {
70
+ let { children: t, navigate: n } = e;
71
+ return n ? /* @__PURE__ */ s(d, {
72
+ ...e,
73
+ navigate: n,
74
+ isStandalone: !1,
75
+ children: t
76
+ }) : /* @__PURE__ */ s(a, { children: /* @__PURE__ */ s(u, {
77
+ ...e,
78
+ isStandalone: !0,
79
+ children: t
80
+ }) });
81
+ };
82
+ //#endregion
83
+ export { f as BuildmyiqProvider, l as useBuildmyiq };
84
+
85
+ //# sourceMappingURL=BuildmyiqContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BuildmyiqContext.js","names":[],"sources":["../../../src/buildmyiq/context/BuildmyiqContext.tsx"],"sourcesContent":["/**\n * Buildmyiq Context Provider\n * @module buildmyiq/context\n *\n * Splits standalone mode (wraps its own MemoryRouter + useNavigate) from\n * integrated mode (the host shell supplies `navigate`). Exposes gateway/auth\n * context + relative navigation helpers to every page.\n */\n\nimport type { FC, PropsWithChildren } from 'react';\nimport { createContext, useContext, useMemo, useCallback } from 'react';\nimport { MemoryRouter, useNavigate as useRouterNavigate } from 'react-router-dom';\nimport type { BuildmyiqRootProps } from '../types';\nimport { joinPath } from '../utils/navigation';\n\ninterface BuildmyiqContextValue extends Omit<\n BuildmyiqRootProps,\n 'registerNavItems' | 'registerFooterItems' | 'navigate'\n> {\n isStandalone: boolean;\n /** Shell's navigate function (only available in integrated mode). */\n navigate?: (path: string) => void;\n /** Navigate to a path relative to basePath. */\n navigateTo: (relativePath: string) => void;\n /** Get the full path for a relative path. */\n getPath: (relativePath: string) => string;\n}\n\nconst BuildmyiqContext = createContext<BuildmyiqContextValue | null>(null);\n\nexport function useBuildmyiq(): BuildmyiqContextValue {\n const context = useContext(BuildmyiqContext);\n if (!context) {\n throw new Error('useBuildmyiq must be used within an BuildmyiqProvider');\n }\n return context;\n}\n\ntype ProviderProps = Omit<BuildmyiqRootProps, 'registerNavItems' | 'registerFooterItems'> & {\n isStandalone: boolean;\n};\n\nconst StandaloneProviderInner: FC<PropsWithChildren<ProviderProps>> = ({\n children,\n basePath,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n}) => {\n const routerNavigate = useRouterNavigate();\n const effectiveBasePath = basePath || '/';\n\n const getPath = useCallback(\n (relativePath: string) => joinPath(effectiveBasePath, relativePath),\n [effectiveBasePath]\n );\n\n const navigateTo = useCallback(\n (relativePath: string) => {\n routerNavigate(joinPath(effectiveBasePath, relativePath));\n },\n [effectiveBasePath, routerNavigate]\n );\n\n const contextValue = useMemo<BuildmyiqContextValue>(\n () => ({\n basePath: effectiveBasePath,\n navigate: undefined,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n isStandalone: true,\n navigateTo,\n getPath,\n }),\n [\n effectiveBasePath,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n navigateTo,\n getPath,\n ]\n );\n\n return <BuildmyiqContext.Provider value={contextValue}>{children}</BuildmyiqContext.Provider>;\n};\n\nconst IntegratedProviderInner: FC<\n PropsWithChildren<ProviderProps & { navigate: (path: string) => void }>\n> = ({\n children,\n basePath,\n navigate,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n}) => {\n const effectiveBasePath = basePath || '/';\n\n const getPath = useCallback(\n (relativePath: string) => joinPath(effectiveBasePath, relativePath),\n [effectiveBasePath]\n );\n\n const navigateTo = useCallback(\n (relativePath: string) => {\n navigate(joinPath(effectiveBasePath, relativePath));\n },\n [effectiveBasePath, navigate]\n );\n\n const contextValue = useMemo<BuildmyiqContextValue>(\n () => ({\n basePath: effectiveBasePath,\n navigate,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n isStandalone: false,\n navigateTo,\n getPath,\n }),\n [\n effectiveBasePath,\n navigate,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n navigateTo,\n getPath,\n ]\n );\n\n return <BuildmyiqContext.Provider value={contextValue}>{children}</BuildmyiqContext.Provider>;\n};\n\nexport const BuildmyiqProvider: FC<PropsWithChildren<BuildmyiqRootProps>> = (props) => {\n const { children, navigate } = props;\n const isStandalone = !navigate;\n\n if (isStandalone) {\n return (\n <MemoryRouter>\n <StandaloneProviderInner {...props} isStandalone={true}>\n {children}\n </StandaloneProviderInner>\n </MemoryRouter>\n );\n }\n\n return (\n <IntegratedProviderInner {...props} navigate={navigate} isStandalone={false}>\n {children}\n </IntegratedProviderInner>\n );\n};\n"],"mappings":";;;;;AA4BA,IAAM,IAAmB,EAA4C,KAAK;AAE1E,SAAgB,IAAsC;CACpD,IAAM,IAAU,EAAW,EAAiB;AAC5C,KAAI,CAAC,EACH,OAAU,MAAM,wDAAwD;AAE1E,QAAO;;AAOT,IAAM,KAAiE,EACrE,aACA,aACA,gBACA,aACA,gBACA,kBACA,mBACI;CACJ,IAAM,IAAiB,GAAmB,EACpC,IAAoB,KAAY,KAEhC,IAAU,GACb,MAAyB,EAAS,GAAmB,EAAa,EACnE,CAAC,EAAkB,CACpB,EAEK,IAAa,GAChB,MAAyB;AACxB,IAAe,EAAS,GAAmB,EAAa,CAAC;IAE3D,CAAC,GAAmB,EAAe,CACpC,EAEK,IAAe,SACZ;EACL,UAAU;EACV,UAAU,KAAA;EACV;EACA;EACA;EACA;EACA;EACA,cAAc;EACd;EACA;EACD,GACD;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CACF;AAED,QAAO,kBAAC,EAAiB,UAAlB;EAA2B,OAAO;EAAe;EAAqC,CAAA;GAGzF,KAED,EACH,aACA,aACA,aACA,gBACA,aACA,gBACA,kBACA,mBACI;CACJ,IAAM,IAAoB,KAAY,KAEhC,IAAU,GACb,MAAyB,EAAS,GAAmB,EAAa,EACnE,CAAC,EAAkB,CACpB,EAEK,IAAa,GAChB,MAAyB;AACxB,IAAS,EAAS,GAAmB,EAAa,CAAC;IAErD,CAAC,GAAmB,EAAS,CAC9B,EAEK,IAAe,SACZ;EACL,UAAU;EACV;EACA;EACA;EACA;EACA;EACA;EACA,cAAc;EACd;EACA;EACD,GACD;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CACF;AAED,QAAO,kBAAC,EAAiB,UAAlB;EAA2B,OAAO;EAAe;EAAqC,CAAA;GAGlF,KAAgE,MAAU;CACrF,IAAM,EAAE,aAAU,gBAAa;AAa/B,QAZsB,IAapB,kBAAC,GAAD;EAAyB,GAAI;EAAiB;EAAU,cAAc;EACnE;EACuB,CAAA,GAXxB,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;EAAyB,GAAI;EAAO,cAAc;EAC/C;EACuB,CAAA,EACb,CAAA"}
@@ -0,0 +1 @@
1
+ import "./BuildmyiqContext.js";
@@ -0,0 +1,5 @@
1
+ import "./useBuildmyiqApi.js";
2
+ import "./useCursorPager.js";
3
+ import "./useBuiFiles.js";
4
+ import "./useBuiComments.js";
5
+ import "./useBuiTags.js";
@@ -0,0 +1,45 @@
1
+ import { useBuildmyiq as e } from "../context/BuildmyiqContext.js";
2
+ import "../context/index.js";
3
+ import { wspaceFetch as t } from "./useBuiFiles.js";
4
+ import { useMutation as n, useQuery as r, useQueryClient as i } from "@tanstack/react-query";
5
+ //#region src/buildmyiq/hooks/useBuiComments.ts
6
+ var a = (e, t) => [
7
+ "buildmyiq",
8
+ "comments",
9
+ e,
10
+ t
11
+ ], o = (n, i) => {
12
+ let { apiGatewayUrl: o } = e();
13
+ return r({
14
+ queryKey: a(n, i ?? ""),
15
+ enabled: !!i,
16
+ queryFn: async () => {
17
+ let e = await t("query BuiGetEntityComments($input: GetCommentsInput!) {\n getEntityComments(input: $input) {\n total\n items {\n id\n authorId\n authorType\n content { text markdown }\n createdAt\n edited\n parentCommentId\n replyCount\n }\n }\n }", { input: {
18
+ entityType: n,
19
+ entityId: i,
20
+ take: 100,
21
+ parentCommentId: null
22
+ } }, o), r = e.getEntityComments.items.filter((e) => !e.parentCommentId);
23
+ return r.sort((e, t) => new Date(e.createdAt).getTime() - new Date(t.createdAt).getTime()), {
24
+ items: r,
25
+ total: e.getEntityComments.total
26
+ };
27
+ }
28
+ });
29
+ }, s = (r, o) => {
30
+ let { apiGatewayUrl: s } = e(), c = i();
31
+ return n({
32
+ mutationFn: async (e) => (await t("mutation BuiCreateEntityComment($input: CreateEntityCommentInput!) {\n createEntityComment(input: $input) { id }\n }", { input: {
33
+ entityType: r,
34
+ entityId: o,
35
+ content: { text: e }
36
+ } }, s)).createEntityComment,
37
+ onSuccess: () => {
38
+ c.invalidateQueries({ queryKey: a(r, o) });
39
+ }
40
+ });
41
+ };
42
+ //#endregion
43
+ export { o as useBuiComments, s as useEcoCreateComment };
44
+
45
+ //# sourceMappingURL=useBuiComments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useBuiComments.js","names":[],"sources":["../../../src/buildmyiq/hooks/useBuiComments.ts"],"sourcesContent":["/**\n * Buildmyiq — wspace-conversations-svc EntityComment REUSE hooks (Integration B).\n * @module buildmyiq/hooks/useBuiComments\n *\n * Adds a discussion surface to Buildmyiq entities WITHOUT any buildmyiq-svc change by\n * reusing the generic EntityComment API composed into the WORKSPACE supergraph.\n * Comments attach to any entity by `(entityType, entityId)`:\n * - `getEntityComments(input): EntityCommentConnection!`\n * - `createEntityComment(input): EntityComment!`\n *\n * Transport reuses `wspaceFetch` from `useBuiFiles` (workspace gateway +\n * workspace token). Lives in a separate file; `useBuildmyiqApi.ts` is\n * untouched.\n */\n\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\n\nimport { useBuildmyiq } from '../context';\nimport { wspaceFetch } from './useBuiFiles';\n\nexport interface BuiComment {\n id: string;\n authorId: string;\n authorType: string;\n content: {\n text: string | null;\n markdown: string | null;\n };\n createdAt: string;\n edited: boolean;\n parentCommentId: string | null;\n replyCount: number;\n}\n\nconst commentsKey = (entityType: string, entityId: string) =>\n ['buildmyiq', 'comments', entityType, entityId] as const;\n\n/** Top-level (root) comments for an Buildmyiq entity, oldest first. */\nexport const useBuiComments = (entityType: string, entityId: string | undefined) => {\n const { apiGatewayUrl } = useBuildmyiq();\n return useQuery({\n queryKey: commentsKey(entityType, entityId ?? ''),\n enabled: Boolean(entityId),\n queryFn: async () => {\n const data = await wspaceFetch<{\n getEntityComments: { items: BuiComment[]; total: number };\n }>(\n `query BuiGetEntityComments($input: GetCommentsInput!) {\n getEntityComments(input: $input) {\n total\n items {\n id\n authorId\n authorType\n content { text markdown }\n createdAt\n edited\n parentCommentId\n replyCount\n }\n }\n }`,\n {\n input: {\n entityType,\n entityId,\n take: 100,\n parentCommentId: null,\n },\n },\n apiGatewayUrl\n );\n // Render root comments oldest-first (server order is not guaranteed).\n const roots = data.getEntityComments.items.filter((c) => !c.parentCommentId);\n roots.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());\n return { items: roots, total: data.getEntityComments.total };\n },\n });\n};\n\nexport const useEcoCreateComment = (entityType: string, entityId: string) => {\n const { apiGatewayUrl } = useBuildmyiq();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (text: string) => {\n const data = await wspaceFetch<{ createEntityComment: { id: string } }>(\n `mutation BuiCreateEntityComment($input: CreateEntityCommentInput!) {\n createEntityComment(input: $input) { id }\n }`,\n {\n input: {\n entityType,\n entityId,\n content: { text },\n },\n },\n apiGatewayUrl\n );\n return data.createEntityComment;\n },\n onSuccess: () => {\n qc.invalidateQueries({ queryKey: commentsKey(entityType, entityId) });\n },\n });\n};\n"],"mappings":";;;;;AAkCA,IAAM,KAAe,GAAoB,MACvC;CAAC;CAAa;CAAY;CAAY;CAAS,EAGpC,KAAkB,GAAoB,MAAiC;CAClF,IAAM,EAAE,qBAAkB,GAAc;AACxC,QAAO,EAAS;EACd,UAAU,EAAY,GAAY,KAAY,GAAG;EACjD,SAAS,EAAQ;EACjB,SAAS,YAAY;GACnB,IAAM,IAAO,MAAM,EAGjB,6YAeA,EACE,OAAO;IACL;IACA;IACA,MAAM;IACN,iBAAiB;IAClB,EACF,EACD,EACD,EAEK,IAAQ,EAAK,kBAAkB,MAAM,QAAQ,MAAM,CAAC,EAAE,gBAAgB;AAE5E,UADA,EAAM,MAAM,GAAG,MAAM,IAAI,KAAK,EAAE,UAAU,CAAC,SAAS,GAAG,IAAI,KAAK,EAAE,UAAU,CAAC,SAAS,CAAC,EAChF;IAAE,OAAO;IAAO,OAAO,EAAK,kBAAkB;IAAO;;EAE/D,CAAC;GAGS,KAAuB,GAAoB,MAAqB;CAC3E,IAAM,EAAE,qBAAkB,GAAc,EAClC,IAAK,GAAgB;AAC3B,QAAO,EAAY;EACjB,YAAY,OAAO,OACJ,MAAM,EACjB,wIAGA,EACE,OAAO;GACL;GACA;GACA,SAAS,EAAE,SAAM;GAClB,EACF,EACD,EACD,EACW;EAEd,iBAAiB;AACf,KAAG,kBAAkB,EAAE,UAAU,EAAY,GAAY,EAAS,EAAE,CAAC;;EAExE,CAAC"}
@@ -0,0 +1,81 @@
1
+ import { useBuildmyiq as e } from "../context/BuildmyiqContext.js";
2
+ import "../context/index.js";
3
+ import { useCallback as t, useState as n } from "react";
4
+ import { useQuery as r } from "@tanstack/react-query";
5
+ import { graphqlFetch as i } from "@burdenoff/fe-libs/shared/graphql";
6
+ //#region src/buildmyiq/hooks/useBuiFiles.ts
7
+ var a = () => {
8
+ let e = globalThis;
9
+ e.__graphqlFetchResponseCache?.clear(), e.__graphqlFetchPendingResponses?.clear(), e.__boffGraphQLResponseCache?.clear();
10
+ }, o = async (e, t, n) => {
11
+ let r = await i({
12
+ gateway: "workspace",
13
+ baseUrl: n,
14
+ query: e,
15
+ variables: t,
16
+ workspaceToken: !0
17
+ });
18
+ if (r.errors?.length) throw Error(r.errors[0]?.message ?? "GraphQL request failed");
19
+ if (!r.data) throw Error("GraphQL response did not include data");
20
+ return /^\s*mutation\b/.test(e) && a(), r.data;
21
+ };
22
+ function s(e, t, n, r) {
23
+ return new Promise((i, a) => {
24
+ let o = new XMLHttpRequest();
25
+ o.open("PUT", e), o.setRequestHeader("Content-Type", n), o.upload.onprogress = (e) => {
26
+ e.lengthComputable && r(Math.round(e.loaded / e.total * 100));
27
+ }, o.onload = () => {
28
+ o.status >= 200 && o.status < 300 ? i() : a(/* @__PURE__ */ Error(`File storage rejected the upload (HTTP ${o.status}).`));
29
+ }, o.onerror = () => a(/* @__PURE__ */ Error("Network error while uploading the file.")), o.onabort = () => a(/* @__PURE__ */ Error("The upload was cancelled.")), o.send(t);
30
+ });
31
+ }
32
+ var c = () => {
33
+ let { apiGatewayUrl: r } = e(), [i, a] = n(!1), [c, l] = n(0), [u, d] = n(null), f = t(() => {
34
+ a(!1), l(0), d(null);
35
+ }, []);
36
+ return {
37
+ upload: t(async (e) => {
38
+ a(!0), l(0), d(null);
39
+ try {
40
+ let t = e.name.lastIndexOf("."), n = t > -1 ? e.name.slice(t + 1) : "", i = e.type || "application/octet-stream", a = await o("mutation BuiInitiateUpload($input: InitiateUploadInput!) {\n initiateUpload(input: $input) {\n uploadUrl\n session { id }\n }\n }", { input: {
41
+ fileName: e.name,
42
+ extension: n,
43
+ mimeType: i,
44
+ fileSize: String(e.size),
45
+ visibility: "WORKSPACE"
46
+ } }, r), { uploadUrl: c } = a.initiateUpload, u = a.initiateUpload.session.id;
47
+ if (!c) throw Error("The file service did not return an upload URL.");
48
+ await s(c, e, i, l);
49
+ let d = await o("mutation BuiCompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) {\n fileId\n file { id name }\n }\n }", { input: { sessionId: u } }, r);
50
+ return l(100), {
51
+ fileId: d.completeUpload.fileId,
52
+ fileName: d.completeUpload.file?.name ?? e.name
53
+ };
54
+ } catch (e) {
55
+ let t = e instanceof Error ? e.message : "The file could not be uploaded.";
56
+ throw d(t), e instanceof Error ? e : Error(t);
57
+ } finally {
58
+ a(!1);
59
+ }
60
+ }, [r]),
61
+ isUploading: i,
62
+ progress: c,
63
+ error: u,
64
+ reset: f
65
+ };
66
+ }, l = (t) => {
67
+ let { apiGatewayUrl: n } = e();
68
+ return r({
69
+ queryKey: [
70
+ "buildmyiq",
71
+ "file",
72
+ t ?? ""
73
+ ],
74
+ enabled: !!t,
75
+ queryFn: async () => (await o("query BuiGetFile($id: ID!) {\n getFile(id: $id) {\n id\n name\n downloadUrl\n mimeType\n size\n }\n }", { id: t }, n)).getFile
76
+ });
77
+ };
78
+ //#endregion
79
+ export { c as useBuiFileUpload, l as useEcoFile, o as wspaceFetch };
80
+
81
+ //# sourceMappingURL=useBuiFiles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useBuiFiles.js","names":[],"sources":["../../../src/buildmyiq/hooks/useBuiFiles.ts"],"sourcesContent":["/**\n * Buildmyiq — wspace-files-svc REUSE hooks (Integration A).\n * @module buildmyiq/hooks/useBuiFiles\n *\n * Reuses the platform file store (`wspace-files-svc`) WITHOUT any buildmyiq-svc\n * change: the buildmyiq schema already exposes `BuiLesson.mediaFileId` /\n * `BuiCertificate.fileId`; this module lets the UI actually produce a `fileId` via\n * the canonical 3-step upload flow composed into the WORKSPACE supergraph:\n * 1. `initiateUpload(input)` → `{ uploadUrl, session { id } }`\n * 2. HTTP `PUT` the bytes to the returned signed `uploadUrl`\n * 3. `completeUpload(input: { sessionId })` → `{ fileId }`\n * plus `getFile(id)` to resolve an existing evidence/report file's name + URL.\n *\n * Transport mirrors `useBuildmyiqApi` exactly — `graphqlFetch({ gateway:\n * 'workspace', workspaceToken: true })` — so no ApolloProvider is required.\n * These hooks live in a SEPARATE file by design; `useBuildmyiqApi.ts` is\n * left untouched.\n */\n\nimport { useCallback, useState } from 'react';\nimport { useQuery } from '@tanstack/react-query';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\n\nimport { useBuildmyiq } from '../context';\n\n// ============================================================================\n// Shared workspace-gateway transport (reused by the comments + tags hooks too)\n// ============================================================================\n\ntype GraphqlReadCacheGlobals = {\n __graphqlFetchResponseCache?: { clear: () => void };\n __graphqlFetchPendingResponses?: { clear: () => void };\n __boffGraphQLResponseCache?: { clear: () => void };\n};\n\n/**\n * fe-libs `graphqlFetch` coalesces `query` responses in a short-lived in-memory\n * cache. After a write we drop those caches so the invalidation refetch that\n * follows reads fresh data (same remedy `useBuildmyiqApi` applies).\n */\nconst clearGraphqlReadCaches = (): void => {\n const g = globalThis as GraphqlReadCacheGlobals;\n g.__graphqlFetchResponseCache?.clear();\n g.__graphqlFetchPendingResponses?.clear();\n g.__boffGraphQLResponseCache?.clear();\n};\n\n/** Execute a workspace-gateway GraphQL operation with the workspace token. */\nexport const wspaceFetch = async <TData>(\n query: string,\n variables: Record<string, unknown>,\n baseUrl?: string\n): Promise<TData> => {\n const response = await graphqlFetch<TData>({\n gateway: 'workspace',\n baseUrl,\n query,\n variables,\n workspaceToken: true,\n });\n\n if (response.errors?.length) {\n throw new Error(response.errors[0]?.message ?? 'GraphQL request failed');\n }\n if (!response.data) {\n throw new Error('GraphQL response did not include data');\n }\n if (/^\\s*mutation\\b/.test(query)) {\n clearGraphqlReadCaches();\n }\n return response.data;\n};\n\n// ============================================================================\n// File upload (initiate → PUT → complete)\n// ============================================================================\n\nexport interface UploadedBuiFile {\n fileId: string;\n fileName: string;\n}\n\n/** `PUT` the blob to the signed URL, reporting byte-progress via `onProgress`. */\nfunction putWithProgress(\n url: string,\n file: File,\n contentType: string,\n onProgress: (percent: number) => void\n): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhr.open('PUT', url);\n xhr.setRequestHeader('Content-Type', contentType);\n xhr.upload.onprogress = (event) => {\n if (event.lengthComputable) {\n onProgress(Math.round((event.loaded / event.total) * 100));\n }\n };\n xhr.onload = () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve();\n } else {\n reject(new Error(`File storage rejected the upload (HTTP ${xhr.status}).`));\n }\n };\n xhr.onerror = () => reject(new Error('Network error while uploading the file.'));\n xhr.onabort = () => reject(new Error('The upload was cancelled.'));\n xhr.send(file);\n });\n}\n\nexport interface UseBuiFileUploadResult {\n upload: (file: File) => Promise<UploadedBuiFile>;\n isUploading: boolean;\n /** 0–100 byte-progress of the signed-URL PUT. */\n progress: number;\n error: string | null;\n reset: () => void;\n}\n\n/**\n * Imperative upload hook: `await upload(file)` runs the platform 3-step flow and\n * resolves to `{ fileId, fileName }`. `fileId` is what the buildmyiq mutations accept\n * (`buiCreateCourse.coverFileId`, `buiIssueCertificate.fileId`).\n */\nexport const useBuiFileUpload = (): UseBuiFileUploadResult => {\n const { apiGatewayUrl } = useBuildmyiq();\n const [isUploading, setIsUploading] = useState(false);\n const [progress, setProgress] = useState(0);\n const [error, setError] = useState<string | null>(null);\n\n const reset = useCallback(() => {\n setIsUploading(false);\n setProgress(0);\n setError(null);\n }, []);\n\n const upload = useCallback(\n async (file: File): Promise<UploadedBuiFile> => {\n setIsUploading(true);\n setProgress(0);\n setError(null);\n try {\n const dotIndex = file.name.lastIndexOf('.');\n const extension = dotIndex > -1 ? file.name.slice(dotIndex + 1) : '';\n const mimeType = file.type || 'application/octet-stream';\n\n // 1. initiate\n const initiated = await wspaceFetch<{\n initiateUpload: { uploadUrl: string | null; session: { id: string } };\n }>(\n `mutation BuiInitiateUpload($input: InitiateUploadInput!) {\n initiateUpload(input: $input) {\n uploadUrl\n session { id }\n }\n }`,\n {\n input: {\n fileName: file.name,\n extension,\n mimeType,\n fileSize: String(file.size),\n visibility: 'WORKSPACE',\n },\n },\n apiGatewayUrl\n );\n\n const { uploadUrl } = initiated.initiateUpload;\n const sessionId = initiated.initiateUpload.session.id;\n if (!uploadUrl) {\n throw new Error('The file service did not return an upload URL.');\n }\n\n // 2. PUT the bytes\n await putWithProgress(uploadUrl, file, mimeType, setProgress);\n\n // 3. complete\n const completed = await wspaceFetch<{\n completeUpload: { fileId: string; file: { id: string; name: string } | null };\n }>(\n `mutation BuiCompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) {\n fileId\n file { id name }\n }\n }`,\n { input: { sessionId } },\n apiGatewayUrl\n );\n\n setProgress(100);\n return {\n fileId: completed.completeUpload.fileId,\n fileName: completed.completeUpload.file?.name ?? file.name,\n };\n } catch (err) {\n const message = err instanceof Error ? err.message : 'The file could not be uploaded.';\n setError(message);\n throw err instanceof Error ? err : new Error(message);\n } finally {\n setIsUploading(false);\n }\n },\n [apiGatewayUrl]\n );\n\n return { upload, isUploading, progress, error, reset };\n};\n\n// ============================================================================\n// Resolve an existing file (name + download URL) for rendering evidence links\n// ============================================================================\n\nexport interface BuiFileMeta {\n id: string;\n name: string;\n downloadUrl: string | null;\n mimeType: string;\n size: string;\n}\n\nexport const useEcoFile = (id: string | null | undefined) => {\n const { apiGatewayUrl } = useBuildmyiq();\n return useQuery({\n queryKey: ['buildmyiq', 'file', id ?? ''],\n enabled: Boolean(id),\n queryFn: async () => {\n const data = await wspaceFetch<{ getFile: BuiFileMeta | null }>(\n `query BuiGetFile($id: ID!) {\n getFile(id: $id) {\n id\n name\n downloadUrl\n mimeType\n size\n }\n }`,\n { id },\n apiGatewayUrl\n );\n return data.getFile;\n },\n });\n};\n"],"mappings":"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"}
@@ -0,0 +1,89 @@
1
+ import { useBuildmyiq as e } from "../context/BuildmyiqContext.js";
2
+ import "../context/index.js";
3
+ import { wspaceFetch as t } from "./useBuiFiles.js";
4
+ import { useMutation as n, useQuery as r, useQueryClient as i } from "@tanstack/react-query";
5
+ //#region src/buildmyiq/hooks/useBuiTags.ts
6
+ var a = "buildmyiq", o = (e, t) => [
7
+ "buildmyiq",
8
+ "entity-tags",
9
+ e,
10
+ t
11
+ ], s = "id label color key", c = (e) => e.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "tag", l = (n, i) => {
12
+ let { apiGatewayUrl: a } = e();
13
+ return r({
14
+ queryKey: o(n, i ?? ""),
15
+ enabled: !!i,
16
+ queryFn: async () => (await t(`query BuiGetEntityTags($entityType: String!, $entityId: ID!) {
17
+ getEntityTags(entityType: $entityType, entityId: $entityId) {
18
+ id
19
+ tag { ${s} }
20
+ }
21
+ }`, {
22
+ entityType: n,
23
+ entityId: i
24
+ }, a)).getEntityTags
25
+ });
26
+ }, u = (n) => {
27
+ let { apiGatewayUrl: i } = e(), a = n.trim();
28
+ return r({
29
+ queryKey: [
30
+ "buildmyiq",
31
+ "tag-search",
32
+ a
33
+ ],
34
+ enabled: a.length > 0,
35
+ queryFn: async () => (await t(`query BuiSearchTags($query: String!, $limit: Int) {
36
+ searchTags(query: $query, limit: $limit) { ${s} }
37
+ }`, {
38
+ query: a,
39
+ limit: 8
40
+ }, i)).searchTags
41
+ });
42
+ }, d = (r, a) => {
43
+ let { apiGatewayUrl: s } = e(), c = i();
44
+ return n({
45
+ mutationFn: async (e) => (await t("mutation BuiAssignTag($input: AssignTagInput!) {\n assignTag(input: $input) { id }\n }", { input: {
46
+ entityType: r,
47
+ entityId: a,
48
+ tagId: e
49
+ } }, s)).assignTag,
50
+ onSuccess: () => c.invalidateQueries({ queryKey: o(r, a) })
51
+ });
52
+ }, f = (r, a) => {
53
+ let { apiGatewayUrl: s } = e(), c = i();
54
+ return n({
55
+ mutationFn: async (e) => (await t("mutation BuiRemoveTagAssignment($id: ID!) {\n removeTagAssignment(id: $id)\n }", { id: e }, s)).removeTagAssignment,
56
+ onSuccess: () => c.invalidateQueries({ queryKey: o(r, a) })
57
+ });
58
+ }, p = async (e) => {
59
+ let n = await t("query BuiGetVocabulary($key: String!) {\n getVocabularyByKey(key: $key) { id }\n }", { key: a }, e);
60
+ return n.getVocabularyByKey?.id ? n.getVocabularyByKey.id : (await t("mutation BuiCreateVocabulary($input: CreateVocabularyInput!) {\n createVocabulary(input: $input) { id }\n }", { input: {
61
+ key: a,
62
+ name: "Buildmyiq",
63
+ description: "Tags for Buildmyiq sites, species and other records.",
64
+ allowCustomTags: !0
65
+ } }, e)).createVocabulary.id;
66
+ }, m = (r, a) => {
67
+ let { apiGatewayUrl: s } = e(), l = i();
68
+ return n({
69
+ mutationFn: async (e) => {
70
+ let n = e.trim();
71
+ if (!n) throw Error("A tag label is required.");
72
+ let i = await t("mutation BuiCreateTag($input: CreateTagInput!) {\n createTag(input: $input) { id }\n }", { input: {
73
+ vocabularyId: await p(s),
74
+ key: `${c(n)}-${Math.random().toString(36).slice(2, 6)}`,
75
+ label: n
76
+ } }, s);
77
+ return await t("mutation BuiAssignCreatedTag($input: AssignTagInput!) {\n assignTag(input: $input) { id }\n }", { input: {
78
+ entityType: r,
79
+ entityId: a,
80
+ tagId: i.createTag.id
81
+ } }, s), i.createTag;
82
+ },
83
+ onSuccess: () => l.invalidateQueries({ queryKey: o(r, a) })
84
+ });
85
+ };
86
+ //#endregion
87
+ export { d as useEcoAssignTag, m as useEcoCreateAndAssignTag, l as useEcoEntityTags, f as useEcoRemoveTagAssignment, u as useEcoTagSearch };
88
+
89
+ //# sourceMappingURL=useBuiTags.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useBuiTags.js","names":[],"sources":["../../../src/buildmyiq/hooks/useBuiTags.ts"],"sourcesContent":["/**\n * Buildmyiq — wspace-tags-svc UniversalTag REUSE hooks (Integration C).\n * @module buildmyiq/hooks/useBuiTags\n *\n * The buildmyiq svc does NOT expose a tags field, so we drive the platform tag\n * assignment store directly (approach (b) in the reuse audit) — no buildmyiq-svc\n * change. All operations are composed into the WORKSPACE supergraph:\n * - `getEntityTags(entityType, entityId)` → current assignments (display)\n * - `searchTags(query)` → pick an existing tag\n * - `createTag(input)` → create a new tag (needs a vocabulary)\n * - `assignTag(input)` → link tag ↔ entity\n * - `removeTagAssignment(id)` → unlink\n *\n * `createTag` requires a `vocabularyId`; we resolve a stable, workspace-scoped\n * \"buildmyiq\" vocabulary (`getVocabularyByKey`, creating it on first use).\n *\n * Transport reuses `wspaceFetch` from `useBuiFiles`. Lives in a separate file;\n * `useBuildmyiqApi.ts` is untouched.\n */\n\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\n\nimport { useBuildmyiq } from '../context';\nimport { wspaceFetch } from './useBuiFiles';\n\nexport interface BuiTag {\n id: string;\n label: string;\n color: string | null;\n key: string;\n}\n\nexport interface BuiTagAssignment {\n id: string;\n tag: BuiTag;\n}\n\nconst BUI_VOCABULARY_KEY = 'buildmyiq';\n\nconst entityTagsKey = (entityType: string, entityId: string) =>\n ['buildmyiq', 'entity-tags', entityType, entityId] as const;\n\nconst TAG_FIELDS = 'id label color key';\n\nconst slugify = (value: string): string =>\n value\n .toLowerCase()\n .trim()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 48) || 'tag';\n\n/** Current tag assignments for an Buildmyiq entity. */\nexport const useEcoEntityTags = (entityType: string, entityId: string | undefined) => {\n const { apiGatewayUrl } = useBuildmyiq();\n return useQuery({\n queryKey: entityTagsKey(entityType, entityId ?? ''),\n enabled: Boolean(entityId),\n queryFn: async () => {\n const data = await wspaceFetch<{ getEntityTags: BuiTagAssignment[] }>(\n `query BuiGetEntityTags($entityType: String!, $entityId: ID!) {\n getEntityTags(entityType: $entityType, entityId: $entityId) {\n id\n tag { ${TAG_FIELDS} }\n }\n }`,\n { entityType, entityId },\n apiGatewayUrl\n );\n return data.getEntityTags;\n },\n });\n};\n\n/** Search existing tags (enabled only for a non-empty query). */\nexport const useEcoTagSearch = (query: string) => {\n const { apiGatewayUrl } = useBuildmyiq();\n const trimmed = query.trim();\n return useQuery({\n queryKey: ['buildmyiq', 'tag-search', trimmed],\n enabled: trimmed.length > 0,\n queryFn: async () => {\n const data = await wspaceFetch<{ searchTags: BuiTag[] }>(\n `query BuiSearchTags($query: String!, $limit: Int) {\n searchTags(query: $query, limit: $limit) { ${TAG_FIELDS} }\n }`,\n { query: trimmed, limit: 8 },\n apiGatewayUrl\n );\n return data.searchTags;\n },\n });\n};\n\n/** Assign an existing tag to the entity. */\nexport const useEcoAssignTag = (entityType: string, entityId: string) => {\n const { apiGatewayUrl } = useBuildmyiq();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (tagId: string) => {\n const data = await wspaceFetch<{ assignTag: { id: string } }>(\n `mutation BuiAssignTag($input: AssignTagInput!) {\n assignTag(input: $input) { id }\n }`,\n { input: { entityType, entityId, tagId } },\n apiGatewayUrl\n );\n return data.assignTag;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: entityTagsKey(entityType, entityId) }),\n });\n};\n\n/** Remove a tag assignment by its assignment id. */\nexport const useEcoRemoveTagAssignment = (entityType: string, entityId: string) => {\n const { apiGatewayUrl } = useBuildmyiq();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (assignmentId: string) => {\n const data = await wspaceFetch<{ removeTagAssignment: boolean }>(\n `mutation BuiRemoveTagAssignment($id: ID!) {\n removeTagAssignment(id: $id)\n }`,\n { id: assignmentId },\n apiGatewayUrl\n );\n return data.removeTagAssignment;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: entityTagsKey(entityType, entityId) }),\n });\n};\n\n/** Resolve (creating on first use) the workspace-scoped \"buildmyiq\" vocabulary. */\nconst resolveEcoVocabularyId = async (baseUrl?: string): Promise<string> => {\n const existing = await wspaceFetch<{ getVocabularyByKey: { id: string } | null }>(\n `query BuiGetVocabulary($key: String!) {\n getVocabularyByKey(key: $key) { id }\n }`,\n { key: BUI_VOCABULARY_KEY },\n baseUrl\n );\n if (existing.getVocabularyByKey?.id) {\n return existing.getVocabularyByKey.id;\n }\n const created = await wspaceFetch<{ createVocabulary: { id: string } }>(\n `mutation BuiCreateVocabulary($input: CreateVocabularyInput!) {\n createVocabulary(input: $input) { id }\n }`,\n {\n input: {\n key: BUI_VOCABULARY_KEY,\n name: 'Buildmyiq',\n description: 'Tags for Buildmyiq sites, species and other records.',\n allowCustomTags: true,\n },\n },\n baseUrl\n );\n return created.createVocabulary.id;\n};\n\n/** Create a new tag from a free-text label and assign it to the entity. */\nexport const useEcoCreateAndAssignTag = (entityType: string, entityId: string) => {\n const { apiGatewayUrl } = useBuildmyiq();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (label: string) => {\n const trimmed = label.trim();\n if (!trimmed) {\n throw new Error('A tag label is required.');\n }\n const vocabularyId = await resolveEcoVocabularyId(apiGatewayUrl);\n // A unique key avoids collisions with existing tags in the vocabulary;\n // the (non-unique) label is what users see.\n const key = `${slugify(trimmed)}-${Math.random().toString(36).slice(2, 6)}`;\n const created = await wspaceFetch<{ createTag: { id: string } }>(\n `mutation BuiCreateTag($input: CreateTagInput!) {\n createTag(input: $input) { id }\n }`,\n { input: { vocabularyId, key, label: trimmed } },\n apiGatewayUrl\n );\n await wspaceFetch<{ assignTag: { id: string } }>(\n `mutation BuiAssignCreatedTag($input: AssignTagInput!) {\n assignTag(input: $input) { id }\n }`,\n { input: { entityType, entityId, tagId: created.createTag.id } },\n apiGatewayUrl\n );\n return created.createTag;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: entityTagsKey(entityType, entityId) }),\n });\n};\n"],"mappings":";;;;;AAqCA,IAAM,IAAqB,aAErB,KAAiB,GAAoB,MACzC;CAAC;CAAa;CAAe;CAAY;CAAS,EAE9C,IAAa,sBAEb,KAAW,MACf,EACG,aAAa,CACb,MAAM,CACN,QAAQ,eAAe,IAAI,CAC3B,QAAQ,YAAY,GAAG,CACvB,MAAM,GAAG,GAAG,IAAI,OAGR,KAAoB,GAAoB,MAAiC;CACpF,IAAM,EAAE,qBAAkB,GAAc;AACxC,QAAO,EAAS;EACd,UAAU,EAAc,GAAY,KAAY,GAAG;EACnD,SAAS,EAAQ;EACjB,SAAS,aACM,MAAM,EACjB;;;oBAGY,EAAW;;YAGvB;GAAE;GAAY;GAAU,EACxB,EACD,EACW;EAEf,CAAC;GAIS,KAAmB,MAAkB;CAChD,IAAM,EAAE,qBAAkB,GAAc,EAClC,IAAU,EAAM,MAAM;AAC5B,QAAO,EAAS;EACd,UAAU;GAAC;GAAa;GAAc;GAAQ;EAC9C,SAAS,EAAQ,SAAS;EAC1B,SAAS,aACM,MAAM,EACjB;uDAC+C,EAAW;YAE1D;GAAE,OAAO;GAAS,OAAO;GAAG,EAC5B,EACD,EACW;EAEf,CAAC;GAIS,KAAmB,GAAoB,MAAqB;CACvE,IAAM,EAAE,qBAAkB,GAAc,EAClC,IAAK,GAAgB;AAC3B,QAAO,EAAY;EACjB,YAAY,OAAO,OACJ,MAAM,EACjB,0GAGA,EAAE,OAAO;GAAE;GAAY;GAAU;GAAO,EAAE,EAC1C,EACD,EACW;EAEd,iBAAiB,EAAG,kBAAkB,EAAE,UAAU,EAAc,GAAY,EAAS,EAAE,CAAC;EACzF,CAAC;GAIS,KAA6B,GAAoB,MAAqB;CACjF,IAAM,EAAE,qBAAkB,GAAc,EAClC,IAAK,GAAgB;AAC3B,QAAO,EAAY;EACjB,YAAY,OAAO,OACJ,MAAM,EACjB,kGAGA,EAAE,IAAI,GAAc,EACpB,EACD,EACW;EAEd,iBAAiB,EAAG,kBAAkB,EAAE,UAAU,EAAc,GAAY,EAAS,EAAE,CAAC;EACzF,CAAC;GAIE,IAAyB,OAAO,MAAsC;CAC1E,IAAM,IAAW,MAAM,EACrB,8FAGA,EAAE,KAAK,GAAoB,EAC3B,EACD;AAkBD,QAjBI,EAAS,oBAAoB,KACxB,EAAS,mBAAmB,MAErB,MAAM,EACpB,uHAGA,EACE,OAAO;EACL,KAAK;EACL,MAAM;EACN,aAAa;EACb,iBAAiB;EAClB,EACF,EACD,EACD,EACc,iBAAiB;GAIrB,KAA4B,GAAoB,MAAqB;CAChF,IAAM,EAAE,qBAAkB,GAAc,EAClC,IAAK,GAAgB;AAC3B,QAAO,EAAY;EACjB,YAAY,OAAO,MAAkB;GACnC,IAAM,IAAU,EAAM,MAAM;AAC5B,OAAI,CAAC,EACH,OAAU,MAAM,2BAA2B;GAM7C,IAAM,IAAU,MAAM,EACpB,0GAGA,EAAE,OAAO;IAAE,cARQ,MAAM,EAAuB,EAAc;IAQrC,KALf,GAAG,EAAQ,EAAQ,CAAC,GAAG,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,GAAG,EAAE;IAKzC,OAAO;IAAS,EAAE,EAChD,EACD;AAQD,UAPA,MAAM,EACJ,iHAGA,EAAE,OAAO;IAAE;IAAY;IAAU,OAAO,EAAQ,UAAU;IAAI,EAAE,EAChE,EACD,EACM,EAAQ;;EAEjB,iBAAiB,EAAG,kBAAkB,EAAE,UAAU,EAAc,GAAY,EAAS,EAAE,CAAC;EACzF,CAAC"}