ordering-ui-react-native 0.18.27 → 0.18.29

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ordering-ui-react-native",
3
- "version": "0.18.27",
3
+ "version": "0.18.29",
4
4
  "description": "Reusable components made in react native",
5
5
  "main": "src/index.tsx",
6
6
  "author": "ordering.inc",
@@ -45,7 +45,7 @@ const NewOrderNotificationUI = (props: any) => {
45
45
  },
46
46
  }
47
47
 
48
- const notificationSound = new Sound(theme.sounds.notification, '', () => {});
48
+ const notificationSound = new Sound(theme.sounds.notification, '', () => { });
49
49
 
50
50
  let _timeout: any = null
51
51
 
@@ -75,11 +75,11 @@ const NewOrderNotificationUI = (props: any) => {
75
75
  await fetch(`${ordering.root}/users/${user.id}/locations`, {
76
76
  method: 'POST',
77
77
  body: JSON.stringify({
78
- location: JSON.stringify({location: `{lat: ${location.latitude}, lng: ${location.longitude}}`})
78
+ location: JSON.stringify({ location: `{lat: ${location.latitude}, lng: ${location.longitude}}` })
79
79
  }),
80
80
  headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` }
81
81
  })
82
- } catch {}
82
+ } catch { }
83
83
  const duration = moment.duration(moment().diff(moment.utc(value?.last_driver_assigned_at)))
84
84
  const assignedSecondsDiff = duration.asSeconds()
85
85
  if (assignedSecondsDiff < 5 && !isBusinessApp) {
@@ -89,7 +89,7 @@ const NewOrderNotificationUI = (props: any) => {
89
89
  if (evtType === 3 || value.author_id === user.id) return
90
90
  handlePlayNotificationSound({
91
91
  evt: evtType,
92
- orderId: evtList[evtType].event === 'messages' ? value?.order_id : value?.id
92
+ orderId: value?.order_id
93
93
  })
94
94
  }
95
95
 
@@ -16,6 +16,8 @@ import {
16
16
  useToast,
17
17
  ToastType
18
18
  } from 'ordering-components/native';
19
+ import { DeviceOrientationMethods } from '../../../../../src/hooks/DeviceOrientation'
20
+
19
21
  import { GooglePlacesAutocomplete } from 'react-native-google-places-autocomplete';
20
22
  import Spinner from 'react-native-loading-spinner-overlay';
21
23
  import { useForm, Controller } from 'react-hook-form';
@@ -38,6 +40,8 @@ import {
38
40
  import { GPSButton } from '../GPSButton';
39
41
  import { ScrollView } from 'react-native-gesture-handler';
40
42
 
43
+ const { useDeviceOrientation } = DeviceOrientationMethods
44
+
41
45
  const inputNames = [
42
46
  { name: 'address', code: 'Address' },
43
47
  { name: 'internal_number', code: 'Internal number' },
@@ -66,6 +70,8 @@ const AddressFormUI = (props: AddressFormParams) => {
66
70
  } = props;
67
71
 
68
72
  const theme = useTheme();
73
+ const [orientationState] = useDeviceOrientation();
74
+
69
75
  const [autoCompleteInputFocused, setAutoCompleteInputFocused] = useState(false)
70
76
 
71
77
  const tagsName = [
@@ -75,6 +81,8 @@ const AddressFormUI = (props: AddressFormParams) => {
75
81
  { icon: theme.images.general.tag_plus, value: 'other' },
76
82
  ];
77
83
 
84
+ const HEIGHT_SCREEN = orientationState?.dimensions?.height
85
+
78
86
  const styles = StyleSheet.create({
79
87
  iconContainer: {
80
88
  display: 'flex',
@@ -524,7 +532,7 @@ const AddressFormUI = (props: AddressFormParams) => {
524
532
  />
525
533
  </View>
526
534
  <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
527
- <AddressFormContainer style={{ height: 600, overflow: 'scroll' }}>
535
+ <AddressFormContainer style={{ height: HEIGHT_SCREEN * .78, overflow: 'scroll' }}>
528
536
  <View>
529
537
  <FormInput>
530
538
  <AutocompleteInput>
@@ -1,14 +1,46 @@
1
1
  import React, { useEffect, useState } from 'react';
2
2
  import { createClient, AnalyticsProvider } from '@segment/analytics-react-native';
3
- import { useEvent, useConfig } from 'ordering-components/native';
3
+ import { useEvent, useConfig, useLanguage } from 'ordering-components/native';
4
4
 
5
5
  export const AnalyticsSegment = (props: any) => {
6
6
  const { children } = props
7
7
 
8
8
  const [events] = useEvent()
9
9
  const [configState] = useConfig()
10
+ const [, t] = useLanguage()
10
11
  const [segmentClient, setSegmentClient] = useState<any>({})
11
12
 
13
+ const handleProductsSearched = (query: any) => {
14
+ segmentClient.track('Products Searched', {
15
+ query: query
16
+ })
17
+ }
18
+
19
+ const handleProductListViewed = (category: any) => {
20
+ segmentClient.track('Product List Viewed', {
21
+ business_id: category.business_id,
22
+ category_id: category.id,
23
+ category: category.name,
24
+ products: category?.products
25
+ })
26
+ }
27
+
28
+ const handlePromotionViewed = (promotion: any) => {
29
+ segmentClient.track('Promotion Viewed', {
30
+ promotion_id: (promotion?.id || '').toString(),
31
+ name: promotion.name,
32
+ position: promotion.position
33
+ })
34
+ }
35
+
36
+ const handlePromotionClicked = (promotion: any) => {
37
+ segmentClient.track('Promotion Clicked', {
38
+ promotion_id: (promotion?.id || '').toString(),
39
+ name: promotion.name,
40
+ position: promotion.position
41
+ })
42
+ }
43
+
12
44
  const handleClickProduct = (product: any) => {
13
45
  segmentClient.track('Product Clicked', {
14
46
  id: product.id,
@@ -18,6 +50,15 @@ export const AnalyticsSegment = (props: any) => {
18
50
  })
19
51
  }
20
52
 
53
+ const handleProductViewed = (product: any) => {
54
+ segmentClient.track('Product Viewed', {
55
+ id: product.id,
56
+ name: product.name,
57
+ category: product.category_id,
58
+ price: product.price
59
+ })
60
+ }
61
+
21
62
  const handleProductAdded = (product: any) => {
22
63
  segmentClient.track('Product Added', {
23
64
  id: product.id,
@@ -38,6 +79,23 @@ export const AnalyticsSegment = (props: any) => {
38
79
  })
39
80
  }
40
81
 
82
+ const handleCartViewed = (cart: any) => {
83
+ segmentClient.track('Cart Viewed', {
84
+ id: cart.uuid,
85
+ products: cart?.products
86
+ })
87
+ }
88
+
89
+ const handleCheckoutStarted = (cart: any) => {
90
+ segmentClient.track('Checkout Started', {
91
+ cart_id: cart?.uuid,
92
+ affiliation: cart?.business?.name,
93
+ revenue: cart?.total,
94
+ tax: cart?.tax_total,
95
+ shipping: cart?.delivery_zone_price
96
+ })
97
+ }
98
+
41
99
  const handleOrderPlaced = (order: any) => {
42
100
  segmentClient.track('Order Placed', {
43
101
  id: order.id,
@@ -65,6 +123,35 @@ export const AnalyticsSegment = (props: any) => {
65
123
  tax: order.tax_total,
66
124
  shipping: order.delivery_zone_price
67
125
  })
126
+
127
+ if (order?.history?.length) {
128
+ const lasthistory = order.history[order.history.length - 1]
129
+ if (lasthistory?.data) {
130
+ lasthistory.data.forEach(item => {
131
+ if (item.attribute === 'status') {
132
+ if (item.new === 15) {
133
+ segmentClient.track('Order Completed', {
134
+ id: order.id,
135
+ affiliation: order.business?.name,
136
+ revenue: order.total,
137
+ tax: order.tax_total,
138
+ shipping: order.delivery_zone_price
139
+ })
140
+ }
141
+ const orderCancelled = [2, 5, 6, 10, 12, 16, 17]
142
+ if (orderCancelled.includes(item.new)) {
143
+ segmentClient.track('Order Cancelled', {
144
+ id: order.id,
145
+ affiliation: order.business?.name,
146
+ revenue: order.total,
147
+ tax: order.tax_total,
148
+ shipping: order.delivery_zone_price
149
+ })
150
+ }
151
+ }
152
+ })
153
+ }
154
+ }
68
155
  }
69
156
 
70
157
  const handleAddOrder = (order: any) => {
@@ -77,6 +164,51 @@ export const AnalyticsSegment = (props: any) => {
77
164
  })
78
165
  }
79
166
 
167
+ const handleCouponEntered = (cart: any) => {
168
+ segmentClient.track('Coupon Entered', {
169
+ cart_id: cart.uuid,
170
+ coupon: cart.coupon
171
+ })
172
+ }
173
+
174
+ const handleCouponApplied = (cart: any) => {
175
+ const coupon: any = cart?.offers?.find(offer => offer.type === 2)
176
+ if (coupon) {
177
+ segmentClient.track('Coupon Applied', {
178
+ cart_id: cart.uuid,
179
+ coupon_id: coupon.id,
180
+ coupon_name: coupon?.name,
181
+ discount: coupon?.summary?.discount
182
+ })
183
+ }
184
+ }
185
+
186
+ const handleCouponDenied = (coupon: any) => {
187
+ segmentClient.track('Coupon Denied', {
188
+ business_id: coupon.business_id,
189
+ coupon: coupon.coupon,
190
+ user_id: coupon.user.id,
191
+ reason: typeof coupon.reason === 'string' ? t(coupon.reason) : t(coupon.reason[0])
192
+ })
193
+ }
194
+
195
+ const handleCouponRemoved = (coupon: any) => {
196
+ segmentClient.track('Coupon Removed', {
197
+ business_id: coupon.business_id,
198
+ coupon_id: coupon.offer_id,
199
+ })
200
+ }
201
+
202
+ const handleProductReviewed = (products: any) => {
203
+ products.forEach((product: any) => {
204
+ segmentClient.track('Product Reviewed', {
205
+ product_id: product.product_id,
206
+ review_body: product.comment,
207
+ rating: product.qualification
208
+ })
209
+ })
210
+ }
211
+
80
212
  const handleLogin = (data: any) => {
81
213
  segmentClient.identify(data.id, {
82
214
  email: data.email,
@@ -86,23 +218,47 @@ export const AnalyticsSegment = (props: any) => {
86
218
 
87
219
  useEffect(() => {
88
220
  if (segmentClient?.config?.writeKey) {
221
+ events.on('products_searched', handleProductsSearched)
222
+ events.on('product_list_viewed', handleProductListViewed)
223
+ events.on('promotion_viewed', handlePromotionViewed)
224
+ events.on('promotion_clicked', handlePromotionClicked)
89
225
  events.on('product_clicked', handleClickProduct)
90
- events.on('userLogin', handleLogin)
226
+ events.on('product_viewed', handleProductViewed)
91
227
  events.on('product_added', handleProductAdded)
92
- events.on('order_placed', handleOrderPlaced)
228
+ events.on('cart_product_removed', handleProductRemoved)
229
+ events.on('cart_viewed', handleCartViewed)
230
+ events.on('checkout_started', handleCheckoutStarted)
93
231
  events.on('order_updated', handleUpdateOrder)
232
+ events.on('coupon_entered', handleCouponEntered)
233
+ events.on('offer_applied', handleCouponApplied)
234
+ events.on('offer_denied', handleCouponDenied)
235
+ events.on('offer_removed', handleCouponRemoved)
236
+ events.on('product_reviewed', handleProductReviewed)
237
+ events.on('userLogin', handleLogin)
238
+ events.on('order_placed', handleOrderPlaced)
94
239
  events.on('order_added', handleAddOrder)
95
- events.on('cart_product_removed', handleProductRemoved)
96
240
  }
97
241
  return () => {
98
242
  if (segmentClient?.config?.writeKey) {
243
+ events.off('products_searched', handleProductsSearched)
244
+ events.off('product_list_viewed', handleProductListViewed)
245
+ events.off('promotion_viewed', handlePromotionViewed)
246
+ events.off('promotion_clicked', handlePromotionClicked)
99
247
  events.off('product_clicked', handleClickProduct)
100
- events.off('userLogin', handleLogin)
248
+ events.off('product_viewed', handleProductViewed)
101
249
  events.off('product_added', handleProductAdded)
102
- events.off('order_placed', handleOrderPlaced)
250
+ events.off('cart_product_removed', handleProductRemoved)
251
+ events.off('cart_viewed', handleCartViewed)
252
+ events.off('checkout_started', handleCheckoutStarted)
103
253
  events.off('order_updated', handleUpdateOrder)
254
+ events.off('coupon_entered', handleCouponEntered)
255
+ events.off('offer_applied', handleCouponApplied)
256
+ events.off('offer_denied', handleCouponDenied)
257
+ events.off('offer_removed', handleCouponRemoved)
258
+ events.off('product_reviewed', handleProductReviewed)
259
+ events.off('userLogin', handleLogin)
260
+ events.off('order_placed', handleOrderPlaced)
104
261
  events.off('order_added', handleAddOrder)
105
- events.off('cart_product_removed', handleProductRemoved)
106
262
  }
107
263
  }
108
264
  }, [segmentClient])
@@ -110,7 +266,7 @@ export const AnalyticsSegment = (props: any) => {
110
266
  useEffect(() => {
111
267
  if (configState?.configs?.segment_track_id?.value) {
112
268
  const _segmentClient: any = createClient({
113
- writeKey: configState?.configs?.segment_track_id?.value
269
+ writeKey: configState?.configs?.segment_track_id?.value,
114
270
  });
115
271
  setSegmentClient(_segmentClient)
116
272
  }
@@ -1,6 +1,6 @@
1
1
  import React, { useState, useRef, useEffect } from 'react';
2
2
  import { TouchableOpacity, View } from 'react-native';
3
- import { useOrder, useLanguage, useUtils, useConfig } from 'ordering-components/native';
3
+ import { useOrder, useLanguage, useUtils, useConfig, useEvent } from 'ordering-components/native';
4
4
  import { useTheme } from 'styled-components/native';
5
5
  import {
6
6
  BIContainer,
@@ -22,7 +22,8 @@ export const BusinessItemAccordion = (props: any) => {
22
22
  handleClearProducts,
23
23
  handleClickCheckout,
24
24
  checkoutButtonDisabled,
25
- isMultiCheckout
25
+ isMultiCheckout,
26
+ isFromUpselling
26
27
  } = props
27
28
 
28
29
  const [orderState] = useOrder();
@@ -30,6 +31,7 @@ export const BusinessItemAccordion = (props: any) => {
30
31
  const [{ parsePrice }] = useUtils();
31
32
  const [{ configs }] = useConfig()
32
33
  const theme = useTheme();
34
+ const [events] = useEvent()
33
35
 
34
36
  const isCartPending = cart?.status === 2
35
37
  const isClosed = !cart?.valid_schedule
@@ -37,6 +39,7 @@ export const BusinessItemAccordion = (props: any) => {
37
39
  const isBusinessChangeEnabled = configs?.cart_change_business_validation?.value === '1'
38
40
 
39
41
  const [isActive, setActiveState] = useState(!!singleBusiness)
42
+ const [viewedCart, setViewedCart] = useState<any>(null)
40
43
 
41
44
  useEffect(() => {
42
45
  const cartsArray = Object.values(orderState?.carts)
@@ -52,6 +55,15 @@ export const BusinessItemAccordion = (props: any) => {
52
55
  return acc = acc
53
56
  }, cart?.subtotal)
54
57
 
58
+ useEffect(() => {
59
+ if (isActive && !isFromUpselling) {
60
+ if (cart?.uuid !== viewedCart?.uuid) {
61
+ setViewedCart(cart)
62
+ events.emit('cart_viewed', cart)
63
+ }
64
+ }
65
+ }, [isActive, viewedCart])
66
+
55
67
  return (
56
68
  <BIContainer isClosed={isClosed} isMultiCheckout={isMultiCheckout} checkoutVisible={!isActive && !isClosed && !!isProducts && !checkoutButtonDisabled}>
57
69
  <BIHeader
@@ -1,5 +1,5 @@
1
1
  import React, { useCallback, useEffect, useRef, useState } from 'react'
2
- import { View, TouchableOpacity, StyleSheet, SafeAreaView, Dimensions, Platform, KeyboardAvoidingViewBase, KeyboardAvoidingView, Vibration } from 'react-native'
2
+ import { View, TouchableOpacity, StyleSheet, SafeAreaView, Dimensions, Platform, KeyboardAvoidingViewBase, KeyboardAvoidingView, Vibration, BackHandler } from 'react-native'
3
3
  import { IOScrollView } from 'react-native-intersection-observer'
4
4
  import { useSafeAreaInsets } from 'react-native-safe-area-context'
5
5
  import { useTheme } from 'styled-components/native';
@@ -11,7 +11,8 @@ import {
11
11
  useUtils,
12
12
  ToastType,
13
13
  useToast,
14
- useConfig
14
+ useConfig,
15
+ useEvent
15
16
  } from 'ordering-components/native'
16
17
  import { Fade, Placeholder, PlaceholderLine } from 'rn-placeholder';
17
18
  import { OButton, OIcon, OModal, OText } from '../shared'
@@ -80,6 +81,7 @@ const BusinessProductsListingUI = (props: BusinessProductsListingParams) => {
80
81
  const [{ parsePrice }] = useUtils()
81
82
  const [, { showToast }] = useToast()
82
83
  const [{ configs }] = useConfig()
84
+ const [events] = useEvent()
83
85
  const isFocused = useIsFocused();
84
86
  const isPreOrder = configs?.preorder_status_enabled?.value === '1'
85
87
 
@@ -133,6 +135,7 @@ const BusinessProductsListingUI = (props: BusinessProductsListingParams) => {
133
135
  const [openService, setOpenService] = useState(false)
134
136
  const [currentProduct, setCurrentProduct] = useState(null)
135
137
  const [searchBarHeight, setSearchBarHeight] = useState(60)
138
+ const [viewedCategory, setViewedCategory] = useState<any>(null)
136
139
 
137
140
  const isCheckoutMultiBusinessEnabled: Boolean = configs?.checkout_multi_business_enabled?.value === '1'
138
141
  const isQuickAddProduct = configs?.add_product_with_one_click?.value === '1'
@@ -178,6 +181,7 @@ const BusinessProductsListingUI = (props: BusinessProductsListingParams) => {
178
181
  productAddedToCartLength
179
182
  })
180
183
  }
184
+ events.emit('product_clicked', product)
181
185
  }
182
186
 
183
187
  const handleCancel = () => {
@@ -309,6 +313,47 @@ const BusinessProductsListingUI = (props: BusinessProductsListingParams) => {
309
313
  return acc = acc
310
314
  }, currentCart?.subtotal)
311
315
 
316
+ const onChangeSearch = (query: any) => {
317
+ handleChangeSearch(query)
318
+ if (query) {
319
+ events.emit('products_searched', query)
320
+ }
321
+ }
322
+
323
+ useEffect(() => {
324
+ let categoryId: any = null
325
+ if (business?.lazy_load_products_recommended) {
326
+ if (categorySelected?.id) {
327
+ categoryId = categorySelected.id
328
+ }
329
+ } else {
330
+ if (selectedCategoryId) {
331
+ const originCategoryId = selectedCategoryId.replace('cat_', '')
332
+ if (!isNaN(originCategoryId)) {
333
+ categoryId = Number(originCategoryId)
334
+ }
335
+ }
336
+ }
337
+ if (categoryId) {
338
+ const _viewedCategory = business.categories.find(category => category.id === categoryId)
339
+ if (_viewedCategory?.id !== viewedCategory?.id) {
340
+ setViewedCategory(_viewedCategory)
341
+ events.emit('product_list_viewed', _viewedCategory)
342
+ }
343
+ }
344
+ }, [business?.lazy_load_products_recommended, selectedCategoryId, categorySelected?.id, viewedCategory])
345
+
346
+ useEffect(() => {
347
+ const handleArrowBack: any = () => {
348
+ navigation.goBack()
349
+ return true
350
+ }
351
+ BackHandler.addEventListener('hardwareBackPress', handleArrowBack);
352
+ return () => {
353
+ BackHandler.removeEventListener('hardwareBackPress', handleArrowBack);
354
+ }
355
+ }, [])
356
+
312
357
  return (
313
358
  <>
314
359
  <View style={{ flex: 1, backgroundColor: backgroundColor }}>
@@ -343,12 +388,12 @@ const BusinessProductsListingUI = (props: BusinessProductsListingParams) => {
343
388
  <WrapSearchBar>
344
389
  <SearchBar
345
390
  autoFocus
346
- onSearch={handleChangeSearch}
391
+ onSearch={onChangeSearch}
347
392
  onCancel={() => handleCancel()}
348
393
  isCancelXButtonShow
349
394
  noBorderShow
350
395
  placeholder={t('SEARCH_PRODUCTS', 'Search Products')}
351
- lazyLoad={businessState?.business?.lazy_load_products_recommended}
396
+ lazyLoad
352
397
  />
353
398
  </WrapSearchBar>
354
399
  )}
@@ -51,7 +51,8 @@ const CartUI = (props: any) => {
51
51
  preorderTimeRange,
52
52
  preorderMaximumDays,
53
53
  preorderMinimumDays,
54
- cateringTypes
54
+ cateringTypes,
55
+ isFromUpselling
55
56
  } = props
56
57
 
57
58
  const theme = useTheme();
@@ -232,6 +233,7 @@ const CartUI = (props: any) => {
232
233
  handleClickCheckout={() => setOpenUpselling(true)}
233
234
  checkoutButtonDisabled={(openUpselling && !canOpenUpselling) || subtotalWithTaxes < cart?.minimum || !cart?.valid_address}
234
235
  isMultiCheckout={isMultiCheckout}
236
+ isFromUpselling={isFromUpselling}
235
237
  >
236
238
  {cart?.products?.length > 0 && cart?.products.map((product: any, i: number) => (
237
239
  <ProductItemAccordion
@@ -418,6 +420,7 @@ const CartUI = (props: any) => {
418
420
  <CouponControl
419
421
  businessId={businessId}
420
422
  price={cart.total}
423
+ cart={cart}
421
424
  />
422
425
  </OSCoupon>
423
426
  </OSTable>
@@ -14,6 +14,7 @@ import {
14
14
  useConfig,
15
15
  useToast,
16
16
  ToastType,
17
+ useEvent
17
18
  } from 'ordering-components/native';
18
19
  import { useTheme } from 'styled-components/native';
19
20
  import { OText, OIcon, OModal, OButton } from '../shared';
@@ -138,6 +139,7 @@ const CheckoutUI = (props: any) => {
138
139
  const [{ parsePrice, parseDate }] = useUtils();
139
140
  const [{ options, carts, loading }, { confirmCart }] = useOrder();
140
141
  const [validationFields] = useValidationFields();
142
+ const [events] = useEvent()
141
143
 
142
144
  const [errorCash, setErrorCash] = useState(false);
143
145
  const [userErrors, setUserErrors] = useState<any>([]);
@@ -329,6 +331,10 @@ const CheckoutUI = (props: any) => {
329
331
  )
330
332
  }
331
333
 
334
+ useEffect(() => {
335
+ cart && events.emit('checkout_started', cart)
336
+ }, [])
337
+
332
338
  return (
333
339
  <>
334
340
  <Container noPadding>
@@ -1,6 +1,6 @@
1
1
  import React, { useEffect } from 'react';
2
2
  import { StyleSheet, Alert, Text } from 'react-native';
3
- import { CouponControl as CouponController, useLanguage } from 'ordering-components/native';
3
+ import { CouponControl as CouponController, useLanguage, useEvent } from 'ordering-components/native';
4
4
  import { useTheme } from 'styled-components/native';
5
5
  import {
6
6
  CContainer,
@@ -18,11 +18,13 @@ const CouponControlUI = (props: any) => {
18
18
  handleRemoveCouponClick,
19
19
  onChangeInputCoupon,
20
20
  confirm,
21
- setConfirm
21
+ setConfirm,
22
+ cart
22
23
  } = props
23
24
 
24
25
  const [, t] = useLanguage()
25
26
  const theme = useTheme();
27
+ const [events] = useEvent()
26
28
 
27
29
  const styles = StyleSheet.create({
28
30
  inputsStyle: {
@@ -45,6 +47,11 @@ const CouponControlUI = (props: any) => {
45
47
  setConfirm({ ...confirm, open: false, error: false })
46
48
  }
47
49
 
50
+ const onButtonApplyClick = () => {
51
+ events.emit('coupon_entered', { ...cart, coupon: couponInput })
52
+ handleButtonApplyClick()
53
+ }
54
+
48
55
  useEffect(() => {
49
56
  if (confirm.content) {
50
57
  Alert.alert(
@@ -94,7 +101,7 @@ const CouponControlUI = (props: any) => {
94
101
  inputStyle={{ fontSize: 12 }}
95
102
  />
96
103
  <OButton
97
- onClick={() => handleButtonApplyClick()}
104
+ onClick={() => onButtonApplyClick()}
98
105
  bgColor={theme.colors.primary}
99
106
  borderColor={theme.colors.primary}
100
107
  textStyle={{ color: 'white', fontSize: 12 }}
@@ -3,7 +3,7 @@ import {
3
3
  useLanguage,
4
4
  PurchaseGiftCard as PurchaseGiftCardController
5
5
  } from 'ordering-components/native'
6
- import { StyleSheet, View, TouchableOpacity } from 'react-native'
6
+ import { StyleSheet, View, TouchableOpacity, ScrollView } from 'react-native'
7
7
  import { Fade, Placeholder, PlaceholderLine } from 'rn-placeholder';
8
8
  import { useTheme } from 'styled-components/native';
9
9
  import { OText, OButton, OIcon } from '../../shared';
@@ -44,7 +44,11 @@ const PurchaseGiftCardUI = (props: any) => {
44
44
  <Container>
45
45
  <OText color={theme.colors.textNormal} weight='bold' size={20} mBottom={40}>{t('PURCHASE_GIFT_CARD', 'Purchase gift card')}</OText>
46
46
  <OText color={theme.colors.textNormal} size={14}>{t('SELECT_ONE_OPTION', 'Select one option')}</OText>
47
- <View>
47
+ <ScrollView
48
+ contentContainerStyle={{
49
+ flexGrow: 1
50
+ }}
51
+ >
48
52
  {productsListState.loading && (
49
53
  [...Array(5).keys()].map(i => (
50
54
  <View key={i} style={style.itemStyle}>
@@ -72,7 +76,7 @@ const PurchaseGiftCardUI = (props: any) => {
72
76
  <OText color={theme.colors.textNormal} size={14}>{product.name}</OText>
73
77
  </TouchableOpacity>
74
78
  ))}
75
- </View>
79
+ </ScrollView>
76
80
  <OButton
77
81
  onClick={() => handleAccept()}
78
82
  text={t('ACCEPT', 'Accept')}
@@ -3,4 +3,6 @@ import styled from 'styled-components/native'
3
3
  export const Container = styled.View`
4
4
  width: 100%;
5
5
  padding-horizontal: 40px;
6
+ flex: 1;
7
+ padding-bottom: 30px;
6
8
  `
@@ -25,6 +25,7 @@ const MultiCartsPaymethodsAndWalletsUI = (props: any) => {
25
25
  businessIds,
26
26
  paymethodsAndWallets,
27
27
  walletsState,
28
+ walletsPaymethod,
28
29
  paymethodSelected,
29
30
  handleSelectPaymethod,
30
31
  handleSelectWallet,
@@ -52,6 +53,8 @@ const MultiCartsPaymethodsAndWalletsUI = (props: any) => {
52
53
  }
53
54
  }
54
55
 
56
+ const creditBalance: any = (wallet: any) => ` = ${parsePrice(wallet.balance / wallet.redemption_rate, { isTruncable: true })}`
57
+
55
58
  const getPayIcon = (method: string) => {
56
59
  switch (method) {
57
60
  case 'cash':
@@ -169,13 +172,16 @@ const MultiCartsPaymethodsAndWalletsUI = (props: any) => {
169
172
  </>
170
173
  ) : (
171
174
  <>
172
- {walletsState?.result?.filter((wallet: any) => paymethodsAndWallets.wallets.find((item: any) => item.type === wallet.type)).map((wallet: any, idx: any) => walletName[wallet.type]?.isActive && (
175
+ {walletsState?.result?.filter((wallet: any) =>
176
+ paymethodsAndWallets.wallets.find((item: any) => item.type === wallet.type))
177
+ .map((wallet: any, idx: any) => walletName[wallet.type]?.isActive &&
178
+ (
173
179
  <WalletItem
174
180
  key={wallet.type}
175
181
  isBottomBorder={idx === paymethodsAndWallets.wallets?.length - 1}
176
- onPress={() => handleSelectWallet(paymethodSelected.wallet_id === wallet.id ? false : true, wallet)}
182
+ onPress={() => handleSelectWallet(!!!walletsPaymethod?.find((walletPay: any) => walletPay.wallet_id === wallet.id)?.id, wallet)}
177
183
  >
178
- {paymethodSelected.wallet_id === wallet.id ? (
184
+ {!!walletsPaymethod?.find((walletPay: any) => walletPay.wallet_id === wallet.id)?.id ? (
179
185
  <MaterialCommunityIcons
180
186
  name="checkbox-marked"
181
187
  size={25}
@@ -189,7 +195,23 @@ const MultiCartsPaymethodsAndWalletsUI = (props: any) => {
189
195
  />
190
196
  )}
191
197
  <OText size={12} style={{ flex: 1, marginLeft: 15 }}>{walletName[wallet.type]?.name}</OText>
192
- <OText size={12}>{parsePrice(wallet.balance)}</OText>
198
+ {wallet.type === 'cash' && (
199
+ <OText>
200
+ {parsePrice(wallet?.balance, { isTruncable: true })}
201
+ </OText>
202
+ )}
203
+ {wallet.type === 'credit_point' && (
204
+ <OText>
205
+ <OText color={theme.colors.primary} weight='bold'>
206
+ {`${wallet?.balance} ${t('POINTS', 'Points')}`}
207
+ </OText>
208
+ <OText>
209
+ {wallet?.balance > 0
210
+ ? creditBalance(wallet)
211
+ : null}
212
+ </OText>
213
+ </OText>
214
+ )}
193
215
  </WalletItem>
194
216
  ))}
195
217
  </>
@@ -63,6 +63,7 @@ const MultiCheckoutUI = (props: any) => {
63
63
  loyaltyPlansState,
64
64
  totalCartsFee,
65
65
  cartGroup,
66
+ walletState,
66
67
  onNavigationRedirectReplace
67
68
  } = props
68
69
 
@@ -86,8 +87,11 @@ const MultiCheckoutUI = (props: any) => {
86
87
  const configTypes = configs?.order_types_allowed?.value.split('|').map((value: any) => Number(value)) || []
87
88
  const isPreOrder = configs?.preorder_status_enabled?.value === '1'
88
89
  const isMultiDriverTips = configs?.checkout_multi_business_enabled?.value === '1'
89
- const isDisablePlaceOrderButton = !(paymethodSelected?.paymethod_id || paymethodSelected?.wallet_id) || (paymethodSelected?.paymethod?.gateway === 'stripe' && !paymethodSelected?.paymethod_data)
90
90
  const walletCarts = (Object.values(carts)?.filter((cart: any) => cart?.products && cart?.products?.length && cart?.status !== 2 && cart?.valid_schedule && cart?.valid_products && cart?.valid_address && cart?.valid_maximum && cart?.valid_minimum && cart?.wallets) || null) || []
91
+ const isDisablePlaceOrderButton = cartGroup?.loading || (!(paymethodSelected?.paymethod_id || paymethodSelected?.wallet_id) && cartGroup?.result?.balance > 0) ||
92
+ (paymethodSelected?.paymethod?.gateway === 'stripe' && !paymethodSelected?.paymethod_data) ||
93
+ walletCarts.length > 0
94
+
91
95
  const driverTipsOptions = typeof configs?.driver_tip_options?.value === 'string'
92
96
  ? JSON.parse(configs?.driver_tip_options?.value) || []
93
97
  : configs?.driver_tip_options?.value || []
@@ -171,6 +175,12 @@ const MultiCheckoutUI = (props: any) => {
171
175
  }
172
176
  }, [openCarts])
173
177
 
178
+ useEffect(() => {
179
+ if (walletState.error) {
180
+ showToast(ToastType.Error, t(walletState.error, walletState.error?.[0]?.replace(/_/g, ' ')))
181
+ }
182
+ }, [walletState.error])
183
+
174
184
  return (
175
185
  <>
176
186
  <Container noPadding>
@@ -248,6 +258,7 @@ const MultiCheckoutUI = (props: any) => {
248
258
  <MultiCartsPaymethodsAndWallets
249
259
  openCarts={openCarts}
250
260
  paymethodSelected={paymethodSelected}
261
+ walletsPaymethod={cartGroup?.result?.wallets}
251
262
  handleSelectPaymethod={handleSelectPaymethod}
252
263
  handleSelectWallet={handleSelectWallet}
253
264
  handlePaymethodDataChange={handlePaymethodDataChange}
@@ -346,12 +357,14 @@ const MultiCheckoutUI = (props: any) => {
346
357
  )}
347
358
  {openCarts.length > 1 && (
348
359
  <ChCartsTotal>
349
- {totalCartsFee && configs?.multi_business_checkout_show_combined_delivery_fee?.value === '1' && (
360
+ {!!totalCartsFee && configs?.multi_business_checkout_show_combined_delivery_fee?.value === '1' && (
350
361
  <View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
351
362
  <OText size={14} lineHeight={24} color={theme.colors.textNormal} weight={'400'}>
352
363
  {t('TOTAL_DELIVERY_FEE', 'Total delivery fee')}
353
364
  </OText>
354
- <OText size={14} lineHeight={24} color={theme.colors.textNormal} weight={'400'}>{parsePrice(totalCartsFee)}</OText>
365
+ <OText size={14} lineHeight={24} color={theme.colors.textNormal} weight={'400'}>
366
+ {parsePrice(totalCartsFee)}
367
+ </OText>
355
368
  </View>
356
369
  )}
357
370
  {openCarts.reduce((sum: any, cart: any) => sum + cart?.driver_tip, 0) > 0 &&
@@ -1,5 +1,5 @@
1
- import React, { useRef } from 'react'
2
- import { useUtils, PageBanner as PageBannerController } from 'ordering-components/native'
1
+ import React, { useEffect, useState, useRef } from 'react'
2
+ import { useUtils, useEvent, PageBanner as PageBannerController } from 'ordering-components/native'
3
3
  import { View, StyleSheet, Dimensions, TouchableOpacity } from 'react-native'
4
4
  import { Fade, Placeholder, PlaceholderLine } from 'rn-placeholder';
5
5
  import Carousel from 'react-native-snap-carousel'
@@ -16,7 +16,10 @@ const PageBannerUI = (props: any) => {
16
16
 
17
17
  const theme = useTheme();
18
18
  const [{ optimizeImage }] = useUtils();
19
- const carouselRef = useRef(null)
19
+ const [events] = useEvent()
20
+ const carouselRef = useRef<any>(null)
21
+ const [currentIndex, setCurrentIndex] = useState(0)
22
+ const [viewedBanner, setViewedBanner] = useState<any>(null)
20
23
 
21
24
  const windowWidth = Dimensions.get('window').width;
22
25
 
@@ -41,10 +44,11 @@ const PageBannerUI = (props: any) => {
41
44
  })
42
45
 
43
46
  const onRedirect = (route: string, params?: any) => {
44
- navigation.navigate(route, params)
47
+ navigation.push(route, params)
45
48
  }
46
49
 
47
- const handleGoToPage = (action: any) => {
50
+ const handleGoToPage = (item: any) => {
51
+ const action = item.action
48
52
  if (!action?.url) return
49
53
  let slug
50
54
  if (action.type === 'business') {
@@ -62,12 +66,14 @@ const PageBannerUI = (props: any) => {
62
66
  productId: action.product_id
63
67
  })
64
68
  }
69
+ const clickedBanner = pageBannerState.result.find(banner => banner.id === item?.banner_id)
70
+ events.emit('promotion_clicked', clickedBanner)
65
71
  }
66
72
 
67
73
  const renderItem = ({ item, index }) => {
68
74
  return (
69
75
  <TouchableOpacity
70
- onPress={() => handleGoToPage(item.action)}
76
+ onPress={() => handleGoToPage(item)}
71
77
  >
72
78
  <View style={styles.sliderWrapper}>
73
79
  <FastImage
@@ -80,6 +86,24 @@ const PageBannerUI = (props: any) => {
80
86
  )
81
87
  }
82
88
 
89
+ const updateIndex = () => {
90
+ setCurrentIndex(carouselRef?.current?.currentIndex)
91
+ }
92
+
93
+ useEffect(() => {
94
+ if (pageBannerState.loading) return
95
+ if (pageBannerState.banner?.items && pageBannerState.banner?.items.length > 0) {
96
+ const bannerId = pageBannerState.banner.items[currentIndex]?.banner_id
97
+ if (pageBannerState.result && bannerId) {
98
+ const _viewedBanner = pageBannerState.result.find(banner => banner.id === bannerId)
99
+ if (_viewedBanner?.id !== viewedBanner?.id) {
100
+ setViewedBanner(_viewedBanner)
101
+ events.emit('promotion_viewed', _viewedBanner)
102
+ }
103
+ }
104
+ }
105
+ }, [pageBannerState.loading, currentIndex, viewedBanner])
106
+
83
107
  return (
84
108
  <>
85
109
  {pageBannerState.loading ? (
@@ -128,6 +152,7 @@ const PageBannerUI = (props: any) => {
128
152
  pagingEnabled
129
153
  removeClippedSubviews={false}
130
154
  inactiveSlideOpacity={1}
155
+ onSnapToItem={updateIndex}
131
156
  />
132
157
  </PageBannerWrapper>
133
158
  )}
@@ -20,7 +20,8 @@ import {
20
20
  useUtils,
21
21
  ToastType,
22
22
  useToast,
23
- useConfig
23
+ useConfig,
24
+ useEvent
24
25
  } from 'ordering-components/native';
25
26
  import uuid from 'react-native-uuid';
26
27
  import { useTheme } from 'styled-components/native';
@@ -81,6 +82,7 @@ export const ProductOptionsUI = (props: any) => {
81
82
 
82
83
  const theme = useTheme();
83
84
  const [, { showToast }] = useToast()
85
+ const [events] = useEvent()
84
86
 
85
87
  const isChewLayout = theme?.header?.components?.layout?.type?.toLowerCase() === 'chew'
86
88
 
@@ -203,6 +205,7 @@ export const ProductOptionsUI = (props: any) => {
203
205
  const [summaryRefHeight, setSummaryRefHeight] = useState(0)
204
206
  const [isScrollAvailable, setIsScrollAvailable] = useState(null)
205
207
  const [editionsLayoutY, setEditionsLayoutY] = useState(null)
208
+ const [viewedProduct, setViewedProduct] = useState<any>(null)
206
209
 
207
210
  const guestCheckoutEnabled = configs?.guest_checkout_enabled?.value === '1'
208
211
  const orderTypeEnabled = !orderTypeList[orderState?.options?.type - 1] || configs?.allowed_order_types_guest_checkout?.value?.includes(orderTypeList[orderState?.options?.type - 1])
@@ -515,6 +518,12 @@ export const ProductOptionsUI = (props: any) => {
515
518
  }
516
519
  }, [])
517
520
 
521
+ useEffect(() => {
522
+ if (!product?.id || product?.id === viewedProduct?.id) return
523
+ setViewedProduct(product)
524
+ events.emit('product_viewed', product)
525
+ }, [product?.id, viewedProduct])
526
+
518
527
  return (
519
528
  <SafeAreaView style={{ flex: 1 }}>
520
529
  <View style={styles.wrapperNavbar}>
@@ -212,7 +212,7 @@ const PromotionsUI = (props: PromotionParams) => {
212
212
  </OText>
213
213
  <ScrollView
214
214
  showsVerticalScrollIndicator={false}
215
- style={{ height: '75%' }}
215
+ style={{ height: '68%' }}
216
216
  >
217
217
  {offerSelected?.businesses?.map((business: any) => {
218
218
  return (
@@ -196,6 +196,7 @@ const UpsellingProductsUI = (props: UpsellingProductsParams) => {
196
196
  {showCartList && cartList.map((cart: any, i: number) => (
197
197
  <CartList key={i}>
198
198
  <Cart
199
+ isFromUpselling
199
200
  cart={cart}
200
201
  cartuuid={cart.uuid}
201
202
  hideUpselling