ordering-ui-react-native 0.15.85 → 0.15.88

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.15.85",
3
+ "version": "0.15.88",
4
4
  "description": "Reusable components made in react native",
5
5
  "main": "src/index.tsx",
6
6
  "author": "ordering.inc",
@@ -0,0 +1,26 @@
1
+ import { useEffect, useState } from 'react'
2
+
3
+ /**
4
+ * Hook for countdown seconds
5
+ * @param {int} initialCount
6
+ * @param {boolean} start
7
+ */
8
+ export function useCountdownTimer (initialCount : number, start : boolean) {
9
+ const [count, setCount] = useState(initialCount)
10
+
11
+ useEffect(() => {
12
+ if (start) {
13
+ const secondsLeft = setInterval(() => {
14
+ setCount(c => c - (c === 0 ? 0 : 1))
15
+ }, 1000)
16
+ return () => clearInterval(secondsLeft)
17
+ }
18
+ }, [start])
19
+
20
+ return [
21
+ count,
22
+ setCount,
23
+ /** reset */
24
+ () => { setCount(initialCount) }
25
+ ]
26
+ }
@@ -12,6 +12,7 @@ import {
12
12
  useConfig
13
13
  } from 'ordering-components/native'
14
14
  import { OButton, OIcon, OModal, OText } from '../shared'
15
+ import Alert from '../../providers/AlertProvider'
15
16
  import { BusinessBasicInformation } from '../BusinessBasicInformation'
16
17
  import { SearchBar } from '../SearchBar'
17
18
  import { BusinessProductsCategories } from '../BusinessProductsCategories'
@@ -48,6 +49,9 @@ const BusinessProductsListingUI = (props: BusinessProductsListingParams) => {
48
49
  errorQuantityProducts,
49
50
  header,
50
51
  logo,
52
+ alertState,
53
+ setAlertState,
54
+ multiRemoveProducts,
51
55
  getNextProducts,
52
56
  } = props
53
57
 
@@ -169,18 +173,24 @@ const BusinessProductsListingUI = (props: BusinessProductsListingParams) => {
169
173
  navigation?.canGoBack() ? navigation.goBack() : navigation.navigate('BottomTab')
170
174
  }
171
175
 
176
+ const adjustBusiness = async (adjustBusinessId: number) => {
177
+ const _carts = orderState?.carts?.[adjustBusinessId]
178
+ const products = _carts?.products
179
+ const unavailableProducts = products.filter((product: any) => product.valid !== true)
180
+ unavailableProducts.length > 0 && multiRemoveProducts && multiRemoveProducts(unavailableProducts, _carts)
181
+ }
182
+
172
183
  const removeCartByReOrder = async () => {
173
- const removeCardId = await _retrieveStoreData('remove-cartId')
174
- if (currentCart && removeCardId) {
175
- clearCart(removeCardId)
176
- _removeStoreData('remove-cartId')
177
- showToast(ToastType.Info, t('PRODUCT_REMOVED', 'Products removed from cart'))
184
+ const adjustBusinessId = await _retrieveStoreData('adjust-cart-products')
185
+ if (currentCart && adjustBusinessId) {
186
+ _removeStoreData('adjust-cart-products')
187
+ adjustBusiness(adjustBusinessId)
178
188
  }
179
189
  }
180
190
 
181
191
  useEffect(() => {
182
192
  removeCartByReOrder()
183
- }, [])
193
+ }, [currentCart])
184
194
 
185
195
  return (
186
196
  <ContainerSafeAreaView
@@ -393,6 +403,13 @@ const BusinessProductsListingUI = (props: BusinessProductsListingParams) => {
393
403
  onRedirect={onRedirect}
394
404
  />
395
405
  )}
406
+ <Alert
407
+ open={alertState?.open || false}
408
+ title=''
409
+ content={[t('NOT_AVAILABLE_PRODUCTS', 'These products are not available.')]}
410
+ onAccept={() => setAlertState({ open: false, content: [] })}
411
+ onClose={() => setAlertState({ open: false, content: [] })}
412
+ />
396
413
  </ContainerSafeAreaView>
397
414
  )
398
415
  }
@@ -137,7 +137,12 @@ const CheckoutUI = (props: any) => {
137
137
  const placeSpotTypes = [3, 4]
138
138
  const isWalletEnabled = configs?.wallet_enabled?.value === '1' && (configs?.wallet_cash_enabled?.value === '1' || configs?.wallet_credit_point_enabled?.value === '1')
139
139
  const isPreOrder = configs?.preorder_status_enabled?.value === '1'
140
- const isDisabledButtonPlace = loading || !cart?.valid || (!paymethodSelected && cart?.balance > 0) || placing || errorCash || cart?.subtotal < cart?.minimum || (placeSpotTypes.includes(options?.type) && !cart?.place)
140
+ const isDisabledButtonPlace = loading || !cart?.valid || (!paymethodSelected && cart?.balance > 0) || placing || errorCash ||
141
+ cart?.subtotal < cart?.minimum || (placeSpotTypes.includes(options?.type) && !cart?.place) ||
142
+ (options.type === 1 &&
143
+ validationFields?.fields?.checkout?.driver_tip?.enabled &&
144
+ validationFields?.fields?.checkout?.driver_tip?.required &&
145
+ (Number(cart?.driver_tip) <= 0))
141
146
 
142
147
  const driverTipsOptions = typeof configs?.driver_tip_options?.value === 'string'
143
148
  ? JSON.parse(configs?.driver_tip_options?.value) || []
@@ -623,7 +628,7 @@ const CheckoutUI = (props: any) => {
623
628
 
624
629
  {!cartState.loading && cart && (
625
630
  <View>
626
- <ChErrors style={{ marginBottom: 0 }}>
631
+ <ChErrors style={{ marginBottom: 10 }}>
627
632
  {!cart?.valid_address && cart?.status !== 2 && (
628
633
  <OText
629
634
  color={theme.colors.error}
@@ -658,6 +663,17 @@ const CheckoutUI = (props: any) => {
658
663
  {t('WARNING_PLACE_SPOT', 'Please, select your spot to place order.')}
659
664
  </OText>
660
665
  )}
666
+ {options.type === 1 &&
667
+ validationFields?.fields?.checkout?.driver_tip?.enabled &&
668
+ validationFields?.fields?.checkout?.driver_tip?.required &&
669
+ (Number(cart?.driver_tip) <= 0) && (
670
+ <OText
671
+ color={theme.colors.error}
672
+ size={12}
673
+ >
674
+ {t('WARNING_INVALID_DRIVER_TIP', 'Driver Tip is required.')}
675
+ </OText>
676
+ )}
661
677
  </ChErrors>
662
678
  </View>
663
679
  )}
@@ -25,7 +25,8 @@ const ForgotPasswordUI = (props: any) => {
25
25
  formState,
26
26
  handleButtonForgotPasswordClick,
27
27
  handleReCaptcha,
28
- enableReCaptcha
28
+ enableReCaptcha,
29
+ reCaptchaValue
29
30
  } = props;
30
31
  const [, t] = useLanguage();
31
32
  const [, { showToast }] = useToast();
@@ -58,9 +59,12 @@ const ForgotPasswordUI = (props: any) => {
58
59
  onChange(value.toLowerCase().replace(/[&,()%";:ç?<>{}\\[\]\s]/g, ''))
59
60
  }
60
61
 
61
- const handleOpenRecaptcha = () => {
62
+ const handleOpenRecaptcha = () => {
62
63
  setRecaptchaVerified(false)
63
- if (!recaptchaConfig?.siteKey) {
64
+ handleReCaptcha(null)
65
+ if (reCaptchaValue) return
66
+
67
+ if (!recaptchaConfig?.siteKey) {
64
68
  showToast(ToastType.Error, t('NO_RECAPTCHA_SITE_KEY', 'The config doesn\'t have recaptcha site key'));
65
69
  return
66
70
  }
@@ -76,6 +80,11 @@ const ForgotPasswordUI = (props: any) => {
76
80
  handleReCaptcha(token)
77
81
  }
78
82
 
83
+ const handleRecaptchaExpire = () => {
84
+ setRecaptchaVerified(false)
85
+ handleReCaptcha(null)
86
+ }
87
+
79
88
  useEffect(() => {
80
89
  if (!formState.loading && emailSent) {
81
90
  if (formState.result?.error) {
@@ -192,7 +201,7 @@ const ForgotPasswordUI = (props: any) => {
192
201
  siteKey={recaptchaConfig?.siteKey}
193
202
  baseUrl={recaptchaConfig?.baseUrl}
194
203
  onVerify={onRecaptchaVerify}
195
- onExpire={() => setRecaptchaVerified(false)}
204
+ onExpire={handleRecaptchaExpire}
196
205
  />
197
206
  </>
198
207
  )}
@@ -0,0 +1,91 @@
1
+ import React, { useEffect } from 'react'
2
+ import { formatSeconds } from '../../../utils'
3
+ import { StyleSheet, TouchableOpacity } from 'react-native';
4
+ import { useCountdownTimer } from '../../../../../../src/hooks/useCountdownTimer';
5
+ import { useLanguage } from 'ordering-components/native';
6
+ import { OTPContainer } from './styles';
7
+ import { OText, OButton } from '../../shared';
8
+ import OTPInputView from '@twotalltotems/react-native-otp-input'
9
+ import { useTheme } from 'styled-components/native';
10
+ import { otpParams } from '../../../types'
11
+
12
+ export const Otp = (props: otpParams) => {
13
+ const {
14
+ willVerifyOtpState,
15
+ setWillVerifyOtpState,
16
+ onSubmit,
17
+ handleLoginOtp,
18
+ setAlertState
19
+ } = props
20
+
21
+ const theme = useTheme();
22
+ const [, t] = useLanguage();
23
+ const [otpLeftTime, _, resetOtpLeftTime]: any = useCountdownTimer(
24
+ 600, willVerifyOtpState)
25
+
26
+
27
+ const handleOnSubmit = () => {
28
+ setAlertState({
29
+ open: true,
30
+ title: t('CODE_SENT', 'The code has been sent'),
31
+ })
32
+ resetOtpLeftTime()
33
+ onSubmit()
34
+ }
35
+
36
+ useEffect(() => {
37
+ if (otpLeftTime === 0) {
38
+ setAlertState({
39
+ open: true,
40
+ title: t('TIME_IS_UP', 'Time is up'),
41
+ content: t('PLEASE_RESEND_CODE', 'Please resend code again')
42
+ })
43
+ }
44
+ }, [otpLeftTime])
45
+
46
+ const loginStyle = StyleSheet.create({
47
+ underlineStyleBase: {
48
+ width: 45,
49
+ height: 60,
50
+ borderWidth: 1,
51
+ fontSize: 16
52
+ },
53
+ underlineStyleHighLighted: {
54
+ borderColor: theme.colors.primary,
55
+ color: theme.colors.primary,
56
+ fontSize: 16
57
+ },
58
+ });
59
+
60
+ return (
61
+ <>
62
+ <OTPContainer>
63
+ <OText size={24}>
64
+ {formatSeconds(otpLeftTime)}
65
+ </OText>
66
+ <OTPInputView
67
+ style={{ width: '100%', height: 150 }}
68
+ pinCount={6}
69
+ autoFocusOnLoad
70
+ codeInputFieldStyle={loginStyle.underlineStyleBase}
71
+ codeInputHighlightStyle={loginStyle.underlineStyleHighLighted}
72
+ onCodeFilled={(code: string) => handleLoginOtp(code)}
73
+ selectionColor={theme.colors.primary}
74
+ />
75
+ <TouchableOpacity onPress={() => handleOnSubmit()} disabled={otpLeftTime > 520}>
76
+ <OText size={16} mBottom={30} color={otpLeftTime > 520 ? theme.colors.disabled : theme.colors.primary}>
77
+ {t('RESEND_CODE', 'Resend code')}
78
+ </OText>
79
+ </TouchableOpacity>
80
+ <OButton
81
+ onClick={() => setWillVerifyOtpState(false)}
82
+ bgColor={theme.colors.white}
83
+ borderColor={theme.colors.primary}
84
+ textStyle={{ color: theme.colors.primary }}
85
+ style={{ borderRadius: 8, width: '100%' }}
86
+ text={t('CANCEL', 'Cancel')}
87
+ />
88
+ </OTPContainer>
89
+ </>
90
+ )
91
+ }
@@ -0,0 +1,7 @@
1
+ import styled from 'styled-components/native';
2
+
3
+ export const OTPContainer = styled.View`
4
+ padding: 20px;
5
+ align-items: center;
6
+ flex: 1
7
+ `
@@ -18,7 +18,6 @@ import { useTheme } from 'styled-components/native';
18
18
  import { FacebookLogin } from '../FacebookLogin';
19
19
  import { VerifyPhone } from '../../../../../src/components/VerifyPhone';
20
20
  import { OModal } from '../../../../../src/components/shared';
21
-
22
21
  import {
23
22
  Container,
24
23
  ButtonsWrapper,
@@ -32,17 +31,19 @@ import {
32
31
  LineSeparator,
33
32
  SkeletonWrapper,
34
33
  TabBtn,
35
- RecaptchaButton
34
+ RecaptchaButton
36
35
  } from './styles';
37
36
 
38
37
  import NavBar from '../NavBar';
39
38
 
40
- import { OText, OButton, OInput, OIcon } from '../shared';
39
+ import { OText, OButton, OInput } from '../shared';
41
40
  import { LoginParams } from '../../types';
42
41
  import { Placeholder, PlaceholderLine, Fade } from 'rn-placeholder';
43
42
  import { GoogleLogin } from '../GoogleLogin';
44
43
  import { AppleLogin } from '../AppleLogin';
44
+ import { Otp } from './Otp'
45
45
  import { TouchableOpacity } from 'react-native-gesture-handler';
46
+ import Alert from '../../../../../src/providers/AlertProvider'
46
47
 
47
48
  const LoginFormUI = (props: LoginParams) => {
48
49
  const {
@@ -51,6 +52,7 @@ const LoginFormUI = (props: LoginParams) => {
51
52
  navigation,
52
53
  useLoginByEmail,
53
54
  useLoginByCellphone,
55
+ useLoginOtp,
54
56
  loginButtonText,
55
57
  forgotButtonText,
56
58
  verifyPhoneState,
@@ -63,7 +65,12 @@ const LoginFormUI = (props: LoginParams) => {
63
65
  onNavigationRedirect,
64
66
  notificationState,
65
67
  handleReCaptcha,
66
- enableReCaptcha
68
+ enableReCaptcha,
69
+ otpType,
70
+ setOtpType,
71
+ generateOtpCode,
72
+ useLoginOtpEmail,
73
+ useLoginOtpCellphone,
67
74
  } = props;
68
75
 
69
76
  const [, { showToast }] = useToast();
@@ -75,6 +82,7 @@ const LoginFormUI = (props: LoginParams) => {
75
82
  const [isLoadingVerifyModal, setIsLoadingVerifyModal] = useState(false);
76
83
  const [isModalVisible, setIsModalVisible] = useState(false);
77
84
  const [isFBLoading, setIsFBLoading] = useState(false);
85
+ const [willVerifyOtpState, setWillVerifyOtpState] = useState(false)
78
86
  const [phoneInputData, setPhoneInputData] = useState({
79
87
  error: '',
80
88
  phone: {
@@ -84,9 +92,11 @@ const LoginFormUI = (props: LoginParams) => {
84
92
  });
85
93
  const [recaptchaConfig, setRecaptchaConfig] = useState<any>({})
86
94
  const [recaptchaVerified, setRecaptchaVerified] = useState(false)
95
+ const [alertState, setAlertState] = useState({ open: false, title: '', content: [] })
87
96
 
88
97
  const theme = useTheme();
89
-
98
+ const isOtpEmail = loginTab === 'otp' && otpType === 'email'
99
+ const isOtpCellphone = loginTab === 'otp' && otpType === 'cellphone'
90
100
  const loginStyle = StyleSheet.create({
91
101
  btnOutline: {
92
102
  backgroundColor: '#FFF',
@@ -106,32 +116,67 @@ const LoginFormUI = (props: LoginParams) => {
106
116
  marginBottom: 7,
107
117
  },
108
118
  recaptchaIcon: {
109
- width: 100,
110
- height: 100,
111
- }
119
+ width: 100,
120
+ height: 100,
121
+ },
122
+ borderStyleBase: {
123
+ width: 30,
124
+ height: 45
125
+ },
126
+
127
+ borderStyleHighLighted: {
128
+ borderColor: "#03DAC6",
129
+ },
130
+
131
+ underlineStyleBase: {
132
+ width: 45,
133
+ height: 60,
134
+ borderWidth: 1,
135
+ fontSize: 16
136
+ },
137
+
138
+ underlineStyleHighLighted: {
139
+ borderColor: theme.colors.primary,
140
+ color: theme.colors.primary,
141
+ fontSize: 16
142
+ },
112
143
  });
113
144
 
114
145
  const emailRef = useRef<any>({});
115
146
  const passwordRef = useRef<any>({});
116
- const recaptchaRef = useRef<any>({});
147
+ const recaptchaRef = useRef<any>({});
117
148
 
118
149
  const handleChangeTab = (val: string) => {
119
150
  props.handleChangeTab(val);
120
151
  setPasswordSee(false);
121
152
  };
122
153
 
123
- const onSubmit = (values: any) => {
154
+ const onSubmit = (values?: any) => {
124
155
  Keyboard.dismiss();
125
- if (phoneInputData.error) {
126
- showToast(ToastType.Error, phoneInputData.error);
127
- return;
156
+ if (loginTab === 'otp') {
157
+ if (phoneInputData.error && (loginTab !== 'otp' || (otpType === 'cellphone' && loginTab === 'otp'))) {
158
+ showToast(ToastType.Error, t('INVALID_PHONE_NUMBER', 'Invalid phone number'));
159
+ return
160
+ }
161
+ if (loginTab === 'otp') {
162
+ generateOtpCode({
163
+ ...values,
164
+ ...phoneInputData.phone
165
+ })
166
+ }
167
+ setWillVerifyOtpState(true)
168
+ } else {
169
+ if (phoneInputData.error) {
170
+ showToast(ToastType.Error, phoneInputData.error);
171
+ return;
172
+ }
173
+ handleButtonLoginClick({
174
+ ...values,
175
+ ...phoneInputData.phone,
176
+ });
128
177
  }
129
- handleButtonLoginClick({
130
- ...values,
131
- ...phoneInputData.phone,
132
- });
133
- };
134
178
 
179
+ };
135
180
  const handleVerifyCodeClick = () => {
136
181
  if (phoneInputData.error) {
137
182
  showToast(ToastType.Error, phoneInputData.error);
@@ -166,9 +211,9 @@ const LoginFormUI = (props: LoginParams) => {
166
211
  onChange(value.toLowerCase().replace(/[&,()%";:ç?<>{}\\[\]\s]/g, ''));
167
212
  };
168
213
 
169
- const handleOpenRecaptcha = () => {
214
+ const handleOpenRecaptcha = () => {
170
215
  setRecaptchaVerified(false)
171
- if (!recaptchaConfig?.siteKey) {
216
+ if (!recaptchaConfig?.siteKey) {
172
217
  showToast(ToastType.Error, t('NO_RECAPTCHA_SITE_KEY', 'The config doesn\'t have recaptcha site key'));
173
218
  return
174
219
  }
@@ -176,14 +221,33 @@ const LoginFormUI = (props: LoginParams) => {
176
221
  showToast(ToastType.Error, t('NO_RECAPTCHA_BASE_URL', 'The config doesn\'t have recaptcha base url'));
177
222
  return
178
223
  }
224
+
179
225
  recaptchaRef.current.open()
180
- }
226
+ }
181
227
 
182
228
  const onRecaptchaVerify = (token: any) => {
183
229
  setRecaptchaVerified(true)
184
230
  handleReCaptcha(token)
185
231
  }
186
232
 
233
+ const handleChangeOtpType = (type: string) => {
234
+ handleChangeTab('otp')
235
+ setOtpType(type)
236
+ }
237
+
238
+ const handleLoginOtp = (code: string) => {
239
+ handleButtonLoginClick({ code })
240
+ setWillVerifyOtpState(false)
241
+ }
242
+
243
+ const closeAlert = () => {
244
+ setAlertState({
245
+ open: false,
246
+ title: '',
247
+ content: []
248
+ })
249
+ }
250
+
187
251
  useEffect(() => {
188
252
  if (configs && Object.keys(configs).length > 0 && enableReCaptcha) {
189
253
  setRecaptchaConfig({
@@ -231,16 +295,26 @@ const LoginFormUI = (props: LoginParams) => {
231
295
  }, [phoneInputData?.phone?.cellphone])
232
296
 
233
297
  useEffect(() => {
234
- register('cellphone', {
235
- required: loginTab === 'cellphone'
236
- ? t('VALIDATION_ERROR_MOBILE_PHONE_REQUIRED', 'The field Mobile phone is required').replace('_attribute_', t('CELLPHONE', 'Cellphone'))
237
- : null
238
- })
239
- }, [register])
298
+ register('cellphone', {
299
+ required: loginTab === 'cellphone'
300
+ ? t('VALIDATION_ERROR_MOBILE_PHONE_REQUIRED', 'The field Mobile phone is required').replace('_attribute_', t('CELLPHONE', 'Cellphone'))
301
+ : null
302
+ })
303
+ }, [register])
240
304
 
241
305
  useEffect(() => {
242
- reset()
243
- }, [loginTab])
306
+ reset()
307
+ }, [loginTab])
308
+
309
+ useEffect(() => {
310
+ if (checkPhoneCodeState?.result?.error) {
311
+ setAlertState({
312
+ open: true,
313
+ content: t(checkPhoneCodeState?.result?.error, checkPhoneCodeState?.result?.error),
314
+ title: ''
315
+ })
316
+ }
317
+ }, [checkPhoneCodeState])
244
318
 
245
319
  return (
246
320
  <Container>
@@ -255,9 +329,9 @@ const LoginFormUI = (props: LoginParams) => {
255
329
  titleStyle={{ marginRight: 0, marginLeft: 0 }}
256
330
  />
257
331
  <FormSide>
258
- {useLoginByEmail && useLoginByCellphone && (
332
+ {((useLoginByEmail && useLoginByCellphone) || useLoginOtp) && (
259
333
  <LoginWith>
260
- <OTabs>
334
+ <OTabs horizontal>
261
335
  {useLoginByEmail && (
262
336
  <TabBtn onPress={() => handleChangeTab('email')}>
263
337
  <OTab
@@ -302,13 +376,57 @@ const LoginFormUI = (props: LoginParams) => {
302
376
  </OTab>
303
377
  </TabBtn>
304
378
  )}
379
+ {useLoginOtpEmail && (
380
+ <TabBtn onPress={() => handleChangeOtpType('email')}>
381
+ <OTab
382
+ style={{
383
+ borderBottomColor:
384
+ isOtpEmail
385
+ ? theme.colors.textNormal
386
+ : theme.colors.border,
387
+ }}>
388
+ <OText
389
+ size={14}
390
+ color={
391
+ isOtpEmail
392
+ ? theme.colors.textNormal
393
+ : theme.colors.disabled
394
+ }
395
+ weight={isOtpEmail ? 'bold' : 'normal'}>
396
+ {t('LOGIN_BY_OTP_EMAIL', 'Login by Otp Email')}
397
+ </OText>
398
+ </OTab>
399
+ </TabBtn>
400
+ )}
401
+ {useLoginOtpCellphone && (
402
+ <TabBtn onPress={() => handleChangeOtpType('cellphone')}>
403
+ <OTab
404
+ style={{
405
+ borderBottomColor:
406
+ isOtpCellphone
407
+ ? theme.colors.textNormal
408
+ : theme.colors.border,
409
+ }}>
410
+ <OText
411
+ size={14}
412
+ color={
413
+ isOtpCellphone
414
+ ? theme.colors.textNormal
415
+ : theme.colors.disabled
416
+ }
417
+ weight={isOtpCellphone ? 'bold' : 'normal'}>
418
+ {t('LOGIN_BY_OTP_PHONE', 'Login by Otp Phone')}
419
+ </OText>
420
+ </OTab>
421
+ </TabBtn>
422
+ )}
305
423
  </OTabs>
306
424
  </LoginWith>
307
425
  )}
308
426
 
309
- {(useLoginByCellphone || useLoginByEmail) && (
427
+ {(useLoginByCellphone || useLoginByEmail || useLoginOtp) && (
310
428
  <FormInput>
311
- {useLoginByEmail && loginTab === 'email' && (
429
+ {((useLoginByEmail && loginTab === 'email') || (loginTab === 'otp' && otpType === 'email')) && (
312
430
  <>
313
431
  {errors?.email && (
314
432
  <OText
@@ -344,10 +462,10 @@ const LoginFormUI = (props: LoginParams) => {
344
462
  rules={{
345
463
  required: {
346
464
  value: true,
347
- message: t(
348
- 'VALIDATION_ERROR_EMAIL_REQUIRED',
349
- 'The field Email is required',
350
- ).replace('_attribute_', t('EMAIL', 'Email'))
465
+ message: t(
466
+ 'VALIDATION_ERROR_EMAIL_REQUIRED',
467
+ 'The field Email is required',
468
+ ).replace('_attribute_', t('EMAIL', 'Email'))
351
469
  },
352
470
  pattern: {
353
471
  value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
@@ -362,7 +480,7 @@ const LoginFormUI = (props: LoginParams) => {
362
480
  </>
363
481
 
364
482
  )}
365
- {useLoginByCellphone && loginTab === 'cellphone' && (
483
+ {((useLoginByCellphone && loginTab === 'cellphone') || (loginTab === 'otp' && otpType === 'cellphone')) && (
366
484
  <View style={{ marginBottom: 28 }}>
367
485
  <PhoneInputNumber
368
486
  data={phoneInputData}
@@ -383,53 +501,56 @@ const LoginFormUI = (props: LoginParams) => {
383
501
  {errors?.password?.message}{errors?.password?.type === 'required' && '*'}
384
502
  </OText>
385
503
  )}
386
- <Controller
387
- control={control}
388
- render={({ onChange, value }: any) => (
389
- <OInput
390
- isSecured={!passwordSee ? true : false}
391
- placeholder={t('PASSWORD', 'Password')}
392
- style={{...loginStyle.inputStyle, marginBottom: 14}}
393
- icon={theme.images.general.lock}
394
- iconCustomRight={
395
- !passwordSee ? (
396
- <MaterialCommunityIcons
397
- name="eye-outline"
398
- size={24}
399
- onPress={() => setPasswordSee(!passwordSee)}
400
- color={theme.colors.disabled}
401
- />
402
- ) : (
403
- <MaterialCommunityIcons
404
- name="eye-off-outline"
405
- size={24}
406
- onPress={() => setPasswordSee(!passwordSee)}
407
- color={theme.colors.disabled}
408
- />
409
- )
504
+ {loginTab !== 'otp' && (
505
+
506
+ <Controller
507
+ control={control}
508
+ render={({ onChange, value }: any) => (
509
+ <OInput
510
+ isSecured={!passwordSee ? true : false}
511
+ placeholder={t('PASSWORD', 'Password')}
512
+ style={{ ...loginStyle.inputStyle, marginBottom: 14 }}
513
+ icon={theme.images.general.lock}
514
+ iconCustomRight={
515
+ !passwordSee ? (
516
+ <MaterialCommunityIcons
517
+ name="eye-outline"
518
+ size={24}
519
+ onPress={() => setPasswordSee(!passwordSee)}
520
+ color={theme.colors.disabled}
521
+ />
522
+ ) : (
523
+ <MaterialCommunityIcons
524
+ name="eye-off-outline"
525
+ size={24}
526
+ onPress={() => setPasswordSee(!passwordSee)}
527
+ color={theme.colors.disabled}
528
+ />
529
+ )
530
+ }
531
+ value={value}
532
+ forwardRef={passwordRef}
533
+ onChange={(val: any) => onChange(val)}
534
+ returnKeyType="done"
535
+ onSubmitEditing={handleSubmit(onSubmit)}
536
+ blurOnSubmit
537
+ borderColor={errors?.password ? theme.colors.danger5 : theme.colors.border}
538
+ />
539
+ )}
540
+ name="password"
541
+ rules={{
542
+ required: {
543
+ value: true,
544
+ message: t(
545
+ 'VALIDATION_ERROR_PASSWORD_REQUIRED',
546
+ 'The field Password is required',
547
+ ).replace('_attribute_', t('PASSWORD', 'Password'))
410
548
  }
411
- value={value}
412
- forwardRef={passwordRef}
413
- onChange={(val: any) => onChange(val)}
414
- returnKeyType="done"
415
- onSubmitEditing={handleSubmit(onSubmit)}
416
- blurOnSubmit
417
- borderColor={errors?.password ? theme.colors.danger5 : theme.colors.border}
418
- />
419
- )}
420
- name="password"
421
- rules={{
422
- required: {
423
- value: true,
424
- message: t(
425
- 'VALIDATION_ERROR_PASSWORD_REQUIRED',
426
- 'The field Password is required',
427
- ).replace('_attribute_', t('PASSWORD', 'Password'))
428
- }
429
- }}
430
- defaultValue=""
431
- />
432
- {onNavigationRedirect && forgotButtonText && (
549
+ }}
550
+ defaultValue=""
551
+ />
552
+ )}
553
+ {onNavigationRedirect && forgotButtonText && loginTab !== 'otp' && (
433
554
  <TouchableOpacity onPress={() => onNavigationRedirect('Forgot')}>
434
555
  <OText size={14} mBottom={18}>
435
556
  {forgotButtonText}
@@ -468,10 +589,9 @@ const LoginFormUI = (props: LoginParams) => {
468
589
  />
469
590
  </>
470
591
  )}
471
-
472
592
  <OButton
473
593
  onClick={handleSubmit(onSubmit)}
474
- text={loginButtonText}
594
+ text={loginTab !== 'otp' ? loginButtonText : t('GET_VERIFY_CODE', 'Get verify code')}
475
595
  bgColor={theme.colors.primary}
476
596
  borderColor={theme.colors.primary}
477
597
  textStyle={{ color: 'white' }}
@@ -480,11 +600,11 @@ const LoginFormUI = (props: LoginParams) => {
480
600
  style={{ borderRadius: 7.6, marginTop: 10, marginBottom: 25 }}
481
601
  />
482
602
  {onNavigationRedirect && registerButtonText && (
483
- <View style={{ flex: 1, flexDirection: 'row', justifyContent: 'center'}}>
603
+ <View style={{ flex: 1, flexDirection: 'row', justifyContent: 'center' }}>
484
604
  <OText size={14}>
485
605
  {t('NEW_ON_PLATFORM', 'New on Ordering?')}
486
606
  </OText>
487
- <TouchableOpacity onPress={() => onNavigationRedirect('Signup')}>
607
+ <TouchableOpacity onPress={() => onNavigationRedirect('Signup')}>
488
608
  <OText size={14} mLeft={5} color={theme.colors.skyBlue}>
489
609
  {t('CREATE_ACCOUNT', 'Create account')}
490
610
  </OText>
@@ -495,11 +615,11 @@ const LoginFormUI = (props: LoginParams) => {
495
615
  )}
496
616
 
497
617
  {useLoginByCellphone &&
498
- loginTab === 'cellphone' &&
499
- configs && Object.keys(configs).length > 0 &&
500
- (configs?.twilio_service_enabled?.value === 'true' ||
501
- configs?.twilio_service_enabled?.value === '1') &&
502
- configs?.twilio_module?.value && (
618
+ loginTab === 'cellphone' &&
619
+ configs && Object.keys(configs).length > 0 &&
620
+ (configs?.twilio_service_enabled?.value === 'true' ||
621
+ configs?.twilio_service_enabled?.value === '1') &&
622
+ configs?.twilio_module?.value && (
503
623
  <>
504
624
  <OrSeparator>
505
625
  <LineSeparator />
@@ -524,59 +644,59 @@ const LoginFormUI = (props: LoginParams) => {
524
644
  )}
525
645
 
526
646
  {configs && Object.keys(configs).length > 0 ? (
527
- (((configs?.facebook_login?.value === 'true' || configs?.facebook_login?.value === '1') && configs?.facebook_id?.value) ||
528
- (configs?.google_login_client_id?.value !== '' && configs?.google_login_client_id?.value !== null)) &&
529
- (
530
- <>
531
- <View
532
- style={{
533
- flexDirection: 'row',
534
- width: '100%',
535
- justifyContent: 'space-between',
536
- alignItems: 'center',
537
- marginVertical: 15
538
- }}>
539
- <View style={loginStyle.line} />
540
- <OText
541
- size={14}
542
- mBottom={10}
543
- style={{ paddingHorizontal: 19 }}
544
- color={theme.colors.disabled}>
545
- {t('OR', 'or')}
546
- </OText>
547
- <View style={loginStyle.line} />
548
- </View>
549
- <ButtonsWrapper>
550
- <SocialButtons>
551
- {(configs?.facebook_login?.value === 'true' || configs?.facebook_login?.value === '1') &&
552
- configs?.facebook_id?.value && (
553
- <FacebookLogin
554
- notificationState={notificationState}
555
- handleErrors={(err: any) => showToast(ToastType.Error, err)}
556
- handleLoading={(val: boolean) => setIsFBLoading(val)}
557
- handleSuccessFacebookLogin={handleSuccessFacebook}
558
- />
559
- )}
560
- {(configs?.google_login_client_id?.value !== '' && configs?.google_login_client_id?.value !== null) && (
561
- <GoogleLogin
562
- notificationState={notificationState}
563
- webClientId={configs?.google_login_client_id?.value}
564
- handleErrors={(err: any) => showToast(ToastType.Error, err)}
565
- handleLoading={(val: boolean) => setIsFBLoading(val)}
566
- handleSuccessGoogleLogin={handleSuccessFacebook}
567
- />
568
- )}
569
- {(configs?.apple_login_client_id?.value !== '' && configs?.google_login_client_id?.value !== null) && (
570
- <AppleLogin
571
- notificationState={notificationState}
572
- handleErrors={(err: any) => showToast(ToastType.Error, err)}
573
- handleLoading={(val: boolean) => setIsFBLoading(val)}
574
- handleSuccessAppleLogin={handleSuccessFacebook}
575
- />
576
- )}
577
- </SocialButtons>
578
- </ButtonsWrapper>
579
- </>
647
+ (((configs?.facebook_login?.value === 'true' || configs?.facebook_login?.value === '1') && configs?.facebook_id?.value) ||
648
+ (configs?.google_login_client_id?.value !== '' && configs?.google_login_client_id?.value !== null)) &&
649
+ (
650
+ <>
651
+ <View
652
+ style={{
653
+ flexDirection: 'row',
654
+ width: '100%',
655
+ justifyContent: 'space-between',
656
+ alignItems: 'center',
657
+ marginVertical: 15
658
+ }}>
659
+ <View style={loginStyle.line} />
660
+ <OText
661
+ size={14}
662
+ mBottom={10}
663
+ style={{ paddingHorizontal: 19 }}
664
+ color={theme.colors.disabled}>
665
+ {t('OR', 'or')}
666
+ </OText>
667
+ <View style={loginStyle.line} />
668
+ </View>
669
+ <ButtonsWrapper>
670
+ <SocialButtons>
671
+ {(configs?.facebook_login?.value === 'true' || configs?.facebook_login?.value === '1') &&
672
+ configs?.facebook_id?.value && (
673
+ <FacebookLogin
674
+ notificationState={notificationState}
675
+ handleErrors={(err: any) => showToast(ToastType.Error, err)}
676
+ handleLoading={(val: boolean) => setIsFBLoading(val)}
677
+ handleSuccessFacebookLogin={handleSuccessFacebook}
678
+ />
679
+ )}
680
+ {(configs?.google_login_client_id?.value !== '' && configs?.google_login_client_id?.value !== null) && (
681
+ <GoogleLogin
682
+ notificationState={notificationState}
683
+ webClientId={configs?.google_login_client_id?.value}
684
+ handleErrors={(err: any) => showToast(ToastType.Error, err)}
685
+ handleLoading={(val: boolean) => setIsFBLoading(val)}
686
+ handleSuccessGoogleLogin={handleSuccessFacebook}
687
+ />
688
+ )}
689
+ {(configs?.apple_login_client_id?.value !== '' && configs?.google_login_client_id?.value !== null) && (
690
+ <AppleLogin
691
+ notificationState={notificationState}
692
+ handleErrors={(err: any) => showToast(ToastType.Error, err)}
693
+ handleLoading={(val: boolean) => setIsFBLoading(val)}
694
+ handleSuccessAppleLogin={handleSuccessFacebook}
695
+ />
696
+ )}
697
+ </SocialButtons>
698
+ </ButtonsWrapper>
699
+ </>
580
700
  )
581
701
  ) : (
582
702
  <SkeletonWrapper>
@@ -592,24 +712,12 @@ const LoginFormUI = (props: LoginParams) => {
592
712
  </Placeholder>
593
713
  </SkeletonWrapper>
594
714
  )}
595
-
596
- {/* {onNavigationRedirect && registerButtonText && (
597
- <ButtonsWrapper>
598
- <OButton
599
- onClick={() => onNavigationRedirect('Signup')}
600
- text={registerButtonText}
601
- style={loginStyle.btnOutline}
602
- borderColor={theme.colors.primary}
603
- imgRightSrc={null}
604
- />
605
- </ButtonsWrapper>
606
- )} */}
607
715
  </FormSide>
608
716
  <OModal
609
717
  open={isModalVisible}
610
718
  onClose={() => setIsModalVisible(false)}
611
- entireModal
612
- title={t('VERIFY_PHONE', 'Verify Phone')}
719
+ entireModal
720
+ title={t('VERIFY_PHONE', 'Verify Phone')}
613
721
  >
614
722
  <VerifyPhone
615
723
  phone={phoneInputData.phone}
@@ -618,9 +726,30 @@ const LoginFormUI = (props: LoginParams) => {
618
726
  handleCheckPhoneCode={handleCheckPhoneCode}
619
727
  setCheckPhoneCodeState={setCheckPhoneCodeState}
620
728
  handleVerifyCodeClick={handleVerifyCodeClick}
621
- onClose={() => setIsModalVisible(false)}
729
+ onClose={() => setIsModalVisible(false)}
730
+ />
731
+ </OModal>
732
+ <OModal
733
+ open={willVerifyOtpState}
734
+ onClose={() => setWillVerifyOtpState(false)}
735
+ entireModal
736
+ title={t('ENTER_VERIFICATION_CODE', 'Enter verification code')}
737
+ >
738
+ <Otp
739
+ willVerifyOtpState={willVerifyOtpState}
740
+ setWillVerifyOtpState={setWillVerifyOtpState}
741
+ handleLoginOtp={handleLoginOtp}
742
+ onSubmit={onSubmit}
743
+ setAlertState={setAlertState}
622
744
  />
623
745
  </OModal>
746
+ <Alert
747
+ open={alertState.open}
748
+ content={alertState.content}
749
+ title={alertState.title || ''}
750
+ onAccept={closeAlert}
751
+ onClose={closeAlert}
752
+ />
624
753
  <Spinner visible={isFBLoading} />
625
754
  </Container>
626
755
  );
@@ -629,7 +758,7 @@ const LoginFormUI = (props: LoginParams) => {
629
758
  export const LoginForm = (props: any) => {
630
759
  const loginProps = {
631
760
  ...props,
632
- isRecaptchaEnable: true,
761
+ isRecaptchaEnable: true,
633
762
  UIComponent: LoginFormUI,
634
763
  };
635
764
  return <LoginFormController {...loginProps} />;
@@ -386,16 +386,23 @@ export const OrderDetailsUI = (props: OrderDetailsParams) => {
386
386
  }
387
387
 
388
388
  useEffect(() => {
389
+ const _businessId = 'businessId:' + businessData?.id
389
390
  if (reorderState?.error) {
390
- const _businessId = 'businessId:' + businessData?.id
391
- const _uuid = carts[_businessId]?.uuid
392
- if (_uuid) {
393
- _setStoreData('remove-cartId', JSON.stringify(_uuid))
391
+ if (businessData?.id) {
392
+ _setStoreData('adjust-cart-products', JSON.stringify(_businessId))
394
393
  navigation.navigate('Business', { store: businessData?.slug })
395
394
  }
396
395
  }
397
- if (!reorderState?.error && reorderState?.result?.uuid) {
398
- onNavigationRedirect && onNavigationRedirect('CheckoutNavigator', { cartUuid: reorderState?.result.uuid })
396
+ if (!reorderState?.error && reorderState.loading === false && businessData?.id) {
397
+ const products = carts?.[_businessId]?.products
398
+ const available = products.every((product: any) => product.valid === true)
399
+
400
+ if (available && reorderState?.result?.uuid) {
401
+ onNavigationRedirect && onNavigationRedirect('CheckoutNavigator', { cartUuid: reorderState?.result.uuid })
402
+ } else {
403
+ _setStoreData('adjust-cart-products', JSON.stringify(_businessId))
404
+ navigation.navigate('Business', { store: businessData?.slug })
405
+ }
399
406
  }
400
407
  }, [reorderState])
401
408
 
@@ -19,6 +19,12 @@ export interface LoginParams {
19
19
  notificationState?: any;
20
20
  handleReCaptcha?: any;
21
21
  enableReCaptcha?: any;
22
+ otpType?: string,
23
+ setOtpType: (type : string) => void,
24
+ generateOtpCode: (values ?: any) => void,
25
+ useLoginOtpEmail?: boolean,
26
+ useLoginOtpCellphone?: boolean,
27
+ useLoginOtp?: boolean
22
28
  }
23
29
  export interface ProfileParams {
24
30
  navigation?: any;
@@ -198,6 +204,9 @@ export interface BusinessProductsListingParams {
198
204
  header?: any;
199
205
  logo?: any;
200
206
  productModal?: any;
207
+ alertState?: { open: boolean, content: any[] };
208
+ setAlertState?: any;
209
+ multiRemoveProducts?: (in1: any, in2: any) => {};
201
210
  getNextProducts?: () => {};
202
211
  handleChangeCategory: (value: any) => {};
203
212
  setProductLogin?: () => {};
@@ -597,4 +606,12 @@ export interface SessionsParams {
597
606
  actionState: any,
598
607
  handleDeleteSession: any,
599
608
  handleDeleteAllSessions: any
600
- }
609
+ }
610
+
611
+ export interface otpParams {
612
+ willVerifyOtpState: boolean,
613
+ setWillVerifyOtpState: (val : boolean) => void,
614
+ onSubmit: () => void,
615
+ handleLoginOtp: (code : string) => void,
616
+ setAlertState: any
617
+ }
@@ -212,4 +212,20 @@ export const formatUrlVideo = (url : string) => {
212
212
  const match = url.match(regExp)
213
213
  const id = (match && match[7].length === 11) ? match[7] : false
214
214
  return `https://www.youtube-nocookie.com/embed/${id}`
215
- }
215
+ }
216
+
217
+ export const formatSeconds = (seconds : number) => {
218
+ // Hours, minutes and seconds
219
+ var hrs = ~~(seconds / 3600)
220
+ var mins = ~~((seconds % 3600) / 60)
221
+ var secs = ~~seconds % 60
222
+
223
+ // Output like '1:01' or '4:03:59' or '123:03:59'
224
+ var ret = ''
225
+ if (hrs > 0) {
226
+ ret += '' + hrs + ':' + (mins < 10 ? '0' : '')
227
+ }
228
+ ret += '' + mins + ':' + (secs < 10 ? '0' : '')
229
+ ret += '' + secs
230
+ return ret
231
+ }