Updated settings components and date filter.

This commit is contained in:
Mike Cao 2023-01-10 17:18:59 -08:00
parent 70ef857dc7
commit 4b5b4db108
16 changed files with 172 additions and 194 deletions

View file

@ -1,99 +1,109 @@
import Calendar from 'assets/calendar-alt.svg';
import DatePickerForm from 'components/metrics/DatePickerForm';
import { endOfYear, isSameDay } from 'date-fns';
import { useState } from 'react';
import { Icon, Modal, Dropdown, Item } from 'react-basics';
import { useIntl, defineMessages } from 'react-intl';
import DatePickerForm from 'components/metrics/DatePickerForm';
import useLocale from 'hooks/useLocale';
import { dateFormat } from 'lib/date';
import PropTypes from 'prop-types';
import { useState } from 'react';
import { Icon, Modal } from 'react-basics';
import { FormattedMessage } from 'react-intl';
import DropDown from './DropDown';
import Calendar from 'assets/calendar-alt.svg';
export const filterOptions = [
{ label: <FormattedMessage id="label.today" defaultMessage="Today" />, value: '1day' },
{
label: (
<FormattedMessage id="label.last-hours" defaultMessage="Last {x} hours" values={{ x: 24 }} />
),
value: '24hour',
},
{
label: <FormattedMessage id="label.yesterday" defaultMessage="Yesterday" />,
value: '-1day',
},
{
label: <FormattedMessage id="label.this-week" defaultMessage="This week" />,
value: '1week',
divider: true,
},
{
label: (
<FormattedMessage id="label.last-days" defaultMessage="Last {x} days" values={{ x: 7 }} />
),
value: '7day',
},
{
label: <FormattedMessage id="label.this-month" defaultMessage="This month" />,
value: '1month',
divider: true,
},
{
label: (
<FormattedMessage id="label.last-days" defaultMessage="Last {x} days" values={{ x: 30 }} />
),
value: '30day',
},
{
label: (
<FormattedMessage id="label.last-days" defaultMessage="Last {x} days" values={{ x: 90 }} />
),
value: '90day',
},
{ label: <FormattedMessage id="label.this-year" defaultMessage="This year" />, value: '1year' },
{
label: <FormattedMessage id="label.all-time" defaultMessage="All time" />,
value: 'all',
divider: true,
},
{
label: <FormattedMessage id="label.custom-range" defaultMessage="Custom range" />,
value: 'custom',
divider: true,
},
];
const messages = defineMessages({
today: { id: 'label.today', defaultMessage: 'Today' },
lastHours: { id: 'label.last-hours', defaultMessage: 'Last {x} hours' },
yesterday: { id: 'label.yesterday', defaultMessage: 'Yesterday' },
thisWeek: { id: 'label.this-week', defaultMessage: 'This week' },
lastDays: { id: 'label.last-days', defaultMessage: 'Last {x} days' },
thisMonth: { id: 'label.this-month', defaultMessage: 'This month' },
thisYear: { id: 'label.this-year', defaultMessage: 'This year' },
allTime: { id: 'label.all-time', defaultMessage: 'All time' },
customRange: { id: 'label.custom-range', defaultMessage: 'Custom-range' },
});
function DateFilter({ value, startDate, endDate, onChange, className, options }) {
function DateFilter({ value, startDate, endDate, onChange, className }) {
const { formatMessage } = useIntl();
const [showPicker, setShowPicker] = useState(false);
const displayValue =
value === 'custom' ? (
const options = [
{ label: formatMessage(messages.today), value: '1day' },
{
label: formatMessage(messages.lastHours, { x: 24 }),
value: '24hour',
},
{
label: formatMessage(messages.yesterday),
value: '-1day',
},
{
label: formatMessage(messages.thisWeek),
value: '1week',
divider: true,
},
{
label: formatMessage(messages.lastDays, { x: 7 }),
value: '7day',
},
{
label: formatMessage(messages.thisMonth),
value: '1month',
divider: true,
},
{
label: formatMessage(messages.lastDays, { x: 30 }),
value: '30day',
},
{
label: formatMessage(messages.lastDays, { x: 90 }),
value: '90day',
},
{ label: formatMessage(messages.thisYear), value: '1year' },
{
label: formatMessage(messages.allTime),
value: 'all',
divider: true,
},
{
label: formatMessage(messages.customRange),
value: 'custom',
divider: true,
},
];
const renderValue = value => {
return value === 'custom' ? (
<CustomRange startDate={startDate} endDate={endDate} onClick={() => handleChange('custom')} />
) : (
value
options.find(e => e.value === value).label
);
};
async function handleChange(value) {
const handleChange = async value => {
if (value === 'custom') {
setShowPicker(true);
return;
}
onChange(value);
}
};
function handlePickerChange(value) {
const handlePickerChange = value => {
setShowPicker(false);
onChange(value);
}
};
const handleClose = () => setShowPicker(false);
return (
<>
<DropDown
<Dropdown
className={className}
value={displayValue}
options={options || filterOptions}
items={options}
renderValue={renderValue}
value={value}
onChange={handleChange}
/>
>
{({ label, value }) => <Item key={value}>{label}</Item>}
</Dropdown>
{showPicker && (
<Modal>
<Modal onClose={handleClose}>
<DatePickerForm
startDate={startDate}
endDate={endDate}
@ -128,12 +138,4 @@ const CustomRange = ({ startDate, endDate, onClick }) => {
);
};
DateFilter.propTypes = {
value: PropTypes.string,
startDate: PropTypes.instanceOf(Date),
endDate: PropTypes.instanceOf(Date),
onChange: PropTypes.func,
className: PropTypes.string,
};
export default DateFilter;

View file

@ -66,8 +66,9 @@ export default function WebsiteChart({
async function handleDateChange(value) {
if (value === 'all') {
const { data, ok } = await get(`/websites/${websiteId}`);
if (ok) {
const data = await get(`/websites/${websiteId}`);
if (data) {
setDateRange({ value, ...getDateRangeValues(new Date(data.createdAt), Date.now()) });
}
} else {

View file

@ -1,12 +1,13 @@
import User from 'assets/user.svg';
import Team from 'assets/users.svg';
import Website from 'assets/website.svg';
import classNames from 'classnames';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { Icon, Item, Menu, Text } from 'react-basics';
import styles from './Nav.module.css';
import useUser from 'hooks/useUser';
import User from 'assets/user.svg';
import Team from 'assets/users.svg';
import Website from 'assets/website.svg';
import Profile from 'assets/profile.svg';
import styles from './Nav.module.css';
export default function Nav() {
const user = useUser();
@ -22,7 +23,7 @@ export default function Nav() {
{ icon: <Website />, label: 'Websites', url: '/settings/websites' },
{ icon: <User />, label: 'Users', url: '/settings/users' },
{ icon: <Team />, label: 'Teams', url: '/settings/teams' },
{ icon: <User />, label: 'Profile', url: '/settings/profile' },
{ icon: <Profile />, label: 'Profile', url: '/settings/profile' },
];
return (

View file

@ -27,7 +27,7 @@
}
.item a {
color: var(--base700);
color: var(--base600);
display: flex;
align-items: center;
gap: 20px;
@ -42,5 +42,4 @@
.item.selected a {
color: var(--base900);
background: var(--base100);
}

View file

@ -2,13 +2,12 @@ import Page from 'components/layout/Page';
import PageHeader from 'components/layout/PageHeader';
import ProfileDetails from 'components/settings/ProfileDetails';
import { useState } from 'react';
import { Breadcrumbs, Icon, Item, Tabs, useToast, Modal, Button } from 'react-basics';
import { Breadcrumbs, Icon, Item, useToast, Modal, Button } from 'react-basics';
import UserPasswordForm from 'components/pages/settings/users/UserPasswordForm';
import Pen from 'assets/pen.svg';
import Lock from 'assets/lock.svg';
export default function ProfileSettings() {
const [edit, setEdit] = useState(false);
const [tab, setTab] = useState('general');
const { toast, showToast } = useToast();
const handleSave = () => {
@ -33,17 +32,14 @@ export default function ProfileSettings() {
</Breadcrumbs>
<Button onClick={handleAdd}>
<Icon>
<Pen />
<Lock />
</Icon>
Change Password
</Button>
</PageHeader>
<Tabs selectedKey={tab} onSelect={setTab} style={{ marginBottom: 30, fontSize: 14 }}>
<Item key="general">General</Item>
</Tabs>
{tab === 'general' && <ProfileDetails />}
<ProfileDetails />
{edit && (
<Modal title="Add website" onClose={handleClose}>
<Modal title="Change password" onClose={handleClose}>
{close => <UserPasswordForm onSave={handleSave} onClose={close} />}
</Modal>
)}

View file

@ -12,7 +12,6 @@ import { useRef } from 'react';
import { useMutation } from '@tanstack/react-query';
import useApi from 'hooks/useApi';
import { ROLES } from 'lib/constants';
import styles from './UserForm.module.css';
const items = [
{
@ -41,14 +40,7 @@ export default function UserEditForm({ data, onSave }) {
};
return (
<Form
key={id}
className={styles.form}
ref={ref}
onSubmit={handleSubmit}
error={error}
values={data}
>
<Form key={id} ref={ref} onSubmit={handleSubmit} error={error} values={data}>
<FormRow label="Username">
<FormInput name="username">
<TextField />

View file

@ -1,30 +1,28 @@
import { FormattedMessage } from 'react-intl';
import DropDown from 'components/common/DropDown';
import { Button } from 'react-basics';
import { useIntl, defineMessages } from 'react-intl';
import { Button, Dropdown, Item, Flexbox } from 'react-basics';
import useLocale from 'hooks/useLocale';
import { DEFAULT_LOCALE } from 'lib/constants';
import styles from './TimezoneSetting.module.css';
import { languages } from 'lib/lang';
const messages = defineMessages({
reset: { id: 'label.reset', defaultMessage: 'Reset' },
});
export default function LanguageSetting() {
const { formatMessage } = useIntl();
const { locale, saveLocale } = useLocale();
const options = Object.keys(languages).map(key => ({ ...languages[key], value: key }));
const options = Object.keys(languages);
function handleReset() {
saveLocale(DEFAULT_LOCALE);
}
return (
<>
<DropDown
menuClassName={styles.menu}
value={locale}
options={options}
onChange={saveLocale}
/>
<Button className={styles.button} size="sm" onClick={handleReset}>
<FormattedMessage id="label.reset" defaultMessage="Reset" />
</Button>
</>
<Flexbox gap={10} style={{ width: 400 }}>
<Dropdown items={options} value={locale} onChange={saveLocale}>
{item => <Item key={item}>{languages[item].label}</Item>}
</Dropdown>
<Button onClick={handleReset}>{formatMessage(messages.reset)}</Button>
</Flexbox>
);
}

View file

@ -1,53 +1,46 @@
import { Form, FormRow } from 'react-basics';
import { useIntl, defineMessages } from 'react-intl';
import TimezoneSetting from 'components/settings/TimezoneSetting';
import DateRangeSetting from 'components/settings/DateRangeSetting';
import LanguageSetting from 'components/settings/LanguageSetting';
import ThemeSetting from 'components/settings/ThemeSetting';
import useUser from 'hooks/useUser';
import { FormattedMessage } from 'react-intl';
import DateRangeSetting from './DateRangeSetting';
import LanguageSetting from './LanguageSetting';
import styles from './ProfileDetails.module.css';
import ThemeSetting from './ThemeSetting';
const messages = defineMessages({
username: { id: 'label.username', defaultMessage: 'Username' },
role: { id: 'label.role', defaultMessage: 'Role' },
timezone: { id: 'label.timezone', defaultMessage: 'Timezone' },
dateRange: { id: 'label.default-date-range', defaultMessage: 'Default date range' },
language: { id: 'label.language', defaultMessage: 'Language' },
theme: { id: 'label.theme', defaultMessage: 'Theme' },
});
export default function ProfileDetails() {
const user = useUser();
const { formatMessage } = useIntl();
if (!user) {
return null;
}
const { username } = user;
const { username, role } = 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>
</>
<Form>
<FormRow label={formatMessage(messages.username)}>{username}</FormRow>
<FormRow label={formatMessage(messages.role)}>{role}</FormRow>
<FormRow label={formatMessage(messages.language)} inline>
<LanguageSetting />
</FormRow>
<FormRow label={formatMessage(messages.timezone)} inline>
<TimezoneSetting />
</FormRow>
<FormRow label={formatMessage(messages.dateRange)} inline>
<DateRangeSetting />
</FormRow>
<FormRow label={formatMessage(messages.theme)}>
<ThemeSetting />
</FormRow>
</Form>
);
}

View file

@ -1,14 +1,17 @@
import { FormattedMessage } from 'react-intl';
import { Dropdown, Item, Button } from 'react-basics';
import { useIntl, defineMessages } from 'react-intl';
import { listTimeZones } from 'timezone-support';
import DropDown from 'components/common/DropDown';
import { Button } from 'react-basics';
import useTimezone from 'hooks/useTimezone';
import { getTimezone } from 'lib/date';
import styles from './TimezoneSetting.module.css';
const messages = defineMessages({
reset: { id: 'label.reset', defaultMessage: 'Reset' },
});
export default function TimezoneSetting() {
const { formatMessage } = useIntl();
const [timezone, saveTimezone] = useTimezone();
const options = listTimeZones().map(n => ({ label: n, value: n }));
const options = listTimeZones();
function handleReset() {
saveTimezone(getTimezone());
@ -16,15 +19,10 @@ export default function TimezoneSetting() {
return (
<>
<DropDown
menuClassName={styles.menu}
value={timezone}
options={options}
onChange={saveTimezone}
/>
<Button className={styles.button} size="sm" onClick={handleReset}>
<FormattedMessage id="label.reset" defaultMessage="Reset" />
</Button>
<Dropdown items={options} value={timezone} onChange={saveTimezone}>
{item => <Item key={item}>{item}</Item>}
</Dropdown>
<Button onClick={handleReset}>{formatMessage(messages.reset)}</Button>
</>
);
}

View file

@ -1,8 +0,0 @@
.menu {
max-height: 300px;
overflow-y: auto;
}
.button {
margin-left: 10px;
}

View file

@ -1,4 +1,3 @@
import User from 'assets/user.svg';
import useConfig from 'hooks/useConfig';
import useUser from 'hooks/useUser';
import { AUTH_TOKEN } from 'lib/constants';
@ -7,8 +6,9 @@ import { useRouter } from 'next/router';
import { useRef, useState } from 'react';
import { Button, Icon, Item, Menu, Popup, Text } from 'react-basics';
import { FormattedMessage } from 'react-intl';
import useDocumentClick from 'hooks/useDocumentClick';
import Profile from 'assets/profile.svg';
import styles from './UserButton.module.css';
import useDocumentClick from '../../hooks/useDocumentClick';
export default function UserButton() {
const [show, setShow] = useState(false);
@ -61,7 +61,7 @@ export default function UserButton() {
<div className={styles.button} ref={ref}>
<Button variant="light" onClick={handleClick}>
<Icon className={styles.icon} size="large">
<User />
<Profile />
</Icon>
</Button>
{show && (