diff --git a/components/WebsiteDetails.js b/components/WebsiteDetails.js index 3a933638..e3a7e2c2 100644 --- a/components/WebsiteDetails.js +++ b/components/WebsiteDetails.js @@ -1,5 +1,6 @@ import React, { useState } from 'react'; import { FormattedMessage } from 'react-intl'; +import { useRouter } from 'next/router'; import classNames from 'classnames'; import WebsiteChart from 'components/metrics/WebsiteChart'; import WorldMap from 'components/common/WorldMap'; @@ -19,12 +20,29 @@ import EventsChart from './metrics/EventsChart'; import useFetch from 'hooks/useFetch'; import Loading from 'components/common/Loading'; -export default function WebsiteDetails({ websiteId }) { - const { data } = useFetch(`/api/website/${websiteId}`); +const views = { + url: PagesTable, + referrer: ReferrersTable, + browser: BrowsersTable, + os: OSTable, + device: DevicesTable, + country: CountriesTable, + event: EventsTable, +}; + +export default function WebsiteDetails({ websiteId, token }) { + const router = useRouter(); + const { data } = useFetch(`/api/website/${websiteId}`, { token }); const [chartLoaded, setChartLoaded] = useState(false); const [countryData, setCountryData] = useState(); const [eventsData, setEventsData] = useState(); - const [expand, setExpand] = useState(); + const { + query: { id, view }, + basePath, + asPath, + } = router; + + const path = `${basePath}/${asPath.split('/')[1]}/${id.join('/')}`; const BackButton = () => ( diff --git a/components/common/Calendar.js b/components/common/Calendar.js index 9ad1be88..0414ff7f 100644 --- a/components/common/Calendar.js +++ b/components/common/Calendar.js @@ -70,34 +70,36 @@ export default function Calendar({ date, minDate, maxDate, onChange }) { : } size="small" /> - {!selectMonth && !selectYear && ( - - )} - {selectMonth && ( - - )} - {selectYear && ( - - )} +
+ {!selectMonth && !selectYear && ( + + )} + {selectMonth && ( + + )} + {selectYear && ( + + )} +
); } @@ -220,42 +222,46 @@ const YearSelector = ({ date, minDate, maxDate, onSelect }) => { return (
-
+
+ + + {chunk(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 index b7e56df1..eb07431b 100644 --- a/components/common/Calendar.module.css +++ b/components/common/Calendar.module.css @@ -3,11 +3,11 @@ flex-direction: column; font-size: var(--font-size-small); flex: 1; - min-height: 285px; + min-height: 306px; } .calendar table { - flex: 1; + width: 100%; border-spacing: 5px; } @@ -64,18 +64,34 @@ font-size: var(--font-size-normal); } +.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); } diff --git a/components/common/DateFilter.js b/components/common/DateFilter.js index c43cb860..fb76a081 100644 --- a/components/common/DateFilter.js +++ b/components/common/DateFilter.js @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import { FormattedMessage } from 'react-intl'; -import { endOfYear } from 'date-fns'; +import { endOfYear, isSameDay } from 'date-fns'; import Modal from './Modal'; import DropDown from './DropDown'; import DatePickerForm from 'components/forms/DatePickerForm'; @@ -112,7 +112,8 @@ const CustomRange = ({ startDate, endDate, onClick }) => { return ( <> } className="mr-2" onClick={handleClick} /> - {`${dateFormat(startDate, 'd LLL y', locale)} — ${dateFormat(endDate, 'd LLL y', locale)}`} + {dateFormat(startDate, 'd LLL y', locale)} + {!isSameDay(startDate, endDate) && ` — ${dateFormat(endDate, 'd LLL y', locale)}`} ); }; diff --git a/components/common/LanguageButton.js b/components/common/LanguageButton.js index 5565b45e..6fe7eb83 100644 --- a/components/common/LanguageButton.js +++ b/components/common/LanguageButton.js @@ -1,12 +1,13 @@ import React, { useState, useRef } from 'react'; import Head from 'next/head'; -import Globe from 'assets/globe.svg'; -import useDocumentClick from 'hooks/useDocumentClick'; import Menu from './Menu'; import Button from './Button'; import { menuOptions } from 'lib/lang'; +import { setItem } from 'lib/web'; +import useLocale from 'hooks/useLocale'; +import useDocumentClick from 'hooks/useDocumentClick'; +import Globe from 'assets/globe.svg'; import styles from './LanguageButton.module.css'; -import useLocale from '../../hooks/useLocale'; export default function LanguageButton({ menuPosition = 'bottom', menuAlign = 'left' }) { const [showMenu, setShowMenu] = useState(false); @@ -16,7 +17,7 @@ export default function LanguageButton({ menuPosition = 'bottom', menuAlign = 'l function handleSelect(value) { setLocale(value); - window.localStorage.setItem('locale', value); + setItem('umami.locale', value); setShowMenu(false); } diff --git a/components/common/NavMenu.js b/components/common/NavMenu.js new file mode 100644 index 00000000..6cbe7559 --- /dev/null +++ b/components/common/NavMenu.js @@ -0,0 +1,32 @@ +import React from 'react'; +import { useRouter } from 'next/router'; +import classNames from 'classnames'; +import styles from './NavMenu.module.css'; + +export default function NavMenu({ options = [], className, onSelect = () => {} }) { + const router = useRouter(); + + return ( +
+ {options + .filter(({ hidden }) => !hidden) + .map(option => { + const { label, value, className: customClassName, render } = option; + + return render ? ( + render(option) + ) : ( +
onSelect(value, e)} + > + {label} +
+ ); + })} +
+ ); +} diff --git a/components/common/NavMenu.module.css b/components/common/NavMenu.module.css new file mode 100644 index 00000000..c5d6c9db --- /dev/null +++ b/components/common/NavMenu.module.css @@ -0,0 +1,20 @@ +.menu { + border: 1px solid var(--gray500); + border-radius: 4px; + overflow: hidden; + z-index: 2; +} + +.option { + padding: 8px 16px; + cursor: pointer; + border-radius: 4px; +} + +.option:hover { + background: var(--gray75); +} + +.selected { + font-weight: 600; +} diff --git a/components/common/RefreshButton.js b/components/common/RefreshButton.js index b3e00a27..1fa02f48 100644 --- a/components/common/RefreshButton.js +++ b/components/common/RefreshButton.js @@ -5,7 +5,7 @@ import { setDateRange } from 'redux/actions/websites'; import Button from './Button'; import Refresh from 'assets/redo.svg'; import Dots from 'assets/ellipsis-h.svg'; -import { useDateRange } from 'hooks/useDateRange'; +import useDateRange from 'hooks/useDateRange'; import { getDateRange } from '../../lib/date'; export default function RefreshButton({ websiteId }) { diff --git a/components/common/UserButton.js b/components/common/UserButton.js index d3c078be..fdcc59fb 100644 --- a/components/common/UserButton.js +++ b/components/common/UserButton.js @@ -27,6 +27,7 @@ export default function UserButton() { value: 'username', className: styles.username, }, + { label: , value: 'profile' }, { label: , value: 'logout' }, ]; @@ -35,6 +36,8 @@ export default function UserButton() { if (value === 'logout') { router.push('/logout'); + } else if (value === 'profile') { + router.push('/settings/profile'); } } diff --git a/components/forms/DatePickerForm.js b/components/forms/DatePickerForm.js index 8ead373d..f8b6416e 100644 --- a/components/forms/DatePickerForm.js +++ b/components/forms/DatePickerForm.js @@ -1,11 +1,15 @@ import React, { useState } from 'react'; import { FormattedMessage } from 'react-intl'; -import { isAfter } from 'date-fns'; +import { isAfter, isBefore, isSameDay } 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'; +import ButtonGroup from '../common/ButtonGroup'; + +const FILTER_DAY = 0; +const FILTER_RANGE = 1; export default function DatePickerForm({ startDate: defaultStartDate, @@ -15,21 +19,59 @@ export default function DatePickerForm({ onChange, onClose, }) { + const [selected, setSelected] = useState( + isSameDay(defaultStartDate, defaultEndDate) ? FILTER_DAY : FILTER_RANGE, + ); + const [date, setDate] = useState(defaultStartDate); const [startDate, setStartDate] = useState(defaultStartDate); const [endDate, setEndDate] = useState(defaultEndDate); + const disabled = + selected === FILTER_DAY + ? isAfter(minDate, date) && isBefore(maxDate, date) + : isAfter(startDate, endDate); + + const buttons = [ + { + label: , + value: FILTER_DAY, + }, + { + label: , + value: FILTER_RANGE, + }, + ]; + function handleSave() { - onChange({ ...getDateRangeValues(startDate, endDate), value: 'custom' }); + if (selected === FILTER_DAY) { + onChange({ ...getDateRangeValues(date, date), value: 'custom' }); + } else { + onChange({ ...getDateRangeValues(startDate, endDate), value: 'custom' }); + } } return (
+
+ +
- - + {selected === FILTER_DAY ? ( + + ) : ( + <> + + + + )}
-