ordering-ui-react-native 0.15.49 → 0.15.52

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.49",
3
+ "version": "0.15.52",
4
4
  "description": "Reusable components made in react native",
5
5
  "main": "src/index.tsx",
6
6
  "author": "ordering.inc",
@@ -99,6 +99,7 @@
99
99
  "react-native-picker-select": "^8.0.4",
100
100
  "react-native-print": "^0.9.0",
101
101
  "react-native-reanimated": "^1.13.1",
102
+ "react-native-recaptcha-that-works": "^1.2.0",
102
103
  "react-native-restart": "^0.0.22",
103
104
  "react-native-safe-area-context": "^3.1.8",
104
105
  "react-native-screens": "^2.11.0",
@@ -1,6 +1,6 @@
1
1
  import React, { useEffect } from 'react';
2
2
  import { Platform, Text, StyleSheet } from 'react-native';
3
- import { useApi, useSession, useLanguage } from 'ordering-components/native';
3
+ import { useApi, useSession, useLanguage, useConfig } from 'ordering-components/native';
4
4
  import { appleAuthAndroid, appleAuth } from '@invertase/react-native-apple-authentication';
5
5
  import uuid from 'react-native-uuid';
6
6
  import Icon from 'react-native-vector-icons/FontAwesome5';
@@ -16,12 +16,12 @@ export const AppleLogin = (props: any) => {
16
16
  } = props
17
17
 
18
18
  const [ordering] = useApi();
19
- const [{ auth }] = useSession();
20
- const [, t] = useLanguage();
21
-
22
- const buttonText = auth
23
- ? t('CONTINUE_WITH_APPLE', 'Logout with Apple')
24
- : t('CONTINUE_WITH_FACEBOOK', 'Continue with Apple');
19
+ const [{ auth }] = useSession();
20
+ const [, t] = useLanguage();
21
+ const [{ configs }] = useConfig()
22
+ const buttonText = auth
23
+ ? t('CONTINUE_WITH_APPLE', 'Logout with Apple')
24
+ : t('CONTINUE_WITH_FACEBOOK', 'Continue with Apple');
25
25
 
26
26
  const performAppleLogin = async (code: string) => {
27
27
  try {
@@ -32,9 +32,10 @@ export const AppleLogin = (props: any) => {
32
32
  code: code
33
33
  })
34
34
  })
35
- if (!response.content.error) {
35
+ const { result, error } = await response.json()
36
+ if (!error) {
36
37
  if (handleSuccessAppleLogin) {
37
- handleSuccessAppleLogin(response.content.result)
38
+ handleSuccessAppleLogin(result)
38
39
  handleLoading && handleLoading(false)
39
40
  }
40
41
  } else {
@@ -52,54 +53,58 @@ export const AppleLogin = (props: any) => {
52
53
  }
53
54
 
54
55
  const onIOSButtonPress = async () => {
55
-
56
- const appleAuthRequestResponse = await appleAuth.performRequest({
57
- requestedOperation: appleAuth.Operation.LOGIN,
58
- requestedScopes: [appleAuth.Scope.EMAIL, appleAuth.Scope.FULL_NAME],
59
- });
60
-
61
- // get current authentication state for user
62
- // /!\ This method must be tested on a real device. On the iOS simulator it always throws an error.
63
- const credentialState = await appleAuth.getCredentialStateForUser(appleAuthRequestResponse.user);
64
-
65
- // use credentialState response to ensure the user is authenticated
66
- if (credentialState === appleAuth.State.AUTHORIZED) {
67
- // user is authenticated
68
- if (appleAuthRequestResponse.authorizationCode) {
69
- performAppleLogin(appleAuthRequestResponse.authorizationCode)
56
+ try {
57
+ const appleAuthRequestResponse = await appleAuth.performRequest({
58
+ requestedOperation: appleAuth.Operation.LOGIN,
59
+ requestedScopes: [appleAuth.Scope.EMAIL, appleAuth.Scope.FULL_NAME],
60
+ });
61
+
62
+ // get current authentication state for user
63
+ // /!\ This method must be tested on a real device. On the iOS simulator it always throws an error.
64
+ const credentialState = await appleAuth.getCredentialStateForUser(appleAuthRequestResponse.user);
65
+
66
+ // use credentialState response to ensure the user is authenticated
67
+ if (credentialState === appleAuth.State.AUTHORIZED) {
68
+ // user is authenticated
69
+ if (appleAuthRequestResponse.authorizationCode) {
70
+ performAppleLogin(appleAuthRequestResponse.authorizationCode)
71
+ }
70
72
  }
73
+ } catch (err: any) {
74
+ handleLoading && handleLoading(false)
75
+ handleErrors && handleErrors(err.message)
71
76
  }
72
-
73
77
  }
74
-
75
78
  const onAndroidButtonPress = async () => {
76
- // Generate secure, random values for state and nonce
77
- const rawNonce: any = uuid.v4();
78
- const state: any = uuid.v4();
79
-
80
- // Configure the request
81
- appleAuthAndroid.configure({
82
- clientId: 'com.example.client-android',
83
- // Return URL added to your Apple dev console. We intercept this redirect, but it must still match
84
- // the URL you provided to Apple. It can be an empty route on your backend as it's never called.
85
- redirectUri: 'https://example.com/auth/callback',
86
- responseType: appleAuthAndroid.ResponseType.ALL,
87
- scope: appleAuthAndroid.Scope.ALL,
88
- // Random nonce value that will be SHA256 hashed before sending to Apple.
89
- nonce: rawNonce,
90
- state,
91
- });
92
-
93
- // Open the browser window for user sign in
94
- const response = await appleAuthAndroid.signIn();
95
-
96
79
  try {
80
+ // Generate secure, random values for state and nonce
81
+ const rawNonce: any = uuid.v4();
82
+ const state: any = uuid.v4();
83
+
84
+ // Configure the request
85
+ appleAuthAndroid.configure({
86
+ // The Service ID you registered with Apple
87
+ clientId: configs?.apple_login_client_id?.value,
88
+ // Return URL added to your Apple dev console. We intercept this redirect, but it must still match
89
+ // the URL you provided to Apple. It can be an empty route on your backend as it's never called.
90
+ redirectUri: 'https://example.com/auth/callback',
91
+ responseType: appleAuthAndroid.ResponseType.ALL,
92
+ scope: appleAuthAndroid.Scope.ALL,
93
+ // Random nonce value that will be SHA256 hashed before sending to Apple.
94
+ nonce: rawNonce,
95
+ state,
96
+ });
97
+
98
+ // Open the browser window for user sign in
99
+ const response = await appleAuthAndroid.signIn();
97
100
  if (response.code) {
98
101
  performAppleLogin(response.code)
99
102
  }
100
103
  } catch (err: any) {
101
-
104
+ handleLoading && handleLoading(false)
105
+ handleErrors && handleErrors(err.message)
102
106
  }
107
+
103
108
  }
104
109
 
105
110
  useEffect(() => {
@@ -115,35 +120,36 @@ export const AppleLogin = (props: any) => {
115
120
  if (Platform.OS === 'android') return appleAuthAndroid.isSupported;
116
121
  return false;
117
122
  }
123
+
118
124
  return (
119
125
  <Container>
120
- {canShowButton() &&
121
- <AppleButton
122
- onPress={() => Platform.OS == 'android' ? onAndroidButtonPress() : onIOSButtonPress()}
123
- >
124
- <Icon
125
- name="apple"
126
- size={20}
127
- color={'black'}
128
- style={style.fbBtn}
129
- />
130
- <Text style={style.textBtn}>
131
- {buttonText}
132
- </Text>
133
- </AppleButton>
126
+ {canShowButton() &&
127
+ <AppleButton
128
+ onPress={() => Platform.OS == 'android' ? onAndroidButtonPress() : onIOSButtonPress()}
129
+ >
130
+ <Icon
131
+ name="apple"
132
+ size={20}
133
+ color={'black'}
134
+ style={style.fbBtn}
135
+ />
136
+ <Text style={style.textBtn}>
137
+ {buttonText}
138
+ </Text>
139
+ </AppleButton>
134
140
  }
135
141
  </Container>
136
142
  );
137
143
  }
138
144
 
139
145
  const style = StyleSheet.create({
140
- fbBtn: {
141
- position: 'absolute',
142
- left: 0,
143
- marginHorizontal: 16
144
- },
145
- textBtn: {
146
- fontSize: 14,
147
- color: '#000000'
148
- }
149
- })
146
+ fbBtn: {
147
+ position: 'absolute',
148
+ left: 0,
149
+ marginHorizontal: 16
150
+ },
151
+ textBtn: {
152
+ fontSize: 14,
153
+ color: '#000000'
154
+ }
155
+ })
@@ -4,6 +4,7 @@ import Spinner from 'react-native-loading-spinner-overlay';
4
4
  import { useForm, Controller } from 'react-hook-form';
5
5
  import { PhoneInputNumber } from '../PhoneInputNumber';
6
6
  import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
7
+ import Recaptcha from 'react-native-recaptcha-that-works'
7
8
 
8
9
  import {
9
10
  LoginForm as LoginFormController,
@@ -18,7 +19,6 @@ import { FacebookLogin } from '../FacebookLogin';
18
19
  import { VerifyPhone } from '../../../../../src/components/VerifyPhone';
19
20
  import { OModal } from '../../../../../src/components/shared';
20
21
 
21
-
22
22
  import {
23
23
  Container,
24
24
  ButtonsWrapper,
@@ -32,6 +32,7 @@ import {
32
32
  LineSeparator,
33
33
  SkeletonWrapper,
34
34
  TabBtn,
35
+ RecaptchaButton
35
36
  } from './styles';
36
37
 
37
38
  import NavBar from '../NavBar';
@@ -60,7 +61,9 @@ const LoginFormUI = (props: LoginParams) => {
60
61
  handleSendVerifyCode,
61
62
  handleCheckPhoneCode,
62
63
  onNavigationRedirect,
63
- notificationState
64
+ notificationState,
65
+ handleReCaptcha,
66
+ enableReCaptcha
64
67
  } = props;
65
68
 
66
69
  const [, { showToast }] = useToast();
@@ -79,6 +82,8 @@ const LoginFormUI = (props: LoginParams) => {
79
82
  cellphone: null,
80
83
  },
81
84
  });
85
+ const [recaptchaConfig, setRecaptchaConfig] = useState<any>({})
86
+ const [recaptchaVerified, setRecaptchaVerified] = useState(false)
82
87
 
83
88
  const theme = useTheme();
84
89
 
@@ -100,10 +105,15 @@ const LoginFormUI = (props: LoginParams) => {
100
105
  flexGrow: 1,
101
106
  marginBottom: 7,
102
107
  },
108
+ recaptchaIcon: {
109
+ width: 100,
110
+ height: 100,
111
+ }
103
112
  });
104
113
 
105
114
  const emailRef = useRef<any>({});
106
115
  const passwordRef = useRef<any>({});
116
+ const recaptchaRef = useRef<any>({});
107
117
 
108
118
  const handleChangeTab = (val: string) => {
109
119
  props.handleChangeTab(val);
@@ -156,6 +166,33 @@ const LoginFormUI = (props: LoginParams) => {
156
166
  onChange(value.toLowerCase().replace(/[&,()%";:ç?<>{}\\[\]\s]/g, ''));
157
167
  };
158
168
 
169
+ const handleOpenRecaptcha = () => {
170
+ setRecaptchaVerified(false)
171
+ if (!recaptchaConfig?.siteKey) {
172
+ showToast(ToastType.Error, t('NO_RECAPTCHA_SITE_KEY', 'The config doesn\'t have recaptcha site key'));
173
+ return
174
+ }
175
+ if (!recaptchaConfig?.baseUrl) {
176
+ showToast(ToastType.Error, t('NO_RECAPTCHA_BASE_URL', 'The config doesn\'t have recaptcha base url'));
177
+ return
178
+ }
179
+ recaptchaRef.current.open()
180
+ }
181
+
182
+ const onRecaptchaVerify = (token: any) => {
183
+ setRecaptchaVerified(true)
184
+ handleReCaptcha(token)
185
+ }
186
+
187
+ useEffect(() => {
188
+ if (configs && Object.keys(configs).length > 0 && enableReCaptcha) {
189
+ setRecaptchaConfig({
190
+ siteKey: configs?.security_recaptcha_site_key?.value || null,
191
+ baseUrl: configs?.security_recaptcha_base_url?.value || null
192
+ })
193
+ }
194
+ }, [configs, enableReCaptcha])
195
+
159
196
  useEffect(() => {
160
197
  if (!formState.loading && formState.result?.error) {
161
198
  formState.result?.result &&
@@ -399,6 +436,39 @@ const LoginFormUI = (props: LoginParams) => {
399
436
  </OText>
400
437
  </TouchableOpacity>
401
438
  )}
439
+
440
+ {enableReCaptcha && (
441
+ <>
442
+ <TouchableOpacity
443
+ onPress={handleOpenRecaptcha}
444
+ >
445
+ <RecaptchaButton>
446
+ {recaptchaVerified ? (
447
+ <MaterialCommunityIcons
448
+ name="checkbox-marked"
449
+ size={26}
450
+ color={theme.colors.primary}
451
+ />
452
+ ) : (
453
+ <MaterialCommunityIcons
454
+ name="checkbox-blank-outline"
455
+ size={26}
456
+ color={theme.colors.mediumGray}
457
+ />
458
+ )}
459
+ <OText size={14} mLeft={8}>{t('VERIFY_ReCAPTCHA', 'Verify reCAPTCHA')}</OText>
460
+ </RecaptchaButton>
461
+ </TouchableOpacity>
462
+ <Recaptcha
463
+ ref={recaptchaRef}
464
+ siteKey={recaptchaConfig?.siteKey}
465
+ baseUrl={recaptchaConfig?.baseUrl}
466
+ onVerify={onRecaptchaVerify}
467
+ onExpire={() => setRecaptchaVerified(false)}
468
+ />
469
+ </>
470
+ )}
471
+
402
472
  <OButton
403
473
  onClick={handleSubmit(onSubmit)}
404
474
  text={loginButtonText}
@@ -559,6 +629,7 @@ const LoginFormUI = (props: LoginParams) => {
559
629
  export const LoginForm = (props: any) => {
560
630
  const loginProps = {
561
631
  ...props,
632
+ isRecaptchaEnable: true,
562
633
  UIComponent: LoginFormUI,
563
634
  };
564
635
  return <LoginFormController {...loginProps} />;
@@ -36,7 +36,7 @@ export const LoginWith = styled.View`
36
36
  width: 100%;
37
37
  align-items: flex-start;
38
38
  border-bottom-width: 1px;
39
- border-bottom-color: ${(props: any) => props.theme.colors.border}
39
+ border-bottom-color: ${(props: any) => props.theme.colors.border};
40
40
  `;
41
41
 
42
42
  export const FormInput = styled.View`
@@ -80,3 +80,8 @@ export const LineSeparator = styled.View`
80
80
  export const SkeletonWrapper = styled.View`
81
81
  width: 90%;
82
82
  `
83
+ export const RecaptchaButton = styled.View`
84
+ flex-direction: row;
85
+ align-items: center;
86
+ margin-bottom: 10px;
87
+ `
@@ -3,14 +3,20 @@ import { TouchableOpacity } from 'react-native';
3
3
  import { LogoutAction } from 'ordering-components/native';
4
4
  import { useTheme } from 'styled-components/native';
5
5
  import { OIcon, OText } from '../shared';
6
+ import { _retrieveStoreData } from '../../providers/StoreUtil';
6
7
 
7
8
  const LogoutButtonUI = (props: any) => {
8
9
  const { handleLogoutClick, text, color, iconSize } = props
9
10
  const theme = useTheme();
10
11
 
12
+ const handleClick = async () => {
13
+ const data = await _retrieveStoreData('notification_state');
14
+ handleLogoutClick(data)
15
+ };
16
+
11
17
  return (
12
18
  <TouchableOpacity
13
- onPress={() => handleLogoutClick()}
19
+ onPress={() => handleClick()}
14
20
  style={{ flexDirection: 'row', alignItems: 'center' }}
15
21
  >
16
22
  <OIcon
@@ -0,0 +1,232 @@
1
+ import React, { useState } from 'react'
2
+ import { PromotionsController, useLanguage, useUtils, useEvent } from 'ordering-components/native'
3
+ import {
4
+ PromotionsContainer,
5
+ SingleOfferContainer,
6
+ OfferInformation,
7
+ SearchBarContainer,
8
+ SingleBusinessOffer,
9
+ AvailableBusinesses,
10
+ OfferData,
11
+ Code,
12
+ BusinessInfo
13
+ } from './styles'
14
+ import { SearchBar } from '../SearchBar'
15
+ import NavBar from '../NavBar'
16
+ import { useTheme } from 'styled-components/native';
17
+ import { OButton, OIcon, OModal, OText } from '../shared'
18
+ import { Placeholder, PlaceholderLine } from 'rn-placeholder'
19
+ import { NotFoundSource } from '../NotFoundSource'
20
+ import { View, StyleSheet, ScrollView } from 'react-native'
21
+ import FastImage from 'react-native-fast-image'
22
+ import { PromotionParams } from '../../types'
23
+ const PromotionsUI = (props : PromotionParams) => {
24
+ const {
25
+ navigation,
26
+ offersState,
27
+ handleSearchValue,
28
+ searchValue,
29
+ offerSelected,
30
+ setOfferSelected
31
+ } = props
32
+
33
+ const theme = useTheme();
34
+
35
+ const styles = StyleSheet.create({
36
+ productStyle: {
37
+ width: 75,
38
+ height: 75,
39
+ borderRadius: 7.6
40
+ },
41
+ buttonStyle: {
42
+ width: 55,
43
+ height: 25,
44
+ paddingLeft: 0,
45
+ paddingRight: 0
46
+ },
47
+ offerTitle: {
48
+ fontSize: 12
49
+ },
50
+ offerDescription: {
51
+ color: '#909BA9',
52
+ fontSize: 10
53
+ },
54
+ offerExtraInfo: {
55
+ fontSize: 10
56
+ },
57
+ modalButtonStyle: {
58
+ width: 100,
59
+ height: 35,
60
+ paddingLeft: 0,
61
+ paddingRight: 0,
62
+ borderRadius: 7.6
63
+ }
64
+ });
65
+
66
+ const [, t] = useLanguage()
67
+ const [{ parseDate, parsePrice, optimizeImage }] = useUtils()
68
+ const [events] = useEvent()
69
+ const [openModal, setOpenModal] = useState(false)
70
+
71
+ const handleClickOffer = (offer : any) => {
72
+ setOpenModal(true)
73
+ setOfferSelected(offer)
74
+ }
75
+
76
+ const handleBusinessClick = (business : any) => {
77
+ events.emit('go_to_page', { page: 'business', params: { store: business.slug } })
78
+ }
79
+
80
+ const filteredOffers = offersState?.offers?.filter((offer : any) => offer.name.toLowerCase().includes(searchValue.toLowerCase()))
81
+
82
+ const targetString = offerSelected?.target === 1
83
+ ? t('SUBTOTAL', 'Subtotal')
84
+ : offerSelected?.target === 2
85
+ ? t('DELIVERY_FEE', 'Delivery fee')
86
+ : t('SERVICE_FEE', 'Service fee')
87
+
88
+ return (
89
+ <PromotionsContainer>
90
+ <NavBar
91
+ onActionLeft={() => navigation.goBack()}
92
+ btnStyle={{ paddingLeft: 0 }}
93
+ paddingTop={20}
94
+ style={{ paddingBottom: 0, flexDirection: 'column', alignItems: 'flex-start' }}
95
+ title={t('PROMOTIONS', 'Promotions')}
96
+ titleAlign={'center'}
97
+ titleStyle={{ fontSize: 16, marginRight: 0, marginLeft: 0, marginBottom: 10 }}
98
+ titleWrapStyle={{ paddingHorizontal: 0 }}
99
+ />
100
+ <SearchBarContainer>
101
+ <SearchBar
102
+ placeholder={t('SEARCH_OFFERS', 'Search offers')}
103
+ onSearch={handleSearchValue}
104
+ />
105
+ </SearchBarContainer>
106
+
107
+ {offersState?.loading && (
108
+ <>
109
+ {[...Array(5).keys()].map((key, i) => (
110
+ <Placeholder key={i} style={{ flexDirection: 'row', marginBottom: 20 }}>
111
+ <PlaceholderLine height={10} width={45} />
112
+ <PlaceholderLine height={10} width={60} />
113
+ <PlaceholderLine height={10} width={75} />
114
+ </Placeholder>
115
+ ))}
116
+ </>
117
+ )}
118
+ {((!offersState?.loading && filteredOffers?.length === 0) || offersState?.error) && (
119
+ <NotFoundSource
120
+ content={offersState?.error || t('NOT_FOUND_OFFERS', 'Not found offers')}
121
+ />
122
+ )}
123
+ <ScrollView>
124
+ {!offersState?.loading && offersState.offers?.length > 0 && filteredOffers?.map((offer : any) => (
125
+ <SingleOfferContainer key={offer.id}>
126
+ <OfferInformation>
127
+ <OText style={styles.offerTitle}>{offer?.name}</OText>
128
+ {offer?.description && (
129
+ <OText style={styles.offerDescription}>{offer?.description}</OText>
130
+ )}
131
+ <OText style={styles.offerExtraInfo}>
132
+ {t('EXPIRES', 'Expires')} {parseDate(offer?.end, { outputFormat: 'MMM DD, YYYY' })}
133
+ </OText>
134
+ <AvailableBusinesses>
135
+ <OText style={styles.offerExtraInfo}>{t('APPLY_FOR', 'Apply for')}:</OText>
136
+ {offer.businesses.map((business: any, i: number) => (
137
+ <OText style={styles.offerExtraInfo} key={business?.id}>{' '}{business?.name}{i + 1 < offer.businesses?.length ? ',' : ''}</OText>
138
+ ))}
139
+ </AvailableBusinesses>
140
+ </OfferInformation>
141
+ <OButton
142
+ onClick={() => handleClickOffer(offer)}
143
+ text={t('VIEW', 'View')}
144
+ style={styles.buttonStyle}
145
+ textStyle={{ fontSize: 10, color: '#fff', flexWrap: 'nowrap' }}
146
+ />
147
+ </SingleOfferContainer>
148
+ ))}
149
+ </ScrollView>
150
+ <OModal
151
+ open={openModal}
152
+ onClose={() => setOpenModal(false)}
153
+ entireModal
154
+
155
+ title={``}
156
+ >
157
+ <View style={{ padding: 20 }}>
158
+ <OText style={{ alignSelf: 'center', fontWeight: '700' }} mBottom={20}>
159
+ {offerSelected?.name} / {t('VALUE_OF_OFFER', 'Value of offer')}: {offerSelected?.rate_type === 1 ? `${offerSelected?.rate}%` : `${parsePrice(offerSelected?.rate)}`}
160
+ </OText>
161
+ <OfferData>
162
+ {offerSelected?.type === 2 && (
163
+ <Code>
164
+ <OText>{t('YOUR_CODE', 'Your code')}</OText>
165
+ <OText color={theme.colors.primary}>{offerSelected.coupon}</OText>
166
+ </Code>
167
+ )}
168
+ <OText>{t('APPLIES_TO', 'Applies to')}: {targetString}</OText>
169
+ {offerSelected?.auto && (
170
+ <OText>{t('OFFER_AUTOMATIC', 'This offer applies automatic')}</OText>
171
+ )}
172
+ {offerSelected?.minimum && (
173
+ <OText>{t('MINIMUM_PURCHASE_FOR_OFFER', 'Minimum purshase for use this offer')}: {parsePrice(offerSelected?.minimum)}</OText>
174
+ )}
175
+ {offerSelected?.max_discount && (
176
+ <OText>{t('MAX_DISCOUNT_ALLOWED', 'Max discount allowed')}: {parsePrice(offerSelected?.max_discount)}</OText>
177
+ )}
178
+ {offerSelected?.description && (
179
+ <OText>{offerSelected?.description}</OText>
180
+ )}
181
+ </OfferData>
182
+ <OText style={{ marginTop: 10, marginBottom: 10 }}>
183
+ {t('AVAILABLE_BUSINESSES_FOR_OFFER', 'Available businesses for this offer')}:
184
+ </OText>
185
+ <ScrollView style={{height: '75%'}}>
186
+ {offerSelected?.businesses?.map((business : any) => {
187
+ return (
188
+ <SingleBusinessOffer key={business.id}>
189
+ {business?.logo ? (
190
+ <FastImage
191
+ style={styles.productStyle}
192
+ source={{
193
+ uri: optimizeImage(business?.logo, 'h_250,c_limit'),
194
+ priority: FastImage.priority.normal,
195
+ }}
196
+ resizeMode={FastImage.resizeMode.cover}
197
+ />
198
+ ) : (
199
+ <OIcon
200
+ src={theme?.images?.dummies?.product}
201
+ style={styles.productStyle}
202
+ />
203
+ )}
204
+ <BusinessInfo>
205
+ <OText>{business.name}</OText>
206
+ <OButton
207
+ onClick={() => handleBusinessClick(business)}
208
+ text={t('GO_TO_BUSINESSS', 'Go to business')}
209
+ style={styles.modalButtonStyle}
210
+ textStyle={{ fontSize: 10, color: '#fff' }}
211
+ />
212
+ </BusinessInfo>
213
+ </SingleBusinessOffer>
214
+ )
215
+ })}
216
+ </ScrollView>
217
+ </View>
218
+ </OModal>
219
+ </PromotionsContainer>
220
+ )
221
+ }
222
+
223
+ export const Promotions = (props : PromotionParams) => {
224
+ const PromotionsProps = {
225
+ ...props,
226
+ UIComponent: PromotionsUI
227
+ }
228
+
229
+ return (
230
+ <PromotionsController {...PromotionsProps} />
231
+ )
232
+ }
@@ -0,0 +1,80 @@
1
+ import styled, { css } from 'styled-components/native'
2
+
3
+ export const PromotionsContainer = styled.View`
4
+ width: 100%;
5
+ `
6
+
7
+ export const SingleOfferContainer = styled.View`
8
+ flex-direction: row;
9
+ width: 100%;
10
+ height: 80px;
11
+ justify-content: space-between;
12
+ align-items: center;
13
+ margin-bottom: 20px;
14
+
15
+ `
16
+
17
+ export const OfferInformation = styled.View`
18
+ justify-content: space-between;
19
+ max-width: 75%;
20
+ `
21
+
22
+ export const SearchBarContainer = styled.View`
23
+ display: flex;
24
+ width: 100%;
25
+ justify-content: flex-start;
26
+ margin-bottom: 20px;
27
+ .search-bar {
28
+ justify-content: flex-start;
29
+ input {
30
+ width: 100%;
31
+ }
32
+ }
33
+ .clear {
34
+ right: 0;
35
+ }
36
+ `
37
+
38
+ export const SingleBusinessOffer = styled.View`
39
+ flex-direction: row;
40
+ `
41
+
42
+ export const AvailableBusinesses = styled.View`
43
+ flex-direction: row;
44
+ overflow: hidden;
45
+ `
46
+
47
+ export const OfferData = styled.View`
48
+ display: flex;
49
+ align-items: center;
50
+ flex-direction: column;
51
+ p{
52
+ color: #909BA9;
53
+ margin: 3px;
54
+ font-size: 14px;
55
+ }
56
+ `
57
+
58
+ export const Code = styled.View`
59
+ display: flex;
60
+ flex-direction: column;
61
+ align-items: center;
62
+ margin-bottom: 10px;
63
+ `
64
+
65
+ export const ValueOfOffer = styled.View`
66
+ p{
67
+ font-size: 16px;
68
+ }
69
+ span{
70
+ font-size: 20px;
71
+ }
72
+ `
73
+
74
+ export const BusinessInfo = styled.View`
75
+ flex: 1;
76
+ flex-direction: row;
77
+ justify-content: space-between;
78
+ align-items: center;
79
+ margin-left: 10px;
80
+ `
@@ -16,6 +16,7 @@ import { LogoutButton } from '../LogoutButton'
16
16
  import { LanguageSelector } from '../LanguageSelector'
17
17
  import MessageCircle from 'react-native-vector-icons/AntDesign'
18
18
  import Ionicons from 'react-native-vector-icons/Ionicons'
19
+ import MaterialIcons from 'react-native-vector-icons/MaterialIcons'
19
20
  import FastImage from 'react-native-fast-image'
20
21
 
21
22
  import {
@@ -101,7 +102,7 @@ const ProfileListUI = (props: ProfileParams) => {
101
102
  const { top, bottom } = useSafeAreaInsets();
102
103
 
103
104
  const isWalletEnabled = configs?.wallet_enabled?.value === '1' && (configs?.wallet_cash_enabled?.value === '1' || configs?.wallet_credit_point_enabled?.value === '1')
104
-
105
+ const IsPromotionsEnabled = configs?.advanced_offers_module === '1' || configs?.advanced_offers_module === 'true'
105
106
  const onRedirect = (route: string, params?: any) => {
106
107
  navigation.navigate(route, params)
107
108
  }
@@ -178,6 +179,12 @@ const ProfileListUI = (props: ProfileParams) => {
178
179
  <OText size={14} lineHeight={24} weight={'400'} color={theme.colors.textNormal}>{t('WALLETS', 'Wallets')}</OText>
179
180
  </ListItem>
180
181
  )}
182
+ {IsPromotionsEnabled && (
183
+ <ListItem onPress={() => onRedirect('Promotions', { isFromProfile: true, isGoBack: true })} activeOpacity={0.7}>
184
+ <MaterialIcons name='local-offer' style={styles.messageIconStyle} color={theme.colors.textNormal} />
185
+ <OText size={14} lineHeight={24} weight={'400'} color={theme.colors.textNormal}>{t('PROMOTIONS', 'Promotions')}</OText>
186
+ </ListItem>
187
+ )}
181
188
  <ListItem onPress={() => navigation.navigate('Help', {})} activeOpacity={0.7}>
182
189
  <OIcon src={theme.images.general.ic_help} width={16} color={theme.colors.textNormal} style={{ marginEnd: 14 }} />
183
190
  <OText size={14} lineHeight={24} weight={'400'} color={theme.colors.textNormal}>{t('HELP', 'Help')}</OText>
@@ -17,6 +17,8 @@ export interface LoginParams {
17
17
  handleSendVerifyCode?: any;
18
18
  handleCheckPhoneCode?: any;
19
19
  notificationState?: any;
20
+ handleReCaptcha?: any;
21
+ enableReCaptcha?: any;
20
22
  }
21
23
  export interface ProfileParams {
22
24
  navigation?: any;
@@ -566,3 +568,12 @@ export interface PlaceSpotParams {
566
568
  getPlacesList?: any,
567
569
  setOpenPlaceModal?: any
568
570
  }
571
+
572
+ export interface PromotionParams {
573
+ navigation: any,
574
+ offersState: any,
575
+ handleSearchValue: any,
576
+ searchValue: string,
577
+ offerSelected: any,
578
+ setOfferSelected: any,
579
+ }