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}
>
-
+
+
+
);
}
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 (
+ <>
+ } />
+
+ } />
+ >
+);