Skip to content
Open
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
330b8e9
feat(orders): ORDERS-7706 add new return page layout
jpajar May 4, 2026
ac9bf8a
feat(returns): ORDERS-7704 condition for new returns on orders page (…
bc-vivekaggarwal May 12, 2026
af49f3e
Merge pull request #2649 from bigcommerce/ORDERS-7706
jpajar May 8, 2026
14208d9
feat(returns): ORDERS-7705 condition for new returns on order details…
bc-vivekaggarwal May 12, 2026
2428327
feat(orders): ORDERS-7734 add template for guest return portal
BC-SEven Apr 28, 2026
6c76072
feat(returns): ORDERS-7708 new-return page — render items from order …
bc-vivekaggarwal May 27, 2026
fe09e79
feat(orders): ORDERS-7331 add template for create return page
May 28, 2026
2d3cdbc
Merge pull request #2649 from bigcommerce/ORDERS-7706
jpajar May 8, 2026
963636a
feat(returns): ORDERS-7717 - add new returns list ui
bc-bryan May 26, 2026
ab8f7d5
feat(returns): ORDERS-7717 - use rma number
bc-bryan May 27, 2026
bfe4b85
feat(returns): ORDERS-7717 - add CHANGELOG item
bc-bryan May 28, 2026
c855cfe
feat(returns): ORDERS-7717 - address copilot review
bc-bryan May 28, 2026
b995492
feat(orders): ORDERS-7718 add return details page layout
jpajar Jun 5, 2026
04f5eef
feat(orders): ORDERS-7718 add status mappping from enum
jpajar Jun 9, 2026
0ee5349
Orders 7764 render create return page with context (#2673)
bc-vivekaggarwal Jun 11, 2026
fe8ed93
feat(orders): ORDERS-7707 improve create return page design for mobil…
jpajar Jun 16, 2026
46ddad6
feat(orders): ORDERS-7715 add createReturn mutation and confirmation …
jpajar Jun 22, 2026
d9fa074
feat(orders): ORDERS-7715 handle double submissions
jpajar Jun 22, 2026
9f59f8d
feat(orders): ORDERS-7859 add error message on failed return creation
jpajar Jun 23, 2026
74042bb
feat(returns): ORDERS-7857 add loading state as return submission (#2…
bc-vivekaggarwal Jun 25, 2026
b751950
feat(returns): ORDERS-7751 - add return details page data
bc-bryan Jun 15, 2026
8c78e1f
feat(orders): ORDERS-7751 add placeholder image
bc-bryan Jun 19, 2026
3bfab15
feat(returns): ORDERS-7751 check resolution and requested exist
bc-bryan Jun 19, 2026
156d3c2
feat(returns): ORDERS-7751 use handlebars for details templates
bc-bryan Jun 25, 2026
521b10d
feat(returns): ORDERS-7751 update thumbnail image TODO with id
bc-bryan Jun 25, 2026
381223d
feat(returns): ORDERS-7751 remove unused localization keys and markup
bc-bryan Jun 25, 2026
2d58e1e
feat(returns): ORDERS-7751 use span tag instead of dt tag
bc-bryan Jun 25, 2026
b73b93b
feat(returns): ORDERS-7751 output localized string instead of status …
bc-bryan Jun 26, 2026
095fd05
feat(returns): ORDERS-7751 add sr-only label for orderId
bc-bryan Jun 26, 2026
baca54d
feat(returns): ORDERS-7751 remove per item status
bc-bryan Jun 30, 2026
ac75952
feat(returns): ORDERS-7751 add order_number_label key
bc-bryan Jun 30, 2026
38101ea
feat(returns): ORDERS-7751 reference correct object for thumbnail fal…
bc-bryan Jun 30, 2026
730feae
feat(orders): ORDERS-7681 add Cancel return button and handler to ret…
jpajar Jun 24, 2026
5b4f55f
feat(orders): ORDERS-7681 extend Account module for ReturnDetails module
jpajar Jul 1, 2026
702765d
feat(returns): ORDERS-7771 tighten spacing on mobile and tablet viewp…
bc-bryan Jul 1, 2026
87f3cd3
feat(returns): ORDERS-7771 update changelog
bc-bryan Jul 1, 2026
4dca697
feat(returns): ORDERS-7771 scope shared account css changes to return…
bc-bryan Jul 1, 2026
e85a534
feat(returns): ORDERS-7771 fix mobile stacking for detail items
bc-bryan Jul 1, 2026
0f0baeb
feat(returns): ORDERS-7771 remove invalid margin value
bc-bryan Jul 1, 2026
1570281
feat(returns): ORDERS-7771 move returns list specific styles to it's …
bc-bryan Jul 1, 2026
cac1afe
Merge pull request #2688 from bigcommerce/ORDERS-7681
jpajar Jul 2, 2026
fa882b4
feat(returns): ORDERS-7771 address copilot feedback
bc-bryan Jul 2, 2026
8a15ff6
Merge pull request #2694 from bigcommerce/ORDERS-7771-lists-returns-p…
bc-bryan Jul 2, 2026
20ca5bc
feat(orders): ORDERS-7736 implement guest return portal page (#2696)
BC-SEven Jul 8, 2026
4a8e4e7
feat(orders): ORDERS-7886 Update error message to generic message wit…
BC-SEven Jul 12, 2026
0092e69
feat(returns): ORDERS-7752 add cursor pagination to returns
bc-bryan Jul 15, 2026
fd27939
feat(orders): ORDERS-7935 add badge for CANCELLED order returns
jpajar Jul 15, 2026
0b49797
feat(returns): ORDERS-7752 use pagination.<resource> key
bc-bryan Jul 15, 2026
e94a291
feat(orders): ORDERS-7736 add client side validation for guest return…
BC-SEven Jul 15, 2026
99a9c87
Merge pull request #2705 from bigcommerce/ORDERS-7935
jpajar Jul 15, 2026
9ccb769
feat(returns): ORDERS-7752 use div with region role
bc-bryan Jul 16, 2026
d217a72
feat(returns): ORDERS-7900: strike requested with resolved resolution…
bc-vivekaggarwal Jul 16, 2026
d067100
feat(orders): ORDERS-7770 add responsive styling for returns details …
jpajar Jul 16, 2026
e9386f6
Merge pull request #2704 from bigcommerce/ORDERS-7752-returns-page-pa…
bc-bryan Jul 17, 2026
32162bb
Merge pull request #2706 from bigcommerce/ORDERS-7770
jpajar Jul 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Draft
- Implement guest return portal page (ORDERS-7736) [#2696](https://github.com/bigcommerce/cornerstone/pull/2696)
- Add mobile and tablet responsiveness for returns list page (ORDERS-7771)
- Add return details page (ORDERS-7751)
- Add new create-return page [#2669](https://github.com/bigcommerce/cornerstone/pull/2669)
- Validate picklist option `available_to_sell` on PDP so Add to Cart is disabled and a "maximum purchasable quantity for {picklist} is {qty}" error is shown when the requested quantity exceeds a selected picklist item's stock, even when the main product still has enough stock [#2684](https://github.com/bigcommerce/cornerstone/pull/2684)
- Render backorder prompts for bundled picklist options on the account order details page, using `linked_bundled_item` data per option; move parent item backorder prompts above the options list; reduce backorder prompt font size and add spacing between prompts and adjacent option rows [#2687](https://github.com/bigcommerce/cornerstone/pull/2687)
- Hide option values on the PDP that would complete a "disable and hide" rule for the current selection, and steer the shopper off a default selection that is itself a forbidden combination [#2678](https://github.com/bigcommerce/cornerstone/pull/2678)
Expand All @@ -18,11 +22,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix duplicate `id="default_instrument"` on Update Payment Method page [#2661](https://github.com/bigcommerce/cornerstone/pull/2661)
- Respect `available_to_sell` on PDP so the Sold Out alert is hidden and the Add to Cart button stays enabled for backorderable products, and is disabled when quantity exceeds `available_to_sell` [#2659](https://github.com/bigcommerce/cornerstone/pull/2659)
- Updated accessibility features [2656](https://github.com/bigcommerce/cornerstone/pull/2656)
- Adds new guest-return-portal page. [2645](https://github.com/bigcommerce/cornerstone/pull/2645)
- Update 'Ship to' copy for multi address orders [#2655](https://github.com/bigcommerce/cornerstone/pull/2655)
- Fixed typo in category page reset-filters live region handler [#2643](https://github.com/bigcommerce/cornerstone/pull/2643)
- Swap content/data keys in onProductOptionsChanged event detail [#2640](https://github.com/bigcommerce/cornerstone/pull/2640)
- Gate Order Details "Return" button on `settings.returns_v2_enabled` with `settings.returns_enabled` fallback (ORDERS-7705)
- Fix shipment info showing for cancelled orders [#2654](https://github.com/bigcommerce/cornerstone/pull/2654)
- Render backorder prompts on My Account "Your orders" page [#2650](https://github.com/bigcommerce/cornerstone/pull/2650)
- Gate account orders list "Return Items" link on returns settings (ORDERS-7704) [#2653](https://github.com/bigcommerce/cornerstone/pull/2653)
- Added new Return list view (ORDERS-7717) [#2664] (https://github.com/bigcommerce/cornerstone/pull/2664)

## 6.19.1 (04-09-2026)
- Update stencil-utils versionto 6.23.0 [#2638](https://github.com/bigcommerce/cornerstone/pull/2638)
Expand Down
7 changes: 7 additions & 0 deletions assets/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ __webpack_public_path__ = window.__webpack_public_path__; // eslint-disable-line
import Global from './theme/global';

const getAccount = () => import('./theme/account');
const getCreateReturn = () => import('./theme/create-return');
const getReturnDetails = () => import('./theme/return-details');
const getGuestReturnPortal = () => import('./theme/guest-return-portal');
const getLogin = () => import('./theme/auth');
const noop = null;

Expand All @@ -12,13 +15,17 @@ const pageClasses = {
account_addressbook: getAccount,
shippingaddressform: getAccount,
account_new_return: getAccount,
create_return: getCreateReturn,
guest_return_portal: getGuestReturnPortal,
'add-wishlist': () => import('./theme/wishlist'),
Comment on lines 14 to 20
account_recentitems: getAccount,
account_downloaditem: getAccount,
editaccount: getAccount,
account_inbox: getAccount,
account_saved_return: getAccount,
account_returns: getAccount,
account_return_detail: getReturnDetails,
return_detail: getReturnDetails,
account_paymentmethods: getAccount,
account_addpaymentmethod: getAccount,
account_editpaymentmethod: getAccount,
Expand Down
173 changes: 173 additions & 0 deletions assets/js/theme/create-return.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import PageManager from './page-manager';

export default class CreateReturn extends PageManager {
onReady() {
const form = document.querySelector('[data-new-return-form]');
if (!form) return;

this.bindOrderLineItemEvents();
this.bindSubmit(form);
}

bindOrderLineItemEvents() {
// All three selects per row (qty, resolution, reason) trigger a submit-state check.
document.querySelectorAll('.newReturn-orderLineItem select').forEach(sel => {
sel.addEventListener('change', () => this.updateSubmitState());
});
}

updateSubmitState() {
const selectedItems = this.getSelectedItems();
const isValid = selectedItems.length > 0 && selectedItems.every(itemRow => {
const itemId = itemRow.dataset?.itemId;
if (!itemId) return false;
const resolutionEl = document.getElementById(`resolution-${itemId}`);
const reasonEl = document.getElementById(`reason-${itemId}`);
return resolutionEl && resolutionEl.value && reasonEl && reasonEl.value;
});

const submitBtn = document.getElementById('return-new-submitBtn');
if (submitBtn) submitBtn.disabled = !isValid;
}

bindSubmit(form) {
form.addEventListener('submit', async event => {
event.preventDefault();

// Ignore clicks while a request is in flight
if (this.isSubmitting) return;
this.isSubmitting = true;

const submitBtn = document.getElementById('return-new-submitBtn');
const overlay = document.querySelector('[data-new-return-view] .loadingOverlay');
if (submitBtn) submitBtn.disabled = true;
if (overlay) overlay.style.display = 'block';
this.clearError();

try {
const response = await this.createReturn(this.buildReturnInput());
const errorMessages = this.getErrorMessages(response);

if (errorMessages.length) {
this.showError(errorMessages.join(' '));
return;
}

this.showConfirmation();
Comment thread
cursor[bot] marked this conversation as resolved.
} catch (error) {
this.showError(this.context.genericError);
} finally {
this.isSubmitting = false;
if (submitBtn) submitBtn.disabled = false;
if (overlay) overlay.style.display = '';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Submit re-enabled without validation

Medium Severity

After a failed or errored createReturn request, the submit handler’s finally block sets return-new-submitBtn to enabled without calling updateSubmitState(), so the button can stay clickable when no line items have quantity, resolution, and reason filled in.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 34bc18e. Configure here.

}
});
}

getErrorMessages(response) {
const transportErrors = Array.isArray(response?.errors) ? response.errors : [];
const returnErrors = Array.isArray(response?.data?.order?.return?.createReturn?.errors)
? response.data.order.return.createReturn.errors
: [];

return transportErrors
.concat(returnErrors)
.map(error => error.message)
.filter(Boolean);
}

showError(message) {
const errorBox = document.getElementById('return-new-error');
if (!errorBox) return;

const messageEl = errorBox.querySelector('[data-return-error-message]');
if (messageEl) messageEl.textContent = message || '';
errorBox.style.display = '';
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

API errors never shown

Medium Severity

getErrorMessages collects GraphQL error text, but showError only reveals the static error box and never writes those messages into the DOM, so shoppers always see the generic copy.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4a8e4e7. Configure here.


clearError() {
const errorBox = document.getElementById('return-new-error');
if (errorBox) errorBox.style.display = 'none';
}

// Storefront GraphQL `createReturn` mutation
createReturn(input) {
return fetch('/graphql', {
Comment thread
cursor[bot] marked this conversation as resolved.
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.context.storefrontApiToken}`,
},
body: JSON.stringify({
query: `mutation CreateReturn($input: CreateOrderReturnInput!) {
order {
return {
createReturn(input: $input) {
entityId
errors {
__typename
... on Error {
message
}
}
}
}
}
}`,
variables: { input },
}),
}).then(response => response.json());
}

// Swaps the form view for the success confirmation view.
showConfirmation() {
const formView = document.querySelector('[data-new-return-view]');
const confirmation = document.querySelector('[data-new-return-confirmation]');

if (formView) formView.style.display = 'none';
if (confirmation) confirmation.style.display = '';
}

/**
* Builds the `CreateOrderReturnInput` payload from the selected line items.
*
* @returns {{ orderEntityId: number, items: Array }}
*/
buildReturnInput() {
return {
orderEntityId: parseInt(this.context.order?.id, 10),
items: this.getSelectedItems().flatMap(itemRow => {
const itemId = itemRow.dataset?.itemId;
if (!itemId) return [];
return [{
lineItemEntityId: parseInt(itemId, 10),
quantity: parseInt(document.getElementById(`qty-${itemId}`)?.value, 10),
reasonEntityId: document.getElementById(`reason-${itemId}`)?.value,
requestedResolution: this.buildRequestedResolution(document.getElementById(`resolution-${itemId}`)?.value),
}];
Comment thread
cursor[bot] marked this conversation as resolved.
}),
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Additional note omitted from payload

Medium Severity

The create-return page collects an “Additional Note” in data-new-return-note, but buildReturnInput() never reads that field, so shopper notes are dropped and never sent with the createReturn mutation.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit bcea92e. Configure here.

}

// Maps a selected resolution value to a `RequestedResolutionInput`
buildRequestedResolution(value) {
const systemResolutionTypes = ['EXCHANGE', 'REFUND', 'STORE_CREDIT'];

if (systemResolutionTypes.includes(value)) {
return { type: value };
}

return { type: 'CUSTOM', customResolutionEntityId: value };
}

// Returns only row containers with a non-zero quantity selected.
getSelectedItems() {
return Array.from(document.querySelectorAll('.newReturn-orderLineItem')).filter(itemRow => {
const itemId = itemRow.dataset?.itemId;
if (!itemId) return false;
const qtyInput = document.getElementById(`qty-${itemId}`);
return qtyInput && parseInt(qtyInput.value, 10) > 0;
});
}
}
103 changes: 103 additions & 0 deletions assets/js/theme/guest-return-portal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import PageManager from './page-manager';
import NotFoundError from './not-found-error';

export default class GuestReturnPortal extends PageManager {
onReady() {
const form = document.querySelector('[data-guest-return-portal-form]');
if (!form) return;

this.bindSubmit(form);
}

bindSubmit(form) {
form.addEventListener('submit', async event => {
event.preventDefault();
// Ignore clicks while a request is in flight
if (this.isSubmitting) return;
this.isSubmitting = true;
const submitBtn = document.getElementById('return-guest-submit-btn');
const overlay = document.querySelector('.guest-return-portal .loadingOverlay');
if (submitBtn) submitBtn.disabled = true;
if (overlay) overlay.style.display = 'block';
const payload = this.buildRequestPayload();

try {
const response = await this.startReturnGuestSession(payload);
const responseData = await response.json();
if (!response.ok) {
throw new Error('Network response was not ok');
}

if (responseData?.errors?.length > 0) {
throw new Error('Failed to start return guest session');
}

if (responseData?.data?.order?.return?.startReturnGuestSession?.errors?.length > 0) {
for (const error of responseData.data.order.return.startReturnGuestSession.errors) {
if (error.__typename === 'NotFoundError') {
throw new NotFoundError(error.message);
}
}
throw new Error('Failed to start return guest session');
}

window.location.href = `/create-return/${payload.orderEntityId}`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Guest redirect ignores locale path

Medium Severity

After a successful guest session, navigation uses a root-absolute /create-return/{orderId} URL, which drops the active language subfolder on multi-language storefronts and can land shoppers on the wrong locale or a 404.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 20ca5bc. Configure here.

} catch (error) {
if (error instanceof NotFoundError) {
this.showError(this.context.notFoundError);
} else {
this.showError(this.context.genericError);
}

this.isSubmitting = false;
if (submitBtn) submitBtn.disabled = false;
if (overlay) overlay.style.display = '';
}
});
}

buildRequestPayload() {
return {
email: document.getElementById('guest-return-email-input')?.value,
orderEntityId: parseInt(document.getElementById('guest-return-order-input')?.value, 10),
};
}

startReturnGuestSession(input) {
return fetch('/graphql', {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

GraphQL fetch omits base URL

Medium Severity

New returns flows call fetch('/graphql', …) with a root-absolute URL, unlike other theme API usage that passes secureBaseUrl, so GraphQL may miss the correct prefixed path on multi-language storefronts.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 99a9c87. Configure here.

method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.context.storefrontApiToken}`,
},
body: JSON.stringify({
query: `mutation StartReturnGuestSession($input: StartReturnGuestSessionInput!) {
order {
return {
startReturnGuestSession(input: $input) {
errors {
__typename
... on Error {
message
}
}
}
}
}
}`,
variables: { input },
}),
});
}

showError(message) {
const alertBox = document.querySelector('.guest-return-portal-error-container .alertBox');
if (!alertBox) return;

const messageElement = alertBox.querySelector('#alertBox-message-text');

alertBox.style.display = 'block';
if (messageElement) messageElement.textContent = message || '';
}
}
6 changes: 6 additions & 0 deletions assets/js/theme/not-found-error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default class NotFoundError extends Error {
constructor(message) {
super(message);
this.name = 'NotFoundError';
}
}
Loading