Merge branch 'mipca_whitelabel' into staging_environment

This commit is contained in:
csawatzky 2026-01-26 11:51:16 -06:00
commit fbdfebc028
18 changed files with 172 additions and 13 deletions

View file

@ -7,6 +7,43 @@ Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
### White-labeling
Steps to add a new white label
1. Client Instructions:
- supply assets and colours to be used for the white-label (favicon if necessary)
- determine a sub-domain to use as mask, dashboard is preferred (ex: dashboard.example.com)
- add a `CNAME` record pointing their custom subdomain (ex: dashboard.example.com) to brandxtech.ca
- add a login button on their website point to `/login` of their custom subdomain (ex: dashboard.example.com/login)
<!-- frontend steps -->
2. Create a PWA folder in `public` that is named after the client and generate PWA assets using [this](https://realfavicongenerator.net/) website (use the company logo but you might need to use a custom favicon depending on the result)
- https://maskable.app/ also create a maskable icon for PWA
- create a 512x512 png as well for PWA
3. Create a white-label branding folder in `src/assets/whitelabels` and create a `darkLogo.png` and `lightLogo.png` from the client's logo
4. In `src/services/whiteLabel.ts`, add a new `Whitelabel` instance with the required fields and add its hostname mapping to the `whitelabels` map (it is very similar to step one but had some constraints when dealing with the `public` folder)
5. If there are any pages specific to the whitelabel be sure to update the side and bottom navigator
<!-- backend steps -->
6. In `dynamic-config.prod.yml`, add a router entry for the new whitelabel
7. In `pond/http/server.go`, add the sub-domain to the `allowedOrigins`
8. In `pond/whitelabel.go`, add an entry for the new white label
7. Create a new `Auth0` application for the client
- use a image bucket like [postimg](https://postimages.org/) to store their company logo and favicon
- add a whitelabel logo
- allow necessary `origins`, `callback URLs`, and `CORS domains` to the application settings (see the default application as a guideline)
- customize the Universal Login page to handle the new whitelabel
- check if the client ID matches this client's Auth0 application
- change the Universal Login theme using white-label colours and images stored in a bucket (ex: [postimg](https://postimages.org/))
8. Enable the white-label sub-domain for CORS in the backend
- in [http.go](https://gitlab.com/brandx/backend/blob/master/pond/http.go), add the white-label sub-domain to the `AllowedOrigins` list
## Expanding the ESLint configuration ## Expanding the ESLint configuration
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:

4
package-lock.json generated
View file

@ -42,7 +42,7 @@
"mui-tel-input": "^7.0.0", "mui-tel-input": "^7.0.0",
"notistack": "^3.0.1", "notistack": "^3.0.1",
"openweathermap-ts": "^1.2.10", "openweathermap-ts": "^1.2.10",
"protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#staging", "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#master",
"query-string": "^9.2.1", "query-string": "^9.2.1",
"react": "^18.3.1", "react": "^18.3.1",
"react-beautiful-dnd": "^13.1.1", "react-beautiful-dnd": "^13.1.1",
@ -10953,7 +10953,7 @@
}, },
"node_modules/protobuf-ts": { "node_modules/protobuf-ts": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#c9ef7906fd97cda8ef4bd149ec4a796159a7c067", "resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#b05bd05f5e9e9eb66c5afbeeee88b512be3135ef",
"dependencies": { "dependencies": {
"protobufjs": "^6.8.8" "protobufjs": "^6.8.8"
} }

View file

@ -54,7 +54,7 @@
"mui-tel-input": "^7.0.0", "mui-tel-input": "^7.0.0",
"notistack": "^3.0.1", "notistack": "^3.0.1",
"openweathermap-ts": "^1.2.10", "openweathermap-ts": "^1.2.10",
"protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#staging", "protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#master",
"query-string": "^9.2.1", "query-string": "^9.2.1",
"react": "^18.3.1", "react": "^18.3.1",
"react-beautiful-dnd": "^13.1.1", "react-beautiful-dnd": "^13.1.1",

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>#da532c</TileColor>
</tile>
</msapplication>
</browserconfig>

Binary file not shown.

After

Width:  |  Height:  |  Size: 808 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
public/MiPCA/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1,33 @@
{
"name": "MiPCA",
"short_name": "MiPCA",
"icons": [
{
"src": "favicon.ico",
"sizes": "48x48",
"type": "image/x-icon"
},
{
"src": "android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "maskable_icon.png",
"sizes": "196x196",
"type": "image/png",
"purpose": "any maskable"
}
],
"start_url": "/",
"theme_color": "#272727",
"background_color": "#303030",
"display": "standalone",
"orientation": "any"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View file

@ -0,0 +1,50 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="1059.000000pt" height="1059.000000pt" viewBox="0 0 1059.000000 1059.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.14, written by Peter Selinger 2001-2017
</metadata>
<g transform="translate(0.000000,1059.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M7029 6666 c-2 -2 -31 -7 -64 -10 -426 -43 -727 -354 -781 -806 -10
-89 -11 -984 -1 -1090 27 -277 151 -517 340 -659 92 -69 256 -140 366 -157 18
-3 43 -8 55 -10 11 -3 80 -8 151 -11 408 -17 711 161 894 525 39 77 95 242
114 335 l14 67 -36 1 c-174 5 -484 0 -490 -7 -4 -5 -15 -36 -25 -69 -40 -134
-128 -250 -226 -300 -73 -37 -171 -51 -268 -38 -177 23 -289 121 -347 303 -16
49 -18 107 -20 522 -3 508 1 556 51 668 15 33 30 60 34 60 3 0 13 12 20 26 20
36 100 92 167 115 116 41 247 36 353 -14 52 -25 157 -127 182 -178 36 -73 56
-125 63 -161 4 -21 10 -38 14 -38 9 -1 494 -1 512 -1 15 1 15 7 -2 79 -37 158
-60 225 -114 332 -141 281 -357 448 -651 504 -62 11 -296 21 -305 12z"/>
<path d="M0 5298 c0 -1071 3 -1348 13 -1349 55 -4 439 -1 444 4 3 3 6 420 6
927 0 506 2 919 4 917 2 -3 62 -190 134 -418 72 -228 178 -565 236 -749 58
-184 108 -345 110 -357 3 -13 10 -23 17 -24 6 0 107 -1 225 -2 205 -2 214 -1
222 18 9 21 232 743 429 1390 29 94 55 173 59 177 3 4 6 -418 5 -938 -1 -861
0 -944 15 -945 93 -5 426 -2 434 3 9 5 12 346 12 1348 l0 1340 -35 1 c-46 0
-555 0 -601 0 l-35 -1 -28 -97 c-15 -54 -32 -111 -37 -128 -5 -16 -40 -140
-78 -275 -38 -135 -101 -360 -141 -500 -40 -140 -81 -284 -90 -320 -10 -36
-41 -146 -69 -245 -29 -99 -54 -191 -57 -205 -8 -39 -12 -36 -28 21 -8 30 -47
167 -86 304 -40 138 -82 288 -95 335 -33 121 -120 427 -221 780 -47 168 -87
311 -87 318 0 6 -7 12 -16 12 -9 1 -161 2 -339 3 l-322 2 0 -1347z"/>
<path d="M2890 6400 l0 -245 253 0 252 0 0 245 0 245 -252 0 -253 0 0 -245z"/>
<path d="M3948 6642 l-46 -3 0 -244 1 -244 591 -3 c575 -3 592 -4 636 -24 85
-39 141 -114 165 -219 43 -186 -50 -367 -212 -411 -38 -10 -170 -12 -610 -10
l-563 2 -1 -30 c-4 -176 0 -1499 5 -1503 4 -5 417 -8 486 -4 14 1 15 63 15
521 0 343 3 520 10 521 5 0 170 1 365 3 384 3 419 7 558 63 270 108 449 345
478 632 10 109 11 132 3 224 -22 244 -131 442 -322 581 -93 68 -273 131 -408
142 -69 6 -1070 11 -1151 6z"/>
<path d="M9279 6642 c-46 -3 -47 -4 -64 -50 -9 -26 -21 -56 -25 -67 -5 -11
-50 -128 -100 -260 -50 -132 -94 -249 -99 -260 -5 -11 -55 -141 -111 -290 -56
-148 -105 -279 -110 -290 -11 -24 2 10 -296 -770 -284 -741 -267 -697 -255
-704 11 -7 2358 -8 2364 -1 3 4 -59 172 -126 340 -3 8 -144 377 -312 820 -169
443 -322 843 -340 890 -18 47 -79 207 -135 355 -57 149 -106 274 -110 278 -7
8 -198 14 -281 9z m144 -932 c8 -25 45 -130 82 -235 37 -104 86 -246 110 -315
24 -69 71 -204 105 -300 34 -96 76 -218 95 -270 18 -52 38 -107 44 -122 6 -16
9 -29 8 -29 -1 -1 -213 -2 -470 -3 l-468 -1 6 23 c5 22 54 162 300 862 51 146
108 307 126 358 17 51 35 89 39 85 4 -4 14 -28 23 -53z"/>
<path d="M2890 4720 c0 -609 3 -770 13 -771 6 0 120 -1 252 -2 l240 0 3 771 2
772 -255 0 -255 0 0 -770z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View file

@ -13,7 +13,7 @@ import BinsIcon from "products/Bindapt/BinsIcon";
import { useGlobalState } from "providers"; import { useGlobalState } from "providers";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { useNavigate, useLocation } from "react-router-dom"; import { useNavigate, useLocation } from "react-router-dom";
import { IsAdaptiveAgriculture, isBXT, IsMiVent, IsAdCon, IsOmniAir } from "services/whiteLabel"; import { IsAdaptiveAgriculture, isBXT, IsMiVent, IsAdCon, IsOmniAir, IsMiPCA } from "services/whiteLabel";
import FieldsIcon from "products/AgIcons/FieldsIcon"; import FieldsIcon from "products/AgIcons/FieldsIcon";
import NexusSTIcon from "products/Construction/NexusSTIcon"; import NexusSTIcon from "products/Construction/NexusSTIcon";
import OmniAirDeviceIcon from "products/AviationIcons/OmniAirDeviceIcon"; import OmniAirDeviceIcon from "products/AviationIcons/OmniAirDeviceIcon";
@ -40,6 +40,7 @@ export default function BottomNavigator(props: Props) {
const isMiVent = IsMiVent(); const isMiVent = IsMiVent();
const isAdCon = IsAdCon(); const isAdCon = IsAdCon();
const isOmni = IsOmniAir(); const isOmni = IsOmniAir();
const isMiPCA = IsMiPCA();
const reRoute = useCallback( const reRoute = useCallback(
(path: string) => { (path: string) => {
@ -117,14 +118,14 @@ export default function BottomNavigator(props: Props) {
value="constructionMap" value="constructionMap"
/> />
)} )}
{isOmni && ( {(isOmni || isMiPCA) && (
<BottomNavigationAction <BottomNavigationAction
label="Map" label="Map"
icon={<AirportMapIcon type={getType()} />} icon={<AirportMapIcon type={getType()} />}
value="aviationMap" value="aviationMap"
/> />
)} )}
{isOmni && ( {(isOmni || isMiPCA) && (
<BottomNavigationAction <BottomNavigationAction
label="Terminals" label="Terminals"
icon={<PlaneIcon type={getType()} />} icon={<PlaneIcon type={getType()} />}
@ -139,7 +140,7 @@ export default function BottomNavigator(props: Props) {
<BindaptIcon type={getType()} /> <BindaptIcon type={getType()} />
) : isAdCon ? ( ) : isAdCon ? (
<NexusSTIcon type={getType()} /> <NexusSTIcon type={getType()} />
) : isOmni ? ( ) : (isOmni || isMiPCA) ? (
<OmniAirDeviceIcon type={getType()} /> <OmniAirDeviceIcon type={getType()} />
) : ( ) : (
<DevicesIcon /> <DevicesIcon />

View file

@ -27,6 +27,7 @@ import {
IsAdaptiveAgriculture, IsAdaptiveAgriculture,
// hasTutorialPlaylist, // hasTutorialPlaylist,
IsAdCon, IsAdCon,
IsMiPCA,
// isBXT, // isBXT,
IsMiVent, IsMiVent,
IsOmniAir, IsOmniAir,
@ -166,7 +167,8 @@ export default function SideNavigator(props: Props) {
const isMiVent = IsMiVent(); const isMiVent = IsMiVent();
const isAg = IsAdaptiveAgriculture() const isAg = IsAdaptiveAgriculture()
const isStreamline = IsStreamline() const isStreamline = IsStreamline()
const isMiPCA = IsOmniAir() const isOmni = IsOmniAir()
const isMiPCA = IsMiPCA()
const isAdCon = IsAdCon() const isAdCon = IsAdCon()
return ( return (
<List className={classes.list} component="nav"> <List className={classes.list} component="nav">
@ -184,7 +186,7 @@ export default function SideNavigator(props: Props) {
</ListItemButton> </ListItemButton>
</Tooltip> </Tooltip>
)} )}
{(isMiPCA || user.hasFeature("admin")) && ( {((isOmni || isMiPCA) || user.hasFeature("admin")) && (
<Tooltip title="Aviation Map" placement="right"> <Tooltip title="Aviation Map" placement="right">
<ListItemButton <ListItemButton
id="tour-aviation-map" id="tour-aviation-map"
@ -240,7 +242,7 @@ export default function SideNavigator(props: Props) {
</ListItemButton> </ListItemButton>
</Tooltip> </Tooltip>
)} )}
{(isMiPCA || user.hasFeature("admin")) && ( {((isOmni || isMiPCA) || user.hasFeature("admin")) && (
<Tooltip title="Terminals" placement="right"> <Tooltip title="Terminals" placement="right">
<ListItemButton <ListItemButton
id="tour-terminals" id="tour-terminals"

View file

@ -192,7 +192,7 @@ export default function Gate(props: Props) {
const deviceDrawer = () => { const deviceDrawer = () => {
return ( return (
<DeviceLinkDrawer <DeviceLinkDrawer
deviceTags={["omniair"]} deviceTags={["omniair", "mipca"]}
devicePrefMap={devPrefs} devicePrefMap={devPrefs}
prefOptions={[ prefOptions={[
<MenuItem <MenuItem

View file

@ -10,7 +10,7 @@ import AeroGrowDarkLogo from "../assets/whitelabels/AeroGrow/darkLogo.png";
import AeroGrowLightLogo from "../assets/whitelabels/AeroGrow/lightLogo.png"; import AeroGrowLightLogo from "../assets/whitelabels/AeroGrow/lightLogo.png";
import MiVentLightLogo from "../assets/whitelabels/MiVent/lightLogo.png"; import MiVentLightLogo from "../assets/whitelabels/MiVent/lightLogo.png";
// import OmniAirLogo from "../assets/whitelabels/OmniAir/OmniAirLogo.png"; // import OmniAirLogo from "../assets/whitelabels/OmniAir/OmniAirLogo.png";
import MiPCALogo from "../assets/whitelabels/OmniAir/MiPCALogo.png"; import MiPCALogo from "../assets/whitelabels/MiPCA/MiPCALogo.png";
import StreamlineLogo from "../assets/whitelabels/Streamline/stream-logo.png" import StreamlineLogo from "../assets/whitelabels/Streamline/stream-logo.png"
// import { green, yellow } from "@mui/material/colors"; // import { green, yellow } from "@mui/material/colors";
@ -284,6 +284,32 @@ export function IsOmniAir(): boolean {
); );
} }
const MIPCA_WHITE_LABEL: WhiteLabel = {
name: "MiPCA",
primaryColour: "#004f9b",
secondaryColour: "yellow",
signatureColour: "#272727",
signatureAccentColour: "#fff",
//omni air and MiPCA are the same client ID in Auth0, it is to replace it, once omniair gets removed we can re-name this
auth0ClientId: import.meta.env.VITE_AUTH0_OMNIAIR_CLIENT_ID,
redirectOnLogout: true,
logoutRedirectTarget: "https://mionetech.com",
darkLogo: MiPCALogo,
lightLogo: MiPCALogo,
transparentLogoBG: true,
blacklist: ["cost"],
docs: "MiPCA",
protips: protips.concat([])
};
export function IsMiPCA(): boolean {
return (
getName() === "MiPCA" ||
window.location.origin.includes("staging") ||
window.location.origin.includes("localhost")
);
}
const whitelabels = new Map<string, WhiteLabel>([ const whitelabels = new Map<string, WhiteLabel>([
["streamline", STREAMLINE_WHITE_LABEL], ["streamline", STREAMLINE_WHITE_LABEL],
["adaptiveag", ADAPTIVE_AGRICULTURE_WHITE_LABEL], ["adaptiveag", ADAPTIVE_AGRICULTURE_WHITE_LABEL],
@ -296,7 +322,8 @@ const whitelabels = new Map<string, WhiteLabel>([
["10.0", ADAPTIVE_CONSTRUCTION_WHITE_LABEL], ["10.0", ADAPTIVE_CONSTRUCTION_WHITE_LABEL],
["mivent", MIVENT_WHITE_LABEL], ["mivent", MIVENT_WHITE_LABEL],
["adaptiveconstruction", ADAPTIVE_CONSTRUCTION_WHITE_LABEL], ["adaptiveconstruction", ADAPTIVE_CONSTRUCTION_WHITE_LABEL],
["omniair", OMNIAIR_WHITE_LABEL] ["omniair", OMNIAIR_WHITE_LABEL],
["mipca", MIPCA_WHITE_LABEL]
]); ]);
export function getWhitelabel(): WhiteLabel { export function getWhitelabel(): WhiteLabel {