@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,187 @@
1
+ import styled from 'styled-components';
2
+ import { Link } from 'react-router-dom';
3
+
4
+ export const Container = styled.div`
5
+ position: relative;
6
+ display: flex;
7
+ flex-direction: column;
8
+ gap: 15px;
9
+ padding: 15px;
10
+ `;
11
+
12
+ export const SpecialOffer = styled.div`
13
+ position: absolute;
14
+ top: 13px;
15
+ left: -15px;
16
+ z-index: 1;
17
+ width: 100px;
18
+ height: 60px;
19
+ color: #FFF;
20
+ line-height: 59px;
21
+ font-weight: 700;
22
+ text-align: center;
23
+ text-transform: uppercase;
24
+ letter-spacing: 2px;
25
+ background-image: url(${ import.meta.env.VITE_API_OBT }/storage/routes/offer.png);
26
+ `;
27
+
28
+ export const Trip = styled.div`
29
+ display: flex;
30
+ flex-direction: column;
31
+ gap: 15px;
32
+
33
+ @media (min-width: 640px) {
34
+ flex-direction: row;
35
+ flex-wrap: wrap;
36
+ }
37
+
38
+ @media (min-width: 1024px) {
39
+ flex-wrap: nowrap;
40
+ }
41
+ `;
42
+
43
+ export const Company = styled.div`
44
+ flex-basis: 100%;
45
+
46
+ @media (min-width: 1024px) {
47
+ flex-basis: 185px;
48
+ }
49
+ `;
50
+
51
+ export const Location = styled.div`
52
+ flex: 1;
53
+ display: flex;
54
+ flex-direction: column;
55
+ align-items: center;
56
+ gap: 5px;
57
+ text-align: center;
58
+
59
+ & > h2 {
60
+ margin-bottom: 0;
61
+ }
62
+
63
+ & > span {
64
+ font-weight: 700;
65
+ color: ${ props => props.theme.font.faded };
66
+ }
67
+ `;
68
+
69
+ export const Time = styled.div`
70
+ display: flex;
71
+ flex-direction: column;
72
+ gap: 8px;
73
+ align-items: center;
74
+ font-weight: 700;
75
+
76
+ & > svg {
77
+ font-size: calc(1.5 * ${ props => props.theme.size.xxl });
78
+ }
79
+
80
+ & > span {
81
+ display: block;
82
+ padding: 3px 10px;
83
+ font-size: ${ props => props.theme.size.l };
84
+ background: ${ props => props.theme.background.neutral };
85
+ border-radius: 100px;
86
+ }
87
+
88
+ @media (min-width: 640px) {
89
+ flex-basis: 120px;
90
+ }
91
+ `;
92
+
93
+ export const Price = styled.div`
94
+ flex-basis: 100%;
95
+ display: flex;
96
+ flex-direction: column;
97
+ gap: 10px;
98
+ align-items: center;
99
+
100
+ & > strong {
101
+ font-size: ${ props => props.theme.size.xxl };
102
+ }
103
+
104
+ @media (min-width: 768px) {
105
+ flex-basis: 165px;
106
+ }
107
+ `;
108
+
109
+ export const LinkCompany = styled(Link)`
110
+ display: block;
111
+ max-width: 165px;
112
+ margin: 0 auto;
113
+
114
+ & > img {
115
+ display: block;
116
+ width: 100%;
117
+ border-radius: 8px;
118
+ }
119
+ `;
120
+
121
+ export const Details = styled.div`
122
+ display: flex;
123
+ flex-direction: column;
124
+ gap: 15px;
125
+
126
+ @media (min-width: 640px) {
127
+ flex-direction: row;
128
+ flex-wrap: wrap;
129
+ }
130
+ `;
131
+
132
+ export const Detail = styled.div`
133
+ padding: 8px 10px;
134
+ font-size: ${ props => props.theme.size.s };
135
+ background: ${ props => props.theme.background.neutral };
136
+ border-radius: ${ props => props.theme.borderRadius };
137
+
138
+ @media (min-width: 480px) {
139
+ flex-basis: calc(50% - 7.5px);
140
+ }
141
+ @media (min-width: 768px) {
142
+ flex-basis: initial;
143
+ flex: 1;
144
+ }
145
+ `;
146
+
147
+ export const TitleDetail = styled.h6`
148
+ margin-bottom: 5px;
149
+ color: ${ props => props.theme.font.faded };
150
+ text-transform: uppercase;
151
+ font-size: ${ props => props.theme.size.xs };
152
+ `;
153
+
154
+ export const LinkRoute = styled(Link)`
155
+ font-weight: 700;
156
+ `;
157
+
158
+ export const ButtonPolicy = styled.button`
159
+ display: flex;
160
+ align-items: center;
161
+ gap: 5px;
162
+ font-weight: 700;
163
+ font-size: ${ props => props.theme.size.s };
164
+ transition: all 0.3s ease;
165
+
166
+ &:hover {
167
+ opacity: .8;
168
+ }
169
+ `;
170
+
171
+ export const Features = styled.div`
172
+ display: flex;
173
+ flex-direction: column;
174
+ align-items: center;
175
+ `;
176
+
177
+ export const TitleFeatures = styled.h6`
178
+ font-size: ${ props => props.theme.size.s };
179
+ margin-bottom: 8px;
180
+ `;
181
+
182
+ export const FeaturesItems = styled.div`
183
+ display: flex;
184
+ flex-wrap: wrap;
185
+ gap: 8px;
186
+ justify-content: center;
187
+ `;
@@ -0,0 +1,130 @@
1
+ import { useState } from 'react';
2
+ import { TFunction } from 'i18next';
3
+ import { Meta } from '@autobusal/common';
4
+ import Step1 from './Step1/Step1';
5
+ import Step2 from './Step2';
6
+ import Step3 from './Step3';
7
+ import Step4 from './Step4/Step4';
8
+ import Step5 from './Step5/Step5';
9
+ import { RoutesSearchForm } from '@autobusal/routes-search/types';
10
+ import { FoundData } from '@autobusal/providers/types/routes';
11
+ import { PersonData } from '@autobusal/providers/types/persons';
12
+
13
+ interface Props {
14
+ t: TFunction<'common'>
15
+ }
16
+
17
+ const RoutesOrder = ({ t }: Props): JSX.Element => {
18
+ const [ redirected, setRedirected ] = useState<boolean>(false);
19
+ const [ current, setCurrent ] = useState<number>(1);
20
+ const [ step1, setStep1 ] = useState<RoutesSearchForm | undefined>(undefined);
21
+ const [ step2, setStep2 ] = useState<FoundData | undefined>(undefined);
22
+ const [ step3, setStep3 ] = useState<FoundData | undefined>(undefined);
23
+ const [ step4, setStep4 ] = useState<PersonData | undefined>(undefined);
24
+
25
+ const onSearchDate = (type: ('departure' | '_return'), day: string): void => {
26
+ if (step1 !== undefined) {
27
+ setStep1({
28
+ ...step1,
29
+ [type]: day
30
+ });
31
+ }
32
+ };
33
+
34
+ const onStep1 = (data: RoutesSearchForm): void => {
35
+ setStep1(data);
36
+ setCurrent(2);
37
+
38
+ // we mark it as redirected
39
+ setRedirected(true);
40
+ };
41
+
42
+ const onStep2 = (data: FoundData): void => {
43
+ setStep2(data);
44
+
45
+ if (step1?.type === 'return') {
46
+ setCurrent(3);
47
+ } else {
48
+ setCurrent(4);
49
+ }
50
+ };
51
+
52
+ const onStep3 = (data: FoundData): void => {
53
+ setStep3(data);
54
+ setCurrent(4);
55
+ };
56
+
57
+ const onStep4 = (data: PersonData): void => {
58
+ setStep4(data);
59
+ setCurrent(5);
60
+ };
61
+
62
+ const onBack = (step: number): void => {
63
+ setCurrent(step);
64
+ };
65
+
66
+ const showStep1 = current === 1;
67
+ const showStep2 = current === 2 && step1 !== undefined;
68
+ const showStep3 = current === 3 && step1 !== undefined && step2 !== undefined;
69
+ const showStep4 = current === 4 && step1 !== undefined && step2 !== undefined;
70
+ const showStep5 = current === 5 && step1 !== undefined && step2 !== undefined && step4 !== undefined;
71
+
72
+ return (
73
+ <Meta title={ t('routes_order.title') } description={ t('routes_order.meta.description') }>
74
+ { showStep1 && (
75
+ <Step1
76
+ value={ step1 }
77
+ redirected={ redirected }
78
+ t={ t }
79
+ onSave={ onStep1 }
80
+ />
81
+ ) }
82
+
83
+ { showStep2 && (
84
+ <Step2
85
+ step1={ step1 }
86
+ t={ t }
87
+ onSearch={ onSearchDate }
88
+ onSave={ onStep2 }
89
+ onBack={ onBack }
90
+ />
91
+ ) }
92
+
93
+ { showStep3 && (
94
+ <Step3
95
+ step1={ step1 }
96
+ step2={ step2 }
97
+ t={ t }
98
+ onSearch={ onSearchDate }
99
+ onSave={ onStep3 }
100
+ onBack={ onBack }
101
+ />
102
+ ) }
103
+
104
+ { showStep4 && (
105
+ <Step4
106
+ values={ step4 }
107
+ step1={ step1 }
108
+ step2={ step2 }
109
+ step3={ step3 }
110
+ t={ t }
111
+ onSave={ onStep4 }
112
+ onBack={ onBack }
113
+ />
114
+ ) }
115
+
116
+ { showStep5 && (
117
+ <Step5
118
+ step1={ step1 }
119
+ step2={ step2 }
120
+ step3={ step3 }
121
+ step4={ step4 }
122
+ t={ t }
123
+ onBack={ onBack }
124
+ />
125
+ ) }
126
+ </Meta>
127
+ );
128
+ };
129
+
130
+ export default RoutesOrder;
@@ -0,0 +1,63 @@
1
+ import { useEffect } from 'react';
2
+ import { useParams, useSearchParams } from 'react-router-dom';
3
+ import { TFunction } from 'i18next';
4
+ import { FaRoute } from 'react-icons/fa';
5
+ import RoutesSearch from '@autobusal/routes-search';
6
+ import prepare from './prepare';
7
+ import Steps from '../Steps/Steps';
8
+ import { Title } from '../styles';
9
+ import { RoutesSearchForm } from '@autobusal/routes-search/types';
10
+ import { GetSettings } from '@autobusal/providers/services';
11
+
12
+ interface Props {
13
+ value?: RoutesSearchForm
14
+ redirected: boolean
15
+ t: TFunction<'common'>
16
+ onSave: (data: RoutesSearchForm) => void
17
+ }
18
+
19
+ const Step1 = ({ value, redirected, t, onSave }: Props): JSX.Element => {
20
+ const params = useParams();
21
+
22
+ const [ searchParams ] = useSearchParams();
23
+
24
+ const { data } = GetSettings();
25
+
26
+ const prepared = prepare(value, params, data.preferences.defaultLocations);
27
+
28
+ const isSubmitted = searchParams.get('type') === 'submit';
29
+
30
+ useEffect(() => {
31
+ if (isSubmitted && !redirected) {
32
+ onSave(prepared);
33
+ }
34
+ }, [isSubmitted, redirected, prepared, onSave]);
35
+
36
+ return (
37
+ <>
38
+ <Title>
39
+ <FaRoute />
40
+ { t('routes_order.step1.title') }
41
+ </Title>
42
+
43
+ <Steps value={ 1 } />
44
+
45
+ <div className="box">
46
+ <RoutesSearch
47
+ type={ prepared.type ?? 'departure' }
48
+ from={ prepared.from }
49
+ to={ prepared.to }
50
+ departure={ prepared.departure }
51
+ _return={ prepared._return ?? '' }
52
+ adultsNo={ prepared.adults }
53
+ childrenNo={ prepared.children }
54
+ babiesNo={ prepared.babies }
55
+ t={ t }
56
+ onSearch={ onSave }
57
+ />
58
+ </div>
59
+ </>
60
+ );
61
+ };
62
+
63
+ export default Step1;
@@ -0,0 +1,38 @@
1
+ import { Params } from 'react-router-dom';
2
+ import { futureDate } from '@autobusal/utilities';
3
+ import { RoutesSearchForm } from '@autobusal/routes-search/types';
4
+
5
+ const prepare = (
6
+ value: (RoutesSearchForm | undefined),
7
+ params: Readonly<Params<string>>,
8
+ defaultLocations: { from: string, to: string }
9
+ ): RoutesSearchForm => {
10
+ if (value !== undefined) {
11
+ return value;
12
+ }
13
+
14
+ const type = params.type === 'return' ? 'return' : 'departure';
15
+
16
+ const from = params.from ?? defaultLocations.from;
17
+ const to = params.to ?? defaultLocations.to;
18
+
19
+ const departure = params.departure?.replace(/-/g, '/') ?? futureDate(1);
20
+ const _return = params._return?.replace(/-/g, '/') ?? futureDate(2);
21
+
22
+ const adults = params.adults ?? 1;
23
+ const children = params.children ?? 0;
24
+ const babies = params.babies ?? 0;
25
+
26
+ return {
27
+ type,
28
+ from,
29
+ to,
30
+ departure,
31
+ _return,
32
+ adults: Number(adults),
33
+ children: Number(children),
34
+ babies: Number(babies)
35
+ };
36
+ };
37
+
38
+ export default prepare;
package/Step2.tsx ADDED
@@ -0,0 +1,55 @@
1
+ import { TFunction } from 'i18next';
2
+ import { RiBus2Line } from 'react-icons/ri';
3
+ import { AiOutlineCaretLeft } from 'react-icons/ai';
4
+ import { Button } from '@autobusal/common';
5
+ import Steps from './Steps/Steps';
6
+ import Found from './Found/Found';
7
+ import { Title, Actions } from './styles';
8
+ import { RoutesSearchForm } from '@autobusal/routes-search/types';
9
+ import { FoundData } from '@autobusal/providers/types/routes';
10
+ import { GetDepartures } from './services';
11
+
12
+ interface Props {
13
+ step1: RoutesSearchForm
14
+ t: TFunction<'common'>
15
+ onSearch: (data: ('departure' | '_return'), day: string) => void
16
+ onSave: (data: FoundData) => void
17
+ onBack: (step: number) => void
18
+ }
19
+
20
+ const Step2 = ({ step1, t, onSearch, onSave, onBack }: Props): JSX.Element => {
21
+ const { data, isFetching } = GetDepartures(step1);
22
+
23
+ return (
24
+ <>
25
+ <Title>
26
+ <RiBus2Line />
27
+ { t('routes_order.step2.title') }
28
+ </Title>
29
+
30
+ <Steps value={ 2 } />
31
+
32
+ <Found
33
+ type="departure"
34
+ loading={ isFetching }
35
+ data={ data }
36
+ step1={ step1 }
37
+ t={ t }
38
+ onSearch={ onSearch }
39
+ onSave={ onSave }
40
+ />
41
+
42
+ <Actions>
43
+ <Button
44
+ type="button"
45
+ subtype="secondary"
46
+ text={ <><AiOutlineCaretLeft />{ t('routes_order.step2.back') }</> }
47
+ noMargin
48
+ onClick={ () => onBack(1) }
49
+ />
50
+ </Actions>
51
+ </>
52
+ );
53
+ };
54
+
55
+ export default Step2;
package/Step3.tsx ADDED
@@ -0,0 +1,56 @@
1
+ import { TFunction } from 'i18next';
2
+ import { RiBus2Line } from 'react-icons/ri';
3
+ import { AiOutlineCaretLeft } from 'react-icons/ai';
4
+ import { Button } from '@autobusal/common';
5
+ import Steps from './Steps/Steps';
6
+ import Found from './Found/Found';
7
+ import { Title, Actions } from './styles';
8
+ import { RoutesSearchForm } from '@autobusal/routes-search/types';
9
+ import { FoundData } from '@autobusal/providers/types/routes';
10
+ import { GetReturns } from './services';
11
+
12
+ interface Props {
13
+ step1: RoutesSearchForm
14
+ step2: FoundData
15
+ t: TFunction<'common'>
16
+ onSearch: (data: ('departure' | '_return'), day: string) => void
17
+ onSave: (data: FoundData) => void
18
+ onBack: (step: number) => void
19
+ }
20
+
21
+ const Step3 = ({ step1, step2, t, onSearch, onSave, onBack }: Props): JSX.Element => {
22
+ const { data, isFetching } = GetReturns(step1, step2);
23
+
24
+ return (
25
+ <>
26
+ <Title>
27
+ <RiBus2Line />
28
+ { t('routes_order.step3.title') }
29
+ </Title>
30
+
31
+ <Steps value={ 3 } />
32
+
33
+ <Found
34
+ type="_return"
35
+ loading={ isFetching }
36
+ data={ data }
37
+ step1={ step1 }
38
+ t={ t }
39
+ onSearch={ onSearch }
40
+ onSave={ onSave }
41
+ />
42
+
43
+ <Actions>
44
+ <Button
45
+ type="button"
46
+ subtype="secondary"
47
+ text={ <><AiOutlineCaretLeft />{ t('routes_order.step3.back') }</> }
48
+ noMargin
49
+ onClick={ () => onBack(2) }
50
+ />
51
+ </Actions>
52
+ </>
53
+ );
54
+ };
55
+
56
+ export default Step3;
@@ -0,0 +1,26 @@
1
+ import { TFunction } from 'i18next';
2
+ import { HiUsers } from 'react-icons/hi';
3
+ import { Inline } from '@autobusal/common';
4
+ import Steps from '../Steps/Steps';
5
+ import { Title } from '../styles';
6
+
7
+ interface Props {
8
+ t: TFunction<'public'>
9
+ }
10
+
11
+ const Loading = ({ t }: Props): JSX.Element => (
12
+ <>
13
+ <Title>
14
+ <HiUsers />
15
+ { t('routes_order.step4.title') }
16
+ </Title>
17
+
18
+ <Steps value={4} />
19
+
20
+ <div className="box">
21
+ <Inline text={ t('seats.loading') } />
22
+ </div>
23
+ </>
24
+ );
25
+
26
+ export default Loading;