30 issues covering key gaps in the frontend. Ready to copy into GitHub Issues.
Status Audit (2026-07-26) — many of the issues listed below are now resolved in
mainbut were never reflected here. The audit below maps each surviving item after thechore(issues): resolve 12 of 30 ISSUES.md items (#1, #11-17, #22, #24, #28, #29)bulk-deploy commit to either ✅ RESOLVED in code (with a path pointer) or 🟡 OPEN (AC still not met). The audit is intentionally terse — see the individual issue blocks for full AC and resolution context.
# Title Status in main#1 Scout Public Profile Page ✅ resolved (per bulk-deploy commit) #2 ContactModal Component ✅ resolved — components/scout/ContactModal.tsx(96 LOC), wired intoapp/[locale]/player/[id]/page.tsx#3 Wire Validator Dashboard ✅ resolved — app/[locale]/validator/page.tsxrendersValidatorPlayerSearch,ApproveForm,RevokeForm#4 Trial Offer UI ✅ resolved — app/[locale]/player/[id]/page.tsximportsTrialOfferForm, gated oncanLogTrialOffer#5 PWA Raster Icons ✅ resolved — public/icons/{icon-16x16,icon-32x32,icon-192x192,icon-512x512,icon-maskable-512x512}.pngpresent;manifest.jsonicons[]covers all sizes#6 Pay-to-Contact on Player Profile ✅ resolved — app/[locale]/player/[id]/page.tsxusesusePayToContact(notbuildPayToContact) and triggersContactModalon success#7 Scout Dashboard Subscription Guard ✅ resolved — components/scout/ScoutDashboardContent.tsxcallsuseRequireSubscription()which redirects to/scout/subscribe?reason=…#8 useTrialOfferHook✅ resolved — exists at hooks/useTrialOffer.ts(multi-line file)#9 Player Profile: IPFSMediaGallery✅ resolved — app/[locale]/player/[id]/page.tsxrenders<IPFSMediaGallery cids={...} />#10 Player Dashboard: PlayerProfileForm/MilestoneTimeline✅ resolved — app/[locale]/player/page.tsxcomposes both components#11 Scout Dashboard: Selectcomponent✅ resolved (per bulk-deploy commit) #12 Add useScoutProfileHook✅ resolved (per bulk-deploy commit) #13 SEP-10 Session: Expired Cookies ✅ resolved (per bulk-deploy commit) #14 Albedo Wallet: isInstalledcheck✅ resolved (per bulk-deploy commit) #15 i18n: Validator / Admin page strings ✅ resolved (per bulk-deploy commit) #16 Improve CI: Add Build Step ✅ resolved (per bulk-deploy commit) #17 NEXT_PUBLIC_ADMIN_ADDRESSValidation✅ resolved (per bulk-deploy commit) #18 Nationality field registration ✅ resolved — components/player/PlayerOnboardingWizard.tsxexposesnationalityRef+ form inputid="wizard-nationality"#19 Loading skeletons on Player Profile ✅ resolved — app/[locale]/player/[id]/page.tsxrenders<PlayerProfileSkeleton ...>duringplayerLoading#20 Scout Subscription: current status ✅ resolved — app/[locale]/scout/subscribe/page.tsxbanner +ScoutDashboardContent.tsxstorefront banner#21 MilestoneListvsMilestoneTimeline✅ resolved (previously in-file ✅ RESOLVED) #22 robots.txtdisallow/adminand/api✅ resolved (per bulk-deploy commit) #23 Error Boundary: Recovery UI ✅ partially — <ErrorBoundary>fallback exists atcomponents/ui/ErrorBoundary.tsx; production-mode detail suppression verified by__tests__/components/ErrorBoundary.test.tsx#24 ContractPausedBannerin App Layout✅ resolved — app/layout.tsxrenders all three banners betweenToastProviderand<main>#25 Scout Dashboard: EmptyState✅ resolved — ScoutDashboardContent.tsxuses<EmptyState>for both grid and wallet-search "not found" states#26 Test Coverage: scout / player hooks 🟡 OPEN — __tests__/hooks/useScout.test.ts,usePayToContact.test.ts,usePlayer.test.ts,useValidator.test.tsare missing#27 Test Coverage: page-level components 🟡 OPEN — __tests__/components/ScoutDashboard.test.tsx,PlayerDashboard.test.tsx,PlayerProfile.test.tsxare missing#28 aria-liveon Toast✅ resolved — components/ui/Toast.tsxuses aria-live + role="status" (per bulk-deploy commit)#29 Indexer HTTP /metricsendpoint✅ resolved (per bulk-deploy commit) #30 Local Development end-to-end docs 🟡 OPEN — DEVELOPMENT.mdhas the step-by-step guide and links fromCONTRIBUTING.md, but the "common errors and their solutions" subsection is sparseNet: 28 resolved, 2 open (#26, #27), plus #23 partial and #30 sparse. The bulk-deploy commit did the heavy lifting on UI-level / config-level items; the remaining gaps are scoped specifically to test coverage.
Labels: feature, scout
The route app/[locale]/scout/[id]/page.tsx does not exist. ScoutProfileCard links to /scout/${scout.id} but the destination is a blank folder with only a .gitkeep.
-
app/[locale]/scout/[id]/page.tsxrenders a full scout public profile - Displays
ScoutProfileCardwith name, organisation, subscription tier, and expiry - Shows contacted-players count and trial-offers count (via
fetchScoutStats) - Shows
ActivityFeedscoped to this scout's events - Page is accessible without wallet connection (read-only view)
- 404-style
EmptyStateshown if scout ID is not found
ScoutProfileCard and ActivityFeed are already built in components/scout/. The page only needs to fetch the scout by ID from the backend API and assemble these components.
Labels: feature, scout, ui
After a scout calls pay_to_contact, the unlocked ContactDetails (email, phone, telegram) have nowhere to display. The usePayToContact hook is complete; the UI modal is missing.
-
components/scout/ContactModal.tsxcreated using the existingModalprimitive - Displays email, phone, and/or telegram fields from
ContactDetails - Shows a loading spinner while the contract call is in-flight
- Shows a mapped error message on failure (subscription expired, insufficient fee, contract paused)
- "Copy" button for each contact field
- Modal is triggered from the player public profile page (
/player/[id])
usePayToContact already handles subscription pre-check, error mapping, and contract invocation. The modal only needs to render its { loading, error } state and the returned ContactDetails.
Labels: feature, validator
app/[locale]/validator/page.tsx is a placeholder — it shows a welcome message but never renders ApproveForm, RevokeForm, or ValidatorPlayerSearch. All three components are fully built.
- Page fetches the connected wallet's validator status via
checkIsValidator - Shows an
EmptyStatewith an explanation if the wallet is not an authorised validator - Renders
ValidatorPlayerSearchto look up players by ID or wallet - Renders
ApproveFormandRevokeFormwhen a player is selected - Milestone actions show
TransactionStatusfeedback
Labels: feature, scout
The log_trial_offer contract function exists and is documented, but there is no frontend UI. Scouts cannot advance a player to Level 3 (Elite Tier) without CLI access to the contract.
-
useTrialOfferhook created wrappinglog_trial_offercontract call - A "Log Trial Offer" button/section added to the player public profile page (visible to scouts with active subscriptions)
- A
TrialOfferModal(or inline form) captures required offer details - On success,
ProgressBarre-fetches and shows Level 3 -
TransactionStatuscomponent used for loading/success/error feedback
Labels: chore, pwa
public/manifest.json references PNG icons at multiple sizes (e.g. 192x192, 512x512) that do not exist. generate-icons.js and generate-icons.py scripts are present but have not been run. The PWA install prompt will fail without them.
- Run
node scripts/generate-icons.jsand commit the generated PNGs topublic/icons/ -
manifest.jsoniconsarray entries all resolve to existing files -
maskableicon variant included for Android adaptive icons - CI step added to verify icons exist
Labels: feature, scout, player
app/[locale]/player/[id]/page.tsx calls buildPayToContact (the unsigned builder), not the full payToContact function from usePayToContact. After the transaction the contact details are never surfaced to the UI.
- Player profile page uses
usePayToContacthook instead of rawbuildPayToContact - After successful payment,
ContactModal(Issue #2) opens with the returnedContactDetails - Button label shows dynamic fee from
PLATFORM_CONTACT_FEE_XLMenv var - Button disabled / hidden when wallet is not connected or scout has no active subscription
Labels: bug, scout
app/[locale]/scout/layout.tsx contains a subscription guard, but its redirect behaviour needs validation. Scouts without an active subscription should be redirected to /scout/subscribe rather than seeing an empty or broken dashboard.
-
useSubscriptioncalled in layout or top of page to check subscription expiry - Expired/absent subscription redirects to
/scout/subscribewith a toast explaining why - Active subscribers proceed normally
- Guard does not flash the dashboard before redirecting
Labels: feature, hooks
No hook wraps log_trial_offer. All other write flows have dedicated hooks (useSubscription, usePayToContact, useValidator). Consistent patterns make the dashboard composable.
-
hooks/useTrialOffer.tscreated following the pattern ofusePayToContact - Builds and submits a
log_trial_offertransaction viasignAndSubmit - Returns
{ submit, loading, error }tuple - Error messages mapped from contract error codes (10 = Unauthorized, 9 = ContractPaused)
- Unit test added under
__tests__/hooks/useTrialOffer.test.ts
Labels: enhancement, player
app/[locale]/player/[id]/page.tsx renders an avatar <img> directly with a hardcoded IPFS gateway URL. The IPFSMediaGallery component already handles gateway URL construction, lazy loading, and video/image detection, but is unused here.
- Replace the raw
<img>in the player profile page withIPFSMediaGallery - Gallery renders both image and video highlights from
player.ipfsHash - Falls back gracefully if
ipfsHashis empty - No hardcoded gateway URLs remain in page components
Labels: enhancement, player
app/[locale]/player/page.tsx contains an inline registration form with plain <input> elements and a hand-rolled milestone list. PlayerProfileForm, UpdateProfileForm, and MilestoneTimeline are fully built components that are never used by the dashboard.
- Replace the inline registration form with
PlayerProfileForm - Replace the inline milestone
<ul>withMilestoneTimeline - When player already has a profile, show
UpdateProfileFormbelow the timeline - Dashboard
page.tsxis reduced to composition of components only (no inline form logic)
Labels: enhancement, scout, ui
app/[locale]/scout/page.tsx uses raw <select> elements for region, position, and level filters. The shared components/ui/Select.tsx primitive exists to provide consistent styling and accessibility but is not used here.
- Region, position, and min-level selects replaced with
<Select>fromcomponents/ui - Filter form visual appearance is consistent with the rest of the UI
- All existing filter logic remains unchanged
Labels: feature, hooks, scout
The upcoming scout public profile page (/scout/[id]) will need to fetch a scout's data from the backend API. There is no useScoutProfile hook equivalent to usePlayer for scouts.
-
hooks/useScoutProfile.tscreated to fetch a scout by ID from the backend API - Returns
{ scout, loading, error }tuple - SWR or
useEffect-based fetching consistent withusePlayerpattern - Unit test added under
__tests__/hooks/useScoutProfile.test.ts
Labels: bug, auth
When a SEP-10 session cookie expires the restoreSession call in WalletContext fails silently. The user sees the app in an unauthenticated state with no explanation.
- On session restore failure,
WalletContextclears stored provider fromlocalStorage - Navbar / wallet button reflects unauthenticated state immediately
- A non-blocking toast informs the user their session expired and they need to reconnect
- No console errors thrown to the user
Labels: bug, auth, wallet
ADAPTERS.albedo.isInstalled checks 'albedo' in window which is unreliable — Albedo works via a popup redirect and does not inject a window.albedo object in all browsers. Users selecting Albedo who don't have it installed get a confusing error.
- Albedo adapter's
isInstalledalways returnstrue(Albedo is web-based, no extension required) - If the Albedo popup is blocked or the user cancels, the error is caught and surfaced as a user-friendly toast
- Unit test updated to reflect new behaviour
Labels: i18n, enhancement
messages/en.json, fr.json, and sw.json contain translations for player and scout flows but the validator and admin pages use hardcoded English strings.
- All user-visible strings in validator dashboard and admin panel extracted to
messages/ - French (
fr.json) and Swahili (sw.json) translations provided for each new key - No hardcoded UI strings remain in
app/[locale]/validator/orapp/[locale]/admin/
Labels: ci, chore
.github/workflows/ci.yml runs lint, test, and env validation but does not run next build. A broken build can be merged without CI catching it.
-
npm run buildstep added to CI after lint and tests - Build output cached between runs where possible
- CI fails fast if
next buildexits non-zero
Labels: chore, config
app/[locale]/admin/page.tsx reads process.env.NEXT_PUBLIC_ADMIN_ADDRESS to restrict access. If this variable is empty (as it is in .env.example) any connected wallet can access admin functions.
-
scripts/validate-env.jswarns (or errors) ifNEXT_PUBLIC_ADMIN_ADDRESSis empty - Admin page shows a clear error message (not a blank screen) when the env var is not configured
-
.env.examplecomment explains this variable is required for production
Labels: bug, player
PlayerVitals has a nationality field but the inline registration form in app/[locale]/player/page.tsx passes nationality: '' unconditionally. The field is absent from the UI entirely.
- Nationality input added to
PlayerProfileForm(and the inline form if not yet replaced by Issue #10) - Nationality value passed correctly to
buildRegisterPlayer - Field validated as non-empty before submission
Labels: enhancement, ux, player
app/[locale]/player/[id]/page.tsx shows a plain "Loading…" text while fetching player data. PlayerCardSkeleton exists but is only used in the scout dashboard grid.
- Replace
"Loading…"text with a skeleton layout matching the profile page structure - Skeleton covers the avatar area, name/stats row, progress bar, and milestone list
- No layout shift when real data loads
Labels: enhancement, scout
app/[locale]/scout/subscribe/page.tsx has the tier selection and payment flow but does not show the scout's current subscription status before they pay. A scout with an active Pro subscription could accidentally repurchase.
-
useSubscriptioncalled on page load to fetch current subscription - If an active subscription exists, a banner shows tier name and expiry date
- Current tier card is visually highlighted in the tier grid
- "Renew" vs "Subscribe" CTA label changes depending on status
Labels: refactor, player
Both MilestoneList and MilestoneTimeline render a player's milestones. MilestoneTimeline is more featureful (timestamps, validator address, animated transitions) but the player dashboard uses a hand-rolled list. There is risk of milestone display diverging.
MilestoneList provided no distinct UX — it was a plain <ul> with no timestamps, validator display, or expand/collapse. MilestoneTimeline is a strict superset. MilestoneList.tsx has been deleted and all milestone rendering is consolidated on MilestoneTimeline. The player dashboard (app/[locale]/player/page.tsx) uses MilestoneTimeline exclusively.
- Audit where each component is used
- Removed
MilestoneListand standardized all usages onMilestoneTimeline - No duplicate milestone rendering logic across the codebase
Labels: security, seo
public/robots.txt currently allows all crawlers everywhere. Admin and API routes should not be indexed by search engines.
-
Disallow: /adminadded torobots.txt -
Disallow: /api/added torobots.txt -
Disallow: /[locale]/adminpattern added for all locale-prefixed admin routes -
next-sitemap.config.jsexcludes these paths from the generated sitemap
Labels: enhancement, ux
app/[locale]/player/page.tsx and app/[locale]/scout/page.tsx wrap content in <ErrorBoundary>, but the validator dashboard wraps in ErrorBoundary without surfacing a useful recovery UI — it just shows a generic fallback.
- All dashboard pages consistently wrap their content in
<ErrorBoundary> -
ErrorBoundaryfallback includes a "Try again" button that resets the boundary - Error details are not shown to end users in production
Labels: enhancement, ux
useContractHealth, useIsPaused, and ContractPausedBanner all exist but the banner is not rendered anywhere in the app layout. Users may attempt transactions while the contract is paused and receive confusing errors.
-
ContractPausedBannerrendered inapp/layout.tsxorapp/[locale]/layout.tsx - Banner is only visible when
useIsPausedreturnstrue - Banner links to a status explanation or Discord
- Banner does not cause layout shift when contract is unpaused
Labels: enhancement, ux, scout
app/[locale]/scout/page.tsx shows "No players found. Try adjusting your filters." as plain text. The shared EmptyState component exists for exactly this purpose.
- Replace inline "No players found" text with
<EmptyState>component -
EmptyStateincludes an icon, headline, subtext, and a "Reset Filters" CTA - Same treatment applied to the wallet-search "not found" state
Labels: testing
__tests__/hooks/ covers useWallet, useSubscription, and useDebounce. useScout, usePayToContact, usePlayer, and useValidator have no tests. Scout discovery and payment are core flows.
-
__tests__/hooks/useScout.test.ts— covers filter call, loading state, error state -
__tests__/hooks/usePayToContact.test.ts— covers successful unlock, subscription expired error, contract paused error -
__tests__/hooks/usePlayer.test.ts— covers player found, player not found, loading -
__tests__/hooks/useValidator.test.ts— covers approve and revoke happy paths
Labels: testing
Component tests cover UI primitives and form components well, but no tests exist for the page-level components: ScoutDashboard, PlayerDashboard, or the public profile pages.
-
__tests__/components/ScoutDashboard.test.tsx— renders filter form, renders player grid, renders empty state -
__tests__/components/PlayerDashboard.test.tsx— renders registration form when no profile, renders milestone timeline when profile exists -
__tests__/components/PlayerProfile.test.tsx— renders player vitals, progress bar, and pay-to-contact button - Mocks for contract functions (
lib/contract) applied consistently
Labels: accessibility, ui
components/ui/Toast.tsx renders notification messages but they are not announced to screen readers. Users relying on assistive technology will miss transaction success/failure feedback.
- Toast container element has
aria-live="polite"andaria-atomic="true" - Error toasts use
aria-live="assertive" - Each toast has a unique, accessible label
-
@axe-core/react(already installed as a dev dependency) audit passes with no violations for toast interactions
Labels: feature, indexer
packages/indexer/src/metrics/IndexerMetrics.ts tracks event counts and processing latency, but there is no HTTP endpoint to expose these metrics. Without an observable endpoint, there is no way to monitor indexer health in production.
- A
/metricsor/healthHTTP endpoint added to the indexer service - Exposes Prometheus-compatible metrics or JSON equivalent: events processed, errors, lag
-
IndexerMetricswired into the endpoint response - README or inline docs describe how to run and observe the indexer
Labels: documentation
README.md covers contract deployment and individual commands but there is no clear sequence for getting the full local stack running: contracts on testnet → backend API → frontend → wallet connection. First-time contributors have to piece this together across DEPLOYMENT.md, CONTRIBUTING.md, and .env.example.
- A
DEVELOPMENT.md(or expandedREADME.mdsection) provides a step-by-step local setup guide - Covers: prerequisites (Node, Rust, Stellar CLI, Freighter),
.env.localsetup, testnet account funding via Friendbot, contract deployment, backend API startup, frontendnpm run dev - Common errors and their solutions documented (missing env vars, unfunded account, wrong network)
-
CONTRIBUTING.mdlinks to the new guide