@autobusal/common 1.21.0 → 1.24.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 CHANGED
@@ -1,11 +1,51 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.24.0
4
+
5
+ **`Policy` is now `Information`.**
6
+
7
+ - `Policy` → `Information`, `useGetPolicy` → `useGetInformation`, and the endpoint behind it moves from `/api/routes/policy` to `/api/routes/information`. **Breaking**: update the import.
8
+ - `RouteItem`'s `ReadPolicy` → `ReadInformation`, reading `route_item.information`.
9
+ - Translation keys: `policy.title` → `information.title`, `route_item.policy` → `route_item.information`.
10
+
11
+ The rename follows the Flexible Ticket model, where what a ticket permits comes from the add-on and the operator's own windows — not from this box.
12
+
3
13
  All notable changes to `@autobusal/common` are documented here. This project follows
4
14
  [Keep a Changelog](https://keepachangelog.com/) and [Semantic Versioning](https://semver.org/).
5
15
 
6
16
  > Note: 1.10.0 through 1.15.1 were published without changelog entries. The
7
17
  > gap is left as-is rather than reconstructed after the fact.
8
18
 
19
+ ## 1.23.0
20
+
21
+ An unbookable date is inert, not merely faded.
22
+
23
+ It was dimmed and otherwise fully interactive: the pointer cursor and the
24
+ hover highlight still invited a click, and the click was answered with a
25
+ browser alert saying the date was invalid. The calendar offered a day, let
26
+ somebody reach for it, then told them off for taking the offer.
27
+
28
+ `pointer-events: none` removes it from the conversation entirely — no cursor
29
+ change, no hover, no click to refuse — alongside the `aria-disabled` and
30
+ `tabIndex=-1` already on the element. Unreachable by mouse, keyboard and
31
+ screen reader alike, and the alert is gone with it.
32
+
33
+ ## 1.22.0
34
+
35
+ The booking calendar floors on the SERVER's date, not the browser's.
36
+
37
+ `new Date()` is the buyer's device clock, which says nothing about where they
38
+ are travelling. Somebody in Shanghai at 06:00 on the 5th, looking at an
39
+ Italian coach that does not leave until the evening of the 4th, had that day
40
+ greyed out — while the server would have sold them the seat quite happily. The
41
+ calendar was refusing a sale the API accepted.
42
+
43
+ It also makes the picker agree with the date strip beside it, which has always
44
+ been built server-side; the two used to disagree about which day came first.
45
+
46
+ A date of birth keeps the local clock, because that one genuinely is about the
47
+ person in front of the screen.
48
+
9
49
  ## 1.21.0
10
50
 
11
51
  `reply` joins the table actions with its own icon and label. The operator
package/Calendar/Days.tsx CHANGED
@@ -19,6 +19,9 @@ const Days = ({ type, value, selected, minDate, onSelected }: Props): JSX.Elemen
19
19
  setCalendar(getCalendar(value));
20
20
  }, [value]);
21
21
 
22
+ // Edited: Ferjolt Ozuni - Date: 2026-08-03
23
+ // Only used to mark which cell is "today" visually - the FLOOR comes from
24
+ // getLimits below, which for a booking picker follows the server.
22
25
  const today = prepareDate(new Date());
23
26
 
24
27
  const limits = getLimits(type);
@@ -19,8 +19,13 @@ const Picker = ({ type, value, selected, minDate, t, onClick }: Props): (JSX.Ele
19
19
  const [ prepared, setPrepared ] = useState<string>(value);
20
20
 
21
21
  const onSelected = (prepared: string, disabled: boolean): void => {
22
+ // Edited: Ferjolt Ozuni - Date: 2026-08-03
23
+ // Silently, not with an alert. A disabled day is now inert in CSS as
24
+ // well (see Day in styles), so this can only be reached by something
25
+ // focusing the element programmatically - and a control that cannot be
26
+ // used should not interrupt somebody to say so.
22
27
  if (disabled) {
23
- return alert(t('general:errors.invalid.date'));
28
+ return;
24
29
  }
25
30
 
26
31
  onClick(prepared);
@@ -51,8 +51,26 @@ export const Day = styled.div<{
51
51
  color: ${ props => props.theme.primary.normal };
52
52
  ` }
53
53
 
54
+ /**
55
+ * Edited: Ferjolt Ozuni - Date: 2026-08-03
56
+ *
57
+ * A date that cannot be booked is INERT, not merely faded.
58
+ *
59
+ * It was dimmed and otherwise fully interactive: the pointer cursor and
60
+ * the hover highlight still invited a click, and the click was answered
61
+ * with a browser alert saying the date was invalid. So the calendar
62
+ * offered a day, let somebody reach for it, and then told them off for
63
+ * taking the offer.
64
+ *
65
+ * pointer-events: none removes it from the conversation entirely - no
66
+ * cursor change, no hover, no click to refuse. Together with the
67
+ * aria-disabled and tabIndex=-1 already on the element, it is unreachable
68
+ * by mouse, keyboard and screen reader alike.
69
+ */
54
70
  ${ props => props.$disabled === true && css`
55
71
  opacity: .3;
72
+ cursor: default;
73
+ pointer-events: none;
56
74
  ` }
57
75
 
58
76
  ${ props => props.$selected && css`
@@ -1,8 +1,48 @@
1
1
  import { createDate } from '@autobusal/utilities';
2
2
  import { CalendarLimit, DatesLimit, CalendarData } from '../types';
3
3
 
4
+ /**
5
+ * The earliest bookable date, as the server reckons it.
6
+ *
7
+ * Edited: Ferjolt Ozuni - Date: 2026-08-03
8
+ *
9
+ * Set once at startup from the settings payload. A module-level value rather
10
+ * than a prop threaded through four components: every calendar in the app
11
+ * wants the same answer, and the alternative was passing it through Picker
12
+ * and Days to reach the one function that needs it.
13
+ */
14
+ let serverToday: Date | null = null;
15
+
16
+ export const setServerToday = (value?: string): void => {
17
+ if (!value) {
18
+ return;
19
+ }
20
+
21
+ const [ day, month, year ] = value.split('/').map(Number);
22
+
23
+ if (!day || !month || !year) {
24
+ return;
25
+ }
26
+
27
+ serverToday = new Date(year, month - 1, day, 0, 0, 0, 0);
28
+ };
29
+
4
30
  export const getLimits = (type: ('picker' | 'dob' | 'date')): CalendarLimit => {
5
- const today = new Date();
31
+ // Edited: Ferjolt Ozuni - Date: 2026-08-03
32
+ //
33
+ // The BOOKING floor comes from the server; a date of birth does not.
34
+ //
35
+ // `new Date()` is the buyer's device clock, which has nothing to do with
36
+ // where they are travelling: somebody in Shanghai at 06:00 on the 5th
37
+ // looking at an Italian coach leaving on the evening of the 4th had that
38
+ // day greyed out, because their phone said it was past. The server would
39
+ // have sold them the seat; the calendar would not let them ask.
40
+ //
41
+ // A date of birth genuinely is about the person in front of the screen,
42
+ // so that one keeps the local clock.
43
+ const today = (type === 'picker' && serverToday)
44
+ ? new Date(serverToday.getTime())
45
+ : new Date();
6
46
 
7
47
  today.setHours(0, 0, 0, 0);
8
48
 
@@ -1,7 +1,7 @@
1
1
  import { TFunction } from 'i18next';
2
2
  import { sanitizeHtml } from '@autobusal/utilities';
3
3
  import Modal from '../Modal/Modal';
4
- import { useGetPolicy } from './services';
4
+ import { useGetInformation } from './services';
5
5
 
6
6
  interface Props {
7
7
  id: number
@@ -9,20 +9,20 @@ interface Props {
9
9
  onClose: () => void
10
10
  }
11
11
 
12
- const Policy = ({ id, t, onClose }: Props): JSX.Element => {
13
- const { data, isFetching } = useGetPolicy(id);
12
+ const Information = ({ id, t, onClose }: Props): JSX.Element => {
13
+ const { data, isFetching } = useGetInformation(id);
14
14
 
15
15
  return (
16
16
  <Modal
17
17
  loading={ isFetching }
18
18
  width={ 750 }
19
- title={ t('policy.title') }
19
+ title={ t('information.title') }
20
20
  content={
21
- <div dangerouslySetInnerHTML={{ __html: sanitizeHtml(data?.policies) }} />
21
+ <div dangerouslySetInnerHTML={{ __html: sanitizeHtml(data?.information) }} />
22
22
  }
23
23
  onClose={ onClose }
24
24
  />
25
25
  );
26
26
  };
27
27
 
28
- export default Policy;
28
+ export default Information;
@@ -1,13 +1,13 @@
1
1
  import { useQuery, UseQueryResult } from '@tanstack/react-query';
2
2
  import { apiClient } from '@autobusal/providers';
3
- import { PolicyData } from '@autobusal/providers/types/routes';
3
+ import { InformationData } from '@autobusal/providers/types/routes';
4
4
 
5
- export const useGetPolicy = (id: number): UseQueryResult<PolicyData> => (
5
+ export const useGetInformation = (id: number): UseQueryResult<InformationData> => (
6
6
  useQuery({
7
- queryKey: ['route-policy', { id }],
7
+ queryKey: ['route-information', { id }],
8
8
  queryFn: async () => (
9
9
  await apiClient
10
- .get('/api/routes/policy', {
10
+ .get('/api/routes/information', {
11
11
  params: { id }
12
12
  })
13
13
  .then(response => (
@@ -1,5 +1,5 @@
1
1
  import { TFunction } from 'i18next';
2
- import ReadPolicy from './ReadPolicy';
2
+ import ReadInformation from './ReadInformation';
3
3
  import Schedule from './Schedule';
4
4
  import Transiting from './Transiting';
5
5
  import Douane from './Douane';
@@ -25,7 +25,7 @@ const More = ({ route, t }: Props): JSX.Element => (
25
25
  </Details>
26
26
 
27
27
  <Policy>
28
- <ReadPolicy id={ route.id } t={ t } />
28
+ <ReadInformation id={ route.id } t={ t } />
29
29
  </Policy>
30
30
  </Container>
31
31
  );
@@ -1,7 +1,7 @@
1
1
  import { useState } from 'react';
2
2
  import { TFunction } from 'i18next';
3
3
  import { IoDocumentText } from 'react-icons/io5';
4
- import Policy from '../../Policy/Policy';
4
+ import Information from '../../Information/Information';
5
5
  import { ButtonPolicy } from './styles';
6
6
 
7
7
  interface Props {
@@ -9,19 +9,19 @@ interface Props {
9
9
  t: TFunction<'public'>
10
10
  }
11
11
 
12
- const ReadPolicy = ({ id, t }: Props): JSX.Element => {
12
+ const ReadInformation = ({ id, t }: Props): JSX.Element => {
13
13
  const [ view, setView ] = useState<boolean>(false);
14
14
 
15
15
  return (
16
16
  <>
17
17
  <ButtonPolicy type="button" onClick={ () => setView(!view) }>
18
18
  <IoDocumentText />
19
- { t('route_item.policy') }
19
+ { t('route_item.information') }
20
20
  </ButtonPolicy>
21
21
 
22
- { view && <Policy id={ id } t={ t } onClose={ () => setView(false) } /> }
22
+ { view && <Information id={ id } t={ t } onClose={ () => setView(false) } /> }
23
23
  </>
24
24
  );
25
25
  };
26
26
 
27
- export default ReadPolicy;
27
+ export default ReadInformation;
package/index.ts CHANGED
@@ -26,7 +26,8 @@ import Paragraph from './Loading/Paragraph';
26
26
  import Passengers from './Passengers/Passengers';
27
27
  import Password from './Password/Password';
28
28
  import Rating from './Rating/Rating';
29
- import Policy from './Policy/Policy';
29
+ import { setServerToday } from './Calendar/utilities/settings';
30
+ import Information from './Information/Information';
30
31
  import Report from './Report/Report';
31
32
  import RouteFeature from './RouteFeature/RouteFeature';
32
33
  import Required from './Required/Required';
@@ -75,7 +76,8 @@ export {
75
76
  Passengers,
76
77
  Password,
77
78
  Rating,
78
- Policy,
79
+ setServerToday,
80
+ Information,
79
81
  Report,
80
82
  RouteFeature,
81
83
  Required,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autobusal/common",
3
- "version": "1.21.0",
3
+ "version": "1.24.0",
4
4
  "author": "Ferjolt Ozuni",
5
5
  "type": "module",
6
6
  "main": "index.ts"