@autobusal/routes-order 1.40.0 → 1.40.2
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/Schema.tsx +19 -1
- package/Availability/service.ts +3 -2
- package/Checkout/Checkout.tsx +6 -0
- package/Checkout/LocalPrice.ts +78 -0
- package/Checkout/Sidebar/Sidebar.tsx +11 -2
- package/Checkout/Sidebar/styles.ts +8 -0
- package/Facts/service.ts +2 -1
- package/RoutesOrder.tsx +5 -0
- package/package.json +1 -1
package/Availability/Schema.tsx
CHANGED
|
@@ -2,6 +2,20 @@ import { TFunction } from 'i18next';
|
|
|
2
2
|
import { JsonLd } from '@autobusal/common';
|
|
3
3
|
import { LOW_SEATS, Prepared } from './service';
|
|
4
4
|
|
|
5
|
+
/** the URL with the assistant/partner attribution tags removed */
|
|
6
|
+
const untagged = (url: string): string => {
|
|
7
|
+
try {
|
|
8
|
+
const parsed = new URL(url);
|
|
9
|
+
|
|
10
|
+
parsed.searchParams.delete('utm_source');
|
|
11
|
+
parsed.searchParams.delete('utm_medium');
|
|
12
|
+
|
|
13
|
+
return parsed.toString();
|
|
14
|
+
} catch {
|
|
15
|
+
return url;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
5
19
|
interface Props {
|
|
6
20
|
// The SAME object the section renders from - see ./service. Not a second
|
|
7
21
|
// pass over the payload that happens to agree with the first one.
|
|
@@ -86,7 +100,11 @@ const Schema = ({ data, t }: Props): (JSX.Element | null) => {
|
|
|
86
100
|
price: row.amount,
|
|
87
101
|
priceCurrency: data.currency,
|
|
88
102
|
|
|
89
|
-
|
|
103
|
+
// Claude - 2026-09-26: without the partner-feed tags. row.url is
|
|
104
|
+
// built for assistants and partners (utm_source=busmagus-api&
|
|
105
|
+
// utm_medium=assistant); on our own page's markup it would credit a
|
|
106
|
+
// search engine's visitors to the assistant channel.
|
|
107
|
+
...(row.url ? { url: untagged(row.url) } : {}),
|
|
90
108
|
|
|
91
109
|
/*
|
|
92
110
|
* READ off the seat count the row prints, never asserted. Below the
|
package/Availability/service.ts
CHANGED
|
@@ -226,7 +226,8 @@ export const dated = (date: string, t: TFunction<'common'>): (string | null) =>
|
|
|
226
226
|
return t('routes_order.availability.date', {
|
|
227
227
|
weekday: t(`data.days.${ weekday }`),
|
|
228
228
|
day: String(Number(parts[3])),
|
|
229
|
-
|
|
229
|
+
// Claude - 2026-09-26: the after-a-day form (genitive in el/hr/sr/bs/me/sl)
|
|
230
|
+
month: t(`data.months_day.${ month }`)
|
|
230
231
|
});
|
|
231
232
|
};
|
|
232
233
|
|
|
@@ -273,7 +274,7 @@ export const checked = (stamp: string, zone: string, t: TFunction<'common'>): (s
|
|
|
273
274
|
|
|
274
275
|
return t('routes_order.availability.updated', {
|
|
275
276
|
day: String(Number(value('day'))),
|
|
276
|
-
month: t(`data.
|
|
277
|
+
month: t(`data.months_day.${ month }`),
|
|
277
278
|
year: value('year'),
|
|
278
279
|
// hour12: false renders midnight as "24" in some engines
|
|
279
280
|
time: `${ value('hour') === '24' ? '00' : value('hour') }:${ value('minute') }`
|
package/Checkout/Checkout.tsx
CHANGED
|
@@ -16,6 +16,7 @@ import Addons from '../Step5/Addons/Addons';
|
|
|
16
16
|
import Billing from '../Step5/Billing/Billing';
|
|
17
17
|
import Payments from '../Step5/Payments/Payments';
|
|
18
18
|
import Sidebar from './Sidebar/Sidebar';
|
|
19
|
+
import { useLocalPrice } from './LocalPrice';
|
|
19
20
|
import { getTotal, convertCoupon, getAvailablePayments, currencyOf, surchargeOf } from '../Step5/utilities';
|
|
20
21
|
import { Title, Actions, Results, Listing } from '../styles';
|
|
21
22
|
import { RoutesSearchForm } from '@autobusal/routes-search/types';
|
|
@@ -396,6 +397,10 @@ const Checkout = ({ values, step1, step2, step3, isPartner, t, onStore, onBack }
|
|
|
396
397
|
value: owed
|
|
397
398
|
} : grandTotal;
|
|
398
399
|
|
|
400
|
+
// Claude - 2026-09-26: the same total in the currency Google quoted, for
|
|
401
|
+
// a traveller who arrived from Google Travel on this route (LocalPrice)
|
|
402
|
+
const local = useLocalPrice(step2.id as number | undefined, payable.value);
|
|
403
|
+
|
|
399
404
|
/**
|
|
400
405
|
* Edited: Ferjolt Ozuni - Date: 2026-08-05
|
|
401
406
|
*
|
|
@@ -634,6 +639,7 @@ const Checkout = ({ values, step1, step2, step3, isPartner, t, onStore, onBack }
|
|
|
634
639
|
addons={ addonItems.map(item => ({ label: item.label, value: `${ item.price } ${ currency }` })) }
|
|
635
640
|
coupon={ coupon }
|
|
636
641
|
total={ payable }
|
|
642
|
+
local={ local }
|
|
637
643
|
surcharge={ surcharge > 0 ? `${ surcharge } ${ currency }` : undefined }
|
|
638
644
|
/* the seller's own cut, taken off what they settle - the same line
|
|
639
645
|
their invoice carries (audit A4-17), so the two documents for one
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { useQuery } from '@tanstack/react-query';
|
|
2
|
+
import { apiClient } from '@autobusal/providers';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The price Google showed, beside the price we charge.
|
|
6
|
+
*
|
|
7
|
+
* Claude - 2026-09-26 (Ferjolt: "build the checkout price display"). Google
|
|
8
|
+
* Travel quotes a departure in its origin's currency (obtapi Google\Transport
|
|
9
|
+
* - lekë from Albania) and requires the landing page to show the price the
|
|
10
|
+
* traveller picked. The ticket link names that currency (`local=ALL`, next
|
|
11
|
+
* to `route=`); this remembers it for the session, and the checkout shows
|
|
12
|
+
* "≈ 1,840 ALL" under the euro total for THAT route only - a later booking
|
|
13
|
+
* from Greece in the same tab shows nothing. Display only: the charge stays
|
|
14
|
+
* in the brand's currency. No rate known: no line.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const KEY = 'checkout_local_currency';
|
|
18
|
+
|
|
19
|
+
// whole units where prices are quoted whole - the same rule as the quote
|
|
20
|
+
const WHOLE = ['ALL', 'MKD', 'RSD', 'HUF', 'ISK'];
|
|
21
|
+
|
|
22
|
+
interface Stored {
|
|
23
|
+
currency: string
|
|
24
|
+
route: number
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** called on every wizard render: keeps the currency the landing URL named */
|
|
28
|
+
export const rememberLocalCurrency = (params: URLSearchParams): void => {
|
|
29
|
+
const currency = params.get('local');
|
|
30
|
+
const route = Number(params.get('route'));
|
|
31
|
+
|
|
32
|
+
if (!currency || !/^[A-Z]{3}$/.test(currency) || !route) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
sessionStorage.setItem(KEY, JSON.stringify({ currency, route }));
|
|
38
|
+
} catch {
|
|
39
|
+
// private mode / storage blocked: the line is a nicety, not a feature
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const stored = (): Stored | null => {
|
|
44
|
+
try {
|
|
45
|
+
const value = JSON.parse(sessionStorage.getItem(KEY) ?? 'null');
|
|
46
|
+
|
|
47
|
+
return value && typeof value.currency === 'string' && typeof value.route === 'number' ? value : null;
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/** "1,840 ALL" for this route's total, or undefined */
|
|
54
|
+
export const useLocalPrice = (routeId: number | undefined, total: number): string | undefined => {
|
|
55
|
+
const local = stored();
|
|
56
|
+
|
|
57
|
+
const currency = local && routeId && local.route === routeId ? local.currency : null;
|
|
58
|
+
|
|
59
|
+
const { data: rate } = useQuery({
|
|
60
|
+
queryKey: ['exchange-rate-local', currency],
|
|
61
|
+
queryFn: async () => (
|
|
62
|
+
await apiClient.get('/api/exchange-rates/local', { params: { currency } }).then(response => response.data?.rate as number | null)
|
|
63
|
+
),
|
|
64
|
+
enabled: currency !== null,
|
|
65
|
+
staleTime: 10 * 60 * 1000,
|
|
66
|
+
retry: false
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
if (!currency || !rate || !(total > 0)) {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const decimals = WHOLE.includes(currency) ? 0 : 2;
|
|
74
|
+
|
|
75
|
+
const amount = Number((total * rate).toFixed(decimals));
|
|
76
|
+
|
|
77
|
+
return `${ amount.toLocaleString(undefined, { minimumFractionDigits: decimals, maximumFractionDigits: decimals }) } ${ currency }`;
|
|
78
|
+
};
|
|
@@ -4,7 +4,7 @@ import { AiOutlineInfoCircle } from 'react-icons/ai';
|
|
|
4
4
|
import { Button } from '@autobusal/common';
|
|
5
5
|
import About from '../../Step5/Summary/About/About';
|
|
6
6
|
import Amount from '../../Step5/Summary/Amount/Amount';
|
|
7
|
-
import { Container, Panel, Actions, Notice } from './styles';
|
|
7
|
+
import { Container, Panel, Actions, Notice, Local } from './styles';
|
|
8
8
|
import { SubTitle } from '../../styles';
|
|
9
9
|
import { Information } from '../../Step5/Summary/styles';
|
|
10
10
|
import { RoutesSearchForm } from '@autobusal/routes-search/types';
|
|
@@ -25,6 +25,11 @@ interface Props {
|
|
|
25
25
|
addons: AddonItem[]
|
|
26
26
|
coupon?: CouponData
|
|
27
27
|
total: TotalData
|
|
28
|
+
/**
|
|
29
|
+
* Claude - 2026-09-26: the total in the currency Google Travel quoted
|
|
30
|
+
* ("1,840 ALL"), shown under it for a Google arrival - display only.
|
|
31
|
+
*/
|
|
32
|
+
local?: string
|
|
28
33
|
/**
|
|
29
34
|
* The card surcharge already included in `total`, formatted with its
|
|
30
35
|
* currency - undefined when the chosen method carries none.
|
|
@@ -65,7 +70,7 @@ interface Props {
|
|
|
65
70
|
* page and scrolled away while add-ons were being ticked - so the number
|
|
66
71
|
* changed out of sight of the person it was changing for.
|
|
67
72
|
*/
|
|
68
|
-
const Sidebar = ({ step1, step2, step3, fare, addons, coupon, total, surcharge, comission, action, single, pending, t, onPay, children, payments }: Props): JSX.Element => (
|
|
73
|
+
const Sidebar = ({ step1, step2, step3, fare, addons, coupon, total, local, surcharge, comission, action, single, pending, t, onPay, children, payments }: Props): JSX.Element => (
|
|
69
74
|
<Container>
|
|
70
75
|
<Panel className="box">
|
|
71
76
|
<SubTitle>
|
|
@@ -116,6 +121,10 @@ const Sidebar = ({ step1, step2, step3, fare, addons, coupon, total, surcharge,
|
|
|
116
121
|
) }
|
|
117
122
|
|
|
118
123
|
<Amount type="total" value={ total.display } t={ t } />
|
|
124
|
+
|
|
125
|
+
{ local && (
|
|
126
|
+
<Local>{ t('routes_order.step5.summary.local', { ns: 'common', amount: local }) }</Local>
|
|
127
|
+
) }
|
|
119
128
|
</div>
|
|
120
129
|
|
|
121
130
|
<Actions>
|
|
@@ -50,3 +50,11 @@ export const Notice = styled.p`
|
|
|
50
50
|
color: ${ props => props.theme.font.faded };
|
|
51
51
|
text-align: center;
|
|
52
52
|
`;
|
|
53
|
+
|
|
54
|
+
// Claude - 2026-09-26: the total in the currency Google Travel quoted
|
|
55
|
+
export const Local = styled.div`
|
|
56
|
+
margin-top: -4px;
|
|
57
|
+
text-align: right;
|
|
58
|
+
color: ${ props => props.theme.font.faded };
|
|
59
|
+
font-size: ${ props => props.theme.size.s };
|
|
60
|
+
`;
|
package/Facts/service.ts
CHANGED
|
@@ -84,7 +84,8 @@ export const ran = (iso: string, t: TFunction<'common'>): (string | null) => {
|
|
|
84
84
|
|
|
85
85
|
return t('routes_order.facts.not_scheduled_last', {
|
|
86
86
|
day: String(Number(parts[3])),
|
|
87
|
-
|
|
87
|
+
// Claude - 2026-09-26: the after-a-day form (genitive in el/hr/sr/bs/me/sl)
|
|
88
|
+
month: t(`data.months_day.${ month }`),
|
|
88
89
|
year: parts[1]
|
|
89
90
|
});
|
|
90
91
|
};
|
package/RoutesOrder.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useState, useEffect, useMemo, useRef } from 'react';
|
|
2
2
|
import { useParams, useSearchParams, useNavigate, useLocation } from 'react-router-dom';
|
|
3
|
+
import { rememberLocalCurrency } from './Checkout/LocalPrice';
|
|
3
4
|
import { TFunction } from 'i18next';
|
|
4
5
|
import { AiOutlineSearch, AiOutlineCaretDown, AiOutlineCaretUp } from 'react-icons/ai';
|
|
5
6
|
import { Meta, PageBreadcrumbs } from '@autobusal/common';
|
|
@@ -109,6 +110,10 @@ const RoutesOrder = ({ t }: Props): JSX.Element => {
|
|
|
109
110
|
*/
|
|
110
111
|
const [ searchParams, setSearchParams ] = useSearchParams();
|
|
111
112
|
|
|
113
|
+
// Claude - 2026-09-26: a Google Travel arrival names the currency Google
|
|
114
|
+
// quoted (`local=ALL`); the checkout shows the price in it too
|
|
115
|
+
rememberLocalCurrency(searchParams);
|
|
116
|
+
|
|
112
117
|
const navigate = useNavigate();
|
|
113
118
|
|
|
114
119
|
const location = useLocation();
|