@autobusal/common 1.10.0 → 1.12.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/Meta.tsx CHANGED
@@ -1,6 +1,6 @@
1
1
  import { useEffect } from 'react';
2
2
  import { useLocation } from 'react-router-dom';
3
- import ReactGA from 'react-ga4';
3
+ import { pageview } from '@autobusal/providers/Setup/analytics';
4
4
  import { useGetSettings } from '@autobusal/providers/services';
5
5
 
6
6
  interface Props {
@@ -124,28 +124,32 @@ const useCanonicalUrl = (explicit?: string): string => {
124
124
  return explicit ?? `${ window.location.origin }${ location.pathname }`;
125
125
  };
126
126
 
127
- // GA4's gtag.js only auto-sends a page_view on its initial `config` call
128
- // (see providers/Setup/analytics.ts, which disables that and initializes
129
- // with send_page_view: false) - every client-side route change in this SPA
130
- // was otherwise invisible to analytics. Meta is the one component rendered
131
- // on nearly every real page (136 call sites across both apps), already
132
- // tracks the canonical path via useLocation, and mounts fresh on every
133
- // navigation - the natural place to fire the pageview React Router itself
134
- // doesn't have a dedicated hook for. The 2-3 truly Meta-less pages (a
135
- // redirect-only auth callback, an invalid-signature bounce) aren't
136
- // meaningful page views anyway.
127
+ // A single-page app never reloads, so GTM's page-load trigger fires exactly
128
+ // once per session - every client-side route change after that is invisible
129
+ // unless it is announced. Meta is the one component rendered on nearly every
130
+ // real page (136 call sites across both apps), already tracks the canonical
131
+ // path via useLocation, and mounts fresh on every navigation - the natural
132
+ // place to fire the pageview React Router itself has no dedicated hook for.
133
+ // The 2-3 truly Meta-less pages (a redirect-only auth callback, an
134
+ // invalid-signature bounce) aren't meaningful page views anyway.
135
+ //
136
+ // Edited: Ferjolt Ozuni - Date: 2026-08-01
137
+ // Pushes to the GTM dataLayer rather than calling GA4 directly. The brand's
138
+ // container decides what to do with it, so switching or adding an analytics
139
+ // product is a change in GTM rather than in this file.
137
140
  const usePageviewTracking = (path: string, title: string): void => {
138
141
  const { data: settings } = useGetSettings();
139
- const gaId = settings.preferences.googleAnalyticsID;
142
+
143
+ const container = settings.analytics?.gtm_id;
140
144
 
141
145
  useEffect(() => {
142
- if (gaId === null) {
146
+ if (!container) {
143
147
  return;
144
148
  }
145
149
 
146
- ReactGA.send({ hitType: 'pageview', page: path, title });
150
+ pageview(path, title);
147
151
  // eslint-disable-next-line react-hooks/exhaustive-deps
148
- }, [gaId, path]);
152
+ }, [container, path]);
149
153
  };
150
154
 
151
155
  const Meta = ({ title, keywords, description, image, url, type = 'website', noIndex = false, children }: Props): JSX.Element => {
@@ -0,0 +1,83 @@
1
+ import { useEffect } from 'react';
2
+ import { TFunction } from 'i18next';
3
+ import { Screen, Content, Logo, Title, Message, Road } from './styles';
4
+ import { useGetSettings } from '@autobusal/providers/services';
5
+
6
+ interface Props {
7
+ t: TFunction<'common'>
8
+ }
9
+
10
+ /**
11
+ * The holding page a brand shows while its site is still being built.
12
+ *
13
+ * Edited: Ferjolt Ozuni - Date: 2026-08-01
14
+ *
15
+ * Rendered INSTEAD of the router, so no real page is reachable while it is
16
+ * on - a visitor who guesses a deep URL gets this too, which is the point.
17
+ *
18
+ * Two things happen alongside the visuals:
19
+ *
20
+ * - crawlers are told not to index. The tag is written directly rather
21
+ * than through <Meta>, because this renders before the app and must not
22
+ * depend on any of it. It also removes any competing robots tag a
23
+ * prerendered snapshot may have baked in, so an older "index" directive
24
+ * cannot win by being first in the document.
25
+ *
26
+ * - Google Tag Manager still runs, because Providers loads it before this
27
+ * renders - a pre-launch page is exactly when a brand wants to measure
28
+ * interest. It uses the brand's ONE container (admin > Analytics), not a
29
+ * separate id of its own; nothing extra is needed here.
30
+ */
31
+ const UnderConstruction = ({ t }: Props): JSX.Element => {
32
+ const { data: settings } = useGetSettings();
33
+
34
+ const construction = settings?.construction;
35
+
36
+ const company = settings?.preferences?.company ?? '';
37
+
38
+ useEffect(() => {
39
+ // Own the robots directive outright - see the note above on why a
40
+ // leftover tag from a prerendered snapshot would otherwise be picked up
41
+ // in preference to this one.
42
+ document.querySelectorAll('meta[name="robots"]').forEach(tag => tag.remove());
43
+
44
+ const robots = document.createElement('meta');
45
+ robots.setAttribute('name', 'robots');
46
+ robots.setAttribute('content', 'noindex, nofollow');
47
+ document.head.appendChild(robots);
48
+
49
+ return () => {
50
+ robots.remove();
51
+ };
52
+ }, []);
53
+
54
+ const title = construction?.title || t('under_construction.title', { ns: 'common', company });
55
+
56
+ const message = construction?.message || t('under_construction.message', { ns: 'common' });
57
+
58
+ return (
59
+ <Screen>
60
+ <Content>
61
+ { settings?.url && (
62
+ // the brand's own light-theme logo, same asset the site header
63
+ // uses; hidden rather than showing a broken image if a brand has
64
+ // not uploaded one yet
65
+ <Logo
66
+ src={ `${ settings.url }/logo-light.svg` }
67
+ alt={ company }
68
+ onError={ (event) => { event.currentTarget.style.display = 'none'; } }
69
+ />
70
+ ) }
71
+
72
+ <Title>{ title }</Title>
73
+
74
+ <Message>{ message }</Message>
75
+
76
+ </Content>
77
+
78
+ <Road aria-hidden="true" />
79
+ </Screen>
80
+ );
81
+ };
82
+
83
+ export default UnderConstruction;
@@ -0,0 +1,115 @@
1
+ import styled, { keyframes } from 'styled-components';
2
+
3
+ /**
4
+ * The holding page shown while a brand is still being built.
5
+ *
6
+ * Edited: Ferjolt Ozuni - Date: 2026-08-01
7
+ *
8
+ * Deliberately self-contained: it renders INSTEAD of the app, before any
9
+ * route or layout, so it cannot lean on the site chrome for anything. It
10
+ * uses the brand's own theme colours so it still looks like the brand
11
+ * rather than a generic maintenance page.
12
+ */
13
+
14
+ const drift = keyframes`
15
+ from { transform: translate3d(0, 0, 0); }
16
+ to { transform: translate3d(-50%, 0, 0); }
17
+ `;
18
+
19
+ const rise = keyframes`
20
+ from { opacity: 0; transform: translateY(14px); }
21
+ to { opacity: 1; transform: translateY(0); }
22
+ `;
23
+
24
+ export const Screen = styled.main`
25
+ position: relative;
26
+ display: flex;
27
+ flex-direction: column;
28
+ align-items: center;
29
+ justify-content: center;
30
+ min-height: 100dvh;
31
+ padding: 40px 20px 0;
32
+ overflow: hidden;
33
+ text-align: center;
34
+ /* Inherit the app's own page background rather than naming a theme key.
35
+ There is no primary.background in the theme - guessing at one fell
36
+ through to primary.neutral, a mid grey that belongs to cards and
37
+ borders, so the holding page came out lighter than the site it stands
38
+ in for. body already carries the brand's real page colour. */
39
+ background: inherit;
40
+ color: ${ props => props.theme.font.normal };
41
+ `;
42
+
43
+ export const Content = styled.div`
44
+ position: relative;
45
+ z-index: 1;
46
+ max-width: 620px;
47
+ animation: ${ rise } 0.6s ease-out both;
48
+ `;
49
+
50
+ export const Logo = styled.img`
51
+ display: block;
52
+ width: 180px;
53
+ height: 64px;
54
+
55
+ /* same rule as everywhere else - never crop or stretch a brand logo */
56
+ object-fit: contain;
57
+ object-position: center;
58
+ margin: 0 auto 32px;
59
+ `;
60
+
61
+ export const Title = styled.h1`
62
+ margin: 0 0 16px;
63
+ font-size: clamp(28px, 5vw, 44px);
64
+ line-height: 1.15;
65
+ `;
66
+
67
+ export const Message = styled.p`
68
+ margin: 0 auto;
69
+ max-width: 46ch;
70
+ font-size: clamp(15px, 2vw, 18px);
71
+ line-height: 1.6;
72
+ color: ${ props => props.theme.font.faded };
73
+ `;
74
+
75
+ /**
76
+ * A slow band of road at the foot of the page. Purely decorative, and
77
+ * hidden from assistive tech - it carries no information.
78
+ */
79
+ export const Road = styled.div`
80
+ position: absolute;
81
+ right: 0;
82
+ bottom: 0;
83
+ left: 0;
84
+ height: 96px;
85
+ opacity: 0.5;
86
+ background: linear-gradient(
87
+ to top,
88
+ ${ props => props.theme.primary.normal }22,
89
+ transparent
90
+ );
91
+
92
+ &::after {
93
+ content: '';
94
+ position: absolute;
95
+ bottom: 34px;
96
+ left: 0;
97
+ width: 200%;
98
+ height: 3px;
99
+ border-radius: 3px;
100
+ background: repeating-linear-gradient(
101
+ to right,
102
+ ${ props => props.theme.primary.normal }66 0 42px,
103
+ transparent 42px 84px
104
+ );
105
+ animation: ${ drift } 6s linear infinite;
106
+ }
107
+
108
+ /* a moving dashed line is exactly the kind of thing that should stop for
109
+ anyone who has asked motion to stop */
110
+ @media (prefers-reduced-motion: reduce) {
111
+ &::after {
112
+ animation: none;
113
+ }
114
+ }
115
+ `;
package/index.ts CHANGED
@@ -31,6 +31,7 @@ import RouteFeature from './RouteFeature/RouteFeature';
31
31
  import RouteItem from './RouteItem/RouteItem';
32
32
  import Row from './Table/Row';
33
33
  import Table from './Table/Table';
34
+ import UnderConstruction from './UnderConstruction/UnderConstruction';
34
35
  import Viewer from './Viewer/Viewer';
35
36
 
36
37
  export {
@@ -71,5 +72,6 @@ export {
71
72
  RouteItem,
72
73
  Row,
73
74
  Table,
75
+ UnderConstruction,
74
76
  Viewer
75
77
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autobusal/common",
3
- "version": "1.10.0",
3
+ "version": "1.12.0",
4
4
  "author": "Ferjolt Ozuni",
5
5
  "type": "module",
6
6
  "main": "index.ts"