feat: add jersey order card to player profile page (#197) #202
No reviewers
Labels
No labels
domain:backend
domain:devops
domain:frontend
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
ldraney/westside-app!202
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "197-jersey-order-card-profile"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Adds a jersey order card to the player profile page, positioned between the Team & Coach card and the Payment card. Shows jersey details when ordered, or an "Order Jersey" CTA linking to
/jersey?player_id={id}(session auth mode from PR #200) when no order exists.Changes
src/routes/(app)/players/[id]/+page.svelte-- Added jersey helper functions (getJerseyOptionLabel,getJerseyOrderBadgeClass,getJerseyOrderLabel), derived state (showJerseyCard,jerseyOrdered), and the jersey order card template section. Card uses existing.info-cardpattern andbadge-jersey-*CSS classes from the admin CRM (PR #193). Visibility gated onisOwner || isAdmin, matching the payment card's permission model.Test Plan
/jersey?player_id={id}jersey_order_status === 'paid'-- see option, size, number, and "Paid" badgejersey_order_status === 'pending'-- see "Pending" badgenpm run buildpasses with no new warningsReview Checklist
.info-cardpattern from other profile cardsisOwner || isAdmin)reversible,jersey_warmup,opt_out)badge-jersey-*from app.cssnpm run buildpasses cleanlyRelated Notes
Add a jersey order card between Team & Coach and Payment sections on the player profile page. Shows current jersey status (option, size, number, order status) when a jersey has been ordered, or an "Order Jersey" CTA button linking to /jersey?player_id={id} when no order exists. Card is only visible to isOwner or isAdmin, matching the payment card's permission model. Uses existing .info-card pattern and badge-jersey-* CSS classes from the admin CRM. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>QA Review -- PR #202
Scope: Jersey order card on player profile page (issue #197)
Acceptance Criteria
jersey_order_statusnot ordered shows "Order Jersey" button/jersey?player_id={id}jersey_order_status === 'paid'shows option, size, number, badgeisOwner || isAdminCode Quality
.info-card,.info-row,.card-footerpatterns. No new CSS required -- reusesbadge-jersey-*classes from app.css. PASS.isOwner || isAdmin). PASS.npm run buildpasses cleanly. PASS.Nits (non-blocking)
showJerseyCardopt-out logic: Hides card only whenopt_out + paid. If someone opted out with another status, they see "Order Jersey" -- this appears intentional (allows changing mind). Fine as-is.getJerseyOptionLabeldefault: Returns raw option string for unknown values. Acceptable -- matches admin CRM pattern.VERDICT: APPROVED
Clean, focused PR. Follows existing patterns exactly. All acceptance criteria met.
PR #202 Review
DOMAIN REVIEW
Tech stack: SvelteKit (Svelte 5 runes), vanilla CSS, Keycloak auth.
Component architecture: The jersey card follows the exact same
.info-card/.info-card-body/.info-row/.card-footerpattern used by Player Info, Team & Coach, and Payment cards on this page. No new components introduced -- this is a template-level addition with helper functions in the same file. Clean and consistent.Permission model: The
showJerseyCardderived state uses(isOwner || isAdmin), which matches the Payment card's guard on line 498 of the existing file. The additional opt-out suppression (!(jersey_option === 'opt_out' && jersey_order_status === 'paid')) is a nice UX touch -- no point showing a completed opt-out.State handling: All jersey states are correctly handled:
CTA link:
/jersey?player_id={$page.params.id}correctly uses the route param and matches the session-authplayer_idpattern established in merged PR #200.Helper functions:
getJerseyOptionLabel,getJerseyOrderBadgeClass,getJerseyOrderLabelall have JSDoc type annotations and handle null/undefined with sensible defaults. The option label values (reversible,jersey_warmup,opt_out) align with what the jersey page uses.CSS dependency: The
badge-jersey-paid,badge-jersey-pending,badge-jersey-shipped,badge-jersey-noneclasses are referenced but defined inapp.cssvia merged PR #193. I cannot pull main to verify they exist on the remote HEAD (read-only agent), but the dependency chain is stated and PR #193 is confirmed merged.Conditional rendering:
jersey_sizeandjersey_numberrows use{#if player.jersey_size}/{#if player.jersey_number}guards, so they degrade gracefully when those fields are null.Accessibility: The "Order Jersey" CTA is an
<a>element (not a button triggering JS navigation), which is correct for a link. Badge text is visible (not icon-only). No accessibility regressions identified.BLOCKERS
None.
isOwner || isAdmin, matching the existing payment card pattern. No new data leakage vector introduced by this PR. (Note: theisOwnercheck is role-based rather than account-ownership-based, which is a pre-existing concern documented in the code comments at line 34-35 -- not introduced here.)player_idin the CTA link comes from the route param ($page.params.id), which is already validated by the existing page load logic.getStatusBadgeClasspattern already in the file. No auth/security duplication.NITS
NIT: The
getJerseyOptionLabeldefault case returnsoption || 'Unknown', which means an unexpected non-null string value (e.g., a future option key) would show as the raw key rather than a human-readable label. Consider whether the default should always be'Unknown'to avoid leaking internal identifiers to users.NIT: The
showJerseyCardderived state hides the card foropt_out + paid, but still shows it foropt_out + pending. This seems intentional (a pending opt-out might need attention), but it is worth confirming this is the desired UX.SOP COMPLIANCE
197-jersey-order-card-profilematches issue #197feat: add jersey order card to player profile page (#197)PROCESS OBSERVATIONS
VERDICT: APPROVED