@autobusal/routes-order 1.9.0 → 1.11.1

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.
@@ -1,9 +1,11 @@
1
1
  import { useState } from 'react';
2
2
  import { TFunction } from 'i18next';
3
3
  import { HiOutlineArrowNarrowRight, HiOutlineDocumentText } from 'react-icons/hi';
4
+ import { BiHourglass } from 'react-icons/bi';
4
5
  import { RiBus2Line } from 'react-icons/ri';
6
+ import { formatDuration, splitPrice } from '../refine';
5
7
  import { Button, RouteFeature, Policy } from '@autobusal/common';
6
- import { Container, SpecialOffer, FavoriteStop, Trip, Company, Location, Time, Price, PriceNotice, LinkCompany, Details, Detail, ButtonPolicy, TitleDetail, LinkRoute, Features, TitleFeatures, FeaturesItems } from './styles';
8
+ import { Container, SpecialOffer, FavoriteStop, Trip, Company, Location, Stop, Iso, Time, Price, Amount, Decimals, Currency, PriceNotice, LinkCompany, Details, Detail, ButtonPolicy, TitleDetail, LinkRoute, FeaturesItems } from './styles';
7
9
  import { FoundData } from '@autobusal/providers/types/routes';
8
10
 
9
11
  // neutral placeholder so a missing/broken operator logo doesn't render the
@@ -28,8 +30,10 @@ const Route = ({ data, type, passengers, t, onSave, children }: Props): JSX.Elem
28
30
  const customs = Object.values(data.customs).join(', ');
29
31
  const transiting = Object.values(data.transiting).join(', ');
30
32
 
33
+ const price = splitPrice(data.price.display);
34
+
31
35
  const features = data.trip.features_data.map((item, index) => (
32
- <RouteFeature key={ index } item={ item } />
36
+ <RouteFeature key={ index } item={ item } compact />
33
37
  ));
34
38
 
35
39
  // Edited: Ferjolt Ozuni - Date: 2026-07-27
@@ -54,22 +58,41 @@ const Route = ({ data, type, passengers, t, onSave, children }: Props): JSX.Elem
54
58
  </Company>
55
59
 
56
60
  <Location>
57
- <h2>{ data.locations.from.city.name } ({ data.locations.from.city.country?.iso_code }) { data.locations.from.departure }</h2>
58
- <span>{ data.locations.from.stop?.name }</span>
61
+ <h2>
62
+ { data.locations.from.city.name }
63
+ <Iso>({ data.locations.from.city.country?.iso_code })</Iso>
64
+ { data.locations.from.departure }
65
+ </h2>
66
+
67
+ <Stop>{ data.locations.from.stop?.name }</Stop>
59
68
  </Location>
60
69
 
61
70
  <Time>
62
71
  <HiOutlineArrowNarrowRight />
63
- <span>{ data.duration }</span>
72
+
73
+ { /* Edited: Ferjolt Ozuni - Date: 2026-08-01
74
+ The clock is the point: this number sits between two real
75
+ departure times, and "08:30" gave nothing to distinguish a
76
+ trip length from a time of day. */ }
77
+ <span><BiHourglass />{ formatDuration(data.duration, t) }</span>
64
78
  </Time>
65
79
 
66
80
  <Location>
67
- <h2>{ data.locations.to.city.name } ({ data.locations.to.city.country?.iso_code }) { data.locations.to.arrival }</h2>
68
- <span>{ data.locations.to.stop?.name }</span>
81
+ <h2>
82
+ { data.locations.to.city.name }
83
+ <Iso>({ data.locations.to.city.country?.iso_code })</Iso>
84
+ { data.locations.to.arrival }
85
+ </h2>
86
+
87
+ <Stop>{ data.locations.to.stop?.name }</Stop>
69
88
  </Location>
70
89
 
71
90
  <Price>
72
- <strong>{ data.price.display }</strong>
91
+ <Amount>
92
+ { price.whole }
93
+ { price.decimals !== null && <Decimals>{ price.decimals }</Decimals> }
94
+ { price.currency !== null && <Currency>{ price.currency }</Currency> }
95
+ </Amount>
73
96
 
74
97
  { /* Edited: Ferjolt Ozuni - Date: 2026-08-01
75
98
  obtapi's price.value is the total for the WHOLE party
@@ -104,6 +127,21 @@ const Route = ({ data, type, passengers, t, onSave, children }: Props): JSX.Elem
104
127
  { children }
105
128
 
106
129
  <Details>
130
+ { /* Edited: Ferjolt Ozuni - Date: 2026-08-01
131
+ Amenities were a separate full-width block under the card.
132
+ They are the same kind of thing as route, transit and policies
133
+ - supporting detail - so they join that row as the first
134
+ column, which is one fewer band of vertical space per result. */ }
135
+ { data.trip.features_data.length > 0 && (
136
+ <Detail>
137
+ <TitleDetail>{ t('routes_order.step2.route.features') }</TitleDetail>
138
+
139
+ <FeaturesItems>
140
+ { features }
141
+ </FeaturesItems>
142
+ </Detail>
143
+ ) }
144
+
107
145
  <Detail>
108
146
  <TitleDetail>{ t('routes_order.step2.route.name') }</TitleDetail>
109
147
  <LinkRoute to={ bookOnUrl ?? data.link } target="_blank" rel="noopener noreferrer">{ data.code }</LinkRoute>
@@ -135,16 +173,6 @@ const Route = ({ data, type, passengers, t, onSave, children }: Props): JSX.Elem
135
173
  ) }
136
174
  </Details>
137
175
 
138
- { data.trip.features_data.length > 0 && (
139
- <Features>
140
- <TitleFeatures>{ t('routes_order.step2.route.features') }</TitleFeatures>
141
-
142
- <FeaturesItems>
143
- { features }
144
- </FeaturesItems>
145
- </Features>
146
- ) }
147
-
148
176
  { policy && typeof data.id === 'number' && <Policy id={ data.id } t={ t } onClose={ () => setPolicy(false) } /> }
149
177
  </Container>
150
178
  );
@@ -64,26 +64,67 @@ export const Company = styled.div`
64
64
  flex-basis: 100%;
65
65
 
66
66
  @media (min-width: 1024px) {
67
- flex-basis: 185px;
67
+ flex: 0 0 185px;
68
68
  }
69
69
  `;
70
70
 
71
+ /**
72
+ * Edited: Ferjolt Ozuni - Date: 2026-08-01
73
+ *
74
+ * min-width: 0 is what makes `flex: 1` mean "an equal share". Without it
75
+ * the default min-width:auto stops a column shrinking below its content,
76
+ * so the arrival column (long city plus a long stop name) grew to 215px
77
+ * while the departure column got 155px - the two halves of the same row
78
+ * were different widths, and no fixed header could ever line up with
79
+ * both. It also stops one long stop name stealing width from the other
80
+ * side of the card.
81
+ */
71
82
  export const Location = styled.div`
72
83
  flex: 1;
84
+ min-width: 0;
73
85
  display: flex;
74
86
  flex-direction: column;
75
87
  align-items: center;
76
- gap: 5px;
88
+ gap: 3px;
77
89
  text-align: center;
78
90
 
79
91
  & > h2 {
80
92
  margin-bottom: 0;
81
93
  }
94
+ `;
82
95
 
83
- & > span {
84
- font-weight: 700;
85
- color: ${ props => props.theme.font.faded };
86
- }
96
+ /**
97
+ * Edited: Ferjolt Ozuni - Date: 2026-08-01
98
+ *
99
+ * The stop name is supporting detail, not a heading. At the same 16px as
100
+ * body copy it competed with the city above it; a step down lets the eye
101
+ * land on the city first and read the stop only when it needs to.
102
+ */
103
+ export const Stop = styled.span`
104
+ font-size: ${ props => props.theme.size.xs };
105
+ font-weight: 700;
106
+ line-height: 1.3;
107
+ color: ${ props => props.theme.font.faded };
108
+ `;
109
+
110
+ /**
111
+ * Edited: Ferjolt Ozuni - Date: 2026-08-01
112
+ *
113
+ * The country code comes out of the database lowercase ("alb", "grc").
114
+ * An ISO country code is by definition upper case, so it is cased here at
115
+ * render time rather than left to the data - every consumer would otherwise
116
+ * have to remember to do it. Set smaller and muted so it qualifies the city
117
+ * without competing with it.
118
+ */
119
+ export const Iso = styled.span`
120
+ // JSX drops the literal spaces around this element, so the gap on either
121
+ // side of "(ALB)" is this margin's job, not whitespace's
122
+ margin: 0 5px;
123
+ font-size: ${ props => props.theme.size.xs };
124
+ font-weight: 700;
125
+ text-transform: uppercase;
126
+ letter-spacing: .5px;
127
+ color: ${ props => props.theme.font.faded };
87
128
  `;
88
129
 
89
130
  export const Time = styled.div`
@@ -98,15 +139,26 @@ export const Time = styled.div`
98
139
  }
99
140
 
100
141
  & > span {
101
- display: block;
102
- padding: 3px 10px;
103
- font-size: ${ props => props.theme.size.l };
142
+ display: flex;
143
+ align-items: center;
144
+ gap: 5px;
145
+ padding: 3px 12px;
146
+ white-space: nowrap;
147
+ font-size: ${ props => props.theme.size.m };
104
148
  background: ${ props => props.theme.background.neutral };
105
149
  border-radius: 100px;
106
150
  }
107
151
 
152
+ // the hourglass is a hint, not a heading - the big arrow above already
153
+ // owns the visual weight in this column
154
+ & > span > svg {
155
+ flex-shrink: 0;
156
+ font-size: ${ props => props.theme.size.m };
157
+ opacity: .7;
158
+ }
159
+
108
160
  @media (min-width: 640px) {
109
- flex-basis: 120px;
161
+ flex: 0 0 120px;
110
162
  }
111
163
  `;
112
164
 
@@ -114,21 +166,52 @@ export const Price = styled.div`
114
166
  flex-basis: 100%;
115
167
  display: flex;
116
168
  flex-direction: column;
117
- gap: 10px;
169
+ gap: 8px;
118
170
  align-items: center;
119
171
 
120
- & > strong {
121
- font-size: ${ props => props.theme.size.xxl };
122
- }
123
-
124
172
  @media (min-width: 768px) {
125
- flex-basis: 165px;
173
+ flex: 0 0 165px;
126
174
  }
127
175
  `;
128
176
 
177
+ /**
178
+ * Edited: Ferjolt Ozuni - Date: 2026-08-01
179
+ *
180
+ * The decimals ride as superscript rather than at full size. On a fare
181
+ * board the cents are almost never what anyone is comparing, and at the
182
+ * headline size they were taking a third of the width of the widest thing
183
+ * on the card. The line-height is pinned to 1 so the shrunk decimals do
184
+ * not leave a gap under the row.
185
+ */
186
+ export const Amount = styled.strong`
187
+ display: flex;
188
+ align-items: flex-start;
189
+ line-height: 1;
190
+ font-size: ${ props => props.theme.size.xxl };
191
+ `;
192
+
193
+ export const Decimals = styled.span`
194
+ margin-left: 1px;
195
+ font-size: ${ props => props.theme.size.s };
196
+ line-height: 1;
197
+ `;
198
+
199
+ export const Currency = styled.span`
200
+ margin-left: 4px;
201
+ align-self: center;
202
+ font-size: ${ props => props.theme.size.m };
203
+ `;
204
+
205
+ /**
206
+ * Edited: Ferjolt Ozuni - Date: 2026-08-01
207
+ *
208
+ * Tucked up against the price it qualifies, which reads as one unit and
209
+ * frees the vertical space the Select button needs.
210
+ */
129
211
  export const PriceNotice = styled.span`
130
- margin-top: -6px;
131
- font-size: ${ props => props.theme.size.xs };
212
+ margin-top: -4px;
213
+ font-size: ${ props => props.theme.size.xxs };
214
+ line-height: 1.25;
132
215
  color: ${ props => props.theme.font.faded };
133
216
  text-align: center;
134
217
  `;
@@ -195,20 +278,15 @@ export const ButtonPolicy = styled.button`
195
278
  }
196
279
  `;
197
280
 
198
- export const Features = styled.div`
199
- display: flex;
200
- flex-direction: column;
201
- align-items: center;
202
- `;
203
-
204
- export const TitleFeatures = styled.h6`
205
- font-size: ${ props => props.theme.size.s };
206
- margin-bottom: 8px;
207
- `;
208
-
281
+ /**
282
+ * Edited: Ferjolt Ozuni - Date: 2026-08-01
283
+ *
284
+ * Left-aligned now that this sits inside a Detail column alongside route,
285
+ * transit and policies, rather than centred in a full-width band of its
286
+ * own under the card.
287
+ */
209
288
  export const FeaturesItems = styled.div`
210
289
  display: flex;
211
290
  flex-wrap: wrap;
212
- gap: 8px;
213
- justify-content: center;
291
+ gap: 6px;
214
292
  `;
@@ -20,7 +20,7 @@ interface Props {
20
20
  * results, which is otherwise easy to forget after scrolling.
21
21
  */
22
22
  const Summary = ({ shown, total, range, t, children }: Props): JSX.Element => (
23
- <Container>
23
+ <Container className="box">
24
24
  <Result>
25
25
  { /* Edited: Ferjolt Ozuni - Date: 2026-08-01
26
26
  `total`, not `count` - i18next treats a `count` variable as a
@@ -1,11 +1,23 @@
1
1
  import styled from 'styled-components';
2
2
 
3
+ /**
4
+ * Edited: Ferjolt Ozuni - Date: 2026-08-01
5
+ *
6
+ * Carries the `box` surface (padding is overridden to something tighter -
7
+ * this is a bar, not a panel) so the count and the sort control sit on the
8
+ * same card background as everything else on the page rather than floating
9
+ * loose on the page background.
10
+ */
3
11
  export const Container = styled.div`
4
12
  display: flex;
5
13
  flex-direction: column;
6
14
  gap: 12px;
7
15
  margin-bottom: 15px;
8
16
 
17
+ &.box {
18
+ padding: 12px 15px;
19
+ }
20
+
9
21
  @media (min-width: 1024px) {
10
22
  flex-direction: row;
11
23
  align-items: center;
package/Found/refine.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { TFunction } from 'i18next';
1
2
  import { FoundData } from '@autobusal/providers/types/routes';
2
3
 
3
4
  /**
@@ -97,6 +98,74 @@ export const durationOf = (item: FoundData): number | null => {
97
98
  return minutesOf(item.duration);
98
99
  };
99
100
 
101
+ /**
102
+ * Turn obtapi's "HH:MM" trip length into something that reads as a
103
+ * duration rather than a clock time.
104
+ *
105
+ * Edited: Ferjolt Ozuni - Date: 2026-08-01
106
+ *
107
+ * "08:30" sitting between two departure times is genuinely ambiguous -
108
+ * nothing about it says "eight and a half hours" rather than "half past
109
+ * eight", and on this card it sits directly between 09:00 and 16:30. The
110
+ * unit labels come from the translation files because "h"/"m" are not
111
+ * universal; Greek and the Cyrillic locales carry their own.
112
+ *
113
+ * The "-" sentinel (obtapi could not compute a duration) passes straight
114
+ * through - it must not become "0h 0m", which would read as a real answer.
115
+ */
116
+ export const formatDuration = (duration: string, t: TFunction<'common'>): string => {
117
+ if (!duration || duration === '-') {
118
+ return duration;
119
+ }
120
+
121
+ const total = minutesOf(duration);
122
+
123
+ const hours = Math.floor(total / 60);
124
+ const minutes = total % 60;
125
+
126
+ const pad = (value: number): string => (value <= 9 ? `0${ value }` : String(value));
127
+
128
+ const parts: string[] = [];
129
+
130
+ if (hours > 0) {
131
+ parts.push(t('routes_order.step2.route.duration.hours', { value: pad(hours) }));
132
+ }
133
+
134
+ // a trip under an hour still needs a number, and so does an exact
135
+ // number of hours - but "08h 00m" is noise, so zero minutes are dropped
136
+ if (minutes > 0 || hours === 0) {
137
+ parts.push(t('routes_order.step2.route.duration.minutes', { value: pad(minutes) }));
138
+ }
139
+
140
+ return parts.join(' ');
141
+ };
142
+
143
+ /**
144
+ * Split obtapi's formatted price into its parts so the decimals can be set
145
+ * as superscript.
146
+ *
147
+ * Edited: Ferjolt Ozuni - Date: 2026-08-01
148
+ *
149
+ * obtapi sends a rendered string ("1,048.00 EUR" - number_format, so the
150
+ * thousands separator is a comma and the decimal point a dot). Splitting it
151
+ * here rather than reformatting from `price.value` keeps the label's own
152
+ * currency and formatting as the single source of truth; we only decide how
153
+ * big each piece is drawn.
154
+ *
155
+ * Anything that does not match the expected shape is returned whole, so an
156
+ * unusual currency format degrades to today's rendering instead of being
157
+ * mangled.
158
+ */
159
+ export const splitPrice = (display: string): { whole: string, decimals: string | null, currency: string | null } => {
160
+ const match = /^(.+)\.(\d{2})\s+(\S+)$/.exec(display ?? '');
161
+
162
+ if (match === null) {
163
+ return { whole: display ?? '', decimals: null, currency: null };
164
+ }
165
+
166
+ return { whole: match[1], decimals: match[2], currency: match[3] };
167
+ };
168
+
100
169
  export const bucketOf = (item: FoundData): BucketKey => {
101
170
  const hour = Math.floor(departureOf(item) / 60);
102
171
 
package/RoutesOrder.tsx CHANGED
@@ -2,12 +2,13 @@ import { useState } from 'react';
2
2
  import { useParams } from 'react-router-dom';
3
3
  import { TFunction } from 'i18next';
4
4
  import { Meta } from '@autobusal/common';
5
+ import RoutesSearch from '@autobusal/routes-search';
5
6
  import { useGetSuggestions } from '@autobusal/routes-search/services';
7
+ import { SearchAgain } from './styles';
6
8
  import Step1 from './Step1/Step1';
7
9
  import Step2 from './Step2';
8
10
  import Step3 from './Step3';
9
- import Step4 from './Step4/Step4';
10
- import Step5 from './Step5/Step5';
11
+ import Checkout from './Checkout/Checkout';
11
12
  import { RoutesSearchForm } from '@autobusal/routes-search/types';
12
13
  import { FoundData } from '@autobusal/providers/types/routes';
13
14
  import { PersonData } from '@autobusal/providers/types/persons';
@@ -80,11 +81,6 @@ const RoutesOrder = ({ t }: Props): JSX.Element => {
80
81
  setCurrent(4);
81
82
  };
82
83
 
83
- const onStep4 = (data: PersonData): void => {
84
- setStep4(data);
85
- setCurrent(5);
86
- };
87
-
88
84
  const onBack = (step: number): void => {
89
85
  setCurrent(step);
90
86
  };
@@ -92,11 +88,48 @@ const RoutesOrder = ({ t }: Props): JSX.Element => {
92
88
  const showStep1 = current === 1;
93
89
  const showStep2 = current === 2 && step1 !== undefined;
94
90
  const showStep3 = current === 3 && step1 !== undefined && step2 !== undefined;
95
- const showStep4 = current === 4 && step1 !== undefined && step2 !== undefined;
96
- const showStep5 = current === 5 && step1 !== undefined && step2 !== undefined && step4 !== undefined;
91
+
92
+ // Edited: Ferjolt Ozuni - Date: 2026-08-01
93
+ // Step 4 is now the whole of the rest: passenger details and payment on
94
+ // one screen, so there is no step 5 and no intermediate handoff of
95
+ // passenger data between them.
96
+ const showCheckout = current === 4 && step1 !== undefined && step2 !== undefined;
97
+
98
+ /**
99
+ * Edited: Ferjolt Ozuni - Date: 2026-08-01
100
+ *
101
+ * The search form stays on screen while the user is choosing a route.
102
+ * Adjusting a date or a city used to mean "Back to Search", which threw
103
+ * away the results and any filters with them.
104
+ *
105
+ * Only on the route-selection screens: past that point a passenger has
106
+ * committed to a specific trip, and quietly re-running the search under
107
+ * them would discard passenger details they had already typed.
108
+ *
109
+ * onStep1 is the same handler Step1 uses, so submitting here re-runs the
110
+ * search and lands back on step 2 exactly as a fresh search would.
111
+ */
112
+ const showSearchAgain = (current === 2 || current === 3) && step1 !== undefined;
97
113
 
98
114
  return (
99
115
  <Meta title={ title } description={ description }>
116
+ { showSearchAgain && step1 !== undefined && (
117
+ <SearchAgain className="box">
118
+ <RoutesSearch
119
+ type={ step1.type ?? 'departure' }
120
+ from={ `${ step1.from }${ step1.stop_from ? `:${ step1.stop_from }` : '' }` }
121
+ to={ `${ step1.to }${ step1.stop_to ? `:${ step1.stop_to }` : '' }` }
122
+ departure={ step1.departure }
123
+ _return={ step1._return ?? '' }
124
+ adultsNo={ step1.adults }
125
+ childrenNo={ step1.children }
126
+ babiesNo={ step1.babies }
127
+ t={ t }
128
+ onSearch={ onStep1 }
129
+ />
130
+ </SearchAgain>
131
+ ) }
132
+
100
133
  { showStep1 && (
101
134
  <Step1
102
135
  value={ step1 }
@@ -127,25 +160,14 @@ const RoutesOrder = ({ t }: Props): JSX.Element => {
127
160
  />
128
161
  ) }
129
162
 
130
- { showStep4 && (
131
- <Step4
163
+ { showCheckout && (
164
+ <Checkout
132
165
  values={ step4 }
133
166
  step1={ step1 }
134
167
  step2={ step2 }
135
168
  step3={ step3 }
136
169
  t={ t }
137
- onSave={ onStep4 }
138
- onBack={ onBack }
139
- />
140
- ) }
141
-
142
- { showStep5 && (
143
- <Step5
144
- step1={ step1 }
145
- step2={ step2 }
146
- step3={ step3 }
147
- step4={ step4 }
148
- t={ t }
170
+ onStore={ setStep4 }
149
171
  onBack={ onBack }
150
172
  />
151
173
  ) }
package/Step2.tsx CHANGED
@@ -8,6 +8,7 @@ import { Title, Actions } from './styles';
8
8
  import { RoutesSearchForm } from '@autobusal/routes-search/types';
9
9
  import { FoundData } from '@autobusal/providers/types/routes';
10
10
  import { useGetDepartures } from './services';
11
+ import { useGetSuggestions } from '@autobusal/routes-search/services';
11
12
 
12
13
  interface Props {
13
14
  step1: RoutesSearchForm
@@ -20,11 +21,43 @@ interface Props {
20
21
  const Step2 = ({ step1, t, onSearch, onSave, onBack }: Props): JSX.Element => {
21
22
  const { data, isFetching } = useGetDepartures(step1);
22
23
 
24
+ /**
25
+ * Edited: Ferjolt Ozuni - Date: 2026-08-01
26
+ *
27
+ * "Select a Departure Schedule" said nothing about the trip and nothing
28
+ * a search engine could use. The heading now names the actual journey.
29
+ *
30
+ * Countries are dropped when both ends share one: "from Tirana Albania
31
+ * to Durres Albania" is noise, while on a cross-border trip the country
32
+ * is the part that matters.
33
+ *
34
+ * Read from the city list rather than from the results, so the heading
35
+ * is right even when the search finds nothing - which is exactly when
36
+ * being told what was searched for matters most. That list is the same
37
+ * cached query the search form's autocomplete already uses, so it costs
38
+ * no extra request.
39
+ */
40
+ const { data: cities } = useGetSuggestions();
41
+
42
+ const from = cities.find(city => city.slug === step1.from);
43
+ const to = cities.find(city => city.slug === step1.to);
44
+
45
+ const sameCountry = from?.country?.id !== undefined && from.country.id === to?.country?.id;
46
+
47
+ const title = from && to
48
+ ? t('routes_order.step2.title_pair', {
49
+ from: from.name,
50
+ to: to.name,
51
+ from_country: sameCountry ? '' : ` ${ from.country?.name ?? '' }`,
52
+ to_country: sameCountry ? '' : ` ${ to.country?.name ?? '' }`
53
+ })
54
+ : t('routes_order.step2.title');
55
+
23
56
  return (
24
57
  <>
25
58
  <Title>
26
59
  <RiBus2Line />
27
- { t('routes_order.step2.title') }
60
+ { title }
28
61
  </Title>
29
62
 
30
63
  <Steps value={ 2 } />
@@ -1,6 +1,6 @@
1
1
  import { TFunction } from 'i18next';
2
2
  import { FieldErrors, FieldValues, UseFormRegister } from 'react-hook-form';
3
- import { Calendar, Gender, ChooseSeat } from '@autobusal/common';
3
+ import { Calendar, Gender, ChooseSeat, Required } from '@autobusal/common';
4
4
  import { Validate, Display } from '@autobusal/utilities';
5
5
  import { Container, Title } from './styles';
6
6
  import { PersonData } from '@autobusal/providers/types/persons';
@@ -55,7 +55,7 @@ const Passenger = ({ type, number, values, fields, withReturn, busFrom, busTo, p
55
55
 
56
56
  <div className="column">
57
57
  <div className="row">
58
- { t('routes_order.step4.first_name') }
58
+ <Required>{ t('routes_order.step4.first_name') }</Required>
59
59
 
60
60
  <input
61
61
  type="text"
@@ -67,7 +67,7 @@ const Passenger = ({ type, number, values, fields, withReturn, busFrom, busTo, p
67
67
  </div>
68
68
 
69
69
  <div className="row">
70
- { t('routes_order.step4.last_name') }
70
+ <Required>{ t('routes_order.step4.last_name') }</Required>
71
71
 
72
72
  <input
73
73
  type="text"
@@ -81,7 +81,7 @@ const Passenger = ({ type, number, values, fields, withReturn, busFrom, busTo, p
81
81
 
82
82
  <div className="column">
83
83
  <div className="row">
84
- { t('routes_order.step4.dob') }
84
+ <Required>{ t('routes_order.step4.dob') }</Required>
85
85
 
86
86
  <Calendar
87
87
  type="dob"
@@ -116,7 +116,7 @@ const Passenger = ({ type, number, values, fields, withReturn, busFrom, busTo, p
116
116
  <div className="column">
117
117
  { has('passport') && (
118
118
  <div className="row">
119
- { t('routes_order.step4.passport') }
119
+ <Required>{ t('routes_order.step4.passport') }</Required>
120
120
 
121
121
  <input
122
122
  type="text"
@@ -130,7 +130,7 @@ const Passenger = ({ type, number, values, fields, withReturn, busFrom, busTo, p
130
130
 
131
131
  { has('phone') && (
132
132
  <div className="row">
133
- { t('routes_order.step4.phone') }
133
+ <Required>{ t('routes_order.step4.phone') }</Required>
134
134
 
135
135
  <input
136
136
  type="text"
@@ -148,7 +148,7 @@ const Passenger = ({ type, number, values, fields, withReturn, busFrom, busTo, p
148
148
  <div className="column">
149
149
  { has('whatsapp') && (
150
150
  <div className="row">
151
- { t('routes_order.step4.whatsapp') }
151
+ <Required>{ t('routes_order.step4.whatsapp') }</Required>
152
152
 
153
153
  <input
154
154
  type="text"
@@ -162,7 +162,7 @@ const Passenger = ({ type, number, values, fields, withReturn, busFrom, busTo, p
162
162
 
163
163
  { has('telegram') && (
164
164
  <div className="row">
165
- { t('routes_order.step4.telegram') }
165
+ <Required>{ t('routes_order.step4.telegram') }</Required>
166
166
 
167
167
  <input
168
168
  type="text"
@@ -179,7 +179,7 @@ const Passenger = ({ type, number, values, fields, withReturn, busFrom, busTo, p
179
179
  { type !== 'baby' && (
180
180
  <div className="column">
181
181
  <div className="row">
182
- { t('seats.departure') }
182
+ <Required>{ t('seats.departure') }</Required>
183
183
 
184
184
  <ChooseSeat
185
185
  name={ names.seatDeparture }
@@ -198,7 +198,7 @@ const Passenger = ({ type, number, values, fields, withReturn, busFrom, busTo, p
198
198
 
199
199
  { withReturn && (
200
200
  <div className="row">
201
- { t('seats.return') }
201
+ <Required>{ t('seats.return') }</Required>
202
202
 
203
203
  <ChooseSeat
204
204
  name={ names.seatReturn }