add-navigation
into main
1 year ago
@@ -2446,10 +2446,6 @@ | |||
"node": ">= 6" | |||
} | |||
}, | |||
"node_modules/eslint-plugin-import": { | |||
"dev": true, | |||
"peer": true | |||
}, | |||
"node_modules/eslint-plugin-prettier": { | |||
"version": "5.0.1", | |||
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.1.tgz", | |||
@@ -0,0 +1,32 @@ | |||
import type { Metadata } from "next"; | |||
import AppBar from "@/components/AppBar"; | |||
import { getServerSession } from "next-auth"; | |||
import { authOptions } from "@/config/authConfig"; | |||
import { redirect } from "next/navigation"; | |||
export const metadata: Metadata = { | |||
title: "Dashboard", | |||
}; | |||
export default async function DashboardLayout({ | |||
children, | |||
}: { | |||
children: React.ReactNode; | |||
}) { | |||
const session = await getServerSession(authOptions); | |||
console.log(session); | |||
if (!session?.user) { | |||
redirect("/login"); | |||
} | |||
return ( | |||
<> | |||
<AppBar | |||
profileName={session.user.name!} | |||
avatarImageSrc={session.user.image || undefined} | |||
/> | |||
<main>{children}</main> | |||
</> | |||
); | |||
} |
@@ -0,0 +1,34 @@ | |||
import MUIAppBar from "@mui/material/AppBar"; | |||
import Toolbar from "@mui/material/Toolbar"; | |||
import React from "react"; | |||
import Profile from "./Profile"; | |||
import Box from "@mui/material/Box"; | |||
import NavigationToggle from "./NavigationToggle"; | |||
import { I18nProvider } from "@/i18n"; | |||
export interface AppBarProps { | |||
avatarImageSrc?: string; | |||
profileName: string; | |||
} | |||
const AppBar: React.FC<AppBarProps> = ({ avatarImageSrc, profileName }) => { | |||
return ( | |||
<MUIAppBar position="fixed"> | |||
<Toolbar> | |||
<I18nProvider namespaces={["common"]}> | |||
<NavigationToggle /> | |||
<Box | |||
sx={{ flexGrow: 1, display: "flex", justifyContent: "flex-end" }} | |||
> | |||
<Profile | |||
avatarImageSrc={avatarImageSrc} | |||
profileName={profileName} | |||
/> | |||
</Box> | |||
</I18nProvider> | |||
</Toolbar> | |||
</MUIAppBar> | |||
); | |||
}; | |||
export default AppBar; |
@@ -0,0 +1,47 @@ | |||
"use client"; | |||
import IconButton from "@mui/material/IconButton"; | |||
import MenuIcon from "@mui/icons-material/Menu"; | |||
import NavigationContent from "../NavigationContent"; | |||
import React from "react"; | |||
import Drawer from "@mui/material/Drawer"; | |||
const NavigationToggle: React.FC = () => { | |||
const [isOpened, setIsOpened] = React.useState(false); | |||
const openNavigation = () => { | |||
setIsOpened(true); | |||
}; | |||
const closeNavigation = () => { | |||
setIsOpened(false); | |||
}; | |||
return ( | |||
<> | |||
<Drawer variant="permanent" sx={{ display: { xs: "none", lg: "block" } }}> | |||
<NavigationContent /> | |||
</Drawer> | |||
<Drawer | |||
sx={{ display: { lg: "none" } }} | |||
open={isOpened} | |||
onClose={closeNavigation} | |||
ModalProps={{ | |||
keepMounted: true, | |||
}} | |||
> | |||
<NavigationContent /> | |||
</Drawer> | |||
<IconButton | |||
sx={{ display: { lg: "none" } }} | |||
onClick={openNavigation} | |||
edge="start" | |||
aria-label="menu" | |||
color="inherit" | |||
> | |||
<MenuIcon fontSize="inherit" /> | |||
</IconButton> | |||
</> | |||
); | |||
}; | |||
export default NavigationToggle; |
@@ -0,0 +1,61 @@ | |||
"use client"; | |||
import IconButton from "@mui/material/IconButton"; | |||
import Menu from "@mui/material/Menu"; | |||
import MenuItem from "@mui/material/MenuItem"; | |||
import Avatar from "@mui/material/Avatar"; | |||
import React from "react"; | |||
import { AppBarProps } from "./AppBar"; | |||
import Divider from "@mui/material/Divider"; | |||
import Typography from "@mui/material/Typography"; | |||
import { useTranslation } from "react-i18next"; | |||
import { signOut } from "next-auth/react"; | |||
type Props = Pick<AppBarProps, "avatarImageSrc" | "profileName">; | |||
const Profile: React.FC<Props> = ({ avatarImageSrc, profileName }) => { | |||
const [profileMenuAnchorEl, setProfileMenuAnchorEl] = | |||
React.useState<HTMLButtonElement>(); | |||
const openProfileMenu: React.MouseEventHandler<HTMLButtonElement> = ( | |||
event, | |||
) => { | |||
setProfileMenuAnchorEl(event.currentTarget); | |||
}; | |||
const closeProfileMenu = () => { | |||
setProfileMenuAnchorEl(undefined); | |||
}; | |||
const { t } = useTranslation("login"); | |||
return ( | |||
<> | |||
<IconButton aria-label="profile" onClick={openProfileMenu}> | |||
<Avatar src={avatarImageSrc} /> | |||
</IconButton> | |||
<Menu | |||
id="profile-menu" | |||
anchorEl={profileMenuAnchorEl} | |||
anchorOrigin={{ | |||
vertical: "bottom", | |||
horizontal: "right", | |||
}} | |||
keepMounted | |||
transformOrigin={{ | |||
vertical: "top", | |||
horizontal: "right", | |||
}} | |||
open={Boolean(profileMenuAnchorEl)} | |||
onClose={closeProfileMenu} | |||
MenuListProps={{ dense: true, disablePadding: true }} | |||
> | |||
<Typography sx={{ mx: "1.5rem", my: "0.5rem" }} fontWeight="bold"> | |||
{profileName} | |||
</Typography> | |||
<Divider /> | |||
<MenuItem onClick={() => signOut()}>{t("Sign out")}</MenuItem> | |||
</Menu> | |||
</> | |||
); | |||
}; | |||
export default Profile; |
@@ -0,0 +1 @@ | |||
export { default } from "./AppBar"; |
@@ -0,0 +1,66 @@ | |||
import Divider from "@mui/material/Divider"; | |||
import Box from "@mui/material/Box"; | |||
import React from "react"; | |||
import List from "@mui/material/List"; | |||
import ListItemButton from "@mui/material/ListItemButton"; | |||
import ListItemText from "@mui/material/ListItemText"; | |||
import ListItemIcon from "@mui/material/ListItemIcon"; | |||
import WorkHistory from "@mui/icons-material/WorkHistory"; | |||
import Dashboard from "@mui/icons-material/Dashboard"; | |||
import RequestQuote from "@mui/icons-material/RequestQuote"; | |||
import Task from "@mui/icons-material/Task"; | |||
import Assignment from "@mui/icons-material/Assignment"; | |||
import Settings from "@mui/icons-material/Settings"; | |||
import Analytics from "@mui/icons-material/Analytics"; | |||
import Payments from "@mui/icons-material/Payments"; | |||
import { useTranslation } from "react-i18next"; | |||
import Typography from "@mui/material/Typography"; | |||
import { usePathname } from "next/navigation"; | |||
import Link from "next/link"; | |||
interface NavigationItem { | |||
icon: React.ReactNode; | |||
label: string; | |||
path: string; | |||
} | |||
const navigationItems: NavigationItem[] = [ | |||
{ icon: <WorkHistory />, label: "User Workspace", path: "/workspace" }, | |||
{ icon: <Dashboard />, label: "Dashboard", path: "/dashboard" }, | |||
{ icon: <RequestQuote />, label: "Expense Claim", path: "/claim" }, | |||
{ icon: <Assignment />, label: "Project Management", path: "/projects" }, | |||
{ icon: <Task />, label: "Task Template", path: "/tasks" }, | |||
{ icon: <Payments />, label: "Invoice", path: "/invoice" }, | |||
{ icon: <Analytics />, label: "Analysis Report", path: "/analytics" }, | |||
{ icon: <Settings />, label: "Setting", path: "/settings" }, | |||
]; | |||
const NavigationContent: React.FC = () => { | |||
const { t } = useTranslation("common"); | |||
const pathname = usePathname(); | |||
return ( | |||
<Box> | |||
<Box sx={{ p: "1.5rem" }}> | |||
{/* Replace this with company logo and/or name */} | |||
<Typography variant="h4">TSMS</Typography> | |||
</Box> | |||
<Divider /> | |||
<List component="nav"> | |||
{navigationItems.map(({ icon, label, path }, index) => { | |||
return ( | |||
<ListItemButton | |||
key={`${label}-${index}`} | |||
selected={pathname.includes(path)} | |||
> | |||
<ListItemIcon>{icon}</ListItemIcon> | |||
<ListItemText primary={<Link href={path}>{t(label)}</Link>} /> | |||
</ListItemButton> | |||
); | |||
})} | |||
</List> | |||
</Box> | |||
); | |||
}; | |||
export default NavigationContent; |
@@ -0,0 +1 @@ | |||
export { default } from "./NavigationContent"; |
@@ -283,6 +283,51 @@ const components: ThemeOptions["components"] = { | |||
variant: "filled", | |||
}, | |||
}, | |||
MuiMenuItem: { | |||
styleOverrides: { | |||
root: { | |||
margin: "0.5rem", | |||
borderRadius: 8, | |||
}, | |||
}, | |||
}, | |||
MuiList: { | |||
styleOverrides: { | |||
padding: { | |||
paddingBlock: "1rem", | |||
paddingInline: "1rem", | |||
}, | |||
}, | |||
}, | |||
MuiListItemButton: { | |||
styleOverrides: { | |||
root: { | |||
borderRadius: 8, | |||
marginBlockEnd: "0.5rem", | |||
a: { | |||
textDecoration: "none", | |||
color: "inherit", | |||
} | |||
}, | |||
}, | |||
}, | |||
MuiListItemText: { | |||
styleOverrides: { | |||
root: { | |||
marginInlineEnd: "2rem", | |||
}, | |||
primary: { | |||
fontWeight: 500, | |||
}, | |||
}, | |||
}, | |||
MuiListItemIcon: { | |||
styleOverrides: { | |||
root: { | |||
color: "inherit", | |||
}, | |||
}, | |||
}, | |||
}; | |||
export default components; |
@@ -79,12 +79,6 @@ const typography: TypographyOptions = { | |||
fontSize: "1.125rem", | |||
lineHeight: 1.2, | |||
}, | |||
fontSize: 0, | |||
fontWeightLight: undefined, | |||
fontWeightRegular: undefined, | |||
fontWeightMedium: undefined, | |||
fontWeightBold: undefined, | |||
htmlFontSize: 0, | |||
}; | |||
export default typography; |