@autobusal/common 1.9.0 → 1.11.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/Avatar.tsx +3 -2
- package/CompanyItem/styles.ts +3 -2
- package/RouteItem/styles.ts +7 -1
- package/UnderConstruction/UnderConstruction.tsx +106 -0
- package/UnderConstruction/styles.ts +115 -0
- package/index.ts +2 -0
- package/package.json +1 -1
- package/styles.ts +51 -0
package/Avatar.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import styled from 'styled-components';
|
|
2
2
|
import { OperatorData } from '@autobusal/providers/types/operators';
|
|
3
3
|
import { AgentData } from '@autobusal/providers/types/agents';
|
|
4
|
+
import { logoBox } from './styles';
|
|
4
5
|
|
|
5
6
|
interface Props {
|
|
6
7
|
display?: OperatorData | AgentData
|
|
@@ -19,8 +20,8 @@ const Container = styled.div`
|
|
|
19
20
|
gap: 10px;
|
|
20
21
|
|
|
21
22
|
& > img {
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
${ logoBox('80px', '48px') }
|
|
24
|
+
|
|
24
25
|
border-radius: ${ props => props.theme.borderRadius };
|
|
25
26
|
}
|
|
26
27
|
`;
|
package/CompanyItem/styles.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import styled, { css } from 'styled-components';
|
|
2
2
|
import { Link } from 'react-router-dom';
|
|
3
|
+
import { logoBox } from '../styles';
|
|
3
4
|
|
|
4
5
|
export const Item = styled.div<{
|
|
5
6
|
$column: boolean
|
|
@@ -41,8 +42,8 @@ const LogoCss = css`
|
|
|
41
42
|
justify-content: center;
|
|
42
43
|
|
|
43
44
|
& > img {
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
${ logoBox('120px', '72px') }
|
|
46
|
+
|
|
46
47
|
border-radius: ${ props => props.theme.borderRadius };
|
|
47
48
|
}
|
|
48
49
|
`;
|
package/RouteItem/styles.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import styled, { css } from 'styled-components';
|
|
2
2
|
import { Link } from 'react-router-dom';
|
|
3
|
+
import { logoFit } from '../styles';
|
|
3
4
|
|
|
4
5
|
export const Container = styled.div`
|
|
5
6
|
padding: 10px;
|
|
@@ -29,8 +30,13 @@ export const Logo = styled(Link)`
|
|
|
29
30
|
border-radius: ${ props => props.theme.borderRadius };
|
|
30
31
|
|
|
31
32
|
& > img {
|
|
32
|
-
|
|
33
|
+
${ logoFit }
|
|
34
|
+
|
|
35
|
+
/* the container carries aspect-ratio: 1.35 - filling it completely is
|
|
36
|
+
what lets object-fit letterbox inside that shape, instead of the
|
|
37
|
+
image dictating its own height and spilling out of it */
|
|
33
38
|
width: 100%;
|
|
39
|
+
height: 100%;
|
|
34
40
|
}
|
|
35
41
|
`;
|
|
36
42
|
|
|
@@ -0,0 +1,106 @@
|
|
|
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 loads, if the brand configured a container. That is
|
|
27
|
+
* deliberate: a pre-launch page is exactly when a brand wants to measure
|
|
28
|
+
* interest, and it is the only analytics that runs here since the app
|
|
29
|
+
* itself never boots.
|
|
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
|
+
useEffect(() => {
|
|
55
|
+
const container = construction?.gtm_id;
|
|
56
|
+
|
|
57
|
+
if (!container) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Guard against a double insert - this component can re-render, and two
|
|
62
|
+
// GTM containers on one page double-count everything they measure.
|
|
63
|
+
if (document.getElementById('gtm-container')) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
(window as any).dataLayer = (window as any).dataLayer || [];
|
|
68
|
+
(window as any).dataLayer.push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' });
|
|
69
|
+
|
|
70
|
+
const script = document.createElement('script');
|
|
71
|
+
script.id = 'gtm-container';
|
|
72
|
+
script.async = true;
|
|
73
|
+
script.src = `https://www.googletagmanager.com/gtm.js?id=${ encodeURIComponent(container) }`;
|
|
74
|
+
document.head.appendChild(script);
|
|
75
|
+
}, [construction?.gtm_id]);
|
|
76
|
+
|
|
77
|
+
const title = construction?.title || t('under_construction.title', { ns: 'common', company });
|
|
78
|
+
|
|
79
|
+
const message = construction?.message || t('under_construction.message', { ns: 'common' });
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<Screen>
|
|
83
|
+
<Content>
|
|
84
|
+
{ settings?.url && (
|
|
85
|
+
// the brand's own light-theme logo, same asset the site header
|
|
86
|
+
// uses; hidden rather than showing a broken image if a brand has
|
|
87
|
+
// not uploaded one yet
|
|
88
|
+
<Logo
|
|
89
|
+
src={ `${ settings.url }/logo-light.svg` }
|
|
90
|
+
alt={ company }
|
|
91
|
+
onError={ (event) => { event.currentTarget.style.display = 'none'; } }
|
|
92
|
+
/>
|
|
93
|
+
) }
|
|
94
|
+
|
|
95
|
+
<Title>{ title }</Title>
|
|
96
|
+
|
|
97
|
+
<Message>{ message }</Message>
|
|
98
|
+
|
|
99
|
+
</Content>
|
|
100
|
+
|
|
101
|
+
<Road aria-hidden="true" />
|
|
102
|
+
</Screen>
|
|
103
|
+
);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
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
package/styles.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { css } from 'styled-components';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* How an operator/agent logo is displayed, everywhere.
|
|
5
|
+
*
|
|
6
|
+
* Edited: Ferjolt Ozuni - Date: 2026-07-31
|
|
7
|
+
*
|
|
8
|
+
* Logos are uploaded by operators at whatever size and shape they happen to
|
|
9
|
+
* have - tall badges, wide wordmarks, squares. Every place that rendered
|
|
10
|
+
* one set a width (or `width: 100%`) and no height and no `object-fit`, so
|
|
11
|
+
* the image was left to take whatever height its own ratio implied: inside
|
|
12
|
+
* a fixed or aspect-ratio'd box that meant it overflowed and was visibly
|
|
13
|
+
* cropped, and in a flex row it stretched its neighbours around.
|
|
14
|
+
*
|
|
15
|
+
* `object-fit: contain` is the whole fix. It scales the image down until it
|
|
16
|
+
* fits INSIDE the box in its own aspect ratio, never cropping and never
|
|
17
|
+
* distorting. Whatever room is left over stays empty - deliberately
|
|
18
|
+
* transparent rather than a colour, so the logo sits on whatever the
|
|
19
|
+
* surrounding card already uses and dark and light themes both work without
|
|
20
|
+
* a second rule.
|
|
21
|
+
*
|
|
22
|
+
* The box needs BOTH dimensions for this to mean anything: with only a
|
|
23
|
+
* width there is no leftover space for `contain` to preserve, and the
|
|
24
|
+
* behaviour collapses back to the original bug. Callers pass the size.
|
|
25
|
+
*
|
|
26
|
+
* `object-position: center` is explicit rather than relied upon - it is the
|
|
27
|
+
* default, but the whole point here is that nothing about the framing is
|
|
28
|
+
* left to chance.
|
|
29
|
+
*/
|
|
30
|
+
export const logoFit = css`
|
|
31
|
+
display: block;
|
|
32
|
+
object-fit: contain;
|
|
33
|
+
object-position: center;
|
|
34
|
+
|
|
35
|
+
/* no fill, so the logo adopts whatever surface it is placed on */
|
|
36
|
+
background: transparent;
|
|
37
|
+
`;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A complete logo box: fits the image inside the given dimensions without
|
|
41
|
+
* cropping or stretching it.
|
|
42
|
+
*
|
|
43
|
+
* Pass the size the layout wants; the image keeps its own proportions
|
|
44
|
+
* within it.
|
|
45
|
+
*/
|
|
46
|
+
export const logoBox = (width: string, height: string) => css`
|
|
47
|
+
${ logoFit }
|
|
48
|
+
|
|
49
|
+
width: ${ width };
|
|
50
|
+
height: ${ height };
|
|
51
|
+
`;
|