Merge branch 'staging_environment' into 3d_bin

This commit is contained in:
csawatzky 2026-04-15 11:33:52 -06:00
commit ff64f8594c
27 changed files with 219 additions and 42 deletions

1
.env
View file

@ -14,6 +14,7 @@ VITE_AUTH0_ADAPTIVE_CONSTRUCTION_CLIENT_ID=32rABabJzXRvJiWivTmeKFgwFiqh4ok7
VITE_AUTH0_AEROGROW_CLIENT_ID=KHl9ooUt1nia1RYw5n224dyggCXdbsSd
VITE_AUTH0_MIVENT_CLIENT_ID=VNALE7RW6l3dY5uYcxgwElZV0lcT25Fg
VITE_AUTH0_OMNIAIR_CLIENT_ID=IblmarD8wFafiD6doxTmOHQ6Bx3L9wWl
VITE_AUTH0_INTELLIFARMS_CLIENT_ID=RYtuAyOcB4DSaaqJMLDMf3pV8SFY9PdY
#Branding (Default theme)
VITE_APP_WEBSITE_TITLE="Adaptive Dashboard"

View file

@ -18,7 +18,10 @@ Steps to add a new white label
- 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)
It is recommended to start with the Auth0 stage once everything from the client has been recieved as things like the client ID will be needed in other stages
<!-- frontend steps -->
Frontend Steps
1. 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
@ -29,22 +32,23 @@ Steps to add a new white label
4. If there are any pages specific to the whitelabel be sure to update the side and bottom navigator
<!-- backend steps -->
Backend Steps
1. In `dynamic-config.prod.yml`, add a router entry for the new whitelabel
2. In `pond/http/server.go`, add the sub-domain to the `allowedOrigins`
3. In `pond/whitelabel.go`, add an entry for the new white label
4. Enable the white-label sub-domain for CORS in the backend by adding it to the allowed origins http/server.go
<!-- Auth steps -->
1. Create a new `Auth0` application for the client
Auth0 Steps
1. Create a new Single Page App on Auth0 using React as the tech
2. Once you have the client ID add it to your .env file in the frontend
3. Adjust settings accordingly in the new application
- 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)
- allow necessary `origins`, `callback URLs`, and `CORS domains` to the application settings (see other applications/whitelabels 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/))
- 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
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 712 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View file

@ -0,0 +1,19 @@
{
"name": "Intellifarms",
"short_name": "Intellifarms",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<image href="Intellifarms_Icon.png" height="512" width="512"/>
</svg>

After

Width:  |  Height:  |  Size: 134 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View file

@ -57,6 +57,8 @@ interface Props {
components?: Map<string, Component>;
setComponents?: React.Dispatch<React.SetStateAction<Map<string, Component>>>;
updateBinStatus?: (componentKeys: string[], removed?: boolean) => void;
componentDevices?: Map<string, number>
setComponentDevices?: React.Dispatch<React.SetStateAction<Map<string, number>>>;
}
interface OpenState {
@ -77,7 +79,9 @@ export default function BinActions(props: Props) {
refreshCallback,
userID,
components,
componentDevices,
setComponents,
setComponentDevices,
updateBinStatus
} = props;
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
@ -245,7 +249,9 @@ export default function BinActions(props: Props) {
open={openState.sensors}
userID={userID}
components={components}
componentDevices={componentDevices}
setComponents={setComponents}
setComponentDevices={setComponentDevices}
updateBinStatus={updateBinStatus}
onClose={refresh => {
if (refresh === true) {

View file

@ -71,7 +71,9 @@ const useStyles = makeStyles((theme: Theme) => {
interface Props {
components?: Map<string, Component>;
componentDevices?: Map<string, number>;
setComponents?: React.Dispatch<React.SetStateAction<Map<string, Component>>>;
setComponentDevices?: React.Dispatch<React.SetStateAction<Map<string, number>>>;
bin: string;
binGrain: pond.Grain;
updateBinStatus?: (componentKeys: string[], removed?: boolean) => void;
@ -84,7 +86,7 @@ interface Option {
}
export default function BinComponents(props: Props) {
const { components, bin, setComponents, updateBinStatus, binGrain } = props;
const { components, componentDevices, bin, setComponents, setComponentDevices, updateBinStatus, binGrain } = props;
const [{as}] = useGlobalState();
const classes = useStyles();
const binAPI = useBinAPI();
@ -208,20 +210,54 @@ export default function BinComponents(props: Props) {
// }
// }, [selectedDevice, componentAPI, deviceComponents, snackbar]);
const removeComponent = (component: string) => {
binAPI.removeComponent(bin, component, as).then(() => {
const removeComponent = (component: string, device?: number) => {
console.log(device)
binAPI.removeComponent(bin, component, device, as).then(() => {
if (components && setComponents) {
if (components.delete(component)) {
let newComponents = new Map(components);
setComponents(newComponents);
}
}
if(componentDevices && setComponentDevices) {
if (componentDevices.delete(component)) {
let newComponentDevices = new Map(componentDevices);
setComponentDevices(newComponentDevices);
}
}
snackbar.info("Component removed from bin");
});
setComponentToRemove(undefined);
};
const removeComponentConfirmation = () => {
const removeComponentConfirmation = (last?: boolean) => {
let lastComponent = false;
let devId: number | undefined;
if(last){
lastComponent = last
}else{
if (componentDevices && componentToRemove) {
const id = componentDevices.get(componentToRemove.key());
if (id !== undefined) {
devId = id
let count = 0;
for (const val of componentDevices.values()) {
if (val === devId) {
count++;
if (count > 1) {
break; // stop early once we know it's not the last
}
}
}
lastComponent = count === 1;
}
}
}
return (
<ResponsiveDialog
fullScreen={false}
@ -229,19 +265,37 @@ export default function BinComponents(props: Props) {
onClose={() => setComponentToRemove(undefined)}>
<DialogTitle>Remove {componentToRemove?.name()}?</DialogTitle>
<DialogContent>
{lastComponent ?
<DialogContentText>
This component {componentToRemove?.name()} is the last component from its device. You can choose to remove only the component itself or the device along with it.
Leaving the device may have unexpected results if the device will continue to be used and it is recommended to remove it if the device is being moved to a new bin.
</DialogContentText>
:
<DialogContentText>
This will remove {componentToRemove?.name()} from this bin. If you don't have direct
access to this component or the device it is attached to, you will not be able to add it
back.
</DialogContentText>
}
</DialogContent>
<DialogActions>
<Button onClick={() => setComponentToRemove(undefined)} color="primary">
Cancel
</Button>
{lastComponent
?
<React.Fragment>
<Button onClick={() => removeComponent(componentToRemove!.key())} color="primary">
Remove Component
</Button>
<Button onClick={() => removeComponent(componentToRemove!.key(), devId)} color="primary">
Remove Component And Device
</Button>
</React.Fragment> :
<Button onClick={() => removeComponent(componentToRemove!.key())} color="primary">
Remove
</Button>
}
</DialogActions>
</ResponsiveDialog>
);
@ -256,7 +310,7 @@ export default function BinComponents(props: Props) {
<DialogTitle>Remove All Components?</DialogTitle>
<DialogContent>
<DialogContentText>
This will remove All attached components from this bin. If you don't have direct access
This will remove All attached components and devices from this bin. If you don't have direct access
to these components or the devices they are attached to, you will not be able to add
them back.
</DialogContentText>
@ -320,6 +374,12 @@ export default function BinComponents(props: Props) {
setComponents(newComponents);
}
}
if(componentDevices && setComponentDevices) {
if (componentDevices.set(component.key(), device)) {
let newComponentDevices = new Map(componentDevices);
setComponentDevices(newComponentDevices);
}
}
//if a grain cable was added to the bin update the components grain type to match the bin
if (preferences.type === pond.BinComponent.BIN_COMPONENT_GRAIN_CABLE) {
let settings = component.settings;
@ -338,16 +398,41 @@ export default function BinComponents(props: Props) {
}
});
} else {
binAPI.removeComponent(bin, component.key(), as).then(resp => {
let lastComponent = false;
if (componentDevices) {
console.log(componentDevices)
let count = 0;
for (const val of componentDevices.values()) {
if (val === device) {
count++;
if (count > 1) {
break; // stop early once we know it's not the last
}
}
}
lastComponent = count === 1;
}
if(lastComponent){
setComponentToRemove(component)
}else{
binAPI.removeComponent(bin, component.key(), undefined, as).then(resp => {
if (components && setComponents) {
if (components.delete(component.key())) {
let newComponents = new Map(components);
setComponents(newComponents);
}
}
if(componentDevices && setComponentDevices) {
if (componentDevices.delete(component.key())) {
let newComponentDevices = new Map(componentDevices);
setComponentDevices(newComponentDevices);
}
}
snackbar.info("Component removed from bin");
});
}
}
};
const removeAll = () => {
@ -567,7 +652,10 @@ export default function BinComponents(props: Props) {
</ListItemAvatar>
<ListItemText inset={cIcon === undefined}>{component.name()}</ListItemText>
<ListItemSecondaryAction>
<IconButton onClick={() => setComponentToRemove(component)}>
<IconButton onClick={() => {
setComponentToRemove(component)
}}>
<Remove />
</IconButton>
</ListItemSecondaryAction>

View file

@ -30,7 +30,9 @@ interface Props {
coords?: { longitude: number; latitude: number };
binYards?: pond.BinYardSettings[];
components?: Map<string, Component>;
componentDevices?: Map<string, number>
setComponents?: React.Dispatch<React.SetStateAction<Map<string, Component>>>;
setComponentDevices?: React.Dispatch<React.SetStateAction<Map<string, number>>>;
updateBinStatus?: (componentKeys: string[], removed?: boolean) => void;
}
@ -43,7 +45,9 @@ export default function BinSensors(props: Props) {
mode,
openedBinYard,
components,
componentDevices,
setComponents,
setComponentDevices,
updateBinStatus
} = props;
const [initialized, setInitialized] = useState(false);
@ -93,7 +97,9 @@ export default function BinSensors(props: Props) {
<Box padding={1}>
<BinComponents
components={components ? components : undefined}
componentDevices={componentDevices}
setComponents={setComponents}
setComponentDevices={setComponentDevices}
bin={bin.key()}
binGrain={bin.grain()}
updateBinStatus={updateBinStatus}

View file

@ -38,10 +38,12 @@ interface Props {
multiGraphZoom?: (domain: number[] | string[]) => void;
colourAboveZero?: ColourData
colourBelowZero?: ColourData
minY?: string | number
maxY?: string | number
}
export default function SingleSetAreaChart(props: Props) {
const { data, maxRef, minRef, newXDomain, multiGraphZoom, yAxisLabel, colourAboveZero, colourBelowZero, tooltipLabel, tooltipUnit } = props;
const { data, maxRef, minRef, newXDomain, multiGraphZoom, yAxisLabel, colourAboveZero, colourBelowZero, tooltipLabel, tooltipUnit, minY, maxY } = props;
const [xDomain, setXDomain] = useState<string[] | number[]>(["dataMin", "dataMax"]);
const [refLeft, setRefLeft] = useState<number | undefined>();
const [refRight, setRefRight] = useState<number | undefined>();
@ -152,7 +154,8 @@ export default function SingleSetAreaChart(props: Props) {
/>
<YAxis
type="number"
domain={["auto", "auto"]}
allowDataOverflow
domain={[minY ?? "auto", maxY ?? "auto"]}
label={{
value: yAxisLabel,
position: "insideLeft",

View file

@ -211,6 +211,8 @@ export default function GateDeltaTempGraph(props: Props){
/>
<SingleSetAreaChart
data={data}
minY={-40} //-40C is the same as -40F
maxY={(user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? 104 : 40)}
tooltipLabel="Delta Temp"
tooltipUnit={user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? " °F" : " °C"}
yAxisLabel={"Delta T" + (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? " °F" : " °C")}

View file

@ -13,7 +13,7 @@ import BinsIcon from "products/Bindapt/BinsIcon";
import { useGlobalState } from "providers";
import { useCallback, useEffect, useState } from "react";
import { useNavigate, useLocation } from "react-router-dom";
import { IsAdaptiveAgriculture, isBXT, IsMiVent, IsAdCon, IsOmniAir, IsMiPCA } from "services/whiteLabel";
import { IsAdaptiveAgriculture, isBXT, IsMiVent, IsAdCon, IsOmniAir, IsMiPCA, IsIntellifarms } from "services/whiteLabel";
import FieldsIcon from "products/AgIcons/FieldsIcon";
import NexusSTIcon from "products/Construction/NexusSTIcon";
import OmniAirDeviceIcon from "products/AviationIcons/OmniAirDeviceIcon";
@ -36,7 +36,8 @@ export default function BottomNavigator(props: Props) {
const { isAuthenticated } = useAuth0();
const [{ user }] = useGlobalState();
const [route, setRoute] = useState(sideIsOpen ? "side" : "");
const isAdaptive = IsAdaptiveAgriculture();
const isAg = IsAdaptiveAgriculture();
const isIntel = IsIntellifarms();
const isMiVent = IsMiVent();
const isAdCon = IsAdCon();
const isOmni = IsOmniAir();
@ -45,7 +46,7 @@ export default function BottomNavigator(props: Props) {
const reRoute = useCallback(
(path: string) => {
if (path === "") {
if (isAdaptive) {
if (isAg || isIntel) {
return "bins";
}
if (isMiVent) {
@ -74,7 +75,7 @@ export default function BottomNavigator(props: Props) {
}
return path;
},
[isAdaptive, isMiVent]
[isAg, isMiVent, isIntel]
);
const autoDetectRoute = useCallback(() => {
@ -105,10 +106,10 @@ export default function BottomNavigator(props: Props) {
const authenticatedNavigation = () => {
return (
<BottomNavigation value={route} onChange={(_, newValue) => handleRouteChange(newValue)}>
{isAdaptive && (
{isAg || isIntel && (
<BottomNavigationAction label="Farm" icon={<FieldsIcon type={getType()} />} value="visualFarm" />
)}
{isAdaptive && (
{isAg || isIntel && (
<BottomNavigationAction label="Bins" icon={<BinsIcon type={getType()} />} value="bins" />
)}
{isAdCon && (
@ -136,7 +137,7 @@ export default function BottomNavigator(props: Props) {
<BottomNavigationAction
label="Devices"
icon={
isAdaptive ? (
(isAg || isIntel) ? (
<BindaptIcon type={getType()} />
) : isAdCon ? (
<NexusSTIcon type={getType()} />

View file

@ -27,6 +27,7 @@ import {
IsAdaptiveAgriculture,
// hasTutorialPlaylist,
IsAdCon,
IsIntellifarms,
IsMiPCA,
// isBXT,
IsMiVent,
@ -166,13 +167,14 @@ export default function SideNavigator(props: Props) {
const authenticatedSideMenu = () => {
const isMiVent = IsMiVent();
const isAg = IsAdaptiveAgriculture()
const isIntel = IsIntellifarms()
const isStreamline = IsStreamline()
const isOmni = IsOmniAir()
const isMiPCA = IsMiPCA()
const isAdCon = IsAdCon()
return (
<List className={classes.list} component="nav">
{(isAg || isStreamline || user.hasFeature("admin")) && (
{(isAg || isIntel || isStreamline || user.hasFeature("admin")) && (
<Tooltip title="Visual Farm" placement="right">
<ListItemButton
id="tour-visual-farm"
@ -214,7 +216,7 @@ export default function SideNavigator(props: Props) {
</ListItemButton>
</Tooltip>
)}
{(isAg || isStreamline || user.hasFeature("admin")) && (
{(isAg || isIntel || isStreamline || user.hasFeature("admin")) && (
<Tooltip title="Contracts" placement="right">
<ListItemButton
id="tour-contracts"
@ -228,7 +230,7 @@ export default function SideNavigator(props: Props) {
</ListItemButton>
</Tooltip>
)}
{(isAg || isStreamline || user.hasFeature("admin")) && (
{(isAg || isIntel || isStreamline || user.hasFeature("admin")) && (
<Tooltip title="Bins" placement="right">
<ListItemButton
id="tour-bins"
@ -270,7 +272,7 @@ export default function SideNavigator(props: Props) {
</ListItemButton>
</Tooltip>
}
{(isAg || isStreamline || user.hasFeature("admin")) && (
{(isAg || isIntel || isStreamline || user.hasFeature("admin")) && (
<Tooltip title="Transactions" placement="right">
<ListItemButton
id="tour-transactions"
@ -362,7 +364,7 @@ export default function SideNavigator(props: Props) {
{open && <ListItemText primary="Users" />}
</ListItemButton>
</Tooltip>
{(isAg || isStreamline || user.hasFeature("admin")) && (
{(isAg || isIntel || isStreamline || user.hasFeature("admin")) && (
<Tooltip title="My Fields" placement="right">
<ListItemButton
id="tour-field-list"
@ -376,7 +378,7 @@ export default function SideNavigator(props: Props) {
</ListItemButton>
</Tooltip>
)}
{(isAg || isStreamline || user.hasFeature("admin")) && (
{(isAg || isIntel || isStreamline || user.hasFeature("admin")) && (
<Tooltip title="Grain Types" placement="right">
<ListItemButton
id="tour-grain-types"
@ -460,7 +462,7 @@ export default function SideNavigator(props: Props) {
</ListItemButton>
</Tooltip>
}
{(isAg || isStreamline || user.hasFeature("admin")) &&
{(isAg || isIntel || isStreamline || user.hasFeature("admin")) &&
<Tooltip title="Marketplace" placement="right">
<ListItemButton
id="tour-marketplace"

View file

@ -271,7 +271,7 @@ export default function Bin(props: Props) {
resp.data.devices.forEach((dev: any) => {
let device = Device.create(dev);
if (attachedDevIds.includes(device.id())) {
devs.push(Device.create(dev));
devs.push(device);
}
});
setDevices(devs);
@ -676,6 +676,8 @@ export default function Bin(props: Props) {
userID={user.id()}
components={components}
setComponents={setComponents}
componentDevices={componentDevices}
setComponentDevices={setComponentDevices}
updateBinStatus={updateStatus}
/>
</Grid>

View file

@ -30,6 +30,7 @@ export interface IBinAPIContext {
removeComponent: (
bin: string,
component: string,
device?: number,
otherTeam?: string
) => Promise<AxiosResponse<pond.RemoveBinComponentResponse>>;
removeAllComponents: (bin: string, otherTeam?: string) => Promise<AxiosResponse<pond.RemoveAllBinComponentsResponse>>;
@ -254,10 +255,11 @@ export default function BinProvider(props: PropsWithChildren<Props>) {
})
};
const removeComponent = (bin: string, component: string, otherTeam?: string) => {
const removeComponent = (bin: string, component: string, device?: number, otherTeam?: string) => {
const view = otherTeam ? otherTeam : as
let url = "/bins/" + bin + "/removeComponent/" + component;
if (view) url = url + "?as=" + view;
if (device) url = url + "?deviceToRemove=" + device
if (view) url = url + (device ? "&as=" : "?as=") + view;
return new Promise<AxiosResponse<pond.RemoveBinComponentResponse>>((resolve, reject)=>{
post<pond.RemoveBinComponentResponse>(pondURL(url)).then(resp => {
resp.data = pond.RemoveBinComponentResponse.fromObject(resp.data)

View file

@ -12,6 +12,8 @@ import MiVentLightLogo from "../assets/whitelabels/MiVent/lightLogo.png";
// import OmniAirLogo from "../assets/whitelabels/OmniAir/OmniAirLogo.png";
import MiPCALogo from "../assets/whitelabels/MiPCA/MiPCALogo.png";
import StreamlineLogo from "../assets/whitelabels/Streamline/stream-logo.png"
import IntellifarmsLogo from "../assets/whitelabels/Intellifarms/IFND-2023-Logo.png"
import IntellifarmsLogoWhite from "../assets/whitelabels/Intellifarms/IFND-2023-Logo-White.png"
// import { green, yellow } from "@mui/material/colors";
const protips: string[] = [
@ -180,6 +182,32 @@ export function IsAdaptiveAgriculture(): boolean {
);
}
const INTELLIFARMS_WHITE_LABEL: WhiteLabel = {
name: "Intellifarms",
primaryColour: "#9E1B32",
secondaryColour: "#4A4F55",
signatureColour: "#272727",
signatureAccentColour: "#fff",
auth0ClientId: import.meta.env.VITE_AUTH0_INTELLIFARMS_CLIENT_ID,
redirectOnLogout: true,
logoutRedirectTarget: "https://myintellifarms.com",
darkLogo: IntellifarmsLogo,
lightLogo: IntellifarmsLogoWhite,
transparentLogoBG: true,
blacklist: [],
docs: "Platform",
protips: protips,
homePage: "/bins"
};
export function IsIntellifarms(): boolean {
return (
getName() === "Intellifarms"
// window.location.origin.includes("staging") ||
// window.location.origin.includes("localhost")
);
}
export function IsStreamline(): boolean {
return (
getName() === "Streamline"
@ -324,7 +352,8 @@ const whitelabels = new Map<string, WhiteLabel>([
["adaptiveconstruction", ADAPTIVE_CONSTRUCTION_WHITE_LABEL],
["omniair", OMNIAIR_WHITE_LABEL],
["mipca", MIPCA_WHITE_LABEL],
["mionetech", MIPCA_WHITE_LABEL]
["mionetech", MIPCA_WHITE_LABEL],
["intellifarms", INTELLIFARMS_WHITE_LABEL]
]);
export function getWhitelabel(): WhiteLabel {
@ -337,7 +366,7 @@ export function getWhitelabel(): WhiteLabel {
return BXT_WHITE_LABEL;
}
if (window.location.origin.includes("localhost")) {
return STREAMLINE_WHITE_LABEL;
return INTELLIFARMS_WHITE_LABEL;
}
if (window.location.origin.includes("staging") || import.meta.env.VITE_LOCAL_STAGING=='true') {
return STAGING_WHITELABEL;