Skip to content

(SP: 3) [SHOP] CP-01 commercial policy refactor: decouple locale, enforce UAH storefront, align checkout, and admin pricing cleanup#441

Merged
ViktorSvertoka merged 11 commits intodevelopfrom
lso/feat/shop-legal
Apr 2, 2026
Merged

(SP: 3) [SHOP] CP-01 commercial policy refactor: decouple locale, enforce UAH storefront, align checkout, and admin pricing cleanup#441
ViktorSvertoka merged 11 commits intodevelopfrom
lso/feat/shop-legal

Conversation

@liudmylasovetovs
Copy link
Copy Markdown
Collaborator

@liudmylasovetovs liudmylasovetovs commented Apr 1, 2026

Description

This PR implements Batch CP-01 — Commercial Policy Refactor on Stabilized Shop Baseline.

The goal of this batch is to remove the incorrect coupling between locale and commercial behavior (currency, payment providers, shipping), while preserving all stabilized shop flows.

Key outcome:

  • locale now controls language/content only
  • standard storefront is UAH across all locales
  • payment providers (Stripe, Monobank) are env/capability-driven, not locale-driven
  • checkout is server-authoritative and locale-agnostic
  • USD remains compatibility-only (not a business requirement)
  • no schema changes, no intl refactor, no regression in existing flows

Related Issue

Issue: #<issue_number>


Changes

PR-A — Policy memo + test repair

  • Added commercial policy decision document
  • Fixed stale tests blocked by LEGAL_CONSENT_REQUIRED
  • Ensured tests reach real assertions

PR-B — Centralized commercial policy

  • Introduced centralized policy layer:
    • commercial-policy.ts
    • commercial-policy.server.ts
  • Removed locale as source of truth for currency/provider logic

PR-C — Storefront read-path switch

  • Catalog, PDP, cart now use policy-based currency (UAH)
  • Product visibility no longer depends on USD rows
  • Monobank visibility no longer tied to locale
  • Shipping methods work on non-uk locales

PR-D — Checkout/server enforcement

  • Checkout no longer derives currency from locale
  • Orders persist UAH across all locales
  • Provider selection is locale-agnostic
  • Client cannot influence order currency
  • Snapshots and Monobank invariants preserved

PR-E — Acceptance & regression gate

  • Added targeted regression tests
  • Added acceptance checklist and report
  • Verified no regressions in stabilized flows

PR-F — Admin pricing cleanup

  • Enforced UAH as required currency in admin flows
  • USD remains optional compatibility only
  • Updated validation, services, and tests accordingly

Additional improvements

  • Unified UAH formatting across locales (uk-UA formatting everywhere)
  • Localized touched UI surfaces (ProductForm, Cart, Checkout UI)
  • Cleaned up test wording and reviewer-bait inconsistencies

Database Changes (if applicable)

  • Schema migration required
  • Seed data updated
  • Breaking changes to existing queries
  • Transaction-safe migration
  • Migration tested locally on Neon

No schema changes in this batch by design (CP-01 constraint)


How Has This Been Tested?

  • Tested locally
  • Verified in development environment
  • Checked responsive layout (if UI-related)
  • Tested accessibility (keyboard / screen reader)

Test strategy

  • Local Postgres only (guarded)
  • PowerShell-based execution
  • Targeted Vitest suites:
    • storefront read policy
    • checkout currency enforcement
    • provider visibility
    • shipping methods
    • admin pricing contract
    • currency formatting
  • No unnecessary wide test runs

Screenshots (if applicable)

  • UAH price formatting unified across locales
  • Admin product form localized
  • Cart and checkout UI localized

Checklist

Before submitting

  • Code has been self-reviewed
  • No TypeScript or console errors
  • Code follows project conventions
  • Scope is limited to this feature/fix
  • No unrelated refactors included
  • English used in code, commits, and docs
  • New dependencies discussed with team
  • Database migration tested locally (if applicable)
  • GitHub Projects card moved to In Review

Reviewers

Summary by CodeRabbit

  • New Features

    • Multi-language support (en/uk/pl) for the admin product form, checkout UI, and error page metadata.
  • Improvements

    • Storefront currency/results standardized to UAH across locales; checkout/payment option behavior made consistent.
    • Localized payment client copy, ARIA labels, and clearer validation/error messages for pricing and photo uploads.
    • Payment provider selection and availability now follow a unified storefront policy.
  • Documentation

    • Added commercial-policy guidance and acceptance checklist for storefront/currency behavior.

@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Apr 1, 2026

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
devlovers-net Ignored Ignored Preview Apr 1, 2026 11:36pm

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 1, 2026

Warning

Rate limit exceeded

@liudmylasovetovs has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 9 minutes and 10 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 9 minutes and 10 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 200c6f73-a64c-49d6-95db-75887bc96f97

📥 Commits

Reviewing files that changed from the base of the PR and between d6ac9e5 and aacffee.

📒 Files selected for processing (2)
  • frontend/app/[locale]/shop/checkout/error/page.tsx
  • frontend/lib/tests/shop/currency.test.ts
📝 Walkthrough

Walkthrough

Centralizes storefront commercial policy (UAH/UA defaults and provider capabilities), localizes admin and checkout UI, requires UAH pricing, delegates provider/currency resolution to server policy, updates checkout/cart/shipping routes, and adds extensive tests and docs reflecting these changes.

Changes

Cohort / File(s) Summary
Commercial policy core
frontend/lib/shop/commercial-policy.ts, frontend/lib/shop/commercial-policy.server.ts, frontend/lib/shop/checkout-display-currency.ts
New standardized storefront policy (UAH/UA), provider capability resolver, provider-candidate selection, and display-currency normalization.
Product form & i18n
frontend/app/[locale]/admin/shop/products/_components/ProductForm.tsx, frontend/messages/en.json, frontend/messages/uk.json, frontend/messages/pl.json
Switched UI strings to useTranslations; replaced legacy photo error constant with parameterized messages; added InvalidMoneyValueError and localized error branching; expanded product-form translations.
Cart / provider policy / capabilities
frontend/app/[locale]/shop/cart/CartPageClient.tsx, frontend/app/[locale]/shop/cart/provider-policy.ts, frontend/app/[locale]/shop/cart/capabilities.ts
Extracted provider/method logic to provider-policy, delegated capability checks to server resolver, and switched currency/country resolution to storefront policy.
Checkout payment UI & error page
frontend/app/[locale]/shop/checkout/payment/StripePaymentClient.tsx, frontend/app/[locale]/shop/checkout/error/page.tsx
Replaced hardcoded strings with translations, derived UI currency via storefront resolver, and made metadata generation localized.
API routes — checkout/cart/shipping
frontend/app/api/shop/checkout/route.ts, frontend/app/api/shop/cart/rehydrate/route.ts, frontend/app/api/shop/shipping/methods/route.ts, frontend/app/api/shop/shipping/np/cities/route.ts, frontend/app/api/shop/shipping/np/warehouses/route.ts
Replaced locale-derived currency/country with standard storefront resolvers for fallbacks and integrated providerCapabilities into checkout/provider gating.
Orders & payments services
frontend/lib/services/orders/checkout.ts, frontend/lib/shop/payments.ts
Checkout/provider resolution now uses commercial-policy helpers; storefrontCurrency enforced as checkout currency; provider candidate and method resolution delegated.
Product pricing & mutations
frontend/lib/services/products/mutations/create.ts, frontend/lib/services/products/mutations/update.ts, frontend/lib/services/products/prices.ts, frontend/lib/validation/shop.ts
Switched validation to require a UAH storefront row (added requiredCurrency option), introduced resolveLegacyCompatPriceMirror, removed requireUsd, moved price validation into transaction scope.
Data & locale helpers
frontend/lib/shop/currency.ts, frontend/lib/shop/locale.ts, frontend/lib/shop/data.ts
Delegated locale→currency/country resolution to commercial-policy; product data reads now use standard storefront currency and ignore locale where applicable.
Tests — policy, checkout, pricing, shipping, admin
frontend/lib/tests/shop/... (many files)
Added/updated comprehensive tests for commercial policy contract, capability delegation, UAH-first pricing, pricing-fingerprint rehydrate usage, admin i18n keys, and shipping defaults.
Docs & acceptance
frontend/docs/shop/commercial-policy-batch-cp-01.md, frontend/docs/shop/commercial-policy-batch-cp-01-acceptance.md
New documentation and acceptance gate describing Batch CP-01 policy, checks, and manual smoke tests.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client (Browser)
    participant CartAPI as /api/shop/cart/rehydrate
    participant CheckoutAPI as /api/shop/checkout
    participant Policy as CommercialPolicy (server)
    participant Orders as Orders Service
    participant Payment as Payment Provider

    Client->>CartAPI: POST items
    CartAPI->>Policy: resolveStandardStorefrontCurrency()
    CartAPI-->>Client: quote (pricingFingerprint, currency=UAH)

    Client->>CheckoutAPI: POST checkout (items, pricingFingerprint, legalConsent)
    CheckoutAPI->>Policy: resolveStandardStorefrontProviderCapabilities()
    CheckoutAPI->>Orders: createOrderWithItems(..., storefrontCurrency=UAH, providerCandidates)
    Orders->>Payment: init payment (chosen provider/method)
    Payment-->>Orders: payment intent / invoice
    Orders-->>CheckoutAPI: order created
    CheckoutAPI-->>Client: order response (currency=UAH, totals)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • AM1007
  • ViktorSvertoka

"I hopped through commits, bright and spry,
UAH in my pocket, flags held high.
Providers lined up, translations in bloom,
Photos and prices find tidy room.
Cheers from a rabbit — tests passed soon!" 🐇✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 PR title accurately and specifically describes the main changes: commercial policy refactor, locale decoupling, UAH storefront enforcement, checkout alignment, and admin pricing cleanup—covering the primary objectives.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch lso/feat/shop-legal

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.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 240533f1d5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (7)
frontend/lib/tests/shop/checkout-stripe-payments-disabled.test.ts (1)

376-394: Minor redundancy: legalConsent is specified twice.

Line 384 explicitly includes legalConsent: TEST_LEGAL_CONSENT in the body, but the postCheckout helper already injects legalConsent: TEST_LEGAL_CONSENT at lines 230. The spread at line 234 (...args.body) will overwrite the first one, so this works correctly but is redundant.

💡 Remove redundant legalConsent from test body
         body: {
           paymentProvider: 'stripe',
           paymentMethod: 'stripe_card',
           paymentCurrency: 'UAH',
-          legalConsent: TEST_LEGAL_CONSENT,
           items: [{ productId, quantity: 1 }],
         },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/lib/tests/shop/checkout-stripe-payments-disabled.test.ts` around
lines 376 - 394, The test payload in the postCheckout call redundantly sets
legalConsent: TEST_LEGAL_CONSENT even though the postCheckout helper already
injects legalConsent via TEST_LEGAL_CONSENT; remove the duplicate key from the
body object in the test to avoid redundancy (update the test that calls
postCheckout in checkout-stripe-payments-disabled.test.ts and remove the
explicit legalConsent property from the body passed to postCheckout while
keeping TEST_LEGAL_CONSENT referenced in the helper).
frontend/lib/tests/shop/admin-product-form-messages.test.ts (1)

24-69: Consider adding locale identification to assertions for easier debugging.

When a key is missing, the test fails without indicating which locale caused the failure. Adding context to assertions would improve debuggability.

💡 Suggested improvement
 describe('admin product form i18n messages', () => {
-  it('keeps product form labels under shop.admin.products.form for all supported locales', () => {
-    for (const messages of [en, uk, pl]) {
+  it.each([
+    ['en', en],
+    ['uk', uk],
+    ['pl', pl],
+  ] as const)('keeps product form labels for %s locale', (localeName, messages) => {
       expect(
         getAtPath(messages as Record<string, unknown>, [
           'shop',
           'admin',
           'products',
           'form',
           'fields',
           'title',
         ])
-      ).toBeTruthy();
+      ).toBeTruthy();
       // ... remaining assertions
-    }
   });
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/lib/tests/shop/admin-product-form-messages.test.ts` around lines 24
- 69, The test should surface which locale is missing a key; change the loop to
iterate with locale names (e.g. for (const [localeName, messages] of
Object.entries({ en, uk, pl })) ) and for each asserted path call getAtPath once
into a variable (e.g. const val = getAtPath(messages as Record<string, unknown>,
[...])) and if (!val) throw new Error(`Missing key shop.admin.products.form...
in locale ${localeName}`) before/instead of expect(val).toBeTruthy(); update all
four assertions this way so failures include the locale (references: en, uk, pl,
getAtPath, test "keeps product form labels under shop.admin.products.form for
all supported locales").
frontend/app/[locale]/shop/checkout/error/page.tsx (1)

23-29: Pass the route locale explicitly into generateMetadata() to follow the recommended next-intl pattern.

While this currently works due to dynamic = 'force-dynamic', the official next-intl documentation recommends explicitly passing the locale from params rather than relying on implicit request-locale resolution. This makes metadata generation more robust and prevents issues if the page's rendering mode changes in the future.

♻️ Suggested refactor
-export async function generateMetadata(): Promise<Metadata> {
-  const t = await getTranslations('shop.checkout.errorPage');
+export async function generateMetadata({
+  params,
+}: {
+  params: Promise<{ locale: string }>;
+}): Promise<Metadata> {
+  const { locale } = await params;
+  const t = await getTranslations({
+    locale,
+    namespace: 'shop.checkout.errorPage',
+  });

   return {
     title: t('metaTitle'),
     description: t('metaDescription'),
   };
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/`[locale]/shop/checkout/error/page.tsx around lines 23 - 29, The
generateMetadata function should accept the route params and pass the route
locale explicitly into getTranslations instead of relying on implicit
resolution; update the generateMetadata signature to receive ({ params }) (or ({
params: { locale } })) and call getTranslations with that locale and the
'shop.checkout.errorPage' namespace so title/description are generated using
params.locale (refer to generateMetadata and getTranslations to locate where to
change).
frontend/app/[locale]/shop/cart/provider-policy.ts (2)

7-20: Consider adding a brief comment explaining why currency is accepted but unused.

The void args.currency pattern indicates intentional API alignment where currency was previously used for provider selection. A brief comment would clarify this is intentional for the policy refactor:

 export function resolveInitialProvider(args: {
   stripeEnabled: boolean;
   monobankEnabled: boolean;
   currency: string | null | undefined;
 }): CheckoutProvider {
+  // currency accepted for API compatibility; provider selection is capability-based post CP-01
   void args.currency;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/`[locale]/shop/cart/provider-policy.ts around lines 7 - 20, Add
a short inline comment above or next to the existing void args.currency in
resolveInitialProvider explaining that currency is intentionally unused but
accepted to preserve the API/shape (e.g., historical provider-selection or
future-proofing for currency-based logic); reference the function name
resolveInitialProvider and the parameter args.currency so reviewers understand
this is purposeful and not a leftover bug.

17-19: Line 19 is redundant given the fallback logic.

Lines 17-19:

if (canUseMonobank) return 'monobank';
if (canUseStripe) return 'stripe';
return 'stripe';

Line 19 (return 'stripe') is only reached when both providers are disabled. Consider whether returning 'stripe' as a fallback when neither is enabled is the intended behavior, or if this should throw/return a different value.

Alternative: Make fallback explicit
   if (canUseMonobank) return 'monobank';
   if (canUseStripe) return 'stripe';
-  return 'stripe';
+  return 'stripe'; // Fallback when no provider is enabled; checkout will fail at validation
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/`[locale]/shop/cart/provider-policy.ts around lines 17 - 19, The
current provider-selection logic redundantly returns 'stripe' as a final
fallback and masks the case when both canUseMonobank and canUseStripe are false;
update the logic around the canUseMonobank and canUseStripe checks to make the
fallback explicit: either remove the redundant final return and throw an error
(or return null/undefined) when neither provider is available, or keep a
deliberate fallback by returning 'stripe' but add a comment and/or a boolean
guard to document intent; reference the canUseMonobank and canUseStripe
conditions in the provider-selection function and update callers to handle the
new error/null case if you choose to remove the implicit 'stripe' fallback.
frontend/app/[locale]/shop/cart/capabilities.ts (1)

1-14: Consider caching or combining calls if resolveStandardStorefrontProviderCapabilities() is expensive.

Each capability function independently calls resolveStandardStorefrontProviderCapabilities(). If this resolver performs I/O or non-trivial computation, consider either:

  1. Exporting a single resolver that returns all capabilities at once for consumers that need multiple values
  2. Ensuring the underlying implementation caches results per request

For now, this is acceptable if the resolver is cheap (simple env reads).

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

In `@frontend/app/`[locale]/shop/cart/capabilities.ts around lines 1 - 14, The
three small accessors (resolveStripeCheckoutEnabled,
resolveMonobankCheckoutEnabled, resolveMonobankGooglePayEnabled) each call
resolveStandardStorefrontProviderCapabilities(), which may be expensive; modify
callers to either use a single cached call to
resolveStandardStorefrontProviderCapabilities() and read the three properties,
or change the module to export a single resolver that returns all capabilities
(and update callers to destructure the returned object), or implement
per-request memoization inside resolveStandardStorefrontProviderCapabilities()
so repeated calls within the same request return a cached result—update usages
of the three functions accordingly to avoid redundant expensive calls.
frontend/lib/shop/commercial-policy.server.ts (1)

14-16: Use one truthy parser for both env flags.

PAYMENTS_ENABLED only accepts exact "true", while SHOP_MONOBANK_GPAY_ENABLED accepts true/1/yes/on. Equivalent configs can therefore disable Monobank checkout while still enabling the Google Pay sub-flag.

♻️ Proposed cleanup
 function isFlagEnabled(value: string | undefined): boolean {
-  return (value ?? '').trim() === 'true';
+  const normalized = (value ?? '').trim().toLowerCase();
+  return (
+    normalized === 'true' ||
+    normalized === '1' ||
+    normalized === 'yes' ||
+    normalized === 'on'
+  );
 }
@@
-  const rawMonobankGooglePay = (
-    readServerEnv('SHOP_MONOBANK_GPAY_ENABLED') ?? ''
-  )
-    .trim()
-    .toLowerCase();
   const monobankGooglePayEnabled =
     monobankCheckoutEnabled &&
-    (rawMonobankGooglePay === 'true' ||
-      rawMonobankGooglePay === '1' ||
-      rawMonobankGooglePay === 'yes' ||
-      rawMonobankGooglePay === 'on');
+    isFlagEnabled(readServerEnv('SHOP_MONOBANK_GPAY_ENABLED'));

Also applies to: 28-49

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

In `@frontend/lib/shop/commercial-policy.server.ts` around lines 14 - 16, The
helper isFlagEnabled currently only treats the exact string "true" as truthy;
change it to normalize the input (trim and toLowerCase) and check against a set
of truthy values like {'true','1','yes','on'} so both PAYMENTS_ENABLED and
SHOP_MONOBANK_GPAY_ENABLED (and other uses in the same file around the 28-49
region) use the same parser; update all callers to use the single isFlagEnabled
function to ensure consistent flag behavior across the file.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/lib/services/products/mutations/update.ts`:
- Around line 83-125: The read of productPrices (existingPriceRows) and the
merged-price validations (building merged, calling assertMergedPricesPolicy and
enforceSaleBadgeRequiresOriginal) must be moved inside the same db.transaction
that performs the upsert/update so they validate the exact write snapshot;
relocate the select-from(productPrices) + merge logic into the transaction
callback, perform a row lock if supported by the DB client before reading, and
run assertMergedPricesPolicy and enforceSaleBadgeRequiresOriginal inside that
transaction before applying any updates to ensure checks are atomic with the
write (references: productPrices, existingPriceRows, merged, mergedRows,
assertMergedPricesPolicy, enforceSaleBadgeRequiresOriginal, db.transaction).

In `@frontend/lib/shop/commercial-policy.ts`:
- Around line 28-40: The function
resolveStandardStorefrontCheckoutProviderCandidates currently short-circuits a
provided requestedProvider before deriving a provider from requestedMethod;
change the logic in resolveStandardStorefrontCheckoutProviderCandidates to first
infer provider(s) from args.requestedMethod via
inferCurrentCheckoutProviderFromMethod, then apply currency/policy filtering and
only after that intersect with args.requestedProvider (if present) so
requestedProvider cannot bypass method-based restrictions; update the parallel
logic at the other occurrence (the block around lines 78-95) similarly, and add
regression tests asserting that requestedProvider: 'monobank' with currency:
'USD' and requestedMethod: 'monobank_invoice' with currency: 'USD' both yield
the tightened (currency-filtered) results.
- Around line 49-65: The helpers
resolveCurrentStandardStorefrontCurrencyFromLocale and
resolveCurrentStandardStorefrontShippingCountryFromLocale still derive currency
and shipping country from the locale, causing non-UK locales to produce null
countries and break shipping logic in callers like buildCheckoutShippingPayload,
resolveShippingAvailability and resolveLocaleAndCurrency; change these helpers
(or the wrappers resolveCurrencyFromLocale/localeToCountry) to stop using
normalizeLocaleTag(locale) as the decision and instead return values from
STANDARD_STOREFRONT_COMMERCIAL_POLICY (currency and shippingCountry)
unconditionally for the standard storefront path so callers always get the fixed
policy values rather than locale-driven ones.

In `@frontend/lib/tests/shop/commercial-policy-delegation.test.ts`:
- Around line 4-7: The beforeEach currently calls vi.clearAllMocks() and
vi.resetModules() but does not remove registrations from vi.doMock(), so mocked
policy stubs can bleed between tests; update the beforeEach in
commercial-policy-delegation.test.ts to explicitly unmock any modules you mock
dynamically (e.g., call vi.unmock('<moduleId>') for the policy stub(s) you use)
after vi.resetModules(), or programmatically iterate known mocked module ids and
call vi.unmock on them so vi.doMock registrations are cleared before each test.

In `@frontend/lib/tests/shop/product-admin-create-pricing-policy.test.ts`:
- Around line 138-158: The test currently uses expect(...).rejects.toMatchObject
which can be satisfied by any object with matching shape; update the assertion
to explicitly check the thrown error class from the service layer by asserting
the promise rejects to an instance of PriceConfigError (using
expect(...).rejects.toBeInstanceOf(PriceConfigError)) and then separately assert
the error properties (e.g., await expect(...).rejects.toMatchObject({ code:
'PRICE_CONFIG_ERROR', currency: 'UAH' })); locate the assertion around
createProduct in the test and replace the single toMatchObject check with these
two awaits referencing createProduct and PriceConfigError.

In `@frontend/lib/tests/shop/public-storefront-read-policy.test.ts`:
- Around line 61-90: The test should assert the exact number of calls and their
precise arguments to prevent hidden legacy fallback queries; replace the three
toHaveBeenNthCalledWith checks with a strict assertion like expecting
shopQueryMocks.getActiveProductsPage toHaveBeenCalledTimes(3) and then compare
shopQueryMocks.getActiveProductsPage.mock.calls (or the equivalent) to an
explicit array of the three expected argument objects (each containing currency:
'UAH', limit: 24, offset: 0, category: undefined, type: undefined, color:
undefined, size: undefined, sort: 'newest') so any extra/legacy call will fail;
target the getActiveProductsPage mock in this test when making the change.

---

Nitpick comments:
In `@frontend/app/`[locale]/shop/cart/capabilities.ts:
- Around line 1-14: The three small accessors (resolveStripeCheckoutEnabled,
resolveMonobankCheckoutEnabled, resolveMonobankGooglePayEnabled) each call
resolveStandardStorefrontProviderCapabilities(), which may be expensive; modify
callers to either use a single cached call to
resolveStandardStorefrontProviderCapabilities() and read the three properties,
or change the module to export a single resolver that returns all capabilities
(and update callers to destructure the returned object), or implement
per-request memoization inside resolveStandardStorefrontProviderCapabilities()
so repeated calls within the same request return a cached result—update usages
of the three functions accordingly to avoid redundant expensive calls.

In `@frontend/app/`[locale]/shop/cart/provider-policy.ts:
- Around line 7-20: Add a short inline comment above or next to the existing
void args.currency in resolveInitialProvider explaining that currency is
intentionally unused but accepted to preserve the API/shape (e.g., historical
provider-selection or future-proofing for currency-based logic); reference the
function name resolveInitialProvider and the parameter args.currency so
reviewers understand this is purposeful and not a leftover bug.
- Around line 17-19: The current provider-selection logic redundantly returns
'stripe' as a final fallback and masks the case when both canUseMonobank and
canUseStripe are false; update the logic around the canUseMonobank and
canUseStripe checks to make the fallback explicit: either remove the redundant
final return and throw an error (or return null/undefined) when neither provider
is available, or keep a deliberate fallback by returning 'stripe' but add a
comment and/or a boolean guard to document intent; reference the canUseMonobank
and canUseStripe conditions in the provider-selection function and update
callers to handle the new error/null case if you choose to remove the implicit
'stripe' fallback.

In `@frontend/app/`[locale]/shop/checkout/error/page.tsx:
- Around line 23-29: The generateMetadata function should accept the route
params and pass the route locale explicitly into getTranslations instead of
relying on implicit resolution; update the generateMetadata signature to receive
({ params }) (or ({ params: { locale } })) and call getTranslations with that
locale and the 'shop.checkout.errorPage' namespace so title/description are
generated using params.locale (refer to generateMetadata and getTranslations to
locate where to change).

In `@frontend/lib/shop/commercial-policy.server.ts`:
- Around line 14-16: The helper isFlagEnabled currently only treats the exact
string "true" as truthy; change it to normalize the input (trim and toLowerCase)
and check against a set of truthy values like {'true','1','yes','on'} so both
PAYMENTS_ENABLED and SHOP_MONOBANK_GPAY_ENABLED (and other uses in the same file
around the 28-49 region) use the same parser; update all callers to use the
single isFlagEnabled function to ensure consistent flag behavior across the
file.

In `@frontend/lib/tests/shop/admin-product-form-messages.test.ts`:
- Around line 24-69: The test should surface which locale is missing a key;
change the loop to iterate with locale names (e.g. for (const [localeName,
messages] of Object.entries({ en, uk, pl })) ) and for each asserted path call
getAtPath once into a variable (e.g. const val = getAtPath(messages as
Record<string, unknown>, [...])) and if (!val) throw new Error(`Missing key
shop.admin.products.form... in locale ${localeName}`) before/instead of
expect(val).toBeTruthy(); update all four assertions this way so failures
include the locale (references: en, uk, pl, getAtPath, test "keeps product form
labels under shop.admin.products.form for all supported locales").

In `@frontend/lib/tests/shop/checkout-stripe-payments-disabled.test.ts`:
- Around line 376-394: The test payload in the postCheckout call redundantly
sets legalConsent: TEST_LEGAL_CONSENT even though the postCheckout helper
already injects legalConsent via TEST_LEGAL_CONSENT; remove the duplicate key
from the body object in the test to avoid redundancy (update the test that calls
postCheckout in checkout-stripe-payments-disabled.test.ts and remove the
explicit legalConsent property from the body passed to postCheckout while
keeping TEST_LEGAL_CONSENT referenced in the helper).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3c34a5a3-9d82-4424-8655-74234928cb46

📥 Commits

Reviewing files that changed from the base of the PR and between 716b4df and 240533f.

📒 Files selected for processing (54)
  • frontend/app/[locale]/admin/shop/products/_components/ProductForm.tsx
  • frontend/app/[locale]/shop/cart/CartPageClient.tsx
  • frontend/app/[locale]/shop/cart/capabilities.ts
  • frontend/app/[locale]/shop/cart/provider-policy.ts
  • frontend/app/[locale]/shop/checkout/error/page.tsx
  • frontend/app/[locale]/shop/checkout/payment/StripePaymentClient.tsx
  • frontend/app/api/shop/cart/rehydrate/route.ts
  • frontend/app/api/shop/checkout/route.ts
  • frontend/app/api/shop/shipping/methods/route.ts
  • frontend/app/api/shop/shipping/np/cities/route.ts
  • frontend/app/api/shop/shipping/np/warehouses/route.ts
  • frontend/docs/shop/commercial-policy-batch-cp-01-acceptance.md
  • frontend/docs/shop/commercial-policy-batch-cp-01.md
  • frontend/lib/services/orders/checkout.ts
  • frontend/lib/services/products/mutations/create.ts
  • frontend/lib/services/products/mutations/update.ts
  • frontend/lib/services/products/prices.ts
  • frontend/lib/shop/checkout-display-currency.ts
  • frontend/lib/shop/commercial-policy.server.ts
  • frontend/lib/shop/commercial-policy.ts
  • frontend/lib/shop/currency.ts
  • frontend/lib/shop/data.ts
  • frontend/lib/shop/locale.ts
  • frontend/lib/shop/payments.ts
  • frontend/lib/tests/shop/admin-product-form-messages.test.ts
  • frontend/lib/tests/shop/admin-product-patch-price-config-error-contract.test.ts
  • frontend/lib/tests/shop/cart-public-provider-policy.test.ts
  • frontend/lib/tests/shop/cart-rehydrate-route-policy.test.ts
  • frontend/lib/tests/shop/catalog-merchandising-cleanup.test.ts
  • frontend/lib/tests/shop/checkout-currency-policy.test.ts
  • frontend/lib/tests/shop/checkout-display-currency.test.ts
  • frontend/lib/tests/shop/checkout-legal-consent-contract.test.ts
  • frontend/lib/tests/shop/checkout-monobank-happy-path.test.ts
  • frontend/lib/tests/shop/checkout-monobank-idempotency-contract.test.ts
  • frontend/lib/tests/shop/checkout-monobank-parse-validation.test.ts
  • frontend/lib/tests/shop/checkout-no-payments.test.ts
  • frontend/lib/tests/shop/checkout-stripe-payments-disabled.test.ts
  • frontend/lib/tests/shop/commercial-policy-delegation.test.ts
  • frontend/lib/tests/shop/commercial-policy.test.ts
  • frontend/lib/tests/shop/currency.test.ts
  • frontend/lib/tests/shop/order-items-snapshot-immutable.test.ts
  • frontend/lib/tests/shop/product-admin-create-pricing-policy.test.ts
  • frontend/lib/tests/shop/product-admin-merged-prices-policy.test.ts
  • frontend/lib/tests/shop/product-photo-plan-fixes.test.ts
  • frontend/lib/tests/shop/product-prices-write-authority-phase8.test.ts
  • frontend/lib/tests/shop/public-product-visibility.test.ts
  • frontend/lib/tests/shop/public-storefront-read-policy.test.ts
  • frontend/lib/tests/shop/shipping-methods-route-p2.test.ts
  • frontend/lib/tests/shop/shipping-np-cities-route-p2.test.ts
  • frontend/lib/tests/shop/shipping-np-warehouses-route-p2.test.ts
  • frontend/lib/validation/shop.ts
  • frontend/messages/en.json
  • frontend/messages/pl.json
  • frontend/messages/uk.json

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (4)
frontend/lib/tests/shop/currency.test.ts (1)

53-81: Consider table-driven locale assertions to reduce repetition.

This block is correct, but the repeated uk/en/pl setup across three tests can be simplified for easier extension and maintenance.

♻️ Optional refactor sketch
+const LOCALES = ['uk', 'en', 'pl'] as const;
+
+function expectSameAcrossLocales(render: (locale: (typeof LOCALES)[number]) => string) {
+  const [base, ...rest] = LOCALES.map(render);
+  for (const value of rest) expect(value).toBe(base);
+  return base;
+}
+
 describe('UAH storefront formatting', () => {
   it('formats UAH identically across uk / en / pl in Ukrainian storefront style', () => {
-    const uk = formatMoney(200000, 'UAH', 'uk');
-    const en = formatMoney(200000, 'UAH', 'en');
-    const pl = formatMoney(200000, 'UAH', 'pl');
-
-    expect(en).toBe(uk);
-    expect(pl).toBe(uk);
+    const uk = expectSameAcrossLocales((locale) => formatMoney(200000, 'UAH', locale));
     expect(normalizeRenderedSpacing(uk)).toBe('2 000,00 ₴');
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/lib/tests/shop/currency.test.ts` around lines 53 - 81, Replace the
repeated uk/en/pl assertions with a table-driven loop: for each test
(formatMoney, formatMoneyCode, formatPrice) create an array of locales const
locales = ['uk','en','pl'] and compute the canonical value using the first
locale (e.g., canonical = formatMoney(200000,'UAH','uk')), then iterate over
locales and assert each locale's output equals canonical; use
normalizeRenderedSpacing(canonical) for the final expectation that checks the
exact rendered string. Reference the functions formatMoney, formatMoneyCode,
formatPrice and helper normalizeRenderedSpacing when locating where to
consolidate the assertions.
frontend/lib/tests/shop/product-admin-create-pricing-policy.test.ts (2)

31-40: Do not swallow teardown failures in afterEach.

Silently ignoring delete errors can mask DB cleanup regressions and leak state across tests.

♻️ Suggested teardown hardening
   afterEach(async () => {
     for (const productId of createdProductIds.splice(0)) {
-      await db
-        .delete(productPrices)
-        .where(eq(productPrices.productId, productId))
-        .catch(() => undefined);
-      await db
-        .delete(products)
-        .where(eq(products.id, productId))
-        .catch(() => undefined);
+      await db.delete(productPrices).where(eq(productPrices.productId, productId));
+      await db.delete(products).where(eq(products.id, productId));
     }
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/lib/tests/shop/product-admin-create-pricing-policy.test.ts` around
lines 31 - 40, The afterEach teardown currently swallows DB delete errors by
using .catch(() => undefined) on the db.delete calls (in the afterEach that
iterates over createdProductIds), which hides failures; change the teardown to
let errors surface so tests fail on cleanup issues — remove the silent .catch
handlers on the db.delete(...).where(eq(productPrices.productId,...)) and
db.delete(...).where(eq(products.id,...)) calls (or replace them with a
try/catch that logs the error and rethrows) so any deletion failure in afterEach
is reported by the test runner.

93-101: Guard legacy before dereferencing fields.

If no row is returned, this currently throws a TypeError instead of a clear test failure message.

🧪 Suggested assertion guard
     const [legacy] = await db
       .select({
         price: products.price,
         originalPrice: products.originalPrice,
         currency: products.currency,
       })
       .from(products)
       .where(eq(products.id, created.id))
       .limit(1);

+    expect(legacy).toBeDefined();
+    if (!legacy) throw new Error('Expected product legacy row to exist');
+
     expect(legacy.currency).toBe('USD');
     expect(String(legacy.price)).toBe(String(toDbMoney(5100)));
     expect(legacy.originalPrice).toBeNull();

Also applies to: 130-132

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

In `@frontend/lib/tests/shop/product-admin-create-pricing-policy.test.ts` around
lines 93 - 101, The test dereferences the result tuple "legacy" returned from
the DB select without checking whether a row was returned, which can cause a
TypeError; add an explicit guard/assertion right after the select (the const
[legacy] = await db.select(...).from(products).where(eq(products.id,
created.id)).limit(1) call) such as asserting legacy is defined (with a clear
failure message) before accessing legacy.price/originalPrice/currency, and apply
the same guard to the other identical select/assertion block later in the file
that also destructures into "legacy".
frontend/app/[locale]/shop/checkout/error/page.tsx (1)

233-238: Remove unnecessary any type casts.

The (order as any) casts bypass type checking despite the fields being properly typed. Since getOrderSummary returns OrderSummaryWithMinor (which includes both currency and totalAmountMinor via the orderSummarySchema), these fields are accessible without casting:

  const totalMinor =
-   typeof (order as any).totalAmountMinor === 'number'
-     ? (order as any).totalAmountMinor
+   typeof order.totalAmountMinor === 'number'
+     ? order.totalAmountMinor
    : null;

- const currency = resolveCheckoutDisplayCurrency((order as any).currency);
+ const currency = resolveCheckoutDisplayCurrency(order.currency);

The currency resolution logic is correct—resolveCheckoutDisplayCurrency safely handles null/undefined by falling back to the standard storefront currency.

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

In `@frontend/app/`[locale]/shop/checkout/error/page.tsx around lines 233 - 238,
Remove the unnecessary (order as any) casts and use the typed
OrderSummaryWithMinor returned by getOrderSummary: access order.totalAmountMinor
(check typeof order.totalAmountMinor === 'number' to set totalMinor) and access
order.currency directly when calling resolveCheckoutDisplayCurrency; the
orderSummarySchema/getOrderSummary guarantee these fields exist so remove the
casts around totalAmountMinor and currency while keeping the existing
null/undefined-safe logic in resolveCheckoutDisplayCurrency.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@frontend/app/`[locale]/shop/checkout/error/page.tsx:
- Around line 233-238: Remove the unnecessary (order as any) casts and use the
typed OrderSummaryWithMinor returned by getOrderSummary: access
order.totalAmountMinor (check typeof order.totalAmountMinor === 'number' to set
totalMinor) and access order.currency directly when calling
resolveCheckoutDisplayCurrency; the orderSummarySchema/getOrderSummary guarantee
these fields exist so remove the casts around totalAmountMinor and currency
while keeping the existing null/undefined-safe logic in
resolveCheckoutDisplayCurrency.

In `@frontend/lib/tests/shop/currency.test.ts`:
- Around line 53-81: Replace the repeated uk/en/pl assertions with a
table-driven loop: for each test (formatMoney, formatMoneyCode, formatPrice)
create an array of locales const locales = ['uk','en','pl'] and compute the
canonical value using the first locale (e.g., canonical =
formatMoney(200000,'UAH','uk')), then iterate over locales and assert each
locale's output equals canonical; use normalizeRenderedSpacing(canonical) for
the final expectation that checks the exact rendered string. Reference the
functions formatMoney, formatMoneyCode, formatPrice and helper
normalizeRenderedSpacing when locating where to consolidate the assertions.

In `@frontend/lib/tests/shop/product-admin-create-pricing-policy.test.ts`:
- Around line 31-40: The afterEach teardown currently swallows DB delete errors
by using .catch(() => undefined) on the db.delete calls (in the afterEach that
iterates over createdProductIds), which hides failures; change the teardown to
let errors surface so tests fail on cleanup issues — remove the silent .catch
handlers on the db.delete(...).where(eq(productPrices.productId,...)) and
db.delete(...).where(eq(products.id,...)) calls (or replace them with a
try/catch that logs the error and rethrows) so any deletion failure in afterEach
is reported by the test runner.
- Around line 93-101: The test dereferences the result tuple "legacy" returned
from the DB select without checking whether a row was returned, which can cause
a TypeError; add an explicit guard/assertion right after the select (the const
[legacy] = await db.select(...).from(products).where(eq(products.id,
created.id)).limit(1) call) such as asserting legacy is defined (with a clear
failure message) before accessing legacy.price/originalPrice/currency, and apply
the same guard to the other identical select/assertion block later in the file
that also destructures into "legacy".

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f70c320b-6dbd-4f86-b00f-1a6a29097ddd

📥 Commits

Reviewing files that changed from the base of the PR and between 240533f and 92eedfa.

📒 Files selected for processing (12)
  • frontend/app/[locale]/shop/checkout/error/page.tsx
  • frontend/lib/services/products/mutations/update.ts
  • frontend/lib/shop/commercial-policy.server.ts
  • frontend/lib/shop/commercial-policy.ts
  • frontend/lib/tests/shop/admin-product-form-messages.test.ts
  • frontend/lib/tests/shop/checkout-stripe-payments-disabled.test.ts
  • frontend/lib/tests/shop/commercial-policy-delegation.test.ts
  • frontend/lib/tests/shop/commercial-policy.test.ts
  • frontend/lib/tests/shop/currency.test.ts
  • frontend/lib/tests/shop/product-admin-create-pricing-policy.test.ts
  • frontend/lib/tests/shop/public-cart-env-contract.test.ts
  • frontend/lib/tests/shop/public-storefront-read-policy.test.ts
✅ Files skipped from review due to trivial changes (3)
  • frontend/lib/tests/shop/public-cart-env-contract.test.ts
  • frontend/lib/tests/shop/admin-product-form-messages.test.ts
  • frontend/lib/tests/shop/commercial-policy.test.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • frontend/lib/tests/shop/checkout-stripe-payments-disabled.test.ts
  • frontend/lib/services/products/mutations/update.ts
  • frontend/lib/tests/shop/commercial-policy-delegation.test.ts
  • frontend/lib/tests/shop/public-storefront-read-policy.test.ts
  • frontend/lib/shop/commercial-policy.server.ts
  • frontend/lib/shop/commercial-policy.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
frontend/lib/tests/shop/currency.test.ts (1)

59-61: Consider adding locale context to loop assertions for easier debugging.

When a loop assertion fails, the default error message won't indicate which locale caused the failure. Adding context would make test failures easier to diagnose.

♻️ Proposed refactor for clearer assertion messages
     for (const locale of otherLocales) {
-      expect(formatMoney(200000, 'UAH', locale)).toBe(canonical);
+      expect(
+        formatMoney(200000, 'UAH', locale),
+        `formatMoney should match canonical for locale "${locale}"`
+      ).toBe(canonical);
     }

Same pattern can be applied to the other loops at lines 70-72 and 84-90.

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

In `@frontend/lib/tests/shop/currency.test.ts` around lines 59 - 61, The loop
assertions using formatMoney(200000, 'UAH', locale) against canonical lack
locale context on failure; update the loop over otherLocales to include the
failing locale in the assertion output (for example, compute const result =
formatMoney(...) and throw or fail with a message containing the locale when
result !== canonical, or convert the loop to a test.each/table-driven test that
includes locale in the test name) and apply the same change to the other similar
loops that call formatMoney in this test file so any failing assertion reports
which locale caused it.
frontend/app/[locale]/shop/checkout/error/page.tsx (1)

90-93: Consider simplifying Promise detection.

The duck-typing approach with (searchParams as any).then works but is somewhat awkward. Since Next.js 15 always provides searchParams as a Promise in async Server Components, you could simplify to always await if the type union is only for documentation purposes.

Alternatively, a cleaner type guard could be used if you need to support both patterns.

♻️ Optional: Simpler approach if always Promise
-  const resolvedSearchParams: SearchParams | undefined =
-    searchParams && typeof (searchParams as any).then === 'function'
-      ? await (searchParams as Promise<SearchParams>)
-      : (searchParams as SearchParams | undefined);
+  const resolvedSearchParams = searchParams ? await searchParams : undefined;

This assumes searchParams type is updated to just Promise<SearchParams> | undefined.

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

In `@frontend/app/`[locale]/shop/checkout/error/page.tsx around lines 90 - 93, The
duck-typing check for searchParams is awkward—replace the conditional with a
simpler approach: if your component always receives a Promise (Next.js 15 async
Server Component), change the handling of searchParams so resolvedSearchParams
is obtained by awaiting the promise (await searchParams) or update the declared
type to Promise<SearchParams> | undefined and simply await it; otherwise
implement a tiny type guard like isPromise(obj): obj != null && typeof (obj as
any).then === 'function' and use that to await only when needed. Ensure you
update the usage around the resolvedSearchParams variable and reference the
searchParams identifier and resolvedSearchParams assignment site when making the
change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@frontend/app/`[locale]/shop/checkout/error/page.tsx:
- Around line 90-93: The duck-typing check for searchParams is awkward—replace
the conditional with a simpler approach: if your component always receives a
Promise (Next.js 15 async Server Component), change the handling of searchParams
so resolvedSearchParams is obtained by awaiting the promise (await searchParams)
or update the declared type to Promise<SearchParams> | undefined and simply
await it; otherwise implement a tiny type guard like isPromise(obj): obj != null
&& typeof (obj as any).then === 'function' and use that to await only when
needed. Ensure you update the usage around the resolvedSearchParams variable and
reference the searchParams identifier and resolvedSearchParams assignment site
when making the change.

In `@frontend/lib/tests/shop/currency.test.ts`:
- Around line 59-61: The loop assertions using formatMoney(200000, 'UAH',
locale) against canonical lack locale context on failure; update the loop over
otherLocales to include the failing locale in the assertion output (for example,
compute const result = formatMoney(...) and throw or fail with a message
containing the locale when result !== canonical, or convert the loop to a
test.each/table-driven test that includes locale in the test name) and apply the
same change to the other similar loops that call formatMoney in this test file
so any failing assertion reports which locale caused it.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e0bbb34a-7dde-4377-8ee6-0bc8efbb0a6c

📥 Commits

Reviewing files that changed from the base of the PR and between 92eedfa and d6ac9e5.

📒 Files selected for processing (3)
  • frontend/app/[locale]/shop/checkout/error/page.tsx
  • frontend/lib/tests/shop/currency.test.ts
  • frontend/lib/tests/shop/product-admin-create-pricing-policy.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/lib/tests/shop/product-admin-create-pricing-policy.test.ts

@ViktorSvertoka ViktorSvertoka merged commit b1a61ba into develop Apr 2, 2026
7 checks passed
@ViktorSvertoka ViktorSvertoka deleted the lso/feat/shop-legal branch April 2, 2026 04:09
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