@autobusal/common 1.6.0 → 1.8.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
@@ -3,6 +3,26 @@
3
3
  All notable changes to `@autobusal/common` are documented here. This project follows
4
4
  [Keep a Changelog](https://keepachangelog.com/) and [Semantic Versioning](https://semver.org/).
5
5
 
6
+ ## [1.8.0] - 2026-07-30
7
+
8
+ ### Added
9
+
10
+ - **GA4 SPA pageview tracking in `Meta`.** gtag.js only auto-sends a
11
+ page_view on its initial `config` call - every client-side route change
12
+ was invisible to analytics (pairs with `@autobusal/providers` 1.6.2, which
13
+ disables the automatic one). Meta now sends an explicit pageview
14
+ (`location.pathname + location.search`, current title) whenever the path
15
+ changes, since it's already rendered on nearly every real page. Excluded
16
+ from the effect's dependencies on purpose: a title-only change (e.g. a
17
+ language switch) does not fire a new pageview, only an actual navigation
18
+ does.
19
+
20
+ ## [1.7.0] - 2026-07-30
21
+
22
+ ### Added
23
+
24
+ - **New `JsonLd` component** - renders a schema.org `<script type="application/ld+json">` block from a plain object, with `<` escaped to `\u003c` so a string value containing "</script>" can't break out of the tag. No hoisting needed (unlike Meta's title/meta/link) - Google explicitly supports structured data anywhere in the document, so this renders in place. First consumers: `@autobusal/providers`' `Preload.tsx` (sitewide Organization + WebSite) and `route-view`/magus's `BlogView.tsx` (BusTrip/BreadcrumbList, Article).
25
+
6
26
  ## [1.6.0] - 2026-07-30
7
27
 
8
28
  ### Added
package/JsonLd.tsx ADDED
@@ -0,0 +1,18 @@
1
+ interface Props {
2
+ data: object
3
+ }
4
+
5
+ // Structured data (schema.org via JSON-LD) is explicitly fine anywhere in the
6
+ // document per Google's own guidance - no need to hoist into <head> the way
7
+ // Meta.tsx's title/meta/link tags are. Escaping `<` prevents any string value
8
+ // (an article title, a company name) that happens to contain "</script>"
9
+ // from breaking out of the script tag - `<` is a valid JSON escape that
10
+ // every JSON-LD parser (including Google's) correctly reads back as `<`.
11
+ const JsonLd = ({ data }: Props): JSX.Element => (
12
+ <script
13
+ type="application/ld+json"
14
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(data).replace(/</g, '\\u003c') }}
15
+ />
16
+ );
17
+
18
+ export default JsonLd;
package/Meta.tsx CHANGED
@@ -1,5 +1,7 @@
1
1
  import { useEffect } from 'react';
2
2
  import { useLocation } from 'react-router-dom';
3
+ import ReactGA from 'react-ga4';
4
+ import { useGetSettings } from '@autobusal/providers/services';
3
5
 
4
6
  interface Props {
5
7
  title: string
@@ -70,11 +72,37 @@ const useCanonicalUrl = (explicit?: string): string => {
70
72
  return explicit ?? `${ window.location.origin }${ location.pathname }`;
71
73
  };
72
74
 
75
+ // GA4's gtag.js only auto-sends a page_view on its initial `config` call
76
+ // (see providers/Setup/analytics.ts, which disables that and initializes
77
+ // with send_page_view: false) - every client-side route change in this SPA
78
+ // was otherwise invisible to analytics. Meta is the one component rendered
79
+ // on nearly every real page (136 call sites across both apps), already
80
+ // tracks the canonical path via useLocation, and mounts fresh on every
81
+ // navigation - the natural place to fire the pageview React Router itself
82
+ // doesn't have a dedicated hook for. The 2-3 truly Meta-less pages (a
83
+ // redirect-only auth callback, an invalid-signature bounce) aren't
84
+ // meaningful page views anyway.
85
+ const usePageviewTracking = (path: string, title: string): void => {
86
+ const { data: settings } = useGetSettings();
87
+ const gaId = settings.preferences.googleAnalyticsID;
88
+
89
+ useEffect(() => {
90
+ if (gaId === null) {
91
+ return;
92
+ }
93
+
94
+ ReactGA.send({ hitType: 'pageview', page: path, title });
95
+ // eslint-disable-next-line react-hooks/exhaustive-deps
96
+ }, [gaId, path]);
97
+ };
98
+
73
99
  const Meta = ({ title, keywords, description, image, url, type = 'website', noIndex = false, children }: Props): JSX.Element => {
74
100
  const fullTitle = withBrand(title);
75
101
  const canonicalUrl = useCanonicalUrl(url);
102
+ const location = useLocation();
76
103
 
77
104
  useSoleTitleOwnership(fullTitle);
105
+ usePageviewTracking(location.pathname + location.search, fullTitle);
78
106
 
79
107
  return (
80
108
  <>
package/index.ts CHANGED
@@ -17,6 +17,7 @@ import File from './File/File';
17
17
  import Gender from './Gender';
18
18
  import { General, Inline, Box, BoxMiddle, GeneralFull } from './Loading/Loading';
19
19
  import IpLogs from './IpLogs/IpLogs';
20
+ import JsonLd from './JsonLd';
20
21
  import Meta from './Meta';
21
22
  import Modal from './Modal/Modal';
22
23
  import NotificationIcon from './Notifications/Icon';
@@ -56,6 +57,7 @@ export {
56
57
  GeneralFull,
57
58
  Inline,
58
59
  IpLogs,
60
+ JsonLd,
59
61
  Meta,
60
62
  Modal,
61
63
  NotificationIcon,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autobusal/common",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
4
4
  "author": "Ferjolt Ozuni",
5
5
  "type": "module",
6
6
  "main": "index.ts"