@autobusal/routes-order 1.38.0 → 1.38.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.
@@ -1,4 +1,5 @@
1
1
  import { TFunction } from 'i18next';
2
+ import { Link } from 'react-router-dom';
2
3
  import { NextDay } from '../Schedule/styles';
3
4
  import { Container, Heading, Lead, Wrap, Table, Time, Faded, Fare, Price, SeatsLeft, Book, Later, More, Updated } from './styles';
4
5
  import { LOW_SEATS, Day, Prepared } from './service';
@@ -27,6 +28,31 @@ interface Props {
27
28
  */
28
29
  export const VISIBLE = 7;
29
30
 
31
+ /**
32
+ * Claude - 2026-09-25 (audit): the API builds each row's `url` for the
33
+ * partner/assistant feed - absolute, and tagged
34
+ * utm_source=busmagus-api&utm_medium=assistant. Rendered as-is on our own
35
+ * pair page, every "Book" click was a full page reload that credited our own
36
+ * visitors to the assistant channel. On this origin it becomes an in-app
37
+ * route with those two tags removed; any other origin keeps the plain link.
38
+ */
39
+ const spaTarget = (url: string): string | null => {
40
+ try {
41
+ const parsed = new URL(url, window.location.href);
42
+
43
+ if (parsed.origin !== window.location.origin) {
44
+ return null;
45
+ }
46
+
47
+ parsed.searchParams.delete('utm_source');
48
+ parsed.searchParams.delete('utm_medium');
49
+
50
+ return parsed.pathname + parsed.search;
51
+ } catch {
52
+ return null;
53
+ }
54
+ };
55
+
30
56
  /**
31
57
  * Real departures on real dates, for the next thirty days.
32
58
  *
@@ -113,7 +139,15 @@ const Availability = ({ id, data, t }: Props): JSX.Element => {
113
139
  </td>
114
140
 
115
141
  <td>
116
- { row.url ? <Book href={ row.url }>{ t('routes_order.availability.book') }</Book> : null }
142
+ { row.url
143
+ ? (() => {
144
+ const to = spaTarget(row.url);
145
+
146
+ return to
147
+ ? <Book as={ Link } to={ to }>{ t('routes_order.availability.book') }</Book>
148
+ : <Book href={ row.url }>{ t('routes_order.availability.book') }</Book>;
149
+ })()
150
+ : null }
117
151
  </td>
118
152
  </tr>
119
153
  )) }
@@ -176,6 +176,8 @@ const Dates = ({ type, step1, t, onSearch }: Props): JSX.Element => {
176
176
  size="small"
177
177
  noMargin
178
178
  text={ <AiOutlineCaretLeft /> }
179
+ // Claude - 2026-09-25 (audit): icon-only, so name it for screen readers
180
+ ariaLabel={ t('routes_order.step2.dates.previous') }
179
181
  onClick={ onPrevious }
180
182
  />
181
183
 
@@ -186,6 +188,7 @@ const Dates = ({ type, step1, t, onSearch }: Props): JSX.Element => {
186
188
  size="small"
187
189
  noMargin
188
190
  text={ <AiOutlineCaretRight /> }
191
+ ariaLabel={ t('routes_order.step2.dates.next') }
189
192
  onClick={ onNext }
190
193
  />
191
194
  </Inner>
package/RoutesOrder.tsx CHANGED
@@ -432,7 +432,10 @@ const RoutesOrder = ({ t }: Props): JSX.Element => {
432
432
  <Step1
433
433
  value={ step1 }
434
434
  redirected={ redirected }
435
- heading={ subject ? 'h2' : 'h1' }
435
+ // Claude - 2026-09-25 (audit): not a heading at all on the pair page -
436
+ // as an h2 it still sat ABOVE the facts h1, so the document outline
437
+ // opened on a second-level heading (an icon plus "Search Schedules")
438
+ heading={ subject ? 'div' : 'h1' }
436
439
  t={ t }
437
440
  onSave={ onStep1 }
438
441
  />
package/Step1/Step1.tsx CHANGED
@@ -22,8 +22,12 @@ interface Props {
22
22
  * /bus-lines/{from}/{to} page named "Search Schedules" to crawlers and
23
23
  * screen readers alike. RoutesOrder decides, because it is the one place
24
24
  * that knows whether the facts block is going to render.
25
+ *
26
+ * Claude - 2026-09-25 (audit): 'div' added - the pair page's form label is
27
+ * no heading at all now (an h2 before the page's h1 broke the outline);
28
+ * Title carries the heading look itself so it renders the same.
25
29
  */
26
- heading?: 'h1' | 'h2'
30
+ heading?: 'h1' | 'h2' | 'div'
27
31
  t: TFunction<'common'>
28
32
  onSave: (data: RoutesSearchForm) => void
29
33
  }
@@ -180,6 +180,24 @@ const Billing = ({ errors, t, refs, only }: Props): JSX.Element => {
180
180
  </div>
181
181
  ) }
182
182
 
183
+ { /* Claude - 2026-09-25 (Ferjolt): a PRIVATE buyer's identification for
184
+ the fiscal invoice - a company is identified by its VAT number
185
+ below, a person by a personal ID number; without one the first
186
+ passenger's passport stands in, so it stays optional. */ }
187
+ { asked('bill_company') && !company && (
188
+ <div className="row">
189
+ <label htmlFor={ id('bill_id_number') }>{ t('routes_order.step5.billing.id_number') }</label>
190
+
191
+ <input type="text" id={ id('bill_id_number') } { ...refs('bill_id_number', Validate('max_length:50', t)) } />
192
+
193
+ <NoticeEmail>
194
+ <AiOutlineInfoCircle /> { t('routes_order.step5.billing.id_number_info') }
195
+ </NoticeEmail>
196
+
197
+ { Display(errors.bill_id_number) }
198
+ </div>
199
+ ) }
200
+
183
201
  { asked('bill_company') && company && (
184
202
  <div className="column">
185
203
  <div className="row">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autobusal/routes-order",
3
- "version": "1.38.0",
3
+ "version": "1.38.2",
4
4
  "author": "Ferjolt Ozuni",
5
5
  "type": "module",
6
6
  "main": "index.ts",
package/services.ts CHANGED
@@ -241,7 +241,12 @@ export const usePostOrder = (
241
241
 
242
242
  if (data !== undefined) {
243
243
  Object.keys(data).forEach(index => {
244
- order[index] = data[index as keyof typeof data];
244
+ const value = data[index as keyof typeof data];
245
+
246
+ // an optional billing field the buyer left out is not sent at all
247
+ if (value !== undefined) {
248
+ order[index] = value;
249
+ }
245
250
  });
246
251
  }
247
252
 
package/styles.ts CHANGED
@@ -10,6 +10,15 @@ export const Title = styled.h1`
10
10
  * document-outline decision, not a request for a smaller heading.
11
11
  */
12
12
  font-size: ${ props => props.theme.size.xxl };
13
+ /*
14
+ * Claude - 2026-09-25 (audit): the global h1-h6 rules, restated, because
15
+ * the pair page now renders this as a <div> (not a heading - see Step1's
16
+ * heading prop) and it must look exactly like the h1 it is elsewhere.
17
+ */
18
+ margin: 0 0 20px 0;
19
+ font-weight: 700;
20
+ line-height: ${ props => props.theme.lineHeight.title };
21
+ font-family: ${ props => props.theme.fontFamily.title }, sans-serif;
13
22
  `;
14
23
 
15
24
  export const SubTitle = styled.h3`