Conversation
- New participant page at /mapathons/[id]/participant/[userId] with SSR data fetching, dynamic OG metadata, hero section with avatar, editable personal message, mapathon details, progress bar, and Join CTA - New client component for interactive features: personal message editing (localStorage), share buttons (SMS, Email, Facebook, X, LinkedIn, Copy Link) - New share redirect page at /share/mapathon/[id]/participant/[userId] with rich OG/Twitter metadata for social crawlers - Updated mapathon detail page: participants and organizers now link to their personal pages, avatars displayed with fallback
Use strict equality checks and reset hasPermanentRamp to null so selecting "No" to "Is there a step?" cleanly hides the 1+/2+ Steps and ramp questions without polluting the submitted review with fabricated answers.
There was a problem hiding this comment.
Pull request overview
Adds participant profile pages for mapathons (including share-friendly URLs/metadata) and updates existing mapathon detail UI to link to these new participant pages.
Changes:
- Added a new participant page under
/mapathons/[id]/participant/[userId]with dynamic social metadata and a client-side sharing UI. - Added a share/redirect route under
/share/mapathon/[id]/participant/[userId]to provide OG/Twitter metadata for participant links. - Updated mapathon detail page lists (organizers/participants) to link to the participant page and show avatars.
- Tweaked review Step1 conditional flow for steps/ramp questions.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/components/addReview/CreateReview/Step1.tsx | Adjusts Step1 tri-state logic and conditional rendering for steps-related questions. |
| src/app/share/mapathon/[id]/participant/[userId]/page.tsx | New share page that generates participant-specific metadata and redirects to the canonical participant page. |
| src/app/(main)/mapathons/[id]/participant/[userId]/ParticipantPageClient.tsx | New client UI for participant messaging + social share actions. |
| src/app/(main)/mapathons/[id]/participant/[userId]/page.tsx | New server page for participant view + metadata generation and participant validation. |
| src/app/(main)/mapathons/[id]/page.tsx | Makes organizer/participant cards link to the new participant page and renders avatars. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| ...prev, | ||
| steps: e, | ||
| hasPermanentRamp: !e ? false : null, | ||
| hasPermanentRamp: null, |
There was a problem hiding this comment.
When the user answers "Is there a step?" with false, this now resets hasPermanentRamp to null. Previously it was forced to false when steps was false, and the review submit payload spreads step1 directly into the API body—so this change alters the submitted semantics from an explicit “no ramp” to “unknown”. If the backend/analytics rely on false when there are no steps, please restore the conditional reset (false when steps=false, null when steps=true) to avoid regressions in stored data.
| hasPermanentRamp: null, | |
| hasPermanentRamp: e === false ? false : null, |
| const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "https://axsmap.com"; | ||
| const imageUrl = `${baseUrl}/axsmap-logo-share.png`; | ||
| const shareUrl = `${baseUrl}/share/mapathon/${params.id}/participant/${params.userId}`; | ||
| const actualUrl = `${baseUrl}/mapathons/${params.id}/participant/${params.userId}`; |
There was a problem hiding this comment.
actualUrl is computed in generateMetadata but never used. This adds dead code and may be flagged by linting; consider removing it or using it (e.g., if you intended to reference the canonical participant URL in metadata).
| const actualUrl = `${baseUrl}/mapathons/${params.id}/participant/${params.userId}`; |
| dangerouslySetInnerHTML={{ | ||
| __html: ` | ||
| setTimeout(function() { | ||
| window.location.href = '${actualUrl}'; | ||
| }, 1000); |
There was a problem hiding this comment.
The redirect script uses dangerouslySetInnerHTML and interpolates actualUrl inside a quoted JS string. Since params.id / params.userId originate from the URL, a crafted value containing quotes could break out of the string and enable script injection. Please avoid string interpolation inside raw script HTML here (e.g., use Next's Script component with a safely-serialized value like JSON.stringify(actualUrl), or perform the redirect in a small client component/useEffect).
| const shareText = `Support my accessibility Mapathon: ${shareUrl || pageUrl}`; | ||
|
|
||
| const handleCopyLink = async () => { | ||
| try { | ||
| await navigator.clipboard.writeText(shareUrl || pageUrl); | ||
| setCopied(true); | ||
| setTimeout(() => setCopied(false), 2000); | ||
| } catch { | ||
| // Fallback | ||
| const textarea = document.createElement("textarea"); | ||
| textarea.value = shareUrl || pageUrl; |
There was a problem hiding this comment.
The editable personalMessage isn’t used in the share payload: shareText is always the hard-coded “Support my accessibility Mapathon: ”. As a result, changing/saving the personal message has no effect on SMS/email/social share content. If the intent is to let participants customize what gets shared, incorporate personalMessage into shareText (and related email/twitter text) so the edit feature is functional.
| const shareText = `Support my accessibility Mapathon: ${shareUrl || pageUrl}`; | |
| const handleCopyLink = async () => { | |
| try { | |
| await navigator.clipboard.writeText(shareUrl || pageUrl); | |
| setCopied(true); | |
| setTimeout(() => setCopied(false), 2000); | |
| } catch { | |
| // Fallback | |
| const textarea = document.createElement("textarea"); | |
| textarea.value = shareUrl || pageUrl; | |
| const resolvedShareUrl = shareUrl || pageUrl; | |
| const trimmedPersonalMessage = personalMessage.trim(); | |
| const shareText = trimmedPersonalMessage | |
| ? `${trimmedPersonalMessage}\n\nSupport my accessibility Mapathon: ${resolvedShareUrl}` | |
| : `Support my accessibility Mapathon: ${resolvedShareUrl}`; | |
| const handleCopyLink = async () => { | |
| try { | |
| await navigator.clipboard.writeText(resolvedShareUrl); | |
| setCopied(true); | |
| setTimeout(() => setCopied(false), 2000); | |
| } catch { | |
| // Fallback | |
| const textarea = document.createElement("textarea"); | |
| textarea.value = resolvedShareUrl; |
| export default function ParticipantPageClient({ | ||
| mapathonId, | ||
| userId, | ||
| mapathonName, | ||
| mapathonAddress, | ||
| mapathonStartDate, |
There was a problem hiding this comment.
Several props are destructured but never used (mapathonAddress, mapathonStartDate, mapathonEndDate, mapathonReviewsGoal, placesMapped, userName). This is dead code and can trigger no-unused-vars lint warnings; consider removing unused props from ParticipantPageClientProps/destructuring (or prefix with _ if they’re intentionally reserved for a follow-up).
| mapathonAddress={mapathon.address} | ||
| mapathonStartDate={mapathon.startDate} | ||
| mapathonEndDate={mapathon.endDate} | ||
| mapathonReviewsGoal={mapathon.reviewsGoal} | ||
| placesMapped={placesMapped} | ||
| userName={userName} |
There was a problem hiding this comment.
ParticipantPageClient is currently passed a number of props that aren’t used in the client component (mapathonAddress, dates, goals, placesMapped, userName). Once the client component props are tightened up, these extra values should be removed here as well to keep the API surface minimal and avoid unused-prop churn.
| mapathonAddress={mapathon.address} | |
| mapathonStartDate={mapathon.startDate} | |
| mapathonEndDate={mapathon.endDate} | |
| mapathonReviewsGoal={mapathon.reviewsGoal} | |
| placesMapped={placesMapped} | |
| userName={userName} |
Use strict equality and reset hasAccessibleElevator/hasPortableRamp when "Are there multiple floors?" toggles, so selecting "No" cleanly hides the follow-up questions instead of relying on truthy evaluation.
No description provided.