diff --git a/components/common/HamburgerButton.js b/components/common/HamburgerButton.js index a2d26779..b3e0b54f 100644 --- a/components/common/HamburgerButton.js +++ b/components/common/HamburgerButton.js @@ -32,9 +32,13 @@ export default function HamburgerButton() { label: formatMessage(labels.users), url: '/settings/users', }, + { + label: formatMessage(labels.profile), + url: '/settings/profile', + }, ], }, - { + cloudMode && { label: formatMessage(labels.profile), url: '/settings/profile', }, diff --git a/components/common/WorldMap.js b/components/common/WorldMap.js index b774702b..b5a40fc5 100644 --- a/components/common/WorldMap.js +++ b/components/common/WorldMap.js @@ -9,7 +9,8 @@ import styles from './WorldMap.module.css'; import useCountryNames from 'hooks/useCountryNames'; import useLocale from 'hooks/useLocale'; import HoverTooltip from './HoverTooltip'; -import { formatLongNumber } from '../../lib/format'; +import { formatLongNumber } from 'lib/format'; +import { percentFilter } from 'lib/filters'; function WorldMap({ data, className }) { const { basePath } = useRouter(); @@ -26,10 +27,11 @@ function WorldMap({ data, className }) { ); const { locale } = useLocale(); const countryNames = useCountryNames(locale); + const metrics = useMemo(() => percentFilter(data), [data]); function getFillColor(code) { if (code === 'AQ') return; - const country = data?.find(({ x }) => x === code); + const country = metrics?.find(({ x }) => x === code); if (!country) { return colors.fillColor; @@ -46,7 +48,7 @@ function WorldMap({ data, className }) { function handleHover(code) { if (code === 'AQ') return; - const country = data?.find(({ x }) => x === code); + const country = metrics?.find(({ x }) => x === code); setTooltip(`${countryNames[code]}: ${formatLongNumber(country?.y || 0)} visitors`); } diff --git a/components/messages.js b/components/messages.js index de92ce8f..9de5f52b 100644 --- a/components/messages.js +++ b/components/messages.js @@ -114,6 +114,8 @@ export const labels = defineMessages({ editDashboard: { id: 'label.edit-dashboard', defaultMessage: 'Edit dashboard' }, title: { id: 'label.title', defaultMessage: 'Title' }, view: { id: 'label.view', defaultMessage: 'View' }, + cities: { id: 'label.cities', defaultMessage: 'Cities' }, + regions: { id: 'label.regions', defaultMessage: 'Regions' }, }); export const messages = defineMessages({ diff --git a/components/metrics/CitiesTable.js b/components/metrics/CitiesTable.js new file mode 100644 index 00000000..4aa61334 --- /dev/null +++ b/components/metrics/CitiesTable.js @@ -0,0 +1,30 @@ +import MetricsTable from './MetricsTable'; +import { emptyFilter } from 'lib/filters'; +import FilterLink from 'components/common/FilterLink'; +import useLocale from 'hooks/useLocale'; +import useMessages from 'hooks/useMessages'; + +export default function CitiesTable({ websiteId, ...props }) { + const { locale } = useLocale(); + const { formatMessage, labels } = useMessages(); + + function renderLink({ x }) { + return ( +
+ +
+ ); + } + + return ( + + ); +} diff --git a/components/metrics/CountriesTable.js b/components/metrics/CountriesTable.js index 5e7d8a8d..dcebe5e0 100644 --- a/components/metrics/CountriesTable.js +++ b/components/metrics/CountriesTable.js @@ -1,11 +1,10 @@ import MetricsTable from './MetricsTable'; -import { percentFilter } from 'lib/filters'; import FilterLink from 'components/common/FilterLink'; import useCountryNames from 'hooks/useCountryNames'; import useLocale from 'hooks/useLocale'; import useMessages from 'hooks/useMessages'; -export default function CountriesTable({ websiteId, onDataLoad, ...props }) { +export default function CountriesTable({ websiteId, ...props }) { const { locale } = useLocale(); const countryNames = useCountryNames(locale); const { formatMessage, labels } = useMessages(); @@ -25,7 +24,6 @@ export default function CountriesTable({ websiteId, onDataLoad, ...props }) { type="country" metric={formatMessage(labels.visitors)} websiteId={websiteId} - onDataLoad={data => onDataLoad?.(percentFilter(data))} renderLabel={renderLink} /> ); diff --git a/components/metrics/MetricsTable.js b/components/metrics/MetricsTable.js index 2e20dfc6..0d83fc22 100644 --- a/components/metrics/MetricsTable.js +++ b/components/metrics/MetricsTable.js @@ -29,7 +29,7 @@ export default function MetricsTable({ const { resolveUrl, router, - query: { url, referrer, os, browser, device, country }, + query: { url, referrer, os, browser, device, country, region, city }, } = usePageQuery(); const { formatMessage, labels } = useMessages(); const { get, useQuery } = useApi(); @@ -37,7 +37,7 @@ export default function MetricsTable({ const { data, isLoading, isFetched, error } = useQuery( [ 'websites:metrics', - { websiteId, type, modified, url, referrer, os, browser, device, country }, + { websiteId, type, modified, url, referrer, os, browser, device, country, region, city }, ], () => get(`/websites/${websiteId}/metrics`, { @@ -50,6 +50,8 @@ export default function MetricsTable({ browser, device, country, + region, + city, }), { onSuccess: onDataLoad, retryDelay: delay || DEFAULT_ANIMATION_DURATION }, ); diff --git a/components/metrics/RegionsTable.js b/components/metrics/RegionsTable.js new file mode 100644 index 00000000..87a15b40 --- /dev/null +++ b/components/metrics/RegionsTable.js @@ -0,0 +1,31 @@ +import MetricsTable from './MetricsTable'; +import { emptyFilter } from 'lib/filters'; +import FilterLink from 'components/common/FilterLink'; +import useLocale from 'hooks/useLocale'; +import useMessages from 'hooks/useMessages'; +import regions from 'public/iso-3166-2.json'; + +export default function RegionsTable({ websiteId, ...props }) { + const { locale } = useLocale(); + const { formatMessage, labels } = useMessages(); + + function renderLink({ x }) { + return ( +
+ +
+ ); + } + + return ( + + ); +} diff --git a/components/metrics/WebsiteChart.js b/components/metrics/WebsiteChart.js index cf900f76..cc27ac25 100644 --- a/components/metrics/WebsiteChart.js +++ b/components/metrics/WebsiteChart.js @@ -33,13 +33,16 @@ export default function WebsiteChart({ const { startDate, endDate, unit, value, modified } = dateRange; const [timezone] = useTimezone(); const { - query: { url, referrer, os, browser, device, country, title }, + query: { url, referrer, os, browser, device, country, region, city, title }, } = usePageQuery(); const { get, useQuery } = useApi(); const { ref, isSticky } = useSticky({ enabled: stickyHeader }); const { data, isLoading, error } = useQuery( - ['websites:pageviews', { websiteId, modified, url, referrer, os, browser, device, country }], + [ + 'websites:pageviews', + { websiteId, modified, url, referrer, os, browser, device, country, region, city, title }, + ], () => get(`/websites/${websiteId}/pageviews`, { startAt: +startDate, @@ -52,6 +55,9 @@ export default function WebsiteChart({ browser, device, country, + region, + city, + title, }), { onSuccess: onDataLoad }, ); @@ -82,7 +88,7 @@ export default function WebsiteChart({