diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 5109b4e34d..3b918a1976 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -32,7 +32,7 @@ repos:
rev: 5.10.1
hooks:
- id: isort
- args: ["--profile", "black", "--filter-files"]
+ args: ['--profile', 'black', '--filter-files']
exclude: ^.*\b(migrations)\b.*$
# Pythonic checks.
- repo: https://github.com/PyCQA/flake8
@@ -41,7 +41,7 @@ repos:
- id: flake8
exclude: docs|migrations|node_modules|revengine/settings
additional_dependencies:
- - "flake8-logging-format"
+ - 'flake8-logging-format'
# # Pylint code checks.
# # "local" because pylint needs all packages to dynamically import.
# - repo: local
@@ -77,7 +77,7 @@ repos:
rev: v2.7.1
hooks:
- id: prettier
- types: [javascript]
+ types_or: [javascript, ts, tsx]
# JS code linter.
- repo: https://github.com/pre-commit/mirrors-eslint
rev: v8.18.0
diff --git a/spa/src/assets/assets.d.ts b/spa/src/assets/assets.d.ts
new file mode 100644
index 0000000000..077b8ac65d
--- /dev/null
+++ b/spa/src/assets/assets.d.ts
@@ -0,0 +1,11 @@
+// Required to import .svg files to TS files
+declare module '*.svg' {
+ const content: any;
+ export default content;
+}
+
+// Required to import .png files to TS files
+declare module '*.png' {
+ const content: any;
+ export default content;
+}
diff --git a/spa/src/assets/icons/logout.svg b/spa/src/assets/icons/logout.svg
new file mode 100644
index 0000000000..7c2f2b5595
--- /dev/null
+++ b/spa/src/assets/icons/logout.svg
@@ -0,0 +1,8 @@
+
diff --git a/spa/src/components/common/AvatarMenu/AvatarMenu.stories.js b/spa/src/components/common/AvatarMenu/AvatarMenu.stories.js
new file mode 100644
index 0000000000..1706f33659
--- /dev/null
+++ b/spa/src/components/common/AvatarMenu/AvatarMenu.stories.js
@@ -0,0 +1,40 @@
+import AvatarMenu from './AvatarMenu';
+
+export default {
+ title: 'Common/AvatarMenu',
+ component: AvatarMenu
+};
+
+export const Default = (props) => (
+
+);
+
+Default.args = {
+ user: {
+ firstName: 'Gui',
+ lastName: 'Mend',
+ email: 'gm@gmail.com'
+ }
+};
+
+export const NoNameAvatar = (props) => (
+
+);
+
+NoNameAvatar.args = {
+ user: {
+ email: 'gm@gmail.com'
+ }
+};
+
+export const EmptyAvatar = (props) => (
+
+);
+
+EmptyAvatar.args = {};
diff --git a/spa/src/components/common/AvatarMenu/AvatarMenu.styled.ts b/spa/src/components/common/AvatarMenu/AvatarMenu.styled.ts
new file mode 100644
index 0000000000..3732276e17
--- /dev/null
+++ b/spa/src/components/common/AvatarMenu/AvatarMenu.styled.ts
@@ -0,0 +1,110 @@
+import {
+ Avatar as MuiAvatar,
+ Popover as MuiPopover,
+ MenuItem as MuiMenuItem,
+ Typography as MuiTypography,
+ ListItemIcon as MuiListItemIcon
+} from '@material-ui/core';
+import styled from 'styled-components';
+
+export const Container = styled.button<{ open: string }>`
+ display: flex;
+ align-items: center;
+ cursor: pointer;
+ padding: 0;
+ background: transparent;
+ border: none;
+ padding: 9px 0 9px 10px;
+ max-height: 48px;
+
+ svg {
+ fill: white;
+ height: 27px;
+ width: 27px;
+ margin-left: -3px;
+ }
+
+ ${(props) =>
+ props.open === 'open' &&
+ `background-color: ${props.theme.colors.account.purple[1]};
+ `}
+
+ :hover {
+ background-color: ${(props) => props.theme.colors.account.purple[1]};
+ }
+`;
+
+export const ModalHeader = styled.p`
+ font-size: ${(props) => props.theme.fontSizesUpdated.sm};
+ font-family: ${(props) => props.theme.systemFont};
+ color: ${(props) => props.theme.colors.muiGrey[700]};
+ font-weight: 600;
+ margin: 0 0 9px;
+`;
+
+export const ListWrapper = styled.ul`
+ padding: 0;
+ margin: 0;
+`;
+
+export const ListItemIcon = styled(MuiListItemIcon)`
+ && {
+ min-width: unset;
+ color: ${(props) => props.theme.colors.sidebarBackground};
+
+ svg {
+ width: 20px;
+ height: 20px;
+ }
+ }
+`;
+
+export const LogoutIconWrapper = styled.img`
+ width: 20px;
+ height: 20px;
+`;
+
+export const Avatar = styled(MuiAvatar)`
+ && {
+ height: 31px;
+ width: 31px;
+ border: 1px solid white;
+ font-size: ${(props) => props.theme.fontSizesUpdated.sm};
+ background-color: ${(props) => props.theme.colors.muiLightBlue[800]};
+ }
+`;
+
+export const Popover = styled(MuiPopover)`
+ .MuiPaper-rounded {
+ min-width: 207px;
+ box-shadow: none;
+ padding: 12px 15px;
+ border: 1px solid ${(props) => props.theme.colors.muiGrey[300]};
+ background-color: ${(props) => props.theme.colors.muiGrey[100]};
+ filter: drop-shadow(0px 2px 20px rgba(0, 0, 0, 0.1));
+ }
+`;
+
+export const Typography = styled(MuiTypography)`
+ && {
+ color: ${(props) => props.theme.colors.sidebarBackground};
+ font-size: ${(props) => props.theme.fontSizesUpdated.sm};
+ font-weight: 400;
+ }
+`;
+
+export const MenuItem = styled(MuiMenuItem)`
+ && {
+ padding: 5px 7px;
+ margin: 0 -7px;
+ border-radius: ${(props) => props.theme.muiBorderRadius.lg};
+ background-color: ${(props) => props.theme.colors.muiGrey[100]};
+ display: flex;
+ align-items: center;
+ gap: 18px;
+
+ :hover {
+ background-color: ${(props) => props.theme.colors.navSectionLabelColor};
+ }
+ }
+`;
diff --git a/spa/src/components/common/AvatarMenu/AvatarMenu.test.tsx b/spa/src/components/common/AvatarMenu/AvatarMenu.test.tsx
new file mode 100644
index 0000000000..3b9d67ccfe
--- /dev/null
+++ b/spa/src/components/common/AvatarMenu/AvatarMenu.test.tsx
@@ -0,0 +1,102 @@
+import { axe } from 'jest-axe';
+import { render, screen, within } from 'test-utils';
+import userEvent from '@testing-library/user-event';
+
+import { FAQ_URL } from 'constants/helperUrls';
+import onLogout from 'components/authentication/logout';
+import AvatarMenu, { AvatarMenuProps } from './AvatarMenu';
+
+jest.mock('components/authentication/logout');
+
+const tree = (props?: AvatarMenuProps) => {
+ return render();
+};
+
+const settingsMenu = 'Settings';
+
+describe('AvatarMenu', () => {
+ it('should be enabled if has user', () => {
+ tree({ user: { email: 'a@a.com' } });
+ expect(screen.getByRole('button', { name: settingsMenu })).toBeEnabled();
+ });
+
+ it('should be disabled if user is undefined', () => {
+ tree();
+ expect(screen.getByRole('button', { name: settingsMenu })).toBeEnabled();
+ });
+
+ it('should render name initials', () => {
+ const nameUser = {
+ firstName: 'first-mock',
+ lastName: 'last-mock',
+ email: 'email@mock.com'
+ };
+ tree({ user: nameUser });
+
+ const avatar = screen.getByTestId('avatar');
+ const title = within(avatar).getByText('FL');
+ expect(title).toBeInTheDocument();
+ });
+
+ it('should render email initial if name is empty', () => {
+ const emailUser = {
+ email: 'email@mock.com'
+ };
+ tree({ user: emailUser });
+
+ const avatar = screen.getByTestId('avatar');
+ expect(avatar.innerHTML).toBe('E');
+ const title = within(avatar).getByText('E');
+ expect(title).toBeInTheDocument();
+ });
+
+ it('should render nothing if user is undefined', () => {
+ tree();
+
+ const avatar = screen.getByTestId('avatar');
+ expect(avatar.innerHTML).toBe('');
+ });
+
+ it('should open settings menu with correct buttons', () => {
+ const emailUser = {
+ email: 'email@mock.com'
+ };
+ tree({ user: emailUser });
+
+ userEvent.click(screen.getByRole('button', { name: settingsMenu }));
+ expect(screen.getByRole('menuitem', { name: 'FAQ' })).toBeEnabled();
+ expect(screen.getByRole('menuitem', { name: 'Sign out' })).toBeEnabled();
+ });
+
+ it('should have correct FAQ link', () => {
+ const oldOpen = jest.spyOn(window, 'open').mockImplementation();
+ const emailUser = {
+ email: 'email@mock.com'
+ };
+ tree({ user: emailUser });
+
+ userEvent.click(screen.getByRole('button', { name: settingsMenu }));
+ userEvent.click(screen.getByRole('menuitem', { name: 'FAQ' }));
+ expect(oldOpen).toBeCalledWith(FAQ_URL, '_blank', 'noopener, noreferrer');
+ oldOpen.mockRestore();
+ });
+
+ it('should logout', () => {
+ const emailUser = {
+ email: 'email@mock.com'
+ };
+ tree({ user: emailUser });
+
+ userEvent.click(screen.getByRole('button', { name: settingsMenu }));
+ userEvent.click(screen.getByRole('menuitem', { name: 'Sign out' }));
+ expect(onLogout).toBeCalledTimes(1);
+ });
+
+ it('is accessible', async () => {
+ const { container } = tree({ user: { email: 'a@a.com' } });
+ expect(await axe(container)).toHaveNoViolations();
+
+ userEvent.click(screen.getByRole('button', { name: settingsMenu }));
+ expect(await axe(container)).toHaveNoViolations();
+ });
+});
diff --git a/spa/src/components/common/AvatarMenu/AvatarMenu.tsx b/spa/src/components/common/AvatarMenu/AvatarMenu.tsx
new file mode 100644
index 0000000000..834e0e0dcc
--- /dev/null
+++ b/spa/src/components/common/AvatarMenu/AvatarMenu.tsx
@@ -0,0 +1,106 @@
+import { MouseEvent, useMemo, useState } from 'react';
+import PropTypes, { InferProps } from 'prop-types';
+import MoreVertIcon from '@material-ui/icons/MoreVert';
+import ContactSupportOutlinedIcon from '@material-ui/icons/ContactSupportOutlined';
+
+import LogoutIcon from 'assets/icons/logout.svg';
+import onLogout from 'components/authentication/logout';
+import { FAQ_URL } from 'constants/helperUrls';
+
+import {
+ Container,
+ ModalHeader,
+ Popover,
+ Avatar,
+ ListWrapper,
+ MenuItem,
+ ListItemIcon,
+ Typography,
+ LogoutIconWrapper
+} from './AvatarMenu.styled';
+
+export type AvatarMenuProps = InferProps;
+
+export const capitalizeInitial = (text?: string) => (text ? text[0].toUpperCase() : '');
+
+const AvatarMenu = ({ user, className }: AvatarMenuProps) => {
+ const [anchorEl, setAnchorEl] = useState(null);
+ const open = Boolean(anchorEl);
+ const id = open ? 'avatar-menu-popover' : undefined;
+
+ const avatarInitials = useMemo(
+ () =>
+ user?.firstName
+ ? `${capitalizeInitial(user?.firstName || '')}${capitalizeInitial(user?.lastName || '')}`
+ : `${capitalizeInitial(user?.email || '')}`,
+ [user?.email, user?.firstName, user?.lastName]
+ );
+
+ const handleClick = (event: MouseEvent) => {
+ setAnchorEl(event.currentTarget);
+ };
+
+ const handleClose = () => {
+ setAnchorEl(null);
+ };
+
+ const handleFAQ = () => {
+ window.open(FAQ_URL, '_blank', 'noopener, noreferrer');
+ };
+
+ return (
+ <>
+
+ {avatarInitials}
+
+
+
+ Settings
+
+
+
+
+
+ >
+ );
+};
+
+const AvatarMenuPropTypes = {
+ user: PropTypes.shape({
+ firstName: PropTypes.string,
+ lastName: PropTypes.string,
+ email: PropTypes.string.isRequired
+ }),
+ className: PropTypes.string
+};
+
+AvatarMenu.propTypes = AvatarMenuPropTypes;
+
+AvatarMenu.defaultProps = {
+ className: ''
+};
+
+export default AvatarMenu;
diff --git a/spa/src/components/common/AvatarMenu/index.ts b/spa/src/components/common/AvatarMenu/index.ts
new file mode 100644
index 0000000000..bccc85e195
--- /dev/null
+++ b/spa/src/components/common/AvatarMenu/index.ts
@@ -0,0 +1 @@
+export { default } from './AvatarMenu';
diff --git a/spa/src/components/dashboard/Dashboard.js b/spa/src/components/dashboard/Dashboard.js
index 6b30089fea..1df812e70d 100644
--- a/spa/src/components/dashboard/Dashboard.js
+++ b/spa/src/components/dashboard/Dashboard.js
@@ -57,7 +57,7 @@ function Dashboard() {
return (
{requiresVerification ? : ''}
-
+
{isEditPage ? null : }
diff --git a/spa/src/components/dashboard/sidebar/DashboardSidebarFooter.js b/spa/src/components/dashboard/sidebar/DashboardSidebarFooter.js
index 0f7b62cf0c..6886e10a8f 100644
--- a/spa/src/components/dashboard/sidebar/DashboardSidebarFooter.js
+++ b/spa/src/components/dashboard/sidebar/DashboardSidebarFooter.js
@@ -1,19 +1,14 @@
import * as S from './DashboardSidebar.styled';
import { ICONS } from 'assets/icons/SvgIcon';
import HelpOutlined from '@material-ui/icons/HelpOutline';
-
-// Exported mainly to help with unit tests.
-export const footerHrefs = {
- faq: 'https://news-revenue-hub.atlassian.net/servicedesk/customer/portal/11/article/2195423496',
- help: 'https://fundjournalism.org/news-revenue-engine-help/'
-};
+import { FAQ_URL, HELP_URL } from 'constants/helperUrls';
const DashboardSidebarFooter = () => (
(
{
function tree(props) {
@@ -17,7 +18,7 @@ describe('DashboardSidebarFooter', () => {
const faqLink = screen.getByRole('listitem', { name: 'FAQ' });
expect(faqLink).toBeVisible();
- expect(faqLink).toHaveAttribute('href', footerHrefs.faq);
+ expect(faqLink).toHaveAttribute('href', FAQ_URL);
expect(faqLink).toHaveAttribute('target', '_blank');
});
@@ -27,7 +28,7 @@ describe('DashboardSidebarFooter', () => {
const helpLink = screen.getByRole('listitem', { name: 'Help' });
expect(helpLink).toBeVisible();
- expect(helpLink).toHaveAttribute('href', footerHrefs.help);
+ expect(helpLink).toHaveAttribute('href', HELP_URL);
expect(helpLink).toHaveAttribute('target', '_blank');
});
diff --git a/spa/src/components/dashboard/topbar/DashboardTopbar.test.js b/spa/src/components/dashboard/topbar/DashboardTopbar.test.js
index e70c44686e..f349bd4545 100644
--- a/spa/src/components/dashboard/topbar/DashboardTopbar.test.js
+++ b/spa/src/components/dashboard/topbar/DashboardTopbar.test.js
@@ -31,11 +31,14 @@ const page = {
published_date: '2021-11-18T21:51:53Z'
};
+const user = {
+ email: 'mock@email.com'
+};
+
describe('Dashboard TopBar', () => {
- it('should show logout link in topbar', () => {
- render();
- fireEvent.click(screen.getByText('Sign out'));
- expect(logout).toHaveBeenCalled();
+ it('should show avatar menu in topbar', () => {
+ render();
+ expect(screen.getByRole('button', { name: 'Settings' })).toBeEnabled();
});
it('should hide grab link button if isEditPage = false', () => {
diff --git a/spa/src/components/dashboard/topbar/DashboardTopbar.js b/spa/src/components/dashboard/topbar/DashboardTopbar.tsx
similarity index 83%
rename from spa/src/components/dashboard/topbar/DashboardTopbar.js
rename to spa/src/components/dashboard/topbar/DashboardTopbar.tsx
index fe673fab47..2cadaefdf7 100644
--- a/spa/src/components/dashboard/topbar/DashboardTopbar.js
+++ b/spa/src/components/dashboard/topbar/DashboardTopbar.tsx
@@ -1,25 +1,27 @@
-import PropTypes from 'prop-types';
-import * as S from './DashboardTopbar.styled';
-import { SvgLogo, Title, BackIconButton } from './DashboardTopbar.styled';
import { ICONS } from 'assets/icons/SvgIcon';
+import PropTypes, { InferProps } from 'prop-types';
import { useAlert } from 'react-alert';
+import * as S from './DashboardTopbar.styled';
+import { BackIconButton, SvgLogo, Title } from './DashboardTopbar.styled';
-import { PagePropTypes } from 'constants/proptypes';
-import { BackIcon } from 'elements/BackButton.styled';
-import { CONTENT_SLUG } from 'routes';
-import BackButton from 'elements/BackButton';
-import useRequest from 'hooks/useRequest';
+import mobileLogo from 'assets/images/logo-mobile.png';
import logo from 'assets/images/logo-nre.png';
import logoBlue from 'assets/images/nre-logo-blue.svg';
-import mobileLogo from 'assets/images/logo-mobile.png';
-import logout from 'components/authentication/logout';
+import { Tooltip } from 'components/base';
+import AvatarMenu from 'components/common/AvatarMenu';
import GrabLink from 'components/common/Button/GrabLink';
import PublishButton from 'components/common/Button/PublishButton';
import UnsavedChangesModal from 'components/pageEditor/UnsavedChangesModal';
+import { PagePropTypes, UserPropTypes } from 'constants/proptypes';
+import BackButton from 'elements/BackButton';
+import { BackIcon } from 'elements/BackButton.styled';
import useModal from 'hooks/useModal';
-import { Tooltip } from 'components/base';
+import useRequest from 'hooks/useRequest';
+import { CONTENT_SLUG } from 'routes';
-function DashboardTopbar({ isEditPage, page, setPage, updatedPage }) {
+type DashboardTopbarTypes = InferProps;
+
+function DashboardTopbar({ isEditPage, page, setPage, updatedPage, user }: DashboardTopbarTypes) {
const alert = useAlert();
const requestPatchPage = useRequest();
const { open: showUnsavedModal, handleClose: closeUnsavedModal, handleOpen: openUnsavedModal } = useModal();
@@ -61,28 +63,23 @@ function DashboardTopbar({ isEditPage, page, setPage, updatedPage }) {
)}
>
) : (
-
-
- Sign out
-
+
)}
);
}
-DashboardTopbar.propTypes = {
+const DashboardTopbarPropTypes = {
isEditPage: PropTypes.bool,
setPage: PropTypes.func,
page: PropTypes.shape(PagePropTypes),
+ user: PropTypes.shape(UserPropTypes),
updatedPage: PropTypes.shape(PagePropTypes)
};
+DashboardTopbar.propTypes = DashboardTopbarPropTypes;
+
DashboardTopbar.defaultProps = {
isEditPage: false,
page: undefined
diff --git a/spa/src/constants/helperUrls.ts b/spa/src/constants/helperUrls.ts
new file mode 100644
index 0000000000..ba198df4dd
--- /dev/null
+++ b/spa/src/constants/helperUrls.ts
@@ -0,0 +1,2 @@
+export const FAQ_URL = 'https://news-revenue-hub.atlassian.net/servicedesk/customer/portal/11/article/2195423496';
+export const HELP_URL = 'https://fundjournalism.org/news-revenue-engine-help/';
diff --git a/spa/src/constants/proptypes.js b/spa/src/constants/proptypes.js
index 01ed18d8af..50c8769b2d 100644
--- a/spa/src/constants/proptypes.js
+++ b/spa/src/constants/proptypes.js
@@ -11,3 +11,9 @@ export const PagePropTypes = {
slug: PropTypes.string,
published_date: PropTypes.string
};
+
+export const UserPropTypes = {
+ firstName: PropTypes.string,
+ lastName: PropTypes.string,
+ email: PropTypes.string.isRequired
+};
diff --git a/spa/src/hooks/useFeatureFlags.ts b/spa/src/hooks/useFeatureFlags.ts
index 9f3aeb6834..8684837218 100644
--- a/spa/src/hooks/useFeatureFlags.ts
+++ b/spa/src/hooks/useFeatureFlags.ts
@@ -6,7 +6,7 @@ interface UseFeatureFlagsHook {
flags: FeatureFlag[];
isLoading: boolean;
isError: boolean;
-};
+}
/* This hook provides any feature flags attached to the user. It
depends on `useUser` which is responsible for retrieving the user from
diff --git a/spa/src/hooks/useFeatureFlags.types.ts b/spa/src/hooks/useFeatureFlags.types.ts
index fc24b01781..e1fcc381f3 100644
--- a/spa/src/hooks/useFeatureFlags.types.ts
+++ b/spa/src/hooks/useFeatureFlags.types.ts
@@ -1,3 +1,3 @@
export interface FeatureFlag {
name: string;
-};
+}
diff --git a/spa/src/hooks/useStyleList.test.tsx b/spa/src/hooks/useStyleList.test.tsx
index b57d31e622..52759decbe 100644
--- a/spa/src/hooks/useStyleList.test.tsx
+++ b/spa/src/hooks/useStyleList.test.tsx
@@ -6,17 +6,16 @@ import { ReactChild } from 'react';
import axios from 'ajax/axios';
import { LIST_STYLES } from 'ajax/endpoints';
-import useStyleList from "./useStyleList";
+import useStyleList from './useStyleList';
import { SIGN_IN } from 'routes';
import { GENERIC_ERROR } from 'constants/textConstants';
-
const mockHistoryPush = jest.fn();
jest.mock('react-router-dom', () => ({
useHistory: () => ({
push: mockHistoryPush
- }),
+ })
}));
const mockAlertError = jest.fn();
@@ -25,25 +24,28 @@ jest.mock('react-alert', () => ({
useAlert: () => ({ error: mockAlertError })
}));
-
const axiosMock = new MockAdapter(axios);
-
describe('useStyleList hook', () => {
-
const wrapper = ({ children }: { children: ReactChild }) => (
- {children}
+
+ {children}
+
);
const stylesList = [
{ id: 1, styles: { foo: 'bar' } },
- { id: 2, styles: { bizz: 'bang' } },
+ { id: 2, styles: { bizz: 'bang' } }
];
beforeEach(() => {
@@ -60,8 +62,12 @@ describe('useStyleList hook', () => {
const spy = jest.spyOn(reactQuery, 'useQueryClient');
// cast this to `as any` so we don't have to provide all 33 params that are in returned
// queryClient in real implementation
- spy.mockReturnValue({invalidateQueries: mockInvalidateQueries} as any)
- const { result: { current: { refetch } } } = renderHook(() => useStyleList(), { wrapper });
+ spy.mockReturnValue({ invalidateQueries: mockInvalidateQueries } as any);
+ const {
+ result: {
+ current: { refetch }
+ }
+ } = renderHook(() => useStyleList(), { wrapper });
expect(typeof refetch).toBe('function');
refetch();
expect(mockInvalidateQueries).toHaveBeenCalledWith(['styles']);
@@ -83,12 +89,12 @@ describe('useStyleList hook', () => {
});
it('redirects user to log in when auth error', async () => {
axiosMock.reset();
- axiosMock.onGet(LIST_STYLES).reply((function (config) {
- return Promise.reject({name: 'AuthenticationError'});
- }));
+ axiosMock.onGet(LIST_STYLES).reply(function (config) {
+ return Promise.reject({ name: 'AuthenticationError' });
+ });
const { waitForValueToChange, result } = renderHook(() => useStyleList(), { wrapper });
await waitForValueToChange(() => result.current.isError);
expect(mockHistoryPush).toHaveBeenCalledTimes(1);
- expect(mockHistoryPush).toHaveBeenCalledWith(SIGN_IN)
+ expect(mockHistoryPush).toHaveBeenCalledWith(SIGN_IN);
});
});
diff --git a/spa/src/hooks/useStyleList.ts b/spa/src/hooks/useStyleList.ts
index d78331b2fe..44cf894045 100644
--- a/spa/src/hooks/useStyleList.ts
+++ b/spa/src/hooks/useStyleList.ts
@@ -12,18 +12,14 @@ async function fetchStyles() {
return data;
}
-
-type StyleStyles =
- | string
- | { [property: string]: StyleStyles }
- | StyleStyles[];
+type StyleStyles = string | { [property: string]: StyleStyles } | StyleStyles[];
interface Style {
- id: number,
- created: string,
- modified: string,
- name: string,
- styles: StyleStyles,
+ id: number;
+ created: string;
+ modified: string;
+ name: string;
+ styles: StyleStyles;
}
export interface UseStyleListResult {
@@ -33,18 +29,17 @@ export interface UseStyleListResult {
refetch: Function;
}
-
-function useStyleList():UseStyleListResult {
+function useStyleList(): UseStyleListResult {
const alert = useAlert();
const history = useHistory();
const queryClient = useQueryClient();
const {
data: styles,
isLoading,
- isError,
+ isError
} = useQuery(['styles'], fetchStyles, {
initialData: [],
- onError: (err:Error) => {
+ onError: (err: Error) => {
if (err?.name === 'AuthenticationError') {
history.push(SIGN_IN);
} else {
@@ -53,9 +48,14 @@ function useStyleList():UseStyleListResult {
}
}
});
- return { styles, isLoading, isError, refetch: () => {
- queryClient.invalidateQueries(['styles']);
- }};
+ return {
+ styles,
+ isLoading,
+ isError,
+ refetch: () => {
+ queryClient.invalidateQueries(['styles']);
+ }
+ };
}
export default useStyleList;
diff --git a/spa/src/styles/defaultTheme.d.ts b/spa/src/styles/defaultTheme.d.ts
index d071b7d70f..6fe00017f7 100644
--- a/spa/src/styles/defaultTheme.d.ts
+++ b/spa/src/styles/defaultTheme.d.ts
@@ -47,6 +47,7 @@ declare module 'styled-components' {
400: string;
500: string;
600: string;
+ 700: string;
800: string;
900: string;
};
@@ -90,24 +91,24 @@ declare module 'styled-components' {
font: { body: string; heading: string };
fontSizes: string[];
fontSizesUpdated: {
- xs: string;
- sm: string;
- md: string;
- lg: string;
- '20': '20px';
- lgx: string;
- lg2x: string;
- lg3x: string;
- h1: string;
- xl: string;
- '2xl': string;
+ xs: '12px';
+ sm: '14px';
+ md: '16px';
+ lg: '18px';
+ 20: '20px';
+ lgx: '24px';
+ lg2x: '28px';
+ lg3x: '30px';
+ h1: '34px';
+ xl: '46px';
+ '2xl': '72px';
};
muiBorderRadius: {
- sm: string;
- md: string;
- lg: string;
- xl: string;
- '2xl': string;
+ sm: '2px';
+ md: '4px';
+ lg: '6px';
+ xl: '10px';
+ '2xl': '12px';
};
radii: string[];
shadows: string[];
diff --git a/spa/src/styles/themes.ts b/spa/src/styles/themes.ts
index b4c3e7f367..ed57762db1 100644
--- a/spa/src/styles/themes.ts
+++ b/spa/src/styles/themes.ts
@@ -53,6 +53,7 @@ export const revEngineTheme: DefaultTheme = {
400: '#c4c4c4',
500: '#969696',
600: '#707070',
+ 700: '#666666',
800: '#3c3c3c',
900: '#282828'
},