feat: add primitive profile settings
This commit is contained in:

committed by
Ömer Faruk Aydın

parent
68e5e6d011
commit
140734b32c
@@ -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}
|
||||
>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
@@ -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 (
|
||||
<BaseDrawer
|
||||
{...props}
|
||||
{...drawerProps}
|
||||
disableBackdropTransition={!iOS}
|
||||
disableDiscovery={iOS}
|
||||
variant={matchSmallScreens ? 'temporary' : 'permanent'}
|
||||
>
|
||||
{/* keep the following encapsulating `div` to have `space-between` children */}
|
||||
<div>
|
||||
<HideOnScroll unmountOnExit>
|
||||
<Toolbar />
|
||||
</HideOnScroll>
|
||||
|
||||
<List sx={{ py: 0, mt: 3 }}>
|
||||
{links.map(({ Icon, primary, to }, index) => (
|
||||
<ListItemLink
|
||||
icon={<SwapCallsIcon htmlColor={theme.palette.primary.main} />}
|
||||
primary={formatMessage('drawer.flows')}
|
||||
to={URLS.FLOWS}
|
||||
onClick={closeOnClick}
|
||||
/>
|
||||
|
||||
<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}
|
||||
key={`${to}-${index}`}
|
||||
icon={<Icon htmlColor={theme.palette.primary.main} />}
|
||||
primary={formatMessage(primary)}
|
||||
to={to}
|
||||
onClick={closeOnClick}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
@@ -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'
|
||||
},
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
@@ -7,7 +7,7 @@ type FormProps = {
|
||||
defaultValues?: UseFormProps['defaultValues'];
|
||||
onSubmit?: SubmitHandler<FieldValues>;
|
||||
render?: (props: UseFormReturn) => React.ReactNode;
|
||||
}
|
||||
};
|
||||
|
||||
const noop = () => null;
|
||||
|
||||
|
@@ -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
|
||||
|
||||
<Box sx={{ display: 'flex', }}>
|
||||
<Drawer
|
||||
links={drawerLinks}
|
||||
open={isDrawerOpen}
|
||||
onOpen={openDrawer}
|
||||
onClose={closeDrawer}
|
||||
|
@@ -14,7 +14,7 @@ type ListItemLinkProps = {
|
||||
|
||||
export default function ListItemLink(props: ListItemLinkProps): React.ReactElement {
|
||||
const { icon, primary, to, onClick } = props;
|
||||
const selected = useMatch({ path: to, end: false });
|
||||
const selected = useMatch({ path: to, end: true });
|
||||
|
||||
const CustomLink = React.useMemo(
|
||||
() =>
|
||||
|
62
packages/web/src/components/SettingsLayout/index.tsx
Normal file
62
packages/web/src/components/SettingsLayout/index.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
@@ -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;
|
||||
|
@@ -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"
|
||||
}
|
||||
|
@@ -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 (
|
||||
<Box sx={{ py: 3 }}>
|
||||
<Container>
|
||||
<Grid container item sx={{ mb: [2, 5] }} columnSpacing={1.5} rowGap={3}>
|
||||
<Container sx={{ py: 3 }}>
|
||||
<Grid container item sx={{ mb: [2, 5] }} rowGap={3}>
|
||||
{executionSteps?.map((executionStep) => (
|
||||
<ExecutionStep key={executionStep.id} executionStep={executionStep} step={executionStep.step} />
|
||||
))}
|
||||
</Grid>
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
77
packages/web/src/pages/ProfileSettings/index.tsx
Normal file
77
packages/web/src/pages/ProfileSettings/index.tsx
Normal 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;
|
@@ -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 (
|
||||
<Routes>
|
||||
@@ -34,6 +35,10 @@ export default (
|
||||
|
||||
<Route path="/" element={<Navigate to={URLS.FLOWS} />} />
|
||||
|
||||
<Route path={`${URLS.SETTINGS}`}>
|
||||
{settingsRoutes}
|
||||
</Route>
|
||||
|
||||
<Route element={<Layout><div>404</div></Layout>} />
|
||||
</Routes>
|
||||
);
|
||||
|
13
packages/web/src/settingsRoutes.tsx
Normal file
13
packages/web/src/settingsRoutes.tsx
Normal 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} />} />
|
||||
</>
|
||||
);
|
Reference in New Issue
Block a user