@burdenoff/microfe-technospam 2026.827.1 → 2026.828.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 (48) 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.d.ts.map +1 -1
  11. package/dist/technospam/TechnoSpamRoutes.js +89 -88
  12. package/dist/technospam/TechnoSpamRoutes.js.map +1 -1
  13. package/dist/technospam/components/TechnoSpamErrorBoundary.d.ts +25 -0
  14. package/dist/technospam/components/TechnoSpamErrorBoundary.d.ts.map +1 -0
  15. package/dist/technospam/components/TechnoSpamErrorBoundary.js +31 -0
  16. package/dist/technospam/components/TechnoSpamErrorBoundary.js.map +1 -0
  17. package/dist/technospam/components/index.d.ts +1 -0
  18. package/dist/technospam/components/index.d.ts.map +1 -1
  19. package/dist/technospam/components/index.js +1 -0
  20. package/dist/technospam/pages/AdminAdminDashboardPagePage.js +7 -7
  21. package/dist/technospam/pages/AdminAdminDashboardPagePage.js.map +1 -1
  22. package/dist/technospam/pages/AnalyticsAnalyticsPagePage.js +1 -1
  23. package/dist/technospam/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
  24. package/dist/technospam/pages/ApplicationsPortfolioPagePage.js +2 -2
  25. package/dist/technospam/pages/ApplicationsPortfolioPagePage.js.map +1 -1
  26. package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.js +1 -1
  27. package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.js.map +1 -1
  28. package/dist/technospam/pages/ApplicationsProblemsPagePage.js +1 -1
  29. package/dist/technospam/pages/ApplicationsProblemsPagePage.js.map +1 -1
  30. package/dist/technospam/pages/CoursesCoursesPagePage.js +1 -1
  31. package/dist/technospam/pages/CoursesCoursesPagePage.js.map +1 -1
  32. package/dist/technospam/pages/CoursesMyLearningPagePage.js +1 -1
  33. package/dist/technospam/pages/CoursesMyLearningPagePage.js.map +1 -1
  34. package/dist/technospam/pages/DashboardDashboardPagePage.js +1 -1
  35. package/dist/technospam/pages/DashboardDashboardPagePage.js.map +1 -1
  36. package/dist/technospam/pages/HackathonsHackathonDetailsPagePage.js +1 -1
  37. package/dist/technospam/pages/HackathonsHackathonDetailsPagePage.js.map +1 -1
  38. package/dist/technospam/pages/HackathonsHackathonsPagePage.js +1 -1
  39. package/dist/technospam/pages/HackathonsHackathonsPagePage.js.map +1 -1
  40. package/dist/technospam/pages/MentorshipMentorsPagePage.js +1 -1
  41. package/dist/technospam/pages/MentorshipMentorsPagePage.js.map +1 -1
  42. package/dist/technospam/pages/MentorshipSessionsPagePage.js +1 -1
  43. package/dist/technospam/pages/MentorshipSessionsPagePage.js.map +1 -1
  44. package/dist/technospam/pages/StudyPlannerStudyPlannerPagePage.js +1 -1
  45. package/dist/technospam/pages/StudyPlannerStudyPlannerPagePage.js.map +1 -1
  46. package/dist/test/setup.d.ts +2 -0
  47. package/dist/test/setup.d.ts.map +1 -0
  48. package/package.json +8 -2
@@ -1 +1 @@
1
- {"version":3,"file":"MentorshipMentorsPagePage.js","names":[],"sources":["../../../src/technospam/pages/MentorshipMentorsPagePage.tsx"],"sourcesContent":["import { createElement, useMemo, useState } from 'react';\nimport { Award, MessageSquare, Search, Sparkles, Star, UserPlus, Users, X } from 'lucide-react';\nimport { Badge, Button, GlassCard, Input, Textarea } from '@burdenoff/fe-libs/ui';\nimport {\n EmptyView,\n ErrorView,\n LoadMore,\n LoadingGrid,\n PageLayout,\n StatCard,\n coverGradient,\n topicIcon,\n} from '../components';\nimport { useTechnoSpam, useTechnoSpamNavigate } from '../providers/TechnoSpamProvider';\nimport {\n pageArg,\n useBecomeMentor,\n useMentorsQuery,\n usePageLimit,\n type TecMentor,\n} from '../api/technospamApi';\nimport { formatMoneyFromMinorUnits } from '../utils/format';\nimport { cn } from '../utils/cn';\nimport { notify } from '../lib/toast';\nimport { useTechnoSpamAnalytics } from '../hooks/useTechnoSpamAnalytics';\n\n/** Circular gradient medallion standing in for a mentor avatar, with a live\n * \"accepting\" presence dot — mirrors the directory mockup within the dark theme. */\nfunction MentorMedallion({ mentor, size = 'md' }: { mentor: TecMentor; size?: 'md' | 'lg' }) {\n const box = size === 'lg' ? 'h-20 w-20' : 'h-16 w-16';\n return (\n <div className=\"relative shrink-0\">\n <span\n className={cn(\n 'flex items-center justify-center rounded-full bg-gradient-to-br text-white shadow-md ring-1 ring-white/15',\n box,\n coverGradient(mentor.headline)\n )}\n >\n {createElement(topicIcon(mentor.expertise.join(' ') || mentor.headline), {\n size: size === 'lg' ? 32 : 26,\n strokeWidth: 1.7,\n })}\n </span>\n {mentor.isAcceptingMentees ? (\n <span className=\"absolute -bottom-0.5 -right-0.5 h-4 w-4 rounded-full border-2 border-bg-surface bg-status-success-text\" />\n ) : null}\n </div>\n );\n}\n\nfunction MentorCard({ mentor, onOpen }: { mentor: TecMentor; onOpen: () => void }) {\n return (\n <GlassCard\n className=\"group flex cursor-pointer flex-col gap-4 p-5 transition-all duration-200 hover:-translate-y-1 hover:shadow-lg\"\n onClick={onOpen}\n >\n <div className=\"flex items-start gap-4\">\n <MentorMedallion mentor={mentor} />\n <div className=\"min-w-0 flex-1\">\n <h3 className=\"truncate text-base font-semibold text-text-primary group-hover:text-accent-primary\">\n {mentor.headline}\n </h3>\n {mentor.role ? (\n <p className=\"mt-0.5 truncate text-xs font-medium text-text-secondary\">{mentor.role}</p>\n ) : null}\n {mentor.isAcceptingMentees ? (\n <Badge\n variant=\"outline\"\n className=\"mt-1.5 inline-flex items-center gap-1.5 border-status-success-border bg-status-success-bg-subtle text-status-success-text\"\n >\n <span className=\"h-1.5 w-1.5 rounded-full bg-status-success-text\" /> Accepting mentees\n </Badge>\n ) : (\n <Badge\n variant=\"outline\"\n className=\"mt-1.5 border-border-seam bg-bg-sunken text-text-secondary\"\n >\n Not accepting\n </Badge>\n )}\n </div>\n </div>\n\n {mentor.bio ? <p className=\"line-clamp-2 text-sm text-text-secondary\">{mentor.bio}</p> : null}\n\n {mentor.expertise.length ? (\n <div className=\"flex flex-wrap gap-1.5\">\n {mentor.expertise.slice(0, 4).map((skill) => (\n <Badge key={skill} variant=\"secondary\" className=\"text-[11px]\">\n {skill}\n </Badge>\n ))}\n {mentor.expertise.length > 4 ? (\n <span className=\"self-center text-xs text-text-muted\">\n +{mentor.expertise.length - 4}\n </span>\n ) : null}\n </div>\n ) : null}\n\n <div className=\"mt-auto grid grid-cols-3 divide-x divide-border-seam rounded-xl border border-border-seam bg-bg-sunken text-center\">\n <div className=\"px-2 py-2.5\">\n <div className=\"flex items-center justify-center gap-1 text-sm font-semibold text-text-primary\">\n <Star size={13} className=\"text-accent-teal\" />\n {mentor.ratingAvg > 0 ? mentor.ratingAvg.toFixed(1) : 'New'}\n </div>\n <div className=\"text-[11px] text-text-muted\">rating</div>\n </div>\n <div className=\"px-2 py-2.5\">\n <div className=\"text-sm font-semibold text-text-primary\">{mentor.sessionsCount}</div>\n <div className=\"text-[11px] text-text-muted\">sessions</div>\n </div>\n <div className=\"px-2 py-2.5\">\n <div className=\"text-sm font-semibold text-text-primary\">\n {formatMoneyFromMinorUnits(mentor.hourlyRate, mentor.currency)}\n </div>\n <div className=\"text-[11px] text-text-muted\">/ hr</div>\n </div>\n </div>\n\n <Button\n className=\"w-full\"\n onClick={(e) => {\n e.stopPropagation();\n onOpen();\n }}\n >\n View profile\n </Button>\n </GlassCard>\n );\n}\n\nfunction BecomeMentorForm({\n onClose,\n onDone,\n workspaceId,\n}: {\n onClose: () => void;\n onDone: () => void;\n workspaceId?: string;\n}) {\n const [headline, setHeadline] = useState('');\n const [role, setRole] = useState('');\n const [bio, setBio] = useState('');\n const [expertise, setExpertise] = useState('');\n const [hourlyRate, setHourlyRate] = useState('');\n const [currency, setCurrency] = useState('USD');\n const [becomeMentor, { loading }] = useBecomeMentor();\n const { emit } = useTechnoSpamAnalytics();\n\n const canSubmit = headline.trim().length > 0 && !loading;\n\n const handleSubmit = async () => {\n if (!canSubmit) return;\n const input: {\n headline: string;\n role?: string;\n bio?: string;\n expertise?: string[];\n hourlyRate?: number;\n currency?: string;\n } = { headline: headline.trim() };\n if (role.trim()) input.role = role.trim();\n if (bio.trim()) input.bio = bio.trim();\n const exp = expertise\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n if (exp.length) input.expertise = exp;\n // The form collects a dollars-and-cents rate (e.g. \"50\" or \"49.99\") but\n // TecMentor.hourlyRate is stored in the smallest currency unit (cents)\n // so BOFF-4433's fee math never loses precision to floating point --\n // convert here rather than storing the raw dollar figure as if it were\n // cents (which would under-charge/under-pay by 100x).\n const rateDollars = Number.parseFloat(hourlyRate);\n if (!Number.isNaN(rateDollars) && rateDollars >= 0) {\n input.hourlyRate = Math.round(rateDollars * 100);\n }\n if (currency.trim()) input.currency = currency.trim().toUpperCase();\n\n emit('technospam.mentor.become_mentor_clicked', { workspaceId });\n const res = await becomeMentor({ variables: { input } });\n if (res.data?.tecBecomeMentor) {\n emit('technospam.mentor.became_mentor', {\n workspaceId,\n entityId: res.data.tecBecomeMentor.id,\n entityType: 'TEC_MENTOR',\n });\n notify.success('Mentor profile created', 'Your profile is pending review.');\n onDone();\n }\n };\n\n return (\n <GlassCard className=\"mb-6 p-5\">\n <div className=\"mb-4 flex items-start justify-between gap-3\">\n <div className=\"flex items-center gap-3\">\n <span className=\"flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-accent-primary to-accent-indigo text-white shadow-sm\">\n <UserPlus size={20} />\n </span>\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">Become a mentor</h2>\n <p className=\"text-sm text-text-secondary\">\n Share your expertise and start mentoring builders in the community.\n </p>\n </div>\n </div>\n <Button variant=\"ghost\" size=\"sm\" onClick={onClose} aria-label=\"Close\">\n <X size={16} />\n </Button>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4\">\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">\n Headline <span className=\"text-status-error-text\">*</span>\n </label>\n <Input\n placeholder=\"e.g. Staff Engineer · Distributed systems & career growth\"\n value={headline}\n onChange={(e) => setHeadline(e.target.value)}\n />\n </div>\n\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">\n Role &amp; company\n </label>\n <Input\n placeholder=\"e.g. Staff Engineer @ Acme\"\n value={role}\n onChange={(e) => setRole(e.target.value)}\n />\n <p className=\"mt-1 text-xs text-text-muted\">\n Shown as a subtitle under your headline. Optional.\n </p>\n </div>\n\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Bio</label>\n <Textarea\n rows={3}\n placeholder=\"Tell mentees about your experience and how you can help.\"\n value={bio}\n onChange={(e) => setBio(e.target.value)}\n />\n </div>\n\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Expertise</label>\n <Input\n placeholder=\"React, System design, Career coaching\"\n value={expertise}\n onChange={(e) => setExpertise(e.target.value)}\n />\n <p className=\"mt-1 text-xs text-text-muted\">Comma-separated topics.</p>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">\n Hourly rate\n </label>\n <Input\n type=\"number\"\n min={0}\n step={0.01}\n placeholder=\"0.00\"\n value={hourlyRate}\n onChange={(e) => setHourlyRate(e.target.value)}\n />\n <p className=\"mt-1 text-xs text-text-muted\">\n Dollars per hour (e.g. 50 or 49.99). Leave 0 for free.\n </p>\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Currency</label>\n <Input\n placeholder=\"USD\"\n value={currency}\n onChange={(e) => setCurrency(e.target.value)}\n />\n </div>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <Button onClick={handleSubmit} disabled={!canSubmit}>\n <UserPlus size={16} /> {loading ? 'Submitting…' : 'Submit profile'}\n </Button>\n <Button variant=\"outline\" onClick={onClose} disabled={loading}>\n Cancel\n </Button>\n </div>\n </div>\n </GlassCard>\n );\n}\n\nexport function MentorshipMentorsPagePage() {\n const navigate = useTechnoSpamNavigate();\n const { currentUser, workspaceId } = useTechnoSpam();\n const { emit } = useTechnoSpamAnalytics();\n const [search, setSearch] = useState('');\n const [showForm, setShowForm] = useState(false);\n\n const { limit, more } = usePageLimit();\n const { data, loading, error, refetch } = useMentorsQuery({\n status: 'ACTIVE',\n pagination: pageArg(limit),\n });\n\n const mentors = data?.tecListMentors.items ?? [];\n const hasMore = data?.tecListMentors.pageInfo.hasMore ?? false;\n const userId = currentUser?.id;\n const alreadyMentor = Boolean(userId && mentors.some((m) => m.userId === userId));\n\n const filtered = useMemo(() => {\n const q = search.trim().toLowerCase();\n if (!q) return mentors;\n return mentors.filter(\n (m) =>\n m.headline.toLowerCase().includes(q) ||\n m.expertise.some((skill) => skill.toLowerCase().includes(q))\n );\n }, [mentors, search]);\n\n const accepting = mentors.filter((m) => m.isAcceptingMentees).length;\n const totalSessions = mentors.reduce((s, m) => s + m.sessionsCount, 0);\n const rated = mentors.filter((m) => m.ratingAvg > 0);\n const avgRating = rated.length\n ? (rated.reduce((s, m) => s + m.ratingAvg, 0) / rated.length).toFixed(1)\n : '—';\n\n return (\n <PageLayout\n hero\n title=\"Find a Mentor\"\n description=\"Connect with experienced builders for 1:1 guidance, code review, and career coaching.\"\n actions={\n <Button variant=\"outline\" onClick={() => navigate('/mentorship/sessions-page')}>\n <MessageSquare size={16} /> My sessions\n </Button>\n }\n >\n {alreadyMentor ? (\n <GlassCard className=\"mb-6 flex items-center gap-3 p-4\">\n <span className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-status-success-bg-subtle text-status-success-text\">\n <Award size={20} />\n </span>\n <div>\n <div className=\"text-sm font-semibold text-text-primary\">You&apos;re a mentor</div>\n <div className=\"text-xs text-text-secondary\">\n Your mentor profile is live. Mentees can request sessions with you.\n </div>\n </div>\n </GlassCard>\n ) : showForm ? (\n <BecomeMentorForm\n onClose={() => setShowForm(false)}\n onDone={() => {\n setShowForm(false);\n void refetch();\n }}\n workspaceId={workspaceId}\n />\n ) : null}\n\n <div className=\"mb-5 grid grid-cols-2 gap-3 sm:grid-cols-4\">\n <StatCard label=\"Mentors\" value={mentors.length} icon={Users} tone=\"blue\" />\n <StatCard label=\"Accepting mentees\" value={accepting} icon={Sparkles} tone=\"green\" />\n <StatCard label=\"Avg rating\" value={avgRating} icon={Star} tone=\"amber\" />\n <StatCard label=\"Total sessions\" value={totalSessions} icon={MessageSquare} tone=\"purple\" />\n </div>\n\n <div className=\"mb-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"relative w-full sm:w-80\">\n <Search\n className=\"pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-text-muted\"\n size={16}\n />\n <Input\n className=\"pl-9\"\n placeholder=\"Search by headline or expertise…\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n />\n </div>\n {!alreadyMentor && !showForm ? (\n <Button onClick={() => setShowForm(true)}>\n <UserPlus size={16} /> Become a mentor\n </Button>\n ) : null}\n </div>\n\n {loading && !mentors.length ? (\n <LoadingGrid />\n ) : error ? (\n <ErrorView title=\"Couldn't load mentors\" error={error} />\n ) : filtered.length === 0 ? (\n <EmptyView\n icon={<Users size={26} />}\n title={search ? 'No matching mentors' : 'No mentors yet'}\n description={\n search\n ? 'Try a different search term.'\n : 'Active mentors will appear here. TechnoSpam is pre-launch — be one of the first to sign up.'\n }\n action={\n !search && !alreadyMentor ? (\n <Button onClick={() => setShowForm(true)}>Become a mentor</Button>\n ) : undefined\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {filtered.map((mentor) => (\n <MentorCard\n key={mentor.id}\n mentor={mentor}\n onOpen={() => {\n emit('technospam.mentor.viewed', {\n workspaceId,\n entityId: mentor.id,\n entityType: 'TEC_MENTOR',\n });\n navigate(`/mentorship/mentor-profile-page?id=${mentor.id}`);\n }}\n />\n ))}\n </div>\n )}\n <LoadMore hasMore={hasMore} loading={loading} onClick={more} />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"MentorshipMentorsPagePage.js","names":[],"sources":["../../../src/technospam/pages/MentorshipMentorsPagePage.tsx"],"sourcesContent":["import { createElement, useMemo, useState } from 'react';\nimport { Award, MessageSquare, Search, Sparkles, Star, UserPlus, Users, X } from 'lucide-react';\nimport { Badge, Button, GlassCard, Input, Textarea } from '@burdenoff/fe-libs/ui';\nimport {\n EmptyView,\n ErrorView,\n LoadMore,\n LoadingGrid,\n PageLayout,\n StatCard,\n coverGradient,\n topicIcon,\n} from '../components';\nimport { useTechnoSpam, useTechnoSpamNavigate } from '../providers/TechnoSpamProvider';\nimport {\n pageArg,\n useBecomeMentor,\n useMentorsQuery,\n usePageLimit,\n type TecMentor,\n} from '../api/technospamApi';\nimport { formatMoneyFromMinorUnits } from '../utils/format';\nimport { cn } from '../utils/cn';\nimport { notify } from '../lib/toast';\nimport { useTechnoSpamAnalytics } from '../hooks/useTechnoSpamAnalytics';\n\n/** Circular gradient medallion standing in for a mentor avatar, with a live\n * \"accepting\" presence dot — mirrors the directory mockup within the dark theme. */\nfunction MentorMedallion({ mentor, size = 'md' }: { mentor: TecMentor; size?: 'md' | 'lg' }) {\n const box = size === 'lg' ? 'h-20 w-20' : 'h-16 w-16';\n return (\n <div className=\"relative shrink-0\">\n <span\n className={cn(\n 'flex items-center justify-center rounded-full bg-gradient-to-br text-white shadow-md ring-1 ring-white/15',\n box,\n coverGradient(mentor.headline)\n )}\n >\n {createElement(topicIcon(mentor.expertise.join(' ') || mentor.headline), {\n size: size === 'lg' ? 32 : 26,\n strokeWidth: 1.7,\n })}\n </span>\n {mentor.isAcceptingMentees ? (\n <span className=\"absolute -bottom-0.5 -right-0.5 h-4 w-4 rounded-full border-2 border-bg-surface bg-status-success-text\" />\n ) : null}\n </div>\n );\n}\n\nfunction MentorCard({ mentor, onOpen }: { mentor: TecMentor; onOpen: () => void }) {\n return (\n <GlassCard\n className=\"group flex cursor-pointer flex-col gap-4 p-5 transition-all duration-200 hover:-translate-y-1 hover:shadow-lg\"\n onClick={onOpen}\n >\n <div className=\"flex items-start gap-4\">\n <MentorMedallion mentor={mentor} />\n <div className=\"min-w-0 flex-1\">\n <h3 className=\"truncate text-base font-semibold text-text-primary group-hover:text-accent-primary\">\n {mentor.headline}\n </h3>\n {mentor.role ? (\n <p className=\"mt-0.5 truncate text-xs font-medium text-text-secondary\">{mentor.role}</p>\n ) : null}\n {mentor.isAcceptingMentees ? (\n <Badge\n variant=\"outline\"\n className=\"mt-1.5 inline-flex items-center gap-1.5 border-status-success-border bg-status-success-bg-subtle text-status-success-text\"\n >\n <span className=\"h-1.5 w-1.5 rounded-full bg-status-success-text\" /> Accepting mentees\n </Badge>\n ) : (\n <Badge\n variant=\"outline\"\n className=\"mt-1.5 border-border-seam bg-bg-sunken text-text-secondary\"\n >\n Not accepting\n </Badge>\n )}\n </div>\n </div>\n\n {mentor.bio ? <p className=\"line-clamp-2 text-sm text-text-secondary\">{mentor.bio}</p> : null}\n\n {mentor.expertise.length ? (\n <div className=\"flex flex-wrap gap-1.5\">\n {mentor.expertise.slice(0, 4).map((skill) => (\n <Badge key={skill} variant=\"secondary\" className=\"text-[11px]\">\n {skill}\n </Badge>\n ))}\n {mentor.expertise.length > 4 ? (\n <span className=\"self-center text-xs text-text-muted\">\n +{mentor.expertise.length - 4}\n </span>\n ) : null}\n </div>\n ) : null}\n\n <div className=\"mt-auto grid grid-cols-3 divide-x divide-border-seam rounded-xl border border-border-seam bg-bg-sunken text-center\">\n <div className=\"px-2 py-2.5\">\n <div className=\"flex items-center justify-center gap-1 text-sm font-semibold text-text-primary\">\n <Star size={13} className=\"text-accent-teal\" />\n {mentor.ratingAvg > 0 ? mentor.ratingAvg.toFixed(1) : 'New'}\n </div>\n <div className=\"text-[11px] text-text-muted\">rating</div>\n </div>\n <div className=\"px-2 py-2.5\">\n <div className=\"text-sm font-semibold text-text-primary\">{mentor.sessionsCount}</div>\n <div className=\"text-[11px] text-text-muted\">sessions</div>\n </div>\n <div className=\"px-2 py-2.5\">\n <div className=\"text-sm font-semibold text-text-primary\">\n {formatMoneyFromMinorUnits(mentor.hourlyRate, mentor.currency)}\n </div>\n <div className=\"text-[11px] text-text-muted\">/ hr</div>\n </div>\n </div>\n\n <Button\n className=\"w-full\"\n onClick={(e) => {\n e.stopPropagation();\n onOpen();\n }}\n >\n View profile\n </Button>\n </GlassCard>\n );\n}\n\nfunction BecomeMentorForm({\n onClose,\n onDone,\n workspaceId,\n}: {\n onClose: () => void;\n onDone: () => void;\n workspaceId?: string;\n}) {\n const [headline, setHeadline] = useState('');\n const [role, setRole] = useState('');\n const [bio, setBio] = useState('');\n const [expertise, setExpertise] = useState('');\n const [hourlyRate, setHourlyRate] = useState('');\n const [currency, setCurrency] = useState('USD');\n const [becomeMentor, { loading }] = useBecomeMentor();\n const { emit } = useTechnoSpamAnalytics();\n\n const canSubmit = headline.trim().length > 0 && !loading;\n\n const handleSubmit = async () => {\n if (!canSubmit) return;\n const input: {\n headline: string;\n role?: string;\n bio?: string;\n expertise?: string[];\n hourlyRate?: number;\n currency?: string;\n } = { headline: headline.trim() };\n if (role.trim()) input.role = role.trim();\n if (bio.trim()) input.bio = bio.trim();\n const exp = expertise\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n if (exp.length) input.expertise = exp;\n // The form collects a dollars-and-cents rate (e.g. \"50\" or \"49.99\") but\n // TecMentor.hourlyRate is stored in the smallest currency unit (cents)\n // so BOFF-4433's fee math never loses precision to floating point --\n // convert here rather than storing the raw dollar figure as if it were\n // cents (which would under-charge/under-pay by 100x).\n const rateDollars = Number.parseFloat(hourlyRate);\n if (!Number.isNaN(rateDollars) && rateDollars >= 0) {\n input.hourlyRate = Math.round(rateDollars * 100);\n }\n if (currency.trim()) input.currency = currency.trim().toUpperCase();\n\n emit('technospam.mentor.become_mentor_clicked', { workspaceId });\n const res = await becomeMentor({ variables: { input } });\n if (res.data?.tecBecomeMentor) {\n emit('technospam.mentor.became_mentor', {\n workspaceId,\n entityId: res.data.tecBecomeMentor.id,\n entityType: 'TEC_MENTOR',\n });\n notify.success('Mentor profile created', 'Your profile is pending review.');\n onDone();\n }\n };\n\n return (\n <GlassCard className=\"mb-6 p-5\">\n <div className=\"mb-4 flex items-start justify-between gap-3\">\n <div className=\"flex items-center gap-3\">\n <span className=\"flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-accent-primary to-accent-indigo text-white shadow-sm\">\n <UserPlus size={20} />\n </span>\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">Become a mentor</h2>\n <p className=\"text-sm text-text-secondary\">\n Share your expertise and start mentoring builders in the community.\n </p>\n </div>\n </div>\n <Button variant=\"ghost\" size=\"sm\" onClick={onClose} aria-label=\"Close\">\n <X size={16} />\n </Button>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4\">\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">\n Headline <span className=\"text-status-error-text\">*</span>\n </label>\n <Input\n placeholder=\"e.g. Staff Engineer · Distributed systems & career growth\"\n value={headline}\n onChange={(e) => setHeadline(e.target.value)}\n />\n </div>\n\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">\n Role &amp; company\n </label>\n <Input\n placeholder=\"e.g. Staff Engineer @ Acme\"\n value={role}\n onChange={(e) => setRole(e.target.value)}\n />\n <p className=\"mt-1 text-xs text-text-muted\">\n Shown as a subtitle under your headline. Optional.\n </p>\n </div>\n\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Bio</label>\n <Textarea\n rows={3}\n placeholder=\"Tell mentees about your experience and how you can help.\"\n value={bio}\n onChange={(e) => setBio(e.target.value)}\n />\n </div>\n\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Expertise</label>\n <Input\n placeholder=\"React, System design, Career coaching\"\n value={expertise}\n onChange={(e) => setExpertise(e.target.value)}\n />\n <p className=\"mt-1 text-xs text-text-muted\">Comma-separated topics.</p>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">\n Hourly rate\n </label>\n <Input\n type=\"number\"\n min={0}\n step={0.01}\n placeholder=\"0.00\"\n value={hourlyRate}\n onChange={(e) => setHourlyRate(e.target.value)}\n />\n <p className=\"mt-1 text-xs text-text-muted\">\n Dollars per hour (e.g. 50 or 49.99). Leave 0 for free.\n </p>\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Currency</label>\n <Input\n placeholder=\"USD\"\n value={currency}\n onChange={(e) => setCurrency(e.target.value)}\n />\n </div>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <Button onClick={handleSubmit} disabled={!canSubmit}>\n <UserPlus size={16} /> {loading ? 'Submitting…' : 'Submit profile'}\n </Button>\n <Button variant=\"outline\" onClick={onClose} disabled={loading}>\n Cancel\n </Button>\n </div>\n </div>\n </GlassCard>\n );\n}\n\nexport function MentorshipMentorsPagePage() {\n const navigate = useTechnoSpamNavigate();\n const { currentUser, workspaceId } = useTechnoSpam();\n const { emit } = useTechnoSpamAnalytics();\n const [search, setSearch] = useState('');\n const [showForm, setShowForm] = useState(false);\n\n const { limit, more } = usePageLimit();\n const { data, loading, error, refetch } = useMentorsQuery({\n status: 'ACTIVE',\n pagination: pageArg(limit),\n });\n\n const mentors = data?.tecListMentors?.items ?? [];\n const hasMore = data?.tecListMentors?.pageInfo?.hasMore ?? false;\n const userId = currentUser?.id;\n const alreadyMentor = Boolean(userId && mentors.some((m) => m.userId === userId));\n\n const filtered = useMemo(() => {\n const q = search.trim().toLowerCase();\n if (!q) return mentors;\n return mentors.filter(\n (m) =>\n m.headline.toLowerCase().includes(q) ||\n m.expertise.some((skill) => skill.toLowerCase().includes(q))\n );\n }, [mentors, search]);\n\n const accepting = mentors.filter((m) => m.isAcceptingMentees).length;\n const totalSessions = mentors.reduce((s, m) => s + m.sessionsCount, 0);\n const rated = mentors.filter((m) => m.ratingAvg > 0);\n const avgRating = rated.length\n ? (rated.reduce((s, m) => s + m.ratingAvg, 0) / rated.length).toFixed(1)\n : '—';\n\n return (\n <PageLayout\n hero\n title=\"Find a Mentor\"\n description=\"Connect with experienced builders for 1:1 guidance, code review, and career coaching.\"\n actions={\n <Button variant=\"outline\" onClick={() => navigate('/mentorship/sessions-page')}>\n <MessageSquare size={16} /> My sessions\n </Button>\n }\n >\n {alreadyMentor ? (\n <GlassCard className=\"mb-6 flex items-center gap-3 p-4\">\n <span className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-status-success-bg-subtle text-status-success-text\">\n <Award size={20} />\n </span>\n <div>\n <div className=\"text-sm font-semibold text-text-primary\">You&apos;re a mentor</div>\n <div className=\"text-xs text-text-secondary\">\n Your mentor profile is live. Mentees can request sessions with you.\n </div>\n </div>\n </GlassCard>\n ) : showForm ? (\n <BecomeMentorForm\n onClose={() => setShowForm(false)}\n onDone={() => {\n setShowForm(false);\n void refetch();\n }}\n workspaceId={workspaceId}\n />\n ) : null}\n\n <div className=\"mb-5 grid grid-cols-2 gap-3 sm:grid-cols-4\">\n <StatCard label=\"Mentors\" value={mentors.length} icon={Users} tone=\"blue\" />\n <StatCard label=\"Accepting mentees\" value={accepting} icon={Sparkles} tone=\"green\" />\n <StatCard label=\"Avg rating\" value={avgRating} icon={Star} tone=\"amber\" />\n <StatCard label=\"Total sessions\" value={totalSessions} icon={MessageSquare} tone=\"purple\" />\n </div>\n\n <div className=\"mb-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"relative w-full sm:w-80\">\n <Search\n className=\"pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-text-muted\"\n size={16}\n />\n <Input\n className=\"pl-9\"\n placeholder=\"Search by headline or expertise…\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n />\n </div>\n {!alreadyMentor && !showForm ? (\n <Button onClick={() => setShowForm(true)}>\n <UserPlus size={16} /> Become a mentor\n </Button>\n ) : null}\n </div>\n\n {loading && !mentors.length ? (\n <LoadingGrid />\n ) : error ? (\n <ErrorView title=\"Couldn't load mentors\" error={error} />\n ) : filtered.length === 0 ? (\n <EmptyView\n icon={<Users size={26} />}\n title={search ? 'No matching mentors' : 'No mentors yet'}\n description={\n search\n ? 'Try a different search term.'\n : 'Active mentors will appear here. TechnoSpam is pre-launch — be one of the first to sign up.'\n }\n action={\n !search && !alreadyMentor ? (\n <Button onClick={() => setShowForm(true)}>Become a mentor</Button>\n ) : undefined\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {filtered.map((mentor) => (\n <MentorCard\n key={mentor.id}\n mentor={mentor}\n onOpen={() => {\n emit('technospam.mentor.viewed', {\n workspaceId,\n entityId: mentor.id,\n entityType: 'TEC_MENTOR',\n });\n navigate(`/mentorship/mentor-profile-page?id=${mentor.id}`);\n }}\n />\n ))}\n </div>\n )}\n <LoadMore hasMore={hasMore} loading={loading} onClick={more} />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -182,7 +182,7 @@ function I({ loading: e, error: t, sessions: n, showMentor: r, emptyTitle: i, em
182
182
  });
183
183
  }
184
184
  function L() {
185
- let a = t(), { currentUser: o, workspaceId: s } = e(), { emit: l } = n(), [y, x] = _("mentee"), [w, T] = _(null), k = o?.id, A = g(k ? { menteeId: k } : {}), P = h({}), F = P.data?.tecListMentors.items.find((e) => e.userId === o?.id), L = F?.id, R = g(L ? { mentorId: L } : {}), [z] = f(), [B] = m(), [V] = p(), H = k ? A.data?.tecListSessions.items ?? [] : [], U = L ? R.data?.tecListSessions.items ?? [] : [], W = [...H, ...U], G = W.filter((e) => e.status === "REQUESTED" || e.status === "CONFIRMED").length, K = W.filter((e) => e.status === "COMPLETED").length, q = async (e) => {
185
+ let a = t(), { currentUser: o, workspaceId: s } = e(), { emit: l } = n(), [y, x] = _("mentee"), [w, T] = _(null), k = o?.id, A = g(k ? { menteeId: k } : {}), P = h({}), F = P.data?.tecListMentors?.items.find((e) => e.userId === o?.id), L = F?.id, R = g(L ? { mentorId: L } : {}), [z] = f(), [B] = m(), [V] = p(), H = k ? A.data?.tecListSessions?.items ?? [] : [], U = L ? R.data?.tecListSessions?.items ?? [] : [], W = [...H, ...U], G = W.filter((e) => e.status === "REQUESTED" || e.status === "CONFIRMED").length, K = W.filter((e) => e.status === "COMPLETED").length, q = async (e) => {
186
186
  T(e), l("technospam.session.cancel_clicked", {
187
187
  workspaceId: s,
188
188
  entityId: e,
@@ -1 +1 @@
1
- {"version":3,"file":"MentorshipSessionsPagePage.js","names":[],"sources":["../../../src/technospam/pages/MentorshipSessionsPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport {\n CalendarClock,\n Check,\n CheckCircle2,\n Clock,\n MessageSquare,\n Users,\n Video,\n X,\n} from 'lucide-react';\nimport { Button, GlassCard, Input, Tabs, TabsList, TabsTrigger } from '@burdenoff/fe-libs/ui';\nimport {\n EmptyView,\n ErrorView,\n LoadingRows,\n PageLayout,\n StatCard,\n StatusBadge,\n} from '../components';\nimport { useTechnoSpam, useTechnoSpamNavigate } from '../providers/TechnoSpamProvider';\nimport {\n useCancelSession,\n useCompleteSession,\n useConfirmSession,\n useMentorsQuery,\n useSessionsQuery,\n type TecMentorSession,\n} from '../api/technospamApi';\nimport { formatDateTime, formatDuration } from '../utils/format';\nimport { notify } from '../lib/toast';\nimport { useTechnoSpamAnalytics } from '../hooks/useTechnoSpamAnalytics';\n\ntype Perspective = 'mentee' | 'mentor';\n\nconst CANCELLABLE = new Set(['REQUESTED', 'CONFIRMED']);\n\nfunction SessionRow({\n session,\n showMentor,\n isMentor,\n onCancel,\n onConfirm,\n onComplete,\n busy,\n}: {\n session: TecMentorSession;\n showMentor: boolean;\n isMentor: boolean;\n onCancel: (id: string) => void;\n onConfirm: (id: string, meetingUrl: string) => Promise<void>;\n onComplete: (id: string, rating: number, feedback: string) => Promise<void>;\n busy: boolean;\n}) {\n const canCancel = CANCELLABLE.has(session.status);\n const [mode, setMode] = useState<'none' | 'confirm' | 'complete'>('none');\n const [meetingUrl, setMeetingUrl] = useState('');\n const [rating, setRating] = useState('5');\n const [feedback, setFeedback] = useState('');\n\n const submitConfirm = async () => {\n await onConfirm(session.id, meetingUrl.trim());\n setMode('none');\n setMeetingUrl('');\n };\n const submitComplete = async () => {\n await onComplete(session.id, Math.min(5, Math.max(1, Number(rating) || 5)), feedback.trim());\n setMode('none');\n };\n\n return (\n <GlassCard className=\"flex flex-col gap-3 p-4 transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md\">\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex items-start gap-3\">\n <span className=\"flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-gradient-to-br from-accent-primary to-accent-purple text-white shadow-sm\">\n <Video size={18} />\n </span>\n <div className=\"min-w-0\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"truncate text-sm font-semibold text-text-primary\">\n {session.topic}\n </span>\n <StatusBadge status={session.status} />\n {session.rating ? (\n <span className=\"text-xs text-text-muted\">★ {session.rating}/5</span>\n ) : null}\n </div>\n {showMentor && session.mentor?.headline ? (\n <div className=\"mt-0.5 truncate text-xs text-text-secondary\">\n with {session.mentor.headline}\n </div>\n ) : null}\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 <CalendarClock size={13} /> {formatDateTime(session.scheduledAt)}\n </span>\n <span className=\"inline-flex items-center gap-1\">\n <Clock size={13} /> {formatDuration(session.durationMinutes)}\n </span>\n </div>\n {session.agenda ? (\n <p className=\"mt-1 line-clamp-2 text-xs text-text-secondary\">{session.agenda}</p>\n ) : null}\n </div>\n </div>\n <div className=\"flex shrink-0 flex-wrap items-center gap-2\">\n {session.meetingUrl ? (\n <Button\n size=\"sm\"\n onClick={() => window.open(session.meetingUrl ?? '', '_blank', 'noopener,noreferrer')}\n >\n <Video size={15} /> Join\n </Button>\n ) : null}\n {isMentor && session.status === 'REQUESTED' ? (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setMode(mode === 'confirm' ? 'none' : 'confirm')}\n >\n <Check size={15} /> Confirm\n </Button>\n ) : null}\n {isMentor && session.status === 'CONFIRMED' ? (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setMode(mode === 'complete' ? 'none' : 'complete')}\n >\n <CheckCircle2 size={15} /> Mark complete\n </Button>\n ) : null}\n {canCancel ? (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => onCancel(session.id)} disabled={busy}>\n <X size={15} /> Cancel\n </Button>\n ) : null}\n </div>\n </div>\n\n {mode === 'confirm' ? (\n <div className=\"flex flex-col gap-2 rounded-lg border border-border-seam bg-bg-sunken p-3 sm:flex-row sm:items-center\">\n <Input\n className=\"flex-1\"\n placeholder=\"Meeting link (e.g. https://meet.…)\"\n value={meetingUrl}\n onChange={(e) => setMeetingUrl(e.target.value)}\n />\n <Button size=\"sm\" onClick={submitConfirm} disabled={busy}>\n Confirm session\n </Button>\n </div>\n ) : null}\n {mode === 'complete' ? (\n <div className=\"flex flex-col gap-2 rounded-lg border border-border-seam bg-bg-sunken p-3\">\n <div className=\"flex items-center gap-2\">\n <label className=\"text-xs text-text-secondary\">Rating (1–5)</label>\n <Input\n type=\"number\"\n min={1}\n max={5}\n className=\"w-20\"\n value={rating}\n onChange={(e) => setRating(e.target.value)}\n />\n </div>\n <Input\n placeholder=\"Feedback for the mentee (optional)\"\n value={feedback}\n onChange={(e) => setFeedback(e.target.value)}\n />\n <div>\n <Button size=\"sm\" onClick={submitComplete} disabled={busy}>\n Complete session\n </Button>\n </div>\n </div>\n ) : null}\n </GlassCard>\n );\n}\n\nfunction SessionsList({\n loading,\n error,\n sessions,\n showMentor,\n emptyTitle,\n emptyDescription,\n emptyAction,\n isMentor,\n onCancel,\n onConfirm,\n onComplete,\n busyId,\n}: {\n loading: boolean;\n error?: { message?: string };\n sessions: TecMentorSession[];\n showMentor: boolean;\n emptyTitle: string;\n emptyDescription: string;\n emptyAction?: React.ReactNode;\n isMentor: boolean;\n onCancel: (id: string) => void;\n onConfirm: (id: string, meetingUrl: string) => Promise<void>;\n onComplete: (id: string, rating: number, feedback: string) => Promise<void>;\n busyId: string | null;\n}) {\n if (loading && sessions.length === 0) return <LoadingRows count={3} height=\"h-24\" />;\n if (error) return <ErrorView title=\"Couldn't load sessions\" error={error} />;\n if (sessions.length === 0) {\n return (\n <EmptyView\n icon={<MessageSquare size={26} />}\n title={emptyTitle}\n description={emptyDescription}\n action={emptyAction}\n />\n );\n }\n const sorted = [...sessions].sort(\n (a, b) => new Date(b.scheduledAt).getTime() - new Date(a.scheduledAt).getTime()\n );\n return (\n <div className=\"space-y-3\">\n {sorted.map((session) => (\n <SessionRow\n key={session.id}\n session={session}\n showMentor={showMentor}\n isMentor={isMentor}\n onCancel={onCancel}\n onConfirm={onConfirm}\n onComplete={onComplete}\n busy={busyId === session.id}\n />\n ))}\n </div>\n );\n}\n\nexport function MentorshipSessionsPagePage() {\n const navigate = useTechnoSpamNavigate();\n const { currentUser, workspaceId } = useTechnoSpam();\n const { emit } = useTechnoSpamAnalytics();\n const [tab, setTab] = useState<Perspective>('mentee');\n const [cancellingId, setCancellingId] = useState<string | null>(null);\n\n const menteeId = currentUser?.id;\n\n // As mentee: sessions the current user booked.\n const menteeQ = useSessionsQuery(menteeId ? { menteeId } : {});\n\n // Resolve whether the current user is a mentor, to load their mentor sessions.\n const mentorsQ = useMentorsQuery({});\n const myMentor = mentorsQ.data?.tecListMentors.items.find((m) => m.userId === currentUser?.id);\n const mentorId = myMentor?.id;\n const mentorQ = useSessionsQuery(mentorId ? { mentorId } : {});\n\n const [cancelSession] = useCancelSession();\n const [confirmSession] = useConfirmSession();\n const [completeSession] = useCompleteSession();\n\n const menteeSessions = menteeId ? (menteeQ.data?.tecListSessions.items ?? []) : [];\n const mentorSessions = mentorId ? (mentorQ.data?.tecListSessions.items ?? []) : [];\n const allSessions = [...menteeSessions, ...mentorSessions];\n const upcomingCount = allSessions.filter(\n (s) => s.status === 'REQUESTED' || s.status === 'CONFIRMED'\n ).length;\n const completedCount = allSessions.filter((s) => s.status === 'COMPLETED').length;\n\n const handleCancel = async (id: string) => {\n setCancellingId(id);\n emit('technospam.session.cancel_clicked', {\n workspaceId,\n entityId: id,\n entityType: 'TEC_MENTOR_SESSION',\n });\n try {\n const res = await cancelSession({ variables: { id } });\n if (res.data?.tecCancelSession) {\n emit('technospam.session.cancelled', {\n workspaceId,\n entityId: id,\n entityType: 'TEC_MENTOR_SESSION',\n });\n notify.success('Session cancelled', 'The session has been cancelled.');\n }\n } finally {\n setCancellingId(null);\n }\n };\n\n const handleConfirm = async (id: string, meetingUrl: string) => {\n setCancellingId(id);\n try {\n const res = await confirmSession({\n variables: { id, meetingUrl: meetingUrl || undefined },\n });\n if (res.data?.tecConfirmSession) {\n notify.success('Session confirmed', 'The mentee has been notified.');\n await mentorQ.refetch();\n }\n } finally {\n setCancellingId(null);\n }\n };\n\n const handleComplete = async (id: string, rating: number, feedback: string) => {\n setCancellingId(id);\n emit('technospam.session.complete_clicked', {\n workspaceId,\n entityId: id,\n entityType: 'TEC_MENTOR_SESSION',\n });\n try {\n const res = await completeSession({\n variables: { id, rating, feedback: feedback || undefined },\n });\n if (res.data?.tecCompleteSession) {\n emit('technospam.session.completed', {\n workspaceId,\n entityId: id,\n entityType: 'TEC_MENTOR_SESSION',\n });\n notify.success('Session completed', 'Nice work — the session is wrapped up.');\n await mentorQ.refetch();\n }\n } finally {\n setCancellingId(null);\n }\n };\n\n if (!currentUser?.id) {\n return (\n <PageLayout hero title=\"My Sessions\" description=\"Track your mentorship sessions.\">\n <EmptyView\n icon={<MessageSquare size={26} />}\n title=\"Sign in to view your sessions\"\n description=\"Your booked and hosted mentorship sessions will appear here once you're signed in.\"\n />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n hero\n title=\"My Sessions\"\n description=\"Manage the mentorship sessions you've booked and the ones you host.\"\n actions={\n <Button variant=\"outline\" onClick={() => navigate('/mentorship/mentors-page')}>\n <Users size={16} /> Find a mentor\n </Button>\n }\n >\n <div className=\"mb-5 grid grid-cols-2 gap-3 sm:grid-cols-4\">\n <StatCard\n label=\"As mentee\"\n value={menteeSessions.length}\n icon={MessageSquare}\n tone=\"blue\"\n />\n <StatCard\n label=\"As mentor\"\n value={mentorId ? mentorSessions.length : 0}\n icon={Users}\n tone=\"purple\"\n />\n <StatCard label=\"Upcoming\" value={upcomingCount} icon={CalendarClock} tone=\"teal\" />\n <StatCard label=\"Completed\" value={completedCount} icon={CheckCircle2} tone=\"green\" />\n </div>\n\n <Tabs value={tab} onValueChange={(v) => setTab(v as Perspective)} className=\"mb-4\">\n <TabsList>\n <TabsTrigger value=\"mentee\">As mentee</TabsTrigger>\n <TabsTrigger value=\"mentor\">As mentor</TabsTrigger>\n </TabsList>\n </Tabs>\n\n {tab === 'mentee' ? (\n <SessionsList\n loading={menteeQ.loading}\n error={menteeQ.error}\n sessions={menteeSessions}\n showMentor\n emptyTitle=\"No sessions booked yet\"\n emptyDescription=\"Find a mentor and request a session to get personalized guidance.\"\n emptyAction={\n <Button onClick={() => navigate('/mentorship/mentors-page')}>Find a mentor</Button>\n }\n isMentor={false}\n onCancel={handleCancel}\n onConfirm={handleConfirm}\n onComplete={handleComplete}\n busyId={cancellingId}\n />\n ) : mentorsQ.loading && !myMentor ? (\n <LoadingRows count={3} height=\"h-24\" />\n ) : !mentorId ? (\n <EmptyView\n icon={<Users size={26} />}\n title=\"You're not a mentor yet\"\n description=\"Create a mentor profile to start hosting sessions and helping others build.\"\n action={\n <Button onClick={() => navigate('/mentorship/mentors-page')}>Become a mentor</Button>\n }\n />\n ) : (\n <SessionsList\n loading={mentorQ.loading}\n error={mentorQ.error}\n sessions={mentorSessions}\n showMentor={false}\n emptyTitle=\"No sessions to host yet\"\n emptyDescription=\"When mentees request time with you, their sessions will show up here.\"\n isMentor\n onCancel={handleCancel}\n onConfirm={handleConfirm}\n onComplete={handleComplete}\n busyId={cancellingId}\n />\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"MentorshipSessionsPagePage.js","names":[],"sources":["../../../src/technospam/pages/MentorshipSessionsPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport {\n CalendarClock,\n Check,\n CheckCircle2,\n Clock,\n MessageSquare,\n Users,\n Video,\n X,\n} from 'lucide-react';\nimport { Button, GlassCard, Input, Tabs, TabsList, TabsTrigger } from '@burdenoff/fe-libs/ui';\nimport {\n EmptyView,\n ErrorView,\n LoadingRows,\n PageLayout,\n StatCard,\n StatusBadge,\n} from '../components';\nimport { useTechnoSpam, useTechnoSpamNavigate } from '../providers/TechnoSpamProvider';\nimport {\n useCancelSession,\n useCompleteSession,\n useConfirmSession,\n useMentorsQuery,\n useSessionsQuery,\n type TecMentorSession,\n} from '../api/technospamApi';\nimport { formatDateTime, formatDuration } from '../utils/format';\nimport { notify } from '../lib/toast';\nimport { useTechnoSpamAnalytics } from '../hooks/useTechnoSpamAnalytics';\n\ntype Perspective = 'mentee' | 'mentor';\n\nconst CANCELLABLE = new Set(['REQUESTED', 'CONFIRMED']);\n\nfunction SessionRow({\n session,\n showMentor,\n isMentor,\n onCancel,\n onConfirm,\n onComplete,\n busy,\n}: {\n session: TecMentorSession;\n showMentor: boolean;\n isMentor: boolean;\n onCancel: (id: string) => void;\n onConfirm: (id: string, meetingUrl: string) => Promise<void>;\n onComplete: (id: string, rating: number, feedback: string) => Promise<void>;\n busy: boolean;\n}) {\n const canCancel = CANCELLABLE.has(session.status);\n const [mode, setMode] = useState<'none' | 'confirm' | 'complete'>('none');\n const [meetingUrl, setMeetingUrl] = useState('');\n const [rating, setRating] = useState('5');\n const [feedback, setFeedback] = useState('');\n\n const submitConfirm = async () => {\n await onConfirm(session.id, meetingUrl.trim());\n setMode('none');\n setMeetingUrl('');\n };\n const submitComplete = async () => {\n await onComplete(session.id, Math.min(5, Math.max(1, Number(rating) || 5)), feedback.trim());\n setMode('none');\n };\n\n return (\n <GlassCard className=\"flex flex-col gap-3 p-4 transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md\">\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex items-start gap-3\">\n <span className=\"flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-gradient-to-br from-accent-primary to-accent-purple text-white shadow-sm\">\n <Video size={18} />\n </span>\n <div className=\"min-w-0\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"truncate text-sm font-semibold text-text-primary\">\n {session.topic}\n </span>\n <StatusBadge status={session.status} />\n {session.rating ? (\n <span className=\"text-xs text-text-muted\">★ {session.rating}/5</span>\n ) : null}\n </div>\n {showMentor && session.mentor?.headline ? (\n <div className=\"mt-0.5 truncate text-xs text-text-secondary\">\n with {session.mentor.headline}\n </div>\n ) : null}\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 <CalendarClock size={13} /> {formatDateTime(session.scheduledAt)}\n </span>\n <span className=\"inline-flex items-center gap-1\">\n <Clock size={13} /> {formatDuration(session.durationMinutes)}\n </span>\n </div>\n {session.agenda ? (\n <p className=\"mt-1 line-clamp-2 text-xs text-text-secondary\">{session.agenda}</p>\n ) : null}\n </div>\n </div>\n <div className=\"flex shrink-0 flex-wrap items-center gap-2\">\n {session.meetingUrl ? (\n <Button\n size=\"sm\"\n onClick={() => window.open(session.meetingUrl ?? '', '_blank', 'noopener,noreferrer')}\n >\n <Video size={15} /> Join\n </Button>\n ) : null}\n {isMentor && session.status === 'REQUESTED' ? (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setMode(mode === 'confirm' ? 'none' : 'confirm')}\n >\n <Check size={15} /> Confirm\n </Button>\n ) : null}\n {isMentor && session.status === 'CONFIRMED' ? (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setMode(mode === 'complete' ? 'none' : 'complete')}\n >\n <CheckCircle2 size={15} /> Mark complete\n </Button>\n ) : null}\n {canCancel ? (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => onCancel(session.id)} disabled={busy}>\n <X size={15} /> Cancel\n </Button>\n ) : null}\n </div>\n </div>\n\n {mode === 'confirm' ? (\n <div className=\"flex flex-col gap-2 rounded-lg border border-border-seam bg-bg-sunken p-3 sm:flex-row sm:items-center\">\n <Input\n className=\"flex-1\"\n placeholder=\"Meeting link (e.g. https://meet.…)\"\n value={meetingUrl}\n onChange={(e) => setMeetingUrl(e.target.value)}\n />\n <Button size=\"sm\" onClick={submitConfirm} disabled={busy}>\n Confirm session\n </Button>\n </div>\n ) : null}\n {mode === 'complete' ? (\n <div className=\"flex flex-col gap-2 rounded-lg border border-border-seam bg-bg-sunken p-3\">\n <div className=\"flex items-center gap-2\">\n <label className=\"text-xs text-text-secondary\">Rating (1–5)</label>\n <Input\n type=\"number\"\n min={1}\n max={5}\n className=\"w-20\"\n value={rating}\n onChange={(e) => setRating(e.target.value)}\n />\n </div>\n <Input\n placeholder=\"Feedback for the mentee (optional)\"\n value={feedback}\n onChange={(e) => setFeedback(e.target.value)}\n />\n <div>\n <Button size=\"sm\" onClick={submitComplete} disabled={busy}>\n Complete session\n </Button>\n </div>\n </div>\n ) : null}\n </GlassCard>\n );\n}\n\nfunction SessionsList({\n loading,\n error,\n sessions,\n showMentor,\n emptyTitle,\n emptyDescription,\n emptyAction,\n isMentor,\n onCancel,\n onConfirm,\n onComplete,\n busyId,\n}: {\n loading: boolean;\n error?: { message?: string };\n sessions: TecMentorSession[];\n showMentor: boolean;\n emptyTitle: string;\n emptyDescription: string;\n emptyAction?: React.ReactNode;\n isMentor: boolean;\n onCancel: (id: string) => void;\n onConfirm: (id: string, meetingUrl: string) => Promise<void>;\n onComplete: (id: string, rating: number, feedback: string) => Promise<void>;\n busyId: string | null;\n}) {\n if (loading && sessions.length === 0) return <LoadingRows count={3} height=\"h-24\" />;\n if (error) return <ErrorView title=\"Couldn't load sessions\" error={error} />;\n if (sessions.length === 0) {\n return (\n <EmptyView\n icon={<MessageSquare size={26} />}\n title={emptyTitle}\n description={emptyDescription}\n action={emptyAction}\n />\n );\n }\n const sorted = [...sessions].sort(\n (a, b) => new Date(b.scheduledAt).getTime() - new Date(a.scheduledAt).getTime()\n );\n return (\n <div className=\"space-y-3\">\n {sorted.map((session) => (\n <SessionRow\n key={session.id}\n session={session}\n showMentor={showMentor}\n isMentor={isMentor}\n onCancel={onCancel}\n onConfirm={onConfirm}\n onComplete={onComplete}\n busy={busyId === session.id}\n />\n ))}\n </div>\n );\n}\n\nexport function MentorshipSessionsPagePage() {\n const navigate = useTechnoSpamNavigate();\n const { currentUser, workspaceId } = useTechnoSpam();\n const { emit } = useTechnoSpamAnalytics();\n const [tab, setTab] = useState<Perspective>('mentee');\n const [cancellingId, setCancellingId] = useState<string | null>(null);\n\n const menteeId = currentUser?.id;\n\n // As mentee: sessions the current user booked.\n const menteeQ = useSessionsQuery(menteeId ? { menteeId } : {});\n\n // Resolve whether the current user is a mentor, to load their mentor sessions.\n const mentorsQ = useMentorsQuery({});\n const myMentor = mentorsQ.data?.tecListMentors?.items.find((m) => m.userId === currentUser?.id);\n const mentorId = myMentor?.id;\n const mentorQ = useSessionsQuery(mentorId ? { mentorId } : {});\n\n const [cancelSession] = useCancelSession();\n const [confirmSession] = useConfirmSession();\n const [completeSession] = useCompleteSession();\n\n const menteeSessions = menteeId ? (menteeQ.data?.tecListSessions?.items ?? []) : [];\n const mentorSessions = mentorId ? (mentorQ.data?.tecListSessions?.items ?? []) : [];\n const allSessions = [...menteeSessions, ...mentorSessions];\n const upcomingCount = allSessions.filter(\n (s) => s.status === 'REQUESTED' || s.status === 'CONFIRMED'\n ).length;\n const completedCount = allSessions.filter((s) => s.status === 'COMPLETED').length;\n\n const handleCancel = async (id: string) => {\n setCancellingId(id);\n emit('technospam.session.cancel_clicked', {\n workspaceId,\n entityId: id,\n entityType: 'TEC_MENTOR_SESSION',\n });\n try {\n const res = await cancelSession({ variables: { id } });\n if (res.data?.tecCancelSession) {\n emit('technospam.session.cancelled', {\n workspaceId,\n entityId: id,\n entityType: 'TEC_MENTOR_SESSION',\n });\n notify.success('Session cancelled', 'The session has been cancelled.');\n }\n } finally {\n setCancellingId(null);\n }\n };\n\n const handleConfirm = async (id: string, meetingUrl: string) => {\n setCancellingId(id);\n try {\n const res = await confirmSession({\n variables: { id, meetingUrl: meetingUrl || undefined },\n });\n if (res.data?.tecConfirmSession) {\n notify.success('Session confirmed', 'The mentee has been notified.');\n await mentorQ.refetch();\n }\n } finally {\n setCancellingId(null);\n }\n };\n\n const handleComplete = async (id: string, rating: number, feedback: string) => {\n setCancellingId(id);\n emit('technospam.session.complete_clicked', {\n workspaceId,\n entityId: id,\n entityType: 'TEC_MENTOR_SESSION',\n });\n try {\n const res = await completeSession({\n variables: { id, rating, feedback: feedback || undefined },\n });\n if (res.data?.tecCompleteSession) {\n emit('technospam.session.completed', {\n workspaceId,\n entityId: id,\n entityType: 'TEC_MENTOR_SESSION',\n });\n notify.success('Session completed', 'Nice work — the session is wrapped up.');\n await mentorQ.refetch();\n }\n } finally {\n setCancellingId(null);\n }\n };\n\n if (!currentUser?.id) {\n return (\n <PageLayout hero title=\"My Sessions\" description=\"Track your mentorship sessions.\">\n <EmptyView\n icon={<MessageSquare size={26} />}\n title=\"Sign in to view your sessions\"\n description=\"Your booked and hosted mentorship sessions will appear here once you're signed in.\"\n />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n hero\n title=\"My Sessions\"\n description=\"Manage the mentorship sessions you've booked and the ones you host.\"\n actions={\n <Button variant=\"outline\" onClick={() => navigate('/mentorship/mentors-page')}>\n <Users size={16} /> Find a mentor\n </Button>\n }\n >\n <div className=\"mb-5 grid grid-cols-2 gap-3 sm:grid-cols-4\">\n <StatCard\n label=\"As mentee\"\n value={menteeSessions.length}\n icon={MessageSquare}\n tone=\"blue\"\n />\n <StatCard\n label=\"As mentor\"\n value={mentorId ? mentorSessions.length : 0}\n icon={Users}\n tone=\"purple\"\n />\n <StatCard label=\"Upcoming\" value={upcomingCount} icon={CalendarClock} tone=\"teal\" />\n <StatCard label=\"Completed\" value={completedCount} icon={CheckCircle2} tone=\"green\" />\n </div>\n\n <Tabs value={tab} onValueChange={(v) => setTab(v as Perspective)} className=\"mb-4\">\n <TabsList>\n <TabsTrigger value=\"mentee\">As mentee</TabsTrigger>\n <TabsTrigger value=\"mentor\">As mentor</TabsTrigger>\n </TabsList>\n </Tabs>\n\n {tab === 'mentee' ? (\n <SessionsList\n loading={menteeQ.loading}\n error={menteeQ.error}\n sessions={menteeSessions}\n showMentor\n emptyTitle=\"No sessions booked yet\"\n emptyDescription=\"Find a mentor and request a session to get personalized guidance.\"\n emptyAction={\n <Button onClick={() => navigate('/mentorship/mentors-page')}>Find a mentor</Button>\n }\n isMentor={false}\n onCancel={handleCancel}\n onConfirm={handleConfirm}\n onComplete={handleComplete}\n busyId={cancellingId}\n />\n ) : mentorsQ.loading && !myMentor ? (\n <LoadingRows count={3} height=\"h-24\" />\n ) : !mentorId ? (\n <EmptyView\n icon={<Users size={26} />}\n title=\"You're not a mentor yet\"\n description=\"Create a mentor profile to start hosting sessions and helping others build.\"\n action={\n <Button onClick={() => navigate('/mentorship/mentors-page')}>Become a mentor</Button>\n }\n />\n ) : (\n <SessionsList\n loading={mentorQ.loading}\n error={mentorQ.error}\n sessions={mentorSessions}\n showMentor={false}\n emptyTitle=\"No sessions to host yet\"\n emptyDescription=\"When mentees request time with you, their sessions will show up here.\"\n isMentor\n onCancel={handleCancel}\n onConfirm={handleConfirm}\n onComplete={handleComplete}\n busyId={cancellingId}\n />\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -198,7 +198,7 @@ function L({ plan: e, onArchived: t, onOpenStep: n }) {
198
198
  });
199
199
  }
200
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));
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
202
  return /* @__PURE__ */ D(t, {
203
203
  hero: !0,
204
204
  title: "Study Planner",
@@ -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 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
+ {"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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"}
@@ -0,0 +1,2 @@
1
+
2
+ //# sourceMappingURL=setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/test/setup.ts"],"names":[],"mappings":"AAAA,OAAO,2BAA2B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-technospam",
3
- "version": "2026.827.1",
3
+ "version": "2026.828.1",
4
4
  "description": "Burdenoff TechnoSpam Microfrontend",
5
5
  "type": "module",
6
6
  "files": [
@@ -30,13 +30,15 @@
30
30
  "lint": "eslint .",
31
31
  "format": "prettier --write \"src/**/*.{ts,tsx}\"",
32
32
  "type:check": "bash scripts/build-limits.sh tsc --noEmit",
33
- "sanity": "bash scripts/build-limits.sh --gate bash -c 'bun run codegen && bun run lint && bun run format && bun run type:check && bun run build'",
33
+ "sanity": "bash scripts/build-limits.sh --gate bash -c 'bun run codegen && bun run lint && bun run format && bun run type:check && bun run test && bun run build'",
34
34
  "schema:pull": "graphql-codegen --project schema-wspace && graphql-codegen --project schema-global",
35
35
  "codegen": "graphql-codegen --project wspace && graphql-codegen --project global",
36
36
  "codegen:all": "bun run schema:pull && bun run codegen",
37
37
  "storybook": "storybook dev -p 6006",
38
38
  "build-storybook": "storybook build",
39
39
  "test:storybook": "test-storybook",
40
+ "test": "vitest run",
41
+ "test:watch": "vitest",
40
42
  "switch:status": "bun scripts/deps-mode.ts status",
41
43
  "switch:remote": "bun scripts/deps-mode.ts remote",
42
44
  "switch:local": "bun scripts/deps-mode.ts local",
@@ -79,7 +81,10 @@
79
81
  "@types/react": "^19.0.2",
80
82
  "@types/react-dom": "^19.0.2",
81
83
  "@vitejs/plugin-react": "^4.3.4",
84
+ "@testing-library/jest-dom": "^6.6.3",
85
+ "@testing-library/react": "^16.1.0",
82
86
  "autoprefixer": "^10.4.20",
87
+ "jsdom": "^25.0.1",
83
88
  "eslint": "^9.18.0",
84
89
  "eslint-plugin-react": "^7.37.5",
85
90
  "eslint-plugin-react-hooks": "^7.0.1",
@@ -92,6 +97,7 @@
92
97
  "typescript": "^5.9.0",
93
98
  "typescript-eslint": "^8.54.0",
94
99
  "vite": "8.0.8",
100
+ "vitest": "^3.0.0",
95
101
  "vite-plugin-dts": "3.9.1"
96
102
  },
97
103
  "peerDependencies": {