@autobusal/routes-order 1.8.0 → 1.9.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.
- package/CHANGELOG.md +731 -0
- package/Checkout/Checkout.tsx +519 -0
- package/Checkout/Sidebar/Sidebar.tsx +124 -0
- package/Checkout/Sidebar/styles.ts +52 -0
- package/Facts/Facts.tsx +271 -0
- package/Facts/questions.ts +250 -0
- package/Facts/service.ts +111 -0
- package/Facts/services.ts +19 -0
- package/Facts/styles.ts +112 -0
- package/Facts/types.ts +167 -0
- package/Faq/Faq.tsx +52 -0
- package/Faq/styles.ts +68 -0
- package/Found/Dates/Dates.tsx +95 -5
- package/Found/Dates/styles.ts +173 -9
- package/Found/Empty/Empty.tsx +136 -0
- package/Found/Empty/styles.ts +98 -0
- package/Found/Filters/Filters.tsx +194 -0
- package/Found/Filters/styles.ts +188 -0
- package/Found/Found.tsx +207 -17
- package/Found/Group/Group.tsx +68 -0
- package/Found/Group/styles.ts +44 -0
- package/Found/Header/styles.ts +20 -4
- package/Found/Orders/Orders.tsx +89 -27
- package/Found/Orders/styles.ts +14 -0
- package/Found/Picks/Picks.tsx +75 -0
- package/Found/Picks/styles.ts +106 -0
- package/Found/Route/Route.tsx +211 -33
- package/Found/Route/styles.ts +491 -46
- package/Found/SameDay/SameDay.tsx +35 -0
- package/Found/SameDay/styles.ts +27 -0
- package/Found/Sort/Sort.tsx +40 -0
- package/Found/Sort/styles.ts +87 -0
- package/Found/Summary/Summary.tsx +49 -0
- package/Found/Summary/styles.ts +42 -0
- package/Found/Watch/Watch.tsx +187 -0
- package/Found/Watch/styles.ts +118 -0
- package/Found/refine.ts +652 -0
- package/Links/Links.tsx +91 -0
- package/Links/styles.ts +75 -0
- package/RoutesOrder.tsx +239 -31
- package/Schedule/Schedule.tsx +123 -0
- package/Schedule/styles.ts +71 -0
- package/Sections/Schema.tsx +186 -0
- package/Sections/Sections.tsx +160 -0
- package/Sections/styles.ts +44 -0
- package/Step1/prepare.ts +21 -2
- package/Step2.tsx +34 -1
- package/Step4/Passenger/Passenger.tsx +253 -24
- package/Step4/Passenger/styles.ts +21 -1
- package/Step4/Passengers/Display.tsx +10 -2
- package/Step4/Passengers/Passengers.tsx +53 -4
- package/Step4/Saved/Choose.tsx +99 -0
- package/Step4/Saved/Remember.tsx +58 -0
- package/Step4/Saved/styles.ts +87 -0
- package/Step4/checkAge.ts +15 -4
- package/Step5/Addons/Addons.tsx +146 -4
- package/Step5/Addons/styles.ts +94 -0
- package/Step5/Billing/Billing.tsx +138 -70
- package/Step5/Coupons/Coupons.tsx +28 -18
- package/Step5/Coupons/styles.ts +41 -8
- package/Step5/Payments/Payments.tsx +36 -5
- package/Step5/Payments/styles.ts +16 -7
- package/Step5/Summary/About/About.tsx +99 -27
- package/Step5/Summary/About/styles.ts +118 -1
- package/Step5/Summary/styles.ts +12 -5
- package/Step5/utilities.ts +67 -12
- package/Steps/Steps.tsx +3 -1
- package/commerce.ts +24 -0
- package/package.json +1 -1
- package/services.ts +302 -13
- package/styles.ts +126 -0
- package/types.ts +84 -1
- package/Found/Booking/Booking.tsx +0 -61
- package/Found/Booking/styles.ts +0 -50
- package/Step4/Step4.tsx +0 -114
- package/Step5/Step5.tsx +0 -185
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { TFunction } from 'i18next';
|
|
2
2
|
import { FaArrowLeft, FaArrowRight } from 'react-icons/fa';
|
|
3
|
-
import {
|
|
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
5
|
import { RoutesSearchForm } from '@autobusal/routes-search/types';
|
|
5
6
|
import { FoundData } from '@autobusal/providers/types/routes';
|
|
6
7
|
|
|
@@ -8,41 +9,112 @@ interface Props {
|
|
|
8
9
|
type: 'departure' | 'return'
|
|
9
10
|
step1: RoutesSearchForm
|
|
10
11
|
data?: FoundData
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Which leg of the journey this is, 1-based.
|
|
15
|
+
*
|
|
16
|
+
* Ferjolt Ozuni - Date: 2026-08-06
|
|
17
|
+
* Only passed on a ROUND TRIP - the caller is the one that knows whether
|
|
18
|
+
* there is a second leg, and a one-way has nothing to number.
|
|
19
|
+
*/
|
|
20
|
+
number?: number
|
|
21
|
+
|
|
11
22
|
t: TFunction<'public'>
|
|
12
23
|
}
|
|
13
24
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
25
|
+
/**
|
|
26
|
+
* One leg of the trip, both ends of it.
|
|
27
|
+
*
|
|
28
|
+
* Edited: Ferjolt Ozuni - Date: 2026-08-01
|
|
29
|
+
*
|
|
30
|
+
* This used to render a single line - the departure point on the outbound
|
|
31
|
+
* leg, the arrival point on the return one - so a one-way buyer reached the
|
|
32
|
+
* payment screen having never been shown where the bus actually drops them.
|
|
33
|
+
* Both ends are now stated, origin above destination.
|
|
34
|
+
*
|
|
35
|
+
* The country is named in full rather than coded: this is the last screen
|
|
36
|
+
* before someone pays, and "Thessaloniki, Greece" settles a doubt that
|
|
37
|
+
* "(GRC)" only half answers.
|
|
38
|
+
*/
|
|
39
|
+
const About = ({ type, step1, data, number, t }: Props): (JSX.Element | null) => {
|
|
17
40
|
if (!data) {
|
|
18
41
|
return null;
|
|
19
42
|
}
|
|
20
43
|
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
stop: data.locations.from.stop?.name,
|
|
25
|
-
city: data.locations.from.city.name
|
|
26
|
-
} : {
|
|
27
|
-
date: step1._return,
|
|
28
|
-
hour: data.locations.to.arrival,
|
|
29
|
-
stop: data.locations.to.stop?.name,
|
|
30
|
-
city: data.locations.to.city.name
|
|
31
|
-
};
|
|
44
|
+
const icon = type === 'departure' ? <FaArrowRight /> : <FaArrowLeft />;
|
|
45
|
+
|
|
46
|
+
const date = type === 'departure' ? step1.departure : step1._return;
|
|
32
47
|
|
|
33
48
|
return (
|
|
34
|
-
<
|
|
35
|
-
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
<
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
<Trip>
|
|
50
|
+
{ number !== undefined && (
|
|
51
|
+
<TripLabel>{ t('routes_order.step5.summary.trip', { number }) }</TripLabel>
|
|
52
|
+
) }
|
|
53
|
+
|
|
54
|
+
<Container>
|
|
55
|
+
<ContainerIcon>
|
|
56
|
+
<Icon>{ icon }</Icon>
|
|
57
|
+
</ContainerIcon>
|
|
58
|
+
|
|
59
|
+
<Information>
|
|
60
|
+
<Title>{ data.name } ({ data.code })</Title>
|
|
61
|
+
|
|
62
|
+
{ /* Edited: Ferjolt Ozuni - Date: 2026-08-01
|
|
63
|
+
Mark and name together, ranged right. The name used to sit
|
|
64
|
+
alone at the foot of the block, so the logo read as decoration
|
|
65
|
+
rather than as the operator being identified. */ }
|
|
66
|
+
<Carrier>
|
|
67
|
+
{ data.operator.logo_url && (
|
|
68
|
+
<Logo src={ data.operator.logo_url } alt={ data.operator.display_name } />
|
|
69
|
+
) }
|
|
70
|
+
|
|
71
|
+
<Operator>{ data.operator.display_name }</Operator>
|
|
72
|
+
</Carrier>
|
|
73
|
+
|
|
74
|
+
<Leg>
|
|
75
|
+
<Point>
|
|
76
|
+
<PointIcon><FaCircleDot /></PointIcon>
|
|
77
|
+
|
|
78
|
+
<PointCity>
|
|
79
|
+
{ data.locations.from.city.name }
|
|
80
|
+
{ data.locations.from.city.country?.name && (
|
|
81
|
+
<Country>, { data.locations.from.city.country.name }</Country>
|
|
82
|
+
) }
|
|
83
|
+
</PointCity>
|
|
84
|
+
|
|
85
|
+
{ /* the date and time are what a buyer double-checks before
|
|
86
|
+
paying, so they carry the weight here */ }
|
|
87
|
+
<PointMeta $strong>
|
|
88
|
+
{ t('routes_order.step5.summary.departs', {
|
|
89
|
+
date,
|
|
90
|
+
hour: data.locations.from.departure,
|
|
91
|
+
stop: data.locations.from.stop?.name
|
|
92
|
+
}) }
|
|
93
|
+
</PointMeta>
|
|
94
|
+
</Point>
|
|
95
|
+
|
|
96
|
+
<Point>
|
|
97
|
+
<PointIcon><FaLocationDot /></PointIcon>
|
|
98
|
+
|
|
99
|
+
<PointCity>
|
|
100
|
+
{ data.locations.to.city.name }
|
|
101
|
+
{ data.locations.to.city.country?.name && (
|
|
102
|
+
<Country>, { data.locations.to.city.country.name }</Country>
|
|
103
|
+
) }
|
|
104
|
+
</PointCity>
|
|
105
|
+
|
|
106
|
+
<PointMeta>
|
|
107
|
+
{ t('routes_order.step5.summary.arrives', {
|
|
108
|
+
hour: data.locations.to.arrival,
|
|
109
|
+
stop: data.locations.to.stop?.name
|
|
110
|
+
}) }
|
|
111
|
+
</PointMeta>
|
|
112
|
+
</Point>
|
|
113
|
+
</Leg>
|
|
114
|
+
</Information>
|
|
115
|
+
</Container>
|
|
116
|
+
</Trip>
|
|
45
117
|
);
|
|
46
118
|
};
|
|
47
119
|
|
|
48
|
-
export default About;
|
|
120
|
+
export default About;
|
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
import styled from 'styled-components';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* One leg, with its number above it.
|
|
5
|
+
*
|
|
6
|
+
* Ferjolt Ozuni - Date: 2026-08-06
|
|
7
|
+
* This wrapper exists so the label can sit ACROSS the leg rather than beside
|
|
8
|
+
* the arrow: Container below is the icon-and-content row, and a heading
|
|
9
|
+
* placed inside it would indent into the content column and read as part of
|
|
10
|
+
* the route name.
|
|
11
|
+
*/
|
|
12
|
+
export const Trip = styled.div`
|
|
13
|
+
flex: 1;
|
|
14
|
+
`;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* "Trip 1", "Trip 2" - only rendered when there are two.
|
|
18
|
+
*
|
|
19
|
+
* Ferjolt Ozuni - Date: 2026-08-06
|
|
20
|
+
* A one-way booking has nothing to enumerate, and numbering a lone leg would
|
|
21
|
+
* invite the reader to go looking for the other one.
|
|
22
|
+
*/
|
|
23
|
+
export const TripLabel = styled.div`
|
|
24
|
+
margin-bottom: 8px;
|
|
25
|
+
color: ${ props => props.theme.font.faded };
|
|
26
|
+
font-size: ${ props => props.theme.size.xs };
|
|
27
|
+
font-weight: 700;
|
|
28
|
+
text-transform: uppercase;
|
|
29
|
+
letter-spacing: 0.06em;
|
|
30
|
+
`;
|
|
31
|
+
|
|
3
32
|
export const Container = styled.div`
|
|
4
33
|
flex: 1;
|
|
5
34
|
display: flex;
|
|
@@ -42,8 +71,96 @@ export const SubTitle = styled.div`
|
|
|
42
71
|
border-radius: ${ props => props.theme.borderRadius };
|
|
43
72
|
`;
|
|
44
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Edited: Ferjolt Ozuni - Date: 2026-08-01
|
|
76
|
+
*
|
|
77
|
+
* Origin above destination, each with a city line and a detail line.
|
|
78
|
+
* Replaces the single pill that carried only one end of the leg.
|
|
79
|
+
*/
|
|
80
|
+
/**
|
|
81
|
+
* Edited: Ferjolt Ozuni - Date: 2026-08-01
|
|
82
|
+
*
|
|
83
|
+
* The line runs down the Leg itself, not down each Point, so it reads as
|
|
84
|
+
* one continuous journey rather than two separate bars. Each point's icon
|
|
85
|
+
* then sits ON that line - centred over it with a background matching the
|
|
86
|
+
* card, so the line appears to pass behind the icon.
|
|
87
|
+
*/
|
|
88
|
+
export const Leg = styled.div`
|
|
89
|
+
position: relative;
|
|
90
|
+
display: flex;
|
|
91
|
+
flex-direction: column;
|
|
92
|
+
gap: 12px;
|
|
93
|
+
margin: 10px 0;
|
|
94
|
+
padding-left: 26px;
|
|
95
|
+
|
|
96
|
+
&::before {
|
|
97
|
+
content: '';
|
|
98
|
+
position: absolute;
|
|
99
|
+
left: 8px;
|
|
100
|
+
top: 6px;
|
|
101
|
+
bottom: 6px;
|
|
102
|
+
width: 2px;
|
|
103
|
+
background: ${ props => props.theme.background.neutral };
|
|
104
|
+
}
|
|
105
|
+
`;
|
|
106
|
+
|
|
107
|
+
export const Point = styled.div`
|
|
108
|
+
position: relative;
|
|
109
|
+
display: flex;
|
|
110
|
+
flex-direction: column;
|
|
111
|
+
`;
|
|
112
|
+
|
|
113
|
+
export const PointIcon = styled.span`
|
|
114
|
+
position: absolute;
|
|
115
|
+
left: -26px;
|
|
116
|
+
top: 0;
|
|
117
|
+
display: flex;
|
|
118
|
+
align-items: center;
|
|
119
|
+
justify-content: center;
|
|
120
|
+
width: 18px;
|
|
121
|
+
height: 18px;
|
|
122
|
+
font-size: 11px;
|
|
123
|
+
color: ${ props => props.theme.primary.contrast };
|
|
124
|
+
background: ${ props => props.theme.primary.normal };
|
|
125
|
+
border-radius: 100px;
|
|
126
|
+
`;
|
|
127
|
+
|
|
128
|
+
export const PointCity = styled.div`
|
|
129
|
+
font-weight: 700;
|
|
130
|
+
`;
|
|
131
|
+
|
|
132
|
+
export const Country = styled.span`
|
|
133
|
+
font-weight: 400;
|
|
134
|
+
color: ${ props => props.theme.font.faded };
|
|
135
|
+
`;
|
|
136
|
+
|
|
137
|
+
export const PointMeta = styled.div<{ $strong?: boolean }>`
|
|
138
|
+
font-size: calc(${ props => props.theme.size.s } - 1px);
|
|
139
|
+
font-weight: ${ props => props.$strong ? 700 : 400 };
|
|
140
|
+
color: ${ props => props.$strong ? props.theme.font.normal : props.theme.font.faded };
|
|
141
|
+
`;
|
|
142
|
+
|
|
143
|
+
// Edited: Ferjolt Ozuni - Date: 2026-08-01
|
|
144
|
+
// The operator's mark under the route name - who the buyer is actually
|
|
145
|
+
// travelling with, which a company name alone conveys less quickly.
|
|
146
|
+
export const Carrier = styled.div`
|
|
147
|
+
display: flex;
|
|
148
|
+
align-items: center;
|
|
149
|
+
justify-content: flex-end;
|
|
150
|
+
gap: 8px;
|
|
151
|
+
margin: 6px 0;
|
|
152
|
+
`;
|
|
153
|
+
|
|
154
|
+
export const Logo = styled.img`
|
|
155
|
+
display: block;
|
|
156
|
+
max-width: 90px;
|
|
157
|
+
max-height: 30px;
|
|
158
|
+
object-fit: contain;
|
|
159
|
+
`;
|
|
160
|
+
|
|
45
161
|
export const Operator = styled.div`
|
|
46
|
-
padding-left: 10px;
|
|
47
162
|
font-size: calc(${ props => props.theme.size.s } - 1px);
|
|
163
|
+
font-weight: 700;
|
|
164
|
+
text-align: right;
|
|
48
165
|
color: ${ props => props.theme.font.faded };
|
|
49
166
|
`;
|
package/Step5/Summary/styles.ts
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import styled from 'styled-components';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* The legs of the booking, stacked.
|
|
5
|
+
*
|
|
6
|
+
* Edited: Ferjolt Ozuni - Date: 2026-08-06
|
|
7
|
+
* They used to sit SIDE BY SIDE from 768px up. That was written when the
|
|
8
|
+
* summary spanned the page; it now lives in the checkout sidebar, a narrow
|
|
9
|
+
* column, where two legs abreast left each one about half the room a single
|
|
10
|
+
* line like "Terminali Lindor Interurban dhe Nderkombetar i Autobusave
|
|
11
|
+
* (TEG)" needs. Vertical at every width, so each leg gets the panel's full
|
|
12
|
+
* width and the two read in the order they are travelled.
|
|
13
|
+
*/
|
|
3
14
|
export const Information = styled.div`
|
|
4
15
|
display: flex;
|
|
5
16
|
flex-direction: column;
|
|
6
|
-
gap:
|
|
7
|
-
|
|
8
|
-
@media (min-width: 768px) {
|
|
9
|
-
flex-direction: row;
|
|
10
|
-
}
|
|
17
|
+
gap: 20px;
|
|
11
18
|
`;
|
package/Step5/utilities.ts
CHANGED
|
@@ -1,24 +1,79 @@
|
|
|
1
1
|
import { FoundData } from '@autobusal/providers/types/routes';
|
|
2
2
|
import { CouponData } from '@autobusal/providers/types/orders';
|
|
3
|
-
import { TotalData } from '../types';
|
|
3
|
+
import { ActionData, TotalData } from '../types';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
/*
|
|
6
|
+
* Edited: Claude - Date: 2026-08-28 (sweep, low)
|
|
7
|
+
*
|
|
8
|
+
* The server formats thousands with a SPACE ('1 250.00 EUR', Fundable
|
|
9
|
+
* 2026-08-19), so split(' ')[0]/[1] read the wrong pieces the moment a
|
|
10
|
+
* basket crosses 1000 - the currency became '250.00' and a couponed total
|
|
11
|
+
* became the number 1, corrupting the rendered total and the GA4 values.
|
|
12
|
+
* The currency is the LAST token; the amount is everything else with the
|
|
13
|
+
* grouping spaces stripped.
|
|
14
|
+
*/
|
|
15
|
+
export const currencyOf = (display: string): string => (
|
|
16
|
+
display.trim().split(' ').pop() ?? ''
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
export const amountOf = (display: string): number => (
|
|
20
|
+
Number(display.trim().split(' ').slice(0, -1).join(''))
|
|
21
|
+
);
|
|
8
22
|
|
|
23
|
+
export const getTotal = (step2: FoundData, step3?: FoundData): TotalData => {
|
|
9
24
|
const total = step2.price.value + (step3?.price.value ?? 0);
|
|
10
25
|
|
|
11
26
|
return {
|
|
12
|
-
display: `${ total } ${
|
|
27
|
+
display: `${ total } ${ currencyOf(step2.price.display) }`,
|
|
13
28
|
value: total
|
|
14
29
|
};
|
|
15
30
|
};
|
|
16
31
|
|
|
17
|
-
export const convertCoupon = (data: CouponData): TotalData =>
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
return {
|
|
32
|
+
export const convertCoupon = (data: CouponData): TotalData => (
|
|
33
|
+
{
|
|
21
34
|
display: data.total,
|
|
22
|
-
value:
|
|
23
|
-
}
|
|
24
|
-
|
|
35
|
+
value: amountOf(data.total)
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The payment methods actually offered on this checkout, in the order the
|
|
41
|
+
* picker shows them.
|
|
42
|
+
*
|
|
43
|
+
* Ferjolt Ozuni - Date: 2026-08-07
|
|
44
|
+
*
|
|
45
|
+
* Pulled out of Payments because two places need the same answer and they
|
|
46
|
+
* must not disagree: the picker decides what to draw, and the button decides
|
|
47
|
+
* whether it is asking the buyer to choose ("Make Payment", after picking a
|
|
48
|
+
* method) or simply to pay ("Pay Now", when there is nothing to pick). A
|
|
49
|
+
* second copy of these conditions would eventually drift and produce a
|
|
50
|
+
* button offering a choice that is not on screen.
|
|
51
|
+
*
|
|
52
|
+
* The conditions themselves are unchanged, including their oddities: `cash`
|
|
53
|
+
* rides on the `funds` flag rather than one of its own, and a partner is
|
|
54
|
+
* offered every card gateway regardless of what the label enabled. Both were
|
|
55
|
+
* already true; this is a move, not a rewrite.
|
|
56
|
+
*/
|
|
57
|
+
export const getAvailablePayments = (data?: ActionData, isPartner = false): string[] => {
|
|
58
|
+
if (!data) {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const available: string[] = [];
|
|
63
|
+
|
|
64
|
+
if (data.reserve && !isPartner) { available.push('reserve'); }
|
|
65
|
+
if (data.funds && !isPartner) { available.push('funds', 'cash'); }
|
|
66
|
+
|
|
67
|
+
// Claude - 2026-08-22 (audit decision BOOK-07): an operator/employee on
|
|
68
|
+
// their own operator's route pays nothing - the flag is route-aware
|
|
69
|
+
// (services.ts) and re-checked server-side at payment time.
|
|
70
|
+
if (data.complimentary && !isPartner) { available.push('complimentary'); }
|
|
71
|
+
|
|
72
|
+
if (data.bkt || isPartner) { available.push('bkt'); }
|
|
73
|
+
if (data.stripe || isPartner) { available.push('stripe'); }
|
|
74
|
+
if (data.mollie || isPartner) { available.push('mollie'); }
|
|
75
|
+
if (data.raiffeisen || isPartner) { available.push('raiffeisen'); }
|
|
76
|
+
if (data.nexi || isPartner) { available.push('nexi'); }
|
|
77
|
+
|
|
78
|
+
return available;
|
|
79
|
+
};
|
package/Steps/Steps.tsx
CHANGED
|
@@ -8,7 +8,9 @@ interface Props {
|
|
|
8
8
|
const Steps = ({ value }: Props): JSX.Element => {
|
|
9
9
|
const items: JSX.Element[] = [];
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
// Edited: Ferjolt Ozuni - Date: 2026-08-01
|
|
12
|
+
// Four, not five: passenger details and checkout are one screen now.
|
|
13
|
+
for (let i = 1; i <= 4; i++) {
|
|
12
14
|
items.push(
|
|
13
15
|
<Item key={ i }>
|
|
14
16
|
<Content $selected={ value > i }>
|
package/commerce.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { CommerceItem } from '@autobusal/providers/Setup/commerce';
|
|
2
|
+
import { FoundData } from '@autobusal/providers/types/routes';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A coach, as the analytics layer sees it.
|
|
6
|
+
*
|
|
7
|
+
* Edited: Ferjolt Ozuni - Date: 2026-08-05
|
|
8
|
+
*
|
|
9
|
+
* One mapping used by every event, so a route cannot be identified one way
|
|
10
|
+
* in `select_item` and another in `purchase` - which is exactly how a funnel
|
|
11
|
+
* ends up unable to join its own steps together.
|
|
12
|
+
*
|
|
13
|
+
* The ROUTE CODE is the id rather than the numeric key: it is what appears
|
|
14
|
+
* on the ticket, in the timetable and on the operator's own paperwork, so a
|
|
15
|
+
* report built on it can be reconciled against something a human recognises.
|
|
16
|
+
*/
|
|
17
|
+
export const item = (route: FoundData): CommerceItem => ({
|
|
18
|
+
item_id: String(route.code ?? route.id),
|
|
19
|
+
item_name: `${ route.locations?.from?.city?.name ?? '' } - ${ route.locations?.to?.city?.name ?? '' }`.trim(),
|
|
20
|
+
item_brand: route.operator?.company,
|
|
21
|
+
price: Number(route.price?.value ?? 0),
|
|
22
|
+
quantity: 1
|
|
23
|
+
});
|
|
24
|
+
|