moved all of the libracart suff from the old frontend into the new new, also...

This commit is contained in:
Saiyida Noor 2025-08-06 15:30:39 +00:00
parent f1f4b46b48
commit add87dca1c
13 changed files with 545 additions and 10 deletions

View file

@ -50,7 +50,8 @@ function App() {
const skipCallbacks = [
"/johndeere",
"/cnhi"
"/cnhi",
"/libracart"
]
return (

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View file

@ -0,0 +1,191 @@
import {
Avatar,
Box,
Button,
DialogActions,
DialogContent,
DialogTitle,
Grid,
MenuItem,
TextField
} from "@mui/material";
import ResponsiveDialog from "common/ResponsiveDialog";
import { teamScope, User } from "models";
import { pond } from "protobuf-ts/pond";
import { useSnackbar, useUserAPI } from "providers";
import { useLibraCartProxyAPI } from "providers/pond/libracartProxyAPI";
import React, { useEffect, useState } from "react";
import { useLocation } from "react-router";
import TeamSearch from "teams/TeamSearch";
export default function LibraCartAccess() {
const [openDialog, setOpenDialog] = useState(false);
const [teamKey, setTeamKey] = useState("");
const [teamUsers, setTeamUsers] = useState<User[]>([]);
const [primaryUser, setPrimaryUser] = useState("");
const [libracartUsername, setLibraCartUserName] = useState("");
const [libracartCode, setLibraCartCode] = useState<string | null>("");
const [libracartAuth, setLibraCartAuth] = useState<string | null>("");
//const [activeStep, setActiveStep] = useState<number>(0);
const userAPI = useUserAPI();
const libracartAPI = useLibraCartProxyAPI();
//const [dataOps, setDataOps] = useState<pond.DataOption[]>([]);
const { openSnack } = useSnackbar();
//const [{ as }] = useGlobalState();
// const integration_url = process.env.REACT_APP_LIBRACART_INTEGRATION_URL;
const integration_url = import.meta.env.VITE_LIBRACART_INTEGRATION_URL;
const submitNewOrganization = () => {
if (libracartCode && libracartAuth) {
libracartAPI
.addAccount(teamKey, primaryUser, libracartCode, libracartAuth, libracartUsername)
.then(resp => {
openSnack("Added New Libra Cart Account Link");
})
.catch(err => {
openSnack("Failed to Add New Libra Cart Account Link");
});
setOpenDialog(false);
}
};
useEffect(() => {
if (teamKey !== "") {
userAPI.listObjectUsers(teamScope(teamKey)).then(resp => {
setTeamUsers(resp.data.users.map((u: pond.User) => User.any(u)));
});
}
}, [teamKey, userAPI]);
// useEffect(() => {
// let code = localStorage.getItem("state");
// if (code) {
// setLibraCartCode(code);
// setOpenDialog(true);
// }
// }, [searchParams, libracartCode]);
useEffect(() => {
let code = new URLSearchParams(window.location.search).get("state"); // this is the account_code
let auth = new URLSearchParams(window.location.search).get("authorization_token"); // this is the authorization used to verify where it came from
if (code && auth) {
setLibraCartCode(code);
setLibraCartAuth(auth);
setOpenDialog(true);
}
}, [window.location]);
const validate = () => {
let invalid = false;
if (libracartUsername === "" || teamKey === "" || primaryUser === "" || libracartCode === "") {
invalid = true;
}
return invalid;
};
const general = () => {
return (
<Box>
<TextField
margin="dense"
label="Libra Cart Email"
fullWidth
variant="outlined"
value={libracartUsername}
onChange={e => setLibraCartUserName(e.target.value)}
/>
<TeamSearch label="Team" setTeamCallback={setTeamKey} />
<TextField
disabled={teamKey === ""}
margin="dense"
label="Primary User"
select
fullWidth
variant="outlined"
value={primaryUser}
onChange={e => setPrimaryUser(e.target.value)}>
{teamUsers.map(u => (
<MenuItem key={u.id()} value={u.id()}>
<Grid container direction="row" alignItems="center">
<Grid item>
<Avatar
alt={u.name()}
src={
u.settings.avatar && u.settings.avatar !== "" ? u.settings.avatar : undefined
}
/>
</Grid>
<Grid item>
<Box marginLeft={2}>{u.name()}</Box>
</Grid>
</Grid>
</MenuItem>
))}
</TextField>
<TextField
margin="dense"
label="code"
disabled
fullWidth
variant="outlined"
value={libracartCode}
/>
<TextField
margin="dense"
label="authorization token"
disabled
fullWidth
variant="outlined"
value={libracartAuth}
/>
</Box>
);
};
const newOrgDialog = () => {
return (
<ResponsiveDialog
open={openDialog}
onClose={() => {
setOpenDialog(false);
}}>
<DialogTitle>Enter New Integration Link</DialogTitle>
<DialogContent>{general()}</DialogContent>
<DialogActions>
<Button
onClick={() => {
setOpenDialog(false);
}}>
Close
</Button>
<Button
disabled={validate()}
onClick={submitNewOrganization}
variant="contained"
color="primary">
Submit
</Button>
</DialogActions>
</ResponsiveDialog>
);
};
return (
<Box style={{ padding: 10 }}>
<Button
variant="contained"
color="primary"
onClick={e => {
e.preventDefault();
window.open(`${integration_url}`, "_blank");
}}>
Link Libra Cart Account
</Button>
To integrate with Libra Cart, navigate to Integrations page on Libra Cart and "Connect" to
Adaptive Agriculture App: https://staging.cloud.agrimatics.com/grain/integrations
{newOrgDialog()}
</Box>
);
}

View file

@ -46,6 +46,7 @@ const Contracts = lazy(() => import("pages/Contracts"));
const Contract = lazy(() => import("pages/Contract"));
const JohnDeere = lazy(() => import("pages/JohnDeere"));
const CNHi = lazy(() => import("pages/CNHi"));
const LibraCart = lazy(() => import("pages/LibraCart"));
export const appendToUrl = (appendage: number | string) => {
const basePath = location.pathname.replace(/\/$/, "");
@ -340,6 +341,9 @@ export default function Router() {
{user.hasFeature("cnhi") &&
<Route path="cnhi" element={<CNHi />} />
}
{user.hasFeature("libra-cart") &&
<Route path="libracart" element={<LibraCart />} />
}
{/* Map routes */}
<Route path="visualFarm" element={<FieldMap />} />
<Route path="aviationMap" element={<AviationMap />} />

View file

@ -48,6 +48,7 @@ import DataDuckIcon from "products/Bindapt/DataDuckIcon";
import ContractsIcon from "products/CommonIcons/contractIcon";
import JohnDeereIcon from "products/CommonIcons/johnDeereIcon";
import CNHiIcon from "products/CommonIcons/cnhiIcon";
import LibraCartIcon from "products/CommonIcons/libracartIcon";
const drawerWidth = 230;
@ -477,6 +478,20 @@ export default function SideNavigator(props: Props) {
{open && <ListItemText primary="Case New Holland" />}
</ListItemButton>
</Tooltip>
}
{user.hasFeature("libra-cart") &&
<Tooltip title="LibraCart" placement="right">
<ListItemButton
id="tour-libraCart"
onClick={() => goTo("/libracart")}
classes={getClasses("/libracart")}
>
<ListItemIcon>
<LibraCartIcon />
</ListItemIcon>
{open && <ListItemText primary="LibraCart" />}
</ListItemButton>
</Tooltip>
}
</List>
)

View file

@ -20,7 +20,7 @@ import PageContainer from "./PageContainer";
export default function CNHi() {
const [currentOrg, setCurrentOrg] = useState("");
const cnhiAPI = useCNHiProxyAPI();
const [organizations, setOrganizations] = useState<Map<string, pond.JDAccount>>(new Map());
const [organizations, setOrganizations] = useState<Map<string, pond.CNHiAccount>>(new Map());
const [fieldAccordion, setFieldAccordion] = useState(false);
const [dataOptions, setDataOptions] = useState<pond.DataOption[]>([]);
const [{ as }] = useGlobalState();

148
src/pages/LibraCart.tsx Normal file
View file

@ -0,0 +1,148 @@
import {
Accordion,
AccordionDetails,
AccordionSummary,
Box,
Button,
Checkbox,
FormControlLabel,
Grid2 as Grid,
MenuItem,
Select
} from "@mui/material";
import { ExpandMore } from "@mui/icons-material";
import LibraCartAccess from "integrations/LibraCart/LibraCartAccess";
import { pond } from "protobuf-ts/pond";
import { useGlobalState } from "providers";
import { useLibraCartProxyAPI } from "providers/pond/libracartProxyAPI";
import React, { useEffect, useState } from "react";
import PageContainer from "./PageContainer";
export default function LibraCart() {
const [currentOrg, setCurrentOrg] = useState("");
const libracartAPI = useLibraCartProxyAPI();
const [organizations, setOrganizations] = useState<Map<string, pond.LibraCartAccount>>(new Map());
const [fieldAccordion, setFieldAccordion] = useState(false);
const [dataOptions, setDataOptions] = useState<pond.DataOption[]>([]);
const [{ as }] = useGlobalState();
//load organizations for the user
useEffect(() => {
setCurrentOrg("");
libracartAPI
.listAccounts(0, 0, as)
.then(resp => {
let tempOrgs: Map<string, pond.JDAccount> = new Map();
resp.data.accounts.forEach(org => {
let organization = pond.JDAccount.fromObject(org);
tempOrgs.set(organization.key, organization);
});
setOrganizations(tempOrgs);
})
.catch(err => {});
}, [libracartAPI, as]);
useEffect(() => {
let organization = organizations.get(currentOrg);
if (organization) {
let currentOptions: pond.DataOption[] = organization.options ?? [];
setDataOptions(currentOptions);
}
}, [currentOrg, organizations]);
const updateOrgData = (checked: boolean, option: pond.DataOption) => {
let currentOps: pond.DataOption[] = dataOptions;
if (checked && !currentOps.includes(option)) {
currentOps.push(option);
} else if (!checked && currentOps.includes(option)) {
currentOps.splice(currentOps.indexOf(option), 1);
}
setDataOptions([...currentOps]);
};
const submit = () => {
libracartAPI.updateAccount(currentOrg, dataOptions, as ?? undefined).then(resp => {
//update the organization in the map to have the correct dataOptions
let org = organizations.get(currentOrg);
if (org) {
org.options = dataOptions;
}
});
};
const fieldOptions = () => {
return (
<React.Fragment>
<Accordion
expanded={fieldAccordion}
onChange={(_, expanded) => {
setFieldAccordion(expanded);
}}>
<AccordionSummary expandIcon={<ExpandMore />}>Field Data</AccordionSummary>
<AccordionDetails>
<Grid container direction="row" spacing={2} alignItems="center" alignContent="center">
<Grid size={6}>
<FormControlLabel
label="Field Boundaries"
control={
<Checkbox
checked={dataOptions.includes(pond.DataOption.DATA_OPTION_FIELDS)}
onChange={(_, checked) => {
//setFields(!fields);
updateOrgData(checked, pond.DataOption.DATA_OPTION_FIELDS);
}}
/>
}
/>
</Grid>
<Grid size={6}>
View your data from Libra Cart on your visual Farm
</Grid>
</Grid>
</AccordionDetails>
</Accordion>
</React.Fragment>
);
};
return (
<PageContainer>
<LibraCartAccess />
<Grid
container
direction="row"
justifyContent="space-between"
alignContent="center"
alignItems="center"
style={{ padding: 10 }}>
<Grid>
<Select
//style={{ maxWidth: 110 }}
title="John Deer Account"
displayEmpty
disableUnderline={true}
value={currentOrg}
onChange={(event: any) => {
setCurrentOrg(event.target.value);
}}>
<MenuItem key={""} value={""}>
Select account to adjust accessable data
</MenuItem>
{Array.from(organizations.values()).map(org => {
return (
<MenuItem key={org.key} value={org.key}>
{org.username}
</MenuItem>
);
})}
</Select>
</Grid>
<Grid>
<Button onClick={submit} variant="contained" color="primary">
Update
</Button>
</Grid>
</Grid>
<Box style={{ padding: 10 }}>{fieldOptions()}</Box>
</PageContainer>
);
}

View file

@ -128,7 +128,8 @@ export default function Users() {
"developer",
"marketplace",
"installer",
"cnhi"
"cnhi",
"libracart"
].sort();
const [rows, setRows] = useState<User[]>([])

View file

@ -0,0 +1,24 @@
// update logo images when they share official black and white
import LibraCartLogoWhite from "assets/marketplaceImages/LibraCartGrey.png";
import LibraCartLogoBlack from "assets/marketplaceImages/LibraCartGrey.png";
import { ImgIcon } from "common/ImgIcon";
import { useThemeType } from "hooks";
interface Props {
type?: "light" | "dark";
}
export default function LibraCartIcon(props: Props) {
const themeType = useThemeType();
const { type } = props;
const src = () => {
if (type) {
return type === "light" ? LibraCartLogoWhite : LibraCartLogoBlack;
}
return themeType === "light" ? LibraCartLogoBlack : LibraCartLogoWhite;
};
return <ImgIcon alt="libra-cart" src={src()} />;
}

View file

@ -45,7 +45,8 @@ export {
useFileControllerAPI,
useContractAPI, //TODO: update api with resolve, reject
useJohnDeereProxyAPI, //TODO: update api with resolve, reject
useCNHiProxyAPI //TODO: update api with resolve, reject
useCNHiProxyAPI, //TODO: update api with resolve, reject
useLibraCartProxyAPI //TODO: update api with resolve, reject
} from "./pond/pond";
// export { SecurityContext, useSecurity } from "./security";
export { SnackbarContext, useSnackbar } from "./Snackbar";

View file

@ -0,0 +1,137 @@
import { AxiosResponse } from "axios";
import { useHTTP } from "hooks";
import { pond } from "protobuf-ts/pond";
import React, { createContext, PropsWithChildren, useContext } from "react";
//import { or } from "utils";
import { pondURL } from "./pond";
export interface ILibraCartProxyAPIContext {
//add new organization
addAccount: (
teamKey: string,
userID: string,
libracartCode: string,
libracartAuth: string,
libracartUsername: string
) => Promise<AxiosResponse<pond.AddLibraCartAccountResponse>>;
//list organizations
listAccounts: (
limit: number,
offset: number,
as?: string,
keys?: string[],
types?: string[]
) => Promise<AxiosResponse<pond.ListLibraCartAccountsResponse>>;
updateAccount: (
key: string,
options: pond.DataOption[],
as?: string
) => Promise<AxiosResponse<pond.UpdateLibraCartAccountResponse>>;
listFields: (
limit: number,
offset: number,
// order?: "asc" | "desc",
// orderBy?: string,
// search?: string,
as?: string,
asRoot?: boolean
) => Promise<AxiosResponse<pond.ListLibraCartFieldsResponse>>;
}
export const LibraCartProxyAPIContext = createContext<ILibraCartProxyAPIContext>(
{} as ILibraCartProxyAPIContext
);
interface Props {}
export default function LibraCartProvider(props: PropsWithChildren<Props>) {
const { children } = props;
const { post, get, put } = useHTTP();
const addAccount = (
teamKey: string,
userID: string,
libracartCode: string,
libracartAuth: string,
libracartUsername: string
) => {
return post<pond.AddLibraCartAccountResponse>(
pondURL(
"/libracartAccounts?team=" +
teamKey +
"&user=" +
userID +
"&code=" +
libracartCode +
"&auth=" +
libracartAuth +
"&libracartUsername=" +
libracartUsername
)
);
};
const listAccounts = (
limit: number,
offset: number,
as?: string,
keys?: string[],
types?: string[]
) => {
return get<pond.ListLibraCartAccountsResponse>(
pondURL(
"/libracartAccounts?limit=" +
limit +
"&offset=" +
offset +
(as ? "&as=" + as : "") +
(keys ? "&keys=" + keys.join(",") : "") +
(types ? "&types=" + types.join(",") : "")
)
);
};
const listFields = (
limit: number,
offset: number,
// order?: "asc" | "desc",
// orderBy?: string,
// search?: string,
as?: string,
asRoot?: boolean
) => {
return get<pond.ListFieldsResponse>(
pondURL(
"/libracartFields" +
"?limit=" +
limit +
"&offset=" +
offset +
(as ? "&as=" + as : "") +
(asRoot ? "&asRoot=" + asRoot.toString() : "")
)
);
};
const updateAccount = (key: string, options: pond.DataOption[], as?: string) => {
return put<pond.UpdateLibraCartAccountResponse>(
pondURL(
"/libracartAccounts/" + key + "?options=" + options.toString() + (as ? "&as=" + as : "")
)
);
};
return (
<LibraCartProxyAPIContext.Provider
value={{
addAccount,
listAccounts,
updateAccount,
listFields
}}>
{children}
</LibraCartProxyAPIContext.Provider>
);
}
export const useLibraCartProxyAPI = () => useContext(LibraCartProxyAPIContext);

View file

@ -35,6 +35,7 @@ import ContractProvider, { useContractAPI } from "./contractAPI";
import UsageProvider, { useUsageAPI } from "./usageAPI";
import KeyManagerProvider, { useKeyManagerAPI } from "./keyManagerAPI";
import JohnDeereProvider, { useJohnDeereProxyAPI } from "./johnDeereProxyAPI";
import LibraCartProvider, { useLibraCartProxyAPI } from "./libracartProxyAPI";
import CNHiProvider, { useCNHiProxyAPI } from "./cnhiProxyAPI";
// import NoteProvider from "providers/noteAPI";
@ -86,11 +87,13 @@ export default function PondProvider(props: PropsWithChildren<any>) {
<ContractProvider>
<JohnDeereProvider>
<CNHiProvider>
<UsageProvider>
<KeyManagerProvider>
{children}
</KeyManagerProvider>
</UsageProvider>
<LibraCartProvider>
<UsageProvider>
<KeyManagerProvider>
{children}
</KeyManagerProvider>
</UsageProvider>
</LibraCartProvider>
</CNHiProvider>
</JohnDeereProvider>
</ContractProvider>
@ -163,5 +166,6 @@ export {
useUsageAPI,
useKeyManagerAPI,
useJohnDeereProxyAPI,
useCNHiProxyAPI
useCNHiProxyAPI,
useLibraCartProxyAPI
};

View file

@ -5,6 +5,7 @@ import { useSnackbar, useUserAPI } from "hooks";
import React, { useEffect, useState } from "react";
import JohnDeereIcon from "products/CommonIcons/johnDeereIcon";
import CNHiIcon from "products/CommonIcons/cnhiIcon";
import LibraCartIcon from "products/CommonIcons/libracartIcon";
//import AgLogo from "assets/whitelabels/AdaptiveAgriculture/AGLogoSquare.png";
import {
IsAdaptiveAgriculture
@ -60,6 +61,14 @@ const agFeatureList: ProductDetails[] = [
"Integrate with the Case New Holland Industrial Center to bring your data into our platform"
// bulletPoints: ["bullet one", "bullet two"],
// questions: [],
},
{
featureName: "libra-cart",
featureLogo: <LibraCartIcon />,
featureCost: 0,
cardImage: FeatureImageTest,
featureTitle: "Libra Cart",
longDescription: "Integrate with Libra Cart to bring your data into our platform"
}
];