ordering-ui-react-native 0.15.90 → 0.15.93

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.
Files changed (36) hide show
  1. package/package.json +1 -1
  2. package/src/components/Checkout/index.tsx +23 -2
  3. package/src/components/SingleProductCard/index.tsx +16 -4
  4. package/themes/business/src/components/Chat/index.tsx +38 -86
  5. package/themes/business/src/components/LoginForm/index.tsx +89 -2
  6. package/themes/business/src/components/LoginForm/styles.tsx +6 -0
  7. package/themes/business/src/components/NewOrderNotification/index.tsx +24 -13
  8. package/themes/business/src/components/OrderDetails/Delivery.tsx +11 -3
  9. package/themes/business/src/components/OrderDetails/OrderContentComponent.tsx +1 -0
  10. package/themes/business/src/components/OrdersOption/index.tsx +5 -2
  11. package/themes/business/src/types/index.tsx +3 -0
  12. package/themes/kiosk/src/components/BusinessMenu/index.tsx +23 -25
  13. package/themes/kiosk/src/components/BusinessesListing/index.tsx +2 -3
  14. package/themes/kiosk/src/components/Cart/index.tsx +10 -11
  15. package/themes/kiosk/src/components/CategoriesMenu/index.tsx +36 -30
  16. package/themes/kiosk/src/components/Checkout/index.tsx +22 -19
  17. package/themes/kiosk/src/components/OrderTypeCardSelector/index.tsx +1 -1
  18. package/themes/kiosk/src/components/PaymentOptions/index.tsx +54 -52
  19. package/themes/kiosk/src/components/ProductOptionSubOption/index.tsx +3 -1
  20. package/themes/original/src/components/AppleLogin/index.tsx +2 -4
  21. package/themes/original/src/components/BusinessListingSearch/index.tsx +117 -7
  22. package/themes/original/src/components/BusinessListingSearch/styles.tsx +14 -1
  23. package/themes/original/src/components/BusinessesListing/index.tsx +3 -1
  24. package/themes/original/src/components/Checkout/index.tsx +36 -28
  25. package/themes/original/src/components/DriverTips/index.tsx +6 -6
  26. package/themes/original/src/components/Help/index.tsx +21 -4
  27. package/themes/original/src/components/LastOrders/index.tsx +12 -1
  28. package/themes/original/src/components/LoginForm/Otp/index.tsx +0 -1
  29. package/themes/original/src/components/LoginForm/index.tsx +42 -9
  30. package/themes/original/src/components/LoginForm/styles.tsx +1 -3
  31. package/themes/original/src/components/OrderDetails/index.tsx +18 -21
  32. package/themes/original/src/components/PaymentOptionWallet/index.tsx +1 -0
  33. package/themes/original/src/components/ProductForm/index.tsx +48 -40
  34. package/themes/original/src/components/ProductOptionSubOption/index.tsx +13 -9
  35. package/themes/original/src/components/Promotions/index.tsx +18 -2
  36. package/themes/original/src/types/index.tsx +3 -1
@@ -113,7 +113,10 @@ const CheckoutUI = (props: any) => {
113
113
  right: Platform.OS === 'ios' ? 5 : (I18nManager.isRTL ? 30 : 0),
114
114
  position: 'absolute',
115
115
  fontSize: 20
116
- }
116
+ },
117
+ wrapperNavbar: Platform.OS === 'ios'
118
+ ? { paddingVertical: 0, paddingHorizontal: 40 }
119
+ : { paddingVertical: 20, paddingHorizontal: 40 }
117
120
  })
118
121
 
119
122
  const [, { showToast }] = useToast();
@@ -133,20 +136,20 @@ const CheckoutUI = (props: any) => {
133
136
  const [isDeliveryOptionModalVisible, setIsDeliveryOptionModalVisible] = useState(false)
134
137
  const [showGateway, setShowGateway] = useState<any>({ closedByUsed: false, open: false });
135
138
  const [webviewPaymethod, setWebviewPaymethod] = useState<any>(null)
136
-
139
+
137
140
  const placeSpotTypes = [3, 4]
138
- const businessConfigs = businessDetails?.business?.configs ?? []
139
- const isWalletCashEnabled = businessConfigs.find((config: any) => config.key === 'wallet_cash_enabled')?.value === '1'
140
- const isWalletCreditPointsEnabled = businessConfigs.find((config: any) => config.key === 'wallet_credit_point_enabled')?.value === '1'
141
- const isWalletEnabled = configs?.cash_wallet?.value && configs?.wallet_enabled?.value === '1' && (isWalletCashEnabled || isWalletCreditPointsEnabled)
141
+ const businessConfigs = businessDetails?.business?.configs ?? []
142
+ const isWalletCashEnabled = businessConfigs.find((config: any) => config.key === 'wallet_cash_enabled')?.value === '1'
143
+ const isWalletCreditPointsEnabled = businessConfigs.find((config: any) => config.key === 'wallet_credit_point_enabled')?.value === '1'
144
+ const isWalletEnabled = configs?.cash_wallet?.value && configs?.wallet_enabled?.value === '1' && (isWalletCashEnabled || isWalletCreditPointsEnabled)
142
145
 
143
146
  const isPreOrder = configs?.preorder_status_enabled?.value === '1'
144
147
  const isDisabledButtonPlace = loading || !cart?.valid || (!paymethodSelected && cart?.balance > 0) || placing || errorCash ||
145
- cart?.subtotal < cart?.minimum || (placeSpotTypes.includes(options?.type) && !cart?.place) ||
146
- (options.type === 1 &&
147
- validationFields?.fields?.checkout?.driver_tip?.enabled &&
148
- validationFields?.fields?.checkout?.driver_tip?.required &&
149
- (Number(cart?.driver_tip) <= 0))
148
+ cart?.subtotal < cart?.minimum || (placeSpotTypes.includes(options?.type) && !cart?.place) ||
149
+ (options.type === 1 &&
150
+ validationFields?.fields?.checkout?.driver_tip?.enabled &&
151
+ validationFields?.fields?.checkout?.driver_tip?.required &&
152
+ (Number(cart?.driver_tip) <= 0))
150
153
 
151
154
  const driverTipsOptions = typeof configs?.driver_tip_options?.value === 'string'
152
155
  ? JSON.parse(configs?.driver_tip_options?.value) || []
@@ -259,13 +262,18 @@ const CheckoutUI = (props: any) => {
259
262
  return (
260
263
  <>
261
264
  <Container noPadding>
262
- <NavBar
263
- isVertical
264
- onActionLeft={() => navigation?.canGoBack() && navigation.goBack()}
265
- title={t('CHECKOUT', 'Checkout')}
266
- titleStyle={{ marginLeft: 0, marginRight: 0, paddingLeft: 40 }}
267
- btnStyle={{ marginLeft: 40, padding: 40 }}
268
- />
265
+ <View style={styles.wrapperNavbar}>
266
+ <NavBar
267
+ title={t('CHECKOUT', 'Checkout')}
268
+ titleAlign={'center'}
269
+ onActionLeft={() => navigation?.canGoBack() && navigation.goBack()}
270
+ showCall={false}
271
+ btnStyle={{ paddingLeft: 0 }}
272
+ style={{ flexDirection: 'column', alignItems: 'flex-start', marginTop: Platform.OS === 'ios' ? 0 : 20 }}
273
+ titleWrapStyle={{ paddingHorizontal: 0 }}
274
+ titleStyle={{ marginRight: 0, marginLeft: 0 }}
275
+ />
276
+ </View>
269
277
  <ChContainer style={styles.pagePadding}>
270
278
  <ChSection style={{ paddingTop: 0 }}>
271
279
  <ChHeader>
@@ -668,16 +676,16 @@ const CheckoutUI = (props: any) => {
668
676
  </OText>
669
677
  )}
670
678
  {options.type === 1 &&
671
- validationFields?.fields?.checkout?.driver_tip?.enabled &&
672
- validationFields?.fields?.checkout?.driver_tip?.required &&
673
- (Number(cart?.driver_tip) <= 0) && (
674
- <OText
675
- color={theme.colors.error}
676
- size={12}
677
- >
678
- {t('WARNING_INVALID_DRIVER_TIP', 'Driver Tip is required.')}
679
- </OText>
680
- )}
679
+ validationFields?.fields?.checkout?.driver_tip?.enabled &&
680
+ validationFields?.fields?.checkout?.driver_tip?.required &&
681
+ (Number(cart?.driver_tip) <= 0) && (
682
+ <OText
683
+ color={theme.colors.error}
684
+ size={12}
685
+ >
686
+ {t('WARNING_INVALID_DRIVER_TIP', 'Driver Tip is required.')}
687
+ </OText>
688
+ )}
681
689
  </ChErrors>
682
690
  </View>
683
691
  )}
@@ -59,11 +59,14 @@ const DriverTipsUI = (props: any) => {
59
59
  tip = isNaN(tip) ? 0 : tip
60
60
  setvalue(tip)
61
61
  }
62
-
62
+
63
63
  return (
64
64
  <DTContainer>
65
+ <DTLabel>
66
+ {t('CUSTOM_DRIVER_TIP_MESSAGE', '100% of these tips go directly to your driver')}
67
+ </DTLabel>
65
68
  <DTWrapperTips>
66
- {driverTipsOptions.map((option: any, i: number) => (
69
+ {!isDriverTipUseCustom && driverTipsOptions.map((option: any, i: number) => (
67
70
  <TouchableOpacity
68
71
  key={i}
69
72
  onPress={() => handlerChangeOption(option)}
@@ -79,7 +82,7 @@ const DriverTipsUI = (props: any) => {
79
82
  </TouchableOpacity>
80
83
  ))}
81
84
  </DTWrapperTips>
82
- {!driverTipsOptions.includes(driverTip) && driverTip > 0 && (
85
+ {(!isDriverTipUseCustom && !driverTipsOptions.includes(driverTip) && driverTip > 0) && (
83
86
  <OText
84
87
  color={theme.colors.error}
85
88
  size={16}
@@ -90,9 +93,6 @@ const DriverTipsUI = (props: any) => {
90
93
  )}
91
94
  {isDriverTipUseCustom && (
92
95
  <DTForm>
93
- <DTLabel>
94
- {t('CUSTOM_DRIVER_TIP_MESSAGE', '100% of these tips go directly to your driver')}
95
- </DTLabel>
96
96
  <DTWrapperInput>
97
97
  <OInput
98
98
  placeholder={placeholderCurrency}
@@ -1,9 +1,11 @@
1
- import React from 'react'
1
+ import React, { useState } from 'react'
2
+ import { RefreshControl } from 'react-native'
2
3
  import { HelpParams } from '../../types'
3
4
  import { useLanguage } from 'ordering-components/native'
4
5
  import NavBar from '../NavBar'
5
6
  import { OText } from '../shared'
6
7
  import { LastOrders } from '../LastOrders'
8
+ import { Container } from '../../layouts/Container'
7
9
 
8
10
  import {
9
11
  HelpSubItem,
@@ -15,13 +17,28 @@ export const Help = (props: HelpParams) => {
15
17
  navigation
16
18
  } = props
17
19
  const [, t] = useLanguage()
20
+ const [refreshing] = useState(false);
21
+ const [refresh, setRefresh] = useState(false)
18
22
 
19
23
  const goToBack = () => navigation?.canGoBack() && navigation.goBack()
20
24
  const onRedirect = (route: string, params?: any) => {
21
25
  navigation.navigate(route, params)
22
26
  }
27
+
28
+ const handleOnRefresh = () => {
29
+ setRefresh(true)
30
+ }
31
+
23
32
  return (
24
- <>
33
+ <Container
34
+ noPadding
35
+ refreshControl={
36
+ <RefreshControl
37
+ refreshing={refreshing}
38
+ onRefresh={() => handleOnRefresh()}
39
+ />
40
+ }
41
+ >
25
42
  <NavBar
26
43
  title={t('HELP', 'Help')}
27
44
  titleAlign={'center'}
@@ -48,8 +65,8 @@ export const Help = (props: HelpParams) => {
48
65
 
49
66
  <LastOrdersContainer>
50
67
  <OText size={18} weight={600}>{t('LAST_ORDERS', 'Last Orders')}</OText>
51
- <LastOrders {...props} onRedirect={onRedirect} />
68
+ <LastOrders {...props} onRedirect={onRedirect} refresh={refresh} setRefresh={setRefresh} />
52
69
  </LastOrdersContainer>
53
- </>
70
+ </Container>
54
71
  )
55
72
  }
@@ -18,11 +18,15 @@ import {
18
18
  OrderContainer,
19
19
  OrderInfo
20
20
  } from './styles'
21
+ import { useEffect } from 'react'
21
22
 
22
23
  const LastOrdersUI = (props: LastOrdersParams) => {
23
24
  const {
24
25
  orderList,
25
- onRedirect
26
+ onRedirect,
27
+ loadOrders,
28
+ refresh,
29
+ setRefresh
26
30
  } = props
27
31
  const { loading, error, orders } = orderList
28
32
 
@@ -46,6 +50,13 @@ const LastOrdersUI = (props: LastOrdersParams) => {
46
50
  onRedirect && onRedirect('OrderDetails', { orderId: uuid })
47
51
  }
48
52
 
53
+ useEffect(() => {
54
+ if(refresh){
55
+ loadOrders(false, false, false, true)
56
+ setRefresh && setRefresh(false)
57
+ }
58
+ }, [refresh])
59
+
49
60
  return (
50
61
  <>
51
62
  {loading ? (
@@ -66,7 +66,6 @@ export const Otp = (props: otpParams) => {
66
66
  <OTPInputView
67
67
  style={{ width: '100%', height: 150 }}
68
68
  pinCount={6}
69
- autoFocusOnLoad
70
69
  codeInputFieldStyle={loginStyle.underlineStyleBase}
71
70
  codeInputHighlightStyle={loginStyle.underlineStyleHighLighted}
72
71
  onCodeFilled={(code: string) => handleLoginOtp(code)}
@@ -93,6 +93,8 @@ const LoginFormUI = (props: LoginParams) => {
93
93
  const [recaptchaConfig, setRecaptchaConfig] = useState<any>({})
94
94
  const [recaptchaVerified, setRecaptchaVerified] = useState(false)
95
95
  const [alertState, setAlertState] = useState({ open: false, title: '', content: [] })
96
+ const [tabLayouts, setTabLayouts] = useState<any>({})
97
+ const tabsRef = useRef<any>(null)
96
98
 
97
99
  const theme = useTheme();
98
100
  const isOtpEmail = loginTab === 'otp' && otpType === 'email'
@@ -146,9 +148,10 @@ const LoginFormUI = (props: LoginParams) => {
146
148
  const passwordRef = useRef<any>({});
147
149
  const recaptchaRef = useRef<any>({});
148
150
 
149
- const handleChangeTab = (val: string) => {
151
+ const handleChangeTab = (val: string, otpType?: string) => {
150
152
  props.handleChangeTab(val);
151
153
  setPasswordSee(false);
154
+ handleCategoryScroll(otpType ? `${val}_${otpType}` : val)
152
155
  };
153
156
 
154
157
  const onSubmit = (values?: any) => {
@@ -231,7 +234,7 @@ const LoginFormUI = (props: LoginParams) => {
231
234
  }
232
235
 
233
236
  const handleChangeOtpType = (type: string) => {
234
- handleChangeTab('otp')
237
+ handleChangeTab('otp', type)
235
238
  setOtpType(type)
236
239
  }
237
240
 
@@ -248,6 +251,20 @@ const LoginFormUI = (props: LoginParams) => {
248
251
  })
249
252
  }
250
253
 
254
+ const handleCategoryScroll = (opc : string) => {
255
+ tabsRef.current.scrollTo({
256
+ x: tabLayouts?.[opc]?.x - 40,
257
+ animated: true
258
+ })
259
+ }
260
+
261
+ const handleOnLayout = (event: any, opc: string) => {
262
+ const _tabLayouts = { ...tabLayouts }
263
+ const categoryKey = opc
264
+ _tabLayouts[categoryKey] = event.nativeEvent.layout
265
+ setTabLayouts(_tabLayouts)
266
+ }
267
+
251
268
  useEffect(() => {
252
269
  if (configs && Object.keys(configs).length > 0 && enableReCaptcha) {
253
270
  setRecaptchaConfig({
@@ -331,9 +348,16 @@ const LoginFormUI = (props: LoginParams) => {
331
348
  <FormSide>
332
349
  {((useLoginByEmail && useLoginByCellphone) || useLoginOtp) && (
333
350
  <LoginWith>
334
- <OTabs horizontal>
351
+ <OTabs
352
+ horizontal
353
+ showsHorizontalScrollIndicator={false}
354
+ ref={tabsRef}
355
+ >
335
356
  {useLoginByEmail && (
336
- <TabBtn onPress={() => handleChangeTab('email')}>
357
+ <TabBtn
358
+ onPress={() => handleChangeTab('email')}
359
+ onLayout={(event: any) => handleOnLayout(event, 'email')}
360
+ >
337
361
  <OTab
338
362
  style={{
339
363
  borderBottomColor:
@@ -355,7 +379,10 @@ const LoginFormUI = (props: LoginParams) => {
355
379
  </TabBtn>
356
380
  )}
357
381
  {useLoginByCellphone && (
358
- <TabBtn onPress={() => handleChangeTab('cellphone')}>
382
+ <TabBtn
383
+ onPress={() => handleChangeTab('cellphone')}
384
+ onLayout={(event: any) => handleOnLayout(event, 'cellphone')}
385
+ >
359
386
  <OTab
360
387
  style={{
361
388
  borderBottomColor:
@@ -377,7 +404,10 @@ const LoginFormUI = (props: LoginParams) => {
377
404
  </TabBtn>
378
405
  )}
379
406
  {useLoginOtpEmail && (
380
- <TabBtn onPress={() => handleChangeOtpType('email')}>
407
+ <TabBtn
408
+ onPress={() => handleChangeOtpType('email')}
409
+ onLayout={(event: any) => handleOnLayout(event, 'otp_email')}
410
+ >
381
411
  <OTab
382
412
  style={{
383
413
  borderBottomColor:
@@ -393,13 +423,16 @@ const LoginFormUI = (props: LoginParams) => {
393
423
  : theme.colors.disabled
394
424
  }
395
425
  weight={isOtpEmail ? 'bold' : 'normal'}>
396
- {t('LOGIN_BY_OTP_EMAIL', 'Login by Otp Email')}
426
+ {t('BY_OTP_EMAIL', 'By Otp Email')}
397
427
  </OText>
398
428
  </OTab>
399
429
  </TabBtn>
400
430
  )}
401
431
  {useLoginOtpCellphone && (
402
- <TabBtn onPress={() => handleChangeOtpType('cellphone')}>
432
+ <TabBtn
433
+ onPress={() => handleChangeOtpType('cellphone')}
434
+ onLayout={(event: any) => handleOnLayout(event, 'otp_cellphone')}
435
+ >
403
436
  <OTab
404
437
  style={{
405
438
  borderBottomColor:
@@ -415,7 +448,7 @@ const LoginFormUI = (props: LoginParams) => {
415
448
  : theme.colors.disabled
416
449
  }
417
450
  weight={isOtpCellphone ? 'bold' : 'normal'}>
418
- {t('LOGIN_BY_OTP_PHONE', 'Login by Otp Phone')}
451
+ {t('BY_OTP_PHONE', 'By Otp Phone')}
419
452
  </OText>
420
453
  </OTab>
421
454
  </TabBtn>
@@ -12,11 +12,9 @@ export const FormSide = styled.View`
12
12
  margin: auto;
13
13
  `;
14
14
 
15
- export const OTabs = styled.View`
15
+ export const OTabs = styled.ScrollView`
16
16
  flex-direction: row;
17
17
  width: 100%;
18
- flex-wrap: wrap;
19
- justify-content: flex-start;
20
18
  margin-bottom: -1px;
21
19
  `;
22
20
 
@@ -42,7 +42,7 @@ import { OSRow } from '../OrderSummary/styles';
42
42
  import AntIcon from 'react-native-vector-icons/AntDesign'
43
43
  import { TaxInformation } from '../TaxInformation';
44
44
  import { Placeholder, PlaceholderLine } from 'rn-placeholder';
45
-
45
+ import NavBar from '../NavBar'
46
46
  export const OrderDetailsUI = (props: OrderDetailsParams) => {
47
47
  const {
48
48
  navigation,
@@ -82,7 +82,8 @@ export const OrderDetailsUI = (props: OrderDetailsParams) => {
82
82
  justifyContent: 'flex-start',
83
83
  paddingLeft: 0,
84
84
  height: 30,
85
- width: 40,
85
+ width: 30,
86
+ marginTop: Platform.OS === 'ios' ? 0 : 30
86
87
  },
87
88
  });
88
89
 
@@ -364,7 +365,7 @@ export const OrderDetailsUI = (props: OrderDetailsParams) => {
364
365
  }
365
366
 
366
367
  const RenderGoogleMap = () => {
367
- const driverLocationString = typeof order?.driver?.location?.location === 'string' && order?.driver?.location?.location?.split(',').map((l : string) => l.replace(/[^-.0-9]/g, ''))
368
+ const driverLocationString = typeof order?.driver?.location?.location === 'string' && order?.driver?.location?.location?.split(',').map((l: string) => l.replace(/[^-.0-9]/g, ''))
368
369
  const parsedLocations = locations.map(location => typeof location?.location === 'string' ? {
369
370
  ...location,
370
371
  lat: parseFloat(location?.location?.split(',')[0].replace(/[^-.0-9]/g, '')),
@@ -498,27 +499,23 @@ export const OrderDetailsUI = (props: OrderDetailsParams) => {
498
499
  {order && Object.keys(order).length > 0 && (
499
500
  <>
500
501
  <Header>
501
- <OButton
502
- imgLeftSrc={theme.images.general.arrow_left}
503
- imgRightSrc={null}
504
- style={styles.btnBackArrow}
505
- onClick={() => handleArrowBack()}
506
- imgLeftStyle={{ tintColor: theme.colors.disabled }}
502
+ <NavBar
503
+ title={`${t('ORDER', 'Order')} #${order?.id}`}
504
+ titleAlign={'center'}
505
+ onActionLeft={handleArrowBack}
506
+ showCall={false}
507
+ btnStyle={{ paddingLeft: 0 }}
508
+ style={{ flexDirection: 'column', alignItems: 'flex-start', marginTop: Platform.OS === 'ios' ? 0 : 20 }}
509
+ titleWrapStyle={{ paddingHorizontal: 0 }}
510
+ titleStyle={{ marginRight: 0, marginLeft: 0 }}
511
+ subTitle={<OText size={12} lineHeight={18} color={theme.colors.textNormal}>
512
+ {order?.delivery_datetime_utc
513
+ ? parseDate(order?.delivery_datetime_utc)
514
+ : parseDate(order?.delivery_datetime, { utc: false })}
515
+ </OText>}
507
516
  />
508
517
  <OrderInfo>
509
518
  <OrderData>
510
- <OText
511
- size={20}
512
- lineHeight={30}
513
- weight={'600'}
514
- color={theme.colors.textNormal}>
515
- {t('ORDER', 'Order')} #{order?.id}
516
- </OText>
517
- <OText size={12} lineHeight={18} color={theme.colors.textNormal}>
518
- {order?.delivery_datetime_utc
519
- ? parseDate(order?.delivery_datetime_utc)
520
- : parseDate(order?.delivery_datetime, { utc: false })}
521
- </OText>
522
519
  {
523
520
  (
524
521
  parseInt(order?.status) === 1 ||
@@ -112,6 +112,7 @@ const PaymentOptionWalletUI = (props: any) => {
112
112
  true: theme.colors.primary,
113
113
  false: theme.colors.disabled
114
114
  }}
115
+ onChange={() => handleOnChange(idx, wallet)}
115
116
  tintColor={theme.colors.disabled}
116
117
  onCheckColor={theme.colors.primary}
117
118
  onTintColor={theme.colors.primary}
@@ -175,6 +175,7 @@ export const ProductOptionsUI = (props: any) => {
175
175
  const [optionLayout, setOptionLayout] = useState<any>({})
176
176
  const [headerRefHeight, setHeaderRefHeight] = useState(0)
177
177
  const [summaryRefHeight, setSummaryRefHeight] = useState(0)
178
+ const [isScrollAvailable, setIsScrollAvailable] = useState(null)
178
179
 
179
180
  const isError = (id: number) => {
180
181
  let bgColor = theme.colors.white;
@@ -278,6 +279,52 @@ export const ProductOptionsUI = (props: any) => {
278
279
  setOptionLayout(_optionLayout)
279
280
  }
280
281
 
282
+ const saveErrors =
283
+ orderState.loading ||
284
+ maxProductQuantity === 0 ||
285
+ Object.keys(errors).length > 0;
286
+
287
+ const ExtraOptions = ({ eID, options }: any) => (
288
+ <>
289
+ {options.map(({ id, name, respect_to, suboptions }: any) => (
290
+ <React.Fragment key={`cont_key_${id}`}>
291
+ {respect_to == null && suboptions?.length > 0 && (
292
+ <TouchableOpacity
293
+ key={`eopt_key_${id}`}
294
+ onPress={() => setSelectedOpt(id)}
295
+ style={[
296
+ styles.extraItem,
297
+ {
298
+ borderBottomColor:
299
+ selOpt == id ? theme.colors.textNormal : theme.colors.border,
300
+ },
301
+ ]}>
302
+ <OText
303
+ color={
304
+ selOpt == id ? theme.colors.textNormal : theme.colors.textSecondary
305
+ }
306
+ size={selOpt == id ? 14 : 12}
307
+ weight={selOpt == id ? '600' : 'normal'}>
308
+ {name}
309
+ </OText>
310
+ </TouchableOpacity>
311
+ )}
312
+ </React.Fragment>
313
+ ))}
314
+ </>
315
+ );
316
+
317
+ const handleGoBack = navigation?.canGoBack()
318
+ ? () => navigation.goBack()
319
+ : () => navigation.navigate('Business', { store: props.businessSlug })
320
+
321
+ useEffect(() => {
322
+ if (isScrollAvailable) {
323
+ setIsScrollAvailable(null)
324
+ scrollDown(isScrollAvailable)
325
+ }
326
+ }, [errors])
327
+
281
328
  useEffect(() => {
282
329
  const imageList: any = []
283
330
  const videoList: any = []
@@ -319,45 +366,6 @@ export const ProductOptionsUI = (props: any) => {
319
366
  }
320
367
  }, [product])
321
368
 
322
- const saveErrors =
323
- orderState.loading ||
324
- maxProductQuantity === 0 ||
325
- Object.keys(errors).length > 0;
326
-
327
- const ExtraOptions = ({ eID, options }: any) => (
328
- <>
329
- {options.map(({ id, name, respect_to, suboptions }: any) => (
330
- <React.Fragment key={`cont_key_${id}`}>
331
- {respect_to == null && suboptions?.length > 0 && (
332
- <TouchableOpacity
333
- key={`eopt_key_${id}`}
334
- onPress={() => setSelectedOpt(id)}
335
- style={[
336
- styles.extraItem,
337
- {
338
- borderBottomColor:
339
- selOpt == id ? theme.colors.textNormal : theme.colors.border,
340
- },
341
- ]}>
342
- <OText
343
- color={
344
- selOpt == id ? theme.colors.textNormal : theme.colors.textSecondary
345
- }
346
- size={selOpt == id ? 14 : 12}
347
- weight={selOpt == id ? '600' : 'normal'}>
348
- {name}
349
- </OText>
350
- </TouchableOpacity>
351
- )}
352
- </React.Fragment>
353
- ))}
354
- </>
355
- );
356
-
357
- const handleGoBack = navigation?.canGoBack()
358
- ? () => navigation.goBack()
359
- : () => navigation.navigate('Business', { store: props.businessSlug })
360
-
361
369
  return (
362
370
  <SafeAreaView style={{ flex: 1 }}>
363
371
  <TopHeader>
@@ -738,7 +746,7 @@ export const ProductOptionsUI = (props: any) => {
738
746
  isSoldOut ||
739
747
  maxProductQuantity <= 0
740
748
  }
741
- scrollDown={scrollDown}
749
+ setIsScrollAvailable={setIsScrollAvailable}
742
750
  error={errors[`id:${option.id}`]}
743
751
  />
744
752
  );
@@ -28,36 +28,40 @@ export const ProductOptionSubOptionUI = (props: any) => {
28
28
  toggleSelect,
29
29
  changePosition,
30
30
  disabled,
31
- error,
32
- scrollDown
31
+ setIsScrollAvailable
33
32
  } = props
34
33
 
34
+ const disableIncrement = option?.limit_suboptions_by_max ? balance === option?.max : state.quantity === suboption?.max || (!state.selected && balance === option?.max)
35
+ const price = option?.with_half_option && suboption?.half_price && state.position !== 'whole' ? suboption?.half_price : suboption?.price
36
+
35
37
  const theme = useTheme();
36
-
37
38
  const [, t] = useLanguage()
38
39
  const [{ parsePrice }] = useUtils()
39
40
  const [showMessage, setShowMessage] = useState(false)
41
+ const [isDirty, setIsDirty] = useState(false)
40
42
 
41
43
  const handleSuboptionClick = () => {
42
44
  toggleSelect()
43
-
44
- if (balance === option?.max - 1 && !state.selected) {
45
- scrollDown(option?.id)
46
- }
45
+ setIsDirty(true)
47
46
 
48
47
  if (balance === option?.max && option?.suboptions?.length > balance && !(option?.min === 1 && option?.max === 1)) {
49
48
  setShowMessage(true)
50
49
  }
51
50
  }
52
51
 
52
+ useEffect(() => {
53
+ if (balance === option?.max && state?.selected && isDirty) {
54
+ setIsDirty(false)
55
+ setIsScrollAvailable(option?.id)
56
+ }
57
+ }, [state?.selected])
58
+
53
59
  useEffect(() => {
54
60
  if (!(balance === option?.max && option?.suboptions?.length > balance && !(option?.min === 1 && option?.max === 1))) {
55
61
  setShowMessage(false)
56
62
  }
57
63
  }, [balance])
58
64
 
59
- const disableIncrement = option?.limit_suboptions_by_max ? balance === option?.max : state.quantity === suboption?.max || (!state.selected && balance === option?.max)
60
- const price = option?.with_half_option && suboption?.half_price && state.position !== 'whole' ? suboption?.half_price : suboption?.price
61
65
  return (
62
66
  <View>
63
67
  <Container onPress={() => handleSuboptionClick()}>
@@ -17,7 +17,7 @@ import { useTheme } from 'styled-components/native';
17
17
  import { OButton, OIcon, OModal, OText } from '../shared'
18
18
  import { Placeholder, PlaceholderLine } from 'rn-placeholder'
19
19
  import { NotFoundSource } from '../NotFoundSource'
20
- import { View, StyleSheet, ScrollView, Platform } from 'react-native'
20
+ import { View, StyleSheet, ScrollView, Platform, RefreshControl } from 'react-native'
21
21
  import FastImage from 'react-native-fast-image'
22
22
  import { PromotionParams } from '../../types'
23
23
  import { Container } from '../../layouts/Container'
@@ -28,6 +28,7 @@ const PromotionsUI = (props: PromotionParams) => {
28
28
  offersState,
29
29
  handleSearchValue,
30
30
  searchValue,
31
+ loadOffers,
31
32
  offerSelected,
32
33
  setOfferSelected
33
34
  } = props
@@ -68,6 +69,7 @@ const PromotionsUI = (props: PromotionParams) => {
68
69
  const [, t] = useLanguage()
69
70
  const [{ parseDate, parsePrice, optimizeImage }] = useUtils()
70
71
  const [openModal, setOpenModal] = useState(false)
72
+ const [refreshing] = useState(false);
71
73
 
72
74
  const handleClickOffer = (offer: any) => {
73
75
  setOpenModal(true)
@@ -79,6 +81,12 @@ const PromotionsUI = (props: PromotionParams) => {
79
81
  navigation.navigate('Business', { store: store.slug })
80
82
  }
81
83
 
84
+ const handleOnRefresh = () => {
85
+ if (!offersState.loading) {
86
+ loadOffers();
87
+ }
88
+ }
89
+
82
90
  const filteredOffers = offersState?.offers?.filter((offer: any) => offer.name.toLowerCase().includes(searchValue.toLowerCase()))
83
91
  const targetString = offerSelected?.target === 1
84
92
  ? t('SUBTOTAL', 'Subtotal')
@@ -87,7 +95,15 @@ const PromotionsUI = (props: PromotionParams) => {
87
95
  : t('SERVICE_FEE', 'Service fee')
88
96
 
89
97
  return (
90
- <Container noPadding>
98
+ <Container
99
+ noPadding
100
+ refreshControl={
101
+ <RefreshControl
102
+ refreshing={refreshing}
103
+ onRefresh={() => handleOnRefresh()}
104
+ />
105
+ }
106
+ >
91
107
  <NavBar
92
108
  title={t('PROMOTIONS', 'Promotions')}
93
109
  titleAlign={'center'}
@@ -153,6 +153,7 @@ export interface BusinessesListingParams {
153
153
  defaultBusinessType?: any;
154
154
  franchiseId?: any;
155
155
  businessId?: any;
156
+ isGuestUser?: any;
156
157
  }
157
158
  export interface HighestRatedBusinessesParams {
158
159
  businessesList: { businesses: Array<any>, loading: boolean, error: null | string };
@@ -576,7 +577,8 @@ export interface BusinessSearchParams {
576
577
  filters: any,
577
578
  businessTypes: Array<number>,
578
579
  setFilters: (filters: any) => void,
579
- lazySearch?: boolean
580
+ lazySearch?: boolean,
581
+ brandList?: any;
580
582
  }
581
583
 
582
584
  export interface NoNetworkParams {