Source: components/dashboard.js

  1. "use client";
  2. import React, { cloneElement, useContext } from "react";
  3. import {
  4. Avatar,
  5. Button,
  6. Box,
  7. Nav,
  8. Text,
  9. Sidebar as SidebarBase,
  10. Header,
  11. Page,
  12. PageContent,
  13. ResponsiveContext,
  14. Grommet,
  15. grommet,
  16. } from "grommet";
  17. import { Logout, DocumentTest, Contract } from "grommet-icons";
  18. import { BRAND_HEX } from "../lib/config";
  19. import { Logo } from "./logo";
  20. import { usePathname, useRouter } from "next/navigation";
  21. import { signOut } from "next-auth/react";
  22. const MOBILE_SIDEBAR_WIDTH = "4.5rem";
  23. const MOBILE_HEADER_HEIGHT = "3.95rem";
  24. const DESKTOP_SIDEBAR_WIDTH = "15rem";
  25. const DESKTOP_HEADER_HEIGHT = "4.688rem";
  26. const SidebarHeader = (props) => {
  27. const { serverSession, size, deviceType } = props;
  28. const isSmall = deviceType === "mobile" || size === "small";
  29. return (
  30. <Box
  31. align="center"
  32. gap="small"
  33. direction="row"
  34. margin={{ bottom: "xxsmall" }}
  35. justify="center"
  36. >
  37. {serverSession?.user?.image ? (
  38. <Avatar src={serverSession.user.image} />
  39. ) : null}
  40. {isSmall ? null : <Text>{serverSession?.user?.name}</Text>}
  41. </Box>
  42. );
  43. };
  44. const SidebarButton = ({ icon, label, selected, ...rest }) => (
  45. <Button
  46. gap="medium"
  47. justify="start"
  48. fill
  49. icon={cloneElement(icon, {
  50. color: selected ? "white" : undefined,
  51. })}
  52. label={label}
  53. plain
  54. {...rest}
  55. style={{
  56. ...rest.style,
  57. whiteSpace: "nowrap",
  58. height: "3rem",
  59. paddingLeft: "2rem",
  60. flex: "unset",
  61. background: selected ? BRAND_HEX : "transparent",
  62. color: selected ? "white" : "unset",
  63. }}
  64. />
  65. );
  66. const SidebarFooter = (props) => {
  67. const { size, deviceType } = props;
  68. const { push } = useRouter();
  69. const pathname = usePathname();
  70. const apiManagement = "/api-management";
  71. if (deviceType === "mobile" || size === "small") {
  72. return (
  73. <Nav gap="small">
  74. <Button
  75. icon={<Contract />}
  76. hoverIndicator={pathname !== apiManagement}
  77. primary={pathname === apiManagement}
  78. onClick={() => push(apiManagement)}
  79. />
  80. <Button
  81. icon={<Logout />}
  82. hoverIndicator
  83. onClick={async () => {
  84. await signOut({
  85. redirect: true,
  86. });
  87. }}
  88. />
  89. </Nav>
  90. );
  91. }
  92. return (
  93. <Nav>
  94. <SidebarButton
  95. icon={<Contract />}
  96. label={"API Management"}
  97. selected={pathname === apiManagement}
  98. onClick={() => push(apiManagement)}
  99. />
  100. <SidebarButton
  101. icon={<Logout />}
  102. label={"Logout"}
  103. onClick={async () => {
  104. await signOut({
  105. redirect: true,
  106. });
  107. }}
  108. />
  109. </Nav>
  110. );
  111. };
  112. /**
  113. * Navigation items are organized by
  114. * usage order (data from G.A.)
  115. */
  116. const MainNavigation = (props) => {
  117. const { size, deviceType } = props;
  118. const { push } = useRouter();
  119. const pathname = usePathname();
  120. const completions = "/completions/";
  121. const matchCompletions = pathname.includes(completions);
  122. if (deviceType === "mobile" || size === "small") {
  123. return (
  124. <Nav gap="small">
  125. <Button
  126. icon={<DocumentTest />}
  127. hoverIndicator={!matchCompletions}
  128. primary={matchCompletions}
  129. onClick={() => push(completions + "pending")}
  130. />
  131. </Nav>
  132. );
  133. }
  134. return (
  135. <Nav gap="medium" fill="vertical" responsive={false}>
  136. <SidebarButton
  137. icon={<DocumentTest />}
  138. label={"Completions"}
  139. selected={matchCompletions}
  140. onClick={() => push(completions + "pending")}
  141. />
  142. </Nav>
  143. );
  144. };
  145. function MobileSidebar(props) {
  146. const { serverSession, size } = props;
  147. return (
  148. <SidebarBase
  149. elevation="large"
  150. responsive={false}
  151. background="light-1"
  152. header={
  153. <SidebarHeader
  154. serverSession={serverSession}
  155. size={size}
  156. deviceType={"mobile"}
  157. />
  158. }
  159. footer={<SidebarFooter size={size} deviceType="mobile" />}
  160. style={{
  161. top: MOBILE_HEADER_HEIGHT,
  162. height: `calc(100vh - ${MOBILE_HEADER_HEIGHT})`,
  163. minHeight: `calc(100vh - ${MOBILE_HEADER_HEIGHT})`,
  164. position: "fixed",
  165. minWidth: MOBILE_SIDEBAR_WIDTH,
  166. maxWidth: MOBILE_SIDEBAR_WIDTH,
  167. borderRight: `1px solid ${BRAND_HEX}`,
  168. // Trick to make the box-shadow from the sidebar and header look good
  169. zIndex: "11",
  170. }}
  171. >
  172. <MainNavigation
  173. size={size}
  174. serverSession={serverSession}
  175. deviceType={"mobile"}
  176. />
  177. </SidebarBase>
  178. );
  179. }
  180. function DesktopSidebar(props) {
  181. const { serverSession, size } = props;
  182. return (
  183. <SidebarBase
  184. responsive={false}
  185. elevation="large"
  186. header={
  187. <SidebarHeader
  188. serverSession={serverSession}
  189. size={size}
  190. deviceType={"desktop"}
  191. />
  192. }
  193. footer={<SidebarFooter deviceType="desktop" />}
  194. pad={{ left: "unset", right: "unset", vertical: "large" }}
  195. background="light-1"
  196. style={{
  197. position: "fixed",
  198. top: DESKTOP_HEADER_HEIGHT,
  199. height: `calc(100vh - ${DESKTOP_HEADER_HEIGHT})`,
  200. minHeight: `calc(100vh - ${DESKTOP_HEADER_HEIGHT})`,
  201. minWidth: DESKTOP_SIDEBAR_WIDTH,
  202. maxWidth: DESKTOP_SIDEBAR_WIDTH,
  203. borderRight: `1px solid ${BRAND_HEX}`,
  204. // Trick to make the box-shadow from the sidebar and header look good
  205. zIndex: "11",
  206. }}
  207. >
  208. <MainNavigation size={size} serverSession={serverSession} />
  209. </SidebarBase>
  210. );
  211. }
  212. function Sidebar(props) {
  213. const { serverSession, size, deviceType } = props;
  214. if (deviceType === "mobile" || size === "small") {
  215. return (
  216. <MobileSidebar
  217. serverSession={serverSession}
  218. size={size}
  219. deviceType={deviceType}
  220. />
  221. );
  222. }
  223. return (
  224. <DesktopSidebar
  225. serverSession={serverSession}
  226. size={size}
  227. deviceType={deviceType}
  228. />
  229. );
  230. }
  231. export default function Dashboard(props) {
  232. const { serverSession, children, deviceType } = props;
  233. const size = useContext(ResponsiveContext);
  234. const isSmall = deviceType === "mobile" || size === "small";
  235. return (
  236. <>
  237. <Grommet
  238. full
  239. theme={grommet}
  240. style={{
  241. overflow: "hidden",
  242. }}
  243. >
  244. <Header
  245. pad="small"
  246. style={{
  247. position: "fixed",
  248. width: "100vw",
  249. borderBottom: `1px solid ${BRAND_HEX}`,
  250. zIndex: "10",
  251. }}
  252. background="light-1"
  253. elevation="large"
  254. >
  255. <Box
  256. style={{
  257. display: "flex",
  258. width: "100%",
  259. flexDirection: "row",
  260. justifyContent: "space-between",
  261. margin: "auto",
  262. maxWidth: "96rem",
  263. // Trick to make the box-shadow from the sidebar and header look good
  264. zIndex: "9",
  265. }}
  266. >
  267. <Logo noTitle />
  268. </Box>
  269. </Header>
  270. <Page background="background-front" kind="full">
  271. <Box direction="row" height={{ min: "100%" }}>
  272. <Sidebar
  273. serverSession={serverSession}
  274. size={size}
  275. deviceType={deviceType}
  276. />
  277. <PageContent
  278. pad="medium"
  279. style={{
  280. width: isSmall
  281. ? `calc(100vw - ${MOBILE_SIDEBAR_WIDTH})`
  282. : `calc(100vw - ${DESKTOP_SIDEBAR_WIDTH})`,
  283. minHeight: isSmall
  284. ? `calc(100vh - ${MOBILE_HEADER_HEIGHT})`
  285. : `calc(100vh - ${DESKTOP_HEADER_HEIGHT})`,
  286. maxHeight: isSmall
  287. ? `calc(100vh - ${MOBILE_HEADER_HEIGHT})`
  288. : `calc(100vh - ${DESKTOP_HEADER_HEIGHT})`,
  289. minWidth: "0px",
  290. marginTop: isSmall
  291. ? MOBILE_HEADER_HEIGHT
  292. : DESKTOP_HEADER_HEIGHT,
  293. marginLeft: isSmall
  294. ? MOBILE_SIDEBAR_WIDTH
  295. : DESKTOP_SIDEBAR_WIDTH,
  296. overflow: "auto",
  297. }}
  298. >
  299. {children}
  300. </PageContent>
  301. </Box>
  302. </Page>
  303. </Grommet>
  304. </>
  305. );
  306. }