@autobusal/common 1.36.1 → 1.36.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,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.36.2 - 2026-08-29
4
+
5
+ - Seat map: a taken seat is DISABLED, struck through and labelled "Seat N - taken", instead of relying on colour alone with a click that was silently swallowed (audit A3-08c).
6
+
7
+ - Drive map: the road line is asked through obtapi's /routes/directions instead of calling OpenRouteService from the browser, which CORS blocked every time - and which shipped a metered third-party key in the bundle to pay for requests that could not succeed (audit A7-26). The hook now answers one feature or null, not a GeoJSON envelope.
8
+
3
9
  ## 1.36.0 (2026-08-29)
4
10
 
5
11
  - Editors offer Save & Close alongside Save & Stay, so correcting a record no longer bounces you back to its list every time (opt-in per page, editing only). The pair is driven from here (Viewer + Actions).
package/Drive/Drive.tsx CHANGED
@@ -26,7 +26,10 @@ const Drive = ({ locations, t }: Props): JSX.Element => {
26
26
  }
27
27
 
28
28
  // we get the coordinates for the roads to paint
29
- const geometryData = MapData?.features[0].geometry.coordinates;
29
+ // Claude - 2026-08-29 (audit A7-26): ONE feature now, not a GeoJSON
30
+ // envelope - and optional the whole way down, because "no line to draw" is
31
+ // an ordinary answer here rather than only a failed request
32
+ const geometryData = MapData?.geometry?.coordinates;
30
33
 
31
34
  // we paint the drive lines
32
35
  const lines = geometryData?.map((item, index) => {
package/Drive/services.ts CHANGED
@@ -1,32 +1,41 @@
1
1
  import { useQuery, UseQueryResult } from '@tanstack/react-query';
2
2
  import { apiClient } from '@autobusal/providers';
3
- import { GeoJsonData } from './types';
3
+ import { DriveLine } from './types';
4
4
 
5
- export const useGetDriveData = (coordinates: number[][]): UseQueryResult<GeoJsonData> => (
5
+ export const useGetDriveData = (coordinates: number[][]): UseQueryResult<DriveLine | null> => (
6
6
  useQuery({
7
7
  // Edited: Ferjolt Ozuni - Date: 2026-08-01
8
8
  // The key was a bare ['drive-data'] with the coordinates absent, so
9
9
  // every route on the site shared one cached itinerary: open one route
10
10
  // page and then another, and the second drew the first one's road line.
11
11
  queryKey: ['drive-data', { coordinates }],
12
- // With no key configured (or a route with fewer than two placed stops)
13
- // this only buys a 401 and a console error on every route page view.
14
- // Skipping it leaves the map showing stops and no road, which is what
15
- // it already did - just quietly.
16
- enabled: Boolean(import.meta.env.VITE_OPEN_ROUTE_SERVICE_KEY) && coordinates.length > 1,
12
+ /*
13
+ * Claude - 2026-08-29 (audit A7-26, low)
14
+ *
15
+ * ASKED THROUGH OUR OWN API, not from the browser. OpenRouteService
16
+ * answers a browser with no Access-Control-Allow-Origin, so this request
17
+ * never actually left - every route page logged a CORS failure and drew
18
+ * its stops with no road between them. And it carried a metered
19
+ * third-party API key, in the bundle, on a public page.
20
+ *
21
+ * obtapi has asked ORS from the server since August for the DISTANCE on
22
+ * this very geometry (Libraries\Routes\Distance, whose own header
23
+ * explains why); this is the same call with the line kept. Cached
24
+ * server-side per geometry, so one answer serves every visitor to a pair
25
+ * instead of each of them paying for their own.
26
+ *
27
+ * The coordinates go over as [lat, lng] pairs - the order this codebase
28
+ * writes a coordinate in everywhere except the ORS request itself, which
29
+ * the server now flips.
30
+ */
31
+ enabled: coordinates.length > 1,
17
32
  queryFn: async () => (
18
33
  await apiClient
19
- .post('https://api.openrouteservice.org/v2/directions/driving-car/geojson', {
20
- coordinates
21
- }, {
22
- headers: {
23
- Authorization: import.meta.env.VITE_OPEN_ROUTE_SERVICE_KEY
24
- },
25
- withCredentials: false,
26
- withXSRFToken: false
34
+ .post('/api/routes/directions', {
35
+ points: coordinates.map(point => [point[1], point[0]])
27
36
  })
28
37
  .then(response => (
29
- response.data
38
+ response.data?.line ?? null
30
39
  ))
31
40
  )
32
41
  })
package/Drive/types.ts CHANGED
@@ -1,11 +1,15 @@
1
- export interface GeoJsonData {
2
- features: FeaturesData[]
3
- }
4
-
5
- interface FeaturesData {
1
+ /**
2
+ * Claude - 2026-08-29 (audit A7-26): the drive line is now ONE feature, not
3
+ * a GeoJSON envelope - obtapi's /routes/directions returns the LineString it
4
+ * picked out of the provider's answer, or null when there is no line to draw
5
+ * (no provider configured, fewer than two placed stops, or the provider
6
+ * unreachable). That also removes the `features[0]` the consumer subscripted,
7
+ * which was one empty collection away from throwing.
8
+ */
9
+ export interface DriveLine {
6
10
  geometry: GeometryData
7
11
  }
8
12
 
9
13
  interface GeometryData {
10
14
  coordinates: string[][]
11
- }
15
+ }
package/Seats/Preview.tsx CHANGED
@@ -18,6 +18,21 @@ const Preview = ({ bus, occupied, picked, selected, t, onSelect, onClose }: Prop
18
18
  const items = bus.seats.map((item, index) => {
19
19
  const isOccupied = occupied.includes(item.no) || picked.includes(item.no);
20
20
 
21
+ /*
22
+ * Claude - 2026-08-29 (audit A3-08c, low)
23
+ *
24
+ * A TAKEN SEAT SAYS SO, rather than only being red. It stayed an enabled
25
+ * button whose click was silently swallowed, and colour was the single
26
+ * thing distinguishing it - so a colour-blind buyer, a screen-reader
27
+ * user, or anybody on a washed-out phone screen in daylight could only
28
+ * tell a sold seat from a free one by clicking it and getting nothing.
29
+ *
30
+ * `disabled` rather than a quiet no-op, because that is what stops a
31
+ * pointer, a keyboard and assistive technology at once; onSelect keeps
32
+ * its own guard regardless. The title carries the reason for a mouse and
33
+ * aria-label carries it for a reader, since the visible content is a bare
34
+ * seat number either way.
35
+ */
21
36
  return (
22
37
  <ButtonOccupy
23
38
  key={ index }
@@ -28,6 +43,12 @@ const Preview = ({ bus, occupied, picked, selected, t, onSelect, onClose }: Prop
28
43
  $height={ item.height }
29
44
  $selected={ selected === item.no }
30
45
  $occupied={ isOccupied }
46
+ disabled={ isOccupied }
47
+ aria-pressed={ selected === item.no }
48
+ title={ isOccupied ? t('seats.taken', { ns: 'common', number: item.no }) : undefined }
49
+ aria-label={ isOccupied
50
+ ? t('seats.taken', { ns: 'common', number: item.no })
51
+ : t('seats.free', { ns: 'common', number: item.no }) }
31
52
  onClick={ () => onSelect(item.no, isOccupied) }
32
53
  >
33
54
  <MdEventSeat /> { item.no }
package/Seats/styles.ts CHANGED
@@ -89,12 +89,25 @@ export const ButtonOccupy = styled.button<{
89
89
  background: ${ props => props.theme.primary.normal };
90
90
  ` }
91
91
 
92
+ /*
93
+ * Claude - 2026-08-29 (audit A3-08c, low): a taken seat is disabled as well
94
+ * as red, so it reads as unavailable to somebody who cannot see the colour.
95
+ * The strike-through and the cursor are the non-colour half of that signal;
96
+ * losing the hover response is the other half - a control that answers
97
+ * nothing should not look like it is about to.
98
+ */
92
99
  ${ props => props.$occupied && css`
93
100
  color: #FFFFFF;
94
101
  background: ${ props => props.theme.font.error };
102
+ text-decoration: line-through;
95
103
  ` }
96
104
 
97
- &:hover {
105
+ &:disabled {
106
+ cursor: not-allowed;
107
+ opacity: .75;
108
+ }
109
+
110
+ &:hover:not(:disabled) {
98
111
  opacity: .8;
99
112
  }
100
113
  `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autobusal/common",
3
- "version": "1.36.1",
3
+ "version": "1.36.2",
4
4
  "author": "Ferjolt Ozuni",
5
5
  "type": "module",
6
6
  "main": "index.ts"