@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.
- package/Found/Booking/Booking.tsx +61 -0
- package/Found/Booking/styles.ts +50 -0
- package/Found/Dates/Dates.tsx +82 -0
- package/Found/Dates/Loading.tsx +14 -0
- package/Found/Dates/styles.ts +50 -0
- package/Found/Found.tsx +31 -0
- package/Found/Header/Header.tsx +21 -0
- package/Found/Header/styles.ts +40 -0
- package/Found/Orders/Orders.tsx +40 -0
- package/Found/Orders/styles.ts +12 -0
- package/Found/Route/Route.tsx +112 -0
- package/Found/Route/styles.ts +187 -0
- package/RoutesOrder.tsx +130 -0
- package/Step1/Step1.tsx +63 -0
- package/Step1/prepare.ts +38 -0
- package/Step2.tsx +55 -0
- package/Step3.tsx +56 -0
- package/Step4/Loading.tsx +26 -0
- package/Step4/Passenger/Passenger.tsx +173 -0
- package/Step4/Passenger/styles.ts +10 -0
- package/Step4/Passengers/Display.tsx +50 -0
- package/Step4/Passengers/Passengers.tsx +96 -0
- package/Step4/Passengers/styles.ts +7 -0
- package/Step4/Step4.tsx +96 -0
- package/Step4/checkAge.ts +48 -0
- package/Step5/Billing/Billing.tsx +161 -0
- package/Step5/Billing/Loading.tsx +22 -0
- package/Step5/Billing/styles.ts +13 -0
- package/Step5/Coupons/Coupons.tsx +71 -0
- package/Step5/Coupons/styles.ts +20 -0
- package/Step5/Payments/Payments.tsx +82 -0
- package/Step5/Payments/styles.ts +42 -0
- package/Step5/Step5.tsx +131 -0
- package/Step5/Summary/About/About.tsx +48 -0
- package/Step5/Summary/About/styles.ts +49 -0
- package/Step5/Summary/Amount/Amount.tsx +24 -0
- package/Step5/Summary/Amount/styles.ts +29 -0
- package/Step5/Summary/Summary.tsx +44 -0
- package/Step5/Summary/Tickets/Preview.tsx +41 -0
- package/Step5/Summary/Tickets/Tickets.tsx +34 -0
- package/Step5/Summary/Tickets/styles.ts +36 -0
- package/Step5/Summary/styles.ts +11 -0
- package/Step5/utilities.ts +24 -0
- package/Steps/Steps.tsx +28 -0
- package/Steps/styles.ts +47 -0
- package/index.ts +3 -0
- package/package.json +6 -0
- package/services.ts +149 -0
- package/styles.ts +24 -0
- package/types.ts +32 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { TFunction } from 'i18next';
|
|
2
|
+
import { FieldErrors, FieldValues, UseFormRegister } from 'react-hook-form';
|
|
3
|
+
import { Calendar, Gender, ChooseSeat } from '@autobusal/common';
|
|
4
|
+
import { Validate, Display } from '@autobusal/utilities';
|
|
5
|
+
import { Container, Title } from './styles';
|
|
6
|
+
import { PersonData } from '@autobusal/providers/types/persons';
|
|
7
|
+
import { OccupiedData } from '@autobusal/providers/types/buses';
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
type: 'adult' | 'child' | 'baby'
|
|
11
|
+
number: number
|
|
12
|
+
values?: PersonData
|
|
13
|
+
withReturn: boolean
|
|
14
|
+
busFrom: OccupiedData
|
|
15
|
+
busTo: OccupiedData
|
|
16
|
+
pickedFrom: number[]
|
|
17
|
+
pickedTo: number[]
|
|
18
|
+
t: TFunction<'public'>
|
|
19
|
+
errors: FieldErrors<FieldValues>
|
|
20
|
+
refs: UseFormRegister<PersonData>
|
|
21
|
+
onSeatSelect: (name: string, type: ('from' | 'to'), seat: number, previous: number) => void
|
|
22
|
+
onUpdate: (name: string, value: (string | number)) => void
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const Passenger = ({ type, number, values, withReturn, busFrom, busTo, pickedFrom, pickedTo, t, errors, refs, onSeatSelect, onUpdate }: Props): JSX.Element => {
|
|
26
|
+
const name = `${ type }${ number }`;
|
|
27
|
+
|
|
28
|
+
const names = {
|
|
29
|
+
firstName: `${ name }_fn`,
|
|
30
|
+
lastName: `${ name }_ln`,
|
|
31
|
+
dob: `${ name }_dob`,
|
|
32
|
+
sex: `${ name }_sex`,
|
|
33
|
+
passport: `${ name }_pass`,
|
|
34
|
+
phone: `${ name }_phone`,
|
|
35
|
+
seatDeparture: `${ name }_sd`,
|
|
36
|
+
seatReturn: `${ name }_sr`
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<Container className="box">
|
|
41
|
+
<Title>
|
|
42
|
+
{ `${ t('routes_order.step4.ticket', { no: number }) } (${ t(`data.persons.${ type }.singular`) })` }
|
|
43
|
+
</Title>
|
|
44
|
+
|
|
45
|
+
<div className="column">
|
|
46
|
+
<div className="row">
|
|
47
|
+
{ t('routes_order.step4.first_name') }
|
|
48
|
+
|
|
49
|
+
<input
|
|
50
|
+
type="text"
|
|
51
|
+
defaultValue={ values !== undefined ? values[names.firstName] : '' }
|
|
52
|
+
{ ...refs(names.firstName, Validate('required|min_length:2|max_length:100', t)) }
|
|
53
|
+
/>
|
|
54
|
+
|
|
55
|
+
{ Display(errors[names.firstName]) }
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<div className="row">
|
|
59
|
+
{ t('routes_order.step4.last_name') }
|
|
60
|
+
|
|
61
|
+
<input
|
|
62
|
+
type="text"
|
|
63
|
+
defaultValue={ values !== undefined ? values[names.lastName] : '' }
|
|
64
|
+
{ ...refs(names.lastName, Validate('required|min_length:2|max_length:100', t)) }
|
|
65
|
+
/>
|
|
66
|
+
|
|
67
|
+
{ Display(errors[names.lastName]) }
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<div className="column">
|
|
72
|
+
<div className="row">
|
|
73
|
+
{ t('routes_order.step4.dob') }
|
|
74
|
+
|
|
75
|
+
<Calendar
|
|
76
|
+
type="dob"
|
|
77
|
+
name={ names.dob }
|
|
78
|
+
defaultValue={ values !== undefined ? String(values[names.dob]) : undefined }
|
|
79
|
+
t={ t }
|
|
80
|
+
validation="required"
|
|
81
|
+
refs={ refs }
|
|
82
|
+
onUpdate={ onUpdate }
|
|
83
|
+
/>
|
|
84
|
+
|
|
85
|
+
{ Display(errors[names.dob]) }
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<div className="row">
|
|
89
|
+
{ t('routes_order.step4.gender') }
|
|
90
|
+
|
|
91
|
+
<Gender
|
|
92
|
+
name={ names.sex }
|
|
93
|
+
defaultValue={ values !== undefined ? Number(values[names.sex]) : undefined }
|
|
94
|
+
t={ t }
|
|
95
|
+
refs={ refs }
|
|
96
|
+
/>
|
|
97
|
+
|
|
98
|
+
{ Display(errors[names.sex]) }
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<div className="column">
|
|
103
|
+
<div className="row">
|
|
104
|
+
{ t('routes_order.step4.passport') }
|
|
105
|
+
|
|
106
|
+
<input
|
|
107
|
+
type="text"
|
|
108
|
+
defaultValue={ values !== undefined ? values[names.passport] : '' }
|
|
109
|
+
{ ...refs(names.passport, Validate('required|min_length:2|max_length:50', t)) }
|
|
110
|
+
/>
|
|
111
|
+
|
|
112
|
+
{ Display(errors[names.passport]) }
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<div className="row">
|
|
116
|
+
{ t('routes_order.step4.phone') }
|
|
117
|
+
|
|
118
|
+
<input
|
|
119
|
+
type="text"
|
|
120
|
+
defaultValue={ values !== undefined ? values[names.phone] : '' }
|
|
121
|
+
{ ...refs(names.phone) }
|
|
122
|
+
/>
|
|
123
|
+
|
|
124
|
+
{ Display(errors[names.phone]) }
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
{ type !== 'baby' && (
|
|
129
|
+
<div className="column">
|
|
130
|
+
<div className="row">
|
|
131
|
+
{ t('seats.departure') }
|
|
132
|
+
|
|
133
|
+
<ChooseSeat
|
|
134
|
+
name={ names.seatDeparture }
|
|
135
|
+
type="from"
|
|
136
|
+
defaultValue={ values !== undefined ? Number(values[names.seatDeparture]) : undefined }
|
|
137
|
+
bus={ busFrom }
|
|
138
|
+
picked={ pickedFrom }
|
|
139
|
+
validation="required|min:1"
|
|
140
|
+
t={ t }
|
|
141
|
+
refs={ refs }
|
|
142
|
+
onUpdate={ onSeatSelect }
|
|
143
|
+
/>
|
|
144
|
+
|
|
145
|
+
{ Display(errors[names.seatDeparture]) }
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
{ withReturn && (
|
|
149
|
+
<div className="row">
|
|
150
|
+
{ t('seats.return') }
|
|
151
|
+
|
|
152
|
+
<ChooseSeat
|
|
153
|
+
name={ names.seatReturn }
|
|
154
|
+
type="to"
|
|
155
|
+
defaultValue={ values !== undefined ? Number(values[names.seatReturn]) : undefined }
|
|
156
|
+
bus={ busTo }
|
|
157
|
+
picked={ pickedTo }
|
|
158
|
+
validation="required|min:1"
|
|
159
|
+
t={ t }
|
|
160
|
+
refs={ refs }
|
|
161
|
+
onUpdate={ onSeatSelect }
|
|
162
|
+
/>
|
|
163
|
+
|
|
164
|
+
{ Display(errors[names.seatReturn]) }
|
|
165
|
+
</div>
|
|
166
|
+
) }
|
|
167
|
+
</div>
|
|
168
|
+
) }
|
|
169
|
+
</Container>
|
|
170
|
+
);
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
export default Passenger;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { TFunction } from 'i18next';
|
|
2
|
+
import { FieldErrors, FieldValues, UseFormRegister } from 'react-hook-form';
|
|
3
|
+
import Passenger from '../Passenger/Passenger';
|
|
4
|
+
import { PersonData } from '@autobusal/providers/types/persons';
|
|
5
|
+
import { OccupiedData } from '@autobusal/providers/types/buses';
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
type: 'adult' | 'child' | 'baby'
|
|
9
|
+
amount: number
|
|
10
|
+
withReturn: boolean
|
|
11
|
+
values?: PersonData
|
|
12
|
+
busFrom: OccupiedData
|
|
13
|
+
busTo: OccupiedData
|
|
14
|
+
pickedFrom: number[]
|
|
15
|
+
pickedTo: number[]
|
|
16
|
+
t: TFunction<'public'>
|
|
17
|
+
errors: FieldErrors<FieldValues>
|
|
18
|
+
refs: UseFormRegister<PersonData>
|
|
19
|
+
onSeatSelect: (name: string, type: ('from' | 'to'), seat: number, previous: number) => void
|
|
20
|
+
onUpdate: (name: string, value: (string | number)) => void
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const Display = ({ type, amount, withReturn, values, busFrom, busTo, pickedFrom, pickedTo, t, errors, refs, onSeatSelect, onUpdate }: Props): JSX.Element[] => {
|
|
24
|
+
const passengers: JSX.Element[] = [];
|
|
25
|
+
|
|
26
|
+
for (let i = 1; i <= amount; i++) {
|
|
27
|
+
passengers.push(
|
|
28
|
+
<Passenger
|
|
29
|
+
key={ i }
|
|
30
|
+
type={ type }
|
|
31
|
+
number={ i }
|
|
32
|
+
withReturn={ withReturn }
|
|
33
|
+
values={ values }
|
|
34
|
+
busFrom={ busFrom }
|
|
35
|
+
busTo={ busTo }
|
|
36
|
+
pickedFrom={ pickedFrom }
|
|
37
|
+
pickedTo={ pickedTo }
|
|
38
|
+
t={ t }
|
|
39
|
+
errors={ errors }
|
|
40
|
+
refs={ refs }
|
|
41
|
+
onSeatSelect={ onSeatSelect }
|
|
42
|
+
onUpdate={ onUpdate }
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return passengers;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export default Display;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { TFunction } from 'i18next';
|
|
3
|
+
import { FieldErrors, FieldValues, UseFormRegister } from 'react-hook-form';
|
|
4
|
+
import Display from './Display';
|
|
5
|
+
import { Container } from './styles';
|
|
6
|
+
import { RoutesSearchForm } from '@autobusal/routes-search/types';
|
|
7
|
+
import { PersonData } from '@autobusal/providers/types/persons';
|
|
8
|
+
import { OccupiedData } from '@autobusal/providers/types/buses';
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
values?: PersonData
|
|
12
|
+
step1: RoutesSearchForm
|
|
13
|
+
busFrom: OccupiedData
|
|
14
|
+
busTo: OccupiedData
|
|
15
|
+
t: TFunction<'public'>
|
|
16
|
+
errors: FieldErrors<FieldValues>
|
|
17
|
+
refs: UseFormRegister<PersonData>
|
|
18
|
+
onUpdate: (name: string, value: (string | number)) => void
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const Passengers = ({ values, step1, busFrom, busTo, t, errors, refs, onUpdate }: Props): JSX.Element => {
|
|
22
|
+
const [ pickedFrom, setPickedFrom ] = useState<number[]>([]);
|
|
23
|
+
const [ pickedTo, setPickedTo ] = useState<number[]>([]);
|
|
24
|
+
|
|
25
|
+
const onSeatSelect = (name: string, type: ('from' | 'to'), seat: number, previous: number): void => {
|
|
26
|
+
let newPicked = type === 'from' ? [...pickedFrom] : [...pickedTo];
|
|
27
|
+
|
|
28
|
+
if (previous > 0) {
|
|
29
|
+
newPicked = pickedFrom.filter(item => item !== previous);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
newPicked.push(seat);
|
|
33
|
+
|
|
34
|
+
if (type === 'from') {
|
|
35
|
+
setPickedFrom(newPicked);
|
|
36
|
+
} else {
|
|
37
|
+
setPickedTo(newPicked);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
onUpdate(name, seat);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Container>
|
|
45
|
+
<Display
|
|
46
|
+
type="adult"
|
|
47
|
+
amount={ step1.adults }
|
|
48
|
+
withReturn={ step1.type === 'return' }
|
|
49
|
+
values={ values }
|
|
50
|
+
busFrom={ busFrom }
|
|
51
|
+
busTo={ busTo }
|
|
52
|
+
pickedFrom={ pickedFrom }
|
|
53
|
+
pickedTo={ pickedTo }
|
|
54
|
+
t={ t }
|
|
55
|
+
errors={ errors }
|
|
56
|
+
refs={ refs }
|
|
57
|
+
onSeatSelect={ onSeatSelect }
|
|
58
|
+
onUpdate={ onUpdate }
|
|
59
|
+
/>
|
|
60
|
+
|
|
61
|
+
<Display
|
|
62
|
+
type="child"
|
|
63
|
+
amount={ step1.children }
|
|
64
|
+
withReturn={ step1.type === 'return' }
|
|
65
|
+
values={ values }
|
|
66
|
+
busFrom={ busFrom }
|
|
67
|
+
busTo={ busTo }
|
|
68
|
+
pickedFrom={ pickedFrom }
|
|
69
|
+
pickedTo={ pickedTo }
|
|
70
|
+
t={ t }
|
|
71
|
+
errors={ errors }
|
|
72
|
+
refs={ refs }
|
|
73
|
+
onSeatSelect={ onSeatSelect }
|
|
74
|
+
onUpdate={ onUpdate }
|
|
75
|
+
/>
|
|
76
|
+
|
|
77
|
+
<Display
|
|
78
|
+
type="baby"
|
|
79
|
+
amount={ step1.babies }
|
|
80
|
+
withReturn={ step1.type === 'return' }
|
|
81
|
+
values={ values }
|
|
82
|
+
busFrom={ busFrom }
|
|
83
|
+
busTo={ busTo }
|
|
84
|
+
pickedFrom={ pickedFrom }
|
|
85
|
+
pickedTo={ pickedTo }
|
|
86
|
+
t={ t }
|
|
87
|
+
errors={ errors }
|
|
88
|
+
refs={ refs }
|
|
89
|
+
onSeatSelect={ onSeatSelect }
|
|
90
|
+
onUpdate={ onUpdate }
|
|
91
|
+
/>
|
|
92
|
+
</Container>
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export default Passengers;
|
package/Step4/Step4.tsx
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { TFunction } from 'i18next';
|
|
2
|
+
import { useForm } from 'react-hook-form';
|
|
3
|
+
import { HiUsers } from 'react-icons/hi';
|
|
4
|
+
import { RiShoppingCartLine } from 'react-icons/ri';
|
|
5
|
+
import { AiOutlineCaretLeft } from 'react-icons/ai';
|
|
6
|
+
import { useBusOccupied } from '@autobusal/hooks';
|
|
7
|
+
import { Button } from '@autobusal/common';
|
|
8
|
+
import Steps from '../Steps/Steps';
|
|
9
|
+
import Loading from './Loading';
|
|
10
|
+
import Passengers from './Passengers/Passengers';
|
|
11
|
+
import checkAge from './checkAge';
|
|
12
|
+
import { Title, Actions } from '../styles';
|
|
13
|
+
import { RoutesSearchForm } from '@autobusal/routes-search/types';
|
|
14
|
+
import { PersonData } from '@autobusal/providers/types/persons';
|
|
15
|
+
import { FoundData } from '@autobusal/providers/types/routes';
|
|
16
|
+
|
|
17
|
+
interface Props {
|
|
18
|
+
values?: PersonData
|
|
19
|
+
step1: RoutesSearchForm
|
|
20
|
+
step2: FoundData
|
|
21
|
+
step3?: FoundData
|
|
22
|
+
isPartner?: boolean
|
|
23
|
+
t: TFunction<'common'>
|
|
24
|
+
onSave: (data: PersonData) => void
|
|
25
|
+
onBack: (step: number) => void
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const Step4 = ({ values, step1, step2, step3, isPartner, t, onSave, onBack }: Props): JSX.Element => {
|
|
29
|
+
const { register, handleSubmit, formState: { errors }, setValue, setError } = useForm<PersonData>();
|
|
30
|
+
|
|
31
|
+
const busFrom = useBusOccupied(step1.departure, step2.id);
|
|
32
|
+
const busTo = useBusOccupied(step1._return, step3?.id);
|
|
33
|
+
|
|
34
|
+
if (busFrom === undefined || busTo === undefined) {
|
|
35
|
+
return <Loading t={ t } />;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const onSubmit = (data: PersonData): void => {
|
|
39
|
+
// if all the ages are ok, we move forward
|
|
40
|
+
if (checkAge(step1, data, t)) {
|
|
41
|
+
onSave(data);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const onUpdate = (name: string, value: (string | number)): void => {
|
|
46
|
+
setValue(name, value);
|
|
47
|
+
setError(name, {});
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const onSendForm = (): void => {
|
|
51
|
+
handleSubmit(onSubmit)();
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<>
|
|
56
|
+
<Title>
|
|
57
|
+
<HiUsers />
|
|
58
|
+
{ t('routes_order.step4.title') }
|
|
59
|
+
</Title>
|
|
60
|
+
|
|
61
|
+
<Steps value={ 4 } />
|
|
62
|
+
|
|
63
|
+
<Passengers
|
|
64
|
+
values={ values }
|
|
65
|
+
step1={ step1 }
|
|
66
|
+
busFrom={ busFrom }
|
|
67
|
+
busTo={ busTo }
|
|
68
|
+
t={ t }
|
|
69
|
+
errors={ errors }
|
|
70
|
+
refs={ register }
|
|
71
|
+
onUpdate={ onUpdate }
|
|
72
|
+
/>
|
|
73
|
+
|
|
74
|
+
<Actions>
|
|
75
|
+
{ isPartner === undefined && (
|
|
76
|
+
<Button
|
|
77
|
+
type="button"
|
|
78
|
+
subtype="secondary"
|
|
79
|
+
text={ <><AiOutlineCaretLeft /> { t('routes_order.step4.back') }</> }
|
|
80
|
+
noMargin
|
|
81
|
+
onClick={ () => onBack(step1._return ? 3 : 2) }
|
|
82
|
+
/>
|
|
83
|
+
) }
|
|
84
|
+
|
|
85
|
+
<Button
|
|
86
|
+
type="button"
|
|
87
|
+
text={ <><RiShoppingCartLine /> { t('routes_order.step4.next') }</> }
|
|
88
|
+
noMargin
|
|
89
|
+
onClick={ onSendForm }
|
|
90
|
+
/>
|
|
91
|
+
</Actions>
|
|
92
|
+
</>
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export default Step4;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { TFunction } from 'i18next';
|
|
2
|
+
import { error, differenceYears, createDate } from '@autobusal/utilities';
|
|
3
|
+
import { PersonData } from '@autobusal/providers/types/persons';
|
|
4
|
+
import { RoutesSearchForm } from '@autobusal/routes-search/types';
|
|
5
|
+
|
|
6
|
+
const checkAge = (step1: RoutesSearchForm, data: PersonData, t: TFunction<'general'>): boolean => {
|
|
7
|
+
// check adults ages
|
|
8
|
+
for (let i = 1; i <= step1.adults; i++) {
|
|
9
|
+
const age = getAge(data[`adult${ i }_dob`]);
|
|
10
|
+
|
|
11
|
+
if (age < 18) {
|
|
12
|
+
error(t('routes_order.step4.errors.age'));
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// check children ages
|
|
18
|
+
for (let i = 1; i <= step1.children; i++) {
|
|
19
|
+
const age = getAge(data[`child${ i }_dob`])
|
|
20
|
+
|
|
21
|
+
if (age < 3 || age > 17) {
|
|
22
|
+
error(t('routes_order.step4.errors.age'));
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// check babies ages
|
|
28
|
+
for (let i = 1; i <= step1.babies; i++) {
|
|
29
|
+
const age = getAge(data[`baby${ i }_dob`]);
|
|
30
|
+
|
|
31
|
+
if (age > 2) {
|
|
32
|
+
error(t('routes_order.step4.errors.age'));
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return true;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const getAge = (date: string | number): number => {
|
|
41
|
+
const age = differenceYears(
|
|
42
|
+
createDate(String(date))
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
return age;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export default checkAge;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { TFunction } from 'i18next';
|
|
3
|
+
import { UseFormRegister, FieldErrors } from 'react-hook-form';
|
|
4
|
+
import { RiPencilFill } from 'react-icons/ri';
|
|
5
|
+
import { AiOutlineInfoCircle } from 'react-icons/ai';
|
|
6
|
+
import { useCountries } from '@autobusal/hooks';
|
|
7
|
+
import { Validate, Display } from '@autobusal/utilities';
|
|
8
|
+
import Loading from './Loading';
|
|
9
|
+
import { SubTitle } from '../../styles';
|
|
10
|
+
import { Container, NoticeEmail } from './styles';
|
|
11
|
+
import { BillingData } from '@autobusal/providers/types/persons';
|
|
12
|
+
|
|
13
|
+
interface Props {
|
|
14
|
+
t: TFunction<'common'>
|
|
15
|
+
errors: FieldErrors
|
|
16
|
+
refs: UseFormRegister<BillingData>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const Billing = ({ errors, t, refs }: Props): JSX.Element => {
|
|
20
|
+
const [ company, setCompany ] = useState<boolean>(false);
|
|
21
|
+
|
|
22
|
+
const countries = useCountries('all');
|
|
23
|
+
|
|
24
|
+
if (countries.isLoading) {
|
|
25
|
+
return <Loading t={ t } />;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const items = countries.data?.map(item => (
|
|
29
|
+
<option key={ item.id } value={ item.id }>{ item.name }</option>
|
|
30
|
+
));
|
|
31
|
+
|
|
32
|
+
const onAddCompany = (event: React.MouseEvent<HTMLInputElement>): void => {
|
|
33
|
+
setCompany((event.target as HTMLInputElement).checked);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<Container className="box">
|
|
38
|
+
<SubTitle>
|
|
39
|
+
<RiPencilFill />
|
|
40
|
+
{ t('routes_order.step5.billing.title') }
|
|
41
|
+
</SubTitle>
|
|
42
|
+
|
|
43
|
+
<div className="column">
|
|
44
|
+
<div className="row">
|
|
45
|
+
{ t('routes_order.step5.billing.first_name') }
|
|
46
|
+
|
|
47
|
+
<input type="text" { ...refs('bill_fn', Validate('required|min_length:2|max_length:100', t)) } />
|
|
48
|
+
|
|
49
|
+
{ Display(errors.bill_fn) }
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<div className="row">
|
|
53
|
+
{ t('routes_order.step5.billing.last_name') }
|
|
54
|
+
|
|
55
|
+
<input type="text" { ...refs('bill_ln', Validate('required|min_length:2|max_length:100', t)) } />
|
|
56
|
+
|
|
57
|
+
{ Display(errors.bill_ln) }
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div className="column">
|
|
62
|
+
<div className="row">
|
|
63
|
+
{ t('routes_order.step5.billing.email') }
|
|
64
|
+
|
|
65
|
+
<input type="email" { ...refs('email', Validate('required|email|max_length:255', t)) } />
|
|
66
|
+
|
|
67
|
+
{ Display(errors.email) }
|
|
68
|
+
|
|
69
|
+
<NoticeEmail>
|
|
70
|
+
<AiOutlineInfoCircle /> { t('routes_order.step5.billing.email_info') }
|
|
71
|
+
</NoticeEmail>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<div className="row">
|
|
75
|
+
{ t('routes_order.step5.billing.phone') }
|
|
76
|
+
|
|
77
|
+
<input type="text" { ...refs('bill_phone', Validate('required|min_length:10|max_length:20', t)) } />
|
|
78
|
+
|
|
79
|
+
{ Display(errors.bill_phone) }
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<div className="row">
|
|
84
|
+
{ t('routes_order.step5.billing.address') }
|
|
85
|
+
|
|
86
|
+
<textarea { ...refs('bill_address', Validate('required|min_length:2', t)) }></textarea>
|
|
87
|
+
|
|
88
|
+
{ Display(errors.bill_address) }
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<div className="column">
|
|
92
|
+
<div className="row">
|
|
93
|
+
{ t('routes_order.step5.billing.city') }
|
|
94
|
+
|
|
95
|
+
<input type="text" { ...refs('bill_city', Validate('required|min_length:2|max_length:100', t)) } />
|
|
96
|
+
|
|
97
|
+
{ Display(errors.bill_city) }
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<div className="row">
|
|
101
|
+
{ t('routes_order.step5.billing.country') }
|
|
102
|
+
|
|
103
|
+
<select { ...refs('bill_country', Validate('required|min:1', t)) }>
|
|
104
|
+
<option value={ 0 }>{ t('routes_order.step5.billing.country_choose') }</option>
|
|
105
|
+
{ items }
|
|
106
|
+
</select>
|
|
107
|
+
|
|
108
|
+
{ Display(errors.bill_country) }
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<div className="row">
|
|
113
|
+
<label>
|
|
114
|
+
<input type="checkbox" defaultChecked={ false } onClick={ (event: React.MouseEvent<HTMLInputElement>) => onAddCompany(event) } /> { t('routes_order.step5.billing.ask_company') }
|
|
115
|
+
</label>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
{ company && (
|
|
119
|
+
<div className="column">
|
|
120
|
+
<div className="row">
|
|
121
|
+
{ t('routes_order.step5.billing.company') }
|
|
122
|
+
|
|
123
|
+
<input type="text" { ...refs('bill_company') } />
|
|
124
|
+
|
|
125
|
+
{ Display(errors.bill_company) }
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
<div className="row">
|
|
129
|
+
{ t('routes_order.step5.billing.nipt') }
|
|
130
|
+
|
|
131
|
+
<input type="text" { ...refs('bill_nipt') } />
|
|
132
|
+
|
|
133
|
+
{ Display(errors.bill_nipt) }
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
) }
|
|
137
|
+
|
|
138
|
+
<div className="row">
|
|
139
|
+
{ t('routes_order.step5.billing.comments') }
|
|
140
|
+
|
|
141
|
+
<textarea { ...refs('comments') }></textarea>
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
<div className="row row-nomargin">
|
|
145
|
+
<label className="single">
|
|
146
|
+
<input type="checkbox" value="1" { ...refs('terms', Validate('required', t)) } />
|
|
147
|
+
|
|
148
|
+
<span dangerouslySetInnerHTML={{
|
|
149
|
+
__html: t('routes_order.step5.billing.terms', {
|
|
150
|
+
terms: `<a href="/page/terms" target="_blank">${ t('routes_order.step5.billing.terms_name') }</a>`
|
|
151
|
+
})
|
|
152
|
+
}} />
|
|
153
|
+
</label>
|
|
154
|
+
|
|
155
|
+
{ Display(errors.terms) }
|
|
156
|
+
</div>
|
|
157
|
+
</Container>
|
|
158
|
+
);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export default Billing;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { TFunction } from 'i18next';
|
|
2
|
+
import { RiPencilFill } from 'react-icons/ri';
|
|
3
|
+
import { Inline } from '@autobusal/common';
|
|
4
|
+
import { SubTitle } from '../../styles';
|
|
5
|
+
import { Container } from './styles';
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
t: TFunction<'common'>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const Loading = ({ t }: Props): JSX.Element => (
|
|
12
|
+
<Container className="box">
|
|
13
|
+
<SubTitle>
|
|
14
|
+
<RiPencilFill />
|
|
15
|
+
{ t('routes_order.step5.billing.title') }
|
|
16
|
+
</SubTitle>
|
|
17
|
+
|
|
18
|
+
<Inline text={ t('routes_order.step5.billing.loading') } />
|
|
19
|
+
</Container>
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
export default Loading;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
|
|
3
|
+
export const Container = styled.div`
|
|
4
|
+
margin-top: 25px;
|
|
5
|
+
`;
|
|
6
|
+
|
|
7
|
+
export const NoticeEmail = styled.div`
|
|
8
|
+
display: flex;
|
|
9
|
+
align-items: center;
|
|
10
|
+
gap: 4px;
|
|
11
|
+
font-size: ${ props => props.theme.size.xs };
|
|
12
|
+
color: ${ props => props.theme.font.info };
|
|
13
|
+
`;
|