From 8a67d9670fdf60ed21ed0e51dbbd164aa79cf82f Mon Sep 17 00:00:00 2001 From: Josh Slaughter <8338893+jdslaugh@users.noreply.github.com> Date: Wed, 25 Sep 2024 16:48:45 -0700 Subject: [PATCH 1/5] frontend: Project Catalog - Rework of layout --- frontend/packages/core/src/index.tsx | 1 + frontend/packages/core/src/link.tsx | 5 + .../projectCatalog/src/config/index.tsx | 137 --------- .../src/details/components/breadcrumbs.tsx | 46 +++ .../src/details/{ => components}/card.tsx | 69 +++-- .../src/details/components/header.tsx | 29 ++ .../src/details/components/layout.tsx | 106 +++++++ .../src/details/components/link-settings.tsx | 110 +++++++ .../src/details/config/index.tsx | 135 +++++++++ .../projectCatalog/src/details/context.tsx | 1 + .../projectCatalog/src/details/header.tsx | 76 ----- .../projectCatalog/src/details/helpers.tsx | 73 ----- .../projectCatalog/src/details/index.tsx | 273 ++++++------------ .../src/details/info/messengerRow.tsx | 2 +- .../src/details/info/repositoryRow.tsx | 2 +- .../projectCatalog/src/details/resolver.tsx | 44 +++ .../projectCatalog/src/helpers/index.ts | 3 + .../projectCatalog/src/helpers/last-event.tsx | 58 ++++ .../projectCatalog/src/helpers/link-text.tsx | 19 ++ .../workflows/projectCatalog/src/index.tsx | 46 +-- 20 files changed, 696 insertions(+), 539 deletions(-) delete mode 100644 frontend/workflows/projectCatalog/src/config/index.tsx create mode 100644 frontend/workflows/projectCatalog/src/details/components/breadcrumbs.tsx rename frontend/workflows/projectCatalog/src/details/{ => components}/card.tsx (75%) create mode 100644 frontend/workflows/projectCatalog/src/details/components/header.tsx create mode 100644 frontend/workflows/projectCatalog/src/details/components/layout.tsx create mode 100644 frontend/workflows/projectCatalog/src/details/components/link-settings.tsx create mode 100644 frontend/workflows/projectCatalog/src/details/config/index.tsx delete mode 100644 frontend/workflows/projectCatalog/src/details/header.tsx delete mode 100644 frontend/workflows/projectCatalog/src/details/helpers.tsx create mode 100644 frontend/workflows/projectCatalog/src/details/resolver.tsx create mode 100644 frontend/workflows/projectCatalog/src/helpers/index.ts create mode 100644 frontend/workflows/projectCatalog/src/helpers/last-event.tsx create mode 100644 frontend/workflows/projectCatalog/src/helpers/link-text.tsx diff --git a/frontend/packages/core/src/index.tsx b/frontend/packages/core/src/index.tsx index b0dbb47f68..b739636284 100644 --- a/frontend/packages/core/src/index.tsx +++ b/frontend/packages/core/src/index.tsx @@ -61,6 +61,7 @@ export { Typography } from "./typography"; export { default as ClutchApp } from "./AppProvider"; export { useTheme } from "./AppProvider/themes"; export { ThemeProvider } from "./Theme"; +export { getDisplayName } from "./utils"; export { css as EMOTION_CSS, keyframes as EMOTION_KEYFRAMES } from "@emotion/react"; diff --git a/frontend/packages/core/src/link.tsx b/frontend/packages/core/src/link.tsx index 59890e5126..853ba551a4 100644 --- a/frontend/packages/core/src/link.tsx +++ b/frontend/packages/core/src/link.tsx @@ -8,6 +8,7 @@ type TextTransform = "none" | "capitalize" | "uppercase" | "lowercase" | "initia const StyledLink = styled(MuiLink)<{ $textTransform: LinkProps["textTransform"]; + $whiteSpace: LinkProps["whiteSpace"]; }>( ({ theme }: { theme: Theme }) => ({ display: "flex", @@ -20,11 +21,13 @@ const StyledLink = styled(MuiLink)<{ }), props => ({ textTransform: props.$textTransform, + ...(props.$whiteSpace ? { whiteSpace: props.$whiteSpace } : {}), }) ); export interface LinkProps extends Pick { textTransform?: TextTransform; + whiteSpace?: React.CSSProperties["whiteSpace"]; target?: React.AnchorHTMLAttributes["target"]; } @@ -33,6 +36,7 @@ export const Link = ({ textTransform = "none", target = "_blank", children, + whiteSpace, ...props }: LinkProps) => ( {children} diff --git a/frontend/workflows/projectCatalog/src/config/index.tsx b/frontend/workflows/projectCatalog/src/config/index.tsx deleted file mode 100644 index 863be0c5d0..0000000000 --- a/frontend/workflows/projectCatalog/src/config/index.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import React from "react"; -import type { clutch as IClutch } from "@clutch-sh/api"; -import type { ClutchError } from "@clutch-sh/core"; -import { - Error, - Grid, - styled, - Tab, - Tabs, - useLocation, - useNavigate, - useParams, -} from "@clutch-sh/core"; - -import type { ProjectConfigPage, ProjectDetailsConfigWorkflowProps } from ".."; -import { ProjectDetailsContext } from "../details/context"; -import ProjectHeader from "../details/header"; -import { fetchProjectInfo } from "../details/helpers"; - -const StyledContainer = styled(Grid)({ - padding: "32px", -}); - -const Config: React.FC = ({ children, defaultRoute = "/" }) => { - const { projectId, configType = defaultRoute } = useParams(); - const location = useLocation(); - const navigate = useNavigate(); - const [projectInfo, setProjectInfo] = React.useState( - null - ); - const [error, setError] = React.useState(null); - const [configPages, setConfigPages] = React.useState([]); - const [selectedPage, setSelectedPage] = React.useState(0); - const projInfo = React.useMemo(() => ({ projectInfo }), [projectInfo]); - - React.useEffect(() => { - fetchProjectInfo(projectId).then(setProjectInfo).catch(setError); - }, []); - - React.useEffect(() => { - if (configPages && configPages.length) { - const splitLoc = location.pathname.split("/"); - const selectedPath = configPages[selectedPage]?.props?.path; - - if (selectedPath) { - if (splitLoc[splitLoc.length - 1] !== "config") { - splitLoc.splice(splitLoc.length - 1, 1, selectedPath); - } else { - splitLoc.push(selectedPath); - } - - // Used to reduce the number of navigation calls when the user is navigating between tabs - if (splitLoc.join("/") !== location.pathname.replace(/%20/, " ")) { - navigate( - { - pathname: splitLoc.join("/"), - search: window.location.search, - }, - { replace: true } - ); - } - } - } - }, [configPages, selectedPage]); - - React.useEffect(() => { - if (children) { - const validPages: ProjectConfigPage[] = []; - - React.Children.forEach(children, (child, index) => { - if (React.isValidElement(child)) { - const { title, path, onError } = child?.props || {}; - - if (title) { - validPages.push( - React.cloneElement(child, { - onError: (e: ClutchError) => { - if (onError) { - onError(e); - } - setError(e); - }, - }) - ); - - if (configType === path) { - setSelectedPage(index); - } - } - } - }); - - setConfigPages(validPages); - } - }, [children]); - - return ( - - - - - - {configPages && configPages.length > 1 ? ( - - - {configPages.map((page, i) => ( - setSelectedPage(i)} - /> - ))} - - - ) : null} - {error && ( - - - - )} - - {configPages && configPages.length > 0 && configPages[selectedPage]} - - - - ); -}; - -export default Config; diff --git a/frontend/workflows/projectCatalog/src/details/components/breadcrumbs.tsx b/frontend/workflows/projectCatalog/src/details/components/breadcrumbs.tsx new file mode 100644 index 0000000000..724742caf7 --- /dev/null +++ b/frontend/workflows/projectCatalog/src/details/components/breadcrumbs.tsx @@ -0,0 +1,46 @@ +import React from "react"; +import { Typography, useTheme } from "@clutch-sh/core"; +import { alpha, Breadcrumbs, Link } from "@mui/material"; + +interface Route { + title: string; + path?: string; +} + +export interface BreadCrumbsProps { + routes?: Route[]; +} + +const BreadCrumbs = ({ routes = [] }: BreadCrumbsProps) => { + const theme = useTheme(); + routes.unshift({ title: "Project Catalog", path: "/catalog" }); + + let builtRoute = routes[0].path; + + const buildCrumb = (route: Route) => { + if (route?.path && route?.path !== builtRoute) { + builtRoute += `/${route.path}`; + } + + return ( + + {route.path ? ( + + {route.title} + + ) : ( + route.title + )} + + ); + }; + + return {routes.map(buildCrumb)}; +}; + +export default BreadCrumbs; diff --git a/frontend/workflows/projectCatalog/src/details/card.tsx b/frontend/workflows/projectCatalog/src/details/components/card.tsx similarity index 75% rename from frontend/workflows/projectCatalog/src/details/card.tsx rename to frontend/workflows/projectCatalog/src/details/components/card.tsx index 46e4fe3446..ff2d7dd022 100644 --- a/frontend/workflows/projectCatalog/src/details/card.tsx +++ b/frontend/workflows/projectCatalog/src/details/components/card.tsx @@ -2,6 +2,7 @@ import React from "react"; import type { TypographyProps } from "@clutch-sh/core"; import { Card, ClutchError, Error, Grid, styled, Typography } from "@clutch-sh/core"; import { LinearProgress, Theme } from "@mui/material"; +import type { GridProps } from "@mui/material"; enum CardType { DYNAMIC = "Dynamic", @@ -15,7 +16,9 @@ export interface CatalogDetailsCard { interface CardTitleProps { title?: string | Element | React.ReactNode; titleVariant?: TypographyProps["variant"]; + titleAlign?: GridProps["alignItems"]; titleIcon?: React.ReactNode; + titleIconAlign?: GridProps["alignItems"]; endAdornment?: React.ReactNode; } @@ -46,10 +49,14 @@ interface CardProps extends CatalogDetailsCard, BaseCardProps {} const StyledCard = styled(Card)({ width: "100%", - height: "fit-content", + height: "100%", padding: "16px", }); +const StyledGrid = styled(Grid)({ + height: "fit-content", +}); + const StyledProgressContainer = styled("div")(({ theme }: { theme: Theme }) => ({ marginBottom: "8px", marginTop: "-12px", @@ -66,30 +73,35 @@ const StyledTitle = styled(Grid)({ textTransform: "capitalize", }); -const StyledTitleContainer = styled(Grid)({ - marginBottom: "8px", -}); - -const BodyContainer = styled("div")({ - paddingLeft: "4px", -}); - -const CardTitle = ({ title, titleVariant = "h4", titleIcon, endAdornment }: CardTitleProps) => ( - +const CardTitle = ({ + title, + titleVariant = "h4", + titleAlign = "flex-start", + titleIcon, + titleIconAlign = "center", + endAdornment, +}: CardTitleProps) => ( + {title && ( - + {titleIcon && {titleIcon}} {title} - + )} {endAdornment && ( ( - <> + {loadingIndicator && loading && ( - - - + + + + + )} - {error ? : children} - + + <>{error ? : children} + + ); const BaseCard = ({ loading, error, ...props }: CardProps) => { @@ -153,8 +169,14 @@ const BaseCard = ({ loading, error, ...props }: CardProps) => { return ( - - + + + + + + + + ); }; @@ -163,4 +185,7 @@ const DynamicCard = (props: BaseCardProps) => ; +type DetailCard = CatalogDetailsCard | typeof DynamicCard | typeof MetaCard; + export { CardType, DynamicCard, MetaCard }; +export type { DetailCard }; diff --git a/frontend/workflows/projectCatalog/src/details/components/header.tsx b/frontend/workflows/projectCatalog/src/details/components/header.tsx new file mode 100644 index 0000000000..e275d0aee7 --- /dev/null +++ b/frontend/workflows/projectCatalog/src/details/components/header.tsx @@ -0,0 +1,29 @@ +import React from "react"; +import { Grid, styled, Typography } from "@clutch-sh/core"; + +export interface ProjectHeaderProps { + title?: string; + description?: string; +} + +const StyledContainer = styled(Grid)({ + width: "100%", + height: "100%", +}); + +const ProjectHeader = ({ title, description = "" }: ProjectHeaderProps) => ( + + + + {title} + + + {description.length > 0 && ( + + {description} + + )} + +); + +export default ProjectHeader; diff --git a/frontend/workflows/projectCatalog/src/details/components/layout.tsx b/frontend/workflows/projectCatalog/src/details/components/layout.tsx new file mode 100644 index 0000000000..0167bb8e8a --- /dev/null +++ b/frontend/workflows/projectCatalog/src/details/components/layout.tsx @@ -0,0 +1,106 @@ +import React from "react"; +import type { clutch as IClutch } from "@clutch-sh/api"; +import { Grid, QuickLinkGroup, styled, useNavigate, useParams } from "@clutch-sh/core"; +import { ProjectDetailsContext } from "../context"; +import BreadCrumbs from "./breadcrumbs"; +import type { BreadCrumbsProps } from "./breadcrumbs"; +import ProjectHeader, { ProjectHeaderProps } from "./header"; +import QuickLinksAndSettings from "./link-settings"; +import { ProjectDetailsWorkflowProps } from ".."; +import fetchProjectInfo from "../resolver"; + +export interface CatalogLayoutProps + extends BreadCrumbsProps, + ProjectHeaderProps, + Pick { + children?: React.ReactNode; + quickLinkSettings?: boolean; +} + +const StyledContainer = styled(Grid)({ + padding: "8px 24px", +}); + +const CatalogLayout = ({ + routes = [], + title, + description, + configLinks = [], + children, + allowDisabled, + quickLinkSettings = true, +}: CatalogLayoutProps) => { + const { projectId } = useParams(); + const navigate = useNavigate(); + const [projectInfo, setProjectInfo] = React.useState( + null + ); + const projInfo = React.useMemo(() => ({ projectId, projectInfo }), [projectId, projectInfo]); + + const redirectNotFound = () => navigate(`/${projectId}/notFound`, { replace: true }); + + const fetchData = () => + fetchProjectInfo(projectId, allowDisabled) + .then(data => { + if (!data) { + redirectNotFound(); + return; + } + setProjectInfo(data as IClutch.core.project.v1.IProject); + }) + .catch(err => { + console.error(err); + }); + + React.useEffect(() => { + let interval; + + fetchData(); + + interval = setInterval(fetchData, 30000); + + return () => (interval ? clearInterval(interval) : undefined); + }, []); + + return ( + + + + + + + + + + + + + + {projectInfo && ( + + )} + + + + + {children && children} + + + + ); +}; + +export default CatalogLayout; diff --git a/frontend/workflows/projectCatalog/src/details/components/link-settings.tsx b/frontend/workflows/projectCatalog/src/details/components/link-settings.tsx new file mode 100644 index 0000000000..d19d090e37 --- /dev/null +++ b/frontend/workflows/projectCatalog/src/details/components/link-settings.tsx @@ -0,0 +1,110 @@ +import React from "react"; +import { useParams } from "react-router-dom"; +import { + checkFeatureEnabled, + Grid, + IconButton, + Link, + Popper, + PopperItem, + QuickLinkGroup, + QuickLinksCard, + styled, + Typography, +} from "@clutch-sh/core"; +import SettingsIcon from "@mui/icons-material/Settings"; +import { isEmpty } from "lodash"; + +import type { ProjectConfigLink } from ".."; + +interface QuickLinksAndSettingsProps { + linkGroups: QuickLinkGroup[]; + configLinks?: ProjectConfigLink[]; + showSettings: boolean; +} + +const StyledPopperItem = styled(PopperItem)({ + "&&&": { + height: "auto", + }, + "& span.MuiTypography-root": { + padding: "0", + }, + "& a.MuiTypography-root": { + padding: "4px 16px", + }, +}); + +const QuickLinksAndSettings = ({ + linkGroups, + configLinks = [], + showSettings, +}: QuickLinksAndSettingsProps) => { + const { projectId } = useParams(); + const anchorRef = React.useRef(null); + const [open, setOpen] = React.useState(false); + const [links, setLinks] = React.useState(configLinks); + + React.useEffect(() => { + const projectConfigFlag = checkFeatureEnabled({ feature: "projectCatalogSettings" }); + if (projectConfigFlag) { + setLinks([ + { + title: "Project Configuration", + path: `/catalog/${projectId}/config`, + icon: , + }, + ...links, + ]); + } + }, []); + + return ( + + {!isEmpty(linkGroups) && ( + + + + )} + {showSettings && links && links.length > 0 && ( + + setOpen(o => !o)} size="medium"> + + + setOpen(false)} + placement="bottom-end" + > + {links.map(link => ( + + + + {link.icon && {link.icon}} + + + {link.title} + + + + + + ))} + + + )} + + ); +}; + +export default QuickLinksAndSettings; diff --git a/frontend/workflows/projectCatalog/src/details/config/index.tsx b/frontend/workflows/projectCatalog/src/details/config/index.tsx new file mode 100644 index 0000000000..3ef248fe04 --- /dev/null +++ b/frontend/workflows/projectCatalog/src/details/config/index.tsx @@ -0,0 +1,135 @@ +import React from "react"; +import type { ClutchError } from "@clutch-sh/core"; +import { Error, Grid, Tab, Tabs, useLocation, useNavigate, useParams } from "@clutch-sh/core"; + +import type { ProjectCatalogProps, WorkflowProps } from "../.."; +import CatalogLayout from "../components/layout"; + +export interface ProjectConfigProps { + title: string; + path: string; + onError?: (error: ClutchError) => void; +} + +export type ProjectConfigPage = React.ReactElement; + +export interface ProjectDetailsConfigWorkflowProps extends WorkflowProps, ProjectCatalogProps { + children?: ProjectConfigPage | ProjectConfigPage[]; + description?: string; + defaultRoute?: string; +} + +const Config = ({ children, defaultRoute = "/" }: ProjectDetailsConfigWorkflowProps) => { + const { configType = defaultRoute } = useParams(); + const location = useLocation(); + const navigate = useNavigate(); + const [error, setError] = React.useState(null); + const [configPages, setConfigPages] = React.useState([]); + const [selectedPage, setSelectedPage] = React.useState(0); + + React.useEffect(() => { + if (configPages && configPages.length) { + const splitLoc = location.pathname.split("/"); + const selectedPath = configPages[selectedPage]?.props?.path; + + if (selectedPath) { + if (splitLoc[splitLoc.length - 1] !== "config") { + splitLoc.splice(splitLoc.length - 1, 1, selectedPath); + } else { + splitLoc.push(selectedPath); + } + + // Used to reduce the number of navigation calls when the user is navigating between tabs + if (splitLoc.join("/") !== location.pathname.replace(/%20/, " ")) { + navigate( + { + pathname: splitLoc.join("/"), + search: window.location.search, + }, + { replace: true } + ); + } + } + } + }, [configPages, selectedPage]); + + React.useEffect(() => { + if (children) { + const validPages: ProjectConfigPage[] = []; + + React.Children.forEach(children, (child, index) => { + if (React.isValidElement(child)) { + const { title, path, onError } = child?.props || {}; + + if (title) { + validPages.push( + React.cloneElement(child, { + onError: (e: ClutchError) => { + if (onError) { + onError(e); + } + setError(e); + }, + }) + ); + + if (configType === path) { + setSelectedPage(index); + } + } + } + }); + + setConfigPages(validPages); + } + }, [children]); + + return ( + <> + {configPages && configPages.length > 1 ? ( + + + {configPages.map((page, i) => ( + setSelectedPage(i)} + /> + ))} + + + ) : null} + {error && ( + + + + )} + + {configPages && configPages.length > 0 && configPages[selectedPage]} + + + ); +}; + +const CatalogConfigPage = ({ + defaultRoute = "/", + description = "", + ...props +}: ProjectDetailsConfigWorkflowProps) => { + const { configType = defaultRoute } = useParams(); + + return ( + + + + ); +}; + +export default CatalogConfigPage; diff --git a/frontend/workflows/projectCatalog/src/details/context.tsx b/frontend/workflows/projectCatalog/src/details/context.tsx index f3121d3d8c..b7a1f794ee 100644 --- a/frontend/workflows/projectCatalog/src/details/context.tsx +++ b/frontend/workflows/projectCatalog/src/details/context.tsx @@ -3,6 +3,7 @@ import type { clutch as IClutch } from "@clutch-sh/api"; interface ContextProps { projectInfo: IClutch.core.project.v1.IProject | null; + projectId: string | null; } const ProjectDetailsContext = React.createContext(undefined); diff --git a/frontend/workflows/projectCatalog/src/details/header.tsx b/frontend/workflows/projectCatalog/src/details/header.tsx deleted file mode 100644 index 2e5e071d57..0000000000 --- a/frontend/workflows/projectCatalog/src/details/header.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import React from "react"; -import { Grid, styled, Typography, useTheme } from "@clutch-sh/core"; -import { alpha, Breadcrumbs, Link } from "@mui/material"; - -interface Route { - title: string; - path?: string; -} - -interface BreadCrumbProps { - routes: Route[]; -} - -interface ProjectHeaderProps extends BreadCrumbProps { - title: string; - description?: string; -} - -const StyledHeading = styled("div")({ - padding: "8px 0px 8px 0px", - textTransform: "capitalize", -}); - -const StyledContainer = styled(Grid)({ - width: "100%", - height: "100%", -}); - -const StyledCrumb = styled(Typography)({ - textTransform: "uppercase", -}); - -const BreadCrumbs = ({ routes = [] }: BreadCrumbProps) => { - const theme = useTheme(); - routes.unshift({ title: "Project Catalog", path: "/catalog" }); - - let builtRoute = routes[0].path; - - const buildCrumb = (route: Route) => { - if (route?.path && route?.path !== builtRoute) { - builtRoute += `/${route.path}`; - } - - return ( - - {route.path ? ( - - {route.title} - - ) : ( - route.title - )} - - ); - }; - - return {routes.map(buildCrumb)}; -}; - -const ProjectHeader = ({ title, routes, description = "" }: ProjectHeaderProps) => ( - - - - - - {title} - - {description.length > 0 && {description}} - -); - -export default ProjectHeader; diff --git a/frontend/workflows/projectCatalog/src/details/helpers.tsx b/frontend/workflows/projectCatalog/src/details/helpers.tsx deleted file mode 100644 index 4ec9add3aa..0000000000 --- a/frontend/workflows/projectCatalog/src/details/helpers.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import React from "react"; -import type { clutch as IClutch } from "@clutch-sh/api"; -import { client, Grid, Link, styled, TimeAgo as EventTime, Typography } from "@clutch-sh/core"; -import { faClock } from "@fortawesome/free-regular-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import * as _ from "lodash"; - -const StyledLink = styled(Link)({ - whiteSpace: "nowrap", -}); - -const fetchProjectInfo = ( - project: string, - allowDisabled: boolean = false -): Promise => - client - .post("/v1/project/getProjects", { projects: [project], excludeDependencies: true }) - .then(resp => { - const { results = {}, partialFailures } = resp.data as IClutch.project.v1.GetProjectsResponse; - - const projectResults = _.get(results, [project, "project"], {}); - - if (_.isEmpty(projectResults) && allowDisabled && partialFailures) { - // Will add disabled projects to the list of projects to display if requested - const failuresMap = partialFailures - .map(p => { - if ((_.get(p, "message", "") ?? "").includes("disabled")) { - const disabledProject = _.get(p, "details.[0]"); - _.set( - disabledProject, - ["data", "description"], - `${disabledProject.name} is disabled.` - ); - return disabledProject; - } - return null; - }) - .filter(Boolean); - - if (failuresMap.length) { - return failuresMap[0]; - } - } - - return projectResults; - }); - -const LinkText = ({ text, link }: { text: string; link?: string }) => { - const returnText = {text}; - - if (link && text) { - return {returnText}; - } - - return returnText; -}; - -const LastEvent = ({ time, ...props }: { time: number }) => { - return time ? ( - <> - - - - - - ago - - - - ) : null; -}; - -export { fetchProjectInfo, LastEvent, LinkText }; diff --git a/frontend/workflows/projectCatalog/src/details/index.tsx b/frontend/workflows/projectCatalog/src/details/index.tsx index 5880725c0c..a9bc6801d4 100644 --- a/frontend/workflows/projectCatalog/src/details/index.tsx +++ b/frontend/workflows/projectCatalog/src/details/index.tsx @@ -1,56 +1,29 @@ import React from "react"; -import { useParams } from "react-router-dom"; -import type { clutch as IClutch } from "@clutch-sh/api"; -import { - checkFeatureEnabled, - Grid, - IconButton, - Link, - Popper, - PopperItem, - QuickLinkGroup, - QuickLinksCard, - styled, - Tooltip, - Typography, -} from "@clutch-sh/core"; +import { Grid, Tooltip, useTheme } from "@clutch-sh/core"; import CodeOffIcon from "@mui/icons-material/CodeOff"; import GroupIcon from "@mui/icons-material/Group"; -import SettingsIcon from "@mui/icons-material/Settings"; -import { capitalize, isEmpty } from "lodash"; +import { capitalize } from "lodash"; -import type { CatalogDetailsChild, ProjectConfigLink, ProjectDetailsWorkflowProps } from ".."; - -import { CardType, DynamicCard, MetaCard } from "./card"; -import { ProjectDetailsContext } from "./context"; -import ProjectHeader from "./header"; -import { fetchProjectInfo } from "./helpers"; +import { CardType, DetailCard, DynamicCard, MetaCard } from "./components/card"; import ProjectInfoCard from "./info"; +import { useMediaQuery } from "@mui/material"; +import { type WorkflowProps, type ProjectCatalogProps, useProjectDetailsContext } from ".."; +import type { ProjectInfoChip } from "./info/chipsRow"; +import CatalogLayout from "./components/layout"; -interface QuickLinksAndSettingsProps { - linkGroups: QuickLinkGroup[]; - configLinks?: ProjectConfigLink[]; -} - -const StyledContainer = styled(Grid)({ - padding: "16px", -}); +export type CatalogDetailsChild = React.ReactElement; -const StyledHeadingContainer = styled(Grid)({ - marginBottom: "24px", -}); +export interface ProjectConfigLink { + title: string; + path: string; + icon?: React.ReactElement; +} -const StyledPopperItem = styled(PopperItem)({ - "&&&": { - height: "auto", - }, - "& span.MuiTypography-root": { - padding: "0", - }, - "& a.MuiTypography-root": { - padding: "4px 16px", - }, -}); +export interface ProjectDetailsWorkflowProps extends WorkflowProps, ProjectCatalogProps { + children?: CatalogDetailsChild | CatalogDetailsChild[]; + chips?: ProjectInfoChip[]; + configLinks?: ProjectConfigLink[]; +} const DisabledItem = ({ name }: { name: string }) => ( @@ -60,87 +33,13 @@ const DisabledItem = ({ name }: { name: string }) => ( ); -const QuickLinksAndSettingsBtn = ({ linkGroups, configLinks = [] }: QuickLinksAndSettingsProps) => { - const { projectId } = useParams(); - const anchorRef = React.useRef(null); - const [open, setOpen] = React.useState(false); - const [links, setLinks] = React.useState(configLinks); - - React.useEffect(() => { - const projectConfigFlag = checkFeatureEnabled({ feature: "projectCatalogSettings" }); - if (projectConfigFlag) { - setLinks([ - { - title: "Project Configuration", - path: `/catalog/${projectId}/config`, - icon: , - }, - ...links, - ]); - } - }, []); - - return ( - - {!isEmpty(linkGroups) && ( - - - - )} - {links && links.length > 0 && ( - - setOpen(o => !o)} size="medium"> - - - setOpen(false)} - placement="bottom-end" - > - {links.map(link => ( - - - - {link.icon && {link.icon}} - - - {link.title} - - - - - - ))} - - - )} - - ); -}; - -const Details: React.FC = ({ - children, - chips, - allowDisabled, - configLinks = [], -}) => { - const { projectId } = useParams(); - const [projectInfo, setProjectInfo] = React.useState( - null - ); +const Details = ({ children, chips }: ProjectDetailsWorkflowProps) => { + const { projectId, projectInfo } = useProjectDetailsContext() || {}; const [metaCards, setMetaCards] = React.useState([]); const [dynamicCards, setDynamicCards] = React.useState([]); - const projInfo = React.useMemo(() => ({ projectInfo }), [projectInfo]); + + const theme = useTheme(); + const shrink = useMediaQuery(theme.breakpoints.down("lg")); React.useEffect(() => { if (children) { @@ -153,16 +52,16 @@ const Details: React.FC = ({ switch (type) { case CardType.METADATA: - tempMetaCards.push(child); + tempMetaCards.push(child as CatalogDetailsChild); break; case CardType.DYNAMIC: - tempDynamicCards.push(child); + tempDynamicCards.push(child as CatalogDetailsChild); break; default: { if (child.type === DynamicCard) { - tempDynamicCards.push(child); + tempDynamicCards.push(child as CatalogDetailsChild); } else if (child.type === MetaCard) { - tempMetaCards.push(child); + tempMetaCards.push(child as CatalogDetailsChild); } // Do nothing, invalid card } @@ -193,70 +92,62 @@ const Details: React.FC = ({ }; return ( - - - {/* Column for project details and header */} - - - - {/* Static Header */} - - - {projectInfo && ( - - - - )} - - - - - {/* Static Info Card */} - } - fetchDataFn={() => fetchProjectInfo(projectId, allowDisabled)} - onSuccess={(data: unknown) => - setProjectInfo(data as IClutch.core.project.v1.IProject) - } - autoReload - loadingIndicator={false} - endAdornment={ - projectInfo?.data?.disabled ? : null - } - > - {projectInfo && } - - - {/* Custom Meta Cards */} - {metaCards.length > 0 && - metaCards.map(card => ( - - {card} - - ))} - - - {/* Custom Dynamic Cards */} - {dynamicCards.length > 0 && - dynamicCards.map(card => ( - - {card} - - ))} - - + <> + + + {projectInfo && ( + } + loadingIndicator={false} + endAdornment={projectInfo?.data?.disabled ? : null} + > + {projectInfo && } + + + )} - - + {metaCards.length > 0 && metaCards.map(card => {card})} + + + {dynamicCards.length > 0 && dynamicCards.map(card => {card})} + + + ); +}; + +const CatalogDetailsPage = ({ + allowDisabled, + configLinks, + ...props +}: ProjectDetailsWorkflowProps) => { + return ( + +
+ ); }; -export default Details; +export default CatalogDetailsPage; diff --git a/frontend/workflows/projectCatalog/src/details/info/messengerRow.tsx b/frontend/workflows/projectCatalog/src/details/info/messengerRow.tsx index 31e5927407..8499aa1e60 100644 --- a/frontend/workflows/projectCatalog/src/details/info/messengerRow.tsx +++ b/frontend/workflows/projectCatalog/src/details/info/messengerRow.tsx @@ -6,7 +6,7 @@ import { faSlack } from "@fortawesome/free-brands-svg-icons"; import { faComment } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { LinkText } from "../helpers"; +import { LinkText } from "../.."; const MessengerRow = ({ projectData }: { projectData: IClutch.core.project.v1.IProject }) => { const [text, setText] = React.useState(); diff --git a/frontend/workflows/projectCatalog/src/details/info/repositoryRow.tsx b/frontend/workflows/projectCatalog/src/details/info/repositoryRow.tsx index 5ce11a6b30..ab5737dcb1 100644 --- a/frontend/workflows/projectCatalog/src/details/info/repositoryRow.tsx +++ b/frontend/workflows/projectCatalog/src/details/info/repositoryRow.tsx @@ -5,7 +5,7 @@ import { faGithub } from "@fortawesome/free-brands-svg-icons"; import { faCode, faCodeBranch } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { LinkText } from "../helpers"; +import { LinkText } from "../../helpers"; interface ProjectPullRequests { number: number; diff --git a/frontend/workflows/projectCatalog/src/details/resolver.tsx b/frontend/workflows/projectCatalog/src/details/resolver.tsx new file mode 100644 index 0000000000..668d5827ea --- /dev/null +++ b/frontend/workflows/projectCatalog/src/details/resolver.tsx @@ -0,0 +1,44 @@ +import type { clutch as IClutch } from "@clutch-sh/api"; +import { client } from "@clutch-sh/core"; +import * as _ from "lodash"; + +const fetchProjectInfo = ( + project: string, + allowDisabled: boolean = false +): Promise => + client + .post("/v1/project/getProjects", { + projects: [project], + excludeDependencies: true, + } as IClutch.project.v1.GetProjectsRequest) + .then(resp => { + const { results = {}, partialFailures } = resp.data as IClutch.project.v1.GetProjectsResponse; + + const projectResults = _.get(results, [project, "project"], {}); + + if (_.isEmpty(projectResults) && allowDisabled && partialFailures) { + // Will add disabled projects to the list of projects to display if requested + const failuresMap = partialFailures + .map(p => { + if ((_.get(p, "message", "") ?? "").includes("disabled")) { + const disabledProject = _.get(p, "details.[0]", { name: "unknown " }); + _.set( + disabledProject, + ["data", "description"], + `${disabledProject.name} is disabled.` + ); + return disabledProject; + } + return null; + }) + .filter(Boolean); + + if (failuresMap.length) { + return failuresMap[0]; + } + } + + return projectResults as IClutch.core.project.v1.IProject; + }); + +export default fetchProjectInfo; diff --git a/frontend/workflows/projectCatalog/src/helpers/index.ts b/frontend/workflows/projectCatalog/src/helpers/index.ts new file mode 100644 index 0000000000..84af78a9f2 --- /dev/null +++ b/frontend/workflows/projectCatalog/src/helpers/index.ts @@ -0,0 +1,3 @@ +export { default as LanguageIcon } from "./language-icon"; +export { default as LastEvent } from "./last-event"; +export { default as LinkText } from "./link-text"; diff --git a/frontend/workflows/projectCatalog/src/helpers/last-event.tsx b/frontend/workflows/projectCatalog/src/helpers/last-event.tsx new file mode 100644 index 0000000000..c472dea20b --- /dev/null +++ b/frontend/workflows/projectCatalog/src/helpers/last-event.tsx @@ -0,0 +1,58 @@ +import React from "react"; +import { Grid, TimeAgo as EventTime, Typography, TypographyProps } from "@clutch-sh/core"; +import { faClock } from "@fortawesome/free-regular-svg-icons"; +import { FontAwesomeIcon, FontAwesomeIconProps } from "@fortawesome/react-fontawesome"; +import * as _ from "lodash"; +import type { GridProps } from "@mui/material"; + +interface Size { + variant: TypographyProps["variant"]; + icon: FontAwesomeIconProps["size"]; + align: GridProps["alignItems"]; +} + +interface SizeMap { + [key: string]: Size; +} + +const SIZE_MAP: SizeMap = { + small: { + variant: "body4", + icon: "sm", + align: "center", + }, + medium: { + variant: "body3", + icon: "1x", + align: "flex-end", + }, + large: { + variant: "body2", + icon: "lg", + align: "flex-end", + }, +}; + +interface LastEventProps { + time: number; + size?: keyof SizeMap; +} + +const LastEvent = ({ time, size = "small", ...props }: LastEventProps) => { + return time ? ( + + + + + + + + ago + + + + + ) : null; +}; + +export default LastEvent; diff --git a/frontend/workflows/projectCatalog/src/helpers/link-text.tsx b/frontend/workflows/projectCatalog/src/helpers/link-text.tsx new file mode 100644 index 0000000000..1497927967 --- /dev/null +++ b/frontend/workflows/projectCatalog/src/helpers/link-text.tsx @@ -0,0 +1,19 @@ +import React from "react"; +import { Link, Typography } from "@clutch-sh/core"; +import * as _ from "lodash"; + +const LinkText = ({ text, link }: { text: string; link?: string }) => { + const returnText = {text}; + + if (link && text) { + return ( + + {returnText} + + ); + } + + return returnText; +}; + +export default LinkText; diff --git a/frontend/workflows/projectCatalog/src/index.tsx b/frontend/workflows/projectCatalog/src/index.tsx index e0da7abd33..57e4e71478 100644 --- a/frontend/workflows/projectCatalog/src/index.tsx +++ b/frontend/workflows/projectCatalog/src/index.tsx @@ -1,47 +1,15 @@ -import type { BaseWorkflowProps, ClutchError, WorkflowConfiguration } from "@clutch-sh/core"; +import type { BaseWorkflowProps, WorkflowConfiguration } from "@clutch-sh/core"; -import type { CatalogDetailsCard } from "./details/card"; -import { CardType, DynamicCard, MetaCard } from "./details/card"; -import type { ProjectInfoChip } from "./details/info/chipsRow"; import Catalog from "./catalog"; -import Config from "./config"; +import Config from "./details/config"; import Details from "./details"; -type DetailCard = CatalogDetailsCard | typeof DynamicCard | typeof MetaCard; - -interface ProjectCatalogProps { +export interface ProjectCatalogProps { allowDisabled?: boolean; } -export interface ProjectConfigLink { - title: string; - path: string; - icon?: React.ReactElement; -} - -export interface ProjectConfigProps { - title: string; - path: string; - onError?: (error: ClutchError) => void; -} - -type CatalogDetailsChild = React.ReactElement; - -export type ProjectConfigPage = React.ReactElement; - export interface WorkflowProps extends BaseWorkflowProps, ProjectCatalogProps {} -export interface ProjectDetailsWorkflowProps extends WorkflowProps, ProjectCatalogProps { - children?: CatalogDetailsChild | CatalogDetailsChild[]; - chips?: ProjectInfoChip[]; - configLinks?: ProjectConfigLink[]; -} - -export interface ProjectDetailsConfigWorkflowProps extends WorkflowProps, ProjectCatalogProps { - children?: ProjectConfigPage | ProjectConfigPage[]; - defaultRoute?: string; -} - const register = (): WorkflowConfiguration => { return { developer: { @@ -81,10 +49,12 @@ const register = (): WorkflowConfiguration => { }; }; -export { CardType, DynamicCard, MetaCard }; -export { LastEvent } from "./details/helpers"; +export * from "./helpers"; +export { CardType, DynamicCard, MetaCard } from "./details/components/card"; export { useProjectDetailsContext } from "./details/context"; export { Details as ProjectDetails, Config as ProjectConfig }; -export type { CatalogDetailsCard, CatalogDetailsChild, ProjectInfoChip }; +export type { CatalogDetailsCard } from "./details/components/card"; +export type { ProjectInfoChip } from "./details/info/chipsRow"; +export type { CatalogDetailsChild } from "./details"; export default register; From f3497a4fbc4eede3f8249f00f93e1bf0e2fe3e63 Mon Sep 17 00:00:00 2001 From: Josh Slaughter <8338893+jdslaugh@users.noreply.github.com> Date: Thu, 26 Sep 2024 11:00:31 -0700 Subject: [PATCH 2/5] Fixing lint and build issues --- frontend/packages/core/src/utils/index.ts | 1 - .../projectCatalog/src/catalog/index.tsx | 2 +- .../src/details/components/card.tsx | 9 ++-- .../src/details/components/layout.tsx | 13 +++--- .../src/details/components/link-settings.tsx | 2 +- .../src/details/config/index.tsx | 3 +- .../projectCatalog/src/details/index.tsx | 46 ++++++++----------- .../src/details/info/messengerRow.tsx | 2 +- .../projectCatalog/src/helpers/last-event.tsx | 1 - .../projectCatalog/src/helpers/link-text.tsx | 1 - .../workflows/projectCatalog/src/index.tsx | 12 ++--- .../projectCatalog/src/types/config.ts | 5 ++ .../projectCatalog/src/types/details.ts | 18 ++++++++ .../projectCatalog/src/types/index.ts | 3 ++ .../projectCatalog/src/types/workflow.ts | 7 +++ 15 files changed, 70 insertions(+), 55 deletions(-) create mode 100644 frontend/workflows/projectCatalog/src/types/config.ts create mode 100644 frontend/workflows/projectCatalog/src/types/details.ts create mode 100644 frontend/workflows/projectCatalog/src/types/index.ts create mode 100644 frontend/workflows/projectCatalog/src/types/workflow.ts diff --git a/frontend/packages/core/src/utils/index.ts b/frontend/packages/core/src/utils/index.ts index b42b50a982..4aa7bbf944 100644 --- a/frontend/packages/core/src/utils/index.ts +++ b/frontend/packages/core/src/utils/index.ts @@ -1,3 +1,2 @@ -// eslint-disable-next-line import/prefer-default-export export { default as getDisplayName } from "./getDisplayName"; export { default as findPathMatchList } from "./pathMatching"; diff --git a/frontend/workflows/projectCatalog/src/catalog/index.tsx b/frontend/workflows/projectCatalog/src/catalog/index.tsx index 7fb9dc9527..173665774e 100644 --- a/frontend/workflows/projectCatalog/src/catalog/index.tsx +++ b/frontend/workflows/projectCatalog/src/catalog/index.tsx @@ -16,7 +16,7 @@ import RestoreIcon from "@mui/icons-material/Restore"; import SearchIcon from "@mui/icons-material/Search"; import { alpha, Box, CircularProgress } from "@mui/material"; -import type { WorkflowProps } from ".."; +import type { WorkflowProps } from "../types"; import catalogReducer from "./catalog-reducer"; import ProjectCard from "./project-card"; diff --git a/frontend/workflows/projectCatalog/src/details/components/card.tsx b/frontend/workflows/projectCatalog/src/details/components/card.tsx index ff2d7dd022..fad6f19720 100644 --- a/frontend/workflows/projectCatalog/src/details/components/card.tsx +++ b/frontend/workflows/projectCatalog/src/details/components/card.tsx @@ -1,10 +1,10 @@ import React from "react"; import type { TypographyProps } from "@clutch-sh/core"; import { Card, ClutchError, Error, Grid, styled, Typography } from "@clutch-sh/core"; -import { LinearProgress, Theme } from "@mui/material"; import type { GridProps } from "@mui/material"; +import { LinearProgress, Theme } from "@mui/material"; -enum CardType { +export enum CardType { DYNAMIC = "Dynamic", METADATA = "Metadata", } @@ -185,7 +185,6 @@ const DynamicCard = (props: BaseCardProps) => ; -type DetailCard = CatalogDetailsCard | typeof DynamicCard | typeof MetaCard; +export type DetailCard = CatalogDetailsCard | typeof DynamicCard | typeof MetaCard; -export { CardType, DynamicCard, MetaCard }; -export type { DetailCard }; +export { DynamicCard, MetaCard }; diff --git a/frontend/workflows/projectCatalog/src/details/components/layout.tsx b/frontend/workflows/projectCatalog/src/details/components/layout.tsx index 0167bb8e8a..589eb37521 100644 --- a/frontend/workflows/projectCatalog/src/details/components/layout.tsx +++ b/frontend/workflows/projectCatalog/src/details/components/layout.tsx @@ -1,13 +1,15 @@ import React from "react"; import type { clutch as IClutch } from "@clutch-sh/api"; import { Grid, QuickLinkGroup, styled, useNavigate, useParams } from "@clutch-sh/core"; + +import type { ProjectDetailsWorkflowProps } from "../../types"; import { ProjectDetailsContext } from "../context"; -import BreadCrumbs from "./breadcrumbs"; +import fetchProjectInfo from "../resolver"; + import type { BreadCrumbsProps } from "./breadcrumbs"; +import BreadCrumbs from "./breadcrumbs"; import ProjectHeader, { ProjectHeaderProps } from "./header"; import QuickLinksAndSettings from "./link-settings"; -import { ProjectDetailsWorkflowProps } from ".."; -import fetchProjectInfo from "../resolver"; export interface CatalogLayoutProps extends BreadCrumbsProps, @@ -49,15 +51,14 @@ const CatalogLayout = ({ setProjectInfo(data as IClutch.core.project.v1.IProject); }) .catch(err => { + // eslint-disable-next-line no-console console.error(err); }); React.useEffect(() => { - let interval; - fetchData(); - interval = setInterval(fetchData, 30000); + const interval = setInterval(fetchData, 30000); return () => (interval ? clearInterval(interval) : undefined); }, []); diff --git a/frontend/workflows/projectCatalog/src/details/components/link-settings.tsx b/frontend/workflows/projectCatalog/src/details/components/link-settings.tsx index d19d090e37..91602274bb 100644 --- a/frontend/workflows/projectCatalog/src/details/components/link-settings.tsx +++ b/frontend/workflows/projectCatalog/src/details/components/link-settings.tsx @@ -15,7 +15,7 @@ import { import SettingsIcon from "@mui/icons-material/Settings"; import { isEmpty } from "lodash"; -import type { ProjectConfigLink } from ".."; +import type { ProjectConfigLink } from "../../types"; interface QuickLinksAndSettingsProps { linkGroups: QuickLinkGroup[]; diff --git a/frontend/workflows/projectCatalog/src/details/config/index.tsx b/frontend/workflows/projectCatalog/src/details/config/index.tsx index 3ef248fe04..011bbd972f 100644 --- a/frontend/workflows/projectCatalog/src/details/config/index.tsx +++ b/frontend/workflows/projectCatalog/src/details/config/index.tsx @@ -2,7 +2,7 @@ import React from "react"; import type { ClutchError } from "@clutch-sh/core"; import { Error, Grid, Tab, Tabs, useLocation, useNavigate, useParams } from "@clutch-sh/core"; -import type { ProjectCatalogProps, WorkflowProps } from "../.."; +import type { ProjectCatalogProps, WorkflowProps } from "../../types"; import CatalogLayout from "../components/layout"; export interface ProjectConfigProps { @@ -15,6 +15,7 @@ export type ProjectConfigPage = React.ReactElement; export interface ProjectDetailsConfigWorkflowProps extends WorkflowProps, ProjectCatalogProps { children?: ProjectConfigPage | ProjectConfigPage[]; + // eslint-disable-next-line react/no-unused-prop-types description?: string; defaultRoute?: string; } diff --git a/frontend/workflows/projectCatalog/src/details/index.tsx b/frontend/workflows/projectCatalog/src/details/index.tsx index a9bc6801d4..1f7c448451 100644 --- a/frontend/workflows/projectCatalog/src/details/index.tsx +++ b/frontend/workflows/projectCatalog/src/details/index.tsx @@ -2,28 +2,15 @@ import React from "react"; import { Grid, Tooltip, useTheme } from "@clutch-sh/core"; import CodeOffIcon from "@mui/icons-material/CodeOff"; import GroupIcon from "@mui/icons-material/Group"; -import { capitalize } from "lodash"; - -import { CardType, DetailCard, DynamicCard, MetaCard } from "./components/card"; -import ProjectInfoCard from "./info"; import { useMediaQuery } from "@mui/material"; -import { type WorkflowProps, type ProjectCatalogProps, useProjectDetailsContext } from ".."; -import type { ProjectInfoChip } from "./info/chipsRow"; -import CatalogLayout from "./components/layout"; - -export type CatalogDetailsChild = React.ReactElement; +import { capitalize } from "lodash"; -export interface ProjectConfigLink { - title: string; - path: string; - icon?: React.ReactElement; -} +import type { CatalogDetailsChild, ProjectDetailsWorkflowProps } from "../types"; -export interface ProjectDetailsWorkflowProps extends WorkflowProps, ProjectCatalogProps { - children?: CatalogDetailsChild | CatalogDetailsChild[]; - chips?: ProjectInfoChip[]; - configLinks?: ProjectConfigLink[]; -} +import { CardType, DynamicCard, MetaCard } from "./components/card"; +import CatalogLayout from "./components/layout"; +import { useProjectDetailsContext } from "./context"; +import ProjectInfoCard from "./info"; const DisabledItem = ({ name }: { name: string }) => ( @@ -107,15 +94,18 @@ const Details = ({ children, chips }: ProjectDetailsWorkflowProps) => { > {projectInfo && ( - } - loadingIndicator={false} - endAdornment={projectInfo?.data?.disabled ? : null} - > - {projectInfo && } - - + } + loadingIndicator={false} + endAdornment={ + projectInfo?.data?.disabled ? ( + + ) : null + } + > + {projectInfo && } + )} {metaCards.length > 0 && metaCards.map(card => {card})} diff --git a/frontend/workflows/projectCatalog/src/details/info/messengerRow.tsx b/frontend/workflows/projectCatalog/src/details/info/messengerRow.tsx index 8499aa1e60..5475185deb 100644 --- a/frontend/workflows/projectCatalog/src/details/info/messengerRow.tsx +++ b/frontend/workflows/projectCatalog/src/details/info/messengerRow.tsx @@ -6,7 +6,7 @@ import { faSlack } from "@fortawesome/free-brands-svg-icons"; import { faComment } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { LinkText } from "../.."; +import { LinkText } from "../../helpers"; const MessengerRow = ({ projectData }: { projectData: IClutch.core.project.v1.IProject }) => { const [text, setText] = React.useState(); diff --git a/frontend/workflows/projectCatalog/src/helpers/last-event.tsx b/frontend/workflows/projectCatalog/src/helpers/last-event.tsx index c472dea20b..84ba247f2f 100644 --- a/frontend/workflows/projectCatalog/src/helpers/last-event.tsx +++ b/frontend/workflows/projectCatalog/src/helpers/last-event.tsx @@ -2,7 +2,6 @@ import React from "react"; import { Grid, TimeAgo as EventTime, Typography, TypographyProps } from "@clutch-sh/core"; import { faClock } from "@fortawesome/free-regular-svg-icons"; import { FontAwesomeIcon, FontAwesomeIconProps } from "@fortawesome/react-fontawesome"; -import * as _ from "lodash"; import type { GridProps } from "@mui/material"; interface Size { diff --git a/frontend/workflows/projectCatalog/src/helpers/link-text.tsx b/frontend/workflows/projectCatalog/src/helpers/link-text.tsx index 1497927967..575a581aa3 100644 --- a/frontend/workflows/projectCatalog/src/helpers/link-text.tsx +++ b/frontend/workflows/projectCatalog/src/helpers/link-text.tsx @@ -1,6 +1,5 @@ import React from "react"; import { Link, Typography } from "@clutch-sh/core"; -import * as _ from "lodash"; const LinkText = ({ text, link }: { text: string; link?: string }) => { const returnText = {text}; diff --git a/frontend/workflows/projectCatalog/src/index.tsx b/frontend/workflows/projectCatalog/src/index.tsx index 57e4e71478..7e0030bd34 100644 --- a/frontend/workflows/projectCatalog/src/index.tsx +++ b/frontend/workflows/projectCatalog/src/index.tsx @@ -1,15 +1,9 @@ -import type { BaseWorkflowProps, WorkflowConfiguration } from "@clutch-sh/core"; +import type { WorkflowConfiguration } from "@clutch-sh/core"; -import Catalog from "./catalog"; import Config from "./details/config"; +import Catalog from "./catalog"; import Details from "./details"; -export interface ProjectCatalogProps { - allowDisabled?: boolean; -} - -export interface WorkflowProps extends BaseWorkflowProps, ProjectCatalogProps {} - const register = (): WorkflowConfiguration => { return { developer: { @@ -55,6 +49,6 @@ export { useProjectDetailsContext } from "./details/context"; export { Details as ProjectDetails, Config as ProjectConfig }; export type { CatalogDetailsCard } from "./details/components/card"; export type { ProjectInfoChip } from "./details/info/chipsRow"; -export type { CatalogDetailsChild } from "./details"; +export type { CatalogDetailsChild } from "./types"; export default register; diff --git a/frontend/workflows/projectCatalog/src/types/config.ts b/frontend/workflows/projectCatalog/src/types/config.ts new file mode 100644 index 0000000000..96d8471e92 --- /dev/null +++ b/frontend/workflows/projectCatalog/src/types/config.ts @@ -0,0 +1,5 @@ +export interface ProjectConfigLink { + title: string; + path: string; + icon?: React.ReactElement; +} diff --git a/frontend/workflows/projectCatalog/src/types/details.ts b/frontend/workflows/projectCatalog/src/types/details.ts new file mode 100644 index 0000000000..410ec2488c --- /dev/null +++ b/frontend/workflows/projectCatalog/src/types/details.ts @@ -0,0 +1,18 @@ +import type { DetailCard } from "../details/components/card"; +import type { ProjectInfoChip } from "../details/info/chipsRow"; + +import type { ProjectConfigLink } from "./config"; +import type { ProjectCatalogProps, WorkflowProps } from "./workflow"; + +export interface ProjectDetailsWorkflowProps extends WorkflowProps, ProjectCatalogProps { + children?: + | ((CatalogDetailsChild | CatalogDetailsChild[]) & + (React.ReactChild | React.ReactFragment | React.ReactPortal | null)) + | undefined; + chips?: ProjectInfoChip[]; + configLinks?: ProjectConfigLink[]; +} + +// ((CatalogDetailsChild | CatalogDetailsChild[]) & (boolean | React.ReactChild | React.ReactFragment | React.ReactPortal | null)) | undefined + +export type CatalogDetailsChild = React.ReactElement; diff --git a/frontend/workflows/projectCatalog/src/types/index.ts b/frontend/workflows/projectCatalog/src/types/index.ts new file mode 100644 index 0000000000..101599840f --- /dev/null +++ b/frontend/workflows/projectCatalog/src/types/index.ts @@ -0,0 +1,3 @@ +export * from "./config"; +export * from "./details"; +export * from "./workflow"; diff --git a/frontend/workflows/projectCatalog/src/types/workflow.ts b/frontend/workflows/projectCatalog/src/types/workflow.ts new file mode 100644 index 0000000000..fa415d1b22 --- /dev/null +++ b/frontend/workflows/projectCatalog/src/types/workflow.ts @@ -0,0 +1,7 @@ +import type { BaseWorkflowProps } from "@clutch-sh/core"; + +export interface ProjectCatalogProps { + allowDisabled?: boolean; +} + +export interface WorkflowProps extends BaseWorkflowProps, ProjectCatalogProps {} From a55baf65baa1c343be91d6583e0a3b539c8ac8c8 Mon Sep 17 00:00:00 2001 From: Josh Slaughter <8338893+jdslaugh@users.noreply.github.com> Date: Thu, 26 Sep 2024 11:20:42 -0700 Subject: [PATCH 3/5] Updating exports --- frontend/workflows/projectCatalog/src/index.tsx | 3 ++- frontend/workflows/projectCatalog/src/types/details.ts | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/workflows/projectCatalog/src/index.tsx b/frontend/workflows/projectCatalog/src/index.tsx index 7e0030bd34..d0fd9d2bff 100644 --- a/frontend/workflows/projectCatalog/src/index.tsx +++ b/frontend/workflows/projectCatalog/src/index.tsx @@ -49,6 +49,7 @@ export { useProjectDetailsContext } from "./details/context"; export { Details as ProjectDetails, Config as ProjectConfig }; export type { CatalogDetailsCard } from "./details/components/card"; export type { ProjectInfoChip } from "./details/info/chipsRow"; -export type { CatalogDetailsChild } from "./types"; +export type { CatalogDetailsChild, ProjectConfigLink } from "./types"; +export type { ProjectConfigProps } from "./details/config"; export default register; diff --git a/frontend/workflows/projectCatalog/src/types/details.ts b/frontend/workflows/projectCatalog/src/types/details.ts index 410ec2488c..274b93efad 100644 --- a/frontend/workflows/projectCatalog/src/types/details.ts +++ b/frontend/workflows/projectCatalog/src/types/details.ts @@ -13,6 +13,4 @@ export interface ProjectDetailsWorkflowProps extends WorkflowProps, ProjectCatal configLinks?: ProjectConfigLink[]; } -// ((CatalogDetailsChild | CatalogDetailsChild[]) & (boolean | React.ReactChild | React.ReactFragment | React.ReactPortal | null)) | undefined - export type CatalogDetailsChild = React.ReactElement; From d70de4a73f6729578cfc141c4144e66d1ea37cb5 Mon Sep 17 00:00:00 2001 From: Josh Slaughter <8338893+jdslaugh@users.noreply.github.com> Date: Thu, 26 Sep 2024 15:37:52 -0700 Subject: [PATCH 4/5] Fixing layout for responsive and allowing for more customization --- .../projectCatalog/src/details/index.tsx | 60 +++++++++---------- .../workflows/projectCatalog/src/index.tsx | 2 +- .../projectCatalog/src/types/details.ts | 7 +++ 3 files changed, 38 insertions(+), 31 deletions(-) diff --git a/frontend/workflows/projectCatalog/src/details/index.tsx b/frontend/workflows/projectCatalog/src/details/index.tsx index 1f7c448451..5625670724 100644 --- a/frontend/workflows/projectCatalog/src/details/index.tsx +++ b/frontend/workflows/projectCatalog/src/details/index.tsx @@ -1,16 +1,20 @@ import React from "react"; -import { Grid, Tooltip, useTheme } from "@clutch-sh/core"; +import { Grid, Tooltip } from "@clutch-sh/core"; import CodeOffIcon from "@mui/icons-material/CodeOff"; import GroupIcon from "@mui/icons-material/Group"; -import { useMediaQuery } from "@mui/material"; import { capitalize } from "lodash"; -import type { CatalogDetailsChild, ProjectDetailsWorkflowProps } from "../types"; +import type { + CatalogDetailsChild, + DetailsLayoutOptions, + ProjectDetailsWorkflowProps, +} from "../types"; import { CardType, DynamicCard, MetaCard } from "./components/card"; import CatalogLayout from "./components/layout"; import { useProjectDetailsContext } from "./context"; import ProjectInfoCard from "./info"; +import { defaultsDeep } from "lodash"; const DisabledItem = ({ name }: { name: string }) => ( @@ -20,13 +24,31 @@ const DisabledItem = ({ name }: { name: string }) => ( ); -const Details = ({ children, chips }: ProjectDetailsWorkflowProps) => { +const defaultLayout: DetailsLayoutOptions = { + metadata: { + direction: "column", + flexWrap: "nowrap", + spacing: 2, + xs: 12, + lg: 4, + xl: 3, + }, + dynamic: { + direction: "column", + flexWrap: "nowrap", + spacing: 2, + xs: 12, + lg: 8, + xl: 9, + }, +}; + +const Details = ({ children, chips, layout }: ProjectDetailsWorkflowProps) => { const { projectId, projectInfo } = useProjectDetailsContext() || {}; const [metaCards, setMetaCards] = React.useState([]); const [dynamicCards, setDynamicCards] = React.useState([]); - const theme = useTheme(); - const shrink = useMediaQuery(theme.breakpoints.down("lg")); + const layoutOptions: DetailsLayoutOptions = defaultsDeep(layout, defaultLayout); React.useEffect(() => { if (children) { @@ -80,18 +102,7 @@ const Details = ({ children, chips }: ProjectDetailsWorkflowProps) => { return ( <> - + {projectInfo && ( { {metaCards.length > 0 && metaCards.map(card => {card})} - + {dynamicCards.length > 0 && dynamicCards.map(card => {card})} diff --git a/frontend/workflows/projectCatalog/src/index.tsx b/frontend/workflows/projectCatalog/src/index.tsx index d0fd9d2bff..39bd6a4d08 100644 --- a/frontend/workflows/projectCatalog/src/index.tsx +++ b/frontend/workflows/projectCatalog/src/index.tsx @@ -49,7 +49,7 @@ export { useProjectDetailsContext } from "./details/context"; export { Details as ProjectDetails, Config as ProjectConfig }; export type { CatalogDetailsCard } from "./details/components/card"; export type { ProjectInfoChip } from "./details/info/chipsRow"; -export type { CatalogDetailsChild, ProjectConfigLink } from "./types"; +export type { CatalogDetailsChild, ProjectConfigLink, DetailsLayoutOptions } from "./types"; export type { ProjectConfigProps } from "./details/config"; export default register; diff --git a/frontend/workflows/projectCatalog/src/types/details.ts b/frontend/workflows/projectCatalog/src/types/details.ts index 274b93efad..18afe1de4a 100644 --- a/frontend/workflows/projectCatalog/src/types/details.ts +++ b/frontend/workflows/projectCatalog/src/types/details.ts @@ -3,6 +3,12 @@ import type { ProjectInfoChip } from "../details/info/chipsRow"; import type { ProjectConfigLink } from "./config"; import type { ProjectCatalogProps, WorkflowProps } from "./workflow"; +import type { GridProps } from "@mui/material"; + +export interface DetailsLayoutOptions { + metadata?: GridProps; + dynamic?: GridProps; +} export interface ProjectDetailsWorkflowProps extends WorkflowProps, ProjectCatalogProps { children?: @@ -11,6 +17,7 @@ export interface ProjectDetailsWorkflowProps extends WorkflowProps, ProjectCatal | undefined; chips?: ProjectInfoChip[]; configLinks?: ProjectConfigLink[]; + layout?: DetailsLayoutOptions; } export type CatalogDetailsChild = React.ReactElement; From 378461a690d3d275b410847fc9174fa5a2940788 Mon Sep 17 00:00:00 2001 From: Josh Slaughter <8338893+jdslaugh@users.noreply.github.com> Date: Fri, 27 Sep 2024 09:42:34 -0700 Subject: [PATCH 5/5] Fixing lint --- frontend/workflows/projectCatalog/src/details/index.tsx | 3 +-- frontend/workflows/projectCatalog/src/types/details.ts | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/workflows/projectCatalog/src/details/index.tsx b/frontend/workflows/projectCatalog/src/details/index.tsx index 5625670724..c1802124bc 100644 --- a/frontend/workflows/projectCatalog/src/details/index.tsx +++ b/frontend/workflows/projectCatalog/src/details/index.tsx @@ -2,7 +2,7 @@ import React from "react"; import { Grid, Tooltip } from "@clutch-sh/core"; import CodeOffIcon from "@mui/icons-material/CodeOff"; import GroupIcon from "@mui/icons-material/Group"; -import { capitalize } from "lodash"; +import { capitalize, defaultsDeep } from "lodash"; import type { CatalogDetailsChild, @@ -14,7 +14,6 @@ import { CardType, DynamicCard, MetaCard } from "./components/card"; import CatalogLayout from "./components/layout"; import { useProjectDetailsContext } from "./context"; import ProjectInfoCard from "./info"; -import { defaultsDeep } from "lodash"; const DisabledItem = ({ name }: { name: string }) => ( diff --git a/frontend/workflows/projectCatalog/src/types/details.ts b/frontend/workflows/projectCatalog/src/types/details.ts index 18afe1de4a..ec6b7114bd 100644 --- a/frontend/workflows/projectCatalog/src/types/details.ts +++ b/frontend/workflows/projectCatalog/src/types/details.ts @@ -1,9 +1,10 @@ +import type { GridProps } from "@mui/material"; + import type { DetailCard } from "../details/components/card"; import type { ProjectInfoChip } from "../details/info/chipsRow"; import type { ProjectConfigLink } from "./config"; import type { ProjectCatalogProps, WorkflowProps } from "./workflow"; -import type { GridProps } from "@mui/material"; export interface DetailsLayoutOptions { metadata?: GridProps;