Skip to content

refactor: centralize error mapping and add shared error notice#120

Open
juanmahidalgo wants to merge 2 commits into
fix/shop-i18nfrom
refactor/shop-error-architecture
Open

refactor: centralize error mapping and add shared error notice#120
juanmahidalgo wants to merge 2 commits into
fix/shop-i18nfrom
refactor/shop-error-architecture

Conversation

@juanmahidalgo

Copy link
Copy Markdown
Collaborator

Stacked on #117 (i18n) — base branch is fix/shop-i18n, so this diff is error-handling only. Retarget to main once #117 merges.

Changes

Replaces the scattered, duplicated error handling with a small shared foundation.

  • ~/lib/errors.ts — one place to map a thrown error to a safe, localized string. friendlyError(e, fallback, { sale }) handles the universal wallet/abort rejection, and (for purchase flows) funds/availability failures; anything unrecognized returns the caller's own context-specific fallback. It never returns raw backend text. Also exports the previously-duplicated isRejection / isInsufficient helpers.
  • ~/components/ErrorNotice.tsx (+ styles) — a consistent inline error banner (alert icon + tinted surface + role="alert") replacing the bare red <p className="error"> text that sat unstyled across the app. Renders nothing when there's no message.
  • Migrated the 5 duplicated per-file friendlyError copies (Cart, BuyModal, MarketCheckout, SellModal, PrimaryListModal) onto the shared helpers, and swapped ~15 raw error <p> render sites across pages + modals to <ErrorNotice>.
  • Removed the now-orphaned buyModal.error.soldOrRemoved / cantBuyOwn keys (folded into a shared errors.* namespace, en + es).

Cart and MarketCheckout keep a thin local mapping (plural "listing changed" copy / the "refreshing the market" sold-out copy respectively) on top of the shared helpers, since those messages are context-specific.

Notes

Test plan

  • tsc -b clean
  • vite build succeeds
  • vitest run — 651 tests pass
  • i18n parity: en/es identical (480 keys each)
  • Manual: trigger an error in each modal/page and confirm the ErrorNotice renders consistently

@vercel

vercel Bot commented Jul 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
shop Ready Ready Preview, Comment Jul 18, 2026 8:52am

Request Review

@decentraland-bot decentraland-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review: refactor: centralize error mapping and add shared error notice

Clean, well-motivated refactoring. The shared errors.ts module and ErrorNotice component are well-designed — the golden rule of never surfacing raw backend text is consistently followed, and the role="alert" on the banner is a nice a11y touch. The intentional local overrides in Cart and MarketCheckout (for context-specific plural/market copy) are well-documented and make sense.

Findings

All minor — nothing blocking merge.


P2 — MyAssets.tsx:93-97 still duplicates isRejection logic inline

This file was touched by the PR (error renders → ErrorNotice), but the catch block at line 93-97 still manually checks err.code === 4001 || msg.includes('reject') || msg.includes('denied') instead of calling the new isRejection(e). It also references the old getCredits.errorCanceled key instead of errors.rejected — both have identical copy today, but they'll drift when a translator updates only one.

Suggestion: Replace with isRejection(e) + t('errors.rejected') to complete the migration in this file.


P2 — Cart.tsx and MarketCheckout.tsx use ad-hoc casts instead of the shared ErrLike type

Both local friendlyError functions cast with (e as { message?: string }) rather than importing the ErrLike type from ~/lib/errors. Not a bug, but inconsistent with the centralization goal — if ErrLike grows a new field later, these won't pick it up.


P2 — ErrorNotice default export is unused (see inline comment)


P2 — Defensive guard against null/undefined throws

The as ErrLike cast in isRejection / isInsufficient / friendlyError would crash if the thrown value is literally null or undefined (accessing .code on null). In practice wallet SDKs and fetch always throw Error objects, and the old code had the same pattern, so this isn't a regression — but a one-line guard (typeof e === 'object' && e !== null ? (e as ErrLike) : {}) would make it bulletproof.


Security

No issues. friendlyError never surfaces raw error text — every path returns a t() key or a caller-supplied t() fallback. No dangerouslySetInnerHTML, no ReDoS risk, no hardcoded secrets, no XSS vectors.

CI

Build + test + e2e still in progress at time of review. Title validation passed ✅

Verdict

Approved — solid refactoring with good documentation. The P2 items are all follow-up material; none blocks merge.


Reviewed by Jarvis 🤖 · Requested by juanmahidalgo via GitHub

)
}

export default ErrorNotice

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P2] All 11 consumers use the named import { ErrorNotice } — this default export is unused. Consider removing it to keep a single canonical import path.

Comment thread app/src/pages/Cart.tsx
return t('getCredits.errorCanceled')
}
if (isRejection(e)) return t('errors.rejected')
const msg = ((e as { message?: string }).message ?? '').toLowerCase()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P2] Nit: this inline cast (e as { message?: string }) duplicates the ErrLike type from ~/lib/errors (which is already imported). Using ErrLike would keep the typing consistent and pick up any future field additions.

Same applies to MarketCheckout.tsx:25.

(Would need to export the type: export type { ErrLike } from errors.ts)

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