ordering-ui-react-native 0.16.30 → 0.16.33

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.
Files changed (24) hide show
  1. package/package.json +2 -1
  2. package/src/components/AddressForm/index.tsx +4 -1
  3. package/themes/business/src/components/Chat/index.tsx +4 -4
  4. package/themes/original/index.tsx +8 -0
  5. package/themes/original/src/components/BusinessBasicInformation/index.tsx +1 -1
  6. package/themes/original/src/components/BusinessProductsListing/index.tsx +255 -208
  7. package/themes/original/src/components/BusinessProductsListing/styles.tsx +5 -0
  8. package/themes/original/src/components/BusinessTypeFilter/index.tsx +106 -38
  9. package/themes/original/src/components/BusinessTypeFilter/styles.tsx +2 -0
  10. package/themes/original/src/components/BusinessesListing/Layout/Appointment/index.tsx +549 -0
  11. package/themes/original/src/components/BusinessesListing/Layout/Appointment/styles.tsx +106 -0
  12. package/themes/original/src/components/BusinessesListing/Layout/Original/index.tsx +519 -0
  13. package/themes/original/src/components/BusinessesListing/{styles.tsx → Layout/Original/styles.tsx} +0 -0
  14. package/themes/original/src/components/BusinessesListing/index.tsx +13 -515
  15. package/themes/original/src/components/MomentSelector/index.tsx +197 -0
  16. package/themes/original/src/components/MomentSelector/styles.tsx +6 -0
  17. package/themes/original/src/components/ProfessionalFilter/index.tsx +128 -0
  18. package/themes/original/src/components/ProfessionalFilter/styles.tsx +0 -0
  19. package/themes/original/src/components/ProfessionalProfile/index.tsx +297 -0
  20. package/themes/original/src/components/ProfessionalProfile/styles.tsx +46 -0
  21. package/themes/original/src/components/ServiceForm/index.tsx +485 -0
  22. package/themes/original/src/components/ServiceForm/styles.tsx +50 -0
  23. package/themes/original/src/types/index.tsx +31 -1
  24. package/themes/original/src/utils/index.tsx +11 -0
@@ -0,0 +1,485 @@
1
+ import React, { useState, useEffect, useRef } from 'react'
2
+ import { useTheme } from 'styled-components/native'
3
+ import { Platform, View, StyleSheet, Dimensions } from 'react-native'
4
+ import { OText, OButton } from '../shared'
5
+ import FastImage from 'react-native-fast-image'
6
+ import IconAntDesign from 'react-native-vector-icons/AntDesign'
7
+ import SelectDropdown from 'react-native-select-dropdown'
8
+ import moment from 'moment'
9
+ import CalendarPicker from 'react-native-calendar-picker'
10
+ import FeatherIcon from 'react-native-vector-icons/Feather';
11
+ import { useSafeAreaInsets } from 'react-native-safe-area-context'
12
+ import { ServiceFormParams } from '../../types'
13
+ import {
14
+ ProductForm as ProductFormController,
15
+ useUtils,
16
+ useLanguage,
17
+ useConfig,
18
+ useOrder,
19
+ useSession
20
+ } from 'ordering-components/native'
21
+
22
+ import {
23
+ Container,
24
+ ProfessionalPhoto,
25
+ InfoWrapper,
26
+ Divider,
27
+ ProfessionalWrapper,
28
+ ScheduleWrapper,
29
+ CalendarWrapper,
30
+ ButtonWrapper
31
+ } from './styles'
32
+
33
+ const screenWidth = Dimensions.get('window').width
34
+
35
+ const ServiceFormUI = (props: ServiceFormParams) => {
36
+ const {
37
+ professionalSelected,
38
+ product,
39
+ handleSave,
40
+ productCart,
41
+ navigation,
42
+ isSoldOut,
43
+ maxProductQuantity,
44
+ onClose
45
+ } = props
46
+
47
+ const theme = useTheme()
48
+ const [, t] = useLanguage()
49
+ const [{ optimizeImage, parsePrice, parseDate }] = useUtils()
50
+ const { top } = useSafeAreaInsets()
51
+ const [{ configs }] = useConfig()
52
+ const [orderState] = useOrder()
53
+ const [{ auth }] = useSession()
54
+
55
+ const [selectDate, setSelectedDate] = useState<any>(new Date())
56
+ const [timeList, setTimeList] = useState<any>([])
57
+ const [isEnabled, setIsEnabled] = useState(false)
58
+ const [timeSelected, setTimeSelected] = useState(null)
59
+ const [dateSelected, setDateSelected] = useState<any>(null)
60
+
61
+ const dropdownRef = useRef<any>(null)
62
+
63
+ const styles = StyleSheet.create({
64
+ photoStyle: {
65
+ width: 42,
66
+ height: 42,
67
+ borderRadius: 7.6
68
+ },
69
+ buttonStyle: {
70
+ borderRadius: 7.6,
71
+ height: 44,
72
+ borderWidth: 0
73
+ },
74
+ professionalSelect: {
75
+ borderRadius: 7.6,
76
+ padding: 11,
77
+ borderWidth: 1,
78
+ borderColor: theme.colors.backgroundGray200
79
+ },
80
+ selectOption: {
81
+ width: '100%',
82
+ backgroundColor: theme.colors.backgroundGray100,
83
+ paddingVertical: 5,
84
+ paddingHorizontal: 14,
85
+ flexDirection: 'row-reverse',
86
+ alignItems: 'center',
87
+ justifyContent: 'space-between',
88
+ height: 40,
89
+ marginBottom: 30
90
+ }
91
+ })
92
+
93
+ const isBusyTime = () => {
94
+ if (professionalSelected?.busy_times?.length === 0 || !dateSelected) return false
95
+ const valid = professionalSelected?.busy_times.some((item: any) => {
96
+ return moment(item?.start).valueOf() <= moment(dateSelected).valueOf() &&
97
+ moment(dateSelected).valueOf() <= moment(item?.end).valueOf()
98
+ })
99
+ return valid
100
+ }
101
+
102
+ const onDateChange = (date: any) => {
103
+ setSelectedDate(date)
104
+ setTimeSelected(null)
105
+ dropdownRef.current.reset()
106
+ }
107
+
108
+ const dropDownIcon = () => {
109
+ return (
110
+ <IconAntDesign
111
+ name='down'
112
+ color={theme.colors.textThird}
113
+ size={12}
114
+ />
115
+ )
116
+ }
117
+
118
+ const customDayHeaderStylesCallback = () => {
119
+ return {
120
+ textStyle: {
121
+ color: theme.colors.disabled,
122
+ fontSize: 12,
123
+ },
124
+ };
125
+ };
126
+
127
+ const handleSaveService = () => {
128
+ const updated = { serviceTime: dateSelected }
129
+ handleSave && handleSave(updated)
130
+ }
131
+
132
+ const validateSelectedDate = (curdate: any, menu: any) => {
133
+ const day = moment(curdate).format('d')
134
+ setIsEnabled(menu?.schedule?.[day]?.enabled || false)
135
+ }
136
+
137
+ const handleRedirectLogin = () => {
138
+ navigation.navigate('Login', {
139
+ store_slug: props.businessSlug
140
+ });
141
+ onClose && onClose()
142
+ };
143
+
144
+ const getTimes = (curdate: any, menu: any) => {
145
+ validateSelectedDate(curdate, menu)
146
+ const date = new Date()
147
+ var dateSeleted = new Date(curdate)
148
+ var times = []
149
+ for (var k = 0; k < menu.schedule[dateSeleted.getDay()].lapses.length; k++) {
150
+ var open = {
151
+ hour: menu.schedule[dateSeleted.getDay()].lapses[k].open.hour,
152
+ minute: menu.schedule[dateSeleted.getDay()].lapses[k].open.minute
153
+ }
154
+ var close = {
155
+ hour: menu.schedule[dateSeleted.getDay()].lapses[k].close.hour,
156
+ minute: menu.schedule[dateSeleted.getDay()].lapses[k].close.minute
157
+ }
158
+ for (var i = open.hour; i <= close.hour; i++) {
159
+ if (date.getDate() !== dateSeleted.getDate() || i >= date.getHours()) {
160
+ let hour = ''
161
+ let meridian = ''
162
+ if (configs?.format_time?.value === '12') {
163
+ if (i === 0) {
164
+ hour = '12'
165
+ meridian = ' ' + t('AM', 'AM')
166
+ } else if (i > 0 && i < 12) {
167
+ hour = (i < 10 ? '0' + i : i)
168
+ meridian = ' ' + t('AM', 'AM')
169
+ } else if (i === 12) {
170
+ hour = '12'
171
+ meridian = ' ' + t('PM', 'PM')
172
+ } else {
173
+ hour = ((i - 12 < 10) ? '0' + (i - 12) : `${(i - 12)}`)
174
+ meridian = ' ' + t('PM', 'PM')
175
+ }
176
+ } else {
177
+ hour = i < 10 ? '0' + i : i
178
+ }
179
+ for (let j = (i === open.hour ? open.minute : 0); j <= (i === close.hour ? close.minute : 59); j += 15) {
180
+ if (i !== date.getHours() || j >= date.getMinutes() || date.getDate() !== dateSeleted.getDate()) {
181
+ times.push({
182
+ text: hour + ':' + (j < 10 ? '0' + j : j) + meridian,
183
+ value: (i < 10 ? '0' + i : i) + ':' + (j < 10 ? '0' + j : j)
184
+ })
185
+ }
186
+ }
187
+ }
188
+ }
189
+ }
190
+ return times
191
+ }
192
+
193
+ const addressRedirect = () => {
194
+ navigation.navigate('AddressList')
195
+ onClose && onClose()
196
+ }
197
+
198
+ useEffect(() => {
199
+ if (selectDate === null) return
200
+ const _times = getTimes(selectDate, professionalSelected)
201
+ setTimeList(_times)
202
+ }, [selectDate, professionalSelected])
203
+
204
+ useEffect(() => {
205
+ if (!selectDate || !timeSelected) {
206
+ setDateSelected(null)
207
+ return
208
+ }
209
+ const date = `${moment(selectDate).format('YYYY-MM-DD')} ${timeSelected}:00`
210
+ setDateSelected(date)
211
+ }, [selectDate, timeSelected])
212
+
213
+ return (
214
+ <Container>
215
+ <ProfessionalPhoto
216
+ source={{
217
+ uri:
218
+ product?.images ||
219
+ optimizeImage(theme?.images?.dummies?.product, 'h_250,c_limit'),
220
+ }}
221
+ />
222
+ <InfoWrapper>
223
+ <OText
224
+ size={20}
225
+ style={{ marginBottom: 4 }}
226
+ weight={Platform.OS === 'ios' ? '600' : 'bold'}
227
+ >
228
+ {product?.name}
229
+ </OText>
230
+ <OText
231
+ size={16}
232
+ style={{ marginBottom: 10 }}
233
+ weight={'400'}
234
+ >
235
+ {parsePrice(product?.price)} • {product?.duration}min
236
+ </OText>
237
+ <OText
238
+ size={14}
239
+ weight={'400'}
240
+ color={theme?.colors?.disabled}
241
+ >
242
+ {product?.description}
243
+ </OText>
244
+ </InfoWrapper>
245
+ <Divider />
246
+ <ProfessionalWrapper>
247
+ <View
248
+ style={{
249
+ flexDirection: 'row',
250
+ justifyContent: 'space-between',
251
+ alignItems: 'center',
252
+ marginBottom: 23
253
+ }}
254
+ >
255
+ <OText
256
+ size={16}
257
+ weight={Platform.OS === 'ios' ? '600' : 'bold'}
258
+ >
259
+ {t('PROFESSIONAL', 'Proffesional')}
260
+ </OText>
261
+ <OText
262
+ size={10}
263
+ weight={'400'}
264
+ color={theme.colors?.danger5}
265
+ >
266
+ {t('REQUIRED', 'Required')}
267
+ </OText>
268
+ </View>
269
+ <View
270
+ style={styles.professionalSelect}
271
+ >
272
+ <View style={{ flexDirection: 'row' }}>
273
+ <FastImage
274
+ style={styles.photoStyle}
275
+ source={{
276
+ uri: optimizeImage(professionalSelected?.photo, 'h_250,c_limit'),
277
+ priority: FastImage.priority.normal,
278
+ }}
279
+ resizeMode={FastImage.resizeMode.cover}
280
+ />
281
+ <View style={{ marginLeft: 14 }}>
282
+ <OText
283
+ size={14}
284
+ weight={'400'}
285
+ >
286
+ {professionalSelected?.name} {professionalSelected?.lastname}
287
+ </OText>
288
+ <OText
289
+ size={12}
290
+ weight={'400'}
291
+ color={isBusyTime() ? theme.colors.danger5 : theme.colors.success500}
292
+ >
293
+ {isBusyTime()
294
+ ? t('BUSY_ON_SELECTED_TIME', 'Busy on selected time')
295
+ : t('AVAILABLE', 'Available')
296
+ }
297
+ </OText>
298
+ </View>
299
+ </View>
300
+ </View>
301
+ </ProfessionalWrapper>
302
+ <ScheduleWrapper>
303
+ <View
304
+ style={{
305
+ flexDirection: 'row',
306
+ justifyContent: 'space-between',
307
+ alignItems: 'center',
308
+ marginBottom: 23
309
+ }}
310
+ >
311
+ <OText
312
+ size={16}
313
+ weight={Platform.OS === 'ios' ? '600' : 'bold'}
314
+ >
315
+ {t('SCHEDULE', 'Schedule')}
316
+ </OText>
317
+ <OText
318
+ size={10}
319
+ weight={'400'}
320
+ color={theme.colors?.danger5}
321
+ >
322
+ {t('REQUIRED', 'Required')}
323
+ </OText>
324
+ </View>
325
+ {(!!professionalSelected?.schedule && isEnabled) ? (
326
+ <CalendarWrapper>
327
+ {timeList?.length > 0 ? (
328
+ <SelectDropdown
329
+ ref={dropdownRef}
330
+ defaultValue={timeSelected}
331
+ data={timeList}
332
+ onSelect={(selectedItem, index) => {
333
+ setTimeSelected(selectedItem.value)
334
+ }}
335
+ buttonTextAfterSelection={(selectedItem, index) => {
336
+ return selectedItem.text
337
+ }}
338
+ rowTextForSelection={(item, index) => {
339
+ return item.text
340
+ }}
341
+ buttonStyle={{borderRadius: 7.6, ...styles.selectOption}}
342
+ buttonTextStyle={{
343
+ color: theme.colors.disabled,
344
+ fontSize: 14,
345
+ textAlign: 'left',
346
+ marginHorizontal: 0
347
+ }}
348
+ dropdownStyle={{
349
+ borderRadius: 8,
350
+ borderColor: theme.colors.lightGray,
351
+ marginTop: Platform.OS === 'ios' ? 12 : -top
352
+ }}
353
+ rowStyle={{
354
+ borderBottomColor: theme.colors.backgroundGray100,
355
+ backgroundColor: theme.colors.backgroundGray100,
356
+ height: 30,
357
+ flexDirection: 'column',
358
+ alignItems: 'flex-start',
359
+ paddingTop: 8,
360
+ paddingHorizontal: 12
361
+ }}
362
+ rowTextStyle={{
363
+ color: theme.colors.disabled,
364
+ fontSize: 14,
365
+ marginHorizontal: 0
366
+ }}
367
+ renderDropdownIcon={() => dropDownIcon()}
368
+ dropdownOverlayColor='transparent'
369
+ />
370
+ ) : (
371
+ <OText
372
+ size={20}
373
+ style={{ marginBottom: 30 }}
374
+ weight={Platform.OS === 'ios' ? '600' : 'bold'}
375
+ >
376
+ {t('NOT_AVAILABLE', 'Not available')}
377
+ </OText>
378
+ )}
379
+
380
+ <CalendarPicker
381
+ previousComponent={
382
+ <FeatherIcon
383
+ name='chevron-left'
384
+ color={theme.colors.disabled}
385
+ size={24}
386
+ style={{ marginHorizontal: 4 }}
387
+ />
388
+ }
389
+ nextComponent={
390
+ <FeatherIcon
391
+ name='chevron-right'
392
+ color={theme.colors.disabled}
393
+ size={24}
394
+ style={{ marginHorizontal: 4 }}
395
+ />
396
+ }
397
+ width={screenWidth - 110}
398
+ selectedDayTextColor={theme.colors.white}
399
+ selectedDayColor={theme.colors.primary}
400
+ todayBackgroundColor={theme.colors.border}
401
+ dayLabelsWrapper={{ borderColor: theme.colors.clear }}
402
+ onDateChange={onDateChange}
403
+ minDate={new Date()}
404
+ customDayHeaderStyles={customDayHeaderStylesCallback}
405
+ selectedStartDate={selectDate}
406
+ />
407
+ </CalendarWrapper>
408
+ ) : (
409
+ <OText
410
+ size={20}
411
+ style={{ marginBottom: 30 }}
412
+ weight={Platform.OS === 'ios' ? '600' : 'bold'}
413
+ >
414
+ {t('NO_SCHEDULE', 'No schedule')}
415
+ </OText>
416
+ )}
417
+ </ScheduleWrapper>
418
+ <ButtonWrapper>
419
+ <OText
420
+ size={14}
421
+ weight={Platform.OS === 'ios' ? '600' : 'bold'}
422
+ >
423
+ {dateSelected
424
+ ? moment(dateSelected).format('hh:mm A')
425
+ : t('ASAP_ABBREVIATION', 'ASAP')}
426
+ </OText>
427
+ {((productCart &&
428
+ auth &&
429
+ orderState.options?.address_id)) && (
430
+ <OButton
431
+ bgColor={theme.colors.primary}
432
+ onClick={() => handleSaveService()}
433
+ text={orderState.loading
434
+ ? t('LOADING', 'Loading')
435
+ : ((isSoldOut || maxProductQuantity <= 0)
436
+ ? t('SOLD_OUT', 'Sold out')
437
+ : t('BOOK', 'Book'))}
438
+ style={styles.buttonStyle}
439
+ isDisabled={isSoldOut || maxProductQuantity <= 0 || !professionalSelected?.id || !dateSelected}
440
+ textStyle={{ fontSize: 14, color: theme.colors.white }}
441
+ />
442
+ )}
443
+ {auth &&
444
+ !orderState.options?.address_id &&
445
+ (orderState.loading ? (
446
+ <OButton
447
+ isDisabled
448
+ text={t('LOADING', 'Loading')}
449
+ imgRightSrc=""
450
+ textStyle={{ fontSize: 10 }}
451
+ />
452
+ ) : (
453
+ <OButton onClick={() => addressRedirect()} />
454
+ ))}
455
+ {!auth && (
456
+ <OButton
457
+ isDisabled={isSoldOut || maxProductQuantity <= 0}
458
+ onClick={() => handleRedirectLogin()}
459
+ text={
460
+ isSoldOut || maxProductQuantity <= 0
461
+ ? t('SOLD_OUT', 'Sold out')
462
+ : t('LOGIN_SIGNUP', 'Login / Sign Up')
463
+ }
464
+ imgRightSrc=""
465
+ textStyle={{ color: theme.colors.primary, fontSize: 14 }}
466
+ style={{
467
+ height: 44,
468
+ borderColor: theme.colors.primary,
469
+ backgroundColor: theme.colors.white,
470
+ }}
471
+ />
472
+ )}
473
+ </ButtonWrapper>
474
+ </Container>
475
+ )
476
+ }
477
+
478
+ export const ServiceForm = (props: any) => {
479
+ const serviceFormProps = {
480
+ ...props,
481
+ UIComponent: ServiceFormUI,
482
+ isService: true
483
+ }
484
+ return <ProductFormController {...serviceFormProps} />
485
+ }
@@ -0,0 +1,50 @@
1
+ import styled from 'styled-components/native'
2
+
3
+ export const Container = styled.ScrollView``
4
+
5
+ export const ProfessionalPhoto = styled.ImageBackground`
6
+ width: 100%;
7
+ position: relative;
8
+ max-height: 258px;
9
+ height: 258px;
10
+ resize-mode: cover;
11
+ `;
12
+
13
+ export const InfoWrapper = styled.View`
14
+ padding-horizontal: 40px;
15
+ margin-vertical: 30px;
16
+ `
17
+
18
+ export const Divider = styled.View`
19
+ width: 100%;
20
+ height: 8px;
21
+ background-color: ${(props: any) => props.theme.colors.backgroundGray100};
22
+ `
23
+
24
+ export const ProfessionalWrapper = styled.View`
25
+ padding-horizontal: 40px;
26
+ margin-top: 30px;
27
+ `
28
+
29
+ export const ScheduleWrapper = styled(ProfessionalWrapper)``
30
+
31
+ export const CalendarWrapper = styled.View`
32
+ flex: 1;
33
+ border-width: 1px;
34
+ border-color: ${(props: any) => props.theme.colors.backgroundGray200};
35
+ border-radius: 7.6px;
36
+ padding: 15px;
37
+ `
38
+
39
+ export const ButtonWrapper = styled.View`
40
+ justify-content: space-between;
41
+ align-items: center;
42
+ flex-direction: row;
43
+ padding-vertical: 13px;
44
+ padding-horizontal: 40px;
45
+ margin-top: 30px;
46
+ margin-bottom: 40px;
47
+ width: 100%;
48
+ border-top-width: 1px;
49
+ border-top-color: ${(props: any) => props.theme.colors.backgroundGray200};
50
+ `
@@ -155,6 +155,9 @@ export interface BusinessesListingParams {
155
155
  businessId?: any;
156
156
  isGuestUser?: any;
157
157
  handleUpdateBusinessList?: any;
158
+ priceLevelSelected?: any;
159
+ handleChangePriceLevel?: any;
160
+ businessTypeSelected?: any;
158
161
  }
159
162
  export interface HighestRatedBusinessesParams {
160
163
  businessesList: { businesses: Array<any>, loading: boolean, error: null | string };
@@ -173,7 +176,8 @@ export interface BusinessTypeFilterParams {
173
176
  defaultBusinessType?: string | null;
174
177
  images?: any
175
178
  typesState?: any
176
- setBusinessTypes?: any
179
+ setBusinessTypes?: any,
180
+ isAppoint?: boolean | undefined
177
181
  }
178
182
  export interface BusinessControllerParams {
179
183
  key?: string | number;
@@ -221,6 +225,8 @@ export interface BusinessProductsListingParams {
221
225
  setProductLogin?: () => {};
222
226
  updateProductModal?: (value: any) => {};
223
227
  handleUpdateProducts?: any;
228
+ professionalSelected?: any;
229
+ handleChangeProfessionalSelected?: any;
224
230
  }
225
231
  export interface BusinessBasicInformationParams {
226
232
  navigation?: any;
@@ -684,6 +690,30 @@ export interface PreviousBusinessOrderedParams {
684
690
  businessLoading?: boolean
685
691
  }
686
692
 
693
+ export interface ServiceFormParams {
694
+ navigation?: any,
695
+ professionalSelected: any,
696
+ product: any,
697
+ handleSave: (value?: any) => {}
698
+ productCart?: any
699
+ isSoldOut: boolean,
700
+ maxProductQuantity: any,
701
+ businessSlug?: string,
702
+ onClose: any
703
+ }
704
+
705
+ export interface ProfessionalFilterParams {
706
+ professionals?: any,
707
+ professionalSelected?: any,
708
+ handleChangeProfessionalSelected: any
709
+ }
710
+
711
+ export interface ProfessionalProfileParams {
712
+ professional: any,
713
+ handleChangeProfessionalSelected: any,
714
+ onClose: any
715
+ }
716
+
687
717
  export interface PreviousProductsOrderedParams {
688
718
  products?: any,
689
719
  onProductClick?: any,
@@ -229,3 +229,14 @@ export const formatSeconds = (seconds : number) => {
229
229
  ret += '' + secs
230
230
  return ret
231
231
  }
232
+
233
+ /**
234
+ * List of price to filter businesses
235
+ */
236
+ export const priceList = [
237
+ { level: '1', content: '$' },
238
+ { level: '2', content: '$$' },
239
+ { level: '3', content: '$$$' },
240
+ { level: '4', content: '$$$$' },
241
+ { level: '5', content: '$$$$$' }
242
+ ]