ordering-ui-react-native 0.15.13 → 0.15.16

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.13",
3
+ "version": "0.15.16",
4
4
  "description": "Reusable components made in react native",
5
5
  "main": "src/index.tsx",
6
6
  "author": "ordering.inc",
@@ -1,6 +1,6 @@
1
1
  import React, { useEffect, useState, useRef } from 'react';
2
2
  import { View, Pressable, StyleSheet, ScrollView, RefreshControl, Linking, Platform, TextInput } from 'react-native';
3
- import { useLanguage, useUtils, useToast, ToastType, OrderListGroups } from 'ordering-components/native';
3
+ import { useLanguage, useUtils, useToast, ToastType, OrderListGroups, useConfig } from 'ordering-components/native';
4
4
  import SelectDropdown from 'react-native-select-dropdown'
5
5
  import { Placeholder, PlaceholderLine, Fade } from 'rn-placeholder';
6
6
  import FeatherIcon from 'react-native-vector-icons/Feather';
@@ -400,20 +400,6 @@ const OrdersOptionUI = (props: OrdersOptionParams) => {
400
400
  setOpenSLASettingModal(false)
401
401
  }
402
402
 
403
- const [settingTimeErrorMessage, setSettingTimeErrorMessage] = useState('')
404
-
405
- const handlSLASettingTime = () => {
406
- if (!hour || !minute) {
407
- setSettingTimeErrorMessage(t('SLA_SETTING_ERROR', 'Time value is invalid'))
408
- return
409
- }
410
- const _settingTimeSecond = hour * 3600 + minute * 60
411
- setSlaSettingTime(_settingTimeSecond)
412
- handleClose()
413
- showToast(ToastType.Success, t('SLA_SETTING_UPDATED', 'SLAs setting updated'))
414
- }
415
-
416
-
417
403
  useEffect(() => {
418
404
  setCurrentFilters(null)
419
405
  onFiltered && onFiltered(null)
@@ -859,24 +845,10 @@ const OrdersOptionUI = (props: OrdersOptionParams) => {
859
845
  key={i}
860
846
  item={item}
861
847
  last={i + 1 === selectedTabStatus.length}
862
- setHour={setHour}
863
- setMinute={setMinute}
864
- setSettingTimeErrorMessage={setSettingTimeErrorMessage}
865
848
  />
866
849
  ))}
867
850
  <VerticalLine />
868
851
  </DeliveryStatusWrapper>
869
- {settingTimeErrorMessage !== '' && (
870
- <OText style={styles.errorMessage}>{settingTimeErrorMessage}</OText>
871
- )}
872
- <Actions>
873
- <OButton
874
- text={t('ACCEPT', 'Accept')}
875
- textStyle={{ color: 'white', fontSize: 14 }}
876
- onClick={handlSLASettingTime}
877
- style={styles.acceptButtonStyle}
878
- />
879
- </Actions>
880
852
  </SlaSettingModalContent>
881
853
  )}
882
854
  </ModalContainer>
@@ -887,7 +859,7 @@ const OrdersOptionUI = (props: OrdersOptionParams) => {
887
859
  };
888
860
 
889
861
  export const StatusBlock = (props: any) => {
890
- const { item, last, setHour, setMinute, setSettingTimeErrorMessage } = props
862
+ const { item, last } = props
891
863
  const [showTime, setShowTime] = useState(false)
892
864
 
893
865
  useEffect(() => {
@@ -898,7 +870,7 @@ export const StatusBlock = (props: any) => {
898
870
 
899
871
  return (
900
872
  <StatusItems>
901
- <Pressable onPress={() => setShowTime(!showTime)} style={{ marginBottom: 5 }}>
873
+ <Pressable style={{ marginBottom: 10 }}>
902
874
  <ItemHeader>
903
875
  <IconWrapper>
904
876
  <OIcon
@@ -916,11 +888,7 @@ export const StatusBlock = (props: any) => {
916
888
  <OText>{item?.des}</OText>
917
889
  </ItemContent>
918
890
  {showTime && (
919
- <Timer
920
- setHour={setHour}
921
- setMinute={setMinute}
922
- setSettingTimeErrorMessage={setSettingTimeErrorMessage}
923
- />
891
+ <Timer />
924
892
  )}
925
893
  {last && (
926
894
  <OverLine />
@@ -929,47 +897,28 @@ export const StatusBlock = (props: any) => {
929
897
  )
930
898
  }
931
899
 
932
- export const Timer = (props: any) => {
933
- const { setHour, setMinute, setSettingTimeErrorMessage } = props
900
+ export const Timer = () => {
934
901
  const [, t] = useLanguage()
935
902
  const theme = useTheme()
903
+ const [{ configs }] = useConfig();
936
904
 
937
905
  const styles = StyleSheet.create({
938
- inputStyle: {
939
- paddingHorizontal: 7,
940
- paddingVertical: 2,
941
- borderRadius: 0,
906
+ settingTime: {
942
907
  fontSize: 14,
908
+ borderWidth: 1,
909
+ borderRadius: 7.6,
910
+ margin: 0,
911
+ marginRight: 10,
912
+ paddingHorizontal: 10,
913
+ paddingTop: 5,
914
+ borderColor: theme.colors.disabled
943
915
  }
944
916
  })
945
917
 
946
- const handleChangeInput = (val: any, type: string) => {
947
- setSettingTimeErrorMessage('')
948
- if (type === 'hour') {
949
- setHour(val)
950
- }
951
- if (type === 'minute') {
952
- setMinute(val)
953
- }
954
- }
955
-
956
918
  return (
957
919
  <TimerInputWrapper>
958
- <TextInput
959
- placeholder='HH'
960
- keyboardType='number-pad'
961
- maxLength={2}
962
- style={{ ...styles.inputStyle, width: 36 }}
963
- onChangeText={hour => handleChangeInput(hour, 'hour')}
964
- />
965
- <OText color={theme.colors.disabled}>:</OText>
966
- <TextInput
967
- placeholder='MM'
968
- keyboardType='number-pad'
969
- maxLength={2}
970
- style={{ ...styles.inputStyle, width: 40 }}
971
- onChangeText={minute => handleChangeInput(minute, 'minute')}
972
- />
920
+ <OText style={styles.settingTime} color={theme.colors.disabled}>{configs?.order_deadlines_delayed_time?.value}</OText>
921
+ <OText>{t('MIN', 'min')}</OText>
973
922
  </TimerInputWrapper>
974
923
  )
975
924
  }
@@ -130,15 +130,12 @@ export const ItemContent = styled.View`
130
130
  `
131
131
 
132
132
  export const TimerInputWrapper = styled.View`
133
- border-width: 1px;
134
- border-radius: 7.6px;
135
133
  color: ${(props: any) => props.theme.colors.disabled};
136
- border-color: ${(props: any) => props.theme.colors.disabled};
134
+ margin-top: 15px;
137
135
  margin-left: 30px;
138
136
  margin-right: 30px;
139
137
  flex-direction: row;
140
- align-items: center;
141
- width: 80px;
138
+ align-items: flex-end;
142
139
  `
143
140
  export const OverLine = styled.View`
144
141
  position: absolute;
@@ -93,7 +93,7 @@ const BusinessProductsListingUI = (props: BusinessProductsListingParams) => {
93
93
  >
94
94
  <Category
95
95
  style={cardStyle}
96
- source={{uri: item.images}}
96
+ source={item.images ? {uri: item.images} : theme.images.categories.all}
97
97
  resizeMode="cover"
98
98
  w={widthScreen}
99
99
  borderRadius={16}
@@ -172,7 +172,7 @@ const BusinessProductsListingUI = (props: BusinessProductsListingParams) => {
172
172
  <OCard
173
173
  key={category.id}
174
174
  title={category?.name || ''}
175
- image={{uri: category?.image}}
175
+ image={category.images ? {uri: category.images} : theme.images.categories.all}
176
176
  style={{
177
177
  width:
178
178
  orientationState?.orientation === LANDSCAPE
@@ -65,18 +65,6 @@ const ForgotPasswordUI = (props: any) => {
65
65
  }
66
66
  }, [formState])
67
67
 
68
- useEffect(() => {
69
- if (Object.keys(errors).length > 0) {
70
- // Convert all errors in one string to show in toast provider
71
- const list = Object.values(errors)
72
- let stringError = ''
73
- list.map((item: any, i: number) => {
74
- stringError += (i + 1) === list.length ? `- ${item.message}` : `- ${item.message}\n`
75
- })
76
- showToast(ToastType.Error, stringError)
77
- }
78
- }, [errors])
79
-
80
68
  return (
81
69
  <Container>
82
70
  <NavBar
@@ -100,6 +88,14 @@ const ForgotPasswordUI = (props: any) => {
100
88
  {t('FORGOT_PASSWORD_TEXT_MESSAGE', "Enter your email address and we'll sent a link to reset your password.")}
101
89
  </OText>
102
90
  <FormInput>
91
+ {errors?.email && (
92
+ <OText
93
+ size={14}
94
+ color={theme.colors.danger5}
95
+ weight={'normal'}>
96
+ {errors?.email?.message}{errors?.email?.type === 'required' && '*'}
97
+ </OText>
98
+ )}
103
99
  <Controller
104
100
  control={control}
105
101
  render={({ onChange, value }: any) => (
@@ -67,7 +67,7 @@ const LoginFormUI = (props: LoginParams) => {
67
67
  const [, t] = useLanguage();
68
68
  const [{ configs }] = useConfig();
69
69
  const [, { login }] = useSession();
70
- const { control, handleSubmit, errors } = useForm();
70
+ const { control, handleSubmit, errors, reset, register, setValue } = useForm();
71
71
  const [passwordSee, setPasswordSee] = useState(false);
72
72
  const [isLoadingVerifyModal, setIsLoadingVerifyModal] = useState(false);
73
73
  const [isModalVisible, setIsModalVisible] = useState(false);
@@ -91,7 +91,7 @@ const LoginFormUI = (props: LoginParams) => {
91
91
  inputStyle: {
92
92
  marginBottom: 28,
93
93
  borderWidth: 1,
94
- borderColor: theme.colors.border,
94
+ // borderColor: theme.colors.border,
95
95
  borderRadius: 7.6,
96
96
  },
97
97
  line: {
@@ -189,33 +189,21 @@ const LoginFormUI = (props: LoginParams) => {
189
189
  }, [verifyPhoneState]);
190
190
 
191
191
  useEffect(() => {
192
- if (Object.keys(errors).length > 0) {
193
- // Convert all errors in one string to show in toast provider
194
- const list = Object.values(errors);
195
- let stringError = '';
196
- if (phoneInputData.error) {
197
- list.unshift({ message: phoneInputData.error });
198
- }
199
- if (
200
- loginTab === 'cellphone' &&
201
- !phoneInputData.error &&
202
- !phoneInputData.phone.country_phone_code &&
203
- !phoneInputData.phone.cellphone
204
- ) {
205
- list.unshift({
206
- message: t(
207
- 'VALIDATION_ERROR_MOBILE_PHONE_REQUIRED',
208
- 'The field Mobile phone is required.',
209
- ),
210
- });
211
- }
212
- list.map((item: any, i: number) => {
213
- stringError +=
214
- i + 1 === list.length ? `- ${item.message}` : `- ${item.message}\n`;
215
- });
216
- showToast(ToastType.Error, stringError);
217
- }
218
- }, [errors]);
192
+ if (phoneInputData?.phone?.cellphone) setValue('cellphone', phoneInputData?.phone?.cellphone, '')
193
+ else setValue('cellphone', '')
194
+ }, [phoneInputData?.phone?.cellphone])
195
+
196
+ useEffect(() => {
197
+ register('cellphone', {
198
+ required: loginTab === 'cellphone'
199
+ ? t('VALIDATION_ERROR_MOBILE_PHONE_REQUIRED', 'The field Mobile phone is required').replace('_attribute_', t('CELLPHONE', 'Cellphone'))
200
+ : null
201
+ })
202
+ }, [register])
203
+
204
+ useEffect(() => {
205
+ reset()
206
+ }, [loginTab])
219
207
 
220
208
  return (
221
209
  <Container>
@@ -284,43 +272,58 @@ const LoginFormUI = (props: LoginParams) => {
284
272
  {(useLoginByCellphone || useLoginByEmail) && (
285
273
  <FormInput>
286
274
  {useLoginByEmail && loginTab === 'email' && (
287
- <Controller
288
- control={control}
289
- render={({ onChange, value }: any) => (
290
- <OInput
291
- placeholder={t('EMAIL', 'Email')}
292
- style={loginStyle.inputStyle}
293
- icon={theme.images.general.email}
294
- onChange={(e: any) => {
295
- handleChangeInputEmail(e, onChange);
296
- }}
297
- value={value}
298
- autoCapitalize="none"
299
- autoCorrect={false}
300
- type="email-address"
301
- autoCompleteType="email"
302
- returnKeyType="next"
303
- onSubmitEditing={() => passwordRef.current?.focus()}
304
- blurOnSubmit={false}
305
- forwardRef={emailRef}
306
- />
275
+ <>
276
+ {errors?.email && (
277
+ <OText
278
+ size={14}
279
+ color={theme.colors.danger5}
280
+ weight={'normal'}>
281
+ {errors?.email?.message}{errors?.email?.type === 'required' && '*'}
282
+ </OText>
307
283
  )}
308
- name="email"
309
- rules={{
310
- required: t(
311
- 'VALIDATION_ERROR_EMAIL_REQUIRED',
312
- 'The field Email is required',
313
- ).replace('_attribute_', t('EMAIL', 'Email')),
314
- pattern: {
315
- value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
316
- message: t(
317
- 'INVALID_ERROR_EMAIL',
318
- 'Invalid email address',
319
- ).replace('_attribute_', t('EMAIL', 'Email')),
320
- },
321
- }}
322
- defaultValue=""
323
- />
284
+ <Controller
285
+ control={control}
286
+ render={({ onChange, value }: any) => (
287
+ <OInput
288
+ placeholder={t('EMAIL', 'Email')}
289
+ style={loginStyle.inputStyle}
290
+ icon={theme.images.general.email}
291
+ onChange={(e: any) => {
292
+ handleChangeInputEmail(e, onChange);
293
+ }}
294
+ value={value}
295
+ autoCapitalize="none"
296
+ autoCorrect={false}
297
+ type="email-address"
298
+ autoCompleteType="email"
299
+ returnKeyType="next"
300
+ onSubmitEditing={() => passwordRef.current?.focus()}
301
+ blurOnSubmit={false}
302
+ forwardRef={emailRef}
303
+ borderColor={errors?.email ? theme.colors.danger5 : theme.colors.border}
304
+ />
305
+ )}
306
+ name="email"
307
+ rules={{
308
+ required: {
309
+ value: true,
310
+ message: t(
311
+ 'VALIDATION_ERROR_EMAIL_REQUIRED',
312
+ 'The field Email is required',
313
+ ).replace('_attribute_', t('EMAIL', 'Email'))
314
+ },
315
+ pattern: {
316
+ value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
317
+ message: t(
318
+ 'INVALID_ERROR_EMAIL',
319
+ 'Invalid email address',
320
+ ).replace('_attribute_', t('EMAIL', 'Email')),
321
+ }
322
+ }}
323
+ defaultValue=""
324
+ />
325
+ </>
326
+
324
327
  )}
325
328
  {useLoginByCellphone && loginTab === 'cellphone' && (
326
329
  <View style={{ marginBottom: 28 }}>
@@ -331,10 +334,18 @@ const LoginFormUI = (props: LoginParams) => {
331
334
  returnKeyType: 'next',
332
335
  onSubmitEditing: () => passwordRef?.current?.focus?.(),
333
336
  }}
337
+ isStartValidation={errors?.cellphone}
334
338
  />
335
339
  </View>
336
340
  )}
337
-
341
+ {errors?.password && (
342
+ <OText
343
+ size={14}
344
+ color={theme.colors.danger5}
345
+ weight={'normal'}>
346
+ {errors?.password?.message}{errors?.password?.type === 'required' && '*'}
347
+ </OText>
348
+ )}
338
349
  <Controller
339
350
  control={control}
340
351
  render={({ onChange, value }: any) => (
@@ -366,14 +377,18 @@ const LoginFormUI = (props: LoginParams) => {
366
377
  returnKeyType="done"
367
378
  onSubmitEditing={handleSubmit(onSubmit)}
368
379
  blurOnSubmit
380
+ borderColor={errors?.password ? theme.colors.danger5 : theme.colors.border}
369
381
  />
370
382
  )}
371
383
  name="password"
372
384
  rules={{
373
- required: t(
385
+ required: {
386
+ value: true,
387
+ message: t(
374
388
  'VALIDATION_ERROR_PASSWORD_REQUIRED',
375
389
  'The field Password is required',
376
- ).replace('_attribute_', t('PASSWORD', 'Password')),
390
+ ).replace('_attribute_', t('PASSWORD', 'Password'))
391
+ }
377
392
  }}
378
393
  defaultValue=""
379
394
  />
@@ -97,7 +97,7 @@ const MessagesUI = (props: MessagesParams) => {
97
97
  }
98
98
 
99
99
  const handleImagePicker = () => {
100
- launchImageLibrary({ mediaType: 'photo', maxHeight: 300, maxWidth: 300, includeBase64: true }, (response: any) => {
100
+ launchImageLibrary({ mediaType: 'photo', maxHeight: 2048, maxWidth: 2048, includeBase64: true }, (response: any) => {
101
101
  if (response.didCancel) {
102
102
  console.log('User cancelled image picker');
103
103
  } else if (response.errorMessage) {
@@ -594,18 +594,20 @@ export const OrderDetailsUI = (props: OrderDetailsParams) => {
594
594
  {order?.business?.name}
595
595
  </OText>
596
596
  <Icons>
597
- <TouchableOpacity
598
- onPress={() => order?.business?.cellphone &&
599
- Linking.openURL(`tel:${order?.business?.cellphone}`)
600
- }
601
- style={{ paddingEnd: 5 }}
602
- >
603
- <OIcon
604
- src={theme.images.general.phone}
605
- width={16}
606
- color={theme.colors.disabled}
607
- />
608
- </TouchableOpacity>
597
+ {!!order?.business?.cellphone && (
598
+ <TouchableOpacity
599
+ onPress={() => order?.business?.cellphone &&
600
+ Linking.openURL(`tel:${order?.business?.cellphone}`)
601
+ }
602
+ style={{ paddingEnd: 5 }}
603
+ >
604
+ <OIcon
605
+ src={theme.images.general.phone}
606
+ width={16}
607
+ color={theme.colors.disabled}
608
+ />
609
+ </TouchableOpacity>
610
+ )}
609
611
  <TouchableOpacity
610
612
  style={{ paddingStart: 5 }}
611
613
  onPress={() => handleOpenMessagesForBusiness()}>
@@ -624,13 +626,15 @@ export const OrderDetailsUI = (props: OrderDetailsParams) => {
624
626
  mBottom={2}>
625
627
  {order?.business?.email}
626
628
  </OText>
627
- <OText
628
- size={12}
629
- lineHeight={18}
630
- color={theme.colors.textNormal}
631
- mBottom={2}>
632
- {order?.business?.cellphone}
633
- </OText>
629
+ {!!order?.business?.cellphone && (
630
+ <OText
631
+ size={12}
632
+ lineHeight={18}
633
+ color={theme.colors.textNormal}
634
+ mBottom={2}>
635
+ {order?.business?.cellphone}
636
+ </OText>
637
+ )}
634
638
  <OText size={12} lineHeight={18} color={theme.colors.textNormal}>
635
639
  {order?.business?.address}
636
640
  </OText>
@@ -978,7 +982,7 @@ export const OrderDetailsUI = (props: OrderDetailsParams) => {
978
982
  marginTop: 10
979
983
  }}
980
984
  >
981
- {order?.payment_events?.map((event: any) => (
985
+ {order?.payment_events?.map((event: any) => event.amount > 0 && (
982
986
  <View
983
987
  key={event.id}
984
988
  style={{
@@ -22,18 +22,23 @@ export const PhoneInputNumber = (props: PhoneInputParams) => {
22
22
  textStyle,
23
23
  flagStyle,
24
24
  noDropIcon,
25
- isDisabled
25
+ isDisabled,
26
+ isStartValidation
26
27
  } = props
27
28
 
28
29
  const theme = useTheme();
29
30
 
31
+ const [, t] = useLanguage()
32
+ const [{ configs }] = useConfig()
33
+ const phoneInput = useRef<PhoneInput>(null);
34
+ const [userphoneNumber, setUserphoneNumber] = useState('');
30
35
 
31
36
  const style = StyleSheet.create({
32
37
  input: {
33
38
  backgroundColor: theme.colors.white,
34
39
  borderRadius: 7.6,
35
40
  borderWidth: 1,
36
- borderColor: theme.colors.border,
41
+ borderColor: (isStartValidation && userphoneNumber === '') ? theme.colors.danger5 : theme.colors.border,
37
42
  paddingBottom: 0,
38
43
  paddingTop: 0,
39
44
  flexGrow: 1,
@@ -48,12 +53,6 @@ export const PhoneInputNumber = (props: PhoneInputParams) => {
48
53
  }
49
54
  })
50
55
 
51
-
52
- const [, t] = useLanguage()
53
- const [{ configs }] = useConfig()
54
- const phoneInput = useRef<PhoneInput>(null);
55
- const [userphoneNumber, setUserphoneNumber] = useState('');
56
-
57
56
  const handleChangeNumber = (number: any) => {
58
57
  setUserphoneNumber(number)
59
58
  }
@@ -97,6 +96,14 @@ export const PhoneInputNumber = (props: PhoneInputParams) => {
97
96
 
98
97
  return (
99
98
  <Wrapper onPress={() => forwardRef?.current?.focus?.()}>
99
+ {(isStartValidation && userphoneNumber === '') && (
100
+ <OText
101
+ size={14}
102
+ color={theme.colors.danger5}
103
+ >
104
+ {t('VALIDATION_ERROR_MOBILE_PHONE_REQUIRED', 'The field Mobile phone is required').replace('_attribute_', t('CELLPHONE', 'Cellphone'))}*
105
+ </OText>
106
+ )}
100
107
  <PhoneInput
101
108
  ref={phoneInput}
102
109
  disabled={isDisabled}
@@ -872,9 +872,13 @@ export const ProductOptionsUI = (props: any) => {
872
872
  </ScrollView>
873
873
  {!loading && !error && product && (
874
874
  <ProductActions ios={Platform?.OS === 'ios'}>
875
- <OText size={16} lineHeight={24} weight={'600'}>
876
- {productCart.total ? parsePrice(productCart?.total) : ''}
877
- </OText>
875
+ <View>
876
+ <OText size={16} lineHeight={24} weight={'600'}>
877
+ {productCart.total ? parsePrice(productCart?.total) : ''}
878
+ </OText>
879
+ {product?.minimum_per_order && productCart?.quantity < product?.minimum_per_order && <OText size={12} color={theme.colors?.red}>{t('MOBILE_MINIMUM_TO_ORDER', 'Min. _number_ ').replace('_number_', product?.minimum_per_order)}</OText>}
880
+ {product?.maximum_per_order && productCart?.quantity > product?.maximum_per_order && <OText size={12} color={theme.colors?.red}>{t('MOBILE_MAXIMUM_TO_ORDER', 'Max. _number_'.replace('_number_', product?.maximum_per_order))}</OText>}
881
+ </View>
878
882
  {productCart && !isSoldOut && maxProductQuantity > 0 && (
879
883
  <View style={styles.quantityControl}>
880
884
  <TouchableOpacity
@@ -883,6 +887,7 @@ export const ProductOptionsUI = (props: any) => {
883
887
  <OIcon
884
888
  src={theme.images.general.minus}
885
889
  width={16}
890
+ style={{ borderWidth: 1, borderColor: 'red' }}
886
891
  color={
887
892
  productCart.quantity === 1 || isSoldOut
888
893
  ? theme.colors.backgroundGray
@@ -926,6 +931,7 @@ export const ProductOptionsUI = (props: any) => {
926
931
  <OIcon
927
932
  src={theme.images.general.plus}
928
933
  width={16}
934
+ style={{ borderWidth: 1, borderColor: 'red' }}
929
935
  color={
930
936
  maxProductQuantity <= 0 ||
931
937
  productCart.quantity >= maxProductQuantity ||
@@ -987,14 +993,14 @@ export const ProductOptionsUI = (props: any) => {
987
993
  ? t('UPDATE', 'Update')
988
994
  : t('ADD', 'Add')
989
995
  }`}
990
- isDisabled={isSoldOut || maxProductQuantity <= 0}
996
+ isDisabled={isSoldOut || maxProductQuantity <= 0 || productCart?.quantity < product?.minimum_per_order || productCart?.quantity > product?.maximum_per_order}
991
997
  textStyle={{
992
998
  color: saveErrors || isSoldOut || maxProductQuantity <= 0 ? theme.colors.primary : theme.colors.white,
993
999
  fontSize: orderState.loading || editMode ? 10 : 14
994
1000
  }}
995
1001
  style={{
996
- backgroundColor: saveErrors || isSoldOut || maxProductQuantity <= 0 ? theme.colors.lightGray : theme.colors.primary,
997
- borderColor: saveErrors || isSoldOut || maxProductQuantity <= 0 ? theme.colors.white : theme.colors.primary,
1002
+ backgroundColor: saveErrors || isSoldOut || maxProductQuantity <= 0 || productCart?.quantity < product?.minimum_per_order || productCart?.quantity > product?.maximum_per_order ? theme.colors.lightGray : theme.colors.primary,
1003
+ borderColor: saveErrors || isSoldOut || maxProductQuantity <= 0 || productCart?.quantity < product?.minimum_per_order || productCart?.quantity > product?.maximum_per_order ? theme.colors.white : theme.colors.primary,
998
1004
  opacity: saveErrors || isSoldOut || maxProductQuantity <= 0 ? 0.3 : 1,
999
1005
  borderRadius: 7.6,
1000
1006
  height: 44,
@@ -68,7 +68,6 @@ const SignupFormUI = (props: SignupParams) => {
68
68
 
69
69
  const theme = useTheme();
70
70
 
71
-
72
71
  const style = StyleSheet.create({
73
72
  btnOutline: {
74
73
  backgroundColor: '#FFF',
@@ -77,7 +76,6 @@ const SignupFormUI = (props: SignupParams) => {
77
76
  inputStyle: {
78
77
  marginBottom: 20,
79
78
  borderWidth: 1,
80
- borderColor: theme.colors.border,
81
79
  borderRadius: 7.6,
82
80
  },
83
81
  wrappText: {
@@ -102,7 +100,7 @@ const SignupFormUI = (props: SignupParams) => {
102
100
  const [, t] = useLanguage();
103
101
  const [, { login }] = useSession();
104
102
  const [{ configs }] = useConfig();
105
- const { control, handleSubmit, errors } = useForm();
103
+ const { control, handleSubmit, errors, register, setValue } = useForm();
106
104
 
107
105
  const [passwordSee, setPasswordSee] = useState(false);
108
106
  const [formValues, setFormValues] = useState(null);
@@ -227,7 +225,7 @@ const SignupFormUI = (props: SignupParams) => {
227
225
  handleButtonSignupClick &&
228
226
  handleButtonSignupClick({
229
227
  ...values,
230
- ...phoneInputData.phone,
228
+ ...((phoneInputData.phone.cellphone !== null && phoneInputData.phone.country_phone_code !== null) && {...phoneInputData.phone}),
231
229
  });
232
230
  if (
233
231
  !formState.loading &&
@@ -298,36 +296,23 @@ const SignupFormUI = (props: SignupParams) => {
298
296
 
299
297
  useEffect(() => {
300
298
  if (Object.keys(errors).length > 0) {
301
- // Convert all errors in one string to show in toast provider
302
- const list = Object.values(errors);
303
- if (phoneInputData.error) {
304
- list.push({ message: phoneInputData.error });
305
- }
306
- if (
307
- !phoneInputData.error &&
308
- !phoneInputData.phone.country_phone_code &&
309
- !phoneInputData.phone.cellphone &&
310
- ((validationFields?.fields?.checkout?.cellphone?.enabled &&
311
- validationFields?.fields?.checkout?.cellphone?.required) ||
312
- configs?.verification_phone_required?.value === '1')
313
- ) {
314
- list.push({
315
- message: t(
316
- 'VALIDATION_ERROR_MOBILE_PHONE_REQUIRED',
317
- 'The field Mobile phone is required.',
318
- ),
319
- });
320
- }
321
- let stringError = '';
322
- list.map((item: any, i: number) => {
323
- stringError +=
324
- i + 1 === list.length ? `- ${item.message}` : `- ${item.message}\n`;
325
- });
326
- showToast(ToastType.Error, stringError);
327
299
  setIsLoadingVerifyModal(false);
328
300
  }
329
301
  }, [errors]);
330
302
 
303
+ useEffect(() => {
304
+ register('cellphone', {
305
+ required: isRequiredField('cellphone')
306
+ ? t('VALIDATION_ERROR_MOBILE_PHONE_REQUIRED', 'The field Mobile phone is required').replace('_attribute_', t('CELLPHONE', 'Cellphone'))
307
+ : null
308
+ })
309
+ }, [register])
310
+
311
+ useEffect(() => {
312
+ if (phoneInputData?.phone?.cellphone) setValue('cellphone', phoneInputData?.phone?.cellphone, '')
313
+ else setValue('cellphone', '')
314
+ }, [phoneInputData?.phone?.cellphone])
315
+
331
316
  useEffect(() => {
332
317
  if (verifyPhoneState && !verifyPhoneState?.loading) {
333
318
  if (verifyPhoneState.result?.error) {
@@ -412,48 +397,58 @@ const SignupFormUI = (props: SignupParams) => {
412
397
  !notValidationFields.includes(field.code) &&
413
398
  showField &&
414
399
  showField(field.code) && (
415
- <Controller
416
- key={field.id}
417
- control={control}
418
- render={({ onChange, value }: any) => (
419
- <OInput
420
- placeholder={t(field.name)}
421
- style={style.inputStyle}
422
- icon={
423
- field.code === 'email'
424
- ? theme.images.general.email
425
- : theme.images.general.user
426
- }
427
- value={value}
428
- onChange={(val: any) =>
429
- field.code !== 'email'
430
- ? onChange(val)
431
- : handleChangeInputEmail(val, onChange)
432
- }
433
- autoCapitalize={
434
- field.code === 'email' ? 'none' : 'sentences'
435
- }
436
- autoCorrect={field.code === 'email' && false}
437
- type={
438
- field.code === 'email' ? 'email-address' : 'default'
439
- }
440
- autoCompleteType={
441
- field.code === 'email' ? 'email' : 'off'
442
- }
443
- returnKeyType="next"
444
- blurOnSubmit={false}
445
- forwardRef={(ref: any) => handleRefs(ref, field.code)}
446
- onSubmitEditing={() =>
447
- field.code === 'email'
448
- ? phoneRef?.current?.focus?.()
449
- : handleFocusRef(getNextFieldCode(i))
450
- }
451
- />
400
+ <React.Fragment key={field.id}>
401
+ {errors?.[`${field.code}`] && (
402
+ <OText
403
+ size={14}
404
+ color={theme.colors.danger5}
405
+ weight={'normal'}>
406
+ {errors?.[`${field.code}`]?.message} {errors?.[`${field.code}`]?.type === 'required' && '*'}
407
+ </OText>
452
408
  )}
453
- name={field.code}
454
- rules={getInputRules(field)}
455
- defaultValue=""
456
- />
409
+ <Controller
410
+ control={control}
411
+ render={({ onChange, value }: any) => (
412
+ <OInput
413
+ placeholder={t(field.name)}
414
+ style={style.inputStyle}
415
+ icon={
416
+ field.code === 'email'
417
+ ? theme.images.general.email
418
+ : theme.images.general.user
419
+ }
420
+ value={value}
421
+ onChange={(val: any) =>
422
+ field.code !== 'email'
423
+ ? onChange(val)
424
+ : handleChangeInputEmail(val, onChange)
425
+ }
426
+ autoCapitalize={
427
+ field.code === 'email' ? 'none' : 'sentences'
428
+ }
429
+ autoCorrect={field.code === 'email' && false}
430
+ type={
431
+ field.code === 'email' ? 'email-address' : 'default'
432
+ }
433
+ autoCompleteType={
434
+ field.code === 'email' ? 'email' : 'off'
435
+ }
436
+ returnKeyType="next"
437
+ blurOnSubmit={false}
438
+ forwardRef={(ref: any) => handleRefs(ref, field.code)}
439
+ onSubmitEditing={() =>
440
+ field.code === 'email'
441
+ ? phoneRef?.current?.focus?.()
442
+ : handleFocusRef(getNextFieldCode(i))
443
+ }
444
+ borderColor={errors?.[`${field.code}`] ? theme.colors.danger5 : theme.colors.border}
445
+ />
446
+ )}
447
+ name={field.code}
448
+ rules={getInputRules(field)}
449
+ defaultValue=""
450
+ />
451
+ </React.Fragment>
457
452
  ),
458
453
  )}
459
454
 
@@ -467,6 +462,7 @@ const SignupFormUI = (props: SignupParams) => {
467
462
  returnKeyType: 'next',
468
463
  onSubmitEditing: () => passwordRef?.current?.focus?.(),
469
464
  }}
465
+ isStartValidation={errors?.cellphone}
470
466
  />
471
467
  </View>
472
468
  )}
@@ -499,99 +495,122 @@ const SignupFormUI = (props: SignupParams) => {
499
495
  </View>
500
496
 
501
497
  {configs?.terms_and_conditions?.value === 'true' && (
502
- <View style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 20 }}>
498
+ <>
499
+ {errors?.termsAccept && (
500
+ <OText
501
+ size={14}
502
+ color={theme.colors.danger5}
503
+ weight={'normal'}>
504
+ {errors?.termsAccept?.message}*
505
+ </OText>
506
+ )}
507
+ <View style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 20 }}>
508
+ <Controller
509
+ control={control}
510
+ render={({ onChange, value }: any) => (
511
+ <CheckBox
512
+ value={value}
513
+ onValueChange={newValue => {
514
+ onChange(newValue)
515
+ }}
516
+ boxType={'square'}
517
+ tintColors={{
518
+ true: theme.colors.primary,
519
+ false: theme.colors.disabled
520
+ }}
521
+ tintColor={theme.colors.disabled}
522
+ onCheckColor={theme.colors.primary}
523
+ onTintColor={theme.colors.primary}
524
+ style={Platform.OS === 'ios' && style.checkBoxStyle}
525
+ />
526
+ )}
527
+ name='termsAccept'
528
+ rules={{
529
+ required: t('VALIDATION_ERROR_ACCEPTED', 'The _attribute_ must be accepted.').replace('_attribute_', t('TERMS_AND_CONDITIONS', 'Terms & Conditions'))
530
+ }}
531
+ defaultValue={false}
532
+ />
533
+ <OText style={{ fontSize: 14, paddingHorizontal: 5 }}>{t('TERMS_AND_CONDITIONS_TEXT', 'I agree with')}</OText>
534
+ <OButton
535
+ imgRightSrc={null}
536
+ text={t('TERMS_AND_CONDITIONS', 'Terms & Conditions')}
537
+ bgColor='#FFF'
538
+ borderColor='#FFF'
539
+ style={{ paddingLeft: 0, paddingRight: 0, height: 30, shadowColor: '#FFF' }}
540
+ textStyle={{ color: theme.colors.primary, marginLeft: 0, marginRight: 0 }}
541
+ onClick={() => handleOpenTermsUrl(configs?.terms_and_conditions_url?.value)}
542
+ />
543
+ </View>
544
+ </>
545
+
546
+ )}
547
+
548
+ {signupTab !== 'cellphone' && (
549
+ <>
550
+ {errors?.password && (
551
+ <OText
552
+ size={14}
553
+ color={theme.colors.danger5}
554
+ weight={'normal'}>
555
+ {errors?.password?.message} {errors?.password?.type === 'required' && '*'}
556
+ </OText>
557
+ )}
503
558
  <Controller
504
559
  control={control}
505
560
  render={({ onChange, value }: any) => (
506
- <CheckBox
561
+ <OInput
562
+ isSecured={!passwordSee ? true : false}
563
+ placeholder={t('PASSWORD', 'Password')}
564
+ style={style.inputStyle}
565
+ icon={theme.images.general.lock}
566
+ iconCustomRight={
567
+ !passwordSee ? (
568
+ <MaterialCommunityIcons
569
+ name="eye-outline"
570
+ color={theme.colors.disabled}
571
+ size={24}
572
+ onPress={() => setPasswordSee(!passwordSee)}
573
+ />
574
+ ) : (
575
+ <MaterialCommunityIcons
576
+ name="eye-off-outline"
577
+ color={theme.colors.disabled}
578
+ size={24}
579
+ onPress={() => setPasswordSee(!passwordSee)}
580
+ />
581
+ )
582
+ }
507
583
  value={value}
508
- onValueChange={newValue => {
509
- onChange(newValue)
510
- }}
511
- boxType={'square'}
512
- tintColors={{
513
- true: theme.colors.primary,
514
- false: theme.colors.disabled
515
- }}
516
- tintColor={theme.colors.disabled}
517
- onCheckColor={theme.colors.primary}
518
- onTintColor={theme.colors.primary}
519
- style={Platform.OS === 'ios' && style.checkBoxStyle}
584
+ onChange={(val: any) => onChange(val)}
585
+ returnKeyType="done"
586
+ onSubmitEditing={handleSubmit(onSubmit)}
587
+ blurOnSubmit
588
+ forwardRef={passwordRef}
589
+ borderColor={errors?.password ? theme.colors.danger5 : theme.colors.border}
520
590
  />
521
591
  )}
522
- name='termsAccept'
592
+ name="password"
523
593
  rules={{
524
- required: t('VALIDATION_ERROR_ACCEPTED', 'The _attribute_ must be accepted.').replace('_attribute_', t('TERMS_AND_CONDITIONS', 'Terms & Conditions'))
594
+ required: isRequiredField('password')
595
+ ? t(
596
+ 'VALIDATION_ERROR_PASSWORD_REQUIRED',
597
+ 'The field Password is required',
598
+ ).replace('_attribute_', t('PASSWORD', 'password'))
599
+ : null,
600
+ minLength: {
601
+ value: 8,
602
+ message: t(
603
+ 'VALIDATION_ERROR_PASSWORD_MIN_STRING',
604
+ 'The Password must be at least 8 characters.',
605
+ )
606
+ .replace('_attribute_', t('PASSWORD', 'Password'))
607
+ .replace('_min_', 8),
608
+ },
525
609
  }}
526
- defaultValue={false}
527
- />
528
- <OText style={{ fontSize: 14, paddingHorizontal: 5 }}>{t('TERMS_AND_CONDITIONS_TEXT', 'I agree with')}</OText>
529
- <OButton
530
- imgRightSrc={null}
531
- text={t('TERMS_AND_CONDITIONS', 'Terms & Conditions')}
532
- bgColor='#FFF'
533
- borderColor='#FFF'
534
- style={{ paddingLeft: 0, paddingRight: 0, height: 30, shadowColor: '#FFF' }}
535
- textStyle={{ color: theme.colors.primary, marginLeft: 0, marginRight: 0 }}
536
- onClick={() => handleOpenTermsUrl(configs?.terms_and_conditions_url?.value)}
610
+ defaultValue=""
537
611
  />
538
- </View>
539
- )}
612
+ </>
540
613
 
541
- {signupTab !== 'cellphone' && (
542
- <Controller
543
- control={control}
544
- render={({ onChange, value }: any) => (
545
- <OInput
546
- isSecured={!passwordSee ? true : false}
547
- placeholder={t('PASSWORD', 'Password')}
548
- style={style.inputStyle}
549
- icon={theme.images.general.lock}
550
- iconCustomRight={
551
- !passwordSee ? (
552
- <MaterialCommunityIcons
553
- name="eye-outline"
554
- color={theme.colors.disabled}
555
- size={24}
556
- onPress={() => setPasswordSee(!passwordSee)}
557
- />
558
- ) : (
559
- <MaterialCommunityIcons
560
- name="eye-off-outline"
561
- color={theme.colors.disabled}
562
- size={24}
563
- onPress={() => setPasswordSee(!passwordSee)}
564
- />
565
- )
566
- }
567
- value={value}
568
- onChange={(val: any) => onChange(val)}
569
- returnKeyType="done"
570
- onSubmitEditing={handleSubmit(onSubmit)}
571
- blurOnSubmit
572
- forwardRef={passwordRef}
573
- />
574
- )}
575
- name="password"
576
- rules={{
577
- required: isRequiredField('password')
578
- ? t(
579
- 'VALIDATION_ERROR_PASSWORD_REQUIRED',
580
- 'The field Password is required',
581
- ).replace('_attribute_', t('PASSWORD', 'password'))
582
- : null,
583
- minLength: {
584
- value: 8,
585
- message: t(
586
- 'VALIDATION_ERROR_PASSWORD_MIN_STRING',
587
- 'The Password must be at least 8 characters.',
588
- )
589
- .replace('_attribute_', t('PASSWORD', 'Password'))
590
- .replace('_min_', 8),
591
- },
592
- }}
593
- defaultValue=""
594
- />
595
614
  )}
596
615
  </>
597
616
  ) : (
@@ -115,6 +115,7 @@ export interface PhoneInputParams {
115
115
  noDropIcon?: boolean;
116
116
  flagStyle?: any;
117
117
  isDisabled?: any;
118
+ isStartValidation?: any;
118
119
  }
119
120
 
120
121
  export interface LanguageSelectorParams {