@burdenoff/microfe-atheletebridge 2026.626.1 → 2026.629.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 +49 -129
- package/dist/atheletebridge/AthleteBridgeRoot.js.map +1 -1
- package/dist/atheletebridge/AthleteBridgeRoutes.js +65 -184
- package/dist/atheletebridge/AthleteBridgeRoutes.js.map +1 -1
- package/dist/atheletebridge/components/CursorPager.js +34 -0
- package/dist/atheletebridge/components/CursorPager.js.map +1 -0
- package/dist/atheletebridge/components/FilterSelect.js +22 -0
- package/dist/atheletebridge/components/FilterSelect.js.map +1 -0
- package/dist/atheletebridge/components/FixtureCard.js +76 -0
- package/dist/atheletebridge/components/FixtureCard.js.map +1 -0
- package/dist/atheletebridge/components/LoadingState.js +36 -0
- package/dist/atheletebridge/components/LoadingState.js.map +1 -0
- package/dist/atheletebridge/components/ProgramCard.js +69 -0
- package/dist/atheletebridge/components/ProgramCard.js.map +1 -0
- package/dist/atheletebridge/components/SessionCard.js +76 -0
- package/dist/atheletebridge/components/SessionCard.js.map +1 -0
- package/dist/atheletebridge/components/StatusBadge.js +44 -35
- package/dist/atheletebridge/components/StatusBadge.js.map +1 -1
- package/dist/atheletebridge/components/TeamCard.js +81 -0
- package/dist/atheletebridge/components/TeamCard.js.map +1 -0
- package/dist/atheletebridge/components/domain.js +47 -0
- package/dist/atheletebridge/components/domain.js.map +1 -0
- package/dist/atheletebridge/components/form.js +29 -0
- package/dist/atheletebridge/components/form.js.map +1 -0
- package/dist/atheletebridge/context/AthleteBridgeContext.js +6 -6
- package/dist/atheletebridge/context/AthleteBridgeContext.js.map +1 -1
- package/dist/atheletebridge/hooks/useAthleteBridgeApi.js +342 -165
- package/dist/atheletebridge/hooks/useAthleteBridgeApi.js.map +1 -1
- package/dist/atheletebridge/index.js +1 -1
- package/dist/atheletebridge/pages/DashboardPage.js +169 -0
- package/dist/atheletebridge/pages/DashboardPage.js.map +1 -0
- package/dist/atheletebridge/pages/StandingsPage.js +166 -0
- package/dist/atheletebridge/pages/StandingsPage.js.map +1 -0
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js +201 -0
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js.map +1 -0
- package/dist/atheletebridge/pages/fixtures/FixtureFormPage.js +205 -0
- package/dist/atheletebridge/pages/fixtures/FixtureFormPage.js.map +1 -0
- package/dist/atheletebridge/pages/fixtures/FixturesListPage.js +92 -0
- package/dist/atheletebridge/pages/fixtures/FixturesListPage.js.map +1 -0
- package/dist/atheletebridge/pages/programs/ProgramDetailPage.js +374 -0
- package/dist/atheletebridge/pages/programs/ProgramDetailPage.js.map +1 -0
- package/dist/atheletebridge/pages/programs/ProgramFormPage.js +207 -0
- package/dist/atheletebridge/pages/programs/ProgramFormPage.js.map +1 -0
- package/dist/atheletebridge/pages/programs/ProgramsListPage.js +113 -0
- package/dist/atheletebridge/pages/programs/ProgramsListPage.js.map +1 -0
- package/dist/atheletebridge/pages/sessions/SessionDetailPage.js +224 -0
- package/dist/atheletebridge/pages/sessions/SessionDetailPage.js.map +1 -0
- package/dist/atheletebridge/pages/sessions/SessionFormPage.js +206 -0
- package/dist/atheletebridge/pages/sessions/SessionFormPage.js.map +1 -0
- package/dist/atheletebridge/pages/sessions/SessionsListPage.js +92 -0
- package/dist/atheletebridge/pages/sessions/SessionsListPage.js.map +1 -0
- package/dist/atheletebridge/pages/teams/TeamDetailPage.js +350 -0
- package/dist/atheletebridge/pages/teams/TeamDetailPage.js.map +1 -0
- package/dist/atheletebridge/pages/teams/TeamFormPage.js +222 -0
- package/dist/atheletebridge/pages/teams/TeamFormPage.js.map +1 -0
- package/dist/atheletebridge/pages/teams/TeamsListPage.js +101 -0
- package/dist/atheletebridge/pages/teams/TeamsListPage.js.map +1 -0
- package/dist/atheletebridge/store/atheletebridgeStore.js +2 -13
- package/dist/atheletebridge/store/atheletebridgeStore.js.map +1 -1
- package/dist/atheletebridge/utils/datetime.js +10 -0
- package/dist/atheletebridge/utils/datetime.js.map +1 -0
- package/dist/atheletebridge/utils/formatters.js +1 -4
- package/dist/atheletebridge/utils/formatters.js.map +1 -1
- package/package.json +1 -1
- package/dist/atheletebridge/components/InvestmentCard.js +0 -96
- package/dist/atheletebridge/components/InvestmentCard.js.map +0 -1
- package/dist/atheletebridge/components/RatingDisplay.js +0 -35
- package/dist/atheletebridge/components/RatingDisplay.js.map +0 -1
- package/dist/atheletebridge/mocks/data.js +0 -1517
- package/dist/atheletebridge/mocks/data.js.map +0 -1
- package/dist/atheletebridge/mocks/users.js +0 -34
- package/dist/atheletebridge/mocks/users.js.map +0 -1
- package/dist/atheletebridge/pages/HomePage.js +0 -936
- package/dist/atheletebridge/pages/HomePage.js.map +0 -1
- package/dist/atheletebridge/pages/admin/AdminAnalyticsPage.js +0 -653
- package/dist/atheletebridge/pages/admin/AdminAnalyticsPage.js.map +0 -1
- package/dist/atheletebridge/pages/admin/AdminDashboard.js +0 -671
- package/dist/atheletebridge/pages/admin/AdminDashboard.js.map +0 -1
- package/dist/atheletebridge/pages/admin/AdminFinancialPage.js +0 -429
- package/dist/atheletebridge/pages/admin/AdminFinancialPage.js.map +0 -1
- package/dist/atheletebridge/pages/admin/AdminOrdersPage.js +0 -667
- package/dist/atheletebridge/pages/admin/AdminOrdersPage.js.map +0 -1
- package/dist/atheletebridge/pages/admin/AdminProfilePage.js +0 -261
- package/dist/atheletebridge/pages/admin/AdminProfilePage.js.map +0 -1
- package/dist/atheletebridge/pages/admin/AdminSettingsPage.js +0 -653
- package/dist/atheletebridge/pages/admin/AdminSettingsPage.js.map +0 -1
- package/dist/atheletebridge/pages/admin/CartManagementPage.js +0 -876
- package/dist/atheletebridge/pages/admin/CartManagementPage.js.map +0 -1
- package/dist/atheletebridge/pages/admin/UserManagementPage.js +0 -900
- package/dist/atheletebridge/pages/admin/UserManagementPage.js.map +0 -1
- package/dist/atheletebridge/pages/cart-owner/AnalyticsDashboard.js +0 -839
- package/dist/atheletebridge/pages/cart-owner/AnalyticsDashboard.js.map +0 -1
- package/dist/atheletebridge/pages/cart-owner/BusinessSettingsPage.js +0 -704
- package/dist/atheletebridge/pages/cart-owner/BusinessSettingsPage.js.map +0 -1
- package/dist/atheletebridge/pages/cart-owner/CartOwnerDashboard.js +0 -792
- package/dist/atheletebridge/pages/cart-owner/CartOwnerDashboard.js.map +0 -1
- package/dist/atheletebridge/pages/cart-owner/CartOwnerProfilePage.js +0 -891
- package/dist/atheletebridge/pages/cart-owner/CartOwnerProfilePage.js.map +0 -1
- package/dist/atheletebridge/pages/cart-owner/CartServiceConfigPage.js +0 -633
- package/dist/atheletebridge/pages/cart-owner/CartServiceConfigPage.js.map +0 -1
- package/dist/atheletebridge/pages/cart-owner/InventoryManagementPage.js +0 -913
- package/dist/atheletebridge/pages/cart-owner/InventoryManagementPage.js.map +0 -1
- package/dist/atheletebridge/pages/cart-owner/OrderManagementPage.js +0 -881
- package/dist/atheletebridge/pages/cart-owner/OrderManagementPage.js.map +0 -1
- package/dist/atheletebridge/pages/cart-owner/StaffManagementPage.js +0 -841
- package/dist/atheletebridge/pages/cart-owner/StaffManagementPage.js.map +0 -1
- package/dist/atheletebridge/pages/common/HelpSupportPage.js +0 -1319
- package/dist/atheletebridge/pages/common/HelpSupportPage.js.map +0 -1
- package/dist/atheletebridge/pages/common/NotificationCenterPage.js +0 -674
- package/dist/atheletebridge/pages/common/NotificationCenterPage.js.map +0 -1
- package/dist/atheletebridge/pages/common/SettingsPage.js +0 -972
- package/dist/atheletebridge/pages/common/SettingsPage.js.map +0 -1
- package/dist/atheletebridge/pages/customer/BookingDetailsPage.js +0 -602
- package/dist/atheletebridge/pages/customer/BookingDetailsPage.js.map +0 -1
- package/dist/atheletebridge/pages/customer/CartDetailPage.js +0 -656
- package/dist/atheletebridge/pages/customer/CartDetailPage.js.map +0 -1
- package/dist/atheletebridge/pages/customer/CartDiscoveryPage.js +0 -558
- package/dist/atheletebridge/pages/customer/CartDiscoveryPage.js.map +0 -1
- package/dist/atheletebridge/pages/customer/CheckoutPage.js +0 -536
- package/dist/atheletebridge/pages/customer/CheckoutPage.js.map +0 -1
- package/dist/atheletebridge/pages/customer/CustomerDashboard.js +0 -539
- package/dist/atheletebridge/pages/customer/CustomerDashboard.js.map +0 -1
- package/dist/atheletebridge/pages/customer/CustomerProfilePage.js +0 -657
- package/dist/atheletebridge/pages/customer/CustomerProfilePage.js.map +0 -1
- package/dist/atheletebridge/pages/customer/DirectServiceBookingPage.js +0 -635
- package/dist/atheletebridge/pages/customer/DirectServiceBookingPage.js.map +0 -1
- package/dist/atheletebridge/pages/customer/FavoritesPage.js +0 -486
- package/dist/atheletebridge/pages/customer/FavoritesPage.js.map +0 -1
- package/dist/atheletebridge/pages/customer/OrderHistoryPage.js +0 -447
- package/dist/atheletebridge/pages/customer/OrderHistoryPage.js.map +0 -1
- package/dist/atheletebridge/pages/customer/OrderTrackingPage.js +0 -393
- package/dist/atheletebridge/pages/customer/OrderTrackingPage.js.map +0 -1
- package/dist/atheletebridge/pages/customer/PaymentMethodsPage.js +0 -494
- package/dist/atheletebridge/pages/customer/PaymentMethodsPage.js.map +0 -1
- package/dist/atheletebridge/pages/customer/ReviewsPage.js +0 -615
- package/dist/atheletebridge/pages/customer/ReviewsPage.js.map +0 -1
- package/dist/atheletebridge/pages/customer/SavedAddressesPage.js +0 -451
- package/dist/atheletebridge/pages/customer/SavedAddressesPage.js.map +0 -1
- package/dist/atheletebridge/pages/investor/InvestmentDetailsPage.js +0 -881
- package/dist/atheletebridge/pages/investor/InvestmentDetailsPage.js.map +0 -1
- package/dist/atheletebridge/pages/investor/InvestmentOpportunitiesPage.js +0 -945
- package/dist/atheletebridge/pages/investor/InvestmentOpportunitiesPage.js.map +0 -1
- package/dist/atheletebridge/pages/investor/InvestorAnalyticsPage.js +0 -686
- package/dist/atheletebridge/pages/investor/InvestorAnalyticsPage.js.map +0 -1
- package/dist/atheletebridge/pages/investor/InvestorDashboard.js +0 -453
- package/dist/atheletebridge/pages/investor/InvestorDashboard.js.map +0 -1
- package/dist/atheletebridge/pages/investor/InvestorInvestmentsPage.js +0 -405
- package/dist/atheletebridge/pages/investor/InvestorInvestmentsPage.js.map +0 -1
- package/dist/atheletebridge/pages/investor/InvestorPortfolioPage.js +0 -577
- package/dist/atheletebridge/pages/investor/InvestorPortfolioPage.js.map +0 -1
- package/dist/atheletebridge/pages/investor/InvestorProfilePage.js +0 -1069
- package/dist/atheletebridge/pages/investor/InvestorProfilePage.js.map +0 -1
- package/dist/atheletebridge/pages/worker/TaskDetailsPage.js +0 -530
- package/dist/atheletebridge/pages/worker/TaskDetailsPage.js.map +0 -1
- package/dist/atheletebridge/pages/worker/WorkerDashboard.js +0 -669
- package/dist/atheletebridge/pages/worker/WorkerDashboard.js.map +0 -1
- package/dist/atheletebridge/pages/worker/WorkerOrdersPage.js +0 -681
- package/dist/atheletebridge/pages/worker/WorkerOrdersPage.js.map +0 -1
- package/dist/atheletebridge/pages/worker/WorkerProfilePage.js +0 -691
- package/dist/atheletebridge/pages/worker/WorkerProfilePage.js.map +0 -1
- package/dist/atheletebridge/pages/worker/WorkerSchedulePage.js +0 -588
- package/dist/atheletebridge/pages/worker/WorkerSchedulePage.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProgramFormPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/programs/ProgramFormPage.tsx"],"sourcesContent":["/**\n * Program Create / Edit Form\n * @module atheletebridge/pages/programs\n */\n\nimport { useEffect, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft, Loader2, Save } from 'lucide-react';\nimport { PageLayout } from '../../components/PageLayout';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FormField, fieldInputClass } from '../../components/form';\nimport { SPORT_LABELS, PROGRAM_LEVEL_LABELS } from '../../components/domain';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport { useProgram, useCreateProgram, useUpdateProgram } from '../../hooks/useAthleteBridgeApi';\nimport type { AthProgramLevel, AthSport } from '../../types';\n\nconst SPORTS = Object.keys(SPORT_LABELS) as AthSport[];\nconst LEVELS = Object.keys(PROGRAM_LEVEL_LABELS) as AthProgramLevel[];\n\ninterface ProgramFormState {\n name: string;\n sport: AthSport;\n level: AthProgramLevel;\n summary: string;\n weeks: string;\n isTemplate: boolean;\n tagsText: string;\n}\n\nconst EMPTY_FORM: ProgramFormState = {\n name: '',\n sport: 'OTHER',\n level: 'BEGINNER',\n summary: '',\n weeks: '4',\n isTemplate: false,\n tagsText: '',\n};\n\nexport function ProgramFormPage() {\n const { id } = useParams<{ id: string }>();\n const isEdit = Boolean(id);\n const { navigateTo } = useAthleteBridge();\n\n const existing = useProgram(id);\n const createProgram = useCreateProgram();\n const updateProgram = useUpdateProgram();\n\n const [form, setForm] = useState<ProgramFormState>(EMPTY_FORM);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n const program = existing.data;\n if (isEdit && program) {\n setForm({\n name: program.name,\n sport: program.sport,\n level: program.level,\n summary: program.summary ?? '',\n weeks: String(program.weeks),\n isTemplate: program.isTemplate,\n tagsText: program.tags.join(', '),\n });\n }\n }, [isEdit, existing.data]);\n\n const set = <K extends keyof ProgramFormState>(key: K, value: ProgramFormState[K]) =>\n setForm((prev) => ({ ...prev, [key]: value }));\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n\n if (!form.name.trim()) {\n setError('Program name is required.');\n return;\n }\n const weeks = Number.parseInt(form.weeks, 10);\n if (Number.isNaN(weeks) || weeks < 1) {\n setError('Weeks must be a positive number.');\n return;\n }\n\n const tags = form.tagsText\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean);\n\n try {\n if (isEdit && id) {\n await updateProgram.mutateAsync({\n id,\n input: {\n name: form.name.trim(),\n sport: form.sport,\n level: form.level,\n summary: form.summary.trim() || undefined,\n weeks,\n isTemplate: form.isTemplate,\n tags,\n },\n });\n navigateTo(`/programs/${id}`);\n } else {\n const created = await createProgram.mutateAsync({\n name: form.name.trim(),\n sport: form.sport,\n level: form.level,\n summary: form.summary.trim() || undefined,\n weeks,\n isTemplate: form.isTemplate,\n tags,\n });\n navigateTo(`/programs/${created.id}`);\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to save program.');\n }\n };\n\n const saving = createProgram.isPending || updateProgram.isPending;\n\n if (isEdit && existing.isLoading) {\n return (\n <PageLayout title=\"Edit program\">\n <LoadingState label=\"Loading program…\" />\n </PageLayout>\n );\n }\n\n if (isEdit && (existing.isError || (!existing.isLoading && !existing.data))) {\n return (\n <PageLayout title=\"Edit program\">\n <ErrorState\n title=\"Program not found\"\n message=\"This program does not exist or you do not have access to it.\"\n onRetry={() => navigateTo('/programs')}\n />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={isEdit ? 'Edit program' : 'New program'}\n description={\n isEdit ? 'Update the training plan details.' : 'Create a new training plan in draft.'\n }\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo(isEdit && id ? `/programs/${id}` : '/programs')}\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=\"Program name\" htmlFor=\"name\" required>\n <input\n id=\"name\"\n className={fieldInputClass}\n value={form.name}\n onChange={(e) => set('name', e.target.value)}\n placeholder=\"e.g. Pre-season strength block\"\n />\n </FormField>\n\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-3\">\n <FormField label=\"Sport\" htmlFor=\"sport\">\n <select\n id=\"sport\"\n className={fieldInputClass}\n value={form.sport}\n onChange={(e) => set('sport', e.target.value as AthSport)}\n >\n {SPORTS.map((s) => (\n <option key={s} value={s}>\n {SPORT_LABELS[s]}\n </option>\n ))}\n </select>\n </FormField>\n\n <FormField label=\"Level\" htmlFor=\"level\">\n <select\n id=\"level\"\n className={fieldInputClass}\n value={form.level}\n onChange={(e) => set('level', e.target.value as AthProgramLevel)}\n >\n {LEVELS.map((l) => (\n <option key={l} value={l}>\n {PROGRAM_LEVEL_LABELS[l]}\n </option>\n ))}\n </select>\n </FormField>\n\n <FormField label=\"Weeks\" htmlFor=\"weeks\">\n <input\n id=\"weeks\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={form.weeks}\n onChange={(e) => set('weeks', e.target.value)}\n placeholder=\"4\"\n />\n </FormField>\n </div>\n\n <FormField label=\"Summary\" htmlFor=\"summary\">\n <textarea\n id=\"summary\"\n className={fieldInputClass}\n rows={3}\n value={form.summary}\n onChange={(e) => set('summary', e.target.value)}\n placeholder=\"What this program is for and who it's aimed at.\"\n />\n </FormField>\n\n <FormField label=\"Tags\" htmlFor=\"tags\" hint=\"Comma-separated, e.g. strength, pre-season.\">\n <input\n id=\"tags\"\n className={fieldInputClass}\n value={form.tagsText}\n onChange={(e) => set('tagsText', e.target.value)}\n placeholder=\"strength, conditioning\"\n />\n </FormField>\n\n <label className=\"flex items-center gap-2.5 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={form.isTemplate}\n onChange={(e) => set('isTemplate', e.target.checked)}\n className=\"h-4 w-4 rounded border-border-seam text-action-primary-bg focus:ring-[var(--color-focus-ring)]\"\n />\n Mark as reusable template\n </label>\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={saving}\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 {saving ? <Loader2 className=\"h-4 w-4 animate-spin\" /> : <Save className=\"h-4 w-4\" />}\n {isEdit ? 'Save changes' : 'Create program'}\n </button>\n </div>\n </form>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { useAthleteBridge as e } from "../../context/AthleteBridgeContext.js";
|
|
2
|
+
import { PageLayout as t } from "../../components/PageLayout.js";
|
|
3
|
+
import { PROGRAM_LEVEL_LABELS as n, SPORT_LABELS as r } from "../../components/domain.js";
|
|
4
|
+
import { EmptyState as i } from "../../components/EmptyState.js";
|
|
5
|
+
import { ErrorState as a, LoadingState as o } from "../../components/LoadingState.js";
|
|
6
|
+
import { usePrograms as s } from "../../hooks/useAthleteBridgeApi.js";
|
|
7
|
+
import { FilterSelect as c } from "../../components/FilterSelect.js";
|
|
8
|
+
import { CursorPager as l } from "../../components/CursorPager.js";
|
|
9
|
+
import { ProgramCard as u } from "../../components/ProgramCard.js";
|
|
10
|
+
import { useState as d } from "react";
|
|
11
|
+
import { Dumbbell as f, Plus as p } from "lucide-react";
|
|
12
|
+
import { Fragment as m, jsx as h, jsxs as g } from "react/jsx-runtime";
|
|
13
|
+
//#region src/atheletebridge/pages/programs/ProgramsListPage.tsx
|
|
14
|
+
var _ = 12, v = Object.keys(r).map((e) => ({
|
|
15
|
+
value: e,
|
|
16
|
+
label: r[e]
|
|
17
|
+
})), y = Object.keys(n).map((e) => ({
|
|
18
|
+
value: e,
|
|
19
|
+
label: n[e]
|
|
20
|
+
})), b = [
|
|
21
|
+
{
|
|
22
|
+
value: "DRAFT",
|
|
23
|
+
label: "Draft"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
value: "PUBLISHED",
|
|
27
|
+
label: "Published"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
value: "ARCHIVED",
|
|
31
|
+
label: "Archived"
|
|
32
|
+
}
|
|
33
|
+
];
|
|
34
|
+
function x() {
|
|
35
|
+
let { navigateTo: n } = e(), [r, x] = d(""), [S, C] = d(""), [w, T] = d(""), [E, D] = d(0), O = s({
|
|
36
|
+
sport: r || void 0,
|
|
37
|
+
level: S || void 0,
|
|
38
|
+
status: w || void 0,
|
|
39
|
+
pagination: {
|
|
40
|
+
limit: _,
|
|
41
|
+
offset: E * _
|
|
42
|
+
}
|
|
43
|
+
}), k = O.data?.items ?? [], A = O.data?.pageInfo.hasMore ?? !1, j = () => D(0);
|
|
44
|
+
return /* @__PURE__ */ g(t, {
|
|
45
|
+
title: "Programs",
|
|
46
|
+
description: "Coach-built training plans with a publish lifecycle and reusable templates.",
|
|
47
|
+
actions: /* @__PURE__ */ g("button", {
|
|
48
|
+
type: "button",
|
|
49
|
+
onClick: () => n("/programs/new"),
|
|
50
|
+
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",
|
|
51
|
+
children: [/* @__PURE__ */ h(p, { className: "h-4 w-4" }), " New program"]
|
|
52
|
+
}),
|
|
53
|
+
children: [/* @__PURE__ */ g("div", {
|
|
54
|
+
className: "mb-6 flex flex-wrap items-center gap-3",
|
|
55
|
+
children: [
|
|
56
|
+
/* @__PURE__ */ h(c, {
|
|
57
|
+
ariaLabel: "Filter by sport",
|
|
58
|
+
allLabel: "All sports",
|
|
59
|
+
value: r,
|
|
60
|
+
onChange: (e) => {
|
|
61
|
+
x(e), j();
|
|
62
|
+
},
|
|
63
|
+
options: v
|
|
64
|
+
}),
|
|
65
|
+
/* @__PURE__ */ h(c, {
|
|
66
|
+
ariaLabel: "Filter by level",
|
|
67
|
+
allLabel: "All levels",
|
|
68
|
+
value: S,
|
|
69
|
+
onChange: (e) => {
|
|
70
|
+
C(e), j();
|
|
71
|
+
},
|
|
72
|
+
options: y
|
|
73
|
+
}),
|
|
74
|
+
/* @__PURE__ */ h(c, {
|
|
75
|
+
ariaLabel: "Filter by status",
|
|
76
|
+
allLabel: "All statuses",
|
|
77
|
+
value: w,
|
|
78
|
+
onChange: (e) => {
|
|
79
|
+
T(e), j();
|
|
80
|
+
},
|
|
81
|
+
options: b
|
|
82
|
+
})
|
|
83
|
+
]
|
|
84
|
+
}), O.isLoading ? /* @__PURE__ */ h(o, { label: "Loading programs…" }) : O.isError ? /* @__PURE__ */ h(a, { onRetry: () => void O.refetch() }) : k.length === 0 ? /* @__PURE__ */ h(i, {
|
|
85
|
+
illustration: "empty-data",
|
|
86
|
+
icon: /* @__PURE__ */ h(f, { className: "h-10 w-10" }),
|
|
87
|
+
title: "No programs found",
|
|
88
|
+
description: r || S || w ? "Try clearing your filters to see all programs." : "Create your first training program to build drills and publish to athletes.",
|
|
89
|
+
action: /* @__PURE__ */ h("button", {
|
|
90
|
+
type: "button",
|
|
91
|
+
onClick: () => n("/programs/new"),
|
|
92
|
+
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",
|
|
93
|
+
children: "Create program"
|
|
94
|
+
})
|
|
95
|
+
}) : /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h("div", {
|
|
96
|
+
className: "grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3",
|
|
97
|
+
children: k.map((e) => /* @__PURE__ */ h(u, {
|
|
98
|
+
program: e,
|
|
99
|
+
onClick: () => n(`/programs/${e.id}`)
|
|
100
|
+
}, e.id))
|
|
101
|
+
}), /* @__PURE__ */ h(l, {
|
|
102
|
+
className: "mt-6",
|
|
103
|
+
page: E,
|
|
104
|
+
hasMore: A,
|
|
105
|
+
onPrev: () => D((e) => Math.max(0, e - 1)),
|
|
106
|
+
onNext: () => D((e) => e + 1)
|
|
107
|
+
})] })]
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
//#endregion
|
|
111
|
+
export { x as ProgramsListPage };
|
|
112
|
+
|
|
113
|
+
//# sourceMappingURL=ProgramsListPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProgramsListPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/programs/ProgramsListPage.tsx"],"sourcesContent":["/**\n * Programs List\n * @module atheletebridge/pages/programs\n */\n\nimport { useState } from 'react';\nimport { Dumbbell, Plus } from 'lucide-react';\nimport { PageLayout } from '../../components/PageLayout';\nimport { ProgramCard } from '../../components/ProgramCard';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FilterSelect } from '../../components/FilterSelect';\nimport { CursorPager } from '../../components/CursorPager';\nimport { SPORT_LABELS, PROGRAM_LEVEL_LABELS } from '../../components/domain';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport { usePrograms } from '../../hooks/useAthleteBridgeApi';\nimport type { AthProgramLevel, AthProgramStatus, AthSport } from '../../types';\n\nconst PAGE_SIZE = 12;\n\nconst SPORT_OPTS = (Object.keys(SPORT_LABELS) as AthSport[]).map((value) => ({\n value,\n label: SPORT_LABELS[value],\n}));\n\nconst LEVEL_OPTS = (Object.keys(PROGRAM_LEVEL_LABELS) as AthProgramLevel[]).map((value) => ({\n value,\n label: PROGRAM_LEVEL_LABELS[value],\n}));\n\nconst STATUS_OPTS: ReadonlyArray<{ value: AthProgramStatus; label: string }> = [\n { value: 'DRAFT', label: 'Draft' },\n { value: 'PUBLISHED', label: 'Published' },\n { value: 'ARCHIVED', label: 'Archived' },\n];\n\nexport function ProgramsListPage() {\n const { navigateTo } = useAthleteBridge();\n const [sport, setSport] = useState<AthSport | ''>('');\n const [level, setLevel] = useState<AthProgramLevel | ''>('');\n const [status, setStatus] = useState<AthProgramStatus | ''>('');\n const [page, setPage] = useState(0);\n\n const programs = usePrograms({\n sport: sport || undefined,\n level: level || undefined,\n status: status || undefined,\n pagination: { limit: PAGE_SIZE, offset: page * PAGE_SIZE },\n });\n\n const items = programs.data?.items ?? [];\n const hasMore = programs.data?.pageInfo.hasMore ?? false;\n\n const resetPage = () => setPage(0);\n\n return (\n <PageLayout\n title=\"Programs\"\n description=\"Coach-built training plans with a publish lifecycle and reusable templates.\"\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/programs/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\" /> New program\n </button>\n }\n >\n <div className=\"mb-6 flex flex-wrap items-center gap-3\">\n <FilterSelect\n ariaLabel=\"Filter by sport\"\n allLabel=\"All sports\"\n value={sport}\n onChange={(v) => {\n setSport(v);\n resetPage();\n }}\n options={SPORT_OPTS}\n />\n <FilterSelect\n ariaLabel=\"Filter by level\"\n allLabel=\"All levels\"\n value={level}\n onChange={(v) => {\n setLevel(v);\n resetPage();\n }}\n options={LEVEL_OPTS}\n />\n <FilterSelect\n ariaLabel=\"Filter by status\"\n allLabel=\"All statuses\"\n value={status}\n onChange={(v) => {\n setStatus(v);\n resetPage();\n }}\n options={STATUS_OPTS}\n />\n </div>\n\n {programs.isLoading ? (\n <LoadingState label=\"Loading programs…\" />\n ) : programs.isError ? (\n <ErrorState onRetry={() => void programs.refetch()} />\n ) : items.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Dumbbell className=\"h-10 w-10\" />}\n title=\"No programs found\"\n description={\n sport || level || status\n ? 'Try clearing your filters to see all programs.'\n : 'Create your first training program to build drills and publish to athletes.'\n }\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/programs/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 Create program\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((program) => (\n <ProgramCard\n key={program.id}\n program={program}\n onClick={() => navigateTo(`/programs/${program.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":";;;;;;;;;;;;;AAkBA,IAAM,IAAY,IAEZ,IAAc,OAAO,KAAK,EAAa,CAAgB,KAAK,OAAW;CAC3E;CACA,OAAO,EAAa;CACrB,EAAE,EAEG,IAAc,OAAO,KAAK,EAAqB,CAAuB,KAAK,OAAW;CAC1F;CACA,OAAO,EAAqB;CAC7B,EAAE,EAEG,IAAyE;CAC7E;EAAE,OAAO;EAAS,OAAO;EAAS;CAClC;EAAE,OAAO;EAAa,OAAO;EAAa;CAC1C;EAAE,OAAO;EAAY,OAAO;EAAY;CACzC;AAED,SAAgB,IAAmB;CACjC,IAAM,EAAE,kBAAe,GAAkB,EACnC,CAAC,GAAO,KAAY,EAAwB,GAAG,EAC/C,CAAC,GAAO,KAAY,EAA+B,GAAG,EACtD,CAAC,GAAQ,KAAa,EAAgC,GAAG,EACzD,CAAC,GAAM,KAAW,EAAS,EAAE,EAE7B,IAAW,EAAY;EAC3B,OAAO,KAAS,KAAA;EAChB,OAAO,KAAS,KAAA;EAChB,QAAQ,KAAU,KAAA;EAClB,YAAY;GAAE,OAAO;GAAW,QAAQ,IAAO;GAAW;EAC3D,CAAC,EAEI,IAAQ,EAAS,MAAM,SAAS,EAAE,EAClC,IAAU,EAAS,MAAM,SAAS,WAAW,IAE7C,UAAkB,EAAQ,EAAE;AAElC,QACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,SACE,kBAAC,UAAD;GACE,MAAK;GACL,eAAe,EAAW,gBAAgB;GAC1C,WAAU;aAHZ,CAKE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,eACrB;;YAVb,CAaE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,GAAD;KACE,WAAU;KACV,UAAS;KACT,OAAO;KACP,WAAW,MAAM;AAEf,MADA,EAAS,EAAE,EACX,GAAW;;KAEb,SAAS;KACT,CAAA;IACF,kBAAC,GAAD;KACE,WAAU;KACV,UAAS;KACT,OAAO;KACP,WAAW,MAAM;AAEf,MADA,EAAS,EAAE,EACX,GAAW;;KAEb,SAAS;KACT,CAAA;IACF,kBAAC,GAAD;KACE,WAAU;KACV,UAAS;KACT,OAAO;KACP,WAAW,MAAM;AAEf,MADA,EAAU,EAAE,EACZ,GAAW;;KAEb,SAAS;KACT,CAAA;IACE;MAEL,EAAS,YACR,kBAAC,GAAD,EAAc,OAAM,qBAAsB,CAAA,GACxC,EAAS,UACX,kBAAC,GAAD,EAAY,eAAe,KAAK,EAAS,SAAS,EAAI,CAAA,GACpD,EAAM,WAAW,IACnB,kBAAC,GAAD;GACE,cAAa;GACb,MAAM,kBAAC,GAAD,EAAU,WAAU,aAAc,CAAA;GACxC,OAAM;GACN,aACE,KAAS,KAAS,IACd,mDACA;GAEN,QACE,kBAAC,UAAD;IACE,MAAK;IACL,eAAe,EAAW,gBAAgB;IAC1C,WAAU;cACX;IAEQ,CAAA;GAEX,CAAA,GAEF,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAM,KAAK,MACV,kBAAC,GAAD;IAEW;IACT,eAAe,EAAW,aAAa,EAAQ,KAAK;IACpD,EAHK,EAAQ,GAGb,CACF;GACE,CAAA,EACN,kBAAC,GAAD;GACE,WAAU;GACJ;GACG;GACT,cAAc,GAAS,MAAM,KAAK,IAAI,GAAG,IAAI,EAAE,CAAC;GAChD,cAAc,GAAS,MAAM,IAAI,EAAE;GACnC,CAAA,CACD,EAAA,CAAA,CAEM"}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { useAthleteBridge as e } from "../../context/AthleteBridgeContext.js";
|
|
2
|
+
import { PageLayout as t } from "../../components/PageLayout.js";
|
|
3
|
+
import { StatusBadge as n } from "../../components/StatusBadge.js";
|
|
4
|
+
import { SESSION_TYPE_LABELS as r } from "../../components/domain.js";
|
|
5
|
+
import { formatDate as i, formatDuration as a } from "../../utils/formatters.js";
|
|
6
|
+
import { ErrorState as o, LoadingState as s } from "../../components/LoadingState.js";
|
|
7
|
+
import { useCancelSession as c, useCompleteSession as l, useConfirmSession as u, useSession as d } from "../../hooks/useAthleteBridgeApi.js";
|
|
8
|
+
import { FormField as f, fieldInputClass as p } from "../../components/form.js";
|
|
9
|
+
import { useState as m } from "react";
|
|
10
|
+
import { ArrowLeft as h, CalendarClock as g, CheckCircle2 as _, Clock as v, Loader2 as y, MapPin as b, Star as x, Video as S, XCircle as C } from "lucide-react";
|
|
11
|
+
import { useParams as w } from "react-router-dom";
|
|
12
|
+
import { jsx as T, jsxs as E } from "react/jsx-runtime";
|
|
13
|
+
import { EmphasisPanel as D, GlassCard as O, ResponsiveDialog as k, ResponsiveDialogContent as A, ResponsiveDialogFooter as j, ResponsiveDialogHeader as M, ResponsiveDialogTitle as N } from "@burdenoff/fe-libs/ui";
|
|
14
|
+
//#region src/atheletebridge/pages/sessions/SessionDetailPage.tsx
|
|
15
|
+
function P() {
|
|
16
|
+
let { id: P } = w(), { navigateTo: F } = e(), I = d(P), L = u(), R = l(), z = c(), [B, V] = m(!1), [H, U] = m(""), [W, G] = m(""), [K, q] = m(null);
|
|
17
|
+
if (I.isLoading) return /* @__PURE__ */ T(t, {
|
|
18
|
+
title: "Session",
|
|
19
|
+
children: /* @__PURE__ */ T(s, { label: "Loading session…" })
|
|
20
|
+
});
|
|
21
|
+
if (I.isError || !I.data) return /* @__PURE__ */ T(t, {
|
|
22
|
+
title: "Session",
|
|
23
|
+
children: /* @__PURE__ */ T(o, {
|
|
24
|
+
title: "Session not found",
|
|
25
|
+
message: "This session does not exist or you do not have access to it.",
|
|
26
|
+
onRetry: () => F("/sessions")
|
|
27
|
+
})
|
|
28
|
+
});
|
|
29
|
+
let J = I.data, Y = J.status === "REQUESTED", X = J.status === "CONFIRMED", Z = J.status === "REQUESTED" || J.status === "CONFIRMED";
|
|
30
|
+
return /* @__PURE__ */ E(t, {
|
|
31
|
+
title: J.title,
|
|
32
|
+
description: r[J.type],
|
|
33
|
+
actions: /* @__PURE__ */ E("button", {
|
|
34
|
+
type: "button",
|
|
35
|
+
onClick: () => F("/sessions"),
|
|
36
|
+
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",
|
|
37
|
+
children: [/* @__PURE__ */ T(h, { className: "h-4 w-4" }), " Back"]
|
|
38
|
+
}),
|
|
39
|
+
children: [
|
|
40
|
+
/* @__PURE__ */ E(O, {
|
|
41
|
+
treatment: "insight",
|
|
42
|
+
className: "mb-8 p-6",
|
|
43
|
+
children: [
|
|
44
|
+
/* @__PURE__ */ T("div", {
|
|
45
|
+
className: "flex flex-wrap items-center gap-3",
|
|
46
|
+
children: /* @__PURE__ */ T(n, { status: J.status })
|
|
47
|
+
}),
|
|
48
|
+
/* @__PURE__ */ E("div", {
|
|
49
|
+
className: "mt-4 grid grid-cols-1 gap-3 text-sm text-text-secondary sm:grid-cols-2",
|
|
50
|
+
children: [
|
|
51
|
+
/* @__PURE__ */ E("span", {
|
|
52
|
+
className: "flex items-center gap-2",
|
|
53
|
+
children: [
|
|
54
|
+
/* @__PURE__ */ T(g, { className: "h-4 w-4" }),
|
|
55
|
+
" ",
|
|
56
|
+
i(J.scheduledAt)
|
|
57
|
+
]
|
|
58
|
+
}),
|
|
59
|
+
/* @__PURE__ */ E("span", {
|
|
60
|
+
className: "flex items-center gap-2",
|
|
61
|
+
children: [
|
|
62
|
+
/* @__PURE__ */ T(v, { className: "h-4 w-4" }),
|
|
63
|
+
" ",
|
|
64
|
+
a(J.durationMin)
|
|
65
|
+
]
|
|
66
|
+
}),
|
|
67
|
+
J.location && /* @__PURE__ */ E("span", {
|
|
68
|
+
className: "flex items-center gap-2",
|
|
69
|
+
children: [
|
|
70
|
+
/* @__PURE__ */ T(b, { className: "h-4 w-4" }),
|
|
71
|
+
" ",
|
|
72
|
+
J.location
|
|
73
|
+
]
|
|
74
|
+
}),
|
|
75
|
+
J.meetingUrl && /* @__PURE__ */ E("a", {
|
|
76
|
+
href: J.meetingUrl,
|
|
77
|
+
target: "_blank",
|
|
78
|
+
rel: "noopener noreferrer",
|
|
79
|
+
className: "flex items-center gap-2 text-primary hover:underline",
|
|
80
|
+
children: [/* @__PURE__ */ T(S, { className: "h-4 w-4" }), " Join meeting"]
|
|
81
|
+
})
|
|
82
|
+
]
|
|
83
|
+
}),
|
|
84
|
+
J.completedAt && /* @__PURE__ */ E("div", {
|
|
85
|
+
className: "mt-5 border-t border-border-seam pt-5",
|
|
86
|
+
children: [
|
|
87
|
+
/* @__PURE__ */ T("h2", {
|
|
88
|
+
className: "mb-2 text-sm font-semibold text-text-primary",
|
|
89
|
+
children: "Outcome"
|
|
90
|
+
}),
|
|
91
|
+
/* @__PURE__ */ E("p", {
|
|
92
|
+
className: "text-sm text-text-secondary",
|
|
93
|
+
children: [
|
|
94
|
+
"Completed ",
|
|
95
|
+
i(J.completedAt),
|
|
96
|
+
J.rating !== null && /* @__PURE__ */ E("span", {
|
|
97
|
+
className: "ml-2 inline-flex items-center gap-1",
|
|
98
|
+
children: [
|
|
99
|
+
/* @__PURE__ */ T(x, { className: "h-3.5 w-3.5" }),
|
|
100
|
+
" ",
|
|
101
|
+
J.rating,
|
|
102
|
+
"/5"
|
|
103
|
+
]
|
|
104
|
+
})
|
|
105
|
+
]
|
|
106
|
+
}),
|
|
107
|
+
J.feedback && /* @__PURE__ */ E("p", {
|
|
108
|
+
className: "mt-2 text-sm text-text-secondary",
|
|
109
|
+
children: [
|
|
110
|
+
"“",
|
|
111
|
+
J.feedback,
|
|
112
|
+
"”"
|
|
113
|
+
]
|
|
114
|
+
})
|
|
115
|
+
]
|
|
116
|
+
})
|
|
117
|
+
]
|
|
118
|
+
}),
|
|
119
|
+
(Y || X || Z) && /* @__PURE__ */ E(D, { children: [/* @__PURE__ */ T("h2", {
|
|
120
|
+
className: "mb-4 text-sm font-semibold text-text-primary",
|
|
121
|
+
children: "Actions"
|
|
122
|
+
}), /* @__PURE__ */ E("div", {
|
|
123
|
+
className: "flex flex-wrap items-center gap-2",
|
|
124
|
+
children: [
|
|
125
|
+
Y && /* @__PURE__ */ E("button", {
|
|
126
|
+
type: "button",
|
|
127
|
+
onClick: () => P && L.mutate(P),
|
|
128
|
+
disabled: L.isPending,
|
|
129
|
+
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",
|
|
130
|
+
children: [L.isPending ? /* @__PURE__ */ T(y, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ T(_, { className: "h-4 w-4" }), "Confirm"]
|
|
131
|
+
}),
|
|
132
|
+
X && /* @__PURE__ */ E("button", {
|
|
133
|
+
type: "button",
|
|
134
|
+
onClick: () => {
|
|
135
|
+
q(null), V(!0);
|
|
136
|
+
},
|
|
137
|
+
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",
|
|
138
|
+
children: [/* @__PURE__ */ T(_, { className: "h-4 w-4" }), " Complete"]
|
|
139
|
+
}),
|
|
140
|
+
Z && /* @__PURE__ */ E("button", {
|
|
141
|
+
type: "button",
|
|
142
|
+
onClick: () => P && z.mutate(P),
|
|
143
|
+
disabled: z.isPending,
|
|
144
|
+
className: "inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:opacity-60",
|
|
145
|
+
children: [z.isPending ? /* @__PURE__ */ T(y, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ T(C, { className: "h-4 w-4" }), "Cancel"]
|
|
146
|
+
})
|
|
147
|
+
]
|
|
148
|
+
})] }),
|
|
149
|
+
/* @__PURE__ */ T(k, {
|
|
150
|
+
open: B,
|
|
151
|
+
onOpenChange: V,
|
|
152
|
+
children: /* @__PURE__ */ E(A, { children: [/* @__PURE__ */ T(M, { children: /* @__PURE__ */ T(N, { children: "Complete session" }) }), /* @__PURE__ */ E("form", {
|
|
153
|
+
onSubmit: async (e) => {
|
|
154
|
+
if (e.preventDefault(), q(null), !P) return;
|
|
155
|
+
let t;
|
|
156
|
+
if (W.trim() && (t = Number.parseInt(W, 10), Number.isNaN(t) || t < 1 || t > 5)) {
|
|
157
|
+
q("Rating must be between 1 and 5.");
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
await R.mutateAsync({
|
|
162
|
+
id: P,
|
|
163
|
+
input: {
|
|
164
|
+
feedback: H.trim() || void 0,
|
|
165
|
+
rating: t
|
|
166
|
+
}
|
|
167
|
+
}), V(!1);
|
|
168
|
+
} catch (e) {
|
|
169
|
+
q(e instanceof Error ? e.message : "Failed to complete session.");
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
className: "space-y-4 px-1",
|
|
173
|
+
children: [
|
|
174
|
+
/* @__PURE__ */ T(f, {
|
|
175
|
+
label: "Feedback",
|
|
176
|
+
htmlFor: "feedback",
|
|
177
|
+
hint: "Optional post-session notes.",
|
|
178
|
+
children: /* @__PURE__ */ T("textarea", {
|
|
179
|
+
id: "feedback",
|
|
180
|
+
className: p,
|
|
181
|
+
rows: 3,
|
|
182
|
+
value: H,
|
|
183
|
+
onChange: (e) => U(e.target.value),
|
|
184
|
+
placeholder: "How did the session go?"
|
|
185
|
+
})
|
|
186
|
+
}),
|
|
187
|
+
/* @__PURE__ */ T(f, {
|
|
188
|
+
label: "Rating (1–5)",
|
|
189
|
+
htmlFor: "rating",
|
|
190
|
+
hint: "Optional.",
|
|
191
|
+
children: /* @__PURE__ */ T("input", {
|
|
192
|
+
id: "rating",
|
|
193
|
+
inputMode: "numeric",
|
|
194
|
+
className: p,
|
|
195
|
+
value: W,
|
|
196
|
+
onChange: (e) => G(e.target.value),
|
|
197
|
+
placeholder: "5"
|
|
198
|
+
})
|
|
199
|
+
}),
|
|
200
|
+
K && /* @__PURE__ */ T("p", {
|
|
201
|
+
className: "text-sm text-status-error-text",
|
|
202
|
+
children: K
|
|
203
|
+
}),
|
|
204
|
+
/* @__PURE__ */ E(j, { children: [/* @__PURE__ */ T("button", {
|
|
205
|
+
type: "button",
|
|
206
|
+
onClick: () => V(!1),
|
|
207
|
+
className: "rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken",
|
|
208
|
+
children: "Cancel"
|
|
209
|
+
}), /* @__PURE__ */ E("button", {
|
|
210
|
+
type: "submit",
|
|
211
|
+
disabled: R.isPending,
|
|
212
|
+
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",
|
|
213
|
+
children: [R.isPending && /* @__PURE__ */ T(y, { className: "h-4 w-4 animate-spin" }), "Complete session"]
|
|
214
|
+
})] })
|
|
215
|
+
]
|
|
216
|
+
})] })
|
|
217
|
+
})
|
|
218
|
+
]
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
//#endregion
|
|
222
|
+
export { P as SessionDetailPage };
|
|
223
|
+
|
|
224
|
+
//# sourceMappingURL=SessionDetailPage.js.map
|
|
@@ -0,0 +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\nimport { useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n ArrowLeft,\n CalendarClock,\n Clock,\n MapPin,\n Video,\n Star,\n CheckCircle2,\n XCircle,\n Loader2,\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 { 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 { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport {\n useSession,\n useConfirmSession,\n useCompleteSession,\n useCancelSession,\n} from '../../hooks/useAthleteBridgeApi';\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('');\n const [error, setError] = useState<string | null>(null);\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\n const handleComplete = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n if (!id) return;\n let ratingNum: number | undefined;\n if (rating.trim()) {\n ratingNum = Number.parseInt(rating, 10);\n if (Number.isNaN(ratingNum) || ratingNum < 1 || ratingNum > 5) {\n setError('Rating must be between 1 and 5.');\n return;\n }\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 <GlassCard treatment=\"insight\" className=\"mb-8 p-6\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <StatusBadge status={s.status} />\n </div>\n <div className=\"mt-4 grid grid-cols-1 gap-3 text-sm text-text-secondary sm:grid-cols-2\">\n <span className=\"flex items-center gap-2\">\n <CalendarClock className=\"h-4 w-4\" /> {formatDate(s.scheduledAt)}\n </span>\n <span className=\"flex items-center gap-2\">\n <Clock className=\"h-4 w-4\" /> {formatDuration(s.durationMin)}\n </span>\n {s.location && (\n <span className=\"flex items-center gap-2\">\n <MapPin className=\"h-4 w-4\" /> {s.location}\n </span>\n )}\n {s.meetingUrl && (\n <a\n href={s.meetingUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"flex items-center gap-2 text-primary hover:underline\"\n >\n <Video className=\"h-4 w-4\" /> Join meeting\n </a>\n )}\n </div>\n\n {s.completedAt && (\n <div className=\"mt-5 border-t border-border-seam pt-5\">\n <h2 className=\"mb-2 text-sm font-semibold text-text-primary\">Outcome</h2>\n <p className=\"text-sm text-text-secondary\">\n Completed {formatDate(s.completedAt)}\n {s.rating !== null && (\n <span className=\"ml-2 inline-flex items-center gap-1\">\n <Star className=\"h-3.5 w-3.5\" /> {s.rating}/5\n </span>\n )}\n </p>\n {s.feedback && <p className=\"mt-2 text-sm text-text-secondary\">“{s.feedback}”</p>}\n </div>\n )}\n </GlassCard>\n\n {/* Lifecycle actions */}\n {(canConfirm || canComplete || canCancel) && (\n <EmphasisPanel>\n <h2 className=\"mb-4 text-sm font-semibold text-text-primary\">Actions</h2>\n <div className=\"flex flex-wrap items-center gap-2\">\n {canConfirm && (\n <button\n type=\"button\"\n onClick={() => id && confirmSession.mutate(id)}\n disabled={confirmSession.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 {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 setCompleteOpen(true);\n }}\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 <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 items-center gap-1.5 rounded-lg border border-border-seam px-4 py-2 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\n </button>\n )}\n </div>\n </EmphasisPanel>\n )}\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 (1–5)\" htmlFor=\"rating\" hint=\"Optional.\">\n <input\n id=\"rating\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={rating}\n onChange={(e) => setRating(e.target.value)}\n placeholder=\"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-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 {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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"}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { useAthleteBridge as e } from "../../context/AthleteBridgeContext.js";
|
|
2
|
+
import { PageLayout as t } from "../../components/PageLayout.js";
|
|
3
|
+
import { SESSION_TYPE_LABELS as n } from "../../components/domain.js";
|
|
4
|
+
import { useBookSession as r, usePrograms as i } from "../../hooks/useAthleteBridgeApi.js";
|
|
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";
|
|
9
|
+
//#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({
|
|
13
|
+
status: "PUBLISHED",
|
|
14
|
+
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, {
|
|
17
|
+
title: "Book session",
|
|
18
|
+
description: "Schedule a coaching session between a coach and an athlete.",
|
|
19
|
+
actions: /* @__PURE__ */ f("button", {
|
|
20
|
+
type: "button",
|
|
21
|
+
onClick: () => m("/sessions"),
|
|
22
|
+
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
|
+
}),
|
|
25
|
+
children: /* @__PURE__ */ f("form", {
|
|
26
|
+
onSubmit: async (e) => {
|
|
27
|
+
if (e.preventDefault(), L(null), !v.trim()) {
|
|
28
|
+
L("Coach ID is required.");
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (!b.trim()) {
|
|
32
|
+
L("Athlete ID is required.");
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (!w.trim()) {
|
|
36
|
+
L("A session title is required.");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (!M) {
|
|
40
|
+
L("A scheduled date and time is required.");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
let t = Number.parseInt(P, 10);
|
|
44
|
+
if (Number.isNaN(t) || t <= 0) {
|
|
45
|
+
L("Duration must be a positive number of minutes.");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
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(),
|
|
58
|
+
durationMin: t
|
|
59
|
+
})).id}`);
|
|
60
|
+
} catch (e) {
|
|
61
|
+
L(e instanceof Error ? e.message : "Failed to book session.");
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
className: "max-w-2xl space-y-5",
|
|
65
|
+
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
|
+
})]
|
|
92
|
+
}),
|
|
93
|
+
/* @__PURE__ */ d(a, {
|
|
94
|
+
label: "Title",
|
|
95
|
+
htmlFor: "title",
|
|
96
|
+
required: !0,
|
|
97
|
+
children: /* @__PURE__ */ d("input", {
|
|
98
|
+
id: "title",
|
|
99
|
+
className: o,
|
|
100
|
+
value: w,
|
|
101
|
+
onChange: (e) => T(e.target.value),
|
|
102
|
+
placeholder: "e.g. Sprint technique review"
|
|
103
|
+
})
|
|
104
|
+
}),
|
|
105
|
+
/* @__PURE__ */ f("div", {
|
|
106
|
+
className: "grid grid-cols-1 gap-5 sm:grid-cols-2",
|
|
107
|
+
children: [/* @__PURE__ */ d(a, {
|
|
108
|
+
label: "Type",
|
|
109
|
+
htmlFor: "type",
|
|
110
|
+
children: /* @__PURE__ */ d("select", {
|
|
111
|
+
id: "type",
|
|
112
|
+
className: o,
|
|
113
|
+
value: E,
|
|
114
|
+
onChange: (e) => D(e.target.value),
|
|
115
|
+
children: p.map((e) => /* @__PURE__ */ d("option", {
|
|
116
|
+
value: e,
|
|
117
|
+
children: n[e]
|
|
118
|
+
}, e))
|
|
119
|
+
})
|
|
120
|
+
}), /* @__PURE__ */ d(a, {
|
|
121
|
+
label: "Program",
|
|
122
|
+
htmlFor: "program",
|
|
123
|
+
hint: "Optional — tie to a published plan.",
|
|
124
|
+
children: /* @__PURE__ */ f("select", {
|
|
125
|
+
id: "program",
|
|
126
|
+
className: o,
|
|
127
|
+
value: S,
|
|
128
|
+
onChange: (e) => C(e.target.value),
|
|
129
|
+
children: [/* @__PURE__ */ d("option", {
|
|
130
|
+
value: "",
|
|
131
|
+
children: "No program"
|
|
132
|
+
}), R.map((e) => /* @__PURE__ */ d("option", {
|
|
133
|
+
value: e.id,
|
|
134
|
+
children: e.name
|
|
135
|
+
}, e.id))]
|
|
136
|
+
})
|
|
137
|
+
})]
|
|
138
|
+
}),
|
|
139
|
+
E !== "ONLINE" && /* @__PURE__ */ d(a, {
|
|
140
|
+
label: "Location",
|
|
141
|
+
htmlFor: "location",
|
|
142
|
+
children: /* @__PURE__ */ d("input", {
|
|
143
|
+
id: "location",
|
|
144
|
+
className: o,
|
|
145
|
+
value: O,
|
|
146
|
+
onChange: (e) => k(e.target.value),
|
|
147
|
+
placeholder: "e.g. Main gym, Court 2"
|
|
148
|
+
})
|
|
149
|
+
}),
|
|
150
|
+
E !== "IN_PERSON" && /* @__PURE__ */ d(a, {
|
|
151
|
+
label: "Meeting URL",
|
|
152
|
+
htmlFor: "meetingUrl",
|
|
153
|
+
children: /* @__PURE__ */ d("input", {
|
|
154
|
+
id: "meetingUrl",
|
|
155
|
+
className: o,
|
|
156
|
+
value: A,
|
|
157
|
+
onChange: (e) => j(e.target.value),
|
|
158
|
+
placeholder: "https://meet.example.com/…"
|
|
159
|
+
})
|
|
160
|
+
}),
|
|
161
|
+
/* @__PURE__ */ f("div", {
|
|
162
|
+
className: "grid grid-cols-1 gap-5 sm:grid-cols-2",
|
|
163
|
+
children: [/* @__PURE__ */ d(a, {
|
|
164
|
+
label: "Scheduled at",
|
|
165
|
+
htmlFor: "scheduledAt",
|
|
166
|
+
required: !0,
|
|
167
|
+
children: /* @__PURE__ */ d("input", {
|
|
168
|
+
id: "scheduledAt",
|
|
169
|
+
type: "datetime-local",
|
|
170
|
+
className: o,
|
|
171
|
+
value: M,
|
|
172
|
+
onChange: (e) => N(e.target.value)
|
|
173
|
+
})
|
|
174
|
+
}), /* @__PURE__ */ d(a, {
|
|
175
|
+
label: "Duration (minutes)",
|
|
176
|
+
htmlFor: "duration",
|
|
177
|
+
children: /* @__PURE__ */ d("input", {
|
|
178
|
+
id: "duration",
|
|
179
|
+
inputMode: "numeric",
|
|
180
|
+
className: o,
|
|
181
|
+
value: P,
|
|
182
|
+
onChange: (e) => F(e.target.value)
|
|
183
|
+
})
|
|
184
|
+
})]
|
|
185
|
+
}),
|
|
186
|
+
I && /* @__PURE__ */ d("p", {
|
|
187
|
+
className: "text-sm text-status-error-text",
|
|
188
|
+
children: I
|
|
189
|
+
}),
|
|
190
|
+
/* @__PURE__ */ d("div", {
|
|
191
|
+
className: "pt-2",
|
|
192
|
+
children: /* @__PURE__ */ f("button", {
|
|
193
|
+
type: "submit",
|
|
194
|
+
disabled: _.isPending,
|
|
195
|
+
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"]
|
|
197
|
+
})
|
|
198
|
+
})
|
|
199
|
+
]
|
|
200
|
+
})
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
//#endregion
|
|
204
|
+
export { m as SessionFormPage };
|
|
205
|
+
|
|
206
|
+
//# sourceMappingURL=SessionFormPage.js.map
|