@autobusal/routes-search 1.3.3 → 1.4.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/RoutesSearch.tsx +54 -4
- package/package.json +5 -2
- package/services.ts +35 -2
- package/styles.ts +32 -0
- package/CHANGELOG.md +0 -41
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
|
+
);
|
package/styles.ts
CHANGED
|
@@ -4,12 +4,32 @@ export const Options = styled.div`
|
|
|
4
4
|
display: flex;
|
|
5
5
|
gap: 15px;
|
|
6
6
|
margin-bottom: 20px;
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* Claude - 2026-09-16 (audit): 44px controls under a thumb. The trip-type
|
|
10
|
+
* buttons are the shared 32px "medium", the fields below are the theme's
|
|
11
|
+
* 38px and the search button its 38px default; common is not this
|
|
12
|
+
* package's to edit, so the phone sizes are set from the containers.
|
|
13
|
+
* min-height beats the global height, and the desktop form is untouched.
|
|
14
|
+
*/
|
|
15
|
+
@media (max-width: 639px) {
|
|
16
|
+
& > button {
|
|
17
|
+
height: 44px;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
7
20
|
`;
|
|
8
21
|
|
|
9
22
|
export const Columns = styled.div`
|
|
10
23
|
display: flex;
|
|
11
24
|
flex-wrap: wrap;
|
|
12
25
|
gap: 15px;
|
|
26
|
+
|
|
27
|
+
// the 44px phone fields - see Options above
|
|
28
|
+
@media (max-width: 639px) {
|
|
29
|
+
input:not([type='checkbox']):not([type='radio']), select {
|
|
30
|
+
min-height: 44px;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
13
33
|
`;
|
|
14
34
|
|
|
15
35
|
export const Column = styled.div`
|
|
@@ -43,6 +63,13 @@ export const Action = styled.div`
|
|
|
43
63
|
justify-content: center;
|
|
44
64
|
align-items: end;
|
|
45
65
|
|
|
66
|
+
// the 44px phone search button - see Options above
|
|
67
|
+
@media (max-width: 639px) {
|
|
68
|
+
& > button {
|
|
69
|
+
height: 44px;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
46
73
|
@media (min-width: 640px) {
|
|
47
74
|
justify-content: right;
|
|
48
75
|
}
|
|
@@ -60,6 +87,11 @@ export const PickerContainer = styled.div`
|
|
|
60
87
|
height: 38px;
|
|
61
88
|
display: flex;
|
|
62
89
|
align-items: center;
|
|
90
|
+
|
|
91
|
+
// the pin stays centred in the taller phone field - see Options above
|
|
92
|
+
@media (max-width: 639px) {
|
|
93
|
+
height: 44px;
|
|
94
|
+
}
|
|
63
95
|
`;
|
|
64
96
|
|
|
65
97
|
export const PickerField = styled.div`
|
package/CHANGELOG.md
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## 1.3.3 (2026-08-29)
|
|
4
|
-
|
|
5
|
-
- 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.
|
|
6
|
-
|
|
7
|
-
## 1.2.0
|
|
8
|
-
|
|
9
|
-
**The date picker follows the departure city.** Choosing a "from" city now sets the picker's floor to that city's timezone, and a pre-filled form (a shared link, or the search form kept on the results page) sets it on mount rather than waiting for a keystroke.
|
|
10
|
-
|
|
11
|
-
An unknown city *clears* the zone rather than keeping the last one — silently flooring a new search on the previous journey's clock is the subtler version of the same bug.
|
|
12
|
-
|
|
13
|
-
All notable changes to `@autobusal/routes-search` are documented here. This
|
|
14
|
-
project adheres to [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
|
|
15
|
-
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
16
|
-
|
|
17
|
-
## [1.1.1] - 2026-08-01
|
|
18
|
-
|
|
19
|
-
### Fixed
|
|
20
|
-
|
|
21
|
-
- **The return date could be set before the departure date.** The picker
|
|
22
|
-
floored at today, so an invalid pair was selectable and only refused by
|
|
23
|
-
an alert after pressing search. It now floors at whatever departure
|
|
24
|
-
currently holds, and follows it when that changes.
|
|
25
|
-
|
|
26
|
-
## [1.1.0] - 2026-07-25
|
|
27
|
-
|
|
28
|
-
### Fixed
|
|
29
|
-
|
|
30
|
-
- **Recovered as canonical source.** This package was published to npm and
|
|
31
|
-
in active use, but had no source anywhere in this monorepo (no
|
|
32
|
-
`packages/routes-search` directory, no git history) - the only copy of its
|
|
33
|
-
code existed as the published npm artifact. Recreated here,
|
|
34
|
-
byte-for-byte identical to the published 1.1.0 tarball (verified via
|
|
35
|
-
`diff` against the installed `node_modules/@autobusal/routes-search`), so it
|
|
36
|
-
can be edited and republished like every other package instead of
|
|
37
|
-
requiring reverse-engineering from `node_modules` or the npm tarball.
|
|
38
|
-
History prior to this point is not recoverable - no git log,
|
|
39
|
-
changelog, or other record of it exists.
|
|
40
|
-
|
|
41
|
-
Authored by Ferjolt Ozuni.
|