diff --git a/README.md b/README.md index 70576b8c..1adfbfb3 100644 --- a/README.md +++ b/README.md @@ -72,13 +72,13 @@ docker compose up Alternatively, to pull just the Umami Docker image with PostgreSQL support: ```bash -docker pull docker.umami.is/umami-software/umami:postgresql-latest +docker pull docker.umami.dev/umami-software/umami:postgresql-latest ``` Or with MySQL support: ```bash -docker pull docker.umami.is/umami-software/umami:mysql-latest +docker pull docker.umami.dev/umami-software/umami:mysql-latest ``` ## Getting updates diff --git a/assets/add-user.svg b/assets/add-user.svg new file mode 100644 index 00000000..9d0544c6 --- /dev/null +++ b/assets/add-user.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/arrow-right.svg b/assets/arrow-right.svg deleted file mode 100644 index efc5d74a..00000000 --- a/assets/arrow-right.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/arrow-up-right-from-square.svg b/assets/arrow-up-right-from-square.svg deleted file mode 100644 index 8f6de672..00000000 --- a/assets/arrow-up-right-from-square.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/buoy.svg b/assets/buoy.svg deleted file mode 100644 index 847a814e..00000000 --- a/assets/buoy.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/calendar-alt.svg b/assets/calendar.svg similarity index 100% rename from assets/calendar-alt.svg rename to assets/calendar.svg diff --git a/assets/card.svg b/assets/card.svg deleted file mode 100644 index ecda0e46..00000000 --- a/assets/card.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/chart-bar.svg b/assets/chart-bar.svg deleted file mode 100644 index 36820b76..00000000 --- a/assets/chart-bar.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/check.svg b/assets/check.svg deleted file mode 100644 index 65810c19..00000000 --- a/assets/check.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/chevron-down.svg b/assets/chevron-down.svg deleted file mode 100644 index 69add632..00000000 --- a/assets/chevron-down.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/clock.svg b/assets/clock.svg new file mode 100644 index 00000000..9c2a9a41 --- /dev/null +++ b/assets/clock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/code.svg b/assets/code.svg deleted file mode 100644 index 0f8e0814..00000000 --- a/assets/code.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/dashboard.svg b/assets/dashboard.svg new file mode 100644 index 00000000..11859d28 --- /dev/null +++ b/assets/dashboard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/ellipsis-h.svg b/assets/ellipsis-h.svg deleted file mode 100644 index 5bb08359..00000000 --- a/assets/ellipsis-h.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/exclamation-triangle.svg b/assets/exclamation-triangle.svg deleted file mode 100644 index 46bef5bc..00000000 --- a/assets/exclamation-triangle.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/external-link.svg b/assets/external-link.svg deleted file mode 100644 index e24896b0..00000000 --- a/assets/external-link.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/list-ul.svg b/assets/list-ul.svg deleted file mode 100644 index 5e632126..00000000 --- a/assets/list-ul.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/lock.svg b/assets/lock.svg new file mode 100644 index 00000000..c13fb7c7 --- /dev/null +++ b/assets/lock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/pen.svg b/assets/pen.svg deleted file mode 100644 index b2979420..00000000 --- a/assets/pen.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/plus.svg b/assets/plus.svg deleted file mode 100644 index e4774d87..00000000 --- a/assets/plus.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/profile.svg b/assets/profile.svg new file mode 100644 index 00000000..133b1bc1 --- /dev/null +++ b/assets/profile.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/sun.svg b/assets/sun.svg index 3e487291..c9654776 100644 --- a/assets/sun.svg +++ b/assets/sun.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/times.svg b/assets/times.svg deleted file mode 100644 index 261bb277..00000000 --- a/assets/times.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/trash.svg b/assets/trash.svg deleted file mode 100644 index 2f525c8f..00000000 --- a/assets/trash.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/user.svg b/assets/user.svg index 62df2c42..a75cbb8d 100644 --- a/assets/user.svg +++ b/assets/user.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/xmark.svg b/assets/xmark.svg deleted file mode 100644 index 83bd5740..00000000 --- a/assets/xmark.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/components/buttons/LanguageButton.js b/components/buttons/LanguageButton.js new file mode 100644 index 00000000..687f639f --- /dev/null +++ b/components/buttons/LanguageButton.js @@ -0,0 +1,51 @@ +import { Icon, Button, PopupTrigger, Popup, Tooltip, Text } from 'react-basics'; +import { useIntl } from 'react-intl'; +import classNames from 'classnames'; +import { languages } from 'lib/lang'; +import useLocale from 'hooks/useLocale'; +import Icons from 'components/icons'; +import { labels } from 'components/messages'; +import styles from './LanguageButton.module.css'; + +export default function LanguageButton({ tooltipPosition = 'top' }) { + const { formatMessage } = useIntl(); + const { locale, saveLocale } = useLocale(); + const items = Object.keys(languages).map(key => ({ ...languages[key], value: key })); + + function handleSelect(value) { + saveLocale(value); + } + + return ( + + + + {formatMessage(labels.language)} + + +
+ {items.map(({ value, label }) => { + return ( +
+ {label} + {value === locale && ( + + + + )} +
+ ); + })} +
+
+
+ ); +} diff --git a/components/buttons/LanguageButton.module.css b/components/buttons/LanguageButton.module.css new file mode 100644 index 00000000..bf51b571 --- /dev/null +++ b/components/buttons/LanguageButton.module.css @@ -0,0 +1,35 @@ +.menu { + display: flex; + flex-flow: row wrap; + min-width: 600px; + max-width: 100vw; + padding: 10px; + background: var(--base50); + z-index: var(--z-index100); + border-radius: 5px; + border: 1px solid var(--border-color); + margin-left: 10px; +} + +.item { + display: flex; + align-items: center; + justify-content: space-between; + min-width: calc(100% / 3); + border-radius: 5px; + padding: 5px 10px; +} + +.item:hover { + background: var(--base75); + cursor: pointer; +} + +.selected { + font-weight: 700; + background: var(--blue100); +} + +.icon { + color: var(--primary400); +} diff --git a/components/buttons/LogoutButton.js b/components/buttons/LogoutButton.js new file mode 100644 index 00000000..f6f2450e --- /dev/null +++ b/components/buttons/LogoutButton.js @@ -0,0 +1,22 @@ +import { Button, Icon, Icons, PopupTrigger, Tooltip } from 'react-basics'; +import Link from 'next/link'; +import { labels } from 'components/messages'; +import { useIntl } from 'react-intl'; + +export default function LogoutButton({ tooltipPosition = 'top' }) { + const { formatMessage } = useIntl(); + return ( + + + + + {formatMessage(labels.logout)} + + + + ); +} diff --git a/components/settings/SettingsButton.js b/components/buttons/SettingsButton.js similarity index 89% rename from components/settings/SettingsButton.js rename to components/buttons/SettingsButton.js index 596b36b9..7c2a2ae2 100644 --- a/components/settings/SettingsButton.js +++ b/components/buttons/SettingsButton.js @@ -1,7 +1,7 @@ import { useRef, useState } from 'react'; import { FormattedMessage } from 'react-intl'; -import TimezoneSetting from './TimezoneSetting'; -import DateRangeSetting from './DateRangeSetting'; +import TimezoneSetting from '../pages/settings/profile/TimezoneSetting'; +import DateRangeSetting from '../pages/settings/profile/DateRangeSetting'; import { Button, Icon } from 'react-basics'; import styles from './SettingsButton.module.css'; import Gear from 'assets/gear.svg'; diff --git a/components/settings/SettingsButton.module.css b/components/buttons/SettingsButton.module.css similarity index 100% rename from components/settings/SettingsButton.module.css rename to components/buttons/SettingsButton.module.css diff --git a/components/buttons/ThemeButton.js b/components/buttons/ThemeButton.js new file mode 100644 index 00000000..4af80882 --- /dev/null +++ b/components/buttons/ThemeButton.js @@ -0,0 +1,42 @@ +import { useTransition, animated } from 'react-spring'; +import { Button, Icon, PopupTrigger, Tooltip } from 'react-basics'; +import { useIntl } from 'react-intl'; +import useTheme from 'hooks/useTheme'; +import Icons from 'components/icons'; +import { labels } from 'components/messages'; +import styles from './ThemeButton.module.css'; + +export default function ThemeButton({ tooltipPosition = 'top' }) { + const [theme, setTheme] = useTheme(); + const { formatMessage } = useIntl(); + + const transitions = useTransition(theme, { + initial: { opacity: 1 }, + from: { + opacity: 0, + transform: `translateY(${theme === 'light' ? '20px' : '-20px'}) scale(0.5)`, + }, + enter: { opacity: 1, transform: 'translateY(0px) scale(1.0)' }, + leave: { + opacity: 0, + transform: `translateY(${theme === 'light' ? '-20px' : '20px'}) scale(0.5)`, + }, + }); + + function handleClick() { + setTheme(theme === 'light' ? 'dark' : 'light'); + } + + return ( + + + {formatMessage(labels.theme)} + + ); +} diff --git a/components/settings/ThemeButton.module.css b/components/buttons/ThemeButton.module.css similarity index 64% rename from components/settings/ThemeButton.module.css rename to components/buttons/ThemeButton.module.css index efde6842..3f7c705a 100644 --- a/components/settings/ThemeButton.module.css +++ b/components/buttons/ThemeButton.module.css @@ -5,9 +5,11 @@ justify-content: center; align-items: center; cursor: pointer; - padding-bottom: 3px; } -.button svg { +.button > div { + display: flex; + justify-content: center; + align-items: center; position: absolute; } diff --git a/components/settings/UserButton.js b/components/buttons/UserButton.js similarity index 93% rename from components/settings/UserButton.js rename to components/buttons/UserButton.js index 542588d6..f6b42d62 100644 --- a/components/settings/UserButton.js +++ b/components/buttons/UserButton.js @@ -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,13 +6,14 @@ 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); const ref = useRef(); - const user = useUser(); + const { user } = useUser(); const router = useRouter(); const { adminDisabled } = useConfig(); @@ -61,7 +61,7 @@ export default function UserButton() {
{show && ( diff --git a/components/settings/UserButton.module.css b/components/buttons/UserButton.module.css similarity index 94% rename from components/settings/UserButton.module.css rename to components/buttons/UserButton.module.css index 1ef6667c..6c4dfdd0 100644 --- a/components/settings/UserButton.module.css +++ b/components/buttons/UserButton.module.css @@ -11,7 +11,6 @@ } .icon svg { - font-size: 16px; height: 16px; width: 16px; } diff --git a/components/common/Calendar.js b/components/common/Calendar.js deleted file mode 100644 index 9fcd26dc..00000000 --- a/components/common/Calendar.js +++ /dev/null @@ -1,282 +0,0 @@ -import { useState } from 'react'; -import classNames from 'classnames'; -import { - startOfWeek, - startOfMonth, - startOfYear, - endOfMonth, - addDays, - subDays, - addYears, - subYears, - addMonths, - setMonth, - setYear, - isSameDay, - isBefore, - isAfter, -} from 'date-fns'; -import { Button, Icon } from 'react-basics'; -import { chunkArray } from 'next-basics'; -import useLocale from 'hooks/useLocale'; -import { dateFormat } from 'lib/date'; -import { getDateLocale } from 'lib/lang'; -import Chevron from 'assets/chevron-down.svg'; -import Cross from 'assets/times.svg'; -import styles from './Calendar.module.css'; - -export default function Calendar({ date, minDate, maxDate, onChange }) { - const { locale } = useLocale(); - const [selectMonth, setSelectMonth] = useState(false); - const [selectYear, setSelectYear] = useState(false); - - const month = dateFormat(date, 'MMMM', locale); - const year = date.getFullYear(); - - function toggleMonthSelect() { - setSelectYear(false); - setSelectMonth(state => !state); - } - - function toggleYearSelect() { - setSelectMonth(false); - setSelectYear(state => !state); - } - - function handleChange(value) { - setSelectMonth(false); - setSelectYear(false); - if (value) { - onChange(value); - } - } - - return ( -
-
-
{date.getDate()}
-
- {month} - - {selectMonth ? : } - -
-
- {year} - - {selectMonth ? : } - -
-
-
- {!selectMonth && !selectYear && ( - - )} - {selectMonth && ( - - )} - {selectYear && ( - - )} -
-
- ); -} - -const DaySelector = ({ date, minDate, maxDate, locale, onSelect }) => { - const dateLocale = getDateLocale(locale); - const weekStartsOn = dateLocale?.options?.weekStartsOn || 0; - const startWeek = startOfWeek(date, { - locale: dateLocale, - weekStartsOn, - }); - const startMonth = startOfMonth(date); - const startDay = subDays(startMonth, startMonth.getDay() - weekStartsOn); - const month = date.getMonth(); - const year = date.getFullYear(); - - const daysOfWeek = []; - for (let i = 0; i < 7; i++) { - daysOfWeek.push(addDays(startWeek, i)); - } - - const days = []; - for (let i = 0; i < 35; i++) { - days.push(addDays(startDay, i)); - } - - return ( - - - - {daysOfWeek.map((day, i) => ( - - ))} - - - - {chunkArray(days, 7).map((week, i) => ( - - {week.map((day, j) => { - const disabled = isBefore(day, minDate) || isAfter(day, maxDate); - return ( - - ); - })} - - ))} - -
- {dateFormat(day, 'EEE', locale)} -
onSelect(day) : null} - > - {day.getDate()} -
- ); -}; - -const MonthSelector = ({ date, minDate, maxDate, locale, onSelect }) => { - const start = startOfYear(date); - const months = []; - for (let i = 0; i < 12; i++) { - months.push(addMonths(start, i)); - } - - function handleSelect(value) { - onSelect(setMonth(date, value)); - } - - return ( - - - {chunkArray(months, 3).map((row, i) => ( - - {row.map((month, j) => { - const disabled = - isBefore(endOfMonth(month), minDate) || isAfter(startOfMonth(month), maxDate); - return ( - - ); - })} - - ))} - -
handleSelect(month.getMonth()) : null} - > - {dateFormat(month, 'MMMM', locale)} -
- ); -}; - -const YearSelector = ({ date, minDate, maxDate, onSelect }) => { - const [currentDate, setCurrentDate] = useState(date); - const year = date.getFullYear(); - const currentYear = currentDate.getFullYear(); - const minYear = minDate.getFullYear(); - const maxYear = maxDate.getFullYear(); - const years = []; - for (let i = 0; i < 15; i++) { - years.push(currentYear - 7 + i); - } - - function handleSelect(value) { - onSelect(setYear(date, value)); - } - - function handlePrevClick() { - setCurrentDate(state => subYears(state, 15)); - } - - function handleNextClick() { - setCurrentDate(state => addYears(state, 15)); - } - - return ( -
-
- -
-
- - - {chunkArray(years, 5).map((row, i) => ( - - {row.map((n, j) => ( - - ))} - - ))} - -
maxYear, - })} - onClick={() => (n < minYear || n > maxYear ? null : handleSelect(n))} - > - {n} -
-
-
- -
-
- ); -}; diff --git a/components/common/Calendar.module.css b/components/common/Calendar.module.css deleted file mode 100644 index 5e50e79a..00000000 --- a/components/common/Calendar.module.css +++ /dev/null @@ -1,111 +0,0 @@ -.calendar { - display: flex; - flex-direction: column; - font-size: var(--font-size-sm); - flex: 1; - min-height: 306px; -} - -.calendar table { - width: 100%; - border-spacing: 5px; -} - -.calendar td { - color: var(--base800); - cursor: pointer; - text-align: center; - vertical-align: center; - height: 40px; - width: 40px; - border-radius: 5px; - border: 1px solid transparent; -} - -.calendar td:hover { - border: 1px solid var(--base300); - background: var(--base75); -} - -.calendar td.faded { - color: var(--base500); -} - -.calendar td.selected { - font-weight: 600; - border: 1px solid var(--base600); -} - -.calendar td.selected:hover { - background: transparent; -} - -.calendar td.disabled { - color: var(--base400); - background: var(--base75); -} - -.calendar td.disabled:hover { - cursor: default; - background: var(--base75); - border-color: transparent; -} - -.calendar td.faded.disabled { - background: var(--base100); -} - -.header { - display: flex; - justify-content: space-evenly; - align-items: center; - font-weight: 700; - line-height: 40px; - font-size: var(--font-size-md); -} - -.body { - display: flex; -} - -.selector { - cursor: pointer; -} - -.pager { - display: flex; - flex: 1; -} - -.pager button { - align-self: center; -} - -.middle { - flex: 1; -} - -.left, -.right { - display: flex; - justify-content: center; - align-items: center; -} - -.left svg { - transform: rotate(90deg); -} - -.right svg { - transform: rotate(-90deg); -} - -.icon { - margin-left: 10px; -} - -@media only screen and (max-width: 992px) { - .calendar table { - max-width: calc(100vw - 30px); - } -} diff --git a/components/common/CopyButton.js b/components/common/CopyButton.js deleted file mode 100644 index e1664a1e..00000000 --- a/components/common/CopyButton.js +++ /dev/null @@ -1,37 +0,0 @@ -import { useState } from 'react'; -import PropTypes from 'prop-types'; -import { Button } from 'react-basics'; -import { FormattedMessage } from 'react-intl'; - -const defaultText = ( - -); - -function CopyButton({ element, ...props }) { - const [text, setText] = useState(defaultText); - - function handleClick() { - if (element?.current) { - element.current.select(); - document.execCommand('copy'); - setText(); - window.getSelection().removeAllRanges(); - } - } - - return ( - - ); -} - -CopyButton.propTypes = { - element: PropTypes.shape({ - current: PropTypes.shape({ - select: PropTypes.func.isRequired, - }), - }), -}; - -export default CopyButton; diff --git a/components/common/DateFilter.js b/components/common/DateFilter.js index e48a7925..77bab32f 100644 --- a/components/common/DateFilter.js +++ b/components/common/DateFilter.js @@ -1,99 +1,109 @@ -import Calendar from 'assets/calendar-alt.svg'; -import DatePickerForm from 'components/forms/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.svg'; -export const filterOptions = [ - { label: , value: '1day' }, - { - label: ( - - ), - value: '24hour', - }, - { - label: , - value: '-1day', - }, - { - label: , - value: '1week', - divider: true, - }, - { - label: ( - - ), - value: '7day', - }, - { - label: , - value: '1month', - divider: true, - }, - { - label: ( - - ), - value: '30day', - }, - { - label: ( - - ), - value: '90day', - }, - { label: , value: '1year' }, - { - label: , - value: 'all', - divider: true, - }, - { - label: , - 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' ? ( 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 ( <> - + > + {({ label, value }) => {label}} + {showPicker && ( - + { ); }; -DateFilter.propTypes = { - value: PropTypes.string, - startDate: PropTypes.instanceOf(Date), - endDate: PropTypes.instanceOf(Date), - onChange: PropTypes.func, - className: PropTypes.string, -}; - export default DateFilter; diff --git a/components/common/DropDown.js b/components/common/DropDown.js deleted file mode 100644 index 7cd2d529..00000000 --- a/components/common/DropDown.js +++ /dev/null @@ -1,66 +0,0 @@ -import { useState, useRef } from 'react'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; -import Menu from './Menu'; -import useDocumentClick from 'hooks/useDocumentClick'; -import Chevron from 'assets/chevron-down.svg'; -import styles from './Dropdown.module.css'; -import { Icon } from 'react-basics'; - -function DropDown({ value, className, menuClassName, options = [], onChange = () => {} }) { - const [showMenu, setShowMenu] = useState(false); - const ref = useRef(); - const selectedOption = options.find(e => e.value === value); - - function handleShowMenu() { - setShowMenu(state => !state); - } - - function handleSelect(selected, e) { - e.stopPropagation(); - setShowMenu(false); - - onChange(selected); - } - - useDocumentClick(e => { - if (!ref.current?.contains(e.target)) { - setShowMenu(false); - } - }); - - return ( -
-
-
{options.find(e => e.value === value)?.label || value}
- - - -
- {showMenu && ( - - )} -
- ); -} - -DropDown.propTypes = { - value: PropTypes.any, - className: PropTypes.string, - menuClassName: PropTypes.string, - options: PropTypes.arrayOf( - PropTypes.shape({ - value: PropTypes.any.isRequired, - label: PropTypes.node, - }), - ), - onChange: PropTypes.func, -}; - -export default DropDown; diff --git a/components/common/Dropdown.module.css b/components/common/Dropdown.module.css deleted file mode 100644 index a1877529..00000000 --- a/components/common/Dropdown.module.css +++ /dev/null @@ -1,28 +0,0 @@ -.dropdown { - position: relative; - display: flex; - justify-content: space-between; - align-items: center; - border: 1px solid var(--base500); - border-radius: 4px; - cursor: pointer; -} - -.value { - flex: 1; - display: flex; - justify-content: space-between; - font-size: var(--font-size-sm); - flex-wrap: nowrap; - white-space: nowrap; - padding: 4px 16px; - min-width: 160px; -} - -.text { - flex: 1; -} - -.icon { - padding-left: 20px; -} diff --git a/components/common/EmptyPlaceholder.js b/components/common/EmptyPlaceholder.js index f7aa6dd7..fa773a01 100644 --- a/components/common/EmptyPlaceholder.js +++ b/components/common/EmptyPlaceholder.js @@ -1,25 +1,16 @@ -import PropTypes from 'prop-types'; -import { Icon, Flexbox } from 'react-basics'; +import { Icon, Text, Flexbox } from 'react-basics'; import Logo from 'assets/logo.svg'; -import styles from './EmptyPlaceholder.module.css'; -function EmptyPlaceholder({ msg, children }) { +function EmptyPlaceholder({ message, children }) { return ( -
- + + -

{msg}

- - {children} - -
+ {message} +
{children}
+ ); } -EmptyPlaceholder.propTypes = { - msg: PropTypes.node, - children: PropTypes.node, -}; - export default EmptyPlaceholder; diff --git a/components/common/EmptyPlaceholder.module.css b/components/common/EmptyPlaceholder.module.css deleted file mode 100644 index a9231836..00000000 --- a/components/common/EmptyPlaceholder.module.css +++ /dev/null @@ -1,15 +0,0 @@ -.placeholder { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - min-height: 600px; -} - -.icon { - margin-bottom: 30px; -} - -.msg { - margin-bottom: 15px; -} diff --git a/components/common/ErrorMessage.js b/components/common/ErrorMessage.js index 2a411425..926e64ac 100644 --- a/components/common/ErrorMessage.js +++ b/components/common/ErrorMessage.js @@ -1,15 +1,17 @@ -import Exclamation from 'assets/exclamation-triangle.svg'; -import { FormattedMessage } from 'react-intl'; +import { useIntl } from 'react-intl'; +import { Icon, Icons, Text } from 'react-basics'; +import { messages } from 'components/messages'; import styles from './ErrorMessage.module.css'; -import { Icon } from 'react-basics'; export default function ErrorMessage() { + const { formatMessage } = useIntl(); + return (
- + - + {formatMessage(messages.error)}
); } diff --git a/components/common/EventDataButton.js b/components/common/EventDataButton.js index 706d07d1..5393d7d5 100644 --- a/components/common/EventDataButton.js +++ b/components/common/EventDataButton.js @@ -1,8 +1,7 @@ -import List from 'assets/list-ul.svg'; -import EventDataForm from 'components/forms/EventDataForm'; +import EventDataForm from 'components/metrics/EventDataForm'; import PropTypes from 'prop-types'; import { useState } from 'react'; -import { Button, Icon, Modal } from 'react-basics'; +import { Button, Icon, Modal, Icons } from 'react-basics'; import { FormattedMessage } from 'react-intl'; import styles from './EventDataButton.module.css'; @@ -24,18 +23,21 @@ function EventDataButton({ websiteId }) { {showEventData && ( - }> - + } + onClose={handleClose} + > + {close => } )} diff --git a/components/common/EventDataButton.module.css b/components/common/EventDataButton.module.css index cd2a2ed6..af332bd2 100644 --- a/components/common/EventDataButton.module.css +++ b/components/common/EventDataButton.module.css @@ -1,3 +1,4 @@ .button { width: fit-content; + margin-bottom: 15px; } diff --git a/components/common/FilterLink.js b/components/common/FilterLink.js index 33211aa1..f91d4764 100644 --- a/components/common/FilterLink.js +++ b/components/common/FilterLink.js @@ -2,8 +2,7 @@ import classNames from 'classnames'; import Link from 'next/link'; import { safeDecodeURI } from 'next-basics'; import usePageQuery from 'hooks/usePageQuery'; -import External from 'assets/arrow-up-right-from-square.svg'; -import { Icon } from 'react-basics'; +import { Icon, Icons } from 'react-basics'; import styles from './FilterLink.module.css'; export default function FilterLink({ id, value, label, externalUrl }) { @@ -26,7 +25,7 @@ export default function FilterLink({ id, value, label, externalUrl }) { {externalUrl && ( - + )} diff --git a/components/common/HamburgerButton.js b/components/common/HamburgerButton.js index 67a1c83a..49f3232e 100644 --- a/components/common/HamburgerButton.js +++ b/components/common/HamburgerButton.js @@ -1,10 +1,9 @@ import { Button, Icon } from 'react-basics'; -import XMark from 'assets/xmark.svg'; -import Bars from 'assets/bars.svg'; import { useState } from 'react'; -import styles from './HamburgerButton.module.css'; -import MobileMenu from './MobileMenu'; import { FormattedMessage } from 'react-intl'; +import MobileMenu from './MobileMenu'; +import Icons from 'components/icons'; +import styles from './HamburgerButton.module.css'; const menuItems = [ { @@ -12,10 +11,13 @@ const menuItems = [ value: '/dashboard', }, { label: , value: '/realtime' }, - { label: , value: '/settings' }, + { + label: , + value: '/buttons', + }, { label: , - value: '/settings/profile', + value: '/buttons/profile', }, { label: , value: '/logout' }, ]; @@ -34,7 +36,7 @@ export default function HamburgerButton() { return ( <> {active && } diff --git a/components/common/Menu.module.css b/components/common/Menu.module.css index 63491d40..d9a23371 100644 --- a/components/common/Menu.module.css +++ b/components/common/Menu.module.css @@ -7,8 +7,6 @@ } .option { - font-size: var(--font-size-sm); - font-weight: normal; background: var(--base50); padding: 4px 16px; cursor: pointer; diff --git a/components/common/MobileMenu.js b/components/common/MobileMenu.js index 1a953d18..1c93f18a 100644 --- a/components/common/MobileMenu.js +++ b/components/common/MobileMenu.js @@ -1,19 +1,25 @@ import classNames from 'classnames'; -import Link from './Link'; -import { Button } from 'react-basics'; -import XMark from 'assets/xmark.svg'; +import Link from 'next/link'; +import { Button, Icon } from 'react-basics'; +import Icons from 'components/icons'; import styles from './MobileMenu.module.css'; export default function MobileMenu({ items = [], onClose }) { return ( -
+
-
{items.map(({ label, value }) => ( - - {label} + + + {label} + ))}
diff --git a/components/common/RefreshButton.js b/components/common/RefreshButton.js index 507ecd34..a23ea9c5 100644 --- a/components/common/RefreshButton.js +++ b/components/common/RefreshButton.js @@ -1,12 +1,10 @@ import { useState, useEffect, useCallback } from 'react'; -import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; import useStore from 'store/queries'; import { setDateRange } from 'store/websites'; import { Button, Icon } from 'react-basics'; -import Refresh from 'assets/redo.svg'; -import Dots from 'assets/ellipsis-h.svg'; import useDateRange from 'hooks/useDateRange'; +import Icons from 'components/icons'; function RefreshButton({ websiteId }) { const [dateRange] = useDateRange(websiteId); @@ -17,7 +15,7 @@ function RefreshButton({ websiteId }) { function handleClick() { if (!loading && dateRange) { setLoading(true); - if (/^[\d]+/.test(dateRange.value)) { + if (/^\d+/.test(dateRange.value)) { setDateRange(websiteId, dateRange.value); } else { setDateRange(websiteId, dateRange); @@ -36,13 +34,11 @@ function RefreshButton({ websiteId }) { size="small" onClick={handleClick} > - {loading ? : } + + + ); } -RefreshButton.propTypes = { - websiteId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), -}; - export default RefreshButton; diff --git a/components/common/UpdateNotice.module.css b/components/common/UpdateNotice.module.css index 5161ea71..1837b4df 100644 --- a/components/common/UpdateNotice.module.css +++ b/components/common/UpdateNotice.module.css @@ -6,7 +6,6 @@ } .message { - font-size: var(--font-size-sm); font-weight: 600; flex: 1; text-align: center; diff --git a/components/forms/EventDataForm.js b/components/forms/EventDataForm.js deleted file mode 100644 index e72e90f8..00000000 --- a/components/forms/EventDataForm.js +++ /dev/null @@ -1,262 +0,0 @@ -import classNames from 'classnames'; -import { Button } from 'react-basics'; -import DateFilter from 'components/common/DateFilter'; -import DropDown from 'components/common/DropDown'; -import FormLayout, { - FormButtons, - FormError, - FormMessage, - FormRow, -} from 'components/layout/FormLayout'; -import DataTable from 'components/metrics/DataTable'; -import FilterTags from 'components/metrics/FilterTags'; -import { Field, Form, Formik } from 'formik'; -import useApi from 'hooks/useApi'; -import useDateRange from 'hooks/useDateRange'; -import { useState, useEffect } from 'react'; -import { FormattedMessage } from 'react-intl'; -import styles from './EventDataForm.module.css'; -import useTimezone from 'hooks/useTimezone'; - -export const filterOptions = [ - { label: 'Count', value: 'count' }, - { label: 'Average', value: 'avg' }, - { label: 'Minimum', value: 'min' }, - { label: 'Maximum', value: 'max' }, - { label: 'Sum', value: 'sum' }, -]; - -export const dateOptions = [ - { label: , value: '1day' }, - { - label: ( - - ), - value: '24hour', - }, - { - label: , - value: '-1day', - }, - { - label: , - value: '1week', - divider: true, - }, - { - label: ( - - ), - value: '7day', - }, - { - label: , - value: '1month', - divider: true, - }, - { - label: ( - - ), - value: '30day', - }, - { - label: ( - - ), - value: '90day', - }, - { label: , value: '1year' }, - { - label: , - value: 'custom', - divider: true, - }, -]; - -export default function EventDataForm({ websiteId, onClose, className }) { - const { post } = useApi(); - const [message, setMessage] = useState(); - const [columns, setColumns] = useState({}); - const [filters, setFilters] = useState({}); - const [data, setData] = useState([]); - const [dateRange, setDateRange] = useDateRange('report'); - const { startDate, endDate, value } = dateRange; - const [timezone] = useTimezone(); - const [isValid, setIsValid] = useState(false); - - useEffect(() => { - if (Object.keys(columns).length > 0) { - setIsValid(true); - } else { - setIsValid(false); - } - }, [columns]); - - const handleAddTag = (value, list, setState, resetForm) => { - setState({ ...list, [`${value.field}`]: value.value }); - resetForm(); - }; - - const handleRemoveTag = (value, list, setState) => { - const newList = { ...list }; - - delete newList[`${value}`]; - - setState(newList); - }; - - const handleSubmit = async () => { - const params = { - website_id: websiteId, - startAt: +startDate, - endAt: +endDate, - timezone, - columns, - filters, - }; - - const { ok, data } = await post(`/websites/${websiteId}/eventdata`, params); - - if (!ok) { - setMessage(); - setData([]); - } else { - setData(data); - setMessage(null); - } - }; - - return ( - <> - {message} -
-
- -
- - - - -
-
- - handleAddTag(value, columns, setColumns, resetForm) - } - > - {({ values, setFieldValue }) => ( -
- - -
- - -
-
- - -
- setFieldValue('value', value)} - className={styles.dropdown} - name="value" - options={filterOptions} - /> - -
-
- - - -
- )} -
- handleRemoveTag(value, columns, setColumns)} - /> -
-
- - handleAddTag(value, filters, setFilters, resetForm) - } - > - {({ values }) => ( -
- - -
- - -
-
- - -
- - -
-
- - - -
- )} -
- handleRemoveTag(value, filters, setFilters)} - /> -
-
-
-
- -
-
- - - - - - ); -} diff --git a/components/forms/Form.module.css b/components/forms/Form.module.css deleted file mode 100644 index 9185da43..00000000 --- a/components/forms/Form.module.css +++ /dev/null @@ -1,63 +0,0 @@ -.form { - display: flex; - flex-direction: column; - gap: 30px; - width: 300px; - margin: 0 auto; -} - -.header { - font-size: 24px; - font-weight: 700; - text-align: center; - margin: 30px auto; -} - -.info { - text-align: center; - padding: 30px 0; -} - -.footer { - display: flex; - flex-direction: column; - gap: 20px; - font-size: 14px; - text-align: center; - margin: 30px auto; -} - -.footer a { - font-weight: 600; -} - -.buttons { - justify-content: center; -} - -.button { - flex: 1; - justify-content: center; -} - -.error { - width: 600px; - margin: 0 auto 30px; - background: var(--base50); - padding: 16px; - color: var(--red400); - border: 1px solid var(--red400); - border-radius: 5px; - text-align: center; -} - -.success { - width: 600px; - margin: 60px auto; - background: var(--base50); - padding: 16px; - color: var(--green400); - border: 1px solid var(--green400); - border-radius: 5px; - text-align: center; -} diff --git a/components/forms/LoginForm.module.css b/components/forms/LoginForm.module.css deleted file mode 100644 index dfd5456a..00000000 --- a/components/forms/LoginForm.module.css +++ /dev/null @@ -1,23 +0,0 @@ -.login { - display: flex; - flex-direction: column; - margin-top: 80px; -} - -.login form { - margin: 0 auto; -} - -.icon { - display: flex; - justify-content: center; - margin: 0 auto; -} - -.header { - margin-bottom: 30px; -} - -.header h1 { - margin: 12px 0; -} diff --git a/components/forms/TeamAddForm.js b/components/forms/TeamAddForm.js deleted file mode 100644 index 78c316ab..00000000 --- a/components/forms/TeamAddForm.js +++ /dev/null @@ -1,35 +0,0 @@ -import { useRef } from 'react'; -import { Form, FormInput, FormButtons, TextField, Button } from 'react-basics'; -import useApi from 'hooks/useApi'; -import styles from './Form.module.css'; -import { useMutation } from '@tanstack/react-query'; - -export default function TeamAddForm({ onSave, onClose }) { - const { post } = useApi(); - const { mutate, error, isLoading } = useMutation(data => post('/teams', data)); - const ref = useRef(null); - - const handleSubmit = async data => { - mutate(data, { - onSuccess: async () => { - onSave(); - }, - }); - }; - - return ( -
- - - - - - - -
- ); -} diff --git a/components/forms/TeamEditForm.js b/components/forms/TeamEditForm.js deleted file mode 100644 index 2ba8c661..00000000 --- a/components/forms/TeamEditForm.js +++ /dev/null @@ -1,33 +0,0 @@ -import { SubmitButton, Form, FormInput, FormRow, FormButtons, TextField } from 'react-basics'; -import { useMutation } from '@tanstack/react-query'; -import { useRef } from 'react'; -import useApi from 'hooks/useApi'; - -export default function TeamEditForm({ teamId, data, onSave }) { - const { post } = useApi(); - const { mutate, error } = useMutation(data => post(`/teams/${teamId}`, data)); - const ref = useRef(null); - - const handleSubmit = async data => { - mutate(data, { - onSuccess: async () => { - ref.current.reset(data); - onSave(data); - }, - }); - }; - - return ( -
- - - - - - - - Save - -
- ); -} diff --git a/components/forms/TrackingCodeForm.js b/components/forms/TrackingCodeForm.js deleted file mode 100644 index 191248f5..00000000 --- a/components/forms/TrackingCodeForm.js +++ /dev/null @@ -1,23 +0,0 @@ -import useConfig from 'hooks/useConfig'; -import { useRef } from 'react'; -import { Form, FormRow, TextArea } from 'react-basics'; - -export default function TrackingCodeForm({ websiteId }) { - const ref = useRef(null); - const { trackerScriptName } = useConfig(); - const code = ``; - - return ( - <> -
- -

- To track stats for this website, place the following code in the{' '} - <head> section of your HTML. -

-