Skip to content

Improved markets support - #41

Merged
damianlegawiec merged 2 commits into
mainfrom
feature/improved-markets
Mar 3, 2026
Merged

Improved markets support#41
damianlegawiec merged 2 commits into
mainfrom
feature/improved-markets

Conversation

@damianlegawiec

@damianlegawiec damianlegawiec commented Mar 3, 2026

Copy link
Copy Markdown
Member

Also update order currency and locale when switching markets

Summary by CodeRabbit

  • New Features

    • Country switcher now synchronizes cart currency and locale when changing regions for seamless checkout.
    • Checkout loads market-scoped country data for more accurate region-specific behavior.
  • Tests

    • Added coverage for order market updates and address/order update flows.
  • Chores

    • Updated dependency versions for improved compatibility.

Also update order currency and locale when switching markets
@coderabbitai

coderabbitai Bot commented Mar 3, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Market-scoped country/market handling added across the app: new markets data module; StoreContext now exposes enriched CountryWithMarket objects; checkout and country-switch flows resolve markets, update order market (currency/locale), and fetch market-specific countries.

Changes

Cohort / File(s) Summary
Dependency Updates
package.json
Bumped @spree/next (^0.6.1 → ^0.6.4) and @spree/sdk (^0.6.0 → ^0.6.3).
Markets API
src/lib/data/markets.ts
New server-side wrappers: getMarkets(), resolveMarket(country), getMarketCountries(marketId) delegating to @spree/next helpers.
Checkout Data & Tests
src/lib/data/checkout.ts, src/lib/data/__tests__/checkout.test.ts
Replaced updateAddresses usage with updateOrder; added updateOrderMarket(orderId, {currency, locale}) and tests for it.
Store Context
src/contexts/StoreContext.tsx
Added CountryWithMarket interface (currency, default_locale, marketId); StoreContext now fetches markets and exposes enriched CountryWithMarket[]; resolution and setters updated accordingly.
Checkout Page
src/app/[country]/[locale]/(checkout)/checkout/[id]/page.tsx
Reworked country loading to resolve market from URL (resolveMarket(urlCountry)), conditionally fetch market-scoped countries (getMarketCountries) and include market in order-loading flow; adjusted hook deps.
Country Switcher
src/components/layout/CountrySwitcher.tsx
Component now uses CountryWithMarket, integrates with cart (useCart), calls updateOrderMarket when currency/locale differ, refreshes cart before persisting navigation, and computes locale from market data.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant CountrySwitcher as CountrySwitcher
    participant Cart as Cart Context
    participant CheckoutAPI as Checkout Data (updateOrderMarket)
    participant Server as Spree Server

    User->>CountrySwitcher: Select country
    CountrySwitcher->>Cart: Read current order (orderId, currency, locale)
    alt Currency or locale differ
        CountrySwitcher->>CheckoutAPI: updateOrderMarket(orderId, {currency, locale})
        CheckoutAPI->>Server: _updateOrder (market/currency/locale)
        Server-->>CheckoutAPI: Updated order
        CheckoutAPI-->>CountrySwitcher: Updated order
        CountrySwitcher->>Cart: refreshCart()
        Cart->>Server: Fetch latest cart
        Server-->>Cart: Cart data
    end
    CountrySwitcher->>CountrySwitcher: Set cookies/state and navigate
Loading
sequenceDiagram
    participant CheckoutPage as Checkout Page
    participant StoreCtx as StoreContext
    participant MarketsAPI as Markets Module
    participant CountriesAPI as Markets Module (getMarketCountries)
    participant Server as Spree Server

    CheckoutPage->>MarketsAPI: resolveMarket(urlCountry)
    MarketsAPI->>Server: _resolveMarket(urlCountry)
    Server-->>MarketsAPI: Market object
    MarketsAPI-->>CheckoutPage: Market or null
    alt Market found
        CheckoutPage->>CountriesAPI: getMarketCountries(market.id)
        CountriesAPI->>Server: _listMarketCountries(marketId)
        Server-->>CountriesAPI: Market-scoped countries
        CountriesAPI-->>CheckoutPage: Countries list
    else Market not found
        CheckoutPage->>CheckoutPage: Use empty countries list
    end
    CheckoutPage->>StoreCtx: Load order with market context
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • Cichorek

Poem

🐰
I hopped through markets, small and grand,
I stitched new countries by gentle hand.
Currencies twirl, locales take flight,
Cart and country now dance just right.
Hop on — the markets gleam tonight! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Improved markets support' accurately reflects the main objective of updating order currency and locale when switching markets, which is the core change across multiple components and data modules.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/improved-markets

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/contexts/StoreContext.tsx (1)

186-188: Keep locale synchronized when setCountry is used.

setCountry currently updates country and currency only. Syncing locale from countryObj.default_locale avoids temporary mixed market state for consumers reading context before navigation completes.

🔧 Suggested adjustment
       if (countryObj) {
         setCurrency(countryObj.currency);
+        setLocaleState(countryObj.default_locale);
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/contexts/StoreContext.tsx` around lines 186 - 188, When updating country
in setCountry, also synchronize locale so consumers don't observe mismatched
market state: in the block where you check countryObj (the same place you call
setCurrency(countryObj.currency)), also call
setLocale(countryObj.default_locale) (or the equivalent state setter used in
this context) so country, currency and locale are updated together; ensure you
reference setCountry, setCurrency, setLocale and countryObj.default_locale in
the change.
src/lib/data/__tests__/checkout.test.ts (1)

125-137: Add the non-Error fallback case for updateOrderMarket.

The new suite covers success and Error rejection, but not the fallback branch for non-Error throws (actionResult behavior).

✅ Suggested test addition
   describe("updateOrderMarket", () => {
@@
     it("returns error on failure", async () => {
       mockUpdateOrder.mockRejectedValue(new Error("Currency not supported"));
@@
       expect(result).toEqual({
         success: false,
         error: "Currency not supported",
       });
     });
+
+    it("returns fallback message for non-Error throws", async () => {
+      mockUpdateOrder.mockRejectedValue("unexpected");
+
+      const result = await updateOrderMarket("order-1", {
+        currency: "EUR",
+        locale: "de",
+      });
+
+      expect(result).toEqual({
+        success: false,
+        error: "Failed to update order market",
+      });
+    });
   });
Based on learnings: Applies to src/lib/data/**/*.{test,spec}.{ts,tsx} : Test Server Actions independently with proper mocking of Spree SDK and cookie dependencies.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/data/__tests__/checkout.test.ts` around lines 125 - 137, Add a test
that covers the non-Error rejection branch of updateOrderMarket by making
mockUpdateOrder reject with a non-Error value (e.g.,
mockUpdateOrder.mockRejectedValue("NonErrorFailure") or an object) and assert
that updateOrderMarket("order-1", {currency: "XYZ", locale: "en"}) returns {
success: false, error: String(rejection) } (i.e., the actionResult fallback
handling of non-Error throws). Reference updateOrderMarket and mockUpdateOrder
in the new test so the fallback branch is exercised.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/`[country]/[locale]/(checkout)/checkout/[id]/page.tsx:
- Around line 193-197: The checkout flow currently treats a rejection from
getMarketCountries as a fatal error; wrap the await
getMarketCountries(market.id) call in its own try/catch (or use .catch) so
failures only affect countriesData and don’t propagate to the outer catch that
marks the entire checkout failed; on error set countriesData = { data: [] as
StoreCountry[] } and optionally log the error (referencing getMarketCountries
and the countriesData variable in page.tsx) so orderData can still be used when
market-country lookup fails.

In `@src/components/layout/CountrySwitcher.tsx`:
- Around line 55-61: The current flow ignores failures from updateOrderMarket
and proceeds to refreshCart and update local state, risking inconsistent
cart/market data; modify the handler that calls updateOrderMarket to await its
result and check its return (e.g., const result = await
updateOrderMarket(cart.id, { currency: newCurrency, locale: newLocale })), and
if result.success is falsy (or result.error exists) then stop the local market
switch: surface/log the error (or show user feedback), do not call refreshCart
or update cookies/navigation, and ensure exceptions from updateOrderMarket are
caught with try/catch so failures prevent the UI/state transition for
functions/variables referenced here (updateOrderMarket, refreshCart, cart,
newCurrency, newLocale).

---

Nitpick comments:
In `@src/contexts/StoreContext.tsx`:
- Around line 186-188: When updating country in setCountry, also synchronize
locale so consumers don't observe mismatched market state: in the block where
you check countryObj (the same place you call setCurrency(countryObj.currency)),
also call setLocale(countryObj.default_locale) (or the equivalent state setter
used in this context) so country, currency and locale are updated together;
ensure you reference setCountry, setCurrency, setLocale and
countryObj.default_locale in the change.

In `@src/lib/data/__tests__/checkout.test.ts`:
- Around line 125-137: Add a test that covers the non-Error rejection branch of
updateOrderMarket by making mockUpdateOrder reject with a non-Error value (e.g.,
mockUpdateOrder.mockRejectedValue("NonErrorFailure") or an object) and assert
that updateOrderMarket("order-1", {currency: "XYZ", locale: "en"}) returns {
success: false, error: String(rejection) } (i.e., the actionResult fallback
handling of non-Error throws). Reference updateOrderMarket and mockUpdateOrder
in the new test so the fallback branch is exercised.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c226cbf and 6968d45.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (7)
  • package.json
  • src/app/[country]/[locale]/(checkout)/checkout/[id]/page.tsx
  • src/components/layout/CountrySwitcher.tsx
  • src/contexts/StoreContext.tsx
  • src/lib/data/__tests__/checkout.test.ts
  • src/lib/data/checkout.ts
  • src/lib/data/markets.ts

Comment thread src/app/[country]/[locale]/(checkout)/checkout/[id]/page.tsx
Comment thread src/components/layout/CountrySwitcher.tsx

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (2)
src/contexts/StoreContext.tsx (1)

135-180: Consider moving store/market initialization to a Server Component to avoid useEffect-based initialization.

This useEffect block at lines 136–180 fetches and initializes critical app state (store, markets, country/locale resolution) on the client. Per guidelines, initialization should avoid useEffect and leverage server-driven patterns instead.

While the Server Actions (getStoreAction(), getMarketsAction()) are correctly used, the overall flow could be optimized by:

  • Converting the StoreProvider to a Server Component that fetches and initializes this data server-side
  • Passing pre-resolved store/market data and country/locale state as props to the provider
  • Using React 19's use() function to unwrap promises alongside Suspense boundaries for loading states

This would eliminate the client-side initialization race condition, improve performance, and align with the Next.js Server Component-first architecture used elsewhere in the codebase.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/contexts/StoreContext.tsx` around lines 135 - 180, The client-side
initialization inside useEffect (fetchData) uses getStoreAction,
getMarketsAction and resolveCountryAndCurrency to set store, countries, locale,
currency and cookies—move this logic to the server: convert the StoreProvider
into a Server Component that calls getStoreAction and getMarketsAction (or uses
React 19 use() to unwrap their promises within a Suspense boundary), perform
buildCountriesFromMarkets and resolveCountryAndCurrency server-side, and pass
the resolved store, countries, countryState, localeState and currency as props
into the provider so the client no longer runs the fetchData useEffect or sets
cookies/redirects there (keep router.replace/cookie-setting handled by
server-rendered redirect or minimal client effect only if unavoidable).
src/lib/data/__tests__/checkout.test.ts (1)

39-39: Use vi.mocked() for type-safe mock casting.

vi.mocked(updateOrder) is the idiomatic vitest pattern that preserves mock typing without any casts, aligning with strict TypeScript checking. The same pattern should be applied to all other mocks on lines 38–45.

♻️ Proposed fixes
-// eslint-disable-next-line `@typescript-eslint/no-explicit-any` -- test fixtures are intentionally partial
-const mockGetCheckout = getCheckout as any;
-const mockUpdateOrder = updateOrder as any;
-const mockAdvance = advance as any;
-const mockGetShipments = getShipmentsSdk as any;
-const mockSelectShippingRate = selectShippingRateSdk as any;
-const mockApplyCoupon = applyCoupon as any;
-const mockRemoveCoupon = removeCoupon as any;
-const mockComplete = complete as any;
+const mockGetCheckout = vi.mocked(getCheckout);
+const mockUpdateOrder = vi.mocked(updateOrder);
+const mockAdvance = vi.mocked(advance);
+const mockGetShipments = vi.mocked(getShipmentsSdk);
+const mockSelectShippingRate = vi.mocked(selectShippingRateSdk);
+const mockApplyCoupon = vi.mocked(applyCoupon);
+const mockRemoveCoupon = vi.mocked(removeCoupon);
+const mockComplete = vi.mocked(complete);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/data/__tests__/checkout.test.ts` at line 39, The tests cast mocks
with unsafe any (e.g., const mockUpdateOrder = updateOrder as any); replace
these with Vitest's type-safe helper by using vi.mocked(updateOrder) and
similarly use vi.mocked(...) for all other mock variables in the same block (the
mocks defined around updateOrder) so mock typing is preserved under strict
TypeScript; update references to mockUpdateOrder accordingly to rely on the
typed mock API.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/contexts/StoreContext.tsx`:
- Around line 135-180: The client-side initialization inside useEffect
(fetchData) uses getStoreAction, getMarketsAction and resolveCountryAndCurrency
to set store, countries, locale, currency and cookies—move this logic to the
server: convert the StoreProvider into a Server Component that calls
getStoreAction and getMarketsAction (or uses React 19 use() to unwrap their
promises within a Suspense boundary), perform buildCountriesFromMarkets and
resolveCountryAndCurrency server-side, and pass the resolved store, countries,
countryState, localeState and currency as props into the provider so the client
no longer runs the fetchData useEffect or sets cookies/redirects there (keep
router.replace/cookie-setting handled by server-rendered redirect or minimal
client effect only if unavoidable).

In `@src/lib/data/__tests__/checkout.test.ts`:
- Line 39: The tests cast mocks with unsafe any (e.g., const mockUpdateOrder =
updateOrder as any); replace these with Vitest's type-safe helper by using
vi.mocked(updateOrder) and similarly use vi.mocked(...) for all other mock
variables in the same block (the mocks defined around updateOrder) so mock
typing is preserved under strict TypeScript; update references to
mockUpdateOrder accordingly to rely on the typed mock API.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6968d45 and f2bb3d0.

📒 Files selected for processing (4)
  • src/app/[country]/[locale]/(checkout)/checkout/[id]/page.tsx
  • src/components/layout/CountrySwitcher.tsx
  • src/contexts/StoreContext.tsx
  • src/lib/data/__tests__/checkout.test.ts

@damianlegawiec
damianlegawiec merged commit cd8136e into main Mar 3, 2026
4 checks passed
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.

1 participant