Skip to content

Add Body Map question component — builder, mobile response & coach view#54

Open
lvaheykitman wants to merge 4 commits into
mainfrom
blast-messaging-v2
Open

Add Body Map question component — builder, mobile response & coach view#54
lvaheykitman wants to merge 4 commits into
mainfrom
blast-messaging-v2

Conversation

@lvaheykitman

Copy link
Copy Markdown
Contributor

What changed

Adds a Body Map question component across three surfaces, each iteration preserved on its own route for side-by-side comparison.

Desktop form builder + preview (/body-map, V2–V6)

  • Question configuration: instructions, body display (radio), symptom types (chips), optional custom question.
  • Inline multi-select symptom panel with per-symptom severity sliders, area notes, and area-grouped read-only chips.
  • Symptom colour system (Pain/Stiffness/Soreness) with a model layer toggle, neutral default-selection state, and Clear area / Clear all controls.

Mobile player-facing response (/body-map-mobile, V3–V10)

  • iPhone-framed single-column flow with the player shell nav.
  • Multi-snap bottom sheet, first-use tap hotspot, per-area notes, and a horizontally-scrollable avatar chip row.

Coach-facing completed response view (/body-map/response, V1–V3)

  • Read-only submitted response with symptom layer toggle and front/back flip.
  • AI summary panel generated via the Anthropic API (with loading + graceful fallback).

Shared

  • Interactive body-model SVG with per-symptom colour fills scaled by severity.

Notes for reviewers

  • This is a prototype: each version route is intentionally kept so design iterations can be compared. Latest are /body-map/v6, /body-map-mobile/v10, /body-map/response/v3.
  • The coach AI summary reads VITE_ANTHROPIC_API_KEY; without it the panel shows the documented "Summary unavailable" fallback and still renders the data-driven list.
  • Exact symptom hex values are used on the experimental colour routes, so the design-system colour lint may warn there.

Generated with Claude Code

lvaheykitman and others added 4 commits May 18, 2026 14:02
- New BlastMessageDrawer with 3-level MLS club drill-down picker
- Squads / Custom Groups / stub modes with mode dropdown
- 30 MLS clubs, Atlanta United FC full First Team roster (25 players)
- Send via channel chips (In-app, Email, SMS) with live reach counts
- Blast messages tab on Messaging page with sent/scheduled list view

Co-Authored-By: Claude <noreply@anthropic.com>
- Step 1: Select recipients — Staff + Athletes sections each with
  expand/collapse search, checkbox list, "Select all" header,
  "Selected (N)" pill, and dark-avatar selected state with remove button
- Step 2: Compose — channel toggles, conditional subject field,
  message body + SMS character counter, Add schedule (title + date range)
- Added atlNamedStaff (12 named ATL staff) and atlFirstTeamFlat exports

Co-Authored-By: Claude <noreply@anthropic.com>
…on flow

Builds out the broadcast messaging flow on top of the V1 baseline:

- Recipient picker with Clubs entry, 4 team-level selectors
  (Athletes / Staff / Guardians / Custom Groups), per-list team filter
  + A–Z/Z–A/Recent sort, and Create-custom-group modal.
- Compose step: filled channel chips (renamed In-app → Broadcast Channel),
  attachments via paperclip + chips, mandatory subject when Email selected,
  schedule with date / time / repeat + Add schedule control.
- Confirmation modal before send + success snackbar (top-right, slide in,
  Cancel/×, auto-dismiss).
- Broadcast history grid replaces the card list — Upcoming / Past tabs,
  Subject / Scheduled date / Status chip / Channel / Recipients (with
  hover tooltip + +N overflow) / three-dot menu. View-mode drawer for
  sent messages, edit drawer for scheduled/recurring with scope dialog.
- Seed data updated with realistic Upcoming + Past rows incl. recurring
  + sent-immediately examples.

Co-Authored-By: Claude <noreply@anthropic.com>
Implements the Body Map question type across multiple surfaces and iterations:

- Desktop form builder + preview (BodyMapPage, V2–V6): question config
  (instructions, body display, symptom types, custom question), inline
  multi-select symptom panel with per-symptom sliders, notes, area-grouped
  chips, symptom colours/layers, and clear controls.
- Mobile player-facing response (BodyMapMobileResponse, V3–V10): iPhone-framed
  single-column flow, bottom sheet with snap points, hotspot, notes, and
  WhatsApp-style chip row.
- Coach-facing read-only completed response view (BodyMapResponseView, V2–V3)
  with an AI summary panel (Anthropic API) and symptom layer toggle.
- Interactive body model SVG with per-symptom colour fills by severity.

Each iteration is preserved on its own route under /body-map and
/body-map-mobile so versions can be compared side by side.

Co-Authored-By: Claude <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a comprehensive body map feature, including SVG assets, builder configuration pages, mobile response views, and read-only response views across multiple iterations. It also adds a blast messaging system with recipient selection, scheduling, and custom group management. The review feedback highlights critical security vulnerabilities where the Anthropic API key is exposed on the client side in BodyMapResponseViewV2 and BodyMapResponseViewV3. Additionally, timezone offset bugs were identified in BlastMessageDrawer and BroadcastDetailDrawer when setting minimum dates and formatting local date-times.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +73 to +75
async function generateBodyMapSummary(response) {
const apiKey = import.meta.env.VITE_ANTHROPIC_API_KEY
if (!apiKey) throw new Error('Anthropic API key not configured')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-critical critical

Exposing the Anthropic API key (VITE_ANTHROPIC_API_KEY) on the client side is a critical security risk. Since Vite embeds VITE_ environment variables directly into the production JS bundle, anyone can inspect the network requests or source code to extract and abuse your API key. To resolve this, move the Anthropic API call to a secure backend endpoint or serverless function, and have the client fetch from that endpoint instead.

async function generateBodyMapSummary(response) {
  const res = await fetch("/api/generate-summary", {
    method: "POST",
    headers: {
      "content-type": "application/json",
    },
    body: JSON.stringify({ response }),
  })
  if (!res.ok) throw new Error("API error: " + res.status)
  const data = await res.json()
  return data.summary
}

Comment on lines +73 to +75
async function generateBodyMapSummary(response) {
const apiKey = import.meta.env.VITE_ANTHROPIC_API_KEY
if (!apiKey) throw new Error('Anthropic API key not configured')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-critical critical

Exposing the Anthropic API key (VITE_ANTHROPIC_API_KEY) on the client side is a critical security risk. Since Vite embeds VITE_ environment variables directly into the production JS bundle, anyone can inspect the network requests or source code to extract and abuse your API key. To resolve this, move the Anthropic API call to a secure backend endpoint or serverless function, and have the client fetch from that endpoint instead.

async function generateBodyMapSummary(response) {
  const res = await fetch("/api/generate-summary", {
    method: "POST",
    headers: {
      "content-type": "application/json",
    },
    body: JSON.stringify({ response }),
  })
  if (!res.ok) throw new Error("API error: " + res.status)
  const data = await res.json()
  return data.summary
}

Comment on lines +854 to +856
<TextField variant="filled" size="small" type="date" label="Start date" value={startDate} onChange={(e) => onStartDateChange(e.target.value)} InputLabelProps={{ shrink: true }} inputProps={{ min: new Date().toISOString().split('T')[0] }} sx={{ flex: 1 }} />
<Typography variant="body2" color="text.disabled">—</Typography>
<TextField variant="filled" size="small" type="date" label="End date" value={endDate} onChange={(e) => onEndDateChange(e.target.value)} InputLabelProps={{ shrink: true }} inputProps={{ min: startDate || new Date().toISOString().split('T')[0] }} sx={{ flex: 1 }} />

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using new Date().toISOString().split('T')[0] to set the minimum date can cause timezone offset bugs. Since .toISOString() returns the date in UTC, it may represent yesterday or tomorrow relative to the user's local timezone (e.g., if a user in UTC-5 is accessing the app late in the evening, the UTC date will already be the next day, making "today" an invalid selection).

Using new Date().toLocaleDateString('en-CA') is a robust and concise way to get the current date formatted as YYYY-MM-DD in the user's local timezone.

Suggested change
<TextField variant="filled" size="small" type="date" label="Start date" value={startDate} onChange={(e) => onStartDateChange(e.target.value)} InputLabelProps={{ shrink: true }} inputProps={{ min: new Date().toISOString().split('T')[0] }} sx={{ flex: 1 }} />
<Typography variant="body2" color="text.disabled"></Typography>
<TextField variant="filled" size="small" type="date" label="End date" value={endDate} onChange={(e) => onEndDateChange(e.target.value)} InputLabelProps={{ shrink: true }} inputProps={{ min: startDate || new Date().toISOString().split('T')[0] }} sx={{ flex: 1 }} />
<TextField variant="filled" size="small" type="date" label="Start date" value={startDate} onChange={(e) => onStartDateChange(e.target.value)} InputLabelProps={{ shrink: true }} inputProps={{ min: new Date().toLocaleDateString('en-CA') }} sx={{ flex: 1 }} />
<Typography variant="body2" color="text.disabled"></Typography>
<TextField variant="filled" size="small" type="date" label="End date" value={endDate} onChange={(e) => onEndDateChange(e.target.value)} InputLabelProps={{ shrink: true }} inputProps={{ min: startDate || new Date().toLocaleDateString('en-CA') }} sx={{ flex: 1 }} />

<TextField
variant="filled" size="small" fullWidth type="datetime-local"
label={isRecurring ? 'Next occurrence' : 'Scheduled date & time'}
value={scheduledFor ? new Date(scheduledFor).toISOString().slice(0, 16) : ''}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using .toISOString().slice(0, 16) directly on a UTC date string to populate a datetime-local input causes a timezone shift. The datetime-local input expects the value in local time format (YYYY-MM-DDTHH:mm), but .toISOString() returns it in UTC. This causes the input to display the wrong time (shifted by the timezone offset) and subsequently save an incorrect time back to the server.

Adjusting the date by the local timezone offset before converting to ISO string ensures the correct local time is displayed.

Suggested change
value={scheduledFor ? new Date(scheduledFor).toISOString().slice(0, 16) : ''}
value={scheduledFor ? new Date(new Date(scheduledFor).getTime() - new Date(scheduledFor).getTimezoneOffset() * 60000).toISOString().slice(0, 16) : ''}

@kittwebhook kittwebhook left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @lvaheykitman, can you please make sure to link a Jira issue on this PR?

@kittwebhook

Copy link
Copy Markdown

I wasn't able to update Design_Prototyping_Kit#54 - check if the bots team has write access to it?

@kittwebhook

Copy link
Copy Markdown

Hi @lvaheykitman, can you please make sure to link a Jira issue on this PR?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants