@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 +6 -0
- package/Drive/Drive.tsx +4 -1
- package/Drive/services.ts +25 -16
- package/Drive/types.ts +10 -6
- package/Seats/Preview.tsx +21 -0
- package/Seats/styles.ts +14 -1
- package/package.json +1 -1
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
|
-
|
|
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 {
|
|
3
|
+
import { DriveLine } from './types';
|
|
4
4
|
|
|
5
|
-
export const useGetDriveData = (coordinates: number[][]): UseQueryResult<
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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('
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
&:
|
|
105
|
+
&:disabled {
|
|
106
|
+
cursor: not-allowed;
|
|
107
|
+
opacity: .75;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
&:hover:not(:disabled) {
|
|
98
111
|
opacity: .8;
|
|
99
112
|
}
|
|
100
113
|
`;
|