@autobusal/routes-order 1.37.7 → 1.37.9
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/Availability/Availability.tsx +8 -1
- package/Availability/Skeleton.tsx +119 -0
- package/Availability/styles.ts +2 -1
- package/Facts/Facts.tsx +9 -3
- package/Facts/styles.ts +12 -0
- package/Found/Dates/Dates.tsx +29 -5
- package/Found/Dates/styles.ts +20 -7
- package/Found/Filters/styles.ts +16 -3
- package/Found/Orders/Orders.tsx +36 -34
- package/Found/Orders/Skeleton.tsx +102 -0
- package/Found/Route/Route.tsx +4 -10
- package/Found/Route/styles.ts +50 -7
- package/Found/Sort/styles.ts +6 -1
- package/Found/refine.ts +0 -46
- package/RoutesOrder.tsx +24 -2
- package/Sections/Sections.tsx +25 -5
- package/Step1/Step1.tsx +32 -4
- package/Step1/prepare.ts +39 -2
- package/Step4/Passenger/Passenger.tsx +67 -14
- package/Step4/Passenger/styles.ts +31 -0
- package/Step5/Billing/Billing.tsx +12 -9
- package/Step5/Billing/styles.ts +8 -0
- package/Step5/Summary/About/About.tsx +15 -2
- package/Step5/Summary/About/styles.ts +22 -0
- package/package.json +1 -1
- package/styles.ts +43 -0
- package/Found/Group/Group.tsx +0 -68
- package/Found/Group/styles.ts +0 -44
|
@@ -93,7 +93,10 @@ const Billing = ({ errors, t, refs, only }: Props): JSX.Element => {
|
|
|
93
93
|
<div className="row">
|
|
94
94
|
<Required htmlFor={ id('bill_fn') }>{ t('routes_order.step5.billing.first_name') }</Required>
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
{ /* Claude - 2026-09-16 (audit): `billing` autocomplete tokens, so a
|
|
97
|
+
browser or password manager fills the block as the billing
|
|
98
|
+
address it is; aria-required says what the asterisk shows. */ }
|
|
99
|
+
<input type="text" id={ id('bill_fn') } autoComplete="billing given-name" aria-required="true" { ...refs('bill_fn', Validate('required|min_length:2|max_length:100', t)) } />
|
|
97
100
|
|
|
98
101
|
{ Display(errors.bill_fn) }
|
|
99
102
|
</div>
|
|
@@ -101,7 +104,7 @@ const Billing = ({ errors, t, refs, only }: Props): JSX.Element => {
|
|
|
101
104
|
<div className="row">
|
|
102
105
|
<Required htmlFor={ id('bill_ln') }>{ t('routes_order.step5.billing.last_name') }</Required>
|
|
103
106
|
|
|
104
|
-
<input type="text" id={ id('bill_ln') } { ...refs('bill_ln', Validate('required|min_length:2|max_length:100', t)) } />
|
|
107
|
+
<input type="text" id={ id('bill_ln') } autoComplete="billing family-name" aria-required="true" { ...refs('bill_ln', Validate('required|min_length:2|max_length:100', t)) } />
|
|
105
108
|
|
|
106
109
|
{ Display(errors.bill_ln) }
|
|
107
110
|
</div>
|
|
@@ -113,7 +116,7 @@ const Billing = ({ errors, t, refs, only }: Props): JSX.Element => {
|
|
|
113
116
|
<div className="row">
|
|
114
117
|
<Required htmlFor={ id('email') }>{ t('routes_order.step5.billing.email') }</Required>
|
|
115
118
|
|
|
116
|
-
<input type="email" id={ id('email') } { ...refs('email', Validate('required|email|max_length:255', t)) } />
|
|
119
|
+
<input type="email" id={ id('email') } autoComplete="email" aria-required="true" { ...refs('email', Validate('required|email|max_length:255', t)) } />
|
|
117
120
|
|
|
118
121
|
{ Display(errors.email) }
|
|
119
122
|
|
|
@@ -127,7 +130,7 @@ const Billing = ({ errors, t, refs, only }: Props): JSX.Element => {
|
|
|
127
130
|
<div className="row">
|
|
128
131
|
<Required htmlFor={ id('bill_phone') }>{ t('routes_order.step5.billing.phone') }</Required>
|
|
129
132
|
|
|
130
|
-
<input type="text" id={ id('bill_phone') } { ...refs('bill_phone', Validate('required|min_length:10|max_length:20', t)) } />
|
|
133
|
+
<input type="text" id={ id('bill_phone') } autoComplete="billing tel" aria-required="true" { ...refs('bill_phone', Validate('required|min_length:10|max_length:20', t)) } />
|
|
131
134
|
|
|
132
135
|
{ Display(errors.bill_phone) }
|
|
133
136
|
</div>
|
|
@@ -138,7 +141,7 @@ const Billing = ({ errors, t, refs, only }: Props): JSX.Element => {
|
|
|
138
141
|
<div className="row">
|
|
139
142
|
<Required htmlFor={ id('bill_address') }>{ t('routes_order.step5.billing.address') }</Required>
|
|
140
143
|
|
|
141
|
-
<textarea id={ id('bill_address') } { ...refs('bill_address', Validate(`required|min_length:${ gaps ? 5 : 2 }`, t)) }></textarea>
|
|
144
|
+
<textarea id={ id('bill_address') } autoComplete="billing street-address" aria-required="true" { ...refs('bill_address', Validate(`required|min_length:${ gaps ? 5 : 2 }`, t)) }></textarea>
|
|
142
145
|
|
|
143
146
|
{ Display(errors.bill_address) }
|
|
144
147
|
</div>
|
|
@@ -149,7 +152,7 @@ const Billing = ({ errors, t, refs, only }: Props): JSX.Element => {
|
|
|
149
152
|
<div className="row">
|
|
150
153
|
<Required htmlFor={ id('bill_city') }>{ t('routes_order.step5.billing.city') }</Required>
|
|
151
154
|
|
|
152
|
-
<input type="text" id={ id('bill_city') } { ...refs('bill_city', Validate(`required|min_length:2|max_length:${ gaps ? 40 : 100 }`, t)) } />
|
|
155
|
+
<input type="text" id={ id('bill_city') } autoComplete="billing address-level2" aria-required="true" { ...refs('bill_city', Validate(`required|min_length:2|max_length:${ gaps ? 40 : 100 }`, t)) } />
|
|
153
156
|
|
|
154
157
|
{ Display(errors.bill_city) }
|
|
155
158
|
</div>
|
|
@@ -159,7 +162,7 @@ const Billing = ({ errors, t, refs, only }: Props): JSX.Element => {
|
|
|
159
162
|
<div className="row">
|
|
160
163
|
<Required htmlFor={ id('bill_country') }>{ t('routes_order.step5.billing.country') }</Required>
|
|
161
164
|
|
|
162
|
-
<select id={ id('bill_country') } { ...refs('bill_country', Validate('required|min:1', t)) }>
|
|
165
|
+
<select id={ id('bill_country') } autoComplete="billing country-name" aria-required="true" { ...refs('bill_country', Validate('required|min:1', t)) }>
|
|
163
166
|
<option value={ 0 }>{ t('routes_order.step5.billing.country_choose') }</option>
|
|
164
167
|
{ items }
|
|
165
168
|
</select>
|
|
@@ -184,7 +187,7 @@ const Billing = ({ errors, t, refs, only }: Props): JSX.Element => {
|
|
|
184
187
|
rather than the asterisked one, but still associated */ }
|
|
185
188
|
<label htmlFor={ id('bill_company') }>{ t('routes_order.step5.billing.company') }</label>
|
|
186
189
|
|
|
187
|
-
<input type="text" id={ id('bill_company') } { ...refs('bill_company') } />
|
|
190
|
+
<input type="text" id={ id('bill_company') } autoComplete="organization" { ...refs('bill_company') } />
|
|
188
191
|
|
|
189
192
|
{ Display(errors.bill_company) }
|
|
190
193
|
</div>
|
|
@@ -210,7 +213,7 @@ const Billing = ({ errors, t, refs, only }: Props): JSX.Element => {
|
|
|
210
213
|
{ asked('terms') && (
|
|
211
214
|
<div className="row row-nomargin">
|
|
212
215
|
<label className="single">
|
|
213
|
-
<input type="checkbox" value="1" { ...refs('terms', Validate('required', t)) } />
|
|
216
|
+
<input type="checkbox" value="1" aria-required="true" { ...refs('terms', Validate('required', t)) } />
|
|
214
217
|
|
|
215
218
|
<span dangerouslySetInnerHTML={{
|
|
216
219
|
__html: t('routes_order.step5.billing.terms', {
|
package/Step5/Billing/styles.ts
CHANGED
|
@@ -2,6 +2,14 @@ import styled from 'styled-components';
|
|
|
2
2
|
|
|
3
3
|
export const Container = styled.div`
|
|
4
4
|
margin-top: 25px;
|
|
5
|
+
|
|
6
|
+
// Claude - 2026-09-16 (audit): the passenger block's 44px phone fields,
|
|
7
|
+
// for the same reason - see Step4/Passenger/styles.ts
|
|
8
|
+
@media (max-width: 639px) {
|
|
9
|
+
input:not([type='checkbox']):not([type='radio']), select {
|
|
10
|
+
min-height: 44px;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
5
13
|
`;
|
|
6
14
|
|
|
7
15
|
export const NoticeEmail = styled.div`
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { TFunction } from 'i18next';
|
|
2
2
|
import { FaArrowLeft, FaArrowRight } from 'react-icons/fa';
|
|
3
3
|
import { FaCircleDot, FaLocationDot } from 'react-icons/fa6';
|
|
4
|
-
import { Trip, TripLabel, Container, ContainerIcon, Icon, Information, Title, Operator, Carrier, Logo, Leg, Point, PointIcon, PointCity, Country, PointMeta } from './styles';
|
|
4
|
+
import { Trip, TripLabel, Container, ContainerIcon, Icon, Information, Title, Line, Code, Operator, Carrier, Logo, Leg, Point, PointIcon, PointCity, Country, PointMeta } from './styles';
|
|
5
5
|
import { RoutesSearchForm } from '@autobusal/routes-search/types';
|
|
6
6
|
import { FoundData } from '@autobusal/providers/types/routes';
|
|
7
7
|
|
|
@@ -57,7 +57,20 @@ const About = ({ type, step1, data, number, t }: Props): (JSX.Element | null) =>
|
|
|
57
57
|
</ContainerIcon>
|
|
58
58
|
|
|
59
59
|
<Information>
|
|
60
|
-
|
|
60
|
+
{ /* Claude - 2026-09-16 (audit): THE LEG THAT WAS BOUGHT, not the
|
|
61
|
+
coach's own name. "THESSALONIKI - VERIA - SHKODER (II)" is the
|
|
62
|
+
operator's route - a coach that carries on to Shkodër - and a
|
|
63
|
+
buyer who searched Thessaloniki to Tirana read it as the wrong
|
|
64
|
+
ticket. The two cities are the title, and the date and time sit
|
|
65
|
+
in bold on the origin line below. The code and the operator's
|
|
66
|
+
route name drop to a line of detail, where they still match
|
|
67
|
+
the printed ticket without claiming to be the journey. */ }
|
|
68
|
+
<Title>{ data.locations.from.city.name } → { data.locations.to.city.name }</Title>
|
|
69
|
+
|
|
70
|
+
<Line>
|
|
71
|
+
<Code title={ t('routes_order.step2.route.name') }>{ data.code }</Code>
|
|
72
|
+
{ data.name }
|
|
73
|
+
</Line>
|
|
61
74
|
|
|
62
75
|
{ /* Edited: Ferjolt Ozuni - Date: 2026-08-01
|
|
63
76
|
Mark and name together, ranged right. The name used to sit
|
|
@@ -62,6 +62,28 @@ export const Title = styled.h2`
|
|
|
62
62
|
text-transform: uppercase;
|
|
63
63
|
`;
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* The route code and the operator's own name for the route, under the title.
|
|
67
|
+
*
|
|
68
|
+
* Claude - 2026-09-16 (audit): detail, set as detail - small and faded, with
|
|
69
|
+
* only the code in the ordinary ink so it can be read back against a ticket.
|
|
70
|
+
* `anywhere`, because an operator's route name lists every city it calls at
|
|
71
|
+
* and the checkout sidebar is a narrow column.
|
|
72
|
+
*/
|
|
73
|
+
export const Line = styled.div`
|
|
74
|
+
margin-top: 2px;
|
|
75
|
+
font-size: ${ props => props.theme.size.xs };
|
|
76
|
+
color: ${ props => props.theme.font.faded };
|
|
77
|
+
overflow-wrap: anywhere;
|
|
78
|
+
`;
|
|
79
|
+
|
|
80
|
+
export const Code = styled.span`
|
|
81
|
+
margin-right: 6px;
|
|
82
|
+
font-weight: 700;
|
|
83
|
+
letter-spacing: .3px;
|
|
84
|
+
color: ${ props => props.theme.font.normal };
|
|
85
|
+
`;
|
|
86
|
+
|
|
65
87
|
export const SubTitle = styled.div`
|
|
66
88
|
display: inline-block;
|
|
67
89
|
padding: 4px 10px;
|
package/package.json
CHANGED
package/styles.ts
CHANGED
|
@@ -4,6 +4,12 @@ export const Title = styled.h1`
|
|
|
4
4
|
display: flex;
|
|
5
5
|
align-items: center;
|
|
6
6
|
gap: 10px;
|
|
7
|
+
/*
|
|
8
|
+
* Claude - 2026-09-16: pinned to the h1 scale, because Step1 now renders
|
|
9
|
+
* this AS an h2 on the pair page (see its heading prop) - the level is a
|
|
10
|
+
* document-outline decision, not a request for a smaller heading.
|
|
11
|
+
*/
|
|
12
|
+
font-size: ${ props => props.theme.size.xxl };
|
|
7
13
|
`;
|
|
8
14
|
|
|
9
15
|
export const SubTitle = styled.h3`
|
|
@@ -147,4 +153,41 @@ export const Listing = styled.div`
|
|
|
147
153
|
// column wider than its share of the row
|
|
148
154
|
flex: 1;
|
|
149
155
|
min-width: 0;
|
|
156
|
+
|
|
157
|
+
/*
|
|
158
|
+
* Edited: Claude - Date: 2026-09-16
|
|
159
|
+
*
|
|
160
|
+
* Real room at the FOOT of the list, on a narrow screen, for whatever the
|
|
161
|
+
* cookie notice (packages/common/CookieNotification) is currently taking
|
|
162
|
+
* up down there - not the fixed "20px" the notice used to assume, since a
|
|
163
|
+
* fixed guess is wrong the moment the sentence wraps to a different number
|
|
164
|
+
* of lines in a longer language. --obt-cookie-notice-height is the
|
|
165
|
+
* notice's own measured height, published as a CSS custom property so
|
|
166
|
+
* this column never has to ask it directly; 0px when the notice is not
|
|
167
|
+
* showing, so a visitor who has already dismissed it loses nothing here.
|
|
168
|
+
*/
|
|
169
|
+
@media (max-width: 639px) {
|
|
170
|
+
padding-bottom: var(--obt-cookie-notice-height, 0px);
|
|
171
|
+
}
|
|
172
|
+
`;
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* A placeholder block, sized as a fraction of its cell.
|
|
176
|
+
*
|
|
177
|
+
* Edited: Claude - Date: 2026-09-16
|
|
178
|
+
*
|
|
179
|
+
* Shared by the two loading skeletons on this page - Availability/Skeleton
|
|
180
|
+
* and Found/Orders/Skeleton - so the one thing they agree on (what a
|
|
181
|
+
* "nothing has arrived yet" rectangle looks like) is defined once rather
|
|
182
|
+
* than twice. A flat fill, not the shimmering-gradient kind: the whole job
|
|
183
|
+
* of a skeleton here is to hold the page still while data arrives, and an
|
|
184
|
+
* animated one is itself something moving on the page for the second or so
|
|
185
|
+
* it is visible.
|
|
186
|
+
*/
|
|
187
|
+
export const Bone = styled.span<{ $width: number, $height?: number }>`
|
|
188
|
+
display: inline-block;
|
|
189
|
+
width: ${ props => props.$width }%;
|
|
190
|
+
height: ${ props => props.$height ?? 14 }px;
|
|
191
|
+
border-radius: 4px;
|
|
192
|
+
background: ${ props => props.theme.background.neutral };
|
|
150
193
|
`;
|
package/Found/Group/Group.tsx
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { useMemo, useState } from 'react';
|
|
2
|
-
import { TFunction } from 'i18next';
|
|
3
|
-
import Route from '../Route/Route';
|
|
4
|
-
import { departureOf } from '../refine';
|
|
5
|
-
import { Container, Title, Times, Time } from './styles';
|
|
6
|
-
import { FoundData } from '@autobusal/providers/types/routes';
|
|
7
|
-
|
|
8
|
-
interface Props {
|
|
9
|
-
items: FoundData[]
|
|
10
|
-
type: 'favorite' | 'normal'
|
|
11
|
-
date?: string
|
|
12
|
-
passengers: number
|
|
13
|
-
t: TFunction<'common'>
|
|
14
|
-
onSave: (data: FoundData) => void
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* One card for a set of interchangeable departures.
|
|
19
|
-
*
|
|
20
|
-
* Edited: Ferjolt Ozuni - Date: 2026-08-01
|
|
21
|
-
*
|
|
22
|
-
* The card shows a real departure - never an abstract summary - and the
|
|
23
|
-
* other times sit under it as a picker. Whatever is selected is what gets
|
|
24
|
-
* booked, so there is no separate "now choose a time" step to forget: the
|
|
25
|
-
* card always represents a bookable trip, starting with the earliest.
|
|
26
|
-
*/
|
|
27
|
-
const Group = ({ items, type, date, passengers, t, onSave }: Props): JSX.Element => {
|
|
28
|
-
// chronological for the picker, regardless of how the list itself is
|
|
29
|
-
// sorted - a row of times out of time order is just confusing
|
|
30
|
-
const departures = useMemo(() => (
|
|
31
|
-
[ ...items ].sort((first, second) => departureOf(first) - departureOf(second))
|
|
32
|
-
), [ items ]);
|
|
33
|
-
|
|
34
|
-
const [ selected, setSelected ] = useState<number>(0);
|
|
35
|
-
|
|
36
|
-
const current = departures[selected] ?? departures[0];
|
|
37
|
-
|
|
38
|
-
return (
|
|
39
|
-
<Route
|
|
40
|
-
type={ type }
|
|
41
|
-
data={ current }
|
|
42
|
-
date={ date }
|
|
43
|
-
passengers={ passengers }
|
|
44
|
-
t={ t }
|
|
45
|
-
onSave={ onSave }
|
|
46
|
-
>
|
|
47
|
-
<Container>
|
|
48
|
-
<Title>{ t('routes_order.step2.route.departures', { total: departures.length }) }</Title>
|
|
49
|
-
|
|
50
|
-
<Times>
|
|
51
|
-
{ departures.map((item, index) => (
|
|
52
|
-
<Time
|
|
53
|
-
key={ item.id }
|
|
54
|
-
type="button"
|
|
55
|
-
$active={ index === selected }
|
|
56
|
-
aria-pressed={ index === selected }
|
|
57
|
-
onClick={ () => setSelected(index) }
|
|
58
|
-
>
|
|
59
|
-
{ item.locations.from.departure }
|
|
60
|
-
</Time>
|
|
61
|
-
)) }
|
|
62
|
-
</Times>
|
|
63
|
-
</Container>
|
|
64
|
-
</Route>
|
|
65
|
-
);
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
export default Group;
|
package/Found/Group/styles.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import styled, { css } from 'styled-components';
|
|
2
|
-
|
|
3
|
-
export const Container = styled.div`
|
|
4
|
-
display: flex;
|
|
5
|
-
flex-direction: column;
|
|
6
|
-
gap: 8px;
|
|
7
|
-
padding: 12px;
|
|
8
|
-
background: ${ props => props.theme.background.neutral };
|
|
9
|
-
border-radius: ${ props => props.theme.borderRadius };
|
|
10
|
-
`;
|
|
11
|
-
|
|
12
|
-
export const Title = styled.h6`
|
|
13
|
-
margin-bottom: 0;
|
|
14
|
-
font-size: ${ props => props.theme.size.xs };
|
|
15
|
-
text-transform: uppercase;
|
|
16
|
-
color: ${ props => props.theme.font.faded };
|
|
17
|
-
`;
|
|
18
|
-
|
|
19
|
-
export const Times = styled.div`
|
|
20
|
-
display: flex;
|
|
21
|
-
flex-wrap: wrap;
|
|
22
|
-
gap: 6px;
|
|
23
|
-
`;
|
|
24
|
-
|
|
25
|
-
export const Time = styled.button<{ $active: boolean }>`
|
|
26
|
-
padding: 5px 12px;
|
|
27
|
-
font-size: ${ props => props.theme.size.s };
|
|
28
|
-
font-weight: 700;
|
|
29
|
-
border-radius: 100px;
|
|
30
|
-
border: 1px solid ${ props => props.theme.inputs.border };
|
|
31
|
-
background: ${ props => props.theme.background.normal };
|
|
32
|
-
color: ${ props => props.theme.font.normal };
|
|
33
|
-
transition: all 0.2s ease;
|
|
34
|
-
|
|
35
|
-
&:hover {
|
|
36
|
-
border-color: ${ props => props.theme.primary.normal };
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
${ props => props.$active && css`
|
|
40
|
-
color: ${ props => props.theme.primary.contrast };
|
|
41
|
-
background: ${ props => props.theme.primary.normal };
|
|
42
|
-
border-color: ${ props => props.theme.primary.normal };
|
|
43
|
-
` }
|
|
44
|
-
`;
|