ordering-ui-react-native 0.14.89 → 0.14.91-release

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 (135) hide show
  1. package/package.json +6 -3
  2. package/src/DeliveryApp.tsx +34 -2
  3. package/src/assets/images/no-network.png +0 -0
  4. package/src/components/BusinessTypeFilter/index.tsx +9 -2
  5. package/src/components/BusinessTypeFilter/styles.tsx +1 -1
  6. package/src/components/BusinessesListing/index.tsx +1 -1
  7. package/src/components/Cart/index.tsx +1 -1
  8. package/src/components/Checkout/index.tsx +0 -1
  9. package/src/components/Home/index.tsx +3 -5
  10. package/src/components/LanguageSelector/index.tsx +65 -97
  11. package/src/components/LanguageSelector/styles.tsx +4 -17
  12. package/src/components/Messages/index.tsx +38 -30
  13. package/src/components/MomentOption/index.tsx +3 -1
  14. package/src/components/OrderDetails/index.tsx +26 -5
  15. package/src/components/PaymentOptions/index.tsx +7 -16
  16. package/src/components/PaymentOptionsWebView/index.tsx +123 -124
  17. package/src/components/ProductForm/index.tsx +1 -1
  18. package/src/components/ProductForm/styles.tsx +1 -0
  19. package/src/components/StripeElementsForm/index.tsx +27 -48
  20. package/src/components/UserProfileForm/index.tsx +35 -1
  21. package/src/components/VerifyPhone/styles.tsx +1 -2
  22. package/src/config.json +0 -2
  23. package/src/pages/Checkout.tsx +1 -1
  24. package/src/providers/AlertProvider.tsx +4 -1
  25. package/src/theme.json +2 -1
  26. package/src/types/index.tsx +2 -9
  27. package/src/utils/index.tsx +2 -1
  28. package/themes/business/index.tsx +4 -0
  29. package/themes/business/src/components/Chat/index.tsx +32 -31
  30. package/themes/business/src/components/NetworkError/index.tsx +61 -0
  31. package/themes/business/src/components/NetworkError/styles.tsx +11 -0
  32. package/themes/business/src/components/OrderDetails/Business.tsx +1 -0
  33. package/themes/business/src/components/OrderDetails/OrderHeaderComponent.tsx +85 -17
  34. package/themes/business/src/components/OrdersListManager/index.tsx +871 -0
  35. package/themes/business/src/components/OrdersListManager/styles.tsx +123 -0
  36. package/themes/business/src/components/OrdersListManager/utils.tsx +216 -0
  37. package/themes/business/src/components/OrdersOption/index.tsx +18 -68
  38. package/themes/business/src/components/OrdersOption/styles.tsx +2 -5
  39. package/themes/business/src/components/PreviousOrders/index.tsx +80 -23
  40. package/themes/business/src/components/ReviewCustomer/index.tsx +5 -1
  41. package/themes/business/src/types/index.tsx +4 -0
  42. package/themes/doordash/src/components/BusinessesListing/index.tsx +1 -1
  43. package/themes/doordash/src/components/LoginForm/index.tsx +1 -2
  44. package/themes/instacart/src/components/BusinessesListing/index.tsx +1 -1
  45. package/themes/kiosk/index.tsx +2 -0
  46. package/themes/kiosk/src/components/BusinessController/index.tsx +27 -6
  47. package/themes/kiosk/src/components/BusinessController/styles.tsx +1 -1
  48. package/themes/kiosk/src/components/BusinessProductsListing/index.tsx +51 -24
  49. package/themes/kiosk/src/components/Cart/index.tsx +1 -1
  50. package/themes/kiosk/src/components/CartBottomSheet/index.tsx +1 -1
  51. package/themes/kiosk/src/components/CartBottomSheet/styles.tsx +1 -1
  52. package/themes/kiosk/src/components/CartContent/index.tsx +13 -3
  53. package/themes/kiosk/src/components/CartItem/index.tsx +20 -8
  54. package/themes/kiosk/src/components/CategoriesMenu/index.tsx +7 -5
  55. package/themes/kiosk/src/components/CustomerName/index.tsx +89 -88
  56. package/themes/kiosk/src/components/Intro/index.tsx +13 -13
  57. package/themes/kiosk/src/components/LanguageSelector/index.tsx +12 -8
  58. package/themes/kiosk/src/components/NavBar/index.tsx +14 -14
  59. package/themes/kiosk/src/components/NetworkError/index.tsx +60 -0
  60. package/themes/kiosk/src/components/NetworkError/styles.tsx +11 -0
  61. package/themes/kiosk/src/components/OptionCard/index.tsx +1 -1
  62. package/themes/kiosk/src/components/OrderDetails/index.tsx +2 -2
  63. package/themes/kiosk/src/components/OrderSummary/index.tsx +1 -1
  64. package/themes/kiosk/src/components/OrderTypeCardSelector/index.tsx +10 -12
  65. package/themes/kiosk/src/components/ProductForm/index.tsx +172 -124
  66. package/themes/kiosk/src/components/ProductForm/styles.tsx +1 -1
  67. package/themes/kiosk/src/components/ProductOption/index.tsx +1 -0
  68. package/themes/kiosk/src/components/ProductOption/styles.tsx +1 -0
  69. package/themes/kiosk/src/components/UpsellingProducts/index.tsx +48 -34
  70. package/themes/kiosk/src/components/shared/OButton.tsx +5 -18
  71. package/themes/kiosk/src/components/shared/OCard.tsx +112 -78
  72. package/themes/kiosk/src/types/index.d.ts +4 -0
  73. package/themes/original/index.tsx +36 -6
  74. package/themes/original/src/components/AddressForm/index.tsx +15 -10
  75. package/themes/original/src/components/AddressList/index.tsx +27 -1
  76. package/themes/original/src/components/AnalyticsSegment/index.tsx +127 -0
  77. package/themes/original/src/components/BusinessBasicInformation/index.tsx +11 -7
  78. package/themes/original/src/components/BusinessController/index.tsx +5 -4
  79. package/themes/original/src/components/BusinessMenuList/index.tsx +4 -2
  80. package/themes/original/src/components/BusinessPreorder/index.tsx +142 -121
  81. package/themes/original/src/components/BusinessProductsCategories/index.tsx +1 -1
  82. package/themes/original/src/components/BusinessProductsList/index.tsx +50 -6
  83. package/themes/original/src/components/BusinessProductsListing/index.tsx +13 -10
  84. package/themes/original/src/components/BusinessReviews/index.tsx +4 -3
  85. package/themes/original/src/components/BusinessesListing/index.tsx +23 -22
  86. package/themes/original/src/components/Cart/index.tsx +43 -10
  87. package/themes/original/src/components/CartContent/index.tsx +2 -2
  88. package/themes/original/src/components/Checkout/index.tsx +54 -30
  89. package/themes/original/src/components/Checkout/styles.tsx +7 -0
  90. package/themes/original/src/components/CouponControl/index.tsx +1 -0
  91. package/themes/original/src/components/DriverTips/index.tsx +1 -1
  92. package/themes/original/src/components/ForgotPasswordForm/index.tsx +8 -12
  93. package/themes/original/src/components/HighestRatedBusinesses/index.tsx +9 -2
  94. package/themes/original/src/components/LoginForm/index.tsx +83 -68
  95. package/themes/original/src/components/Messages/index.tsx +24 -21
  96. package/themes/original/src/components/Messages/styles.tsx +1 -3
  97. package/themes/original/src/components/MomentOption/index.tsx +127 -152
  98. package/themes/original/src/components/MomentOption/styles.tsx +42 -18
  99. package/themes/original/src/components/NetworkError/index.tsx +61 -0
  100. package/themes/original/src/components/NetworkError/styles.tsx +11 -0
  101. package/themes/original/src/components/OrderDetails/index.tsx +103 -124
  102. package/themes/original/src/components/OrderDetails/styles.tsx +3 -1
  103. package/themes/original/src/components/OrderProgress/index.tsx +2 -3
  104. package/themes/original/src/components/OrderSummary/index.tsx +35 -2
  105. package/themes/original/src/components/OrdersOption/index.tsx +16 -40
  106. package/themes/original/src/components/OrdersOption/styles.tsx +0 -5
  107. package/themes/original/src/components/PaymentOptionWallet/index.tsx +1 -1
  108. package/themes/original/src/components/PaymentOptions/index.tsx +21 -24
  109. package/themes/original/src/components/PhoneInputNumber/index.tsx +15 -8
  110. package/themes/original/src/components/PlaceSpot/index.tsx +114 -0
  111. package/themes/original/src/components/PlaceSpot/styles.tsx +11 -0
  112. package/themes/original/src/components/PreviousOrders/index.tsx +4 -0
  113. package/themes/original/src/components/ProductForm/index.tsx +154 -105
  114. package/themes/original/src/components/ProductForm/styles.tsx +5 -3
  115. package/themes/original/src/components/ProductOptionSubOption/index.tsx +6 -1
  116. package/themes/original/src/components/ReviewDriver/index.tsx +2 -1
  117. package/themes/original/src/components/ReviewOrder/index.tsx +2 -17
  118. package/themes/original/src/components/SignupForm/index.tsx +173 -154
  119. package/themes/original/src/components/SingleProductCard/index.tsx +6 -13
  120. package/themes/original/src/components/SingleProductCard/styles.tsx +1 -1
  121. package/themes/original/src/components/SingleProductReview/index.tsx +4 -0
  122. package/themes/original/src/components/StripeElementsForm/index.tsx +55 -72
  123. package/themes/original/src/components/UpsellingProducts/index.tsx +6 -6
  124. package/themes/original/src/components/UserDetails/index.tsx +4 -95
  125. package/themes/original/src/components/UserFormDetails/index.tsx +2 -14
  126. package/themes/original/src/components/UserProfile/index.tsx +16 -9
  127. package/themes/original/src/components/UserProfileForm/index.tsx +16 -8
  128. package/themes/original/src/components/VerifyPhone/index.tsx +10 -7
  129. package/themes/original/src/components/VerifyPhone/styles.tsx +2 -1
  130. package/themes/original/src/components/shared/HeaderTitle.tsx +20 -0
  131. package/themes/original/src/components/shared/index.tsx +2 -0
  132. package/themes/original/src/types/index.tsx +18 -8
  133. package/themes/single-business/src/components/OrderTypeSelector/index.tsx +5 -5
  134. package/themes/uber-eats/src/components/BusinessesListing/index.tsx +1 -1
  135. package/src/components/StripeMethodForm/index.tsx +0 -163
@@ -9,6 +9,8 @@ import {
9
9
  import {
10
10
  PaymentOptions as PaymentOptionsController,
11
11
  useLanguage,
12
+ ToastType,
13
+ useToast,
12
14
  } from 'ordering-components/native';
13
15
  import { useTheme } from 'styled-components/native';
14
16
  import { PaymentOptionCash } from '../PaymentOptionCash';
@@ -30,8 +32,6 @@ import {
30
32
  import { getIconCard, flatArray } from '../../utils';
31
33
 
32
34
  const stripeOptions: any = ['stripe_direct', 'stripe', 'stripe_connect']
33
- const methodsPay = ['google_pay', 'apple_pay']
34
- const stripeDirectMethods = ['stripe_direct', ...methodsPay]
35
35
  // const stripeRedirectOptions = [
36
36
  // { name: 'Bancontact', value: 'bancontact' },
37
37
  // { name: 'Alipay', value: 'alipay' },
@@ -54,12 +54,11 @@ const PaymentOptionsUI = (props: any) => {
54
54
  handlePaymethodClick,
55
55
  handlePaymethodDataChange,
56
56
  isOpenMethod,
57
- handlePaymentMethodClickCustom,
58
- handlePlaceOrder
57
+ handlePaymentMethodClickCustom
59
58
  } = props
60
59
 
61
60
  const theme = useTheme();
62
-
61
+ const [, { showToast }] = useToast();
63
62
 
64
63
  const getPayIcon = (method: string) => {
65
64
  switch (method) {
@@ -95,11 +94,18 @@ const PaymentOptionsUI = (props: any) => {
95
94
  // ]
96
95
 
97
96
  const handlePaymentMethodClick = (paymethod: any) => {
98
- const isPopupMethod = ['stripe', 'stripe_direct', 'stripe_connect', 'stripe_redirect', 'paypal'].includes(paymethod?.gateway)
99
- if (webViewPaymentGateway.includes(paymethod?.gateway)) {
100
- handlePaymentMethodClickCustom(paymethod)
101
- }
102
- handlePaymethodClick(paymethod, isPopupMethod)
97
+ if (cart?.balance > 0) {
98
+ const isPopupMethod = ['stripe', 'stripe_direct', 'stripe_connect', 'stripe_redirect', 'paypal'].includes(paymethod?.gateway)
99
+ if (webViewPaymentGateway.includes(paymethod?.gateway)) {
100
+ handlePaymentMethodClickCustom(paymethod)
101
+ }
102
+ handlePaymethodClick(paymethod, isPopupMethod)
103
+ return
104
+ }
105
+ showToast(
106
+ ToastType.Error,
107
+ t('CART_BALANCE_ZERO', 'Sorry, the amount to pay is equal to zero and it is not necessary to select a payment method'))
108
+ ;
103
109
  }
104
110
 
105
111
  useEffect(() => {
@@ -120,12 +126,6 @@ const PaymentOptionsUI = (props: any) => {
120
126
  }
121
127
  }, [props.paySelected])
122
128
 
123
- useEffect(() => {
124
- if (methodsPay.includes(paymethodSelected?.gateway) && paymethodData?.id && paymethodSelected?.data?.card) {
125
- handlePlaceOrder()
126
- }
127
- }, [paymethodData, paymethodSelected])
128
-
129
129
  const renderPaymethods = ({ item }: any) => {
130
130
  return (
131
131
  <TouchableOpacity
@@ -154,7 +154,7 @@ const PaymentOptionsUI = (props: any) => {
154
154
  )
155
155
  }
156
156
 
157
- const excludeIds: any = [32]; //exclude paypal & connect & redirect
157
+ const excludeIds: any = [32, 66]; //exclude paypal & connect & redirect
158
158
 
159
159
  return (
160
160
  <PMContainer>
@@ -287,11 +287,11 @@ const PaymentOptionsUI = (props: any) => {
287
287
  </KeyboardAvoidingView>
288
288
  </OModal>
289
289
 
290
- {/* Stripe direct, Google pay, Apple pay */}
290
+ {/* Stripe direct */}
291
291
  <OModal
292
292
  entireModal
293
293
  title={t('ADD_CREDIT_OR_DEBIT_CARD', 'Add credit or debit card')}
294
- open={stripeDirectMethods?.includes(isOpenMethod?.paymethod?.gateway) && !paymethodData.id}
294
+ open={isOpenMethod?.paymethod?.gateway === 'stripe_direct' && !paymethodData?.id}
295
295
  onClose={() => handlePaymethodClick(null)}
296
296
  >
297
297
  <KeyboardAvoidingView
@@ -300,13 +300,10 @@ const PaymentOptionsUI = (props: any) => {
300
300
  enabled={Platform.OS === 'ios' ? true : false}
301
301
  >
302
302
  <StripeElementsForm
303
- cart={cart}
304
- paymethod={isOpenMethod?.paymethod?.gateway}
305
- methodsPay={methodsPay}
306
303
  businessId={props.businessId}
307
- publicKey={isOpenMethod?.paymethod?.credentials?.publishable || isOpenMethod?.paymethod?.credentials?.publishable_key}
304
+ publicKey={isOpenMethod?.paymethod?.credentials?.publishable}
308
305
  handleSource={handlePaymethodDataChange}
309
- onCancel={() => handlePaymethodClick(null)}
306
+ onCancel={() => handlePaymethodClick(false)}
310
307
  />
311
308
  </KeyboardAvoidingView>
312
309
  </OModal>
@@ -22,18 +22,23 @@ export const PhoneInputNumber = (props: PhoneInputParams) => {
22
22
  textStyle,
23
23
  flagStyle,
24
24
  noDropIcon,
25
- isDisabled
25
+ isDisabled,
26
+ isStartValidation
26
27
  } = props
27
28
 
28
29
  const theme = useTheme();
29
30
 
31
+ const [, t] = useLanguage()
32
+ const [{ configs }] = useConfig()
33
+ const phoneInput = useRef<PhoneInput>(null);
34
+ const [userphoneNumber, setUserphoneNumber] = useState('');
30
35
 
31
36
  const style = StyleSheet.create({
32
37
  input: {
33
38
  backgroundColor: theme.colors.white,
34
39
  borderRadius: 7.6,
35
40
  borderWidth: 1,
36
- borderColor: theme.colors.border,
41
+ borderColor: (isStartValidation && userphoneNumber === '') ? theme.colors.danger5 : theme.colors.border,
37
42
  paddingBottom: 0,
38
43
  paddingTop: 0,
39
44
  flexGrow: 1,
@@ -48,12 +53,6 @@ export const PhoneInputNumber = (props: PhoneInputParams) => {
48
53
  }
49
54
  })
50
55
 
51
-
52
- const [, t] = useLanguage()
53
- const [{ configs }] = useConfig()
54
- const phoneInput = useRef<PhoneInput>(null);
55
- const [userphoneNumber, setUserphoneNumber] = useState('');
56
-
57
56
  const handleChangeNumber = (number: any) => {
58
57
  setUserphoneNumber(number)
59
58
  }
@@ -97,6 +96,14 @@ export const PhoneInputNumber = (props: PhoneInputParams) => {
97
96
 
98
97
  return (
99
98
  <Wrapper onPress={() => forwardRef?.current?.focus?.()}>
99
+ {(isStartValidation && userphoneNumber === '') && (
100
+ <OText
101
+ size={14}
102
+ color={theme.colors.danger5}
103
+ >
104
+ {t('VALIDATION_ERROR_MOBILE_PHONE_REQUIRED', 'The field Mobile phone is required').replace('_attribute_', t('CELLPHONE', 'Cellphone'))}*
105
+ </OText>
106
+ )}
100
107
  <PhoneInput
101
108
  ref={phoneInput}
102
109
  disabled={isDisabled}
@@ -0,0 +1,114 @@
1
+ import React, { useEffect, useState } from 'react'
2
+ import { View } from 'react-native'
3
+ import { PlaceSpot as PlaceSpotController, useLanguage } from 'ordering-components/native'
4
+ import { PlaceGroupContainer, PlaceSpotContainer } from './styles'
5
+ import { NotFoundSource } from '../NotFoundSource'
6
+ import { OText, ODropDown } from '../shared'
7
+ import { Placeholder, PlaceholderLine } from 'rn-placeholder'
8
+ import { PlaceSpotParams } from '../../types'
9
+
10
+ const PlaceSpotUI = (props: PlaceSpotParams) => {
11
+ const {
12
+ isOpenPlaceSpot,
13
+ cart,
14
+ placesState,
15
+ handleChangePlace,
16
+ getPlacesList,
17
+ setOpenPlaceModal
18
+ } = props
19
+
20
+ const [, t] = useLanguage()
21
+ const [placeGroupSelected, setPlaceGroupSelected] = useState<any>(null)
22
+
23
+ const getPlacesGroups = () => {
24
+ const groups = placesState.placeGroups?.filter((group: any) => group?.enabled && placesState?.places?.find((place: any) => place?.enabled && place?.place_group_id === group?.id))
25
+ return groups.map((group: any) => ({
26
+ value: group,
27
+ content: group?.name,
28
+ showOnSelected: group?.name
29
+ }))
30
+ }
31
+
32
+ const getPlaces = () => {
33
+ const places = placeGroupSelected && placesState?.places?.filter((place: any) => place?.enabled && place?.place_group_id === placeGroupSelected?.id)
34
+ return places.map((place: any) => ({
35
+ value: place,
36
+ content: place.name,
37
+ showOnSelected: place.name
38
+ }))
39
+ }
40
+
41
+ const handlerChangePlace = (place: any) => {
42
+ setOpenPlaceModal(false)
43
+ handleChangePlace(place)
44
+ }
45
+
46
+
47
+ useEffect(() => {
48
+ if (!placesState?.loading) {
49
+ const placeGroupOnCart = placesState?.placeGroups.find((group: any) => group?.id === cart?.place?.place_group_id)
50
+ setPlaceGroupSelected(placeGroupOnCart)
51
+ }
52
+ }, [placesState])
53
+
54
+ useEffect(() => {
55
+ getPlacesList()
56
+ }, [isOpenPlaceSpot])
57
+
58
+ return (
59
+ <PlaceSpotContainer>
60
+ {(placesState.error || placesState?.placeGroups?.length === 0) && !placesState?.loading && (
61
+ <NotFoundSource
62
+ content={t('NO_PLACES_THIS_BUSINESS', 'There are not places for this business')}
63
+ />
64
+ )}
65
+ {placesState?.loading && (
66
+ <Placeholder>
67
+ <PlaceGroupContainer>
68
+ <PlaceholderLine width={100} height={25} />
69
+ <PlaceholderLine height={30} />
70
+ </PlaceGroupContainer>
71
+ <View>
72
+ <PlaceholderLine width={120} height={25} />
73
+ <PlaceholderLine height={30} />
74
+ </View>
75
+ </Placeholder>
76
+ )}
77
+ {!(placesState.error || placesState?.placeGroups?.length === 0) && !placesState?.loading && (
78
+ <>
79
+ <PlaceGroupContainer>
80
+ <OText size={16} mBottom={10}>{t('PLACE_GROUP', 'Place group')}</OText>
81
+ <ODropDown
82
+ placeholder={t('PLACE_GROUP', 'Place group')}
83
+ options={getPlacesGroups()}
84
+ onSelect={(group: any) => setPlaceGroupSelected(group)}
85
+ defaultValue={placeGroupSelected ?? cart?.place}
86
+ isModal
87
+ />
88
+ </PlaceGroupContainer>
89
+ {placeGroupSelected && (
90
+ <View>
91
+ <OText size={16} mBottom={10}>{t('SELECT_YOUR_SPOT', 'Select your spot')}</OText>
92
+ <ODropDown
93
+ onSelect={(place: any) => handlerChangePlace(place)}
94
+ placeholder={t('SELECT_YOUR_SPOT', 'Select your spot')}
95
+ options={getPlaces()}
96
+ defaultValue={placesState?.places?.find((place : any) => place?.id === cart?.place_id)}
97
+ isModal
98
+ />
99
+ </View>
100
+ )}
101
+ </>
102
+ )}
103
+ </PlaceSpotContainer>
104
+ )
105
+ }
106
+
107
+ export const PlaceSpot = (props: PlaceSpotParams) => {
108
+ const placeSpotProps = {
109
+ ...props,
110
+ UIComponent: PlaceSpotUI
111
+ }
112
+
113
+ return <PlaceSpotController {...placeSpotProps} />
114
+ }
@@ -0,0 +1,11 @@
1
+ import styled from 'styled-components/native'
2
+
3
+ export const PlaceSpotContainer = styled.View`
4
+ min-height: 300px;
5
+ padding: 20px;
6
+ `
7
+
8
+ export const PlaceGroupContainer = styled.View`
9
+ margin-bottom: 40px;
10
+ margin-top: 20px;
11
+ `
@@ -86,6 +86,10 @@ export const PreviousOrders = (props: PreviousOrdersParams) => {
86
86
  id: order?.id,
87
87
  business_id: order?.business_id,
88
88
  logo: order.business?.logo,
89
+ driver: order?.driver,
90
+ products: order?.products,
91
+ review: order?.review,
92
+ user_review: order?.user_review
89
93
  },
90
94
  });
91
95
  };
@@ -37,7 +37,8 @@ import {
37
37
  ExtraOptionWrap,
38
38
  WeightUnitSwitch,
39
39
  WeightUnitItem,
40
- TopActions
40
+ TopActions,
41
+ ProductSummary
41
42
  } from './styles';
42
43
  import { OButton, OIcon, OInput, OText } from '../shared';
43
44
  import { ScrollView } from 'react-native-gesture-handler';
@@ -45,7 +46,6 @@ import { ProductOptionSubOption } from '../ProductOptionSubOption';
45
46
  import { NotFoundSource } from '../NotFoundSource';
46
47
  import { Placeholder, PlaceholderLine, Fade } from 'rn-placeholder';
47
48
  import { useState } from 'react';
48
-
49
49
  const windowHeight = Dimensions.get('window').height;
50
50
  const windowWidth = Dimensions.get('window').width;
51
51
 
@@ -170,8 +170,11 @@ export const ProductOptionsUI = (props: any) => {
170
170
  pieces: true
171
171
  })
172
172
  const [pricePerWeightUnit, setPricePerWeightUnit] = useState<any>(null)
173
-
173
+ const scrollViewRef = useRef<any>(null);
174
174
  const swiperRef: any = useRef(null)
175
+ const [optionLayout, setOptionLayout] = useState<any>({})
176
+ const [headerRefHeight, setHeaderRefHeight] = useState(0)
177
+ const [summaryRefHeight, setSummaryRefHeight] = useState(0)
175
178
 
176
179
  const isError = (id: number) => {
177
180
  let bgColor = theme.colors.white;
@@ -253,6 +256,28 @@ export const ProductOptionsUI = (props: any) => {
253
256
  handleChangeProductCartQuantity(quantity)
254
257
  }
255
258
 
259
+ const scrollDown = (id: any) => {
260
+ const isErrors = Object.values(errors).length > 0
261
+ if (!isErrors) {
262
+ return
263
+ }
264
+ const targetOptionId = Object.getOwnPropertyNames(errors).filter(item => !item.includes(id))[0]
265
+ const targetY = optionLayout[targetOptionId]?.y
266
+ if (targetY) {
267
+ scrollViewRef.current.scrollTo({
268
+ y: targetY + headerRefHeight + summaryRefHeight,
269
+ animated: true
270
+ })
271
+ }
272
+ }
273
+
274
+ const handleOnLayout = (event: any, optionId: any) => {
275
+ const _optionLayout = { ...optionLayout }
276
+ const optionKey = 'id:' + optionId
277
+ _optionLayout[optionKey] = { y: event.nativeEvent.layout?.y }
278
+ setOptionLayout(_optionLayout)
279
+ }
280
+
256
281
  useEffect(() => {
257
282
  const imageList: any = []
258
283
  const videoList: any = []
@@ -264,8 +289,24 @@ export const ProductOptionsUI = (props: any) => {
264
289
  }
265
290
  if (img?.video) {
266
291
  const keys = img?.video.split('/')
267
- const _videoId = keys[keys.length - 1]
268
- videoList.push(_videoId)
292
+ let _videoId = keys[keys.length - 1]
293
+
294
+ if (_videoId.includes('watch')) {
295
+ const __url = _videoId.split('=')[1]
296
+ _videoId = __url
297
+ } else if (_videoId.includes('?')) {
298
+ const __url = _videoId.split('?')[0]
299
+ _videoId = __url
300
+ }
301
+
302
+ if (_videoId.search(/&/i) >= 0) {
303
+ _videoId = _videoId.split('&')[0]
304
+ } else if (_videoId.search(/\?/i) >= 0) {
305
+ _videoId = _videoId.split('?')[0]
306
+ }
307
+ if ((_videoId.length === 11)) {
308
+ videoList.push(_videoId)
309
+ }
269
310
  }
270
311
  }
271
312
  }
@@ -304,9 +345,9 @@ export const ProductOptionsUI = (props: any) => {
304
345
  </OText>
305
346
  </TouchableOpacity>
306
347
  )}
307
- {options.map(({ id, name, respect_to }: any) => (
348
+ {options.map(({ id, name, respect_to, suboptions }: any) => (
308
349
  <React.Fragment key={`cont_key_${id}`}>
309
- {respect_to == null && (
350
+ {respect_to == null && suboptions?.length > 0 && (
310
351
  <TouchableOpacity
311
352
  key={`eopt_key_${id}`}
312
353
  onPress={() => setSelectedOpt(id)}
@@ -343,10 +384,10 @@ export const ProductOptionsUI = (props: any) => {
343
384
  <OIcon src={theme.images.general.arrow_left} width={15} />
344
385
  </TopActions>
345
386
  </TopHeader>
346
- <ScrollView>
387
+ <ScrollView ref={scrollViewRef}>
347
388
  {!error && (
348
389
  <View style={{ paddingBottom: 80 }}>
349
- <WrapHeader>
390
+ <WrapHeader onLayout={(event: any) => setHeaderRefHeight(event.nativeEvent.layout?.height)}>
350
391
  {loading && !product ? (
351
392
  <View style={styles.productHeaderSkeleton}>
352
393
  <Placeholder Animation={Fade}>
@@ -396,7 +437,7 @@ export const ProductOptionsUI = (props: any) => {
396
437
  <FastImage
397
438
  style={{ height: '100%', opacity: isSoldOut ? 0.5 : 1 }}
398
439
  source={{
399
- uri: optimizeImage(img, 'h_258,c_limit'),
440
+ uri: optimizeImage(img, 'h_1024,c_limit'),
400
441
  priority: FastImage.priority.normal,
401
442
  }}
402
443
  />
@@ -470,96 +511,98 @@ export const ProductOptionsUI = (props: any) => {
470
511
  )}
471
512
  </WrapHeader>
472
513
  <WrapContent>
473
- <ProductTitle>
474
- {loading && !product ? (
475
- <Placeholder Animation={Fade}>
476
- <View
477
- style={{
478
- flexDirection: 'row',
479
- justifyContent: 'space-between',
480
- }}>
481
- <PlaceholderLine width={40} height={20} />
482
- <PlaceholderLine width={30} height={20} />
483
- </View>
484
- </Placeholder>
485
- ) : (
486
- <>
487
- <View style={{ flexDirection: 'row' }}>
488
- <OText
489
- size={20}
490
- lineHeight={30}
491
- weight={'600'}
492
- style={{ flex: 1, marginBottom: 10 }}>
493
- {product?.name || productCart.name}
494
- </OText>
495
- {!!product?.calories && (
496
- <OText size={16} style={{ color: '#808080' }}>{product?.calories} cal
514
+ <ProductSummary onLayout={(event: any) => setSummaryRefHeight(event.nativeEvent.layout?.height)}>
515
+ <ProductTitle>
516
+ {loading && !product ? (
517
+ <Placeholder Animation={Fade}>
518
+ <View
519
+ style={{
520
+ flexDirection: 'row',
521
+ justifyContent: 'space-between',
522
+ }}>
523
+ <PlaceholderLine width={40} height={20} />
524
+ <PlaceholderLine width={30} height={20} />
525
+ </View>
526
+ </Placeholder>
527
+ ) : (
528
+ <>
529
+ <View style={{ flexDirection: 'row' }}>
530
+ <OText
531
+ size={20}
532
+ lineHeight={30}
533
+ weight={'600'}
534
+ style={{ flex: 1, marginBottom: 10 }}>
535
+ {product?.name || productCart.name}
497
536
  </OText>
498
- )}
499
- </View>
500
- {((!!product?.sku && product?.sku !== '-1' && product?.sku !== '1') || (!!product?.estimated_person)) && (
501
- <OText size={14} style={{ flex: I18nManager.isRTL ? 1 : 0 }} color={'#909BA9'} mBottom={7}>
502
- {
503
- ((product?.sku && product?.sku !== '-1' && product?.sku !== '1') || (productCart?.sku && productCart?.sku !== '-1' && productCart?.sku !== '1'))
504
- && <>{t('SKU', 'Sku')}{' '}{product?.sku || productCart?.sku}</>
505
- }
506
- {product?.sku && product?.sku !== '-1' && product?.sku !== '1' && product?.estimated_person && (
507
- <>&nbsp;&#183;&nbsp;</>
508
- )}
509
- {product?.estimated_person
510
- && <>{product?.estimated_person}{' '}{t('ESTIMATED_PERSONS', 'persons')}</>
511
- }
512
- </OText>
513
- )}
514
- {isHaveWeight ? (
515
- <OText size={16} lineHeight={24} color={theme.colors.primary}>{parsePrice(pricePerWeightUnit)} / {product?.weight_unit}</OText>
516
- ) : (
517
- <View style={{ flexDirection: 'row', marginBottom: 10 }}>
518
- <OText size={16} style={{ flex: I18nManager.isRTL ? 1 : 0 }} color={theme.colors.primary}>{productCart.price ? parsePrice(productCart.price) : ''}</OText>
519
- {product?.offer_price !== null && product?.in_offer && (
520
- <OText style={{
521
- fontSize: 14,
522
- color: '#808080',
523
- textDecorationLine: 'line-through',
524
- marginLeft: 7,
525
- marginRight: 7
526
- }}>{product?.offer_price ? parsePrice(product?.offer_price) : ''}</OText>
537
+ {!!product?.calories && (
538
+ <OText size={16} style={{ color: '#808080' }}>{product?.calories} cal
539
+ </OText>
527
540
  )}
528
541
  </View>
529
- )}
530
- </>
531
- )}
532
- </ProductTitle>
533
- <ProductDescription>
534
- <OText color={theme.colors.textSecondary} size={12} lineHeight={18}>
535
- {product?.description || productCart?.description}
536
- </OText>
537
- </ProductDescription>
538
- <ScrollView
539
- horizontal
540
- showsHorizontalScrollIndicator={false}
541
- contentContainerStyle={{ paddingBottom: 30 }}
542
- >
543
- {product?.tags?.map((tag: any) => (
544
- <View
545
- key={tag.id}
546
- style={styles.productTagWrapper}
547
- >
548
- {!!tag?.image ? (
549
- <OIcon
550
- url={optimizeImage(tag?.image, 'h_40,c_limit')}
551
- style={styles.productTagImageStyle}
552
- />
553
- ) : (
554
- <OIcon
555
- src={theme.images?.dummies?.product}
556
- style={styles.productTagImageStyle}
557
- />
558
- )}
559
- <OText color={theme.colors.textSecondary} size={12} style={styles.productTagNameStyle}>{tag.name}</OText>
560
- </View>
561
- ))}
562
- </ScrollView>
542
+ {((!!product?.sku && product?.sku !== '-1' && product?.sku !== '1') || (!!product?.estimated_person)) && (
543
+ <OText size={14} style={{ flex: I18nManager.isRTL ? 1 : 0 }} color={'#909BA9'} mBottom={7}>
544
+ {
545
+ ((product?.sku && product?.sku !== '-1' && product?.sku !== '1') || (productCart?.sku && productCart?.sku !== '-1' && productCart?.sku !== '1'))
546
+ && <>{t('SKU', 'Sku')}{' '}{product?.sku || productCart?.sku}</>
547
+ }
548
+ {product?.sku && product?.sku !== '-1' && product?.sku !== '1' && product?.estimated_person && (
549
+ <>&nbsp;&#183;&nbsp;</>
550
+ )}
551
+ {product?.estimated_person
552
+ && <>{product?.estimated_person}{' '}{t('ESTIMATED_PERSONS', 'persons')}</>
553
+ }
554
+ </OText>
555
+ )}
556
+ {isHaveWeight ? (
557
+ <OText size={16} lineHeight={24} color={theme.colors.primary}>{parsePrice(pricePerWeightUnit)} / {product?.weight_unit}</OText>
558
+ ) : (
559
+ <View style={{ flexDirection: 'row', marginBottom: 10 }}>
560
+ <OText size={16} style={{ flex: I18nManager.isRTL ? 1 : 0 }} color={theme.colors.primary}>{productCart.price ? parsePrice(productCart.price) : ''}</OText>
561
+ {product?.offer_price !== null && product?.in_offer && (
562
+ <OText style={{
563
+ fontSize: 14,
564
+ color: '#808080',
565
+ textDecorationLine: 'line-through',
566
+ marginLeft: 7,
567
+ marginRight: 7
568
+ }}>{product?.offer_price ? parsePrice(product?.offer_price) : ''}</OText>
569
+ )}
570
+ </View>
571
+ )}
572
+ </>
573
+ )}
574
+ </ProductTitle>
575
+ <ProductDescription>
576
+ <OText color={theme.colors.textSecondary} size={12} lineHeight={18}>
577
+ {product?.description || productCart?.description}
578
+ </OText>
579
+ </ProductDescription>
580
+ <ScrollView
581
+ horizontal
582
+ showsHorizontalScrollIndicator={false}
583
+ contentContainerStyle={{ paddingBottom: 30 }}
584
+ >
585
+ {product?.tags?.map((tag: any) => (
586
+ <View
587
+ key={tag.id}
588
+ style={styles.productTagWrapper}
589
+ >
590
+ {!!tag?.image ? (
591
+ <OIcon
592
+ url={optimizeImage(tag?.image, 'h_40,c_limit')}
593
+ style={styles.productTagImageStyle}
594
+ />
595
+ ) : (
596
+ <OIcon
597
+ src={theme.images?.dummies?.product}
598
+ style={styles.productTagImageStyle}
599
+ />
600
+ )}
601
+ <OText color={theme.colors.textSecondary} size={12} style={styles.productTagNameStyle}>{tag.name}</OText>
602
+ </View>
603
+ ))}
604
+ </ScrollView>
605
+ </ProductSummary>
563
606
  {loading && !product ? (
564
607
  <>
565
608
  {[...Array(2)].map((item, i) => (
@@ -658,7 +701,7 @@ export const ProductOptionsUI = (props: any) => {
658
701
  return (
659
702
  <React.Fragment key={`popt_${option.id}`}>
660
703
  {showOption(option) && (
661
- <View style={styles.optionContainer}>
704
+ <View style={styles.optionContainer} onLayout={(event: any) => handleOnLayout(event, option?.id)}>
662
705
  <ProductOption
663
706
  option={option}
664
707
  currentState={currentState}
@@ -695,6 +738,8 @@ export const ProductOptionsUI = (props: any) => {
695
738
  isSoldOut ||
696
739
  maxProductQuantity <= 0
697
740
  }
741
+ scrollDown={scrollDown}
742
+ error={errors[`id:${option.id}`]}
698
743
  />
699
744
  );
700
745
  },
@@ -834,9 +879,13 @@ export const ProductOptionsUI = (props: any) => {
834
879
  </ScrollView>
835
880
  {!loading && !error && product && (
836
881
  <ProductActions ios={Platform?.OS === 'ios'}>
837
- <OText size={16} lineHeight={24} weight={'600'}>
838
- {productCart.total ? parsePrice(productCart?.total) : ''}
839
- </OText>
882
+ <View>
883
+ <OText size={16} lineHeight={24} weight={'600'}>
884
+ {productCart.total ? parsePrice(productCart?.total) : ''}
885
+ </OText>
886
+ {product?.minimum_per_order && productCart?.quantity < product?.minimum_per_order && <OText size={12} color={theme.colors?.red}>{t('MOBILE_MINIMUM_TO_ORDER', 'Min. _number_ ').replace('_number_', product?.minimum_per_order)}</OText>}
887
+ {product?.maximum_per_order && productCart?.quantity > product?.maximum_per_order && <OText size={12} color={theme.colors?.red}>{t('MOBILE_MAXIMUM_TO_ORDER', 'Max. _number_'.replace('_number_', product?.maximum_per_order))}</OText>}
888
+ </View>
840
889
  {productCart && !isSoldOut && maxProductQuantity > 0 && (
841
890
  <View style={styles.quantityControl}>
842
891
  <TouchableOpacity
@@ -855,7 +904,7 @@ export const ProductOptionsUI = (props: any) => {
855
904
  {qtyBy?.pieces && (
856
905
  <TextInput
857
906
  keyboardType='numeric'
858
- value={`${productCart?.quantity > 0 ? productCart?.quantity: ''}`}
907
+ value={`${productCart?.quantity > 0 ? productCart?.quantity : ''}`}
859
908
  onChangeText={(val: any) => onChangeProductCartQuantity(parseInt(val))}
860
909
  editable={!orderState.loading}
861
910
  style={{
@@ -949,14 +998,14 @@ export const ProductOptionsUI = (props: any) => {
949
998
  ? t('UPDATE', 'Update')
950
999
  : t('ADD', 'Add')
951
1000
  }`}
952
- isDisabled={isSoldOut || maxProductQuantity <= 0}
1001
+ isDisabled={isSoldOut || maxProductQuantity <= 0 || (product?.minimum_per_order && (productCart?.quantity < product?.minimum_per_order)) || (product?.maximum_per_order && (productCart?.quantity > product?.maximum_per_order))}
953
1002
  textStyle={{
954
1003
  color: saveErrors || isSoldOut || maxProductQuantity <= 0 ? theme.colors.primary : theme.colors.white,
955
1004
  fontSize: orderState.loading || editMode ? 10 : 14
956
1005
  }}
957
1006
  style={{
958
- backgroundColor: saveErrors || isSoldOut || maxProductQuantity <= 0 ? theme.colors.lightGray : theme.colors.primary,
959
- borderColor: saveErrors || isSoldOut || maxProductQuantity <= 0 ? theme.colors.white : theme.colors.primary,
1007
+ backgroundColor: saveErrors || isSoldOut || maxProductQuantity <= 0 || (product?.minimum_per_order && (productCart?.quantity < product?.minimum_per_order)) || (product?.maximum_per_order && (productCart?.quantity > product?.maximum_per_order)) ? theme.colors.lightGray : theme.colors.primary,
1008
+ borderColor: saveErrors || isSoldOut || maxProductQuantity <= 0 || (product?.minimum_per_order && (productCart?.quantity < product?.minimum_per_order)) || (product?.maximum_per_order && (productCart?.quantity > product?.maximum_per_order)) ? theme.colors.white : theme.colors.primary,
960
1009
  opacity: saveErrors || isSoldOut || maxProductQuantity <= 0 ? 0.3 : 1,
961
1010
  borderRadius: 7.6,
962
1011
  height: 44,