Merge feature/264-order-management into develop @W-23254713 - #3914
Conversation
…dk-react (#3864) * @W-22931003@ Add cancelOmsOrder and getOmsMetaData hooks to commerce-sdk-react - Add CancelOmsOrder to ShopperOrdersMutations enum (mutation.ts) - Add useOmsMetaData query hook for fetching cancel/return reason codes (query.ts) - Add getOmsMetaData query key helper (queryKeyHelpers.ts) - Add cancelOmsOrder cache invalidation: invalidates order query on success (cache.ts) - Update query test map with useOmsMetaData - Add returnOmsOrder to unimplemented endpoints list (available in SDK but no hook yet) Requires commerce-sdk-isomorphic with shopper-orders-oas v1.14.1 (branch: update-shopper-orders-1.14.1, not yet published to npm) * Add changelog entry for cancelOmsOrder and getOmsMetaData hooks * Address PR review feedback - Invalidate getCustomerOrders cache on cancel (order history shows updated status) - Add comments for unimplemented endpoints in test file - Bump commerce-sdk-isomorphic to 5.1.0-unstable-20260611093359 (preview with OAS v1.14.1) * Update package-lock.json for commerce-sdk-isomorphic unstable version * Fix prettier: inline single-element array in cancelOmsOrder cache * Fix tests for unstable SDK: remove endpoints no longer in preview version - Remove setupCustomerPaymentMethodReference from ShopperCustomers unimplemented list - Remove resolveQualifiers from ShopperExperience unimplemented list * Remove changelog entry (will add when merging to develop) * Add unit tests for cancelOmsOrder mutation and cache invalidation
Add returnOmsOrder mutation
@W-22918057 Extract standalone OrderTracking component Pure refactor: move the inline tracking render from renderShippingMethod in order-detail.jsx into a standalone, testable app/components/order-tracking component, wired at both call sites (delivery/OMS-fallback and OMS-multi). The OMS-multi call site no longer needs a wrapping React.Fragment (key moves onto OrderTracking), and the now-unused ChakraLink import is removed from order-detail.jsx. DOM output is identical and i18n message IDs are unchanged (shipping-status descriptors are kept as inline formatMessage literals so babel-plugin-formatjs still statically extracts them). The existing orders.test.js suite plus new isolated component tests pass (96/96); lint clean. First of the Order Tracking epic work items.
@W-22918455 Render estimated delivery date on order tracking Render expectedDeliveryDate and actualDeliveryDate on the OrderTracking component as locale-formatted "Expected delivery: <date>" / "Delivered: <date>" lines, shown when present and omitted when absent (or when the date is missing or malformed — formatTrackingDate returns null on an unparseable value so it never renders "Invalid Date" or throws). Wired at both order-detail call sites; these fields are OMS-only (no ECOM fallback — they exist only on omsData.shipments[]). Adds two i18n keys (extracted + compiled). Unit and page tests cover the present, absent, and malformed cases across single, multi-shipment, and ECOM-fallback paths.
* @W-22918457 Guard null delivery dates in OrderTracking QA on W-22918455 found that new Date(null) returns the epoch (1970-01-01), not Invalid Date, so a null expectedDeliveryDate/actualDeliveryDate slipped past the isNaN guard and rendered '31 Dec 1969' to the shopper. Add a falsy guard (catches null, undefined, '') to formatTrackingDate, with a regression test for the explicit-null case. Defensive regardless of whether OMS serializes null on the wire. * @W-22918457 Re-trigger CI (flaky e2e SLAS login)
Consume useOrder's isError and render a full-card error (OrderNotFoundCard-style: square corners, title + description + Back to Order History) instead of hanging on the loading skeleton forever (the AC6 bug). A successful order with no omsData is NOT an error — it renders normally via the ECOM fallback. Adds the OrderLoadError component, i18n keys, and unit tests for the error, back-link, and no-omsData paths. Partial (products-only) failure keeps graceful degradation per the team's decision.
… can ingest(#3875) @W-22952388@ Sync payment-instrument amount before createOrder so OMS can ingest
…3878) * @W-22821836@ Invalidate customer orders cache after createOrder After a registered shopper places an order, useCustomerOrders kept serving the stale (pre-order) result and only refreshed on a fresh login, because createOrder's cache-update matrix only invalidated getCustomerBaskets. Add getCustomerOrders to the same invalidate list so the account order-history page reflects the new order immediately. Guest checkouts (no customerId) remain a no-op.
* @W-22806925@ Integrate SCAPI Cancel Order API with UI - Replace dummy cancel handler with real useShopperOrdersMutation(CancelOmsOrder) - Fetch reason codes from useOmsMetaData API when modal opens - Update cancel eligibility: omsData must exist + quantityAvailableToCancel === quantityOrdered for all items - Remove oms.enabled config flag — cancel visibility driven by order.omsData presence - Remove hardcoded CANCELLATION_REASONS — dropdown populated from API response - Hide dropdown when no reason codes available (allows cancel without reason) - Pass isSubmitting from mutation state to disable modal buttons during request - Show skeleton while reason codes are loading - Remove extracted translation messages for hardcoded reasons - Add mock for useShopperOrdersMutation/useOmsMetaData in order detail tests * Fix: keep modal open during API call, close only after response * Fix CI: prettier formatting and add changelog entry * Revert changelog entry — will add when merging to develop * Fix prettier: correct indentation for map, revert formatMessage to multiline * Pre-fetch OMS metadata on page load instead of on modal open * Fix prettier: inline useOmsMetaData and formatMessage calls * Fix prettier conflict: extract formatMessage to variables (under 100 char width) * Fix prettier: match showCancelSuccess format with showCancelError * Address Jie's review: initialize default reason and add no-reason test - Pre-select the default reason code when modal opens (from API response) - Add test: passes empty string when no reason codes provided and confirm clicked - Update existing tests to expect default reason pre-selection * Fix lint and tighten canCancel check - Add item.omsData != null guard to prevent undefined === undefined passing - Inline formatMessage in showCancelSuccess (satisfies generated-project prettier) - Inline test props (satisfies generated-project prettier) * Fix prettier: revert showCancelSuccess formatMessage to multiline (matches showCancelError)
feat: surface OMS return eligibility on order detail
…sages (#3884) * @W-22930993@ Handle cancel order API error scenarios with specific messages - 404: "Unable to cancel order" / "We could not find this order. Please refresh and try again." - 409: "Unable to cancel order" / "This order is already being processed and cannot be canceled. Please reach out to the Merchant." - Everything else (400, 401, 500, network): "Something went wrong" / "We couldn't process your cancellation right now. Please wait a moment and try again." Error status is read from error.response.status (commerce-sdk-isomorphic ResponseError shape). Cancel button remains enabled on error to allow retry. * Fix prettier: inline defaultMessage values * Address review: disable button on terminal errors, fix copy - Disable cancel button after 404/409 (terminal — retrying won't help) - Keep button enabled for transient errors (500/network) to allow retry - Fix spelling: "canceled" → "cancelled", "Merchant" → "merchant" - Reword 409 copy: "Please contact the merchant for assistance." * Address review: revert copy change, fix spelling only, add error tests - Revert message rewording — keep original copy, only fix "canceled" → "cancelled" and "Merchant" → "merchant" - Add unit tests for cancel error scenarios (404, 409, 500, network failure) - Tests verify correct title and description for each error code * Fix lint: split long defaultMessage lines, multiline isDisabled, remove unused act
…s unavailable (#3883) * @W-22930989@ Hide cancellation reason dropdown when OMS metadata API fails The dropdown is already hidden when reasonCodes is undefined (metadata fetch failed). Add retry: 1 to the useOmsMetaData query so React Query automatically retries once before giving up. If both attempts fail, the modal renders without the dropdown and the shopper can still confirm cancellation — the server applies the default reason. * Add unit tests for metadata API failure scenarios * Address review: remove retry override, conditional modal text - Remove retry: 1 — let TanStack default (3) handle resilience - Show "Confirm cancellation below." when dropdown is hidden (no reason codes) - Show "Select a reason and confirm cancellation." when dropdown is visible - Update test for conditional text
feat: return-item-selection modal on order detail
…3895) feat: add return review step + returnOmsOrder submission
* @W-22806929@ Add comprehensive cancel order integration tests 12 tests covering: - Eligibility: OMS order shows cancel, non-OMS hides it, partially shipped disables, already-cancelled disables, wrong customer disables - Happy path: modal opens, API called with correct payload, success feedback - Reason selection: submits selected reason to API - Error states: button disabled after 409 (terminal), enabled after 500 (retry) - Modal UX: keep order closes without API call, no-reason-codes shows alternative text * Address review: fix vacuous tests and lint errors - Fix "cancel submits with selected reason": rewrite as "cancel submits empty body when no reason codes available" — tests the actual contract (cancelReasonCodes: [] means no dropdown, empty body sent to API) - Fix "does not show cancel button for different customer": rename to "disables cancel button", assert unconditionally (button IS rendered but disabled for OMS orders with wrong customerId) - Rename "quantityAvailableToCancel < quantityOrdered" to "not fully cancellable" — clearer wording matching the strict-equality contract - Fix prettier: multiline mock resolved values - Remove conditional expects (jest/no-conditional-expect lint rule)
merge develop into feature/264-order-management
…3900) order-level status matrix util from item omsData
item-level return error states
…r badges (#3902) * feat: wire getOrderDisplayStatus into order badges @W-23093717 Use item-level status derivation to show Cancelled badge (red with × icon) on both order-detail and order-history pages when all items are cancelled. Non-cancelled orders retain the existing raw status display. * Address review: hoist isCancelled out of IIFEs, add negative tests - Hoist `isCancelled` derivation above the JSX return in order-detail and to the top of the .map() callback in order-history, removing the inline IIFEs. - Add negative tests asserting a partially-cancelled order does NOT render the "Cancelled" badge (regression guard). * Address review: memoize isCancelled, add OMS-only scope comment - Wrap isCancelled in useMemo on order-detail to avoid recomputing on every render (matches canCancel/returnableItems pattern). - Add comment clarifying the derivation is OMS-only; pure ECOM cancellations fall through to the raw status string in the badge. - Fix broken import from merge (return-error-utils missing `import {`). * Extract shared OrderStatusBadge component De-duplicate the cancelled badge JSX from order-detail and order-history into a reusable component with memoized status derivation. Unifies the message ID (order_status_badge.label.cancelled) so translators only localize the string once. Prepares a clean extension point for follow-up status WIs. --------- Signed-off-by: sf-madhuri-uppu <madhuri.uppu@salesforce.com>
feat: show return status in order status badge
…#3898) * fix(order-tracking): make carrier tracking links external A carrier tracking URL can arrive without a scheme (e.g. `www.carrier.com/t`). Rendered directly in an `href`, the browser treats it as a relative path and resolves it against the current page, so the tracking-number link navigated inside the app (`/account/orders/www.carrier.com/t`) instead of going to the carrier. Add `ensureExternalUrl` (app/utils/url.js): prepend `https://` to a scheme-less URL, keep already-absolute http(s) URLs, and reject unsafe or non-web values (`javascript:`, `data:`, `mailto:`, app-internal paths, …) by returning `undefined` so the link renders inactive rather than executable or wrong. Apply it to the OrderTracking tracking-number link. * chore: trigger CI re-run * harden ensureExternalUrl per review feedback Address review findings on the carrier-tracking-link normalizer. The function trusted the parsed scheme but never validated the resulting host, so several inputs produced an href that read like the carrier yet navigated elsewhere. - Reject userinfo: `https://www.ups.com@evil.com` parses to host `evil.com` with `www.ups.com` as the username — now rejected on both parse branches. - Reject backslash forms (`\` is treated as `/` by the URL parser) and run the relative-path guard on the raw input so a control char between slashes can't collapse `/x/host` into a protocol-relative `//host`. - Reject non-string input (was throwing on a non-string and crashing the render). - Reject bare filenames / empty-label hosts (e.g. `data.html`, `a..b`) that aren't real external hosts. - Drop the unreachable http branch and the dead IPv4 check. Centralize the checks in `isSafeExternalUrl` (http(s) only, no userinfo, plausible host), applied to both the absolute and prepended candidates. Add regression tests asserting the resolved host for each case. * harden ensureExternalUrl against dotted-protocol host confusion A scheme-less value shaped `label.tld://host` parses to a dotted protocol, so it skipped the dot-less validation and fell through to be re-prepended into `https://label.tld//host` — a link that reads like one host but navigates to another (same class as the userinfo spoof). Reject a dotted-protocol parse that carries an authority; a genuine `host:port` has an empty host on that parse and still resolves. Review feedback.
…#3906) * feat: add Track Shipment button to order detail @W-23091033 Render a "Track Shipment" order action per OMS shipment that has a tracking URL — each button opens its own shipment's carrier URL in a new tab (same destination as the tracking-number link), with no numeric suffix. When no shipment has a tracking URL, a single disabled button is shown. The Order Actions row is now full-width stacked on mobile and inline from the sm breakpoint up, so additional buttons no longer overflow on narrow screens. * feat: per-shipment tracking cards + single track action @W-23091033 Align the order-detail tracking UI with storefront-next and the order tracking epic's "flat list, no address association" scope: - Render tracking as a flat list of per-shipment cards (one card per omsData.shipments[] entry, ECOM order.shipments[] fallback), each with carrier, status, tracking-number link, and delivery dates. No address on the cards and no positional OMS-to-ECOM index join. - Consolidate the Track Shipment order action to a single button that links to the first shipment with a carrier URL (disabled when none), matching storefront-next getTrackShipmentHref. - Move the shipping address to a single order-level block shown only for single-shipment delivery orders; multi-shipment orders no longer associate an address per shipment. Updates the order detail tests and extracts the new Tracking heading message. * feat: per-shipment item boxes on order detail @W-23091033 Restructure the Items Ordered area to mirror storefront-next: render one bordered box per ECOM delivery shipment, each with a "Shipment N" header, that shipment's status, its items (grouped strictly by shipmentId; all items when there's a single delivery shipment), and its own native shipping address and method. Pickup-only orders (no delivery shipment) fall back to a flat item list. The top summary card no longer carries a standalone shipping-address block — the address now lives inside each shipment box. The Tracking cards stay in a separate flat section (OMS-preferred, ECOM fallback) and carry no address: there is no reliable join key between an OMS shipment and a specific ECOM shipment, so a per-shipment box never index-joins OMS data (the deferred TD). Reconcile the multi-shipment / BOPIS tests to the new per-box behavior: they previously asserted addresses were hidden and only passed because the products endpoint was unmocked (which suppressed the box render); they now mock products and assert each shipment's own address renders in its box, plus strict item-to-box placement by shipmentId. * feat: move tracking inside the first shipment box @W-23091033 Match the storefront-next design branch: the flat tracking list now renders INSIDE the first shipment box, after its items and before its shipping address, instead of as a standalone section above the items. It is still the whole flat list (rendered in box 1 only, never split per shipment), so there is no positional OMS↔ECOM index-join. Pickup-only orders (no delivery box) append the flat tracking list after the flat item list so tracking is never lost. Update the regression-lock test: it previously asserted tracking was a separate section; it now asserts tracking sits inside the first shipment box with the shipping address as a sibling block (no address inside a tracking card). * feat: tracking as flat section below shipment boxes @W-23091033 OMS tracking (omsData.shipments[]) has no join key back to a specific ECOM shipment (deferred TD-0326366), so the layout must not imply which tracking belongs to which shipment. Move the flat tracking list out of the first shipment box and render it as a single section BELOW all the boxes — a peer of the boxes, not nested in one. Each shipment box still shows its own products (grouped by ECOM shipmentId) and its own native address, both of which ARE known; only the tracking↔shipment link is unknown, and the flat list is honest about that. One code path covers single, multi, and pickup-only orders. Update the regression-lock test: it now asserts tracking renders OUTSIDE (not nested in) the shipment boxes, with no address/payment text in the section. * feat: Track Shipment dropdown for multi-shipment orders @W-23091033 UX option A for multi-shipment tracking (one of two branches for review). When an order has more than one shipment with a carrier tracking URL, the Track Shipment order action becomes a dropdown (Popover — the shared UI barrel exposes no Chakra Menu) listing each tracking number as an external link, so the shopper can pick which carrier link to open. Single-shipment orders keep the simple external-link button; orders with no tracking URL keep the single disabled button. This is interim: we still cannot say which tracking maps to which shipment (deferred TD-0326366), so options are labeled by tracking number, not by shipment contents. * chore(order-detail): self-contained rationale in tracking comments Rewrite the order-detail and test comments to explain the multi-shipment tracking behavior in project-internal terms (OMS and ECOM shipment arrays share no join key, so a tracking entry can't be tied to a specific shipment) instead of referencing tracking artifacts or another product. Customer-facing code should not carry process/cross-product references; the rationale lives in the spec and PR instead. * fix(order-detail): address review findings on tracking layout @W-23091033 - Surface items that match no shipment box (untagged, or a shipmentId naming no delivery shipment) in an "Other items" box, so multi-shipment orders never silently drop purchased items while the item-count header still counts them. - Localize the per-shipment box status pill via a shared formatter, so the box header and the tracking card render the same localized status instead of one showing a raw snake_case token in non-English locales. - Make the disabled Track Shipment button use aria-disabled + a hidden disabled-reason hint (keyboard/SR focusable), matching the Cancel/Return buttons in the same row, instead of a native disabled button. * fix(order-detail): meet AA contrast on shipment status pill @W-23091033 The "Shipment N" status pill rendered gray.700 text on gray.200 (4.04:1), which fails WCAG AA (4.5:1) and tripped the registered happy-path a11y snapshot (the color-contrast violation captured on the order detail page). Darken the text to gray.800 on the same gray.200 background for 5.88:1.
…821845 (#3907) order-management feature README + strip internal GUS refs
…eturn modal closes (#3910) return focus to the trigger when the return modal closes
apply CX UI text guidelines to order & account UI
|
Git2Gus App is installed but the |
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
…management # Conflicts: # packages/template-retail-react-app/CHANGELOG.md
Replace the temporary 5.1.0-unstable-* dev build with stable 5.4.0, the first release carrying the OMS Shopper Orders endpoints (oms-return-order, oms-cancel-order, oms-meta-data). develop's 5.2.0 has no OMS endpoints, so this is required for the OMS hooks to work rather than a plain bump. Build + tsc clean; ShopperOrders hooks 33/33 pass against 5.4.0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The isomorphic 5.2.0 -> 5.4.0 bump adds endpoints with no React hooks yet, so the 'all endpoints have hooks' guard tests failed. Add each new endpoint (requestOtp/verifyOtp, setupCustomerPaymentMethodReference, promoteTemporaryBasket, getComponent/resolveQualifiers, getProduct Images/Prices/Promotions) to the expected unimplemented list with a TODO, matching the test's documented convention. Implementing the hooks is out of scope for the OMS integration. Full commerce-sdk-react suite: 778/778 pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The commerce-sdk-isomorphic 5.2.0 -> 5.4.0 upgrade pushes vendor.js to 397.07KB, just over the 397 kB budget. Bump to 398 kB (main.js unchanged). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| /> | ||
| :{' '} | ||
| {trackingHref ? ( | ||
| <ChakraLink href={trackingHref} isExternal color="blue.600"> |
There was a problem hiding this comment.
isExternal on Chakra's Link only emits rel="noopener", not noopener noreferrer — so reverse-tabnabbing is covered, but the full order-detail URL still leaks as the Referer to the (attacker-influenceable) carrier host. The ensureExternalUrl JSDoc and the comment above claim noreferrer is applied. Should we set rel="noopener noreferrer" explicitly here, or soften the docs?
There was a problem hiding this comment.
Fixed in 719a562 — added an explicit rel="noopener noreferrer" here (and on the two Track Shipment links in order-detail). Since Chakra spreads ...rest after its built-in rel, the explicit prop wins over the isExternal-derived noopener, so the Referer no longer leaks to the carrier host.
| return statusEligible && shippingEligible | ||
| }, [isOmsEnabled, isRegistered, order, customerId]) | ||
| return ( | ||
| order.productItems?.every( |
There was a problem hiding this comment.
[].every(...) is vacuously true, so an OMS order with an empty productItems array would enable Cancel on an order with nothing in it. (Undefined is safe via ?.every ... ?? false — only the empty-array case leaks.) Worth a order.productItems?.length > 0 && guard in front.
There was a problem hiding this comment.
Fixed in 719a562 — added an explicit order.productItems?.length > 0 guard before the .every(...) so an empty items array no longer vacuously enables Cancel.
| if (!isOmsEnabled || !isRegistered || !order) return false | ||
| if (!isRegistered || !order) return false | ||
| if (!order.omsData) return false | ||
| const ownsOrder = order.customerInfo?.customerId === customerId |
There was a problem hiding this comment.
If both sides are undefined, undefined === undefined grants ownership — today only the isRegistered gate above prevents that, and SCAPI is the authoritative server-side check. Cross-customer access is correctly blocked (ids differ). Still, might be worth hardening to !!customerId && order.customerInfo?.customerId === customerId so it never depends on isRegistered implying a truthy id. Same pattern at L263 for returns.
There was a problem hiding this comment.
Fixed in 719a562 — ownership now requires a concrete id: !!customerId && order.customerInfo?.customerId === customerId, so two undefineds can no longer match. Belt-and-suspenders alongside the isRegistered gate and SCAPI.
| .filter(([, row]) => row?.checked) | ||
| .reduce((items, [itemId, row]) => { | ||
| const quantity = Number(row.quantity) | ||
| if (!Number.isFinite(quantity) || quantity <= 0) return items |
There was a problem hiding this comment.
nit: this drops non-positive/non-numeric quantities but not fractional or over-max ones — the JSDoc says the UX is integer-valued, yet 2.5 would serialize through. The server 400 ReturnQuantityExceeded is the real backstop, but a Number.isInteger check (and clamping in the picker's change handler) would make the invariant explicit rather than emergent.
There was a problem hiding this comment.
Left as-is for this integration PR — the quantity stepper UI can only emit integers, and the server 400 ReturnQuantityExceeded is the authoritative backstop. Noted as a follow-up nit rather than tightening the validator (and its tests) here.
| @@ -1,4 +1,7 @@ | |||
| ## v10.1.0-dev | |||
| - Bump `vendor.js` bundle-size budget from 397 kB to 398 kB to accommodate the `commerce-sdk-isomorphic` 5.4.0 upgrade. | |||
| - [Feature] Add item-level order returns on the order detail page. Registered shoppers who own an OMS-managed order can return eligible items (OMS-driven via `quantityAvailableToReturn`) through a modal with per-item quantity and reason selection, inline success/error feedback, and a status badge that reflects return progress (including partially-returned multi-unit lines). [#3904](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3904) | |||
There was a problem hiding this comment.
Not this line — the older #3861 entry just below (around L10) still says the cancel feature is "Gated behind app.oms.enabled config flag." This PR removed that flag (the feature is now data-gated on !!order.omsData), so that sentence is now stale and should be dropped.
- Add explicit rel="noopener noreferrer" to external carrier tracking links (Chakra isExternal only emits noopener, leaking the order page URL as Referer to the carrier host) - Guard canCancel against empty productItems ([].every is vacuously true) and require a concrete customerId match for ownership - Correct ensureExternalUrl JSDoc: isExternal alone yields only noopener - Fix stale CHANGELOG #3861 entry (cancel is data-gated on order.omsData, not the removed app.oms.enabled flag) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
719a562
| [omsShipmentCount, ecomShipmentCount] | ||
| ) | ||
| const returnableItems = useMemo(() => getReturnableItems(order), [order]) | ||
| const ownsOrder = order?.customerInfo?.customerId === customerId |
There was a problem hiding this comment.
The canCancel fix (L411) hardened this exact pattern with !!customerId &&, but the return path here still uses the bare comparison — so undefined === undefined grants ownership when both ids are missing (feeds showStartReturn). Same defect class as the cancel one; worth mirroring the guard: !!customerId && order?.customerInfo?.customerId === customerId. Not blocking (also gated by isRegistered + server-side SCAPI).
There was a problem hiding this comment.
Fixed in 66bfa7f — mirrored the guard on the return path: !!customerId && order?.customerInfo?.customerId === customerId, matching the canCancel fix. Good catch, this was the twin I missed.
| expect(buttons[0]).toHaveTextContent(/^Track Shipment$/) | ||
| expect(buttons[0]).toHaveAttribute('href', 'https://carrier.example.com/BBB') | ||
| expect(buttons[0]).toHaveAttribute('target', '_blank') | ||
| expect(buttons[0]).toHaveAttribute('rel', expect.stringContaining('noopener')) |
There was a problem hiding this comment.
nit: stringContaining('noopener') passes for both "noopener" and "noopener noreferrer", so it won't catch a regression that drops the noreferrer token this fix just added. Tightening to stringContaining('noreferrer') (here and L2962) would actually pin the fix.
There was a problem hiding this comment.
Fixed in 66bfa7f — tightened both assertions (L2926, L2962) to stringContaining('noreferrer') so a regression dropping the token now fails the test.
- Mirror the concrete-customerId ownership guard on the return path (order-detail L263): !!customerId && ... so undefined === undefined can't grant ownership. Twin of the canCancel guard. - Tighten the Track Shipment rel assertions to stringContaining 'noreferrer' so a regression dropping the token is actually caught. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…54713 Rows 16 & 17 of the UI text review sheet: - "Confirm cancellation" button -> "Confirm Cancellation" (button caps) - "This cancels the entire order." -> "Cancel the entire order." Updates en-US/en-GB source + compiled translations and test assertions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6a41cff to
72ae0ac
Compare
…-23254713
CX UI text guideline caps for buttons:
- "Keep order" -> "Keep Order" (cancel modal)
- "Review return" -> "Review Return" (return modal)
- "Submit return" -> "Submit Return" (return modal)
- "Track shipment {number}" -> "Track Shipment {number}" (tracking dropdown)
Updates en-US/en-GB source + compiled translations.
Also drops the changelog line for the cancel-modal copy tweak.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
72ae0ac to
4425edc
Compare
Code Review — PR #3914Comprehensive 4-angle review (line-by-line bugs, removed behavior, cross-file tracing, reuse/efficiency). Findings ranked by severity: Correctness / Bugs
Cleanup / Maintainability
SummaryThe only actionable bug is the missing |
|
Thanks for the thorough pass! Notes on the findings: Finding 1 (finalFocusRef) — not a bug; this is intentional. The Finding 5 (button casing) — already addressed. "Keep order"/"Confirm cancellation" are now "Keep Order"/"Confirm Cancellation" (plus Review Return / Submit Return / Track Shipment {number}) per the CX UI text review — landed in e2971e9 / 4425edc. Looks like the review ran against an earlier commit. Findings 2 (epoch date), 3 (useFeedbackState refactor), 4 (300ms magic number) — fair observations. Finding 2 is defensive against a sentinel the SOM API doesn't currently send; findings 3 and 4 are cleanup best handled as a follow-up rather than in this integration PR. Noted, thanks. |
…fund docs @W-23276443 (#3919) * chore(oms-ui): order-detail cleanup — feedback hook, announce const, epoch guard, refund docs @W-23276443 Non-blocking follow-ups from the PR #3914 automated review: - Extract a shared useFeedbackState hook for the cancel/return feedback banners (two independent instances so the Cancelled badge stays keyed to cancel feedback only). Behavior-preserving. - Name the 300ms screen-reader announce delay (ANNOUNCE_DELAY_MS), owned by the hook; all three call sites route through announce(). - Guard formatTrackingDate against truthy epoch-era sentinel dates so a "1 Jan 1970" never renders. - Document the downstream OMS/SOM refund flow (out of PWA Kit scope). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: drop CHANGELOG entry (cleanup/docs-only, skip changelog) @W-23276443 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(oms-ui): inline ANNOUNCE_DELAY_MS instead of extracting a hook @W-23276443 Drop the useFeedbackState hook (and its test) in favor of a module-level ANNOUNCE_DELAY_MS const in order-detail.jsx. The hook added a net-larger diff (new file + test) to centralize a small timer idiom; a named constant covers the actual value-add (no more magic 300) without the indirection. The inline useState/useRef feedback pairs and unmount cleanup are restored as-is. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(oms-ui): apply review feedback on refunds section @W-23276443 Address knhage's review suggestions: simpler heading, expand PSP acronym on first use, drop internal source attribution, reduce bold usage, and rephrase step arrows as sentences. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Integrates the
feature/264-order-managementbranch intodevelop. This lands thefull Salesforce Order Management (OMS/SOM) shopper experience on the retail-react-app
order detail / account pages — grouped below into the three shopper actions it delivers:
Order Returns, Order Cancellation, and Shipment Tracking — plus the shared
foundation they build on.
25 commits, built up across the WIs below. All constituent changes already merged to the
feature branch via their own reviewed PRs; this PR is the integration merge.
Order Returns
returnOmsOrdersubmission (feat: add return review step + returnOmsOrder submission @W-22821838 #3895)Order Cancellation
Shipment Tracking
OrderTrackingcomponent (@W-22918057 Extract standalone OrderTracking component #3865)Shared foundation
Supporting work the three flows above depend on:
cancelOmsOrder,returnOmsOrdermutations andgetOmsMetaDatahook (@W-22931003 Add cancelOmsOrder and getOmsMetaData hooks to commerce-sdk-react #3864, @W-22821835@ Add returnOmsOrder mutation to commerce-sdk-react #3869)getOrderDisplayStatusaggregates item-levelomsDatainto one display status (feat: order-level status matrix util from item omsData @W-23163246 #3900)createOrderso OMS can ingest (@W-22821836@ Sync payment-instrument amount before createOrder so OMS can ingest #3875); invalidateuseCustomerOrdersaftercreateOrder(fix: invalidateuseCustomerOrdersaftercreateOrder@W-22821836 #3878)Integration fixes (post branch-open)
Applied on this branch to make the merge into
developclean and green — not part of theoriginal feature work:
commerce-sdk-isomorphicto stable5.4.0. The branch carried a temporary5.1.0-unstable-*dev build. develop is on5.2.0, which has no OMS Shopper Ordersendpoints;
5.4.0is the first stable release that shipsoms-return-order,oms-cancel-order, andoms-meta-data, so the OMS hooks require it. Build +tscclean.5.4.0endpoints as unimplemented. The 5.2.0 → 5.4.0 bump adds 8 endpoints(across ShopperLogin, ShopperCustomers, ShopperBasketsV2, ShopperExperience, ShopperProducts)
that don't have React hooks yet, which tripped the SDK's "all endpoints have hooks" guard
tests. Added each to the test's
expectedunimplemented list with a TODO per the test's ownconvention; implementing those hooks is out of scope for this OMS integration. Full
commerce-sdk-reactsuite is 778/778 green.commerce-sdk-reactCHANGELOG entry for the OMS order-action hooks(
cancelOmsOrder/returnOmsOrdermutations +getOmsMetaData), which had landed withoutone — required to pass the per-package changelog check.
vendor.jsbundle-size budget 397 kB → 398 kB. The 5.4.0 isomorphic upgradepushes
vendor.jsto 397.07KB, just over the old budget;main.jsis unchanged.rel="noopener noreferrer"to theexternal carrier tracking links (Chakra
isExternalalone only emitsnoopener, leaking theorder-page URL as
Referer); hardenedcanCancel/return ownership checks to require aconcrete
customerIdand guard the empty-productItemscase; corrected theensureExternalUrlJSDoc; and fixed a stale CHANGELOG line that referenced the removed
app.oms.enabledflag.noreferrer(not justnoopener) so aregression that drops the token is actually caught.
sheet:
Keep Order,Confirm Cancellation,Review Return,Submit Return,Track Shipment {number}, andCancel the entire order.(source + en-US/en-GB/en-XAtranslations regenerated).
Testing
Each constituent PR carried its own unit/integration coverage and passed CI on merge to the
feature branch. CI on this integration PR is the gate before merge.