@autobusal/routes-order 1.29.0 → 1.30.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 CHANGED
@@ -1,5 +1,36 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.30.1
4
+
5
+ ### Fixed
6
+
7
+ - **Step 3 could never list a single return leg.** `useGetReturns` spread the
8
+ search form straight into the query string, so the return date went over as
9
+ `_return` — the form's name for it, because `return` is a reserved word in
10
+ JS — while the API validates `return`. Every request answered
11
+ `422 The return field is required`, and "Select a Return Schedule" was empty
12
+ for every round trip ever searched. The order builder in the same file has
13
+ always done this mapping (`order.return = step1._return`); only this call
14
+ was missed.
15
+
16
+ Paired with an obtapi fix — `Routes\Search\Returns::find()` returned nothing
17
+ on its success path — so the step was broken twice over and neither failure
18
+ could be seen past the other.
19
+
20
+ ## 1.30.0
21
+
22
+ ### Added
23
+
24
+ - **The SMS booking-confirmation add-on at checkout**, with its own number
25
+ field prefilled from the passenger phone already collected — somebody
26
+ booking for a relative wants the text to reach the traveller. The
27
+ description says the free route alerts are not what is being sold here.
28
+ - **A line under the passenger phone field** saying what the number is used
29
+ for: reaching you about this trip, and service messages if the departure
30
+ changes — never marketing. The number was always collected so a driver
31
+ could ring it; texting it is a different use of the same data, and saying
32
+ so at the point of collection is what makes it consent rather than an
33
+ assumption.
3
34
  ## 1.29.0
4
35
 
5
36
  ### Removed
@@ -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
 
@@ -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
+ `;
@@ -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
- if (!data || (!data.whatsapp.available && !data.telegram.available && !hasFlex)) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autobusal/routes-order",
3
- "version": "1.29.0",
3
+ "version": "1.30.1",
4
4
  "author": "Ferjolt Ozuni",
5
5
  "type": "module",
6
6
  "main": "index.ts"
package/services.ts CHANGED
@@ -47,6 +47,18 @@ export const useGetAlternatives = (data: RoutesSearchForm, enabled: boolean): Us
47
47
  })
48
48
  );
49
49
 
50
+ /**
51
+ * The return legs on offer for an already-chosen outbound.
52
+ *
53
+ * Edited: Ferjolt Ozuni - Date: 2026-08-05
54
+ * The return date goes over as `return`. The form calls it `_return`
55
+ * because `return` is a reserved word in JS, and spreading the form
56
+ * straight into the query string therefore sent a field the API does not
57
+ * know while omitting the one it validates - so this request answered 422
58
+ * "The return field is required" every single time, and step 3 could never
59
+ * list anything. The order builder below has always done this mapping
60
+ * (`order.return = step1._return`); only this call was missed.
61
+ */
50
62
  export const useGetReturns = (data: RoutesSearchForm, departure: FoundData): UseQueryResult<FoundData[]> => (
51
63
  useQuery({
52
64
  queryKey: ['search-routes-step3', { data }],
@@ -55,6 +67,7 @@ export const useGetReturns = (data: RoutesSearchForm, departure: FoundData): Use
55
67
  .get('/api/routes/search/return', {
56
68
  params: {
57
69
  ...data,
70
+ return: data._return,
58
71
  operator_id: departure.operator.id
59
72
  }
60
73
  })
@@ -156,6 +169,14 @@ export const usePostOrder = (
156
169
  order.addon_telegram = '1';
157
170
  }
158
171
 
172
+ // The number may be blank here: obtapi falls back to the first
173
+ // passenger phone the form already collected (Orders\Make\Addons),
174
+ // so ticking the box on a route that asks for a phone is enough.
175
+ if (addons.sms) {
176
+ order.addon_sms = '1';
177
+ order.addon_sms_number = addons.smsNumber;
178
+ }
179
+
159
180
  if (addons.flex) {
160
181
  order.addon_flex = '1';
161
182
  }
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