From 8d26cf072438e8fafe4fd4ce2d08de3f49013cf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=B8vring?= Date: Wed, 25 Oct 2023 08:17:30 +0200 Subject: [PATCH 01/10] Makes the drawer responsive (#60) * Makes the drawer responsive * Moves SidebarContainer to responsive folder * Updates naming of SidebarContainer API * Adds SidebarHeader * Adds TrailingToolbar component * Updates property names --- .../projects/view/client/ProjectsPage.tsx | 11 +- .../sidebar/view/BaseSidebarContainer.tsx | 159 ------------------ .../view/{SidebarContent.tsx => Sidebar.tsx} | 8 +- src/features/sidebar/view/SidebarHeader.tsx | 10 ++ src/features/sidebar/view/base/Drawer.tsx | 44 +++++ .../sidebar/view/base/PrimaryHeader.tsx | 43 +++++ .../sidebar/view/base/SecondaryContent.tsx | 51 ++++++ .../sidebar/view/base/SecondaryHeader.tsx | 70 ++++++++ .../sidebar/view/base/responsive/Drawer.tsx | 41 +++++ .../view/base/responsive/SecondaryContent.tsx | 31 ++++ .../view/base/responsive/SecondaryHeader.tsx | 35 ++++ .../view/base/responsive/SidebarContainer.tsx | 50 ++++++ .../sidebar/view/client/SidebarContainer.tsx | 60 +++---- .../sidebar/view/client/TrailingToolbar.tsx | 23 +++ 14 files changed, 433 insertions(+), 203 deletions(-) delete mode 100644 src/features/sidebar/view/BaseSidebarContainer.tsx rename src/features/sidebar/view/{SidebarContent.tsx => Sidebar.tsx} (79%) create mode 100644 src/features/sidebar/view/SidebarHeader.tsx create mode 100644 src/features/sidebar/view/base/Drawer.tsx create mode 100644 src/features/sidebar/view/base/PrimaryHeader.tsx create mode 100644 src/features/sidebar/view/base/SecondaryContent.tsx create mode 100644 src/features/sidebar/view/base/SecondaryHeader.tsx create mode 100644 src/features/sidebar/view/base/responsive/Drawer.tsx create mode 100644 src/features/sidebar/view/base/responsive/SecondaryContent.tsx create mode 100644 src/features/sidebar/view/base/responsive/SecondaryHeader.tsx create mode 100644 src/features/sidebar/view/base/responsive/SidebarContainer.tsx create mode 100644 src/features/sidebar/view/client/TrailingToolbar.tsx diff --git a/src/features/projects/view/client/ProjectsPage.tsx b/src/features/projects/view/client/ProjectsPage.tsx index 32a82e07..db35f5bf 100644 --- a/src/features/projects/view/client/ProjectsPage.tsx +++ b/src/features/projects/view/client/ProjectsPage.tsx @@ -57,7 +57,7 @@ export default function ProjectsPage({ }, [router, projectId, versionId, specificationId, stateContainer.selection]) return ( } - secondary={ - - } - toolbarTrailing={ + trailingToolbar={ { @@ -79,6 +76,8 @@ export default function ProjectsPage({ }} /> } - /> + > + + ) } diff --git a/src/features/sidebar/view/BaseSidebarContainer.tsx b/src/features/sidebar/view/BaseSidebarContainer.tsx deleted file mode 100644 index 68a6561b..00000000 --- a/src/features/sidebar/view/BaseSidebarContainer.tsx +++ /dev/null @@ -1,159 +0,0 @@ -import { ReactNode } from "react" -import { Box, Drawer, Divider, IconButton, Toolbar } from "@mui/material" -import MuiAppBar, { AppBarProps as MuiAppBarProps } from "@mui/material/AppBar" -import { ChevronLeft, Menu } from "@mui/icons-material" -import { styled, useTheme } from "@mui/material/styles" - -const drawerWidth = 320 - -const Main = styled("main", { shouldForwardProp: (prop) => prop !== "open" })<{ - open?: boolean -}>(({ theme, open }) => ({ - display: "flex", - flexDirection: "column", - flexGrow: 1, - overflowY: "auto", - transition: theme.transitions.create("margin", { - easing: theme.transitions.easing.sharp, - duration: theme.transitions.duration.leavingScreen, - }), - marginLeft: `-${drawerWidth}px`, - ...(open && { - transition: theme.transitions.create("margin", { - easing: theme.transitions.easing.easeOut, - duration: theme.transitions.duration.enteringScreen, - }), - marginLeft: 0, - }) -})) - -const DrawerHeaderWrapper = styled("div")(({ theme }) => ({ - display: "flex", - alignItems: "center", - // necessary for content to be below app bar - ...theme.mixins.toolbar -})) - -const DrawerHeader = ({ - primaryHeader, - handleDrawerClose -}: { - primaryHeader: ReactNode, - handleDrawerClose: () => void -}) => { - return ( - - - - - {primaryHeader != null && - - {primaryHeader} - - } - - ) -} - -interface AppBarProps extends MuiAppBarProps { - open?: boolean -} - -const AppBar = styled(MuiAppBar, { - shouldForwardProp: (prop) => prop !== "open", -})(({ theme, open }) => ({ - transition: theme.transitions.create(["margin", "width"], { - easing: theme.transitions.easing.sharp, - duration: theme.transitions.duration.leavingScreen, - }), - ...(open && { - width: `calc(100% - ${drawerWidth}px)`, - marginLeft: `${drawerWidth}px`, - transition: theme.transitions.create(["margin", "width"], { - easing: theme.transitions.easing.easeOut, - duration: theme.transitions.duration.enteringScreen, - }), - }), -})) - -const BaseSidebarContainer = ({ - isDrawerOpen, - onToggleDrawerOpen, - primaryHeader, - primary, - secondaryHeader, - secondary -}: { - isDrawerOpen: boolean - onToggleDrawerOpen: (isDrawerOpen: boolean) => void - primaryHeader?: ReactNode - primary: ReactNode - secondaryHeader?: ReactNode - secondary: ReactNode -}) => { - const theme = useTheme() - return ( - - - - onToggleDrawerOpen(true)} - edge="start" - sx={{ - mr: 2, - color: theme.palette.text.primary, - ...(isDrawerOpen && { display: "none" }) - }} - > - - - {secondaryHeader != null && secondaryHeader} - - - - - onToggleDrawerOpen(false)} - primaryHeader={primaryHeader} - /> - {primary} - -
- - - {secondary} - -
- - ) -} - -export default BaseSidebarContainer diff --git a/src/features/sidebar/view/SidebarContent.tsx b/src/features/sidebar/view/Sidebar.tsx similarity index 79% rename from src/features/sidebar/view/SidebarContent.tsx rename to src/features/sidebar/view/Sidebar.tsx index cb65606b..e08fe8a1 100644 --- a/src/features/sidebar/view/SidebarContent.tsx +++ b/src/features/sidebar/view/Sidebar.tsx @@ -3,7 +3,11 @@ import { Box } from "@mui/material" import UserListItem from "@/features/user/view/UserListItem" import SettingsButton from "@/features/settings/view/SettingsButton" -const SidebarContent = ({ children }: { children: ReactNode }) => { +const Sidebar = ({ + children +}: { + children: ReactNode +}) => { return ( <> @@ -14,4 +18,4 @@ const SidebarContent = ({ children }: { children: ReactNode }) => { ) } -export default SidebarContent +export default Sidebar diff --git a/src/features/sidebar/view/SidebarHeader.tsx b/src/features/sidebar/view/SidebarHeader.tsx new file mode 100644 index 00000000..12e67e8c --- /dev/null +++ b/src/features/sidebar/view/SidebarHeader.tsx @@ -0,0 +1,10 @@ +import Image from "next/image" +import { Stack } from "@mui/material" + +export default function SidebarHeader() { + return ( + + Duck + + ) +} \ No newline at end of file diff --git a/src/features/sidebar/view/base/Drawer.tsx b/src/features/sidebar/view/base/Drawer.tsx new file mode 100644 index 00000000..d3d224bb --- /dev/null +++ b/src/features/sidebar/view/base/Drawer.tsx @@ -0,0 +1,44 @@ +import { ReactNode } from "react" +import { SxProps } from "@mui/system" +import { Drawer as MuiDrawer } from "@mui/material" +import PrimaryHeader from "./PrimaryHeader" + +export default function Drawer({ + variant, + width, + isOpen, + header, + onClose, + sx, + children +}: { + variant: "persistent" | "temporary", + width: number + isOpen: boolean + header: ReactNode, + onClose: () => void, + sx: SxProps, + children?: ReactNode +}) { + return ( + + + {header} + + {children} + + ) +} \ No newline at end of file diff --git a/src/features/sidebar/view/base/PrimaryHeader.tsx b/src/features/sidebar/view/base/PrimaryHeader.tsx new file mode 100644 index 00000000..a88d4b7e --- /dev/null +++ b/src/features/sidebar/view/base/PrimaryHeader.tsx @@ -0,0 +1,43 @@ +import { ReactNode } from "react" +import { Box, IconButton } from "@mui/material" +import ChevronLeftIcon from "@mui/icons-material/ChevronLeft" +import { styled } from "@mui/material/styles" + +const PrimaryHeaderWrapper = styled("div")(({ theme }) => ({ + display: "flex", + alignItems: "center", + // necessary for content to be below app bar + ...theme.mixins.toolbar +})) + +export default function PrimaryHeader({ + width, + onClose, + children +}: { + width: number, + onClose: () => void + children?: ReactNode +}) { + return ( + + + + + {children != null && + + {children} + + } + + ) +} diff --git a/src/features/sidebar/view/base/SecondaryContent.tsx b/src/features/sidebar/view/base/SecondaryContent.tsx new file mode 100644 index 00000000..d1aea2a1 --- /dev/null +++ b/src/features/sidebar/view/base/SecondaryContent.tsx @@ -0,0 +1,51 @@ +import { ReactNode } from "react" +import { SxProps } from "@mui/system" +import { Box, Toolbar } from "@mui/material" +import { styled } from "@mui/material/styles" + +interface MainProps { + drawerWidth: number + isDrawerOpen: boolean +} + +const Main = styled("main", { + shouldForwardProp: (prop) => prop !== "isDrawerOpen" +})(({ theme, drawerWidth, isDrawerOpen }) => ({ + display: "flex", + flexDirection: "column", + flexGrow: 1, + overflowY: "auto", + transition: theme.transitions.create("margin", { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen + }), + marginLeft: `-${drawerWidth}px`, + ...(isDrawerOpen && { + transition: theme.transitions.create("margin", { + easing: theme.transitions.easing.easeOut, + duration: theme.transitions.duration.enteringScreen, + }), + marginLeft: 0 + }) +})) + +export default function SecondaryContent({ + drawerWidth, + isDrawerOpen, + children, + sx +}: { + drawerWidth: number + isDrawerOpen: boolean + children: ReactNode + sx?: SxProps +}) { + return ( +
+ + + {children} + +
+ ) +} diff --git a/src/features/sidebar/view/base/SecondaryHeader.tsx b/src/features/sidebar/view/base/SecondaryHeader.tsx new file mode 100644 index 00000000..5562d71c --- /dev/null +++ b/src/features/sidebar/view/base/SecondaryHeader.tsx @@ -0,0 +1,70 @@ +import { ReactNode } from "react" +import { SxProps } from "@mui/system" +import { Divider, IconButton, Toolbar } from "@mui/material" +import MuiAppBar, { AppBarProps as MuiAppBarProps } from "@mui/material/AppBar" +import { styled, useTheme } from "@mui/material/styles" +import MenuIcon from "@mui/icons-material/Menu" + +interface AppBarProps extends MuiAppBarProps { + drawerWidth: number + isDrawerOpen?: boolean +} + +const AppBar = styled(MuiAppBar, { + shouldForwardProp: (prop) => prop !== "isDrawerOpen" +})(({ theme, drawerWidth, isDrawerOpen }) => ({ + transition: theme.transitions.create(["margin", "width"], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen + }), + ...(isDrawerOpen && { + width: `calc(100% - ${drawerWidth}px)`, + marginLeft: `${drawerWidth}px`, + transition: theme.transitions.create(["margin", "width"], { + easing: theme.transitions.easing.easeOut, + duration: theme.transitions.duration.enteringScreen + }) + }) +})) + +export default function SecondaryHeader({ + drawerWidth, + isDrawerOpen, + onOpen, + children, + sx +}: { + drawerWidth: number + isDrawerOpen: boolean + onOpen: () => void + children: ReactNode, + sx?: SxProps +}) { + const theme = useTheme() + return ( + + + + + + {children} + + + + ) +} diff --git a/src/features/sidebar/view/base/responsive/Drawer.tsx b/src/features/sidebar/view/base/responsive/Drawer.tsx new file mode 100644 index 00000000..3da62fe5 --- /dev/null +++ b/src/features/sidebar/view/base/responsive/Drawer.tsx @@ -0,0 +1,41 @@ +import { ReactNode } from "react" +import Drawer from "../Drawer" + +export default function RespnsiveDrawer({ + width, + isOpen, + header, + onClose, + children +}: { + width: number + isOpen: boolean + header: ReactNode, + onClose: () => void, + children?: ReactNode +}) { + return ( + <> + + {children} + + + {children} + + + ) +} \ No newline at end of file diff --git a/src/features/sidebar/view/base/responsive/SecondaryContent.tsx b/src/features/sidebar/view/base/responsive/SecondaryContent.tsx new file mode 100644 index 00000000..046ada23 --- /dev/null +++ b/src/features/sidebar/view/base/responsive/SecondaryContent.tsx @@ -0,0 +1,31 @@ +import { ReactNode } from "react" +import SecondaryContent from "../SecondaryContent" + +export default function RespnsiveDrawer({ + drawerWidth, + offsetContent, + children +}: { + drawerWidth: number + offsetContent: boolean + children: ReactNode +}) { + return ( + <> + + {children} + + + {children} + + + ) +} \ No newline at end of file diff --git a/src/features/sidebar/view/base/responsive/SecondaryHeader.tsx b/src/features/sidebar/view/base/responsive/SecondaryHeader.tsx new file mode 100644 index 00000000..f2f8835d --- /dev/null +++ b/src/features/sidebar/view/base/responsive/SecondaryHeader.tsx @@ -0,0 +1,35 @@ +import { ReactNode } from "react" +import SecondaryHeader from "../SecondaryHeader" + +export default function RespnsiveDrawer({ + drawerWidth, + offsetContent, + onOpen, + children +}: { + drawerWidth: number + offsetContent: boolean + onOpen: () => void + children: ReactNode +}) { + return ( + <> + + {children} + + + {children} + + + ) +} \ No newline at end of file diff --git a/src/features/sidebar/view/base/responsive/SidebarContainer.tsx b/src/features/sidebar/view/base/responsive/SidebarContainer.tsx new file mode 100644 index 00000000..14d1091f --- /dev/null +++ b/src/features/sidebar/view/base/responsive/SidebarContainer.tsx @@ -0,0 +1,50 @@ +import { ReactNode } from "react" +import { Box } from "@mui/material" +import Drawer from "./Drawer" +import SecondaryHeader from "./SecondaryHeader" +import SecondaryContent from "./SecondaryContent" + +const SidebarContainer = ({ + isDrawerOpen, + onToggleDrawerOpen, + sidebarHeader, + sidebar, + header, + children +}: { + isDrawerOpen: boolean + onToggleDrawerOpen: (isDrawerOpen: boolean) => void + sidebarHeader?: ReactNode + sidebar: ReactNode + header?: ReactNode + children?: ReactNode +}) => { + const drawerWidth = 320 + return ( + + onToggleDrawerOpen(true)} + > + {header} + + onToggleDrawerOpen(false)} + > + {sidebar} + + + {children} + + + ) +} + +export default SidebarContainer diff --git a/src/features/sidebar/view/client/SidebarContainer.tsx b/src/features/sidebar/view/client/SidebarContainer.tsx index f9d2fb1d..9da62449 100644 --- a/src/features/sidebar/view/client/SidebarContainer.tsx +++ b/src/features/sidebar/view/client/SidebarContainer.tsx @@ -2,54 +2,42 @@ import dynamic from "next/dynamic" import { ReactNode } from "react" -import Image from "next/image" -import { Box, Stack } from "@mui/material" -import { useTheme } from "@mui/material/styles" import { useSessionStorage } from "usehooks-ts" -import BaseSidebarContainer from "../BaseSidebarContainer" -import SidebarContent from "../SidebarContent" +import ResponsiveSidebarContainer from "../base/responsive/SidebarContainer" +import Sidebar from "../Sidebar" +import SidebarHeader from "../SidebarHeader" +import TrailingToolbar from "./TrailingToolbar" const SidebarContainer = ({ - primary, - secondary, - toolbarTrailing + sidebar, + children, + trailingToolbar }: { - primary: ReactNode - secondary?: ReactNode - toolbarTrailing?: ReactNode + sidebar?: ReactNode + children?: ReactNode + trailingToolbar?: ReactNode }) => { const [open, setOpen] = useSessionStorage("isDrawerOpen", true) - const theme = useTheme() return ( - - Duck - + sidebarHeader={ + } - primary={ - - {primary} - + sidebar={ + + {sidebar} + } - secondaryHeader={ - <> - {toolbarTrailing != undefined && - - {toolbarTrailing} - - } - + header={trailingToolbar && + + {trailingToolbar} + } - secondary={secondary} - /> + > + {children} + ) } diff --git a/src/features/sidebar/view/client/TrailingToolbar.tsx b/src/features/sidebar/view/client/TrailingToolbar.tsx new file mode 100644 index 00000000..60fdbf73 --- /dev/null +++ b/src/features/sidebar/view/client/TrailingToolbar.tsx @@ -0,0 +1,23 @@ +"use client" + +import { ReactNode } from "react" +import { Box } from "@mui/material" +import { useTheme } from "@mui/material/styles" + +export default function TrailingToolbar({ + children +}: { + children?: ReactNode +}) { + const theme = useTheme() + return ( + + {children} + + ) +} From 7c6b27c4cc5b750f4705fef49fd5794c0e987c7a Mon Sep 17 00:00:00 2001 From: Ulrik Andersen Date: Wed, 25 Oct 2023 09:35:11 +0200 Subject: [PATCH 02/10] Press the Duck to go home (#61) --- src/features/sidebar/view/SidebarHeader.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/features/sidebar/view/SidebarHeader.tsx b/src/features/sidebar/view/SidebarHeader.tsx index 12e67e8c..fd9948a5 100644 --- a/src/features/sidebar/view/SidebarHeader.tsx +++ b/src/features/sidebar/view/SidebarHeader.tsx @@ -1,10 +1,11 @@ import Image from "next/image" import { Stack } from "@mui/material" +import Link from "next/link" export default function SidebarHeader() { return ( - Duck + Duck ) } \ No newline at end of file From 2a10473e3f63be052c77bb6b530895dbf1bc8522 Mon Sep 17 00:00:00 2001 From: Ulrik Andersen Date: Wed, 25 Oct 2023 15:02:51 +0200 Subject: [PATCH 03/10] Delete session cookie upon force logout (#62) * Read host from header instead of req.nextUrl.host nextUrl seems to always return localhost - even if using dev.local * Delete session cookie upon force logout --- src/app/api/auth/forceLogout/route.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/app/api/auth/forceLogout/route.ts b/src/app/api/auth/forceLogout/route.ts index d7a7818d..bc902b44 100644 --- a/src/app/api/auth/forceLogout/route.ts +++ b/src/app/api/auth/forceLogout/route.ts @@ -12,7 +12,11 @@ export async function GET(req: NextRequest) { // should be provided that is not needed as the user is not fully logged // in at this point. const url = new URL(AUTH0_ISSUER_BASE_URL + "/oidc/logout") - const redirectURI = req.nextUrl.protocol + "//" + req.nextUrl.host + const host = req.headers.get('host') + const redirectURI = req.nextUrl.protocol + "//" + host url.searchParams.append("post_logout_redirect_uri", redirectURI) - return NextResponse.redirect(url) + + const response = NextResponse.redirect(url) + response.cookies.delete("appSession") + return response } From 401e7a9e4d5d768f9b74d5c917b40af1fb9313e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=B8vring?= Date: Wed, 25 Oct 2023 20:33:01 +0200 Subject: [PATCH 04/10] Optimizes the app for mobile (#63) --- src/common/theme/theme.ts | 9 ++ src/features/projects/view/MobileToolbar.tsx | 43 ++++++++ ...aryContent.tsx => ProjectsPageContent.tsx} | 4 +- .../view/ProjectsPageTrailingToolbarItem.tsx | 66 ------------- .../projects/view/TrailingToolbarItem.tsx | 97 +++++++++++++++++++ .../projects/view/client/ProjectsPage.tsx | 39 +++++--- .../view/docs/SpecificationSelector.tsx | 19 ++-- .../projects/view/docs/VersionSelector.tsx | 19 ++-- src/features/settings/view/SettingsButton.tsx | 2 +- src/features/sidebar/view/SidebarHeader.tsx | 3 +- src/features/sidebar/view/base/Drawer.tsx | 14 +-- .../sidebar/view/base/PrimaryHeader.tsx | 32 +++--- .../sidebar/view/base/SecondaryHeader.tsx | 68 +++++-------- .../sidebar/view/base/SecondaryWrapper.tsx | 50 ++++++++++ .../sidebar/view/base/responsive/Drawer.tsx | 15 ++- .../view/base/responsive/SecondaryHeader.tsx | 72 ++++++++------ .../view/base/responsive/SecondaryWrapper.tsx | 32 ++++++ .../view/base/responsive/SidebarContainer.tsx | 39 ++++---- .../sidebar/view/client/SidebarContainer.tsx | 38 +++++--- src/features/user/view/UserListItem.tsx | 2 +- 20 files changed, 423 insertions(+), 240 deletions(-) create mode 100644 src/features/projects/view/MobileToolbar.tsx rename src/features/projects/view/{ProjectsPageSecondaryContent.tsx => ProjectsPageContent.tsx} (92%) delete mode 100644 src/features/projects/view/ProjectsPageTrailingToolbarItem.tsx create mode 100644 src/features/projects/view/TrailingToolbarItem.tsx create mode 100644 src/features/sidebar/view/base/SecondaryWrapper.tsx create mode 100644 src/features/sidebar/view/base/responsive/SecondaryWrapper.tsx diff --git a/src/common/theme/theme.ts b/src/common/theme/theme.ts index 38b69d88..1e76754b 100644 --- a/src/common/theme/theme.ts +++ b/src/common/theme/theme.ts @@ -22,6 +22,15 @@ const theme = () => createTheme({ disableRipple: true } } + }, + breakpoints: { + values: { + xs: 0, + sm: 600, + md: 900, + lg: 1200, + xl: 1536, + } } }) diff --git a/src/features/projects/view/MobileToolbar.tsx b/src/features/projects/view/MobileToolbar.tsx new file mode 100644 index 00000000..5c715c07 --- /dev/null +++ b/src/features/projects/view/MobileToolbar.tsx @@ -0,0 +1,43 @@ +import { Stack } from "@mui/material" +import Project from "../domain/Project" +import Version from "../domain/Version" +import OpenApiSpecification from "../domain/OpenApiSpecification" +import VersionSelector from "./docs/VersionSelector" +import SpecificationSelector from "./docs/SpecificationSelector" + +const MobileToolbar = ({ + project, + version, + specification, + onSelectVersion, + onSelectSpecification +}: { + project: Project + version: Version + specification: OpenApiSpecification + onSelectVersion: (versionId: string) => void, + onSelectSpecification: (specificationId: string) => void +}) => { + return ( + + + + + ) +} + +export default MobileToolbar diff --git a/src/features/projects/view/ProjectsPageSecondaryContent.tsx b/src/features/projects/view/ProjectsPageContent.tsx similarity index 92% rename from src/features/projects/view/ProjectsPageSecondaryContent.tsx rename to src/features/projects/view/ProjectsPageContent.tsx index 360e00f5..70c6f598 100644 --- a/src/features/projects/view/ProjectsPageSecondaryContent.tsx +++ b/src/features/projects/view/ProjectsPageContent.tsx @@ -2,7 +2,7 @@ import { ProjectPageStateContainer, ProjectPageState } from "../domain/ProjectPa import ProjectErrorContent from "./ProjectErrorContent" import DocumentationViewer from "./docs/DocumentationViewer" -const ProjectsPageSecondaryContent = ({ +const ProjectsPageContent = ({ stateContainer }: { stateContainer: ProjectPageStateContainer @@ -24,4 +24,4 @@ const ProjectsPageSecondaryContent = ({ } } -export default ProjectsPageSecondaryContent \ No newline at end of file +export default ProjectsPageContent \ No newline at end of file diff --git a/src/features/projects/view/ProjectsPageTrailingToolbarItem.tsx b/src/features/projects/view/ProjectsPageTrailingToolbarItem.tsx deleted file mode 100644 index b6f7537f..00000000 --- a/src/features/projects/view/ProjectsPageTrailingToolbarItem.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { Stack, IconButton, Typography, Link } from "@mui/material" -import { ProjectPageStateContainer, ProjectPageState } from "../domain/ProjectPageState" -import VersionSelector from "./docs/VersionSelector" -import SpecificationSelector from "./docs/SpecificationSelector" -import EditIcon from "@mui/icons-material/Edit" - -const ProjectsPageTrailingToolbarItem = ( - { - stateContainer, - onSelectVersion, - onSelectSpecification - }: { - stateContainer: ProjectPageStateContainer, - onSelectVersion: (versionId: string) => void, - onSelectSpecification: (specificationId: string) => void - } -) => { - switch (stateContainer.state) { - case ProjectPageState.HAS_SELECTION: - return ( - - {stateContainer.selection!.version.url && - - {stateContainer.selection!.project.name} - - } - {!stateContainer.selection!.version.url && - - {stateContainer.selection!.project.name} - - } - / - - / - - {stateContainer.selection!.specification.editURL && - - - - } - - ) - case ProjectPageState.LOADING: - case ProjectPageState.NO_PROJECT_SELECTED: - case ProjectPageState.PROJECT_NOT_FOUND: - case ProjectPageState.VERSION_NOT_FOUND: - case ProjectPageState.SPECIFICATION_NOT_FOUND: - return <> - } -} - -export default ProjectsPageTrailingToolbarItem \ No newline at end of file diff --git a/src/features/projects/view/TrailingToolbarItem.tsx b/src/features/projects/view/TrailingToolbarItem.tsx new file mode 100644 index 00000000..41caba90 --- /dev/null +++ b/src/features/projects/view/TrailingToolbarItem.tsx @@ -0,0 +1,97 @@ +import { SxProps } from "@mui/system" +import { Stack, IconButton, Typography, Link } from "@mui/material" +import Project from "../domain/Project" +import Version from "../domain/Version" +import OpenApiSpecification from "../domain/OpenApiSpecification" +import VersionSelector from "./docs/VersionSelector" +import SpecificationSelector from "./docs/SpecificationSelector" +import EditIcon from "@mui/icons-material/Edit" + +const TrailingToolbarItem = ({ + project, + version, + specification, + onSelectVersion, + onSelectSpecification +}: { + project: Project + version: Version + specification: OpenApiSpecification + onSelectVersion: (versionId: string) => void, + onSelectSpecification: (specificationId: string) => void +}) => { + return ( + <> + + + + + + / + + / + + {specification.editURL && + + + + } + + + ) +} + +export default TrailingToolbarItem + +const ProjectName = ({ + url, + text, + sx +}: { + url?: string + text: string + sx?: SxProps +}) => { + if (url) { + return ( + + {text} + + ) + } else { + return ( + + {text} + + ) + } +} \ No newline at end of file diff --git a/src/features/projects/view/client/ProjectsPage.tsx b/src/features/projects/view/client/ProjectsPage.tsx index db35f5bf..76109660 100644 --- a/src/features/projects/view/client/ProjectsPage.tsx +++ b/src/features/projects/view/client/ProjectsPage.tsx @@ -5,8 +5,9 @@ import { useRouter } from "next/navigation" import SidebarContainer from "@/features/sidebar/view/client/SidebarContainer" import Project from "../../domain/Project" import ProjectList from "../ProjectList" -import ProjectsPageSecondaryContent from "../ProjectsPageSecondaryContent" -import ProjectsPageTrailingToolbarItem from "../ProjectsPageTrailingToolbarItem" +import ProjectsPageContent from "../ProjectsPageContent" +import TrailingToolbarItem from "../TrailingToolbarItem" +import MobileToolbar from "../MobileToolbar" import { getProjectPageState } from "../../domain/ProjectPageState" import projectNavigator from "../../domain/projectNavigator" import updateWindowTitle from "../../domain/updateWindowTitle" @@ -55,8 +56,15 @@ export default function ProjectsPage({ const urlSelection = { projectId, versionId, specificationId } projectNavigator.navigateIfNeeded(router, urlSelection, stateContainer.selection) }, [router, projectId, versionId, specificationId, stateContainer.selection]) + const selectVersion = (versionId: string) => { + projectNavigator.navigateToVersion(router, stateContainer.selection!, versionId) + } + const selectSpecification = (specificationId: string) => { + projectNavigator.navigate(router, projectId!, versionId!, specificationId) + } return ( } - trailingToolbar={ - { - projectNavigator.navigateToVersion(router, stateContainer.selection!, versionId) - }} - onSelectSpecification={(specificationId: string) => { - projectNavigator.navigate(router, projectId!, versionId!, specificationId) - }} + toolbarTrailingItem={stateContainer.selection && + + } + mobileToolbar={stateContainer.selection && + } > - + ) } diff --git a/src/features/projects/view/docs/SpecificationSelector.tsx b/src/features/projects/view/docs/SpecificationSelector.tsx index f0723652..834e78b8 100644 --- a/src/features/projects/view/docs/SpecificationSelector.tsx +++ b/src/features/projects/view/docs/SpecificationSelector.tsx @@ -1,24 +1,23 @@ +import { SxProps } from "@mui/system" import { SelectChangeEvent, Select, MenuItem, FormControl } from "@mui/material" import OpenApiSpecification from "../../domain/OpenApiSpecification" -interface SpecificationSelectorProps { +const SpecificationSelector = ({ + specifications, + selection, + onSelect, + sx +}: { specifications: OpenApiSpecification[] selection: string onSelect: (specificationId: string) => void -} - -const SpecificationSelector: React.FC< - SpecificationSelectorProps -> = ({ - specifications, - selection, - onSelect + sx?: SxProps }) => { const handleVersionChange = (event: SelectChangeEvent) => { onSelect(event.target.value) } return ( - + {versions.map(version => diff --git a/src/features/settings/view/SettingsButton.tsx b/src/features/settings/view/SettingsButton.tsx index f12bbca2..dc1bc0e9 100644 --- a/src/features/settings/view/SettingsButton.tsx +++ b/src/features/settings/view/SettingsButton.tsx @@ -31,7 +31,7 @@ const SettingsButton: React.FC = () => { > - + diff --git a/src/features/sidebar/view/SidebarHeader.tsx b/src/features/sidebar/view/SidebarHeader.tsx index fd9948a5..12e67e8c 100644 --- a/src/features/sidebar/view/SidebarHeader.tsx +++ b/src/features/sidebar/view/SidebarHeader.tsx @@ -1,11 +1,10 @@ import Image from "next/image" import { Stack } from "@mui/material" -import Link from "next/link" export default function SidebarHeader() { return ( - Duck + Duck ) } \ No newline at end of file diff --git a/src/features/sidebar/view/base/Drawer.tsx b/src/features/sidebar/view/base/Drawer.tsx index d3d224bb..5a335588 100644 --- a/src/features/sidebar/view/base/Drawer.tsx +++ b/src/features/sidebar/view/base/Drawer.tsx @@ -1,22 +1,21 @@ import { ReactNode } from "react" import { SxProps } from "@mui/system" import { Drawer as MuiDrawer } from "@mui/material" -import PrimaryHeader from "./PrimaryHeader" export default function Drawer({ variant, width, isOpen, - header, onClose, + keepMounted, sx, children }: { variant: "persistent" | "temporary", width: number isOpen: boolean - header: ReactNode, - onClose: () => void, + onClose?: () => void + keepMounted?: boolean sx: SxProps, children?: ReactNode }) { @@ -25,6 +24,10 @@ export default function Drawer({ variant={variant} anchor="left" open={isOpen} + onClose={onClose} + ModalProps={{ + keepMounted: keepMounted || false + }} sx={{ ...sx, width: width, @@ -35,9 +38,6 @@ export default function Drawer({ } }} > - - {header} - {children} ) diff --git a/src/features/sidebar/view/base/PrimaryHeader.tsx b/src/features/sidebar/view/base/PrimaryHeader.tsx index a88d4b7e..a5e96a26 100644 --- a/src/features/sidebar/view/base/PrimaryHeader.tsx +++ b/src/features/sidebar/view/base/PrimaryHeader.tsx @@ -1,43 +1,39 @@ import { ReactNode } from "react" import { Box, IconButton } from "@mui/material" import ChevronLeftIcon from "@mui/icons-material/ChevronLeft" -import { styled } from "@mui/material/styles" - -const PrimaryHeaderWrapper = styled("div")(({ theme }) => ({ - display: "flex", - alignItems: "center", - // necessary for content to be below app bar - ...theme.mixins.toolbar -})) export default function PrimaryHeader({ + canCloseDrawer, width, onClose, children }: { + canCloseDrawer: boolean, width: number, onClose: () => void children?: ReactNode }) { return ( - + - + - {children != null && - - {children} - - } - + }} + > + {children} +
+ ) } diff --git a/src/features/sidebar/view/base/SecondaryHeader.tsx b/src/features/sidebar/view/base/SecondaryHeader.tsx index 5562d71c..98d9b44c 100644 --- a/src/features/sidebar/view/base/SecondaryHeader.tsx +++ b/src/features/sidebar/view/base/SecondaryHeader.tsx @@ -1,70 +1,50 @@ import { ReactNode } from "react" import { SxProps } from "@mui/system" -import { Divider, IconButton, Toolbar } from "@mui/material" -import MuiAppBar, { AppBarProps as MuiAppBarProps } from "@mui/material/AppBar" -import { styled, useTheme } from "@mui/material/styles" +import { Box, Divider, IconButton } from "@mui/material" +import { useTheme } from "@mui/material/styles" import MenuIcon from "@mui/icons-material/Menu" -interface AppBarProps extends MuiAppBarProps { - drawerWidth: number - isDrawerOpen?: boolean -} - -const AppBar = styled(MuiAppBar, { - shouldForwardProp: (prop) => prop !== "isDrawerOpen" -})(({ theme, drawerWidth, isDrawerOpen }) => ({ - transition: theme.transitions.create(["margin", "width"], { - easing: theme.transitions.easing.sharp, - duration: theme.transitions.duration.leavingScreen - }), - ...(isDrawerOpen && { - width: `calc(100% - ${drawerWidth}px)`, - marginLeft: `${drawerWidth}px`, - transition: theme.transitions.create(["margin", "width"], { - easing: theme.transitions.easing.easeOut, - duration: theme.transitions.duration.enteringScreen - }) - }) -})) - export default function SecondaryHeader({ - drawerWidth, - isDrawerOpen, - onOpen, + showOpenDrawer, + onOpenDrawer, + trailingItem, children, sx }: { - drawerWidth: number - isDrawerOpen: boolean - onOpen: () => void - children: ReactNode, + showOpenDrawer: boolean + onOpenDrawer: () => void + trailingItem?: ReactNode + children?: ReactNode sx?: SxProps }) { const theme = useTheme() return ( - - + - {children} - + + {trailingItem} + + + {children} - + ) } diff --git a/src/features/sidebar/view/base/SecondaryWrapper.tsx b/src/features/sidebar/view/base/SecondaryWrapper.tsx new file mode 100644 index 00000000..f2d15c59 --- /dev/null +++ b/src/features/sidebar/view/base/SecondaryWrapper.tsx @@ -0,0 +1,50 @@ +import { ReactNode } from "react" +import { SxProps } from "@mui/system" +import { Stack } from "@mui/material" +import { styled } from "@mui/material/styles" + +interface WrapperStackProps { + drawerWidth: number + isDrawerOpen: boolean +} + +const WrapperStack = styled(Stack, { + shouldForwardProp: (prop) => prop !== "isDrawerOpen" +})(({ theme, drawerWidth, isDrawerOpen }) => ({ + transition: theme.transitions.create("margin", { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen + }), + marginLeft: `-${drawerWidth}px`, + ...(isDrawerOpen && { + transition: theme.transitions.create("margin", { + easing: theme.transitions.easing.easeOut, + duration: theme.transitions.duration.enteringScreen, + }), + marginLeft: 0 + }) +})) + +export default function SecondaryWrapper({ + drawerWidth, + isDrawerOpen, + children, + sx +}: { + drawerWidth: number + isDrawerOpen: boolean + children: ReactNode + sx?: SxProps +}) { + return ( + + {children} + + ) +} diff --git a/src/features/sidebar/view/base/responsive/Drawer.tsx b/src/features/sidebar/view/base/responsive/Drawer.tsx index 3da62fe5..8a027f77 100644 --- a/src/features/sidebar/view/base/responsive/Drawer.tsx +++ b/src/features/sidebar/view/base/responsive/Drawer.tsx @@ -4,14 +4,12 @@ import Drawer from "../Drawer" export default function RespnsiveDrawer({ width, isOpen, - header, onClose, children }: { width: number isOpen: boolean - header: ReactNode, - onClose: () => void, + onClose?: () => void children?: ReactNode }) { return ( @@ -21,7 +19,7 @@ export default function RespnsiveDrawer({ width={width} isOpen={isOpen} onClose={onClose} - header={header} + keepMounted={true} sx={{ display: { xs: "block", sm: "none" } }} > {children} @@ -30,12 +28,11 @@ export default function RespnsiveDrawer({ variant="persistent" width={width} isOpen={isOpen} - onClose={onClose} - header={header} + keepMounted={false} sx={{ display: { xs: "none", sm: "block" } }} - > - {children} - + > + {children} + ) } \ No newline at end of file diff --git a/src/features/sidebar/view/base/responsive/SecondaryHeader.tsx b/src/features/sidebar/view/base/responsive/SecondaryHeader.tsx index f2f8835d..acf9fe3d 100644 --- a/src/features/sidebar/view/base/responsive/SecondaryHeader.tsx +++ b/src/features/sidebar/view/base/responsive/SecondaryHeader.tsx @@ -1,35 +1,51 @@ import { ReactNode } from "react" +import { SxProps } from "@mui/system" +import { Box, Divider, IconButton, Stack, Collapse } from "@mui/material" import SecondaryHeader from "../SecondaryHeader" +import ExpandCircleDownIcon from "@mui/icons-material/ExpandCircleDown" -export default function RespnsiveDrawer({ - drawerWidth, - offsetContent, - onOpen, - children +export default function ResponsiveSecondaryHeader({ + showOpenDrawer, + onOpenDrawer, + showMobileToolbar, + onToggleMobileToolbar, + trailingItem, + mobileToolbar, + sx }: { - drawerWidth: number - offsetContent: boolean - onOpen: () => void - children: ReactNode + showOpenDrawer: boolean + onOpenDrawer: () => void + showMobileToolbar: boolean + onToggleMobileToolbar: (showMobileToolbar: boolean) => void + trailingItem?: ReactNode + mobileToolbar?: ReactNode + sx?: SxProps }) { return ( - <> - - {children} - - - {children} - - + + {trailingItem} + + onToggleMobileToolbar(!showMobileToolbar) }> + + + + + } + > + {mobileToolbar && + + + {mobileToolbar} + + + } + ) -} \ No newline at end of file +} diff --git a/src/features/sidebar/view/base/responsive/SecondaryWrapper.tsx b/src/features/sidebar/view/base/responsive/SecondaryWrapper.tsx new file mode 100644 index 00000000..e217a2ad --- /dev/null +++ b/src/features/sidebar/view/base/responsive/SecondaryWrapper.tsx @@ -0,0 +1,32 @@ +import { ReactNode } from "react" +import SecondaryWrapper from "../SecondaryWrapper" + +export default function ResponsiveDrawer({ + drawerWidth, + offsetContent, + children +}: { + drawerWidth: number + offsetContent: boolean + children: ReactNode +}) { + const sx = { overflow: "hidden" } + return ( + <> + + {children} + + + {children} + + + ) +} \ No newline at end of file diff --git a/src/features/sidebar/view/base/responsive/SidebarContainer.tsx b/src/features/sidebar/view/base/responsive/SidebarContainer.tsx index 14d1091f..95629029 100644 --- a/src/features/sidebar/view/base/responsive/SidebarContainer.tsx +++ b/src/features/sidebar/view/base/responsive/SidebarContainer.tsx @@ -1,10 +1,11 @@ import { ReactNode } from "react" -import { Box } from "@mui/material" +import { Stack } from "@mui/material" import Drawer from "./Drawer" -import SecondaryHeader from "./SecondaryHeader" -import SecondaryContent from "./SecondaryContent" +import PrimaryHeader from "../PrimaryHeader" +import SecondaryWrapper from "./SecondaryWrapper" const SidebarContainer = ({ + canCloseDrawer, isDrawerOpen, onToggleDrawerOpen, sidebarHeader, @@ -12,6 +13,7 @@ const SidebarContainer = ({ header, children }: { + canCloseDrawer: boolean, isDrawerOpen: boolean onToggleDrawerOpen: (isDrawerOpen: boolean) => void sidebarHeader?: ReactNode @@ -21,29 +23,28 @@ const SidebarContainer = ({ }) => { const drawerWidth = 320 return ( - - onToggleDrawerOpen(true)} - > - {header} - + onToggleDrawerOpen(false)} > + onToggleDrawerOpen(false)} + > + {sidebarHeader} + {sidebar} - - {children} - - + + {header} +
+ {children} +
+
+ ) } diff --git a/src/features/sidebar/view/client/SidebarContainer.tsx b/src/features/sidebar/view/client/SidebarContainer.tsx index 9da62449..5f62a7b8 100644 --- a/src/features/sidebar/view/client/SidebarContainer.tsx +++ b/src/features/sidebar/view/client/SidebarContainer.tsx @@ -1,39 +1,53 @@ "use client" import dynamic from "next/dynamic" -import { ReactNode } from "react" +import { ReactNode, useEffect } from "react" import { useSessionStorage } from "usehooks-ts" import ResponsiveSidebarContainer from "../base/responsive/SidebarContainer" +import ResponsiveSecondaryHeader from "../base/responsive/SecondaryHeader" import Sidebar from "../Sidebar" import SidebarHeader from "../SidebarHeader" -import TrailingToolbar from "./TrailingToolbar" const SidebarContainer = ({ + canCloseDrawer, sidebar, children, - trailingToolbar + toolbarTrailingItem, + mobileToolbar }: { + canCloseDrawer: boolean, sidebar?: ReactNode children?: ReactNode - trailingToolbar?: ReactNode + toolbarTrailingItem?: ReactNode + mobileToolbar?: ReactNode }) => { const [open, setOpen] = useSessionStorage("isDrawerOpen", true) + const [showMobileToolbar, setShowMobileToolbar] = useSessionStorage("isMobileToolbarVisible", true) + useEffect(() => { + if (!canCloseDrawer) { + setOpen(true) + } + }, [canCloseDrawer]) return ( - } + sidebarHeader={} sidebar={ {sidebar} } - header={trailingToolbar && - - {trailingToolbar} - + header={ + setOpen(true)} + showMobileToolbar={showMobileToolbar} + onToggleMobileToolbar={setShowMobileToolbar} + trailingItem={toolbarTrailingItem} + mobileToolbar={mobileToolbar} + /> } > {children} diff --git a/src/features/user/view/UserListItem.tsx b/src/features/user/view/UserListItem.tsx index 195263e5..a208b79f 100644 --- a/src/features/user/view/UserListItem.tsx +++ b/src/features/user/view/UserListItem.tsx @@ -15,7 +15,7 @@ const UserListItem: React.FC<{ display: "flex", flexDirection: "row", alignItems: "center", - padding: "15px" + padding: 2 }} > {!isLoading && user && user.picture && From da58de53df75a4559fb28883c546256622628e1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=B8vring?= Date: Thu, 26 Oct 2023 00:24:07 +0200 Subject: [PATCH 05/10] Fixes linting warnings (#64) --- src/features/sidebar/view/base/responsive/SecondaryHeader.tsx | 2 +- src/features/sidebar/view/client/SidebarContainer.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features/sidebar/view/base/responsive/SecondaryHeader.tsx b/src/features/sidebar/view/base/responsive/SecondaryHeader.tsx index acf9fe3d..c837d016 100644 --- a/src/features/sidebar/view/base/responsive/SecondaryHeader.tsx +++ b/src/features/sidebar/view/base/responsive/SecondaryHeader.tsx @@ -1,6 +1,6 @@ import { ReactNode } from "react" import { SxProps } from "@mui/system" -import { Box, Divider, IconButton, Stack, Collapse } from "@mui/material" +import { Box, IconButton, Stack, Collapse } from "@mui/material" import SecondaryHeader from "../SecondaryHeader" import ExpandCircleDownIcon from "@mui/icons-material/ExpandCircleDown" diff --git a/src/features/sidebar/view/client/SidebarContainer.tsx b/src/features/sidebar/view/client/SidebarContainer.tsx index 5f62a7b8..87fe5644 100644 --- a/src/features/sidebar/view/client/SidebarContainer.tsx +++ b/src/features/sidebar/view/client/SidebarContainer.tsx @@ -27,7 +27,7 @@ const SidebarContainer = ({ if (!canCloseDrawer) { setOpen(true) } - }, [canCloseDrawer]) + }, [canCloseDrawer, setOpen]) return ( Date: Thu, 26 Oct 2023 11:39:24 +0200 Subject: [PATCH 06/10] Feature/add-image-caching (#65) * Add cache for images * Use stale-while-revalidate * Add docker composer file for redis --------- Co-authored-by: Nicolas Vancea --- docker-compose.yaml | 13 ++++++++++++ .../[owner]/[repository]/[...path]/route.ts | 20 ++++++++++++++++--- 2 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 docker-compose.yaml diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 00000000..e2212f02 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,13 @@ +version: '3.8' +services: + cache: + image: redis:6.2-alpine + restart: always + ports: + - '6379:6379' + command: redis-server --save 20 1 --loglevel warning + volumes: + - cache:/data +volumes: + cache: + driver: local \ No newline at end of file diff --git a/src/app/api/github/blob/[owner]/[repository]/[...path]/route.ts b/src/app/api/github/blob/[owner]/[repository]/[...path]/route.ts index ec082b6b..6c4ca236 100644 --- a/src/app/api/github/blob/[owner]/[repository]/[...path]/route.ts +++ b/src/app/api/github/blob/[owner]/[repository]/[...path]/route.ts @@ -8,12 +8,26 @@ interface GetBlobParams { } export async function GET(req: NextRequest, { params }: { params: GetBlobParams }) { + const path = params.path.join("/") const item = await gitHubClient.getRepositoryContent({ repositoryOwner: params.owner, repositoryName: params.repository, - path: params.path.join("/"), - ref: req.nextUrl.searchParams.get("ref") || undefined + path: path, + ref: req.nextUrl.searchParams.get("ref") ?? undefined }) const url = new URL(item.downloadURL) - return NextResponse.redirect(url) + const imageRegex = /\.(jpg|jpeg|png|webp|avif|gif)$/; + + if (new RegExp(imageRegex).exec(path)) { + const file = await fetch(url).then(r => r.blob()); + const headers = new Headers(); + const cacheExpirationInSeconds = 60 * 60 * 24 * 30; // 30 days + + headers.set("Content-Type", "image/*"); + headers.set("cache-control", `stale-while-revalidate=${cacheExpirationInSeconds}`); + + return new NextResponse(file, { status: 200, statusText: "OK", headers }) + } else { + return NextResponse.redirect(url) + } } From d994c797ae9f56ff80c62502d02cf51c2f454eec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=B8vring?= Date: Thu, 26 Oct 2023 11:54:48 +0200 Subject: [PATCH 07/10] Fixes clicking duck to go home (#66) --- src/features/sidebar/view/SidebarHeader.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/features/sidebar/view/SidebarHeader.tsx b/src/features/sidebar/view/SidebarHeader.tsx index 12e67e8c..09e5b13c 100644 --- a/src/features/sidebar/view/SidebarHeader.tsx +++ b/src/features/sidebar/view/SidebarHeader.tsx @@ -1,10 +1,13 @@ import Image from "next/image" import { Stack } from "@mui/material" +import Link from "next/link" export default function SidebarHeader() { return ( - Duck + + Duck + ) -} \ No newline at end of file +} From b0a59c5c2da9e3e8ee61b8de86342570e1eedadd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=B8vring?= Date: Thu, 26 Oct 2023 12:05:23 +0200 Subject: [PATCH 08/10] Automatically closes sidebar on mobile (#67) --- .../projects/view/client/ProjectsPage.tsx | 21 ++++++++++++------- .../sidebar/view/client/SidebarContainer.tsx | 7 +++++++ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/features/projects/view/client/ProjectsPage.tsx b/src/features/projects/view/client/ProjectsPage.tsx index 76109660..71a9cf5e 100644 --- a/src/features/projects/view/client/ProjectsPage.tsx +++ b/src/features/projects/view/client/ProjectsPage.tsx @@ -1,7 +1,9 @@ "use client" -import { useEffect } from "react" +import { useState, useEffect } from "react" import { useRouter } from "next/navigation" +import { useTheme } from "@mui/material/styles" +import useMediaQuery from "@mui/material/useMediaQuery" import SidebarContainer from "@/features/sidebar/view/client/SidebarContainer" import Project from "../../domain/Project" import ProjectList from "../ProjectList" @@ -25,7 +27,10 @@ export default function ProjectsPage({ specificationId?: string }) { const router = useRouter() + const theme = useTheme() + const isDesktopLayout = useMediaQuery(theme.breakpoints.up("sm")) const { projects: clientProjects, error, isLoading: isClientLoading } = useProjects() + const [forceCloseSidebar, setForceCloseSidebar] = useState(false) const projects = isClientLoading ? (serverProjects || []) : clientProjects const isLoading = serverProjects === undefined && isClientLoading const stateContainer = getProjectPageState({ @@ -36,11 +41,6 @@ export default function ProjectsPage({ selectedVersionId: versionId, selectedSpecificationId: specificationId }) - const handleProjectSelected = (project: Project) => { - const version = project.versions[0] - const specification = version.specifications[0] - projectNavigator.navigate(router, project.id, version.id, specification.id) - } useEffect(() => { updateWindowTitle( document, @@ -56,6 +56,12 @@ export default function ProjectsPage({ const urlSelection = { projectId, versionId, specificationId } projectNavigator.navigateIfNeeded(router, urlSelection, stateContainer.selection) }, [router, projectId, versionId, specificationId, stateContainer.selection]) + const selectProject = (project: Project) => { + setForceCloseSidebar(!isDesktopLayout) + const version = project.versions[0] + const specification = version.specifications[0] + projectNavigator.navigate(router, project.id, version.id, specification.id) + } const selectVersion = (versionId: string) => { projectNavigator.navigateToVersion(router, stateContainer.selection!, versionId) } @@ -65,12 +71,13 @@ export default function ProjectsPage({ return ( } toolbarTrailingItem={stateContainer.selection && diff --git a/src/features/sidebar/view/client/SidebarContainer.tsx b/src/features/sidebar/view/client/SidebarContainer.tsx index 87fe5644..d282fe47 100644 --- a/src/features/sidebar/view/client/SidebarContainer.tsx +++ b/src/features/sidebar/view/client/SidebarContainer.tsx @@ -10,12 +10,14 @@ import SidebarHeader from "../SidebarHeader" const SidebarContainer = ({ canCloseDrawer, + forceClose, sidebar, children, toolbarTrailingItem, mobileToolbar }: { canCloseDrawer: boolean, + forceClose: boolean, sidebar?: ReactNode children?: ReactNode toolbarTrailingItem?: ReactNode @@ -28,6 +30,11 @@ const SidebarContainer = ({ setOpen(true) } }, [canCloseDrawer, setOpen]) + useEffect(() => { + if (forceClose) { + setOpen(false) + } + }, [forceClose]) return ( Date: Thu, 26 Oct 2023 12:09:37 +0200 Subject: [PATCH 09/10] Fixes layout issue of menu button (#68) --- src/features/sidebar/view/base/PrimaryHeader.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/features/sidebar/view/base/PrimaryHeader.tsx b/src/features/sidebar/view/base/PrimaryHeader.tsx index a5e96a26..9aa90c67 100644 --- a/src/features/sidebar/view/base/PrimaryHeader.tsx +++ b/src/features/sidebar/view/base/PrimaryHeader.tsx @@ -17,8 +17,12 @@ export default function PrimaryHeader({ From 0a4eb81b4b0872c048ad140ac6e28d0b8d1ed0cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=B8vring?= Date: Thu, 26 Oct 2023 13:51:52 +0200 Subject: [PATCH 10/10] Fixes comments not posted when processing web hook (#69) * Refactors logic to be more readable * Fixes empty allowlist --- src/app/api/hooks/github/route.ts | 15 +++++++----- ...toryNameCheckingPullRequestEventHandler.ts | 24 ++++++++++++++----- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/app/api/hooks/github/route.ts b/src/app/api/hooks/github/route.ts index b408e86c..f6739101 100644 --- a/src/app/api/hooks/github/route.ts +++ b/src/app/api/hooks/github/route.ts @@ -13,12 +13,15 @@ const { GITHUB_WEBHOK_REPOSITORY_DISALLOWLIST } = process.env -const allowedRepositoryNames = (GITHUB_WEBHOK_REPOSITORY_ALLOWLIST || "") - .split(",") - .map(e => e.trim()) -const disallowedRepositoryNames = (GITHUB_WEBHOK_REPOSITORY_DISALLOWLIST || "") - .split(",") - .map(e => e.trim()) +const listFromCommaSeparatedString = (str?: string) => { + if (!str) { + return [] + } + return str.split(",").map(e => e.trim()) +} + +const allowedRepositoryNames = listFromCommaSeparatedString(GITHUB_WEBHOK_REPOSITORY_ALLOWLIST) +const disallowedRepositoryNames = listFromCommaSeparatedString(GITHUB_WEBHOK_REPOSITORY_DISALLOWLIST) const hookHandler = new GitHubHookHandler({ secret: GITHUB_WEBHOOK_SECRET, diff --git a/src/features/hooks/domain/RepositoryNameCheckingPullRequestEventHandler.ts b/src/features/hooks/domain/RepositoryNameCheckingPullRequestEventHandler.ts index 82edc1f9..cf45fa81 100644 --- a/src/features/hooks/domain/RepositoryNameCheckingPullRequestEventHandler.ts +++ b/src/features/hooks/domain/RepositoryNameCheckingPullRequestEventHandler.ts @@ -16,18 +16,30 @@ export default class RepositoryNameCheckingPullRequestEventHandler implements IP } async pullRequestOpened(event: IPullRequestOpenedEvent): Promise { - if (!event.repositoryName.match(/-openapi$/)) { + if (!this.repositoryNameHasExpectedSuffix(event.repositoryName)) { return } - if ( - this.allowedRepositoryNames.length != 0 && - !this.allowedRepositoryNames.includes(event.repositoryName) - ) { + if (!this.isAllowedRepositoryName(event.repositoryName)) { return } - if (this.disallowedRepositoryNames.includes(event.repositoryName)) { + if (this.isDisallowedRepositoryName(event.repositoryName)) { return } return await this.eventHandler.pullRequestOpened(event) } + + private repositoryNameHasExpectedSuffix(repositoryName: string) { + return repositoryName.match(/-openapi$/) + } + + private isAllowedRepositoryName(repositoryName: string) { + if (this.allowedRepositoryNames.length == 0) { + return true + } + return this.allowedRepositoryNames.includes(repositoryName) + } + + private isDisallowedRepositoryName(repositoryName: string) { + return this.disallowedRepositoryNames.includes(repositoryName) + } }