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 +2 -1
- package/src/components/Checkout/index.tsx +1 -0
- package/src/components/PaymentOptions/index.tsx +15 -6
- package/src/components/ProductForm/index.tsx +1 -1
- package/src/components/StripeElementsForm/index.tsx +48 -27
- package/src/components/StripeMethodForm/index.tsx +163 -0
- package/src/components/shared/OIcon.tsx +4 -1
- package/src/config.json +2 -0
- package/src/types/index.tsx +9 -0
- package/themes/original/src/components/BusinessBasicInformation/index.tsx +32 -1
- package/themes/original/src/components/BusinessProductsList/index.tsx +44 -47
- package/themes/original/src/components/OrdersOption/index.tsx +40 -16
- package/themes/original/src/components/OrdersOption/styles.tsx +5 -0
- package/themes/original/src/components/ProductForm/index.tsx +115 -36
- package/themes/original/src/types/index.tsx +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ordering-ui-react-native",
|
|
3
|
-
"version": "0.14.
|
|
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",
|
|
@@ -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
|
-
|
|
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
|
|
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(
|
|
307
|
+
onCancel={() => handlePaymethodClick(null)}
|
|
299
308
|
/>
|
|
300
309
|
</KeyboardAvoidingView>
|
|
301
310
|
</OModal>
|
|
@@ -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
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
package/src/types/index.tsx
CHANGED
|
@@ -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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
<
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
{
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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 ? (
|
|
@@ -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:
|
|
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
|
|
229
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
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
|
|
206
|
+
categories: Array<any>;
|
|
207
207
|
categoryState?: any;
|
|
208
208
|
onProductClick?: any;
|
|
209
209
|
handleSearchRedirect?: () => {};
|