@faststore/core 4.3.0-dev.1 → 4.3.0-dev.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-generate.log +4 -4
- package/.turbo/turbo-test.log +99 -29
- package/@generated/cached-operations.json +3 -1
- package/@generated/gql.ts +29 -5
- package/@generated/graphql.ts +129 -8
- package/@generated/persisted-documents.json +5 -1
- package/@generated/schema.graphql +69 -0
- package/CHANGELOG.md +12 -0
- package/cms/faststore/pages/cms_content_type__landingpage.jsonc +6 -0
- package/cms/faststore/schema.json +123 -91
- package/cms/faststore/sections.json +5 -0
- package/package.json +6 -6
- package/src/components/auth/ProfileChallenge/ProfileChallenge.tsx +2 -2
- package/src/components/search/SearchInput/SearchInput.tsx +139 -332
- package/src/components/sections/Navbar/Navbar.tsx +1 -0
- package/src/components/sections/Navbar/section.module.scss +3 -0
- package/src/sdk/auth/index.ts +2 -2
- package/src/sdk/orderEntry/useOrderEntry.ts +58 -0
- package/src/sdk/orderEntry/useOrderEntryOperation.ts +132 -0
- package/src/sdk/orderEntry/useOrderEntryUpload.ts +96 -0
- package/src/sdk/orderEntry/useOrderFormItems.ts +57 -0
- package/src/styles/global/index.scss +16 -0
- package/test/components/auth/ProfileChallenge.test.tsx +51 -0
- package/test/components/search/SearchInput.test.ts +72 -0
- package/test/components/search/SearchInputComponent.test.tsx +276 -0
- package/test/sdk/auth/useAuth.test.ts +94 -0
- package/test/sdk/orderEntry/useOrderEntry.test.ts +139 -0
- package/test/sdk/orderEntry/useOrderEntryOperation.test.ts +205 -0
- package/test/sdk/orderEntry/useOrderEntryUpload.test.ts +142 -0
- package/test/sdk/orderEntry/useOrderFormItems.test.ts +132 -0
- package/test/server/index.test.ts +4 -0
- package/src/sdk/product/useBulkProductsQuery.ts +0 -128
|
@@ -27,16 +27,13 @@ import {
|
|
|
27
27
|
Icon as UIIcon,
|
|
28
28
|
IconButton as UIIconButton,
|
|
29
29
|
SearchInput as UISearchInput,
|
|
30
|
-
useCSVParser,
|
|
31
30
|
useOnClickOutside,
|
|
32
|
-
useUI,
|
|
33
31
|
} from '@faststore/ui'
|
|
34
32
|
|
|
35
33
|
import type {
|
|
36
|
-
CSVData,
|
|
37
|
-
Product,
|
|
38
34
|
SearchInputFieldProps as UISearchInputFieldProps,
|
|
39
35
|
SearchInputFieldRef as UISearchInputFieldRef,
|
|
36
|
+
Product,
|
|
40
37
|
} from '@faststore/ui'
|
|
41
38
|
|
|
42
39
|
import type { SearchProviderContextValue } from '@faststore/ui'
|
|
@@ -46,10 +43,13 @@ import useSearchHistory from 'src/sdk/search/useSearchHistory'
|
|
|
46
43
|
import useSuggestions from 'src/sdk/search/useSuggestions'
|
|
47
44
|
|
|
48
45
|
import { cartStore } from 'src/sdk/cart'
|
|
49
|
-
import {
|
|
50
|
-
import { useBulkProductsQuery } from 'src/sdk/product/useBulkProductsQuery'
|
|
46
|
+
import type { CartItem } from 'src/sdk/cart'
|
|
51
47
|
import { usePriceFormatter } from 'src/sdk/product/useFormattedPrice'
|
|
52
|
-
import {
|
|
48
|
+
import { useAuth } from 'src/sdk/auth'
|
|
49
|
+
import { useOrderEntry } from 'src/sdk/orderEntry/useOrderEntry'
|
|
50
|
+
import { useOrderFormItems } from 'src/sdk/orderEntry/useOrderFormItems'
|
|
51
|
+
import type { OrderFormCartItem } from 'src/sdk/orderEntry/useOrderFormItems'
|
|
52
|
+
import { formatSearchPath } from 'src/sdk/search/formatSearchPath'
|
|
53
53
|
import { formatFileName, formatFileSize } from 'src/utils/utilities'
|
|
54
54
|
|
|
55
55
|
const SearchDropdown = lazy(
|
|
@@ -74,9 +74,6 @@ export type SearchInputProps = {
|
|
|
74
74
|
loadingLabel?: string
|
|
75
75
|
searchHistoryTitle?: string
|
|
76
76
|
searchTopTitle?: string
|
|
77
|
-
// Called when the user clicks Search in the file upload card, with the parsed CSV data.
|
|
78
|
-
// Use this to run bulk search, add to cart, or analytics.
|
|
79
|
-
onFileSearch?: (data: CSVData) => void
|
|
80
77
|
} & Omit<UISearchInputFieldProps, 'onSubmit' | 'attachmentButtonIcon'>
|
|
81
78
|
|
|
82
79
|
export type SearchInputRef = UISearchInputFieldRef & {
|
|
@@ -92,6 +89,22 @@ const sendAnalytics = async (term: string) => {
|
|
|
92
89
|
})
|
|
93
90
|
}
|
|
94
91
|
|
|
92
|
+
export function mapOrderFormItemsToProducts(
|
|
93
|
+
items: OrderFormCartItem[]
|
|
94
|
+
): Product[] {
|
|
95
|
+
return items.map((item) => ({
|
|
96
|
+
id: item.id,
|
|
97
|
+
name: item.name,
|
|
98
|
+
price: item.price,
|
|
99
|
+
quantityUpdated: false,
|
|
100
|
+
image: { url: item.imageUrl ?? '', alternateName: item.name },
|
|
101
|
+
inventory: item.availability === 'available' ? 9999 : 0,
|
|
102
|
+
availability:
|
|
103
|
+
item.availability === 'available' ? 'available' : 'outOfStock',
|
|
104
|
+
selectedCount: item.quantity,
|
|
105
|
+
}))
|
|
106
|
+
}
|
|
107
|
+
|
|
95
108
|
const SearchInput = forwardRef<SearchInputRef, SearchInputProps>(
|
|
96
109
|
function SearchInput(
|
|
97
110
|
{
|
|
@@ -101,11 +114,6 @@ const SearchInput = forwardRef<SearchInputRef, SearchInputProps>(
|
|
|
101
114
|
sort,
|
|
102
115
|
placeholder,
|
|
103
116
|
quickOrderSettings,
|
|
104
|
-
submitButtonAriaLabel,
|
|
105
|
-
loadingLabel,
|
|
106
|
-
searchHistoryTitle,
|
|
107
|
-
searchTopTitle,
|
|
108
|
-
onFileSearch,
|
|
109
117
|
...otherProps
|
|
110
118
|
},
|
|
111
119
|
ref
|
|
@@ -124,40 +132,55 @@ const SearchInput = forwardRef<SearchInputRef, SearchInputProps>(
|
|
|
124
132
|
const [hasFile, setHasFile] = useState(false)
|
|
125
133
|
const [isQuickOrderDrawerOpen, setIsQuickOrderDrawerOpen] = useState(false)
|
|
126
134
|
const [quickOrderProducts, setQuickOrderProducts] = useState<Product[]>([])
|
|
127
|
-
const [
|
|
135
|
+
const [oesOrderFormItems, setOesOrderFormItems] = useState<
|
|
136
|
+
OrderFormCartItem[]
|
|
137
|
+
>([])
|
|
128
138
|
|
|
129
139
|
const searchRef = useRef<HTMLDivElement>(null)
|
|
130
140
|
const { addToSearchHistory } = useSearchHistory()
|
|
131
141
|
const router = useRouter()
|
|
132
|
-
const formatSearchPath = useFormatSearchPath()
|
|
133
142
|
const priceFormatter = usePriceFormatter()
|
|
134
|
-
const { pushToast } = useUI()
|
|
135
143
|
|
|
136
|
-
const [csvData, setCsvData] = useState<CSVData | null>(null)
|
|
137
144
|
const [selectedFile, setSelectedFile] = useState<File | null>(null)
|
|
138
|
-
const [skusToFetch, setSkusToFetch] = useState<string[]>([])
|
|
139
|
-
const [skuQuantityMap, setSkuQuantityMap] = useState<
|
|
140
|
-
Record<string, number>
|
|
141
|
-
>({})
|
|
142
|
-
const [isLoadingWithDelay, setIsLoadingWithDelay] = useState(false)
|
|
143
|
-
|
|
144
|
-
const csvParserOptions = useMemo(
|
|
145
|
-
() => ({ delimiter: ',' as const, skipEmptyLines: true }),
|
|
146
|
-
[]
|
|
147
|
-
)
|
|
148
145
|
|
|
149
|
-
const csvParser = useCSVParser(csvParserOptions)
|
|
150
146
|
const {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
147
|
+
submitFile,
|
|
148
|
+
status: oesStatus,
|
|
149
|
+
isUploading: isOESUploading,
|
|
150
|
+
isProcessing: isOESProcessing,
|
|
151
|
+
error: oesError,
|
|
152
|
+
reset: resetOES,
|
|
153
|
+
} = useOrderEntry()
|
|
154
|
+
|
|
155
|
+
const {
|
|
156
|
+
fetchOrderFormItems,
|
|
157
|
+
items: orderFormItems,
|
|
158
|
+
reset: resetOrderFormItems,
|
|
159
|
+
} = useOrderFormItems()
|
|
160
|
+
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
if (
|
|
163
|
+
(oesStatus?.status === 'SUCCESS' ||
|
|
164
|
+
oesStatus?.status === 'PARTIAL_SUCCESS') &&
|
|
165
|
+
oesStatus.entityId
|
|
166
|
+
) {
|
|
167
|
+
fetchOrderFormItems(oesStatus.entityId)
|
|
168
|
+
}
|
|
169
|
+
}, [oesStatus?.status, oesStatus?.entityId, fetchOrderFormItems])
|
|
170
|
+
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
if (!orderFormItems || orderFormItems.length === 0) return
|
|
173
|
+
setOesOrderFormItems(orderFormItems)
|
|
174
|
+
setQuickOrderProducts(mapOrderFormItemsToProducts(orderFormItems))
|
|
175
|
+
setFileUploadVisible(false)
|
|
176
|
+
setIsUploadOpen(false)
|
|
177
|
+
setIsQuickOrderDrawerOpen(true)
|
|
178
|
+
}, [orderFormItems])
|
|
179
|
+
|
|
180
|
+
const { isAuthenticated } = useAuth()
|
|
181
|
+
const isQuickOrderEnabled =
|
|
182
|
+
(quickOrderSettings?.quickOrder ?? false) && isAuthenticated
|
|
159
183
|
const attachmentButton = quickOrderSettings?.attachmentButton
|
|
160
|
-
const toastMessages = quickOrderSettings?.toastMessages
|
|
161
184
|
const drawerConfig = quickOrderSettings?.drawer
|
|
162
185
|
const a11yLabels = quickOrderSettings?.accessibilityLabels
|
|
163
186
|
const fileUploadCardConfig = quickOrderSettings?.fileUploadCard
|
|
@@ -166,16 +189,6 @@ const SearchInput = forwardRef<SearchInputRef, SearchInputProps>(
|
|
|
166
189
|
resetSearchInput: () => setSearchQuery(''),
|
|
167
190
|
}))
|
|
168
191
|
|
|
169
|
-
// Map CSV parser error types to FileUploadErrorType
|
|
170
|
-
const mapCSVErrorToFileUploadErrorType = (
|
|
171
|
-
csvErrorType?: string
|
|
172
|
-
): FileUploadErrorType => {
|
|
173
|
-
if (csvErrorType === 'FILE_ERROR') {
|
|
174
|
-
return FileUploadErrorType.Unreadable
|
|
175
|
-
}
|
|
176
|
-
return FileUploadErrorType.InvalidStructure
|
|
177
|
-
}
|
|
178
|
-
|
|
179
192
|
const onSearchSelection: SearchProviderContextValue['onSearchSelection'] = (
|
|
180
193
|
term,
|
|
181
194
|
path
|
|
@@ -185,252 +198,31 @@ const SearchInput = forwardRef<SearchInputRef, SearchInputProps>(
|
|
|
185
198
|
setSearchDropdownVisible(false)
|
|
186
199
|
}
|
|
187
200
|
|
|
188
|
-
const handleFileSelect =
|
|
201
|
+
const handleFileSelect = (files: File[]) => {
|
|
189
202
|
if (files.length === 0) return
|
|
190
|
-
|
|
191
203
|
setHasFile(true)
|
|
192
204
|
setIsUploadOpen(true)
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
setSelectedFile(file)
|
|
196
|
-
|
|
197
|
-
onClearError()
|
|
198
|
-
setCsvData(null)
|
|
199
|
-
setQuickOrderProducts([])
|
|
200
|
-
setSkusToFetch([])
|
|
201
|
-
setSkuQuantityMap({})
|
|
202
|
-
setIsQuickOrderDrawerOpen(false)
|
|
203
|
-
setNoProductsError(false)
|
|
204
|
-
|
|
205
|
-
const result = await onParseFile(file)
|
|
206
|
-
|
|
207
|
-
if (result && result.data && result.data.length > 0) {
|
|
208
|
-
setCsvData(result)
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const handleDownloadTemplate = async () => {
|
|
213
|
-
try {
|
|
214
|
-
const csvContent = await onGenerateTemplate()
|
|
215
|
-
|
|
216
|
-
if (csvContent) {
|
|
217
|
-
const blob = new Blob([csvContent], { type: 'text/csv' })
|
|
218
|
-
const url = window.URL.createObjectURL(blob)
|
|
219
|
-
const a = document.createElement('a')
|
|
220
|
-
a.href = url
|
|
221
|
-
a.download = 'template.csv'
|
|
222
|
-
document.body.appendChild(a)
|
|
223
|
-
a.click()
|
|
224
|
-
document.body.removeChild(a)
|
|
225
|
-
window.URL.revokeObjectURL(url)
|
|
226
|
-
}
|
|
227
|
-
} catch (error) {
|
|
228
|
-
console.error('Failed to download template:', error)
|
|
229
|
-
}
|
|
205
|
+
setSelectedFile(files[0])
|
|
206
|
+
resetOES()
|
|
230
207
|
}
|
|
231
208
|
|
|
232
209
|
const handleDismiss = () => {
|
|
233
|
-
setCsvData(null)
|
|
234
210
|
setSelectedFile(null)
|
|
235
|
-
setSkusToFetch([])
|
|
236
|
-
setSkuQuantityMap({})
|
|
237
211
|
setQuickOrderProducts([])
|
|
212
|
+
setOesOrderFormItems([])
|
|
238
213
|
setFileUploadVisible(false)
|
|
239
214
|
setHasFile(false)
|
|
240
215
|
setIsUploadOpen(false)
|
|
241
|
-
|
|
242
|
-
|
|
216
|
+
resetOES()
|
|
217
|
+
resetOrderFormItems()
|
|
243
218
|
}
|
|
244
219
|
|
|
245
220
|
const handleSearch = async (_file?: File) => {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const fileToParse = _file || selectedFile
|
|
250
|
-
|
|
251
|
-
if (!fileToParse) {
|
|
252
|
-
pushToast({
|
|
253
|
-
title: toastMessages?.noFileSelected?.title,
|
|
254
|
-
message: toastMessages?.noFileSelected?.message,
|
|
255
|
-
status: 'ERROR',
|
|
256
|
-
icon: <UIIcon name="CircleWavyWarning" width={30} height={30} />,
|
|
257
|
-
})
|
|
258
|
-
return
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
try {
|
|
262
|
-
const parsePromise = onParseFile(fileToParse)
|
|
263
|
-
|
|
264
|
-
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
265
|
-
setTimeout(() => {
|
|
266
|
-
reject(new Error(toastMessages?.fileTimeout?.message))
|
|
267
|
-
}, 30000)
|
|
268
|
-
})
|
|
269
|
-
|
|
270
|
-
const result = (await Promise.race([
|
|
271
|
-
parsePromise,
|
|
272
|
-
timeoutPromise,
|
|
273
|
-
])) as Awaited<ReturnType<typeof onParseFile>>
|
|
274
|
-
|
|
275
|
-
if (result && result.data && result.data.length > 0) {
|
|
276
|
-
dataToUse = result
|
|
277
|
-
setCsvData(result)
|
|
278
|
-
} else {
|
|
279
|
-
pushToast({
|
|
280
|
-
title: toastMessages?.noDataFound?.title,
|
|
281
|
-
message: toastMessages?.noDataFound?.message,
|
|
282
|
-
status: 'ERROR',
|
|
283
|
-
icon: <UIIcon name="CircleWavyWarning" width={30} height={30} />,
|
|
284
|
-
})
|
|
285
|
-
return
|
|
286
|
-
}
|
|
287
|
-
} catch (error) {
|
|
288
|
-
const errorMessage =
|
|
289
|
-
error instanceof Error
|
|
290
|
-
? error.message
|
|
291
|
-
: toastMessages?.fileProcessingError?.defaultMessage
|
|
292
|
-
pushToast({
|
|
293
|
-
title: toastMessages?.fileProcessingError?.title,
|
|
294
|
-
message: errorMessage,
|
|
295
|
-
status: 'ERROR',
|
|
296
|
-
icon: <UIIcon name="CircleWavyWarning" width={30} height={30} />,
|
|
297
|
-
})
|
|
298
|
-
return
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
if (!dataToUse || !dataToUse.data || dataToUse.data.length === 0) {
|
|
303
|
-
pushToast({
|
|
304
|
-
title: toastMessages?.noDataAvailable?.title,
|
|
305
|
-
message: toastMessages?.noDataAvailable?.message,
|
|
306
|
-
status: 'ERROR',
|
|
307
|
-
icon: <UIIcon name="CircleWavyWarning" width={30} height={30} />,
|
|
308
|
-
})
|
|
309
|
-
return
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
const payload = {
|
|
313
|
-
fileName: dataToUse.fileName,
|
|
314
|
-
totalRows: dataToUse.totalRows,
|
|
315
|
-
fileSize: dataToUse.fileSize,
|
|
316
|
-
data: dataToUse.data,
|
|
317
|
-
}
|
|
318
|
-
try {
|
|
319
|
-
window.dispatchEvent(
|
|
320
|
-
new CustomEvent('faststore:file-search', { detail: payload })
|
|
321
|
-
)
|
|
322
|
-
} catch {
|
|
323
|
-
// ignore in envs without window
|
|
324
|
-
}
|
|
325
|
-
onFileSearch?.(dataToUse)
|
|
326
|
-
|
|
327
|
-
const map: Record<string, number> = {}
|
|
328
|
-
for (const item of dataToUse.data) {
|
|
329
|
-
const sku = item.sku?.trim()
|
|
330
|
-
if (!sku) continue
|
|
331
|
-
map[sku] = (map[sku] ?? 0) + (item.quantity ?? 1)
|
|
332
|
-
}
|
|
333
|
-
const uniqueSkus = Object.keys(map)
|
|
334
|
-
|
|
335
|
-
if (uniqueSkus.length === 0) {
|
|
336
|
-
pushToast({
|
|
337
|
-
title: toastMessages?.noValidSkus?.title,
|
|
338
|
-
message: toastMessages?.noValidSkus?.message,
|
|
339
|
-
status: 'ERROR',
|
|
340
|
-
icon: <UIIcon name="CircleWavyWarning" width={30} height={30} />,
|
|
341
|
-
})
|
|
342
|
-
return
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
setQuickOrderProducts([])
|
|
346
|
-
setNoProductsError(false)
|
|
347
|
-
setIsLoadingWithDelay(true)
|
|
348
|
-
setSkuQuantityMap(map)
|
|
349
|
-
// Open drawer immediately to show loading skeleton
|
|
350
|
-
setIsQuickOrderDrawerOpen(true)
|
|
351
|
-
setFileUploadVisible(false)
|
|
352
|
-
setSkusToFetch(uniqueSkus)
|
|
221
|
+
const file = _file ?? selectedFile
|
|
222
|
+
if (!file) return
|
|
223
|
+
await submitFile(file)
|
|
353
224
|
}
|
|
354
225
|
|
|
355
|
-
const { products: fetchedProducts, isLoading: isLoadingProducts } =
|
|
356
|
-
useBulkProductsQuery(skusToFetch)
|
|
357
|
-
|
|
358
|
-
useEffect(() => {
|
|
359
|
-
if (skusToFetch.length > 0 && isLoadingProducts) {
|
|
360
|
-
// Clear products and show loading skeleton
|
|
361
|
-
setQuickOrderProducts([])
|
|
362
|
-
setIsLoadingWithDelay(true)
|
|
363
|
-
// Keep drawer open to show loading skeleton
|
|
364
|
-
setIsQuickOrderDrawerOpen(true)
|
|
365
|
-
setFileUploadVisible(false)
|
|
366
|
-
setNoProductsError(false)
|
|
367
|
-
return
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
if (
|
|
371
|
-
!isLoadingProducts &&
|
|
372
|
-
skusToFetch.length > 0 &&
|
|
373
|
-
Object.keys(skuQuantityMap).length > 0 &&
|
|
374
|
-
fetchedProducts.length > 0
|
|
375
|
-
) {
|
|
376
|
-
// Add artificial delay to test loading state
|
|
377
|
-
setIsLoadingWithDelay(true)
|
|
378
|
-
|
|
379
|
-
const timeoutId = setTimeout(() => {
|
|
380
|
-
const convertedProducts: Product[] = []
|
|
381
|
-
|
|
382
|
-
fetchedProducts.forEach((productData) => {
|
|
383
|
-
if (productData.product && !productData.error) {
|
|
384
|
-
const requestedQuantity =
|
|
385
|
-
skuQuantityMap[productData.sku ?? ''] ?? 1
|
|
386
|
-
|
|
387
|
-
const convertedProduct = convertProductToQuickOrder(
|
|
388
|
-
productData.product,
|
|
389
|
-
requestedQuantity
|
|
390
|
-
)
|
|
391
|
-
|
|
392
|
-
if (convertedProduct) {
|
|
393
|
-
convertedProducts.push(convertedProduct)
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
})
|
|
397
|
-
|
|
398
|
-
setQuickOrderProducts(convertedProducts)
|
|
399
|
-
setIsLoadingWithDelay(false)
|
|
400
|
-
|
|
401
|
-
if (convertedProducts.length > 0) {
|
|
402
|
-
setIsQuickOrderDrawerOpen(true)
|
|
403
|
-
setFileUploadVisible(false)
|
|
404
|
-
setNoProductsError(false)
|
|
405
|
-
} else {
|
|
406
|
-
// Keep drawer open to show empty state message
|
|
407
|
-
setQuickOrderProducts([])
|
|
408
|
-
setIsQuickOrderDrawerOpen(true)
|
|
409
|
-
setFileUploadVisible(false)
|
|
410
|
-
setNoProductsError(true)
|
|
411
|
-
}
|
|
412
|
-
}, 2000) // 2 second delay for testing
|
|
413
|
-
|
|
414
|
-
return () => {
|
|
415
|
-
clearTimeout(timeoutId)
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
if (
|
|
420
|
-
!isLoadingProducts &&
|
|
421
|
-
skusToFetch.length > 0 &&
|
|
422
|
-
Object.keys(skuQuantityMap).length > 0 &&
|
|
423
|
-
fetchedProducts.length === 0
|
|
424
|
-
) {
|
|
425
|
-
// Keep drawer open to show empty state message
|
|
426
|
-
setQuickOrderProducts([])
|
|
427
|
-
setIsQuickOrderDrawerOpen(true)
|
|
428
|
-
setFileUploadVisible(false)
|
|
429
|
-
setNoProductsError(true)
|
|
430
|
-
setIsLoadingWithDelay(false)
|
|
431
|
-
}
|
|
432
|
-
}, [fetchedProducts, skusToFetch, skuQuantityMap, isLoadingProducts])
|
|
433
|
-
|
|
434
226
|
useOnClickOutside(searchRef, () => {
|
|
435
227
|
setSearchDropdownVisible(customSearchDropdownVisibleCondition ?? false)
|
|
436
228
|
setFileUploadVisible(false)
|
|
@@ -451,48 +243,60 @@ const SearchInput = forwardRef<SearchInputRef, SearchInputProps>(
|
|
|
451
243
|
const buttonProps = {
|
|
452
244
|
onClick: onSearchClick,
|
|
453
245
|
testId: buttonTestId,
|
|
454
|
-
'aria-label': submitButtonAriaLabel,
|
|
455
246
|
}
|
|
456
247
|
|
|
457
248
|
const handleAddToCart = useCallback(
|
|
458
249
|
(productsToAdd: Product[]) => {
|
|
459
|
-
|
|
250
|
+
for (const product of productsToAdd) {
|
|
460
251
|
if (
|
|
461
|
-
product.selectedCount
|
|
462
|
-
product.availability
|
|
463
|
-
)
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
gtin: fetchedProduct.gtin,
|
|
480
|
-
additionalProperty: fetchedProduct.additionalProperty,
|
|
252
|
+
product.selectedCount <= 0 ||
|
|
253
|
+
product.availability !== 'available'
|
|
254
|
+
)
|
|
255
|
+
continue
|
|
256
|
+
|
|
257
|
+
const originalItem = oesOrderFormItems.find(
|
|
258
|
+
(item) => item.id === product.id
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
const cartItem: Omit<CartItem, 'id'> = {
|
|
262
|
+
itemOffered: {
|
|
263
|
+
sku: product.id,
|
|
264
|
+
name: product.name,
|
|
265
|
+
unitMultiplier: originalItem?.unitMultiplier ?? 1,
|
|
266
|
+
image: [
|
|
267
|
+
{
|
|
268
|
+
url: product.image.url,
|
|
269
|
+
alternateName: product.image.alternateName,
|
|
481
270
|
},
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
271
|
+
],
|
|
272
|
+
brand: { name: '' },
|
|
273
|
+
isVariantOf: {
|
|
274
|
+
productGroupID: '',
|
|
275
|
+
name: product.name,
|
|
276
|
+
skuVariants: {
|
|
277
|
+
activeVariations: {},
|
|
278
|
+
slugsMap: {},
|
|
279
|
+
availableVariations: {},
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
gtin: '',
|
|
283
|
+
additionalProperty: [],
|
|
284
|
+
},
|
|
285
|
+
seller: { identifier: originalItem?.seller ?? '1' },
|
|
286
|
+
quantity: product.selectedCount,
|
|
287
|
+
price: product.price,
|
|
288
|
+
priceWithTaxes: product.price,
|
|
289
|
+
listPrice: originalItem?.listPrice ?? product.price,
|
|
290
|
+
listPriceWithTaxes: originalItem?.listPrice ?? product.price,
|
|
490
291
|
}
|
|
491
|
-
})
|
|
492
292
|
|
|
293
|
+
cartStore.addItem(cartItem)
|
|
294
|
+
}
|
|
493
295
|
setIsQuickOrderDrawerOpen(false)
|
|
296
|
+
setQuickOrderProducts([])
|
|
297
|
+
setOesOrderFormItems([])
|
|
494
298
|
},
|
|
495
|
-
[
|
|
299
|
+
[oesOrderFormItems]
|
|
496
300
|
)
|
|
497
301
|
|
|
498
302
|
const getCompletedStatusText = useCallback(
|
|
@@ -521,9 +325,7 @@ const SearchInput = forwardRef<SearchInputRef, SearchInputProps>(
|
|
|
521
325
|
{hidden ? (
|
|
522
326
|
<UIIconButton
|
|
523
327
|
type="submit"
|
|
524
|
-
aria-label={
|
|
525
|
-
a11yLabels?.searchButtonAriaLabel ?? submitButtonAriaLabel
|
|
526
|
-
}
|
|
328
|
+
aria-label={a11yLabels?.searchButtonAriaLabel ?? 'Search'}
|
|
527
329
|
icon={<UIIcon name="MagnifyingGlass" />}
|
|
528
330
|
size="small"
|
|
529
331
|
{...buttonProps}
|
|
@@ -585,9 +387,6 @@ const SearchInput = forwardRef<SearchInputRef, SearchInputProps>(
|
|
|
585
387
|
<SearchDropdown
|
|
586
388
|
sort={sort as SearchState['sort']}
|
|
587
389
|
quickOrderSettings={quickOrderSettings}
|
|
588
|
-
loadingLabel={loadingLabel}
|
|
589
|
-
searchHistoryTitle={searchHistoryTitle}
|
|
590
|
-
searchTopTitle={searchTopTitle}
|
|
591
390
|
onChangeCustomSearchDropdownVisible={
|
|
592
391
|
setCustomSearchDropdownVisibleCondition
|
|
593
392
|
}
|
|
@@ -611,23 +410,25 @@ const SearchInput = forwardRef<SearchInputRef, SearchInputProps>(
|
|
|
611
410
|
}
|
|
612
411
|
searchButtonLabel={fileUploadCardConfig?.searchButtonLabel}
|
|
613
412
|
uploadingStatusText={fileUploadCardConfig?.uploadingStatusText}
|
|
413
|
+
processingStatusText={
|
|
414
|
+
fileUploadCardConfig?.processingStatusText ?? 'Importing...'
|
|
415
|
+
}
|
|
614
416
|
getCompletedStatusText={getCompletedStatusText}
|
|
615
417
|
errorMessages={resolvedErrorMessages}
|
|
616
418
|
accept={fileUploadCardConfig?.acceptedFileTypes}
|
|
617
419
|
isOpen={isUploadOpen || hasFile || fileUploadVisible}
|
|
618
420
|
onDismiss={handleDismiss}
|
|
619
421
|
onFileSelect={handleFileSelect}
|
|
620
|
-
onDownloadTemplate={handleDownloadTemplate}
|
|
621
422
|
formatterFileSize={formatFileSize}
|
|
622
423
|
formatterFileName={formatFileName}
|
|
623
424
|
onSearch={handleSearch}
|
|
624
|
-
isUploading={
|
|
625
|
-
|
|
626
|
-
{
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
425
|
+
isUploading={isOESUploading}
|
|
426
|
+
isProcessing={isOESProcessing}
|
|
427
|
+
hasError={!!oesError || oesStatus?.status === 'FAILED'}
|
|
428
|
+
{...((oesError || oesStatus?.status === 'FAILED') && {
|
|
429
|
+
errorType: FileUploadErrorType.Unreadable,
|
|
430
|
+
errorMessage:
|
|
431
|
+
oesError?.message ?? oesStatus?.message ?? undefined,
|
|
631
432
|
})}
|
|
632
433
|
/>
|
|
633
434
|
)}
|
|
@@ -639,16 +440,23 @@ const SearchInput = forwardRef<SearchInputRef, SearchInputProps>(
|
|
|
639
440
|
onClick: () => {
|
|
640
441
|
setIsQuickOrderDrawerOpen(false)
|
|
641
442
|
setQuickOrderProducts([])
|
|
642
|
-
|
|
643
|
-
setSkuQuantityMap({})
|
|
443
|
+
setOesOrderFormItems([])
|
|
644
444
|
},
|
|
645
445
|
}}
|
|
646
446
|
providerProps={{
|
|
647
447
|
initialProducts: quickOrderProducts,
|
|
648
|
-
isLoading:
|
|
649
|
-
totalRequestedSkus:
|
|
448
|
+
isLoading: false,
|
|
449
|
+
totalRequestedSkus: 0,
|
|
650
450
|
onAddToCart: handleAddToCart,
|
|
651
|
-
alertMessages:
|
|
451
|
+
alertMessages: {
|
|
452
|
+
...drawerConfig?.alertMessages,
|
|
453
|
+
...(oesStatus?.status === 'PARTIAL_SUCCESS' && {
|
|
454
|
+
notFound:
|
|
455
|
+
oesStatus.message ??
|
|
456
|
+
drawerConfig?.alertMessages?.notFound ??
|
|
457
|
+
'Some items could not be imported.',
|
|
458
|
+
}),
|
|
459
|
+
},
|
|
652
460
|
}}
|
|
653
461
|
>
|
|
654
462
|
<QuickOrderDrawerHeader
|
|
@@ -660,8 +468,7 @@ const SearchInput = forwardRef<SearchInputRef, SearchInputProps>(
|
|
|
660
468
|
onCloseDrawer={() => {
|
|
661
469
|
setIsQuickOrderDrawerOpen(false)
|
|
662
470
|
setQuickOrderProducts([])
|
|
663
|
-
|
|
664
|
-
setSkuQuantityMap({})
|
|
471
|
+
setOesOrderFormItems([])
|
|
665
472
|
}}
|
|
666
473
|
/>
|
|
667
474
|
<QuickOrderDrawerProducts
|
|
@@ -108,6 +108,7 @@ export interface NavbarProps {
|
|
|
108
108
|
removeButtonAriaLabel?: string
|
|
109
109
|
searchButtonLabel?: string
|
|
110
110
|
uploadingStatusText?: string
|
|
111
|
+
processingStatusText?: string
|
|
111
112
|
completedStatusTemplate?: string
|
|
112
113
|
acceptedFileTypes?: string
|
|
113
114
|
errorMessages?: Partial<
|
|
@@ -36,6 +36,9 @@
|
|
|
36
36
|
@include meta.load-css("~@faststore/ui/src/components/organisms/Navbar/styles.scss");
|
|
37
37
|
@include meta.load-css("~@faststore/ui/src/components/organisms/SlideOver/styles.scss");
|
|
38
38
|
@include meta.load-css("~@faststore/ui/src/components/organisms/SKUMatrix/styles.scss");
|
|
39
|
+
@include meta.load-css("~@faststore/ui/src/components/molecules/Card/styles.scss");
|
|
40
|
+
@include meta.load-css("~@faststore/ui/src/components/molecules/FileUploadCard/styles.scss");
|
|
41
|
+
@include meta.load-css("~@faststore/ui/src/components/molecules/FileUploadStatus/styles.scss");
|
|
39
42
|
@include meta.load-css("~@faststore/ui/src/components/organisms/QuickOrderDrawer/styles.scss");
|
|
40
43
|
|
|
41
44
|
// Sets Navbar height on desktop to avoid CLS issue - Cumulative Layout Shift
|
package/src/sdk/auth/index.ts
CHANGED
|
@@ -17,7 +17,7 @@ export const useAuth = () => {
|
|
|
17
17
|
Boolean(channel.salesChannel) &&
|
|
18
18
|
channel.hasOnlyDefaultSalesChannel === false
|
|
19
19
|
|
|
20
|
-
const
|
|
20
|
+
const isAuthenticated = hasSalesChannel && person
|
|
21
21
|
|
|
22
|
-
return {
|
|
22
|
+
return { isAuthenticated, profile: person, channel, isValidating }
|
|
23
23
|
}
|