@burdenoff/microfe-technospam 2026.1102.2 → 2026.1102.3
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.
- package/dist/technospam/TechnoSpamRoot.d.ts.map +1 -1
- package/dist/technospam/TechnoSpamRoot.js +187 -178
- package/dist/technospam/TechnoSpamRoot.js.map +1 -1
- package/dist/technospam/api/technospamApi.js +141 -141
- package/dist/technospam/api/technospamApi.js.map +1 -1
- package/dist/technospam/components/MediaCover.js +2 -2
- package/dist/technospam/components/MediaCover.js.map +1 -1
- package/dist/technospam/components/MobileSectionTabs.d.ts +9 -0
- package/dist/technospam/components/MobileSectionTabs.d.ts.map +1 -0
- package/dist/technospam/components/MobileSectionTabs.js +43 -0
- package/dist/technospam/components/MobileSectionTabs.js.map +1 -0
- package/dist/technospam/components/PageLayout.d.ts +7 -1
- package/dist/technospam/components/PageLayout.d.ts.map +1 -1
- package/dist/technospam/components/PageLayout.js +40 -28
- package/dist/technospam/components/PageLayout.js.map +1 -1
- package/dist/technospam/components/lesson/SaveCourseOffline.js +6 -6
- package/dist/technospam/hooks/usePhoneLayout.d.ts +18 -0
- package/dist/technospam/hooks/usePhoneLayout.d.ts.map +1 -0
- package/dist/technospam/hooks/usePhoneLayout.js +14 -0
- package/dist/technospam/hooks/usePhoneLayout.js.map +1 -0
- package/dist/technospam/pages/AdminAdminDashboardPagePage.js +1 -1
- package/dist/technospam/pages/AdminAdminDashboardPagePage.js.map +1 -1
- package/dist/technospam/pages/ApplicationsProblemsPagePage.d.ts.map +1 -1
- package/dist/technospam/pages/ApplicationsProblemsPagePage.js +89 -87
- package/dist/technospam/pages/ApplicationsProblemsPagePage.js.map +1 -1
- package/dist/technospam/pages/CoursesCourseDetailsPagePage.js +3 -3
- package/dist/technospam/pages/CoursesCourseDetailsPagePage.js.map +1 -1
- package/dist/technospam/pages/CoursesCoursesPagePage.d.ts.map +1 -1
- package/dist/technospam/pages/CoursesCoursesPagePage.js +117 -81
- package/dist/technospam/pages/CoursesCoursesPagePage.js.map +1 -1
- package/dist/technospam/pages/CoursesMyLearningPagePage.js +1 -1
- package/dist/technospam/pages/CoursesMyLearningPagePage.js.map +1 -1
- package/dist/technospam/pages/DashboardDashboardPagePage.d.ts.map +1 -1
- package/dist/technospam/pages/DashboardDashboardPagePage.js +21 -17
- package/dist/technospam/pages/DashboardDashboardPagePage.js.map +1 -1
- package/dist/technospam/pages/HackathonsHackathonDetailsPagePage.js +3 -3
- package/dist/technospam/pages/HackathonsHackathonDetailsPagePage.js.map +1 -1
- package/dist/technospam/pages/MentorshipMentorProfilePagePage.js +4 -4
- package/dist/technospam/pages/MentorshipMentorProfilePagePage.js.map +1 -1
- package/dist/technospam/pages/MentorshipMentorsPagePage.d.ts.map +1 -1
- package/dist/technospam/pages/MentorshipMentorsPagePage.js +6 -4
- package/dist/technospam/pages/MentorshipMentorsPagePage.js.map +1 -1
- package/dist/technospam/pages/MentorshipSessionsPagePage.js +1 -1
- package/dist/technospam/pages/MentorshipSessionsPagePage.js.map +1 -1
- package/dist/technospam/types/index.d.ts +7 -0
- package/dist/technospam/types/index.d.ts.map +1 -1
- package/dist/technospam/utils/mobileTabs.d.ts +45 -0
- package/dist/technospam/utils/mobileTabs.d.ts.map +1 -0
- package/dist/technospam/utils/mobileTabs.js +70 -0
- package/dist/technospam/utils/mobileTabs.js.map +1 -0
- package/dist/test/matchMedia.d.ts +6 -0
- package/dist/test/matchMedia.d.ts.map +1 -0
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MentorshipMentorProfilePagePage.js","names":[],"sources":["../../../src/technospam/pages/MentorshipMentorProfilePagePage.tsx"],"sourcesContent":["import { createElement, useState } from 'react';\nimport { useSearchParams } from 'react-router';\nimport {\n Award,\n CalendarClock,\n CreditCard,\n DollarSign,\n MessageSquare,\n Star,\n Video,\n} from 'lucide-react';\nimport { Badge, Button, GlassCard, Input, Skeleton, Textarea } from '@burdenoff/fe-libs/ui';\nimport {\n EmptyView,\n ErrorView,\n PageLayout,\n StatCard,\n StatusBadge,\n coverGradient,\n topicIcon,\n} from '../components';\nimport { useTechnoSpam } from '../providers/TechnoSpamProvider';\nimport {\n useBookSession,\n useMentorQuery,\n useSessionCostEstimate,\n type TecMentorSession,\n} from '../api/technospamApi';\nimport { formatDateTime, formatDuration, formatMoneyFromMinorUnits } from '../utils/format';\nimport { cn } from '../utils/cn';\nimport { notify } from '../lib/toast';\nimport { useTechnoSpamAnalytics } from '../hooks/useTechnoSpamAnalytics';\n\nfunction BookSessionForm({\n mentorId,\n disabled,\n onBooked,\n workspaceId,\n}: {\n mentorId: string;\n disabled: boolean;\n onBooked?: () => void;\n workspaceId?: string;\n}) {\n const [topic, setTopic] = useState('');\n const [agenda, setAgenda] = useState('');\n const [scheduledAt, setScheduledAt] = useState('');\n const [durationMinutes, setDurationMinutes] = useState('60');\n const [bookSession, { loading }] = useBookSession();\n const { emit } = useTechnoSpamAnalytics();\n\n const parsedDate = scheduledAt ? new Date(scheduledAt) : null;\n const validDate = parsedDate != null && !Number.isNaN(parsedDate.getTime());\n const canSubmit = topic.trim().length > 0 && validDate && !loading && !disabled;\n\n // Live cost preview -- computed server-side from the mentor's real rate,\n // the exact same calculation tecBookSession snapshots onto the session.\n // Shown BEFORE booking so the mentee sees the real price up front.\n const parsedDuration = Number.parseInt(durationMinutes, 10);\n const { data: costData, loading: costLoading } = useSessionCostEstimate(\n mentorId,\n Number.isNaN(parsedDuration) ? 0 : parsedDuration\n );\n const estimate = costData?.tecCalculateSessionCost;\n\n const handleSubmit = async () => {\n if (!canSubmit || !parsedDate) return;\n const input: {\n mentorId: string;\n topic: string;\n agenda?: string;\n scheduledAt: string;\n durationMinutes?: number;\n } = {\n mentorId,\n topic: topic.trim(),\n scheduledAt: parsedDate.toISOString(),\n };\n if (agenda.trim()) input.agenda = agenda.trim();\n const dur = Number.parseInt(durationMinutes, 10);\n if (!Number.isNaN(dur) && dur > 0) input.durationMinutes = dur;\n\n emit('technospam.mentor.book_clicked', {\n workspaceId,\n entityId: mentorId,\n entityType: 'TEC_MENTOR',\n });\n const res = await bookSession({ variables: { input } });\n if (res.data?.tecBookSession) {\n emit('technospam.mentor.booked', {\n workspaceId,\n entityId: mentorId,\n entityType: 'TEC_MENTOR',\n sessionId: res.data.tecBookSession.id,\n });\n notify.success('Session requested', 'Your mentor will confirm the booking soon.');\n setTopic('');\n setAgenda('');\n setScheduledAt('');\n setDurationMinutes('60');\n onBooked?.();\n }\n };\n\n return (\n <GlassCard className=\"p-5\">\n <div className=\"mb-4 flex items-start 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-purple text-white shadow-sm\">\n <CalendarClock size={20} />\n </span>\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">Book a session</h2>\n <p className=\"text-sm text-text-secondary\">Schedule 1:1 time with your mentor.</p>\n </div>\n </div>\n\n {disabled ? (\n <p className=\"rounded-lg border border-border-seam bg-bg-sunken px-3 py-2 text-sm text-text-secondary\">\n This mentor isn't accepting new mentees right now.\n </p>\n ) : null}\n\n <div className=\"mt-4 grid grid-cols-1 gap-4\">\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">\n Topic <span className=\"text-status-error-text\">*</span>\n </label>\n <Input\n placeholder=\"e.g. System design interview prep\"\n value={topic}\n onChange={(e) => setTopic(e.target.value)}\n disabled={disabled}\n />\n </div>\n\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Agenda</label>\n <Textarea\n rows={3}\n placeholder=\"What would you like to cover? Share context so your mentor can prepare.\"\n value={agenda}\n onChange={(e) => setAgenda(e.target.value)}\n disabled={disabled}\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label\n htmlFor=\"technospam-booking-when\"\n className=\"mb-1.5 block text-sm font-medium text-text-primary\"\n >\n When <span className=\"text-status-error-text\">*</span>\n </label>\n <Input\n id=\"technospam-booking-when\"\n required\n type=\"datetime-local\"\n value={scheduledAt}\n onChange={(e) => setScheduledAt(e.target.value)}\n disabled={disabled}\n />\n </div>\n <div>\n <label\n htmlFor=\"technospam-booking-duration\"\n className=\"mb-1.5 block text-sm font-medium text-text-primary\"\n >\n Duration (min)\n </label>\n <Input\n id=\"technospam-booking-duration\"\n type=\"number\"\n min={15}\n step={15}\n value={durationMinutes}\n onChange={(e) => setDurationMinutes(e.target.value)}\n disabled={disabled}\n />\n </div>\n </div>\n\n <div className=\"rounded-lg border border-border-seam bg-bg-sunken px-3 py-2.5\">\n {costLoading && !estimate ? (\n <Skeleton className=\"h-5 w-40 rounded\" />\n ) : estimate ? (\n <>\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"text-text-secondary\">Session cost</span>\n <span className=\"font-semibold text-text-primary\">\n {formatMoneyFromMinorUnits(estimate.costAmount, estimate.currency)}\n </span>\n </div>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n Includes a {(estimate.platformFeeBps / 100).toFixed(0)}% platform fee (\n {formatMoneyFromMinorUnits(estimate.platformFeeAmount, estimate.currency)}) — the\n mentor keeps {formatMoneyFromMinorUnits(estimate.netAmount, estimate.currency)}.\n </p>\n </>\n ) : (\n <p className=\"text-sm text-text-muted\">Enter a duration to preview the session cost.</p>\n )}\n </div>\n\n <Button\n className=\"w-full bg-gradient-to-r from-accent-primary to-accent-purple\"\n onClick={handleSubmit}\n disabled={!canSubmit}\n >\n <CalendarClock size={16} /> {loading ? 'Requesting…' : 'Request session'}\n </Button>\n\n <div className=\"flex items-center justify-between gap-2 rounded-lg border border-border-seam px-3 py-2\">\n <span className=\"flex items-center gap-1.5 text-xs text-text-muted\">\n <CreditCard size={14} /> Payment integration pending\n </span>\n <Button\n variant=\"outline\"\n size=\"sm\"\n disabled\n title=\"Online payment collection is not live yet\"\n >\n Pay now\n </Button>\n </div>\n </div>\n </GlassCard>\n );\n}\n\nfunction SessionSummaryRow({ session }: { session: TecMentorSession }) {\n return (\n <div className=\"flex items-center gap-3 rounded-lg px-1 py-3 transition-colors hover:bg-bg-sunken\">\n <span className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-accent-purple/12 text-accent-purple\">\n <Video size={16} />\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"truncate text-sm font-medium text-text-primary\">{session.topic}</div>\n <div className=\"text-xs text-text-muted\">\n {formatDateTime(session.scheduledAt)} · {formatDuration(session.durationMinutes)}\n </div>\n </div>\n <StatusBadge status={session.status} />\n </div>\n );\n}\n\nexport function MentorshipMentorProfilePagePage() {\n const [params] = useSearchParams();\n const id = params.get('id') ?? '';\n const { workspaceId } = useTechnoSpam();\n const { data, loading, error, refetch } = useMentorQuery(id);\n\n const mentor = data?.tecMentor ?? null;\n\n if (loading && !mentor) {\n return (\n <PageLayout title=\"Mentor\" backPath=\"/mentorship/mentors-page\">\n <Skeleton className=\"mb-4 h-40 w-full rounded-xl\" />\n <Skeleton className=\"h-64 w-full rounded-xl\" />\n </PageLayout>\n );\n }\n\n if (error || !mentor) {\n return (\n <PageLayout title=\"Mentor\" backPath=\"/mentorship/mentors-page\">\n {error ? (\n <ErrorView title=\"Couldn't load mentor\" error={error} />\n ) : (\n <EmptyView\n title=\"Mentor not found\"\n description=\"This mentor profile may no longer be available.\"\n />\n )}\n </PageLayout>\n );\n }\n\n const sessions = mentor.sessions ?? [];\n const recentSessions = [...sessions]\n .sort((a, b) => new Date(b.scheduledAt).getTime() - new Date(a.scheduledAt).getTime())\n .slice(0, 5);\n\n const HeroIcon = topicIcon(mentor.expertise.join(' ') || mentor.headline);\n\n return (\n <PageLayout hero backPath=\"/mentorship/mentors-page\" title=\"Mentor Profile\">\n <GlassCard className=\"mb-6 overflow-hidden p-0\">\n <div\n className={cn(\n 'relative h-24 overflow-hidden bg-gradient-to-br',\n coverGradient(mentor.headline)\n )}\n >\n {createElement(HeroIcon, {\n className: 'pointer-events-none absolute -right-3 -top-4 text-white/15',\n size: 120,\n strokeWidth: 1.3,\n })}\n <div className=\"pointer-events-none absolute inset-0 bg-gradient-to-t from-black/25 to-transparent\" />\n </div>\n <div className=\"px-6 pb-6\">\n <div className=\"-mt-10 flex flex-col gap-4 sm:flex-row sm:items-end\">\n <div className=\"relative shrink-0\">\n <span\n className={cn(\n 'flex h-20 w-20 items-center justify-center rounded-full bg-gradient-to-br text-white shadow-lg ring-4 ring-bg-surface',\n coverGradient(mentor.headline)\n )}\n >\n {createElement(HeroIcon, { size: 34, strokeWidth: 1.6 })}\n </span>\n {mentor.isAcceptingMentees ? (\n <span className=\"absolute bottom-1 right-1 h-4 w-4 rounded-full border-2 border-bg-surface bg-status-success-text\" />\n ) : null}\n </div>\n <div className=\"min-w-0 flex-1 sm:pb-1\">\n <h1 className=\"text-xl font-semibold text-text-primary sm:text-2xl\">\n {mentor.headline}\n </h1>\n {mentor.role ? (\n <p className=\"mt-1 text-sm font-medium text-text-secondary\">{mentor.role}</p>\n ) : null}\n <div className=\"mt-2 flex flex-wrap items-center gap-2\">\n <StatusBadge status={mentor.status} />\n {mentor.isAcceptingMentees ? (\n <Badge\n variant=\"outline\"\n className=\"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\n mentees\n </Badge>\n ) : null}\n </div>\n </div>\n </div>\n\n {mentor.bio ? (\n <p className=\"mt-4 max-w-2xl text-sm leading-relaxed text-text-secondary\">\n {mentor.bio}\n </p>\n ) : null}\n\n {mentor.expertise.length ? (\n <div className=\"mt-4 flex flex-wrap gap-1.5\">\n {mentor.expertise.map((skill) => (\n <Badge key={skill} variant=\"secondary\">\n {skill}\n </Badge>\n ))}\n </div>\n ) : null}\n </div>\n </GlassCard>\n\n <div className=\"mb-6 grid grid-cols-2 gap-3 sm:grid-cols-4\">\n <StatCard\n label=\"Rate\"\n value={`${formatMoneyFromMinorUnits(mentor.hourlyRate, mentor.currency)}/hr`}\n icon={DollarSign}\n tone=\"blue\"\n />\n <StatCard\n label=\"Rating\"\n value={mentor.ratingAvg > 0 ? mentor.ratingAvg.toFixed(1) : 'New'}\n icon={Star}\n tone=\"amber\"\n />\n <StatCard\n label=\"Sessions\"\n value={mentor.sessionsCount}\n icon={MessageSquare}\n tone=\"purple\"\n />\n <StatCard\n label=\"Status\"\n value={mentor.status === 'ACTIVE' ? 'Active' : 'Pending'}\n icon={Award}\n tone=\"teal\"\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <BookSessionForm\n mentorId={mentor.id}\n disabled={!mentor.isAcceptingMentees}\n onBooked={() => void refetch()}\n workspaceId={workspaceId}\n />\n\n <GlassCard className=\"p-5\">\n <div className=\"mb-3 flex items-center gap-2\">\n <CalendarClock size={18} className=\"text-accent-teal\" />\n <h2 className=\"text-base font-semibold text-text-primary\">Recent sessions</h2>\n </div>\n {recentSessions.length === 0 ? (\n <p className=\"py-6 text-center text-sm text-text-muted\">\n No sessions booked yet. Be the first to request time with this mentor.\n </p>\n ) : (\n <div className=\"divide-y divide-border-seam\">\n {recentSessions.map((session) => (\n <SessionSummaryRow key={session.id} session={session} />\n ))}\n </div>\n )}\n </GlassCard>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MentorshipMentorProfilePagePage.js","names":[],"sources":["../../../src/technospam/pages/MentorshipMentorProfilePagePage.tsx"],"sourcesContent":["import { createElement, useState } from 'react';\nimport { useSearchParams } from 'react-router';\nimport {\n Award,\n CalendarClock,\n CreditCard,\n DollarSign,\n MessageSquare,\n Star,\n Video,\n} from 'lucide-react';\nimport { Badge, Button, GlassCard, Input, Skeleton, Textarea } from '@burdenoff/fe-libs/ui';\nimport {\n EmptyView,\n ErrorView,\n PageLayout,\n StatCard,\n StatusBadge,\n coverGradient,\n topicIcon,\n} from '../components';\nimport { useTechnoSpam } from '../providers/TechnoSpamProvider';\nimport {\n useBookSession,\n useMentorQuery,\n useSessionCostEstimate,\n type TecMentorSession,\n} from '../api/technospamApi';\nimport { formatDateTime, formatDuration, formatMoneyFromMinorUnits } from '../utils/format';\nimport { cn } from '../utils/cn';\nimport { notify } from '../lib/toast';\nimport { useTechnoSpamAnalytics } from '../hooks/useTechnoSpamAnalytics';\n\nfunction BookSessionForm({\n mentorId,\n disabled,\n onBooked,\n workspaceId,\n}: {\n mentorId: string;\n disabled: boolean;\n onBooked?: () => void;\n workspaceId?: string;\n}) {\n const [topic, setTopic] = useState('');\n const [agenda, setAgenda] = useState('');\n const [scheduledAt, setScheduledAt] = useState('');\n const [durationMinutes, setDurationMinutes] = useState('60');\n const [bookSession, { loading }] = useBookSession();\n const { emit } = useTechnoSpamAnalytics();\n\n const parsedDate = scheduledAt ? new Date(scheduledAt) : null;\n const validDate = parsedDate != null && !Number.isNaN(parsedDate.getTime());\n const canSubmit = topic.trim().length > 0 && validDate && !loading && !disabled;\n\n // Live cost preview -- computed server-side from the mentor's real rate,\n // the exact same calculation tecBookSession snapshots onto the session.\n // Shown BEFORE booking so the mentee sees the real price up front.\n const parsedDuration = Number.parseInt(durationMinutes, 10);\n const { data: costData, loading: costLoading } = useSessionCostEstimate(\n mentorId,\n Number.isNaN(parsedDuration) ? 0 : parsedDuration\n );\n const estimate = costData?.tecCalculateSessionCost;\n\n const handleSubmit = async () => {\n if (!canSubmit || !parsedDate) return;\n const input: {\n mentorId: string;\n topic: string;\n agenda?: string;\n scheduledAt: string;\n durationMinutes?: number;\n } = {\n mentorId,\n topic: topic.trim(),\n scheduledAt: parsedDate.toISOString(),\n };\n if (agenda.trim()) input.agenda = agenda.trim();\n const dur = Number.parseInt(durationMinutes, 10);\n if (!Number.isNaN(dur) && dur > 0) input.durationMinutes = dur;\n\n emit('technospam.mentor.book_clicked', {\n workspaceId,\n entityId: mentorId,\n entityType: 'TEC_MENTOR',\n });\n const res = await bookSession({ variables: { input } });\n if (res.data?.tecBookSession) {\n emit('technospam.mentor.booked', {\n workspaceId,\n entityId: mentorId,\n entityType: 'TEC_MENTOR',\n sessionId: res.data.tecBookSession.id,\n });\n notify.success('Session requested', 'Your mentor will confirm the booking soon.');\n setTopic('');\n setAgenda('');\n setScheduledAt('');\n setDurationMinutes('60');\n onBooked?.();\n }\n };\n\n return (\n <GlassCard className=\"p-5\">\n <div className=\"mb-4 flex items-start 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-purple text-action-primary-text shadow-sm\">\n <CalendarClock size={20} />\n </span>\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">Book a session</h2>\n <p className=\"text-sm text-text-secondary\">Schedule 1:1 time with your mentor.</p>\n </div>\n </div>\n\n {disabled ? (\n <p className=\"rounded-lg border border-border-seam bg-bg-sunken px-3 py-2 text-sm text-text-secondary\">\n This mentor isn't accepting new mentees right now.\n </p>\n ) : null}\n\n <div className=\"mt-4 grid grid-cols-1 gap-4\">\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">\n Topic <span className=\"text-status-error-text\">*</span>\n </label>\n <Input\n placeholder=\"e.g. System design interview prep\"\n value={topic}\n onChange={(e) => setTopic(e.target.value)}\n disabled={disabled}\n />\n </div>\n\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Agenda</label>\n <Textarea\n rows={3}\n placeholder=\"What would you like to cover? Share context so your mentor can prepare.\"\n value={agenda}\n onChange={(e) => setAgenda(e.target.value)}\n disabled={disabled}\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label\n htmlFor=\"technospam-booking-when\"\n className=\"mb-1.5 block text-sm font-medium text-text-primary\"\n >\n When <span className=\"text-status-error-text\">*</span>\n </label>\n <Input\n id=\"technospam-booking-when\"\n required\n type=\"datetime-local\"\n value={scheduledAt}\n onChange={(e) => setScheduledAt(e.target.value)}\n disabled={disabled}\n />\n </div>\n <div>\n <label\n htmlFor=\"technospam-booking-duration\"\n className=\"mb-1.5 block text-sm font-medium text-text-primary\"\n >\n Duration (min)\n </label>\n <Input\n id=\"technospam-booking-duration\"\n type=\"number\"\n min={15}\n step={15}\n value={durationMinutes}\n onChange={(e) => setDurationMinutes(e.target.value)}\n disabled={disabled}\n />\n </div>\n </div>\n\n <div className=\"rounded-lg border border-border-seam bg-bg-sunken px-3 py-2.5\">\n {costLoading && !estimate ? (\n <Skeleton className=\"h-5 w-40 rounded\" />\n ) : estimate ? (\n <>\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"text-text-secondary\">Session cost</span>\n <span className=\"font-semibold text-text-primary\">\n {formatMoneyFromMinorUnits(estimate.costAmount, estimate.currency)}\n </span>\n </div>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n Includes a {(estimate.platformFeeBps / 100).toFixed(0)}% platform fee (\n {formatMoneyFromMinorUnits(estimate.platformFeeAmount, estimate.currency)}) — the\n mentor keeps {formatMoneyFromMinorUnits(estimate.netAmount, estimate.currency)}.\n </p>\n </>\n ) : (\n <p className=\"text-sm text-text-muted\">Enter a duration to preview the session cost.</p>\n )}\n </div>\n\n <Button\n className=\"w-full bg-gradient-to-r from-accent-primary to-accent-purple\"\n onClick={handleSubmit}\n disabled={!canSubmit}\n >\n <CalendarClock size={16} /> {loading ? 'Requesting…' : 'Request session'}\n </Button>\n\n <div className=\"flex items-center justify-between gap-2 rounded-lg border border-border-seam px-3 py-2\">\n <span className=\"flex items-center gap-1.5 text-xs text-text-muted\">\n <CreditCard size={14} /> Payment integration pending\n </span>\n <Button\n variant=\"outline\"\n size=\"sm\"\n disabled\n title=\"Online payment collection is not live yet\"\n >\n Pay now\n </Button>\n </div>\n </div>\n </GlassCard>\n );\n}\n\nfunction SessionSummaryRow({ session }: { session: TecMentorSession }) {\n return (\n <div className=\"flex items-center gap-3 rounded-lg px-1 py-3 transition-colors hover:bg-bg-sunken\">\n <span className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-accent-purple/12 text-accent-purple\">\n <Video size={16} />\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"truncate text-sm font-medium text-text-primary\">{session.topic}</div>\n <div className=\"text-xs text-text-muted\">\n {formatDateTime(session.scheduledAt)} · {formatDuration(session.durationMinutes)}\n </div>\n </div>\n <StatusBadge status={session.status} />\n </div>\n );\n}\n\nexport function MentorshipMentorProfilePagePage() {\n const [params] = useSearchParams();\n const id = params.get('id') ?? '';\n const { workspaceId } = useTechnoSpam();\n const { data, loading, error, refetch } = useMentorQuery(id);\n\n const mentor = data?.tecMentor ?? null;\n\n if (loading && !mentor) {\n return (\n <PageLayout title=\"Mentor\" backPath=\"/mentorship/mentors-page\">\n <Skeleton className=\"mb-4 h-40 w-full rounded-xl\" />\n <Skeleton className=\"h-64 w-full rounded-xl\" />\n </PageLayout>\n );\n }\n\n if (error || !mentor) {\n return (\n <PageLayout title=\"Mentor\" backPath=\"/mentorship/mentors-page\">\n {error ? (\n <ErrorView title=\"Couldn't load mentor\" error={error} />\n ) : (\n <EmptyView\n title=\"Mentor not found\"\n description=\"This mentor profile may no longer be available.\"\n />\n )}\n </PageLayout>\n );\n }\n\n const sessions = mentor.sessions ?? [];\n const recentSessions = [...sessions]\n .sort((a, b) => new Date(b.scheduledAt).getTime() - new Date(a.scheduledAt).getTime())\n .slice(0, 5);\n\n const HeroIcon = topicIcon(mentor.expertise.join(' ') || mentor.headline);\n\n return (\n <PageLayout hero backPath=\"/mentorship/mentors-page\" title=\"Mentor Profile\">\n <GlassCard className=\"mb-6 overflow-hidden p-0\">\n <div\n className={cn(\n 'relative h-24 overflow-hidden bg-gradient-to-br',\n coverGradient(mentor.headline)\n )}\n >\n {createElement(HeroIcon, {\n className: 'pointer-events-none absolute -right-3 -top-4 text-action-primary-text/15',\n size: 120,\n strokeWidth: 1.3,\n })}\n <div className=\"pointer-events-none absolute inset-0 bg-gradient-to-t from-bg-overlay/60 to-transparent\" />\n </div>\n <div className=\"px-6 pb-6\">\n <div className=\"-mt-10 flex flex-col gap-4 sm:flex-row sm:items-end\">\n <div className=\"relative shrink-0\">\n <span\n className={cn(\n 'flex h-20 w-20 items-center justify-center rounded-full bg-gradient-to-br text-action-primary-text shadow-lg ring-4 ring-bg-surface',\n coverGradient(mentor.headline)\n )}\n >\n {createElement(HeroIcon, { size: 34, strokeWidth: 1.6 })}\n </span>\n {mentor.isAcceptingMentees ? (\n <span className=\"absolute bottom-1 right-1 h-4 w-4 rounded-full border-2 border-bg-surface bg-status-success-text\" />\n ) : null}\n </div>\n <div className=\"min-w-0 flex-1 sm:pb-1\">\n <h1 className=\"text-xl font-semibold text-text-primary sm:text-2xl\">\n {mentor.headline}\n </h1>\n {mentor.role ? (\n <p className=\"mt-1 text-sm font-medium text-text-secondary\">{mentor.role}</p>\n ) : null}\n <div className=\"mt-2 flex flex-wrap items-center gap-2\">\n <StatusBadge status={mentor.status} />\n {mentor.isAcceptingMentees ? (\n <Badge\n variant=\"outline\"\n className=\"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\n mentees\n </Badge>\n ) : null}\n </div>\n </div>\n </div>\n\n {mentor.bio ? (\n <p className=\"mt-4 max-w-2xl text-sm leading-relaxed text-text-secondary\">\n {mentor.bio}\n </p>\n ) : null}\n\n {mentor.expertise.length ? (\n <div className=\"mt-4 flex flex-wrap gap-1.5\">\n {mentor.expertise.map((skill) => (\n <Badge key={skill} variant=\"secondary\">\n {skill}\n </Badge>\n ))}\n </div>\n ) : null}\n </div>\n </GlassCard>\n\n <div className=\"mb-6 grid grid-cols-2 gap-3 sm:grid-cols-4\">\n <StatCard\n label=\"Rate\"\n value={`${formatMoneyFromMinorUnits(mentor.hourlyRate, mentor.currency)}/hr`}\n icon={DollarSign}\n tone=\"blue\"\n />\n <StatCard\n label=\"Rating\"\n value={mentor.ratingAvg > 0 ? mentor.ratingAvg.toFixed(1) : 'New'}\n icon={Star}\n tone=\"amber\"\n />\n <StatCard\n label=\"Sessions\"\n value={mentor.sessionsCount}\n icon={MessageSquare}\n tone=\"purple\"\n />\n <StatCard\n label=\"Status\"\n value={mentor.status === 'ACTIVE' ? 'Active' : 'Pending'}\n icon={Award}\n tone=\"teal\"\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <BookSessionForm\n mentorId={mentor.id}\n disabled={!mentor.isAcceptingMentees}\n onBooked={() => void refetch()}\n workspaceId={workspaceId}\n />\n\n <GlassCard className=\"p-5\">\n <div className=\"mb-3 flex items-center gap-2\">\n <CalendarClock size={18} className=\"text-accent-teal\" />\n <h2 className=\"text-base font-semibold text-text-primary\">Recent sessions</h2>\n </div>\n {recentSessions.length === 0 ? (\n <p className=\"py-6 text-center text-sm text-text-muted\">\n No sessions booked yet. Be the first to request time with this mentor.\n </p>\n ) : (\n <div className=\"divide-y divide-border-seam\">\n {recentSessions.map((session) => (\n <SessionSummaryRow key={session.id} session={session} />\n ))}\n </div>\n )}\n </GlassCard>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MentorshipMentorsPagePage.d.ts","sourceRoot":"","sources":["../../../src/technospam/pages/MentorshipMentorsPagePage.tsx"],"names":[],"mappings":"AAsTA,wBAAgB,yBAAyB,
|
|
1
|
+
{"version":3,"file":"MentorshipMentorsPagePage.d.ts","sourceRoot":"","sources":["../../../src/technospam/pages/MentorshipMentorsPagePage.tsx"],"names":[],"mappings":"AAsTA,wBAAgB,yBAAyB,gCAkLxC"}
|
|
@@ -20,7 +20,7 @@ function L({ mentor: e, size: t = "md" }) {
|
|
|
20
20
|
return /* @__PURE__ */ j("div", {
|
|
21
21
|
className: "relative shrink-0",
|
|
22
22
|
children: [/* @__PURE__ */ A("span", {
|
|
23
|
-
className: i("flex items-center justify-center rounded-full bg-gradient-to-br text-
|
|
23
|
+
className: i("flex items-center justify-center rounded-full bg-gradient-to-br text-action-primary-text shadow-md ring-1 ring-action-primary-text/15", t === "lg" ? "h-20 w-20" : "h-16 w-16", s(e.headline)),
|
|
24
24
|
children: y(c(e.expertise.join(" ") || e.headline), {
|
|
25
25
|
size: t === "lg" ? 32 : 26,
|
|
26
26
|
strokeWidth: 1.7
|
|
@@ -32,7 +32,7 @@ function R({ mentor: e, onOpen: t }) {
|
|
|
32
32
|
let n = r();
|
|
33
33
|
return /* @__PURE__ */ j(P, {
|
|
34
34
|
"data-testid": "mentor-card",
|
|
35
|
-
className: "group flex cursor-pointer flex-col gap-
|
|
35
|
+
className: "group flex cursor-pointer flex-col gap-3 p-4 transition-all duration-200 hover:-translate-y-1 hover:shadow-lg sm:gap-4 sm:p-5",
|
|
36
36
|
onClick: t,
|
|
37
37
|
children: [
|
|
38
38
|
/* @__PURE__ */ j("div", {
|
|
@@ -136,7 +136,7 @@ function z({ onClose: e, onDone: t, workspaceId: r }) {
|
|
|
136
136
|
children: [/* @__PURE__ */ j("div", {
|
|
137
137
|
className: "flex items-center gap-3",
|
|
138
138
|
children: [/* @__PURE__ */ A("span", {
|
|
139
|
-
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-
|
|
139
|
+
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-action-primary-text shadow-sm",
|
|
140
140
|
children: /* @__PURE__ */ A(D, { size: 20 })
|
|
141
141
|
}), /* @__PURE__ */ j("div", { children: [/* @__PURE__ */ A("h2", {
|
|
142
142
|
className: "text-base font-semibold text-text-primary",
|
|
@@ -278,6 +278,7 @@ function B() {
|
|
|
278
278
|
}, [W, g]), Y = W.filter((e) => e.isAcceptingMentees).length, X = W.reduce((e, t) => e + t.sessionsCount, 0), Z = W.filter((e) => e.ratingAvg > 0), Q = Z.length ? (Z.reduce((e, t) => e + t.ratingAvg, 0) / Z.length).toFixed(1) : "—";
|
|
279
279
|
return /* @__PURE__ */ j(a, {
|
|
280
280
|
hero: !0,
|
|
281
|
+
mobileSection: !0,
|
|
281
282
|
title: m("technospam.mentorship.findAMentorTitle", "Find a Mentor"),
|
|
282
283
|
description: m("technospam.mentorship.description", "Connect with experienced builders for 1:1 guidance, code review, and career coaching."),
|
|
283
284
|
actions: /* @__PURE__ */ j(N, {
|
|
@@ -310,7 +311,7 @@ function B() {
|
|
|
310
311
|
workspaceId: c
|
|
311
312
|
}) : null,
|
|
312
313
|
/* @__PURE__ */ j("div", {
|
|
313
|
-
className: "mb-5
|
|
314
|
+
className: "mb-5 hidden grid-cols-2 gap-3 sm:grid sm:grid-cols-4",
|
|
314
315
|
children: [
|
|
315
316
|
/* @__PURE__ */ A(o, {
|
|
316
317
|
label: m("technospam.mentorship.statMentors", "Mentors"),
|
|
@@ -353,6 +354,7 @@ function B() {
|
|
|
353
354
|
onChange: (e) => y(e.target.value)
|
|
354
355
|
})]
|
|
355
356
|
}), !q && !k ? /* @__PURE__ */ j(N, {
|
|
357
|
+
className: "min-h-11 sm:min-h-0",
|
|
356
358
|
onClick: () => M(!0),
|
|
357
359
|
children: [
|
|
358
360
|
/* @__PURE__ */ A(D, { size: 16 }),
|
|
@@ -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';\nimport { useTr } from '../hooks/useTr';\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 const tr = useTr();\n return (\n <GlassCard\n data-testid=\"mentor-card\"\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\" />{' '}\n {tr('technospam.common.acceptingMentees', '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 {tr('technospam.common.notAcceptingMentees', '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\">\n {tr('technospam.common.rating', 'rating')}\n </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\">\n {tr('technospam.common.sessions', 'sessions')}\n </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\">\n {tr('technospam.common.perHour', '/ hr')}\n </div>\n </div>\n </div>\n\n <Button\n className=\"w-full\"\n onClick={(e) => {\n e.stopPropagation();\n onOpen();\n }}\n >\n {tr('technospam.common.viewProfile', '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 & 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 tr = useTr();\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={tr('technospam.mentorship.findAMentorTitle', 'Find a Mentor')}\n description={tr(\n 'technospam.mentorship.description',\n 'Connect with experienced builders for 1:1 guidance, code review, and career coaching.'\n )}\n actions={\n <Button variant=\"outline\" onClick={() => navigate('/mentorship/sessions-page')}>\n <MessageSquare size={16} /> {tr('technospam.common.mySessions', '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'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\n label={tr('technospam.mentorship.statMentors', 'Mentors')}\n value={mentors.length}\n icon={Users}\n tone=\"blue\"\n />\n <StatCard\n label={tr('technospam.mentorship.statAcceptingMentees', 'Accepting mentees')}\n value={accepting}\n icon={Sparkles}\n tone=\"green\"\n />\n <StatCard\n label={tr('technospam.mentorship.statAvgRating', 'Avg rating')}\n value={avgRating}\n icon={Star}\n tone=\"amber\"\n />\n <StatCard\n label={tr('technospam.mentorship.statTotalSessions', 'Total sessions')}\n value={totalSessions}\n icon={MessageSquare}\n tone=\"purple\"\n />\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 aria-label={tr('technospam.mentorship.searchLabel', 'Search mentors')}\n placeholder={tr(\n 'technospam.mentorship.searchPlaceholder',\n 'Search by headline or expertise…'\n )}\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} /> {tr('technospam.common.becomeAMentor', 'Become a mentor')}\n </Button>\n ) : null}\n </div>\n\n {loading && !mentors.length ? (\n <LoadingGrid />\n ) : error ? (\n <ErrorView\n title={tr('technospam.common.errorLoadMentors', \"Couldn't load mentors\")}\n error={error}\n />\n ) : filtered.length === 0 ? (\n <EmptyView\n icon={<Users size={26} />}\n title={\n search\n ? tr('technospam.mentorship.emptySearchTitle', 'No matching mentors')\n : tr('technospam.mentorship.emptyDefaultTitle', 'No mentors yet')\n }\n description={\n search\n ? tr('technospam.mentorship.emptySearchDesc', 'Try a different search term.')\n : tr(\n 'technospam.mentorship.emptyDefaultDesc',\n 'Active mentors will appear here. TechnoSpam is pre-launch — be one of the first to sign up.'\n )\n }\n action={\n !search && !alreadyMentor ? (\n <Button onClick={() => setShowForm(true)}>\n {tr('technospam.common.becomeAMentor', 'Become a mentor')}\n </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';\nimport { useTr } from '../hooks/useTr';\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-action-primary-text shadow-md ring-1 ring-action-primary-text/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 const tr = useTr();\n return (\n <GlassCard\n data-testid=\"mentor-card\"\n className=\"group flex cursor-pointer flex-col gap-3 p-4 transition-all duration-200 hover:-translate-y-1 hover:shadow-lg sm:gap-4 sm:p-5\"\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\" />{' '}\n {tr('technospam.common.acceptingMentees', '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 {tr('technospam.common.notAcceptingMentees', '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\">\n {tr('technospam.common.rating', 'rating')}\n </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\">\n {tr('technospam.common.sessions', 'sessions')}\n </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\">\n {tr('technospam.common.perHour', '/ hr')}\n </div>\n </div>\n </div>\n\n <Button\n className=\"w-full\"\n onClick={(e) => {\n e.stopPropagation();\n onOpen();\n }}\n >\n {tr('technospam.common.viewProfile', '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-action-primary-text 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 & 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 tr = useTr();\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 mobileSection\n title={tr('technospam.mentorship.findAMentorTitle', 'Find a Mentor')}\n description={tr(\n 'technospam.mentorship.description',\n 'Connect with experienced builders for 1:1 guidance, code review, and career coaching.'\n )}\n actions={\n <Button variant=\"outline\" onClick={() => navigate('/mentorship/sessions-page')}>\n <MessageSquare size={16} /> {tr('technospam.common.mySessions', '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'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 {/* Directory stats are a desktop summary; phones go straight to the cards. */}\n <div className=\"mb-5 hidden grid-cols-2 gap-3 sm:grid sm:grid-cols-4\">\n <StatCard\n label={tr('technospam.mentorship.statMentors', 'Mentors')}\n value={mentors.length}\n icon={Users}\n tone=\"blue\"\n />\n <StatCard\n label={tr('technospam.mentorship.statAcceptingMentees', 'Accepting mentees')}\n value={accepting}\n icon={Sparkles}\n tone=\"green\"\n />\n <StatCard\n label={tr('technospam.mentorship.statAvgRating', 'Avg rating')}\n value={avgRating}\n icon={Star}\n tone=\"amber\"\n />\n <StatCard\n label={tr('technospam.mentorship.statTotalSessions', 'Total sessions')}\n value={totalSessions}\n icon={MessageSquare}\n tone=\"purple\"\n />\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 aria-label={tr('technospam.mentorship.searchLabel', 'Search mentors')}\n placeholder={tr(\n 'technospam.mentorship.searchPlaceholder',\n 'Search by headline or expertise…'\n )}\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n />\n </div>\n {!alreadyMentor && !showForm ? (\n <Button className=\"min-h-11 sm:min-h-0\" onClick={() => setShowForm(true)}>\n <UserPlus size={16} /> {tr('technospam.common.becomeAMentor', 'Become a mentor')}\n </Button>\n ) : null}\n </div>\n\n {loading && !mentors.length ? (\n <LoadingGrid />\n ) : error ? (\n <ErrorView\n title={tr('technospam.common.errorLoadMentors', \"Couldn't load mentors\")}\n error={error}\n />\n ) : filtered.length === 0 ? (\n <EmptyView\n icon={<Users size={26} />}\n title={\n search\n ? tr('technospam.mentorship.emptySearchTitle', 'No matching mentors')\n : tr('technospam.mentorship.emptyDefaultTitle', 'No mentors yet')\n }\n description={\n search\n ? tr('technospam.mentorship.emptySearchDesc', 'Try a different search term.')\n : tr(\n 'technospam.mentorship.emptyDefaultDesc',\n 'Active mentors will appear here. TechnoSpam is pre-launch — be one of the first to sign up.'\n )\n }\n action={\n !search && !alreadyMentor ? (\n <Button onClick={() => setShowForm(true)}>\n {tr('technospam.common.becomeAMentor', 'Become a mentor')}\n </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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"}
|
|
@@ -34,7 +34,7 @@ function V({ session: e, showMentor: t, isMentor: n, onCancel: r, onConfirm: a,
|
|
|
34
34
|
children: [/* @__PURE__ */ N("div", {
|
|
35
35
|
className: "flex items-start gap-3",
|
|
36
36
|
children: [/* @__PURE__ */ M("span", {
|
|
37
|
-
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-
|
|
37
|
+
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-action-primary-text shadow-sm",
|
|
38
38
|
children: /* @__PURE__ */ M(A, { size: 18 })
|
|
39
39
|
}), /* @__PURE__ */ N("div", {
|
|
40
40
|
className: "min-w-0",
|
|
@@ -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 {\n Button,\n GlassCard,\n Input,\n Tabs,\n TabsContent,\n TabsList,\n TabsTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport { TAB_PANEL_FOCUS_CLASS } from '../lib/a11y';\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';\nimport {\n FOCUSED_CARD_CLASS,\n focusableCardProps,\n useFocusedRecordParam,\n} from '../hooks/useFocusedRecordParam';\nimport { FOCUS_PARAMS } from '../utils/pageContext';\nimport { cn } from '../utils/cn';\nimport { useTr } from '../hooks/useTr';\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 focused,\n onFocusChange,\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 /** True when this is the session the page (and the assistant) is focused on. */\n focused: boolean;\n onFocusChange: (id: string | null) => void;\n}) {\n // Acting on a session focuses it, so \"cancel this session\" names this one.\n const focus = () => onFocusChange(session.id);\n const toggleFocus = () => onFocusChange(focused ? null : session.id);\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\n className={cn(\n 'flex cursor-pointer flex-col gap-3 p-4 transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md',\n focused && FOCUSED_CARD_CLASS\n )}\n {...focusableCardProps(focused, toggleFocus)}\n >\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 <button\n type=\"button\"\n aria-pressed={focused}\n onClick={toggleFocus}\n className=\"truncate text-left text-sm font-semibold text-text-primary hover:text-accent-primary\"\n >\n {session.topic}\n </button>\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={() => {\n focus();\n setMode(mode === 'confirm' ? 'none' : 'confirm');\n }}\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={() => {\n focus();\n setMode(mode === 'complete' ? 'none' : 'complete');\n }}\n >\n <CheckCircle2 size={15} /> Mark complete\n </Button>\n ) : null}\n {canCancel ? (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => {\n focus();\n onCancel(session.id);\n }}\n disabled={busy}\n >\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 focusedId,\n onFocusChange,\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 focusedId: string | null;\n onFocusChange: (id: string | null) => void;\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 focused={focusedId === session.id}\n onFocusChange={onFocusChange}\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 tr = useTr();\n const [tab, setTab] = useState<Perspective>('mentee');\n const [cancellingId, setCancellingId] = useState<string | null>(null);\n // The focused session lives in `?sessionId=` so the assistant's page context\n // names it (\"cancel this session\") and a reload keeps it focused.\n const [focusedSessionId, setFocusedSessionId] = useFocusedRecordParam(FOCUS_PARAMS.session);\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\n hero\n title={tr('technospam.mentorship.sessionsTitle', 'My Sessions')}\n description={tr(\n 'technospam.mentorship.sessionsDescription',\n 'Track your mentorship sessions.'\n )}\n >\n <EmptyView\n icon={<MessageSquare size={26} />}\n title={tr('technospam.mentorship.signInTitle', 'Sign in to view your sessions')}\n description={tr(\n 'technospam.mentorship.signInDesc',\n \"Your booked and hosted mentorship sessions will appear here once you're signed in.\"\n )}\n />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n hero\n title={tr('technospam.mentorship.sessionsTitle', 'My Sessions')}\n description={tr(\n 'technospam.mentorship.sessionsDescription',\n \"Manage the mentorship sessions you've booked and the ones you host.\"\n )}\n actions={\n <Button variant=\"outline\" onClick={() => navigate('/mentorship/mentors-page')}>\n <Users size={16} /> {tr('technospam.common.findMentor', '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={tr('technospam.mentorship.statAsMentee', 'As mentee')}\n value={menteeSessions.length}\n icon={MessageSquare}\n tone=\"blue\"\n />\n <StatCard\n label={tr('technospam.mentorship.statAsMentor', 'As mentor')}\n value={mentorId ? mentorSessions.length : 0}\n icon={Users}\n tone=\"purple\"\n />\n <StatCard\n label={tr('technospam.mentorship.statUpcoming', 'Upcoming')}\n value={upcomingCount}\n icon={CalendarClock}\n tone=\"teal\"\n />\n <StatCard\n label={tr('technospam.mentorship.statCompleted', 'Completed')}\n value={completedCount}\n icon={CheckCircle2}\n tone=\"green\"\n />\n </div>\n\n {/* The tabs filter one list, so that list is their tab panel: Radix points every\n trigger's aria-controls at a panel id, and without a TabsContent the id\n resolved to nothing (axe aria-valid-attr-value, critical; BOFF-3157). */}\n <Tabs value={tab} onValueChange={(v) => setTab(v as Perspective)} className=\"gap-4\">\n <TabsList>\n <TabsTrigger value=\"mentee\">\n {tr('technospam.mentorship.tabAsMentee', 'As mentee')}\n </TabsTrigger>\n <TabsTrigger value=\"mentor\">\n {tr('technospam.mentorship.tabAsMentor', 'As mentor')}\n </TabsTrigger>\n </TabsList>\n\n <TabsContent value={tab} className={TAB_PANEL_FOCUS_CLASS}>\n {tab === 'mentee' ? (\n <SessionsList\n loading={menteeQ.loading}\n error={menteeQ.error}\n sessions={menteeSessions}\n showMentor\n emptyTitle={tr(\n 'technospam.mentorship.noSessionsBookedTitle',\n 'No sessions booked yet'\n )}\n emptyDescription={tr(\n 'technospam.mentorship.noSessionsBookedDesc',\n 'Find a mentor and request a session to get personalized guidance.'\n )}\n emptyAction={\n <Button onClick={() => navigate('/mentorship/mentors-page')}>\n {tr('technospam.common.findMentor', 'Find a mentor')}\n </Button>\n }\n isMentor={false}\n onCancel={handleCancel}\n onConfirm={handleConfirm}\n onComplete={handleComplete}\n busyId={cancellingId}\n focusedId={focusedSessionId}\n onFocusChange={setFocusedSessionId}\n />\n ) : mentorsQ.loading && !myMentor ? (\n <LoadingRows count={3} height=\"h-24\" />\n ) : !mentorId ? (\n <EmptyView\n icon={<Users size={26} />}\n title={tr('technospam.mentorship.notAMentorTitle', \"You're not a mentor yet\")}\n description={tr(\n 'technospam.mentorship.notAMentorDesc',\n 'Create a mentor profile to start hosting sessions and helping others build.'\n )}\n action={\n <Button onClick={() => navigate('/mentorship/mentors-page')}>\n {tr('technospam.common.becomeAMentor', 'Become a mentor')}\n </Button>\n }\n />\n ) : (\n <SessionsList\n loading={mentorQ.loading}\n error={mentorQ.error}\n sessions={mentorSessions}\n showMentor={false}\n emptyTitle={tr(\n 'technospam.mentorship.noSessionsToHostTitle',\n 'No sessions to host yet'\n )}\n emptyDescription={tr(\n 'technospam.mentorship.noSessionsToHostDesc',\n 'When mentees request time with you, their sessions will show up here.'\n )}\n isMentor\n onCancel={handleCancel}\n onConfirm={handleConfirm}\n onComplete={handleComplete}\n busyId={cancellingId}\n focusedId={focusedSessionId}\n onFocusChange={setFocusedSessionId}\n />\n )}\n </TabsContent>\n </Tabs>\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 {\n Button,\n GlassCard,\n Input,\n Tabs,\n TabsContent,\n TabsList,\n TabsTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport { TAB_PANEL_FOCUS_CLASS } from '../lib/a11y';\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';\nimport {\n FOCUSED_CARD_CLASS,\n focusableCardProps,\n useFocusedRecordParam,\n} from '../hooks/useFocusedRecordParam';\nimport { FOCUS_PARAMS } from '../utils/pageContext';\nimport { cn } from '../utils/cn';\nimport { useTr } from '../hooks/useTr';\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 focused,\n onFocusChange,\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 /** True when this is the session the page (and the assistant) is focused on. */\n focused: boolean;\n onFocusChange: (id: string | null) => void;\n}) {\n // Acting on a session focuses it, so \"cancel this session\" names this one.\n const focus = () => onFocusChange(session.id);\n const toggleFocus = () => onFocusChange(focused ? null : session.id);\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\n className={cn(\n 'flex cursor-pointer flex-col gap-3 p-4 transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md',\n focused && FOCUSED_CARD_CLASS\n )}\n {...focusableCardProps(focused, toggleFocus)}\n >\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-action-primary-text 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 <button\n type=\"button\"\n aria-pressed={focused}\n onClick={toggleFocus}\n className=\"truncate text-left text-sm font-semibold text-text-primary hover:text-accent-primary\"\n >\n {session.topic}\n </button>\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={() => {\n focus();\n setMode(mode === 'confirm' ? 'none' : 'confirm');\n }}\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={() => {\n focus();\n setMode(mode === 'complete' ? 'none' : 'complete');\n }}\n >\n <CheckCircle2 size={15} /> Mark complete\n </Button>\n ) : null}\n {canCancel ? (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => {\n focus();\n onCancel(session.id);\n }}\n disabled={busy}\n >\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 focusedId,\n onFocusChange,\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 focusedId: string | null;\n onFocusChange: (id: string | null) => void;\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 focused={focusedId === session.id}\n onFocusChange={onFocusChange}\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 tr = useTr();\n const [tab, setTab] = useState<Perspective>('mentee');\n const [cancellingId, setCancellingId] = useState<string | null>(null);\n // The focused session lives in `?sessionId=` so the assistant's page context\n // names it (\"cancel this session\") and a reload keeps it focused.\n const [focusedSessionId, setFocusedSessionId] = useFocusedRecordParam(FOCUS_PARAMS.session);\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\n hero\n title={tr('technospam.mentorship.sessionsTitle', 'My Sessions')}\n description={tr(\n 'technospam.mentorship.sessionsDescription',\n 'Track your mentorship sessions.'\n )}\n >\n <EmptyView\n icon={<MessageSquare size={26} />}\n title={tr('technospam.mentorship.signInTitle', 'Sign in to view your sessions')}\n description={tr(\n 'technospam.mentorship.signInDesc',\n \"Your booked and hosted mentorship sessions will appear here once you're signed in.\"\n )}\n />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n hero\n title={tr('technospam.mentorship.sessionsTitle', 'My Sessions')}\n description={tr(\n 'technospam.mentorship.sessionsDescription',\n \"Manage the mentorship sessions you've booked and the ones you host.\"\n )}\n actions={\n <Button variant=\"outline\" onClick={() => navigate('/mentorship/mentors-page')}>\n <Users size={16} /> {tr('technospam.common.findMentor', '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={tr('technospam.mentorship.statAsMentee', 'As mentee')}\n value={menteeSessions.length}\n icon={MessageSquare}\n tone=\"blue\"\n />\n <StatCard\n label={tr('technospam.mentorship.statAsMentor', 'As mentor')}\n value={mentorId ? mentorSessions.length : 0}\n icon={Users}\n tone=\"purple\"\n />\n <StatCard\n label={tr('technospam.mentorship.statUpcoming', 'Upcoming')}\n value={upcomingCount}\n icon={CalendarClock}\n tone=\"teal\"\n />\n <StatCard\n label={tr('technospam.mentorship.statCompleted', 'Completed')}\n value={completedCount}\n icon={CheckCircle2}\n tone=\"green\"\n />\n </div>\n\n {/* The tabs filter one list, so that list is their tab panel: Radix points every\n trigger's aria-controls at a panel id, and without a TabsContent the id\n resolved to nothing (axe aria-valid-attr-value, critical; BOFF-3157). */}\n <Tabs value={tab} onValueChange={(v) => setTab(v as Perspective)} className=\"gap-4\">\n <TabsList>\n <TabsTrigger value=\"mentee\">\n {tr('technospam.mentorship.tabAsMentee', 'As mentee')}\n </TabsTrigger>\n <TabsTrigger value=\"mentor\">\n {tr('technospam.mentorship.tabAsMentor', 'As mentor')}\n </TabsTrigger>\n </TabsList>\n\n <TabsContent value={tab} className={TAB_PANEL_FOCUS_CLASS}>\n {tab === 'mentee' ? (\n <SessionsList\n loading={menteeQ.loading}\n error={menteeQ.error}\n sessions={menteeSessions}\n showMentor\n emptyTitle={tr(\n 'technospam.mentorship.noSessionsBookedTitle',\n 'No sessions booked yet'\n )}\n emptyDescription={tr(\n 'technospam.mentorship.noSessionsBookedDesc',\n 'Find a mentor and request a session to get personalized guidance.'\n )}\n emptyAction={\n <Button onClick={() => navigate('/mentorship/mentors-page')}>\n {tr('technospam.common.findMentor', 'Find a mentor')}\n </Button>\n }\n isMentor={false}\n onCancel={handleCancel}\n onConfirm={handleConfirm}\n onComplete={handleComplete}\n busyId={cancellingId}\n focusedId={focusedSessionId}\n onFocusChange={setFocusedSessionId}\n />\n ) : mentorsQ.loading && !myMentor ? (\n <LoadingRows count={3} height=\"h-24\" />\n ) : !mentorId ? (\n <EmptyView\n icon={<Users size={26} />}\n title={tr('technospam.mentorship.notAMentorTitle', \"You're not a mentor yet\")}\n description={tr(\n 'technospam.mentorship.notAMentorDesc',\n 'Create a mentor profile to start hosting sessions and helping others build.'\n )}\n action={\n <Button onClick={() => navigate('/mentorship/mentors-page')}>\n {tr('technospam.common.becomeAMentor', 'Become a mentor')}\n </Button>\n }\n />\n ) : (\n <SessionsList\n loading={mentorQ.loading}\n error={mentorQ.error}\n sessions={mentorSessions}\n showMentor={false}\n emptyTitle={tr(\n 'technospam.mentorship.noSessionsToHostTitle',\n 'No sessions to host yet'\n )}\n emptyDescription={tr(\n 'technospam.mentorship.noSessionsToHostDesc',\n 'When mentees request time with you, their sessions will show up here.'\n )}\n isMentor\n onCancel={handleCancel}\n onConfirm={handleConfirm}\n onComplete={handleComplete}\n busyId={cancellingId}\n focusedId={focusedSessionId}\n onFocusChange={setFocusedSessionId}\n />\n )}\n </TabsContent>\n </Tabs>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -8,6 +8,13 @@ export interface NavItem {
|
|
|
8
8
|
order?: number;
|
|
9
9
|
group?: string;
|
|
10
10
|
children?: NavItem[];
|
|
11
|
+
/** Match `path` exactly instead of as a prefix when deciding the active item. */
|
|
12
|
+
exact?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Installed-app bottom tab ranking. The host shell owns the TechnoSpam tabs;
|
|
15
|
+
* the module never sets this itself (kept so tests can assert that).
|
|
16
|
+
*/
|
|
17
|
+
mobilePriority?: number;
|
|
11
18
|
}
|
|
12
19
|
export interface FooterItem {
|
|
13
20
|
id: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/technospam/types/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/technospam/types/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;IACrB,iFAAiF;IACjF,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;CACtC;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,WAAW,CAAC,EAAE;QACZ,EAAE,EAAE,MAAM,CAAC;QACX,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,GAAG,IAAI,CAAC;IACT,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,EAAE,KAAK,MAAM,IAAI,CAAC;IAC9D,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,EAAE,KAAK,MAAM,IAAI,CAAC;CACxE;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,CAAC;IACvD,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The four learner journeys the phone layouts are built around (BOFF-4108,
|
|
3
|
+
* mockups technospam-18/19/22): Home, Courses, Practice and Mentors. Profile
|
|
4
|
+
* is the shell's own "More" tab, which already carries settings and the
|
|
5
|
+
* account menu, so it is not duplicated here.
|
|
6
|
+
*/
|
|
7
|
+
export type MobileTabId = 'home' | 'courses' | 'practice' | 'mentors';
|
|
8
|
+
export interface MobileTabDef {
|
|
9
|
+
id: MobileTabId;
|
|
10
|
+
labelKey: string;
|
|
11
|
+
label: string;
|
|
12
|
+
/** Module-relative section prefix; every page under it lights the tab up. */
|
|
13
|
+
section: string;
|
|
14
|
+
/** Module-relative page the tab opens. */
|
|
15
|
+
home: string;
|
|
16
|
+
}
|
|
17
|
+
export declare const MOBILE_TABS: readonly MobileTabDef[];
|
|
18
|
+
/**
|
|
19
|
+
* Which phone tab owns `pathname`. The module index (`/technospam`) renders the
|
|
20
|
+
* dashboard, so it belongs to Home; anything outside the four sections
|
|
21
|
+
* (hackathons, analytics, study planner, admin) belongs to no tab.
|
|
22
|
+
*/
|
|
23
|
+
export declare function activeMobileTab(pathname: string, basePath: string): MobileTabId | null;
|
|
24
|
+
/** Absolute path a tab navigates to, under the MFE's basePath. */
|
|
25
|
+
export declare function mobileTabTarget(tab: MobileTabDef, basePath: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* Child ids of the "TechnoSpam" sidebar item that the bottom tabs replace in
|
|
28
|
+
* the installed app. Dropping them keeps the shell's "More" sheet from
|
|
29
|
+
* listing (and lighting up for) the same pages the tabs already cover.
|
|
30
|
+
*/
|
|
31
|
+
export declare const NATIVE_TAB_COVERED_CHILD_IDS: ReadonlySet<string>;
|
|
32
|
+
/** Practice pages covered by the Practice tab; the rest of Applications stays in More. */
|
|
33
|
+
export declare const NATIVE_TAB_COVERED_GRANDCHILD_IDS: ReadonlySet<string>;
|
|
34
|
+
interface TreeItem {
|
|
35
|
+
id: string;
|
|
36
|
+
children?: TreeItem[];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Remove the tab-covered sections from the module's nav children. Sections
|
|
40
|
+
* left with no children (Applications without its practice pages keeps
|
|
41
|
+
* Portfolio, so it survives) are dropped too.
|
|
42
|
+
*/
|
|
43
|
+
export declare function withoutTabCoveredItems<T extends TreeItem>(children: readonly T[]): T[];
|
|
44
|
+
export {};
|
|
45
|
+
//# sourceMappingURL=mobileTabs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mobileTabs.d.ts","sourceRoot":"","sources":["../../../src/technospam/utils/mobileTabs.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,CAAC;AAEtE,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,WAAW,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,6EAA6E;IAC7E,OAAO,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;CACd;AAED,eAAO,MAAM,WAAW,EAAE,SAAS,YAAY,EA6B9C,CAAC;AAOF;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAYtF;AAED,kEAAkE;AAClE,wBAAgB,eAAe,CAAC,GAAG,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE3E;AAED;;;;GAIG;AACH,eAAO,MAAM,4BAA4B,EAAE,WAAW,CAAC,MAAM,CAK3D,CAAC;AAEH,0FAA0F;AAC1F,eAAO,MAAM,iCAAiC,EAAE,WAAW,CAAC,MAAM,CAIhE,CAAC;AAEH,UAAU,QAAQ;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,QAAQ,EAAE,QAAQ,EAAE,SAAS,CAAC,EAAE,GAAG,CAAC,EAAE,CAatF"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
//#region src/technospam/utils/mobileTabs.ts
|
|
2
|
+
var e = [
|
|
3
|
+
{
|
|
4
|
+
id: "home",
|
|
5
|
+
labelKey: "technospam.mobile.tabs.home",
|
|
6
|
+
label: "Home",
|
|
7
|
+
section: "dashboard",
|
|
8
|
+
home: "dashboard/dashboard-page"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
id: "courses",
|
|
12
|
+
labelKey: "technospam.mobile.tabs.courses",
|
|
13
|
+
label: "Courses",
|
|
14
|
+
section: "courses",
|
|
15
|
+
home: "courses/courses-page"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: "practice",
|
|
19
|
+
labelKey: "technospam.mobile.tabs.practice",
|
|
20
|
+
label: "Practice",
|
|
21
|
+
section: "applications",
|
|
22
|
+
home: "applications/problems-page"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: "mentors",
|
|
26
|
+
labelKey: "technospam.mobile.tabs.mentors",
|
|
27
|
+
label: "Mentors",
|
|
28
|
+
section: "mentorship",
|
|
29
|
+
home: "mentorship/mentors-page"
|
|
30
|
+
}
|
|
31
|
+
];
|
|
32
|
+
function t(e) {
|
|
33
|
+
return e.length > 1 && e.endsWith("/") ? e.slice(0, -1) : e;
|
|
34
|
+
}
|
|
35
|
+
function n(n, r) {
|
|
36
|
+
let i = t(r), a = (i && i !== "/" && (n === i || n.startsWith(`${i}/`)) ? n.slice(i.length) : n).replace(/^\/+/, "").replace(/\/+$/, "");
|
|
37
|
+
if (a === "") return "home";
|
|
38
|
+
for (let t of e) if (a === t.section || a.startsWith(`${t.section}/`)) return t.id;
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
var r = new Set([
|
|
42
|
+
"technospam-home",
|
|
43
|
+
"technospam-dashboard-section",
|
|
44
|
+
"technospam-courses",
|
|
45
|
+
"technospam-mentorship"
|
|
46
|
+
]), i = new Set([
|
|
47
|
+
"technospam-applications-problems-page",
|
|
48
|
+
"technospam-applications-problem-details-page",
|
|
49
|
+
"technospam-applications-solutions-page"
|
|
50
|
+
]);
|
|
51
|
+
function a(e) {
|
|
52
|
+
let t = [];
|
|
53
|
+
for (let n of e) {
|
|
54
|
+
if (r.has(n.id)) continue;
|
|
55
|
+
if (!n.children) {
|
|
56
|
+
t.push(n);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
let e = n.children.filter((e) => !i.has(e.id));
|
|
60
|
+
e.length !== 0 && t.push({
|
|
61
|
+
...n,
|
|
62
|
+
children: e
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return t;
|
|
66
|
+
}
|
|
67
|
+
//#endregion
|
|
68
|
+
export { e as MOBILE_TABS, n as activeMobileTab, a as withoutTabCoveredItems };
|
|
69
|
+
|
|
70
|
+
//# sourceMappingURL=mobileTabs.js.map
|