feat: Leaderboard sharing and embed widget (Closes #613)#788
feat: Leaderboard sharing and embed widget (Closes #613)#788JosephOladele wants to merge 2 commits into
Conversation
Adds lib/leaderboard.ts (getRankedLeaderboard, computeLeaderboardStats, findPlayerRank, getHuntSummary) as the single source of truth for rank derivation, plus lib/siteUrl.ts for resolving the canonical absolute base URL. Covered by 12 passing unit tests. Foundation for Samuel1-ona#613 leaderboard sharing and embed widget.
package.json was not valid JSON (missing commas after the test:mutation script and the @stryker-mutator/vitest-runner devDependency), which broke npm install and all test runs. Refreshes package-lock.json accordingly.
|
Heads up: main currently doesn't compile or boot. package.json is invalid JSON. Missing commas after the test:mutation script entry and after the @stryker-mutator/vitest-runner devDependency. This breaks npm install entirely. Items 5 and 6 look like leftovers from an in progress data layer refactor. I'm happy to pull these fixes into a dedicated "fix: repair broken main build" PR if you'd rather keep them out of the #613 feature PR. Just let me know which you prefer. |
Summary
Implements leaderboard sharing and an embeddable widget for #613.
All ranking logic flows through a single server-safe module (
lib/leaderboard.ts) so the OG image, public page, embed widget, and existing API stay consistent. Onenext/ogroute powers both the shareable board image and the personal "share my rank" card.What's included
lib/leaderboard.ts— server-safe ranking core:getRankedLeaderboard(sorts by points desc, standard competition ranking for ties),computeLeaderboardStats,findPlayerRank(case-insensitive),getHuntSummary(graceful title fallback). Covered by 12 unit tests.lib/siteUrl.ts— canonical absolute base-URL helper.app/api/og/leaderboard/[id]/route.tsx—next/ogimage (1200×630). Board summary by default; personal rank card when?address=matches a player.app/leaderboard/[id]/page.tsx— public, crawlable leaderboard page with full OG/Twitter metadata viagenerateMetadata.app/embed/leaderboard/[id]/page.tsx— chrome-less widget intended for cross-origin<iframe>embedding.components/ShareLeaderboard.tsx— "share my rank as image" (address input) + copy-to-clipboard<iframe>embed snippet.next.config.ts— carves/embed/*out of the globalX-Frame-Options: DENYso the widget can be framed.Incidental build fixes
While building this, I found
maindid not compile or boot. These are pre-existing issues unrelated to the feature, fixed here so the app runs (happy to split into a separate PR if preferred):package.json— invalid JSON (missing commas) brokenpm install.@testing-library/dompeer dep (whole test suite was failing to import).app/layout.tsx— duplicatePageSkeleton/PageTransitionWrapperimports.app/layout.tsx— missingheadersimport fromnext/headers.app/providers.tsx— missingqueryCachePolicyimport from@/lib/queryKeys.components/Header.tsx— undefinedMOCK_NOTIFICATIONSreference.Testing
npm test -- lib/__tests__/leaderboard.test.ts→ 12 passing.Follow-ups (not in this PR)
app/api/v1/hunts/[id]/leaderboard/route.tsfor third-party JSON consumers.middleware.tsCSPframe-ancestorscarve-out for/embed/*(next.config handlesX-Frame-Options; middleware CSP still needs the matching exception for full cross-origin embedding).Note for maintainers
package-lock.jsonwas regenerated by npm (the repo's lockfile appeared pnpm-based). Let me know if you'd prefer I revert the lockfile and you regenerate with pnpm.Closes #613