@burdenoff/microfe-atheletebridge 2026.629.1 → 2026.705.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.
Files changed (36) hide show
  1. package/dist/atheletebridge/AthleteBridgeRoot.js +45 -37
  2. package/dist/atheletebridge/AthleteBridgeRoot.js.map +1 -1
  3. package/dist/atheletebridge/AthleteBridgeRoutes.js +10 -6
  4. package/dist/atheletebridge/AthleteBridgeRoutes.js.map +1 -1
  5. package/dist/atheletebridge/components/AthleteSelect.js +66 -0
  6. package/dist/atheletebridge/components/AthleteSelect.js.map +1 -0
  7. package/dist/atheletebridge/components/DrillVideoLink.js +28 -0
  8. package/dist/atheletebridge/components/DrillVideoLink.js.map +1 -0
  9. package/dist/atheletebridge/components/MetricsCard.js +20 -46
  10. package/dist/atheletebridge/components/MetricsCard.js.map +1 -1
  11. package/dist/atheletebridge/components/UserSearchSelect.js +129 -0
  12. package/dist/atheletebridge/components/UserSearchSelect.js.map +1 -0
  13. package/dist/atheletebridge/components/domain.js +10 -1
  14. package/dist/atheletebridge/components/domain.js.map +1 -1
  15. package/dist/atheletebridge/hooks/useAthleteBridgeApi.js +313 -180
  16. package/dist/atheletebridge/hooks/useAthleteBridgeApi.js.map +1 -1
  17. package/dist/atheletebridge/pages/DashboardPage.js +77 -69
  18. package/dist/atheletebridge/pages/DashboardPage.js.map +1 -1
  19. package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js +4 -4
  20. package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js.map +1 -1
  21. package/dist/atheletebridge/pages/performance/PerformancePage.js +496 -0
  22. package/dist/atheletebridge/pages/performance/PerformancePage.js.map +1 -0
  23. package/dist/atheletebridge/pages/programs/ProgramDetailPage.js +176 -142
  24. package/dist/atheletebridge/pages/programs/ProgramDetailPage.js.map +1 -1
  25. package/dist/atheletebridge/pages/sessions/SessionDetailPage.js +86 -85
  26. package/dist/atheletebridge/pages/sessions/SessionDetailPage.js.map +1 -1
  27. package/dist/atheletebridge/pages/sessions/SessionFormPage.js +92 -98
  28. package/dist/atheletebridge/pages/sessions/SessionFormPage.js.map +1 -1
  29. package/dist/atheletebridge/pages/teams/TeamDetailPage.js +132 -137
  30. package/dist/atheletebridge/pages/teams/TeamDetailPage.js.map +1 -1
  31. package/dist/atheletebridge/utils/formatters.js +2 -25
  32. package/dist/atheletebridge/utils/formatters.js.map +1 -1
  33. package/dist/atheletebridge/utils/url.js +17 -0
  34. package/dist/atheletebridge/utils/url.js.map +1 -0
  35. package/dist/index.js +4 -4
  36. package/package.json +5 -5
@@ -3,197 +3,191 @@ import { PageLayout as t } from "../../components/PageLayout.js";
3
3
  import { SESSION_TYPE_LABELS as n } from "../../components/domain.js";
4
4
  import { useBookSession as r, usePrograms as i } from "../../hooks/useAthleteBridgeApi.js";
5
5
  import { FormField as a, fieldInputClass as o } from "../../components/form.js";
6
- import { useState as s } from "react";
7
- import { ArrowLeft as c, Loader2 as l, Save as u } from "lucide-react";
8
- import { jsx as d, jsxs as f } from "react/jsx-runtime";
6
+ import { AthleteSelect as s } from "../../components/AthleteSelect.js";
7
+ import { useState as c } from "react";
8
+ import { ArrowLeft as l, Loader2 as u, Save as d, User as f } from "lucide-react";
9
+ import { jsx as p, jsxs as m } from "react/jsx-runtime";
9
10
  //#region src/atheletebridge/pages/sessions/SessionFormPage.tsx
10
- var p = Object.keys(n);
11
- function m() {
12
- let { navigateTo: m, currentUser: h } = e(), g = i({
11
+ var h = Object.keys(n);
12
+ function g() {
13
+ let { navigateTo: g, currentUser: _ } = e(), v = i({
13
14
  status: "PUBLISHED",
14
15
  pagination: { limit: 100 }
15
- }), _ = r(), [v, y] = s(h?.id ?? ""), [b, x] = s(""), [S, C] = s(""), [w, T] = s(""), [E, D] = s("IN_PERSON"), [O, k] = s(""), [A, j] = s(""), [M, N] = s(""), [P, F] = s("60"), [I, L] = s(null), R = g.data?.items ?? [];
16
- return /* @__PURE__ */ d(t, {
16
+ }), y = r(), b = _?.id ?? "", x = _?.name?.trim() || [_?.firstName, _?.lastName].filter(Boolean).join(" ").trim() || _?.email || "You", [S, C] = c(""), [w, T] = c(""), [E, D] = c(""), [O, k] = c("IN_PERSON"), [A, j] = c(""), [M, N] = c(""), [P, F] = c(""), [I, L] = c("60"), [R, z] = c(null), B = v.data?.items ?? [];
17
+ return /* @__PURE__ */ p(t, {
17
18
  title: "Book session",
18
19
  description: "Schedule a coaching session between a coach and an athlete.",
19
- actions: /* @__PURE__ */ f("button", {
20
+ actions: /* @__PURE__ */ m("button", {
20
21
  type: "button",
21
- onClick: () => m("/sessions"),
22
+ onClick: () => g("/sessions"),
22
23
  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",
23
- children: [/* @__PURE__ */ d(c, { className: "h-4 w-4" }), " Cancel"]
24
+ children: [/* @__PURE__ */ p(l, { className: "h-4 w-4" }), " Cancel"]
24
25
  }),
25
- children: /* @__PURE__ */ f("form", {
26
+ children: /* @__PURE__ */ m("form", {
26
27
  onSubmit: async (e) => {
27
- if (e.preventDefault(), L(null), !v.trim()) {
28
- L("Coach ID is required.");
28
+ if (e.preventDefault(), z(null), !b.trim()) {
29
+ z("Unable to identify you as the coach — please refresh and try again.");
29
30
  return;
30
31
  }
31
- if (!b.trim()) {
32
- L("Athlete ID is required.");
32
+ if (!S.trim()) {
33
+ z("Athlete ID is required.");
33
34
  return;
34
35
  }
35
- if (!w.trim()) {
36
- L("A session title is required.");
36
+ if (!E.trim()) {
37
+ z("A session title is required.");
37
38
  return;
38
39
  }
39
- if (!M) {
40
- L("A scheduled date and time is required.");
40
+ if (!P) {
41
+ z("A scheduled date and time is required.");
41
42
  return;
42
43
  }
43
- let t = Number.parseInt(P, 10);
44
+ let t = Number.parseInt(I, 10);
44
45
  if (Number.isNaN(t) || t <= 0) {
45
- L("Duration must be a positive number of minutes.");
46
+ z("Duration must be a positive number of minutes.");
46
47
  return;
47
48
  }
48
49
  try {
49
- m(`/sessions/${(await _.mutateAsync({
50
- coachId: v.trim(),
51
- athleteId: b.trim(),
52
- programId: S || void 0,
53
- title: w.trim(),
54
- type: E,
55
- location: E === "ONLINE" ? void 0 : O.trim() || void 0,
56
- meetingUrl: E === "IN_PERSON" ? void 0 : A.trim() || void 0,
57
- scheduledAt: new Date(M).toISOString(),
50
+ g(`/sessions/${(await y.mutateAsync({
51
+ coachId: b.trim(),
52
+ athleteId: S.trim(),
53
+ programId: w || void 0,
54
+ title: E.trim(),
55
+ type: O,
56
+ location: O === "ONLINE" ? void 0 : A.trim() || void 0,
57
+ meetingUrl: O === "IN_PERSON" ? void 0 : M.trim() || void 0,
58
+ scheduledAt: new Date(P).toISOString(),
58
59
  durationMin: t
59
60
  })).id}`);
60
61
  } catch (e) {
61
- L(e instanceof Error ? e.message : "Failed to book session.");
62
+ z(e instanceof Error ? e.message : "Failed to book session.");
62
63
  }
63
64
  },
64
65
  className: "max-w-2xl space-y-5",
65
66
  children: [
66
- /* @__PURE__ */ f("div", {
67
- className: "grid grid-cols-1 gap-5 sm:grid-cols-2",
68
- children: [/* @__PURE__ */ d(a, {
69
- label: "Coach ID",
70
- htmlFor: "coachId",
71
- required: !0,
72
- hint: "Defaults to you.",
73
- children: /* @__PURE__ */ d("input", {
74
- id: "coachId",
75
- className: o,
76
- value: v,
77
- onChange: (e) => y(e.target.value),
78
- placeholder: "coach-uuid"
79
- })
80
- }), /* @__PURE__ */ d(a, {
81
- label: "Athlete ID",
82
- htmlFor: "athleteId",
83
- required: !0,
84
- children: /* @__PURE__ */ d("input", {
85
- id: "athleteId",
86
- className: o,
87
- value: b,
88
- onChange: (e) => x(e.target.value),
89
- placeholder: "athlete-uuid"
90
- })
91
- })]
67
+ /* @__PURE__ */ p(a, {
68
+ label: "Coach",
69
+ hint: "You're booking this session as the coach.",
70
+ children: /* @__PURE__ */ m("div", {
71
+ className: "flex items-center gap-2 rounded-lg border border-border-seam bg-bg-sunken px-3 py-2 text-sm text-text-primary",
72
+ children: [/* @__PURE__ */ p("span", {
73
+ className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-bg-surface text-text-secondary",
74
+ children: /* @__PURE__ */ p(f, { className: "h-4 w-4" })
75
+ }), /* @__PURE__ */ p("span", {
76
+ className: "min-w-0 truncate font-medium",
77
+ children: x
78
+ })]
79
+ })
92
80
  }),
93
- /* @__PURE__ */ d(a, {
81
+ /* @__PURE__ */ p(s, {
82
+ athleteId: S,
83
+ onSelect: ({ athleteId: e }) => C(e),
84
+ required: !0,
85
+ idPrefix: "session-athlete"
86
+ }),
87
+ /* @__PURE__ */ p(a, {
94
88
  label: "Title",
95
89
  htmlFor: "title",
96
90
  required: !0,
97
- children: /* @__PURE__ */ d("input", {
91
+ children: /* @__PURE__ */ p("input", {
98
92
  id: "title",
99
93
  className: o,
100
- value: w,
101
- onChange: (e) => T(e.target.value),
94
+ value: E,
95
+ onChange: (e) => D(e.target.value),
102
96
  placeholder: "e.g. Sprint technique review"
103
97
  })
104
98
  }),
105
- /* @__PURE__ */ f("div", {
99
+ /* @__PURE__ */ m("div", {
106
100
  className: "grid grid-cols-1 gap-5 sm:grid-cols-2",
107
- children: [/* @__PURE__ */ d(a, {
101
+ children: [/* @__PURE__ */ p(a, {
108
102
  label: "Type",
109
103
  htmlFor: "type",
110
- children: /* @__PURE__ */ d("select", {
104
+ children: /* @__PURE__ */ p("select", {
111
105
  id: "type",
112
106
  className: o,
113
- value: E,
114
- onChange: (e) => D(e.target.value),
115
- children: p.map((e) => /* @__PURE__ */ d("option", {
107
+ value: O,
108
+ onChange: (e) => k(e.target.value),
109
+ children: h.map((e) => /* @__PURE__ */ p("option", {
116
110
  value: e,
117
111
  children: n[e]
118
112
  }, e))
119
113
  })
120
- }), /* @__PURE__ */ d(a, {
114
+ }), /* @__PURE__ */ p(a, {
121
115
  label: "Program",
122
116
  htmlFor: "program",
123
117
  hint: "Optional — tie to a published plan.",
124
- children: /* @__PURE__ */ f("select", {
118
+ children: /* @__PURE__ */ m("select", {
125
119
  id: "program",
126
120
  className: o,
127
- value: S,
128
- onChange: (e) => C(e.target.value),
129
- children: [/* @__PURE__ */ d("option", {
121
+ value: w,
122
+ onChange: (e) => T(e.target.value),
123
+ children: [/* @__PURE__ */ p("option", {
130
124
  value: "",
131
125
  children: "No program"
132
- }), R.map((e) => /* @__PURE__ */ d("option", {
126
+ }), B.map((e) => /* @__PURE__ */ p("option", {
133
127
  value: e.id,
134
128
  children: e.name
135
129
  }, e.id))]
136
130
  })
137
131
  })]
138
132
  }),
139
- E !== "ONLINE" && /* @__PURE__ */ d(a, {
133
+ O !== "ONLINE" && /* @__PURE__ */ p(a, {
140
134
  label: "Location",
141
135
  htmlFor: "location",
142
- children: /* @__PURE__ */ d("input", {
136
+ children: /* @__PURE__ */ p("input", {
143
137
  id: "location",
144
138
  className: o,
145
- value: O,
146
- onChange: (e) => k(e.target.value),
139
+ value: A,
140
+ onChange: (e) => j(e.target.value),
147
141
  placeholder: "e.g. Main gym, Court 2"
148
142
  })
149
143
  }),
150
- E !== "IN_PERSON" && /* @__PURE__ */ d(a, {
144
+ O !== "IN_PERSON" && /* @__PURE__ */ p(a, {
151
145
  label: "Meeting URL",
152
146
  htmlFor: "meetingUrl",
153
- children: /* @__PURE__ */ d("input", {
147
+ children: /* @__PURE__ */ p("input", {
154
148
  id: "meetingUrl",
155
149
  className: o,
156
- value: A,
157
- onChange: (e) => j(e.target.value),
150
+ value: M,
151
+ onChange: (e) => N(e.target.value),
158
152
  placeholder: "https://meet.example.com/…"
159
153
  })
160
154
  }),
161
- /* @__PURE__ */ f("div", {
155
+ /* @__PURE__ */ m("div", {
162
156
  className: "grid grid-cols-1 gap-5 sm:grid-cols-2",
163
- children: [/* @__PURE__ */ d(a, {
157
+ children: [/* @__PURE__ */ p(a, {
164
158
  label: "Scheduled at",
165
159
  htmlFor: "scheduledAt",
166
160
  required: !0,
167
- children: /* @__PURE__ */ d("input", {
161
+ children: /* @__PURE__ */ p("input", {
168
162
  id: "scheduledAt",
169
163
  type: "datetime-local",
170
164
  className: o,
171
- value: M,
172
- onChange: (e) => N(e.target.value)
165
+ value: P,
166
+ onChange: (e) => F(e.target.value)
173
167
  })
174
- }), /* @__PURE__ */ d(a, {
168
+ }), /* @__PURE__ */ p(a, {
175
169
  label: "Duration (minutes)",
176
170
  htmlFor: "duration",
177
- children: /* @__PURE__ */ d("input", {
171
+ children: /* @__PURE__ */ p("input", {
178
172
  id: "duration",
179
173
  inputMode: "numeric",
180
174
  className: o,
181
- value: P,
182
- onChange: (e) => F(e.target.value)
175
+ value: I,
176
+ onChange: (e) => L(e.target.value)
183
177
  })
184
178
  })]
185
179
  }),
186
- I && /* @__PURE__ */ d("p", {
180
+ R && /* @__PURE__ */ p("p", {
187
181
  className: "text-sm text-status-error-text",
188
- children: I
182
+ children: R
189
183
  }),
190
- /* @__PURE__ */ d("div", {
184
+ /* @__PURE__ */ p("div", {
191
185
  className: "pt-2",
192
- children: /* @__PURE__ */ f("button", {
186
+ children: /* @__PURE__ */ m("button", {
193
187
  type: "submit",
194
- disabled: _.isPending,
188
+ disabled: y.isPending,
195
189
  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 disabled:opacity-60",
196
- children: [_.isPending ? /* @__PURE__ */ d(l, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ d(u, { className: "h-4 w-4" }), "Book session"]
190
+ children: [y.isPending ? /* @__PURE__ */ p(u, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ p(d, { className: "h-4 w-4" }), "Book session"]
197
191
  })
198
192
  })
199
193
  ]
@@ -201,6 +195,6 @@ function m() {
201
195
  });
202
196
  }
203
197
  //#endregion
204
- export { m as SessionFormPage };
198
+ export { g as SessionFormPage };
205
199
 
206
200
  //# sourceMappingURL=SessionFormPage.js.map
@@ -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\nimport { useState } from 'react';\nimport { ArrowLeft, Loader2, Save } from 'lucide-react';\nimport { PageLayout } from '../../components/PageLayout';\nimport { FormField, fieldInputClass } from '../../components/form';\nimport { SESSION_TYPE_LABELS } from '../../components/domain';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport { useBookSession, usePrograms } from '../../hooks/useAthleteBridgeApi';\nimport type { AthSessionType } from '../../types';\n\nconst TYPES = Object.keys(SESSION_TYPE_LABELS) 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 const [coachId, setCoachId] = useState(currentUser?.id ?? '');\n const [athleteId, setAthleteId] = 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\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n\n if (!coachId.trim()) {\n setError('Coach ID is required.');\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=\"max-w-2xl space-y-5\">\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <FormField label=\"Coach ID\" htmlFor=\"coachId\" required hint=\"Defaults to you.\">\n <input\n id=\"coachId\"\n className={fieldInputClass}\n value={coachId}\n onChange={(e) => setCoachId(e.target.value)}\n placeholder=\"coach-uuid\"\n />\n </FormField>\n <FormField label=\"Athlete ID\" htmlFor=\"athleteId\" required>\n <input\n id=\"athleteId\"\n className={fieldInputClass}\n value={athleteId}\n onChange={(e) => setAthleteId(e.target.value)}\n placeholder=\"athlete-uuid\"\n />\n </FormField>\n </div>\n\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\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <FormField label=\"Type\" htmlFor=\"type\">\n <select\n id=\"type\"\n className={fieldInputClass}\n value={type}\n onChange={(e) => setType(e.target.value as AthSessionType)}\n >\n {TYPES.map((tp) => (\n <option key={tp} value={tp}>\n {SESSION_TYPE_LABELS[tp]}\n </option>\n ))}\n </select>\n </FormField>\n\n <FormField label=\"Program\" htmlFor=\"program\" hint=\"Optional — tie to a published plan.\">\n <select\n id=\"program\"\n className={fieldInputClass}\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 </FormField>\n </div>\n\n {type !== 'ONLINE' && (\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 )}\n\n {type !== 'IN_PERSON' && (\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 )}\n\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <FormField label=\"Scheduled at\" htmlFor=\"scheduledAt\" required>\n <input\n id=\"scheduledAt\"\n type=\"datetime-local\"\n className={fieldInputClass}\n value={scheduledAt}\n onChange={(e) => setScheduledAt(e.target.value)}\n />\n </FormField>\n <FormField label=\"Duration (minutes)\" htmlFor=\"duration\">\n <input\n id=\"duration\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={durationMin}\n onChange={(e) => setDurationMin(e.target.value)}\n />\n </FormField>\n </div>\n\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n\n <div className=\"pt-2\">\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-4 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 </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\nimport { useState } from 'react';\nimport { ArrowLeft, Loader2, Save, User as UserIcon } from 'lucide-react';\nimport { PageLayout } from '../../components/PageLayout';\nimport { FormField, fieldInputClass } from '../../components/form';\nimport { AthleteSelect } from '../../components/AthleteSelect';\nimport { SESSION_TYPE_LABELS } from '../../components/domain';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport { useBookSession, usePrograms } from '../../hooks/useAthleteBridgeApi';\nimport type { AthSessionType } from '../../types';\n\nconst TYPES = Object.keys(SESSION_TYPE_LABELS) 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 [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\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=\"max-w-2xl space-y-5\">\n <FormField label=\"Coach\" hint=\"You're booking this session as the coach.\">\n <div className=\"flex items-center gap-2 rounded-lg border border-border-seam bg-bg-sunken px-3 py-2 text-sm text-text-primary\">\n <span className=\"flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-bg-surface text-text-secondary\">\n <UserIcon className=\"h-4 w-4\" />\n </span>\n <span className=\"min-w-0 truncate font-medium\">{coachName}</span>\n </div>\n </FormField>\n\n <AthleteSelect\n athleteId={athleteId}\n onSelect={({ athleteId: id }) => setAthleteId(id)}\n required\n idPrefix=\"session-athlete\"\n />\n\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\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <FormField label=\"Type\" htmlFor=\"type\">\n <select\n id=\"type\"\n className={fieldInputClass}\n value={type}\n onChange={(e) => setType(e.target.value as AthSessionType)}\n >\n {TYPES.map((tp) => (\n <option key={tp} value={tp}>\n {SESSION_TYPE_LABELS[tp]}\n </option>\n ))}\n </select>\n </FormField>\n\n <FormField label=\"Program\" htmlFor=\"program\" hint=\"Optional — tie to a published plan.\">\n <select\n id=\"program\"\n className={fieldInputClass}\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 </FormField>\n </div>\n\n {type !== 'ONLINE' && (\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 )}\n\n {type !== 'IN_PERSON' && (\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 )}\n\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <FormField label=\"Scheduled at\" htmlFor=\"scheduledAt\" required>\n <input\n id=\"scheduledAt\"\n type=\"datetime-local\"\n className={fieldInputClass}\n value={scheduledAt}\n onChange={(e) => setScheduledAt(e.target.value)}\n />\n </FormField>\n <FormField label=\"Duration (minutes)\" htmlFor=\"duration\">\n <input\n id=\"duration\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={durationMin}\n onChange={(e) => setDurationMin(e.target.value)}\n />\n </FormField>\n </div>\n\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n\n <div className=\"pt-2\">\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-4 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 </form>\n </PageLayout>\n );\n}\n"],"mappings":"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"}