diff --git a/.env b/.env
index 9fdc8f3..90ef699 100644
--- a/.env
+++ b/.env
@@ -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"
diff --git a/README.md b/README.md
index 8528fdc..4a47964 100644
--- a/README.md
+++ b/README.md
@@ -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
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
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
-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:
diff --git a/public/Intellifarms/IFND-2023-Logo-White.png b/public/Intellifarms/IFND-2023-Logo-White.png
new file mode 100644
index 0000000..0afb3bf
Binary files /dev/null and b/public/Intellifarms/IFND-2023-Logo-White.png differ
diff --git a/public/Intellifarms/IFND-2023-Logo.png b/public/Intellifarms/IFND-2023-Logo.png
new file mode 100644
index 0000000..11ff1c8
Binary files /dev/null and b/public/Intellifarms/IFND-2023-Logo.png differ
diff --git a/public/Intellifarms/android-chrome-192x192.png b/public/Intellifarms/android-chrome-192x192.png
new file mode 100644
index 0000000..11f1839
Binary files /dev/null and b/public/Intellifarms/android-chrome-192x192.png differ
diff --git a/public/Intellifarms/android-chrome-512x512.png b/public/Intellifarms/android-chrome-512x512.png
new file mode 100644
index 0000000..e4f7470
Binary files /dev/null and b/public/Intellifarms/android-chrome-512x512.png differ
diff --git a/public/Intellifarms/apple-touch-icon.png b/public/Intellifarms/apple-touch-icon.png
new file mode 100644
index 0000000..408dde9
Binary files /dev/null and b/public/Intellifarms/apple-touch-icon.png differ
diff --git a/public/Intellifarms/browserconfig.xml b/public/Intellifarms/browserconfig.xml
new file mode 100644
index 0000000..f62a123
--- /dev/null
+++ b/public/Intellifarms/browserconfig.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+ #ffffff
+
+
+
diff --git a/public/Intellifarms/favicon-16x16.png b/public/Intellifarms/favicon-16x16.png
new file mode 100644
index 0000000..f052e75
Binary files /dev/null and b/public/Intellifarms/favicon-16x16.png differ
diff --git a/public/Intellifarms/favicon-32x32.png b/public/Intellifarms/favicon-32x32.png
new file mode 100644
index 0000000..460f109
Binary files /dev/null and b/public/Intellifarms/favicon-32x32.png differ
diff --git a/public/Intellifarms/favicon-48x48.png b/public/Intellifarms/favicon-48x48.png
new file mode 100644
index 0000000..948fec7
Binary files /dev/null and b/public/Intellifarms/favicon-48x48.png differ
diff --git a/public/Intellifarms/favicon.ico b/public/Intellifarms/favicon.ico
new file mode 100644
index 0000000..f3b5e0c
Binary files /dev/null and b/public/Intellifarms/favicon.ico differ
diff --git a/public/Intellifarms/manifest.json b/public/Intellifarms/manifest.json
new file mode 100644
index 0000000..5288b8b
--- /dev/null
+++ b/public/Intellifarms/manifest.json
@@ -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"
+}
\ No newline at end of file
diff --git a/public/Intellifarms/mstile-150x150.png b/public/Intellifarms/mstile-150x150.png
new file mode 100644
index 0000000..03b9ab0
Binary files /dev/null and b/public/Intellifarms/mstile-150x150.png differ
diff --git a/public/Intellifarms/safari-pinned-tab.svg b/public/Intellifarms/safari-pinned-tab.svg
new file mode 100644
index 0000000..6ea0a3f
--- /dev/null
+++ b/public/Intellifarms/safari-pinned-tab.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/src/assets/whitelabels/Intellifarms/IFND-2023-Logo-White.png b/src/assets/whitelabels/Intellifarms/IFND-2023-Logo-White.png
new file mode 100644
index 0000000..0afb3bf
Binary files /dev/null and b/src/assets/whitelabels/Intellifarms/IFND-2023-Logo-White.png differ
diff --git a/src/assets/whitelabels/Intellifarms/IFND-2023-Logo.png b/src/assets/whitelabels/Intellifarms/IFND-2023-Logo.png
new file mode 100644
index 0000000..11ff1c8
Binary files /dev/null and b/src/assets/whitelabels/Intellifarms/IFND-2023-Logo.png differ
diff --git a/src/bin/BinActions.tsx b/src/bin/BinActions.tsx
index a425ea6..c9f7c8f 100644
--- a/src/bin/BinActions.tsx
+++ b/src/bin/BinActions.tsx
@@ -57,6 +57,8 @@ interface Props {
components?: Map;
setComponents?: React.Dispatch>>;
updateBinStatus?: (componentKeys: string[], removed?: boolean) => void;
+ componentDevices?: Map
+ setComponentDevices?: React.Dispatch>>;
}
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);
@@ -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) {
diff --git a/src/bin/BinComponents.tsx b/src/bin/BinComponents.tsx
index 7864c46..0b81cba 100644
--- a/src/bin/BinComponents.tsx
+++ b/src/bin/BinComponents.tsx
@@ -71,7 +71,9 @@ const useStyles = makeStyles((theme: Theme) => {
interface Props {
components?: Map;
+ componentDevices?: Map;
setComponents?: React.Dispatch>>;
+ setComponentDevices?: React.Dispatch>>;
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 (
setComponentToRemove(undefined)}>
Remove {componentToRemove?.name()}?
-
- 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.
-
+ {lastComponent ?
+
+ 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.
+
+ :
+
+ 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.
+
+ }
+ {lastComponent
+ ?
+
+
+
+ :
+ }
);
@@ -256,7 +310,7 @@ export default function BinComponents(props: Props) {
Remove All Components?
- 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.
@@ -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,15 +398,40 @@ export default function BinComponents(props: Props) {
}
});
} else {
- binAPI.removeComponent(bin, component.key(), as).then(resp => {
- if (components && setComponents) {
+ 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");
- });
+ });
+ }
}
};
@@ -567,7 +652,10 @@ export default function BinComponents(props: Props) {
{component.name()}
- setComponentToRemove(component)}>
+ {
+ setComponentToRemove(component)
+
+ }}>
diff --git a/src/bin/BinSensors.tsx b/src/bin/BinSensors.tsx
index f1c81ac..492091e 100644
--- a/src/bin/BinSensors.tsx
+++ b/src/bin/BinSensors.tsx
@@ -30,7 +30,9 @@ interface Props {
coords?: { longitude: number; latitude: number };
binYards?: pond.BinYardSettings[];
components?: Map;
+ componentDevices?: Map
setComponents?: React.Dispatch>>;
+ setComponentDevices?: React.Dispatch>>;
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) {
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(["dataMin", "dataMax"]);
const [refLeft, setRefLeft] = useState();
const [refRight, setRefRight] = useState();
@@ -152,7 +154,8 @@ export default function SingleSetAreaChart(props: Props) {
/>
{
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 (
handleRouteChange(newValue)}>
- {isAdaptive && (
+ {isAg || isIntel && (
} value="visualFarm" />
)}
- {isAdaptive && (
+ {isAg || isIntel && (
} value="bins" />
)}
{isAdCon && (
@@ -136,7 +137,7 @@ export default function BottomNavigator(props: Props) {
) : isAdCon ? (
diff --git a/src/navigation/SideNavigator.tsx b/src/navigation/SideNavigator.tsx
index 6bab36c..815f30b 100644
--- a/src/navigation/SideNavigator.tsx
+++ b/src/navigation/SideNavigator.tsx
@@ -27,6 +27,7 @@ import {
IsAdaptiveAgriculture,
// hasTutorialPlaylist,
IsAdCon,
+ IsIntellifarms,
IsMiPCA,
// isBXT,
IsMiVent,
@@ -165,14 +166,15 @@ export default function SideNavigator(props: Props) {
const authenticatedSideMenu = () => {
const isMiVent = IsMiVent();
- const isAg = IsAdaptiveAgriculture()
+ const isAg = IsAdaptiveAgriculture()
+ const isIntel = IsIntellifarms()
const isStreamline = IsStreamline()
const isOmni = IsOmniAir()
const isMiPCA = IsMiPCA()
const isAdCon = IsAdCon()
return (
- {(isAg || isStreamline || user.hasFeature("admin")) && (
+ {(isAg || isIntel || isStreamline || user.hasFeature("admin")) && (
)}
- {(isAg || isStreamline || user.hasFeature("admin")) && (
+ {(isAg || isIntel || isStreamline || user.hasFeature("admin")) && (
)}
- {(isAg || isStreamline || user.hasFeature("admin")) && (
+ {(isAg || isIntel || isStreamline || user.hasFeature("admin")) && (
}
- {(isAg || isStreamline || user.hasFeature("admin")) && (
+ {(isAg || isIntel || isStreamline || user.hasFeature("admin")) && (
}
- {(isAg || isStreamline || user.hasFeature("admin")) && (
+ {(isAg || isIntel || isStreamline || user.hasFeature("admin")) && (
)}
- {(isAg || isStreamline || user.hasFeature("admin")) && (
+ {(isAg || isIntel || isStreamline || user.hasFeature("admin")) && (
}
- {(isAg || isStreamline || user.hasFeature("admin")) &&
+ {(isAg || isIntel || isStreamline || user.hasFeature("admin")) &&
{
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}
/>
diff --git a/src/providers/pond/binAPI.tsx b/src/providers/pond/binAPI.tsx
index def6699..e0d46f9 100644
--- a/src/providers/pond/binAPI.tsx
+++ b/src/providers/pond/binAPI.tsx
@@ -30,6 +30,7 @@ export interface IBinAPIContext {
removeComponent: (
bin: string,
component: string,
+ device?: number,
otherTeam?: string
) => Promise>;
removeAllComponents: (bin: string, otherTeam?: string) => Promise>;
@@ -254,10 +255,11 @@ export default function BinProvider(props: PropsWithChildren) {
})
};
- 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>((resolve, reject)=>{
post(pondURL(url)).then(resp => {
resp.data = pond.RemoveBinComponentResponse.fromObject(resp.data)
diff --git a/src/services/whiteLabel.ts b/src/services/whiteLabel.ts
index 29c4427..e53ef6a 100644
--- a/src/services/whiteLabel.ts
+++ b/src/services/whiteLabel.ts
@@ -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([
["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;