@autobusal/common 1.33.1 → 1.33.3

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,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.33.3
4
+
5
+ ### Added
6
+
7
+ - **`RouteItem` shows a "from" price** next to the route name/code - the lowest fare across the route's own `prices`, using the row's own pre-formatted `adult_display` (same currency/decimal convention as everywhere else). Both `browse` and `view` modes get it; a route with no prices at all shows nothing, same as it always could.
8
+
9
+ ## 1.33.2
10
+
11
+ ### Added
12
+
13
+ - **`Meta` now defaults `og:image`/`twitter:image` to the label's own homepage hero photo** (`{settings.url}/home/background-light.jpg`) when a page doesn't pass its own `image`. Most of the ~136 call sites never had a share-preview image at all, so a link pasted into a chat assistant or social post previewed with nothing. A page with something more specific (an operator's logo, a blog post's own photo) still passes its own `image` and wins - this only fills the gap.
3
14
 
4
15
  ## 1.33.1
5
16
 
package/Meta.tsx CHANGED
@@ -237,11 +237,38 @@ const useAlternates = (noIndex: boolean, explicitUrl?: string): { locale: string
237
237
  ];
238
238
  };
239
239
 
240
+ /**
241
+ * Edited: Ferjolt Ozuni - Date: 2026-08-11
242
+ *
243
+ * A default og:image/twitter:image for whichever of Meta's ~136 call
244
+ * sites never passes one - route detail, Help, blog index, and most
245
+ * static pages had NO share-preview image at all, so a link pasted into
246
+ * a chat assistant or social post previewed with nothing. Rather than
247
+ * hunt down every call site individually, the fallback lives here: the
248
+ * same hero photo the homepage itself uses (`home/background-light.jpg`,
249
+ * already uploaded per-label - see RoutesOrder.tsx's identical asset
250
+ * path), which every label already has. The DARK variant is deliberately
251
+ * not used here regardless of the visitor's own theme - a share preview
252
+ * is generated by whatever unpacks the URL server-side, not by this
253
+ * visitor's browser, so there is no "current theme" to match; picking
254
+ * one consistently is more correct than guessing.
255
+ *
256
+ * A page with something more specific to show (an operator's logo, a
257
+ * blog post's own photo) still passes its own `image` and wins - this
258
+ * only fills the gap when nothing more specific exists.
259
+ */
260
+ const useDefaultImage = (explicit?: string): (string | undefined) => {
261
+ const { data: settings } = useGetSettings();
262
+
263
+ return explicit ?? (settings ? `${ settings.url }/home/background-light.jpg` : undefined);
264
+ };
265
+
240
266
  const Meta = ({ title, keywords, description, image, url, type = 'website', noIndex = false, children }: Props): JSX.Element => {
241
267
  const fullTitle = withBrand(title);
242
268
  const canonicalUrl = useCanonicalUrl(url);
243
269
  const location = useLocation();
244
270
  const robots = noIndex ? 'noindex, nofollow' : 'index, follow';
271
+ const resolvedImage = useDefaultImage(image);
245
272
 
246
273
  const alternates = useAlternates(noIndex, url);
247
274
 
@@ -255,13 +282,13 @@ const Meta = ({ title, keywords, description, image, url, type = 'website', noIn
255
282
  { selector: 'meta[property="og:type"]', attr: 'content', value: type },
256
283
  { selector: 'meta[property="og:title"]', attr: 'content', value: fullTitle },
257
284
  { selector: 'meta[property="og:description"]', attr: 'content', value: description },
258
- { selector: 'meta[property="og:image"]', attr: 'content', value: image },
285
+ { selector: 'meta[property="og:image"]', attr: 'content', value: resolvedImage },
259
286
  { selector: 'meta[property="og:url"]', attr: 'content', value: canonicalUrl },
260
287
  { selector: 'meta[property="og:site_name"]', attr: 'content', value: brand },
261
288
  { selector: 'meta[name="twitter:card"]', attr: 'content', value: 'summary_large_image' },
262
289
  { selector: 'meta[name="twitter:title"]', attr: 'content', value: fullTitle },
263
290
  { selector: 'meta[name="twitter:description"]', attr: 'content', value: description },
264
- { selector: 'meta[name="twitter:image"]', attr: 'content', value: image }
291
+ { selector: 'meta[name="twitter:image"]', attr: 'content', value: resolvedImage }
265
292
  ]);
266
293
  usePageviewTracking(location.pathname + location.search, fullTitle);
267
294
 
@@ -284,7 +311,7 @@ const Meta = ({ title, keywords, description, image, url, type = 'website', noIn
284
311
  <meta property="og:type" content={ type } />
285
312
  <meta property="og:title" content={ fullTitle } />
286
313
  { description && <meta property="og:description" content={ description } /> }
287
- { image && <meta property="og:image" content={ image } /> }
314
+ { resolvedImage && <meta property="og:image" content={ resolvedImage } /> }
288
315
  <meta property="og:url" content={ canonicalUrl } />
289
316
  { brand && <meta property="og:site_name" content={ brand } /> }
290
317
 
@@ -292,7 +319,7 @@ const Meta = ({ title, keywords, description, image, url, type = 'website', noIn
292
319
  <meta name="twitter:card" content="summary_large_image" />
293
320
  <meta name="twitter:title" content={ fullTitle } />
294
321
  { description && <meta name="twitter:description" content={ description } /> }
295
- { image && <meta name="twitter:image" content={ image } /> }
322
+ { resolvedImage && <meta name="twitter:image" content={ resolvedImage } /> }
296
323
 
297
324
  { children }
298
325
  </>
@@ -2,7 +2,7 @@ import { useState } from 'react';
2
2
  import { TFunction } from 'i18next';
3
3
  import { HiOutlineInformationCircle, HiOutlineClock, HiArrowCircleRight } from 'react-icons/hi';
4
4
  import More from './More/More';
5
- import { Container, About, Image, Logo, Information, Name, Code, ButtonMore, Locations, Location, City, Iso, Date, Arrow, Order, LinkOrder } from './styles';
5
+ import { Container, About, Image, Logo, Information, Name, Code, Price, ButtonMore, Locations, Location, City, Iso, Date, Arrow, Order, LinkOrder } from './styles';
6
6
  import { RouteData } from '@autobusal/providers/types/routes';
7
7
 
8
8
  interface Props {
@@ -16,6 +16,19 @@ const RouteItem = ({ type, route, t }: Props): JSX.Element => {
16
16
 
17
17
  const lastLocation = route.locations.length - 1;
18
18
 
19
+ // Edited: Ferjolt Ozuni - Date: 2026-08-11
20
+ // The cheapest fare on this route, same "from" semantics as everywhere
21
+ // else a lowest price is shown (Facts.price_from, results-page cards) -
22
+ // `prices` is one row per from/to leg the route sells, not one row for
23
+ // the whole journey, so the honest "from" figure is the minimum across
24
+ // all of them. Uses the row's OWN `adult_display` rather than
25
+ // reformatting `adult` itself - the server already applied this label's
26
+ // currency symbol/decimal convention, and re-deriving that here would
27
+ // risk disagreeing with every other price on the site.
28
+ const cheapest = route.prices.length > 0
29
+ ? route.prices.reduce((min, price) => (price.adult < min.adult ? price : min))
30
+ : null;
31
+
19
32
  return (
20
33
  <Container className="box">
21
34
  <About>
@@ -30,6 +43,8 @@ const RouteItem = ({ type, route, t }: Props): JSX.Element => {
30
43
 
31
44
  <Code>{ route.code }</Code>
32
45
 
46
+ { cheapest && <Price>{ t('route_item.price_from', { price: cheapest.adult_display }) }</Price> }
47
+
33
48
  <ButtonMore type="button" onClick={ () => setShow(!show) }>
34
49
  <HiOutlineInformationCircle />
35
50
  { t('route_item.info') }
@@ -60,6 +60,12 @@ export const Code = styled.div`
60
60
  font-size: ${ props => props.theme.size.xs };
61
61
  `;
62
62
 
63
+ export const Price = styled.div`
64
+ font-weight: 700;
65
+ color: ${ props => props.theme.primary.normal };
66
+ font-size: ${ props => props.theme.size.s };
67
+ `;
68
+
63
69
  export const ButtonMore = styled.button`
64
70
  display: flex;
65
71
  align-items: center;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autobusal/common",
3
- "version": "1.33.1",
3
+ "version": "1.33.3",
4
4
  "author": "Ferjolt Ozuni",
5
5
  "type": "module",
6
6
  "main": "index.ts"