@autobusal/routes-order 1.27.0 → 1.30.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/CHANGELOG.md +46 -0
- package/Checkout/Checkout.tsx +12 -0
- package/Facts/types.ts +7 -0
- package/Found/Group/Group.tsx +3 -1
- package/Found/Orders/Orders.tsx +12 -0
- package/Found/Route/Route.tsx +64 -2
- package/Found/Route/styles.ts +41 -0
- package/Schedule/Schedule.tsx +15 -2
- package/Schedule/styles.ts +18 -0
- package/Sections/Sections.tsx +10 -20
- package/Step4/Passenger/Passenger.tsx +9 -1
- package/Step4/Passenger/styles.ts +21 -1
- package/Step5/Addons/Addons.tsx +54 -3
- package/package.json +1 -1
- package/services.ts +8 -0
- package/types.ts +9 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,51 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.30.0
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **The SMS booking-confirmation add-on at checkout**, with its own number
|
|
8
|
+
field prefilled from the passenger phone already collected — somebody
|
|
9
|
+
booking for a relative wants the text to reach the traveller. The
|
|
10
|
+
description says the free route alerts are not what is being sold here.
|
|
11
|
+
- **A line under the passenger phone field** saying what the number is used
|
|
12
|
+
for: reaching you about this trip, and service messages if the departure
|
|
13
|
+
changes — never marketing. The number was always collected so a driver
|
|
14
|
+
could ring it; texting it is a different use of the same data, and saying
|
|
15
|
+
so at the point of collection is what makes it consent rather than an
|
|
16
|
+
assumption.
|
|
17
|
+
## 1.29.0
|
|
18
|
+
|
|
19
|
+
### Removed
|
|
20
|
+
|
|
21
|
+
- **The reviews block on the route-pair page.** A review wall sat in the
|
|
22
|
+
middle of a page whose job is to compare departures. What a reader needs
|
|
23
|
+
there about reputation is one number per operator, which the result cards
|
|
24
|
+
already carry; the reviews themselves belong on the operator's own page,
|
|
25
|
+
where somebody who wants to read them has gone looking for them. The `pair`
|
|
26
|
+
mode of the reviews API is untouched — nothing calls it from here now.
|
|
27
|
+
- The "Reviews" entry in the in-page nav, with it.
|
|
28
|
+
|
|
29
|
+
## 1.28.0
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
- **Overnight trips now say so.** A result card that arrives on a later day
|
|
34
|
+
prints the calendar day under BOTH ends and a `+1` against the arrival
|
|
35
|
+
time; the pair-page timetable carries the same marker. "22:30 → 06:00" is
|
|
36
|
+
the one thing on a result card that can be read as the exact opposite of
|
|
37
|
+
what it means, and the duration beside it does not fix that because nobody
|
|
38
|
+
reads a duration to work out a date.
|
|
39
|
+
- Nothing is added to a same-day card. A date under every arrival would make
|
|
40
|
+
the overnight ones stop standing out, which is the only reason this exists.
|
|
41
|
+
|
|
42
|
+
### Changed
|
|
43
|
+
|
|
44
|
+
- The day comes from the timetable's own midnight markers, computed
|
|
45
|
+
server-side, never from "the arrival looks earlier than the departure" —
|
|
46
|
+
which is wrong for exactly the cross-border journeys whose two ends keep
|
|
47
|
+
different clocks.
|
|
48
|
+
|
|
3
49
|
## 1.27.0
|
|
4
50
|
|
|
5
51
|
**`reloadDocument` is gone — the results follow the URL.**
|
package/Checkout/Checkout.tsx
CHANGED
|
@@ -77,6 +77,8 @@ const Checkout = ({ values, step1, step2, step3, isPartner, t, onStore, onBack }
|
|
|
77
77
|
const [ addonWhatsapp, setAddonWhatsapp ] = useState<boolean>(false);
|
|
78
78
|
const [ addonWhatsappNumber, setAddonWhatsappNumber ] = useState<string>('');
|
|
79
79
|
const [ addonTelegram, setAddonTelegram ] = useState<boolean>(false);
|
|
80
|
+
const [ addonSms, setAddonSms ] = useState<boolean>(false);
|
|
81
|
+
const [ addonSmsNumber, setAddonSmsNumber ] = useState<string>('');
|
|
80
82
|
const [ addonFlex, setAddonFlex ] = useState<boolean>(false);
|
|
81
83
|
|
|
82
84
|
const { data: UserData } = useUserStore();
|
|
@@ -118,6 +120,10 @@ const Checkout = ({ values, step1, step2, step3, isPartner, t, onStore, onBack }
|
|
|
118
120
|
label: t('routes_order.step5.addons.telegram.label'),
|
|
119
121
|
price: SettingsData.addons.telegram.price
|
|
120
122
|
} : null,
|
|
123
|
+
addonSms && SettingsData?.addons?.sms?.available ? {
|
|
124
|
+
label: t('routes_order.step5.addons.sms.label'),
|
|
125
|
+
price: SettingsData.addons.sms.price
|
|
126
|
+
} : null,
|
|
121
127
|
addonFlex && FlexData?.available && FlexData.price !== undefined ? {
|
|
122
128
|
label: t('routes_order.step5.flex.title'),
|
|
123
129
|
price: FlexData.price
|
|
@@ -150,6 +156,8 @@ const Checkout = ({ values, step1, step2, step3, isPartner, t, onStore, onBack }
|
|
|
150
156
|
whatsapp: addonWhatsapp,
|
|
151
157
|
whatsappNumber: addonWhatsappNumber,
|
|
152
158
|
telegram: addonTelegram,
|
|
159
|
+
sms: addonSms,
|
|
160
|
+
smsNumber: addonSmsNumber,
|
|
153
161
|
flex: addonFlex
|
|
154
162
|
});
|
|
155
163
|
|
|
@@ -271,11 +279,15 @@ const Checkout = ({ values, step1, step2, step3, isPartner, t, onStore, onBack }
|
|
|
271
279
|
whatsapp={ addonWhatsapp }
|
|
272
280
|
whatsappNumber={ addonWhatsappNumber }
|
|
273
281
|
telegram={ addonTelegram }
|
|
282
|
+
sms={ addonSms }
|
|
283
|
+
smsNumber={ addonSmsNumber }
|
|
274
284
|
flex={ addonFlex }
|
|
275
285
|
t={ t }
|
|
276
286
|
onChangeWhatsapp={ setAddonWhatsapp }
|
|
277
287
|
onChangeWhatsappNumber={ setAddonWhatsappNumber }
|
|
278
288
|
onChangeTelegram={ setAddonTelegram }
|
|
289
|
+
onChangeSms={ setAddonSms }
|
|
290
|
+
onChangeSmsNumber={ setAddonSmsNumber }
|
|
279
291
|
onChangeFlex={ (value) => {
|
|
280
292
|
setAddonFlex(value);
|
|
281
293
|
|
package/Facts/types.ts
CHANGED
|
@@ -37,6 +37,13 @@ export interface ScheduleRow {
|
|
|
37
37
|
rating: { average: number, count: number } | null
|
|
38
38
|
departure: string | null
|
|
39
39
|
arrival: string | null
|
|
40
|
+
/**
|
|
41
|
+
* Edited: Ferjolt Ozuni - Date: 2026-08-05
|
|
42
|
+
* Days between boarding and arriving - 0 same day, 1 the next morning.
|
|
43
|
+
* A timetable of two clock times per row cannot show an overnight service
|
|
44
|
+
* without it.
|
|
45
|
+
*/
|
|
46
|
+
offset?: number
|
|
40
47
|
minutes: number | null
|
|
41
48
|
// 0 is a direct service
|
|
42
49
|
stops: number
|
package/Found/Group/Group.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import { FoundData } from '@autobusal/providers/types/routes';
|
|
|
8
8
|
interface Props {
|
|
9
9
|
items: FoundData[]
|
|
10
10
|
type: 'favorite' | 'normal'
|
|
11
|
+
date?: string
|
|
11
12
|
passengers: number
|
|
12
13
|
t: TFunction<'common'>
|
|
13
14
|
onSave: (data: FoundData) => void
|
|
@@ -23,7 +24,7 @@ interface Props {
|
|
|
23
24
|
* booked, so there is no separate "now choose a time" step to forget: the
|
|
24
25
|
* card always represents a bookable trip, starting with the earliest.
|
|
25
26
|
*/
|
|
26
|
-
const Group = ({ items, type, passengers, t, onSave }: Props): JSX.Element => {
|
|
27
|
+
const Group = ({ items, type, date, passengers, t, onSave }: Props): JSX.Element => {
|
|
27
28
|
// chronological for the picker, regardless of how the list itself is
|
|
28
29
|
// sorted - a row of times out of time order is just confusing
|
|
29
30
|
const departures = useMemo(() => (
|
|
@@ -38,6 +39,7 @@ const Group = ({ items, type, passengers, t, onSave }: Props): JSX.Element => {
|
|
|
38
39
|
<Route
|
|
39
40
|
type={ type }
|
|
40
41
|
data={ current }
|
|
42
|
+
date={ date }
|
|
41
43
|
passengers={ passengers }
|
|
42
44
|
t={ t }
|
|
43
45
|
onSave={ onSave }
|
package/Found/Orders/Orders.tsx
CHANGED
|
@@ -71,6 +71,16 @@ const Orders = ({ loading, data, empty, refined, sort, type, passengers, preferr
|
|
|
71
71
|
* Grouping runs AFTER sorting: groupItems keeps input order, so a
|
|
72
72
|
* collapsed set of shuttles lands wherever its best member sorted to.
|
|
73
73
|
*/
|
|
74
|
+
/**
|
|
75
|
+
* Edited: Ferjolt Ozuni - Date: 2026-08-05
|
|
76
|
+
*
|
|
77
|
+
* The day these results board on, for the cards that arrive on a later
|
|
78
|
+
* one. It is the date that was SEARCHED - the same value the strip above
|
|
79
|
+
* highlights - rather than anything the server sends back, so the two can
|
|
80
|
+
* never disagree about which day a card is for.
|
|
81
|
+
*/
|
|
82
|
+
const date = step1[type];
|
|
83
|
+
|
|
74
84
|
const render = (list: FoundData[], kind: 'favorite' | 'normal'): JSX.Element[] => (
|
|
75
85
|
groupItems(sortItems(list, sort)).map(group => (
|
|
76
86
|
group.length > 1
|
|
@@ -79,6 +89,7 @@ const Orders = ({ loading, data, empty, refined, sort, type, passengers, preferr
|
|
|
79
89
|
key={ group[0].id }
|
|
80
90
|
items={ group }
|
|
81
91
|
type={ kind }
|
|
92
|
+
date={ date }
|
|
82
93
|
passengers={ passengers }
|
|
83
94
|
t={ t }
|
|
84
95
|
onSave={ onSave }
|
|
@@ -89,6 +100,7 @@ const Orders = ({ loading, data, empty, refined, sort, type, passengers, preferr
|
|
|
89
100
|
key={ group[0].id }
|
|
90
101
|
type={ kind }
|
|
91
102
|
data={ group[0] }
|
|
103
|
+
date={ date }
|
|
92
104
|
passengers={ passengers }
|
|
93
105
|
t={ t }
|
|
94
106
|
onSave={ onSave }
|
package/Found/Route/Route.tsx
CHANGED
|
@@ -6,7 +6,8 @@ import { RiBus2Line } from 'react-icons/ri';
|
|
|
6
6
|
import { MdOutlineAltRoute } from 'react-icons/md';
|
|
7
7
|
import { formatDuration, splitPrice } from '../refine';
|
|
8
8
|
import { Button, RouteFeature, Information, Rating } from '@autobusal/common';
|
|
9
|
-
import {
|
|
9
|
+
import { createDate, getDayParts } from '@autobusal/utilities';
|
|
10
|
+
import { Container, ButtonDetails, SpecialOffer, FavoriteStop, Trip, Company, Location, Stop, Iso, Time, Price, Amount, Decimals, Currency, PriceNotice, LinkCompany, CodeCompany, Details, Detail, ButtonPolicy, TitleDetail, FeaturesItems, Leaves, Day, NextDay } from './styles';
|
|
10
11
|
import { FoundData } from '@autobusal/providers/types/routes';
|
|
11
12
|
|
|
12
13
|
// neutral placeholder so a missing/broken operator logo doesn't render the
|
|
@@ -16,6 +17,11 @@ const LOGO_FALLBACK = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/s
|
|
|
16
17
|
interface Props {
|
|
17
18
|
data: FoundData
|
|
18
19
|
type: 'favorite' | 'normal'
|
|
20
|
+
// Edited: Ferjolt Ozuni - Date: 2026-08-05
|
|
21
|
+
// The searched boarding day, dd/mm/yyyy. Only used to print the two dates
|
|
22
|
+
// on an overnight trip - a card that arrives the same day says nothing
|
|
23
|
+
// about days at all, which is why this can be absent.
|
|
24
|
+
date?: string
|
|
19
25
|
passengers: number
|
|
20
26
|
t: TFunction<'common'>
|
|
21
27
|
onSave: (data: FoundData) => void
|
|
@@ -25,7 +31,7 @@ interface Props {
|
|
|
25
31
|
children?: React.ReactNode
|
|
26
32
|
}
|
|
27
33
|
|
|
28
|
-
const Route = ({ data, type, passengers, t, onSave, children }: Props): JSX.Element => {
|
|
34
|
+
const Route = ({ data, type, date, passengers, t, onSave, children }: Props): JSX.Element => {
|
|
29
35
|
const [ information, setInformation ] = useState<boolean>(false);
|
|
30
36
|
|
|
31
37
|
// Edited: Ferjolt Ozuni - Date: 2026-08-03
|
|
@@ -63,6 +69,53 @@ const Route = ({ data, type, passengers, t, onSave, children }: Props): JSX.Elem
|
|
|
63
69
|
|
|
64
70
|
const soon = leaves !== null && leaves < 60;
|
|
65
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Edited: Ferjolt Ozuni - Date: 2026-08-05
|
|
74
|
+
*
|
|
75
|
+
* Overnight trips. "22:30 -> 06:00" is the one thing on this card that
|
|
76
|
+
* can be read as the exact opposite of what it means - a seven-and-a-half
|
|
77
|
+
* hour journey looks like a sixteen-hour one run backwards - and the
|
|
78
|
+
* duration beside it does not fix that, because nobody reads a duration
|
|
79
|
+
* to work out a date. So when the coach arrives on a later day, BOTH ends
|
|
80
|
+
* print their day and the arrival carries a +1.
|
|
81
|
+
*
|
|
82
|
+
* Nothing is added to a same-day card: a date under every arrival on a
|
|
83
|
+
* page of same-day results is noise that makes the overnight ones stop
|
|
84
|
+
* standing out, which is the only reason any of this is here.
|
|
85
|
+
*
|
|
86
|
+
* The offset comes from the timetable's own midnight markers, computed
|
|
87
|
+
* server-side (Libraries\Routes\Search\Locations::offset) - it is not
|
|
88
|
+
* inferred from "arrival looks earlier than departure", which is wrong
|
|
89
|
+
* for exactly the cross-border journeys where the two ends keep
|
|
90
|
+
* different clocks.
|
|
91
|
+
*/
|
|
92
|
+
const offset = data.arrival_offset ?? 0;
|
|
93
|
+
|
|
94
|
+
const days = ((): { departure: string, arrival: string } | null => {
|
|
95
|
+
if (offset <= 0 || !date) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const departs = createDate(date);
|
|
100
|
+
|
|
101
|
+
// an invalid or unexpected date string must not print "NaN Invalid" on
|
|
102
|
+
// a result card - showing no day is better than showing a wrong one
|
|
103
|
+
if (Number.isNaN(departs.getTime())) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const arrives = new Date(departs);
|
|
108
|
+
arrives.setUTCDate(arrives.getUTCDate() + offset);
|
|
109
|
+
|
|
110
|
+
const format = (value: Date): string => {
|
|
111
|
+
const parts = getDayParts(value, t as any);
|
|
112
|
+
|
|
113
|
+
return `${ parts.day } ${ parts.month }`;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
return { departure: format(departs), arrival: format(arrives) };
|
|
117
|
+
})();
|
|
118
|
+
|
|
66
119
|
return (
|
|
67
120
|
<Container className="box" $type={ type }>
|
|
68
121
|
{ data.price.offer === true && <SpecialOffer>{ t('routes_order.step2.route.offer') }</SpecialOffer> }
|
|
@@ -116,6 +169,8 @@ const Route = ({ data, type, passengers, t, onSave, children }: Props): JSX.Elem
|
|
|
116
169
|
{ data.locations.from.departure }
|
|
117
170
|
</h2>
|
|
118
171
|
|
|
172
|
+
{ days && <Day>{ days.departure }</Day> }
|
|
173
|
+
|
|
119
174
|
<Stop>{ data.locations.from.stop?.name }</Stop>
|
|
120
175
|
</Location>
|
|
121
176
|
|
|
@@ -144,8 +199,15 @@ const Route = ({ data, type, passengers, t, onSave, children }: Props): JSX.Elem
|
|
|
144
199
|
<Iso>({ data.locations.to.city.country?.iso_code })</Iso>
|
|
145
200
|
{ ' ' }
|
|
146
201
|
{ data.locations.to.arrival }
|
|
202
|
+
{ offset > 0 && (
|
|
203
|
+
<NextDay title={ t('routes_order.step2.route.next_day', { count: offset }) }>
|
|
204
|
+
+{ offset }
|
|
205
|
+
</NextDay>
|
|
206
|
+
) }
|
|
147
207
|
</h2>
|
|
148
208
|
|
|
209
|
+
{ days && <Day>{ days.arrival }</Day> }
|
|
210
|
+
|
|
149
211
|
<Stop>{ data.locations.to.stop?.name }</Stop>
|
|
150
212
|
</Location>
|
|
151
213
|
|
package/Found/Route/styles.ts
CHANGED
|
@@ -609,3 +609,44 @@ export const Leaves = styled.div<{ $soon: boolean }>`
|
|
|
609
609
|
background: ${ props => (props.$soon ? props.theme.font.error : props.theme.primary.neutral) };
|
|
610
610
|
`;
|
|
611
611
|
|
|
612
|
+
/**
|
|
613
|
+
* The +1 on an overnight arrival.
|
|
614
|
+
*
|
|
615
|
+
* Edited: Ferjolt Ozuni - Date: 2026-08-05
|
|
616
|
+
*
|
|
617
|
+
* Sits INSIDE the h2, right against the clock time it qualifies, because
|
|
618
|
+
* that is the number it changes the meaning of. Raised and shrunk rather
|
|
619
|
+
* than boxed: it reads as an annotation on the time, the way a footnote
|
|
620
|
+
* marker does, and a second pill on a card that already carries an offer
|
|
621
|
+
* badge and a countdown would compete with both.
|
|
622
|
+
*
|
|
623
|
+
* The title attribute carries the sentence - "arrives the next day" - so
|
|
624
|
+
* the meaning is available to anyone for whom "+1" is not self-evident,
|
|
625
|
+
* which on a first visit is most people.
|
|
626
|
+
*/
|
|
627
|
+
export const NextDay = styled.sup`
|
|
628
|
+
margin-left: 4px;
|
|
629
|
+
padding: 1px 4px;
|
|
630
|
+
border-radius: 4px;
|
|
631
|
+
font-size: ${ props => props.theme.size.xs };
|
|
632
|
+
font-weight: 700;
|
|
633
|
+
white-space: nowrap;
|
|
634
|
+
vertical-align: super;
|
|
635
|
+
cursor: help;
|
|
636
|
+
color: ${ props => props.theme.font.normal };
|
|
637
|
+
background: ${ props => props.theme.primary.neutral };
|
|
638
|
+
`;
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* The calendar day under one end of an overnight trip.
|
|
642
|
+
*
|
|
643
|
+
* Rendered on BOTH ends or neither: a date shown only over the arrival
|
|
644
|
+
* leaves the reader working out what day the departure was, which is the
|
|
645
|
+
* question the whole thing exists to answer.
|
|
646
|
+
*/
|
|
647
|
+
export const Day = styled.span`
|
|
648
|
+
font-size: ${ props => props.theme.size.xs };
|
|
649
|
+
font-weight: 700;
|
|
650
|
+
white-space: nowrap;
|
|
651
|
+
color: ${ props => props.theme.primary.normal };
|
|
652
|
+
`;
|
package/Schedule/Schedule.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TFunction } from 'i18next';
|
|
2
2
|
import { Rating } from '@autobusal/common';
|
|
3
|
-
import { Container, Table, Time, Faded, Price, Operator } from './styles';
|
|
3
|
+
import { Container, Table, Time, Faded, Price, Operator, NextDay } from './styles';
|
|
4
4
|
import { ScheduleRow } from '../Facts/types';
|
|
5
5
|
|
|
6
6
|
interface Props {
|
|
@@ -69,7 +69,20 @@ const Schedule = ({ id, from, to, rows, t }: Props): (JSX.Element | null) => {
|
|
|
69
69
|
{ rows.map((row, index) => (
|
|
70
70
|
<tr key={ index }>
|
|
71
71
|
<td><Time>{ row.departure ?? '-' }</Time></td>
|
|
72
|
-
|
|
72
|
+
{ /* Edited: Ferjolt Ozuni - Date: 2026-08-05
|
|
73
|
+
The same +1 the result cards carry. A timetable is read
|
|
74
|
+
precisely to plan around a time, so a row whose arrival
|
|
75
|
+
is the following morning and does not say so is the one
|
|
76
|
+
row on the page somebody will actually act on wrongly. */ }
|
|
77
|
+
<td>
|
|
78
|
+
<Time>{ row.arrival ?? '-' }</Time>
|
|
79
|
+
|
|
80
|
+
{ (row.offset ?? 0) > 0 && (
|
|
81
|
+
<NextDay title={ t('routes_order.step2.route.next_day', { count: row.offset }) }>
|
|
82
|
+
+{ row.offset }
|
|
83
|
+
</NextDay>
|
|
84
|
+
) }
|
|
85
|
+
</td>
|
|
73
86
|
<td><Faded>{ duration(row.minutes) }</Faded></td>
|
|
74
87
|
|
|
75
88
|
<td>
|
package/Schedule/styles.ts
CHANGED
|
@@ -51,3 +51,21 @@ export const Operator = styled.div`
|
|
|
51
51
|
flex-direction: column;
|
|
52
52
|
gap: 2px;
|
|
53
53
|
`;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The +1 on an overnight arrival, matching the result cards.
|
|
57
|
+
*
|
|
58
|
+
* Edited: Ferjolt Ozuni - Date: 2026-08-05
|
|
59
|
+
*/
|
|
60
|
+
export const NextDay = styled.sup`
|
|
61
|
+
margin-left: 4px;
|
|
62
|
+
padding: 1px 4px;
|
|
63
|
+
border-radius: 4px;
|
|
64
|
+
font-size: ${ props => props.theme.size.xs };
|
|
65
|
+
font-weight: 700;
|
|
66
|
+
white-space: nowrap;
|
|
67
|
+
vertical-align: super;
|
|
68
|
+
cursor: help;
|
|
69
|
+
color: ${ props => props.theme.font.normal };
|
|
70
|
+
background: ${ props => props.theme.primary.neutral };
|
|
71
|
+
`;
|
package/Sections/Sections.tsx
CHANGED
|
@@ -2,7 +2,6 @@ import { TFunction } from 'i18next';
|
|
|
2
2
|
import Facts from '../Facts/Facts';
|
|
3
3
|
import Schedule from '../Schedule/Schedule';
|
|
4
4
|
import Links from '../Links/Links';
|
|
5
|
-
import { Reviews } from '@autobusal/reviews';
|
|
6
5
|
import Schema from './Schema';
|
|
7
6
|
import { useGetFacts } from '../Facts/services';
|
|
8
7
|
import { Nav, Anchor, Anchored } from './styles';
|
|
@@ -17,7 +16,6 @@ const TRIPS = 'trips';
|
|
|
17
16
|
const FACTS = 'facts';
|
|
18
17
|
const SCHEDULE = 'schedule';
|
|
19
18
|
const LINKS = 'links';
|
|
20
|
-
const REVIEWS = 'reviews';
|
|
21
19
|
|
|
22
20
|
/**
|
|
23
21
|
* Everything on a route-pair page that is not the booking wizard.
|
|
@@ -65,8 +63,6 @@ const Sections = ({ from, to, t }: Props): (JSX.Element | null) => {
|
|
|
65
63
|
{ schedule.length > 0 && (
|
|
66
64
|
<Anchor href={ `#${ SCHEDULE }` }>{ t('routes_order.sections.schedule') }</Anchor>
|
|
67
65
|
) }
|
|
68
|
-
|
|
69
|
-
<Anchor href={ `#${ REVIEWS }` }>{ t('routes_order.sections.reviews') }</Anchor>
|
|
70
66
|
</Nav>
|
|
71
67
|
|
|
72
68
|
{ /* Edited: Ferjolt Ozuni - Date: 2026-08-03
|
|
@@ -82,22 +78,16 @@ const Sections = ({ from, to, t }: Props): (JSX.Element | null) => {
|
|
|
82
78
|
<Schedule id={ SCHEDULE } from={ facts.from } to={ facts.to } rows={ schedule } t={ t } />
|
|
83
79
|
</Anchored>
|
|
84
80
|
|
|
85
|
-
{ /* Edited: Ferjolt Ozuni - Date: 2026-08-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
{ /* the anchor lives on a wrapper: Reviews takes the SUBJECT's id,
|
|
96
|
-
which a pair does not have - it is named by its two ends */ }
|
|
97
|
-
<div id={ REVIEWS }>
|
|
98
|
-
<Reviews type="pair" id={ 0 } pair={ { from: from ?? '', to: to ?? '' } } t={ t } />
|
|
99
|
-
</div>
|
|
100
|
-
</Anchored>
|
|
81
|
+
{ /* Edited: Ferjolt Ozuni - Date: 2026-08-05
|
|
82
|
+
NO reviews block here any more. A pair-level review wall sat in
|
|
83
|
+
the middle of a page whose job is to compare departures, where
|
|
84
|
+
what a reader needs about reputation is one number per operator -
|
|
85
|
+
and the result cards already carry exactly that.
|
|
86
|
+
|
|
87
|
+
The reviews themselves live on the operator's own page, which is
|
|
88
|
+
where somebody who wants to read them has gone looking for them.
|
|
89
|
+
The `pair` mode of Reviews\BrowseController is untouched and
|
|
90
|
+
still works; nothing calls it from here. */ }
|
|
101
91
|
|
|
102
92
|
<Links
|
|
103
93
|
id={ LINKS }
|
|
@@ -2,7 +2,7 @@ import { TFunction } from 'i18next';
|
|
|
2
2
|
import { FieldErrors, FieldValues, UseFormRegister } from 'react-hook-form';
|
|
3
3
|
import { Calendar, Gender, ChooseSeat, Required } from '@autobusal/common';
|
|
4
4
|
import { Validate, Display } from '@autobusal/utilities';
|
|
5
|
-
import { Container, Title } from './styles';
|
|
5
|
+
import { Container, Title, Notice } from './styles';
|
|
6
6
|
import { PersonData } from '@autobusal/providers/types/persons';
|
|
7
7
|
import { OccupiedData } from '@autobusal/providers/types/buses';
|
|
8
8
|
|
|
@@ -139,6 +139,14 @@ const Passenger = ({ type, number, values, fields, withReturn, busFrom, busTo, p
|
|
|
139
139
|
/>
|
|
140
140
|
|
|
141
141
|
{ Display(errors[names.phone]) }
|
|
142
|
+
|
|
143
|
+
{ /* Edited: Ferjolt Ozuni - Date: 2026-08-05
|
|
144
|
+
Said because the use changed. This number was collected
|
|
145
|
+
for the operator to ring; it is now also where a service
|
|
146
|
+
SMS goes if the departure moves. Service messages about
|
|
147
|
+
this booking only - never marketing, which is what makes
|
|
148
|
+
the sentence true. */ }
|
|
149
|
+
<Notice>{ t('routes_order.step4.phone_notice') }</Notice>
|
|
142
150
|
</div>
|
|
143
151
|
) }
|
|
144
152
|
</div>
|
|
@@ -7,4 +7,24 @@ export const Container = styled.div`
|
|
|
7
7
|
export const Title = styled.h3`
|
|
8
8
|
padding-bottom: 16px;
|
|
9
9
|
border-bottom: 1px solid ${ props => props.theme.background.neutral };
|
|
10
|
-
`;
|
|
10
|
+
`;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* What the phone number will be used for.
|
|
14
|
+
*
|
|
15
|
+
* Edited: Ferjolt Ozuni - Date: 2026-08-05
|
|
16
|
+
*
|
|
17
|
+
* The number has always been collected so the operator could ring it. It is
|
|
18
|
+
* now also the number a service SMS goes to when a departure changes, and
|
|
19
|
+
* that is a different use of the same data - so it is said here, at the
|
|
20
|
+
* point of collection, rather than assumed. Quiet, but not hidden: a notice
|
|
21
|
+
* nobody can read is not consent.
|
|
22
|
+
*/
|
|
23
|
+
export const Notice = styled.small`
|
|
24
|
+
display: block;
|
|
25
|
+
margin-top: 4px;
|
|
26
|
+
font-size: ${ props => props.theme.size.xxs };
|
|
27
|
+
font-weight: 400;
|
|
28
|
+
line-height: 1.4;
|
|
29
|
+
color: ${ props => props.theme.font.faded };
|
|
30
|
+
`;
|
package/Step5/Addons/Addons.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TFunction } from 'i18next';
|
|
2
2
|
import { useState } from 'react';
|
|
3
|
-
import { RiWhatsappLine, RiTelegramLine, RiCalendarCheckLine, RiArrowUpSLine, RiArrowDownSLine } from 'react-icons/ri';
|
|
3
|
+
import { RiWhatsappLine, RiTelegramLine, RiMessage2Line, RiCalendarCheckLine, RiArrowUpSLine, RiArrowDownSLine } from 'react-icons/ri';
|
|
4
4
|
import { FlexOffer } from '@autobusal/providers/types/routes';
|
|
5
5
|
import { SubTitle } from '../../styles';
|
|
6
6
|
import { Container, Option, Notice, Terms, Term, FlexHeader, FlexTitle, FlexToggle, FlexPrice, FlexAdd, FlexDetails } from './styles';
|
|
@@ -8,6 +8,14 @@ import { Container, Option, Notice, Terms, Term, FlexHeader, FlexTitle, FlexTogg
|
|
|
8
8
|
interface AddonsData {
|
|
9
9
|
whatsapp: { available: boolean, price: number }
|
|
10
10
|
telegram: { available: boolean, price: number }
|
|
11
|
+
/**
|
|
12
|
+
* Edited: Ferjolt Ozuni - Date: 2026-08-05
|
|
13
|
+
* The paid per-order CONFIRMATION by SMS. Route alerts over SMS are free
|
|
14
|
+
* and reach every passenger who gave a phone number - this is only the
|
|
15
|
+
* text that confirms the booking. Optional so an older settings payload
|
|
16
|
+
* does not crash the checkout.
|
|
17
|
+
*/
|
|
18
|
+
sms?: { available: boolean, price: number }
|
|
11
19
|
}
|
|
12
20
|
|
|
13
21
|
interface Props {
|
|
@@ -17,11 +25,15 @@ interface Props {
|
|
|
17
25
|
whatsapp: boolean
|
|
18
26
|
whatsappNumber: string
|
|
19
27
|
telegram: boolean
|
|
28
|
+
sms: boolean
|
|
29
|
+
smsNumber: string
|
|
20
30
|
flex: boolean
|
|
21
31
|
t: TFunction<'common'>
|
|
22
32
|
onChangeWhatsapp: (value: boolean) => void
|
|
23
33
|
onChangeWhatsappNumber: (value: string) => void
|
|
24
34
|
onChangeTelegram: (value: boolean) => void
|
|
35
|
+
onChangeSms: (value: boolean) => void
|
|
36
|
+
onChangeSmsNumber: (value: string) => void
|
|
25
37
|
onChangeFlex: (value: boolean) => void
|
|
26
38
|
}
|
|
27
39
|
|
|
@@ -30,7 +42,7 @@ interface Props {
|
|
|
30
42
|
// enabled (an empty box would be confusing). The WhatsApp addon collects
|
|
31
43
|
// the number here; Telegram is a "connect after purchase" flow (a bot
|
|
32
44
|
// can't message a bare phone number cold), so it's just a checkbox.
|
|
33
|
-
const Addons = ({ data, flexOffer, currency, whatsapp, whatsappNumber, telegram, flex, t, onChangeWhatsapp, onChangeWhatsappNumber, onChangeTelegram, onChangeFlex }: Props): (JSX.Element | null) => {
|
|
45
|
+
const Addons = ({ data, flexOffer, currency, whatsapp, whatsappNumber, telegram, sms, smsNumber, flex, t, onChangeWhatsapp, onChangeWhatsappNumber, onChangeTelegram, onChangeSms, onChangeSmsNumber, onChangeFlex }: Props): (JSX.Element | null) => {
|
|
34
46
|
// Edited: Ferjolt Ozuni - Date: 2026-08-03
|
|
35
47
|
// Flexible Ticket is only on offer where the operators behind the chosen
|
|
36
48
|
// routes have agreed to honour it, which is a per-booking answer the
|
|
@@ -45,7 +57,9 @@ const Addons = ({ data, flexOffer, currency, whatsapp, whatsappNumber, telegram,
|
|
|
45
57
|
// check before making it.
|
|
46
58
|
const [ open, setOpen ] = useState<boolean>(false);
|
|
47
59
|
|
|
48
|
-
|
|
60
|
+
const hasSms = data?.sms?.available === true;
|
|
61
|
+
|
|
62
|
+
if (!data || (!data.whatsapp.available && !data.telegram.available && !hasSms && !hasFlex)) {
|
|
49
63
|
return null;
|
|
50
64
|
}
|
|
51
65
|
|
|
@@ -100,6 +114,43 @@ const Addons = ({ data, flexOffer, currency, whatsapp, whatsappNumber, telegram,
|
|
|
100
114
|
</Option>
|
|
101
115
|
) }
|
|
102
116
|
|
|
117
|
+
{ /* Edited: Ferjolt Ozuni - Date: 2026-08-05
|
|
118
|
+
The CONFIRMATION text, not route alerts - those are free and go to
|
|
119
|
+
every passenger who left a phone number, whether or not anything
|
|
120
|
+
was bought here. The description says so, because "SMS
|
|
121
|
+
notifications" as a paid line would otherwise read as buying the
|
|
122
|
+
delay warnings too.
|
|
123
|
+
|
|
124
|
+
Prefilled from the passenger phone the form above already
|
|
125
|
+
collected, and still editable: somebody booking for a relative
|
|
126
|
+
wants the text to reach the traveller. */ }
|
|
127
|
+
{ hasSms && (
|
|
128
|
+
<Option>
|
|
129
|
+
<label>
|
|
130
|
+
<input
|
|
131
|
+
type="checkbox"
|
|
132
|
+
checked={ sms }
|
|
133
|
+
onChange={ (event) => onChangeSms(event.target.checked) }
|
|
134
|
+
/>
|
|
135
|
+
|
|
136
|
+
<RiMessage2Line />
|
|
137
|
+
|
|
138
|
+
{ t('routes_order.step5.addons.sms.label') } (+{ data.sms?.price } { currency })
|
|
139
|
+
</label>
|
|
140
|
+
|
|
141
|
+
<Notice>{ t('routes_order.step5.addons.sms.description') }</Notice>
|
|
142
|
+
|
|
143
|
+
{ sms && (
|
|
144
|
+
<input
|
|
145
|
+
type="text"
|
|
146
|
+
placeholder={ t('routes_order.step5.addons.sms.phone_placeholder') }
|
|
147
|
+
value={ smsNumber }
|
|
148
|
+
onChange={ (event) => onChangeSmsNumber(event.target.value) }
|
|
149
|
+
/>
|
|
150
|
+
) }
|
|
151
|
+
</Option>
|
|
152
|
+
) }
|
|
153
|
+
|
|
103
154
|
{ hasFlex && (
|
|
104
155
|
<Option>
|
|
105
156
|
<FlexHeader>
|
package/package.json
CHANGED
package/services.ts
CHANGED
|
@@ -156,6 +156,14 @@ export const usePostOrder = (
|
|
|
156
156
|
order.addon_telegram = '1';
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
// The number may be blank here: obtapi falls back to the first
|
|
160
|
+
// passenger phone the form already collected (Orders\Make\Addons),
|
|
161
|
+
// so ticking the box on a route that asks for a phone is enough.
|
|
162
|
+
if (addons.sms) {
|
|
163
|
+
order.addon_sms = '1';
|
|
164
|
+
order.addon_sms_number = addons.smsNumber;
|
|
165
|
+
}
|
|
166
|
+
|
|
159
167
|
if (addons.flex) {
|
|
160
168
|
order.addon_flex = '1';
|
|
161
169
|
}
|
package/types.ts
CHANGED
|
@@ -72,6 +72,15 @@ export interface AddonsSelection {
|
|
|
72
72
|
whatsappNumber: string
|
|
73
73
|
telegram: boolean
|
|
74
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Edited: Ferjolt Ozuni - Date: 2026-08-05
|
|
77
|
+
* The paid per-order CONFIRMATION by SMS - route alerts over SMS are free
|
|
78
|
+
* and need nothing here. Carries its own number like WhatsApp does, since
|
|
79
|
+
* somebody booking for a relative wants the text to reach the traveller.
|
|
80
|
+
*/
|
|
81
|
+
sms: boolean
|
|
82
|
+
smsNumber: string
|
|
83
|
+
|
|
75
84
|
// Edited: Ferjolt Ozuni - Date: 2026-08-03
|
|
76
85
|
// Flexible Ticket. A bare boolean, like Telegram - the window and the
|
|
77
86
|
// price are the server's to decide (Libraries\Orders\Flex), and a client
|