diff --git a/README.md b/README.md index a241d409..397dcdbf 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,6 @@ Or with MySQL support: docker pull ghcr.io/mikecao/umami:mysql-latest ``` - ## Getting updates To get the latest features, simply do a pull, install any new dependencies, and rebuild: diff --git a/assets/calendar-alt.svg b/assets/calendar-alt.svg new file mode 100644 index 00000000..230c4e66 --- /dev/null +++ b/assets/calendar-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/list-ul.svg b/assets/list-ul.svg new file mode 100644 index 00000000..5e632126 --- /dev/null +++ b/assets/list-ul.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/components/WebsiteDetails.js b/components/WebsiteDetails.js index eed69296..3a933638 100644 --- a/components/WebsiteDetails.js +++ b/components/WebsiteDetails.js @@ -28,6 +28,7 @@ export default function WebsiteDetails({ websiteId }) { const BackButton = () => ( - {showMenu && ( - - )} - + <> + + {locale === 'zh-CN' && ( + + )} + {locale === 'ja-JP' && ( + + )} + +
+ + {showMenu && ( + + )} +
+ ); } diff --git a/components/common/Menu.js b/components/common/Menu.js index 4cb5a885..283ee1fb 100644 --- a/components/common/Menu.js +++ b/components/common/Menu.js @@ -25,7 +25,7 @@ export default function Menu({ {options .filter(({ hidden }) => !hidden) .map(option => { - const { label, value, className: customClassName, render } = option; + const { label, value, className: customClassName, render, divider } = option; return render ? ( render(option) @@ -34,6 +34,7 @@ export default function Menu({ key={value} className={classNames(styles.option, optionClassName, customClassName, { [selectedClassName]: selectedOption === value, + [styles.divider]: divider, })} onClick={e => onSelect(value, e)} > diff --git a/components/common/Menu.module.css b/components/common/Menu.module.css index be394b71..9bcd642f 100644 --- a/components/common/Menu.module.css +++ b/components/common/Menu.module.css @@ -40,3 +40,7 @@ .right { right: 0; } + +.divider { + border-top: 1px solid var(--gray300); +} diff --git a/components/common/Modal.module.css b/components/common/Modal.module.css index 9ca0d778..3702e774 100644 --- a/components/common/Modal.module.css +++ b/components/common/Modal.module.css @@ -1,5 +1,5 @@ .modal { - position: absolute; + position: fixed; top: 0; left: 0; right: 0; @@ -28,6 +28,7 @@ background: var(--gray50); min-width: 400px; min-height: 100px; + max-width: 100vw; z-index: 1; border: 1px solid var(--gray300); padding: 30px; diff --git a/components/forms/DatePickerForm.js b/components/forms/DatePickerForm.js new file mode 100644 index 00000000..8ead373d --- /dev/null +++ b/components/forms/DatePickerForm.js @@ -0,0 +1,41 @@ +import React, { useState } from 'react'; +import { FormattedMessage } from 'react-intl'; +import { isAfter } from 'date-fns'; +import Calendar from 'components/common/Calendar'; +import Button from 'components/common/Button'; +import { FormButtons } from 'components/layout/FormLayout'; +import { getDateRangeValues } from 'lib/date'; +import styles from './DatePickerForm.module.css'; + +export default function DatePickerForm({ + startDate: defaultStartDate, + endDate: defaultEndDate, + minDate, + maxDate, + onChange, + onClose, +}) { + const [startDate, setStartDate] = useState(defaultStartDate); + const [endDate, setEndDate] = useState(defaultEndDate); + + function handleSave() { + onChange({ ...getDateRangeValues(startDate, endDate), value: 'custom' }); + } + + return ( +
+
+ + +
+ + + + +
+ ); +} diff --git a/components/forms/DatePickerForm.module.css b/components/forms/DatePickerForm.module.css new file mode 100644 index 00000000..dcedc17a --- /dev/null +++ b/components/forms/DatePickerForm.module.css @@ -0,0 +1,25 @@ +.container { + display: flex; + flex-direction: column; + width: 800px; + max-width: 100vw; +} + +.calendars { + display: flex; +} + +.calendars > div:first-child { + padding-right: 20px; + border-right: 1px solid var(--gray300); +} + +.calendars > div:last-child { + padding-left: 20px; +} + +@media only screen and (max-width: 768px) { + .calendars { + flex-direction: column; + } +} diff --git a/components/forms/DeleteForm.js b/components/forms/DeleteForm.js index 1ba81626..f53b286f 100644 --- a/components/forms/DeleteForm.js +++ b/components/forms/DeleteForm.js @@ -10,10 +10,12 @@ import FormLayout, { } from 'components/layout/FormLayout'; import { FormattedMessage } from 'react-intl'; +const CONFIRMATION_WORD = 'DELETE'; + const validate = ({ confirmation }) => { const errors = {}; - if (confirmation !== 'DELETE') { + if (confirmation !== CONFIRMATION_WORD) { errors.confirmation = !confirmation ? ( ) : ( @@ -44,7 +46,7 @@ export default function DeleteForm({ values, onSave, onClose }) { validate={validate} onSubmit={handleSubmit} > - {() => ( + {props => (
DELETE }} + values={{ delete: {CONFIRMATION_WORD} }} />

@@ -71,7 +73,11 @@ export default function DeleteForm({ values, onSave, onClose }) { -
@@ -74,7 +78,6 @@ export default function WebsiteChart({ unit={unit} records={getDateLength(startDate, endDate, unit)} /> -
diff --git a/components/metrics/WebsiteChart.module.css b/components/metrics/WebsiteChart.module.css index 3bd9db19..ea0fcaee 100644 --- a/components/metrics/WebsiteChart.module.css +++ b/components/metrics/WebsiteChart.module.css @@ -29,3 +29,15 @@ border-bottom: 1px solid var(--gray300); z-index: 3; } + +.filter { + display: flex; + justify-content: flex-end; + align-items: center; +} + +@media only screen and (max-width: 992px) { + .filter { + display: block; + } +} diff --git a/components/settings/AccountSettings.js b/components/settings/AccountSettings.js index 63f16350..21ca04f5 100644 --- a/components/settings/AccountSettings.js +++ b/components/settings/AccountSettings.js @@ -1,10 +1,13 @@ import React, { useState } from 'react'; +import { FormattedMessage } from 'react-intl'; +import { useRouter } from 'next/router'; 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 AccountEditForm from 'components/forms/AccountEditForm'; import ButtonLayout from 'components/layout/ButtonLayout'; import DeleteForm from 'components/forms/DeleteForm'; @@ -13,11 +16,11 @@ 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 List from 'assets/list-ul.svg'; import styles from './AccountSettings.module.css'; -import Toast from '../common/Toast'; -import { FormattedMessage } from 'react-intl'; export default function AccountSettings() { + const router = useRouter(); const [addAccount, setAddAccount] = useState(); const [editAccount, setEditAccount] = useState(); const [deleteAccount, setDeleteAccount] = useState(); @@ -30,6 +33,13 @@ export default function AccountSettings() { const Buttons = row => row.username !== 'admin' ? ( +