mirror of
https://github.com/umami-software/umami.git
synced 2026-02-07 22:27:16 +01:00
Rewrite admin. (#1713)
* Rewrite admin. * Clean up password forms. * Fix naming issues. * CSS Naming.
This commit is contained in:
parent
f4db04c3c6
commit
e1f99a7d01
113 changed files with 2054 additions and 1872 deletions
|
|
@ -3,6 +3,7 @@ import { defineMessages, useIntl } from 'react-intl';
|
|||
import MenuButton from 'components/common/MenuButton';
|
||||
import Gear from 'assets/gear.svg';
|
||||
import { saveDashboard } from 'store/dashboard';
|
||||
import { Icon } from 'react-basics';
|
||||
|
||||
const messages = defineMessages({
|
||||
toggleCharts: { id: 'message.toggle-charts', defaultMessage: 'Toggle charts' },
|
||||
|
|
@ -32,5 +33,11 @@ export default function DashboardSettingsButton() {
|
|||
}
|
||||
}
|
||||
|
||||
return <MenuButton icon={<Gear />} options={menuOptions} onSelect={handleSelect} hideLabel />;
|
||||
return (
|
||||
<MenuButton options={menuOptions} onSelect={handleSelect} hideLabel>
|
||||
<Icon>
|
||||
<Gear />
|
||||
</Icon>
|
||||
</MenuButton>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import DateFilter, { filterOptions } from 'components/common/DateFilter';
|
||||
import Button from 'components/common/Button';
|
||||
import { Button } from 'react-basics';
|
||||
import useDateRange from 'hooks/useDateRange';
|
||||
import { DEFAULT_DATE_RANGE } from 'lib/constants';
|
||||
import styles from './DateRangeSetting.module.css';
|
||||
|
|
@ -28,7 +28,7 @@ export default function DateRangeSetting() {
|
|||
endDate={endDate}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<Button className={styles.button} size="small" onClick={handleReset}>
|
||||
<Button className={styles.button} size="sm" onClick={handleReset}>
|
||||
<FormattedMessage id="label.reset" defaultMessage="Reset" />
|
||||
</Button>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import useLocale from 'hooks/useLocale';
|
|||
import MenuButton from 'components/common/MenuButton';
|
||||
import Globe from 'assets/globe.svg';
|
||||
import styles from './LanguageButton.module.css';
|
||||
import { Icon } from 'react-basics';
|
||||
|
||||
export default function LanguageButton() {
|
||||
const { locale, saveLocale } = useLocale();
|
||||
|
|
@ -15,13 +16,16 @@ export default function LanguageButton() {
|
|||
|
||||
return (
|
||||
<MenuButton
|
||||
icon={<Globe />}
|
||||
options={menuOptions}
|
||||
value={locale}
|
||||
menuClassName={styles.menu}
|
||||
buttonVariant="light"
|
||||
onSelect={handleSelect}
|
||||
hideLabel
|
||||
/>
|
||||
>
|
||||
<Icon>
|
||||
<Globe />
|
||||
</Icon>
|
||||
</MenuButton>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import DropDown from 'components/common/DropDown';
|
||||
import Button from 'components/common/Button';
|
||||
import { Button } from 'react-basics';
|
||||
import useLocale from 'hooks/useLocale';
|
||||
import { DEFAULT_LOCALE } from 'lib/constants';
|
||||
import styles from './TimezoneSetting.module.css';
|
||||
|
|
@ -23,7 +23,7 @@ export default function LanguageSetting() {
|
|||
options={options}
|
||||
onChange={saveLocale}
|
||||
/>
|
||||
<Button className={styles.button} size="small" onClick={handleReset}>
|
||||
<Button className={styles.button} size="sm" onClick={handleReset}>
|
||||
<FormattedMessage id="label.reset" defaultMessage="Reset" />
|
||||
</Button>
|
||||
</>
|
||||
|
|
|
|||
53
components/settings/ProfileDetails.js
Normal file
53
components/settings/ProfileDetails.js
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import TimezoneSetting from 'components/settings/TimezoneSetting';
|
||||
import useUser from 'hooks/useUser';
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import DateRangeSetting from './DateRangeSetting';
|
||||
import LanguageSetting from './LanguageSetting';
|
||||
import styles from './ProfileSettings.module.css';
|
||||
import ThemeSetting from './ThemeSetting';
|
||||
|
||||
export default function ProfileDetails() {
|
||||
const { user } = useUser();
|
||||
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { username } = user;
|
||||
|
||||
return (
|
||||
<>
|
||||
<dl className={styles.list}>
|
||||
<dt>
|
||||
<FormattedMessage id="label.username" defaultMessage="Username" />
|
||||
</dt>
|
||||
<dd>{username}</dd>
|
||||
<dt>
|
||||
<FormattedMessage id="label.timezone" defaultMessage="Timezone" />
|
||||
</dt>
|
||||
<dd>
|
||||
<TimezoneSetting />
|
||||
</dd>
|
||||
<dt>
|
||||
<FormattedMessage id="label.default-date-range" defaultMessage="Default date range" />
|
||||
</dt>
|
||||
<dd>
|
||||
<DateRangeSetting />
|
||||
</dd>
|
||||
<dt>
|
||||
<FormattedMessage id="label.language" defaultMessage="Language" />
|
||||
</dt>
|
||||
<dd>
|
||||
<LanguageSetting />
|
||||
</dd>
|
||||
<dt>
|
||||
<FormattedMessage id="label.theme" defaultMessage="Theme" />
|
||||
</dt>
|
||||
<dd>
|
||||
<ThemeSetting />
|
||||
</dd>
|
||||
</dl>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
import React, { useState } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import Button from 'components/common/Button';
|
||||
import Modal from 'components/common/Modal';
|
||||
import Toast from 'components/common/Toast';
|
||||
import ChangePasswordForm from 'components/forms/ChangePasswordForm';
|
||||
import TimezoneSetting from 'components/settings/TimezoneSetting';
|
||||
import Dots from 'assets/ellipsis-h.svg';
|
||||
import styles from './ProfileSettings.module.css';
|
||||
import DateRangeSetting from './DateRangeSetting';
|
||||
import useEscapeKey from 'hooks/useEscapeKey';
|
||||
import useUser from 'hooks/useUser';
|
||||
import LanguageSetting from './LanguageSetting';
|
||||
import ThemeSetting from './ThemeSetting';
|
||||
|
||||
export default function ProfileSettings() {
|
||||
const { user } = useUser();
|
||||
const [changePassword, setChangePassword] = useState(false);
|
||||
const [message, setMessage] = useState(null);
|
||||
|
||||
function handleSave() {
|
||||
setChangePassword(false);
|
||||
setMessage(<FormattedMessage id="message.save-success" defaultMessage="Saved successfully." />);
|
||||
}
|
||||
|
||||
useEscapeKey(() => {
|
||||
setChangePassword(false);
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { userId, username } = user;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader>
|
||||
<div>
|
||||
<FormattedMessage id="label.profile" defaultMessage="Profile" />
|
||||
</div>
|
||||
<Button icon={<Dots />} size="small" onClick={() => setChangePassword(true)}>
|
||||
<FormattedMessage id="label.change-password" defaultMessage="Change password" />
|
||||
</Button>
|
||||
</PageHeader>
|
||||
<dl className={styles.list}>
|
||||
<dt>
|
||||
<FormattedMessage id="label.username" defaultMessage="Username" />
|
||||
</dt>
|
||||
<dd>{username}</dd>
|
||||
<dt>
|
||||
<FormattedMessage id="label.timezone" defaultMessage="Timezone" />
|
||||
</dt>
|
||||
<dd>
|
||||
<TimezoneSetting />
|
||||
</dd>
|
||||
<dt>
|
||||
<FormattedMessage id="label.default-date-range" defaultMessage="Default date range" />
|
||||
</dt>
|
||||
<dd>
|
||||
<DateRangeSetting />
|
||||
</dd>
|
||||
<dt>
|
||||
<FormattedMessage id="label.language" defaultMessage="Language" />
|
||||
</dt>
|
||||
<dd>
|
||||
<LanguageSetting />
|
||||
</dd>
|
||||
<dt>
|
||||
<FormattedMessage id="label.theme" defaultMessage="Theme" />
|
||||
</dt>
|
||||
<dd>
|
||||
<ThemeSetting />
|
||||
</dd>
|
||||
</dl>
|
||||
{changePassword && (
|
||||
<Modal
|
||||
title={<FormattedMessage id="label.change-password" defaultMessage="Change password" />}
|
||||
>
|
||||
<ChangePasswordForm
|
||||
values={{ userId }}
|
||||
onSave={handleSave}
|
||||
onClose={() => setChangePassword(false)}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
{message && <Toast message={message} onClose={() => setMessage(null)} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ import React, { useRef, useState } from 'react';
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
import TimezoneSetting from './TimezoneSetting';
|
||||
import DateRangeSetting from './DateRangeSetting';
|
||||
import Button from 'components/common/Button';
|
||||
import { Button, Icon } from 'react-basics';
|
||||
import styles from './SettingsButton.module.css';
|
||||
import Gear from 'assets/gear.svg';
|
||||
import useDocumentClick from '../../hooks/useDocumentClick';
|
||||
|
|
@ -23,7 +23,11 @@ export default function SettingsButton() {
|
|||
|
||||
return (
|
||||
<div className={styles.button} ref={ref}>
|
||||
<Button icon={<Gear />} variant="light" onClick={handleClick} />
|
||||
<Button variant="light" onClick={handleClick}>
|
||||
<Icon>
|
||||
<Gear />
|
||||
</Icon>
|
||||
</Button>
|
||||
{show && (
|
||||
<div className={styles.panel}>
|
||||
<dt>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import useTheme from 'hooks/useTheme';
|
|||
import Sun from 'assets/sun.svg';
|
||||
import Moon from 'assets/moon.svg';
|
||||
import styles from './ThemeButton.module.css';
|
||||
import Icon from '../common/Icon';
|
||||
import { Icon } from 'react-basics';
|
||||
|
||||
export default function ThemeButton() {
|
||||
const [theme, setTheme] = useTheme();
|
||||
|
|
@ -30,7 +30,7 @@ export default function ThemeButton() {
|
|||
<div className={styles.button} onClick={handleClick}>
|
||||
{transitions((styles, item) => (
|
||||
<animated.div key={item} style={styles}>
|
||||
<Icon icon={item === 'light' ? <Sun /> : <Moon />} />
|
||||
<Icon>{item === 'light' ? <Sun /> : <Moon />}</Icon>
|
||||
</animated.div>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import classNames from 'classnames';
|
||||
import Button from 'components/common/Button';
|
||||
import { Button, Icon } from 'react-basics';
|
||||
import useTheme from 'hooks/useTheme';
|
||||
import Sun from 'assets/sun.svg';
|
||||
import Moon from 'assets/moon.svg';
|
||||
|
|
@ -12,14 +12,20 @@ export default function ThemeSetting() {
|
|||
<div className={styles.buttons}>
|
||||
<Button
|
||||
className={classNames({ [styles.active]: theme === 'light' })}
|
||||
icon={<Sun />}
|
||||
onClick={() => setTheme('light')}
|
||||
/>
|
||||
>
|
||||
<Icon>
|
||||
<Sun />
|
||||
</Icon>
|
||||
</Button>
|
||||
<Button
|
||||
className={classNames({ [styles.active]: theme === 'dark' })}
|
||||
icon={<Moon />}
|
||||
onClick={() => setTheme('dark')}
|
||||
/>
|
||||
>
|
||||
<Icon>
|
||||
<Moon />
|
||||
</Icon>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
import { listTimeZones } from 'timezone-support';
|
||||
import DropDown from 'components/common/DropDown';
|
||||
import Button from 'components/common/Button';
|
||||
import { Button } from 'react-basics';
|
||||
import useTimezone from 'hooks/useTimezone';
|
||||
import { getTimezone } from 'lib/date';
|
||||
import styles from './TimezoneSetting.module.css';
|
||||
|
|
@ -23,7 +23,7 @@ export default function TimezoneSetting() {
|
|||
options={options}
|
||||
onChange={saveTimezone}
|
||||
/>
|
||||
<Button className={styles.button} size="small" onClick={handleReset}>
|
||||
<Button className={styles.button} size="sm" onClick={handleReset}>
|
||||
<FormattedMessage id="label.reset" defaultMessage="Reset" />
|
||||
</Button>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { useRouter } from 'next/router';
|
||||
import { removeItem } from 'next-basics';
|
||||
import MenuButton from 'components/common/MenuButton';
|
||||
import Icon from 'components/common/Icon';
|
||||
import User from 'assets/user.svg';
|
||||
import styles from './UserButton.module.css';
|
||||
import { AUTH_TOKEN } from 'lib/constants';
|
||||
import useUser from 'hooks/useUser';
|
||||
import useConfig from 'hooks/useConfig';
|
||||
import useUser from 'hooks/useUser';
|
||||
import { AUTH_TOKEN } from 'lib/constants';
|
||||
import { removeItem } from 'next-basics';
|
||||
import { useRouter } from 'next/router';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { Button, Icon, Item, Menu, Popup, Text } from 'react-basics';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import styles from './UserButton.module.css';
|
||||
import useDocumentClick from '../../hooks/useDocumentClick';
|
||||
|
||||
export default function UserButton() {
|
||||
const [show, setShow] = useState(false);
|
||||
const ref = useRef();
|
||||
const { user } = useUser();
|
||||
const router = useRouter();
|
||||
const { adminDisabled } = useConfig();
|
||||
|
|
@ -31,26 +33,48 @@ export default function UserButton() {
|
|||
label: <FormattedMessage id="label.profile" defaultMessage="Profile" />,
|
||||
value: 'profile',
|
||||
hidden: adminDisabled,
|
||||
divider: true,
|
||||
},
|
||||
{ label: <FormattedMessage id="label.logout" defaultMessage="Logout" />, value: 'logout' },
|
||||
];
|
||||
|
||||
function handleClick() {
|
||||
setShow(state => !state);
|
||||
}
|
||||
|
||||
function handleSelect(value) {
|
||||
if (value === 'logout') {
|
||||
removeItem(AUTH_TOKEN);
|
||||
router.push('/login');
|
||||
} else if (value === 'profile') {
|
||||
router.push('/settings/profile');
|
||||
router.push('/profile');
|
||||
}
|
||||
}
|
||||
|
||||
useDocumentClick(e => {
|
||||
if (!ref.current?.contains(e.target)) {
|
||||
setShow(false);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<MenuButton
|
||||
icon={<Icon icon={<User />} size="large" />}
|
||||
buttonVariant="light"
|
||||
options={menuOptions}
|
||||
onSelect={handleSelect}
|
||||
hideLabel
|
||||
/>
|
||||
<div className={styles.button} ref={ref}>
|
||||
<Button variant="light" onClick={handleClick}>
|
||||
<Icon className={styles.icon} size="large">
|
||||
<User />
|
||||
</Icon>
|
||||
</Button>
|
||||
{show && (
|
||||
<Popup className={styles.menu} position="bottom" gap={5}>
|
||||
<Menu items={menuOptions} onSelect={handleSelect}>
|
||||
{({ label, value }) => (
|
||||
<Item key={value}>
|
||||
<Text>{label}</Text>
|
||||
</Item>
|
||||
)}
|
||||
</Menu>
|
||||
</Popup>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
.button {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.username {
|
||||
border-bottom: 1px solid var(--base500);
|
||||
}
|
||||
|
|
@ -5,3 +9,18 @@
|
|||
.username:hover {
|
||||
background: var(--base50);
|
||||
}
|
||||
|
||||
.icon svg {
|
||||
font-size: 16px;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.menu {
|
||||
left: -50%;
|
||||
background: var(--base50);
|
||||
border: 1px solid var(--base500);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
z-index: 100;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,133 +0,0 @@
|
|||
import React, { useState } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import Link from 'next/link';
|
||||
import classNames from 'classnames';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import Button from 'components/common/Button';
|
||||
import Icon from 'components/common/Icon';
|
||||
import Table from 'components/common/Table';
|
||||
import Modal from 'components/common/Modal';
|
||||
import Toast from 'components/common/Toast';
|
||||
import UserEditForm from 'components/forms/UserEditForm';
|
||||
import ButtonLayout from 'components/layout/ButtonLayout';
|
||||
import DeleteForm from 'components/forms/DeleteForm';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import Pen from 'assets/pen.svg';
|
||||
import Plus from 'assets/plus.svg';
|
||||
import Trash from 'assets/trash.svg';
|
||||
import Check from 'assets/check.svg';
|
||||
import LinkIcon from 'assets/external-link.svg';
|
||||
import styles from './UserSettings.module.css';
|
||||
|
||||
export default function UserSettings() {
|
||||
const [addUser, setAddUser] = useState();
|
||||
const [editUser, setEditUser] = useState();
|
||||
const [deleteUser, setDeleteUser] = useState();
|
||||
const [saved, setSaved] = useState(0);
|
||||
const [message, setMessage] = useState();
|
||||
const { data } = useFetch(`/users`, {}, [saved]);
|
||||
|
||||
const Checkmark = ({ isAdmin }) => (isAdmin ? <Icon icon={<Check />} size="medium" /> : null);
|
||||
|
||||
const DashboardLink = row => {
|
||||
return (
|
||||
<Link href={`/dashboard/${row.id}/${row.username}`}>
|
||||
<a>
|
||||
<Icon icon={<LinkIcon />} />
|
||||
</a>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
const Buttons = row => (
|
||||
<ButtonLayout align="right">
|
||||
<Button icon={<Pen />} size="small" onClick={() => setEditUser(row)}>
|
||||
<FormattedMessage id="label.edit" defaultMessage="Edit" />
|
||||
</Button>
|
||||
{!row.isAdmin && (
|
||||
<Button icon={<Trash />} size="small" onClick={() => setDeleteUser(row)}>
|
||||
<FormattedMessage id="label.delete" defaultMessage="Delete" />
|
||||
</Button>
|
||||
)}
|
||||
</ButtonLayout>
|
||||
);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
key: 'username',
|
||||
label: <FormattedMessage id="label.username" defaultMessage="Username" />,
|
||||
className: 'col-12 col-lg-4',
|
||||
},
|
||||
{
|
||||
key: 'isAdmin',
|
||||
label: <FormattedMessage id="label.administrator" defaultMessage="Administrator" />,
|
||||
className: 'col-12 col-lg-3',
|
||||
render: Checkmark,
|
||||
},
|
||||
{
|
||||
key: 'dashboard',
|
||||
label: <FormattedMessage id="label.dashboard" defaultMessage="Dashboard" />,
|
||||
className: 'col-12 col-lg-3',
|
||||
render: DashboardLink,
|
||||
},
|
||||
{
|
||||
key: 'actions',
|
||||
className: classNames(styles.buttons, 'col-12 col-lg-2 pt-2 pt-md-0'),
|
||||
render: Buttons,
|
||||
},
|
||||
];
|
||||
|
||||
function handleSave() {
|
||||
setSaved(state => state + 1);
|
||||
setMessage(<FormattedMessage id="message.save-success" defaultMessage="Saved successfully." />);
|
||||
handleClose();
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
setEditUser(null);
|
||||
setAddUser(null);
|
||||
setDeleteUser(null);
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader>
|
||||
<div>
|
||||
<FormattedMessage id="label.users" defaultMessage="Users" />
|
||||
</div>
|
||||
<Button icon={<Plus />} size="small" onClick={() => setAddUser(true)}>
|
||||
<FormattedMessage id="label.add-user" defaultMessage="Add user" />
|
||||
</Button>
|
||||
</PageHeader>
|
||||
<Table columns={columns} rows={data} />
|
||||
{editUser && (
|
||||
<Modal title={<FormattedMessage id="label.edit-user" defaultMessage="Edit user" />}>
|
||||
<UserEditForm
|
||||
values={{ ...editUser, password: '' }}
|
||||
onSave={handleSave}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
{addUser && (
|
||||
<Modal title={<FormattedMessage id="label.add-user" defaultMessage="Add user" />}>
|
||||
<UserEditForm onSave={handleSave} onClose={handleClose} />
|
||||
</Modal>
|
||||
)}
|
||||
{deleteUser && (
|
||||
<Modal title={<FormattedMessage id="label.delete-user" defaultMessage="Delete user" />}>
|
||||
<DeleteForm
|
||||
values={{ type: 'users', id: deleteUser.id, name: deleteUser.username }}
|
||||
onSave={handleSave}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
{message && <Toast message={message} onClose={() => setMessage(null)} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
.buttons {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
flex: 1;
|
||||
}
|
||||
|
|
@ -1,232 +0,0 @@
|
|||
import React, { useState } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
import Link from 'components/common/Link';
|
||||
import Table from 'components/common/Table';
|
||||
import Button from 'components/common/Button';
|
||||
import OverflowText from 'components/common/OverflowText';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import Modal from 'components/common/Modal';
|
||||
import WebsiteEditForm from 'components/forms/WebsiteEditForm';
|
||||
import ResetForm from 'components/forms/ResetForm';
|
||||
import DeleteForm from 'components/forms/DeleteForm';
|
||||
import TrackingCodeForm from 'components/forms/TrackingCodeForm';
|
||||
import ShareUrlForm from 'components/forms/ShareUrlForm';
|
||||
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
|
||||
import ButtonLayout from 'components/layout/ButtonLayout';
|
||||
import Toast from 'components/common/Toast';
|
||||
import Favicon from 'components/common/Favicon';
|
||||
import Pen from 'assets/pen.svg';
|
||||
import Trash from 'assets/trash.svg';
|
||||
import Reset from 'assets/redo.svg';
|
||||
import Plus from 'assets/plus.svg';
|
||||
import Code from 'assets/code.svg';
|
||||
import LinkIcon from 'assets/link.svg';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import useUser from 'hooks/useUser';
|
||||
import styles from './WebsiteSettings.module.css';
|
||||
|
||||
export default function WebsiteSettings() {
|
||||
const { user } = useUser();
|
||||
const [editWebsite, setEditWebsite] = useState();
|
||||
const [resetWebsite, setResetWebsite] = useState();
|
||||
const [deleteWebsite, setDeleteWebsite] = useState();
|
||||
const [addWebsite, setAddWebsite] = useState();
|
||||
const [showCode, setShowCode] = useState();
|
||||
const [showUrl, setShowUrl] = useState();
|
||||
const [saved, setSaved] = useState(0);
|
||||
const [message, setMessage] = useState();
|
||||
|
||||
const { data } = useFetch('/websites', { params: { include_all: !!user?.isAdmin } }, [saved]);
|
||||
|
||||
const Buttons = row => (
|
||||
<ButtonLayout align="right">
|
||||
{row.shareId && (
|
||||
<Button
|
||||
icon={<LinkIcon />}
|
||||
size="small"
|
||||
tooltip={<FormattedMessage id="message.get-share-url" defaultMessage="Get share URL" />}
|
||||
tooltipId={`button-share-${row.id}`}
|
||||
onClick={() => setShowUrl(row)}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
icon={<Code />}
|
||||
size="small"
|
||||
tooltip={
|
||||
<FormattedMessage id="message.get-tracking-code" defaultMessage="Get tracking code" />
|
||||
}
|
||||
tooltipId={`button-code-${row.id}`}
|
||||
onClick={() => setShowCode(row)}
|
||||
/>
|
||||
<Button
|
||||
icon={<Pen />}
|
||||
size="small"
|
||||
tooltip={<FormattedMessage id="label.edit" defaultMessage="Edit" />}
|
||||
tooltipId={`button-edit-${row.id}`}
|
||||
onClick={() => setEditWebsite(row)}
|
||||
/>
|
||||
<Button
|
||||
icon={<Reset />}
|
||||
size="small"
|
||||
tooltip={<FormattedMessage id="label.reset" defaultMessage="Reset" />}
|
||||
tooltipId={`button-reset-${row.id}`}
|
||||
onClick={() => setResetWebsite(row)}
|
||||
/>
|
||||
<Button
|
||||
icon={<Trash />}
|
||||
size="small"
|
||||
tooltip={<FormattedMessage id="label.delete" defaultMessage="Delete" />}
|
||||
tooltipId={`button-delete-${row.id}`}
|
||||
onClick={() => setDeleteWebsite(row)}
|
||||
/>
|
||||
</ButtonLayout>
|
||||
);
|
||||
|
||||
const DetailsLink = ({ id, name, domain }) => (
|
||||
<Link className={styles.detailLink} href="/websites/[...id]" as={`/websites/${id}/${name}`}>
|
||||
<Favicon domain={domain} />
|
||||
<OverflowText tooltipId={`${id}-name`}>{name}</OverflowText>
|
||||
</Link>
|
||||
);
|
||||
|
||||
const Domain = ({ domain, id }) => (
|
||||
<OverflowText tooltipId={`${id}-domain`}>{domain}</OverflowText>
|
||||
);
|
||||
|
||||
const adminColumns = [
|
||||
{
|
||||
key: 'name',
|
||||
label: <FormattedMessage id="label.name" defaultMessage="Name" />,
|
||||
className: 'col-12 col-lg-4 col-xl-3',
|
||||
render: DetailsLink,
|
||||
},
|
||||
{
|
||||
key: 'domain',
|
||||
label: <FormattedMessage id="label.domain" defaultMessage="Domain" />,
|
||||
className: 'col-12 col-lg-4 col-xl-3',
|
||||
render: Domain,
|
||||
},
|
||||
{
|
||||
key: 'user',
|
||||
label: <FormattedMessage id="label.owner" defaultMessage="Owner" />,
|
||||
className: 'col-12 col-lg-4 col-xl-1',
|
||||
},
|
||||
{
|
||||
key: 'action',
|
||||
className: classNames(styles.buttons, 'col-12 col-xl-5 pt-2 pt-xl-0'),
|
||||
render: Buttons,
|
||||
},
|
||||
];
|
||||
|
||||
const columns = [
|
||||
{
|
||||
key: 'name',
|
||||
label: <FormattedMessage id="label.name" defaultMessage="Name" />,
|
||||
className: 'col-12 col-lg-6 col-xl-4',
|
||||
render: DetailsLink,
|
||||
},
|
||||
{
|
||||
key: 'domain',
|
||||
label: <FormattedMessage id="label.domain" defaultMessage="Domain" />,
|
||||
className: 'col-12 col-lg-6 col-xl-4',
|
||||
render: Domain,
|
||||
},
|
||||
{
|
||||
key: 'action',
|
||||
className: classNames(styles.buttons, 'col-12 col-xl-4 pt-2 pt-xl-0'),
|
||||
render: Buttons,
|
||||
},
|
||||
];
|
||||
|
||||
function handleSave() {
|
||||
setSaved(state => state + 1);
|
||||
setMessage(<FormattedMessage id="message.save-success" defaultMessage="Saved successfully." />);
|
||||
handleClose();
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
setAddWebsite(null);
|
||||
setEditWebsite(null);
|
||||
setResetWebsite(null);
|
||||
setDeleteWebsite(null);
|
||||
setShowCode(null);
|
||||
setShowUrl(null);
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const empty = (
|
||||
<EmptyPlaceholder
|
||||
msg={
|
||||
<FormattedMessage
|
||||
id="message.no-websites-configured"
|
||||
defaultMessage="You don't have any websites configured."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Button icon={<Plus />} size="medium" onClick={() => setAddWebsite(true)}>
|
||||
<FormattedMessage id="label.add-website" defaultMessage="Add website" />
|
||||
</Button>
|
||||
</EmptyPlaceholder>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader>
|
||||
<div>
|
||||
<FormattedMessage id="label.websites" defaultMessage="Websites" />
|
||||
</div>
|
||||
<Button icon={<Plus />} size="small" onClick={() => setAddWebsite(true)}>
|
||||
<FormattedMessage id="label.add-website" defaultMessage="Add website" />
|
||||
</Button>
|
||||
</PageHeader>
|
||||
<Table columns={user.isAdmin ? adminColumns : columns} rows={data} empty={empty} />
|
||||
{editWebsite && (
|
||||
<Modal title={<FormattedMessage id="label.edit-website" defaultMessage="Edit website" />}>
|
||||
<WebsiteEditForm values={editWebsite} onSave={handleSave} onClose={handleClose} />
|
||||
</Modal>
|
||||
)}
|
||||
{addWebsite && (
|
||||
<Modal title={<FormattedMessage id="label.add-website" defaultMessage="Add website" />}>
|
||||
<WebsiteEditForm onSave={handleSave} onClose={handleClose} />
|
||||
</Modal>
|
||||
)}
|
||||
{resetWebsite && (
|
||||
<Modal
|
||||
title={<FormattedMessage id="label.reset-website" defaultMessage="Reset statistics" />}
|
||||
>
|
||||
<ResetForm
|
||||
values={{ type: 'websites', id: resetWebsite.id, name: resetWebsite.name }}
|
||||
onSave={handleSave}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
{deleteWebsite && (
|
||||
<Modal
|
||||
title={<FormattedMessage id="label.delete-website" defaultMessage="Delete website" />}
|
||||
>
|
||||
<DeleteForm
|
||||
values={{ type: 'websites', id: deleteWebsite.id, name: deleteWebsite.name }}
|
||||
onSave={handleSave}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
{showCode && (
|
||||
<Modal title={<FormattedMessage id="label.tracking-code" defaultMessage="Tracking code" />}>
|
||||
<TrackingCodeForm values={showCode} onClose={handleClose} />
|
||||
</Modal>
|
||||
)}
|
||||
{showUrl && (
|
||||
<Modal title={<FormattedMessage id="label.share-url" defaultMessage="Share URL" />}>
|
||||
<ShareUrlForm values={showUrl} onClose={handleClose} />
|
||||
</Modal>
|
||||
)}
|
||||
{message && <Toast message={message} onClose={() => setMessage(null)} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
.col {
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.detailLink {
|
||||
width: 100%;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue