@autobusal/routes-order 1.31.0 → 1.31.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/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.31.2
4
+
5
+ ### Changed
6
+
7
+ - **The route card and booking summary show the operator's `display_name`**, not `company` - an operator that set a brand name (`@autobusal/providers` 1.34.0) now appears under that name in search results and the booking summary, same as everywhere else it's now shown.
8
+
9
+ ## 1.31.1
10
+
11
+ ### Changed
12
+
13
+ - **One payment method means one button.** The picker no longer renders when
14
+ there is nothing to pick, and the button reads "Pay Now" rather than "Make
15
+ Payment" - a heading over a single option the buyer had to press before
16
+ pressing the real one was a click that only ever had one answer, on the
17
+ last screen before paying.
18
+
3
19
  ## 1.31.0
4
20
 
5
21
  ### Changed
@@ -15,7 +15,7 @@ import Addons from '../Step5/Addons/Addons';
15
15
  import Billing from '../Step5/Billing/Billing';
16
16
  import Payments from '../Step5/Payments/Payments';
17
17
  import Sidebar from './Sidebar/Sidebar';
18
- import { getTotal, convertCoupon } from '../Step5/utilities';
18
+ import { getTotal, convertCoupon, getAvailablePayments } from '../Step5/utilities';
19
19
  import { Title, Actions, Results, Listing } from '../styles';
20
20
  import { RoutesSearchForm } from '@autobusal/routes-search/types';
21
21
  import { PersonData, BillingData } from '@autobusal/providers/types/persons';
@@ -24,7 +24,7 @@ import { CouponData } from '@autobusal/providers/types/orders';
24
24
  import { TotalData } from '../types';
25
25
  import { useUserStore } from '@autobusal/providers/stores/user';
26
26
  import { useGetSettings } from '@autobusal/providers/services';
27
- import { usePostOrder, useGetFlex } from '../services';
27
+ import { usePostOrder, useGetFlex, useGetPayments } from '../services';
28
28
  import { addon as commerceAddon, beginCheckout, stash } from '@autobusal/providers/Setup/commerce';
29
29
  import { item as commerceItem } from '../commerce';
30
30
 
@@ -74,6 +74,20 @@ const Checkout = ({ values, step1, step2, step3, isPartner, t, onStore, onBack }
74
74
  const [ total, setTotal ] = useState<TotalData>(getTotal(step2, step3));
75
75
  const [ action, setAction ] = useState<string | undefined>(undefined);
76
76
 
77
+ /*
78
+ * Edited: Ferjolt Ozuni - Date: 2026-08-07
79
+ *
80
+ * Whether there is a payment CHOICE to be made, which decides both whether
81
+ * the picker draws itself and what the button says.
82
+ *
83
+ * The same query the picker runs, under the same key, so react-query
84
+ * answers it from cache - this reads the answer, it does not fetch it a
85
+ * second time.
86
+ */
87
+ const { data: paymentsData } = useGetPayments();
88
+
89
+ const singleMethod = getAvailablePayments(paymentsData, isPartner ?? false).length === 1;
90
+
77
91
  const [ addonWhatsapp, setAddonWhatsapp ] = useState<boolean>(false);
78
92
  const [ addonWhatsappNumber, setAddonWhatsappNumber ] = useState<string>('');
79
93
  const [ addonTelegram, setAddonTelegram ] = useState<boolean>(false);
@@ -312,6 +326,7 @@ const Checkout = ({ values, step1, step2, step3, isPartner, t, onStore, onBack }
312
326
  coupon={ coupon }
313
327
  total={ grandTotal }
314
328
  action={ action }
329
+ single={ singleMethod }
315
330
  pending={ isPending }
316
331
  t={ t }
317
332
  onPay={ onPay }
@@ -26,6 +26,7 @@ interface Props {
26
26
  coupon?: CouponData
27
27
  total: TotalData
28
28
  action?: string
29
+ single?: boolean
29
30
  pending: boolean
30
31
  t: TFunction<'common'>
31
32
  onPay: () => void
@@ -45,7 +46,7 @@ interface Props {
45
46
  * page and scrolled away while add-ons were being ticked - so the number
46
47
  * changed out of sight of the person it was changing for.
47
48
  */
48
- const Sidebar = ({ step1, step2, step3, fare, addons, coupon, total, action, pending, t, onPay, children, payments }: Props): JSX.Element => (
49
+ const Sidebar = ({ step1, step2, step3, fare, addons, coupon, total, action, single, pending, t, onPay, children, payments }: Props): JSX.Element => (
49
50
  <Container>
50
51
  <Panel className="box">
51
52
  <SubTitle>
@@ -89,7 +90,13 @@ const Sidebar = ({ step1, step2, step3, fare, addons, coupon, total, action, pen
89
90
  { /* Edited: Ferjolt Ozuni - Date: 2026-08-01
90
91
  The label follows the chosen method, so the button says what it
91
92
  is about to do - "Reserve" does not take money, and a button
92
- reading "Make Payment" for it would be a lie. */ }
93
+ reading "Make Payment" for it would be a lie.
94
+
95
+ Edited: Ferjolt Ozuni - Date: 2026-08-07
96
+ And when there is only one method, the picker above renders
97
+ nothing, so this is the whole payment step. "Make Payment" reads
98
+ as "proceed to the part where you pay"; with nothing left in
99
+ between, it should say what actually happens next. */ }
93
100
  <Button
94
101
  loading={ pending }
95
102
  noMargin
@@ -98,7 +105,9 @@ const Sidebar = ({ step1, step2, step3, fare, addons, coupon, total, action, pen
98
105
  <MdDone />
99
106
  { action === 'reserve'
100
107
  ? t('routes_order.step5.reserve')
101
- : t('routes_order.step5.next') }
108
+ : single
109
+ ? t('routes_order.step5.pay_now')
110
+ : t('routes_order.step5.next') }
102
111
  </>
103
112
  ) }
104
113
  onClick={ onPay }
@@ -131,7 +131,7 @@ const Route = ({ data, type, date, passengers, t, onSave, children }: Props): JS
131
131
 
132
132
  <Trip>
133
133
  <Company>
134
- <LinkCompany to={ bookOnUrl ?? data.operator.link } target="_blank" rel="noopener noreferrer" title={ data.operator.company }><img src={ data.operator.logo_url || LOGO_FALLBACK } onError={ (e) => { e.currentTarget.onerror = null; e.currentTarget.src = LOGO_FALLBACK; } } alt={ data.operator.company } /></LinkCompany>
134
+ <LinkCompany to={ bookOnUrl ?? data.operator.link } target="_blank" rel="noopener noreferrer" title={ data.operator.display_name }><img src={ data.operator.logo_url || LOGO_FALLBACK } onError={ (e) => { e.currentTarget.onerror = null; e.currentTarget.src = LOGO_FALLBACK; } } alt={ data.operator.display_name } /></LinkCompany>
135
135
 
136
136
  { /* Edited: Ferjolt Ozuni - Date: 2026-08-03
137
137
  The OPERATOR's rating rather than this route's: a single route
@@ -240,7 +240,7 @@ const Route = ({ data, type, date, passengers, t, onSave, children }: Props): JS
240
240
  text={
241
241
  <>
242
242
  <RiBus2Line />
243
- { isExternalRedirect ? t('routes_order.step2.route.book_on', { provider: data.operator.company }) : t('routes_order.step2.route.select') }
243
+ { isExternalRedirect ? t('routes_order.step2.route.book_on', { provider: data.operator.display_name }) : t('routes_order.step2.route.select') }
244
244
  </>
245
245
  }
246
246
  onClick={ () => (isExternalRedirect && bookOnUrl ? window.open(bookOnUrl, '_blank', 'noopener,noreferrer') : onSave(data)) }
@@ -7,6 +7,7 @@ import { Inline } from '@autobusal/common';
7
7
  import { SubTitle } from '../../styles';
8
8
  import { Container, ContainerPayments, ButtonPayment } from './styles';
9
9
  import { useGetPayments } from '../../services';
10
+ import { getAvailablePayments } from '../utilities';
10
11
 
11
12
  interface Props {
12
13
  selected: string
@@ -15,9 +16,11 @@ interface Props {
15
16
  onChange: (action: string) => void
16
17
  }
17
18
 
18
- const Payments = ({ selected, isPartner, t, onChange }: Props): JSX.Element => {
19
+ const Payments = ({ selected, isPartner, t, onChange }: Props): JSX.Element | null => {
19
20
  const { data, isLoading, isSuccess } = useGetPayments();
20
21
 
22
+ const available = getAvailablePayments(data, isPartner);
23
+
21
24
  useEffect(() => {
22
25
  if (isSuccess) {
23
26
  const found = Object.keys(data).find(item => {
@@ -37,6 +40,26 @@ const Payments = ({ selected, isPartner, t, onChange }: Props): JSX.Element => {
37
40
  );
38
41
  }
39
42
 
43
+ /*
44
+ * Edited: Ferjolt Ozuni - Date: 2026-08-07
45
+ *
46
+ * One method is not a choice, so there is nothing to draw.
47
+ *
48
+ * A heading reading "Payment Method" over a single button the buyer must
49
+ * press before pressing the real one is a step that only ever has one
50
+ * answer - and it is the last screen before paying, which is the worst
51
+ * place to spend a click. The effect above has already selected it, so the
52
+ * button beside this can simply say "Pay Now".
53
+ *
54
+ * Rendering nothing rather than hiding with CSS: the picker is a control,
55
+ * and a control nobody can act on should not be in the page at all - it
56
+ * would still be reachable by keyboard and still announced by a screen
57
+ * reader as a thing to decide.
58
+ */
59
+ if (available.length <= 1) {
60
+ return null;
61
+ }
62
+
40
63
  return (
41
64
  <>
42
65
  <Container>
@@ -65,10 +65,10 @@ const About = ({ type, step1, data, number, t }: Props): (JSX.Element | null) =>
65
65
  rather than as the operator being identified. */ }
66
66
  <Carrier>
67
67
  { data.operator.logo_url && (
68
- <Logo src={ data.operator.logo_url } alt={ data.operator.company } />
68
+ <Logo src={ data.operator.logo_url } alt={ data.operator.display_name } />
69
69
  ) }
70
70
 
71
- <Operator>{ data.operator.company }</Operator>
71
+ <Operator>{ data.operator.display_name }</Operator>
72
72
  </Carrier>
73
73
 
74
74
  <Leg>
@@ -1,6 +1,6 @@
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
5
  export const getTotal = (step2: FoundData, step3?: FoundData): TotalData => {
6
6
  // we get the currency
@@ -21,4 +21,41 @@ export const convertCoupon = (data: CouponData): TotalData => {
21
21
  display: data.total,
22
22
  value: Number(currency[0])
23
23
  };
24
- };
24
+ };
25
+
26
+ /**
27
+ * The payment methods actually offered on this checkout, in the order the
28
+ * picker shows them.
29
+ *
30
+ * Ferjolt Ozuni - Date: 2026-08-07
31
+ *
32
+ * Pulled out of Payments because two places need the same answer and they
33
+ * must not disagree: the picker decides what to draw, and the button decides
34
+ * whether it is asking the buyer to choose ("Make Payment", after picking a
35
+ * method) or simply to pay ("Pay Now", when there is nothing to pick). A
36
+ * second copy of these conditions would eventually drift and produce a
37
+ * button offering a choice that is not on screen.
38
+ *
39
+ * The conditions themselves are unchanged, including their oddities: `cash`
40
+ * rides on the `funds` flag rather than one of its own, and a partner is
41
+ * offered every card gateway regardless of what the label enabled. Both were
42
+ * already true; this is a move, not a rewrite.
43
+ */
44
+ export const getAvailablePayments = (data?: ActionData, isPartner = false): string[] => {
45
+ if (!data) {
46
+ return [];
47
+ }
48
+
49
+ const available: string[] = [];
50
+
51
+ if (data.reserve && !isPartner) { available.push('reserve'); }
52
+ if (data.funds && !isPartner) { available.push('funds', 'cash'); }
53
+
54
+ if (data.bkt || isPartner) { available.push('bkt'); }
55
+ if (data.stripe || isPartner) { available.push('stripe'); }
56
+ if (data.mollie || isPartner) { available.push('mollie'); }
57
+ if (data.raiffeisen || isPartner) { available.push('raiffeisen'); }
58
+ if (data.nexi || isPartner) { available.push('nexi'); }
59
+
60
+ return available;
61
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autobusal/routes-order",
3
- "version": "1.31.0",
3
+ "version": "1.31.2",
4
4
  "author": "Ferjolt Ozuni",
5
5
  "type": "module",
6
6
  "main": "index.ts"