ordering-ui-react-native 0.16.76 → 0.16.77

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.16.76",
3
+ "version": "0.16.77",
4
4
  "description": "Reusable components made in react native",
5
5
  "main": "src/index.tsx",
6
6
  "author": "ordering.inc",
@@ -33,6 +33,7 @@
33
33
  "lint": "eslint ."
34
34
  },
35
35
  "dependencies": {
36
+ "@fatnlazycat/react-native-recaptcha-v3": "^1.0.3",
36
37
  "@invertase/react-native-apple-authentication": "^2.1.5",
37
38
  "@react-native-async-storage/async-storage": "^1.15.5",
38
39
  "@react-native-clipboard/clipboard": "^1.8.4",
@@ -0,0 +1 @@
1
+ declare module '@fatnlazycat/react-native-recaptcha-v3'
@@ -9,6 +9,7 @@ import {
9
9
  } from 'react-native';
10
10
  import { useForm, Controller } from 'react-hook-form';
11
11
  import Recaptcha from 'react-native-recaptcha-that-works'
12
+ import ReCaptcha from '@fatnlazycat/react-native-recaptcha-v3'
12
13
  import { TouchableOpacity } from 'react-native-gesture-handler';
13
14
  import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
14
15
  import {
@@ -52,8 +53,8 @@ const LoginFormUI = (props: LoginParams) => {
52
53
  allowedLevels,
53
54
  useRootPoint,
54
55
  notificationState,
55
- handleReCaptcha,
56
- enableReCaptcha
56
+ handleReCaptcha,
57
+ enableReCaptcha
57
58
  } = props;
58
59
 
59
60
  const [ordering, { setOrdering }] = useApi();
@@ -67,7 +68,7 @@ const LoginFormUI = (props: LoginParams) => {
67
68
  const inputRef = useRef<any>(null);
68
69
  const inputMailRef = useRef<any>(null);
69
70
 
70
- const [projectName, setProjectName] = useState({name: '', isFocued: false});
71
+ const [projectName, setProjectName] = useState({ name: '', isFocued: false });
71
72
  const [passwordSee, setPasswordSee] = useState(false);
72
73
  const [isLoadingVerifyModal, setIsLoadingVerifyModal] = useState(false);
73
74
  const [isModalVisible, setIsModalVisible] = useState(false);
@@ -92,36 +93,50 @@ const LoginFormUI = (props: LoginParams) => {
92
93
  const [formValues, setFormValues] = useState(null);
93
94
 
94
95
  const [recaptchaConfig, setRecaptchaConfig] = useState<any>({})
95
- const [recaptchaVerified, setRecaptchaVerified] = useState(false)
96
+ const [recaptchaVerified, setRecaptchaVerified] = useState(false)
96
97
 
97
98
  const recaptchaRef = useRef<any>({});
98
99
 
99
100
  const handleOpenRecaptcha = () => {
100
- setRecaptchaVerified(false)
101
- if (!recaptchaConfig?.siteKey) {
102
- showToast(ToastType.Error, t('NO_RECAPTCHA_SITE_KEY', 'The config doesn\'t have recaptcha site key'));
103
- return
104
- }
105
- if (!recaptchaConfig?.baseUrl) {
106
- showToast(ToastType.Error, t('NO_RECAPTCHA_BASE_URL', 'The config doesn\'t have recaptcha base url'));
107
- return
108
- }
109
- recaptchaRef.current.open()
110
- }
111
-
112
- const onRecaptchaVerify = (token: any) => {
113
- setRecaptchaVerified(true)
114
- handleReCaptcha(token)
115
- }
116
-
117
- useEffect(() => {
118
- if (configs && Object.keys(configs).length > 0 && enableReCaptcha) {
119
- setRecaptchaConfig({
120
- siteKey: configs?.security_recaptcha_site_key?.value || null,
121
- baseUrl: configs?.security_recaptcha_base_url?.value || null
122
- })
123
- }
124
- }, [configs, enableReCaptcha])
101
+ setRecaptchaVerified(false)
102
+ if (!recaptchaConfig?.siteKey) {
103
+ showToast(ToastType.Error, t('NO_RECAPTCHA_SITE_KEY', 'The config doesn\'t have recaptcha site key'));
104
+ return
105
+ }
106
+ if (!recaptchaConfig?.baseUrl) {
107
+ showToast(ToastType.Error, t('NO_RECAPTCHA_BASE_URL', 'The config doesn\'t have recaptcha base url'));
108
+ return
109
+ }
110
+ recaptchaRef.current.open()
111
+ }
112
+
113
+ const onRecaptchaVerify = (token: any) => {
114
+ setRecaptchaVerified(true)
115
+ handleReCaptcha({ code: token, version: recaptchaConfig?.version })
116
+ }
117
+
118
+ useEffect(() => {
119
+ if (configs && Object.keys(configs).length > 0 && enableReCaptcha) {
120
+ if (configs?.security_recaptcha_type?.value === 'v3' &&
121
+ configs?.security_recaptcha_score_v3?.value > 0 &&
122
+ configs?.security_recaptcha_site_key_v3?.value
123
+ ) {
124
+ setRecaptchaConfig({
125
+ version: 'v3',
126
+ siteKey: configs?.security_recaptcha_site_key_v3?.value || null,
127
+ baseUrl: configs?.security_recaptcha_base_url?.value || null
128
+ })
129
+ return
130
+ }
131
+ if (configs?.security_recaptcha_site_key?.value) {
132
+ setRecaptchaConfig({
133
+ version: 'v2',
134
+ siteKey: configs?.security_recaptcha_site_key?.value || null,
135
+ baseUrl: configs?.security_recaptcha_base_url?.value || null
136
+ })
137
+ }
138
+ }
139
+ }, [configs, enableReCaptcha])
125
140
 
126
141
  useEffect(() => {
127
142
  const projectInputInterval = setInterval(() => {
@@ -245,6 +260,17 @@ const LoginFormUI = (props: LoginParams) => {
245
260
 
246
261
  useEffect(() => {
247
262
  if (!formState?.loading && formState?.result?.error) {
263
+ if (formState.result?.result?.[0] === 'ERROR_AUTH_VERIFICATION_CODE') {
264
+ setRecaptchaVerified(false)
265
+ setSubmitted(false)
266
+ setRecaptchaConfig({
267
+ version: 'v2',
268
+ siteKey: configs?.security_recaptcha_site_key?.value || null,
269
+ baseUrl: configs?.security_recaptcha_base_url?.value || null
270
+ })
271
+ showToast(ToastType.Info, t('TRY_AGAIN', 'Please try again'))
272
+ return
273
+ }
248
274
  formState?.result?.result &&
249
275
  showToast(
250
276
  ToastType.Error,
@@ -252,17 +278,17 @@ const LoginFormUI = (props: LoginParams) => {
252
278
  ? getTraduction(formState.result?.result)
253
279
  : loginTab === 'email' &&
254
280
  typeof formState.result?.result !== 'string'
255
- ? getTraduction(formState.result?.result[0])
256
- : loginTab === 'cellphone' &&
257
- typeof formState.result?.result === 'string'
258
- ? getTraduction(formState.result?.result).replace(
259
- t('USER', 'user').toLowerCase(),
260
- t('PHONE_NUMER', 'Phone number'),
261
- )
262
- : getTraduction(formState.result?.result[0]).replace(
263
- t('USER', 'user').toLowerCase(),
264
- t('PHONE_NUMER', 'Phone number'),
265
- ),
281
+ ? getTraduction(formState.result?.result[0])
282
+ : loginTab === 'cellphone' &&
283
+ typeof formState.result?.result === 'string'
284
+ ? getTraduction(formState.result?.result).replace(
285
+ t('USER', 'user').toLowerCase(),
286
+ t('PHONE_NUMER', 'Phone number'),
287
+ )
288
+ : getTraduction(formState.result?.result[0]).replace(
289
+ t('USER', 'user').toLowerCase(),
290
+ t('PHONE_NUMER', 'Phone number'),
291
+ ),
266
292
  );
267
293
  setSubmitted(false)
268
294
  }
@@ -516,7 +542,7 @@ const LoginFormUI = (props: LoginParams) => {
516
542
  icon={theme.images.general.project}
517
543
  iconColor={theme.colors.arrowColor}
518
544
  onChange={(e: any) => {
519
- setProjectName({name: e?.target?.value, isFocued: true})
545
+ setProjectName({ name: e?.target?.value, isFocued: true })
520
546
  onChange(e?.target?.value);
521
547
  setSubmitted(false);
522
548
  }}
@@ -545,7 +571,7 @@ const LoginFormUI = (props: LoginParams) => {
545
571
  icon={theme.images.logos.emailInputIcon}
546
572
  iconColor={theme.colors.arrowColor}
547
573
  onChange={(e: any) => {
548
- setProjectName({...projectName, isFocued: false})
574
+ setProjectName({ ...projectName, isFocued: false })
549
575
  handleChangeInputEmail(e, onChange);
550
576
  }}
551
577
  selectionColor={theme.colors.primary}
@@ -650,40 +676,50 @@ const LoginFormUI = (props: LoginParams) => {
650
676
  </OText>
651
677
  </Pressable>
652
678
  )}
653
-
654
- {enableReCaptcha && (
679
+ {(enableReCaptcha && recaptchaConfig?.version) && (
655
680
  <>
656
- <TouchableOpacity
657
- style={{ marginBottom: 15 }}
658
- onPress={handleOpenRecaptcha}
659
- >
660
- <RecaptchaButton>
661
- {recaptchaVerified ? (
662
- <MaterialCommunityIcons
663
- name="checkbox-marked"
664
- size={26}
665
- color={theme.colors.primary}
666
- />
667
- ) : (
668
- <MaterialCommunityIcons
669
- name="checkbox-blank-outline"
670
- size={26}
671
- color={theme.colors.mediumGray}
672
- />
673
- )}
674
- <OText size={14} mLeft={8}>{t('VERIFY_ReCAPTCHA', 'Verify reCAPTCHA')}</OText>
675
- </RecaptchaButton>
676
- </TouchableOpacity>
677
- <Recaptcha
678
- ref={recaptchaRef}
679
- siteKey={recaptchaConfig?.siteKey}
680
- baseUrl={recaptchaConfig?.baseUrl}
681
- onVerify={onRecaptchaVerify}
682
- onExpire={() => setRecaptchaVerified(false)}
683
- />
681
+ {recaptchaConfig?.version === 'v3' ? (
682
+ <ReCaptcha
683
+ url={recaptchaConfig?.baseUrl}
684
+ siteKey={recaptchaConfig?.siteKey}
685
+ containerStyle={{ height: 40 }}
686
+ onExecute={onRecaptchaVerify}
687
+ reCaptchaType={1}
688
+ />
689
+ ) : (
690
+ <>
691
+ <TouchableOpacity
692
+ style={{ marginBottom: 15 }}
693
+ onPress={handleOpenRecaptcha}
694
+ >
695
+ <RecaptchaButton>
696
+ {recaptchaVerified ? (
697
+ <MaterialCommunityIcons
698
+ name="checkbox-marked"
699
+ size={26}
700
+ color={theme.colors.primary}
701
+ />
702
+ ) : (
703
+ <MaterialCommunityIcons
704
+ name="checkbox-blank-outline"
705
+ size={26}
706
+ color={theme.colors.mediumGray}
707
+ />
708
+ )}
709
+ <OText size={14} mLeft={8}>{t('VERIFY_ReCAPTCHA', 'Verify reCAPTCHA')}</OText>
710
+ </RecaptchaButton>
711
+ </TouchableOpacity>
712
+ <Recaptcha
713
+ ref={recaptchaRef}
714
+ siteKey={recaptchaConfig?.siteKey}
715
+ baseUrl={recaptchaConfig?.baseUrl}
716
+ onVerify={onRecaptchaVerify}
717
+ onExpire={() => setRecaptchaVerified(false)}
718
+ />
719
+ </>)
720
+ }
684
721
  </>
685
- )}
686
-
722
+ )}
687
723
  <OButton
688
724
  onClick={handleLogin}
689
725
  text={t('LOGIN', 'Login')}
@@ -1,10 +1,15 @@
1
- import React, { useEffect, useState } from 'react';
1
+ import React, { useEffect, useState, useRef } from 'react';
2
2
  import { StyleSheet, View } from 'react-native';
3
3
  import { useForm, Controller } from 'react-hook-form';
4
4
  import { useTheme } from 'styled-components/native';
5
+ import { TouchableOpacity } from 'react-native-gesture-handler';
6
+ import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
7
+ import Recaptcha from 'react-native-recaptcha-that-works'
8
+ import ReCaptcha from '@fatnlazycat/react-native-recaptcha-v3'
5
9
 
6
10
  import {
7
11
  LoginForm as LoginFormController,
12
+ useConfig,
8
13
  useLanguage,
9
14
  ToastType,
10
15
  useToast,
@@ -13,7 +18,8 @@ import {
13
18
 
14
19
  import {
15
20
  WelcomeTextContainer,
16
- LogoWrapper
21
+ LogoWrapper,
22
+ RecaptchaButton
17
23
  } from './styles';
18
24
 
19
25
  import { OText, OButton, OInput, OIcon } from '../shared';
@@ -26,14 +32,19 @@ const LoginFormUI = (props: LoginParams) => {
26
32
  loginButtonText,
27
33
  formState,
28
34
  handleButtonLoginClick,
29
- useRootPoint
35
+ useRootPoint,
36
+ handleReCaptcha
30
37
  } = props;
31
38
 
32
39
  const theme = useTheme()
40
+ const [{ configs }] = useConfig()
33
41
  const [ordering, { setOrdering }] = useApi();
34
42
  const [, { showToast }] = useToast();
35
43
  const [, t] = useLanguage();
36
- const {control, handleSubmit, formState: {errors}} = useForm();
44
+ const [recaptchaConfig, setRecaptchaConfig] = useState<any>({})
45
+ const [recaptchaVerified, setRecaptchaVerified] = useState(false)
46
+ const recaptchaRef = useRef<any>({});
47
+ const { control, handleSubmit, formState: { errors } } = useForm();
37
48
  const [orientationState] = useDeviceOrientation();
38
49
 
39
50
  const [formsStateValues, setFormsStateValues] = useState<any>({ isSubmitted: false })
@@ -60,6 +71,25 @@ const LoginFormUI = (props: LoginParams) => {
60
71
  onChange(value.toLowerCase().replace(/[&,()%";:ç?<>{}\\[\]\s]/g, ''));
61
72
  };
62
73
 
74
+ const handleOpenRecaptcha = () => {
75
+ setRecaptchaVerified(false)
76
+ if (!recaptchaConfig?.siteKey) {
77
+ showToast(ToastType.Error, t('NO_RECAPTCHA_SITE_KEY', 'The config doesn\'t have recaptcha site key'));
78
+ return
79
+ }
80
+ if (!recaptchaConfig?.baseUrl) {
81
+ showToast(ToastType.Error, t('NO_RECAPTCHA_BASE_URL', 'The config doesn\'t have recaptcha base url'));
82
+ return
83
+ }
84
+
85
+ recaptchaRef.current.open()
86
+ }
87
+
88
+ const onRecaptchaVerify = (token: any) => {
89
+ setRecaptchaVerified(true)
90
+ handleReCaptcha && handleReCaptcha({ code: token, version: recaptchaConfig?.version })
91
+ }
92
+
63
93
  const styles = StyleSheet.create({
64
94
  logo: {
65
95
  height: 80,
@@ -87,6 +117,20 @@ const LoginFormUI = (props: LoginParams) => {
87
117
 
88
118
  useEffect(() => {
89
119
  if (!formState.loading && formState.result?.error) {
120
+ if (formState.result?.result?.[0] === 'ERROR_AUTH_VERIFICATION_CODE') {
121
+ setRecaptchaVerified(false)
122
+ setRecaptchaConfig({
123
+ version: 'v2',
124
+ siteKey: configs?.security_recaptcha_site_key?.value || null,
125
+ baseUrl: configs?.security_recaptcha_base_url?.value || null
126
+ })
127
+ showToast(ToastType.Info, t('TRY_AGAIN', 'Please try again'))
128
+ setFormsStateValues({
129
+ ...formsStateValues,
130
+ isSubmitted: false,
131
+ })
132
+ return
133
+ }
90
134
  formState.result?.result && showToast(
91
135
  ToastType.Error,
92
136
  typeof formState.result?.result === 'string'
@@ -122,9 +166,32 @@ const LoginFormUI = (props: LoginParams) => {
122
166
  }
123
167
  }, [errors]);
124
168
 
169
+ useEffect(() => {
170
+ if (configs && Object.keys(configs).length > 0) {
171
+ if (configs?.security_recaptcha_type?.value === 'v3' &&
172
+ configs?.security_recaptcha_score_v3?.value > 0 &&
173
+ configs?.security_recaptcha_site_key_v3?.value
174
+ ) {
175
+ setRecaptchaConfig({
176
+ version: 'v3',
177
+ siteKey: configs?.security_recaptcha_site_key_v3?.value || null,
178
+ baseUrl: configs?.security_recaptcha_base_url?.value || null
179
+ })
180
+ return
181
+ }
182
+ if (configs?.security_recaptcha_site_key?.value) {
183
+ setRecaptchaConfig({
184
+ version: 'v2',
185
+ siteKey: configs?.security_recaptcha_site_key?.value || null,
186
+ baseUrl: configs?.security_recaptcha_base_url?.value || null
187
+ })
188
+ }
189
+ }
190
+ }, [configs])
191
+
125
192
  const logo = (
126
193
  <LogoWrapper>
127
- <OIcon src={theme.images.logos.logotype} style={styles.logo}/>
194
+ <OIcon src={theme.images.logos.logotype} style={styles.logo} />
128
195
  </LogoWrapper>
129
196
  );
130
197
 
@@ -144,7 +211,7 @@ const LoginFormUI = (props: LoginParams) => {
144
211
  value={value}
145
212
  autoCapitalize='none'
146
213
  autoCorrect={false}
147
- inputStyle={{textAlign: 'center'}}
214
+ inputStyle={{ textAlign: 'center' }}
148
215
  onChange={(e: any) => {
149
216
  onChange(e?.target?.value);
150
217
  setFormsStateValues({
@@ -167,7 +234,7 @@ const LoginFormUI = (props: LoginParams) => {
167
234
  autoCapitalize="none"
168
235
  autoCorrect={false}
169
236
  type="email-address"
170
- inputStyle={{textAlign: 'center'}}
237
+ inputStyle={{ textAlign: 'center' }}
171
238
  onChange={(e: any) => {
172
239
  handleChangeInputEmail(e, onChange);
173
240
  }}
@@ -199,7 +266,7 @@ const LoginFormUI = (props: LoginParams) => {
199
266
  style={styles.inputStyle}
200
267
  value={value}
201
268
  onChange={(val: any) => onChange(val)}
202
- inputStyle={{textAlign: 'center'}}
269
+ inputStyle={{ textAlign: 'center' }}
203
270
  />
204
271
  )}
205
272
  name="password"
@@ -211,7 +278,49 @@ const LoginFormUI = (props: LoginParams) => {
211
278
  }}
212
279
  defaultValue=""
213
280
  />
214
-
281
+ {(recaptchaConfig?.version) && (
282
+ <>
283
+ {recaptchaConfig?.version === 'v3' ? (
284
+ <ReCaptcha
285
+ url={recaptchaConfig?.baseUrl}
286
+ siteKey={recaptchaConfig?.siteKey}
287
+ containerStyle={{ height: 40 }}
288
+ onExecute={onRecaptchaVerify}
289
+ reCaptchaType={1}
290
+ />
291
+ ) : (
292
+ <>
293
+ <TouchableOpacity
294
+ onPress={handleOpenRecaptcha}
295
+ >
296
+ <RecaptchaButton>
297
+ {recaptchaVerified ? (
298
+ <MaterialCommunityIcons
299
+ name="checkbox-marked"
300
+ size={26}
301
+ color={theme.colors.primary}
302
+ />
303
+ ) : (
304
+ <MaterialCommunityIcons
305
+ name="checkbox-blank-outline"
306
+ size={26}
307
+ color={theme.colors.mediumGray}
308
+ />
309
+ )}
310
+ <OText size={14} mLeft={8}>{t('VERIFY_ReCAPTCHA', 'Verify reCAPTCHA')}</OText>
311
+ </RecaptchaButton>
312
+ </TouchableOpacity>
313
+ <Recaptcha
314
+ ref={recaptchaRef}
315
+ siteKey={recaptchaConfig?.siteKey}
316
+ baseUrl={recaptchaConfig?.baseUrl}
317
+ onVerify={onRecaptchaVerify}
318
+ onExpire={() => setRecaptchaVerified(false)}
319
+ />
320
+ </>)
321
+ }
322
+ </>
323
+ )}
215
324
  <OButton
216
325
  onClick={handleSubmit(onSubmit)}
217
326
  text={loginButtonText}
@@ -280,7 +389,7 @@ const LoginFormUI = (props: LoginParams) => {
280
389
  ? 0 : 0,
281
390
  }}
282
391
  >
283
- { welcome }
392
+ {welcome}
284
393
  {orientationState?.orientation === LANDSCAPE && (
285
394
  <View style={{
286
395
  justifyContent: 'flex-end',
@@ -327,6 +436,7 @@ export const LoginForm = (props: any) => {
327
436
  const loginProps = {
328
437
  ...props,
329
438
  UIComponent: LoginFormUI,
439
+ isRecaptchaEnable: true
330
440
  };
331
441
  return <LoginFormController {...loginProps} />;
332
442
  };
@@ -7,3 +7,8 @@ export const LogoWrapper = styled.View`
7
7
  export const WelcomeTextContainer = styled.View`
8
8
  margin-bottom: 30px;
9
9
  `;
10
+ export const RecaptchaButton = styled.View`
11
+ flex-direction: row;
12
+ align-items: center;
13
+ margin-bottom: 10px;
14
+ `
@@ -82,6 +82,7 @@ export interface LoginParams {
82
82
  handleSendVerifyCode?: any;
83
83
  handleCheckPhoneCode?: any;
84
84
  useRootPoint?: any;
85
+ handleReCaptcha?: (vlaue: any) => void;
85
86
  }
86
87
 
87
88
  export interface ProductItemAccordionParams {
@@ -368,6 +368,7 @@ export const BusinessListingSearchUI = (props: BusinessSearchParams) => {
368
368
  key={business.id}
369
369
  business={business}
370
370
  isBusinessOpen={business.open}
371
+ enableIntersection={false}
371
372
  handleCustomClick={() => onBusinessClick(business)}
372
373
  handleUpdateBusinessList={handleUpdateBusinessList}
373
374
  orderType={orderState?.options?.type}
@@ -439,11 +440,12 @@ export const BusinessListingSearchUI = (props: BusinessSearchParams) => {
439
440
  key={product?.id}
440
441
  isSoldOut={(product.inventoried && !product.quantity)}
441
442
  product={product}
443
+ enableIntersection={false}
442
444
  businessId={business?.id}
443
445
  onProductClick={(product: any) => onProductClick(business, category?.id, product?.id)}
444
446
  productAddedToCartLength={0}
445
447
  handleUpdateProducts={(productId: number, changes: any) => handleUpdateProducts(productId, category?.id, business?.id, changes)}
446
- style={{ width: screenWidth - 120, marginRight: i === category?.products?.length - 1 ? 0 : 20 }}
448
+ style={{ width: screenWidth - 80, maxWidth: screenWidth - 80, marginRight: 20 }}
447
449
  />
448
450
  )))}
449
451
 
@@ -0,0 +1,87 @@
1
+ import React from 'react'
2
+ import { useTheme } from 'styled-components/native'
3
+ import { StyleSheet } from 'react-native'
4
+ import { SubCategoriesContainer, ContainerButton } from './styles'
5
+ import { OButton } from '../../shared'
6
+ import { useLanguage } from 'ordering-components/native'
7
+
8
+ function SubcategoriesComponentPropsAreEqual(prev: any, next: any) {
9
+ return prev.subcategoriesSelected === next.subcategoriesSelected &&
10
+ prev.category === next.category
11
+ }
12
+
13
+ interface SubcategoriesComponentParams {
14
+ subcategoriesSelected?: any,
15
+ category?: any,
16
+ onClickSubcategory: any
17
+ }
18
+
19
+ const SubcategoriesComponent = (props : SubcategoriesComponentParams) => {
20
+ const {
21
+ subcategoriesSelected,
22
+ category,
23
+ onClickSubcategory
24
+ } = props
25
+
26
+ const theme = useTheme()
27
+ const [, t] = useLanguage()
28
+ const allsubcategorySelected = !subcategoriesSelected?.some((subcategory: any) => category?.id === subcategory?.parent_category_id)
29
+
30
+ const bpStyles = StyleSheet.create({
31
+ catWrap: { flexDirection: 'row', alignItems: 'center', marginBottom: 19 },
32
+ catIcon: {
33
+ borderRadius: 7.6,
34
+ shadowColor: '#000000',
35
+ shadowOpacity: 0.1,
36
+ shadowOffset: { width: 0, height: 0 },
37
+ shadowRadius: 1,
38
+ marginEnd: 13,
39
+ },
40
+ categoryButtonStyle: {
41
+ borderWidth: 0,
42
+ marginLeft: 5,
43
+ marginRight: 5,
44
+ marginBottom: 10,
45
+ height: 35,
46
+ paddingLeft: 3,
47
+ paddingRight: 3,
48
+ }
49
+ });
50
+
51
+
52
+ return (
53
+ <SubCategoriesContainer>
54
+ <ContainerButton
55
+ isSelected={allsubcategorySelected}
56
+ >
57
+ <OButton
58
+ onClick={() => onClickSubcategory(null, category)}
59
+ bgColor={allsubcategorySelected ? theme.colors.primary : theme.colors.backgroundGray}
60
+ text={`${t('ALL', 'All')} ${allsubcategorySelected ? 'X' : ''}`}
61
+ style={bpStyles.categoryButtonStyle}
62
+ textStyle={{ color: allsubcategorySelected ? theme.colors.white : theme.colors.textNormal, fontSize: 12 }}
63
+ />
64
+ </ContainerButton>
65
+ {category?.subcategories?.map((subcategory: any) => {
66
+ const isSubcategorySelected = subcategoriesSelected?.find((_subcategory: any) => _subcategory?.id === subcategory?.id)
67
+ return (
68
+ <ContainerButton
69
+ key={subcategory?.id}
70
+ isSelected={isSubcategorySelected}
71
+ >
72
+ <OButton
73
+ onClick={() => onClickSubcategory(subcategory, category)}
74
+ bgColor={isSubcategorySelected ? theme.colors.primary : theme.colors.backgroundGray}
75
+ text={`${subcategory?.name} ${isSubcategorySelected ? 'X' : ''}`}
76
+ style={bpStyles.categoryButtonStyle}
77
+ textStyle={{ color: isSubcategorySelected ? theme.colors.white : theme.colors.textNormal, fontSize: 12 }}
78
+ />
79
+ </ContainerButton>
80
+ )
81
+ }
82
+ )}
83
+ </SubCategoriesContainer>
84
+ )
85
+ }
86
+
87
+ export const SubcategoriesComponentMemoized = React.memo(SubcategoriesComponent, SubcategoriesComponentPropsAreEqual)
@@ -0,0 +1,12 @@
1
+ import styled from "styled-components/native";
2
+
3
+ export const SubCategoriesContainer = styled.View`
4
+ flex-direction: row;
5
+ flex-wrap: wrap;
6
+ margin-bottom: 10px;
7
+ `
8
+
9
+ export const ContainerButton = styled.View`
10
+ `
11
+
12
+ export const HeaderWrapper = styled.View``
@@ -12,6 +12,7 @@ import { useTheme } from 'styled-components/native';
12
12
  import { shape } from '../../utils'
13
13
  import { CategoryDescriptionMemoized } from './CategoryDescription';
14
14
  import { OrderItAgain } from '../OrderItAgain'
15
+ import { SubcategoriesComponentMemoized } from './SubcategoriesComponent';
15
16
 
16
17
  const BusinessProductsListUI = (props: BusinessProductsListParams) => {
17
18
  const {
@@ -70,49 +71,15 @@ const BusinessProductsListUI = (props: BusinessProductsListParams) => {
70
71
  }
71
72
  }
72
73
 
73
- const SubcategoriesComponent = ({ category }: any) => {
74
- const allsubcategorySelected = !subcategoriesSelected?.some((subcategory: any) => category?.id === subcategory?.parent_category_id)
75
-
76
- return (
77
- <SubCategoriesContainer>
78
- <ContainerButton
79
- isSelected={allsubcategorySelected}
80
- >
81
- <OButton
82
- onClick={() => onClickSubcategory(null, category)}
83
- bgColor={allsubcategorySelected ? theme.colors.primary : theme.colors.backgroundGray}
84
- text={`${t('ALL', 'All')} ${allsubcategorySelected ? 'X' : ''}`}
85
- style={bpStyles.categoryButtonStyle}
86
- textStyle={{ color: allsubcategorySelected ? theme.colors.white : theme.colors.textNormal, fontSize: 12 }}
87
- />
88
- </ContainerButton>
89
- {category?.subcategories?.map((subcategory: any) => {
90
- const isSubcategorySelected = subcategoriesSelected?.find((_subcategory: any) => _subcategory?.id === subcategory?.id)
91
- return (
92
- <ContainerButton
93
- key={subcategory?.id}
94
- isSelected={isSubcategorySelected}
95
- >
96
- <OButton
97
- onClick={() => onClickSubcategory(subcategory, category)}
98
- bgColor={isSubcategorySelected ? theme.colors.primary : theme.colors.backgroundGray}
99
- text={`${subcategory?.name} ${isSubcategorySelected ? 'X' : ''}`}
100
- style={bpStyles.categoryButtonStyle}
101
- textStyle={{ color: isSubcategorySelected ? theme.colors.white : theme.colors.textNormal, fontSize: 12 }}
102
- />
103
- </ContainerButton>
104
- )
105
- }
106
- )}
107
- </SubCategoriesContainer>
108
- )
109
- }
110
-
111
74
  return (
112
75
  <ProductsContainer renderToHardwareTextureAndroid={categoryState.loading || isBusinessLoading}>
113
76
  <HeaderWrapper>
114
77
  {category?.subcategories?.length > 0 && (
115
- <SubcategoriesComponent category={category} />
78
+ <SubcategoriesComponentMemoized
79
+ category={category}
80
+ subcategoriesSelected={subcategoriesSelected}
81
+ onClickSubcategory={onClickSubcategory}
82
+ />
116
83
  )}
117
84
  </HeaderWrapper>
118
85
  {previouslyProducts?.length > 0 && (
@@ -136,7 +103,7 @@ const BusinessProductsListUI = (props: BusinessProductsListParams) => {
136
103
  <SingleProductCard
137
104
  key={'prod_' + product.id + `_${i}`}
138
105
  isSoldOut={product.inventoried && !product.quantity}
139
- enableIntersection={!isFiltMode}
106
+ enableIntersection={!isFiltMode && categoryState.products?.length < 80}
140
107
  product={product}
141
108
  businessId={businessId}
142
109
  categoryState={categoryState}
@@ -164,7 +131,7 @@ const BusinessProductsListUI = (props: BusinessProductsListParams) => {
164
131
  key={'feat_' + product.id + `_${i}`}
165
132
  isSoldOut={product.inventoried && !product.quantity}
166
133
  product={product}
167
- enableIntersection={!isFiltMode}
134
+ enableIntersection={!isFiltMode && categoryState.products?.length < 80}
168
135
  businessId={businessId}
169
136
  categoryState={categoryState}
170
137
  onProductClick={onProductClick}
@@ -251,13 +218,17 @@ const BusinessProductsListUI = (props: BusinessProductsListParams) => {
251
218
  </View>
252
219
  )}
253
220
  {category?.subcategories?.length > 0 && !isFiltMode && (
254
- <SubcategoriesComponent category={category} />
221
+ <SubcategoriesComponentMemoized
222
+ category={category}
223
+ subcategoriesSelected={subcategoriesSelected}
224
+ onClickSubcategory={onClickSubcategory}
225
+ />
255
226
  )}
256
227
  <>
257
228
  {products.sort((a: any, b: any) => a.rank - b.rank).map((product: any, i: any) => (
258
229
  <SingleProductCard
259
230
  key={`${product?.id}_${i}`}
260
- enableIntersection={!isFiltMode}
231
+ enableIntersection={!isFiltMode && categoryState.products?.length < 80}
261
232
  isSoldOut={product.inventoried && !product.quantity}
262
233
  businessId={businessId}
263
234
  product={product}
@@ -279,7 +250,7 @@ const BusinessProductsListUI = (props: BusinessProductsListParams) => {
279
250
  <>
280
251
  {[...Array(categoryState?.pagination?.nextPageItems).keys()].map(
281
252
  (item, i) => (
282
- <View style={{ minHeight: 165, marginBottom: 28, padding: 12 }}>
253
+ <View style={{ minHeight: 165, marginBottom: 28, padding: 12 }} key={i}>
283
254
  <Placeholder style={{ padding: 5 }} Animation={Fade}>
284
255
  <View style={{ flexDirection: 'row' }}>
285
256
  <Placeholder style={{ paddingVertical: 10, flex: 1 }}>
@@ -363,6 +363,7 @@ const BusinessProductsListingUI = (props: BusinessProductsListingParams) => {
363
363
  onScroll={handlePageScroll}
364
364
  onScrollBeginDrag={handleTouchDrag}
365
365
  scrollEventThrottle={16}
366
+ bounces={false}
366
367
  >
367
368
  <BusinessBasicInformation
368
369
  navigation={navigation}
@@ -445,9 +445,9 @@ const BusinessesListingUI = (props: BusinessesListingParams) => {
445
445
  </View>
446
446
  {!isChewLayout ? (
447
447
  <HeaderWrapper
448
- source={theme.images.general.homeHero}
448
+ source={theme.images.backgrounds.business_list_header}
449
449
  style={{ paddingTop: top + 20 }}
450
- resizeMode='stretch'
450
+ resizeMode='cover'
451
451
  >
452
452
  {!auth && (
453
453
  <TouchableOpacity onPress={() => navigation?.canGoBack() && navigation.goBack()} style={{ position: 'absolute', marginStart: 40, paddingVertical: 20 }}>
@@ -93,6 +93,7 @@ export const BusinessesListing = (props: any) => {
93
93
  bottomContainerStyle={{ height: 'auto', borderRadius: 10 }}
94
94
  titleStyle={{ textAlign: 'center' }}
95
95
  closeIcon={theme.images.general.close}
96
+ presentationStyle='overFullScreen'
96
97
  >
97
98
  {lastOrderReview?.order && <ReviewTrigger order={lastOrderReview?.order} handleOpenOrderReview={handleOpenOrderReview} />}
98
99
  </OBottomPopup>
@@ -106,8 +106,7 @@ const CheckoutUI = (props: any) => {
106
106
  padding: 20
107
107
  },
108
108
  pagePadding: {
109
- paddingLeft: 40,
110
- paddingRight: 40
109
+ paddingHorizontal: 40
111
110
  },
112
111
  icon: {
113
112
  top: 15,
@@ -76,7 +76,6 @@ export const ChCart = styled(ChPaymethods)``
76
76
 
77
77
  export const WalletPaymentOptionContainer = styled(ChPaymethods)`
78
78
  padding-bottom: 0;
79
- margin-left: -20px;
80
79
  `
81
80
 
82
81
  export const ChPlaceOrderBtn = styled.View`
@@ -5,6 +5,7 @@ import { useForm, Controller } from 'react-hook-form';
5
5
  import { PhoneInputNumber } from '../PhoneInputNumber';
6
6
  import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
7
7
  import Recaptcha from 'react-native-recaptcha-that-works'
8
+ import ReCaptcha from '@fatnlazycat/react-native-recaptcha-v3'
8
9
 
9
10
  import {
10
11
  LoginForm as LoginFormController,
@@ -235,7 +236,7 @@ const LoginFormUI = (props: LoginParams) => {
235
236
 
236
237
  const onRecaptchaVerify = (token: any) => {
237
238
  setRecaptchaVerified(true)
238
- handleReCaptcha(token)
239
+ handleReCaptcha({ code: token, version: recaptchaConfig?.version })
239
240
  }
240
241
 
241
242
  const handleChangeOtpType = (type: string) => {
@@ -272,15 +273,39 @@ const LoginFormUI = (props: LoginParams) => {
272
273
 
273
274
  useEffect(() => {
274
275
  if (configs && Object.keys(configs).length > 0 && enableReCaptcha) {
275
- setRecaptchaConfig({
276
- siteKey: configs?.security_recaptcha_site_key?.value || null,
277
- baseUrl: configs?.security_recaptcha_base_url?.value || null
278
- })
276
+ if (configs?.security_recaptcha_type?.value === 'v3' &&
277
+ configs?.security_recaptcha_score_v3?.value > 0 &&
278
+ configs?.security_recaptcha_site_key_v3?.value
279
+ ) {
280
+ setRecaptchaConfig({
281
+ version: 'v3',
282
+ siteKey: configs?.security_recaptcha_site_key_v3?.value || null,
283
+ baseUrl: configs?.security_recaptcha_base_url?.value || null
284
+ })
285
+ return
286
+ }
287
+ if (configs?.security_recaptcha_site_key?.value) {
288
+ setRecaptchaConfig({
289
+ version: 'v2',
290
+ siteKey: configs?.security_recaptcha_site_key?.value || null,
291
+ baseUrl: configs?.security_recaptcha_base_url?.value || null
292
+ })
293
+ }
279
294
  }
280
295
  }, [configs, enableReCaptcha])
281
296
 
282
297
  useEffect(() => {
283
298
  if (!formState.loading && formState.result?.error) {
299
+ if (formState.result?.result?.[0] === 'ERROR_AUTH_VERIFICATION_CODE') {
300
+ setRecaptchaVerified(false)
301
+ setRecaptchaConfig({
302
+ version: 'v2',
303
+ siteKey: configs?.security_recaptcha_site_key?.value || null,
304
+ baseUrl: configs?.security_recaptcha_base_url?.value || null
305
+ })
306
+ showToast(ToastType.Info, t('TRY_AGAIN', 'Please try again'))
307
+ return
308
+ }
284
309
  formState.result?.result &&
285
310
  showToast(
286
311
  ToastType.Error,
@@ -596,35 +621,47 @@ const LoginFormUI = (props: LoginParams) => {
596
621
  </TouchableOpacity>
597
622
  )}
598
623
 
599
- {enableReCaptcha && (
624
+ {(enableReCaptcha && recaptchaConfig?.version) && (
600
625
  <>
601
- <TouchableOpacity
602
- onPress={handleOpenRecaptcha}
603
- >
604
- <RecaptchaButton>
605
- {recaptchaVerified ? (
606
- <MaterialCommunityIcons
607
- name="checkbox-marked"
608
- size={26}
609
- color={theme.colors.primary}
610
- />
611
- ) : (
612
- <MaterialCommunityIcons
613
- name="checkbox-blank-outline"
614
- size={26}
615
- color={theme.colors.mediumGray}
616
- />
617
- )}
618
- <OText size={14} mLeft={8}>{t('VERIFY_ReCAPTCHA', 'Verify reCAPTCHA')}</OText>
619
- </RecaptchaButton>
620
- </TouchableOpacity>
621
- <Recaptcha
622
- ref={recaptchaRef}
623
- siteKey={recaptchaConfig?.siteKey}
624
- baseUrl={recaptchaConfig?.baseUrl}
625
- onVerify={onRecaptchaVerify}
626
- onExpire={() => setRecaptchaVerified(false)}
627
- />
626
+ {recaptchaConfig?.version === 'v3' ? (
627
+ <ReCaptcha
628
+ url={recaptchaConfig?.baseUrl}
629
+ siteKey={recaptchaConfig?.siteKey}
630
+ containerStyle={{ height: 40 }}
631
+ onExecute={onRecaptchaVerify}
632
+ reCaptchaType={1}
633
+ />
634
+ ) : (
635
+ <>
636
+ <TouchableOpacity
637
+ onPress={handleOpenRecaptcha}
638
+ >
639
+ <RecaptchaButton>
640
+ {recaptchaVerified ? (
641
+ <MaterialCommunityIcons
642
+ name="checkbox-marked"
643
+ size={26}
644
+ color={theme.colors.primary}
645
+ />
646
+ ) : (
647
+ <MaterialCommunityIcons
648
+ name="checkbox-blank-outline"
649
+ size={26}
650
+ color={theme.colors.mediumGray}
651
+ />
652
+ )}
653
+ <OText size={14} mLeft={8}>{t('VERIFY_ReCAPTCHA', 'Verify reCAPTCHA')}</OText>
654
+ </RecaptchaButton>
655
+ </TouchableOpacity>
656
+ <Recaptcha
657
+ ref={recaptchaRef}
658
+ siteKey={recaptchaConfig?.siteKey}
659
+ baseUrl={recaptchaConfig?.baseUrl}
660
+ onVerify={onRecaptchaVerify}
661
+ onExpire={() => setRecaptchaVerified(false)}
662
+ />
663
+ </>)
664
+ }
628
665
  </>
629
666
  )}
630
667
  <OButton
@@ -7,6 +7,7 @@ import CheckBox from '@react-native-community/checkbox';
7
7
  import { PhoneInputNumber } from '../PhoneInputNumber';
8
8
  import { FacebookLogin } from '../FacebookLogin';
9
9
  import Recaptcha from 'react-native-recaptcha-that-works'
10
+ import ReCaptcha from '@fatnlazycat/react-native-recaptcha-v3'
10
11
 
11
12
  import {
12
13
  SignupForm as SignUpController,
@@ -347,20 +348,44 @@ const SignupFormUI = (props: SignupParams) => {
347
348
 
348
349
  const onRecaptchaVerify = (token: any) => {
349
350
  setRecaptchaVerified(true)
350
- handleReCaptcha && handleReCaptcha(token)
351
+ handleReCaptcha && handleReCaptcha({ code: token, version: recaptchaConfig?.version })
351
352
  }
352
353
 
353
354
  useEffect(() => {
354
355
  if (configs && Object.keys(configs).length > 0 && enableReCaptcha) {
355
- setRecaptchaConfig({
356
- siteKey: configs?.security_recaptcha_site_key?.value || null,
357
- baseUrl: configs?.security_recaptcha_base_url?.value || null
358
- })
356
+ if (configs?.security_recaptcha_type?.value === 'v3' &&
357
+ configs?.security_recaptcha_score_v3?.value > 0 &&
358
+ configs?.security_recaptcha_site_key_v3?.value
359
+ ) {
360
+ setRecaptchaConfig({
361
+ version: 'v3',
362
+ siteKey: configs?.security_recaptcha_site_key_v3?.value || null,
363
+ baseUrl: configs?.security_recaptcha_base_url?.value || null
364
+ })
365
+ return
366
+ }
367
+ if (configs?.security_recaptcha_site_key?.value) {
368
+ setRecaptchaConfig({
369
+ version: 'v2',
370
+ siteKey: configs?.security_recaptcha_site_key?.value || null,
371
+ baseUrl: configs?.security_recaptcha_base_url?.value || null
372
+ })
373
+ }
359
374
  }
360
375
  }, [configs, enableReCaptcha])
361
376
 
362
377
  useEffect(() => {
363
378
  if (!formState.loading && formState.result?.error) {
379
+ if (formState.result?.result?.[0] === 'ERROR_AUTH_VERIFICATION_CODE') {
380
+ setRecaptchaVerified(false)
381
+ setRecaptchaConfig({
382
+ version: 'v2',
383
+ siteKey: configs?.security_recaptcha_site_key?.value || null,
384
+ baseUrl: configs?.security_recaptcha_base_url?.value || null
385
+ })
386
+ showToast(ToastType.Info, t('TRY_AGAIN', 'Please try again'))
387
+ return
388
+ }
364
389
  formState.result?.result &&
365
390
  showToast(ToastType.Error, formState.result?.result[0]);
366
391
  setIsLoadingVerifyModal(false);
@@ -627,36 +652,49 @@ const SignupFormUI = (props: SignupParams) => {
627
652
  </View>
628
653
  )}
629
654
 
630
- {enableReCaptcha && (
655
+ {(enableReCaptcha && recaptchaConfig?.version) && (
631
656
  <>
632
- <TouchableOpacity
633
- onPress={handleOpenRecaptcha}
634
- style={{ marginHorizontal: 4, marginBottom: 10 }}
635
- >
636
- <RecaptchaButton>
637
- {recaptchaVerified ? (
638
- <MaterialCommunityIcons
639
- name="checkbox-marked"
640
- size={23}
641
- color={theme.colors.primary}
642
- />
643
- ) : (
644
- <MaterialCommunityIcons
645
- name="checkbox-blank-outline"
646
- size={23}
647
- color={theme.colors.disabled}
648
- />
649
- )}
650
- <OText size={14} mLeft={8}>{t('VERIFY_ReCAPTCHA', 'Verify reCAPTCHA')}</OText>
651
- </RecaptchaButton>
652
- </TouchableOpacity>
653
- <Recaptcha
654
- ref={recaptchaRef}
655
- siteKey={recaptchaConfig?.siteKey}
656
- baseUrl={recaptchaConfig?.baseUrl}
657
- onVerify={onRecaptchaVerify}
658
- onExpire={() => setRecaptchaVerified(false)}
659
- />
657
+ {recaptchaConfig?.version === 'v3' ? (
658
+ <ReCaptcha
659
+ url={recaptchaConfig?.baseUrl}
660
+ siteKey={recaptchaConfig?.siteKey}
661
+ containerStyle={{ height: 40 }}
662
+ onExecute={onRecaptchaVerify}
663
+ reCaptchaType={1}
664
+ />
665
+ ) : (
666
+ <>
667
+ <TouchableOpacity
668
+ onPress={handleOpenRecaptcha}
669
+ style={{ marginHorizontal: 4, marginBottom: 10 }}
670
+ >
671
+ <RecaptchaButton>
672
+ {recaptchaVerified ? (
673
+ <MaterialCommunityIcons
674
+ name="checkbox-marked"
675
+ size={23}
676
+ color={theme.colors.primary}
677
+ />
678
+ ) : (
679
+ <MaterialCommunityIcons
680
+ name="checkbox-blank-outline"
681
+ size={23}
682
+ color={theme.colors.disabled}
683
+ />
684
+ )}
685
+ <OText size={14} mLeft={8}>{t('VERIFY_ReCAPTCHA', 'Verify reCAPTCHA')}</OText>
686
+ </RecaptchaButton>
687
+ </TouchableOpacity>
688
+ <Recaptcha
689
+ ref={recaptchaRef}
690
+ siteKey={recaptchaConfig?.siteKey}
691
+ baseUrl={recaptchaConfig?.baseUrl}
692
+ onVerify={onRecaptchaVerify}
693
+ onExpire={() => setRecaptchaVerified(false)}
694
+ />
695
+ </>
696
+ )}
697
+
660
698
  </>
661
699
  )}
662
700
  {(signUpTab === 'default') && (
@@ -42,7 +42,7 @@ const SingleProductCardUI = React.memo((props: SingleProductCardParams) => {
42
42
  const theme = useTheme();
43
43
  const hideAddButton = theme?.business_view?.components?.products?.components?.add_to_cart_button?.hidden ?? true
44
44
 
45
- const fadeAnim = useRef(new Animated.Value(0)).current;
45
+ const fadeAnim = useRef(new Animated.Value(enableIntersection ? 0 : 1)).current;
46
46
 
47
47
  const styles = StyleSheet.create({
48
48
  container: {
@@ -50,7 +50,7 @@ const SingleProductCardUI = React.memo((props: SingleProductCardParams) => {
50
50
  borderRadius: 7.6,
51
51
  borderColor: theme.colors.border,
52
52
  marginBottom: 28,
53
- minHeight: 165
53
+ minHeight: hideAddButton ? 100 : 165
54
54
  },
55
55
  titleWrapper: {
56
56
  flexDirection: 'row',
@@ -123,7 +123,7 @@ const SingleProductCardUI = React.memo((props: SingleProductCardParams) => {
123
123
  maxCartProductConfig,
124
124
  maxCartProductInventory,
125
125
  );
126
-
126
+
127
127
  const fadeIn = () => {
128
128
  Animated.timing(fadeAnim, {
129
129
  toValue: 1,
@@ -141,16 +141,18 @@ const SingleProductCardUI = React.memo((props: SingleProductCardParams) => {
141
141
  }
142
142
 
143
143
  const handleChangeIntersection = () => {
144
- setIsIntersectionObserver(true);
145
- fadeIn();
144
+ if (enableIntersection) {
145
+ setIsIntersectionObserver(true);
146
+ fadeIn();
147
+ }
146
148
  }
147
149
 
148
150
  useEffect(() => {
149
- if (!enableIntersection) fadeIn()
151
+ if (enableIntersection) fadeIn()
150
152
  }, [enableIntersection])
151
153
 
152
154
  return (
153
- <InView style={{ minHeight: 200 }} triggerOnce={true} onChange={(inView: boolean) => handleChangeIntersection()}>
155
+ <InView style={{ minHeight: hideAddButton ? 125 : 165 }} triggerOnce={true} onChange={(inView: boolean) => handleChangeIntersection()}>
154
156
  {isIntersectionObserver ? (
155
157
  <CardContainer
156
158
  showAddButton={!hideAddButton}
@@ -164,7 +166,7 @@ const SingleProductCardUI = React.memo((props: SingleProductCardParams) => {
164
166
  <View style={{ flexDirection: 'row' }}>
165
167
  {productAddedToCartLength > 0 && (
166
168
  <QuantityContainer style={[styles.quantityContainer, {
167
- transform: [{ translateX: 25 }, { translateY: -55 }],
169
+ transform: [{ translateX: 25 }, { translateY: hideAddButton ? -25 : -55 }],
168
170
  }]}>
169
171
  <OText size={12} color={theme.colors.white}>{productAddedToCartLength.toString()}</OText>
170
172
  </QuantityContainer>
@@ -281,7 +283,7 @@ const SingleProductCardUI = React.memo((props: SingleProductCardParams) => {
281
283
  )}
282
284
  </CardContainer>
283
285
  ) : (
284
- <View style={{ minHeight: 165, marginBottom: 28, padding: 12 }}>
286
+ <View style={{ marginBottom: 28, padding: 12, height: hideAddButton ? 125 : 165 }}>
285
287
  <Placeholder style={{ padding: 5 }} Animation={Fade}>
286
288
  <View style={{ flexDirection: 'row' }}>
287
289
  <Placeholder style={{ paddingVertical: 10, flex: 1 }}>
@@ -14,6 +14,7 @@ interface Props {
14
14
  bottomContainerStyle?: any;
15
15
  titleStyle?: any;
16
16
  closeIcon?: any;
17
+ presentationStyle?: "fullScreen" | "pageSheet" | "formSheet" | "overFullScreen" | undefined
17
18
  }
18
19
  const OBottomPopup = (props: Props) => {
19
20
  const {
@@ -25,17 +26,18 @@ const OBottomPopup = (props: Props) => {
25
26
  isStatusBar,
26
27
  titleStyle,
27
28
  bottomContainerStyle,
28
- closeIcon
29
+ closeIcon,
30
+ presentationStyle
29
31
  } = props
30
32
  const { top, bottom } = useSafeAreaInsets();
31
-
33
+
32
34
  return (
33
35
  <Modal
34
36
  animationType='slide'
35
37
  transparent={transparent}
36
38
  visible={open}
37
39
  onRequestClose={() => onClose()}
38
- presentationStyle={'fullScreen'}
40
+ presentationStyle={presentationStyle || 'fullScreen'}
39
41
  >
40
42
  {isStatusBar && <StatusBar translucent={false} />}
41
43
  <View style={styles.container}>