@burdenoff/microfe-technospam 2026.731.1 → 2026.827.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/node_modules/recharts/es6/cartesian/Brush.js.map +1 -1
  2. package/dist/node_modules/recharts/es6/cartesian/ErrorBar.js.map +1 -1
  3. package/dist/node_modules/recharts/es6/cartesian/ReferenceArea.js.map +1 -1
  4. package/dist/node_modules/recharts/es6/cartesian/ReferenceDot.js.map +1 -1
  5. package/dist/node_modules/recharts/es6/cartesian/ReferenceLine.js.map +1 -1
  6. package/dist/node_modules/recharts/es6/cartesian/XAxis.js.map +1 -1
  7. package/dist/node_modules/recharts/es6/cartesian/YAxis.js.map +1 -1
  8. package/dist/node_modules/recharts/es6/component/ResponsiveContainer.js.map +1 -1
  9. package/dist/node_modules/sonner/dist/index.js.map +1 -1
  10. package/dist/technospam/TechnoSpamRoutes.js +1 -1
  11. package/dist/technospam/TechnoSpamRoutes.js.map +1 -1
  12. package/dist/technospam/api/technospamApi.d.ts +19 -0
  13. package/dist/technospam/api/technospamApi.d.ts.map +1 -1
  14. package/dist/technospam/api/technospamApi.js +171 -150
  15. package/dist/technospam/api/technospamApi.js.map +1 -1
  16. package/dist/technospam/components/SubmissionViews.d.ts +31 -0
  17. package/dist/technospam/components/SubmissionViews.d.ts.map +1 -0
  18. package/dist/technospam/components/SubmissionViews.js +149 -0
  19. package/dist/technospam/components/SubmissionViews.js.map +1 -0
  20. package/dist/technospam/components/index.d.ts +1 -0
  21. package/dist/technospam/components/index.d.ts.map +1 -1
  22. package/dist/technospam/components/index.js +1 -0
  23. package/dist/technospam/pages/AdminAdminDashboardPagePage.d.ts.map +1 -1
  24. package/dist/technospam/pages/AdminAdminDashboardPagePage.js +725 -500
  25. package/dist/technospam/pages/AdminAdminDashboardPagePage.js.map +1 -1
  26. package/dist/technospam/pages/AnalyticsAnalyticsPagePage.d.ts.map +1 -1
  27. package/dist/technospam/pages/AnalyticsAnalyticsPagePage.js +343 -262
  28. package/dist/technospam/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
  29. package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.d.ts.map +1 -1
  30. package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.js +97 -235
  31. package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.js.map +1 -1
  32. package/dist/technospam/pages/CoursesCourseDetailsPagePage.d.ts.map +1 -1
  33. package/dist/technospam/pages/CoursesCourseDetailsPagePage.js +294 -203
  34. package/dist/technospam/pages/CoursesCourseDetailsPagePage.js.map +1 -1
  35. package/dist/technospam/pages/CoursesMyLearningPagePage.d.ts.map +1 -1
  36. package/dist/technospam/pages/CoursesMyLearningPagePage.js +2 -2
  37. package/dist/technospam/pages/CoursesMyLearningPagePage.js.map +1 -1
  38. package/dist/technospam/pages/HackathonsHackathonDetailsPagePage.js +1 -1
  39. package/dist/technospam/pages/HackathonsHackathonDetailsPagePage.js.map +1 -1
  40. package/dist/technospam/pages/MentorshipMentorProfilePagePage.js +2 -3
  41. package/dist/technospam/pages/MentorshipMentorProfilePagePage.js.map +1 -1
  42. package/dist/technospam/pages/StudyPlannerStudyPlannerPagePage.d.ts.map +1 -1
  43. package/dist/technospam/pages/StudyPlannerStudyPlannerPagePage.js +180 -121
  44. package/dist/technospam/pages/StudyPlannerStudyPlannerPagePage.js.map +1 -1
  45. package/dist/technospam/providers/TechnoSpamProvider.js +1 -1
  46. package/dist/technospam/providers/TechnoSpamProvider.js.map +1 -1
  47. package/package.json +5 -5
@@ -1,266 +1,325 @@
1
1
  import { useTechnoSpamNavigate as e } from "../providers/TechnoSpamProvider.js";
2
2
  import { PageLayout as t } from "../components/PageLayout.js";
3
3
  import { StatCard as n } from "../components/StatCard.js";
4
- import { formatDate as r } from "../utils/format.js";
5
- import { StatusBadge as i } from "../components/StatusBadge.js";
6
- import { EmptyView as a, ErrorView as o, LoadingRows as s } from "../components/StateViews.js";
4
+ import { formatDate as r, formatDuration as i } from "../utils/format.js";
5
+ import { StatusBadge as a } from "../components/StatusBadge.js";
6
+ import { EmptyView as o, ErrorView as s, LoadingRows as c } from "../components/StateViews.js";
7
7
  import "../components/index.js";
8
- import { notify as c } from "../lib/toast.js";
9
- import { useArchiveStudyPlan as l, useGenerateStudyPlan as u, useMyStudyPlansQuery as d, useUpdateStudyPlanStepStatus as f } from "../api/technospamApi.js";
10
- import { useState as p } from "react";
11
- import { Archive as m, Calendar as h, CheckCircle2 as g, Circle as _, Clock as v, ListChecks as y, Sparkles as b, Target as x } from "lucide-react";
12
- import { jsx as S, jsxs as C } from "react/jsx-runtime";
13
- import { Button as w, GlassCard as T, Input as E } from "@burdenoff/fe-libs/ui";
14
- import { nativeConfirm as D } from "@burdenoff/fe-libs/shared/native";
8
+ import { notify as l } from "../lib/toast.js";
9
+ import { useArchiveStudyPlan as u, useGenerateStudyPlan as d, useMyStudyPlansQuery as f, useUpdateStudyPlanStepStatus as p } from "../api/technospamApi.js";
10
+ import { useState as m } from "react";
11
+ import { Archive as h, ArrowUpRight as g, Calendar as _, CheckCircle2 as v, Circle as y, Clock as b, ListChecks as x, Sparkles as S, Target as C, Timer as w, TrendingUp as T } from "lucide-react";
12
+ import { jsx as E, jsxs as D } from "react/jsx-runtime";
13
+ import { Button as O, GlassCard as k, Input as A } from "@burdenoff/fe-libs/ui";
14
+ import { nativeConfirm as j } from "@burdenoff/fe-libs/shared/native";
15
15
  //#region src/technospam/pages/StudyPlannerStudyPlannerPagePage.tsx
16
- var O = 1, k = 52;
17
- function A({ step: e, onMark: t }) {
18
- let n = e.status === "COMPLETED", r = e.status === "SKIPPED";
19
- return /* @__PURE__ */ C("div", {
16
+ var M = 1, N = 52;
17
+ function P(e) {
18
+ return e.kind === "COURSE" ? `/courses/course-details-page?id=${e.targetId}` : `/applications/problem-details-page?id=${e.targetId}`;
19
+ }
20
+ function F(e) {
21
+ return e.steps.reduce((e, t) => e + t.estimatedMinutes, 0);
22
+ }
23
+ function I({ step: e, onMark: t, onOpen: n }) {
24
+ let r = e.status === "COMPLETED", o = e.status === "SKIPPED";
25
+ return /* @__PURE__ */ D("div", {
20
26
  className: "flex flex-col gap-2 rounded-lg border border-border-seam bg-bg-surface p-3 sm:flex-row sm:items-start sm:justify-between",
21
- children: [/* @__PURE__ */ C("div", {
27
+ children: [/* @__PURE__ */ D("div", {
22
28
  className: "flex min-w-0 gap-3",
23
- children: [/* @__PURE__ */ S("button", {
29
+ children: [/* @__PURE__ */ E("button", {
24
30
  type: "button",
25
31
  onClick: () => t("COMPLETED"),
26
- disabled: n,
32
+ disabled: r,
27
33
  className: "mt-0.5 shrink-0 text-text-muted transition-colors hover:text-status-success-text disabled:cursor-default",
28
- "aria-label": n ? "Completed" : "Mark complete",
29
- children: n ? /* @__PURE__ */ S(g, {
34
+ "aria-label": r ? "Completed" : "Mark complete",
35
+ children: r ? /* @__PURE__ */ E(v, {
30
36
  size: 18,
31
37
  className: "text-status-success-text"
32
- }) : /* @__PURE__ */ S(_, { size: 18 })
33
- }), /* @__PURE__ */ C("div", {
38
+ }) : /* @__PURE__ */ E(y, { size: 18 })
39
+ }), /* @__PURE__ */ D("div", {
34
40
  className: "min-w-0",
35
- children: [/* @__PURE__ */ C("div", {
41
+ children: [/* @__PURE__ */ D("div", {
36
42
  className: "flex flex-wrap items-center gap-2",
37
43
  children: [
38
- /* @__PURE__ */ S("span", {
39
- className: `text-sm font-medium ${n || r ? "text-text-muted line-through" : "text-text-primary"}`,
40
- children: e.title
44
+ /* @__PURE__ */ D("button", {
45
+ type: "button",
46
+ onClick: n,
47
+ className: `group inline-flex items-center gap-1 text-left text-sm font-medium transition-colors hover:text-accent-primary ${r || o ? "text-text-muted line-through" : "text-text-primary"}`,
48
+ children: [e.title, /* @__PURE__ */ E(g, {
49
+ size: 13,
50
+ "aria-hidden": !0,
51
+ className: "shrink-0 text-text-muted transition-colors group-hover:text-accent-primary"
52
+ })]
41
53
  }),
42
- /* @__PURE__ */ S("span", {
54
+ /* @__PURE__ */ E("span", {
43
55
  className: "rounded-full border border-border-seam px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-text-muted",
44
56
  children: e.kind
45
57
  }),
46
- /* @__PURE__ */ C("span", {
58
+ /* @__PURE__ */ D("span", {
47
59
  className: "text-xs text-text-muted",
48
60
  children: ["Week ", e.scheduledWeek]
61
+ }),
62
+ /* @__PURE__ */ D("span", {
63
+ className: "inline-flex items-center gap-1 text-xs text-text-muted",
64
+ children: [
65
+ /* @__PURE__ */ E(b, { size: 11 }),
66
+ " ",
67
+ i(e.estimatedMinutes)
68
+ ]
49
69
  })
50
70
  ]
51
- }), /* @__PURE__ */ S("p", {
71
+ }), /* @__PURE__ */ E("p", {
52
72
  className: "mt-1 text-xs text-text-secondary",
53
73
  children: e.rationale
54
74
  })]
55
75
  })]
56
- }), !n && !r ? /* @__PURE__ */ S(w, {
57
- variant: "ghost",
58
- size: "sm",
59
- onClick: () => t("SKIPPED"),
60
- className: "shrink-0",
61
- children: "Skip"
62
- }) : /* @__PURE__ */ S(i, { status: e.status })]
76
+ }), /* @__PURE__ */ E("div", {
77
+ className: "flex shrink-0 items-center gap-2",
78
+ children: !r && !o ? /* @__PURE__ */ E(O, {
79
+ variant: "ghost",
80
+ size: "sm",
81
+ onClick: () => t("SKIPPED"),
82
+ children: "Skip"
83
+ }) : /* @__PURE__ */ E(a, { status: e.status })
84
+ })]
63
85
  });
64
86
  }
65
- function j({ plan: e, onArchived: t }) {
66
- let [n] = f(), [a, { loading: o }] = l(), [s, u] = p(e.status === "ACTIVE"), d = async (e, t) => {
67
- await n({ variables: { input: {
87
+ function L({ plan: e, onArchived: t, onOpenStep: n }) {
88
+ let [o] = p(), [s, { loading: c }] = u(), [d, f] = m(e.status === "ACTIVE"), g = async (e, t) => {
89
+ await o({ variables: { input: {
68
90
  stepId: e,
69
91
  status: t
70
92
  } } });
71
93
  };
72
- return /* @__PURE__ */ C(T, {
94
+ return /* @__PURE__ */ D(k, {
73
95
  className: "space-y-4 p-4 sm:p-5",
74
96
  children: [
75
- /* @__PURE__ */ C("div", {
97
+ /* @__PURE__ */ D("div", {
76
98
  className: "flex flex-wrap items-start justify-between gap-3",
77
- children: [/* @__PURE__ */ C("div", {
99
+ children: [/* @__PURE__ */ D("div", {
78
100
  className: "min-w-0",
79
- children: [/* @__PURE__ */ C("div", {
101
+ children: [/* @__PURE__ */ D("div", {
80
102
  className: "flex flex-wrap items-center gap-2",
81
103
  children: [
82
- /* @__PURE__ */ S("h3", {
104
+ /* @__PURE__ */ E("h3", {
83
105
  className: "text-sm font-semibold text-text-primary",
84
106
  children: e.goal
85
107
  }),
86
- /* @__PURE__ */ S(i, { status: e.status }),
87
- e.topic ? /* @__PURE__ */ S("span", {
108
+ /* @__PURE__ */ E(a, { status: e.status }),
109
+ e.topic ? /* @__PURE__ */ E("span", {
88
110
  className: "rounded-full bg-accent-teal/15 px-2 py-0.5 text-[11px] font-medium text-accent-teal",
89
111
  children: e.topic
90
112
  }) : null
91
113
  ]
92
- }), /* @__PURE__ */ C("div", {
114
+ }), /* @__PURE__ */ D("div", {
93
115
  className: "mt-1 flex flex-wrap items-center gap-3 text-xs text-text-muted",
94
116
  children: [
95
- /* @__PURE__ */ C("span", {
117
+ /* @__PURE__ */ D("span", {
96
118
  className: "inline-flex items-center gap-1",
97
119
  children: [
98
- /* @__PURE__ */ S(h, { size: 12 }),
120
+ /* @__PURE__ */ E(_, { size: 12 }),
99
121
  " ",
100
122
  e.timeframeWeeks,
101
123
  "-week plan"
102
124
  ]
103
125
  }),
104
- e.targetCompletionDate ? /* @__PURE__ */ C("span", {
126
+ e.targetCompletionDate ? /* @__PURE__ */ D("span", {
105
127
  className: "inline-flex items-center gap-1",
106
128
  children: [
107
- /* @__PURE__ */ S(v, { size: 12 }),
129
+ /* @__PURE__ */ E(b, { size: 12 }),
108
130
  " target ",
109
131
  r(e.targetCompletionDate)
110
132
  ]
111
133
  }) : null,
112
- /* @__PURE__ */ C("span", {
134
+ /* @__PURE__ */ D("span", {
113
135
  className: "inline-flex items-center gap-1",
114
136
  children: [
115
- /* @__PURE__ */ S(y, { size: 12 }),
137
+ /* @__PURE__ */ E(x, { size: 12 }),
116
138
  " ",
139
+ e.progressPct,
140
+ "% · ",
117
141
  e.completedSteps,
118
- "/",
142
+ " of",
143
+ " ",
119
144
  e.totalSteps,
120
- " steps done"
145
+ " steps"
146
+ ]
147
+ }),
148
+ /* @__PURE__ */ D("span", {
149
+ className: "inline-flex items-center gap-1",
150
+ children: [
151
+ /* @__PURE__ */ E(w, { size: 12 }),
152
+ " ",
153
+ i(F(e)),
154
+ " planned"
121
155
  ]
122
156
  })
123
157
  ]
124
158
  })]
125
- }), /* @__PURE__ */ C("div", {
159
+ }), /* @__PURE__ */ D("div", {
126
160
  className: "flex shrink-0 items-center gap-2",
127
- children: [/* @__PURE__ */ S(w, {
161
+ children: [/* @__PURE__ */ E(O, {
128
162
  variant: "ghost",
129
163
  size: "sm",
130
- onClick: () => u((e) => !e),
131
- children: s ? "Hide steps" : "Show steps"
132
- }), e.status === "ARCHIVED" ? null : /* @__PURE__ */ C(w, {
164
+ onClick: () => f((e) => !e),
165
+ children: d ? "Hide steps" : "Show steps"
166
+ }), e.status === "ARCHIVED" ? null : /* @__PURE__ */ D(O, {
133
167
  variant: "outline",
134
168
  size: "sm",
135
169
  onClick: async () => {
136
- await D({
170
+ await j({
137
171
  title: "Archive plan",
138
172
  message: `Archive the plan for "${e.goal}"? You can always generate a new one.`,
139
173
  okButtonTitle: "Archive",
140
174
  cancelButtonTitle: "Cancel"
141
- }) && (await a({ variables: { id: e.id } })).data?.tecArchiveStudyPlan && (c.success("Plan archived"), t());
175
+ }) && (await s({ variables: { id: e.id } })).data?.tecArchiveStudyPlan && (l.success("Plan archived"), t());
142
176
  },
143
- disabled: o,
144
- children: [/* @__PURE__ */ S(m, { size: 14 }), " Archive"]
177
+ disabled: c,
178
+ children: [/* @__PURE__ */ E(h, { size: 14 }), " Archive"]
145
179
  })]
146
180
  })]
147
181
  }),
148
- /* @__PURE__ */ S("div", {
182
+ /* @__PURE__ */ E("div", {
149
183
  className: "h-1.5 w-full overflow-hidden rounded-full bg-bg-sunken",
150
- children: /* @__PURE__ */ S("div", {
184
+ children: /* @__PURE__ */ E("div", {
151
185
  className: "h-full rounded-full bg-gradient-to-r from-accent-primary via-accent-indigo to-accent-purple transition-all duration-500",
152
186
  style: { width: `${e.progressPct}%` }
153
187
  })
154
188
  }),
155
- s ? /* @__PURE__ */ S("div", {
189
+ d ? /* @__PURE__ */ E("div", {
156
190
  className: "space-y-2",
157
- children: e.steps.map((e) => /* @__PURE__ */ S(A, {
191
+ children: e.steps.map((e) => /* @__PURE__ */ E(I, {
158
192
  step: e,
159
- onMark: (t) => void d(e.id, t)
193
+ onMark: (t) => void g(e.id, t),
194
+ onOpen: () => n(e)
160
195
  }, e.id))
161
196
  }) : null
162
197
  ]
163
198
  });
164
199
  }
165
- function M() {
166
- let r = e(), { data: i, loading: l, error: f, refetch: m } = d(), [h, { loading: g }] = u(), [_, v] = p(""), [D, A] = p(""), [M, N] = p("4"), P = i?.tecMyStudyPlans.items ?? [], F = P.filter((e) => e.status !== "ARCHIVED");
167
- return /* @__PURE__ */ C(t, {
200
+ function R() {
201
+ let r = e(), { data: a, loading: u, error: p, refetch: h } = f(), [g, { loading: _ }] = d(), [y, b] = m(""), [j, I] = m(""), [R, z] = m("4"), B = a?.tecMyStudyPlans.items ?? [], V = B.filter((e) => e.status === "ACTIVE"), H = B.filter((e) => e.status !== "ARCHIVED"), U = H.reduce((e, t) => e + t.completedSteps, 0), W = H.reduce((e, t) => e + t.totalSteps, 0), G = H.reduce((e, t) => e + F(t), 0), K = B.filter((e) => e.status === "COMPLETED").length, q = (e) => r(P(e));
202
+ return /* @__PURE__ */ D(t, {
168
203
  hero: !0,
169
204
  title: "Study Planner",
170
205
  description: "Tell us your goal and timeframe — we'll sequence real courses and practice problems for you, skipping what you've already done.",
171
- actions: /* @__PURE__ */ C(w, {
206
+ actions: /* @__PURE__ */ D(O, {
172
207
  variant: "outline",
173
208
  onClick: () => r("/courses/courses-page"),
174
- children: [/* @__PURE__ */ S(x, { size: 16 }), " Browse catalog"]
209
+ children: [/* @__PURE__ */ E(C, { size: 16 }), " Browse catalog"]
175
210
  }),
176
211
  children: [
177
- /* @__PURE__ */ C(T, {
212
+ /* @__PURE__ */ D(k, {
178
213
  className: "mb-6 space-y-4 p-4 sm:p-5",
179
- children: [/* @__PURE__ */ C("div", {
214
+ children: [/* @__PURE__ */ D("div", {
180
215
  className: "flex items-center gap-2",
181
- children: [/* @__PURE__ */ S("span", {
216
+ children: [/* @__PURE__ */ E("span", {
182
217
  className: "flex h-8 w-8 items-center justify-center rounded-lg bg-accent-primary/15 text-accent-primary",
183
- children: /* @__PURE__ */ S(b, { size: 16 })
184
- }), /* @__PURE__ */ C("div", { children: [/* @__PURE__ */ S("h2", {
218
+ children: /* @__PURE__ */ E(S, { size: 16 })
219
+ }), /* @__PURE__ */ D("div", { children: [/* @__PURE__ */ E("h2", {
185
220
  className: "text-sm font-semibold text-text-primary",
186
221
  children: "Generate a plan"
187
- }), /* @__PURE__ */ S("p", {
222
+ }), /* @__PURE__ */ E("p", {
188
223
  className: "text-xs text-text-muted",
189
224
  children: "Rule-based sequencing over the real catalog — not an AI/LLM feature."
190
225
  })] })]
191
- }), /* @__PURE__ */ C("div", {
226
+ }), /* @__PURE__ */ D("div", {
192
227
  className: "grid grid-cols-1 gap-3 sm:grid-cols-[2fr_1fr_120px_auto]",
193
228
  children: [
194
- /* @__PURE__ */ S(E, {
195
- value: _,
196
- onChange: (e) => v(e.target.value),
229
+ /* @__PURE__ */ E(A, {
230
+ value: y,
231
+ onChange: (e) => b(e.target.value),
197
232
  placeholder: "Your goal, e.g. \"become a full-stack engineer\""
198
233
  }),
199
- /* @__PURE__ */ S(E, {
200
- value: D,
201
- onChange: (e) => A(e.target.value),
234
+ /* @__PURE__ */ E(A, {
235
+ value: j,
236
+ onChange: (e) => I(e.target.value),
202
237
  placeholder: "Topic (optional)"
203
238
  }),
204
- /* @__PURE__ */ S(E, {
239
+ /* @__PURE__ */ E(A, {
205
240
  type: "number",
206
- min: O,
207
- max: k,
208
- value: M,
209
- onChange: (e) => N(e.target.value),
241
+ min: M,
242
+ max: N,
243
+ value: R,
244
+ onChange: (e) => z(e.target.value),
210
245
  placeholder: "Weeks"
211
246
  }),
212
- /* @__PURE__ */ S(w, {
247
+ /* @__PURE__ */ E(O, {
213
248
  onClick: async () => {
214
- let e = _.trim();
215
- if (!e) return c.error("Describe your goal first", "e.g. \"become a full-stack engineer\"");
216
- let t = Math.min(k, Math.max(O, Number.parseInt(M, 10) || 4)), n = await h({ variables: { input: {
249
+ let e = y.trim();
250
+ if (!e) return l.error("Describe your goal first", "e.g. \"become a full-stack engineer\"");
251
+ let t = Math.min(N, Math.max(M, Number.parseInt(R, 10) || 4)), n = await g({ variables: { input: {
217
252
  goal: e,
218
- topic: D.trim() || void 0,
253
+ topic: j.trim() || void 0,
219
254
  timeframeWeeks: t
220
255
  } } });
221
- n.data?.tecGenerateStudyPlan && (c.success("Plan generated", `${n.data.tecGenerateStudyPlan.totalSteps} steps sequenced for you.`), v(""), A(""));
256
+ n.data?.tecGenerateStudyPlan && (l.success("Plan generated", `${n.data.tecGenerateStudyPlan.totalSteps} steps sequenced for you.`), b(""), I(""));
222
257
  },
223
- disabled: g,
224
- children: g ? "Generating…" : "Generate plan"
258
+ disabled: _,
259
+ children: _ ? "Generating…" : "Generate plan"
225
260
  })
226
261
  ]
227
262
  })]
228
263
  }),
229
- /* @__PURE__ */ C("div", {
230
- className: "mb-3 flex items-center justify-between",
231
- children: [/* @__PURE__ */ S("h2", {
232
- className: "text-sm font-semibold text-text-primary",
233
- children: "Your plans"
234
- }), /* @__PURE__ */ S(n, {
235
- label: "Active plans",
236
- value: F.length,
237
- hint: "Plans currently in progress",
238
- icon: y,
239
- tone: "indigo",
240
- className: "hidden sm:block"
241
- })]
264
+ /* @__PURE__ */ D("div", {
265
+ className: "mb-6 grid grid-cols-2 gap-3 sm:grid-cols-4",
266
+ children: [
267
+ /* @__PURE__ */ E(n, {
268
+ label: "Active plans",
269
+ value: V.length,
270
+ hint: "Plans currently in progress",
271
+ icon: x,
272
+ tone: "indigo"
273
+ }),
274
+ /* @__PURE__ */ E(n, {
275
+ label: "Steps completed",
276
+ value: `${U} / ${W}`,
277
+ hint: "Across your unarchived plans",
278
+ icon: v,
279
+ tone: "green"
280
+ }),
281
+ /* @__PURE__ */ E(n, {
282
+ label: "Time planned",
283
+ value: i(G),
284
+ hint: "Sum of every scheduled step",
285
+ icon: w,
286
+ tone: "teal"
287
+ }),
288
+ /* @__PURE__ */ E(n, {
289
+ label: "Plans completed",
290
+ value: K,
291
+ hint: "Every step reached a terminal state",
292
+ icon: T,
293
+ tone: "purple"
294
+ })
295
+ ]
296
+ }),
297
+ /* @__PURE__ */ E("h2", {
298
+ className: "mb-3 text-sm font-semibold text-text-primary",
299
+ children: "Your plans"
242
300
  }),
243
- l && !P.length ? /* @__PURE__ */ S(s, {
301
+ u && !B.length ? /* @__PURE__ */ E(c, {
244
302
  count: 2,
245
303
  height: "h-32"
246
- }) : f ? /* @__PURE__ */ S(o, {
304
+ }) : p ? /* @__PURE__ */ E(s, {
247
305
  title: "Couldn't load your study plans",
248
- error: f
249
- }) : P.length === 0 ? /* @__PURE__ */ S(a, {
250
- icon: /* @__PURE__ */ S(b, { size: 26 }),
306
+ error: p
307
+ }) : B.length === 0 ? /* @__PURE__ */ E(o, {
308
+ icon: /* @__PURE__ */ E(S, { size: 26 }),
251
309
  title: "No study plans yet",
252
310
  description: "Describe a goal above and we'll sequence a plan from the real course and practice catalog."
253
- }) : /* @__PURE__ */ S("div", {
311
+ }) : /* @__PURE__ */ E("div", {
254
312
  className: "space-y-4",
255
- children: P.map((e) => /* @__PURE__ */ S(j, {
313
+ children: B.map((e) => /* @__PURE__ */ E(L, {
256
314
  plan: e,
257
- onArchived: () => void m()
315
+ onArchived: () => void h(),
316
+ onOpenStep: q
258
317
  }, e.id))
259
318
  })
260
319
  ]
261
320
  });
262
321
  }
263
322
  //#endregion
264
- export { M as StudyPlannerStudyPlannerPagePage };
323
+ export { R as StudyPlannerStudyPlannerPagePage };
265
324
 
266
325
  //# sourceMappingURL=StudyPlannerStudyPlannerPagePage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"StudyPlannerStudyPlannerPagePage.js","names":[],"sources":["../../../src/technospam/pages/StudyPlannerStudyPlannerPagePage.tsx"],"sourcesContent":["/**\n * Study Planner (BOFF-4415).\n *\n * HONESTY NOTE: plan generation is a deterministic, rule-based scheduler —\n * it sequences real published courses/problems by level/difficulty and\n * excludes content you've already completed/solved. It is NOT an AI/LLM\n * feature. True adaptive re-planning from your submission history and\n * skill-gap analysis (the \"TechnoSage\" epic, BOFF-3874/3895) is deferred —\n * it depends on the shared platform AI gateway (BOFF-3601), which has not\n * been built yet.\n *\n * This single page intentionally consolidates what the nav scaffolding\n * originally anticipated as several separate pages (a plans list, a plan\n * details view, an edit view, learning paths, goals) into one focused\n * surface: generate a plan, see your active plans, and track progress. The\n * richer standalone views are deferred as follow-up scope.\n */\nimport { useState } from 'react';\nimport {\n Archive,\n Calendar,\n CheckCircle2,\n Circle,\n Clock,\n ListChecks,\n Sparkles,\n Target,\n} from 'lucide-react';\nimport { Button, GlassCard, Input } from '@burdenoff/fe-libs/ui';\nimport {\n EmptyView,\n ErrorView,\n LoadingRows,\n PageLayout,\n StatCard,\n StatusBadge,\n} from '../components';\nimport { useTechnoSpamNavigate } from '../providers/TechnoSpamProvider';\nimport {\n type TecStudyPlan,\n type TecStudyPlanStep,\n useArchiveStudyPlan,\n useGenerateStudyPlan,\n useMyStudyPlansQuery,\n useUpdateStudyPlanStepStatus,\n} from '../api/technospamApi';\nimport { formatDate } from '../utils/format';\nimport { notify } from '../lib/toast';\nimport { nativeConfirm } from '@burdenoff/fe-libs/shared/native';\n\nconst MIN_WEEKS = 1;\nconst MAX_WEEKS = 52;\n\nfunction StepRow({\n step,\n onMark,\n}: {\n step: TecStudyPlanStep;\n onMark: (status: 'COMPLETED' | 'SKIPPED') => void;\n}) {\n const done = step.status === 'COMPLETED';\n const skipped = step.status === 'SKIPPED';\n return (\n <div className=\"flex flex-col gap-2 rounded-lg border border-border-seam bg-bg-surface p-3 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"flex min-w-0 gap-3\">\n <button\n type=\"button\"\n onClick={() => onMark('COMPLETED')}\n disabled={done}\n className=\"mt-0.5 shrink-0 text-text-muted transition-colors hover:text-status-success-text disabled:cursor-default\"\n aria-label={done ? 'Completed' : 'Mark complete'}\n >\n {done ? (\n <CheckCircle2 size={18} className=\"text-status-success-text\" />\n ) : (\n <Circle size={18} />\n )}\n </button>\n <div className=\"min-w-0\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span\n className={`text-sm font-medium ${done || skipped ? 'text-text-muted line-through' : 'text-text-primary'}`}\n >\n {step.title}\n </span>\n <span className=\"rounded-full border border-border-seam px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-text-muted\">\n {step.kind}\n </span>\n <span className=\"text-xs text-text-muted\">Week {step.scheduledWeek}</span>\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">{step.rationale}</p>\n </div>\n </div>\n {!done && !skipped ? (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => onMark('SKIPPED')} className=\"shrink-0\">\n Skip\n </Button>\n ) : (\n <StatusBadge status={step.status} />\n )}\n </div>\n );\n}\n\nfunction PlanCard({ plan, onArchived }: { plan: TecStudyPlan; onArchived: () => void }) {\n const [updateStep] = useUpdateStudyPlanStepStatus();\n const [archivePlan, { loading: archiving }] = useArchiveStudyPlan();\n const [expanded, setExpanded] = useState(plan.status === 'ACTIVE');\n\n const handleMark = async (stepId: string, status: 'COMPLETED' | 'SKIPPED') => {\n await updateStep({ variables: { input: { stepId, status } } });\n };\n\n const handleArchive = async () => {\n const confirmed = await nativeConfirm({\n title: 'Archive plan',\n message: `Archive the plan for \"${plan.goal}\"? You can always generate a new one.`,\n okButtonTitle: 'Archive',\n cancelButtonTitle: 'Cancel',\n });\n if (!confirmed) return;\n const res = await archivePlan({ variables: { id: plan.id } });\n if (res.data?.tecArchiveStudyPlan) {\n notify.success('Plan archived');\n onArchived();\n }\n };\n\n return (\n <GlassCard className=\"space-y-4 p-4 sm:p-5\">\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <h3 className=\"text-sm font-semibold text-text-primary\">{plan.goal}</h3>\n <StatusBadge status={plan.status} />\n {plan.topic ? (\n <span className=\"rounded-full bg-accent-teal/15 px-2 py-0.5 text-[11px] font-medium text-accent-teal\">\n {plan.topic}\n </span>\n ) : null}\n </div>\n <div className=\"mt-1 flex flex-wrap items-center gap-3 text-xs text-text-muted\">\n <span className=\"inline-flex items-center gap-1\">\n <Calendar size={12} /> {plan.timeframeWeeks}-week plan\n </span>\n {plan.targetCompletionDate ? (\n <span className=\"inline-flex items-center gap-1\">\n <Clock size={12} /> target {formatDate(plan.targetCompletionDate)}\n </span>\n ) : null}\n <span className=\"inline-flex items-center gap-1\">\n <ListChecks size={12} /> {plan.completedSteps}/{plan.totalSteps} steps done\n </span>\n </div>\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n <Button variant=\"ghost\" size=\"sm\" onClick={() => setExpanded((v) => !v)}>\n {expanded ? 'Hide steps' : 'Show steps'}\n </Button>\n {plan.status !== 'ARCHIVED' ? (\n <Button variant=\"outline\" size=\"sm\" onClick={handleArchive} disabled={archiving}>\n <Archive size={14} /> Archive\n </Button>\n ) : null}\n </div>\n </div>\n\n <div className=\"h-1.5 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-full rounded-full bg-gradient-to-r from-accent-primary via-accent-indigo to-accent-purple transition-all duration-500\"\n style={{ width: `${plan.progressPct}%` }}\n />\n </div>\n\n {expanded ? (\n <div className=\"space-y-2\">\n {plan.steps.map((step) => (\n <StepRow\n key={step.id}\n step={step}\n onMark={(status) => void handleMark(step.id, status)}\n />\n ))}\n </div>\n ) : null}\n </GlassCard>\n );\n}\n\nexport function StudyPlannerStudyPlannerPagePage() {\n const navigate = useTechnoSpamNavigate();\n const { data, loading, error, refetch } = useMyStudyPlansQuery();\n const [generatePlan, { loading: generating }] = useGenerateStudyPlan();\n\n const [goal, setGoal] = useState('');\n const [topic, setTopic] = useState('');\n const [weeks, setWeeks] = useState('4');\n\n const plans = data?.tecMyStudyPlans.items ?? [];\n const activePlans = plans.filter((p) => p.status !== 'ARCHIVED');\n\n const handleGenerate = async () => {\n const trimmedGoal = goal.trim();\n if (!trimmedGoal)\n return notify.error('Describe your goal first', 'e.g. \"become a full-stack engineer\"');\n const parsedWeeks = Math.min(MAX_WEEKS, Math.max(MIN_WEEKS, Number.parseInt(weeks, 10) || 4));\n const res = await generatePlan({\n variables: {\n input: { goal: trimmedGoal, topic: topic.trim() || undefined, timeframeWeeks: parsedWeeks },\n },\n });\n if (res.data?.tecGenerateStudyPlan) {\n notify.success(\n 'Plan generated',\n `${res.data.tecGenerateStudyPlan.totalSteps} steps sequenced for you.`\n );\n setGoal('');\n setTopic('');\n }\n };\n\n return (\n <PageLayout\n hero\n title=\"Study Planner\"\n description=\"Tell us your goal and timeframe — we'll sequence real courses and practice problems for you, skipping what you've already done.\"\n actions={\n <Button variant=\"outline\" onClick={() => navigate('/courses/courses-page')}>\n <Target size={16} /> Browse catalog\n </Button>\n }\n >\n <GlassCard className=\"mb-6 space-y-4 p-4 sm:p-5\">\n <div className=\"flex items-center gap-2\">\n <span className=\"flex h-8 w-8 items-center justify-center rounded-lg bg-accent-primary/15 text-accent-primary\">\n <Sparkles size={16} />\n </span>\n <div>\n <h2 className=\"text-sm font-semibold text-text-primary\">Generate a plan</h2>\n <p className=\"text-xs text-text-muted\">\n Rule-based sequencing over the real catalog — not an AI/LLM feature.\n </p>\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-[2fr_1fr_120px_auto]\">\n <Input\n value={goal}\n onChange={(e) => setGoal(e.target.value)}\n placeholder='Your goal, e.g. \"become a full-stack engineer\"'\n />\n <Input\n value={topic}\n onChange={(e) => setTopic(e.target.value)}\n placeholder=\"Topic (optional)\"\n />\n <Input\n type=\"number\"\n min={MIN_WEEKS}\n max={MAX_WEEKS}\n value={weeks}\n onChange={(e) => setWeeks(e.target.value)}\n placeholder=\"Weeks\"\n />\n <Button onClick={handleGenerate} disabled={generating}>\n {generating ? 'Generating…' : 'Generate plan'}\n </Button>\n </div>\n </GlassCard>\n\n <div className=\"mb-3 flex items-center justify-between\">\n <h2 className=\"text-sm font-semibold text-text-primary\">Your plans</h2>\n <StatCard\n label=\"Active plans\"\n value={activePlans.length}\n hint=\"Plans currently in progress\"\n icon={ListChecks}\n tone=\"indigo\"\n className=\"hidden sm:block\"\n />\n </div>\n\n {loading && !plans.length ? (\n <LoadingRows count={2} height=\"h-32\" />\n ) : error ? (\n <ErrorView title=\"Couldn't load your study plans\" error={error} />\n ) : plans.length === 0 ? (\n <EmptyView\n icon={<Sparkles size={26} />}\n title=\"No study plans yet\"\n description=\"Describe a goal above and we'll sequence a plan from the real course and practice catalog.\"\n />\n ) : (\n <div className=\"space-y-4\">\n {plans.map((plan) => (\n <PlanCard key={plan.id} plan={plan} onArchived={() => void refetch()} />\n ))}\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"StudyPlannerStudyPlannerPagePage.js","names":[],"sources":["../../../src/technospam/pages/StudyPlannerStudyPlannerPagePage.tsx"],"sourcesContent":["/**\n * Study Planner (BOFF-4415).\n *\n * HONESTY NOTE: plan generation is a deterministic, rule-based scheduler —\n * it sequences real published courses/problems by level/difficulty and\n * excludes content you've already completed/solved. It is NOT an AI/LLM\n * feature. True adaptive re-planning from your submission history and\n * skill-gap analysis (the \"TechnoSage\" epic, BOFF-3874/3895) is deferred —\n * it depends on the shared platform AI gateway (BOFF-3601), which has not\n * been built yet.\n *\n * This single page intentionally consolidates what the nav scaffolding\n * originally anticipated as several separate pages (a plans list, a plan\n * details view, an edit view, learning paths, goals) into one focused\n * surface: generate a plan, see your active plans, and track progress. The\n * richer standalone views are deferred as follow-up scope.\n */\nimport { useState } from 'react';\nimport {\n Archive,\n ArrowUpRight,\n Calendar,\n CheckCircle2,\n Circle,\n Clock,\n ListChecks,\n Sparkles,\n Target,\n Timer,\n TrendingUp,\n} from 'lucide-react';\nimport { Button, GlassCard, Input } from '@burdenoff/fe-libs/ui';\nimport {\n EmptyView,\n ErrorView,\n LoadingRows,\n PageLayout,\n StatCard,\n StatusBadge,\n} from '../components';\nimport { useTechnoSpamNavigate } from '../providers/TechnoSpamProvider';\nimport {\n type TecStudyPlan,\n type TecStudyPlanStep,\n useArchiveStudyPlan,\n useGenerateStudyPlan,\n useMyStudyPlansQuery,\n useUpdateStudyPlanStepStatus,\n} from '../api/technospamApi';\nimport { formatDate, formatDuration } from '../utils/format';\nimport { notify } from '../lib/toast';\nimport { nativeConfirm } from '@burdenoff/fe-libs/shared/native';\n\nconst MIN_WEEKS = 1;\nconst MAX_WEEKS = 52;\n\n/**\n * Where a plan step points. `TecStudyPlanStep.targetId` is documented as \"the\n * real TecCourse.id or TecProblem.id this step targets\", so a step is a live\n * link into the catalogue rather than a label the learner has to go find.\n */\nfunction stepTargetPath(step: TecStudyPlanStep): string {\n return step.kind === 'COURSE'\n ? `/courses/course-details-page?id=${step.targetId}`\n : `/applications/problem-details-page?id=${step.targetId}`;\n}\n\n/** Total scheduled minutes across a plan's steps (TecStudyPlanStep.estimatedMinutes). */\nfunction planMinutes(plan: TecStudyPlan): number {\n return plan.steps.reduce((total, step) => total + step.estimatedMinutes, 0);\n}\n\nfunction StepRow({\n step,\n onMark,\n onOpen,\n}: {\n step: TecStudyPlanStep;\n onMark: (status: 'COMPLETED' | 'SKIPPED') => void;\n onOpen: () => void;\n}) {\n const done = step.status === 'COMPLETED';\n const skipped = step.status === 'SKIPPED';\n return (\n <div className=\"flex flex-col gap-2 rounded-lg border border-border-seam bg-bg-surface p-3 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"flex min-w-0 gap-3\">\n <button\n type=\"button\"\n onClick={() => onMark('COMPLETED')}\n disabled={done}\n className=\"mt-0.5 shrink-0 text-text-muted transition-colors hover:text-status-success-text disabled:cursor-default\"\n aria-label={done ? 'Completed' : 'Mark complete'}\n >\n {done ? (\n <CheckCircle2 size={18} className=\"text-status-success-text\" />\n ) : (\n <Circle size={18} />\n )}\n </button>\n <div className=\"min-w-0\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={onOpen}\n className={`group inline-flex items-center gap-1 text-left text-sm font-medium transition-colors hover:text-accent-primary ${\n done || skipped ? 'text-text-muted line-through' : 'text-text-primary'\n }`}\n >\n {step.title}\n <ArrowUpRight\n size={13}\n aria-hidden\n className=\"shrink-0 text-text-muted transition-colors group-hover:text-accent-primary\"\n />\n </button>\n <span className=\"rounded-full border border-border-seam px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-text-muted\">\n {step.kind}\n </span>\n <span className=\"text-xs text-text-muted\">Week {step.scheduledWeek}</span>\n <span className=\"inline-flex items-center gap-1 text-xs text-text-muted\">\n <Clock size={11} /> {formatDuration(step.estimatedMinutes)}\n </span>\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">{step.rationale}</p>\n </div>\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n {!done && !skipped ? (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => onMark('SKIPPED')}>\n Skip\n </Button>\n ) : (\n <StatusBadge status={step.status} />\n )}\n </div>\n </div>\n );\n}\n\nfunction PlanCard({\n plan,\n onArchived,\n onOpenStep,\n}: {\n plan: TecStudyPlan;\n onArchived: () => void;\n onOpenStep: (step: TecStudyPlanStep) => void;\n}) {\n const [updateStep] = useUpdateStudyPlanStepStatus();\n const [archivePlan, { loading: archiving }] = useArchiveStudyPlan();\n const [expanded, setExpanded] = useState(plan.status === 'ACTIVE');\n\n const handleMark = async (stepId: string, status: 'COMPLETED' | 'SKIPPED') => {\n await updateStep({ variables: { input: { stepId, status } } });\n };\n\n const handleArchive = async () => {\n const confirmed = await nativeConfirm({\n title: 'Archive plan',\n message: `Archive the plan for \"${plan.goal}\"? You can always generate a new one.`,\n okButtonTitle: 'Archive',\n cancelButtonTitle: 'Cancel',\n });\n if (!confirmed) return;\n const res = await archivePlan({ variables: { id: plan.id } });\n if (res.data?.tecArchiveStudyPlan) {\n notify.success('Plan archived');\n onArchived();\n }\n };\n\n return (\n <GlassCard className=\"space-y-4 p-4 sm:p-5\">\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <h3 className=\"text-sm font-semibold text-text-primary\">{plan.goal}</h3>\n <StatusBadge status={plan.status} />\n {plan.topic ? (\n <span className=\"rounded-full bg-accent-teal/15 px-2 py-0.5 text-[11px] font-medium text-accent-teal\">\n {plan.topic}\n </span>\n ) : null}\n </div>\n <div className=\"mt-1 flex flex-wrap items-center gap-3 text-xs text-text-muted\">\n <span className=\"inline-flex items-center gap-1\">\n <Calendar size={12} /> {plan.timeframeWeeks}-week plan\n </span>\n {plan.targetCompletionDate ? (\n <span className=\"inline-flex items-center gap-1\">\n <Clock size={12} /> target {formatDate(plan.targetCompletionDate)}\n </span>\n ) : null}\n <span className=\"inline-flex items-center gap-1\">\n <ListChecks size={12} /> {plan.progressPct}% · {plan.completedSteps} of{' '}\n {plan.totalSteps} steps\n </span>\n <span className=\"inline-flex items-center gap-1\">\n <Timer size={12} /> {formatDuration(planMinutes(plan))} planned\n </span>\n </div>\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n <Button variant=\"ghost\" size=\"sm\" onClick={() => setExpanded((v) => !v)}>\n {expanded ? 'Hide steps' : 'Show steps'}\n </Button>\n {plan.status !== 'ARCHIVED' ? (\n <Button variant=\"outline\" size=\"sm\" onClick={handleArchive} disabled={archiving}>\n <Archive size={14} /> Archive\n </Button>\n ) : null}\n </div>\n </div>\n\n <div className=\"h-1.5 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-full rounded-full bg-gradient-to-r from-accent-primary via-accent-indigo to-accent-purple transition-all duration-500\"\n style={{ width: `${plan.progressPct}%` }}\n />\n </div>\n\n {expanded ? (\n <div className=\"space-y-2\">\n {plan.steps.map((step) => (\n <StepRow\n key={step.id}\n step={step}\n onMark={(status) => void handleMark(step.id, status)}\n onOpen={() => onOpenStep(step)}\n />\n ))}\n </div>\n ) : null}\n </GlassCard>\n );\n}\n\nexport function StudyPlannerStudyPlannerPagePage() {\n const navigate = useTechnoSpamNavigate();\n const { data, loading, error, refetch } = useMyStudyPlansQuery();\n const [generatePlan, { loading: generating }] = useGenerateStudyPlan();\n\n const [goal, setGoal] = useState('');\n const [topic, setTopic] = useState('');\n const [weeks, setWeeks] = useState('4');\n\n const plans = data?.tecMyStudyPlans.items ?? [];\n const activePlans = plans.filter((p) => p.status === 'ACTIVE');\n const livePlans = plans.filter((p) => p.status !== 'ARCHIVED');\n // Every figure below is a straight sum over fields the plan query already\n // returns -- no derived \"are you on track?\" judgement is implied.\n const stepsDone = livePlans.reduce((total, p) => total + p.completedSteps, 0);\n const stepsTotal = livePlans.reduce((total, p) => total + p.totalSteps, 0);\n const minutesPlanned = livePlans.reduce((total, p) => total + planMinutes(p), 0);\n const plansCompleted = plans.filter((p) => p.status === 'COMPLETED').length;\n\n const openStep = (step: TecStudyPlanStep) => navigate(stepTargetPath(step));\n\n const handleGenerate = async () => {\n const trimmedGoal = goal.trim();\n if (!trimmedGoal)\n return notify.error('Describe your goal first', 'e.g. \"become a full-stack engineer\"');\n const parsedWeeks = Math.min(MAX_WEEKS, Math.max(MIN_WEEKS, Number.parseInt(weeks, 10) || 4));\n const res = await generatePlan({\n variables: {\n input: { goal: trimmedGoal, topic: topic.trim() || undefined, timeframeWeeks: parsedWeeks },\n },\n });\n if (res.data?.tecGenerateStudyPlan) {\n notify.success(\n 'Plan generated',\n `${res.data.tecGenerateStudyPlan.totalSteps} steps sequenced for you.`\n );\n setGoal('');\n setTopic('');\n }\n };\n\n return (\n <PageLayout\n hero\n title=\"Study Planner\"\n description=\"Tell us your goal and timeframe — we'll sequence real courses and practice problems for you, skipping what you've already done.\"\n actions={\n <Button variant=\"outline\" onClick={() => navigate('/courses/courses-page')}>\n <Target size={16} /> Browse catalog\n </Button>\n }\n >\n <GlassCard className=\"mb-6 space-y-4 p-4 sm:p-5\">\n <div className=\"flex items-center gap-2\">\n <span className=\"flex h-8 w-8 items-center justify-center rounded-lg bg-accent-primary/15 text-accent-primary\">\n <Sparkles size={16} />\n </span>\n <div>\n <h2 className=\"text-sm font-semibold text-text-primary\">Generate a plan</h2>\n <p className=\"text-xs text-text-muted\">\n Rule-based sequencing over the real catalog — not an AI/LLM feature.\n </p>\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-[2fr_1fr_120px_auto]\">\n <Input\n value={goal}\n onChange={(e) => setGoal(e.target.value)}\n placeholder='Your goal, e.g. \"become a full-stack engineer\"'\n />\n <Input\n value={topic}\n onChange={(e) => setTopic(e.target.value)}\n placeholder=\"Topic (optional)\"\n />\n <Input\n type=\"number\"\n min={MIN_WEEKS}\n max={MAX_WEEKS}\n value={weeks}\n onChange={(e) => setWeeks(e.target.value)}\n placeholder=\"Weeks\"\n />\n <Button onClick={handleGenerate} disabled={generating}>\n {generating ? 'Generating…' : 'Generate plan'}\n </Button>\n </div>\n </GlassCard>\n\n <div className=\"mb-6 grid grid-cols-2 gap-3 sm:grid-cols-4\">\n <StatCard\n label=\"Active plans\"\n value={activePlans.length}\n hint=\"Plans currently in progress\"\n icon={ListChecks}\n tone=\"indigo\"\n />\n <StatCard\n label=\"Steps completed\"\n value={`${stepsDone} / ${stepsTotal}`}\n hint=\"Across your unarchived plans\"\n icon={CheckCircle2}\n tone=\"green\"\n />\n <StatCard\n label=\"Time planned\"\n value={formatDuration(minutesPlanned)}\n hint=\"Sum of every scheduled step\"\n icon={Timer}\n tone=\"teal\"\n />\n <StatCard\n label=\"Plans completed\"\n value={plansCompleted}\n hint=\"Every step reached a terminal state\"\n icon={TrendingUp}\n tone=\"purple\"\n />\n </div>\n\n <h2 className=\"mb-3 text-sm font-semibold text-text-primary\">Your plans</h2>\n\n {loading && !plans.length ? (\n <LoadingRows count={2} height=\"h-32\" />\n ) : error ? (\n <ErrorView title=\"Couldn't load your study plans\" error={error} />\n ) : plans.length === 0 ? (\n <EmptyView\n icon={<Sparkles size={26} />}\n title=\"No study plans yet\"\n description=\"Describe a goal above and we'll sequence a plan from the real course and practice catalog.\"\n />\n ) : (\n <div className=\"space-y-4\">\n {plans.map((plan) => (\n <PlanCard\n key={plan.id}\n plan={plan}\n onArchived={() => void refetch()}\n onOpenStep={openStep}\n />\n ))}\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { createContext as e, useContext as t, useMemo as n } from "react";
2
- import { MemoryRouter as r } from "react-router-dom";
2
+ import { MemoryRouter as r } from "react-router";
3
3
  import { QueryClient as i, QueryClientProvider as a } from "@tanstack/react-query";
4
4
  import { jsx as o } from "react/jsx-runtime";
5
5
  //#region src/technospam/providers/TechnoSpamProvider.tsx
@@ -1 +1 @@
1
- {"version":3,"file":"TechnoSpamProvider.js","names":[],"sources":["../../../src/technospam/providers/TechnoSpamProvider.tsx"],"sourcesContent":["import type { FC, PropsWithChildren } from 'react';\nimport { createContext, useContext, useMemo } from 'react';\nimport { MemoryRouter } from 'react-router-dom';\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query';\nimport type { TechnoSpamRootProps } from '../types';\n\ntype TechnoSpamContextValue = TechnoSpamRootProps;\n\nconst TechnoSpamContext = createContext<TechnoSpamContextValue | null>(null);\n\nexport const useTechnoSpam = () => {\n const context = useContext(TechnoSpamContext);\n if (!context) throw new Error('useTechnoSpam must be used within TechnoSpamProvider');\n return context;\n};\n\n/**\n * basePath-aware navigation. Returns a `navigate(path)` that prepends the MFE\n * basePath (so pages navigate with module-relative paths like\n * `/courses/courses-page`) and falls back to react-router when the host does\n * not inject a navigate function.\n */\nexport const useTechnoSpamNavigate = (): ((path: string) => void) => {\n const { navigate: hostNavigate, basePath = '' } = useTechnoSpam();\n const base = basePath.endsWith('/') ? basePath.slice(0, -1) : basePath;\n return (path: string) => {\n const target =\n !base || base === '' || path.startsWith(base)\n ? path\n : `${base}${path.startsWith('/') ? path : `/${path}`}`;\n if (hostNavigate) hostNavigate(target);\n else if (typeof window !== 'undefined') window.history.pushState({}, '', target);\n };\n};\n\nexport const TechnoSpamProvider: FC<PropsWithChildren<TechnoSpamRootProps>> = ({\n children,\n ...props\n}) => {\n const queryClient = useMemo(\n () =>\n new QueryClient({\n defaultOptions: {\n queries: { retry: 1, refetchOnWindowFocus: false, staleTime: 5 * 60 * 1000 },\n },\n }),\n []\n );\n\n const content = (\n <QueryClientProvider client={queryClient}>\n <TechnoSpamContext.Provider value={props}>{children}</TechnoSpamContext.Provider>\n </QueryClientProvider>\n );\n\n if (props.basePath) return content;\n return <MemoryRouter>{content}</MemoryRouter>;\n};\n"],"mappings":";;;;;AAQA,IAAM,IAAoB,EAA6C,KAAK,EAE/D,UAAsB;CACjC,IAAM,IAAU,EAAW,EAAkB;AAC7C,KAAI,CAAC,EAAS,OAAU,MAAM,uDAAuD;AACrF,QAAO;GASI,UAAwD;CACnE,IAAM,EAAE,UAAU,GAAc,cAAW,OAAO,GAAe,EAC3D,IAAO,EAAS,SAAS,IAAI,GAAG,EAAS,MAAM,GAAG,GAAG,GAAG;AAC9D,SAAQ,MAAiB;EACvB,IAAM,IACJ,CAAC,KAAQ,MAAS,MAAM,EAAK,WAAW,EAAK,GACzC,IACA,GAAG,IAAO,EAAK,WAAW,IAAI,GAAG,IAAO,IAAI;AAClD,EAAI,IAAc,EAAa,EAAO,GAC7B,OAAO,SAAW,OAAa,OAAO,QAAQ,UAAU,EAAE,EAAE,IAAI,EAAO;;GAIvE,KAAkE,EAC7E,aACA,GAAG,QACC;CAWJ,IAAM,IACJ,kBAAC,GAAD;EAAqB,QAXH,QAEhB,IAAI,EAAY,EACd,gBAAgB,EACd,SAAS;GAAE,OAAO;GAAG,sBAAsB;GAAO,WAAW,MAAS;GAAM,EAC7E,EACF,CAAC,EACJ,EAAE,CACH;YAIG,kBAAC,EAAkB,UAAnB;GAA4B,OAAO;GAAQ;GAAsC,CAAA;EAC7D,CAAA;AAIxB,QADI,EAAM,WAAiB,IACpB,kBAAC,GAAD,EAAA,UAAe,GAAuB,CAAA"}
1
+ {"version":3,"file":"TechnoSpamProvider.js","names":[],"sources":["../../../src/technospam/providers/TechnoSpamProvider.tsx"],"sourcesContent":["import type { FC, PropsWithChildren } from 'react';\nimport { createContext, useContext, useMemo } from 'react';\nimport { MemoryRouter } from 'react-router';\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query';\nimport type { TechnoSpamRootProps } from '../types';\n\ntype TechnoSpamContextValue = TechnoSpamRootProps;\n\nconst TechnoSpamContext = createContext<TechnoSpamContextValue | null>(null);\n\nexport const useTechnoSpam = () => {\n const context = useContext(TechnoSpamContext);\n if (!context) throw new Error('useTechnoSpam must be used within TechnoSpamProvider');\n return context;\n};\n\n/**\n * basePath-aware navigation. Returns a `navigate(path)` that prepends the MFE\n * basePath (so pages navigate with module-relative paths like\n * `/courses/courses-page`) and falls back to react-router when the host does\n * not inject a navigate function.\n */\nexport const useTechnoSpamNavigate = (): ((path: string) => void) => {\n const { navigate: hostNavigate, basePath = '' } = useTechnoSpam();\n const base = basePath.endsWith('/') ? basePath.slice(0, -1) : basePath;\n return (path: string) => {\n const target =\n !base || base === '' || path.startsWith(base)\n ? path\n : `${base}${path.startsWith('/') ? path : `/${path}`}`;\n if (hostNavigate) hostNavigate(target);\n else if (typeof window !== 'undefined') window.history.pushState({}, '', target);\n };\n};\n\nexport const TechnoSpamProvider: FC<PropsWithChildren<TechnoSpamRootProps>> = ({\n children,\n ...props\n}) => {\n const queryClient = useMemo(\n () =>\n new QueryClient({\n defaultOptions: {\n queries: { retry: 1, refetchOnWindowFocus: false, staleTime: 5 * 60 * 1000 },\n },\n }),\n []\n );\n\n const content = (\n <QueryClientProvider client={queryClient}>\n <TechnoSpamContext.Provider value={props}>{children}</TechnoSpamContext.Provider>\n </QueryClientProvider>\n );\n\n if (props.basePath) return content;\n return <MemoryRouter>{content}</MemoryRouter>;\n};\n"],"mappings":";;;;;AAQA,IAAM,IAAoB,EAA6C,KAAK,EAE/D,UAAsB;CACjC,IAAM,IAAU,EAAW,EAAkB;AAC7C,KAAI,CAAC,EAAS,OAAU,MAAM,uDAAuD;AACrF,QAAO;GASI,UAAwD;CACnE,IAAM,EAAE,UAAU,GAAc,cAAW,OAAO,GAAe,EAC3D,IAAO,EAAS,SAAS,IAAI,GAAG,EAAS,MAAM,GAAG,GAAG,GAAG;AAC9D,SAAQ,MAAiB;EACvB,IAAM,IACJ,CAAC,KAAQ,MAAS,MAAM,EAAK,WAAW,EAAK,GACzC,IACA,GAAG,IAAO,EAAK,WAAW,IAAI,GAAG,IAAO,IAAI;AAClD,EAAI,IAAc,EAAa,EAAO,GAC7B,OAAO,SAAW,OAAa,OAAO,QAAQ,UAAU,EAAE,EAAE,IAAI,EAAO;;GAIvE,KAAkE,EAC7E,aACA,GAAG,QACC;CAWJ,IAAM,IACJ,kBAAC,GAAD;EAAqB,QAXH,QAEhB,IAAI,EAAY,EACd,gBAAgB,EACd,SAAS;GAAE,OAAO;GAAG,sBAAsB;GAAO,WAAW,MAAS;GAAM,EAC7E,EACF,CAAC,EACJ,EAAE,CACH;YAIG,kBAAC,EAAkB,UAAnB;GAA4B,OAAO;GAAQ;GAAsC,CAAA;EAC7D,CAAA;AAIxB,QADI,EAAM,WAAiB,IACpB,kBAAC,GAAD,EAAA,UAAe,GAAuB,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-technospam",
3
- "version": "2026.731.1",
3
+ "version": "2026.827.1",
4
4
  "description": "Burdenoff TechnoSpam Microfrontend",
5
5
  "type": "module",
6
6
  "files": [
@@ -46,14 +46,14 @@
46
46
  },
47
47
  "dependencies": {
48
48
  "@apollo/client": "^4.0.11",
49
- "@burdenoff/fe-libs": "2026.713.6",
49
+ "@burdenoff/fe-libs": "2026.801.1",
50
50
  "@tanstack/react-query": "^5.90.16",
51
51
  "clsx": "^2.1.1",
52
52
  "graphql": "^16.10.0",
53
53
  "lucide-react": "^0.468.0",
54
54
  "react": "^19.0.0",
55
55
  "react-dom": "^19.0.0",
56
- "react-router-dom": "^7.1.3",
56
+ "react-router": "^8.3.0",
57
57
  "sonner": "^1.7.4",
58
58
  "tailwind-merge": "^3.4.0",
59
59
  "zustand": "^5.0.3",
@@ -101,10 +101,10 @@
101
101
  "zustand": "^5.0.0"
102
102
  },
103
103
  "publishConfig": {
104
- "access": "restricted"
104
+ "access": "public"
105
105
  },
106
106
  "overrides": {
107
- "@burdenoff/fe-libs": "2026.713.6",
107
+ "@burdenoff/fe-libs": "2026.801.1",
108
108
  "lodash": ">=4.17.21",
109
109
  "lodash-es": ">=4.17.21",
110
110
  "dompurify": ">=3.3.4",