added the marketplace page to be able to add the jd and cnh features

This commit is contained in:
csawatzky 2025-04-11 12:17:20 -06:00
parent 2816884771
commit 411ff5b14c
5 changed files with 336 additions and 0 deletions

View file

@ -21,6 +21,7 @@ import Ventilation from "pages/VentEditor";
// import Transactions from "pages/Transactions"; // import Transactions from "pages/Transactions";
// import BinCableEstimator from "pages/BinCableEstimator"; // import BinCableEstimator from "pages/BinCableEstimator";
import { useGlobalState } from "providers"; import { useGlobalState } from "providers";
import Marketplace from "userFeatures/UserFeatures";
// import Fields from "pages/Fields"; // import Fields from "pages/Fields";
//import Site from "pages/Site"; //import Site from "pages/Site";
@ -329,6 +330,7 @@ export default function Router(props: Props) {
<Route path="api" element={<APIDocs />} /> <Route path="api" element={<APIDocs />} />
} }
<Route path="fields" element={<Fields />} /> <Route path="fields" element={<Fields />} />
<Route path="marketplace" element={<Marketplace />} />
{/* Map pages */} {/* Map pages */}
<Route path="visualFarm" element={<FieldMap />} /> <Route path="visualFarm" element={<FieldMap />} />

View file

@ -366,6 +366,18 @@ export default function SideNavigator(props: Props) {
</ListItemButton> </ListItemButton>
</Tooltip> </Tooltip>
} }
<Tooltip title="Marketplace" placement="right">
<ListItemButton
id="tour-marketplace"
onClick={() => goTo("/marketplace")}
classes={getClasses("/marketplace")}
>
<ListItemIcon>
<Code />
</ListItemIcon>
{open && <ListItemText primary="Marketplace" />}
</ListItemButton>
</Tooltip>
</List> </List>
) )
} }

10
src/pages/Marketplace.tsx Normal file
View file

@ -0,0 +1,10 @@
import PageContainer from "./PageContainer";
import UserFeatures from "userFeatures/UserFeatures";
export default function Marketplace() {
return (
<PageContainer>
<UserFeatures />
</PageContainer>
);
}

View file

@ -0,0 +1,169 @@
import {
// Accordion,
// AccordionDetails,
// AccordionSummary,
Avatar,
Box,
Button,
Card,
DialogActions,
DialogContent,
Divider,
Grid2 as Grid,
List,
ListItem,
Typography
} from "@mui/material";
// import { Close, ExpandMore } from "@material-ui/icons";
import { Close } from "@mui/icons-material";
import ResponsiveDialog from "common/ResponsiveDialog";
//import { useMobile } from "hooks";
import React, { useState } from "react";
import { ProductDetails } from "./UserFeatures";
interface Props {
feature: ProductDetails;
hasFeature: boolean;
updateFeature: (add: boolean) => void;
}
export default function FeatureCard(props: Props) {
const { feature, hasFeature, updateFeature } = props;
const [openDialog, setOpenDialog] = useState(false);
// const [currentQuestion, setCurrentQuestion] = useState(0);
//const isMobile = useMobile()
// const handleExpand = (index: number) => {
// setCurrentQuestion(index === currentQuestion ? 0 : index);
// };
const productDialog = () => {
return (
<ResponsiveDialog
open={openDialog}
onClose={() => {
setOpenDialog(false);
}}>
<DialogActions style={{ padding: 0 }}>
<img width="100%" alt={feature.featureName} src={feature.cardImage} />
<Button
onClick={() => {
setOpenDialog(false);
}}
style={{ position: "absolute", top: "10px" }}>
<Close />
</Button>
</DialogActions>
<DialogContent style={{ padding: 8 }}>
<List>
<ListItem>
<Grid
container
direction="row"
alignItems="center"
spacing={2}
wrap="nowrap"
justifyContent="space-between">
<Grid>
<Avatar style={{ height: 40, width: 40 }}>{feature.featureLogo}</Avatar>
</Grid>
<Grid>
<Typography style={{ fontWeight: 650, fontSize: 15 }}>
{feature.featureTitle}
</Typography>
</Grid>
<Grid>
<Button
variant="contained"
onClick={() => {
updateFeature(!hasFeature);
}}>
{hasFeature ? "Deactivate" : "Activate"}
</Button>
<Typography style={{ fontWeight: 350, fontSize: 15 }}>
{feature.featureCost > 0 ? feature.featureCost : "Free"}
</Typography>
</Grid>
</Grid>
</ListItem>
{/* <Divider />
<ListItem>
Possibly put Screenshots Here
</ListItem> */}
<Divider />
<ListItem>
<Box>
<Typography style={{ fontWeight: 350, fontSize: 13 }}>
{feature.longDescription}
</Typography>
{/* {feature.bulletPoints && (
<ul>
{feature.bulletPoints.map((b, i) => (
<li key={i} style={{ display: "list-item" }}>
<Typography style={{ fontWeight: 350, fontSize: 13 }}>{b}</Typography>
</li>
))}
</ul>
)} */}
</Box>
</ListItem>
<Divider />
<ListItem disableGutters>
<Box style={{ width: "100%" }}>
{/* {feature.questions.map((q, i) => (
<Accordion
key={i}
expanded={currentQuestion === i + 1}
onChange={() => handleExpand(i + 1)}>
<AccordionSummary expandIcon={<ExpandMore />}>
<Typography style={{ fontWeight: 650, fontSize: 13 }}>
{q.question}
</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography style={{ fontWeight: 350, fontSize: 13 }}>{q.answer}</Typography>
</AccordionDetails>
</Accordion>
))} */}
</Box>
</ListItem>
</List>
</DialogContent>
</ResponsiveDialog>
);
};
return (
<React.Fragment>
{productDialog()}
<Card
style={{ height: "100%", cursor: "pointer" }}
onClick={() => {
setOpenDialog(true);
}}>
<Box width="100%" alignItems="center" display="flex" justifyContent="center">
<img width="100%" alt={feature.featureName} src={feature.cardImage} />
</Box>
<Grid
style={{ padding: 10 }}
container
direction="row"
alignItems="center"
spacing={1}
wrap="nowrap">
<Grid>
<Avatar style={{ height: 40, width: 40 }}>{feature.featureLogo}</Avatar>
</Grid>
<Grid>
<Typography style={{ fontWeight: 650, fontSize: 17 }}>
{feature.featureTitle}
</Typography>
<Typography style={{ fontWeight: 350, fontSize: 15 }}>
{feature.featureCost > 0 ? feature.featureCost : "Free"}
</Typography>
</Grid>
</Grid>
</Card>
</React.Fragment>
);
}

View file

@ -0,0 +1,143 @@
import { Box, Grid2 as Grid } from "@mui/material";
import { useGlobalState } from "providers";
import { useSnackbar, useUserAPI } from "hooks";
import React, { useEffect, useState } from "react";
import JohnDeereIcon from "products/CommonIcons/johnDeereIcon";
import CNHiIcon from "products/CommonIcons/cnhiIcon";
//import AgLogo from "assets/whitelabels/AdaptiveAgriculture/AGLogoSquare.png";
import {
IsAdaptiveAgriculture
// IsAdCon,
// IsMiVent,
// IsOmniAir
} from "services/whiteLabel";
import FeatureCard from "./FeatureCard";
//import { ImgIcon } from "common/ImgIcon";
//images to import for the image in the card for the feature make sure to have a 2:1 aspect ratio to keep the cards symmetrical
import FeatureImageTest from "assets/marketplaceImages/VisualFarmMPImage.png";
// export interface FAQ {
// question: string;
// answer: string;
// }
export interface ProductDetails {
featureName: string; //string matching the entry in the features array found in Users.tsx
featureTitle: string; //title of feature to display to user
featureCost: number;
cardImage: string; //image to display on the feature card
featureLogo: JSX.Element; //image to display in the logo section of the card and product dialog
//galleryImages: string[] //screenshots to show the feature in action
longDescription: string; //a longer description of the feature to show in the dialog
// bulletPoints?: string[];
// questions: FAQ[];
hidden?: boolean; //if the feature is not quite ready for the public ie in testing, if a user has the beta flag they will see these hidden features
}
const agFeatureList: ProductDetails[] = [
{
featureName: "john-deere",
featureLogo: <JohnDeereIcon />,
featureCost: 0,
cardImage: FeatureImageTest,
featureTitle: "John Deere Operations Center",
longDescription:
"Integrate with the John Deere Operations Center to bring your data into our platform"
// bulletPoints: ["bullet one", "bullet two"],
// questions: [
// { question: "question 1", answer: "answer 1" },
// { question: "question 2", answer: "answer 2" }
// ]
},
{
featureName: "cnhi",
featureLogo: <CNHiIcon />,
featureCost: 0,
cardImage: FeatureImageTest,
featureTitle: "Case New Holland Industrial Center",
longDescription:
"Integrate with the Case New Holland Industrial Center to bring your data into our platform"
// bulletPoints: ["bullet one", "bullet two"],
// questions: [],
}
];
// const constructionFeatureList: ProductDetails[] = []
// const aviationFeatureList: ProductDetails[] = []
// const miningFeatureList: ProductDetails[] = []
// const universalFeatureList: ProductDetails[] = []
export default function Marketplace() {
const [{ user }, dispatch] = useGlobalState();
const userAPI = useUserAPI();
const [userFeatures, setUserFeatures] = useState<string[]>([]);
const [featureList, setFeaturList] = useState<ProductDetails[]>([]);
const { error, success } = useSnackbar();
useEffect(() => {
let list: ProductDetails[] = [];
if (IsAdaptiveAgriculture()) {
list = list.concat(agFeatureList);
}
// if(IsAdCon()){
// list = list.concat(constructionFeatureList)
// }
// if(IsOmniAir()){
// list = list.concat(aviationFeatureList)
// }
// if(IsMiVent()){
// list = list.concat(miningFeatureList)
// }
// list = list.concat(universalFeatureList)
setFeaturList(list);
}, []);
useEffect(() => {
setUserFeatures(user.settings.features);
}, [user]);
const updateUser = (feature: string, added: boolean) => {
if (added) {
user.settings.features.push(feature);
} else {
user.settings.features = user.settings.features.filter(v => v !== feature);
}
setUserFeatures([...user.settings.features]);
userAPI
.updateUser(user.id(), user.protobuf())
.then(resp => {
success("Updated " + feature + " feature");
dispatch({ key: "user", value: user });
})
.catch(err => {
error("Failed to update " + feature + " feature");
});
};
return (
<Box padding={2}>
<Grid container spacing={3} justifyContent="space-between" alignItems="center" alignContent="center">
{featureList.map(feat => {
if (!feat.hidden || user.hasFeature("beta")) {
return (
<Grid key={feat.featureName} size={{ xs:12, sm:6, md:4, lg:4}}>
<FeatureCard
feature={feat}
updateFeature={checked => {
updateUser(feat.featureName, checked);
}}
hasFeature={userFeatures.includes(feat.featureName)}
/>
</Grid>
);
}
return null;
})}
</Grid>
</Box>
);
}