ordering-ui-react-native 0.20.8-release → 0.20.9-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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ordering-ui-react-native",
3
- "version": "0.20.8-release",
3
+ "version": "0.20.9-release",
4
4
  "description": "Reusable components made in react native",
5
5
  "main": "src/index.tsx",
6
6
  "author": "ordering.inc",
@@ -43,6 +43,7 @@ import { NewOrderNotification } from './src/components/NewOrderNotification';
43
43
  import { DriverSchedule } from './src/components/DriverSchedule';
44
44
  import { ScheduleBlocked } from './src/components/ScheduleBlocked';
45
45
  import { OrderDetailsLogistic } from './src/components/OrderDetailsLogistic'
46
+ import { Sessions } from './src/components/Sessions';
46
47
  //OComponents
47
48
  import {
48
49
  OText,
@@ -106,6 +107,7 @@ export {
106
107
  ReviewCustomer,
107
108
  SafeAreaContainerLayout,
108
109
  SearchBar,
110
+ Sessions,
109
111
  SignupForm,
110
112
  StoresList,
111
113
  UserFormDetailsUI,
@@ -20,7 +20,9 @@ import {
20
20
  useEvent,
21
21
  useLanguage,
22
22
  useSession,
23
- useConfig
23
+ useConfig,
24
+ useToast,
25
+ ToastType
24
26
  } from 'ordering-components/native'
25
27
 
26
28
  import { OIcon, OText } from '../shared'
@@ -108,11 +110,12 @@ const SoundPlayerComponent = (props: any) => {
108
110
 
109
111
  const NewOrderNotificationUI = (props: any) => {
110
112
  const { isBusinessApp, evtList } = props
111
-
113
+ const [, t] = useLanguage()
112
114
  const [events] = useEvent()
113
115
  const [{ user, token }] = useSession()
114
116
  const [ordering] = useApi()
115
117
  const [{ configs }] = useConfig()
118
+ const [, { showToast }] = useToast()
116
119
  const { getCurrentLocation } = useLocation()
117
120
  const [currentEvent, setCurrentEvent] = useState<any>(null)
118
121
 
@@ -124,6 +127,10 @@ const NewOrderNotificationUI = (props: any) => {
124
127
  if (value?.driver) {
125
128
  try {
126
129
  const location = await getCurrentLocation()
130
+ if (!location?.latitude || !location?.longitude) {
131
+ showToast(t('ERROR_UPDATING_COORDS', 'Error updating coords'), ToastType.Error)
132
+ return
133
+ }
127
134
  await fetch(`${ordering.root}/users/${user.id}/locations`, {
128
135
  method: 'POST',
129
136
  body: JSON.stringify({
@@ -50,6 +50,7 @@ const { useDeviceOrientation, PORTRAIT } = DeviceOrientationMethods
50
50
 
51
51
  const OrdersOptionUI = (props: OrdersOptionParams) => {
52
52
  const {
53
+ navigation,
53
54
  setCurrentFilters,
54
55
  tabs,
55
56
  currentTabSelected,
@@ -385,6 +386,13 @@ const OrdersOptionUI = (props: OrdersOptionParams) => {
385
386
  setTags({ values: [] })
386
387
  }, [currentTabSelected])
387
388
 
389
+ useEffect(() => {
390
+ const unsubcribe = navigation.addListener('focus', () => {
391
+ currentTabSelected === 'logisticOrders' ? loadLogisticOrders() : loadOrders && loadOrders({ newFetch: true })
392
+ })
393
+ return unsubcribe
394
+ }, [navigation, loadOrders, loadLogisticOrders])
395
+
388
396
  return (
389
397
  <>
390
398
  <View style={styles.header}>
@@ -1,5 +1,5 @@
1
1
  import React, { useEffect, useState } from 'react';
2
- import { Platform, PlatformIOSStatic, StyleSheet, TouchableOpacity, View } from 'react-native';
2
+ import { Platform, PlatformIOSStatic, Pressable, StyleSheet, View } from 'react-native';
3
3
  import DeviceInfo from 'react-native-device-info';
4
4
  import { useTheme } from 'styled-components/native';
5
5
  import { useLanguage, useUtils, useConfig } from 'ordering-components/native';
@@ -138,8 +138,7 @@ export const OrderItem = React.memo((props: any) => {
138
138
  }, [configState.loading])
139
139
 
140
140
  return (
141
- <TouchableOpacity
142
- activeOpacity={1}
141
+ <Pressable
143
142
  disabled={order?.locked && isLogisticOrder}
144
143
  style={styles.cardButton}
145
144
  onPress={() => handlePressOrder({ ...order, logistic_order_id: _order?.id })}
@@ -237,6 +236,6 @@ export const OrderItem = React.memo((props: any) => {
237
236
  )}
238
237
  </Information>
239
238
  </Card>
240
- </TouchableOpacity>
239
+ </Pressable>
241
240
  )
242
241
  }, OrderItemPropsAreEqual)
@@ -0,0 +1,187 @@
1
+ import React, { useState } from 'react'
2
+ import { View, TouchableOpacity, Platform, StyleSheet } from 'react-native'
3
+ import { useLanguage, useSession, useUtils, Sessions as SessionsController } from 'ordering-components/native'
4
+ import { SessionsParams } from '../../types'
5
+ import { OAlert } from '../../../../../src/components/shared'
6
+ import { OButton, OIcon, OText } from '../shared'
7
+ import { useTheme } from 'styled-components/native'
8
+ import { Fade, Placeholder, PlaceholderLine } from 'rn-placeholder'
9
+ import AntIcon from 'react-native-vector-icons/AntDesign'
10
+
11
+ import {
12
+ SessionsWrapper,
13
+ SessionItem,
14
+ DurationWrapper,
15
+ Container
16
+ } from './styles'
17
+
18
+ export const SessionsUI = (props: SessionsParams) => {
19
+ const {
20
+ navigation,
21
+ sessionsList,
22
+ actionState,
23
+ handleDeleteSession,
24
+ handleDeleteAllSessions
25
+ } = props
26
+
27
+ const [, t] = useLanguage()
28
+ const [{ user }] = useSession()
29
+ const [{ parseDate }] = useUtils()
30
+ const theme = useTheme()
31
+ const [confirm, setConfirm] = useState<any>({ open: false, content: null, handleOnAccept: null, id: null, title: null })
32
+ const goToBack = () => navigation?.canGoBack() && navigation.goBack()
33
+
34
+ const onDeleteSession = (session: any) => {
35
+ setConfirm({
36
+ open: true,
37
+ title: t('WEB_APPNAME', 'Ordering'),
38
+ content: [t('QUESTION_DELETE_SESSION', 'Are you sure to delete this session?')],
39
+ handleOnAccept: () => {
40
+ handleDeleteSession(session)
41
+ setConfirm({ ...confirm, open: false })
42
+ }
43
+ })
44
+ }
45
+
46
+ const onDeleteAllSessions = (isOldUser: any, deleteCurrent: any) => {
47
+ setConfirm({
48
+ open: true,
49
+ title: t('WEB_APPNAME', 'Ordering'),
50
+ content:
51
+ isOldUser
52
+ ? [t('QUESTION_ENABLE_ALL_SESSIONS', 'Are you sure to enable all sessions?')]
53
+ : deleteCurrent
54
+ ? [t('QUESTION_DELETE_ALL_SESSIONS', 'Are you sure that you want to delete all sessions?')]
55
+ : [t('QUESTION_DELETE_ALL_SESSIONS_EXCEPT_CURRENT', 'Are you sure that you want to delete all sessions except current?')],
56
+ handleOnAccept: () => {
57
+ handleDeleteAllSessions(deleteCurrent)
58
+ setConfirm({ ...confirm, open: false })
59
+ }
60
+ })
61
+ }
62
+
63
+ const styles = StyleSheet.create({
64
+ titleGroups: {
65
+ alignItems: 'center',
66
+ flexDirection: 'row',
67
+ minHeight: 33,
68
+ },
69
+ btnBackArrow: {
70
+ borderWidth: 0,
71
+ width: 32,
72
+ height: 32,
73
+ tintColor: theme.colors.textGray,
74
+ backgroundColor: theme.colors.clear,
75
+ borderColor: theme.colors.clear,
76
+ shadowColor: theme.colors.clear,
77
+ paddingLeft: 0,
78
+ paddingRight: 0,
79
+ marginTop: Platform.OS === 'ios' ? 30 : 10
80
+ },
81
+ innerPadding: {
82
+ paddingLeft: 10,
83
+ paddingRight: 10
84
+ }
85
+ });
86
+
87
+ return (
88
+ <Container
89
+ pdng={Platform.OS === 'ios' ? '10px' : '8px'}
90
+ style={styles.innerPadding}
91
+ >
92
+ <View style={styles.titleGroups}>
93
+ <TouchableOpacity onPress={() => goToBack()} style={styles.btnBackArrow}>
94
+ <OIcon src={theme.images.general.arrow_left} color={theme.colors.textGray} />
95
+ </TouchableOpacity>
96
+ </View>
97
+ <OText size={24} style={{ paddingTop: 12 }}>
98
+ {t('SESSIONS', 'Sessions')}
99
+ </OText>
100
+ {user?.session_strategy === 'jwt_session' ? (
101
+ <>
102
+ {sessionsList.loading ? (
103
+ [...Array(5).keys()].map(i => (
104
+ <SessionItem key={i}>
105
+ <Placeholder Animation={Fade}>
106
+ <View style={{ flexDirection: 'row', alignItems: 'center' }}>
107
+ <View style={{ flex: 1 }}>
108
+ <PlaceholderLine width={40} />
109
+ <PlaceholderLine width={40} />
110
+ </View>
111
+ <PlaceholderLine width={5} />
112
+ </View>
113
+ </Placeholder>
114
+ </SessionItem>
115
+ ))
116
+ ) : (
117
+ sessionsList.sessions.length > 0 ? (
118
+ <SessionsWrapper>
119
+ {sessionsList.sessions.reverse().map((session: any) => (
120
+ <SessionItem key={session.id}>
121
+ <DurationWrapper>
122
+ <OText>{parseDate(session.created_at)}</OText>
123
+ <OText>{parseDate(session.valid_thru)}</OText>
124
+ </DurationWrapper>
125
+ {session.current && (
126
+ <OText mLeft={15} style={{ flex: 1 }}>({t('CURRENT', 'Current')})</OText>
127
+ )}
128
+ <TouchableOpacity
129
+ onPress={() => onDeleteSession(session)}
130
+ >
131
+ <AntIcon name='close' size={16} color={theme.colors.red} />
132
+ </TouchableOpacity>
133
+ </SessionItem>
134
+ ))}
135
+ <OButton
136
+ text={t('DELETE_ALL_SESSIONS', 'Delete all sessions')}
137
+ isDisabled={actionState.loading}
138
+ textStyle={{ color: theme.colors.white, fontSize: 14 }}
139
+ onClick={() => onDeleteAllSessions(false, true)}
140
+ style={{ borderRadius: 7.6, marginTop: 30 }}
141
+ />
142
+ <OButton
143
+ text={t('DELETE_ALL_SESSIONS_EXCEPT_CURRENT', 'Delete all sessions except current')}
144
+ isDisabled={actionState.loading}
145
+ textStyle={{ color: theme.colors.white, fontSize: 14 }}
146
+ onClick={() => onDeleteAllSessions(false, false)}
147
+ style={{ borderRadius: 7.6, marginTop: 20 }}
148
+ />
149
+ </SessionsWrapper>
150
+ ) : (
151
+ <OText>{t('YOU_DONT_HAVE_ANY_SESSIONS', 'You don\'t have any sessions')}</OText>
152
+ )
153
+ )}
154
+ </>
155
+ ) : (
156
+ <View>
157
+ <OText>
158
+ {t('YOU_DONT_HAVE_ENABLED_THE_SESSIONS', 'You don\'t have enabled the sessions, please active them to have a better control of your sessions.')}
159
+ </OText>
160
+ <OButton
161
+ text={t('ACTIVE_SESSIONS', 'Active sessions')}
162
+ isDisabled={actionState.loading}
163
+ textStyle={{ color: theme.colors.white, fontSize: 14 }}
164
+ onClick={() => onDeleteAllSessions(true, false)}
165
+ style={{ borderRadius: 7.6, marginTop: 20 }}
166
+ />
167
+ </View>
168
+ )}
169
+ <OAlert
170
+ open={confirm.open}
171
+ title={confirm.title}
172
+ content={confirm.content}
173
+ onAccept={confirm.handleOnAccept}
174
+ onCancel={() => setConfirm({ ...confirm, open: false, title: null })}
175
+ onClose={() => setConfirm({ ...confirm, open: false, title: null })}
176
+ />
177
+ </Container>
178
+ )
179
+ }
180
+
181
+ export const Sessions = (props: SessionsParams) => {
182
+ const sessionsProps = {
183
+ ...props,
184
+ UIComponent: SessionsUI
185
+ }
186
+ return <SessionsController {...sessionsProps} />
187
+ }
@@ -0,0 +1,20 @@
1
+ import styled from 'styled-components/native'
2
+
3
+ export const SessionsWrapper = styled.View`
4
+ `
5
+ export const SessionItem = styled.View`
6
+ flex-direction: row;
7
+ align-items: center;
8
+ justify-content: space-between;
9
+ padding-vertical: 15px;
10
+ border-bottom-color: ${(props: any) => props.theme.colors.lightGray};
11
+ border-bottom-width: 1px;
12
+ `
13
+ export const DurationWrapper = styled.View`
14
+ /* flex-direction: row; */
15
+ `
16
+
17
+ export const Container = styled.View`
18
+ padding-top: ${(props: any) => props.pdng};
19
+ margin-bottom: 50px;
20
+ `
@@ -513,6 +513,17 @@ const ProfileUI = (props: ProfileParams) => {
513
513
  marginTop: 10
514
514
  }} />
515
515
  </Pressable>
516
+ <Pressable style={{ marginBottom: 10 }} onPress={() => navigation.navigate('Sessions')}>
517
+ <View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
518
+ <OText size={16}>{t('SESSIONS', 'Sessions')}</OText>
519
+ <AntDesignIcon size={18} name='right' />
520
+ </View>
521
+ <View style={{
522
+ borderBottomColor: theme.colors.tabBar,
523
+ borderBottomWidth: 1,
524
+ marginTop: 10
525
+ }} />
526
+ </Pressable>
516
527
  <Actions>
517
528
  <LanguageSelector />
518
529
 
@@ -48,8 +48,8 @@ export const useLocation = () => {
48
48
  GeoLocation.getCurrentPosition(
49
49
  ({ coords }) => {
50
50
  resolve({
51
- latitude: coords.latitude,
52
- longitude: coords.longitude,
51
+ latitude: typeof coords.latitude !== 'number' && !Number.isNaN(coords.latitude) ? coords.latitude : 0,
52
+ longitude: typeof coords.longitude !== 'number' && !Number.isNaN(coords.longitude) ? coords.longitude : 0,
53
53
  speed: coords.speed,
54
54
  });
55
55
  },
@@ -63,9 +63,10 @@ export const useLocation = () => {
63
63
  watchId.current = GeoLocation.watchPosition(
64
64
  ({ coords }) => {
65
65
  if (!isMounted.current) return;
66
+ if (typeof coords.latitude !== 'number' || typeof coords.longitude !== 'number') return
66
67
  const location: Location = {
67
- latitude: coords.latitude,
68
- longitude: coords.longitude,
68
+ latitude: coords.latitude || 0,
69
+ longitude: coords.longitude || 0,
69
70
  speed: coords.speed,
70
71
  };
71
72
  setUserLocation(location);
@@ -630,3 +630,11 @@ export interface OrderDetailsLogisticParams {
630
630
  orderAssingId: number,
631
631
  order: any
632
632
  }
633
+
634
+ export interface SessionsParams {
635
+ navigation: any,
636
+ sessionsList: any,
637
+ actionState: any,
638
+ handleDeleteSession: any,
639
+ handleDeleteAllSessions: any
640
+ }
@@ -200,7 +200,11 @@ const CartUI = (props: any) => {
200
200
  if (cart?.business_id) {
201
201
  setOpenUpselling(true)
202
202
  } else {
203
- handleUpsellingPage(cart)
203
+ onNavigationRedirect('CheckoutNavigator', {
204
+ screen: 'CheckoutPage',
205
+ cartUuid: cart?.uuid,
206
+ cartTotal: cart?.total
207
+ }, true)
204
208
  }
205
209
  }
206
210
 
@@ -135,53 +135,55 @@ export const CartContent = (props: any) => {
135
135
  {isMultiCheckout && (
136
136
  <>
137
137
  {!!cartsAvailable.length && (
138
- <ChCartsTotal>
139
- {!!totalCartsFee && configs?.multi_business_checkout_show_combined_delivery_fee?.value === '1' && (
140
- <View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
141
- <OText size={14} lineHeight={24} color={theme.colors.textNormal} weight={'400'}>
142
- {t('TOTAL_DELIVERY_FEE', 'Total delivery fee')}
143
- </OText>
144
- <OText size={14} lineHeight={24} color={theme.colors.textNormal} weight={'400'}>
145
- {parsePrice(totalCartsFee)}
146
- </OText>
147
- </View>
148
- )}
149
- {cartsAvailable.reduce((sum: any, cart: any) => sum + cart?.driver_tip, 0) > 0 &&
150
- configs?.multi_business_checkout_show_combined_driver_tip?.value === '1' && (
138
+ <>
139
+ <ChCartsTotal>
140
+ {!!totalCartsFee && configs?.multi_business_checkout_show_combined_delivery_fee?.value === '1' && (
151
141
  <View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
152
142
  <OText size={14} lineHeight={24} color={theme.colors.textNormal} weight={'400'}>
153
- {t('DRIVER_TIP', 'Driver tip')}
143
+ {t('TOTAL_DELIVERY_FEE', 'Total delivery fee')}
154
144
  </OText>
155
145
  <OText size={14} lineHeight={24} color={theme.colors.textNormal} weight={'400'}>
156
- {parsePrice(cartsAvailable.reduce((sum: any, cart: any) => sum + cart?.driver_tip, 0))}
146
+ {parsePrice(totalCartsFee)}
157
147
  </OText>
158
148
  </View>
159
149
  )}
160
- <View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
161
- <OText size={16} lineHeight={24} color={theme.colors.textNormal} weight={'500'}>
162
- {t('TOTAL_FOR_ALL_CARTS', 'Total for all Carts')}
163
- </OText>
164
- <OText size={16} lineHeight={24} color={theme.colors.textNormal} weight={'500'}>{parsePrice(totalCartsPrice)}</OText>
165
- </View>
166
- <View style={{ flexDirection: 'row', justifyContent: 'center', marginVertical: 20 }}>
167
- <OText size={14} color={theme.colors.textNormal} weight={'300'} style={{ textAlign: 'center' }}>
168
- {t('CART_GROUP_MESSAGE_ALERT', 'Discounts may be applied at the time of payment for this group.')}
169
- </OText>
170
- </View>
171
- </ChCartsTotal>
150
+ {cartsAvailable.reduce((sum: any, cart: any) => sum + cart?.driver_tip, 0) > 0 &&
151
+ configs?.multi_business_checkout_show_combined_driver_tip?.value === '1' && (
152
+ <View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
153
+ <OText size={14} lineHeight={24} color={theme.colors.textNormal} weight={'400'}>
154
+ {t('DRIVER_TIP', 'Driver tip')}
155
+ </OText>
156
+ <OText size={14} lineHeight={24} color={theme.colors.textNormal} weight={'400'}>
157
+ {parsePrice(cartsAvailable.reduce((sum: any, cart: any) => sum + cart?.driver_tip, 0))}
158
+ </OText>
159
+ </View>
160
+ )}
161
+ <View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
162
+ <OText size={16} lineHeight={24} color={theme.colors.textNormal} weight={'500'}>
163
+ {t('TOTAL_FOR_ALL_CARTS', 'Total for all Carts')}
164
+ </OText>
165
+ <OText size={16} lineHeight={24} color={theme.colors.textNormal} weight={'500'}>{parsePrice(totalCartsPrice)}</OText>
166
+ </View>
167
+ <View style={{ flexDirection: 'row', justifyContent: 'center', marginVertical: 20 }}>
168
+ <OText size={14} color={theme.colors.textNormal} weight={'300'} style={{ textAlign: 'center' }}>
169
+ {t('CART_GROUP_MESSAGE_ALERT', 'Discounts may be applied at the time of payment for this group.')}
170
+ </OText>
171
+ </View>
172
+ </ChCartsTotal>
173
+ <CheckoutAction style={{ marginTop: 0 }}>
174
+ <OButton
175
+ text={t('CHECKOUT', 'Checkout')}
176
+ bgColor={!cartsAvailable.length ? theme.colors.secundary : theme.colors.primary}
177
+ isDisabled={!cartsAvailable.length}
178
+ borderColor={theme.colors.primary}
179
+ imgRightSrc={null}
180
+ textStyle={{ color: 'white', textAlign: 'center', flex: 1 }}
181
+ onClick={() => handleCheckoutRedirect()}
182
+ style={{ width: '100%', flexDirection: 'row', justifyContent: 'center', borderRadius: 7.6, shadowOpacity: 0 }}
183
+ />
184
+ </CheckoutAction>
185
+ </>
172
186
  )}
173
- <CheckoutAction style={{ marginTop: 0 }}>
174
- <OButton
175
- text={t('CHECKOUT', 'Checkout')}
176
- bgColor={!cartsAvailable.length ? theme.colors.secundary : theme.colors.primary}
177
- isDisabled={!cartsAvailable.length}
178
- borderColor={theme.colors.primary}
179
- imgRightSrc={null}
180
- textStyle={{ color: 'white', textAlign: 'center', flex: 1 }}
181
- onClick={() => handleCheckoutRedirect()}
182
- style={{ width: '100%', flexDirection: 'row', justifyContent: 'center', borderRadius: 7.6, shadowOpacity: 0 }}
183
- />
184
- </CheckoutAction>
185
187
  </>
186
188
  )}
187
189
  </>
@@ -5,6 +5,7 @@ import NativeStripeSdk from '@stripe/stripe-react-native/src/NativeStripeSdk'
5
5
  import Picker from 'react-native-country-picker-modal';
6
6
  import MaterialIcons from 'react-native-vector-icons/MaterialIcons'
7
7
  import IconAntDesign from 'react-native-vector-icons/AntDesign';
8
+ import { useIsFocused } from '@react-navigation/native';
8
9
 
9
10
  import ReactNativeHapticFeedback from "react-native-haptic-feedback";
10
11
  import {
@@ -100,10 +101,12 @@ const CheckoutUI = (props: any) => {
100
101
  currency,
101
102
  merchantId,
102
103
  setPlaceSpotNumber,
103
- maxDate
104
+ maxDate,
105
+ urlscheme
104
106
  } = props
105
107
 
106
108
  const theme = useTheme();
109
+ const isFocused = useIsFocused();
107
110
 
108
111
  const styles = StyleSheet.create({
109
112
  btnBackArrow: {
@@ -208,7 +211,7 @@ const CheckoutUI = (props: any) => {
208
211
  const isDisabledButtonPlace = loading || !cart?.valid || (!paymethodSelected && cart?.balance > 0) ||
209
212
  placing || errorCash || subtotalWithTaxes < cart?.minimum ||
210
213
  (cardsMethods.includes(paymethodSelected?.gateway) && cardList?.cards?.length === 0) ||
211
- (options.type === 1 &&
214
+ (options.type === 1 && !isGiftCardCart &&
212
215
  validationFields?.fields?.checkout?.driver_tip?.enabled &&
213
216
  validationFields?.fields?.checkout?.driver_tip?.required &&
214
217
  (Number(cart?.driver_tip) <= 0)) ||
@@ -413,6 +416,7 @@ const CheckoutUI = (props: any) => {
413
416
  }, [])
414
417
 
415
418
  useEffect(() => {
419
+ if (!isFocused) return
416
420
  if (!cartState?.loading && (cartState?.error || typeof cartState?.cart === 'string')) {
417
421
  const error = cartState?.error || typeof cartState.cart === 'string' && cartState.cart
418
422
  if (error) {
@@ -420,7 +424,7 @@ const CheckoutUI = (props: any) => {
420
424
  navigation.navigate('BusinessList')
421
425
  }
422
426
  }
423
- }, [cartState?.error, cartState?.cart, cartState?.loading])
427
+ }, [cartState?.error, cartState?.cart, cartState?.loading, isFocused])
424
428
 
425
429
  useEffect(() => {
426
430
  const keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', () => {
@@ -814,6 +818,7 @@ const CheckoutUI = (props: any) => {
814
818
  handlePaymentMethodClickCustom={handlePaymentMethodClick}
815
819
  handlePlaceOrder={handlePlaceOrder}
816
820
  merchantId={merchantId}
821
+ urlscheme={urlscheme}
817
822
  setMethodPaySupported={setMethodPaySupported}
818
823
  methodPaySupported={methodPaySupported}
819
824
  placeByMethodPay={placeByMethodPay}
@@ -955,7 +960,7 @@ const CheckoutUI = (props: any) => {
955
960
  {t('INVALID_CART_MOMENT', 'Selected schedule time is invalid, please select a schedule into the business schedule interval.')}
956
961
  </OText>
957
962
  )}
958
- {options.type === 1 &&
963
+ {options.type === 1 && !isGiftCardCart &&
959
964
  validationFields?.fields?.checkout?.driver_tip?.enabled &&
960
965
  validationFields?.fields?.checkout?.driver_tip?.required &&
961
966
  (Number(cart?.driver_tip) <= 0) && (
@@ -1,4 +1,4 @@
1
- import React, { useState, useEffect } from 'react'
1
+ import React, { useState, useEffect, useCallback } from 'react'
2
2
  import {
3
3
  useLanguage,
4
4
  useConfig,
@@ -82,12 +82,12 @@ const MultiCheckoutUI = (props: any) => {
82
82
  wrapperNavbar: {
83
83
  paddingHorizontal: 20,
84
84
  backgroundColor: theme?.colors?.white,
85
- borderWidth: 0
85
+ borderWidth: 0
86
86
  },
87
87
  detailWrapper: {
88
- paddingHorizontal: 20,
89
- width: '100%'
90
- },
88
+ paddingHorizontal: 20,
89
+ width: '100%'
90
+ },
91
91
  })
92
92
 
93
93
  const [, { showToast }] = useToast();
@@ -143,16 +143,17 @@ const MultiCheckoutUI = (props: any) => {
143
143
  ?.reduce((sum: any, cart: any) => sum + clearAmount((cart?.subtotal + getIncludedTaxes(cart)) * accumulationRateBusiness(cart?.business_id)), 0)
144
144
  ?.toFixed(configs.format_number_decimal_length?.value ?? 2)
145
145
 
146
- const [showTitle, setShowTitle] = useState(false)
146
+ const [showTitle, setShowTitle] = useState(false)
147
147
  const [isUserDetailsEdit, setIsUserDetailsEdit] = useState(false);
148
148
  const [phoneUpdate, setPhoneUpdate] = useState(false);
149
149
  const [userErrors, setUserErrors] = useState<any>([]);
150
+ const [cartsOpened, setCartsOpened] = useState([])
150
151
  const [placeByMethodPay, setPlaceByMethodPay] = useState(false)
151
- const [allowedGuest, setAllowedGuest] = useState(false)
152
- const [isOpen, setIsOpen] = useState(false)
153
- const [requiredFields, setRequiredFields] = useState<any>([])
154
- const stripePaymethods: any = ['stripe', 'stripe_direct', 'stripe_connect', 'stripe_redirect']
155
- const [openModal, setOpenModal] = useState({ login: false, signup: false, isGuest: false })
152
+ const [allowedGuest, setAllowedGuest] = useState(false)
153
+ const [isOpen, setIsOpen] = useState(false)
154
+ const [requiredFields, setRequiredFields] = useState<any>([])
155
+ const stripePaymethods: any = ['stripe', 'stripe_direct', 'stripe_connect', 'stripe_redirect']
156
+ const [openModal, setOpenModal] = useState({ login: false, signup: false, isGuest: false })
156
157
  const [methodPaySupported, setMethodPaySupported] = useState({ enabled: false, message: null, loading: true })
157
158
  const methodsPay = ['global_google_pay', 'global_apple_pay']
158
159
  const isDisablePlaceOrderButton = cartGroup?.loading || placing || (!(paymethodSelected?.paymethod_id || paymethodSelected?.wallet_id) && cartGroup?.result?.balance > 0) ||
@@ -170,7 +171,7 @@ const MultiCheckoutUI = (props: any) => {
170
171
  setUserErrors([])
171
172
  const errors = []
172
173
  const notFields = ['coupon', 'driver_tip', 'mobile_phone', 'address', 'zipcode', 'address_notes']
173
- const _requiredFields: any = []
174
+ const _requiredFields: any = []
174
175
 
175
176
  Object.values(validationFields?.fields?.checkout).map((field: any) => {
176
177
  if (field?.required && !notFields.includes(field.code)) {
@@ -188,7 +189,7 @@ const MultiCheckoutUI = (props: any) => {
188
189
  ) {
189
190
  _requiredFields.push('cellphone')
190
191
  }
191
- setRequiredFields(_requiredFields)
192
+ setRequiredFields(_requiredFields)
192
193
 
193
194
  if (phoneUpdate) {
194
195
  errors.push(t('NECESSARY_UPDATE_COUNTRY_PHONE_CODE', 'It is necessary to update your phone number'))
@@ -203,18 +204,18 @@ const MultiCheckoutUI = (props: any) => {
203
204
 
204
205
  const handlePlaceOrder = (confirmPayment?: any) => {
205
206
  if (stripePaymethods.includes(paymethodSelected?.gateway) && user?.guest_id) {
206
- setOpenModal({ ...openModal, signup: true, isGuest: true })
207
- return
208
- }
207
+ setOpenModal({ ...openModal, signup: true, isGuest: true })
208
+ return
209
+ }
209
210
 
210
211
  if (!userErrors.length && (!requiredFields?.length || allowedGuest)) {
211
212
  handleGroupPlaceOrder && handleGroupPlaceOrder(confirmPayment)
212
213
  return
213
214
  }
214
215
  if (requiredFields?.length) {
215
- setIsOpen(true)
216
- return
217
- }
216
+ setIsOpen(true)
217
+ return
218
+ }
218
219
  let stringError = ''
219
220
  Object.values(userErrors).map((item: any, i: number) => {
220
221
  stringError += (i + 1) === userErrors.length ? `- ${item?.message || item}` : `- ${item?.message || item}\n`
@@ -224,26 +225,34 @@ const MultiCheckoutUI = (props: any) => {
224
225
  }
225
226
 
226
227
  const handlePlaceOrderAsGuest = () => {
227
- setIsOpen(false)
228
- handleGroupPlaceOrder && handleGroupPlaceOrder()
229
- }
228
+ setIsOpen(false)
229
+ handleGroupPlaceOrder && handleGroupPlaceOrder()
230
+ }
230
231
 
231
232
  const handleSuccessSignup = (user: any) => {
232
- login({
233
- user,
234
- token: user?.session?.access_token
235
- })
236
- openModal?.isGuest && handlePlaceOrderAsGuest()
237
- setOpenModal({ ...openModal, signup: false, isGuest: false })
238
- }
239
-
240
- const handleSuccessLogin = (user: any) => {
241
- if (user) setOpenModal({ ...openModal, login: false })
242
- }
233
+ login({
234
+ user,
235
+ token: user?.session?.access_token
236
+ })
237
+ openModal?.isGuest && handlePlaceOrderAsGuest()
238
+ setOpenModal({ ...openModal, signup: false, isGuest: false })
239
+ }
240
+
241
+ const handleSuccessLogin = (user: any) => {
242
+ if (user) setOpenModal({ ...openModal, login: false })
243
+ }
243
244
 
244
245
  const handleScroll = ({ nativeEvent: { contentOffset } }: any) => {
245
- setShowTitle(contentOffset.y > 30)
246
- }
246
+ setShowTitle(contentOffset.y > 30)
247
+ }
248
+
249
+ const handleGoBack = () => {
250
+ if (navigation?.canGoBack()) {
251
+ navigation.goBack()
252
+ } else {
253
+ navigation.navigate('BottomTab', { screen: 'Cart' })
254
+ }
255
+ }
247
256
 
248
257
  useEffect(() => {
249
258
  if (validationFields && validationFields?.fields?.checkout) {
@@ -283,13 +292,25 @@ const MultiCheckoutUI = (props: any) => {
283
292
  }
284
293
  }, [paymethodSelected])
285
294
 
295
+ const changeActiveState = useCallback((isClosed: boolean, uuid: string) => {
296
+ const isActive = cartsOpened?.includes?.(uuid)
297
+ if (isActive || !isClosed) {
298
+ setCartsOpened(cartsOpened?.filter?.((_uuid) => _uuid !== uuid))
299
+ } else {
300
+ setCartsOpened([
301
+ ...cartsOpened,
302
+ uuid
303
+ ])
304
+ }
305
+ }, [cartsOpened])
306
+
286
307
  return (
287
308
  <>
288
309
  <SafeAreaView style={{ backgroundColor: theme.colors.backgroundPage }}>
289
310
  <View style={styles.wrapperNavbar}>
290
311
  <TopHeader>
291
312
  <>
292
- <TopActions onPress={() => navigation?.canGoBack() && navigation.goBack()}>
313
+ <TopActions onPress={() => handleGoBack()}>
293
314
  <IconAntDesign
294
315
  name='arrowleft'
295
316
  size={26}
@@ -321,8 +342,8 @@ const MultiCheckoutUI = (props: any) => {
321
342
  paddingTop={Platform.OS === 'ios' ? 0 : 4}
322
343
  btnStyle={{ paddingLeft: 0 }}
323
344
  titleWrapStyle={{ paddingHorizontal: 0 }}
324
- titleStyle={{ marginRight: 0, marginLeft: 0 }}
325
- style={{ marginTop: 20 }}
345
+ titleStyle={{ marginRight: 0, marginLeft: 0 }}
346
+ style={{ marginTop: 20 }}
326
347
  />
327
348
  </View>
328
349
  <ChContainer style={styles.pagePadding}>
@@ -514,6 +535,9 @@ const MultiCheckoutUI = (props: any) => {
514
535
  hideDriverTip={configs?.multi_business_checkout_show_combined_driver_tip?.value === '1'}
515
536
  onNavigationRedirect={(route: string, params: any) => props.navigation.navigate(route, params)}
516
537
  businessConfigs={cart?.business?.configs}
538
+ cartsOpened={cartsOpened}
539
+ changeActiveState={changeActiveState}
540
+ isActive={cartsOpened?.includes?.(cart?.uuid)}
517
541
  />
518
542
  {openCarts.length > 1 && (
519
543
  <View style={{ height: 8, backgroundColor: theme.colors.backgroundGray100, marginTop: 13, marginHorizontal: -40 }} />
@@ -623,30 +647,30 @@ const MultiCheckoutUI = (props: any) => {
623
647
  </ScrollView>
624
648
  </OModal>
625
649
  <OModal
626
- open={isOpen}
627
- onClose={() => setIsOpen(false)}
628
- >
629
- <View style={styles.detailWrapper}>
630
- <UserDetails
631
- isUserDetailsEdit
632
- useValidationFields
633
- useDefualtSessionManager
634
- useSessionUser
635
- isCheckout
636
- isEdit
637
- phoneUpdate={phoneUpdate}
638
- togglePhoneUpdate={togglePhoneUpdate}
639
- requiredFields={requiredFields}
640
- hideUpdateButton
641
- handlePlaceOrderAsGuest={handlePlaceOrderAsGuest}
642
- onClose={() => {
643
- setIsOpen(false)
644
- handlePlaceOrder()
645
- }}
646
- setIsOpen={setIsOpen}
647
- />
648
- </View>
649
- </OModal>
650
+ open={isOpen}
651
+ onClose={() => setIsOpen(false)}
652
+ >
653
+ <View style={styles.detailWrapper}>
654
+ <UserDetails
655
+ isUserDetailsEdit
656
+ useValidationFields
657
+ useDefualtSessionManager
658
+ useSessionUser
659
+ isCheckout
660
+ isEdit
661
+ phoneUpdate={phoneUpdate}
662
+ togglePhoneUpdate={togglePhoneUpdate}
663
+ requiredFields={requiredFields}
664
+ hideUpdateButton
665
+ handlePlaceOrderAsGuest={handlePlaceOrderAsGuest}
666
+ onClose={() => {
667
+ setIsOpen(false)
668
+ handlePlaceOrder()
669
+ }}
670
+ setIsOpen={setIsOpen}
671
+ />
672
+ </View>
673
+ </OModal>
650
674
  </Container>
651
675
 
652
676
  <FloatingButton
@@ -72,7 +72,7 @@ export const MultiOrdersDetailsUI = (props: any) => {
72
72
  navigation?.canGoBack() && navigation.goBack();
73
73
  return;
74
74
  }
75
- navigation.navigate('BusinessList');
75
+ navigation.navigate('BottomTab');
76
76
  return true
77
77
  }
78
78
 
@@ -65,6 +65,7 @@ const PaymentOptionsUI = (props: any) => {
65
65
  handlePaymentMethodClickCustom,
66
66
  handlePlaceOrder,
67
67
  merchantId,
68
+ urlscheme,
68
69
  setMethodPaySupported,
69
70
  placeByMethodPay,
70
71
  methodPaySupported,
@@ -360,6 +361,7 @@ const PaymentOptionsUI = (props: any) => {
360
361
  handleSource={handlePaymethodDataChange}
361
362
  onCancel={() => handlePaymethodClick(null)}
362
363
  merchantId={merchantId}
364
+ urlscheme={urlscheme}
363
365
  setMethodPaySupported={setMethodPaySupported}
364
366
  methodPaySupported={methodPaySupported}
365
367
  placeByMethodPay={placeByMethodPay}
@@ -407,6 +409,7 @@ const PaymentOptionsUI = (props: any) => {
407
409
  handleSource={handlePaymethodDataChange}
408
410
  onCancel={() => handlePaymethodClick(null)}
409
411
  merchantId={merchantId}
412
+ urlscheme={urlscheme}
410
413
  publicKeyAddCard={isOpenMethod?.paymethod?.credentials?.stripe?.publishable}
411
414
  />
412
415
  </KeyboardAvoidingView>
@@ -74,9 +74,9 @@ export const ProductItemAccordion = (props: ProductItemAccordionParams) => {
74
74
 
75
75
  const [isActive, setActiveState] = useState(false)
76
76
  const [isServiceOpen, setIsServiceOpen] = useState(false)
77
+ const [productQuantityState, setProductQuantityState] = useState(product.quantity.toString())
77
78
  // const [setHeight, setHeightState] = useState({ height: new Animated.Value(0) })
78
79
  // const [setRotate, setRotateState] = useState({ angle: new Animated.Value(0) })
79
- let productQuantity = product.quantity.toString()
80
80
 
81
81
  const productInfo = () => {
82
82
  if (isCartProduct) {
@@ -120,7 +120,7 @@ export const ProductItemAccordion = (props: ProductItemAccordionParams) => {
120
120
 
121
121
  const handleChangeQuantity = (value: string) => {
122
122
  if (!orderState.loading) {
123
- productQuantity = value
123
+ setProductQuantityState(value)
124
124
  if (parseInt(value) === 0) {
125
125
  onDeleteProduct && onDeleteProduct(product)
126
126
  } else {
@@ -210,7 +210,7 @@ export const ProductItemAccordion = (props: ProductItemAccordionParams) => {
210
210
  <RNPickerSelect
211
211
  items={productOptions}
212
212
  onValueChange={handleChangeQuantity}
213
- value={productQuantity}
213
+ value={productQuantityState}
214
214
  style={pickerStyle}
215
215
  useNativeAndroidPickerStyle={false}
216
216
  placeholder={{}}
@@ -286,15 +286,15 @@ export const ProductItemAccordion = (props: ProductItemAccordionParams) => {
286
286
  {productInfo().ingredients.length > 0 && productInfo().ingredients.some((ingredient: any) => !ingredient.selected) && (
287
287
  <ProductOptionsList>
288
288
  <OText size={10} color={theme.colors.textSecondary}>{t('INGREDIENTS', 'Ingredients')}</OText>
289
- {productInfo().ingredients.map((ingredient: any) => !ingredient.selected && (
290
- <OText size={10} color={theme.colors.textThird} key={ingredient.id} style={{ marginLeft: 10 }}>{t('NO', 'No')} {ingredient.name}</OText>
289
+ {productInfo().ingredients.map((ingredient: any, i) => !ingredient.selected && (
290
+ <OText size={10} color={theme.colors.textThird} key={ingredient.id + i} style={{ marginLeft: 10 }}>{t('NO', 'No')} {ingredient.name}</OText>
291
291
  ))}
292
292
  </ProductOptionsList>
293
293
  )}
294
294
  {productInfo().options.length > 0 && (
295
295
  <ProductOptionsList>
296
- {productInfo().options.sort((a: any, b: any) => a.rank - b.rank).map((option: any, i: number) => (
297
- <ProductOption key={option.id + i}>
296
+ {productInfo().options.sort((a: any, b: any) => a.rank - b.rank).map((option: any) => (
297
+ <ProductOption key={option.id}>
298
298
  <OText size={10} color={theme.colors.textSecondary}>{option.name}</OText>
299
299
  {option.suboptions.map((suboption: any) => (
300
300
  <ProductSubOption key={suboption.id}>
@@ -34,7 +34,8 @@ const StripeElementsFormUI = (props: any) => {
34
34
  methodPaySupported,
35
35
  setPlaceByMethodPay,
36
36
  cartTotal,
37
- publicKeyAddCard
37
+ publicKeyAddCard,
38
+ urlScheme
38
39
  } = props;
39
40
 
40
41
  const theme = useTheme();
@@ -204,7 +205,7 @@ const StripeElementsFormUI = (props: any) => {
204
205
  <StripeProvider
205
206
  publishableKey={isToSave}
206
207
  merchantIdentifier={merchantId}
207
- urlScheme={merchantId}
208
+ urlScheme={`${urlScheme}://checkout/${cart?.uuid}`}
208
209
  >
209
210
  {methodsPay?.includes(paymethod) ? (
210
211
  <StripeMethodForm