Skip to main content

Architecture

Routing and localisation​

Two locales, English (default) and Spanish, served as path prefixes: /… and /es/…. src/routes/index.tsx mounts the same pageRoutes array twice under a shared Layout; there is no separate Spanish component tree.

  • Strings come from react-i18next namespaces in src/lib/i18n/locales/{en,es}/<namespace>.json (common, nav, footer, home, about, books, store, checkout, conferences, donate, resources, contact, news, gettingStarted, pureWarrior, pureWarriorAbout, account).
  • Locale detection: URL path (custom detector in src/lib/i18n/detect.ts), then localStorage, then the browser. LocaleProvider keeps i18n.language and <html lang> in sync with the URL.
  • LocalizedLink / LocalizedNavLink prefix /es for internal links and pass external, anchor and mail links through untouched.

Content sources​

Most marketing copy was moved off Sanity into static JSON and i18n files (snapshot and rationale in docs/content-extraction/README.md). Sanity now holds only siteSettings, past conference recaps, newsArticle and contactPage. Resources moved to the backend; events, products and funds always came from it.

Sanity access goes through getClient(preview) in src/lib/sanity/client.ts (published CDN client or stega-enabled draft client) and the useSanityQuery<T> hook, which returns a {status, data, error} union. GROQ queries live in src/lib/sanity/queries/.

Backend access​

Each src/lib/*.ts module builds ${VITE_EMAW_API_BASE_URL}/rest/... and uses fetch with the shared useAsync pattern. There is no global API client.

  • Auth (src/lib/auth.ts, src/lib/account/AuthProvider.tsx): access token in memory, refresh token in localStorage['emaw:refresh'] and sent in the body (the backend's HttpOnly cookie is third-party here). authFetch refreshes once on 401. optionalAuthHeaders() lets checkouts run signed in or as a guest.
  • Hand-off (src/lib/app.ts, OpenAppPage): /app is the only entry point into the web app; see Authentication.
  • Stripe (src/lib/stripe.ts): one loadStripe instance per locale; CHECKOUT_APPEARANCE and PAYMENT_ELEMENT_OPTIONS (card only; Apple Pay, Google Pay and Link off; billing fields from the site's own AddressFields). All three checkouts use @stripe/react-stripe-js/checkout Elements with a backend-issued client_secret.

State​

  • Cart: CartProvider in Layout, lines {slug, quantity} in localStorage, useCart().
  • Checkout steps for events and donations are kept in sessionStorage so a refresh resumes them (emaw:donation-checkout, 30 minutes).
  • Auth: AuthProvider context, useAuth(), useAfterSignIn() for ?next= routing.

Media​

Resource media URLs come fully formed from the backend (media.url, an R2 public URL). ResourceVideoPlayer renders an HTML5 <video> or a youtube-nocookie.com iframe. Cover images are Azure Blob URLs. The site has no R2 configuration.