ordering-ui-react-native 0.16.29 → 0.16.32

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 +15 -2
  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,519 @@
1
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
2
+ import { Fade, Placeholder, PlaceholderLine } from 'rn-placeholder';
3
+ import Geolocation from '@react-native-community/geolocation'
4
+ import { getTrackingStatus, requestTrackingPermission } from 'react-native-tracking-transparency'
5
+ import {
6
+ View,
7
+ StyleSheet,
8
+ ScrollView,
9
+ Platform,
10
+ TouchableOpacity,
11
+ RefreshControl,
12
+ AppState
13
+ } from 'react-native';
14
+ import {
15
+ BusinessList as BusinessesListingController,
16
+ useLanguage,
17
+ useSession,
18
+ useOrder,
19
+ useConfig,
20
+ useUtils,
21
+ } from 'ordering-components/native';
22
+ import { useTheme } from 'styled-components/native';
23
+ import Ionicons from 'react-native-vector-icons/Ionicons'
24
+
25
+ import {
26
+ Search,
27
+ OrderControlContainer,
28
+ AddressInput,
29
+ WrapMomentOption,
30
+ HeaderWrapper,
31
+ ListWrapper,
32
+ FeaturedWrapper,
33
+ OrderProgressWrapper,
34
+ FarAwayMessage,
35
+ AddressInputContainer
36
+ } from './styles';
37
+
38
+ import { SearchBar } from '../../../SearchBar';
39
+ import { OIcon, OText } from '../../../shared';
40
+ import { BusinessesListingParams } from '../../../../types';
41
+ import { NotFoundSource } from '../../../NotFoundSource';
42
+ import { BusinessTypeFilter } from '../../../BusinessTypeFilter';
43
+ import { BusinessController } from '../../../BusinessController';
44
+ import { OrderTypeSelector } from '../../../OrderTypeSelector';
45
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
46
+ import { BusinessFeaturedController } from '../../../BusinessFeaturedController';
47
+ import { HighestRatedBusinesses } from '../../../HighestRatedBusinesses';
48
+ import { getTypesText, convertToRadian } from '../../../../utils';
49
+ import { OrderProgress } from '../../../OrderProgress';
50
+ import { useFocusEffect, useIsFocused } from '@react-navigation/native';
51
+
52
+ const PIXELS_TO_SCROLL = 2000;
53
+
54
+ const BusinessesListingUI = (props: BusinessesListingParams) => {
55
+ const {
56
+ navigation,
57
+ businessesList,
58
+ searchValue,
59
+ getBusinesses,
60
+ handleChangeBusinessType,
61
+ handleBusinessClick,
62
+ paginationProps,
63
+ handleChangeSearch,
64
+ businessId,
65
+ isGuestUser,
66
+ handleUpdateBusinessList
67
+ } = props;
68
+ const theme = useTheme();
69
+ const isFocused = useIsFocused();
70
+ const appState = useRef(AppState.currentState)
71
+ const [appStateVisible, setAppStateVisible] = useState(appState.current);
72
+ const [refreshing] = useState(false);
73
+ const styles = StyleSheet.create({
74
+ container: {
75
+ marginBottom: 0,
76
+ },
77
+ welcome: {
78
+ flex: 1,
79
+ flexDirection: 'row',
80
+ },
81
+ inputStyle: {
82
+ backgroundColor: theme.colors.inputDisabled,
83
+ flex: 1,
84
+ },
85
+ wrapperOrderOptions: {
86
+ width: '100%',
87
+ flexDirection: 'row',
88
+ justifyContent: 'center',
89
+ marginBottom: 10,
90
+ zIndex: 100,
91
+ },
92
+ borderStyle: {
93
+ borderColor: theme.colors.backgroundGray,
94
+ borderWidth: 1,
95
+ borderRadius: 10,
96
+ },
97
+ searchInput: {
98
+ fontSize: 16,
99
+ backgroundColor: theme.colors.white,
100
+ paddingLeft: 10,
101
+ paddingTop: 7
102
+ },
103
+ iconStyle: {
104
+ fontSize: 18,
105
+ color: theme.colors.warning5,
106
+ marginRight: 8
107
+ },
108
+ farAwayMsg: {
109
+ paddingVertical: 6,
110
+ paddingHorizontal: 20
111
+ },
112
+ inputContainerStyles: {
113
+ backgroundColor: theme.colors.white,
114
+ borderColor: theme.colors.backgroundGray,
115
+ borderWidth: 1,
116
+ }
117
+ });
118
+
119
+
120
+ const [, t] = useLanguage();
121
+ const [{ user, auth }] = useSession();
122
+ const [orderState] = useOrder();
123
+ const [{ configs }] = useConfig();
124
+ const [{ parseDate }] = useUtils();
125
+
126
+ const { top } = useSafeAreaInsets();
127
+
128
+ const [featuredBusiness, setFeaturedBusinesses] = useState(Array);
129
+ const [isFarAway, setIsFarAway] = useState(false)
130
+ const [businessTypes, setBusinessTypes] = useState(null)
131
+ const [orderTypeValue, setOrderTypeValue] = useState(orderState?.options.value)
132
+ const isPreorderEnabled = (configs?.preorder_status_enabled?.value === '1' || configs?.preorder_status_enabled?.value === 'true') &&
133
+ Number(configs?.max_days_preorder?.value) > 0
134
+ const isPreOrderSetting = configs?.preorder_status_enabled?.value === '1'
135
+ const timerId = useRef<any>(false)
136
+ const [favoriteIds, setFavoriteIds] = useState<any>([])
137
+
138
+ // const panResponder = useRef(
139
+ // PanResponder.create({
140
+ // onMoveShouldSetPanResponder: (e, gestureState) => {
141
+ // const { dx, dy } = gestureState;
142
+ // resetInactivityTimeout()
143
+ // return (Math.abs(dx) > 20) || (Math.abs(dy) > 20);
144
+ // },
145
+ // })
146
+ // ).current
147
+
148
+ const handleMomentClick = () => {
149
+ if (isPreorderEnabled) {
150
+ navigation.navigate('MomentOption')
151
+ }
152
+ }
153
+
154
+ const configTypes =
155
+ configs?.order_types_allowed?.value
156
+ .split('|')
157
+ .map((value: any) => Number(value)) || [];
158
+
159
+ const handleScroll = ({ nativeEvent }: any) => {
160
+ const y = nativeEvent.contentOffset.y;
161
+ const height = nativeEvent.contentSize.height;
162
+ const hasMore = !(
163
+ paginationProps.totalPages === paginationProps.currentPage
164
+ );
165
+
166
+ if (y + PIXELS_TO_SCROLL > height && !businessesList.loading && hasMore) {
167
+ getBusinesses();
168
+ }
169
+ };
170
+
171
+ const getDistance = (lat1: any, lon1: any, lat2: any, lon2: any) => {
172
+ const R = 6371 // km
173
+ const dLat = convertToRadian(lat2 - lat1)
174
+ const dLon = convertToRadian(lon2 - lon1)
175
+ const curLat1 = convertToRadian(lat1)
176
+ const curLat2 = convertToRadian(lat2)
177
+ const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.sin(dLon / 2) * Math.sin(dLon / 2) * Math.cos(curLat1) * Math.cos(curLat2)
178
+ const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
179
+ return R * c
180
+ }
181
+
182
+ const resetInactivityTimeout = () => {
183
+ clearTimeout(timerId.current)
184
+ timerId.current = setInterval(() => {
185
+ getBusinesses(true)
186
+ }, 120000)
187
+ }
188
+
189
+ useEffect(() => {
190
+ if (!businessesList?.loading) {
191
+ const fb = businessesList.businesses.filter((b) => b.featured === true && b?.open);
192
+ const ary = [];
193
+ while (fb.length > 0) {
194
+ ary.push(fb.splice(0, 2));
195
+ }
196
+ setFeaturedBusinesses(ary);
197
+ }
198
+ resetInactivityTimeout()
199
+ }, [businessesList.loading])
200
+
201
+ const handleOnRefresh = () => {
202
+ if (!businessesList.loading) {
203
+ getBusinesses(true);
204
+ }
205
+ }
206
+
207
+ const checkUserLocation = async () => {
208
+ let trackingStatus = await getTrackingStatus()
209
+ if (trackingStatus === 'not-determined') {
210
+ trackingStatus = await requestTrackingPermission()
211
+ }
212
+ if (trackingStatus === 'authorized' || trackingStatus === 'unavailable') {
213
+ Geolocation.getCurrentPosition((pos) => {
214
+ const crd = pos.coords
215
+ const distance = getDistance(crd.latitude, crd.longitude, orderState?.options?.address?.location?.lat, orderState?.options?.address?.location?.lng)
216
+ if (distance > 20) setIsFarAway(true)
217
+ else setIsFarAway(false)
218
+ }, (err) => {
219
+ console.log(`ERROR(${err.code}): ${err.message}`)
220
+ }, {
221
+ enableHighAccuracy: true, timeout: 15000, maximumAge: 10000
222
+ })
223
+ }
224
+ }
225
+
226
+ useEffect(() => {
227
+ checkUserLocation()
228
+ }, [orderState?.options?.address?.location])
229
+
230
+ useEffect(() => {
231
+ if (!orderState?.loading) {
232
+ setOrderTypeValue(orderState?.options?.type)
233
+ }
234
+ }, [orderState?.options?.type])
235
+
236
+ useFocusEffect(
237
+ useCallback(() => {
238
+ resetInactivityTimeout()
239
+ return () => clearTimeout(timerId.current)
240
+ }, [navigation])
241
+ )
242
+
243
+ useEffect(() => {
244
+ if (!businessesList?.businesses?.length) return
245
+ const ids = [...favoriteIds]
246
+ businessesList.businesses.forEach((business: any) => {
247
+ if (business?.favorite) {
248
+ ids.push(business?.id)
249
+ }
250
+ })
251
+ setFavoriteIds([...new Set(ids)])
252
+ }, [businessesList?.businesses?.length])
253
+
254
+ return (
255
+ <ScrollView style={styles.container} onScroll={(e) => handleScroll(e)} showsVerticalScrollIndicator={false}
256
+ refreshControl={
257
+ <RefreshControl
258
+ refreshing={refreshing}
259
+ onRefresh={() => handleOnRefresh()}
260
+ />
261
+ }
262
+ >
263
+ <View style={{ height: isFarAway ? 150 : 100, marginTop: Platform.OS == 'ios' ? 0 : 50 }}>
264
+ <Search>
265
+ <AddressInput
266
+ onPress={() =>
267
+ auth
268
+ ? navigation.navigate('AddressList', { isFromBusinesses: true })
269
+ : navigation.navigate('AddressForm', {
270
+ address: orderState.options?.address,
271
+ isFromBusinesses: true,
272
+ isGuestUser: isGuestUser
273
+ })
274
+ }>
275
+ <AddressInputContainer>
276
+ <OIcon
277
+ src={theme.images.general.pin}
278
+ color={theme.colors.disabled}
279
+ width={16}
280
+ style={{ marginRight: 10 }}
281
+ />
282
+ <OText size={12} numberOfLines={1}>
283
+ {orderState?.options?.address?.address}
284
+ </OText>
285
+ <OIcon
286
+ src={theme.images.general.arrow_down}
287
+ width={10}
288
+ style={{ marginStart: 8 }}
289
+ />
290
+ </AddressInputContainer>
291
+ </AddressInput>
292
+ </Search>
293
+ {isFarAway && (
294
+ <FarAwayMessage style={styles.farAwayMsg}>
295
+ <Ionicons name='md-warning-outline' style={styles.iconStyle} />
296
+ <OText size={12} numberOfLines={1} ellipsizeMode={'tail'} color={theme.colors.textNormal}>{t('YOU_ARE_FAR_FROM_ADDRESS', 'You are far from this address')}</OText>
297
+ </FarAwayMessage>
298
+ )}
299
+
300
+ <OrderControlContainer>
301
+ <View style={styles.wrapperOrderOptions}>
302
+ {isPreOrderSetting && (
303
+ <WrapMomentOption
304
+ onPress={() => handleMomentClick()}>
305
+ <OText
306
+ size={12}
307
+ numberOfLines={1}
308
+ ellipsizeMode="tail"
309
+ color={theme.colors.textSecondary}>
310
+ {orderState.options?.moment
311
+ ? parseDate(orderState.options?.moment, { outputFormat: configs?.dates_moment_format?.value })
312
+ : t('ASAP_ABBREVIATION', 'ASAP')}
313
+ </OText>
314
+ {isPreorderEnabled && (
315
+ <OIcon
316
+ src={theme.images.general.arrow_down}
317
+ width={10}
318
+ style={{ marginStart: 8 }}
319
+ />
320
+ )}
321
+ </WrapMomentOption>
322
+ )}
323
+ <WrapMomentOption onPress={() => navigation.navigate('OrderTypes', { configTypes: configTypes, setOrderTypeValue })}>
324
+ <OText size={12} numberOfLines={1} ellipsizeMode={'tail'} color={theme.colors.textSecondary}>{t(getTypesText(orderTypeValue || orderState?.options?.type || 1), 'Delivery')}</OText>
325
+ <OIcon
326
+ src={theme.images.general.arrow_down}
327
+ width={10}
328
+ style={{ marginStart: 8 }}
329
+ />
330
+ </WrapMomentOption>
331
+ </View>
332
+ </OrderControlContainer>
333
+ </View>
334
+ <HeaderWrapper
335
+ source={theme.images.general.homeHero}
336
+ style={{ paddingTop: top + 20 }}
337
+ resizeMode='stretch'
338
+ >
339
+ {!auth && (
340
+ <TouchableOpacity onPress={() => navigation?.canGoBack() && navigation.goBack()} style={{ position: 'absolute', marginStart: 40, paddingVertical: 20 }}>
341
+ <OIcon src={theme.images.general.arrow_left} width={20} style={{ tintColor: theme.colors.white }} />
342
+ </TouchableOpacity>
343
+ )}
344
+ </HeaderWrapper>
345
+ {!businessId && (
346
+ <SearchBar
347
+ onSearch={handleChangeSearch}
348
+ searchValue={searchValue}
349
+ lazyLoad
350
+ hideIcon
351
+ isCancelXButtonShow={!!searchValue}
352
+ onCancel={() => handleChangeSearch('')}
353
+ placeholder={t('SEARCH', 'Search')}
354
+ height={50}
355
+ isDisabled={!businessTypes}
356
+ inputContainerStyles={styles.inputContainerStyles}
357
+ containerStyles={{
358
+ marginHorizontal: 40,
359
+ marginTop: 20
360
+ }}
361
+ inputStyle={{ ...styles.searchInput, ...Platform.OS === 'ios' ? { paddingBottom: 6 } : { paddingBottom: 4 } }}
362
+ onSubmitEditing={() => { configs?.advanced_business_search_enabled?.value === '1' && navigation.navigate('BusinessSearch', { businessTypes, defaultTerm: searchValue }) }}
363
+ />
364
+ )}
365
+ <OrderProgressWrapper>
366
+ <OrderProgress
367
+ {...props}
368
+ isFocused={isFocused}
369
+ />
370
+ </OrderProgressWrapper>
371
+
372
+ {
373
+ !businessId && !props.franchiseId && featuredBusiness && featuredBusiness.length > 0 && (
374
+ <FeaturedWrapper>
375
+ <OText size={16} style={{ marginLeft: 40 }} weight={Platform.OS === 'ios' ? '600' : 'bold'}>{t('BUSINESS_FEATURE', 'Featured business')}</OText>
376
+ <ScrollView
377
+ showsHorizontalScrollIndicator={false}
378
+ nestedScrollEnabled
379
+ horizontal contentContainerStyle={{ paddingHorizontal: 40 }}>
380
+ {featuredBusiness.map((bAry: any, idx) => (
381
+ <View key={'f-listing_' + idx}>
382
+ <BusinessFeaturedController
383
+ business={bAry[0]}
384
+ isBusinessOpen={bAry[0]?.open}
385
+ handleCustomClick={handleBusinessClick}
386
+ orderType={orderState?.options?.type}
387
+ />
388
+ {bAry.length > 1 && (
389
+ <BusinessFeaturedController
390
+ business={bAry[1]}
391
+ isBusinessOpen={bAry[1]?.open}
392
+ handleCustomClick={handleBusinessClick}
393
+ orderType={orderState?.options?.type}
394
+ />
395
+ )}
396
+ </View>
397
+ ))}
398
+ </ScrollView>
399
+ </FeaturedWrapper>
400
+ )
401
+ }
402
+ <View style={{ height: 8, backgroundColor: theme.colors.backgroundGray100 }} />
403
+ {
404
+ !businessId && !props.franchiseId && (
405
+ <HighestRatedBusinesses
406
+ onBusinessClick={handleBusinessClick}
407
+ navigation={navigation}
408
+ favoriteIds={favoriteIds}
409
+ setFavoriteIds={setFavoriteIds}
410
+ />
411
+ )
412
+ }
413
+ <View style={{ height: 8, backgroundColor: theme.colors.backgroundGray100 }} />
414
+ <ListWrapper>
415
+ {!businessId && (
416
+ <BusinessTypeFilter
417
+ images={props.images}
418
+ businessTypes={props.businessTypes}
419
+ defaultBusinessType={props.defaultBusinessType}
420
+ handleChangeBusinessType={handleChangeBusinessType}
421
+ setBusinessTypes={setBusinessTypes}
422
+ />
423
+ )}
424
+ {!businessesList.loading && businessesList.businesses.length === 0 && (
425
+ <NotFoundSource
426
+ content={t(
427
+ 'NOT_FOUND_BUSINESSES',
428
+ 'No businesses to delivery / pick up at this address, please change filters or change address.',
429
+ )}
430
+ />
431
+ )}
432
+ {businessesList.businesses?.map(
433
+ (business: any, i: number) => (
434
+ <BusinessController
435
+ key={`${business.id}_` + i}
436
+ business={business}
437
+ isBusinessOpen={business.open}
438
+ handleCustomClick={handleBusinessClick}
439
+ orderType={orderState?.options?.type}
440
+ navigation={navigation}
441
+ businessHeader={business?.header}
442
+ businessFeatured={business?.featured}
443
+ businessLogo={business?.logo}
444
+ businessReviews={business?.reviews}
445
+ businessDeliveryPrice={business?.delivery_price}
446
+ businessDeliveryTime={business?.delivery_time}
447
+ businessPickupTime={business?.pickup_time}
448
+ businessDistance={business?.distance}
449
+ handleUpdateBusinessList={handleUpdateBusinessList}
450
+ favoriteIds={favoriteIds}
451
+ setFavoriteIds={setFavoriteIds}
452
+ />
453
+ )
454
+ )}
455
+ {businessesList.loading && (
456
+ <>
457
+ {[
458
+ ...Array(
459
+ paginationProps.nextPageItems
460
+ ? paginationProps.nextPageItems
461
+ : 8,
462
+ ).keys(),
463
+ ].map((item, i) => (
464
+ <Placeholder
465
+ Animation={Fade}
466
+ key={i}
467
+ style={{ marginBottom: 20 }}>
468
+ <View style={{ width: '100%' }}>
469
+ <PlaceholderLine
470
+ height={200}
471
+ style={{ marginBottom: 20, borderRadius: 25 }}
472
+ />
473
+ <View style={{ paddingHorizontal: 10 }}>
474
+ <View
475
+ style={{
476
+ flexDirection: 'row',
477
+ justifyContent: 'space-between',
478
+ }}>
479
+ <PlaceholderLine
480
+ height={25}
481
+ width={40}
482
+ style={{ marginBottom: 10 }}
483
+ />
484
+ <PlaceholderLine
485
+ height={25}
486
+ width={20}
487
+ style={{ marginBottom: 10 }}
488
+ />
489
+ </View>
490
+ <PlaceholderLine
491
+ height={20}
492
+ width={30}
493
+ style={{ marginBottom: 10 }}
494
+ />
495
+ <PlaceholderLine
496
+ height={20}
497
+ width={80}
498
+ style={{ marginBottom: 10 }}
499
+ />
500
+ </View>
501
+ </View>
502
+ </Placeholder>
503
+ ))}
504
+ </>
505
+ )}
506
+ </ListWrapper>
507
+ </ScrollView>
508
+ );
509
+ };
510
+
511
+ export const BusinessesListing = (props: BusinessesListingParams) => {
512
+ const BusinessesListingProps = {
513
+ ...props,
514
+ isForceSearch: Platform.OS === 'ios',
515
+ UIComponent: BusinessesListingUI,
516
+ };
517
+
518
+ return <BusinessesListingController {...BusinessesListingProps} />;
519
+ };