app/NewRootComponent.tsx (275 lines of code) (raw):
import React, { useContext, useEffect, useState } from "react";
import { makeStyles } from "@material-ui/core/styles";
import { UserContext } from "@guardian/pluto-headers";
import {
Button,
Fade,
Grid,
Paper,
Typography,
List,
ListItem,
Link,
ButtonBase,
} from "@material-ui/core";
import Stack from "@mui/material/Stack";
import OpenInNewIcon from "@mui/icons-material/OpenInNew";
import ProjectsPanel from "./panels/ProjectsPanel";
import clsx from "clsx";
import DeliverablesPanel from "./panels/DeliverablesPanel";
import { ChevronRight } from "@material-ui/icons";
import { makeLoginUrl, OAuthContext } from "@guardian/pluto-headers";
import NotLoggedInPanel from "./panels/NotLoggedInPanel";
import HelpPanel from "./panels/HelpPanel";
import ObitsPanel from "./panels/ObitsPanel";
import { getOverdueCommissions } from "./services/PlutoCore";
import Alert from "@material-ui/lab/Alert";
const useStyles = makeStyles((theme) => ({
fullwidthAlert: {
"&:hover": {
backgroundColor: theme.palette.warning.light,
cursor: "pointer",
textDecoration: "underline",
},
width: "100%",
},
overdueAlert: {
margin: theme.spacing(2, 0),
padding: theme.spacing(2),
backgroundColor: theme.palette.error.main,
color: theme.palette.getContrastText(theme.palette.error.main),
borderRadius: theme.shape.borderRadius,
},
overdueListItem: {
"&:hover": {
backgroundColor: theme.palette.error.light, // Lighter red on hover
cursor: "pointer", // Change cursor to pointer to indicate clickable
//underline the link on hover
textDecoration: "underline",
},
},
overdueListLink: {
textDecoration: "none",
color: theme.palette.getContrastText(theme.palette.error.main),
"&:hover": {
textDecoration: "underline", // Underline on hover to indicate clickability
},
},
panelContent: {
padding: "1em",
maxWidth: "800px", // Set a maxWidth here for the content
margin: "auto", // This will handle the centering
},
bannerText: {
textAlign: "center",
},
separated: {
marginBottom: "1em",
},
forceWhite: {
color: theme.palette.common.white,
textShadow: "2px 2px 4px #00000070",
},
buttonBaseLink: {
width: "100%",
justifyContent: "flex-start",
textDecoration: "none", // Ensure the link is not underlined by default
"&:hover": {
textDecoration: "underline", // Add underline on hover
},
},
}));
const LoggedInRoot: React.FC = () => {
const [showDeliverables, setShowDeliverables] = useState(true);
const [showHelp, setShowHelp] = useState(true);
const classes = useStyles();
const hideHelp = () => {
localStorage.setItem("pluto-hide-help", "true");
setShowHelp(false);
};
useEffect(() => {
setShowHelp(localStorage.getItem("pluto-hide-help") !== "true");
}, []);
return (
<>
<Typography
variant="h6"
className={clsx(
classes.bannerText,
classes.separated,
classes.forceWhite
)}
>
What do you need to find?
</Typography>
<Grid container justifyContent="center" spacing={6}>
<Fade in={showHelp}>
{showHelp ? (
<Grid item xs={12} md={12} lg={12} className={classes.panelContent}>
<HelpPanel
className={classes.panelContent}
hideRequested={hideHelp}
/>
</Grid>
) : (
<span></span>
)}
</Fade>
<Fade in={true}>
<Grid item xs={12} md={12} lg={12}>
<ProjectsPanel className={classes.panelContent} />
</Grid>
</Fade>
<Fade in={showDeliverables}>
<Grid item xs={12} md={12} lg={12}>
<DeliverablesPanel
className={classes.panelContent}
//always show deliverables panel now, as we have the general "search for deliverables" option above
onLoaded={(haveContent) => setShowDeliverables(true)}
/>
</Grid>
</Fade>
<Fade in={true}>
<Grid item xs={12} md={12} lg={12}>
<ObitsPanel className={classes.panelContent} obitsToShow={4} />
</Grid>
</Fade>
</Grid>
</>
);
};
const LoggedOutRoot: React.FC = () => {
//const classes = rootComponentStyles();
const oauthContext = useContext(OAuthContext);
const doLogin = () => {
oauthContext
? window.location.assign(makeLoginUrl(oauthContext))
: alert(
"Could not load login metadata, this should not happen. Please contact multimediatech."
);
};
return (
<NotLoggedInPanel
bannerText="You need to log in to access the Multimedia production system.
When prompted enter your email address in the format firstname.lastname@theguardian.com,
and then approve on your phone when prompted from the Microsoft Authenticator app"
>
<Grid item>
<Button
style={{ marginLeft: "auto", marginRight: "auto" }}
variant="contained"
endIcon={<ChevronRight />}
onClick={doLogin}
>
Log me in
</Button>
</Grid>
</NotLoggedInPanel>
);
};
const NewRootComponent: React.FC = () => {
const userContext = useContext(UserContext);
const classes = useStyles(); // Use custom useStyles instead of rootComponentStyles for the alert
const [overdueCommissions, setOverdueCommissions] = useState([]);
useEffect(() => {
if (userContext.profile) {
const user = `${userContext.profile.first_name}_${userContext.profile.family_name}`;
console.log("Fetching overdue commissions for", userContext.profile);
Promise.all([
getOverdueCommissions(user, "In Production"),
getOverdueCommissions(user, "New"),
])
.then((results) => {
// Manually concatenate the arrays instead of using flat()
const combinedResults = [].concat(...results);
setOverdueCommissions(combinedResults || []);
})
.catch((error) => {
console.error("Failed to fetch overdue commissions:", error);
});
}
}, [userContext.profile]);
const displayName = () =>
userContext.profile
? userContext.profile.first_name || userContext.profile.family_name
? `${userContext.profile.first_name} ${userContext.profile.family_name}`
: userContext.profile.username
: undefined;
const renderOverdueCommissionsAlert = () => (
<Stack sx={{ width: "100%" }} spacing={2}>
<Alert variant="filled" className={classes.overdueAlert} severity="error">
You have {overdueCommissions.length} Commission
{overdueCommissions.length > 1
? "s that have passed their completion dates. Please set their statuses to 'Completed' or adjust their scheduled completion dates if the commissions are still ongoing."
: " that has passed its completion date. Please set its status to 'Completed' or adjust the scheduled completion date if the commission is still ongoing."}
</Alert>
{overdueCommissions.map((entry) => (
<ButtonBase
key={entry["id"]}
component="a"
href={`/pluto-core/commission/${entry["id"]}`}
className={classes.buttonBaseLink}
>
<Alert
variant="filled"
className={classes.fullwidthAlert}
severity="warning"
style={{ flexGrow: 1 }}
>
{entry["title"]}
</Alert>
<span
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
const newWindow = window.open(
`/pluto-core/commission/${entry["id"]}`,
"newwindow",
"width=800,height=600,left=200,top=200,toolbar=no,menubar=no,scrollbars=no,resizable=yes"
);
if (newWindow) {
const checkWindowClosed = setInterval(() => {
if (newWindow.closed) {
clearInterval(checkWindowClosed);
window.location.reload();
}
}, 1000);
} else {
console.error(
"Failed to open the new window. This could be due to a popup blocker or browser policy."
);
}
}}
style={{ cursor: "pointer", padding: "0 12px" }}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="26"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="feather feather-external-link"
>
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
<polyline points="15 3 21 3 21 9"></polyline>
<line x1="10" y1="14" x2="21" y2="3"></line>
</svg>
</span>
</ButtonBase>
))}
</Stack>
);
return (
<>
{overdueCommissions.length > 0 && renderOverdueCommissionsAlert()}
<Typography
variant="h1"
className={clsx(classes.bannerText, classes.forceWhite)}
>
{userContext.profile ? `Welcome ${displayName()}` : "Welcome to Pluto"}
</Typography>
{userContext.profile ? <LoggedInRoot /> : <LoggedOutRoot />}
</>
);
};
export default NewRootComponent;