ordering-ui-react-native 0.15.49 → 0.15.50

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.50",
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",
@@ -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
+ `
@@ -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
+ }