@burdenoff/microfe-atheletebridge 2026.829.1 → 2026.830.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/components/LoadingState.d.ts.map +1 -1
- 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.map +1 -1
- package/dist/atheletebridge/pages/DashboardPage.d.ts.map +1 -1
- 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.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.d.ts.map +1 -1
- 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.map +1 -1
- package/dist/atheletebridge/pages/onboarding/OnboardingPage.d.ts.map +1 -1
- 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.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramDetailPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionDetailPage.d.ts.map +1 -1
- 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.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamDetailPage.d.ts.map +1 -1
- 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.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TeamFormPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/teams/TeamFormPage.tsx"],"sourcesContent":["/**\n * Team Create / Edit Form\n * @module atheletebridge/pages/teams\n */\n\nimport { useEffect, useState } from 'react';\nimport { useParams } from 'react-router';\nimport { ArrowLeft, CalendarDays, Loader2, MapPin, Save, Search, X } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FormField, fieldInputClass } from '../../components/form';\nimport { UserAvatar } from '../../components/UserAvatar';\nimport { SPORT_ICONS, SPORT_LABELS } from '../../components/domain';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport {\n useTeam,\n useCreateTeam,\n useUpdateTeam,\n useUserDirectory,\n useUserSearch,\n directoryUserName,\n} from '../../hooks/useAthleteBridgeApi';\nimport { useAbTranslate } from '../../i18n';\nimport type { AthSport, AthTeamStatus } from '../../types';\n\nconst SPORTS = Object.keys(SPORT_LABELS) as AthSport[];\nconst STATUSES: AthTeamStatus[] = ['FORMING', 'ACTIVE', 'INACTIVE', 'ARCHIVED'];\nconst DESCRIPTION_LIMIT = 300;\n\n/** Faint sport glyph rendered inside the Sport select trigger. */\nfunction SportGlyph({ sport }: { sport: AthSport }) {\n const Icon = SPORT_ICONS[sport];\n return (\n <Icon\n className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-muted\"\n aria-hidden=\"true\"\n />\n );\n}\n\ninterface HeadCoachPickerProps {\n userId: string;\n displayName: string;\n onSelect: (value: { userId: string; displayName: string }) => void;\n}\n\n/**\n * Optional head-coach picker — a lightweight directory search wired to the real\n * `useUserSearch` hook, degrading to a paste-a-user-id field when the directory\n * is unavailable. Labelled correctly for the head-coach role.\n */\nfunction HeadCoachPicker({ userId, displayName, onSelect }: HeadCoachPickerProps) {\n const [term, setTerm] = useState('');\n const search = useUserSearch(term);\n const results = search.data ?? [];\n const active = term.trim().length >= 2;\n const unavailable = search.isError;\n\n if (userId) {\n return (\n <FormField label=\"Head coach\" hint=\"Optional — the coach who leads this squad.\">\n <div className=\"flex items-center gap-2.5 rounded-lg border border-border-seam bg-bg-surface px-3 py-2\">\n <UserAvatar name={displayName} id={userId} size=\"sm\" />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {displayName || 'Selected coach'}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">{userId}</p>\n </div>\n <button\n type=\"button\"\n aria-label=\"Clear head coach\"\n onClick={() => onSelect({ userId: '', displayName: '' })}\n className=\"rounded-md p-1 text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n </div>\n </FormField>\n );\n }\n\n if (unavailable) {\n return (\n <FormField\n label=\"Head coach\"\n htmlFor=\"headCoachId\"\n hint=\"Optional — directory unavailable here; paste the auth-svc user id.\"\n >\n <input\n id=\"headCoachId\"\n className={fieldInputClass}\n value={userId}\n onChange={(e) => onSelect({ userId: e.target.value, displayName })}\n placeholder=\"user-uuid\"\n />\n </FormField>\n );\n }\n\n return (\n <FormField label=\"Head coach\" hint=\"Optional — the coach who leads this squad.\">\n <div className=\"space-y-2\">\n <div className=\"relative\">\n <Search\n className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-muted\"\n aria-hidden=\"true\"\n />\n <input\n className={`${fieldInputClass} pl-9`}\n value={term}\n onChange={(e) => setTerm(e.target.value)}\n placeholder=\"Search coaches by name or email…\"\n aria-label=\"Search for a head coach\"\n />\n {search.isFetching && (\n <Loader2 className=\"absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 animate-spin text-text-muted\" />\n )}\n </div>\n {active && !search.isFetching && results.length === 0 && (\n <p className=\"text-xs text-text-secondary\">No users match “{term.trim()}”.</p>\n )}\n {results.length > 0 && (\n <ul className=\"max-h-56 divide-y divide-border-seam overflow-auto rounded-lg border border-border-seam bg-bg-surface\">\n {results.map((user) => (\n <li key={user.id}>\n <button\n type=\"button\"\n onClick={() => {\n onSelect({ userId: user.id, displayName: directoryUserName(user) });\n setTerm('');\n }}\n className=\"flex w-full items-center gap-2.5 px-3 py-2 text-left transition-colors hover:bg-bg-sunken\"\n >\n <UserAvatar name={directoryUserName(user)} id={user.id} size=\"sm\" />\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate text-sm font-medium text-text-primary\">\n {directoryUserName(user)}\n </span>\n <span className=\"block truncate text-xs text-text-secondary\">{user.email}</span>\n </span>\n </button>\n </li>\n ))}\n </ul>\n )}\n </div>\n </FormField>\n );\n}\n\ninterface TeamFormState {\n name: string;\n sport: AthSport;\n status: AthTeamStatus;\n description: string;\n ageGroup: string;\n season: string;\n homeVenue: string;\n headCoachId: string;\n headCoachName: string;\n tags: string[];\n}\n\nconst EMPTY_FORM: TeamFormState = {\n name: '',\n sport: 'OTHER',\n status: 'FORMING',\n description: '',\n ageGroup: '',\n season: '',\n homeVenue: '',\n headCoachId: '',\n headCoachName: '',\n tags: [],\n};\n\nexport function TeamFormPage() {\n const tr = useAbTranslate();\n const { id } = useParams<{ id: string }>();\n const isEdit = Boolean(id);\n const { navigateTo } = useAthleteBridge();\n\n const existing = useTeam(id);\n const createTeam = useCreateTeam();\n const updateTeam = useUpdateTeam();\n\n const [form, setForm] = useState<TeamFormState>(EMPTY_FORM);\n const [tagInput, setTagInput] = useState('');\n const [error, setError] = useState<string | null>(null);\n\n // Resolve the existing head coach's display name so the picker shows it on edit.\n const coachDir = useUserDirectory(isEdit ? [existing.data?.headCoachId] : []);\n const resolvedCoachName = coachDir.nameOf(existing.data?.headCoachId ?? undefined);\n\n useEffect(() => {\n const team = existing.data;\n if (isEdit && team) {\n setForm((prev) => ({\n name: team.name,\n sport: team.sport,\n status: team.status,\n description: team.description ?? '',\n ageGroup: team.ageGroup ?? '',\n season: team.season ?? '',\n homeVenue: team.homeVenue ?? '',\n headCoachId: team.headCoachId ?? '',\n headCoachName: resolvedCoachName ?? prev.headCoachName,\n tags: team.tags,\n }));\n }\n }, [isEdit, existing.data, resolvedCoachName]);\n\n const set = <K extends keyof TeamFormState>(key: K, value: TeamFormState[K]) =>\n setForm((prev) => ({ ...prev, [key]: value }));\n\n const commitTag = (raw: string) => {\n const tag = raw.trim();\n if (!tag) return;\n setForm((prev) =>\n prev.tags.some((t) => t.toLowerCase() === tag.toLowerCase())\n ? prev\n : { ...prev, tags: [...prev.tags, tag] }\n );\n setTagInput('');\n };\n\n const removeTag = (tag: string) =>\n setForm((prev) => ({ ...prev, tags: prev.tags.filter((t) => t !== tag) }));\n\n const handleTagKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Enter' || e.key === ',') {\n e.preventDefault();\n commitTag(tagInput);\n } else if (e.key === 'Backspace' && !tagInput && form.tags.length) {\n removeTag(form.tags[form.tags.length - 1]);\n }\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n\n if (!form.name.trim()) {\n setError('Team name is required.');\n return;\n }\n\n // Fold any half-typed tag still in the input into the submission.\n const pending = tagInput.trim();\n const tags =\n pending && !form.tags.some((t) => t.toLowerCase() === pending.toLowerCase())\n ? [...form.tags, pending]\n : form.tags;\n\n const shared = {\n name: form.name.trim(),\n sport: form.sport,\n status: form.status,\n description: form.description.trim() || undefined,\n ageGroup: form.ageGroup.trim() || undefined,\n season: form.season.trim() || undefined,\n homeVenue: form.homeVenue.trim() || undefined,\n headCoachId: form.headCoachId.trim() || undefined,\n tags,\n };\n\n try {\n if (isEdit && id) {\n await updateTeam.mutateAsync({ id, input: shared });\n navigateTo(`/teams/${id}`);\n } else {\n const created = await createTeam.mutateAsync(shared);\n navigateTo(`/teams/${created.id}`);\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to save team.');\n }\n };\n\n const saving = createTeam.isPending || updateTeam.isPending;\n\n if (isEdit && existing.isLoading) {\n return (\n <PageLayout title={tr('atheletebridge.pages.teamForm.editTitle', 'Edit team')}>\n <LoadingState label={tr('atheletebridge.pages.teamForm.loading', 'Loading team…')} />\n </PageLayout>\n );\n }\n\n if (isEdit && (existing.isError || (!existing.isLoading && !existing.data))) {\n return (\n <PageLayout title={tr('atheletebridge.pages.teamForm.editTitle', 'Edit team')}>\n <ErrorState\n title={tr('atheletebridge.pages.teamForm.notFoundTitle', 'Team not found')}\n message={tr(\n 'atheletebridge.pages.teamForm.notFoundMessage',\n 'This team does not exist or you do not have access to it.'\n )}\n onRetry={() => navigateTo('/teams')}\n />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={\n isEdit\n ? tr('atheletebridge.pages.teamForm.editTitle', 'Edit team')\n : tr('atheletebridge.pages.teamForm.newTitle', 'New team')\n }\n description={\n isEdit\n ? tr(\n 'atheletebridge.pages.teamForm.editDescription',\n 'Update the squad details.'\n )\n : tr(\n 'atheletebridge.pages.teamForm.newDescription',\n 'Create a new squad to build a roster.'\n )\n }\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo(isEdit && id ? `/teams/${id}` : '/teams')}\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('common.cancel', 'Cancel')}\n </button>\n }\n >\n <form onSubmit={handleSubmit} className=\"mx-auto max-w-3xl\">\n <GlassCard treatment=\"insight\" className=\"p-6 sm:p-8\">\n <div className=\"space-y-5\">\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <FormField label=\"Team 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. Riverside United U18\"\n />\n </FormField>\n\n <FormField label=\"Sport\" htmlFor=\"sport\">\n <div className=\"relative\">\n <SportGlyph sport={form.sport} />\n <select\n id=\"sport\"\n className={`${fieldInputClass} pl-9`}\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 </div>\n </FormField>\n </div>\n\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <FormField label=\"Status\" htmlFor=\"status\">\n <select\n id=\"status\"\n className={fieldInputClass}\n value={form.status}\n onChange={(e) => set('status', e.target.value as AthTeamStatus)}\n >\n {STATUSES.map((s) => (\n <option key={s} value={s}>\n {s.charAt(0) + s.slice(1).toLowerCase()}\n </option>\n ))}\n </select>\n </FormField>\n\n <FormField label=\"Age group\" htmlFor=\"ageGroup\">\n <input\n id=\"ageGroup\"\n className={fieldInputClass}\n value={form.ageGroup}\n onChange={(e) => set('ageGroup', e.target.value)}\n placeholder=\"e.g. U18\"\n />\n </FormField>\n </div>\n\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <FormField label=\"Season\" htmlFor=\"season\">\n <div className=\"relative\">\n <CalendarDays\n className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-muted\"\n aria-hidden=\"true\"\n />\n <input\n id=\"season\"\n className={`${fieldInputClass} pl-9`}\n value={form.season}\n onChange={(e) => set('season', e.target.value)}\n placeholder=\"e.g. 2025/26\"\n />\n </div>\n </FormField>\n\n <FormField label=\"Home venue\" htmlFor=\"homeVenue\">\n <div className=\"relative\">\n <MapPin\n className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-muted\"\n aria-hidden=\"true\"\n />\n <input\n id=\"homeVenue\"\n className={`${fieldInputClass} pl-9`}\n value={form.homeVenue}\n onChange={(e) => set('homeVenue', e.target.value)}\n placeholder=\"e.g. Riverside Stadium\"\n />\n </div>\n </FormField>\n </div>\n\n <HeadCoachPicker\n userId={form.headCoachId}\n displayName={form.headCoachName}\n onSelect={({ userId, displayName }) =>\n setForm((prev) => ({\n ...prev,\n headCoachId: userId,\n headCoachName: displayName,\n }))\n }\n />\n\n <FormField label=\"Description\" htmlFor=\"description\">\n <textarea\n id=\"description\"\n className={fieldInputClass}\n rows={3}\n maxLength={DESCRIPTION_LIMIT}\n value={form.description}\n onChange={(e) => set('description', e.target.value)}\n placeholder=\"A short description of the team.\"\n />\n <div className=\"mt-1 text-right text-xs tabular-nums text-text-muted\">\n {form.description.length}/{DESCRIPTION_LIMIT}\n </div>\n </FormField>\n\n <FormField\n label=\"Tags\"\n htmlFor=\"tags\"\n hint=\"Type a tag and press Enter to add it; click × to remove.\"\n >\n <div className=\"flex flex-wrap items-center gap-1.5 rounded-lg border border-border-seam bg-bg-surface px-2 py-1.5 focus-within:ring-2 focus-within:ring-[var(--color-focus-ring)]\">\n {form.tags.map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center gap-1 rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\"\n >\n {tag}\n <button\n type=\"button\"\n onClick={() => removeTag(tag)}\n aria-label={`Remove tag ${tag}`}\n className=\"rounded-full text-text-muted transition-colors hover:text-status-error-text\"\n >\n <X className=\"h-3 w-3\" />\n </button>\n </span>\n ))}\n <input\n id=\"tags\"\n className=\"min-w-[8rem] flex-1 bg-transparent px-1 py-0.5 text-sm text-text-primary placeholder:text-text-muted focus:outline-none\"\n value={tagInput}\n onChange={(e) => setTagInput(e.target.value)}\n onKeyDown={handleTagKeyDown}\n onBlur={() => commitTag(tagInput)}\n placeholder={form.tags.length ? 'Add tag and press Enter…' : 'youth, development'}\n />\n </div>\n </FormField>\n\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n </div>\n\n <div className=\"mt-6 flex items-center justify-end gap-3 border-t border-border-seam pt-5\">\n <button\n type=\"button\"\n onClick={() => navigateTo(isEdit && id ? `/teams/${id}` : '/teams')}\n className=\"rounded-lg px-4 py-2 text-sm font-medium text-text-secondary transition-colors hover:text-text-primary\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={saving}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-semibold 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\n ? tr('atheletebridge.pages.teamForm.saveChanges', 'Save changes')\n : tr('atheletebridge.pages.teamForm.createTeam', 'Create team')}\n </button>\n </div>\n </GlassCard>\n </form>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TeamFormPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/teams/TeamFormPage.tsx"],"sourcesContent":["/**\n * Team Create / Edit Form\n * @module atheletebridge/pages/teams\n */\n\nimport { useEffect, useState } from 'react';\nimport { useParams } from 'react-router';\nimport { ArrowLeft, CalendarDays, Loader2, MapPin, Save, Search, X } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FormField, fieldInputClass } from '../../components/form';\nimport { UserAvatar } from '../../components/UserAvatar';\nimport { SPORT_ICONS, SPORT_LABELS } from '../../components/domain';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport {\n useTeam,\n useCreateTeam,\n useUpdateTeam,\n useUserDirectory,\n useUserSearch,\n directoryUserName,\n} from '../../hooks/useAthleteBridgeApi';\nimport { useAbTranslate } from '../../i18n';\nimport type { AthSport, AthTeamStatus } from '../../types';\n\nconst SPORTS = Object.keys(SPORT_LABELS) as AthSport[];\nconst STATUSES: AthTeamStatus[] = ['FORMING', 'ACTIVE', 'INACTIVE', 'ARCHIVED'];\nconst DESCRIPTION_LIMIT = 300;\n\n/** Faint sport glyph rendered inside the Sport select trigger. */\nfunction SportGlyph({ sport }: { sport: AthSport }) {\n const Icon = SPORT_ICONS[sport];\n return (\n <Icon\n className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-muted\"\n aria-hidden=\"true\"\n />\n );\n}\n\ninterface HeadCoachPickerProps {\n userId: string;\n displayName: string;\n onSelect: (value: { userId: string; displayName: string }) => void;\n}\n\n/**\n * Optional head-coach picker — a lightweight directory search wired to the real\n * `useUserSearch` hook, degrading to a paste-a-user-id field when the directory\n * is unavailable. Labelled correctly for the head-coach role.\n */\nfunction HeadCoachPicker({ userId, displayName, onSelect }: HeadCoachPickerProps) {\n const [term, setTerm] = useState('');\n const search = useUserSearch(term);\n const results = search.data ?? [];\n const active = term.trim().length >= 2;\n const unavailable = search.isError;\n\n if (userId) {\n return (\n <FormField label=\"Head coach\" hint=\"Optional — the coach who leads this squad.\">\n <div className=\"flex items-center gap-2.5 rounded-lg border border-border-seam bg-bg-surface px-3 py-2\">\n <UserAvatar name={displayName} id={userId} size=\"sm\" />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {displayName || 'Selected coach'}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">{userId}</p>\n </div>\n <button\n type=\"button\"\n aria-label=\"Clear head coach\"\n onClick={() => onSelect({ userId: '', displayName: '' })}\n className=\"rounded-md p-1 text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n </div>\n </FormField>\n );\n }\n\n if (unavailable) {\n return (\n <FormField\n label=\"Head coach\"\n htmlFor=\"headCoachId\"\n hint=\"Optional — directory unavailable here; paste the auth-svc user id.\"\n >\n <input\n id=\"headCoachId\"\n className={fieldInputClass}\n value={userId}\n onChange={(e) => onSelect({ userId: e.target.value, displayName })}\n placeholder=\"user-uuid\"\n />\n </FormField>\n );\n }\n\n return (\n <FormField label=\"Head coach\" hint=\"Optional — the coach who leads this squad.\">\n <div className=\"space-y-2\">\n <div className=\"relative\">\n <Search\n className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-muted\"\n aria-hidden=\"true\"\n />\n <input\n className={`${fieldInputClass} pl-9`}\n value={term}\n onChange={(e) => setTerm(e.target.value)}\n placeholder=\"Search coaches by name or email…\"\n aria-label=\"Search for a head coach\"\n />\n {search.isFetching && (\n <Loader2 className=\"absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 animate-spin text-text-muted\" />\n )}\n </div>\n {active && !search.isFetching && results.length === 0 && (\n <p className=\"text-xs text-text-secondary\">No users match “{term.trim()}”.</p>\n )}\n {results.length > 0 && (\n <ul className=\"max-h-56 divide-y divide-border-seam overflow-auto rounded-lg border border-border-seam bg-bg-surface\">\n {results.map((user) => (\n <li key={user.id}>\n <button\n type=\"button\"\n onClick={() => {\n onSelect({ userId: user.id, displayName: directoryUserName(user) });\n setTerm('');\n }}\n className=\"flex w-full items-center gap-2.5 px-3 py-2 text-left transition-colors hover:bg-bg-sunken\"\n >\n <UserAvatar name={directoryUserName(user)} id={user.id} size=\"sm\" />\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate text-sm font-medium text-text-primary\">\n {directoryUserName(user)}\n </span>\n <span className=\"block truncate text-xs text-text-secondary\">{user.email}</span>\n </span>\n </button>\n </li>\n ))}\n </ul>\n )}\n </div>\n </FormField>\n );\n}\n\ninterface TeamFormState {\n name: string;\n sport: AthSport;\n status: AthTeamStatus;\n description: string;\n ageGroup: string;\n season: string;\n homeVenue: string;\n headCoachId: string;\n headCoachName: string;\n tags: string[];\n}\n\nconst EMPTY_FORM: TeamFormState = {\n name: '',\n sport: 'OTHER',\n status: 'FORMING',\n description: '',\n ageGroup: '',\n season: '',\n homeVenue: '',\n headCoachId: '',\n headCoachName: '',\n tags: [],\n};\n\nexport function TeamFormPage() {\n const tr = useAbTranslate();\n const { id } = useParams<{ id: string }>();\n const isEdit = Boolean(id);\n const { navigateTo } = useAthleteBridge();\n\n const existing = useTeam(id);\n const createTeam = useCreateTeam();\n const updateTeam = useUpdateTeam();\n\n const [form, setForm] = useState<TeamFormState>(EMPTY_FORM);\n const [tagInput, setTagInput] = useState('');\n const [error, setError] = useState<string | null>(null);\n\n // Resolve the existing head coach's display name so the picker shows it on edit.\n const coachDir = useUserDirectory(isEdit ? [existing.data?.headCoachId] : []);\n const resolvedCoachName = coachDir.nameOf(existing.data?.headCoachId ?? undefined);\n\n useEffect(() => {\n const team = existing.data;\n if (isEdit && team) {\n setForm((prev) => ({\n name: team.name,\n sport: team.sport,\n status: team.status,\n description: team.description ?? '',\n ageGroup: team.ageGroup ?? '',\n season: team.season ?? '',\n homeVenue: team.homeVenue ?? '',\n headCoachId: team.headCoachId ?? '',\n headCoachName: resolvedCoachName ?? prev.headCoachName,\n tags: team.tags,\n }));\n }\n }, [isEdit, existing.data, resolvedCoachName]);\n\n const set = <K extends keyof TeamFormState>(key: K, value: TeamFormState[K]) =>\n setForm((prev) => ({ ...prev, [key]: value }));\n\n const commitTag = (raw: string) => {\n const tag = raw.trim();\n if (!tag) return;\n setForm((prev) =>\n prev.tags.some((t) => t.toLowerCase() === tag.toLowerCase())\n ? prev\n : { ...prev, tags: [...prev.tags, tag] }\n );\n setTagInput('');\n };\n\n const removeTag = (tag: string) =>\n setForm((prev) => ({ ...prev, tags: prev.tags.filter((t) => t !== tag) }));\n\n const handleTagKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Enter' || e.key === ',') {\n e.preventDefault();\n commitTag(tagInput);\n } else if (e.key === 'Backspace' && !tagInput && form.tags.length) {\n removeTag(form.tags[form.tags.length - 1]);\n }\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n\n if (!form.name.trim()) {\n setError('Team name is required.');\n return;\n }\n\n // Fold any half-typed tag still in the input into the submission.\n const pending = tagInput.trim();\n const tags =\n pending && !form.tags.some((t) => t.toLowerCase() === pending.toLowerCase())\n ? [...form.tags, pending]\n : form.tags;\n\n const shared = {\n name: form.name.trim(),\n sport: form.sport,\n status: form.status,\n description: form.description.trim() || undefined,\n ageGroup: form.ageGroup.trim() || undefined,\n season: form.season.trim() || undefined,\n homeVenue: form.homeVenue.trim() || undefined,\n headCoachId: form.headCoachId.trim() || undefined,\n tags,\n };\n\n try {\n if (isEdit && id) {\n await updateTeam.mutateAsync({ id, input: shared });\n navigateTo(`/teams/${id}`);\n } else {\n const created = await createTeam.mutateAsync(shared);\n navigateTo(`/teams/${created.id}`);\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to save team.');\n }\n };\n\n const saving = createTeam.isPending || updateTeam.isPending;\n\n if (isEdit && existing.isLoading) {\n return (\n <PageLayout title={tr('atheletebridge.pages.teamForm.editTitle', 'Edit team')}>\n <LoadingState label={tr('atheletebridge.pages.teamForm.loading', 'Loading team…')} />\n </PageLayout>\n );\n }\n\n if (isEdit && (existing.isError || (!existing.isLoading && !existing.data))) {\n return (\n <PageLayout title={tr('atheletebridge.pages.teamForm.editTitle', 'Edit team')}>\n <ErrorState\n title={tr('atheletebridge.pages.teamForm.notFoundTitle', 'Team not found')}\n message={tr(\n 'atheletebridge.pages.teamForm.notFoundMessage',\n 'This team does not exist or you do not have access to it.'\n )}\n onRetry={() => navigateTo('/teams')}\n />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={\n isEdit\n ? tr('atheletebridge.pages.teamForm.editTitle', 'Edit team')\n : tr('atheletebridge.pages.teamForm.newTitle', 'New team')\n }\n description={\n isEdit\n ? tr('atheletebridge.pages.teamForm.editDescription', 'Update the squad details.')\n : tr(\n 'atheletebridge.pages.teamForm.newDescription',\n 'Create a new squad to build a roster.'\n )\n }\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo(isEdit && id ? `/teams/${id}` : '/teams')}\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('common.cancel', 'Cancel')}\n </button>\n }\n >\n <form onSubmit={handleSubmit} className=\"mx-auto max-w-3xl\">\n <GlassCard treatment=\"insight\" className=\"p-6 sm:p-8\">\n <div className=\"space-y-5\">\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <FormField label=\"Team 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. Riverside United U18\"\n />\n </FormField>\n\n <FormField label=\"Sport\" htmlFor=\"sport\">\n <div className=\"relative\">\n <SportGlyph sport={form.sport} />\n <select\n id=\"sport\"\n className={`${fieldInputClass} pl-9`}\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 </div>\n </FormField>\n </div>\n\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <FormField label=\"Status\" htmlFor=\"status\">\n <select\n id=\"status\"\n className={fieldInputClass}\n value={form.status}\n onChange={(e) => set('status', e.target.value as AthTeamStatus)}\n >\n {STATUSES.map((s) => (\n <option key={s} value={s}>\n {s.charAt(0) + s.slice(1).toLowerCase()}\n </option>\n ))}\n </select>\n </FormField>\n\n <FormField label=\"Age group\" htmlFor=\"ageGroup\">\n <input\n id=\"ageGroup\"\n className={fieldInputClass}\n value={form.ageGroup}\n onChange={(e) => set('ageGroup', e.target.value)}\n placeholder=\"e.g. U18\"\n />\n </FormField>\n </div>\n\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <FormField label=\"Season\" htmlFor=\"season\">\n <div className=\"relative\">\n <CalendarDays\n className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-muted\"\n aria-hidden=\"true\"\n />\n <input\n id=\"season\"\n className={`${fieldInputClass} pl-9`}\n value={form.season}\n onChange={(e) => set('season', e.target.value)}\n placeholder=\"e.g. 2025/26\"\n />\n </div>\n </FormField>\n\n <FormField label=\"Home venue\" htmlFor=\"homeVenue\">\n <div className=\"relative\">\n <MapPin\n className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-muted\"\n aria-hidden=\"true\"\n />\n <input\n id=\"homeVenue\"\n className={`${fieldInputClass} pl-9`}\n value={form.homeVenue}\n onChange={(e) => set('homeVenue', e.target.value)}\n placeholder=\"e.g. Riverside Stadium\"\n />\n </div>\n </FormField>\n </div>\n\n <HeadCoachPicker\n userId={form.headCoachId}\n displayName={form.headCoachName}\n onSelect={({ userId, displayName }) =>\n setForm((prev) => ({\n ...prev,\n headCoachId: userId,\n headCoachName: displayName,\n }))\n }\n />\n\n <FormField label=\"Description\" htmlFor=\"description\">\n <textarea\n id=\"description\"\n className={fieldInputClass}\n rows={3}\n maxLength={DESCRIPTION_LIMIT}\n value={form.description}\n onChange={(e) => set('description', e.target.value)}\n placeholder=\"A short description of the team.\"\n />\n <div className=\"mt-1 text-right text-xs tabular-nums text-text-muted\">\n {form.description.length}/{DESCRIPTION_LIMIT}\n </div>\n </FormField>\n\n <FormField\n label=\"Tags\"\n htmlFor=\"tags\"\n hint=\"Type a tag and press Enter to add it; click × to remove.\"\n >\n <div className=\"flex flex-wrap items-center gap-1.5 rounded-lg border border-border-seam bg-bg-surface px-2 py-1.5 focus-within:ring-2 focus-within:ring-[var(--color-focus-ring)]\">\n {form.tags.map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center gap-1 rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\"\n >\n {tag}\n <button\n type=\"button\"\n onClick={() => removeTag(tag)}\n aria-label={`Remove tag ${tag}`}\n className=\"rounded-full text-text-muted transition-colors hover:text-status-error-text\"\n >\n <X className=\"h-3 w-3\" />\n </button>\n </span>\n ))}\n <input\n id=\"tags\"\n className=\"min-w-[8rem] flex-1 bg-transparent px-1 py-0.5 text-sm text-text-primary placeholder:text-text-muted focus:outline-none\"\n value={tagInput}\n onChange={(e) => setTagInput(e.target.value)}\n onKeyDown={handleTagKeyDown}\n onBlur={() => commitTag(tagInput)}\n placeholder={form.tags.length ? 'Add tag and press Enter…' : 'youth, development'}\n />\n </div>\n </FormField>\n\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n </div>\n\n <div className=\"mt-6 flex items-center justify-end gap-3 border-t border-border-seam pt-5\">\n <button\n type=\"button\"\n onClick={() => navigateTo(isEdit && id ? `/teams/${id}` : '/teams')}\n className=\"rounded-lg px-4 py-2 text-sm font-medium text-text-secondary transition-colors hover:text-text-primary\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={saving}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-semibold 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\n ? tr('atheletebridge.pages.teamForm.saveChanges', 'Save changes')\n : tr('atheletebridge.pages.teamForm.createTeam', 'Create team')}\n </button>\n </div>\n </GlassCard>\n </form>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@burdenoff/microfe-atheletebridge",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.830.1",
|
|
4
4
|
"description": "Burdenoff AthleteBridge Microfrontend - Sports, coaching & training platform for teams, fixtures, programs, sessions and athlete performance",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|