diff --git a/assets/js/app.js b/assets/js/app.js index b9e92bfdc8..2d95c5bef2 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -3,6 +3,7 @@ __webpack_public_path__ = window.__webpack_public_path__; // eslint-disable-line import Global from './theme/global'; const getAccount = () => import('./theme/account'); +const getAddReturnNew = () => import('./theme/add-return-new'); const getLogin = () => import('./theme/auth'); const noop = null; @@ -11,7 +12,7 @@ const pageClasses = { account_order: getAccount, account_addressbook: getAccount, shippingaddressform: getAccount, - account_new_return: getAccount, + account_new_return: getAddReturnNew, 'add-wishlist': () => import('./theme/wishlist'), account_recentitems: getAccount, account_downloaditem: getAccount, diff --git a/assets/js/theme/add-return-new.js b/assets/js/theme/add-return-new.js new file mode 100644 index 0000000000..44fe3cd0ef --- /dev/null +++ b/assets/js/theme/add-return-new.js @@ -0,0 +1,253 @@ +import PageManager from './page-manager'; + +export default class AddReturnNew extends PageManager { + onReady() { + const $form = $('[data-new-return-form]'); + if (!$form.length) return; + + // --------------------------------------------------------------------------- + // Hardcoded page data which mirrors the TypeScript returns-ui interface. + // Replace with Stencil context when it is available. + // --------------------------------------------------------------------------- + this.pageData = { + order: { + id: '101', + datePlaced: new Date('2024-01-01'), + status: 'Completed', + currency: 'AUD', + isTaxInclusive: true, + shipping: { + address: { + fullName: 'Jane Smith', + address1: '1-3 Smail Street', + city: 'Ultimo', + state: 'NSW', + zip: '2007', + country: 'Australia', + }, + method: 'Australia Post', + dateShipped: 'May 15, 2024', + trackingNumber: '7E27315406641', + }, + items: [ + { + id: 'item-1', + name: 'Product Name', + variant: 'Blue / Black / Green', + sku: 'SKU-001', + quantity: 1, + returnableQuantity: 1, + thumbnailUrl: '', + totalIncTax: 123.99, + totalExTax: 110.71, + }, + { + id: 'item-2', + name: 'Product Name', + variant: 'Blue / Black / Green', + sku: 'SKU-002', + quantity: 1, + returnableQuantity: 1, + thumbnailUrl: '', + totalIncTax: 123.99, + totalExTax: 110.71, + }, + { + id: 'item-3', + name: 'Product Name', + variant: 'Blue / Black / Green', + sku: 'SKU-003', + quantity: 1, + returnableQuantity: 1, + thumbnailUrl: '', + totalIncTax: 123.99, + totalExTax: 110.71, + }, + ], + }, + reasons: [ + { id: 'reason-1', nameForMerchant: 'Damaged or defective', active: true }, + { id: 'reason-2', nameForMerchant: 'Wrong item received', active: true }, + { id: 'reason-3', nameForMerchant: 'Changed my mind', active: true }, + { id: 'reason-4', nameForMerchant: 'Item not as described', active: true }, + { id: 'reason-5', nameForMerchant: 'Arrived too late', active: true }, + ], + resolutions: [ + { resolutionType: 'Refund' }, + { resolutionType: 'Exchange' }, + { resolutionType: 'Store Credit' }, + ], + }; + + this.renderHeader(); + this.renderShippingAddress(); + this.renderShippingMethod(); + this.renderOrderLineItems(); + this.bindSubmit($form); + } + + renderHeader() { + const { order } = this.pageData; + const dateLabel = order.datePlaced.toLocaleDateString('en-AU', { year: 'numeric', month: 'long', day: 'numeric' }); + + document.getElementById('return-new-orderId').textContent = order.id; + document.getElementById('return-new-statusBadge').textContent = order.status.toUpperCase(); + document.getElementById('return-new-datePlaced').textContent = `Order date: ${dateLabel}`; + } + + renderShippingAddress() { + const { address } = this.pageData.order.shipping; + const shippingAddressHtml = `

Shipping address

+

${this.escHtml(address.fullName)}

+

${this.escHtml(address.address1)}

+

${this.escHtml(address.city)}, ${this.escHtml(address.state)} ${this.escHtml(address.zip)}

+

${this.escHtml(address.country)}

`; + + document.getElementById('return-new-shippingAddress').innerHTML = shippingAddressHtml; + } + + renderShippingMethod() { + const { shipping } = this.pageData.order; + const shippingMethodHtml = `

Shipping method

+

${this.escHtml(shipping.method)}

+

Shipped on ${this.escHtml(shipping.dateShipped)}

+

${this.escHtml(shipping.trackingNumber)}

`; + + document.getElementById('return-new-shippingMethod').innerHTML = shippingMethodHtml; + } + + renderOrderLineItems() { + const { order, reasons, resolutions } = this.pageData; + + const resolutionOptions = [ + '', + ...resolutions.map(resolution => { + const optionValue = this.isCustomResolution(resolution) ? this.escHtml(resolution.id) : this.escHtml(resolution.resolutionType); + const optionLabel = this.isCustomResolution(resolution) ? this.escHtml(resolution.nameForMerchant) : this.escHtml(resolution.resolutionType); + return ``; + }), + ].join(''); + + const reasonOptions = [ + '', + ...reasons + .filter(reason => reason.active) + .map(reason => ``), + ].join(''); + + const orderLineItemsHtml = order.items.map(item => { + const price = new Intl.NumberFormat('en-AU', { style: 'currency', currency: order.currency }).format(item.totalIncTax); + const thumbnailHtml = item.thumbnailUrl + ? `${this.escHtml(item.name)}` + : '
No image
'; + + return ` +
+ ${thumbnailHtml} +
+

${this.escHtml(item.name)}

+

${this.escHtml(item.variant)}

+

${price} x ${item.quantity}

+
+
+
+ + + +
+ + +
+
`; + }).join(''); + + document.getElementById('return-new-itemsList').innerHTML = orderLineItemsHtml; + this.bindOrderLineItemEvents(); + } + + bindOrderLineItemEvents() { + document.querySelectorAll('.form-increment .button--icon').forEach(button => { + button.addEventListener('click', () => { + const itemId = button.getAttribute('data-item-id'); + const action = button.getAttribute('data-action'); + const item = this.pageData.order.items.find(orderLineItem => orderLineItem.id === itemId); + const quantityInput = document.getElementById(`qty-${itemId}`); + let quantity = parseInt(quantityInput.value, 10); + + if (action === 'inc' && quantity < item.returnableQuantity) quantity++; + else if (action === 'dec' && quantity > 0) quantity--; + + quantityInput.value = quantity; + document.querySelector(`[data-action="dec"][data-item-id="${itemId}"]`).disabled = quantity === 0; + document.querySelector(`[data-action="inc"][data-item-id="${itemId}"]`).disabled = quantity >= item.returnableQuantity; + + this.updateSubmitState(); + }); + }); + + document.querySelectorAll('.newReturn-select').forEach(selectElement => { + selectElement.addEventListener('change', () => this.updateSubmitState()); + }); + } + + updateSubmitState() { + const selectedItems = this.pageData.order.items.filter(item => parseInt(document.getElementById(`qty-${item.id}`).value, 10) > 0); + + const isValid = selectedItems.length > 0 && selectedItems.every(item => ( + document.getElementById(`resolution-${item.id}`).value + && document.getElementById(`reason-${item.id}`).value + )); + + document.getElementById('return-new-submitBtn').disabled = !isValid; + } + + bindSubmit($form) { + $form.on('submit', event => { + event.preventDefault(); + + const selectedItems = this.pageData.order.items.filter(item => parseInt(document.getElementById(`qty-${item.id}`).value, 10) > 0); + + const newReturn = { + orderId: this.pageData.order.id, + items: selectedItems.map(item => ({ + id: item.id, + quantity: parseInt(document.getElementById(`qty-${item.id}`).value, 10), + reasonId: document.getElementById(`reason-${item.id}`).value, + resolution: document.getElementById(`resolution-${item.id}`).value, + })), + }; + + // TODO: wire up API call when available + console.log('Submitting return:', JSON.stringify(newReturn, null, 2)); + }); + } + + isCustomResolution(resolution) { + return 'id' in resolution; + } + + escHtml(str) { + return String(str) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + } +} diff --git a/assets/scss/components/stencil/addReturn/_addReturn.scss b/assets/scss/components/stencil/addReturn/_addReturn.scss index 1c120b9746..e5d41bf84c 100644 --- a/assets/scss/components/stencil/addReturn/_addReturn.scss +++ b/assets/scss/components/stencil/addReturn/_addReturn.scss @@ -1,6 +1,10 @@ // ============================================================================= // ADD RETURN REQUEST (CSS) // ============================================================================= +// +// Styles shared by both the legacy add-return page (.account--addReturn) +// and the new add-return-new page (.newReturn). +// ============================================================================= .account--addReturn { @@ -137,3 +141,246 @@ width: grid-calc(6, $total-columns); } } + + +// ============================================================================= +// NEW RETURN PAGE (.newReturn) +// ============================================================================= + +.newReturn { + color: color("link"); + max-width: remCalc(1100px); + margin: 0 auto; + padding: 0 spacing("single") spacing("double"); +} + +.newReturn-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: spacing("eighth"); +} + +.newReturn-titleGroup { + display: flex; + align-items: center; + gap: spacing("half"); +} + +.newReturn-title { + font-size: remCalc(28px); + font-weight: fontWeight("bold"); + margin: 0; +} + +.newReturn-statusBadge { + display: inline-block; + padding: spacing("eighth") spacing("quarter"); + border: remCalc(1.5px) solid color("orderStatus", "completed"); + border-radius: remCalc(4px); + font-size: remCalc(11px); + font-weight: fontWeight("bold"); + letter-spacing: remCalc(1px); + color: color("orderStatus", "completed"); + text-transform: uppercase; +} + +.newReturn-headerActions { + display: flex; + align-items: center; + gap: spacing("half"); +} + +.newReturn-btnBack { + padding: spacing("quarter") spacing("single") * 0.83; + border: remCalc(1.5px) solid color("link"); + border-radius: remCalc(6px); + background: color("whites", "bright"); + font-size: fontSize("smallest"); + font-weight: fontWeight("medium"); + cursor: pointer; + text-decoration: none; + color: color("link"); +} + +.newReturn-btnPolicy { + padding: spacing("quarter") spacing("single") * 0.83; + border: 0; + border-radius: remCalc(6px); + background: color("greys", "darkest"); + color: color("whites", "bright"); + font-size: fontSize("smallest"); + font-weight: fontWeight("medium"); + cursor: pointer; + text-decoration: none; +} + +.newReturn-orderDate { + font-size: remCalc(13px); + color: color("greys", "base"); + margin: spacing("eighth") 0 spacing("single") * 0.83; +} + +.newReturn-divider { + border: 0; + border-top: container("border"); + margin: spacing("single") 0; +} + + +.newReturn-shippingGrid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: remCalc(32px); +} + +.newReturn-shippingSection { + + h4 { + font-size: fontSize("smallest"); + font-weight: fontWeight("semibold"); + color: color("link"); + margin: 0 0 spacing("quarter"); + } + + p, + a { + font-size: remCalc(13px); + margin: remCalc(2px) 0; + text-decoration: none; + } + + p { + color: color("greys", "dark"); + } +} + + +.newReturn-orderLineItem { + display: flex; + align-items: center; + gap: remCalc(16px); + padding: remCalc(16px) 0; + border-bottom: container("border"); + + &:last-child { + border-bottom: 0; + } +} + +.newReturn-orderLineItemThumbnail { + width: remCalc(72px); + height: remCalc(72px); + object-fit: cover; + border-radius: remCalc(6px); + flex-shrink: 0; + background: color("greys", "lighter"); +} + +.newReturn-orderLineItemThumbnail--placeholder { + width: remCalc(72px); + height: remCalc(72px); + border-radius: remCalc(6px); + flex-shrink: 0; + background: color("greys", "light"); + display: flex; + align-items: center; + justify-content: center; + color: color("greys", "medium"); + font-size: remCalc(11px); +} + +.newReturn-orderLineItemInfo { + flex: 1; + min-width: 0; +} + +.newReturn-orderLineItemName { + font-size: remCalc(15px); + font-weight: fontWeight("semibold"); + margin: 0 0 remCalc(2px); +} + +.newReturn-orderLineItemVariant { + font-size: remCalc(13px); + color: color("greys", "base"); + margin: 0 0 remCalc(2px); +} + +.newReturn-orderLineItemPrice { + font-size: remCalc(13px); + color: color("greys", "dark"); + margin: 0; +} + +.newReturn-orderLineItemControls { + display: flex; + align-items: center; + gap: spacing("half"); + flex-shrink: 0; + + //Override layout here so the counter sits in a horizontal row + // without touching the shared component. + .form-increment { + display: flex; + align-items: center; + + } + + .form-input--incrementTotal { + height: remCalc(36px); + line-height: remCalc(36px); + vertical-align: middle; + } +} + +.newReturn-select { + height: remCalc(36px); + padding: 0 remCalc(28px) 0 spacing("quarter") * 1.67; + border: remCalc(1.5px) solid color("greys", "medium"); + border-radius: remCalc(6px); + font-size: remCalc(13px); + color: color("greys", "dark"); + // stylelint-disable-next-line function-url-quotes + background: color("whites", "bright") url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23798289'/%3E%3C/svg%3E") no-repeat right spacing("quarter") * 1.67 center; + background-size: remCalc(10px); + appearance: none; + cursor: pointer; + min-width: remCalc(180px); + + &:disabled { + background-color: color("greys", "lighter"); + color: color("greys", "medium"); + cursor: not-allowed; + border-color: color("greys", "light"); + } +} + +.newReturn-submitRow { + display: flex; + justify-content: flex-end; + margin-top: spacing("half"); +} + +.newReturn-submitBtn { + width: 100%; + max-width: remCalc(520px); + padding: remCalc(14px); + background: color("greys", "darkest"); + color: color("whites", "bright"); + border: 0; + border-radius: remCalc(8px); + font-size: remCalc(15px); + font-weight: fontWeight("semibold"); + cursor: pointer; + transition: background 0.2s; + + &:hover { + background: color("greys", "dark"); + } + + &:disabled { + background: color("greys", "medium"); + cursor: not-allowed; + } +} diff --git a/package-lock.json b/package-lock.json index 13d38eee83..6c6bd63f43 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "bigcommerce-cornerstone", - "version": "6.19.0", + "version": "6.19.1", "license": "MIT", "dependencies": { "@bigcommerce/stencil-utils": "6.23.0", diff --git a/templates/pages/account/add-return-new.html b/templates/pages/account/add-return-new.html new file mode 100644 index 0000000000..24dcc3fa65 --- /dev/null +++ b/templates/pages/account/add-return-new.html @@ -0,0 +1,44 @@ +{{#partial "page"}} + +{{> components/common/breadcrumbs breadcrumbs=breadcrumbs}} +{{> components/account/navigation account_page='returns'}} + +
+ +
+
+

Request Return for Order #

+ +
+ +
+

+ +
+ +
+
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +{{/partial}} +{{> layout/base}}