@autobusal/routes-order 1.13.0 → 1.21.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.
@@ -1,5 +1,5 @@
1
1
  import { TFunction } from 'i18next';
2
- import { Container, Pick, Label, Value, Meta } from './styles';
2
+ import { Container, Pick, Label, Value, Meta, Company } from './styles';
3
3
  import { SortKey, sortItems } from '../refine';
4
4
  import { FoundData } from '@autobusal/providers/types/routes';
5
5
 
@@ -55,8 +55,15 @@ const Picks = ({ items, sort, t, onSelect }: Props): JSX.Element | null => {
55
55
  <Value>{ best.price.display }</Value>
56
56
 
57
57
  <Meta>
58
- { best.duration !== '-' && `${ best.duration } · ` }
59
- { best.operator.company }
58
+ { best.duration !== '-' && best.duration }
59
+
60
+ { /* The operator's name is hidden on a phone - see Company in
61
+ styles. The separator goes with it, or a card reads
62
+ "4h ·" with nothing after the dot. */ }
63
+ <Company>
64
+ { best.duration !== '-' && ' · ' }
65
+ { best.operator.company }
66
+ </Company>
60
67
  </Meta>
61
68
  </Pick>
62
69
  );
@@ -1,13 +1,22 @@
1
1
  import styled, { css } from 'styled-components';
2
2
 
3
+ /**
4
+ * Edited: Ferjolt Ozuni - Date: 2026-08-03
5
+ *
6
+ * Three across at every width, including phones, where these used to stack.
7
+ * Stacked, they cost three full bands of vertical space before a single
8
+ * result - so the thing that exists to summarise the list was pushing the
9
+ * list itself off the screen. Side by side they are a strip, and comparing
10
+ * three numbers is what they are for; a column of three is a list to read.
11
+ */
3
12
  export const Container = styled.div`
4
13
  display: grid;
5
- gap: 10px;
14
+ gap: 6px;
6
15
  margin-bottom: 15px;
7
- grid-template-columns: 1fr;
16
+ grid-template-columns: repeat(3, 1fr);
8
17
 
9
18
  @media (min-width: 640px) {
10
- grid-template-columns: repeat(3, 1fr);
19
+ gap: 10px;
11
20
  }
12
21
  `;
13
22
 
@@ -15,9 +24,15 @@ export const Pick = styled.button<{ $active: boolean }>`
15
24
  display: flex;
16
25
  flex-direction: column;
17
26
  align-items: flex-start;
18
- gap: 4px;
19
- padding: 12px 15px;
27
+ gap: 2px;
28
+ padding: 8px 9px;
20
29
  text-align: left;
30
+ min-width: 0;
31
+
32
+ @media (min-width: 640px) {
33
+ gap: 4px;
34
+ padding: 12px 15px;
35
+ }
21
36
  border-radius: ${ props => props.theme.borderRadius };
22
37
  border: 1px solid ${ props => props.theme.inputs.border };
23
38
  background: ${ props => props.theme.background.normal };
@@ -34,22 +49,58 @@ export const Pick = styled.button<{ $active: boolean }>`
34
49
  `;
35
50
 
36
51
  export const Label = styled.span`
37
- font-size: ${ props => props.theme.size.xs };
52
+ max-width: 100%;
53
+ overflow: hidden;
54
+ font-size: calc(${ props => props.theme.size.xs } - 1px);
38
55
  text-transform: uppercase;
39
- letter-spacing: .5px;
56
+ letter-spacing: 0;
40
57
  font-weight: 700;
41
58
  color: ${ props => props.theme.font.faded };
59
+ text-overflow: ellipsis;
60
+ white-space: nowrap;
61
+
62
+ @media (min-width: 640px) {
63
+ font-size: ${ props => props.theme.size.xs };
64
+ letter-spacing: .5px;
65
+ }
42
66
  `;
43
67
 
44
68
  export const Value = styled.strong`
45
- font-size: ${ props => props.theme.size.l };
69
+ font-size: ${ props => props.theme.size.m };
70
+
71
+ @media (min-width: 640px) {
72
+ font-size: ${ props => props.theme.size.l };
73
+ }
46
74
  `;
47
75
 
48
76
  export const Meta = styled.span`
49
77
  overflow: hidden;
50
78
  max-width: 100%;
51
- font-size: ${ props => props.theme.size.xs };
79
+ font-size: calc(${ props => props.theme.size.xs } - 1px);
52
80
  color: ${ props => props.theme.font.faded };
53
81
  text-overflow: ellipsis;
54
82
  white-space: nowrap;
83
+
84
+ @media (min-width: 640px) {
85
+ font-size: ${ props => props.theme.size.xs };
86
+ }
87
+ `;
88
+
89
+ /**
90
+ * The operator's name, dropped on a phone.
91
+ *
92
+ * Edited: Ferjolt Ozuni - Date: 2026-08-03
93
+ *
94
+ * At a third of a 375px screen each card gets about 110px, and a company
95
+ * name is the one thing here that will not fit in it - it would ellipsis to
96
+ * two or three characters, which is noise rather than information. The
97
+ * duration stays, because it is short and it is the number being compared;
98
+ * the name is one line down in the results anyway.
99
+ */
100
+ export const Company = styled.span`
101
+ display: none;
102
+
103
+ @media (min-width: 640px) {
104
+ display: inline;
105
+ }
55
106
  `;
@@ -1,11 +1,11 @@
1
1
  import { useState } from 'react';
2
2
  import { TFunction } from 'i18next';
3
- import { HiOutlineArrowNarrowRight, HiOutlineDocumentText } from 'react-icons/hi';
3
+ import { HiOutlineArrowNarrowRight, HiOutlineDocumentText, HiChevronDown, HiChevronUp } from 'react-icons/hi';
4
4
  import { BiHourglass } from 'react-icons/bi';
5
5
  import { RiBus2Line } from 'react-icons/ri';
6
6
  import { formatDuration, splitPrice } from '../refine';
7
7
  import { Button, RouteFeature, Policy, Rating } from '@autobusal/common';
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';
8
+ import { Container, ButtonDetails, SpecialOffer, FavoriteStop, Trip, Company, Location, Stop, Iso, Time, Price, Amount, Decimals, Currency, PriceNotice, LinkCompany, Details, Detail, ButtonPolicy, TitleDetail, LinkRoute, FeaturesItems, Leaves } from './styles';
9
9
  import { FoundData } from '@autobusal/providers/types/routes';
10
10
 
11
11
  // neutral placeholder so a missing/broken operator logo doesn't render the
@@ -27,6 +27,14 @@ interface Props {
27
27
  const Route = ({ data, type, passengers, t, onSave, children }: Props): JSX.Element => {
28
28
  const [ policy, setPolicy ] = useState<boolean>(false);
29
29
 
30
+ // Edited: Ferjolt Ozuni - Date: 2026-08-03
31
+ // Closed to begin with. Amenities, route code, transit and policies are
32
+ // supporting detail on a row somebody is scanning, and four bands of it
33
+ // per result pushed the next result off a phone screen. The content stays
34
+ // in the DOM either way (see Details in styles) so the prerendered
35
+ // snapshot a crawler reads is unchanged.
36
+ const [ details, setDetails ] = useState<boolean>(false);
37
+
30
38
  const customs = Object.values(data.customs).join(', ');
31
39
  const transiting = Object.values(data.transiting).join(', ');
32
40
 
@@ -47,11 +55,26 @@ const Route = ({ data, type, passengers, t, onSave, children }: Props): JSX.Elem
47
55
  ? `${ import.meta.env.VITE_API_OBT }/api/external/go/${ data.external.offer_id }`
48
56
  : undefined;
49
57
 
58
+ // Edited: Ferjolt Ozuni - Date: 2026-08-03
59
+ // Only today's results carry this - obtapi sends null on every other date,
60
+ // so there is no "in 26 days" badge to suppress here.
61
+ const leaves = data.departs_in ?? null;
62
+
63
+ const soon = leaves !== null && leaves < 60;
64
+
50
65
  return (
51
66
  <Container className="box" $type={ type }>
52
67
  { data.price.offer === true && <SpecialOffer>{ t('routes_order.step2.route.offer') }</SpecialOffer> }
53
68
  { type === 'favorite' && <FavoriteStop>{ t('routes_order.step2.route.preferred_stop') }</FavoriteStop> }
54
69
 
70
+ { leaves !== null && (
71
+ <Leaves $soon={ soon }>
72
+ { leaves < 60
73
+ ? t('routes_order.step2.route.leaves_minutes', { minutes: leaves })
74
+ : t('routes_order.step2.route.leaves_hours', { hours: Math.floor(leaves / 60), minutes: leaves % 60 }) }
75
+ </Leaves>
76
+ ) }
77
+
55
78
  <Trip>
56
79
  <Company>
57
80
  <LinkCompany to={ bookOnUrl ?? data.operator.link } target="_blank" rel="noopener noreferrer" title={ data.operator.company }><img src={ data.operator.logo_url || LOGO_FALLBACK } onError={ (e) => { e.currentTarget.onerror = null; e.currentTarget.src = LOGO_FALLBACK; } } alt={ data.operator.company } /></LinkCompany>
@@ -134,7 +157,16 @@ const Route = ({ data, type, passengers, t, onSave, children }: Props): JSX.Elem
134
157
 
135
158
  { children }
136
159
 
137
- <Details>
160
+ <ButtonDetails
161
+ type="button"
162
+ aria-expanded={ details }
163
+ onClick={ () => setDetails(!details) }
164
+ >
165
+ { details ? <HiChevronUp /> : <HiChevronDown /> }
166
+ { t('routes_order.step2.route.details') }
167
+ </ButtonDetails>
168
+
169
+ <Details $open={ details }>
138
170
  { /* Edited: Ferjolt Ozuni - Date: 2026-08-01
139
171
  Amenities were a separate full-width block under the card.
140
172
  They are the same kind of thing as route, transit and policies
@@ -6,8 +6,20 @@ export const Container = styled.div<{ $type: 'favorite' | 'normal' }>`
6
6
  position: relative;
7
7
  display: flex;
8
8
  flex-direction: column;
9
- gap: 15px;
10
- padding: 15px;
9
+
10
+ /**
11
+ * Edited: Ferjolt Ozuni - Date: 2026-08-03
12
+ * Tighter on a phone. Every result carried 15px of padding and 15px
13
+ * between each of its bands, which is most of a thumb of screen spent on
14
+ * gaps before anything is read.
15
+ */
16
+ gap: 8px;
17
+ padding: 10px;
18
+
19
+ @media (min-width: 640px) {
20
+ gap: 15px;
21
+ padding: 15px;
22
+ }
11
23
 
12
24
  ${ props => props.$type === 'favorite' && css`
13
25
  border: 1px solid ${ props => props.theme.font.success };
@@ -45,14 +57,32 @@ export const FavoriteStop = styled.div`
45
57
  border-bottom-right-radius: ${ props => props.theme.borderRadius };
46
58
  `;
47
59
 
60
+ /**
61
+ * Edited: Ferjolt Ozuni - Date: 2026-08-03
62
+ *
63
+ * VERTICAL on a phone, side by side from 640px up.
64
+ *
65
+ * Side by side was tried first and does not survive the content: each end
66
+ * gets about 96px at 375px, while "Thessaloniki (GRC) 16:30" measures 215px
67
+ * - it painted 50px outside the card, and the page never scrolled sideways
68
+ * so nothing looked broken until it was measured against the box it was
69
+ * supposed to be in. Stacked, each end has the full width, so nothing is
70
+ * truncated and nothing escapes.
71
+ *
72
+ * Compact is bought back a different way: tight gaps, a short arrow turned
73
+ * to point down the column, and the price on one row instead of a centred
74
+ * stack. Same information, roughly a third less height than before.
75
+ */
48
76
  export const Trip = styled.div`
49
77
  display: flex;
50
78
  flex-direction: column;
51
- gap: 15px;
79
+ gap: 6px;
52
80
 
53
81
  @media (min-width: 640px) {
54
82
  flex-direction: row;
55
83
  flex-wrap: wrap;
84
+ align-items: center;
85
+ gap: 15px;
56
86
  }
57
87
 
58
88
  @media (min-width: 1024px) {
@@ -95,12 +125,49 @@ export const Location = styled.div`
95
125
  min-width: 0;
96
126
  display: flex;
97
127
  flex-direction: column;
98
- align-items: center;
99
- gap: 3px;
100
- text-align: center;
101
128
 
129
+ /**
130
+ * Edited: Ferjolt Ozuni - Date: 2026-08-03
131
+ * Left-aligned while the ends are stacked - centred text in a full-width
132
+ * column leaves the two times floating in the middle of the card with
133
+ * nothing to line them up against. Centred again from 640px, where the
134
+ * two ends sit either side of the arrow and the symmetry is the point.
135
+ */
136
+ align-items: flex-start;
137
+ gap: 1px;
138
+ text-align: left;
139
+
140
+ @media (min-width: 640px) {
141
+ align-items: center;
142
+ gap: 3px;
143
+ text-align: center;
144
+ }
145
+
146
+ /**
147
+ * Edited: Ferjolt Ozuni - Date: 2026-08-03
148
+ *
149
+ * The heading has to be allowed to WRAP now that the two ends share a
150
+ * row. The min-width:0 above lets this column shrink, but the heading
151
+ * inside kept its intrinsic single-line width and simply painted outside
152
+ * the card - "Thessaloniki (GRC) 16:30" measured 215px inside a 96px
153
+ * column and ran 50px past the card's right edge. The page never scrolled
154
+ * sideways, so nothing looked broken until the text was measured against
155
+ * the box it was supposed to be in.
156
+ *
157
+ * A step down in size as well, because three columns of a 375px screen do
158
+ * not have room for a headline at body-copy scale.
159
+ */
102
160
  & > h2 {
103
161
  margin-bottom: 0;
162
+ max-width: 100%;
163
+ line-height: 1.25;
164
+ overflow-wrap: anywhere;
165
+ }
166
+
167
+ @media (min-width: 640px) {
168
+ & > h2 {
169
+ overflow-wrap: normal;
170
+ }
104
171
  }
105
172
  `;
106
173
 
@@ -111,11 +178,30 @@ export const Location = styled.div`
111
178
  * body copy it competed with the city above it; a step down lets the eye
112
179
  * land on the city first and read the stop only when it needs to.
113
180
  */
181
+ /**
182
+ * Edited: Ferjolt Ozuni - Date: 2026-08-03
183
+ *
184
+ * Clamped to two lines on a phone. Now that the two ends of the journey
185
+ * share a row, each gets about 96px - and a name like "Terminali Lindor
186
+ * Interurban dhe Nderkombetar i Autobusave (TEG)" ran to five lines and
187
+ * stretched the whole row to match, so one terminal with a long name made
188
+ * every card taller than the screen. Two lines and an ellipsis, which is
189
+ * what the reference layouts do with the same problem.
190
+ */
114
191
  export const Stop = styled.span`
192
+ display: -webkit-box;
193
+ -webkit-line-clamp: 2;
194
+ -webkit-box-orient: vertical;
195
+ overflow: hidden;
115
196
  font-size: ${ props => props.theme.size.xs };
116
197
  font-weight: 700;
117
198
  line-height: 1.3;
118
199
  color: ${ props => props.theme.font.faded };
200
+
201
+ @media (min-width: 1024px) {
202
+ display: block;
203
+ overflow: visible;
204
+ }
119
205
  `;
120
206
 
121
207
  /**
@@ -140,13 +226,39 @@ export const Iso = styled.span`
140
226
 
141
227
  export const Time = styled.div`
142
228
  display: flex;
143
- flex-direction: column;
144
- gap: 8px;
229
+ flex-direction: row;
230
+ gap: 6px;
145
231
  align-items: center;
146
232
  font-weight: 700;
147
233
 
234
+ @media (min-width: 640px) {
235
+ flex-direction: column;
236
+ gap: 8px;
237
+ }
238
+
239
+ /**
240
+ * Edited: Ferjolt Ozuni - Date: 2026-08-03
241
+ * The arrow sat between two stacked blocks at 1.5x the largest type in
242
+ * the theme. Now that the two ends of the journey share a row with it,
243
+ * that size would take the width the city names need - it is a
244
+ * separator, not a headline.
245
+ */
246
+ /**
247
+ * Edited: Ferjolt Ozuni - Date: 2026-08-03
248
+ * Turned to point DOWN while the two ends are stacked - an arrow pointing
249
+ * right between two things sitting one above the other says the wrong
250
+ * thing about which way the journey goes.
251
+ */
148
252
  & > svg {
149
- font-size: calc(1.5 * ${ props => props.theme.size.xxl });
253
+ font-size: ${ props => props.theme.size.l };
254
+ transform: rotate(90deg);
255
+ }
256
+
257
+ @media (min-width: 640px) {
258
+ & > svg {
259
+ font-size: calc(1.5 * ${ props => props.theme.size.xxl });
260
+ transform: none;
261
+ }
150
262
  }
151
263
 
152
264
  & > span {
@@ -176,12 +288,44 @@ export const Time = styled.div`
176
288
  export const Price = styled.div`
177
289
  flex-basis: 100%;
178
290
  display: flex;
179
- flex-direction: column;
180
- gap: 8px;
291
+
292
+ /**
293
+ * Edited: Ferjolt Ozuni - Date: 2026-08-03
294
+ *
295
+ * On a phone: the fare and the button share one row, and the tax note sits
296
+ * under them.
297
+ *
298
+ * Reordered in CSS rather than in the markup, because the source order -
299
+ * fare, note, button - is the right one to read aloud and the right one
300
+ * for a crawler: the note explains the number it follows. Only the
301
+ * PAINTING needs to differ, and the order property says exactly that
302
+ * without moving the note away from the price it qualifies.
303
+ */
304
+ flex-direction: row;
305
+ flex-wrap: wrap;
306
+ justify-content: space-between;
181
307
  align-items: center;
308
+ gap: 4px 8px;
309
+
310
+ & > *:nth-child(1) { order: 1; }
311
+
312
+ /* the note drops to its own line under both */
313
+ & > *:nth-child(2) { order: 3; flex-basis: 100%; }
314
+
315
+ & > *:nth-child(3) { order: 2; }
182
316
 
183
317
  @media (min-width: 768px) {
184
318
  flex: 0 0 165px;
319
+ flex-direction: column;
320
+ justify-content: initial;
321
+ gap: 8px;
322
+
323
+ & > *:nth-child(1),
324
+ & > *:nth-child(2),
325
+ & > *:nth-child(3) {
326
+ order: initial;
327
+ flex-basis: auto;
328
+ }
185
329
  }
186
330
  `;
187
331
 
@@ -220,11 +364,20 @@ export const Currency = styled.span`
220
364
  * frees the vertical space the Select button needs.
221
365
  */
222
366
  export const PriceNotice = styled.span`
223
- margin-top: -4px;
367
+ /**
368
+ * Edited: Ferjolt Ozuni - Date: 2026-08-03
369
+ * Left-aligned under the fare on a phone, where it is a full-width line
370
+ * beneath a price and a button rather than the middle of a centred stack.
371
+ */
224
372
  font-size: ${ props => props.theme.size.xxs };
225
373
  line-height: 1.25;
226
374
  color: ${ props => props.theme.font.faded };
227
- text-align: center;
375
+ text-align: left;
376
+
377
+ @media (min-width: 768px) {
378
+ margin-top: -4px;
379
+ text-align: center;
380
+ }
228
381
  `;
229
382
 
230
383
  export const LinkCompany = styled(Link)`
@@ -239,24 +392,64 @@ export const LinkCompany = styled(Link)`
239
392
  }
240
393
  `;
241
394
 
242
- export const Details = styled.div`
243
- display: flex;
244
- flex-direction: column;
245
- gap: 15px;
395
+ /**
396
+ * Edited: Ferjolt Ozuni - Date: 2026-08-03
397
+ *
398
+ * Collapsible, and inline at every width including phones.
399
+ *
400
+ * Hidden with CSS rather than unmounted, deliberately. These cards are on a
401
+ * page the prerender crawl snapshots for every route pair in the sitemap, so
402
+ * amenities, the route code, transit countries and the policy link have to
403
+ * be IN the DOM whether or not a reader has opened them - unmounting would
404
+ * take real per-pair content out of exactly the pages Tier 3 exists to
405
+ * fatten.
406
+ *
407
+ * Wrapping instead of stacking on a phone turns four bands of vertical space
408
+ * into one or two, which is the space the results themselves wanted.
409
+ */
410
+ export const Details = styled.div<{ $open: boolean }>`
411
+ display: ${ props => (props.$open ? 'flex' : 'none') };
412
+ flex-wrap: wrap;
413
+ gap: 6px;
246
414
 
247
415
  @media (min-width: 640px) {
248
- flex-direction: row;
249
- flex-wrap: wrap;
416
+ gap: 15px;
250
417
  }
251
418
  `;
252
419
 
253
- export const Detail = styled.div`
254
- padding: 8px 10px;
420
+ /**
421
+ * The disclosure. Small and quiet - it is a way to get at supporting detail,
422
+ * not an action competing with "select".
423
+ */
424
+ export const ButtonDetails = styled.button`
425
+ display: inline-flex;
426
+ align-items: center;
427
+ gap: 5px;
428
+ align-self: flex-start;
429
+ padding: 4px 0;
255
430
  font-size: ${ props => props.theme.size.s };
431
+ color: ${ props => props.theme.font.faded };
432
+
433
+ &:hover {
434
+ color: ${ props => props.theme.font.normal };
435
+ }
436
+
437
+ svg {
438
+ transition: transform 0.2s ease;
439
+ }
440
+ `;
441
+
442
+ export const Detail = styled.div`
443
+ flex: 1 1 auto;
444
+ min-width: 0;
445
+ padding: 5px 8px;
446
+ font-size: calc(${ props => props.theme.size.s } - 1px);
256
447
  background: ${ props => props.theme.background.neutral };
257
448
  border-radius: ${ props => props.theme.borderRadius };
258
449
 
259
450
  @media (min-width: 480px) {
451
+ padding: 8px 10px;
452
+ font-size: ${ props => props.theme.size.s };
260
453
  flex-basis: calc(50% - 7.5px);
261
454
  }
262
455
  @media (min-width: 768px) {
@@ -300,4 +493,28 @@ export const FeaturesItems = styled.div`
300
493
  display: flex;
301
494
  flex-wrap: wrap;
302
495
  gap: 6px;
303
- `;
496
+ `;
497
+
498
+ /**
499
+ * "Leaves in 40 minutes", on today's departures only.
500
+ *
501
+ * Edited: Ferjolt Ozuni - Date: 2026-08-03
502
+ *
503
+ * Same-day seats are sellable now, and a time alone does not tell somebody
504
+ * whether they can make it - "14:00" reads very differently at nine in the
505
+ * morning and at half past one. Under an hour it turns urgent, because that
506
+ * is the point where the answer stops being "yes" and starts being "only if
507
+ * you leave now".
508
+ */
509
+ export const Leaves = styled.div<{ $soon: boolean }>`
510
+ align-self: flex-start;
511
+ padding: 3px 10px;
512
+ border-radius: 100px;
513
+ font-size: ${ props => props.theme.size.xs };
514
+ font-weight: 700;
515
+ white-space: nowrap;
516
+
517
+ color: ${ props => (props.$soon ? '#FFF' : props.theme.font.normal) };
518
+ background: ${ props => (props.$soon ? props.theme.font.error : props.theme.primary.neutral) };
519
+ `;
520
+
@@ -0,0 +1,35 @@
1
+ import { TFunction } from 'i18next';
2
+ import { HiOutlineExclamationCircle } from 'react-icons/hi';
3
+ import { Container } from './styles';
4
+
5
+ interface Props {
6
+ t: TFunction<'common'>
7
+ }
8
+
9
+ /**
10
+ * The caution that comes with selling today's seats.
11
+ *
12
+ * Edited: Ferjolt Ozuni - Date: 2026-08-03
13
+ *
14
+ * Same-day departures are sellable now, and two things about them are worth
15
+ * saying plainly before somebody pays.
16
+ *
17
+ * A coach that has already left is filtered out server-side
18
+ * (Search\Departed), and since countries carry a timezone that filter is now
19
+ * judged against the DEPARTURE STOP's own clock rather than ours - so the
20
+ * warning is no longer about doubt on our side.
21
+ *
22
+ * What remains is the honest thing to say when the margin is minutes: every
23
+ * time on the page is local to its stop, the seat is real, the coach is
24
+ * about to go, and whether somebody can reach the terminal in time is not
25
+ * something we can know for them.
26
+ */
27
+ const SameDay = ({ t }: Props): JSX.Element => (
28
+ <Container>
29
+ <HiOutlineExclamationCircle />
30
+
31
+ <span>{ t('routes_order.step2.same_day') }</span>
32
+ </Container>
33
+ );
34
+
35
+ export default SameDay;
@@ -0,0 +1,27 @@
1
+ import styled from 'styled-components';
2
+
3
+ /**
4
+ * Edited: Ferjolt Ozuni - Date: 2026-08-03
5
+ *
6
+ * Shown only on a same-day search. A caution, not an alarm - the whole point
7
+ * of selling today is that these seats are worth selling, and a red banner
8
+ * over every one of them would say the opposite.
9
+ */
10
+ export const Container = styled.div`
11
+ display: flex;
12
+ gap: 8px;
13
+ align-items: flex-start;
14
+ margin-bottom: 15px;
15
+ padding: 10px 12px;
16
+ border-radius: ${ props => props.theme.borderRadius };
17
+ background: ${ props => props.theme.primary.neutral };
18
+ color: ${ props => props.theme.font.normal };
19
+ font-size: ${ props => props.theme.size.s };
20
+ line-height: 1.4;
21
+
22
+ & > svg {
23
+ flex: 0 0 auto;
24
+ margin-top: 2px;
25
+ font-size: ${ props => props.theme.size.l };
26
+ }
27
+ `;