@autobusal/routes-search 1.3.3 → 1.4.0
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 +4 -0
- package/RoutesSearch.tsx +54 -4
- package/package.json +1 -1
- package/services.ts +35 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.4.0 (2026-08-29)
|
|
4
|
+
|
|
5
|
+
- A departure city near the visitor pre-fills From (obtapi /api/geo/origin), applied once and only while the field is untouched; opt-in through the new `suggest` prop.
|
|
6
|
+
|
|
3
7
|
## 1.3.3 (2026-08-29)
|
|
4
8
|
|
|
5
9
|
- A city with a single station lists that station instead of an identical "All Stations" entry beside it; the submitted value stays the city's slug, so URLs are unchanged.
|
package/RoutesSearch.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useEffect, useCallback } from 'react';
|
|
1
|
+
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
2
2
|
import { TFunction } from 'i18next';
|
|
3
3
|
import { useForm } from 'react-hook-form';
|
|
4
4
|
import { FaArrowUp, FaHistory } from 'react-icons/fa';
|
|
@@ -9,7 +9,7 @@ import { getCities } from './utilities';
|
|
|
9
9
|
import Picker from './Picker';
|
|
10
10
|
import { Options, Columns, Column, Item, Title, Action, PickerField } from './styles';
|
|
11
11
|
import { RoutesSearchForm } from './types';
|
|
12
|
-
import { useGetSuggestions } from './services';
|
|
12
|
+
import { useGetSuggestions, useSuggestedOrigin } from './services';
|
|
13
13
|
|
|
14
14
|
interface Props {
|
|
15
15
|
type: 'departure' | 'return'
|
|
@@ -20,11 +20,21 @@ interface Props {
|
|
|
20
20
|
adultsNo: number
|
|
21
21
|
childrenNo: number
|
|
22
22
|
babiesNo: number
|
|
23
|
+
/**
|
|
24
|
+
* Edited: Claude - Date: 2026-08-29
|
|
25
|
+
*
|
|
26
|
+
* Open "From" on a departure city near the visitor instead of the
|
|
27
|
+
* brand's configured default. Only for a FRESH form (the homepage, the
|
|
28
|
+
* bare /bus-lines page) - never for the collapsed form on a results
|
|
29
|
+
* page, where the pair on screen is the one being searched and must not
|
|
30
|
+
* be quietly rewritten under the visitor.
|
|
31
|
+
*/
|
|
32
|
+
suggest?: boolean
|
|
23
33
|
t: TFunction<'common'>
|
|
24
34
|
onSearch: (data: RoutesSearchForm) => void
|
|
25
35
|
}
|
|
26
36
|
|
|
27
|
-
const RoutesSearch = ({ type, from, to, departure, _return, adultsNo, childrenNo, babiesNo, t, onSearch }: Props): JSX.Element => {
|
|
37
|
+
const RoutesSearch = ({ type, from, to, departure, _return, adultsNo, childrenNo, babiesNo, suggest = false, t, onSearch }: Props): JSX.Element => {
|
|
28
38
|
const [ selected, setSelected ] = useState<'departure' | 'return'>(type);
|
|
29
39
|
|
|
30
40
|
// Edited: Ferjolt Ozuni - Date: 2026-08-01
|
|
@@ -54,6 +64,41 @@ const RoutesSearch = ({ type, from, to, departure, _return, adultsNo, childrenNo
|
|
|
54
64
|
|
|
55
65
|
const { data: CitiesData } = useGetSuggestions();
|
|
56
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Where the visitor most likely wants to leave from.
|
|
69
|
+
*
|
|
70
|
+
* Edited: Claude - Date: 2026-08-29
|
|
71
|
+
*
|
|
72
|
+
* Applied ONCE and only while the field is untouched: it is a better
|
|
73
|
+
* opening guess than the brand default, not a correction of anybody's
|
|
74
|
+
* typing. The remount-to-reseed trick is the map picker's, above -
|
|
75
|
+
* Autocomplete is uncontrolled, so changing its defaultValue needs a new
|
|
76
|
+
* key.
|
|
77
|
+
*/
|
|
78
|
+
const { data: suggestedOrigin } = useSuggestedOrigin(suggest);
|
|
79
|
+
|
|
80
|
+
const touchedFrom = useRef<boolean>(false);
|
|
81
|
+
const appliedSuggestion = useRef<boolean>(false);
|
|
82
|
+
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (!suggest || appliedSuggestion.current || touchedFrom.current) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!suggestedOrigin || suggestedOrigin === from) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
appliedSuggestion.current = true;
|
|
93
|
+
|
|
94
|
+
setPicked(previous => ({ ...previous, from: suggestedOrigin }));
|
|
95
|
+
|
|
96
|
+
setValue('from', suggestedOrigin);
|
|
97
|
+
|
|
98
|
+
onDeparture(suggestedOrigin);
|
|
99
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
100
|
+
}, [ suggestedOrigin ]);
|
|
101
|
+
|
|
57
102
|
/**
|
|
58
103
|
* Tell the date picker which clock this journey starts on.
|
|
59
104
|
*
|
|
@@ -158,7 +203,12 @@ const RoutesSearch = ({ type, from, to, departure, _return, adultsNo, childrenNo
|
|
|
158
203
|
validation="required"
|
|
159
204
|
t={ t }
|
|
160
205
|
refs={ register }
|
|
161
|
-
onUpdate={ (_, value) => {
|
|
206
|
+
onUpdate={ (_, value) => {
|
|
207
|
+
touchedFrom.current = true;
|
|
208
|
+
|
|
209
|
+
setValue('from', value);
|
|
210
|
+
onDeparture(String(value));
|
|
211
|
+
} }
|
|
162
212
|
/>
|
|
163
213
|
|
|
164
214
|
<Picker t={ t } onPick={ (slug) => onPick('from', slug) } />
|
package/package.json
CHANGED
package/services.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useSuspenseQuery, UseSuspenseQueryResult } from '@tanstack/react-query';
|
|
1
|
+
import { useQuery, useSuspenseQuery, UseQueryResult, UseSuspenseQueryResult } from '@tanstack/react-query';
|
|
2
2
|
import { apiClient } from '@autobusal/providers';
|
|
3
3
|
import { CityData } from '@autobusal/providers/types/locations';
|
|
4
4
|
|
|
@@ -13,4 +13,37 @@ export const useGetSuggestions = (): UseSuspenseQueryResult<CityData[]> => (
|
|
|
13
13
|
))
|
|
14
14
|
)
|
|
15
15
|
})
|
|
16
|
-
);
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The departure city to open the form on, for THIS visitor.
|
|
20
|
+
*
|
|
21
|
+
* Claude - Date: 2026-08-29
|
|
22
|
+
*
|
|
23
|
+
* obtapi answers with the origin serving the most destinations in the
|
|
24
|
+
* visitor's own country - see Libraries\Geo\Origin, and note the
|
|
25
|
+
* precision is the COUNTRY: the geo database is country-level, so this is
|
|
26
|
+
* "somewhere you can actually leave from near you", not "your city".
|
|
27
|
+
*
|
|
28
|
+
* A plain useQuery, not a suspense one: the form must render immediately
|
|
29
|
+
* with the brand's default and improve on it when the answer arrives -
|
|
30
|
+
* suspending the whole search box on a geolocation lookup would be a far
|
|
31
|
+
* worse trade than a slightly-late pre-fill. Null (no database, a private
|
|
32
|
+
* address, a country we sell nothing from) simply leaves the default.
|
|
33
|
+
*/
|
|
34
|
+
export const useSuggestedOrigin = (enabled: boolean): UseQueryResult<string | null> => (
|
|
35
|
+
useQuery({
|
|
36
|
+
queryKey: ['search-suggested-origin'],
|
|
37
|
+
enabled,
|
|
38
|
+
// the answer only changes with the brand's inventory, and the visitor's
|
|
39
|
+
// country does not change mid-session
|
|
40
|
+
staleTime: 1000 * 60 * 30,
|
|
41
|
+
retry: false,
|
|
42
|
+
queryFn: async () => (
|
|
43
|
+
await apiClient
|
|
44
|
+
.get('/api/geo/origin')
|
|
45
|
+
.then(response => response?.data?.from ?? null)
|
|
46
|
+
.catch(() => null)
|
|
47
|
+
)
|
|
48
|
+
})
|
|
49
|
+
);
|