From 140734b32ceaf6f6fd4a553d522d74dd99d0e096 Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Tue, 29 Mar 2022 20:50:01 +0200 Subject: [PATCH] feat: add primitive profile settings --- .../components/AccountDropdownMenu/index.tsx | 14 +++- packages/web/src/components/Drawer/index.tsx | 82 ++++++++++--------- packages/web/src/components/Drawer/style.ts | 13 ++- packages/web/src/components/Form/index.tsx | 2 +- packages/web/src/components/Layout/index.tsx | 33 +++++++- .../web/src/components/ListItemLink/index.tsx | 2 +- .../src/components/SettingsLayout/index.tsx | 62 ++++++++++++++ packages/web/src/config/urls.ts | 5 ++ packages/web/src/locales/en.json | 11 ++- packages/web/src/pages/Execution/index.tsx | 17 ++-- .../web/src/pages/ProfileSettings/index.tsx | 77 +++++++++++++++++ packages/web/src/routes.tsx | 5 ++ packages/web/src/settingsRoutes.tsx | 13 +++ 13 files changed, 279 insertions(+), 57 deletions(-) create mode 100644 packages/web/src/components/SettingsLayout/index.tsx create mode 100644 packages/web/src/pages/ProfileSettings/index.tsx create mode 100644 packages/web/src/settingsRoutes.tsx diff --git a/packages/web/src/components/AccountDropdownMenu/index.tsx b/packages/web/src/components/AccountDropdownMenu/index.tsx index dd88fa6e..449eab80 100644 --- a/packages/web/src/components/AccountDropdownMenu/index.tsx +++ b/packages/web/src/components/AccountDropdownMenu/index.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { useNavigate } from 'react-router-dom'; import MenuItem from '@mui/material/MenuItem'; import Menu, { MenuProps } from '@mui/material/Menu'; +import { Link } from 'react-router-dom'; import * as URLS from 'config/urls'; import useAuthentication from 'hooks/useAuthentication'; @@ -49,7 +50,18 @@ function AccountDropdownMenu(props: AccountDropdownMenuProps): React.ReactElemen open={open} onClose={onClose} > - {formatMessage('accountDropdownMenu.logout')} + + {formatMessage('accountDropdownMenu.settings')} + + + + {formatMessage('accountDropdownMenu.logout')} + ); } diff --git a/packages/web/src/components/Drawer/index.tsx b/packages/web/src/components/Drawer/index.tsx index 282e44d5..2e6daf5b 100644 --- a/packages/web/src/components/Drawer/index.tsx +++ b/packages/web/src/components/Drawer/index.tsx @@ -4,21 +4,28 @@ import { SwipeableDrawerProps } from '@mui/material/SwipeableDrawer'; import Toolbar from '@mui/material/Toolbar'; import List from '@mui/material/List'; import Divider from '@mui/material/Divider'; -import AppsIcon from '@mui/icons-material/Apps'; -import SwapCallsIcon from '@mui/icons-material/SwapCalls'; -import HistoryIcon from '@mui/icons-material/History'; -import ExploreIcon from '@mui/icons-material/Explore'; import useMediaQuery from '@mui/material/useMediaQuery'; import ListItemLink from 'components/ListItemLink'; import HideOnScroll from 'components/HideOnScroll'; import useFormatMessage from 'hooks/useFormatMessage'; -import * as URLS from 'config/urls'; import { Drawer as BaseDrawer } from './style'; const iOS = typeof navigator !== 'undefined' && /iPad|iPhone|iPod/.test(navigator.userAgent); -export default function Drawer(props: SwipeableDrawerProps): React.ReactElement { +type DrawerLink = { + Icon: React.ElementType; + primary: string; + to: string; +}; + +type DrawerProps = { + links: DrawerLink[]; + bottomLinks?: DrawerLink[]; +} & SwipeableDrawerProps; + +export default function Drawer(props: DrawerProps): React.ReactElement { + const { links = [], bottomLinks = [], ...drawerProps } = props; const theme = useTheme(); const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md'), { noSsr: true }); const formatMessage = useFormatMessage(); @@ -31,46 +38,43 @@ export default function Drawer(props: SwipeableDrawerProps): React.ReactElement return ( - - - + {/* keep the following encapsulating `div` to have `space-between` children */} +
+ + + + + + {links.map(({ Icon, primary, to }, index) => ( + } + primary={formatMessage(primary)} + to={to} + onClick={closeOnClick} + /> + ))} + + + +
- } - primary={formatMessage('drawer.flows')} - to={URLS.FLOWS} - onClick={closeOnClick} - /> - - } - primary={formatMessage('drawer.apps')} - to={URLS.APPS} - onClick={closeOnClick} - /> - - } - primary={formatMessage('drawer.executions')} - to={URLS.EXECUTIONS} - onClick={closeOnClick} - /> - - } - primary={formatMessage('drawer.explore')} - to={URLS.EXPLORE} - onClick={closeOnClick} - /> + {bottomLinks.map(({ Icon, primary, to }, index) => ( + } + primary={formatMessage(primary)} + to={to} + onClick={closeOnClick} + /> + ))} - -
); } diff --git a/packages/web/src/components/Drawer/style.ts b/packages/web/src/components/Drawer/style.ts index 43c87e12..bdc5bd1f 100644 --- a/packages/web/src/components/Drawer/style.ts +++ b/packages/web/src/components/Drawer/style.ts @@ -1,4 +1,5 @@ import { styled, Theme, CSSObject } from '@mui/material/styles'; +import { drawerClasses } from '@mui/material/Drawer'; import MuiSwipeableDrawer from '@mui/material/SwipeableDrawer'; const drawerWidth = 300; @@ -35,11 +36,19 @@ export const Drawer = styled(MuiSwipeableDrawer)( boxSizing: 'border-box', ...(open && { ...openedMixin(theme), - '& .MuiDrawer-paper': openedMixin(theme), + [`& .${drawerClasses.paper}`]: { + ...openedMixin(theme), + display: 'flex', + justifyContent: 'space-between' + }, }), ...(!open && { ...closedMixin(theme), - '& .MuiDrawer-paper': closedMixin(theme), + [`& .${drawerClasses.paper}`]: { + ...closedMixin(theme), + display: 'flex', + justifyContent: 'space-between' + }, }), }), ); diff --git a/packages/web/src/components/Form/index.tsx b/packages/web/src/components/Form/index.tsx index 32691591..2c0a7d5a 100644 --- a/packages/web/src/components/Form/index.tsx +++ b/packages/web/src/components/Form/index.tsx @@ -7,7 +7,7 @@ type FormProps = { defaultValues?: UseFormProps['defaultValues']; onSubmit?: SubmitHandler; render?: (props: UseFormReturn) => React.ReactNode; -} +}; const noop = () => null; diff --git a/packages/web/src/components/Layout/index.tsx b/packages/web/src/components/Layout/index.tsx index 4962f1ae..67404800 100644 --- a/packages/web/src/components/Layout/index.tsx +++ b/packages/web/src/components/Layout/index.tsx @@ -1,16 +1,44 @@ import * as React from 'react'; +import Box from '@mui/material/Box'; +import Toolbar from '@mui/material/Toolbar'; import { useTheme } from '@mui/material/styles'; import useMediaQuery from '@mui/material/useMediaQuery'; +import AppsIcon from '@mui/icons-material/Apps'; +import SwapCallsIcon from '@mui/icons-material/SwapCalls'; +import HistoryIcon from '@mui/icons-material/History'; +import ExploreIcon from '@mui/icons-material/Explore'; -import Box from '@mui/material/Box'; +import * as URLS from 'config/urls'; import AppBar from 'components/AppBar'; import Drawer from 'components/Drawer'; -import Toolbar from '@mui/material/Toolbar'; type PublicLayoutProps = { children: React.ReactNode; } +const drawerLinks = [ + { + Icon: SwapCallsIcon, + primary: 'drawer.flows', + to: URLS.FLOWS, + }, + { + Icon: AppsIcon, + primary: 'drawer.apps', + to: URLS.APPS, + }, + { + Icon: HistoryIcon, + primary: 'drawer.executions', + to: URLS.EXECUTIONS, + }, + { + Icon: ExploreIcon, + primary: 'drawer.explore', + to: URLS.EXPLORE, + }, +]; + export default function PublicLayout({ children }: PublicLayoutProps): React.ReactElement { const theme = useTheme(); const matchSmallScreens = useMediaQuery(theme.breakpoints.down('lg'), { noSsr: true }); @@ -25,6 +53,7 @@ export default function PublicLayout({ children }: PublicLayoutProps): React.Rea diff --git a/packages/web/src/components/SettingsLayout/index.tsx b/packages/web/src/components/SettingsLayout/index.tsx new file mode 100644 index 00000000..e1f0cd68 --- /dev/null +++ b/packages/web/src/components/SettingsLayout/index.tsx @@ -0,0 +1,62 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import Toolbar from '@mui/material/Toolbar'; +import { useTheme } from '@mui/material/styles'; +import useMediaQuery from '@mui/material/useMediaQuery'; +import AccountCircleIcon from '@mui/icons-material/AccountCircle'; +import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew'; + +import * as URLS from 'config/urls'; +import AppBar from 'components/AppBar'; +import Drawer from 'components/Drawer'; + +type SettingsLayoutProps = { + children: React.ReactNode; +} + +const drawerLinks = [ + { + Icon: AccountCircleIcon, + primary: 'settingsDrawer.myProfile', + to: URLS.SETTINGS_PROFILE, + }, +]; + +const drawerBottomLinks = [ + { + Icon: ArrowBackIosNewIcon, + primary: 'settingsDrawer.goBack', + to: '/', + } +]; + +export default function SettingsLayout({ children }: SettingsLayoutProps): React.ReactElement { + const theme = useTheme(); + const matchSmallScreens = useMediaQuery(theme.breakpoints.down('lg'), { noSsr: true }); + const [isDrawerOpen, setDrawerOpen] = React.useState(!matchSmallScreens); + + const openDrawer = () => setDrawerOpen(true); + const closeDrawer = () => setDrawerOpen(false); + + return ( + <> + + + + + + + + + {children} + + + + ); +} diff --git a/packages/web/src/config/urls.ts b/packages/web/src/config/urls.ts index 4f315e8f..89c0e63b 100644 --- a/packages/web/src/config/urls.ts +++ b/packages/web/src/config/urls.ts @@ -29,5 +29,10 @@ export const FLOWS = '/flows'; export const FLOW = (flowId: string): string => `/editor/${flowId}`; export const FLOW_PATTERN = '/flows/:flowId'; +export const SETTINGS = '/settings'; +export const SETTINGS_DASHBOARD = SETTINGS; +export const SETTINGS_PROFILE = '/settings/profile'; +export const PROFILE = 'profile'; + export const DASHBOARD = FLOWS; diff --git a/packages/web/src/locales/en.json b/packages/web/src/locales/en.json index 3d23f718..0a1dc4f7 100644 --- a/packages/web/src/locales/en.json +++ b/packages/web/src/locales/en.json @@ -1,12 +1,15 @@ { "brandText": "Automatisch", "searchPlaceholder": "Search", + "accountDropdownMenu.settings": "Settings", "accountDropdownMenu.logout": "Logout", "drawer.dashboard": "Dashboard", "drawer.flows": "Flows", "drawer.apps": "My Apps", "drawer.executions": "Executions", "drawer.explore": "Explore", + "settingsDrawer.myProfile": "My Profile", + "settingsDrawer.goBack": "Go to the dashboard", "app.connectionCount": "{count} connections", "app.flowCount": "{count} flows", "app.addConnection": "Add connection", @@ -38,5 +41,11 @@ "flowStep.actionType": "Action", "flows.create": "Create flow", "flows.title": "Flows", - "executions.title": "Executions" + "executions.title": "Executions", + "profileSettings.title": "My Profile", + "profileSettings.email": "E-mail", + "profileSettings.updateEmail": "Update e-mail", + "profileSettings.newPassword": "New password", + "profileSettings.confirmNewPassword": "Confirm new password", + "profileSettings.updatePassword": "Update password" } diff --git a/packages/web/src/pages/Execution/index.tsx b/packages/web/src/pages/Execution/index.tsx index 94a2c699..0c4ffcf0 100644 --- a/packages/web/src/pages/Execution/index.tsx +++ b/packages/web/src/pages/Execution/index.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; import { useParams } from 'react-router-dom'; import { useQuery } from '@apollo/client'; -import Box from '@mui/material/Box'; import Grid from '@mui/material/Grid'; import type { IExecutionStep } from '@automatisch/types'; @@ -39,14 +38,12 @@ export default function Execution(): React.ReactElement { }, [executionId, fetchMore, pageInfo]); return ( - - - - {executionSteps?.map((executionStep) => ( - - ))} - - - + + + {executionSteps?.map((executionStep) => ( + + ))} + + ); }; diff --git a/packages/web/src/pages/ProfileSettings/index.tsx b/packages/web/src/pages/ProfileSettings/index.tsx new file mode 100644 index 00000000..0ae0bff9 --- /dev/null +++ b/packages/web/src/pages/ProfileSettings/index.tsx @@ -0,0 +1,77 @@ +import * as React from 'react'; +import { styled } from '@mui/material/styles'; +import Grid from '@mui/material/Grid'; +import Button from '@mui/material/Button'; + +import PageTitle from 'components/PageTitle'; +import Container from 'components/Container'; +import Form from 'components/Form'; +import TextField from 'components/TextField'; +import useFormatMessage from 'hooks/useFormatMessage'; + +const StyledForm = styled(Form)` + display: flex; + align-items: end; + flex-direction: column; +`; + +function ProfileSettings() { + const formatMessage = useFormatMessage(); + + return ( + + + + + {formatMessage('profileSettings.title')} + + + + + + + + + + + + + + + + + + + + + ); +} + +export default ProfileSettings; diff --git a/packages/web/src/routes.tsx b/packages/web/src/routes.tsx index 188c1e79..2154662e 100644 --- a/packages/web/src/routes.tsx +++ b/packages/web/src/routes.tsx @@ -11,6 +11,7 @@ import Explore from 'pages/Explore'; import Login from 'pages/Login'; import EditorRoutes from 'pages/Editor/routes'; import * as URLS from 'config/urls'; +import settingsRoutes from './settingsRoutes'; export default ( @@ -34,6 +35,10 @@ export default ( } /> + + {settingsRoutes} + +
404
} />
); diff --git a/packages/web/src/settingsRoutes.tsx b/packages/web/src/settingsRoutes.tsx new file mode 100644 index 00000000..aba00d2d --- /dev/null +++ b/packages/web/src/settingsRoutes.tsx @@ -0,0 +1,13 @@ +import { Route, Navigate } from 'react-router-dom'; +import SettingsLayout from 'components/SettingsLayout'; +import ProfileSettings from 'pages/ProfileSettings'; + +import * as URLS from 'config/urls'; + +export default ( + <> + } /> + + } /> + +);