Skip to content

Commit 111c076

Browse files
feat: control floating button visibility and fix launch when hidden
1 parent 8151418 commit 111c076

File tree

15 files changed

+509
-14
lines changed

15 files changed

+509
-14
lines changed

packages/template-retail-react-app/app/components/search/index.jsx

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,19 @@ const Search = (props) => {
140140
const appOrigin = useAppOrigin()
141141
const sfLanguage = normalizeLocaleToSalesforce(locale.id)
142142

143-
const askAgentOnSearchEnabled = useMemo(() => {
144-
const {enabled, askAgentOnSearch} = getCommerceAgentConfig()
145-
return isAskAgentOnSearchEnabled(enabled, askAgentOnSearch)
146-
}, [config.app.commerceAgent])
143+
const commerceAgentConfig = useMemo(
144+
() => getCommerceAgentConfig(),
145+
[config.app?.commerceAgent, config.app?.commerceAgentSettings]
146+
)
147+
const askAgentOnSearchEnabled = useMemo(
148+
() =>
149+
isAskAgentOnSearchEnabled(
150+
commerceAgentConfig?.enabled,
151+
commerceAgentConfig?.askAgentOnSearch
152+
),
153+
[commerceAgentConfig]
154+
)
155+
const enableAgentFromSearchSuggestions = commerceAgentConfig?.enableAgentFromSearchSuggestions
147156

148157
const [isOpen, setIsOpen] = useState(false)
149158
const [searchQuery, setSearchQuery] = useState('')
@@ -331,6 +340,11 @@ const Search = (props) => {
331340
}
332341
}
333342

343+
const onAskAssistantClick = () => {
344+
launchChat()
345+
clearInput()
346+
}
347+
334348
const shouldOpenPopover = () => {
335349
// As per design we only want to show the popover if the input is focused and we have recent searches saved
336350
// or we have search suggestions available and have inputed some text (empty text in this scenario should show recent searches)
@@ -402,6 +416,8 @@ const Search = (props) => {
402416
closeAndNavigate={closeAndNavigate}
403417
recentSearches={recentSearches}
404418
searchSuggestions={searchSuggestions}
419+
enableAgentFromSearchSuggestions={enableAgentFromSearchSuggestions}
420+
onAskAssistantClick={onAskAssistantClick}
405421
/>
406422
</PopoverContent>
407423
</HideOnMobile>
@@ -430,6 +446,8 @@ const Search = (props) => {
430446
closeAndNavigate={closeAndNavigate}
431447
recentSearches={recentSearches}
432448
searchSuggestions={searchSuggestions}
449+
enableAgentFromSearchSuggestions={enableAgentFromSearchSuggestions}
450+
onAskAssistantClick={onAskAssistantClick}
433451
/>
434452
)}
435453
</Flex>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright (c) 2021, salesforce.com, inc.
3+
* All rights reserved.
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
import React from 'react'
8+
import PropTypes from 'prop-types'
9+
import {Box, Text} from '@salesforce/retail-react-app/app/components/shared/ui'
10+
import {FormattedMessage} from 'react-intl'
11+
import {SparkleIcon, ChevronRightIcon} from '@salesforce/retail-react-app/app/components/icons'
12+
13+
const AskAssistantBanner = ({onClick, styles}) => {
14+
return (
15+
<Box
16+
{...styles.askAssistantBanner}
17+
as="button"
18+
type="button"
19+
width="full"
20+
textAlign="left"
21+
onClick={onClick}
22+
aria-label="Ask Assistant - Discover, compare and shop smarter with your personal shopping assistant"
23+
>
24+
<Box {...styles.askAssistantBannerContent}>
25+
<Box {...styles.askAssistantBannerIcon} as={SparkleIcon} boxSize={6} />
26+
<Box>
27+
<Text {...styles.askAssistantBannerTitle}>
28+
<FormattedMessage
29+
defaultMessage="Ask Shopping Agent"
30+
id="search.suggestions.askAssistant.title"
31+
/>
32+
</Text>
33+
<Text {...styles.askAssistantBannerDescription}>
34+
<FormattedMessage
35+
defaultMessage="Discover, compare, and shop smarter with your personal Shopping Agent."
36+
id="search.suggestions.askAssistant.description"
37+
/>
38+
</Text>
39+
</Box>
40+
</Box>
41+
<Box {...styles.askAssistantBannerArrow} as={ChevronRightIcon} boxSize={5} />
42+
</Box>
43+
)
44+
}
45+
46+
AskAssistantBanner.propTypes = {
47+
onClick: PropTypes.func.isRequired,
48+
styles: PropTypes.object
49+
}
50+
51+
export default AskAssistantBanner
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright (c) 2021, salesforce.com, inc.
3+
* All rights reserved.
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
import React from 'react'
8+
import {screen} from '@testing-library/react'
9+
import userEvent from '@testing-library/user-event'
10+
import {renderWithProviders} from '@salesforce/retail-react-app/app/utils/test-utils'
11+
import AskAssistantBanner from '@salesforce/retail-react-app/app/components/search/partials/ask-assistant-banner'
12+
13+
const baseStyles = {
14+
askAssistantBanner: {},
15+
askAssistantBannerContent: {},
16+
askAssistantBannerIcon: {},
17+
askAssistantBannerTitle: {},
18+
askAssistantBannerDescription: {},
19+
askAssistantBannerArrow: {}
20+
}
21+
22+
test('renders Ask Assistant banner with title and description', () => {
23+
renderWithProviders(<AskAssistantBanner onClick={jest.fn()} styles={baseStyles} />)
24+
25+
expect(
26+
screen.getByRole('button', {
27+
name: /ask assistant.*discover, compare and shop smarter/i
28+
})
29+
).toBeInTheDocument()
30+
expect(screen.getByText('Ask Shopping Agent')).toBeInTheDocument()
31+
expect(
32+
screen.getByText(/Discover, compare, and shop smarter with your personal Shopping Agent/i)
33+
).toBeInTheDocument()
34+
})
35+
36+
test('calls onClick when banner is clicked', async () => {
37+
const user = userEvent.setup()
38+
const onClick = jest.fn()
39+
40+
renderWithProviders(<AskAssistantBanner onClick={onClick} styles={baseStyles} />)
41+
42+
const button = screen.getByRole('button', {
43+
name: /ask assistant.*discover, compare and shop smarter/i
44+
})
45+
await user.click(button)
46+
47+
expect(onClick).toHaveBeenCalledTimes(1)
48+
})

packages/template-retail-react-app/app/components/search/partials/search-suggestions-section.jsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,19 @@ import PropTypes from 'prop-types'
99
import {Box} from '@salesforce/retail-react-app/app/components/shared/ui'
1010
import Suggestions from '@salesforce/retail-react-app/app/components/search/partials/suggestions'
1111
import HorizontalSuggestions from '@salesforce/retail-react-app/app/components/search/partials/horizontal-suggestions'
12+
import AskAssistantBanner from '@salesforce/retail-react-app/app/components/search/partials/ask-assistant-banner'
1213
import {FormattedMessage} from 'react-intl'
1314
import {HideOnDesktop, HideOnMobile} from '@salesforce/retail-react-app/app/components/responsive'
1415
import Link from '@salesforce/retail-react-app/app/components/link'
1516
import {searchUrlBuilder} from '@salesforce/retail-react-app/app/utils/url'
1617

17-
const SuggestionSection = ({searchSuggestions, closeAndNavigate, styles}) => {
18+
const SuggestionSection = ({
19+
searchSuggestions,
20+
closeAndNavigate,
21+
styles,
22+
showAskAssistantBanner,
23+
onAskAssistantClick
24+
}) => {
1825
const hasCategories = searchSuggestions?.categorySuggestions?.length
1926
const hasProducts = searchSuggestions?.productSuggestions?.length
2027
const hasPhraseSuggestions = searchSuggestions?.phraseSuggestions?.length
@@ -95,6 +102,9 @@ const SuggestionSection = ({searchSuggestions, closeAndNavigate, styles}) => {
95102
/>
96103
</Fragment>
97104
)}
105+
{showAskAssistantBanner && onAskAssistantClick && (
106+
<AskAssistantBanner onClick={onAskAssistantClick} styles={styles} />
107+
)}
98108
</HideOnDesktop>
99109
{/* Desktop - Vertical and Horizontal alignment */}
100110
<HideOnMobile>
@@ -189,6 +199,9 @@ const SuggestionSection = ({searchSuggestions, closeAndNavigate, styles}) => {
189199
)}
190200
</Box>
191201
</Box>
202+
{showAskAssistantBanner && onAskAssistantClick && (
203+
<AskAssistantBanner onClick={onAskAssistantClick} styles={styles} />
204+
)}
192205
</HideOnMobile>
193206
</Fragment>
194207
)
@@ -197,7 +210,9 @@ const SuggestionSection = ({searchSuggestions, closeAndNavigate, styles}) => {
197210
SuggestionSection.propTypes = {
198211
searchSuggestions: PropTypes.object.isRequired,
199212
closeAndNavigate: PropTypes.func.isRequired,
200-
styles: PropTypes.object.isRequired
213+
styles: PropTypes.object.isRequired,
214+
showAskAssistantBanner: PropTypes.bool,
215+
onAskAssistantClick: PropTypes.func
201216
}
202217

203218
export default SuggestionSection

packages/template-retail-react-app/app/components/search/partials/search-suggestions-section.test.jsx

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,13 @@ jest.mock('@salesforce/retail-react-app/app/components/dynamic-image', () => {
2828
const baseStyles = {
2929
textContainer: {},
3030
sectionHeader: {},
31-
phraseContainer: {}
31+
phraseContainer: {},
32+
askAssistantBanner: {},
33+
askAssistantBannerContent: {},
34+
askAssistantBannerIcon: {},
35+
askAssistantBannerTitle: {},
36+
askAssistantBannerDescription: {},
37+
askAssistantBannerArrow: {}
3238
}
3339

3440
const makeSearchSuggestions = (overrides = {}) => ({
@@ -137,3 +143,94 @@ test('renders nothing when there are no categories, products, or phrase suggesti
137143
expect(screen.queryByText('Products')).not.toBeInTheDocument()
138144
expect(screen.queryByTestId('sf-horizontal-product-suggestions')).not.toBeInTheDocument()
139145
})
146+
147+
describe('Ask Assistant banner', () => {
148+
test('renders Ask Assistant banner when showAskAssistantBanner and onAskAssistantClick are provided', () => {
149+
const searchSuggestions = makeSearchSuggestions({
150+
categorySuggestions: [{type: 'category', name: 'Women', link: '/women'}]
151+
})
152+
153+
renderWithProviders(
154+
<SuggestionSection
155+
searchSuggestions={searchSuggestions}
156+
closeAndNavigate={jest.fn()}
157+
styles={baseStyles}
158+
showAskAssistantBanner={true}
159+
onAskAssistantClick={jest.fn()}
160+
/>
161+
)
162+
163+
const banners = screen.getAllByRole('button', {
164+
name: /ask assistant.*discover, compare and shop smarter/i
165+
})
166+
expect(banners.length).toBeGreaterThanOrEqual(1)
167+
})
168+
169+
test('does not render Ask Assistant banner when showAskAssistantBanner is false', () => {
170+
const searchSuggestions = makeSearchSuggestions({
171+
categorySuggestions: [{type: 'category', name: 'Women', link: '/women'}]
172+
})
173+
174+
renderWithProviders(
175+
<SuggestionSection
176+
searchSuggestions={searchSuggestions}
177+
closeAndNavigate={jest.fn()}
178+
styles={baseStyles}
179+
showAskAssistantBanner={false}
180+
onAskAssistantClick={jest.fn()}
181+
/>
182+
)
183+
184+
expect(
185+
screen.queryByRole('button', {
186+
name: /ask assistant.*discover, compare and shop smarter/i
187+
})
188+
).not.toBeInTheDocument()
189+
})
190+
191+
test('does not render Ask Assistant banner when onAskAssistantClick is not provided', () => {
192+
const searchSuggestions = makeSearchSuggestions({
193+
categorySuggestions: [{type: 'category', name: 'Women', link: '/women'}]
194+
})
195+
196+
renderWithProviders(
197+
<SuggestionSection
198+
searchSuggestions={searchSuggestions}
199+
closeAndNavigate={jest.fn()}
200+
styles={baseStyles}
201+
showAskAssistantBanner={true}
202+
/>
203+
)
204+
205+
expect(
206+
screen.queryByRole('button', {
207+
name: /ask assistant.*discover, compare and shop smarter/i
208+
})
209+
).not.toBeInTheDocument()
210+
})
211+
212+
test('clicking Ask Assistant banner calls onAskAssistantClick', async () => {
213+
const user = userEvent.setup()
214+
const onAskAssistantClick = jest.fn()
215+
const searchSuggestions = makeSearchSuggestions({
216+
recentSearchSuggestions: [{type: 'recent', name: 'shoes', link: '/search?q=shoes'}]
217+
})
218+
219+
renderWithProviders(
220+
<SuggestionSection
221+
searchSuggestions={searchSuggestions}
222+
closeAndNavigate={jest.fn()}
223+
styles={baseStyles}
224+
showAskAssistantBanner={true}
225+
onAskAssistantClick={onAskAssistantClick}
226+
/>
227+
)
228+
229+
const banner = screen.getAllByRole('button', {
230+
name: /ask assistant.*discover, compare and shop smarter/i
231+
})[0]
232+
await user.click(banner)
233+
234+
expect(onAskAssistantClick).toHaveBeenCalledTimes(1)
235+
})
236+
})

packages/template-retail-react-app/app/components/search/partials/search-suggestions.jsx

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,15 @@ import PropTypes from 'prop-types'
99
import {Stack, useMultiStyleConfig} from '@salesforce/retail-react-app/app/components/shared/ui'
1010
import RecentSearches from '@salesforce/retail-react-app/app/components/search/partials/recent-searches'
1111
import SuggestionSection from '@salesforce/retail-react-app/app/components/search/partials/search-suggestions-section'
12+
import AskAssistantBanner from '@salesforce/retail-react-app/app/components/search/partials/ask-assistant-banner'
1213

13-
const SearchSuggestions = ({recentSearches, searchSuggestions, closeAndNavigate}) => {
14+
const SearchSuggestions = ({
15+
recentSearches,
16+
searchSuggestions,
17+
closeAndNavigate,
18+
enableAgentFromSearchSuggestions,
19+
onAskAssistantClick
20+
}) => {
1421
const styles = useMultiStyleConfig('SearchSuggestions')
1522
const hasCategories = searchSuggestions?.categorySuggestions?.length
1623
const hasProducts = searchSuggestions?.productSuggestions?.length
@@ -19,6 +26,8 @@ const SearchSuggestions = ({recentSearches, searchSuggestions, closeAndNavigate}
1926
const hasRecentSearches = searchSuggestions?.recentSearchSuggestions?.length
2027
const hasSuggestions =
2128
hasCategories || hasProducts || hasBrands || hasPopularSearches || hasRecentSearches
29+
const showAskAssistantBanner =
30+
enableAgentFromSearchSuggestions === 'true' || enableAgentFromSearchSuggestions === true
2231

2332
return (
2433
<Stack {...styles.container}>
@@ -27,12 +36,19 @@ const SearchSuggestions = ({recentSearches, searchSuggestions, closeAndNavigate}
2736
searchSuggestions={searchSuggestions}
2837
closeAndNavigate={closeAndNavigate}
2938
styles={styles}
39+
showAskAssistantBanner={showAskAssistantBanner}
40+
onAskAssistantClick={onAskAssistantClick}
3041
/>
3142
) : (
32-
<RecentSearches
33-
recentSearches={recentSearches}
34-
closeAndNavigate={closeAndNavigate}
35-
/>
43+
<>
44+
<RecentSearches
45+
recentSearches={recentSearches}
46+
closeAndNavigate={closeAndNavigate}
47+
/>
48+
{showAskAssistantBanner && onAskAssistantClick && (
49+
<AskAssistantBanner onClick={onAskAssistantClick} styles={styles} />
50+
)}
51+
</>
3652
)}
3753
</Stack>
3854
)
@@ -41,7 +57,9 @@ const SearchSuggestions = ({recentSearches, searchSuggestions, closeAndNavigate}
4157
SearchSuggestions.propTypes = {
4258
recentSearches: PropTypes.array,
4359
searchSuggestions: PropTypes.object,
44-
closeAndNavigate: PropTypes.func
60+
closeAndNavigate: PropTypes.func,
61+
enableAgentFromSearchSuggestions: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
62+
onAskAssistantClick: PropTypes.func
4563
}
4664

4765
export default SearchSuggestions

0 commit comments

Comments
 (0)