Source: components/dashboard.jsx

  1. import React, { cloneElement, useContext } from "react";
  2. import {
  3. Avatar,
  4. Button,
  5. Box,
  6. Nav,
  7. Text,
  8. Sidebar as SidebarBase,
  9. Header,
  10. Page,
  11. PageContent,
  12. ResponsiveContext,
  13. } from "grommet";
  14. import {
  15. Book,
  16. Logout,
  17. Magic,
  18. Star,
  19. UserSettings,
  20. BarChart,
  21. Inbox,
  22. } from "grommet-icons";
  23. import { BRAND_HEX } from "../lib/config";
  24. import { Logo } from "./logo";
  25. import PageActions from "./page-actions";
  26. import { useRouter } from "next/router";
  27. import { signOut } from "next-auth/react";
  28. import { useTranslation } from "next-i18next";
  29. const MOBILE_SIDEBAR_WIDTH = "4.5rem";
  30. const MOBILE_HEADER_HEIGHT = "3.95rem";
  31. const DESKTOP_SIDEBAR_WIDTH = "15rem";
  32. const DESKTOP_HEADER_HEIGHT = "4.688rem";
  33. const SidebarHeader = (props) => {
  34. const { serverSession, size, deviceType } = props;
  35. const isSmall = deviceType === "mobile" || size === "small";
  36. return (
  37. <Box
  38. align="center"
  39. gap="small"
  40. direction="row"
  41. margin={{ bottom: "xxsmall" }}
  42. justify="center"
  43. >
  44. <Avatar src={serverSession.user.image} />
  45. {isSmall ? null : <Text>{serverSession.user.name}</Text>}
  46. </Box>
  47. );
  48. };
  49. const SidebarButton = ({ icon, label, selected, ...rest }) => (
  50. <Button
  51. gap="medium"
  52. justify="start"
  53. fill
  54. icon={cloneElement(icon, {
  55. color: selected ? "white" : undefined,
  56. })}
  57. label={label}
  58. plain
  59. {...rest}
  60. style={{
  61. ...rest.style,
  62. whiteSpace: "nowrap",
  63. height: "3rem",
  64. paddingLeft: "3rem",
  65. flex: "unset",
  66. background: selected ? BRAND_HEX : "transparent",
  67. color: selected ? "white" : "unset",
  68. }}
  69. />
  70. );
  71. const SidebarFooter = (props) => {
  72. const { size, deviceType, t } = props;
  73. const { pathname: rawPathname, push, locale } = useRouter();
  74. const account = `/${locale}/account`;
  75. const callback = `/${locale}`;
  76. const pathname = `/${locale}${rawPathname}`;
  77. if (deviceType === "mobile" || size === "small") {
  78. return (
  79. <Nav gap="small">
  80. <Button
  81. icon={<UserSettings />}
  82. hoverIndicator={pathname !== account}
  83. primary={pathname === account}
  84. onClick={() => push(account)}
  85. />
  86. <Button
  87. icon={<Logout />}
  88. hoverIndicator
  89. onClick={async () => {
  90. const data = await signOut({
  91. redirect: false,
  92. callbackUrl: callback,
  93. });
  94. push(data.url);
  95. }}
  96. />
  97. </Nav>
  98. );
  99. }
  100. return (
  101. <Nav>
  102. <SidebarButton
  103. icon={<UserSettings />}
  104. label={t("my-account")}
  105. selected={pathname === account}
  106. onClick={() => push(account)}
  107. />
  108. <SidebarButton
  109. icon={<Logout />}
  110. label={t("logout")}
  111. onClick={async () => {
  112. const data = await signOut({
  113. redirect: false,
  114. callbackUrl: callback,
  115. });
  116. push(data.url);
  117. }}
  118. />
  119. </Nav>
  120. );
  121. };
  122. /**
  123. * Navigation items are organized by
  124. * usage order (data from G.A.)
  125. */
  126. const MainNavigation = (props) => {
  127. const { size, serverSession, deviceType, t } = props;
  128. const { pathname: rawPathname, push, locale } = useRouter();
  129. const dreams = `/${locale}/dreams`;
  130. const myDreams = `/${locale}/my-dreams`;
  131. const insights = `/${locale}/insights`;
  132. const inbox = `/${locale}/inbox`;
  133. const savedDreams = `/${locale}/saved-dreams`;
  134. const pathname = `/${locale}${rawPathname}`;
  135. if (deviceType === "mobile" || size === "small") {
  136. return (
  137. <Nav gap="small">
  138. <Button
  139. icon={<Magic />}
  140. hoverIndicator={pathname !== dreams}
  141. primary={pathname === dreams}
  142. onClick={() => push(dreams)}
  143. />
  144. <Button
  145. icon={<Book />}
  146. hoverIndicator={pathname !== myDreams}
  147. primary={pathname === myDreams}
  148. onClick={() => push(myDreams)}
  149. />
  150. <Button
  151. icon={<BarChart />}
  152. hoverIndicator={pathname !== insights}
  153. primary={pathname === insights}
  154. onClick={() => push(insights)}
  155. />
  156. <Button
  157. icon={<Inbox />}
  158. hoverIndicator={pathname !== inbox}
  159. primary={pathname === inbox}
  160. onClick={() => push(inbox)}
  161. badge={
  162. serverSession?.inboxCount
  163. ? {
  164. value: serverSession?.inboxCount,
  165. background: {
  166. color: pathname === inbox ? "#6FFFB0" : BRAND_HEX,
  167. },
  168. }
  169. : 0
  170. }
  171. />
  172. <Button
  173. icon={<Star />}
  174. hoverIndicator={pathname !== savedDreams}
  175. primary={pathname === savedDreams}
  176. onClick={() => push(savedDreams)}
  177. />
  178. </Nav>
  179. );
  180. }
  181. return (
  182. <Nav gap="medium" fill="vertical" responsive={false}>
  183. <SidebarButton
  184. icon={<Magic />}
  185. label={t("discover")}
  186. selected={pathname === dreams}
  187. onClick={() => push(dreams)}
  188. />
  189. <SidebarButton
  190. icon={<Book />}
  191. label={t("my-dreams")}
  192. selected={pathname === myDreams}
  193. onClick={() => push(myDreams)}
  194. />
  195. <SidebarButton
  196. icon={<BarChart />}
  197. label={t("insights")}
  198. selected={pathname === insights}
  199. onClick={() => push(insights)}
  200. />
  201. <SidebarButton
  202. icon={
  203. <Button
  204. as="span"
  205. plain
  206. icon={<Inbox color={pathname === inbox ? "white" : undefined} />}
  207. badge={
  208. serverSession?.inboxCount
  209. ? {
  210. value: serverSession?.inboxCount,
  211. background: {
  212. color: pathname === inbox ? "#6FFFB0" : BRAND_HEX,
  213. },
  214. }
  215. : 0
  216. }
  217. />
  218. }
  219. label={t("inbox")}
  220. selected={pathname === inbox}
  221. onClick={() => push(inbox)}
  222. />
  223. <SidebarButton
  224. icon={<Star />}
  225. label={t("saved")}
  226. selected={pathname === savedDreams}
  227. onClick={() => push(savedDreams)}
  228. />
  229. </Nav>
  230. );
  231. };
  232. function MobileSidebar(props) {
  233. const { serverSession, size, t } = props;
  234. return (
  235. <SidebarBase
  236. elevation="large"
  237. responsive={false}
  238. background="light-1"
  239. header={
  240. <SidebarHeader
  241. serverSession={serverSession}
  242. size={size}
  243. deviceType={"mobile"}
  244. t={t}
  245. />
  246. }
  247. footer={<SidebarFooter size={size} deviceType="mobile" t={t} />}
  248. style={{
  249. top: MOBILE_HEADER_HEIGHT,
  250. height: `calc(100vh - ${MOBILE_HEADER_HEIGHT})`,
  251. minHeight: `calc(100vh - ${MOBILE_HEADER_HEIGHT})`,
  252. position: "fixed",
  253. minWidth: MOBILE_SIDEBAR_WIDTH,
  254. maxWidth: MOBILE_SIDEBAR_WIDTH,
  255. borderRight: `1px solid ${BRAND_HEX}`,
  256. // Trick to make the box-shadow from the sidebar and header look good
  257. zIndex: "11",
  258. }}
  259. >
  260. <MainNavigation
  261. size={size}
  262. serverSession={serverSession}
  263. deviceType={"mobile"}
  264. t={t}
  265. />
  266. </SidebarBase>
  267. );
  268. }
  269. function DesktopSidebar(props) {
  270. const { serverSession, size, t } = props;
  271. return (
  272. <SidebarBase
  273. responsive={false}
  274. elevation="large"
  275. header={
  276. <SidebarHeader
  277. serverSession={serverSession}
  278. size={size}
  279. deviceType={"desktop"}
  280. t={t}
  281. />
  282. }
  283. footer={<SidebarFooter deviceType="desktop" t={t} />}
  284. pad={{ left: "unset", right: "unset", vertical: "large" }}
  285. background="light-1"
  286. style={{
  287. position: "fixed",
  288. top: DESKTOP_HEADER_HEIGHT,
  289. height: `calc(100vh - ${DESKTOP_HEADER_HEIGHT})`,
  290. minHeight: `calc(100vh - ${DESKTOP_HEADER_HEIGHT})`,
  291. minWidth: DESKTOP_SIDEBAR_WIDTH,
  292. maxWidth: DESKTOP_SIDEBAR_WIDTH,
  293. borderRight: `1px solid ${BRAND_HEX}`,
  294. // Trick to make the box-shadow from the sidebar and header look good
  295. zIndex: "11",
  296. }}
  297. >
  298. <MainNavigation size={size} serverSession={serverSession} t={t} />
  299. </SidebarBase>
  300. );
  301. }
  302. function Sidebar(props) {
  303. const { serverSession, size, deviceType, t } = props;
  304. if (deviceType === "mobile" || size === "small") {
  305. return (
  306. <MobileSidebar
  307. serverSession={serverSession}
  308. size={size}
  309. deviceType={deviceType}
  310. t={t}
  311. />
  312. );
  313. }
  314. return (
  315. <DesktopSidebar
  316. serverSession={serverSession}
  317. size={size}
  318. deviceType={deviceType}
  319. t={t}
  320. />
  321. );
  322. }
  323. export default function Dashboard(props) {
  324. const { serverSession, children, deviceType } = props;
  325. const size = useContext(ResponsiveContext);
  326. const { t } = useTranslation("dashboard");
  327. const isSmall = deviceType === "mobile" || size === "small";
  328. return (
  329. <>
  330. <Header
  331. pad="small"
  332. style={{
  333. position: "fixed",
  334. width: "100vw",
  335. borderBottom: `1px solid ${BRAND_HEX}`,
  336. zIndex: "10",
  337. }}
  338. background="light-1"
  339. elevation="large"
  340. >
  341. <Box
  342. style={{
  343. display: "flex",
  344. width: "100%",
  345. flexDirection: "row",
  346. justifyContent: "space-between",
  347. margin: "auto",
  348. maxWidth: "96rem",
  349. // Trick to make the box-shadow from the sidebar and header look good
  350. zIndex: "9",
  351. }}
  352. >
  353. <Logo noTitle />
  354. <PageActions serverSession={serverSession} />
  355. </Box>
  356. </Header>
  357. <Page background="background-front" kind="full">
  358. <Box direction="row" height={{ min: "100%" }}>
  359. <Sidebar
  360. serverSession={serverSession}
  361. size={size}
  362. deviceType={deviceType}
  363. t={t}
  364. />
  365. <PageContent
  366. style={{
  367. width: isSmall
  368. ? `calc(100vw - ${MOBILE_SIDEBAR_WIDTH})`
  369. : `calc(100vw - ${DESKTOP_SIDEBAR_WIDTH})`,
  370. minHeight: isSmall
  371. ? `calc(100vh - ${MOBILE_HEADER_HEIGHT})`
  372. : `calc(100vh - ${DESKTOP_HEADER_HEIGHT})`,
  373. minWidth: "0px",
  374. marginTop: isSmall ? MOBILE_HEADER_HEIGHT : DESKTOP_HEADER_HEIGHT,
  375. marginLeft: isSmall
  376. ? MOBILE_SIDEBAR_WIDTH
  377. : DESKTOP_SIDEBAR_WIDTH,
  378. }}
  379. >
  380. {children}
  381. </PageContent>
  382. </Box>
  383. </Page>
  384. </>
  385. );
  386. }