feat: add primitive profile settings

This commit is contained in:
Ali BARIN
2022-03-29 20:50:01 +02:00
committed by Ömer Faruk Aydın
parent 68e5e6d011
commit 140734b32c
13 changed files with 279 additions and 57 deletions

View File

@@ -2,6 +2,7 @@ import * as React from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import MenuItem from '@mui/material/MenuItem'; import MenuItem from '@mui/material/MenuItem';
import Menu, { MenuProps } from '@mui/material/Menu'; import Menu, { MenuProps } from '@mui/material/Menu';
import { Link } from 'react-router-dom';
import * as URLS from 'config/urls'; import * as URLS from 'config/urls';
import useAuthentication from 'hooks/useAuthentication'; import useAuthentication from 'hooks/useAuthentication';
@@ -49,7 +50,18 @@ function AccountDropdownMenu(props: AccountDropdownMenuProps): React.ReactElemen
open={open} open={open}
onClose={onClose} onClose={onClose}
> >
<MenuItem onClick={logout}>{formatMessage('accountDropdownMenu.logout')}</MenuItem> <MenuItem
component={Link}
to={URLS.SETTINGS_DASHBOARD}
>
{formatMessage('accountDropdownMenu.settings')}
</MenuItem>
<MenuItem
onClick={logout}
>
{formatMessage('accountDropdownMenu.logout')}
</MenuItem>
</Menu> </Menu>
); );
} }

View File

@@ -4,21 +4,28 @@ import { SwipeableDrawerProps } from '@mui/material/SwipeableDrawer';
import Toolbar from '@mui/material/Toolbar'; import Toolbar from '@mui/material/Toolbar';
import List from '@mui/material/List'; import List from '@mui/material/List';
import Divider from '@mui/material/Divider'; 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 useMediaQuery from '@mui/material/useMediaQuery';
import ListItemLink from 'components/ListItemLink'; import ListItemLink from 'components/ListItemLink';
import HideOnScroll from 'components/HideOnScroll'; import HideOnScroll from 'components/HideOnScroll';
import useFormatMessage from 'hooks/useFormatMessage'; import useFormatMessage from 'hooks/useFormatMessage';
import * as URLS from 'config/urls';
import { Drawer as BaseDrawer } from './style'; import { Drawer as BaseDrawer } from './style';
const iOS = typeof navigator !== 'undefined' && /iPad|iPhone|iPod/.test(navigator.userAgent); 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 theme = useTheme();
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md'), { noSsr: true }); const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md'), { noSsr: true });
const formatMessage = useFormatMessage(); const formatMessage = useFormatMessage();
@@ -31,46 +38,43 @@ export default function Drawer(props: SwipeableDrawerProps): React.ReactElement
return ( return (
<BaseDrawer <BaseDrawer
{...props} {...drawerProps}
disableBackdropTransition={!iOS} disableBackdropTransition={!iOS}
disableDiscovery={iOS} disableDiscovery={iOS}
variant={matchSmallScreens ? 'temporary' : 'permanent'} variant={matchSmallScreens ? 'temporary' : 'permanent'}
> >
{/* keep the following encapsulating `div` to have `space-between` children */}
<div>
<HideOnScroll unmountOnExit> <HideOnScroll unmountOnExit>
<Toolbar /> <Toolbar />
</HideOnScroll> </HideOnScroll>
<List sx={{ py: 0, mt: 3 }}> <List sx={{ py: 0, mt: 3 }}>
{links.map(({ Icon, primary, to }, index) => (
<ListItemLink <ListItemLink
icon={<SwapCallsIcon htmlColor={theme.palette.primary.main} />} key={`${to}-${index}`}
primary={formatMessage('drawer.flows')} icon={<Icon htmlColor={theme.palette.primary.main} />}
to={URLS.FLOWS} primary={formatMessage(primary)}
onClick={closeOnClick} to={to}
/>
<ListItemLink
icon={<AppsIcon htmlColor={theme.palette.primary.main} />}
primary={formatMessage('drawer.apps')}
to={URLS.APPS}
onClick={closeOnClick}
/>
<ListItemLink
icon={<HistoryIcon htmlColor={theme.palette.primary.main} />}
primary={formatMessage('drawer.executions')}
to={URLS.EXECUTIONS}
onClick={closeOnClick}
/>
<ListItemLink
icon={<ExploreIcon htmlColor={theme.palette.primary.main} />}
primary={formatMessage('drawer.explore')}
to={URLS.EXPLORE}
onClick={closeOnClick} onClick={closeOnClick}
/> />
))}
</List> </List>
<Divider /> <Divider />
</div>
<List sx={{ py: 0, mt: 3 }}>
{bottomLinks.map(({ Icon, primary, to }, index) => (
<ListItemLink
key={`${to}-${index}`}
icon={<Icon htmlColor={theme.palette.primary.main} />}
primary={formatMessage(primary)}
to={to}
onClick={closeOnClick}
/>
))}
</List>
</BaseDrawer> </BaseDrawer>
); );
} }

View File

@@ -1,4 +1,5 @@
import { styled, Theme, CSSObject } from '@mui/material/styles'; import { styled, Theme, CSSObject } from '@mui/material/styles';
import { drawerClasses } from '@mui/material/Drawer';
import MuiSwipeableDrawer from '@mui/material/SwipeableDrawer'; import MuiSwipeableDrawer from '@mui/material/SwipeableDrawer';
const drawerWidth = 300; const drawerWidth = 300;
@@ -35,11 +36,19 @@ export const Drawer = styled(MuiSwipeableDrawer)(
boxSizing: 'border-box', boxSizing: 'border-box',
...(open && { ...(open && {
...openedMixin(theme), ...openedMixin(theme),
'& .MuiDrawer-paper': openedMixin(theme), [`& .${drawerClasses.paper}`]: {
...openedMixin(theme),
display: 'flex',
justifyContent: 'space-between'
},
}), }),
...(!open && { ...(!open && {
...closedMixin(theme), ...closedMixin(theme),
'& .MuiDrawer-paper': closedMixin(theme), [`& .${drawerClasses.paper}`]: {
...closedMixin(theme),
display: 'flex',
justifyContent: 'space-between'
},
}), }),
}), }),
); );

View File

@@ -7,7 +7,7 @@ type FormProps = {
defaultValues?: UseFormProps['defaultValues']; defaultValues?: UseFormProps['defaultValues'];
onSubmit?: SubmitHandler<FieldValues>; onSubmit?: SubmitHandler<FieldValues>;
render?: (props: UseFormReturn) => React.ReactNode; render?: (props: UseFormReturn) => React.ReactNode;
} };
const noop = () => null; const noop = () => null;

View File

@@ -1,16 +1,44 @@
import * as React from 'react'; import * as React from 'react';
import Box from '@mui/material/Box';
import Toolbar from '@mui/material/Toolbar';
import { useTheme } from '@mui/material/styles'; import { useTheme } from '@mui/material/styles';
import useMediaQuery from '@mui/material/useMediaQuery'; 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 AppBar from 'components/AppBar';
import Drawer from 'components/Drawer'; import Drawer from 'components/Drawer';
import Toolbar from '@mui/material/Toolbar';
type PublicLayoutProps = { type PublicLayoutProps = {
children: React.ReactNode; 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 { export default function PublicLayout({ children }: PublicLayoutProps): React.ReactElement {
const theme = useTheme(); const theme = useTheme();
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('lg'), { noSsr: true }); const matchSmallScreens = useMediaQuery(theme.breakpoints.down('lg'), { noSsr: true });
@@ -25,6 +53,7 @@ export default function PublicLayout({ children }: PublicLayoutProps): React.Rea
<Box sx={{ display: 'flex', }}> <Box sx={{ display: 'flex', }}>
<Drawer <Drawer
links={drawerLinks}
open={isDrawerOpen} open={isDrawerOpen}
onOpen={openDrawer} onOpen={openDrawer}
onClose={closeDrawer} onClose={closeDrawer}

View File

@@ -14,7 +14,7 @@ type ListItemLinkProps = {
export default function ListItemLink(props: ListItemLinkProps): React.ReactElement { export default function ListItemLink(props: ListItemLinkProps): React.ReactElement {
const { icon, primary, to, onClick } = props; const { icon, primary, to, onClick } = props;
const selected = useMatch({ path: to, end: false }); const selected = useMatch({ path: to, end: true });
const CustomLink = React.useMemo( const CustomLink = React.useMemo(
() => () =>

View File

@@ -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 (
<>
<AppBar drawerOpen={isDrawerOpen} onDrawerOpen={openDrawer} onDrawerClose={closeDrawer} />
<Box sx={{ display: 'flex', }}>
<Drawer
links={drawerLinks}
bottomLinks={drawerBottomLinks}
open={isDrawerOpen}
onOpen={openDrawer}
onClose={closeDrawer}
/>
<Box sx={{ flex: 1, }}>
<Toolbar />
{children}
</Box>
</Box>
</>
);
}

View File

@@ -29,5 +29,10 @@ export const FLOWS = '/flows';
export const FLOW = (flowId: string): string => `/editor/${flowId}`; export const FLOW = (flowId: string): string => `/editor/${flowId}`;
export const FLOW_PATTERN = '/flows/: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; export const DASHBOARD = FLOWS;

View File

@@ -1,12 +1,15 @@
{ {
"brandText": "Automatisch", "brandText": "Automatisch",
"searchPlaceholder": "Search", "searchPlaceholder": "Search",
"accountDropdownMenu.settings": "Settings",
"accountDropdownMenu.logout": "Logout", "accountDropdownMenu.logout": "Logout",
"drawer.dashboard": "Dashboard", "drawer.dashboard": "Dashboard",
"drawer.flows": "Flows", "drawer.flows": "Flows",
"drawer.apps": "My Apps", "drawer.apps": "My Apps",
"drawer.executions": "Executions", "drawer.executions": "Executions",
"drawer.explore": "Explore", "drawer.explore": "Explore",
"settingsDrawer.myProfile": "My Profile",
"settingsDrawer.goBack": "Go to the dashboard",
"app.connectionCount": "{count} connections", "app.connectionCount": "{count} connections",
"app.flowCount": "{count} flows", "app.flowCount": "{count} flows",
"app.addConnection": "Add connection", "app.addConnection": "Add connection",
@@ -38,5 +41,11 @@
"flowStep.actionType": "Action", "flowStep.actionType": "Action",
"flows.create": "Create flow", "flows.create": "Create flow",
"flows.title": "Flows", "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"
} }

View File

@@ -1,7 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { useQuery } from '@apollo/client'; import { useQuery } from '@apollo/client';
import Box from '@mui/material/Box';
import Grid from '@mui/material/Grid'; import Grid from '@mui/material/Grid';
import type { IExecutionStep } from '@automatisch/types'; import type { IExecutionStep } from '@automatisch/types';
@@ -39,14 +38,12 @@ export default function Execution(): React.ReactElement {
}, [executionId, fetchMore, pageInfo]); }, [executionId, fetchMore, pageInfo]);
return ( return (
<Box sx={{ py: 3 }}> <Container sx={{ py: 3 }}>
<Container> <Grid container item sx={{ mb: [2, 5] }} rowGap={3}>
<Grid container item sx={{ mb: [2, 5] }} columnSpacing={1.5} rowGap={3}>
{executionSteps?.map((executionStep) => ( {executionSteps?.map((executionStep) => (
<ExecutionStep key={executionStep.id} executionStep={executionStep} step={executionStep.step} /> <ExecutionStep key={executionStep.id} executionStep={executionStep} step={executionStep.step} />
))} ))}
</Grid> </Grid>
</Container> </Container>
</Box>
); );
}; };

View File

@@ -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 (
<Container sx={{ py: 3, display: 'flex', justifyContent: 'center' }}>
<Grid container item xs={12} sm={9} md={6}>
<Grid item xs={12} sx={{ mb: [2, 5] }} >
<PageTitle>
{formatMessage('profileSettings.title')}
</PageTitle>
</Grid>
<Grid item xs={12} justifyContent="flex-end">
<StyledForm>
<TextField
fullWidth
name="email"
label={formatMessage('profileSettings.email')}
margin="normal"
/>
<Button
variant="contained"
sx={{ mt: 2 }}
type="submit"
>
{formatMessage('profileSettings.updateEmail')}
</Button>
</StyledForm>
<StyledForm>
<TextField
fullWidth
name="password"
label={formatMessage('profileSettings.newPassword')}
margin="normal"
/>
<TextField
fullWidth
name="confirmPassword"
label={formatMessage('profileSettings.confirmNewPassword')}
margin="normal"
/>
<Button
variant="contained"
sx={{ mt: 2 }}
type="submit"
>
{formatMessage('profileSettings.updatePassword')}
</Button>
</StyledForm>
</Grid>
</Grid>
</Container>
);
}
export default ProfileSettings;

View File

@@ -11,6 +11,7 @@ import Explore from 'pages/Explore';
import Login from 'pages/Login'; import Login from 'pages/Login';
import EditorRoutes from 'pages/Editor/routes'; import EditorRoutes from 'pages/Editor/routes';
import * as URLS from 'config/urls'; import * as URLS from 'config/urls';
import settingsRoutes from './settingsRoutes';
export default ( export default (
<Routes> <Routes>
@@ -34,6 +35,10 @@ export default (
<Route path="/" element={<Navigate to={URLS.FLOWS} />} /> <Route path="/" element={<Navigate to={URLS.FLOWS} />} />
<Route path={`${URLS.SETTINGS}`}>
{settingsRoutes}
</Route>
<Route element={<Layout><div>404</div></Layout>} /> <Route element={<Layout><div>404</div></Layout>} />
</Routes> </Routes>
); );

View File

@@ -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 (
<>
<Route path={URLS.SETTINGS_PROFILE} element={<SettingsLayout><ProfileSettings /></SettingsLayout>} />
<Route path={URLS.SETTINGS} element={<Navigate to={URLS.SETTINGS_PROFILE} />} />
</>
);