@burdenoff/microfe-atheletebridge 2026.720.1 → 2026.731.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/atheletebridge/AthleteBridgeRoot.js.map +1 -1
- package/dist/atheletebridge/AthleteBridgeRoutes.js.map +1 -1
- package/dist/atheletebridge/admin/AdminDashboardPage.js.map +1 -1
- package/dist/atheletebridge/admin/AdminRoot.js.map +1 -1
- package/dist/atheletebridge/components/AthleteSelect.js.map +1 -1
- package/dist/atheletebridge/components/Crest.js.map +1 -1
- package/dist/atheletebridge/components/CursorPager.js.map +1 -1
- package/dist/atheletebridge/components/DrillVideoLink.js.map +1 -1
- package/dist/atheletebridge/components/EmptyState.js.map +1 -1
- package/dist/atheletebridge/components/FilterSelect.js.map +1 -1
- package/dist/atheletebridge/components/FixtureCard.js.map +1 -1
- package/dist/atheletebridge/components/LoadingState.js.map +1 -1
- package/dist/atheletebridge/components/MatchupRow.js.map +1 -1
- package/dist/atheletebridge/components/MetricsCard.js.map +1 -1
- package/dist/atheletebridge/components/PageLayout.js.map +1 -1
- package/dist/atheletebridge/components/ProgramCard.js.map +1 -1
- package/dist/atheletebridge/components/RouteAnalytics.js.map +1 -1
- package/dist/atheletebridge/components/Scoreboard.js.map +1 -1
- package/dist/atheletebridge/components/SessionCard.js.map +1 -1
- package/dist/atheletebridge/components/SessionStatusTimeline.js.map +1 -1
- package/dist/atheletebridge/components/StatusBadge.js.map +1 -1
- package/dist/atheletebridge/components/TeamCard.js.map +1 -1
- package/dist/atheletebridge/components/UserAvatar.js.map +1 -1
- package/dist/atheletebridge/components/UserSearchSelect.js.map +1 -1
- package/dist/atheletebridge/components/availability/AvailabilityRosterPanel.d.ts +9 -0
- package/dist/atheletebridge/components/availability/AvailabilityRosterPanel.d.ts.map +1 -0
- package/dist/atheletebridge/components/availability/AvailabilityRosterPanel.js +118 -0
- package/dist/atheletebridge/components/availability/AvailabilityRosterPanel.js.map +1 -0
- package/dist/atheletebridge/components/availability/AvailabilityRsvpControl.d.ts +9 -0
- package/dist/atheletebridge/components/availability/AvailabilityRsvpControl.d.ts.map +1 -0
- package/dist/atheletebridge/components/availability/AvailabilityRsvpControl.js +88 -0
- package/dist/atheletebridge/components/availability/AvailabilityRsvpControl.js.map +1 -0
- package/dist/atheletebridge/components/domain.js.map +1 -1
- package/dist/atheletebridge/components/form.js.map +1 -1
- package/dist/atheletebridge/components/viz/BarChart.js.map +1 -1
- package/dist/atheletebridge/components/viz/DonutChart.js +1 -1
- package/dist/atheletebridge/components/viz/DonutChart.js.map +1 -1
- package/dist/atheletebridge/components/viz/LineChart.js.map +1 -1
- package/dist/atheletebridge/components/viz/RatingStars.js.map +1 -1
- package/dist/atheletebridge/components/viz/Sparkline.js.map +1 -1
- package/dist/atheletebridge/components/viz/StatCard.js.map +1 -1
- package/dist/atheletebridge/components/viz/Timeline.js.map +1 -1
- package/dist/atheletebridge/components/viz/lib/CartesianFrame.js.map +1 -1
- package/dist/atheletebridge/components/viz/lib/ResponsiveSvg.js.map +1 -1
- package/dist/atheletebridge/components/viz/lib/format.js.map +1 -1
- package/dist/atheletebridge/components/viz/lib/geometry.js.map +1 -1
- package/dist/atheletebridge/components/viz/lib/hooks.js.map +1 -1
- package/dist/atheletebridge/components/viz/lib/palette.js.map +1 -1
- package/dist/atheletebridge/context/AthleteBridgeContext.js.map +1 -1
- package/dist/atheletebridge/hooks/useAthleteBridgeApi.d.ts +18 -1
- package/dist/atheletebridge/hooks/useAthleteBridgeApi.d.ts.map +1 -1
- package/dist/atheletebridge/hooks/useAthleteBridgeApi.js +71 -33
- package/dist/atheletebridge/hooks/useAthleteBridgeApi.js.map +1 -1
- package/dist/atheletebridge/i18n.js.map +1 -1
- package/dist/atheletebridge/pages/DashboardPage.js.map +1 -1
- package/dist/atheletebridge/pages/StandingsPage.js.map +1 -1
- package/dist/atheletebridge/pages/analytics/CoachAnalyticsPage.js.map +1 -1
- package/dist/atheletebridge/pages/athletes/AthleteProfilePage.js.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js +87 -75
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixtureFormPage.js.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixturesListPage.js.map +1 -1
- package/dist/atheletebridge/pages/onboarding/OnboardingPage.js.map +1 -1
- package/dist/atheletebridge/pages/performance/PerformancePage.js.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramFormPage.js.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramsListPage.js.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionFormPage.js.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionsListPage.js.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamFormPage.js.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamsListPage.js.map +1 -1
- package/dist/atheletebridge/pages/tournaments/TournamentBracketPage.js.map +1 -1
- package/dist/atheletebridge/store/atheletebridgeStore.js.map +1 -1
- package/dist/atheletebridge/types/index.d.ts +27 -0
- package/dist/atheletebridge/types/index.d.ts.map +1 -1
- package/dist/atheletebridge/utils/cn.js.map +1 -1
- package/dist/atheletebridge/utils/datetime.js.map +1 -1
- package/dist/atheletebridge/utils/formatters.js.map +1 -1
- package/dist/atheletebridge/utils/identity.js.map +1 -1
- package/dist/atheletebridge/utils/navigation.js.map +1 -1
- package/dist/atheletebridge/utils/pageContext.js.map +1 -1
- package/dist/atheletebridge/utils/timeAgo.js.map +1 -1
- package/dist/atheletebridge/utils/url.js.map +1 -1
- package/dist/node_modules/clsx/dist/clsx.js.map +1 -1
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +1 -1
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -1
- package/dist/services/createSandboxAssistantTransport.js.map +1 -1
- package/package.json +5 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SessionDetailPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/sessions/SessionDetailPage.tsx"],"sourcesContent":["/**\n * Session Detail\n * @module atheletebridge/pages/sessions\n *\n * Reads a single session (athSession) and drives its lifecycle:\n * confirm (athConfirmSession), complete with feedback + rating\n * (athCompleteSession) and cancel (athCancelSession).\n *\n * The layout mirrors the concept mock: a lifecycle timeline, paired\n * Coach / Athlete / Schedule cards (identities resolved via the platform User\n * federation lookup), an iconized meeting-link card, and an Actions panel with\n * an interactive 5-star rating in the Complete flow. Semantic tokens only,\n * responsive down to a single mobile column (with bottom-sheet dialogs).\n */\n\nimport { useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n ArrowLeft,\n CalendarClock,\n Clock,\n MapPin,\n Video,\n CheckCircle2,\n XCircle,\n Loader2,\n Mail,\n ExternalLink,\n} from 'lucide-react';\nimport {\n GlassCard,\n EmphasisPanel,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { UserAvatar } from '../../components/UserAvatar';\nimport { SessionStatusTimeline } from '../../components/SessionStatusTimeline';\nimport { RatingStars } from '../../components/viz';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FormField, fieldInputClass } from '../../components/form';\nimport { SESSION_TYPE_LABELS } from '../../components/domain';\nimport { formatDate, formatDuration } from '../../utils/formatters';\nimport { safeExternalHref } from '../../utils/url';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport {\n useSession,\n useUserDirectory,\n useConfirmSession,\n useCompleteSession,\n useCancelSession,\n} from '../../hooks/useAthleteBridgeApi';\n\n/** A labelled identity tile (Coach / Athlete) — monogram avatar + name + email. */\nfunction PartyCard({\n role,\n name,\n email,\n id,\n}: {\n role: string;\n name?: string | null;\n email?: string | null;\n id: string;\n}) {\n const displayName = name?.trim() || `User ${id.slice(0, 6)}`;\n return (\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-muted\">{role}</p>\n <div className=\"mt-3 flex items-center gap-3\">\n <UserAvatar name={name} id={id} size=\"lg\" />\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-semibold text-text-primary\">{displayName}</p>\n <p className=\"truncate text-xs text-text-secondary\">{role}</p>\n </div>\n </div>\n {email ? (\n <a\n href={`mailto:${email}`}\n className=\"mt-3 flex items-center gap-1.5 text-xs text-text-secondary hover:text-text-primary hover:underline\"\n >\n <Mail className=\"h-3.5 w-3.5 shrink-0\" aria-hidden=\"true\" />\n <span className=\"truncate\">{email}</span>\n </a>\n ) : null}\n </GlassCard>\n );\n}\n\nexport function SessionDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useAthleteBridge();\n\n const session = useSession(id);\n const confirmSession = useConfirmSession();\n const completeSession = useCompleteSession();\n const cancelSession = useCancelSession();\n\n const [completeOpen, setCompleteOpen] = useState(false);\n const [feedback, setFeedback] = useState('');\n const [rating, setRating] = useState<number>(0);\n const [error, setError] = useState<string | null>(null);\n\n // Resolve coach + athlete identities in one batched federation call.\n const directoryIds = useMemo(\n () => (session.data ? [session.data.coachId, session.data.athleteId] : []),\n [session.data]\n );\n const directory = useUserDirectory(directoryIds);\n\n if (session.isLoading) {\n return (\n <PageLayout title=\"Session\">\n <LoadingState label=\"Loading session…\" />\n </PageLayout>\n );\n }\n\n if (session.isError || !session.data) {\n return (\n <PageLayout title=\"Session\">\n <ErrorState\n title=\"Session not found\"\n message=\"This session does not exist or you do not have access to it.\"\n onRetry={() => navigateTo('/sessions')}\n />\n </PageLayout>\n );\n }\n\n const s = session.data;\n const canConfirm = s.status === 'REQUESTED';\n const canComplete = s.status === 'CONFIRMED';\n const canCancel = s.status === 'REQUESTED' || s.status === 'CONFIRMED';\n // Sanitize the user-controlled meeting URL before it becomes a clickable href.\n const meetingHref = safeExternalHref(s.meetingUrl);\n\n const handleComplete = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n if (!id) return;\n const ratingNum = rating > 0 ? rating : undefined;\n if (ratingNum !== undefined && (ratingNum < 1 || ratingNum > 5)) {\n setError('Rating must be between 1 and 5.');\n return;\n }\n try {\n await completeSession.mutateAsync({\n id,\n input: {\n feedback: feedback.trim() || undefined,\n rating: ratingNum,\n },\n });\n setCompleteOpen(false);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to complete session.');\n }\n };\n\n return (\n <PageLayout\n title={s.title}\n description={SESSION_TYPE_LABELS[s.type]}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/sessions')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back\n </button>\n }\n >\n {/* Lifecycle timeline + prominent status */}\n <GlassCard treatment=\"insight\" className=\"mb-6 p-5 sm:p-6\">\n <div className=\"mb-5 flex items-center justify-between gap-3\">\n <span className=\"text-xs font-medium uppercase tracking-wide text-text-muted\">\n Lifecycle\n </span>\n <StatusBadge status={s.status} />\n </div>\n <SessionStatusTimeline\n status={s.status}\n createdAt={s.createdAt}\n completedAt={s.completedAt}\n />\n </GlassCard>\n\n {/* Coach / Athlete / Schedule — three-up on desktop, stacked on mobile */}\n <div className=\"mb-6 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n <PartyCard\n role=\"Coach\"\n id={s.coachId}\n name={directory.nameOf(s.coachId)}\n email={directory.emailOf(s.coachId)}\n />\n <PartyCard\n role=\"Athlete\"\n id={s.athleteId}\n name={directory.nameOf(s.athleteId)}\n email={directory.emailOf(s.athleteId)}\n />\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-muted\">Schedule</p>\n <div className=\"mt-3 space-y-2.5 text-sm text-text-primary\">\n <span className=\"flex items-center gap-2\">\n <CalendarClock className=\"h-4 w-4 shrink-0 text-text-secondary\" aria-hidden=\"true\" />\n {formatDate(s.scheduledAt)}\n </span>\n <span className=\"flex items-center gap-2 text-text-secondary\">\n <Clock className=\"h-4 w-4 shrink-0\" aria-hidden=\"true\" />\n {formatDuration(s.durationMin)}\n </span>\n {s.type === 'IN_PERSON' && s.location ? (\n <span className=\"flex items-center gap-2 text-text-secondary\">\n <MapPin className=\"h-4 w-4 shrink-0\" aria-hidden=\"true\" /> {s.location}\n </span>\n ) : null}\n </div>\n </GlassCard>\n </div>\n\n {/* Details + Actions */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n {/* Left: session details, meeting link, outcome */}\n <div className=\"space-y-6\">\n <GlassCard treatment=\"glass\" className=\"p-5 sm:p-6\">\n <h2 className=\"text-sm font-semibold text-text-primary\">Session details</h2>\n <p className=\"mt-2 text-sm text-text-secondary\">\n {SESSION_TYPE_LABELS[s.type]} session\n {s.location ? ` · ${s.location}` : ''}.\n </p>\n\n {meetingHref ? (\n <a\n href={meetingHref}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"mt-4 flex items-center gap-3 rounded-xl border border-border-seam bg-bg-sunken p-3 transition-colors hover:bg-bg-surface focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <span className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary\">\n <Video className=\"h-5 w-5\" aria-hidden=\"true\" />\n </span>\n <span className=\"min-w-0 flex-1\">\n <span className=\"block text-sm font-medium text-text-primary\">Meeting link</span>\n <span className=\"block truncate text-xs text-text-secondary\">\n Online session — tap to join\n </span>\n </span>\n <span className=\"inline-flex items-center gap-1 text-sm font-medium text-text-link\">\n Join <ExternalLink className=\"h-4 w-4\" aria-hidden=\"true\" />\n </span>\n </a>\n ) : null}\n\n {s.completedAt ? (\n <div className=\"mt-5 border-t border-border-seam pt-5\">\n <h3 className=\"mb-2 text-sm font-semibold text-text-primary\">Outcome</h3>\n <p className=\"text-sm text-text-secondary\">Completed {formatDate(s.completedAt)}</p>\n {s.rating !== null ? (\n <div className=\"mt-2\">\n <RatingStars value={s.rating} size={18} showValue />\n </div>\n ) : null}\n {s.feedback ? (\n <p className=\"mt-3 rounded-lg bg-bg-sunken p-3 text-sm italic text-text-secondary\">\n “{s.feedback}”\n </p>\n ) : null}\n </div>\n ) : null}\n </GlassCard>\n </div>\n\n {/* Right: lifecycle actions */}\n {canConfirm || canComplete || canCancel ? (\n <EmphasisPanel className=\"h-fit\">\n <h2 className=\"mb-4 text-sm font-semibold text-text-primary\">Actions</h2>\n <div className=\"flex flex-col gap-2.5\">\n {canConfirm && (\n <button\n type=\"button\"\n onClick={() => id && confirmSession.mutate(id)}\n disabled={confirmSession.isPending}\n className=\"inline-flex w-full items-center justify-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2.5 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-60\"\n >\n {confirmSession.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <CheckCircle2 className=\"h-4 w-4\" />\n )}\n Confirm\n </button>\n )}\n {canComplete && (\n <button\n type=\"button\"\n onClick={() => {\n setError(null);\n setFeedback(s.feedback ?? '');\n setRating(s.rating ?? 0);\n setCompleteOpen(true);\n }}\n className=\"inline-flex w-full items-center justify-center gap-2 rounded-lg bg-status-success-bg-subtle px-4 py-2.5 text-sm font-medium text-status-success-text ring-1 ring-inset ring-status-success-border transition-colors hover:bg-status-success-bg-subtle/70\"\n >\n <CheckCircle2 className=\"h-4 w-4\" /> Complete\n </button>\n )}\n {canCancel && (\n <button\n type=\"button\"\n onClick={() => id && cancelSession.mutate(id)}\n disabled={cancelSession.isPending}\n className=\"inline-flex w-full items-center justify-center gap-2 rounded-lg border border-status-error-border px-4 py-2.5 text-sm font-medium text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:opacity-60\"\n >\n {cancelSession.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <XCircle className=\"h-4 w-4\" />\n )}\n Cancel session\n </button>\n )}\n </div>\n {canComplete ? (\n <p className=\"mt-3 text-xs text-text-muted\">\n Completing lets you leave feedback and a 1–5 star rating for the athlete.\n </p>\n ) : null}\n </EmphasisPanel>\n ) : null}\n </div>\n\n {/* Complete dialog */}\n <ResponsiveDialog open={completeOpen} onOpenChange={setCompleteOpen}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Complete session</ResponsiveDialogTitle>\n </ResponsiveDialogHeader>\n <form onSubmit={handleComplete} className=\"space-y-4 px-1\">\n <FormField label=\"Feedback\" htmlFor=\"feedback\" hint=\"Optional post-session notes.\">\n <textarea\n id=\"feedback\"\n className={fieldInputClass}\n rows={3}\n value={feedback}\n onChange={(e) => setFeedback(e.target.value)}\n placeholder=\"How did the session go?\"\n />\n </FormField>\n <FormField label=\"Rating\" hint=\"Optional — tap a star.\">\n <RatingStars\n value={rating}\n onChange={setRating}\n size={30}\n showLabels\n ariaLabel=\"Rate this session out of 5\"\n />\n </FormField>\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n <ResponsiveDialogFooter>\n <button\n type=\"button\"\n onClick={() => setCompleteOpen(false)}\n className=\"rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={completeSession.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-status-success-bg-subtle px-4 py-2 text-sm font-medium text-status-success-text ring-1 ring-inset ring-status-success-border transition-colors hover:bg-status-success-bg-subtle/70 disabled:opacity-60\"\n >\n {completeSession.isPending && <Loader2 className=\"h-4 w-4 animate-spin\" />}\n Complete session\n </button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SessionDetailPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/sessions/SessionDetailPage.tsx"],"sourcesContent":["/**\n * Session Detail\n * @module atheletebridge/pages/sessions\n *\n * Reads a single session (athSession) and drives its lifecycle:\n * confirm (athConfirmSession), complete with feedback + rating\n * (athCompleteSession) and cancel (athCancelSession).\n *\n * The layout mirrors the concept mock: a lifecycle timeline, paired\n * Coach / Athlete / Schedule cards (identities resolved via the platform User\n * federation lookup), an iconized meeting-link card, and an Actions panel with\n * an interactive 5-star rating in the Complete flow. Semantic tokens only,\n * responsive down to a single mobile column (with bottom-sheet dialogs).\n */\n\nimport { useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n ArrowLeft,\n CalendarClock,\n Clock,\n MapPin,\n Video,\n CheckCircle2,\n XCircle,\n Loader2,\n Mail,\n ExternalLink,\n} from 'lucide-react';\nimport {\n GlassCard,\n EmphasisPanel,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { UserAvatar } from '../../components/UserAvatar';\nimport { SessionStatusTimeline } from '../../components/SessionStatusTimeline';\nimport { RatingStars } from '../../components/viz';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FormField, fieldInputClass } from '../../components/form';\nimport { SESSION_TYPE_LABELS } from '../../components/domain';\nimport { formatDate, formatDuration } from '../../utils/formatters';\nimport { safeExternalHref } from '../../utils/url';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport {\n useSession,\n useUserDirectory,\n useConfirmSession,\n useCompleteSession,\n useCancelSession,\n} from '../../hooks/useAthleteBridgeApi';\n\n/** A labelled identity tile (Coach / Athlete) — monogram avatar + name + email. */\nfunction PartyCard({\n role,\n name,\n email,\n id,\n}: {\n role: string;\n name?: string | null;\n email?: string | null;\n id: string;\n}) {\n const displayName = name?.trim() || `User ${id.slice(0, 6)}`;\n return (\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-muted\">{role}</p>\n <div className=\"mt-3 flex items-center gap-3\">\n <UserAvatar name={name} id={id} size=\"lg\" />\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-semibold text-text-primary\">{displayName}</p>\n <p className=\"truncate text-xs text-text-secondary\">{role}</p>\n </div>\n </div>\n {email ? (\n <a\n href={`mailto:${email}`}\n className=\"mt-3 flex items-center gap-1.5 text-xs text-text-secondary hover:text-text-primary hover:underline\"\n >\n <Mail className=\"h-3.5 w-3.5 shrink-0\" aria-hidden=\"true\" />\n <span className=\"truncate\">{email}</span>\n </a>\n ) : null}\n </GlassCard>\n );\n}\n\nexport function SessionDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useAthleteBridge();\n\n const session = useSession(id);\n const confirmSession = useConfirmSession();\n const completeSession = useCompleteSession();\n const cancelSession = useCancelSession();\n\n const [completeOpen, setCompleteOpen] = useState(false);\n const [feedback, setFeedback] = useState('');\n const [rating, setRating] = useState<number>(0);\n const [error, setError] = useState<string | null>(null);\n\n // Resolve coach + athlete identities in one batched federation call.\n const directoryIds = useMemo(\n () => (session.data ? [session.data.coachId, session.data.athleteId] : []),\n [session.data]\n );\n const directory = useUserDirectory(directoryIds);\n\n if (session.isLoading) {\n return (\n <PageLayout title=\"Session\">\n <LoadingState label=\"Loading session…\" />\n </PageLayout>\n );\n }\n\n if (session.isError || !session.data) {\n return (\n <PageLayout title=\"Session\">\n <ErrorState\n title=\"Session not found\"\n message=\"This session does not exist or you do not have access to it.\"\n onRetry={() => navigateTo('/sessions')}\n />\n </PageLayout>\n );\n }\n\n const s = session.data;\n const canConfirm = s.status === 'REQUESTED';\n const canComplete = s.status === 'CONFIRMED';\n const canCancel = s.status === 'REQUESTED' || s.status === 'CONFIRMED';\n // Sanitize the user-controlled meeting URL before it becomes a clickable href.\n const meetingHref = safeExternalHref(s.meetingUrl);\n\n const handleComplete = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n if (!id) return;\n const ratingNum = rating > 0 ? rating : undefined;\n if (ratingNum !== undefined && (ratingNum < 1 || ratingNum > 5)) {\n setError('Rating must be between 1 and 5.');\n return;\n }\n try {\n await completeSession.mutateAsync({\n id,\n input: {\n feedback: feedback.trim() || undefined,\n rating: ratingNum,\n },\n });\n setCompleteOpen(false);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to complete session.');\n }\n };\n\n return (\n <PageLayout\n title={s.title}\n description={SESSION_TYPE_LABELS[s.type]}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/sessions')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back\n </button>\n }\n >\n {/* Lifecycle timeline + prominent status */}\n <GlassCard treatment=\"insight\" className=\"mb-6 p-5 sm:p-6\">\n <div className=\"mb-5 flex items-center justify-between gap-3\">\n <span className=\"text-xs font-medium uppercase tracking-wide text-text-muted\">\n Lifecycle\n </span>\n <StatusBadge status={s.status} />\n </div>\n <SessionStatusTimeline\n status={s.status}\n createdAt={s.createdAt}\n completedAt={s.completedAt}\n />\n </GlassCard>\n\n {/* Coach / Athlete / Schedule — three-up on desktop, stacked on mobile */}\n <div className=\"mb-6 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n <PartyCard\n role=\"Coach\"\n id={s.coachId}\n name={directory.nameOf(s.coachId)}\n email={directory.emailOf(s.coachId)}\n />\n <PartyCard\n role=\"Athlete\"\n id={s.athleteId}\n name={directory.nameOf(s.athleteId)}\n email={directory.emailOf(s.athleteId)}\n />\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-muted\">Schedule</p>\n <div className=\"mt-3 space-y-2.5 text-sm text-text-primary\">\n <span className=\"flex items-center gap-2\">\n <CalendarClock className=\"h-4 w-4 shrink-0 text-text-secondary\" aria-hidden=\"true\" />\n {formatDate(s.scheduledAt)}\n </span>\n <span className=\"flex items-center gap-2 text-text-secondary\">\n <Clock className=\"h-4 w-4 shrink-0\" aria-hidden=\"true\" />\n {formatDuration(s.durationMin)}\n </span>\n {s.type === 'IN_PERSON' && s.location ? (\n <span className=\"flex items-center gap-2 text-text-secondary\">\n <MapPin className=\"h-4 w-4 shrink-0\" aria-hidden=\"true\" /> {s.location}\n </span>\n ) : null}\n </div>\n </GlassCard>\n </div>\n\n {/* Details + Actions */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n {/* Left: session details, meeting link, outcome */}\n <div className=\"space-y-6\">\n <GlassCard treatment=\"glass\" className=\"p-5 sm:p-6\">\n <h2 className=\"text-sm font-semibold text-text-primary\">Session details</h2>\n <p className=\"mt-2 text-sm text-text-secondary\">\n {SESSION_TYPE_LABELS[s.type]} session\n {s.location ? ` · ${s.location}` : ''}.\n </p>\n\n {meetingHref ? (\n <a\n href={meetingHref}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"mt-4 flex items-center gap-3 rounded-xl border border-border-seam bg-bg-sunken p-3 transition-colors hover:bg-bg-surface focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <span className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary\">\n <Video className=\"h-5 w-5\" aria-hidden=\"true\" />\n </span>\n <span className=\"min-w-0 flex-1\">\n <span className=\"block text-sm font-medium text-text-primary\">Meeting link</span>\n <span className=\"block truncate text-xs text-text-secondary\">\n Online session — tap to join\n </span>\n </span>\n <span className=\"inline-flex items-center gap-1 text-sm font-medium text-text-link\">\n Join <ExternalLink className=\"h-4 w-4\" aria-hidden=\"true\" />\n </span>\n </a>\n ) : null}\n\n {s.completedAt ? (\n <div className=\"mt-5 border-t border-border-seam pt-5\">\n <h3 className=\"mb-2 text-sm font-semibold text-text-primary\">Outcome</h3>\n <p className=\"text-sm text-text-secondary\">Completed {formatDate(s.completedAt)}</p>\n {s.rating !== null ? (\n <div className=\"mt-2\">\n <RatingStars value={s.rating} size={18} showValue />\n </div>\n ) : null}\n {s.feedback ? (\n <p className=\"mt-3 rounded-lg bg-bg-sunken p-3 text-sm italic text-text-secondary\">\n “{s.feedback}”\n </p>\n ) : null}\n </div>\n ) : null}\n </GlassCard>\n </div>\n\n {/* Right: lifecycle actions */}\n {canConfirm || canComplete || canCancel ? (\n <EmphasisPanel className=\"h-fit\">\n <h2 className=\"mb-4 text-sm font-semibold text-text-primary\">Actions</h2>\n <div className=\"flex flex-col gap-2.5\">\n {canConfirm && (\n <button\n type=\"button\"\n onClick={() => id && confirmSession.mutate(id)}\n disabled={confirmSession.isPending}\n className=\"inline-flex w-full items-center justify-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2.5 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-60\"\n >\n {confirmSession.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <CheckCircle2 className=\"h-4 w-4\" />\n )}\n Confirm\n </button>\n )}\n {canComplete && (\n <button\n type=\"button\"\n onClick={() => {\n setError(null);\n setFeedback(s.feedback ?? '');\n setRating(s.rating ?? 0);\n setCompleteOpen(true);\n }}\n className=\"inline-flex w-full items-center justify-center gap-2 rounded-lg bg-status-success-bg-subtle px-4 py-2.5 text-sm font-medium text-status-success-text ring-1 ring-inset ring-status-success-border transition-colors hover:bg-status-success-bg-subtle/70\"\n >\n <CheckCircle2 className=\"h-4 w-4\" /> Complete\n </button>\n )}\n {canCancel && (\n <button\n type=\"button\"\n onClick={() => id && cancelSession.mutate(id)}\n disabled={cancelSession.isPending}\n className=\"inline-flex w-full items-center justify-center gap-2 rounded-lg border border-status-error-border px-4 py-2.5 text-sm font-medium text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:opacity-60\"\n >\n {cancelSession.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <XCircle className=\"h-4 w-4\" />\n )}\n Cancel session\n </button>\n )}\n </div>\n {canComplete ? (\n <p className=\"mt-3 text-xs text-text-muted\">\n Completing lets you leave feedback and a 1–5 star rating for the athlete.\n </p>\n ) : null}\n </EmphasisPanel>\n ) : null}\n </div>\n\n {/* Complete dialog */}\n <ResponsiveDialog open={completeOpen} onOpenChange={setCompleteOpen}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Complete session</ResponsiveDialogTitle>\n </ResponsiveDialogHeader>\n <form onSubmit={handleComplete} className=\"space-y-4 px-1\">\n <FormField label=\"Feedback\" htmlFor=\"feedback\" hint=\"Optional post-session notes.\">\n <textarea\n id=\"feedback\"\n className={fieldInputClass}\n rows={3}\n value={feedback}\n onChange={(e) => setFeedback(e.target.value)}\n placeholder=\"How did the session go?\"\n />\n </FormField>\n <FormField label=\"Rating\" hint=\"Optional — tap a star.\">\n <RatingStars\n value={rating}\n onChange={setRating}\n size={30}\n showLabels\n ariaLabel=\"Rate this session out of 5\"\n />\n </FormField>\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n <ResponsiveDialogFooter>\n <button\n type=\"button\"\n onClick={() => setCompleteOpen(false)}\n className=\"rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={completeSession.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-status-success-bg-subtle px-4 py-2 text-sm font-medium text-status-success-text ring-1 ring-inset ring-status-success-border transition-colors hover:bg-status-success-bg-subtle/70 disabled:opacity-60\"\n >\n {completeSession.isPending && <Loader2 className=\"h-4 w-4 animate-spin\" />}\n Complete session\n </button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SessionFormPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/sessions/SessionFormPage.tsx"],"sourcesContent":["/**\n * Book Session Form\n * @module atheletebridge/pages/sessions\n *\n * Mirrors the concept mock: a GlassCard-framed two-column form with a read-only\n * coach identity (the current user), a team→athlete picker, a segmented session\n * type control, and iconized schedule inputs. Every field maps to a real\n * AthBookSessionInput property — nothing is fabricated. Semantic tokens only.\n */\n\nimport { type KeyboardEvent, useState } from 'react';\nimport {\n ArrowLeft,\n Loader2,\n Save,\n Lock,\n Users,\n Video,\n Blend,\n Calendar,\n Clock,\n type LucideIcon,\n} from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { FormField, fieldInputClass } from '../../components/form';\nimport { AthleteSelect } from '../../components/AthleteSelect';\nimport { UserAvatar } from '../../components/UserAvatar';\nimport { SPORT_ICONS, SESSION_TYPE_LABELS } from '../../components/domain';\nimport { cn } from '../../utils/cn';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport { useBookSession, usePrograms } from '../../hooks/useAthleteBridgeApi';\nimport type { AthSessionType } from '../../types';\n\nconst TYPE_META: Record<AthSessionType, { label: string; icon: LucideIcon }> = {\n IN_PERSON: { label: SESSION_TYPE_LABELS.IN_PERSON, icon: Users },\n ONLINE: { label: SESSION_TYPE_LABELS.ONLINE, icon: Video },\n HYBRID: { label: SESSION_TYPE_LABELS.HYBRID, icon: Blend },\n};\nconst TYPES = Object.keys(TYPE_META) as AthSessionType[];\n\nexport function SessionFormPage() {\n const { navigateTo, currentUser } = useAthleteBridge();\n\n const programs = usePrograms({ status: 'PUBLISHED', pagination: { limit: 100 } });\n const bookSession = useBookSession();\n\n // The coach is always the current user — sent under the hood, shown read-only.\n const coachId = currentUser?.id ?? '';\n const coachName =\n currentUser?.name?.trim() ||\n [currentUser?.firstName, currentUser?.lastName].filter(Boolean).join(' ').trim() ||\n currentUser?.email ||\n 'You';\n\n const [athleteId, setAthleteId] = useState('');\n const [athleteName, setAthleteName] = useState('');\n const [programId, setProgramId] = useState('');\n const [title, setTitle] = useState('');\n const [type, setType] = useState<AthSessionType>('IN_PERSON');\n const [location, setLocation] = useState('');\n const [meetingUrl, setMeetingUrl] = useState('');\n const [scheduledAt, setScheduledAt] = useState('');\n const [durationMin, setDurationMin] = useState('60');\n const [error, setError] = useState<string | null>(null);\n\n const programOptions = programs.data?.items ?? [];\n const selectedProgram = programOptions.find((pr) => pr.id === programId);\n const ProgramSportIcon = selectedProgram ? SPORT_ICONS[selectedProgram.sport] : undefined;\n\n const handleTypeKey = (e: KeyboardEvent<HTMLDivElement>) => {\n const idx = TYPES.indexOf(type);\n if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {\n e.preventDefault();\n setType(TYPES[(idx + 1) % TYPES.length]);\n } else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {\n e.preventDefault();\n setType(TYPES[(idx - 1 + TYPES.length) % TYPES.length]);\n }\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n\n if (!coachId.trim()) {\n setError('Unable to identify you as the coach — please refresh and try again.');\n return;\n }\n if (!athleteId.trim()) {\n setError('Athlete ID is required.');\n return;\n }\n if (!title.trim()) {\n setError('A session title is required.');\n return;\n }\n if (!scheduledAt) {\n setError('A scheduled date and time is required.');\n return;\n }\n const duration = Number.parseInt(durationMin, 10);\n if (Number.isNaN(duration) || duration <= 0) {\n setError('Duration must be a positive number of minutes.');\n return;\n }\n\n try {\n const created = await bookSession.mutateAsync({\n coachId: coachId.trim(),\n athleteId: athleteId.trim(),\n programId: programId || undefined,\n title: title.trim(),\n type,\n location: type !== 'ONLINE' ? location.trim() || undefined : undefined,\n meetingUrl: type !== 'IN_PERSON' ? meetingUrl.trim() || undefined : undefined,\n scheduledAt: new Date(scheduledAt).toISOString(),\n durationMin: duration,\n });\n navigateTo(`/sessions/${created.id}`);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to book session.');\n }\n };\n\n return (\n <PageLayout\n title=\"Book session\"\n description=\"Schedule a coaching session between a coach and an athlete.\"\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/sessions')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Cancel\n </button>\n }\n >\n <form onSubmit={handleSubmit} className=\"mx-auto max-w-3xl\">\n <GlassCard treatment=\"insight\" className=\"p-5 sm:p-7\">\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n {/* Coach (read-only self) */}\n <FormField label=\"Coach\" hint=\"You're booking this session as the coach.\">\n <div className=\"flex items-center gap-3 rounded-lg border border-border-seam bg-bg-sunken px-3 py-2 text-sm text-text-primary\">\n <UserAvatar name={coachName} id={coachId} size=\"sm\" />\n <span className=\"min-w-0 flex-1 truncate font-medium\">{coachName}</span>\n <Lock className=\"h-3.5 w-3.5 shrink-0 text-text-muted\" aria-hidden=\"true\" />\n </div>\n </FormField>\n\n {/* Title */}\n <FormField label=\"Title\" htmlFor=\"title\" required>\n <input\n id=\"title\"\n className={fieldInputClass}\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n placeholder=\"e.g. Sprint technique review\"\n />\n </FormField>\n </div>\n\n {/* Team → athlete picker (spans full width) */}\n <div className=\"mt-5\">\n <AthleteSelect\n athleteId={athleteId}\n onSelect={({ athleteId: id, displayName }) => {\n setAthleteId(id);\n setAthleteName(displayName);\n }}\n required\n idPrefix=\"session-athlete\"\n />\n {athleteId && athleteName ? (\n <div className=\"mt-3 inline-flex items-center gap-2 rounded-full border border-border-seam bg-bg-sunken py-1 pl-1 pr-3\">\n <UserAvatar name={athleteName} id={athleteId} size=\"xs\" />\n <span className=\"text-xs font-medium text-text-primary\">{athleteName}</span>\n </div>\n ) : null}\n </div>\n\n {/* Type — segmented control */}\n <div className=\"mt-5\">\n <span className=\"mb-1.5 block text-sm font-medium text-text-primary\">Type</span>\n <div\n role=\"radiogroup\"\n aria-label=\"Session type\"\n onKeyDown={handleTypeKey}\n className=\"grid grid-cols-3 gap-2\"\n >\n {TYPES.map((tp) => {\n const { label, icon: Icon } = TYPE_META[tp];\n const active = type === tp;\n return (\n <button\n key={tp}\n type=\"button\"\n role=\"radio\"\n aria-checked={active}\n tabIndex={active ? 0 : -1}\n onClick={() => setType(tp)}\n className={cn(\n 'flex items-center justify-center gap-2 rounded-lg border px-3 py-2.5 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]',\n active\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)] text-text-primary ring-1 ring-inset ring-action-primary-bg'\n : 'border-border-seam text-text-secondary hover:bg-bg-sunken'\n )}\n >\n <Icon className=\"h-4 w-4 shrink-0\" aria-hidden=\"true\" />\n {label}\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Program */}\n <div className=\"mt-5\">\n <FormField label=\"Program\" htmlFor=\"program\" hint=\"Optional — tie to a published plan.\">\n <div className=\"relative\">\n {ProgramSportIcon ? (\n <ProgramSportIcon\n className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\"\n aria-hidden=\"true\"\n />\n ) : null}\n <select\n id=\"program\"\n className={cn(fieldInputClass, ProgramSportIcon && 'pl-9')}\n value={programId}\n onChange={(e) => setProgramId(e.target.value)}\n >\n <option value=\"\">No program</option>\n {programOptions.map((pr) => (\n <option key={pr.id} value={pr.id}>\n {pr.name}\n </option>\n ))}\n </select>\n </div>\n </FormField>\n </div>\n\n {type !== 'ONLINE' && (\n <div className=\"mt-5\">\n <FormField label=\"Location\" htmlFor=\"location\">\n <input\n id=\"location\"\n className={fieldInputClass}\n value={location}\n onChange={(e) => setLocation(e.target.value)}\n placeholder=\"e.g. Main gym, Court 2\"\n />\n </FormField>\n </div>\n )}\n\n {type !== 'IN_PERSON' && (\n <div className=\"mt-5\">\n <FormField label=\"Meeting URL\" htmlFor=\"meetingUrl\">\n <input\n id=\"meetingUrl\"\n className={fieldInputClass}\n value={meetingUrl}\n onChange={(e) => setMeetingUrl(e.target.value)}\n placeholder=\"https://meet.example.com/…\"\n />\n </FormField>\n </div>\n )}\n\n <div className=\"mt-5 grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <FormField label=\"Scheduled at\" htmlFor=\"scheduledAt\" required>\n <div className=\"relative\">\n <Calendar\n className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\"\n aria-hidden=\"true\"\n />\n <input\n id=\"scheduledAt\"\n type=\"datetime-local\"\n className={cn(fieldInputClass, 'pl-9')}\n value={scheduledAt}\n onChange={(e) => setScheduledAt(e.target.value)}\n />\n </div>\n </FormField>\n <FormField label=\"Duration (minutes)\" htmlFor=\"duration\">\n <div className=\"relative\">\n <Clock\n className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\"\n aria-hidden=\"true\"\n />\n <input\n id=\"duration\"\n inputMode=\"numeric\"\n className={cn(fieldInputClass, 'pl-9')}\n value={durationMin}\n onChange={(e) => setDurationMin(e.target.value)}\n />\n </div>\n </FormField>\n </div>\n\n {error && <p className=\"mt-5 text-sm text-status-error-text\">{error}</p>}\n\n {/* Footer */}\n <div className=\"mt-6 flex items-center justify-end gap-3 border-t border-border-seam pt-5\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/sessions')}\n className=\"rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={bookSession.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-60\"\n >\n {bookSession.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"h-4 w-4\" />\n )}\n Book session\n </button>\n </div>\n </GlassCard>\n </form>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SessionFormPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/sessions/SessionFormPage.tsx"],"sourcesContent":["/**\n * Book Session Form\n * @module atheletebridge/pages/sessions\n *\n * Mirrors the concept mock: a GlassCard-framed two-column form with a read-only\n * coach identity (the current user), a team→athlete picker, a segmented session\n * type control, and iconized schedule inputs. Every field maps to a real\n * AthBookSessionInput property — nothing is fabricated. Semantic tokens only.\n */\n\nimport { type KeyboardEvent, useState } from 'react';\nimport {\n ArrowLeft,\n Loader2,\n Save,\n Lock,\n Users,\n Video,\n Blend,\n Calendar,\n Clock,\n type LucideIcon,\n} from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { FormField, fieldInputClass } from '../../components/form';\nimport { AthleteSelect } from '../../components/AthleteSelect';\nimport { UserAvatar } from '../../components/UserAvatar';\nimport { SPORT_ICONS, SESSION_TYPE_LABELS } from '../../components/domain';\nimport { cn } from '../../utils/cn';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport { useBookSession, usePrograms } from '../../hooks/useAthleteBridgeApi';\nimport type { AthSessionType } from '../../types';\n\nconst TYPE_META: Record<AthSessionType, { label: string; icon: LucideIcon }> = {\n IN_PERSON: { label: SESSION_TYPE_LABELS.IN_PERSON, icon: Users },\n ONLINE: { label: SESSION_TYPE_LABELS.ONLINE, icon: Video },\n HYBRID: { label: SESSION_TYPE_LABELS.HYBRID, icon: Blend },\n};\nconst TYPES = Object.keys(TYPE_META) as AthSessionType[];\n\nexport function SessionFormPage() {\n const { navigateTo, currentUser } = useAthleteBridge();\n\n const programs = usePrograms({ status: 'PUBLISHED', pagination: { limit: 100 } });\n const bookSession = useBookSession();\n\n // The coach is always the current user — sent under the hood, shown read-only.\n const coachId = currentUser?.id ?? '';\n const coachName =\n currentUser?.name?.trim() ||\n [currentUser?.firstName, currentUser?.lastName].filter(Boolean).join(' ').trim() ||\n currentUser?.email ||\n 'You';\n\n const [athleteId, setAthleteId] = useState('');\n const [athleteName, setAthleteName] = useState('');\n const [programId, setProgramId] = useState('');\n const [title, setTitle] = useState('');\n const [type, setType] = useState<AthSessionType>('IN_PERSON');\n const [location, setLocation] = useState('');\n const [meetingUrl, setMeetingUrl] = useState('');\n const [scheduledAt, setScheduledAt] = useState('');\n const [durationMin, setDurationMin] = useState('60');\n const [error, setError] = useState<string | null>(null);\n\n const programOptions = programs.data?.items ?? [];\n const selectedProgram = programOptions.find((pr) => pr.id === programId);\n const ProgramSportIcon = selectedProgram ? SPORT_ICONS[selectedProgram.sport] : undefined;\n\n const handleTypeKey = (e: KeyboardEvent<HTMLDivElement>) => {\n const idx = TYPES.indexOf(type);\n if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {\n e.preventDefault();\n setType(TYPES[(idx + 1) % TYPES.length]);\n } else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {\n e.preventDefault();\n setType(TYPES[(idx - 1 + TYPES.length) % TYPES.length]);\n }\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n\n if (!coachId.trim()) {\n setError('Unable to identify you as the coach — please refresh and try again.');\n return;\n }\n if (!athleteId.trim()) {\n setError('Athlete ID is required.');\n return;\n }\n if (!title.trim()) {\n setError('A session title is required.');\n return;\n }\n if (!scheduledAt) {\n setError('A scheduled date and time is required.');\n return;\n }\n const duration = Number.parseInt(durationMin, 10);\n if (Number.isNaN(duration) || duration <= 0) {\n setError('Duration must be a positive number of minutes.');\n return;\n }\n\n try {\n const created = await bookSession.mutateAsync({\n coachId: coachId.trim(),\n athleteId: athleteId.trim(),\n programId: programId || undefined,\n title: title.trim(),\n type,\n location: type !== 'ONLINE' ? location.trim() || undefined : undefined,\n meetingUrl: type !== 'IN_PERSON' ? meetingUrl.trim() || undefined : undefined,\n scheduledAt: new Date(scheduledAt).toISOString(),\n durationMin: duration,\n });\n navigateTo(`/sessions/${created.id}`);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to book session.');\n }\n };\n\n return (\n <PageLayout\n title=\"Book session\"\n description=\"Schedule a coaching session between a coach and an athlete.\"\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/sessions')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Cancel\n </button>\n }\n >\n <form onSubmit={handleSubmit} className=\"mx-auto max-w-3xl\">\n <GlassCard treatment=\"insight\" className=\"p-5 sm:p-7\">\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n {/* Coach (read-only self) */}\n <FormField label=\"Coach\" hint=\"You're booking this session as the coach.\">\n <div className=\"flex items-center gap-3 rounded-lg border border-border-seam bg-bg-sunken px-3 py-2 text-sm text-text-primary\">\n <UserAvatar name={coachName} id={coachId} size=\"sm\" />\n <span className=\"min-w-0 flex-1 truncate font-medium\">{coachName}</span>\n <Lock className=\"h-3.5 w-3.5 shrink-0 text-text-muted\" aria-hidden=\"true\" />\n </div>\n </FormField>\n\n {/* Title */}\n <FormField label=\"Title\" htmlFor=\"title\" required>\n <input\n id=\"title\"\n className={fieldInputClass}\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n placeholder=\"e.g. Sprint technique review\"\n />\n </FormField>\n </div>\n\n {/* Team → athlete picker (spans full width) */}\n <div className=\"mt-5\">\n <AthleteSelect\n athleteId={athleteId}\n onSelect={({ athleteId: id, displayName }) => {\n setAthleteId(id);\n setAthleteName(displayName);\n }}\n required\n idPrefix=\"session-athlete\"\n />\n {athleteId && athleteName ? (\n <div className=\"mt-3 inline-flex items-center gap-2 rounded-full border border-border-seam bg-bg-sunken py-1 pl-1 pr-3\">\n <UserAvatar name={athleteName} id={athleteId} size=\"xs\" />\n <span className=\"text-xs font-medium text-text-primary\">{athleteName}</span>\n </div>\n ) : null}\n </div>\n\n {/* Type — segmented control */}\n <div className=\"mt-5\">\n <span className=\"mb-1.5 block text-sm font-medium text-text-primary\">Type</span>\n <div\n role=\"radiogroup\"\n aria-label=\"Session type\"\n onKeyDown={handleTypeKey}\n className=\"grid grid-cols-3 gap-2\"\n >\n {TYPES.map((tp) => {\n const { label, icon: Icon } = TYPE_META[tp];\n const active = type === tp;\n return (\n <button\n key={tp}\n type=\"button\"\n role=\"radio\"\n aria-checked={active}\n tabIndex={active ? 0 : -1}\n onClick={() => setType(tp)}\n className={cn(\n 'flex items-center justify-center gap-2 rounded-lg border px-3 py-2.5 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]',\n active\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)] text-text-primary ring-1 ring-inset ring-action-primary-bg'\n : 'border-border-seam text-text-secondary hover:bg-bg-sunken'\n )}\n >\n <Icon className=\"h-4 w-4 shrink-0\" aria-hidden=\"true\" />\n {label}\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Program */}\n <div className=\"mt-5\">\n <FormField label=\"Program\" htmlFor=\"program\" hint=\"Optional — tie to a published plan.\">\n <div className=\"relative\">\n {ProgramSportIcon ? (\n <ProgramSportIcon\n className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\"\n aria-hidden=\"true\"\n />\n ) : null}\n <select\n id=\"program\"\n className={cn(fieldInputClass, ProgramSportIcon && 'pl-9')}\n value={programId}\n onChange={(e) => setProgramId(e.target.value)}\n >\n <option value=\"\">No program</option>\n {programOptions.map((pr) => (\n <option key={pr.id} value={pr.id}>\n {pr.name}\n </option>\n ))}\n </select>\n </div>\n </FormField>\n </div>\n\n {type !== 'ONLINE' && (\n <div className=\"mt-5\">\n <FormField label=\"Location\" htmlFor=\"location\">\n <input\n id=\"location\"\n className={fieldInputClass}\n value={location}\n onChange={(e) => setLocation(e.target.value)}\n placeholder=\"e.g. Main gym, Court 2\"\n />\n </FormField>\n </div>\n )}\n\n {type !== 'IN_PERSON' && (\n <div className=\"mt-5\">\n <FormField label=\"Meeting URL\" htmlFor=\"meetingUrl\">\n <input\n id=\"meetingUrl\"\n className={fieldInputClass}\n value={meetingUrl}\n onChange={(e) => setMeetingUrl(e.target.value)}\n placeholder=\"https://meet.example.com/…\"\n />\n </FormField>\n </div>\n )}\n\n <div className=\"mt-5 grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <FormField label=\"Scheduled at\" htmlFor=\"scheduledAt\" required>\n <div className=\"relative\">\n <Calendar\n className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\"\n aria-hidden=\"true\"\n />\n <input\n id=\"scheduledAt\"\n type=\"datetime-local\"\n className={cn(fieldInputClass, 'pl-9')}\n value={scheduledAt}\n onChange={(e) => setScheduledAt(e.target.value)}\n />\n </div>\n </FormField>\n <FormField label=\"Duration (minutes)\" htmlFor=\"duration\">\n <div className=\"relative\">\n <Clock\n className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\"\n aria-hidden=\"true\"\n />\n <input\n id=\"duration\"\n inputMode=\"numeric\"\n className={cn(fieldInputClass, 'pl-9')}\n value={durationMin}\n onChange={(e) => setDurationMin(e.target.value)}\n />\n </div>\n </FormField>\n </div>\n\n {error && <p className=\"mt-5 text-sm text-status-error-text\">{error}</p>}\n\n {/* Footer */}\n <div className=\"mt-6 flex items-center justify-end gap-3 border-t border-border-seam pt-5\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/sessions')}\n className=\"rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={bookSession.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-60\"\n >\n {bookSession.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"h-4 w-4\" />\n )}\n Book session\n </button>\n </div>\n </GlassCard>\n </form>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SessionsListPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/sessions/SessionsListPage.tsx"],"sourcesContent":["/**\n * Sessions List\n * @module atheletebridge/pages/sessions\n */\n\nimport { useMemo, useState } from 'react';\nimport { ClipboardCheck, Plus } from 'lucide-react';\nimport { PageLayout } from '../../components/PageLayout';\nimport { SessionCard } from '../../components/SessionCard';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FilterSelect } from '../../components/FilterSelect';\nimport { CursorPager } from '../../components/CursorPager';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport { useSessions, useUserDirectory } from '../../hooks/useAthleteBridgeApi';\nimport type { AthSessionStatus } from '../../types';\n\nconst PAGE_SIZE = 12;\n\nconst STATUS_OPTS: ReadonlyArray<{ value: AthSessionStatus; label: string }> = [\n { value: 'REQUESTED', label: 'Requested' },\n { value: 'CONFIRMED', label: 'Confirmed' },\n { value: 'COMPLETED', label: 'Completed' },\n { value: 'CANCELLED', label: 'Cancelled' },\n { value: 'NO_SHOW', label: 'No show' },\n];\n\nexport function SessionsListPage() {\n const { navigateTo } = useAthleteBridge();\n const [status, setStatus] = useState<AthSessionStatus | ''>('');\n const [page, setPage] = useState(0);\n\n const sessions = useSessions({\n status: status || undefined,\n pagination: { limit: PAGE_SIZE, offset: page * PAGE_SIZE },\n });\n\n const items = sessions.data?.items ?? [];\n const hasMore = sessions.data?.pageInfo.hasMore ?? false;\n\n // Resolve the athlete + coach behind each session so cards read like the mock\n // (name + monogram avatar) rather than a bare id. One batched federation call.\n const directoryIds = useMemo(\n () => (sessions.data?.items ?? []).flatMap((s) => [s.athleteId, s.coachId]),\n [sessions.data]\n );\n const directory = useUserDirectory(directoryIds);\n\n return (\n <PageLayout\n title=\"Sessions\"\n description=\"One-to-one coaching sessions with a request → confirm → complete flow.\"\n actions={\n <button\n type=\"button\"\n data-tour=\"sessions-create\"\n onClick={() => navigateTo('/sessions/new')}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" /> Book session\n </button>\n }\n >\n <div data-tour=\"sessions-filters\" className=\"mb-6 flex flex-wrap items-center gap-3\">\n <FilterSelect\n ariaLabel=\"Filter by status\"\n allLabel=\"All statuses\"\n value={status}\n onChange={(v) => {\n setStatus(v);\n setPage(0);\n }}\n options={STATUS_OPTS}\n />\n </div>\n\n {sessions.isLoading ? (\n <LoadingState label=\"Loading sessions…\" />\n ) : sessions.isError ? (\n <ErrorState onRetry={() => void sessions.refetch()} />\n ) : items.length === 0 ? (\n <EmptyState\n illustration=\"empty-calendar\"\n icon={<ClipboardCheck className=\"h-10 w-10\" />}\n title=\"No sessions found\"\n description={\n status\n ? 'Try clearing your filter to see all sessions.'\n : 'Book a coaching session with an athlete to get started.'\n }\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/sessions/new')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n Book session\n </button>\n }\n />\n ) : (\n <>\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {items.map((session) => (\n <SessionCard\n key={session.id}\n session={session}\n athleteName={directory.nameOf(session.athleteId)}\n coachName={directory.nameOf(session.coachId)}\n onClick={() => navigateTo(`/sessions/${session.id}`)}\n />\n ))}\n </div>\n <CursorPager\n className=\"mt-6\"\n page={page}\n hasMore={hasMore}\n onPrev={() => setPage((p) => Math.max(0, p - 1))}\n onNext={() => setPage((p) => p + 1)}\n />\n </>\n )}\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;AAiBA,IAAM,IAAY,IAEZ,IAAyE;CAC7E;EAAE,OAAO;EAAa,OAAO;
|
|
1
|
+
{"version":3,"file":"SessionsListPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/sessions/SessionsListPage.tsx"],"sourcesContent":["/**\n * Sessions List\n * @module atheletebridge/pages/sessions\n */\n\nimport { useMemo, useState } from 'react';\nimport { ClipboardCheck, Plus } from 'lucide-react';\nimport { PageLayout } from '../../components/PageLayout';\nimport { SessionCard } from '../../components/SessionCard';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FilterSelect } from '../../components/FilterSelect';\nimport { CursorPager } from '../../components/CursorPager';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport { useSessions, useUserDirectory } from '../../hooks/useAthleteBridgeApi';\nimport type { AthSessionStatus } from '../../types';\n\nconst PAGE_SIZE = 12;\n\nconst STATUS_OPTS: ReadonlyArray<{ value: AthSessionStatus; label: string }> = [\n { value: 'REQUESTED', label: 'Requested' },\n { value: 'CONFIRMED', label: 'Confirmed' },\n { value: 'COMPLETED', label: 'Completed' },\n { value: 'CANCELLED', label: 'Cancelled' },\n { value: 'NO_SHOW', label: 'No show' },\n];\n\nexport function SessionsListPage() {\n const { navigateTo } = useAthleteBridge();\n const [status, setStatus] = useState<AthSessionStatus | ''>('');\n const [page, setPage] = useState(0);\n\n const sessions = useSessions({\n status: status || undefined,\n pagination: { limit: PAGE_SIZE, offset: page * PAGE_SIZE },\n });\n\n const items = sessions.data?.items ?? [];\n const hasMore = sessions.data?.pageInfo.hasMore ?? false;\n\n // Resolve the athlete + coach behind each session so cards read like the mock\n // (name + monogram avatar) rather than a bare id. One batched federation call.\n const directoryIds = useMemo(\n () => (sessions.data?.items ?? []).flatMap((s) => [s.athleteId, s.coachId]),\n [sessions.data]\n );\n const directory = useUserDirectory(directoryIds);\n\n return (\n <PageLayout\n title=\"Sessions\"\n description=\"One-to-one coaching sessions with a request → confirm → complete flow.\"\n actions={\n <button\n type=\"button\"\n data-tour=\"sessions-create\"\n onClick={() => navigateTo('/sessions/new')}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" /> Book session\n </button>\n }\n >\n <div data-tour=\"sessions-filters\" className=\"mb-6 flex flex-wrap items-center gap-3\">\n <FilterSelect\n ariaLabel=\"Filter by status\"\n allLabel=\"All statuses\"\n value={status}\n onChange={(v) => {\n setStatus(v);\n setPage(0);\n }}\n options={STATUS_OPTS}\n />\n </div>\n\n {sessions.isLoading ? (\n <LoadingState label=\"Loading sessions…\" />\n ) : sessions.isError ? (\n <ErrorState onRetry={() => void sessions.refetch()} />\n ) : items.length === 0 ? (\n <EmptyState\n illustration=\"empty-calendar\"\n icon={<ClipboardCheck className=\"h-10 w-10\" />}\n title=\"No sessions found\"\n description={\n status\n ? 'Try clearing your filter to see all sessions.'\n : 'Book a coaching session with an athlete to get started.'\n }\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/sessions/new')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n Book session\n </button>\n }\n />\n ) : (\n <>\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {items.map((session) => (\n <SessionCard\n key={session.id}\n session={session}\n athleteName={directory.nameOf(session.athleteId)}\n coachName={directory.nameOf(session.coachId)}\n onClick={() => navigateTo(`/sessions/${session.id}`)}\n />\n ))}\n </div>\n <CursorPager\n className=\"mt-6\"\n page={page}\n hasMore={hasMore}\n onPrev={() => setPage((p) => Math.max(0, p - 1))}\n onNext={() => setPage((p) => p + 1)}\n />\n </>\n )}\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;AAiBA,IAAM,IAAY,IAEZ,IAAyE;CAC7E;EAAE,OAAO;EAAa,OAAO;CAAY;CACzC;EAAE,OAAO;EAAa,OAAO;CAAY;CACzC;EAAE,OAAO;EAAa,OAAO;CAAY;CACzC;EAAE,OAAO;EAAa,OAAO;CAAY;CACzC;EAAE,OAAO;EAAW,OAAO;CAAU;AACvC;AAEA,SAAgB,IAAmB;CACjC,IAAM,EAAE,kBAAe,EAAiB,GAClC,CAAC,GAAQ,KAAa,EAAgC,EAAE,GACxD,CAAC,GAAM,KAAW,EAAS,CAAC,GAE5B,IAAW,EAAY;EAC3B,QAAQ,KAAU,KAAA;EAClB,YAAY;GAAE,OAAO;GAAW,QAAQ,IAAO;EAAU;CAC3D,CAAC,GAEK,IAAQ,EAAS,MAAM,SAAS,CAAC,GACjC,IAAU,EAAS,MAAM,SAAS,WAAW,IAQ7C,IAAY,EAJG,SACZ,EAAS,MAAM,SAAS,CAAC,GAAG,SAAS,MAAM,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,GAC1E,CAAC,EAAS,IAAI,CAEmB,CAAY;CAE/C,OACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,SACE,kBAAC,UAAD;GACE,MAAK;GACL,aAAU;GACV,eAAe,EAAW,eAAe;GACzC,WAAU;aAJZ,CAME,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA,GAAC,eACtB;;YAXZ,CAcE,kBAAC,OAAD;GAAK,aAAU;GAAmB,WAAU;aAC1C,kBAAC,GAAD;IACE,WAAU;IACV,UAAS;IACT,OAAO;IACP,WAAW,MAAM;KAEf,AADA,EAAU,CAAC,GACX,EAAQ,CAAC;IACX;IACA,SAAS;GACV,CAAA;EACE,CAAA,GAEJ,EAAS,YACR,kBAAC,GAAD,EAAc,OAAM,oBAAqB,CAAA,IACvC,EAAS,UACX,kBAAC,GAAD,EAAY,eAAe,KAAK,EAAS,QAAQ,EAAI,CAAA,IACnD,EAAM,WAAW,IACnB,kBAAC,GAAD;GACE,cAAa;GACb,MAAM,kBAAC,GAAD,EAAgB,WAAU,YAAa,CAAA;GAC7C,OAAM;GACN,aACE,IACI,kDACA;GAEN,QACE,kBAAC,UAAD;IACE,MAAK;IACL,eAAe,EAAW,eAAe;IACzC,WAAU;cACX;GAEO,CAAA;EAEX,CAAA,IAED,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAM,KAAK,MACV,kBAAC,GAAD;IAEW;IACT,aAAa,EAAU,OAAO,EAAQ,SAAS;IAC/C,WAAW,EAAU,OAAO,EAAQ,OAAO;IAC3C,eAAe,EAAW,aAAa,EAAQ,IAAI;GACpD,GALM,EAAQ,EAKd,CACF;EACE,CAAA,GACL,kBAAC,GAAD;GACE,WAAU;GACJ;GACG;GACT,cAAc,GAAS,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC,CAAC;GAC/C,cAAc,GAAS,MAAM,IAAI,CAAC;EACnC,CAAA,CACD,EAAA,CAAA,CAEM;;AAEhB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TeamDetailPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/teams/TeamDetailPage.tsx"],"sourcesContent":["/**\n * Team Detail\n * @module atheletebridge/pages/teams\n *\n * Reads a single team (with members + fixtures) from `athTeam`, supports adding,\n * editing (athUpdateMember — role/position/jersey/status inline per roster row)\n * and removing members (athAddMember / athRemoveMember), editing and archiving\n * the team (athUpdateTeam via the form / athArchiveTeam), links each roster row\n * to the athlete profile, and renders the team's fixtures as crest-vs-crest\n * matchup rows.\n */\n\nimport { useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n ArrowLeft,\n CalendarDays,\n Check,\n Edit,\n Archive,\n MapPin,\n Pencil,\n Plus,\n Trash2,\n UserPlus,\n Loader2,\n X,\n} from 'lucide-react';\nimport { useMutation, useQueryClient } from '@tanstack/react-query';\nimport { graphqlFetch, clearGraphqlFetchResponseCache } from '@burdenoff/fe-libs/shared/graphql';\nimport {\n GlassCard,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { Crest } from '../../components/Crest';\nimport { UserAvatar } from '../../components/UserAvatar';\nimport { MatchupRow } from '../../components/MatchupRow';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FormField, fieldInputClass } from '../../components/form';\nimport { UserSearchSelect } from '../../components/UserSearchSelect';\nimport { SPORT_LABELS, MEMBER_ROLE_LABELS } from '../../components/domain';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport {\n useTeam,\n useArchiveTeam,\n useAddMember,\n useRemoveMember,\n} from '../../hooks/useAthleteBridgeApi';\nimport type { AthMember, AthMemberRole, AthMemberStatus, AthUpdateMemberInput } from '../../types';\n\nconst ROLES = Object.keys(MEMBER_ROLE_LABELS) as AthMemberRole[];\nconst MEMBER_STATUSES: AthMemberStatus[] = ['TRIAL', 'ACTIVE', 'INJURED', 'SUSPENDED', 'RELEASED'];\n\nconst memberStatusLabel = (s: AthMemberStatus): string => s.charAt(0) + s.slice(1).toLowerCase();\n\ninterface AddMemberForm {\n userId: string;\n displayName: string;\n role: AthMemberRole;\n status: AthMemberStatus;\n position: string;\n jerseyNumber: string;\n}\n\nconst EMPTY_MEMBER: AddMemberForm = {\n userId: '',\n displayName: '',\n role: 'PLAYER',\n status: 'ACTIVE',\n position: '',\n jerseyNumber: '',\n};\n\ninterface RowDraft {\n role: AthMemberRole;\n status: AthMemberStatus;\n position: string;\n jerseyNumber: string;\n}\n\n/**\n * Inline roster-member update wired directly to the shipped `athUpdateMember`\n * mutation (the shared kit has add/remove hooks but no update hook). Uses the\n * same workspace-token graphqlFetch primitive as the rest of the MFE and clears\n * the short-lived read cache so the team refetch is fresh.\n */\nconst useUpdateMemberInline = (teamId: string | undefined) => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async ({ id, input }: { id: string; input: AthUpdateMemberInput }) => {\n const res = await graphqlFetch<{ athUpdateMember: AthMember }>({\n gateway: 'workspace',\n baseUrl: apiGatewayUrl,\n query: `mutation AthUpdateMember($id: ID!, $input: AthUpdateMemberInput!) {\n athUpdateMember(id: $id, input: $input) {\n id teamId displayName role status position jerseyNumber\n }\n }`,\n variables: { id, input },\n workspaceToken: true,\n });\n if (res.errors?.length) {\n throw new Error(res.errors[0]?.message ?? 'Failed to update member.');\n }\n clearGraphqlFetchResponseCache();\n if (!res.data) throw new Error('Update did not return data.');\n return res.data.athUpdateMember;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'team', teamId] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'members', teamId] });\n },\n });\n};\n\nexport function TeamDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useAthleteBridge();\n\n const team = useTeam(id);\n const archiveTeam = useArchiveTeam();\n const addMember = useAddMember();\n const removeMember = useRemoveMember(id);\n const updateMember = useUpdateMemberInline(id);\n\n const [addOpen, setAddOpen] = useState(false);\n const [memberForm, setMemberForm] = useState<AddMemberForm>(EMPTY_MEMBER);\n const [memberError, setMemberError] = useState<string | null>(null);\n\n const [editingId, setEditingId] = useState<string | null>(null);\n const [draft, setDraft] = useState<RowDraft | null>(null);\n const [rowError, setRowError] = useState<string | null>(null);\n\n if (team.isLoading) {\n return (\n <PageLayout title=\"Team\">\n <LoadingState label=\"Loading team…\" />\n </PageLayout>\n );\n }\n\n if (team.isError || !team.data) {\n return (\n <PageLayout title=\"Team\">\n <ErrorState\n title=\"Team not found\"\n message=\"This team does not exist or you do not have access to it.\"\n onRetry={() => navigateTo('/teams')}\n />\n </PageLayout>\n );\n }\n\n const t = team.data;\n const members = t.members?.items ?? [];\n const fixtures = t.fixtures?.items ?? [];\n\n const metaParts = [\n SPORT_LABELS[t.sport],\n t.ageGroup ?? undefined,\n t.season ?? undefined,\n t.homeVenue ?? undefined,\n ].filter(Boolean) as string[];\n\n const handleArchive = async () => {\n if (!id) return;\n await archiveTeam.mutateAsync(id);\n navigateTo('/teams');\n };\n\n const startEdit = (m: AthMember) => {\n setRowError(null);\n setEditingId(m.id);\n setDraft({\n role: m.role,\n status: m.status,\n position: m.position ?? '',\n jerseyNumber: m.jerseyNumber != null ? String(m.jerseyNumber) : '',\n });\n };\n\n const cancelEdit = () => {\n setEditingId(null);\n setDraft(null);\n setRowError(null);\n };\n\n const saveEdit = async (memberId: string) => {\n if (!draft) return;\n setRowError(null);\n const trimmedJersey = draft.jerseyNumber.trim();\n let jersey: number | undefined;\n if (trimmedJersey) {\n jersey = Number.parseInt(trimmedJersey, 10);\n if (Number.isNaN(jersey)) {\n setRowError('Jersey number must be a number.');\n return;\n }\n }\n try {\n await updateMember.mutateAsync({\n id: memberId,\n input: {\n role: draft.role,\n status: draft.status,\n position: draft.position.trim() || undefined,\n jerseyNumber: jersey,\n },\n });\n cancelEdit();\n } catch (err) {\n setRowError(err instanceof Error ? err.message : 'Failed to update member.');\n }\n };\n\n const handleAddMember = async (e: React.FormEvent) => {\n e.preventDefault();\n setMemberError(null);\n if (!id) return;\n if (!memberForm.userId.trim() || !memberForm.displayName.trim()) {\n setMemberError('User ID and display name are required.');\n return;\n }\n const jersey = memberForm.jerseyNumber.trim()\n ? Number.parseInt(memberForm.jerseyNumber, 10)\n : undefined;\n if (jersey !== undefined && Number.isNaN(jersey)) {\n setMemberError('Jersey number must be a number.');\n return;\n }\n try {\n await addMember.mutateAsync({\n teamId: id,\n userId: memberForm.userId.trim(),\n displayName: memberForm.displayName.trim(),\n role: memberForm.role,\n status: memberForm.status,\n position: memberForm.position.trim() || undefined,\n jerseyNumber: jersey,\n });\n setMemberForm(EMPTY_MEMBER);\n setAddOpen(false);\n } catch (err) {\n setMemberError(err instanceof Error ? err.message : 'Failed to add member.');\n }\n };\n\n const openProfile = (memberId: string) => navigateTo(`/teams/${t.id}/athletes/${memberId}`);\n\n return (\n <PageLayout\n title={t.name}\n gradientTitle\n description={metaParts.join(' · ')}\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/teams')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/teams/${t.id}/edit`)}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-semibold text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n <Edit className=\"h-4 w-4\" /> Edit\n </button>\n {t.status !== 'ARCHIVED' && (\n <button\n type=\"button\"\n onClick={handleArchive}\n disabled={archiveTeam.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:opacity-60\"\n >\n {archiveTeam.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Archive className=\"h-4 w-4\" />\n )}{' '}\n Archive\n </button>\n )}\n </div>\n }\n >\n {/* Hero: crest + meta strip */}\n <GlassCard treatment=\"insight\" className=\"mb-8 p-6\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center\">\n <Crest name={t.name} sport={t.sport} size={64} />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <h2 className=\"text-xl font-semibold text-text-primary\">{t.name}</h2>\n <StatusBadge status={t.status} />\n </div>\n <div className=\"mt-1.5 flex flex-wrap items-center gap-x-2 gap-y-1 text-sm text-text-secondary\">\n {metaParts.map((part, i) => (\n <span key={part} className=\"flex items-center gap-2\">\n {i > 0 && (\n <span aria-hidden=\"true\" className=\"text-text-muted\">\n ·\n </span>\n )}\n {part === t.homeVenue ? (\n <span className=\"flex items-center gap-1\">\n <MapPin className=\"h-3.5 w-3.5\" aria-hidden=\"true\" /> {part}\n </span>\n ) : (\n part\n )}\n </span>\n ))}\n </div>\n </div>\n </div>\n {t.description && <p className=\"mt-4 text-sm text-text-secondary\">{t.description}</p>}\n {t.tags.length > 0 && (\n <div className=\"mt-4 flex flex-wrap gap-1.5\">\n {t.tags.map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\"\n >\n {tag}\n </span>\n ))}\n </div>\n )}\n </GlassCard>\n\n {/* Roster */}\n <section className=\"mb-8\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Roster ({members.length})</h2>\n <button\n type=\"button\"\n onClick={() => {\n setMemberError(null);\n setAddOpen(true);\n }}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-3 py-1.5 text-sm font-semibold text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n <UserPlus className=\"h-4 w-4\" /> Add member\n </button>\n </div>\n\n {members.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<UserPlus className=\"h-10 w-10\" />}\n title=\"No members yet\"\n description=\"Add players, coaches and staff to build the roster.\"\n />\n ) : (\n <GlassCard treatment=\"insight\" className=\"overflow-hidden p-0\">\n <div className=\"overflow-x-auto\">\n <table className=\"w-full min-w-[720px] border-collapse text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam text-left text-xs uppercase tracking-wide text-text-secondary\">\n <th className=\"px-4 py-3 font-medium\">Player</th>\n <th className=\"px-4 py-3 font-medium\">Role</th>\n <th className=\"px-4 py-3 font-medium\">Position</th>\n <th className=\"px-4 py-3 font-medium\">Jersey #</th>\n <th className=\"px-4 py-3 font-medium\">Status</th>\n <th className=\"px-4 py-3 text-right font-medium\">Actions</th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border-seam\">\n {members.map((m) => {\n const isEditing = editingId === m.id;\n const busy = updateMember.isPending && isEditing;\n return (\n <tr\n key={m.id}\n className=\"align-middle transition-colors hover:bg-bg-sunken/60\"\n >\n {/* Player */}\n <td className=\"px-4 py-3\">\n <button\n type=\"button\"\n onClick={() => openProfile(m.id)}\n className=\"group flex items-center gap-3 text-left focus-visible:outline-none\"\n title={`View ${m.displayName}'s profile`}\n >\n <UserAvatar\n name={m.displayName}\n id={m.userId}\n jersey={m.jerseyNumber}\n size=\"sm\"\n />\n <span className=\"min-w-0\">\n <span className=\"block truncate font-medium text-text-primary group-hover:text-text-link\">\n {m.displayName}\n </span>\n <span className=\"block truncate text-xs text-text-secondary group-hover:text-text-link\">\n View profile\n </span>\n </span>\n </button>\n </td>\n\n {/* Role */}\n <td className=\"px-4 py-3\">\n {isEditing && draft ? (\n <select\n aria-label=\"Role\"\n className={fieldInputClass}\n value={draft.role}\n onChange={(e) =>\n setDraft((d) =>\n d ? { ...d, role: e.target.value as AthMemberRole } : d\n )\n }\n >\n {ROLES.map((r) => (\n <option key={r} value={r}>\n {MEMBER_ROLE_LABELS[r]}\n </option>\n ))}\n </select>\n ) : (\n <span className=\"text-text-secondary\">\n {MEMBER_ROLE_LABELS[m.role]}\n </span>\n )}\n </td>\n\n {/* Position */}\n <td className=\"px-4 py-3\">\n {isEditing && draft ? (\n <input\n aria-label=\"Position\"\n className={fieldInputClass}\n value={draft.position}\n onChange={(e) =>\n setDraft((d) => (d ? { ...d, position: e.target.value } : d))\n }\n placeholder=\"e.g. Striker\"\n />\n ) : (\n <span className=\"text-text-secondary\">{m.position || '—'}</span>\n )}\n </td>\n\n {/* Jersey # */}\n <td className=\"px-4 py-3\">\n {isEditing && draft ? (\n <input\n aria-label=\"Jersey number\"\n inputMode=\"numeric\"\n className={`${fieldInputClass} w-20`}\n value={draft.jerseyNumber}\n onChange={(e) =>\n setDraft((d) => (d ? { ...d, jerseyNumber: e.target.value } : d))\n }\n placeholder=\"9\"\n />\n ) : (\n <span className=\"tabular-nums text-text-primary\">\n {m.jerseyNumber ?? '—'}\n </span>\n )}\n </td>\n\n {/* Status */}\n <td className=\"px-4 py-3\">\n {isEditing && draft ? (\n <select\n aria-label=\"Status\"\n className={fieldInputClass}\n value={draft.status}\n onChange={(e) =>\n setDraft((d) =>\n d ? { ...d, status: e.target.value as AthMemberStatus } : d\n )\n }\n >\n {MEMBER_STATUSES.map((s) => (\n <option key={s} value={s}>\n {memberStatusLabel(s)}\n </option>\n ))}\n </select>\n ) : (\n <StatusBadge status={m.status} />\n )}\n </td>\n\n {/* Actions */}\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center justify-end gap-1\">\n {isEditing ? (\n <>\n <button\n type=\"button\"\n onClick={() => void saveEdit(m.id)}\n disabled={busy}\n aria-label=\"Save changes\"\n className=\"rounded-lg p-2 text-status-success-text transition-colors hover:bg-status-success-bg-subtle disabled:opacity-60\"\n >\n {busy ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Check className=\"h-4 w-4\" />\n )}\n </button>\n <button\n type=\"button\"\n onClick={cancelEdit}\n disabled={busy}\n aria-label=\"Cancel editing\"\n className=\"rounded-lg p-2 text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary disabled:opacity-60\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n </>\n ) : (\n <>\n <button\n type=\"button\"\n onClick={() => startEdit(m)}\n aria-label={`Edit ${m.displayName}`}\n className=\"rounded-lg p-2 text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary\"\n >\n <Pencil className=\"h-4 w-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => void removeMember.mutate(m.id)}\n disabled={removeMember.isPending}\n aria-label={`Remove ${m.displayName}`}\n className=\"rounded-lg p-2 text-text-secondary transition-colors hover:bg-status-error-bg-subtle hover:text-status-error-text disabled:opacity-60\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </>\n )}\n </div>\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n {rowError && (\n <p className=\"border-t border-border-seam px-4 py-2 text-sm text-status-error-text\">\n {rowError}\n </p>\n )}\n </GlassCard>\n )}\n </section>\n\n {/* Fixtures */}\n <section>\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Fixtures ({fixtures.length})</h2>\n <div className=\"flex items-center gap-2\">\n {fixtures.length > 0 && (\n <button\n type=\"button\"\n onClick={() => navigateTo('/fixtures')}\n className=\"text-sm font-medium text-text-link transition-colors hover:underline\"\n >\n View all\n </button>\n )}\n <button\n type=\"button\"\n onClick={() => navigateTo(`/fixtures/new?homeTeamId=${t.id}`)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Plus className=\"h-4 w-4\" /> Schedule\n </button>\n </div>\n </div>\n {fixtures.length === 0 ? (\n <EmptyState\n illustration=\"empty-calendar\"\n icon={<CalendarDays className=\"h-10 w-10\" />}\n title=\"No fixtures scheduled\"\n description=\"Schedule a match for this team to start tracking results.\"\n />\n ) : (\n <GlassCard treatment=\"insight\" className=\"px-4 py-1\">\n <div className=\"divide-y divide-border-seam\">\n {fixtures.map((f) => (\n <MatchupRow\n key={f.id}\n fixture={f}\n onClick={() => navigateTo(`/fixtures/${f.id}`)}\n />\n ))}\n </div>\n </GlassCard>\n )}\n </section>\n\n {/* Add member dialog */}\n <ResponsiveDialog open={addOpen} onOpenChange={setAddOpen}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Add member</ResponsiveDialogTitle>\n </ResponsiveDialogHeader>\n <form onSubmit={handleAddMember} className=\"space-y-4 px-1\">\n <UserSearchSelect\n userId={memberForm.userId}\n displayName={memberForm.displayName}\n onSelect={({ userId, displayName }) =>\n setMemberForm((p) => ({ ...p, userId, displayName: displayName || p.displayName }))\n }\n required\n />\n <FormField label=\"Display name\" htmlFor=\"displayName\" required>\n <input\n id=\"displayName\"\n className={fieldInputClass}\n value={memberForm.displayName}\n onChange={(e) => setMemberForm((p) => ({ ...p, displayName: e.target.value }))}\n placeholder=\"e.g. Alex Morgan\"\n />\n </FormField>\n <div className=\"grid grid-cols-2 gap-4\">\n <FormField label=\"Role\" htmlFor=\"role\">\n <select\n id=\"role\"\n className={fieldInputClass}\n value={memberForm.role}\n onChange={(e) =>\n setMemberForm((p) => ({ ...p, role: e.target.value as AthMemberRole }))\n }\n >\n {ROLES.map((r) => (\n <option key={r} value={r}>\n {MEMBER_ROLE_LABELS[r]}\n </option>\n ))}\n </select>\n </FormField>\n <FormField label=\"Status\" htmlFor=\"memberStatus\">\n <select\n id=\"memberStatus\"\n className={fieldInputClass}\n value={memberForm.status}\n onChange={(e) =>\n setMemberForm((p) => ({ ...p, status: e.target.value as AthMemberStatus }))\n }\n >\n {MEMBER_STATUSES.map((s) => (\n <option key={s} value={s}>\n {memberStatusLabel(s)}\n </option>\n ))}\n </select>\n </FormField>\n </div>\n <div className=\"grid grid-cols-2 gap-4\">\n <FormField label=\"Position\" htmlFor=\"position\">\n <input\n id=\"position\"\n className={fieldInputClass}\n value={memberForm.position}\n onChange={(e) => setMemberForm((p) => ({ ...p, position: e.target.value }))}\n placeholder=\"e.g. Striker\"\n />\n </FormField>\n <FormField label=\"Jersey #\" htmlFor=\"jersey\">\n <input\n id=\"jersey\"\n className={fieldInputClass}\n inputMode=\"numeric\"\n value={memberForm.jerseyNumber}\n onChange={(e) => setMemberForm((p) => ({ ...p, jerseyNumber: e.target.value }))}\n placeholder=\"9\"\n />\n </FormField>\n </div>\n {memberError && <p className=\"text-sm text-status-error-text\">{memberError}</p>}\n <ResponsiveDialogFooter>\n <button\n type=\"button\"\n onClick={() => setAddOpen(false)}\n className=\"rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={addMember.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-semibold text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-60\"\n >\n {addMember.isPending && <Loader2 className=\"h-4 w-4 animate-spin\" />}\n Add member\n </button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TeamDetailPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/teams/TeamDetailPage.tsx"],"sourcesContent":["/**\n * Team Detail\n * @module atheletebridge/pages/teams\n *\n * Reads a single team (with members + fixtures) from `athTeam`, supports adding,\n * editing (athUpdateMember — role/position/jersey/status inline per roster row)\n * and removing members (athAddMember / athRemoveMember), editing and archiving\n * the team (athUpdateTeam via the form / athArchiveTeam), links each roster row\n * to the athlete profile, and renders the team's fixtures as crest-vs-crest\n * matchup rows.\n */\n\nimport { useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n ArrowLeft,\n CalendarDays,\n Check,\n Edit,\n Archive,\n MapPin,\n Pencil,\n Plus,\n Trash2,\n UserPlus,\n Loader2,\n X,\n} from 'lucide-react';\nimport { useMutation, useQueryClient } from '@tanstack/react-query';\nimport { graphqlFetch, clearGraphqlFetchResponseCache } from '@burdenoff/fe-libs/shared/graphql';\nimport {\n GlassCard,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { Crest } from '../../components/Crest';\nimport { UserAvatar } from '../../components/UserAvatar';\nimport { MatchupRow } from '../../components/MatchupRow';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FormField, fieldInputClass } from '../../components/form';\nimport { UserSearchSelect } from '../../components/UserSearchSelect';\nimport { SPORT_LABELS, MEMBER_ROLE_LABELS } from '../../components/domain';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport {\n useTeam,\n useArchiveTeam,\n useAddMember,\n useRemoveMember,\n} from '../../hooks/useAthleteBridgeApi';\nimport type { AthMember, AthMemberRole, AthMemberStatus, AthUpdateMemberInput } from '../../types';\n\nconst ROLES = Object.keys(MEMBER_ROLE_LABELS) as AthMemberRole[];\nconst MEMBER_STATUSES: AthMemberStatus[] = ['TRIAL', 'ACTIVE', 'INJURED', 'SUSPENDED', 'RELEASED'];\n\nconst memberStatusLabel = (s: AthMemberStatus): string => s.charAt(0) + s.slice(1).toLowerCase();\n\ninterface AddMemberForm {\n userId: string;\n displayName: string;\n role: AthMemberRole;\n status: AthMemberStatus;\n position: string;\n jerseyNumber: string;\n}\n\nconst EMPTY_MEMBER: AddMemberForm = {\n userId: '',\n displayName: '',\n role: 'PLAYER',\n status: 'ACTIVE',\n position: '',\n jerseyNumber: '',\n};\n\ninterface RowDraft {\n role: AthMemberRole;\n status: AthMemberStatus;\n position: string;\n jerseyNumber: string;\n}\n\n/**\n * Inline roster-member update wired directly to the shipped `athUpdateMember`\n * mutation (the shared kit has add/remove hooks but no update hook). Uses the\n * same workspace-token graphqlFetch primitive as the rest of the MFE and clears\n * the short-lived read cache so the team refetch is fresh.\n */\nconst useUpdateMemberInline = (teamId: string | undefined) => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async ({ id, input }: { id: string; input: AthUpdateMemberInput }) => {\n const res = await graphqlFetch<{ athUpdateMember: AthMember }>({\n gateway: 'workspace',\n baseUrl: apiGatewayUrl,\n query: `mutation AthUpdateMember($id: ID!, $input: AthUpdateMemberInput!) {\n athUpdateMember(id: $id, input: $input) {\n id teamId displayName role status position jerseyNumber\n }\n }`,\n variables: { id, input },\n workspaceToken: true,\n });\n if (res.errors?.length) {\n throw new Error(res.errors[0]?.message ?? 'Failed to update member.');\n }\n clearGraphqlFetchResponseCache();\n if (!res.data) throw new Error('Update did not return data.');\n return res.data.athUpdateMember;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'team', teamId] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'members', teamId] });\n },\n });\n};\n\nexport function TeamDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useAthleteBridge();\n\n const team = useTeam(id);\n const archiveTeam = useArchiveTeam();\n const addMember = useAddMember();\n const removeMember = useRemoveMember(id);\n const updateMember = useUpdateMemberInline(id);\n\n const [addOpen, setAddOpen] = useState(false);\n const [memberForm, setMemberForm] = useState<AddMemberForm>(EMPTY_MEMBER);\n const [memberError, setMemberError] = useState<string | null>(null);\n\n const [editingId, setEditingId] = useState<string | null>(null);\n const [draft, setDraft] = useState<RowDraft | null>(null);\n const [rowError, setRowError] = useState<string | null>(null);\n\n if (team.isLoading) {\n return (\n <PageLayout title=\"Team\">\n <LoadingState label=\"Loading team…\" />\n </PageLayout>\n );\n }\n\n if (team.isError || !team.data) {\n return (\n <PageLayout title=\"Team\">\n <ErrorState\n title=\"Team not found\"\n message=\"This team does not exist or you do not have access to it.\"\n onRetry={() => navigateTo('/teams')}\n />\n </PageLayout>\n );\n }\n\n const t = team.data;\n const members = t.members?.items ?? [];\n const fixtures = t.fixtures?.items ?? [];\n\n const metaParts = [\n SPORT_LABELS[t.sport],\n t.ageGroup ?? undefined,\n t.season ?? undefined,\n t.homeVenue ?? undefined,\n ].filter(Boolean) as string[];\n\n const handleArchive = async () => {\n if (!id) return;\n await archiveTeam.mutateAsync(id);\n navigateTo('/teams');\n };\n\n const startEdit = (m: AthMember) => {\n setRowError(null);\n setEditingId(m.id);\n setDraft({\n role: m.role,\n status: m.status,\n position: m.position ?? '',\n jerseyNumber: m.jerseyNumber != null ? String(m.jerseyNumber) : '',\n });\n };\n\n const cancelEdit = () => {\n setEditingId(null);\n setDraft(null);\n setRowError(null);\n };\n\n const saveEdit = async (memberId: string) => {\n if (!draft) return;\n setRowError(null);\n const trimmedJersey = draft.jerseyNumber.trim();\n let jersey: number | undefined;\n if (trimmedJersey) {\n jersey = Number.parseInt(trimmedJersey, 10);\n if (Number.isNaN(jersey)) {\n setRowError('Jersey number must be a number.');\n return;\n }\n }\n try {\n await updateMember.mutateAsync({\n id: memberId,\n input: {\n role: draft.role,\n status: draft.status,\n position: draft.position.trim() || undefined,\n jerseyNumber: jersey,\n },\n });\n cancelEdit();\n } catch (err) {\n setRowError(err instanceof Error ? err.message : 'Failed to update member.');\n }\n };\n\n const handleAddMember = async (e: React.FormEvent) => {\n e.preventDefault();\n setMemberError(null);\n if (!id) return;\n if (!memberForm.userId.trim() || !memberForm.displayName.trim()) {\n setMemberError('User ID and display name are required.');\n return;\n }\n const jersey = memberForm.jerseyNumber.trim()\n ? Number.parseInt(memberForm.jerseyNumber, 10)\n : undefined;\n if (jersey !== undefined && Number.isNaN(jersey)) {\n setMemberError('Jersey number must be a number.');\n return;\n }\n try {\n await addMember.mutateAsync({\n teamId: id,\n userId: memberForm.userId.trim(),\n displayName: memberForm.displayName.trim(),\n role: memberForm.role,\n status: memberForm.status,\n position: memberForm.position.trim() || undefined,\n jerseyNumber: jersey,\n });\n setMemberForm(EMPTY_MEMBER);\n setAddOpen(false);\n } catch (err) {\n setMemberError(err instanceof Error ? err.message : 'Failed to add member.');\n }\n };\n\n const openProfile = (memberId: string) => navigateTo(`/teams/${t.id}/athletes/${memberId}`);\n\n return (\n <PageLayout\n title={t.name}\n gradientTitle\n description={metaParts.join(' · ')}\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/teams')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/teams/${t.id}/edit`)}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-semibold text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n <Edit className=\"h-4 w-4\" /> Edit\n </button>\n {t.status !== 'ARCHIVED' && (\n <button\n type=\"button\"\n onClick={handleArchive}\n disabled={archiveTeam.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:opacity-60\"\n >\n {archiveTeam.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Archive className=\"h-4 w-4\" />\n )}{' '}\n Archive\n </button>\n )}\n </div>\n }\n >\n {/* Hero: crest + meta strip */}\n <GlassCard treatment=\"insight\" className=\"mb-8 p-6\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center\">\n <Crest name={t.name} sport={t.sport} size={64} />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <h2 className=\"text-xl font-semibold text-text-primary\">{t.name}</h2>\n <StatusBadge status={t.status} />\n </div>\n <div className=\"mt-1.5 flex flex-wrap items-center gap-x-2 gap-y-1 text-sm text-text-secondary\">\n {metaParts.map((part, i) => (\n <span key={part} className=\"flex items-center gap-2\">\n {i > 0 && (\n <span aria-hidden=\"true\" className=\"text-text-muted\">\n ·\n </span>\n )}\n {part === t.homeVenue ? (\n <span className=\"flex items-center gap-1\">\n <MapPin className=\"h-3.5 w-3.5\" aria-hidden=\"true\" /> {part}\n </span>\n ) : (\n part\n )}\n </span>\n ))}\n </div>\n </div>\n </div>\n {t.description && <p className=\"mt-4 text-sm text-text-secondary\">{t.description}</p>}\n {t.tags.length > 0 && (\n <div className=\"mt-4 flex flex-wrap gap-1.5\">\n {t.tags.map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\"\n >\n {tag}\n </span>\n ))}\n </div>\n )}\n </GlassCard>\n\n {/* Roster */}\n <section className=\"mb-8\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Roster ({members.length})</h2>\n <button\n type=\"button\"\n onClick={() => {\n setMemberError(null);\n setAddOpen(true);\n }}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-3 py-1.5 text-sm font-semibold text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n <UserPlus className=\"h-4 w-4\" /> Add member\n </button>\n </div>\n\n {members.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<UserPlus className=\"h-10 w-10\" />}\n title=\"No members yet\"\n description=\"Add players, coaches and staff to build the roster.\"\n />\n ) : (\n <GlassCard treatment=\"insight\" className=\"overflow-hidden p-0\">\n <div className=\"overflow-x-auto\">\n <table className=\"w-full min-w-[720px] border-collapse text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam text-left text-xs uppercase tracking-wide text-text-secondary\">\n <th className=\"px-4 py-3 font-medium\">Player</th>\n <th className=\"px-4 py-3 font-medium\">Role</th>\n <th className=\"px-4 py-3 font-medium\">Position</th>\n <th className=\"px-4 py-3 font-medium\">Jersey #</th>\n <th className=\"px-4 py-3 font-medium\">Status</th>\n <th className=\"px-4 py-3 text-right font-medium\">Actions</th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border-seam\">\n {members.map((m) => {\n const isEditing = editingId === m.id;\n const busy = updateMember.isPending && isEditing;\n return (\n <tr\n key={m.id}\n className=\"align-middle transition-colors hover:bg-bg-sunken/60\"\n >\n {/* Player */}\n <td className=\"px-4 py-3\">\n <button\n type=\"button\"\n onClick={() => openProfile(m.id)}\n className=\"group flex items-center gap-3 text-left focus-visible:outline-none\"\n title={`View ${m.displayName}'s profile`}\n >\n <UserAvatar\n name={m.displayName}\n id={m.userId}\n jersey={m.jerseyNumber}\n size=\"sm\"\n />\n <span className=\"min-w-0\">\n <span className=\"block truncate font-medium text-text-primary group-hover:text-text-link\">\n {m.displayName}\n </span>\n <span className=\"block truncate text-xs text-text-secondary group-hover:text-text-link\">\n View profile\n </span>\n </span>\n </button>\n </td>\n\n {/* Role */}\n <td className=\"px-4 py-3\">\n {isEditing && draft ? (\n <select\n aria-label=\"Role\"\n className={fieldInputClass}\n value={draft.role}\n onChange={(e) =>\n setDraft((d) =>\n d ? { ...d, role: e.target.value as AthMemberRole } : d\n )\n }\n >\n {ROLES.map((r) => (\n <option key={r} value={r}>\n {MEMBER_ROLE_LABELS[r]}\n </option>\n ))}\n </select>\n ) : (\n <span className=\"text-text-secondary\">\n {MEMBER_ROLE_LABELS[m.role]}\n </span>\n )}\n </td>\n\n {/* Position */}\n <td className=\"px-4 py-3\">\n {isEditing && draft ? (\n <input\n aria-label=\"Position\"\n className={fieldInputClass}\n value={draft.position}\n onChange={(e) =>\n setDraft((d) => (d ? { ...d, position: e.target.value } : d))\n }\n placeholder=\"e.g. Striker\"\n />\n ) : (\n <span className=\"text-text-secondary\">{m.position || '—'}</span>\n )}\n </td>\n\n {/* Jersey # */}\n <td className=\"px-4 py-3\">\n {isEditing && draft ? (\n <input\n aria-label=\"Jersey number\"\n inputMode=\"numeric\"\n className={`${fieldInputClass} w-20`}\n value={draft.jerseyNumber}\n onChange={(e) =>\n setDraft((d) => (d ? { ...d, jerseyNumber: e.target.value } : d))\n }\n placeholder=\"9\"\n />\n ) : (\n <span className=\"tabular-nums text-text-primary\">\n {m.jerseyNumber ?? '—'}\n </span>\n )}\n </td>\n\n {/* Status */}\n <td className=\"px-4 py-3\">\n {isEditing && draft ? (\n <select\n aria-label=\"Status\"\n className={fieldInputClass}\n value={draft.status}\n onChange={(e) =>\n setDraft((d) =>\n d ? { ...d, status: e.target.value as AthMemberStatus } : d\n )\n }\n >\n {MEMBER_STATUSES.map((s) => (\n <option key={s} value={s}>\n {memberStatusLabel(s)}\n </option>\n ))}\n </select>\n ) : (\n <StatusBadge status={m.status} />\n )}\n </td>\n\n {/* Actions */}\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center justify-end gap-1\">\n {isEditing ? (\n <>\n <button\n type=\"button\"\n onClick={() => void saveEdit(m.id)}\n disabled={busy}\n aria-label=\"Save changes\"\n className=\"rounded-lg p-2 text-status-success-text transition-colors hover:bg-status-success-bg-subtle disabled:opacity-60\"\n >\n {busy ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Check className=\"h-4 w-4\" />\n )}\n </button>\n <button\n type=\"button\"\n onClick={cancelEdit}\n disabled={busy}\n aria-label=\"Cancel editing\"\n className=\"rounded-lg p-2 text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary disabled:opacity-60\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n </>\n ) : (\n <>\n <button\n type=\"button\"\n onClick={() => startEdit(m)}\n aria-label={`Edit ${m.displayName}`}\n className=\"rounded-lg p-2 text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary\"\n >\n <Pencil className=\"h-4 w-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => void removeMember.mutate(m.id)}\n disabled={removeMember.isPending}\n aria-label={`Remove ${m.displayName}`}\n className=\"rounded-lg p-2 text-text-secondary transition-colors hover:bg-status-error-bg-subtle hover:text-status-error-text disabled:opacity-60\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </>\n )}\n </div>\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n {rowError && (\n <p className=\"border-t border-border-seam px-4 py-2 text-sm text-status-error-text\">\n {rowError}\n </p>\n )}\n </GlassCard>\n )}\n </section>\n\n {/* Fixtures */}\n <section>\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Fixtures ({fixtures.length})</h2>\n <div className=\"flex items-center gap-2\">\n {fixtures.length > 0 && (\n <button\n type=\"button\"\n onClick={() => navigateTo('/fixtures')}\n className=\"text-sm font-medium text-text-link transition-colors hover:underline\"\n >\n View all\n </button>\n )}\n <button\n type=\"button\"\n onClick={() => navigateTo(`/fixtures/new?homeTeamId=${t.id}`)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Plus className=\"h-4 w-4\" /> Schedule\n </button>\n </div>\n </div>\n {fixtures.length === 0 ? (\n <EmptyState\n illustration=\"empty-calendar\"\n icon={<CalendarDays className=\"h-10 w-10\" />}\n title=\"No fixtures scheduled\"\n description=\"Schedule a match for this team to start tracking results.\"\n />\n ) : (\n <GlassCard treatment=\"insight\" className=\"px-4 py-1\">\n <div className=\"divide-y divide-border-seam\">\n {fixtures.map((f) => (\n <MatchupRow\n key={f.id}\n fixture={f}\n onClick={() => navigateTo(`/fixtures/${f.id}`)}\n />\n ))}\n </div>\n </GlassCard>\n )}\n </section>\n\n {/* Add member dialog */}\n <ResponsiveDialog open={addOpen} onOpenChange={setAddOpen}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Add member</ResponsiveDialogTitle>\n </ResponsiveDialogHeader>\n <form onSubmit={handleAddMember} className=\"space-y-4 px-1\">\n <UserSearchSelect\n userId={memberForm.userId}\n displayName={memberForm.displayName}\n onSelect={({ userId, displayName }) =>\n setMemberForm((p) => ({ ...p, userId, displayName: displayName || p.displayName }))\n }\n required\n />\n <FormField label=\"Display name\" htmlFor=\"displayName\" required>\n <input\n id=\"displayName\"\n className={fieldInputClass}\n value={memberForm.displayName}\n onChange={(e) => setMemberForm((p) => ({ ...p, displayName: e.target.value }))}\n placeholder=\"e.g. Alex Morgan\"\n />\n </FormField>\n <div className=\"grid grid-cols-2 gap-4\">\n <FormField label=\"Role\" htmlFor=\"role\">\n <select\n id=\"role\"\n className={fieldInputClass}\n value={memberForm.role}\n onChange={(e) =>\n setMemberForm((p) => ({ ...p, role: e.target.value as AthMemberRole }))\n }\n >\n {ROLES.map((r) => (\n <option key={r} value={r}>\n {MEMBER_ROLE_LABELS[r]}\n </option>\n ))}\n </select>\n </FormField>\n <FormField label=\"Status\" htmlFor=\"memberStatus\">\n <select\n id=\"memberStatus\"\n className={fieldInputClass}\n value={memberForm.status}\n onChange={(e) =>\n setMemberForm((p) => ({ ...p, status: e.target.value as AthMemberStatus }))\n }\n >\n {MEMBER_STATUSES.map((s) => (\n <option key={s} value={s}>\n {memberStatusLabel(s)}\n </option>\n ))}\n </select>\n </FormField>\n </div>\n <div className=\"grid grid-cols-2 gap-4\">\n <FormField label=\"Position\" htmlFor=\"position\">\n <input\n id=\"position\"\n className={fieldInputClass}\n value={memberForm.position}\n onChange={(e) => setMemberForm((p) => ({ ...p, position: e.target.value }))}\n placeholder=\"e.g. Striker\"\n />\n </FormField>\n <FormField label=\"Jersey #\" htmlFor=\"jersey\">\n <input\n id=\"jersey\"\n className={fieldInputClass}\n inputMode=\"numeric\"\n value={memberForm.jerseyNumber}\n onChange={(e) => setMemberForm((p) => ({ ...p, jerseyNumber: e.target.value }))}\n placeholder=\"9\"\n />\n </FormField>\n </div>\n {memberError && <p className=\"text-sm text-status-error-text\">{memberError}</p>}\n <ResponsiveDialogFooter>\n <button\n type=\"button\"\n onClick={() => setAddOpen(false)}\n className=\"rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={addMember.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-semibold text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-60\"\n >\n {addMember.isPending && <Loader2 className=\"h-4 w-4 animate-spin\" />}\n Add member\n </button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|