11/*
2- * Copyright (c) 2023 , salesforce.com, inc.
2+ * Copyright (c) 2025 , salesforce.com, inc.
33 * All rights reserved.
44 * SPDX-License-Identifier: BSD-3-Clause
55 * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
66 */
77
8- import React from 'react'
8+ import React , { useState } from 'react'
99import PropTypes from 'prop-types'
10- import { FormattedMessage } from 'react-intl'
10+ import { FormattedMessage , useIntl } from 'react-intl'
1111import {
1212 Modal ,
1313 ModalOverlay ,
@@ -17,73 +17,200 @@ import {
1717 ModalBody ,
1818 ModalCloseButton ,
1919 Button ,
20- Text
20+ Menu ,
21+ MenuButton ,
22+ MenuList ,
23+ MenuItem ,
24+ Box ,
25+ Stack ,
26+ useBreakpointValue
2127} from '@chakra-ui/react'
28+ import { ChevronDownIcon } from '@chakra-ui/icons'
29+ import { useProducts } from '@salesforce/commerce-sdk-react'
30+ import ProductList from '@salesforce/retail-react-app/app/components/product-list'
31+ import {
32+ messages ,
33+ CANCELLATION_REASONS
34+ } from '@salesforce/retail-react-app/app/components/cancel-order-modal/constants'
35+
36+ const onClient = typeof window !== 'undefined'
2237
2338/**
24- * A Modal for requesting order cancellation
39+ * Modal component for requesting order cancellation.
40+ * Displays order products and provides a dropdown for selecting cancellation reasons.
41+ *
42+ * @param {boolean } isOpen - Controls modal visibility
43+ * @param {Function } onClose - Callback to close the modal
44+ * @param {Object } order - Order object containing productItems and currency
45+ * @param {Function } onCancel - Callback fired when cancellation is requested (order, reason)
46+ * @returns {JSX.Element } Modal component with order content or "No order provided" message
2547 */
26- const CancelOrderModal = ( { isOpen, onClose, order, onRequestCancellation, ...props } ) => {
27- const handleCancelOrder = ( ) => {
28- if ( ! onRequestCancellation ) {
29- console . error ( 'Cancel order modal: onRequestCancellation is required' )
30- return
48+ const CancelOrderModal = ( { isOpen, onClose, order, onCancel} ) => {
49+ const intl = useIntl ( )
50+ const [ selectedReason , setSelectedReason ] = useState ( '' )
51+
52+ // Fetch product data for order items
53+ const productIds = order ?. productItems ?. map ( ( product ) => product . productId ) || [ ]
54+ const { data : products , isLoading} = useProducts (
55+ {
56+ parameters : {
57+ ids : productIds . join ( ',' ) ,
58+ allImages : true
59+ }
60+ } ,
61+ {
62+ enabled : ! ! productIds . length && onClient ,
63+ select : ( result ) => {
64+ return result ?. data ?. reduce ( ( result , item ) => {
65+ const key = item . id
66+ result [ key ] = item
67+ return result
68+ } , { } )
69+ }
70+ }
71+ )
72+
73+ // Merge product data with order items
74+ const variants =
75+ order ?. productItems ?. map ( ( item ) => {
76+ const product = products ?. [ item . productId ]
77+ return {
78+ ...( product ? product : { } ) ,
79+ isProductUnavailable : ! product ,
80+ ...item
81+ }
82+ } ) || [ ]
83+
84+ // For responsive sizing
85+ const modalSize = useBreakpointValue ( { base : 'full' , md : '2xl' } )
86+ const buttonSize = useBreakpointValue ( { base : 'lg' , md : 'md' } )
87+
88+ const cancellationReasons = CANCELLATION_REASONS . map ( ( reason ) => ( {
89+ id : reason . id ,
90+ label : intl . formatMessage ( messages [ reason . messageKey ] )
91+ } ) )
92+
93+ const getCancellationReasonDisplayText = ( ) => {
94+ if ( selectedReason ) {
95+ return cancellationReasons . find ( ( reason ) => reason . id === selectedReason ) ?. label
3196 }
97+ return intl . formatMessage ( messages . selectReason )
98+ }
3299
33- onRequestCancellation ( order )
100+ const handleCancel = ( ) => {
101+ onCancel ( order , selectedReason )
34102 onClose ( )
35103 }
36104
37105 return (
38- < Modal isOpen = { isOpen } onClose = { onClose } isCentered { ...props } >
106+ < Modal
107+ isOpen = { isOpen }
108+ onClose = { onClose }
109+ size = { modalSize }
110+ isCentered
111+ scrollBehavior = "inside"
112+ >
39113 < ModalOverlay />
40114 < ModalContent >
41115 < ModalHeader >
42- < FormattedMessage
43- defaultMessage = "Request Cancellation"
44- id = "cancel_order_modal.title.request_cancellation"
45- />
116+ < FormattedMessage { ...messages . requestCancellation } />
46117 </ ModalHeader >
47118 < ModalCloseButton />
48- < ModalBody >
49- { /* TODO: Add order details here W-18998712 */ }
50- < Text >
51- < FormattedMessage
52- defaultMessage = "This is a blank modal for canceling the order."
53- id = "cancel_order_modal.content.placeholder"
54- />
55- </ Text >
119+ < ModalBody pb = { 6 } >
120+ { order ? (
121+ ! isLoading ? (
122+ < ProductList
123+ variants = { variants }
124+ currency = { order . currency }
125+ imageWidth = "20"
126+ padding = { 4 }
127+ spacing = { 2 }
128+ />
129+ ) : (
130+ < Box textAlign = "center" color = "gray.500" fontSize = "md" py = { 8 } >
131+ Loading products...
132+ </ Box >
133+ )
134+ ) : (
135+ < Box textAlign = "center" color = "gray.500" fontSize = "md" py = { 8 } >
136+ < FormattedMessage { ...messages . noOrderProvided } />
137+ </ Box >
138+ ) }
56139 </ ModalBody >
140+
57141 < ModalFooter >
58- < Button variant = "solid" onClick = { handleCancelOrder } >
59- < FormattedMessage
60- defaultMessage = "Request Cancellation"
61- id = "cancel_order_modal.button.confirm"
62- />
63- </ Button >
142+ < Stack
143+ direction = { [ 'column' , 'row' ] }
144+ spacing = { 4 }
145+ w = "full"
146+ justify = "space-between"
147+ align = "center"
148+ >
149+ { /* Cancellation Reason Dropdown */ }
150+ < Box w = "full" flex = { 1 } >
151+ < Menu >
152+ < MenuButton
153+ as = { Button }
154+ rightIcon = { < ChevronDownIcon /> }
155+ variant = "outline"
156+ size = { buttonSize }
157+ w = "full"
158+ textAlign = "left"
159+ justifyContent = "space-between"
160+ fontWeight = "normal"
161+ color = { selectedReason ? 'black' : 'gray.500' }
162+ isDisabled = { ! order }
163+ >
164+ { getCancellationReasonDisplayText ( ) }
165+ </ MenuButton >
166+ < MenuList maxH = "72" overflowY = "auto" >
167+ { cancellationReasons . map ( ( reason ) => (
168+ < MenuItem
169+ key = { reason . id }
170+ onClick = { ( ) => setSelectedReason ( reason . id ) }
171+ bg = {
172+ selectedReason === reason . id ? 'blue.50' : undefined
173+ }
174+ color = {
175+ selectedReason === reason . id
176+ ? 'blue.600'
177+ : reason . isDefault
178+ ? 'gray.500'
179+ : undefined
180+ }
181+ fontWeight = {
182+ selectedReason === reason . id ? 'medium' : undefined
183+ }
184+ >
185+ { reason . label }
186+ </ MenuItem >
187+ ) ) }
188+ </ MenuList >
189+ </ Menu >
190+ </ Box >
191+
192+ { /* Request Cancellation Button */ }
193+ < Button
194+ colorScheme = "blue"
195+ onClick = { handleCancel }
196+ size = { buttonSize }
197+ w = { [ 'full' , 'auto' ] }
198+ isDisabled = { ! order }
199+ >
200+ < FormattedMessage { ...messages . requestCancellation } />
201+ </ Button >
202+ </ Stack >
64203 </ ModalFooter >
65204 </ ModalContent >
66205 </ Modal >
67206 )
68207}
69208
70209CancelOrderModal . propTypes = {
71- /**
72- * Whether the modal is open
73- */
74210 isOpen : PropTypes . bool . isRequired ,
75- /**
76- * Callback to close the modal
77- */
78211 onClose : PropTypes . func . isRequired ,
79- /**
80- * Order object for cancellation
81- */
82212 order : PropTypes . object . isRequired ,
83- /**
84- * Callback when user confirms cancellation request
85- */
86- onRequestCancellation : PropTypes . func . isRequired
213+ onCancel : PropTypes . func . isRequired
87214}
88215
89216export default CancelOrderModal
0 commit comments