ordering-ui-react-native 0.14.68 → 0.14.71

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.14.68",
3
+ "version": "0.14.71",
4
4
  "description": "Reusable components made in react native",
5
5
  "main": "src/index.tsx",
6
6
  "author": "ordering.inc",
@@ -108,6 +108,7 @@
108
108
  "react-native-uuid": "^2.0.1",
109
109
  "react-native-vector-icons": "^7.1.0",
110
110
  "react-native-webview": "^11.6.4",
111
+ "react-native-youtube-iframe": "^2.2.2",
111
112
  "rn-placeholder": "^3.0.3",
112
113
  "styled-components": "^5.1.1",
113
114
  "styled-system": "^5.1.5",
@@ -529,6 +529,7 @@ const CheckoutUI = (props: any) => {
529
529
  paySelected={paymethodSelected}
530
530
  handlePaymentMethodClickCustom={handlePaymentMethodClick}
531
531
  setCardData={setCardData}
532
+ handlePlaceOrder={handlePlaceOrder}
532
533
  />
533
534
  </ChPaymethods>
534
535
  </ChSection>
@@ -61,11 +61,14 @@ const PaymentOptionsUI = (props: any) => {
61
61
  handlePaymethodDataChange,
62
62
  handlePaymentMethodClickCustom,
63
63
  isOpenMethod,
64
- setCardData
64
+ setCardData,
65
+ handlePlaceOrder
65
66
  } = props
66
67
 
67
68
  const theme = useTheme();
68
69
  const [, t] = useLanguage();
70
+ const methodsPay = ['google_pay', 'apple_pay']
71
+ const stripeDirectMethods = ['stripe_direct', ...methodsPay]
69
72
 
70
73
  const [addCardOpen, setAddCardOpen] = useState({ stripe: false, stripeConnect: false });
71
74
  let paymethodSelected = props.paySelected || props.paymethodSelected || isOpenMethod?.paymethod
@@ -119,8 +122,11 @@ const PaymentOptionsUI = (props: any) => {
119
122
  }, [props.paySelected])
120
123
 
121
124
  useEffect(() => {
122
- setCardData(paymethodData)
123
- }, [paymethodData])
125
+ setCardData && setCardData(paymethodData)
126
+ if (methodsPay.includes(paymethodSelected?.gateway) && paymethodData?.id && paymethodSelected?.data?.card) {
127
+ handlePlaceOrder()
128
+ }
129
+ }, [paymethodData, paymethodSelected])
124
130
 
125
131
  const renderPaymethods = ({ item }: any) => {
126
132
  return (
@@ -283,7 +289,7 @@ const PaymentOptionsUI = (props: any) => {
283
289
  <OModal
284
290
  entireModal
285
291
  title={t('ADD_CREDIT_OR_DEBIT_CARD', 'Add credit or debit card')}
286
- open={isOpenMethod?.paymethod?.gateway === 'stripe_direct' && !paymethodData?.id}
292
+ open={stripeDirectMethods?.includes(isOpenMethod?.paymethod?.gateway) && !paymethodData.id}
287
293
  onClose={() => handlePaymethodClick(null)}
288
294
  >
289
295
  <KeyboardAvoidingView
@@ -292,10 +298,13 @@ const PaymentOptionsUI = (props: any) => {
292
298
  enabled={Platform.OS === 'ios' ? true : false}
293
299
  >
294
300
  <StripeElementsForm
301
+ cart={cart}
302
+ paymethod={isOpenMethod?.paymethod?.gateway}
303
+ methodsPay={methodsPay}
295
304
  businessId={props.businessId}
296
- publicKey={isOpenMethod?.paymethod?.credentials?.publishable}
305
+ publicKey={isOpenMethod?.paymethod?.credentials?.publishable || isOpenMethod?.paymethod?.credentials?.publishable_key}
297
306
  handleSource={handlePaymethodDataChange}
298
- onCancel={() => handlePaymethodClick(false)}
307
+ onCancel={() => handlePaymethodClick(null)}
299
308
  />
300
309
  </KeyboardAvoidingView>
301
310
  </OModal>
@@ -335,7 +335,7 @@ export const ProductOptionsUI = (props: any) => {
335
335
  />
336
336
  ) : (
337
337
  <OButton
338
- onClick={navigation.navigate('AddressList')}
338
+ onClick={() => navigation.navigate('AddressList')}
339
339
  />
340
340
  )
341
341
  )}
@@ -7,10 +7,11 @@ import {
7
7
  useConfirmSetupIntent,
8
8
  createPaymentMethod
9
9
  } from '@stripe/stripe-react-native';
10
-
10
+ import configs from '../../config.json'
11
11
  import { ErrorMessage } from './styles';
12
12
 
13
13
  import { StripeElementsForm as StripeFormController } from './naked';
14
+ import { StripeMethodForm } from '../StripeMethodForm';
14
15
  import { OButton, OText } from '../shared';
15
16
  import { useTheme } from 'styled-components/native';
16
17
 
@@ -22,6 +23,10 @@ const StripeElementsFormUI = (props: any) => {
22
23
  businessId,
23
24
  requirements,
24
25
  stripeTokenHandler,
26
+ methodsPay,
27
+ paymethod,
28
+ onCancel,
29
+ cart
25
30
  } = props;
26
31
 
27
32
  const theme = useTheme();
@@ -121,33 +126,49 @@ const StripeElementsFormUI = (props: any) => {
121
126
  <View style={styles.container}>
122
127
  {publicKey ? (
123
128
  <View style={{ flex: 1 }}>
124
- <StripeProvider publishableKey={publicKey}>
125
- <CardField
126
- postalCodeEnabled={true}
127
- cardStyle={{
128
- backgroundColor: '#FFFFFF',
129
- textColor: '#000000',
130
- }}
131
- style={{
132
- width: '100%',
133
- height: 50,
134
- marginVertical: 30,
135
- zIndex: 9999,
136
- }}
137
- onCardChange={(cardDetails: any) => setCard(cardDetails)}
138
- />
129
+ <StripeProvider
130
+ publishableKey={publicKey}
131
+ merchantIdentifier={`merchant.${configs.apple_app_id}`}
132
+ >
133
+ {methodsPay.includes(paymethod) ? (
134
+ <StripeMethodForm
135
+ handleSource={handleSource}
136
+ onCancel={onCancel}
137
+ cart={cart}
138
+ setErrors={setErrors}
139
+ paymethod={paymethod}
140
+ devMode={publicKey?.includes('test')}
141
+ />
142
+ ) : (
143
+ <CardField
144
+ postalCodeEnabled={true}
145
+ cardStyle={{
146
+ backgroundColor: '#FFFFFF',
147
+ textColor: '#000000',
148
+ }}
149
+ style={{
150
+ width: '100%',
151
+ height: 50,
152
+ marginVertical: 30,
153
+ zIndex: 9999,
154
+ }}
155
+ onCardChange={(cardDetails: any) => setCard(cardDetails)}
156
+ />
157
+ )}
139
158
  </StripeProvider>
140
- <OButton
141
- text={t('SAVE_CARD', 'Save card')}
142
- bgColor={isCompleted ? theme.colors.primary : theme.colors.backgroundGray}
143
- borderColor={isCompleted ? theme.colors.primary :theme.colors.backgroundGray}
144
- style={styles.btnAddStyle}
145
- textStyle={{color: 'white'}}
146
- imgRightSrc={null}
147
- onClick={() => handleSaveCard()}
148
- isDisabled={!isCompleted}
149
- isLoading={confirmSetupLoading || values.loadingAdd || createPmLoading}
150
- />
159
+ {!methodsPay?.includes(paymethod) && (
160
+ <OButton
161
+ text={t('SAVE_CARD', 'Save card')}
162
+ bgColor={isCompleted ? theme.colors.primary : theme.colors.backgroundGray}
163
+ borderColor={isCompleted ? theme.colors.primary :theme.colors.backgroundGray}
164
+ style={styles.btnAddStyle}
165
+ textStyle={{color: 'white'}}
166
+ imgRightSrc={null}
167
+ onClick={() => handleSaveCard()}
168
+ isDisabled={!isCompleted}
169
+ isLoading={confirmSetupLoading || values.loadingAdd || createPmLoading}
170
+ />
171
+ )}
151
172
  {!!errors && (
152
173
  <ErrorMessage>
153
174
  <OText
@@ -0,0 +1,163 @@
1
+ import React, { useState, useEffect } from 'react'
2
+ import { useLanguage } from 'ordering-components/native'
3
+ import { GooglePayButton, useGooglePay, ApplePayButton, useApplePay } from '@stripe/stripe-react-native'
4
+ import { OButton } from '../shared';
5
+ import { Platform, View } from 'react-native';
6
+ import { StripeMethodFormParams } from '../../types';
7
+
8
+ export const StripeMethodForm = (props: StripeMethodFormParams) => {
9
+ const {
10
+ cart,
11
+ handleSource,
12
+ onCancel,
13
+ setErrors,
14
+ paymethod,
15
+ devMode
16
+ } = props
17
+ const { initGooglePay, createGooglePayPaymentMethod, loading } = useGooglePay();
18
+ const { presentApplePay, isApplePaySupported } = useApplePay();
19
+ const [initialized, setInitialized] = useState(false);
20
+ const [, t] = useLanguage()
21
+
22
+ useEffect(() => {
23
+ if (paymethod !== 'google_pay') return
24
+ if (Platform.OS === 'ios') {
25
+ setErrors(t('GOOGLE_PAY_NOT_SUPPORTED', 'Google pay not supported'))
26
+ return
27
+ }
28
+ const initialize = async () => {
29
+ try {
30
+ const { error } = await initGooglePay({
31
+ testEnv: devMode,
32
+ merchantName: 'Widget Store',
33
+ countryCode: 'US',
34
+ billingAddressConfig: {
35
+ format: 'FULL',
36
+ isPhoneNumberRequired: true,
37
+ isRequired: false,
38
+ },
39
+ existingPaymentMethodRequired: false,
40
+ isEmailRequired: true,
41
+ });
42
+
43
+ if (error) {
44
+ setErrors(error.code + ' - ' + error.message);
45
+ return;
46
+ }
47
+ setInitialized(true);
48
+ } catch (err: any) {
49
+ setErrors('Catch ' + err?.message)
50
+ }
51
+ }
52
+ initialize();
53
+ }, [initGooglePay]);
54
+
55
+ useEffect(() => {
56
+ if (paymethod !== 'apple_pay') return
57
+ if (Platform.OS === 'android') {
58
+ setErrors(t('APPLE_PAY_NOT_SUPPORTED', 'Apple pay not supported'))
59
+ return
60
+ }
61
+ }, [])
62
+
63
+ const createPaymentMethod = async () => {
64
+
65
+ const { error, paymentMethod } = await createGooglePayPaymentMethod({
66
+ amount: cart?.balance ?? cart?.total,
67
+ currencyCode: 'USD',
68
+ });
69
+
70
+ if (error) {
71
+ setErrors(error.code + ' - ' + error.message);
72
+ return;
73
+ } else if (paymentMethod) {
74
+ handleSource({
75
+ ...paymentMethod?.Card,
76
+ id: paymentMethod.id,
77
+ type: paymentMethod.type,
78
+ source_id: paymentMethod?.id,
79
+ card: {
80
+ brand: paymentMethod.Card.brand,
81
+ last4: paymentMethod.Card.last4
82
+ }
83
+ })
84
+ onCancel()
85
+ }
86
+ setInitialized(false);
87
+ };
88
+
89
+ const pay = async () => {
90
+ if (!isApplePaySupported) {
91
+ setErrors(t('APPLE_PAY_NOT_SUPPORTED', 'Apple pay not supported'))
92
+ return
93
+ }
94
+
95
+ const { error, paymentMethod } = await presentApplePay({
96
+ cartItems: cart?.products?.map((product: any) => ({ label: product?.name, amount: product?.price?.toString?.() })),
97
+ country: 'US',
98
+ currency: 'USD',
99
+ shippingMethods: [
100
+ {
101
+ amount: cart?.balance?.toString() ?? cart?.total?.toString?.(),
102
+ identifier: 'DPS',
103
+ label: 'Courier',
104
+ detail: 'Delivery',
105
+ type: 'final',
106
+ },
107
+ ],
108
+
109
+ requiredShippingAddressFields: ['emailAddress', 'phoneNumber'],
110
+ requiredBillingContactFields: ['phoneNumber', 'name'],
111
+ });
112
+ if (error) {
113
+ setErrors(error.code + ' - ' + error.message);
114
+ } else if (paymentMethod) {
115
+ handleSource({
116
+ ...paymentMethod?.Card,
117
+ id: paymentMethod.id,
118
+ type: paymentMethod.type,
119
+ source_id: paymentMethod?.id,
120
+ card: {
121
+ brand: paymentMethod.Card.brand,
122
+ last4: paymentMethod.Card.last4
123
+ }
124
+ })
125
+ onCancel()
126
+ }
127
+ }
128
+
129
+ return (
130
+ <>
131
+ {paymethod === 'google_pay' ? (
132
+ <View>
133
+ {!loading && initialized && (
134
+ <OButton
135
+ textStyle={{
136
+ color: '#fff'
137
+ }}
138
+ imgRightSrc={null}
139
+ onClick={createPaymentMethod}
140
+ isDisabled={!initialized}
141
+ text={t('PAY_WITH_GOOGLE_PAY', 'Pay with Google Pay')}
142
+ />
143
+ )}
144
+ </View>
145
+ ) : (
146
+ <View>
147
+ {isApplePaySupported && (
148
+ <ApplePayButton
149
+ onPress={pay}
150
+ type="plain"
151
+ buttonStyle="black"
152
+ borderRadius={4}
153
+ style={{
154
+ width: '100%',
155
+ height: 50,
156
+ }}
157
+ />
158
+ )}
159
+ </View>
160
+ )}
161
+ </>
162
+ )
163
+ }
@@ -2,6 +2,7 @@
2
2
  import * as React from 'react'
3
3
  import { ImageStyle } from 'react-native'
4
4
  import styled from 'styled-components/native'
5
+ import { useTheme } from 'styled-components/native'
5
6
 
6
7
  const Wrapper = styled.View``
7
8
 
@@ -23,10 +24,12 @@ interface Props {
23
24
  }
24
25
 
25
26
  const OImage = (props: Props): React.ReactElement => {
27
+ const theme = useTheme();
28
+
26
29
  return (
27
30
  <Wrapper style={{ borderRadius: props.style?.borderRadius, overflow: 'hidden', marginHorizontal: props.style?.marginHorizontal }}>
28
31
  <SImage
29
- source={props.src ? props.src : props.url ? { uri: props.url } : props.dummy ? props.dummy : require('../../assets/icons/lunch.png')}
32
+ source={props.src ? props.src : props.url ? { uri: props.url } : props.dummy ? props.dummy : theme.images.general.lunch || require('../../assets/icons/lunch.png')}
30
33
  style={{
31
34
  tintColor: props.color,
32
35
  flex: props.isWrap ? 1 : 0,
package/src/config.json CHANGED
@@ -3,6 +3,8 @@
3
3
  "notification_app": "orderingapp",
4
4
  "app_name": "Ordering",
5
5
  "project": "reactdemo",
6
+ "apple_app_id": "com.delivery",
7
+ "android_app_id": "com.delivery",
6
8
  "api": {
7
9
  "url": "https://apiv4.ordering.co",
8
10
  "language": "en",
@@ -473,3 +473,12 @@ export interface HelpGuideParams {
473
473
  export interface HelpAccountAndPaymentParams {
474
474
  navigation: any;
475
475
  }
476
+
477
+ export interface StripeMethodFormParams {
478
+ cart: any;
479
+ handleSource: ({id, card} : {id : string, card : any}) => void;
480
+ onCancel: () => void;
481
+ setErrors: (error: string) => void;
482
+ paymethod: string;
483
+ devMode?: boolean;
484
+ }
@@ -1,4 +1,4 @@
1
- import React, { useState } from 'react';
1
+ import React, { useState, useEffect } from 'react';
2
2
  import { StyleSheet, View, TouchableOpacity } from 'react-native';
3
3
  import { useUtils, useOrder, useLanguage } from 'ordering-components/native';
4
4
  import { useTheme } from 'styled-components/native';
@@ -7,6 +7,12 @@ import { BusinessBasicInformationParams } from '../../types';
7
7
  import { convertHoursToMinutes } from '../../utils';
8
8
  import { BusinessInformation } from '../BusinessInformation';
9
9
  import { BusinessReviews } from '../BusinessReviews';
10
+ import dayjs from 'dayjs';
11
+ import timezone from 'dayjs/plugin/timezone';
12
+ import isBetween from 'dayjs/plugin/isBetween';
13
+
14
+ dayjs.extend(timezone);
15
+ dayjs.extend(isBetween);
10
16
 
11
17
  import {
12
18
  BusinessContainer,
@@ -45,6 +51,31 @@ export const BusinessBasicInformation = (
45
51
  return _types.join(', ');
46
52
  };
47
53
 
54
+
55
+ useEffect(() => {
56
+ if (businessState?.loading) return
57
+ let timeout: any = null
58
+ const currentDate = dayjs().tz(businessState?.business?.timezone)
59
+ let lapse = null
60
+ if (businessState?.business?.today?.enabled) {
61
+ lapse = businessState?.business?.today?.lapses?.find((lapse: any) => {
62
+ const from = currentDate.hour(lapse.open.hour).minute(lapse.open.minute)
63
+ const to = currentDate.hour(lapse.close.hour).minute(lapse.close.minute)
64
+ return currentDate.unix() >= from.unix() && currentDate.unix() <= to.unix()
65
+ })
66
+ }
67
+ if (lapse) {
68
+ const to = currentDate.hour(lapse.close.hour).minute(lapse.close.minute)
69
+ const timeToClose = (to.unix() - currentDate.unix()) * 1000
70
+ timeout = setTimeout(() => {
71
+ navigation.navigate('BusinessPreorder', { business: businessState?.business, handleBusinessClick: () => navigation?.goBack() })
72
+ }, timeToClose)
73
+ }
74
+ return () => {
75
+ timeout && clearTimeout(timeout)
76
+ }
77
+ }, [businessState?.business])
78
+
48
79
  return (
49
80
  <BusinessContainer>
50
81
  <BusinessHeader
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { ProductsList, useLanguage, useUtils } from 'ordering-components/native';
2
+ import { ProductsList, useLanguage, useUtils, useConfig } from 'ordering-components/native';
3
3
  import { SingleProductCard } from '../SingleProductCard';
4
4
  import { NotFoundSource } from '../NotFoundSource';
5
5
  import { BusinessProductsListParams } from '../../types';
@@ -31,6 +31,8 @@ const BusinessProductsListUI = (props: BusinessProductsListParams) => {
31
31
 
32
32
  const [, t] = useLanguage();
33
33
  const [{ optimizeImage }] = useUtils()
34
+ const [{ configs }] = useConfig()
35
+ const isUseParentCategory = configs?.use_parent_category?.value === 'true' || configs?.use_parent_category?.value === '1'
34
36
 
35
37
  const handleOnLayout = (event: any, categoryId: any) => {
36
38
  const _categoriesLayout = { ...categoriesLayout }
@@ -80,52 +82,47 @@ const BusinessProductsListUI = (props: BusinessProductsListParams) => {
80
82
  </View>
81
83
  )}
82
84
 
83
- {!category.id &&
84
- categories &&
85
- categories
86
- .filter((category) => category.id !== null)
87
- .map((category, i, _categories) => {
88
- const products =
89
- categoryState.products?.filter(
90
- (product: any) => product.category_id === category.id,
91
- ) || [];
92
- return (
93
- <React.Fragment key={'cat_' + category.id}>
94
- {products.length > 0 && (
95
- <>
96
- <View
97
- style={bpStyles.catWrap}
98
- onLayout={(event: any) => handleOnLayout(event, category.id)}
99
- >
100
- <View style={bpStyles.catIcon}>
101
- <OIcon
102
- url={optimizeImage(category.image, 'h_100,c_limit')}
103
- width={41}
104
- height={41}
105
- style={{ borderRadius: 7.6 }}
106
- />
107
- </View>
108
- <OText size={16} weight="600">
109
- {category.name}
110
- </OText>
111
- </View>
112
- <>
113
- {products.sort((a: any, b: any) => a.rank - b.rank).map((product: any, i: any) => (
114
- <SingleProductCard
115
- key={i}
116
- isSoldOut={product.inventoried && !product.quantity}
117
- businessId={businessId}
118
- product={product}
119
- onProductClick={onProductClick}
120
- productAddedToCartLength={currentCart?.products?.reduce((productsLength: number, Cproduct: any) => { return productsLength + (Cproduct?.id === product?.id ? Cproduct?.quantity : 0) }, 0)}
121
- />
122
- ))}
123
- </>
124
- </>
125
- )}
126
- </React.Fragment>
127
- );
128
- })}
85
+ {!category?.id && categories.filter(category => category?.id !== null).map((category, i, _categories) => {
86
+ const products = !isUseParentCategory
87
+ ? categoryState?.products?.filter((product : any) => product?.category_id === category?.id) ?? []
88
+ : categoryState?.products?.filter((product : any) => category?.children?.some((cat : any) => cat.category_id === product?.category_id)) ?? []
89
+ return (
90
+ <React.Fragment key={'cat_' + category.id}>
91
+ {products.length > 0 && (
92
+ <>
93
+ <View
94
+ style={bpStyles.catWrap}
95
+ onLayout={(event: any) => handleOnLayout(event, category.id)}
96
+ >
97
+ <View style={bpStyles.catIcon}>
98
+ <OIcon
99
+ url={optimizeImage(category.image, 'h_100,c_limit')}
100
+ width={41}
101
+ height={41}
102
+ style={{ borderRadius: 7.6 }}
103
+ />
104
+ </View>
105
+ <OText size={16} weight="600">
106
+ {category.name}
107
+ </OText>
108
+ </View>
109
+ <>
110
+ {products.sort((a: any, b: any) => a.rank - b.rank).map((product: any, i: any) => (
111
+ <SingleProductCard
112
+ key={i}
113
+ isSoldOut={product.inventoried && !product.quantity}
114
+ businessId={businessId}
115
+ product={product}
116
+ onProductClick={onProductClick}
117
+ productAddedToCartLength={currentCart?.products?.reduce((productsLength: number, Cproduct: any) => { return productsLength + (Cproduct?.id === product?.id ? Cproduct?.quantity : 0) }, 0)}
118
+ />
119
+ ))}
120
+ </>
121
+ </>
122
+ )}
123
+ </React.Fragment>
124
+ );
125
+ })}
129
126
 
130
127
  {(categoryState.loading || isBusinessLoading) && (
131
128
  <>
@@ -2,12 +2,12 @@ import React, { useState, useEffect } from 'react'
2
2
  import { OrderList, useLanguage, useOrder, ToastType, useToast } from 'ordering-components/native'
3
3
  import { useTheme } from 'styled-components/native';
4
4
  import { useFocusEffect } from '@react-navigation/native'
5
- import { OText } from '../shared'
5
+ import { OText, OButton } from '../shared'
6
6
  import { NotFoundSource } from '../NotFoundSource'
7
7
  import { ActiveOrders } from '../ActiveOrders'
8
8
  import { PreviousOrders } from '../PreviousOrders'
9
9
 
10
- import { OptionTitle } from './styles'
10
+ import { OptionTitle, NoOrdersWrapper } from './styles'
11
11
  import { OrdersOptionParams } from '../../types'
12
12
 
13
13
  import {
@@ -32,6 +32,8 @@ const OrdersOptionUI = (props: OrdersOptionParams) => {
32
32
  loadMoreStatus,
33
33
  loadMoreOrders,
34
34
  loadOrders,
35
+ setOrdersLength,
36
+ ordersLength
35
37
  } = props
36
38
 
37
39
  const theme = useTheme();
@@ -112,28 +114,50 @@ const OrdersOptionUI = (props: OrdersOptionParams) => {
112
114
  }
113
115
  }, [loadMoreStatus, loading, pagination])
114
116
 
117
+ useEffect(() => {
118
+ if (loading) return
119
+
120
+ const updateOrders = orders.filter((order: any) => orderStatus.includes(order.status))
121
+
122
+ if (activeOrders) {
123
+ setOrdersLength && setOrdersLength({ ...ordersLength, activeOrdersLength: updateOrders?.length })
124
+ } else if (!preOrders) {
125
+ setOrdersLength && setOrdersLength({ ...ordersLength, previousOrdersLength: updateOrders?.length })
126
+ }
127
+ }, [orders?.length])
128
+
115
129
  return (
116
130
  <>
117
- {(orders.length > 0) && (
118
- <>
119
- <OptionTitle>
120
- <OText size={16} lineHeight={24} weight={'500'} color={theme.colors.textNormal} mBottom={10} >
121
- {titleContent || (activeOrders
122
- ? t('ACTIVE', 'Active')
123
- : preOrders
124
- ? t('PREORDERS', 'Preorders')
125
- : t('PAST', 'Past'))}
126
- </OText>
127
- </OptionTitle>
128
- </>
129
- )}
130
- {!loading && orders.length === 0 && (
131
+ <OptionTitle>
132
+ <OText size={16} lineHeight={24} weight={'500'} color={theme.colors.textNormal} mBottom={10} >
133
+ {titleContent || (activeOrders
134
+ ? t('ACTIVE', 'Active')
135
+ : preOrders
136
+ ? t('PREORDERS', 'Preorders')
137
+ : t('PAST', 'Past'))}
138
+ </OText>
139
+ </OptionTitle>
140
+ {!(activeOrders && ordersLength.activeOrdersLength === 0 && ordersLength.previousOrdersLength === 0) && !loading && orders.length === 0 && (
131
141
  <NotFoundSource
132
142
  content={t('NO_RESULTS_FOUND', 'Sorry, no results found')}
133
143
  image={imageFails}
134
144
  conditioned
135
145
  />
136
146
  )}
147
+ {!loading && ordersLength.activeOrdersLength === 0 && ordersLength.previousOrdersLength === 0 && activeOrders && (
148
+ <NoOrdersWrapper>
149
+ <OText size={14} numberOfLines={1}>
150
+ {t('YOU_DONT_HAVE_ORDERS', 'You don\'t have any orders')}
151
+ </OText>
152
+ <OButton
153
+ text={t('ORDER_NOW', 'Order now')}
154
+ onClick={() => onNavigationRedirect && onNavigationRedirect('BusinessList')}
155
+ textStyle={{ color: 'white', fontSize: 14 }}
156
+ style={{ borderRadius: 7.6, marginBottom: 10, marginTop: 10, height: 44, paddingLeft: 10, paddingRight: 10 }}
157
+ />
158
+
159
+ </NoOrdersWrapper>
160
+ )}
137
161
  {loading && (
138
162
  <>
139
163
  {!activeOrders ? (
@@ -3,3 +3,8 @@ import styled from 'styled-components/native'
3
3
  export const OptionTitle = styled.View`
4
4
  margin-top: 24px;
5
5
  `
6
+
7
+ export const NoOrdersWrapper = styled.View`
8
+ flex-direction: column;
9
+ align-items: center;
10
+ `
@@ -1,10 +1,12 @@
1
- import React, { useEffect, useRef } from 'react';
1
+ import React, { useEffect, useRef, useCallback } from 'react';
2
2
  import {
3
3
  ProductForm as ProductOptions,
4
4
  useSession,
5
5
  useLanguage,
6
6
  useOrder,
7
- useUtils
7
+ useUtils,
8
+ ToastType,
9
+ useToast
8
10
  } from 'ordering-components/native';
9
11
  import { useTheme } from 'styled-components/native';
10
12
  import { ProductIngredient } from '../ProductIngredient';
@@ -12,11 +14,13 @@ import { ProductOption } from '../ProductOption';
12
14
  import Swiper from 'react-native-swiper'
13
15
  import FastImage from 'react-native-fast-image';
14
16
  import IconAntDesign from 'react-native-vector-icons/AntDesign';
17
+ import YoutubePlayer from "react-native-youtube-iframe"
18
+ import { TextInput } from 'react-native'
15
19
  import {
16
20
  Grayscale
17
21
  } from 'react-native-color-matrix-image-filters'
18
22
 
19
- import { View, TouchableOpacity, StyleSheet, Dimensions, I18nManager, SafeAreaView } from 'react-native';
23
+ import { View, TouchableOpacity, StyleSheet, Dimensions, I18nManager, SafeAreaView, Button, Alert } from 'react-native';
20
24
 
21
25
  import {
22
26
  WrapHeader,
@@ -53,6 +57,7 @@ export const ProductOptionsUI = (props: any) => {
53
57
  productCart,
54
58
  increment,
55
59
  decrement,
60
+ handleChangeProductCartQuantity,
56
61
  showOption,
57
62
  maxProductQuantity,
58
63
  errors,
@@ -64,6 +69,7 @@ export const ProductOptionsUI = (props: any) => {
64
69
  } = props;
65
70
 
66
71
  const theme = useTheme();
72
+ const [, { showToast }] = useToast()
67
73
 
68
74
  const styles = StyleSheet.create({
69
75
  mainContainer: {
@@ -158,6 +164,7 @@ export const ProductOptionsUI = (props: any) => {
158
164
  const [indexGallery, setIndexGallery] = useState(0)
159
165
  const [selOpt, setSelectedOpt] = useState(0);
160
166
  const [isHaveWeight, setIsHaveWeight] = useState(false)
167
+ const [playing, setPlaying] = useState(false);
161
168
  const [qtyBy, setQtyBy] = useState({
162
169
  weight_unit: false,
163
170
  pieces: true
@@ -216,7 +223,7 @@ export const ProductOptionsUI = (props: any) => {
216
223
 
217
224
  const handleRedirectLogin = () => {
218
225
  navigation.navigate('Login', {
219
- store_slug: props.businessSlug
226
+ store_slug: props.businessSlug
220
227
  });
221
228
  };
222
229
 
@@ -224,15 +231,42 @@ export const ProductOptionsUI = (props: any) => {
224
231
  setQtyBy({ [val]: true, [!val]: false })
225
232
  }
226
233
 
234
+ const onStateChange = useCallback((state) => {
235
+ if (state === "ended") {
236
+ setPlaying(false);
237
+ }
238
+ }, []);
239
+
240
+ const togglePlaying = useCallback(() => {
241
+ setPlaying((prev) => !prev);
242
+ }, []);
243
+
244
+ const onChangeProductCartQuantity = (quantity: number) => {
245
+ if (quantity >= maxProductQuantity) {
246
+ showToast(ToastType.Error, t('MAX_QUANTITY', 'The max quantity is _number_').replace('_number_', maxProductQuantity))
247
+ return
248
+ }
249
+ handleChangeProductCartQuantity(quantity)
250
+ }
251
+
227
252
  useEffect(() => {
228
- const productImgList: any = []
229
- product?.images && productImgList.push(product.images)
253
+ const imageList: any = []
254
+ const videoList: any = []
255
+ product?.images && imageList.push(product.images)
230
256
  if (product?.gallery && product?.gallery.length > 0) {
231
257
  for (const img of product?.gallery) {
232
- productImgList.push(img.file)
258
+ if (img?.file) {
259
+ imageList.push(img?.file)
260
+ }
261
+ if (img?.video) {
262
+ const keys = img?.video.split('/')
263
+ const _videoId = keys[keys.length - 1]
264
+ videoList.push(_videoId)
265
+ }
233
266
  }
234
267
  }
235
- setGallery(productImgList)
268
+ const gallery = imageList.concat(videoList)
269
+ setGallery(gallery)
236
270
 
237
271
  if (product?.weight && product?.weight_unit) {
238
272
  setIsHaveWeight(true)
@@ -349,18 +383,30 @@ export const ProductOptionsUI = (props: any) => {
349
383
  </View>
350
384
  }
351
385
  >
352
- {gallery.length > 0 && gallery.map((img, i) => (
386
+ {gallery && gallery.length > 0 && gallery.map((img, i) => (
353
387
  <View
354
388
  style={styles.slide1}
355
389
  key={i}
356
390
  >
357
- <FastImage
358
- style={{ height: '100%', opacity: isSoldOut ? 0.5 : 1 }}
359
- source={{
360
- uri: optimizeImage(img, 'h_258,c_limit'),
361
- priority: FastImage.priority.normal,
362
- }}
363
- />
391
+ {img.includes('image') ? (
392
+ <FastImage
393
+ style={{ height: '100%', opacity: isSoldOut ? 0.5 : 1 }}
394
+ source={{
395
+ uri: optimizeImage(img, 'h_258,c_limit'),
396
+ priority: FastImage.priority.normal,
397
+ }}
398
+ />
399
+ ) : (
400
+ <>
401
+ <YoutubePlayer
402
+ height={300}
403
+ play={playing}
404
+ videoId={img}
405
+ onChangeState={onStateChange}
406
+ />
407
+ <Button title={playing ? "pause" : "play"} onPress={togglePlaying} />
408
+ </>
409
+ )}
364
410
  </View>
365
411
  ))}
366
412
  </Swiper>
@@ -384,18 +430,33 @@ export const ProductOptionsUI = (props: any) => {
384
430
  opacity: index === thumbsSwiper ? 1 : 0.8
385
431
  }}
386
432
  >
387
- <OIcon
388
- url={img}
389
- style={{
390
- borderColor: theme.colors.lightGray,
391
- borderRadius: 8,
392
- minHeight: '100%',
393
- opacity: isSoldOut ? 0.5 : 1
394
- }}
395
- width={56}
396
- height={56}
397
- cover
398
- />
433
+ {img.includes('image') ? (
434
+ <OIcon
435
+ url={img}
436
+ style={{
437
+ borderColor: theme.colors.lightGray,
438
+ borderRadius: 8,
439
+ minHeight: '100%',
440
+ opacity: isSoldOut ? 0.5 : 1
441
+ }}
442
+ width={56}
443
+ height={56}
444
+ cover
445
+ />
446
+ ) : (
447
+ <OIcon
448
+ url={'http://img.youtube.com/vi/' + img + '/0.jpg'}
449
+ style={{
450
+ borderColor: theme.colors.lightGray,
451
+ borderRadius: 8,
452
+ minHeight: '100%',
453
+ opacity: isSoldOut ? 0.5 : 1
454
+ }}
455
+ width={56}
456
+ height={56}
457
+ cover
458
+ />
459
+ )}
399
460
  </View>
400
461
  </TouchableOpacity>
401
462
 
@@ -787,14 +848,32 @@ export const ProductOptionsUI = (props: any) => {
787
848
  }
788
849
  />
789
850
  </TouchableOpacity>
790
- <OText
791
- size={12}
792
- lineHeight={18}
793
- style={{ minWidth: 40, textAlign: 'center' }}
794
- >
795
- {qtyBy?.pieces && productCart.quantity}
796
- {qtyBy?.weight_unit && productCart.quantity * product?.weight}
797
- </OText>
851
+ {qtyBy?.pieces && (
852
+ <TextInput
853
+ keyboardType='numeric'
854
+ value={`${productCart.quantity}` || ''}
855
+ onChangeText={(val: any) => onChangeProductCartQuantity(parseInt(val))}
856
+ editable={!orderState.loading}
857
+ style={{
858
+ borderWidth: 1,
859
+ textAlign: 'center',
860
+ minWidth: 60,
861
+ borderRadius: 8,
862
+ borderColor: theme.colors.inputBorderColor,
863
+ height: 44,
864
+ marginHorizontal: 10
865
+ }}
866
+ />
867
+ )}
868
+ {qtyBy?.weight_unit && (
869
+ <OText
870
+ size={12}
871
+ lineHeight={18}
872
+ style={{ minWidth: 40, textAlign: 'center' }}
873
+ >
874
+ {productCart.quantity * product?.weight}
875
+ </OText>
876
+ )}
798
877
  <TouchableOpacity
799
878
  onPress={increment}
800
879
  disabled={
@@ -203,7 +203,7 @@ export interface BusinessProductsListParams {
203
203
  errors?: any;
204
204
  businessId?: number;
205
205
  category?: any;
206
- categories?: Array<any>;
206
+ categories: Array<any>;
207
207
  categoryState?: any;
208
208
  onProductClick?: any;
209
209
  handleSearchRedirect?: () => {};