@burdenoff/microfe-atheletebridge 2026.827.1 → 2026.829.2
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/components/LoadingState.d.ts.map +1 -1
- package/dist/atheletebridge/components/LoadingState.js +39 -32
- package/dist/atheletebridge/components/LoadingState.js.map +1 -1
- package/dist/atheletebridge/components/TeamCard.d.ts.map +1 -1
- package/dist/atheletebridge/components/TeamCard.js +45 -49
- package/dist/atheletebridge/components/TeamCard.js.map +1 -1
- package/dist/atheletebridge/pages/DashboardPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/DashboardPage.js +56 -24
- package/dist/atheletebridge/pages/DashboardPage.js.map +1 -1
- package/dist/atheletebridge/pages/analytics/CoachAnalyticsPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/analytics/CoachAnalyticsPage.js +58 -56
- package/dist/atheletebridge/pages/analytics/CoachAnalyticsPage.js.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js +110 -106
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixturesListPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixturesListPage.js +80 -71
- package/dist/atheletebridge/pages/fixtures/FixturesListPage.js.map +1 -1
- package/dist/atheletebridge/pages/onboarding/OnboardingPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/onboarding/OnboardingPage.js +244 -210
- package/dist/atheletebridge/pages/onboarding/OnboardingPage.js.map +1 -1
- package/dist/atheletebridge/pages/performance/PerformancePage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/performance/PerformancePage.js +197 -148
- package/dist/atheletebridge/pages/performance/PerformancePage.js.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramDetailPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramDetailPage.js +169 -143
- package/dist/atheletebridge/pages/programs/ProgramDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramsListPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramsListPage.js +77 -72
- package/dist/atheletebridge/pages/programs/ProgramsListPage.js.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionDetailPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionDetailPage.js +104 -86
- package/dist/atheletebridge/pages/sessions/SessionDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionsListPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionsListPage.js +54 -49
- package/dist/atheletebridge/pages/sessions/SessionsListPage.js.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamDetailPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamDetailPage.js +286 -267
- package/dist/atheletebridge/pages/teams/TeamDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamFormPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamFormPage.js +185 -180
- package/dist/atheletebridge/pages/teams/TeamFormPage.js.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamsListPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamsListPage.js +70 -74
- package/dist/atheletebridge/pages/teams/TeamsListPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,25 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { useAbTranslate as e } from "../../i18n.js";
|
|
2
|
+
import { useAthleteBridge as t } from "../../context/AthleteBridgeContext.js";
|
|
3
|
+
import { PageLayout as n } from "../../components/PageLayout.js";
|
|
4
|
+
import { RatingStars as r } from "../../components/viz/RatingStars.js";
|
|
4
5
|
import "../../components/viz/index.js";
|
|
5
|
-
import { StatusBadge as
|
|
6
|
-
import { SESSION_TYPE_LABELS as
|
|
7
|
-
import { formatDate as
|
|
8
|
-
import { UserAvatar as
|
|
9
|
-
import { ErrorState as
|
|
10
|
-
import { useCancelSession as
|
|
11
|
-
import { FormField as
|
|
12
|
-
import { SessionStatusTimeline as
|
|
13
|
-
import { safeExternalHref as
|
|
14
|
-
import { useMemo as
|
|
15
|
-
import { ArrowLeft as re, CalendarClock as ie, CheckCircle2 as
|
|
16
|
-
import { useParams as
|
|
6
|
+
import { StatusBadge as i } from "../../components/StatusBadge.js";
|
|
7
|
+
import { SESSION_TYPE_LABELS as a } from "../../components/domain.js";
|
|
8
|
+
import { formatDate as o, formatDuration as ee } from "../../utils/formatters.js";
|
|
9
|
+
import { UserAvatar as s } from "../../components/UserAvatar.js";
|
|
10
|
+
import { ErrorState as c, LoadingState as te } from "../../components/LoadingState.js";
|
|
11
|
+
import { useCancelSession as l, useCompleteSession as ne, useConfirmSession as u, useSession as d, useUserDirectory as f } from "../../hooks/useAthleteBridgeApi.js";
|
|
12
|
+
import { FormField as p, fieldInputClass as m } from "../../components/form.js";
|
|
13
|
+
import { SessionStatusTimeline as h } from "../../components/SessionStatusTimeline.js";
|
|
14
|
+
import { safeExternalHref as g } from "../../utils/url.js";
|
|
15
|
+
import { useMemo as _, useState as v } from "react";
|
|
16
|
+
import { ArrowLeft as re, CalendarClock as ie, CheckCircle2 as y, Clock as ae, ExternalLink as b, Loader2 as x, Mail as S, MapPin as C, Video as w, XCircle as T } from "lucide-react";
|
|
17
|
+
import { useParams as oe } from "react-router";
|
|
17
18
|
import { jsx as E, jsxs as D } from "react/jsx-runtime";
|
|
18
|
-
import { EmphasisPanel as
|
|
19
|
+
import { EmphasisPanel as se, GlassCard as O, ResponsiveDialog as k, ResponsiveDialogContent as A, ResponsiveDialogFooter as j, ResponsiveDialogHeader as M, ResponsiveDialogTitle as N } from "@burdenoff/fe-libs/ui";
|
|
19
20
|
//#region src/atheletebridge/pages/sessions/SessionDetailPage.tsx
|
|
20
|
-
function
|
|
21
|
+
function P({ role: e, name: t, email: n, id: r }) {
|
|
21
22
|
let i = t?.trim() || `User ${r.slice(0, 6)}`;
|
|
22
|
-
return /* @__PURE__ */ D(
|
|
23
|
+
return /* @__PURE__ */ D(O, {
|
|
23
24
|
treatment: "metric",
|
|
24
25
|
className: "p-4",
|
|
25
26
|
children: [
|
|
@@ -29,7 +30,7 @@ function F({ role: e, name: t, email: n, id: r }) {
|
|
|
29
30
|
}),
|
|
30
31
|
/* @__PURE__ */ D("div", {
|
|
31
32
|
className: "mt-3 flex items-center gap-3",
|
|
32
|
-
children: [/* @__PURE__ */ E(
|
|
33
|
+
children: [/* @__PURE__ */ E(s, {
|
|
33
34
|
name: t,
|
|
34
35
|
id: r,
|
|
35
36
|
size: "lg"
|
|
@@ -58,41 +59,45 @@ function F({ role: e, name: t, email: n, id: r }) {
|
|
|
58
59
|
]
|
|
59
60
|
});
|
|
60
61
|
}
|
|
61
|
-
function
|
|
62
|
-
let { id:
|
|
63
|
-
if (I.isLoading) return /* @__PURE__ */ E(
|
|
64
|
-
title: "Session",
|
|
65
|
-
children: /* @__PURE__ */ E(te, { label: "Loading session…" })
|
|
62
|
+
function F() {
|
|
63
|
+
let s = e(), { id: S } = oe(), { navigateTo: F } = t(), I = d(S), L = u(), R = ne(), z = l(), [B, V] = v(!1), [H, U] = v(""), [W, G] = v(0), [K, q] = v(null), J = f(_(() => I.data ? [I.data.coachId, I.data.athleteId] : [], [I.data]));
|
|
64
|
+
if (I.isLoading) return /* @__PURE__ */ E(n, {
|
|
65
|
+
title: s("atheletebridge.pages.sessionDetail.title", "Session"),
|
|
66
|
+
children: /* @__PURE__ */ E(te, { label: s("atheletebridge.pages.sessionDetail.loading", "Loading session…") })
|
|
66
67
|
});
|
|
67
|
-
if (I.isError || !I.data) return /* @__PURE__ */ E(
|
|
68
|
-
title: "Session",
|
|
69
|
-
children: /* @__PURE__ */ E(
|
|
70
|
-
title: "Session not found",
|
|
71
|
-
message: "This session does not exist or you do not have access to it.",
|
|
72
|
-
onRetry: () =>
|
|
68
|
+
if (I.isError || !I.data) return /* @__PURE__ */ E(n, {
|
|
69
|
+
title: s("atheletebridge.pages.sessionDetail.title", "Session"),
|
|
70
|
+
children: /* @__PURE__ */ E(c, {
|
|
71
|
+
title: s("atheletebridge.pages.sessionDetail.notFoundTitle", "Session not found"),
|
|
72
|
+
message: s("atheletebridge.pages.sessionDetail.notFoundMessage", "This session does not exist or you do not have access to it."),
|
|
73
|
+
onRetry: () => F("/sessions")
|
|
73
74
|
})
|
|
74
75
|
});
|
|
75
|
-
let Y = I.data, X = Y.status === "REQUESTED", Z = Y.status === "CONFIRMED", Q = Y.status === "REQUESTED" || Y.status === "CONFIRMED", $ =
|
|
76
|
-
return /* @__PURE__ */ D(
|
|
76
|
+
let Y = I.data, X = Y.status === "REQUESTED", Z = Y.status === "CONFIRMED", Q = Y.status === "REQUESTED" || Y.status === "CONFIRMED", $ = g(Y.meetingUrl);
|
|
77
|
+
return /* @__PURE__ */ D(n, {
|
|
77
78
|
title: Y.title,
|
|
78
|
-
description:
|
|
79
|
+
description: a[Y.type],
|
|
79
80
|
actions: /* @__PURE__ */ D("button", {
|
|
80
81
|
type: "button",
|
|
81
|
-
onClick: () =>
|
|
82
|
+
onClick: () => F("/sessions"),
|
|
82
83
|
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",
|
|
83
|
-
children: [
|
|
84
|
+
children: [
|
|
85
|
+
/* @__PURE__ */ E(re, { className: "h-4 w-4" }),
|
|
86
|
+
" ",
|
|
87
|
+
s("atheletebridge.pages.sessionDetail.back", "Back")
|
|
88
|
+
]
|
|
84
89
|
}),
|
|
85
90
|
children: [
|
|
86
|
-
/* @__PURE__ */ D(
|
|
91
|
+
/* @__PURE__ */ D(O, {
|
|
87
92
|
treatment: "insight",
|
|
88
93
|
className: "mb-6 p-5 sm:p-6",
|
|
89
94
|
children: [/* @__PURE__ */ D("div", {
|
|
90
95
|
className: "mb-5 flex items-center justify-between gap-3",
|
|
91
96
|
children: [/* @__PURE__ */ E("span", {
|
|
92
97
|
className: "text-xs font-medium uppercase tracking-wide text-text-muted",
|
|
93
|
-
children: "Lifecycle"
|
|
94
|
-
}), /* @__PURE__ */ E(
|
|
95
|
-
}), /* @__PURE__ */ E(
|
|
98
|
+
children: s("atheletebridge.pages.sessionDetail.lifecycle", "Lifecycle")
|
|
99
|
+
}), /* @__PURE__ */ E(i, { status: Y.status })]
|
|
100
|
+
}), /* @__PURE__ */ E(h, {
|
|
96
101
|
status: Y.status,
|
|
97
102
|
createdAt: Y.createdAt,
|
|
98
103
|
completedAt: Y.completedAt
|
|
@@ -101,24 +106,24 @@ function I() {
|
|
|
101
106
|
/* @__PURE__ */ D("div", {
|
|
102
107
|
className: "mb-6 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3",
|
|
103
108
|
children: [
|
|
104
|
-
/* @__PURE__ */ E(
|
|
105
|
-
role: "Coach",
|
|
109
|
+
/* @__PURE__ */ E(P, {
|
|
110
|
+
role: s("atheletebridge.pages.sessionDetail.coach", "Coach"),
|
|
106
111
|
id: Y.coachId,
|
|
107
112
|
name: J.nameOf(Y.coachId),
|
|
108
113
|
email: J.emailOf(Y.coachId)
|
|
109
114
|
}),
|
|
110
|
-
/* @__PURE__ */ E(
|
|
111
|
-
role: "Athlete",
|
|
115
|
+
/* @__PURE__ */ E(P, {
|
|
116
|
+
role: s("atheletebridge.pages.sessionDetail.athlete", "Athlete"),
|
|
112
117
|
id: Y.athleteId,
|
|
113
118
|
name: J.nameOf(Y.athleteId),
|
|
114
119
|
email: J.emailOf(Y.athleteId)
|
|
115
120
|
}),
|
|
116
|
-
/* @__PURE__ */ D(
|
|
121
|
+
/* @__PURE__ */ D(O, {
|
|
117
122
|
treatment: "metric",
|
|
118
123
|
className: "p-4",
|
|
119
124
|
children: [/* @__PURE__ */ E("p", {
|
|
120
125
|
className: "text-xs font-medium uppercase tracking-wide text-text-muted",
|
|
121
|
-
children: "Schedule"
|
|
126
|
+
children: s("atheletebridge.pages.sessionDetail.schedule", "Schedule")
|
|
122
127
|
}), /* @__PURE__ */ D("div", {
|
|
123
128
|
className: "mt-3 space-y-2.5 text-sm text-text-primary",
|
|
124
129
|
children: [
|
|
@@ -127,11 +132,11 @@ function I() {
|
|
|
127
132
|
children: [/* @__PURE__ */ E(ie, {
|
|
128
133
|
className: "h-4 w-4 shrink-0 text-text-secondary",
|
|
129
134
|
"aria-hidden": "true"
|
|
130
|
-
}),
|
|
135
|
+
}), o(Y.scheduledAt)]
|
|
131
136
|
}),
|
|
132
137
|
/* @__PURE__ */ D("span", {
|
|
133
138
|
className: "flex items-center gap-2 text-text-secondary",
|
|
134
|
-
children: [/* @__PURE__ */ E(
|
|
139
|
+
children: [/* @__PURE__ */ E(ae, {
|
|
135
140
|
className: "h-4 w-4 shrink-0",
|
|
136
141
|
"aria-hidden": "true"
|
|
137
142
|
}), ee(Y.durationMin)]
|
|
@@ -156,19 +161,20 @@ function I() {
|
|
|
156
161
|
className: "grid grid-cols-1 gap-6 lg:grid-cols-2",
|
|
157
162
|
children: [/* @__PURE__ */ E("div", {
|
|
158
163
|
className: "space-y-6",
|
|
159
|
-
children: /* @__PURE__ */ D(
|
|
164
|
+
children: /* @__PURE__ */ D(O, {
|
|
160
165
|
treatment: "glass",
|
|
161
166
|
className: "p-5 sm:p-6",
|
|
162
167
|
children: [
|
|
163
168
|
/* @__PURE__ */ E("h2", {
|
|
164
169
|
className: "text-sm font-semibold text-text-primary",
|
|
165
|
-
children: "Session details"
|
|
170
|
+
children: s("atheletebridge.pages.sessionDetail.sessionDetails", "Session details")
|
|
166
171
|
}),
|
|
167
172
|
/* @__PURE__ */ D("p", {
|
|
168
173
|
className: "mt-2 text-sm text-text-secondary",
|
|
169
174
|
children: [
|
|
170
|
-
|
|
171
|
-
"
|
|
175
|
+
a[Y.type],
|
|
176
|
+
" ",
|
|
177
|
+
s("atheletebridge.pages.sessionDetail.sessionTypeSuffix", "session"),
|
|
172
178
|
Y.location ? ` · ${Y.location}` : "",
|
|
173
179
|
"."
|
|
174
180
|
]
|
|
@@ -190,18 +196,22 @@ function I() {
|
|
|
190
196
|
className: "min-w-0 flex-1",
|
|
191
197
|
children: [/* @__PURE__ */ E("span", {
|
|
192
198
|
className: "block text-sm font-medium text-text-primary",
|
|
193
|
-
children: "Meeting link"
|
|
199
|
+
children: s("atheletebridge.pages.sessionDetail.meetingLink", "Meeting link")
|
|
194
200
|
}), /* @__PURE__ */ E("span", {
|
|
195
201
|
className: "block truncate text-xs text-text-secondary",
|
|
196
|
-
children: "Online session — tap to join"
|
|
202
|
+
children: s("atheletebridge.pages.sessionDetail.onlineSessionHint", "Online session — tap to join")
|
|
197
203
|
})]
|
|
198
204
|
}),
|
|
199
205
|
/* @__PURE__ */ D("span", {
|
|
200
206
|
className: "inline-flex items-center gap-1 text-sm font-medium text-text-link",
|
|
201
|
-
children: [
|
|
202
|
-
|
|
203
|
-
"
|
|
204
|
-
|
|
207
|
+
children: [
|
|
208
|
+
s("atheletebridge.pages.sessionDetail.join", "Join"),
|
|
209
|
+
" ",
|
|
210
|
+
/* @__PURE__ */ E(b, {
|
|
211
|
+
className: "h-4 w-4",
|
|
212
|
+
"aria-hidden": "true"
|
|
213
|
+
})
|
|
214
|
+
]
|
|
205
215
|
})
|
|
206
216
|
]
|
|
207
217
|
}) : null,
|
|
@@ -210,15 +220,19 @@ function I() {
|
|
|
210
220
|
children: [
|
|
211
221
|
/* @__PURE__ */ E("h3", {
|
|
212
222
|
className: "mb-2 text-sm font-semibold text-text-primary",
|
|
213
|
-
children: "Outcome"
|
|
223
|
+
children: s("atheletebridge.pages.sessionDetail.outcome", "Outcome")
|
|
214
224
|
}),
|
|
215
225
|
/* @__PURE__ */ D("p", {
|
|
216
226
|
className: "text-sm text-text-secondary",
|
|
217
|
-
children: [
|
|
227
|
+
children: [
|
|
228
|
+
s("atheletebridge.pages.sessionDetail.completed", "Completed"),
|
|
229
|
+
" ",
|
|
230
|
+
o(Y.completedAt)
|
|
231
|
+
]
|
|
218
232
|
}),
|
|
219
233
|
Y.rating === null ? null : /* @__PURE__ */ E("div", {
|
|
220
234
|
className: "mt-2",
|
|
221
|
-
children: /* @__PURE__ */ E(
|
|
235
|
+
children: /* @__PURE__ */ E(r, {
|
|
222
236
|
value: Y.rating,
|
|
223
237
|
size: 18,
|
|
224
238
|
showValue: !0
|
|
@@ -236,22 +250,22 @@ function I() {
|
|
|
236
250
|
}) : null
|
|
237
251
|
]
|
|
238
252
|
})
|
|
239
|
-
}), X || Z || Q ? /* @__PURE__ */ D(
|
|
253
|
+
}), X || Z || Q ? /* @__PURE__ */ D(se, {
|
|
240
254
|
className: "h-fit",
|
|
241
255
|
children: [
|
|
242
256
|
/* @__PURE__ */ E("h2", {
|
|
243
257
|
className: "mb-4 text-sm font-semibold text-text-primary",
|
|
244
|
-
children: "Actions"
|
|
258
|
+
children: s("atheletebridge.pages.sessionDetail.actions", "Actions")
|
|
245
259
|
}),
|
|
246
260
|
/* @__PURE__ */ D("div", {
|
|
247
261
|
className: "flex flex-col gap-2.5",
|
|
248
262
|
children: [
|
|
249
263
|
X && /* @__PURE__ */ D("button", {
|
|
250
264
|
type: "button",
|
|
251
|
-
onClick: () =>
|
|
265
|
+
onClick: () => S && L.mutate(S),
|
|
252
266
|
disabled: L.isPending,
|
|
253
267
|
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",
|
|
254
|
-
children: [L.isPending ? /* @__PURE__ */ E(x, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ E(
|
|
268
|
+
children: [L.isPending ? /* @__PURE__ */ E(x, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ E(y, { className: "h-4 w-4" }), s("atheletebridge.pages.sessionDetail.confirm", "Confirm")]
|
|
255
269
|
}),
|
|
256
270
|
Z && /* @__PURE__ */ D("button", {
|
|
257
271
|
type: "button",
|
|
@@ -259,30 +273,34 @@ function I() {
|
|
|
259
273
|
q(null), U(Y.feedback ?? ""), G(Y.rating ?? 0), V(!0);
|
|
260
274
|
},
|
|
261
275
|
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",
|
|
262
|
-
children: [
|
|
276
|
+
children: [
|
|
277
|
+
/* @__PURE__ */ E(y, { className: "h-4 w-4" }),
|
|
278
|
+
" ",
|
|
279
|
+
s("atheletebridge.pages.sessionDetail.complete", "Complete")
|
|
280
|
+
]
|
|
263
281
|
}),
|
|
264
282
|
Q && /* @__PURE__ */ D("button", {
|
|
265
283
|
type: "button",
|
|
266
|
-
onClick: () =>
|
|
284
|
+
onClick: () => S && z.mutate(S),
|
|
267
285
|
disabled: z.isPending,
|
|
268
286
|
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",
|
|
269
|
-
children: [z.isPending ? /* @__PURE__ */ E(x, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ E(T, { className: "h-4 w-4" }), "Cancel session"]
|
|
287
|
+
children: [z.isPending ? /* @__PURE__ */ E(x, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ E(T, { className: "h-4 w-4" }), s("atheletebridge.pages.sessionDetail.cancelSession", "Cancel session")]
|
|
270
288
|
})
|
|
271
289
|
]
|
|
272
290
|
}),
|
|
273
291
|
Z ? /* @__PURE__ */ E("p", {
|
|
274
292
|
className: "mt-3 text-xs text-text-muted",
|
|
275
|
-
children: "Completing lets you leave feedback and a 1–5 star rating for the athlete."
|
|
293
|
+
children: s("atheletebridge.pages.sessionDetail.completeTooltip", "Completing lets you leave feedback and a 1–5 star rating for the athlete.")
|
|
276
294
|
}) : null
|
|
277
295
|
]
|
|
278
296
|
}) : null]
|
|
279
297
|
}),
|
|
280
|
-
/* @__PURE__ */ E(
|
|
298
|
+
/* @__PURE__ */ E(k, {
|
|
281
299
|
open: B,
|
|
282
300
|
onOpenChange: V,
|
|
283
|
-
children: /* @__PURE__ */ D(
|
|
301
|
+
children: /* @__PURE__ */ D(A, { children: [/* @__PURE__ */ E(M, { children: /* @__PURE__ */ E(N, { children: s("atheletebridge.pages.sessionDetail.completeDialogTitle", "Complete session") }) }), /* @__PURE__ */ D("form", {
|
|
284
302
|
onSubmit: async (e) => {
|
|
285
|
-
if (e.preventDefault(), q(null), !
|
|
303
|
+
if (e.preventDefault(), q(null), !S) return;
|
|
286
304
|
let t = W > 0 ? W : void 0;
|
|
287
305
|
if (t !== void 0 && (t < 1 || t > 5)) {
|
|
288
306
|
q("Rating must be between 1 and 5.");
|
|
@@ -290,7 +308,7 @@ function I() {
|
|
|
290
308
|
}
|
|
291
309
|
try {
|
|
292
310
|
await R.mutateAsync({
|
|
293
|
-
id:
|
|
311
|
+
id: S,
|
|
294
312
|
input: {
|
|
295
313
|
feedback: H.trim() || void 0,
|
|
296
314
|
rating: t
|
|
@@ -302,44 +320,44 @@ function I() {
|
|
|
302
320
|
},
|
|
303
321
|
className: "space-y-4 px-1",
|
|
304
322
|
children: [
|
|
305
|
-
/* @__PURE__ */ E(
|
|
306
|
-
label: "Feedback",
|
|
323
|
+
/* @__PURE__ */ E(p, {
|
|
324
|
+
label: s("atheletebridge.pages.sessionDetail.feedbackLabel", "Feedback"),
|
|
307
325
|
htmlFor: "feedback",
|
|
308
|
-
hint: "Optional post-session notes.",
|
|
326
|
+
hint: s("atheletebridge.pages.sessionDetail.feedbackHint", "Optional post-session notes."),
|
|
309
327
|
children: /* @__PURE__ */ E("textarea", {
|
|
310
328
|
id: "feedback",
|
|
311
|
-
className:
|
|
329
|
+
className: m,
|
|
312
330
|
rows: 3,
|
|
313
331
|
value: H,
|
|
314
332
|
onChange: (e) => U(e.target.value),
|
|
315
|
-
placeholder: "How did the session go?"
|
|
333
|
+
placeholder: s("atheletebridge.pages.sessionDetail.feedbackPlaceholder", "How did the session go?")
|
|
316
334
|
})
|
|
317
335
|
}),
|
|
318
|
-
/* @__PURE__ */ E(
|
|
319
|
-
label: "Rating",
|
|
320
|
-
hint: "Optional — tap a star.",
|
|
321
|
-
children: /* @__PURE__ */ E(
|
|
336
|
+
/* @__PURE__ */ E(p, {
|
|
337
|
+
label: s("atheletebridge.pages.sessionDetail.ratingLabel", "Rating"),
|
|
338
|
+
hint: s("atheletebridge.pages.sessionDetail.ratingHint", "Optional — tap a star."),
|
|
339
|
+
children: /* @__PURE__ */ E(r, {
|
|
322
340
|
value: W,
|
|
323
341
|
onChange: G,
|
|
324
342
|
size: 30,
|
|
325
343
|
showLabels: !0,
|
|
326
|
-
ariaLabel: "Rate this session out of 5"
|
|
344
|
+
ariaLabel: s("atheletebridge.pages.sessionDetail.ratingAria", "Rate this session out of 5")
|
|
327
345
|
})
|
|
328
346
|
}),
|
|
329
347
|
K && /* @__PURE__ */ E("p", {
|
|
330
348
|
className: "text-sm text-status-error-text",
|
|
331
349
|
children: K
|
|
332
350
|
}),
|
|
333
|
-
/* @__PURE__ */ D(
|
|
351
|
+
/* @__PURE__ */ D(j, { children: [/* @__PURE__ */ E("button", {
|
|
334
352
|
type: "button",
|
|
335
353
|
onClick: () => V(!1),
|
|
336
354
|
className: "rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken",
|
|
337
|
-
children: "Cancel"
|
|
355
|
+
children: s("common.cancel", "Cancel")
|
|
338
356
|
}), /* @__PURE__ */ D("button", {
|
|
339
357
|
type: "submit",
|
|
340
358
|
disabled: R.isPending,
|
|
341
359
|
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",
|
|
342
|
-
children: [R.isPending && /* @__PURE__ */ E(x, { className: "h-4 w-4 animate-spin" }), "Complete session"]
|
|
360
|
+
children: [R.isPending && /* @__PURE__ */ E(x, { className: "h-4 w-4 animate-spin" }), s("atheletebridge.pages.sessionDetail.completeAction", "Complete session")]
|
|
343
361
|
})] })
|
|
344
362
|
]
|
|
345
363
|
})] })
|
|
@@ -348,6 +366,6 @@ function I() {
|
|
|
348
366
|
});
|
|
349
367
|
}
|
|
350
368
|
//#endregion
|
|
351
|
-
export {
|
|
369
|
+
export { F as SessionDetailPage };
|
|
352
370
|
|
|
353
371
|
//# sourceMappingURL=SessionDetailPage.js.map
|
|
@@ -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';\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';\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';\nimport { useAbTranslate } from '../../i18n';\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 tr = useAbTranslate();\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={tr('atheletebridge.pages.sessionDetail.title', 'Session')}>\n <LoadingState\n label={tr('atheletebridge.pages.sessionDetail.loading', 'Loading session…')}\n />\n </PageLayout>\n );\n }\n\n if (session.isError || !session.data) {\n return (\n <PageLayout title={tr('atheletebridge.pages.sessionDetail.title', 'Session')}>\n <ErrorState\n title={tr('atheletebridge.pages.sessionDetail.notFoundTitle', 'Session not found')}\n message={tr(\n 'atheletebridge.pages.sessionDetail.notFoundMessage',\n 'This session does not exist or you do not have access to it.'\n )}\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\" /> {tr('atheletebridge.pages.sessionDetail.back', '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 {tr('atheletebridge.pages.sessionDetail.lifecycle', '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={tr('atheletebridge.pages.sessionDetail.coach', 'Coach')}\n id={s.coachId}\n name={directory.nameOf(s.coachId)}\n email={directory.emailOf(s.coachId)}\n />\n <PartyCard\n role={tr('atheletebridge.pages.sessionDetail.athlete', '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\">\n {tr('atheletebridge.pages.sessionDetail.schedule', 'Schedule')}\n </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\">\n {tr('atheletebridge.pages.sessionDetail.sessionDetails', 'Session details')}\n </h2>\n <p className=\"mt-2 text-sm text-text-secondary\">\n {SESSION_TYPE_LABELS[s.type]}{' '}\n {tr('atheletebridge.pages.sessionDetail.sessionTypeSuffix', '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\">\n {tr('atheletebridge.pages.sessionDetail.meetingLink', 'Meeting link')}\n </span>\n <span className=\"block truncate text-xs text-text-secondary\">\n {tr(\n 'atheletebridge.pages.sessionDetail.onlineSessionHint',\n 'Online session — tap to join'\n )}\n </span>\n </span>\n <span className=\"inline-flex items-center gap-1 text-sm font-medium text-text-link\">\n {tr('atheletebridge.pages.sessionDetail.join', 'Join')}{' '}\n <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\">\n {tr('atheletebridge.pages.sessionDetail.outcome', 'Outcome')}\n </h3>\n <p className=\"text-sm text-text-secondary\">\n {tr('atheletebridge.pages.sessionDetail.completed', 'Completed')}{' '}\n {formatDate(s.completedAt)}\n </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\">\n {tr('atheletebridge.pages.sessionDetail.actions', 'Actions')}\n </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 {tr('atheletebridge.pages.sessionDetail.confirm', '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\" />{' '}\n {tr('atheletebridge.pages.sessionDetail.complete', '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 {tr('atheletebridge.pages.sessionDetail.cancelSession', 'Cancel session')}\n </button>\n )}\n </div>\n {canComplete ? (\n <p className=\"mt-3 text-xs text-text-muted\">\n {tr(\n 'atheletebridge.pages.sessionDetail.completeTooltip',\n 'Completing lets you leave feedback and a 1–5 star rating for the athlete.'\n )}\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>\n {tr('atheletebridge.pages.sessionDetail.completeDialogTitle', 'Complete session')}\n </ResponsiveDialogTitle>\n </ResponsiveDialogHeader>\n <form onSubmit={handleComplete} className=\"space-y-4 px-1\">\n <FormField\n label={tr('atheletebridge.pages.sessionDetail.feedbackLabel', 'Feedback')}\n htmlFor=\"feedback\"\n hint={tr(\n 'atheletebridge.pages.sessionDetail.feedbackHint',\n 'Optional post-session notes.'\n )}\n >\n <textarea\n id=\"feedback\"\n className={fieldInputClass}\n rows={3}\n value={feedback}\n onChange={(e) => setFeedback(e.target.value)}\n placeholder={tr(\n 'atheletebridge.pages.sessionDetail.feedbackPlaceholder',\n 'How did the session go?'\n )}\n />\n </FormField>\n <FormField\n label={tr('atheletebridge.pages.sessionDetail.ratingLabel', 'Rating')}\n hint={tr('atheletebridge.pages.sessionDetail.ratingHint', 'Optional — tap a star.')}\n >\n <RatingStars\n value={rating}\n onChange={setRating}\n size={30}\n showLabels\n ariaLabel={tr(\n 'atheletebridge.pages.sessionDetail.ratingAria',\n 'Rate this session out of 5'\n )}\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 {tr('common.cancel', '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 {tr('atheletebridge.pages.sessionDetail.completeAction', '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":"SessionsListPage.d.ts","sourceRoot":"","sources":["../../../../src/atheletebridge/pages/sessions/SessionsListPage.tsx"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"SessionsListPage.d.ts","sourceRoot":"","sources":["../../../../src/atheletebridge/pages/sessions/SessionsListPage.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAyBH,wBAAgB,gBAAgB,gCA4G/B"}
|