@autobusal/routes-order 1.0.0

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 (50) hide show
  1. package/Found/Booking/Booking.tsx +61 -0
  2. package/Found/Booking/styles.ts +50 -0
  3. package/Found/Dates/Dates.tsx +82 -0
  4. package/Found/Dates/Loading.tsx +14 -0
  5. package/Found/Dates/styles.ts +50 -0
  6. package/Found/Found.tsx +31 -0
  7. package/Found/Header/Header.tsx +21 -0
  8. package/Found/Header/styles.ts +40 -0
  9. package/Found/Orders/Orders.tsx +40 -0
  10. package/Found/Orders/styles.ts +12 -0
  11. package/Found/Route/Route.tsx +112 -0
  12. package/Found/Route/styles.ts +187 -0
  13. package/RoutesOrder.tsx +130 -0
  14. package/Step1/Step1.tsx +63 -0
  15. package/Step1/prepare.ts +38 -0
  16. package/Step2.tsx +55 -0
  17. package/Step3.tsx +56 -0
  18. package/Step4/Loading.tsx +26 -0
  19. package/Step4/Passenger/Passenger.tsx +173 -0
  20. package/Step4/Passenger/styles.ts +10 -0
  21. package/Step4/Passengers/Display.tsx +50 -0
  22. package/Step4/Passengers/Passengers.tsx +96 -0
  23. package/Step4/Passengers/styles.ts +7 -0
  24. package/Step4/Step4.tsx +96 -0
  25. package/Step4/checkAge.ts +48 -0
  26. package/Step5/Billing/Billing.tsx +161 -0
  27. package/Step5/Billing/Loading.tsx +22 -0
  28. package/Step5/Billing/styles.ts +13 -0
  29. package/Step5/Coupons/Coupons.tsx +71 -0
  30. package/Step5/Coupons/styles.ts +20 -0
  31. package/Step5/Payments/Payments.tsx +82 -0
  32. package/Step5/Payments/styles.ts +42 -0
  33. package/Step5/Step5.tsx +131 -0
  34. package/Step5/Summary/About/About.tsx +48 -0
  35. package/Step5/Summary/About/styles.ts +49 -0
  36. package/Step5/Summary/Amount/Amount.tsx +24 -0
  37. package/Step5/Summary/Amount/styles.ts +29 -0
  38. package/Step5/Summary/Summary.tsx +44 -0
  39. package/Step5/Summary/Tickets/Preview.tsx +41 -0
  40. package/Step5/Summary/Tickets/Tickets.tsx +34 -0
  41. package/Step5/Summary/Tickets/styles.ts +36 -0
  42. package/Step5/Summary/styles.ts +11 -0
  43. package/Step5/utilities.ts +24 -0
  44. package/Steps/Steps.tsx +28 -0
  45. package/Steps/styles.ts +47 -0
  46. package/index.ts +3 -0
  47. package/package.json +6 -0
  48. package/services.ts +149 -0
  49. package/styles.ts +24 -0
  50. package/types.ts +32 -0
@@ -0,0 +1,61 @@
1
+ import { TFunction } from 'i18next';
2
+ import { createDate } from '@autobusal/utilities';
3
+ import { Container, About, LinkBooking } from './styles';
4
+ import { RoutesSearchForm } from '@autobusal/routes-search/types';
5
+ import { FoundData } from '@autobusal/providers/types/routes';
6
+ import { GetSettings } from '@autobusal/providers/services';
7
+
8
+ interface Props {
9
+ data?: FoundData[]
10
+ step1: RoutesSearchForm
11
+ t: TFunction<'public'>
12
+ }
13
+
14
+ const Booking = ({ data, step1, t }: Props): (JSX.Element | null) => {
15
+ const { data: settings } = GetSettings();
16
+
17
+ if (data === undefined || data?.length === 0) {
18
+ return null;
19
+ }
20
+
21
+ const destination = data[0].locations.to;
22
+
23
+ // stop if no booking city
24
+ if (destination.city.booking_id === undefined) {
25
+ return null;
26
+ }
27
+
28
+ // stop if no booking id
29
+ if (settings.preferences.bookingID === undefined) {
30
+ return null;
31
+ }
32
+
33
+ const from = createDate(step1.departure);
34
+ const to = getReturnDate(step1._return, from);
35
+
36
+ const location = `https://www.booking.com/searchresults.xu.html?city=${ destination.city.booking_id }&aid=&checkin_monthday=${ from.getDate() }&checkin_month=${ from.getMonth() + 1 }&checkin_year=${ from.getFullYear() }&checkout_monthday=${ to.getDate() }&checkout_month=${ to.getMonth() + 1 }&checkout_year=${ to.getFullYear() }&no_rooms=1&group_adults=2&room1=A%2CA`;
37
+
38
+ return (
39
+ <Container>
40
+ <About>
41
+ { t('routes_order.step2.booking.title', { location: destination.city.name }) }
42
+ <img src="https://s-ec.bstatic.com/static/img/b26logo/booking_logo_retina/22615963add19ac6b6d715a97c8d477e8b95b7ea.png" />
43
+ </About>
44
+
45
+ <LinkBooking to={ location } target="_blank">{ t('routes_order.step2.booking.search') }</LinkBooking>
46
+ </Container>
47
+ );
48
+ };
49
+
50
+ const getReturnDate = (_return: (string | undefined), from: Date): Date => {
51
+ if (_return !== undefined) {
52
+ return createDate(_return);
53
+ }
54
+
55
+ const to = new Date(from);
56
+ to.setDate(to.getDate() + 2);
57
+
58
+ return to;
59
+ };
60
+
61
+ export default Booking;
@@ -0,0 +1,50 @@
1
+ import { Link } from 'react-router-dom';
2
+ import styled from 'styled-components';
3
+
4
+ export const Container = styled.div`
5
+ display: flex;
6
+ flex-direction: column;
7
+ gap: 15px;
8
+ margin-bottom: 15px;
9
+ padding: 15px;
10
+ background: #003580;
11
+ border-radius: ${ props => props.theme.borderRadius };
12
+
13
+ @media (min-width: 480px) {
14
+ flex-direction: row;
15
+ align-items: center;
16
+ }
17
+ `;
18
+
19
+ export const About = styled.div`
20
+ color: #FFFFFF;
21
+ font-weight: 700;
22
+
23
+ > img {
24
+ display: block;
25
+ width: 110px;
26
+ margin-top: 6px;
27
+ }
28
+ `;
29
+
30
+ export const LinkBooking = styled(Link)`
31
+ display: inline-block;
32
+ max-width: 150px;
33
+ padding: 6px 0;
34
+ text-align: center;
35
+ font-weight: 700;
36
+ color: #333333;
37
+ background: #FFFFFF;
38
+ border-radius: 100px;
39
+ transition: all 0.3s ease;
40
+
41
+ &:hover {
42
+ text-decoration: none;
43
+ box-shadow: 0 4px 24px 0 rgba(255, 255, 255, 0.5);
44
+ }
45
+
46
+ @media (min-width: 480px) {
47
+ flex-basis: 150px;
48
+ margin-left: auto;
49
+ }
50
+ `;
@@ -0,0 +1,82 @@
1
+ import { useState } from 'react';
2
+ import { TFunction } from 'i18next';
3
+ import { AiOutlineCaretLeft, AiOutlineCaretRight } from 'react-icons/ai';
4
+ import { Button } from '@autobusal/common';
5
+ import { createDate, getFormattedShort } from '@autobusal/utilities';
6
+ import Loading from './Loading';
7
+ import { Container, Inner, ButtonDay } from './styles';
8
+ import { RoutesSearchForm } from '@autobusal/routes-search/types';
9
+ import { FoundDay } from '../../types';
10
+ import { GetDates } from '../../services';
11
+
12
+ interface Props {
13
+ type: 'departure' | '_return'
14
+ step1: RoutesSearchForm
15
+ t: TFunction<'common'>
16
+ onSearch: (type: ('departure' | '_return'), day: string) => void
17
+ }
18
+
19
+ const Dates = ({ type, step1, t, onSearch }: Props): JSX.Element => {
20
+ const [number, setNumber] = useState<number>(0);
21
+
22
+ const { data, isLoading } = GetDates(step1, type, number);
23
+
24
+ if (isLoading) {
25
+ return <Loading t={ t } />;
26
+ }
27
+
28
+ const onClick = (item: FoundDay, available: boolean): void => {
29
+ if (available) {
30
+ onSearch(type, item.day);
31
+
32
+ setNumber(0);
33
+ }
34
+ };
35
+
36
+ const onPrevious = (): void => {
37
+ setNumber(number - 1);
38
+ };
39
+
40
+ const onNext = (): void => {
41
+ setNumber(number + 1);
42
+ };
43
+
44
+ const today = new Date();
45
+
46
+ const items = data?.map(item => {
47
+ const date = createDate(item.day);
48
+
49
+ // if date in the future, and we have routes available
50
+ const isAvailable = today.getTime() <= date.getTime() && item.routes;
51
+
52
+ return (
53
+ <ButtonDay key={ item.id } $available={ isAvailable } $selected={ step1[type] === item.day } onClick={ () => onClick(item, isAvailable) }>{ getFormattedShort(date, t) }</ButtonDay>
54
+ );
55
+ })
56
+
57
+ return (
58
+ <Container>
59
+ <Inner>
60
+ <Button
61
+ type="button"
62
+ size="small"
63
+ noMargin
64
+ text={ <AiOutlineCaretLeft /> }
65
+ onClick={ onPrevious }
66
+ />
67
+
68
+ { items }
69
+
70
+ <Button
71
+ type="button"
72
+ size="small"
73
+ noMargin
74
+ text={ <AiOutlineCaretRight /> }
75
+ onClick={ onNext }
76
+ />
77
+ </Inner>
78
+ </Container>
79
+ );
80
+ };
81
+
82
+ export default Dates;
@@ -0,0 +1,14 @@
1
+ import { TFunction } from 'i18next';
2
+ import { Container, ContainerLoading } from './styles';
3
+
4
+ interface Props {
5
+ t: TFunction<'common'>
6
+ }
7
+
8
+ const Loading = ({ t }: Props): JSX.Element => (
9
+ <Container>
10
+ <ContainerLoading>{ t('routes_order.step2.loading.dates') }</ContainerLoading>
11
+ </Container>
12
+ );
13
+
14
+ export default Loading;
@@ -0,0 +1,50 @@
1
+ import styled, { css } from 'styled-components';
2
+
3
+ export const Container = styled.div`
4
+ overflow-x: auto;
5
+ margin-bottom: 15px;
6
+ padding: 10px 15px;
7
+ background-color: ${ props => props.theme.background.neutral };
8
+ border-radius: ${ props => props.theme.borderRadius };
9
+ `;
10
+
11
+ export const Inner = styled.div`
12
+ min-width: 900px;
13
+ display: flex;
14
+ gap: 5px;
15
+ justify-content: center;
16
+ align-items: center;
17
+ `;
18
+
19
+ export const ButtonDay = styled.button<{ $available: boolean, $selected: boolean }>`
20
+ flex: 1;
21
+ height: 26px;
22
+ line-height: 26px;
23
+ font-weight: 700;
24
+ text-align: center;
25
+ font-size: ${ props => props.theme.size.s };
26
+ border-radius: 100px;
27
+ transition: all 0.3s ease;
28
+
29
+ ${ props => !props.$available && css`
30
+ opacity: .6;
31
+ ` }
32
+
33
+ &:hover {
34
+ color: ${ props => props.theme.primary.contrast };
35
+ background: ${ props => props.theme.primary.normal };
36
+ }
37
+
38
+ ${ props => props.$selected && css`
39
+ color: ${ props => props.theme.primary.contrast };
40
+ background: ${ props => props.theme.primary.normal } !important;
41
+ ` }
42
+ `;
43
+
44
+ export const ContainerLoading = styled.div`
45
+ height: 26px;
46
+ text-align: center;
47
+ font-weight: 700;
48
+ line-height: 26px;
49
+ font-size: ${ props => props.theme.size.s };
50
+ `;
@@ -0,0 +1,31 @@
1
+ import { TFunction } from 'i18next';
2
+ import Dates from './Dates/Dates';
3
+ import Booking from './Booking/Booking';
4
+ import Header from './Header/Header';
5
+ import Orders from './Orders/Orders';
6
+ import { RoutesSearchForm } from '@autobusal/routes-search/types';
7
+ import { FoundData } from '@autobusal/providers/types/routes';
8
+
9
+ interface Props {
10
+ type: 'departure' | '_return'
11
+ loading: boolean
12
+ data?: FoundData[]
13
+ step1: RoutesSearchForm
14
+ t: TFunction<'common'>
15
+ onSearch: (type: ('departure' | '_return'), day: string) => void
16
+ onSave: (data: FoundData) => void
17
+ }
18
+
19
+ const Found = ({ type, loading, data, step1, t, onSearch, onSave }: Props): JSX.Element => (
20
+ <>
21
+ <Dates type={ type } step1={ step1 } t={ t } onSearch={ onSearch } />
22
+
23
+ { (data !== undefined && data.length > 0) && <Booking data={ data } step1={ step1 } t={ t } /> }
24
+
25
+ <Header t={ t } />
26
+
27
+ <Orders loading={ loading } data={ data } t={ t } onSave={ onSave } />
28
+ </>
29
+ );
30
+
31
+ export default Found;
@@ -0,0 +1,21 @@
1
+ import { TFunction } from 'i18next';
2
+ import { RiBus2Line } from 'react-icons/ri';
3
+ import { BiArrowFromLeft, BiHourglass } from 'react-icons/bi';
4
+ import { FaFlagCheckered, FaCoins } from 'react-icons/fa';
5
+ import { Container, Company, Location, Time, Price } from './styles';
6
+
7
+ interface Props {
8
+ t: TFunction<'public'>
9
+ }
10
+
11
+ const Header = ({ t }: Props): JSX.Element => (
12
+ <Container className="box">
13
+ <Company><RiBus2Line /> { t('routes_order.step2.header.company') }</Company>
14
+ <Location><BiArrowFromLeft /> { t('routes_order.step2.header.departs') }</Location>
15
+ <Time><BiHourglass /> { t('routes_order.step2.header.duration') }</Time>
16
+ <Location><FaFlagCheckered /> { t('routes_order.step2.header.arrives') }</Location>
17
+ <Price><FaCoins /> { t('routes_order.step2.header.price') }</Price>
18
+ </Container>
19
+ );
20
+
21
+ export default Header;
@@ -0,0 +1,40 @@
1
+ import styled from 'styled-components';
2
+
3
+ export const Container = styled.div`
4
+ display: none;
5
+ padding: 8px 4px;
6
+ margin-bottom: 15px;
7
+
8
+ @media (min-width: 1024px) {
9
+ display: flex;
10
+ }
11
+ `;
12
+
13
+ const Item = styled.div`
14
+ display: flex;
15
+ flex-direction: column;
16
+ gap: 4px;
17
+ font-weight: 700;
18
+ align-items: center;
19
+ font-size: ${ props => props.theme.size.s };
20
+
21
+ & > svg {
22
+ font-size: ${ props => props.theme.size.xxl };
23
+ }
24
+ `;
25
+
26
+ export const Company = styled(Item)`
27
+ flex-basis: 185px;
28
+ `;
29
+
30
+ export const Location = styled(Item)`
31
+ flex: 1;
32
+ `;
33
+
34
+ export const Time = styled(Item)`
35
+ flex-basis: 120px;
36
+ `;
37
+
38
+ export const Price = styled(Item)`
39
+ flex-basis: 165px;
40
+ `;
@@ -0,0 +1,40 @@
1
+ import { TFunction } from 'i18next';
2
+ import { Inline } from '@autobusal/common';
3
+ import Route from '../Route/Route';
4
+ import { Container, ContainerNotFound } from './styles';
5
+ import { FoundData } from '@autobusal/providers/types/routes';
6
+
7
+ interface Props {
8
+ loading: boolean
9
+ data?: FoundData[]
10
+ t: TFunction<'common'>
11
+ onSave: (data: FoundData) => void
12
+ }
13
+
14
+ const Orders = ({ loading, data, t, onSave }: Props): JSX.Element => {
15
+ if (loading) {
16
+ return (
17
+ <div className="box">
18
+ <Inline text={ t('routes_order.step2.loading.routes') } />
19
+ </div>
20
+ );
21
+ }
22
+
23
+ if (data?.length == 0) {
24
+ return (
25
+ <ContainerNotFound className="box">{ t('routes_order.step2.not_found') }</ContainerNotFound>
26
+ );
27
+ }
28
+
29
+ const items = data?.map(item => (
30
+ <Route key={ item.id } data={ item } t={ t } onSave={ onSave } />
31
+ ));
32
+
33
+ return (
34
+ <Container>
35
+ { items }
36
+ </Container>
37
+ );
38
+ };
39
+
40
+ export default Orders;
@@ -0,0 +1,12 @@
1
+ import styled from 'styled-components';
2
+
3
+ export const Container = styled.div`
4
+ display: flex;
5
+ flex-direction: column;
6
+ gap: 15px;
7
+ `;
8
+
9
+ export const ContainerNotFound = styled.div`
10
+ text-align: center;
11
+ color: ${ props => props.theme.font.faded };
12
+ `;
@@ -0,0 +1,112 @@
1
+ import { useState } from 'react';
2
+ import { TFunction } from 'i18next';
3
+ import { HiOutlineArrowNarrowRight, HiOutlineDocumentText } from 'react-icons/hi';
4
+ import { RiBus2Line } from 'react-icons/ri';
5
+ import { Button, RouteFeature, Policy } from '@autobusal/common';
6
+ import { Container, SpecialOffer, Trip, Company, Location, Time, Price, LinkCompany, Details, Detail, ButtonPolicy, TitleDetail, LinkRoute, Features, TitleFeatures, FeaturesItems } from './styles';
7
+ import { FoundData } from '@autobusal/providers/types/routes';
8
+
9
+ interface Props {
10
+ data: FoundData
11
+ t: TFunction<'common'>
12
+ onSave: (data: FoundData) => void
13
+ }
14
+
15
+ const Route = ({ data, t, onSave }: Props): JSX.Element => {
16
+ const [ policy, setPolicy ] = useState<boolean>(false);
17
+
18
+ const customs = Object.values(data.customs).join(', ');
19
+ const transiting = Object.values(data.transiting).join(', ');
20
+
21
+ const features = data.trip.features_data.map((item, index) => (
22
+ <RouteFeature key={ index } item={ item } />
23
+ ));
24
+
25
+ return (
26
+ <Container className="box">
27
+ { data.price.offer === true && <SpecialOffer>{ t('routes_order.step2.route.offer') }</SpecialOffer> }
28
+
29
+ <Trip>
30
+ <Company>
31
+ <LinkCompany to={ data.operator.link } target="_blank" title={ data.operator.company }><img src={ data.operator.logo_url } /></LinkCompany>
32
+ </Company>
33
+
34
+ <Location>
35
+ <h2>{ data.locations.from.city.name } ({ data.locations.from.city.country?.iso_code }) { data.locations.from.departure }</h2>
36
+ <span>{ data.locations.from.stop?.name }</span>
37
+ </Location>
38
+
39
+ <Time>
40
+ <HiOutlineArrowNarrowRight />
41
+ <span>{ data.duration }</span>
42
+ </Time>
43
+
44
+ <Location>
45
+ <h2>{ data.locations.to.city.name } ({ data.locations.to.city.country?.iso_code }) { data.locations.to.arrival }</h2>
46
+ <span>{ data.locations.to.stop?.name }</span>
47
+ </Location>
48
+
49
+ <Price>
50
+ <strong>{ data.price.display }</strong>
51
+
52
+ <Button
53
+ type="button"
54
+ size="medium"
55
+ noMargin
56
+ text={
57
+ <>
58
+ <RiBus2Line />
59
+ { t('routes_order.step2.route.select') }
60
+ </>
61
+ }
62
+ onClick={ () => onSave(data) }
63
+ />
64
+ </Price>
65
+ </Trip>
66
+
67
+ <Details>
68
+ <Detail>
69
+ <TitleDetail>{ t('routes_order.step2.route.name') }</TitleDetail>
70
+ <LinkRoute to={ data.link } target="_blank">{ data.code }</LinkRoute>
71
+ </Detail>
72
+
73
+ { customs && (
74
+ <Detail>
75
+ <TitleDetail>{ t('routes_order.step2.route.customs') }</TitleDetail>
76
+ { customs }
77
+ </Detail>
78
+ ) }
79
+
80
+ { transiting !== '' && (
81
+ <Detail>
82
+ <TitleDetail>{ t('routes_order.step2.route.transit') }</TitleDetail>
83
+ { transiting }
84
+ </Detail>
85
+ ) }
86
+
87
+ <Detail>
88
+ <TitleDetail>{ t('routes_order.step2.route.policies.title') }</TitleDetail>
89
+
90
+ <ButtonPolicy type="button" onClick={ () => setPolicy(true) }>
91
+ <HiOutlineDocumentText />
92
+ { t('routes_order.step2.route.policies.view') }
93
+ </ButtonPolicy>
94
+ </Detail>
95
+ </Details>
96
+
97
+ { data.trip.features_data.length > 0 && (
98
+ <Features>
99
+ <TitleFeatures>{ t('routes_order.step2.route.features') }</TitleFeatures>
100
+
101
+ <FeaturesItems>
102
+ { features }
103
+ </FeaturesItems>
104
+ </Features>
105
+ ) }
106
+
107
+ { policy && <Policy id={ data.id } t={ t } onClose={ () => setPolicy(false) } /> }
108
+ </Container>
109
+ );
110
+ };
111
+
112
+ export default Route;