@autobusal/routes-order 1.40.1 → 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.
@@ -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/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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autobusal/routes-order",
3
- "version": "1.40.1",
3
+ "version": "1.40.2",
4
4
  "author": "Ferjolt Ozuni",
5
5
  "type": "module",
6
6
  "main": "index.ts",