@burdenoff/microfe-technospam 2026.827.1 → 2026.829.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 (68) hide show
  1. package/dist/index.js +4 -3
  2. package/dist/technospam/TechnoSpamRoot.js +4 -4
  3. package/dist/technospam/TechnoSpamRoutes.d.ts.map +1 -1
  4. package/dist/technospam/TechnoSpamRoutes.js +89 -88
  5. package/dist/technospam/TechnoSpamRoutes.js.map +1 -1
  6. package/dist/technospam/components/PageLayout.d.ts.map +1 -1
  7. package/dist/technospam/components/PageLayout.js +24 -23
  8. package/dist/technospam/components/PageLayout.js.map +1 -1
  9. package/dist/technospam/components/StateViews.d.ts +1 -1
  10. package/dist/technospam/components/StateViews.d.ts.map +1 -1
  11. package/dist/technospam/components/StateViews.js +28 -26
  12. package/dist/technospam/components/StateViews.js.map +1 -1
  13. package/dist/technospam/components/TagInput.d.ts +1 -1
  14. package/dist/technospam/components/TagInput.d.ts.map +1 -1
  15. package/dist/technospam/components/TagInput.js +39 -34
  16. package/dist/technospam/components/TagInput.js.map +1 -1
  17. package/dist/technospam/components/TechnoSpamErrorBoundary.d.ts +25 -0
  18. package/dist/technospam/components/TechnoSpamErrorBoundary.d.ts.map +1 -0
  19. package/dist/technospam/components/TechnoSpamErrorBoundary.js +31 -0
  20. package/dist/technospam/components/TechnoSpamErrorBoundary.js.map +1 -0
  21. package/dist/technospam/components/index.d.ts +1 -0
  22. package/dist/technospam/components/index.d.ts.map +1 -1
  23. package/dist/technospam/components/index.js +1 -0
  24. package/dist/technospam/hooks/useTr.d.ts +17 -0
  25. package/dist/technospam/hooks/useTr.d.ts.map +1 -0
  26. package/dist/technospam/hooks/useTr.js +12 -0
  27. package/dist/technospam/hooks/useTr.js.map +1 -0
  28. package/dist/technospam/index.d.ts +1 -0
  29. package/dist/technospam/index.d.ts.map +1 -1
  30. package/dist/technospam/index.js +1 -0
  31. package/dist/technospam/pages/AdminAdminDashboardPagePage.js +7 -7
  32. package/dist/technospam/pages/AdminAdminDashboardPagePage.js.map +1 -1
  33. package/dist/technospam/pages/AnalyticsAnalyticsPagePage.d.ts.map +1 -1
  34. package/dist/technospam/pages/AnalyticsAnalyticsPagePage.js +198 -189
  35. package/dist/technospam/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
  36. package/dist/technospam/pages/ApplicationsPortfolioPagePage.js +2 -2
  37. package/dist/technospam/pages/ApplicationsPortfolioPagePage.js.map +1 -1
  38. package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.js +1 -1
  39. package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.js.map +1 -1
  40. package/dist/technospam/pages/ApplicationsProblemsPagePage.d.ts.map +1 -1
  41. package/dist/technospam/pages/ApplicationsProblemsPagePage.js +165 -163
  42. package/dist/technospam/pages/ApplicationsProblemsPagePage.js.map +1 -1
  43. package/dist/technospam/pages/CoursesCoursesPagePage.d.ts.map +1 -1
  44. package/dist/technospam/pages/CoursesCoursesPagePage.js +117 -113
  45. package/dist/technospam/pages/CoursesCoursesPagePage.js.map +1 -1
  46. package/dist/technospam/pages/CoursesMyLearningPagePage.js +1 -1
  47. package/dist/technospam/pages/CoursesMyLearningPagePage.js.map +1 -1
  48. package/dist/technospam/pages/DashboardDashboardPagePage.d.ts.map +1 -1
  49. package/dist/technospam/pages/DashboardDashboardPagePage.js +211 -193
  50. package/dist/technospam/pages/DashboardDashboardPagePage.js.map +1 -1
  51. package/dist/technospam/pages/HackathonsHackathonDetailsPagePage.js +1 -1
  52. package/dist/technospam/pages/HackathonsHackathonDetailsPagePage.js.map +1 -1
  53. package/dist/technospam/pages/HackathonsHackathonsPagePage.d.ts.map +1 -1
  54. package/dist/technospam/pages/HackathonsHackathonsPagePage.js +110 -110
  55. package/dist/technospam/pages/HackathonsHackathonsPagePage.js.map +1 -1
  56. package/dist/technospam/pages/MentorshipMentorsPagePage.d.ts.map +1 -1
  57. package/dist/technospam/pages/MentorshipMentorsPagePage.js +174 -160
  58. package/dist/technospam/pages/MentorshipMentorsPagePage.js.map +1 -1
  59. package/dist/technospam/pages/MentorshipSessionsPagePage.d.ts.map +1 -1
  60. package/dist/technospam/pages/MentorshipSessionsPagePage.js +156 -151
  61. package/dist/technospam/pages/MentorshipSessionsPagePage.js.map +1 -1
  62. package/dist/technospam/pages/StudyPlannerStudyPlannerPagePage.js +1 -1
  63. package/dist/technospam/pages/StudyPlannerStudyPlannerPagePage.js.map +1 -1
  64. package/dist/technospam/utils/i18n.js +1 -1
  65. package/dist/technospam/utils/i18n.js.map +1 -1
  66. package/dist/test/setup.d.ts +2 -0
  67. package/dist/test/setup.d.ts.map +1 -0
  68. package/package.json +8 -2
@@ -1,62 +1,64 @@
1
- import { AlertTriangle as e, Inbox as t } from "lucide-react";
2
- import { jsx as n, jsxs as r } from "react/jsx-runtime";
3
- import { Skeleton as i } from "@burdenoff/fe-libs/ui";
1
+ import { useTr as e } from "../hooks/useTr.js";
2
+ import { AlertTriangle as t, Inbox as n } from "lucide-react";
3
+ import { jsx as r, jsxs as i } from "react/jsx-runtime";
4
+ import { Skeleton as a } from "@burdenoff/fe-libs/ui";
4
5
  //#region src/technospam/components/StateViews.tsx
5
- function a({ title: e, description: i, action: a, icon: o }) {
6
- return /* @__PURE__ */ r("div", {
6
+ function o({ title: e, description: t, action: a, icon: o }) {
7
+ return /* @__PURE__ */ i("div", {
7
8
  className: "flex flex-col items-center justify-center gap-3 rounded-2xl border border-border-seam bg-bg-surface px-6 py-14 text-center",
8
9
  children: [
9
- /* @__PURE__ */ n("span", {
10
+ /* @__PURE__ */ r("span", {
10
11
  className: "flex h-14 w-14 items-center justify-center rounded-2xl bg-bg-sunken text-text-muted",
11
- children: o ?? /* @__PURE__ */ n(t, { size: 26 })
12
+ children: o ?? /* @__PURE__ */ r(n, { size: 26 })
12
13
  }),
13
- /* @__PURE__ */ n("h3", {
14
+ /* @__PURE__ */ r("h3", {
14
15
  className: "text-base font-semibold text-text-primary",
15
16
  children: e
16
17
  }),
17
- i ? /* @__PURE__ */ n("p", {
18
+ t ? /* @__PURE__ */ r("p", {
18
19
  className: "max-w-md text-sm text-text-secondary",
19
- children: i
20
+ children: t
20
21
  }) : null,
21
- a ? /* @__PURE__ */ n("div", {
22
+ a ? /* @__PURE__ */ r("div", {
22
23
  className: "mt-2",
23
24
  children: a
24
25
  }) : null
25
26
  ]
26
27
  });
27
28
  }
28
- function o({ title: t = "Couldn't load this yet", error: i }) {
29
- return /* @__PURE__ */ r("div", {
29
+ function s({ title: n, error: a }) {
30
+ let o = e();
31
+ return /* @__PURE__ */ i("div", {
30
32
  className: "flex flex-col items-center justify-center gap-3 rounded-2xl border border-border-seam bg-bg-surface px-6 py-14 text-center",
31
33
  children: [
32
- /* @__PURE__ */ n("span", {
34
+ /* @__PURE__ */ r("span", {
33
35
  className: "flex h-14 w-14 items-center justify-center rounded-2xl bg-status-error-bg-subtle text-status-error-text",
34
- children: /* @__PURE__ */ n(e, { size: 26 })
36
+ children: /* @__PURE__ */ r(t, { size: 26 })
35
37
  }),
36
- /* @__PURE__ */ n("h3", {
38
+ /* @__PURE__ */ r("h3", {
37
39
  className: "text-base font-semibold text-text-primary",
38
- children: t
40
+ children: n ?? o("technospam.common.errorTitle", "Couldn't load this yet")
39
41
  }),
40
- /* @__PURE__ */ n("p", {
42
+ /* @__PURE__ */ r("p", {
41
43
  className: "max-w-md text-sm text-text-secondary",
42
- children: i?.message ?? "Please try again in a moment."
44
+ children: a?.message ?? o("technospam.common.tryAgain", "Please try again in a moment.")
43
45
  })
44
46
  ]
45
47
  });
46
48
  }
47
- function s({ count: e = 6, height: t = "h-56" }) {
48
- return /* @__PURE__ */ n("div", {
49
+ function c({ count: e = 6, height: t = "h-56" }) {
50
+ return /* @__PURE__ */ r("div", {
49
51
  className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3",
50
- children: Array.from({ length: e }).map((e, r) => /* @__PURE__ */ n(i, { className: `${t} w-full rounded-xl` }, r))
52
+ children: Array.from({ length: e }).map((e, n) => /* @__PURE__ */ r(a, { className: `${t} w-full rounded-xl` }, n))
51
53
  });
52
54
  }
53
- function c({ count: e = 4, height: t = "h-20" }) {
54
- return /* @__PURE__ */ n("div", {
55
+ function l({ count: e = 4, height: t = "h-20" }) {
56
+ return /* @__PURE__ */ r("div", {
55
57
  className: "space-y-3",
56
- children: Array.from({ length: e }).map((e, r) => /* @__PURE__ */ n(i, { className: `${t} w-full rounded-xl` }, r))
58
+ children: Array.from({ length: e }).map((e, n) => /* @__PURE__ */ r(a, { className: `${t} w-full rounded-xl` }, n))
57
59
  });
58
60
  }
59
61
  //#endregion
60
- export { a as EmptyView, o as ErrorView, s as LoadingGrid, c as LoadingRows };
62
+ export { o as EmptyView, s as ErrorView, c as LoadingGrid, l as LoadingRows };
61
63
 
62
64
  //# sourceMappingURL=StateViews.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"StateViews.js","names":[],"sources":["../../../src/technospam/components/StateViews.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { AlertTriangle, Inbox } from 'lucide-react';\nimport { Skeleton } from '@burdenoff/fe-libs/ui';\n\n/**\n * Self-contained empty state (does not depend on host-shell illustration assets).\n * A soft glass medallion + lucide icon, a title, an optional description and action.\n */\nexport function EmptyView({\n title,\n description,\n action,\n icon,\n}: {\n title: string;\n description?: string;\n action?: ReactNode;\n icon?: ReactNode;\n}) {\n return (\n <div className=\"flex flex-col items-center justify-center gap-3 rounded-2xl border border-border-seam bg-bg-surface px-6 py-14 text-center\">\n <span className=\"flex h-14 w-14 items-center justify-center rounded-2xl bg-bg-sunken text-text-muted\">\n {icon ?? <Inbox size={26} />}\n </span>\n <h3 className=\"text-base font-semibold text-text-primary\">{title}</h3>\n {description ? <p className=\"max-w-md text-sm text-text-secondary\">{description}</p> : null}\n {action ? <div className=\"mt-2\">{action}</div> : null}\n </div>\n );\n}\n\n/** Consistent error state, surfaced from a GraphQL/network error. */\nexport function ErrorView({\n title = \"Couldn't load this yet\",\n error,\n}: {\n title?: string;\n error?: { message?: string };\n}) {\n return (\n <div className=\"flex flex-col items-center justify-center gap-3 rounded-2xl border border-border-seam bg-bg-surface px-6 py-14 text-center\">\n <span className=\"flex h-14 w-14 items-center justify-center rounded-2xl bg-status-error-bg-subtle text-status-error-text\">\n <AlertTriangle size={26} />\n </span>\n <h3 className=\"text-base font-semibold text-text-primary\">{title}</h3>\n <p className=\"max-w-md text-sm text-text-secondary\">\n {error?.message ?? 'Please try again in a moment.'}\n </p>\n </div>\n );\n}\n\n/** Skeleton grid for card lists. */\nexport function LoadingGrid({ count = 6, height = 'h-56' }: { count?: number; height?: string }) {\n return (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {Array.from({ length: count }).map((_, i) => (\n <Skeleton key={i} className={`${height} w-full rounded-xl`} />\n ))}\n </div>\n );\n}\n\n/** Skeleton rows for stacked lists. */\nexport function LoadingRows({ count = 4, height = 'h-20' }: { count?: number; height?: string }) {\n return (\n <div className=\"space-y-3\">\n {Array.from({ length: count }).map((_, i) => (\n <Skeleton key={i} className={`${height} w-full rounded-xl`} />\n ))}\n </div>\n );\n}\n"],"mappings":";;;;AAQA,SAAgB,EAAU,EACxB,UACA,gBACA,WACA,WAMC;AACD,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,QAAD;IAAM,WAAU;cACb,KAAQ,kBAAC,GAAD,EAAO,MAAM,IAAM,CAAA;IACvB,CAAA;GACP,kBAAC,MAAD;IAAI,WAAU;cAA6C;IAAW,CAAA;GACrE,IAAc,kBAAC,KAAD;IAAG,WAAU;cAAwC;IAAgB,CAAA,GAAG;GACtF,IAAS,kBAAC,OAAD;IAAK,WAAU;cAAQ;IAAa,CAAA,GAAG;GAC7C;;;AAKV,SAAgB,EAAU,EACxB,WAAQ,0BACR,YAIC;AACD,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,QAAD;IAAM,WAAU;cACd,kBAAC,GAAD,EAAe,MAAM,IAAM,CAAA;IACtB,CAAA;GACP,kBAAC,MAAD;IAAI,WAAU;cAA6C;IAAW,CAAA;GACtE,kBAAC,KAAD;IAAG,WAAU;cACV,GAAO,WAAW;IACjB,CAAA;GACA;;;AAKV,SAAgB,EAAY,EAAE,WAAQ,GAAG,YAAS,UAA+C;AAC/F,QACE,kBAAC,OAAD;EAAK,WAAU;YACZ,MAAM,KAAK,EAAE,QAAQ,GAAO,CAAC,CAAC,KAAK,GAAG,MACrC,kBAAC,GAAD,EAAkB,WAAW,GAAG,EAAO,qBAAuB,EAA/C,EAA+C,CAC9D;EACE,CAAA;;AAKV,SAAgB,EAAY,EAAE,WAAQ,GAAG,YAAS,UAA+C;AAC/F,QACE,kBAAC,OAAD;EAAK,WAAU;YACZ,MAAM,KAAK,EAAE,QAAQ,GAAO,CAAC,CAAC,KAAK,GAAG,MACrC,kBAAC,GAAD,EAAkB,WAAW,GAAG,EAAO,qBAAuB,EAA/C,EAA+C,CAC9D;EACE,CAAA"}
1
+ {"version":3,"file":"StateViews.js","names":[],"sources":["../../../src/technospam/components/StateViews.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { AlertTriangle, Inbox } from 'lucide-react';\nimport { Skeleton } from '@burdenoff/fe-libs/ui';\nimport { useTr } from '../hooks/useTr';\n\n/**\n * Self-contained empty state (does not depend on host-shell illustration assets).\n * A soft glass medallion + lucide icon, a title, an optional description and action.\n */\nexport function EmptyView({\n title,\n description,\n action,\n icon,\n}: {\n title: string;\n description?: string;\n action?: ReactNode;\n icon?: ReactNode;\n}) {\n return (\n <div className=\"flex flex-col items-center justify-center gap-3 rounded-2xl border border-border-seam bg-bg-surface px-6 py-14 text-center\">\n <span className=\"flex h-14 w-14 items-center justify-center rounded-2xl bg-bg-sunken text-text-muted\">\n {icon ?? <Inbox size={26} />}\n </span>\n <h3 className=\"text-base font-semibold text-text-primary\">{title}</h3>\n {description ? <p className=\"max-w-md text-sm text-text-secondary\">{description}</p> : null}\n {action ? <div className=\"mt-2\">{action}</div> : null}\n </div>\n );\n}\n\n/** Consistent error state, surfaced from a GraphQL/network error. */\nexport function ErrorView({ title, error }: { title?: string; error?: { message?: string } }) {\n const tr = useTr();\n return (\n <div className=\"flex flex-col items-center justify-center gap-3 rounded-2xl border border-border-seam bg-bg-surface px-6 py-14 text-center\">\n <span className=\"flex h-14 w-14 items-center justify-center rounded-2xl bg-status-error-bg-subtle text-status-error-text\">\n <AlertTriangle size={26} />\n </span>\n <h3 className=\"text-base font-semibold text-text-primary\">\n {title ?? tr('technospam.common.errorTitle', \"Couldn't load this yet\")}\n </h3>\n <p className=\"max-w-md text-sm text-text-secondary\">\n {error?.message ?? tr('technospam.common.tryAgain', 'Please try again in a moment.')}\n </p>\n </div>\n );\n}\n\n/** Skeleton grid for card lists. */\nexport function LoadingGrid({ count = 6, height = 'h-56' }: { count?: number; height?: string }) {\n return (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {Array.from({ length: count }).map((_, i) => (\n <Skeleton key={i} className={`${height} w-full rounded-xl`} />\n ))}\n </div>\n );\n}\n\n/** Skeleton rows for stacked lists. */\nexport function LoadingRows({ count = 4, height = 'h-20' }: { count?: number; height?: string }) {\n return (\n <div className=\"space-y-3\">\n {Array.from({ length: count }).map((_, i) => (\n <Skeleton key={i} className={`${height} w-full rounded-xl`} />\n ))}\n </div>\n );\n}\n"],"mappings":";;;;;AASA,SAAgB,EAAU,EACxB,UACA,gBACA,WACA,WAMC;AACD,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,QAAD;IAAM,WAAU;cACb,KAAQ,kBAAC,GAAD,EAAO,MAAM,IAAM,CAAA;IACvB,CAAA;GACP,kBAAC,MAAD;IAAI,WAAU;cAA6C;IAAW,CAAA;GACrE,IAAc,kBAAC,KAAD;IAAG,WAAU;cAAwC;IAAgB,CAAA,GAAG;GACtF,IAAS,kBAAC,OAAD;IAAK,WAAU;cAAQ;IAAa,CAAA,GAAG;GAC7C;;;AAKV,SAAgB,EAAU,EAAE,UAAO,YAA2D;CAC5F,IAAM,IAAK,GAAO;AAClB,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,QAAD;IAAM,WAAU;cACd,kBAAC,GAAD,EAAe,MAAM,IAAM,CAAA;IACtB,CAAA;GACP,kBAAC,MAAD;IAAI,WAAU;cACX,KAAS,EAAG,gCAAgC,yBAAyB;IACnE,CAAA;GACL,kBAAC,KAAD;IAAG,WAAU;cACV,GAAO,WAAW,EAAG,8BAA8B,gCAAgC;IAClF,CAAA;GACA;;;AAKV,SAAgB,EAAY,EAAE,WAAQ,GAAG,YAAS,UAA+C;AAC/F,QACE,kBAAC,OAAD;EAAK,WAAU;YACZ,MAAM,KAAK,EAAE,QAAQ,GAAO,CAAC,CAAC,KAAK,GAAG,MACrC,kBAAC,GAAD,EAAkB,WAAW,GAAG,EAAO,qBAAuB,EAA/C,EAA+C,CAC9D;EACE,CAAA;;AAKV,SAAgB,EAAY,EAAE,WAAQ,GAAG,YAAS,UAA+C;AAC/F,QACE,kBAAC,OAAD;EAAK,WAAU;YACZ,MAAM,KAAK,EAAE,QAAQ,GAAO,CAAC,CAAC,KAAK,GAAG,MACrC,kBAAC,GAAD,EAAkB,WAAW,GAAG,EAAO,qBAAuB,EAA/C,EAA+C,CAC9D;EACE,CAAA"}
@@ -13,6 +13,6 @@ interface TagInputProps {
13
13
  * arrays stored on Tec* entities while the workspace-wide @burdenoff/microfe-tags
14
14
  * taxonomy is being adopted product-wide.
15
15
  */
16
- export declare function TagInput({ value, onChange, placeholder, className, maxTags, }: TagInputProps): import("react").JSX.Element;
16
+ export declare function TagInput({ value, onChange, placeholder, className, maxTags }: TagInputProps): import("react").JSX.Element;
17
17
  export {};
18
18
  //# sourceMappingURL=TagInput.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"TagInput.d.ts","sourceRoot":"","sources":["../../../src/technospam/components/TagInput.tsx"],"names":[],"mappings":"AAKA,UAAU,aAAa;IACrB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,QAAQ,EACR,WAA0B,EAC1B,SAAS,EACT,OAAO,GACR,EAAE,aAAa,+BA4Df"}
1
+ {"version":3,"file":"TagInput.d.ts","sourceRoot":"","sources":["../../../src/technospam/components/TagInput.tsx"],"names":[],"mappings":"AAMA,UAAU,aAAa;IACrB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,aAAa,+BA6D3F"}
@@ -1,58 +1,63 @@
1
- import { cn as e } from "../utils/cn.js";
2
- import { useCallback as t, useState as n } from "react";
3
- import { Plus as r, X as i } from "lucide-react";
4
- import { jsx as a, jsxs as o } from "react/jsx-runtime";
5
- import { Button as s, Input as c } from "@burdenoff/fe-libs/ui";
1
+ import { useTr as e } from "../hooks/useTr.js";
2
+ import { cn as t } from "../utils/cn.js";
3
+ import { useCallback as n, useState as r } from "react";
4
+ import { Plus as i, X as a } from "lucide-react";
5
+ import { jsx as o, jsxs as s } from "react/jsx-runtime";
6
+ import { Button as c, Input as l } from "@burdenoff/fe-libs/ui";
6
7
  //#region src/technospam/components/TagInput.tsx
7
- function l({ value: l, onChange: u, placeholder: d = "Add a tag…", className: f, maxTags: p }) {
8
- let [m, h] = n(""), g = t(() => {
9
- let e = m.split(",").map((e) => e.trim()).filter(Boolean);
8
+ function u({ value: u, onChange: d, placeholder: f, className: p, maxTags: m }) {
9
+ let h = e(), [g, _] = r(""), v = n(() => {
10
+ let e = g.split(",").map((e) => e.trim()).filter(Boolean);
10
11
  if (e.length === 0) return;
11
- let t = Array.from(new Set([...l, ...e]));
12
- u(p ? t.slice(0, p) : t), h("");
12
+ let t = Array.from(new Set([...u, ...e]));
13
+ d(m ? t.slice(0, m) : t), _("");
13
14
  }, [
14
- m,
15
- l,
15
+ g,
16
16
  u,
17
- p
18
- ]), _ = t((e) => {
19
- u(l.filter((t) => t !== e));
20
- }, [l, u]);
21
- return /* @__PURE__ */ o("div", {
22
- className: e("space-y-2", f),
23
- children: [/* @__PURE__ */ a("div", {
17
+ d,
18
+ m
19
+ ]), y = n((e) => {
20
+ d(u.filter((t) => t !== e));
21
+ }, [u, d]);
22
+ return /* @__PURE__ */ s("div", {
23
+ className: t("space-y-2", p),
24
+ children: [/* @__PURE__ */ o("div", {
24
25
  className: "flex flex-wrap items-center gap-1.5",
25
- children: l.map((e) => /* @__PURE__ */ o("span", {
26
+ children: u.map((e) => /* @__PURE__ */ s("span", {
26
27
  className: "inline-flex items-center gap-1 rounded-full border border-border-seam bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-secondary",
27
- children: [e, /* @__PURE__ */ a("button", {
28
+ children: [e, /* @__PURE__ */ o("button", {
28
29
  type: "button",
29
- onClick: () => _(e),
30
+ onClick: () => y(e),
30
31
  className: "rounded-full p-0.5 hover:bg-bg-surface hover:text-status-error-text focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[var(--color-focus-ring)]",
31
- "aria-label": `Remove ${e}`,
32
- children: /* @__PURE__ */ a(i, { size: 10 })
32
+ "aria-label": h("technospam.common.removeTagAria", "Remove {{tag}}", { tag: e }),
33
+ children: /* @__PURE__ */ o(a, { size: 10 })
33
34
  })]
34
35
  }, e))
35
- }), /* @__PURE__ */ o("div", {
36
+ }), /* @__PURE__ */ s("div", {
36
37
  className: "flex items-center gap-2",
37
- children: [/* @__PURE__ */ a(c, {
38
- value: m,
39
- onChange: (e) => h(e.target.value),
38
+ children: [/* @__PURE__ */ o(l, {
39
+ value: g,
40
+ onChange: (e) => _(e.target.value),
40
41
  onKeyDown: (e) => {
41
- e.key === "Enter" && (e.preventDefault(), g());
42
+ e.key === "Enter" && (e.preventDefault(), v());
42
43
  },
43
- placeholder: d,
44
+ placeholder: f ?? h("technospam.common.addTag", "Add a tag…"),
44
45
  className: "flex-1"
45
- }), /* @__PURE__ */ o(s, {
46
+ }), /* @__PURE__ */ s(c, {
46
47
  type: "button",
47
48
  variant: "outline",
48
49
  size: "sm",
49
- onClick: g,
50
- children: [/* @__PURE__ */ a(r, { size: 14 }), " Add"]
50
+ onClick: v,
51
+ children: [
52
+ /* @__PURE__ */ o(i, { size: 14 }),
53
+ " ",
54
+ h("technospam.common.add", "Add")
55
+ ]
51
56
  })]
52
57
  })]
53
58
  });
54
59
  }
55
60
  //#endregion
56
- export { l as TagInput };
61
+ export { u as TagInput };
57
62
 
58
63
  //# sourceMappingURL=TagInput.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"TagInput.js","names":[],"sources":["../../../src/technospam/components/TagInput.tsx"],"sourcesContent":["import { useState, useCallback } from 'react';\nimport { X, Plus } from 'lucide-react';\nimport { Input, Button } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\n\ninterface TagInputProps {\n value: string[];\n onChange: (tags: string[]) => void;\n placeholder?: string;\n className?: string;\n maxTags?: number;\n}\n\n/**\n * Small controlled tag input. Renders existing tags as removable chips and\n * commits a new tag on Enter or when the add button is pressed.\n *\n * This is the TechnoSpam-local tag picker; it works with the legacy string-tag\n * arrays stored on Tec* entities while the workspace-wide @burdenoff/microfe-tags\n * taxonomy is being adopted product-wide.\n */\nexport function TagInput({\n value,\n onChange,\n placeholder = 'Add a tag…',\n className,\n maxTags,\n}: TagInputProps) {\n const [draft, setDraft] = useState('');\n\n const commit = useCallback(() => {\n const next = draft\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean);\n if (next.length === 0) return;\n const merged = Array.from(new Set([...value, ...next]));\n onChange(maxTags ? merged.slice(0, maxTags) : merged);\n setDraft('');\n }, [draft, value, onChange, maxTags]);\n\n const remove = useCallback(\n (tag: string) => {\n onChange(value.filter((t) => t !== tag));\n },\n [value, onChange]\n );\n\n return (\n <div className={cn('space-y-2', className)}>\n <div className=\"flex flex-wrap items-center gap-1.5\">\n {value.map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center gap-1 rounded-full border border-border-seam bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-secondary\"\n >\n {tag}\n <button\n type=\"button\"\n onClick={() => remove(tag)}\n className=\"rounded-full p-0.5 hover:bg-bg-surface hover:text-status-error-text focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[var(--color-focus-ring)]\"\n aria-label={`Remove ${tag}`}\n >\n <X size={10} />\n </button>\n </span>\n ))}\n </div>\n <div className=\"flex items-center gap-2\">\n <Input\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n commit();\n }\n }}\n placeholder={placeholder}\n className=\"flex-1\"\n />\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={commit}>\n <Plus size={14} /> Add\n </Button>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;AAqBA,SAAgB,EAAS,EACvB,UACA,aACA,iBAAc,cACd,cACA,cACgB;CAChB,IAAM,CAAC,GAAO,KAAY,EAAS,GAAG,EAEhC,IAAS,QAAkB;EAC/B,IAAM,IAAO,EACV,MAAM,IAAI,CACV,KAAK,MAAM,EAAE,MAAM,CAAC,CACpB,OAAO,QAAQ;AAClB,MAAI,EAAK,WAAW,EAAG;EACvB,IAAM,IAAS,MAAM,KAAK,IAAI,IAAI,CAAC,GAAG,GAAO,GAAG,EAAK,CAAC,CAAC;AAEvD,EADA,EAAS,IAAU,EAAO,MAAM,GAAG,EAAQ,GAAG,EAAO,EACrD,EAAS,GAAG;IACX;EAAC;EAAO;EAAO;EAAU;EAAQ,CAAC,EAE/B,IAAS,GACZ,MAAgB;AACf,IAAS,EAAM,QAAQ,MAAM,MAAM,EAAI,CAAC;IAE1C,CAAC,GAAO,EAAS,CAClB;AAED,QACE,kBAAC,OAAD;EAAK,WAAW,EAAG,aAAa,EAAU;YAA1C,CACE,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAM,KAAK,MACV,kBAAC,QAAD;IAEE,WAAU;cAFZ,CAIG,GACD,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAO,EAAI;KAC1B,WAAU;KACV,cAAY,UAAU;eAEtB,kBAAC,GAAD,EAAG,MAAM,IAAM,CAAA;KACR,CAAA,CACJ;MAZA,EAYA,CACP;GACE,CAAA,EACN,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD;IACE,OAAO;IACP,WAAW,MAAM,EAAS,EAAE,OAAO,MAAM;IACzC,YAAY,MAAM;AAChB,KAAI,EAAE,QAAQ,YACZ,EAAE,gBAAgB,EAClB,GAAQ;;IAGC;IACb,WAAU;IACV,CAAA,EACF,kBAAC,GAAD;IAAQ,MAAK;IAAS,SAAQ;IAAU,MAAK;IAAK,SAAS;cAA3D,CACE,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA,EAAA,OACX;MACL;KACF"}
1
+ {"version":3,"file":"TagInput.js","names":[],"sources":["../../../src/technospam/components/TagInput.tsx"],"sourcesContent":["import { useState, useCallback } from 'react';\nimport { X, Plus } from 'lucide-react';\nimport { Input, Button } from '@burdenoff/fe-libs/ui';\nimport { useTr } from '../hooks/useTr';\nimport { cn } from '../utils/cn';\n\ninterface TagInputProps {\n value: string[];\n onChange: (tags: string[]) => void;\n placeholder?: string;\n className?: string;\n maxTags?: number;\n}\n\n/**\n * Small controlled tag input. Renders existing tags as removable chips and\n * commits a new tag on Enter or when the add button is pressed.\n *\n * This is the TechnoSpam-local tag picker; it works with the legacy string-tag\n * arrays stored on Tec* entities while the workspace-wide @burdenoff/microfe-tags\n * taxonomy is being adopted product-wide.\n */\nexport function TagInput({ value, onChange, placeholder, className, maxTags }: TagInputProps) {\n const tr = useTr();\n const [draft, setDraft] = useState('');\n\n const commit = useCallback(() => {\n const next = draft\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean);\n if (next.length === 0) return;\n const merged = Array.from(new Set([...value, ...next]));\n onChange(maxTags ? merged.slice(0, maxTags) : merged);\n setDraft('');\n }, [draft, value, onChange, maxTags]);\n\n const remove = useCallback(\n (tag: string) => {\n onChange(value.filter((t) => t !== tag));\n },\n [value, onChange]\n );\n\n return (\n <div className={cn('space-y-2', className)}>\n <div className=\"flex flex-wrap items-center gap-1.5\">\n {value.map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center gap-1 rounded-full border border-border-seam bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-secondary\"\n >\n {tag}\n <button\n type=\"button\"\n onClick={() => remove(tag)}\n className=\"rounded-full p-0.5 hover:bg-bg-surface hover:text-status-error-text focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[var(--color-focus-ring)]\"\n aria-label={tr('technospam.common.removeTagAria', 'Remove {{tag}}', { tag })}\n >\n <X size={10} />\n </button>\n </span>\n ))}\n </div>\n <div className=\"flex items-center gap-2\">\n <Input\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n commit();\n }\n }}\n placeholder={placeholder ?? tr('technospam.common.addTag', 'Add a tag…')}\n className=\"flex-1\"\n />\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={commit}>\n <Plus size={14} /> {tr('technospam.common.add', 'Add')}\n </Button>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;AAsBA,SAAgB,EAAS,EAAE,UAAO,aAAU,gBAAa,cAAW,cAA0B;CAC5F,IAAM,IAAK,GAAO,EACZ,CAAC,GAAO,KAAY,EAAS,GAAG,EAEhC,IAAS,QAAkB;EAC/B,IAAM,IAAO,EACV,MAAM,IAAI,CACV,KAAK,MAAM,EAAE,MAAM,CAAC,CACpB,OAAO,QAAQ;AAClB,MAAI,EAAK,WAAW,EAAG;EACvB,IAAM,IAAS,MAAM,KAAK,IAAI,IAAI,CAAC,GAAG,GAAO,GAAG,EAAK,CAAC,CAAC;AAEvD,EADA,EAAS,IAAU,EAAO,MAAM,GAAG,EAAQ,GAAG,EAAO,EACrD,EAAS,GAAG;IACX;EAAC;EAAO;EAAO;EAAU;EAAQ,CAAC,EAE/B,IAAS,GACZ,MAAgB;AACf,IAAS,EAAM,QAAQ,MAAM,MAAM,EAAI,CAAC;IAE1C,CAAC,GAAO,EAAS,CAClB;AAED,QACE,kBAAC,OAAD;EAAK,WAAW,EAAG,aAAa,EAAU;YAA1C,CACE,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAM,KAAK,MACV,kBAAC,QAAD;IAEE,WAAU;cAFZ,CAIG,GACD,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAO,EAAI;KAC1B,WAAU;KACV,cAAY,EAAG,mCAAmC,kBAAkB,EAAE,QAAK,CAAC;eAE5E,kBAAC,GAAD,EAAG,MAAM,IAAM,CAAA;KACR,CAAA,CACJ;MAZA,EAYA,CACP;GACE,CAAA,EACN,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD;IACE,OAAO;IACP,WAAW,MAAM,EAAS,EAAE,OAAO,MAAM;IACzC,YAAY,MAAM;AAChB,KAAI,EAAE,QAAQ,YACZ,EAAE,gBAAgB,EAClB,GAAQ;;IAGZ,aAAa,KAAe,EAAG,4BAA4B,aAAa;IACxE,WAAU;IACV,CAAA,EACF,kBAAC,GAAD;IAAQ,MAAK;IAAS,SAAQ;IAAU,MAAK;IAAK,SAAS;cAA3D;KACE,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA;;KAAE,EAAG,yBAAyB,MAAM;KAC/C;MACL;KACF"}
@@ -0,0 +1,25 @@
1
+ import { Component, ReactNode } from 'react';
2
+
3
+ interface Props {
4
+ children: ReactNode;
5
+ title?: string;
6
+ }
7
+ interface State {
8
+ hasError: boolean;
9
+ error: Error | null;
10
+ }
11
+ /**
12
+ * Per-route error boundary for the TechnoSpam MFE.
13
+ *
14
+ * Catches render errors inside TechnoSpam pages (e.g. a GraphQL response with
15
+ * an unexpectedly null connection field) and renders an explicit error state
16
+ * inside the module chrome instead of letting the error bubble to the root
17
+ * boundary or leaving the content area blank.
18
+ */
19
+ export declare class TechnoSpamErrorBoundary extends Component<Props, State> {
20
+ constructor(props: Props);
21
+ static getDerivedStateFromError(error: Error): State;
22
+ render(): ReactNode;
23
+ }
24
+ export {};
25
+ //# sourceMappingURL=TechnoSpamErrorBoundary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TechnoSpamErrorBoundary.d.ts","sourceRoot":"","sources":["../../../src/technospam/components/TechnoSpamErrorBoundary.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAGlD,UAAU,KAAK;IACb,QAAQ,EAAE,SAAS,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,KAAK;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,qBAAa,uBAAwB,SAAQ,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC;gBACtD,KAAK,EAAE,KAAK;IAKxB,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,KAAK,GAAG,KAAK;IAIpD,MAAM,IAAI,SAAS;CAapB"}
@@ -0,0 +1,31 @@
1
+ import { ErrorView as e } from "./StateViews.js";
2
+ import { Component as t } from "react";
3
+ import { jsx as n } from "react/jsx-runtime";
4
+ //#region src/technospam/components/TechnoSpamErrorBoundary.tsx
5
+ var r = class extends t {
6
+ constructor(e) {
7
+ super(e), this.state = {
8
+ hasError: !1,
9
+ error: null
10
+ };
11
+ }
12
+ static getDerivedStateFromError(e) {
13
+ return {
14
+ hasError: !0,
15
+ error: e
16
+ };
17
+ }
18
+ render() {
19
+ return this.state.hasError ? /* @__PURE__ */ n("div", {
20
+ className: "flex h-full min-h-[50vh] items-center justify-center p-6",
21
+ children: /* @__PURE__ */ n(e, {
22
+ title: this.props.title ?? "Couldn't load this page",
23
+ error: this.state.error ?? void 0
24
+ })
25
+ }) : this.props.children;
26
+ }
27
+ };
28
+ //#endregion
29
+ export { r as TechnoSpamErrorBoundary };
30
+
31
+ //# sourceMappingURL=TechnoSpamErrorBoundary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TechnoSpamErrorBoundary.js","names":[],"sources":["../../../src/technospam/components/TechnoSpamErrorBoundary.tsx"],"sourcesContent":["import { Component, type ReactNode } from 'react';\nimport { ErrorView } from './StateViews';\n\ninterface Props {\n children: ReactNode;\n title?: string;\n}\n\ninterface State {\n hasError: boolean;\n error: Error | null;\n}\n\n/**\n * Per-route error boundary for the TechnoSpam MFE.\n *\n * Catches render errors inside TechnoSpam pages (e.g. a GraphQL response with\n * an unexpectedly null connection field) and renders an explicit error state\n * inside the module chrome instead of letting the error bubble to the root\n * boundary or leaving the content area blank.\n */\nexport class TechnoSpamErrorBoundary extends Component<Props, State> {\n constructor(props: Props) {\n super(props);\n this.state = { hasError: false, error: null };\n }\n\n static getDerivedStateFromError(error: Error): State {\n return { hasError: true, error };\n }\n\n render(): ReactNode {\n if (this.state.hasError) {\n return (\n <div className=\"flex h-full min-h-[50vh] items-center justify-center p-6\">\n <ErrorView\n title={this.props.title ?? \"Couldn't load this page\"}\n error={this.state.error ?? undefined}\n />\n </div>\n );\n }\n return this.props.children;\n }\n}\n"],"mappings":";;;;AAqBA,IAAa,IAAb,cAA6C,EAAwB;CACnE,YAAY,GAAc;AAExB,EADA,MAAM,EAAM,EACZ,KAAK,QAAQ;GAAE,UAAU;GAAO,OAAO;GAAM;;CAG/C,OAAO,yBAAyB,GAAqB;AACnD,SAAO;GAAE,UAAU;GAAM;GAAO;;CAGlC,SAAoB;AAWlB,SAVI,KAAK,MAAM,WAEX,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,GAAD;IACE,OAAO,KAAK,MAAM,SAAS;IAC3B,OAAO,KAAK,MAAM,SAAS,KAAA;IAC3B,CAAA;GACE,CAAA,GAGH,KAAK,MAAM"}
@@ -8,4 +8,5 @@ export { TagStrip } from './TagStrip';
8
8
  export { TagInput } from './TagInput';
9
9
  export { UpgradeGate, PremiumBadge } from './UpgradeGate';
10
10
  export { CodePanel, IoBlock, TestResultsPanel } from './SubmissionViews';
11
+ export { TechnoSpamErrorBoundary } from './TechnoSpamErrorBoundary';
11
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/technospam/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC9E,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/technospam/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC9E,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC"}
@@ -8,3 +8,4 @@ import "./TagStrip.js";
8
8
  import "./TagInput.js";
9
9
  import "./UpgradeGate.js";
10
10
  import "./SubmissionViews.js";
11
+ import "./TechnoSpamErrorBoundary.js";
@@ -0,0 +1,17 @@
1
+ /**
2
+ * useTr — translation hook with fallback semantics.
3
+ *
4
+ * Wraps `useI18n().t()` so that when a key has no translation (i.e. the
5
+ * provider returns the key itself), the caller's English fallback is used
6
+ * instead. This keeps consumer call sites consistent and avoids leaking
7
+ * untranslated keys into the UI.
8
+ *
9
+ * Supports optional interpolation params using `{{key}}` placeholders.
10
+ *
11
+ * @example
12
+ * const tr = useTr();
13
+ * return <h1>{tr('technospam.dashboard.title', 'Dashboard')}</h1>;
14
+ * return <p>{tr('technospam.delete.confirm', 'Delete {{count}} items?', { count: 5 })}</p>;
15
+ */
16
+ export declare function useTr(): (key: string, fallback: string, params?: Record<string, string | number>) => string;
17
+ //# sourceMappingURL=useTr.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTr.d.ts","sourceRoot":"","sources":["../../../src/technospam/hooks/useTr.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,KAAK,IAAI,CACvB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KACrC,MAAM,CAOV"}
@@ -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/technospam/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/technospam/hooks/useTr.ts"],"sourcesContent":["import { useCallback } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { tWithFallback } from '../utils/i18n';\n\n/**\n * useTr — translation hook with fallback semantics.\n *\n * Wraps `useI18n().t()` so that when a key has no translation (i.e. the\n * provider returns the key itself), the caller's English fallback is used\n * instead. This keeps consumer call sites consistent and avoids leaking\n * untranslated keys into the UI.\n *\n * Supports optional interpolation params using `{{key}}` placeholders.\n *\n * @example\n * const tr = useTr();\n * return <h1>{tr('technospam.dashboard.title', 'Dashboard')}</h1>;\n * return <p>{tr('technospam.delete.confirm', 'Delete {{count}} items?', { count: 5 })}</p>;\n */\nexport function useTr(): (\n key: string,\n fallback: string,\n params?: Record<string, string | number>\n) => string {\n const { t } = useI18n();\n return useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>): string =>\n tWithFallback(t, key, fallback, params),\n [t]\n );\n}\n"],"mappings":";;;;AAmBA,SAAgB,IAIJ;CACV,IAAM,EAAE,SAAM,GAAS;AACvB,QAAO,GACJ,GAAa,GAAkB,MAC9B,EAAc,GAAG,GAAK,GAAU,EAAO,EACzC,CAAC,EAAE,CACJ"}
@@ -2,5 +2,6 @@ export { TechnoSpamRoot } from './TechnoSpamRoot';
2
2
  export type { TechnoSpamRootProps, NavItem, FooterItem } from './types';
3
3
  export { useTechnoSpam, useTechnoSpamNavigate } from './providers/TechnoSpamProvider';
4
4
  export * from './services/createSandboxAssistantTransport';
5
+ export { useTr } from './hooks/useTr';
5
6
  export { useTechnoSpamAnalytics } from './hooks/useTechnoSpamAnalytics';
6
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/technospam/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,YAAY,EAAE,mBAAmB,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACxE,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAOtF,cAAc,4CAA4C,CAAC;AAG3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/technospam/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,YAAY,EAAE,mBAAmB,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACxE,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAOtF,cAAc,4CAA4C,CAAC;AAG3D,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAGtC,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import "./providers/TechnoSpamProvider.js";
2
2
  import "./hooks/useTechnoSpamAnalytics.js";
3
+ import "./hooks/useTr.js";
3
4
  import "./TechnoSpamRoot.js";
4
5
  import "./services/createSandboxAssistantTransport.js";
@@ -199,7 +199,7 @@ function Ie({ lesson: e, onSaved: t, onCancel: n }) {
199
199
  });
200
200
  }
201
201
  function Le({ courseId: e }) {
202
- let { data: t, loading: n, refetch: r } = O(e), [i, { loading: a }] = T(), [o] = z(), [c, l] = B(null), [d, f] = B(null), [p, h] = B(""), [g, _] = B("ARTICLE"), [y, b] = B(""), [x, S] = B(""), [C, w] = B(!1), E = [...t?.tecListLessons.items ?? []].sort((e, t) => e.position - t.position), D = async (e) => {
202
+ let { data: t, loading: n, refetch: r } = O(e), [i, { loading: a }] = T(), [o] = z(), [c, l] = B(null), [d, f] = B(null), [p, h] = B(""), [g, _] = B("ARTICLE"), [y, b] = B(""), [x, S] = B(""), [C, w] = B(!1), E = [...t?.tecListLessons?.items ?? []].sort((e, t) => e.position - t.position), D = async (e) => {
203
203
  f(e.id);
204
204
  try {
205
205
  let t = await o({ variables: {
@@ -435,7 +435,7 @@ function Re({ submission: e, busy: t, onGrade: n, onRegrade: r }) {
435
435
  });
436
436
  }
437
437
  function ze({ problemId: e }) {
438
- let { data: t, loading: n, refetch: r } = L({ problemId: e }), [i] = D(), [a] = N(), [o, s] = B(null), [c, l] = B(!1), d = t?.tecListSubmissions.items ?? [], f = c ? d.filter((e) => e.verdict === "PENDING") : d, p = d.filter((e) => e.verdict === "PENDING").length, h = async (e, t) => {
438
+ let { data: t, loading: n, refetch: r } = L({ problemId: e }), [i] = D(), [a] = N(), [o, s] = B(null), [c, l] = B(!1), d = t?.tecListSubmissions?.items ?? [], f = c ? d.filter((e) => e.verdict === "PENDING") : d, p = d.filter((e) => e.verdict === "PENDING").length, h = async (e, t) => {
439
439
  s(e);
440
440
  try {
441
441
  (await i({ variables: {
@@ -498,7 +498,7 @@ function ze({ problemId: e }) {
498
498
  });
499
499
  }
500
500
  function Be({ hackathon: e }) {
501
- let { data: t, loading: n, refetch: r } = R(e.id), [i] = P(), [a, o] = B(null), [s, c] = B({}), l = t?.tecListTeams.items ?? [], u = async (e, t) => {
501
+ let { data: t, loading: n, refetch: r } = R(e.id), [i] = P(), [a, o] = B(null), [s, c] = B({}), l = t?.tecListTeams?.items ?? [], u = async (e, t) => {
502
502
  let n = Math.max(0, Number(s[e] ?? "0") || 0);
503
503
  o(e);
504
504
  try {
@@ -624,7 +624,7 @@ function Be({ hackathon: e }) {
624
624
  });
625
625
  }
626
626
  function Ve() {
627
- let { data: e, loading: t, error: n, refetch: r } = S({}), [o, { loading: c }] = C(), [l] = j(), [g] = b(), [_, y] = B(!1), [x, w] = B(null), [T, E] = B(null), [D, ee] = B(""), [O, k] = B(""), [te, A] = B(""), [M, N] = B(""), [P, F] = B("BEGINNER"), [I, L] = B(""), [R, z] = B([]), V = e?.tecListCourses.items ?? [], U = () => {
627
+ let { data: e, loading: t, error: n, refetch: r } = S({}), [o, { loading: c }] = C(), [l] = j(), [g] = b(), [_, y] = B(!1), [x, w] = B(null), [T, E] = B(null), [D, ee] = B(""), [O, k] = B(""), [te, A] = B(""), [M, N] = B(""), [P, F] = B("BEGINNER"), [I, L] = B(""), [R, z] = B([]), V = e?.tecListCourses?.items ?? [], U = () => {
628
628
  ee(""), k(""), A(""), N(""), F("BEGINNER"), L(""), z([]), y(!1);
629
629
  }, re = async () => {
630
630
  let e = D.trim();
@@ -819,7 +819,7 @@ function Ve() {
819
819
  });
820
820
  }
821
821
  function He() {
822
- let { data: e, loading: t, error: n, refetch: r } = A({}), [a, { loading: o }] = E(), [s] = M(), [c, l] = B(!1), [h, g] = B(null), [_, y] = B(null), [b, x] = B(""), [S, C] = B(""), [w, T] = B(""), [D, ee] = B(""), [O, k] = B("EASY"), [te, j] = B(""), [N, P] = B(""), [F, I] = B(""), L = e?.tecListProblems.items ?? [], R = () => {
822
+ let { data: e, loading: t, error: n, refetch: r } = A({}), [a, { loading: o }] = E(), [s] = M(), [c, l] = B(!1), [h, g] = B(null), [_, y] = B(null), [b, x] = B(""), [S, C] = B(""), [w, T] = B(""), [D, ee] = B(""), [O, k] = B("EASY"), [te, j] = B(""), [N, P] = B(""), [F, I] = B(""), L = e?.tecListProblems?.items ?? [], R = () => {
823
823
  x(""), C(""), T(""), ee(""), k("EASY"), j(""), P(""), I(""), l(!1);
824
824
  }, z = async () => {
825
825
  let e = b.trim();
@@ -1002,7 +1002,7 @@ function He() {
1002
1002
  });
1003
1003
  }
1004
1004
  function Ue() {
1005
- let { data: e, loading: t, error: n, refetch: r } = ee({}), [a, { loading: s }] = w(), [l] = te(), [u] = F(), [g] = I(), [_] = x(), [y, b] = B(!1), [S, C] = B(null), [T, E] = B(null), [D, O] = B(""), [k, A] = B(""), [j, M] = B(""), [N, P] = B(""), [L, R] = B(""), [z, ne] = B(""), [H, U] = B(""), [re, ae] = B(""), [oe, ce] = B([]), W = e?.tecListHackathons.items ?? [], ue = () => {
1005
+ let { data: e, loading: t, error: n, refetch: r } = ee({}), [a, { loading: s }] = w(), [l] = te(), [u] = F(), [g] = I(), [_] = x(), [y, b] = B(!1), [S, C] = B(null), [T, E] = B(null), [D, O] = B(""), [k, A] = B(""), [j, M] = B(""), [N, P] = B(""), [L, R] = B(""), [z, ne] = B(""), [H, U] = B(""), [re, ae] = B(""), [oe, ce] = B([]), W = e?.tecListHackathons?.items ?? [], ue = () => {
1006
1006
  O(""), A(""), M(""), P(""), R(""), ne(""), U(""), ae(""), ce([]), b(!1);
1007
1007
  }, fe = async () => {
1008
1008
  let e = D.trim();
@@ -1215,7 +1215,7 @@ function Ue() {
1215
1215
  });
1216
1216
  }
1217
1217
  function We() {
1218
- let { data: e, loading: t, error: n, refetch: r } = k({}), [a] = y(), [o, s] = B(null), c = e?.tecListMentors.items ?? [], u = c.filter((e) => e.status === "PENDING").length, h = async (e) => {
1218
+ let { data: e, loading: t, error: n, refetch: r } = k({}), [a] = y(), [o, s] = B(null), c = e?.tecListMentors?.items ?? [], u = c.filter((e) => e.status === "PENDING").length, h = async (e) => {
1219
1219
  s(e);
1220
1220
  try {
1221
1221
  (await a({ variables: { id: e } })).data?.tecApproveMentor && (v.success("Mentor approved", "They can now accept mentees."), await r());