From a5700d4a257f7e7cf6e0820cceebee664c7f452a Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Wed, 17 May 2023 23:20:06 -0700 Subject: [PATCH] Added reports section. --- assets/funnel.svg | 1 + assets/lightbulb.svg | 80 +++++++++++++++++++ assets/nodes.svg | 1 + components/input/DateFilter.js | 38 ++++----- components/input/WebsiteDateFilter.js | 26 ++++++ components/layout/NavBar.js | 1 + components/messages.js | 3 + components/metrics/WebsiteChart.js | 4 +- components/pages/reports/EventDataReport.js | 33 ++++++++ components/pages/reports/Report.js | 5 ++ components/pages/reports/ReportHeader.js | 42 ++++++++++ components/pages/reports/ReportsList.js | 66 +++++++++++++++ .../pages/reports/ReportsList.module.css | 32 ++++++++ components/pages/reports/reports.module.css | 14 ++++ .../settings/profile/DateRangeSetting.js | 3 +- hooks/index.js | 17 ++++ hooks/useApi.ts | 4 +- hooks/useConfig.js | 4 +- hooks/useCountryNames.js | 4 +- hooks/useDateRange.js | 4 +- hooks/useDocumentClick.js | 4 +- hooks/useEscapeKey.js | 4 +- hooks/useForceUpdate.js | 4 +- hooks/useLanguageNames.js | 4 +- hooks/useLocale.js | 4 +- hooks/useMessages.js | 4 +- hooks/usePageQuery.js | 4 +- hooks/useRequireLogin.js | 4 +- hooks/useShareToken.js | 4 +- hooks/useSticky.js | 4 +- hooks/useTheme.js | 4 +- hooks/useTimezone.js | 4 +- hooks/useUser.js | 4 +- pages/api/websites/[id]/data.ts | 13 +++ pages/reports/event-data/index.js | 5 ++ pages/reports/index.js | 13 +++ 36 files changed, 422 insertions(+), 43 deletions(-) create mode 100644 assets/funnel.svg create mode 100644 assets/lightbulb.svg create mode 100644 assets/nodes.svg create mode 100644 components/input/WebsiteDateFilter.js create mode 100644 components/pages/reports/EventDataReport.js create mode 100644 components/pages/reports/Report.js create mode 100644 components/pages/reports/ReportHeader.js create mode 100644 components/pages/reports/ReportsList.js create mode 100644 components/pages/reports/ReportsList.module.css create mode 100644 components/pages/reports/reports.module.css create mode 100644 hooks/index.js create mode 100644 pages/api/websites/[id]/data.ts create mode 100644 pages/reports/event-data/index.js create mode 100644 pages/reports/index.js diff --git a/assets/funnel.svg b/assets/funnel.svg new file mode 100644 index 00000000..46f4623f --- /dev/null +++ b/assets/funnel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/lightbulb.svg b/assets/lightbulb.svg new file mode 100644 index 00000000..73b699a3 --- /dev/null +++ b/assets/lightbulb.svg @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/nodes.svg b/assets/nodes.svg new file mode 100644 index 00000000..0bbd37c4 --- /dev/null +++ b/assets/nodes.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/components/input/DateFilter.js b/components/input/DateFilter.js index b6c1ee72..b7521e27 100644 --- a/components/input/DateFilter.js +++ b/components/input/DateFilter.js @@ -3,31 +3,22 @@ import { Icon, Modal, Dropdown, Item, Text, Flexbox } from 'react-basics'; import { endOfYear, isSameDay } from 'date-fns'; import DatePickerForm from 'components/metrics/DatePickerForm'; import useLocale from 'hooks/useLocale'; -import { dateFormat, getDateRangeValues } from 'lib/date'; +import { dateFormat } from 'lib/date'; import Icons from 'components/icons'; -import useApi from 'hooks/useApi'; -import useDateRange from 'hooks/useDateRange'; import useMessages from 'hooks/useMessages'; -export function DateFilter({ websiteId, value, className }) { +export function DateFilter({ + value, + startDate, + endDate, + className, + onChange, + showAllTime = false, + alignment = 'end', +}) { const { formatMessage, labels } = useMessages(); - const { get } = useApi(); - const [dateRange, setDateRange] = useDateRange(websiteId); - const { startDate, endDate } = dateRange; const [showPicker, setShowPicker] = useState(false); - async function handleDateChange(value) { - if (value === 'all' && websiteId) { - const data = await get(`/websites/${websiteId}`); - - if (data) { - setDateRange({ value, ...getDateRangeValues(new Date(data.createdAt), Date.now()) }); - } - } else if (value !== 'all') { - setDateRange(value); - } - } - const options = [ { label: formatMessage(labels.today), value: '1day' }, { @@ -61,7 +52,7 @@ export function DateFilter({ websiteId, value, className }) { value: '90day', }, { label: formatMessage(labels.thisYear), value: '1year' }, - websiteId && { + showAllTime && { label: formatMessage(labels.allTime), value: 'all', divider: true, @@ -86,12 +77,12 @@ export function DateFilter({ websiteId, value, className }) { setShowPicker(true); return; } - handleDateChange(value); + onChange(value); }; const handlePickerChange = value => { setShowPicker(false); - handleDateChange(value); + onChange(value); }; const handleClose = () => setShowPicker(false); @@ -103,7 +94,8 @@ export function DateFilter({ websiteId, value, className }) { items={options} renderValue={renderValue} value={value} - alignment="end" + alignment={alignment} + placeholder={formatMessage(labels.selectDate)} onChange={handleChange} > {({ label, value, divider }) => ( diff --git a/components/input/WebsiteDateFilter.js b/components/input/WebsiteDateFilter.js new file mode 100644 index 00000000..0d5d7569 --- /dev/null +++ b/components/input/WebsiteDateFilter.js @@ -0,0 +1,26 @@ +import { getDateRangeValues } from 'lib/date'; +import useApi from 'hooks/useApi'; +import useDateRange from 'hooks/useDateRange'; +import DateFilter from './DateFilter'; + +export default function WebsiteDateFilter({ websiteId, value }) { + const { get } = useApi(); + const [dateRange, setDateRange] = useDateRange(websiteId); + const { startDate, endDate } = dateRange; + + const handleChange = async value => { + if (value === 'all' && websiteId) { + const data = await get(`/websites/${websiteId}`); + + if (data) { + setDateRange({ value, ...getDateRangeValues(new Date(data.createdAt), Date.now()) }); + } + } else if (value !== 'all') { + setDateRange(value); + } + }; + + return ( + + ); +} diff --git a/components/layout/NavBar.js b/components/layout/NavBar.js index 5a6c877e..b5532ba0 100644 --- a/components/layout/NavBar.js +++ b/components/layout/NavBar.js @@ -18,6 +18,7 @@ export function NavBar() { const links = [ { label: formatMessage(labels.dashboard), url: '/dashboard' }, + { label: formatMessage(labels.reports), url: '/reports' }, { label: formatMessage(labels.realtime), url: '/realtime' }, !cloudMode && { label: formatMessage(labels.settings), url: '/settings' }, ].filter(n => n); diff --git a/components/messages.js b/components/messages.js index 245e8591..bdc8770d 100644 --- a/components/messages.js +++ b/components/messages.js @@ -97,6 +97,7 @@ export const labels = defineMessages({ allTime: { id: 'label.all-time', defaultMessage: 'All time' }, customRange: { id: 'label.custom-range', defaultMessage: 'Custom range' }, selectWebsite: { id: 'label.select-website', defaultMessage: 'Select website' }, + selectDate: { id: 'label.select-date', defaultMessage: 'Select date' }, all: { id: 'label.all', defaultMessage: 'All' }, sessions: { id: 'label.sessions', defaultMessage: 'Sessions' }, pageNotFound: { id: 'message.page-not-found', defaultMessage: 'Page not found' }, @@ -117,6 +118,8 @@ export const labels = defineMessages({ view: { id: 'label.view', defaultMessage: 'View' }, cities: { id: 'label.cities', defaultMessage: 'Cities' }, regions: { id: 'label.regions', defaultMessage: 'Regions' }, + reports: { id: 'label.reports', defaultMessage: 'Reports' }, + eventData: { id: 'label.event-data', defaultMessage: 'Event data' }, }); export const messages = defineMessages({ diff --git a/components/metrics/WebsiteChart.js b/components/metrics/WebsiteChart.js index 6614d40f..7b902df1 100644 --- a/components/metrics/WebsiteChart.js +++ b/components/metrics/WebsiteChart.js @@ -5,7 +5,7 @@ import classNames from 'classnames'; import PageviewsChart from './PageviewsChart'; import MetricsBar from './MetricsBar'; import WebsiteHeader from './WebsiteHeader'; -import DateFilter from 'components/input/DateFilter'; +import WebsiteDateFilter from 'components/input/WebsiteDateFilter'; import ErrorMessage from 'components/common/ErrorMessage'; import FilterTags from 'components/metrics/FilterTags'; import RefreshButton from 'components/input/RefreshButton'; @@ -107,7 +107,7 @@ export function WebsiteChart({
- +
diff --git a/components/pages/reports/EventDataReport.js b/components/pages/reports/EventDataReport.js new file mode 100644 index 00000000..8c01e25f --- /dev/null +++ b/components/pages/reports/EventDataReport.js @@ -0,0 +1,33 @@ +import { useState } from 'react'; +import { Form, FormRow, FormInput, TextField } from 'react-basics'; +import AppLayout from 'components/layout/AppLayout'; +import Report from './Report'; +import ReportHeader from './ReportHeader'; +import useMessages from 'hooks/useMessages'; +import Nodes from 'assets/nodes.svg'; +import styles from './reports.module.css'; + +export default function EventDataReport({ websiteId, data }) { + const [values, setValues] = useState({ query: '' }); + const { formatMessage, labels } = useMessages(); + + return ( + + + } /> +
+
+
+ + + + + +
+
+
+
+
+
+ ); +} diff --git a/components/pages/reports/Report.js b/components/pages/reports/Report.js new file mode 100644 index 00000000..1754e89f --- /dev/null +++ b/components/pages/reports/Report.js @@ -0,0 +1,5 @@ +import Page from 'components/layout/Page'; + +export default function Report({ children, ...props }) { + return {children}; +} diff --git a/components/pages/reports/ReportHeader.js b/components/pages/reports/ReportHeader.js new file mode 100644 index 00000000..44502149 --- /dev/null +++ b/components/pages/reports/ReportHeader.js @@ -0,0 +1,42 @@ +import { useState } from 'react'; +import { Flexbox, Icon, Text } from 'react-basics'; +import WebsiteSelect from 'components/input/WebsiteSelect'; +import PageHeader from 'components/layout/PageHeader'; +import DateFilter from 'components/input/DateFilter'; +import { parseDateRange } from 'lib/date'; + +export default function ReportHeader({ title, icon }) { + const [websiteId, setWebsiteId] = useState(); + const [dateRange, setDateRange] = useState({}); + const { value, startDate, endDate } = dateRange; + + const handleSelect = id => { + setWebsiteId(id); + }; + + const handleDateChange = value => setDateRange(parseDateRange(value)); + + const Title = () => { + return ( + <> + {icon} + {title} + + ); + }; + + return ( + }> + + + + + + ); +} diff --git a/components/pages/reports/ReportsList.js b/components/pages/reports/ReportsList.js new file mode 100644 index 00000000..44dc556d --- /dev/null +++ b/components/pages/reports/ReportsList.js @@ -0,0 +1,66 @@ +import Link from 'next/link'; +import { Button, Icons, Text, Icon } from 'react-basics'; +import Page from 'components/layout/Page'; +import PageHeader from 'components/layout/PageHeader'; +import Funnel from 'assets/funnel.svg'; +import Nodes from 'assets/nodes.svg'; +import Lightbulb from 'assets/lightbulb.svg'; +import styles from './ReportsList.module.css'; + +const reports = [ + { + title: 'Event data', + description: 'Query your event data.', + url: '/reports/event-data', + icon: , + }, + { + title: 'Funnel', + description: 'Understand the conversion and drop-off rate of users.', + url: '/reports/funnel', + icon: , + }, + { + title: 'Insights', + description: 'Explore your data by applying segments and filters.', + url: '/reports/insights', + icon: , + }, +]; + +function Report({ title, description, url, icon }) { + return ( +
+
+ {icon} + {title} +
+
{description}
+
+ + + +
+
+ ); +} + +export default function ReportsList() { + return ( + + +
+ {reports.map(({ title, description, url, icon }) => { + return ( + + ); + })} +
+
+ ); +} diff --git a/components/pages/reports/ReportsList.module.css b/components/pages/reports/ReportsList.module.css new file mode 100644 index 00000000..0cdcb835 --- /dev/null +++ b/components/pages/reports/ReportsList.module.css @@ -0,0 +1,32 @@ +.reports { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(360px, 1fr)); + gap: 20px; +} + +.report { + display: flex; + flex-direction: column; + gap: 20px; + padding: 20px; + border: 1px solid var(--base500); + border-radius: var(--border-radius); +} + +.title { + display: flex; + gap: 10px; + align-items: center; + font-size: var(--font-size-lg); + font-weight: 700; +} + +.description { + flex: 1; +} + +.buttons { + display: flex; + align-items: center; + justify-content: center; +} diff --git a/components/pages/reports/reports.module.css b/components/pages/reports/reports.module.css new file mode 100644 index 00000000..e18a857a --- /dev/null +++ b/components/pages/reports/reports.module.css @@ -0,0 +1,14 @@ +.container { + display: grid; + grid-template-rows: 1fr; + grid-template-columns: max-content 1fr; +} + +.menu { + width: 300px; + grid-column: 1 / 2; +} + +.content { + grid-column: 2 / 3; +} diff --git a/components/pages/settings/profile/DateRangeSetting.js b/components/pages/settings/profile/DateRangeSetting.js index 152aba1d..44c3dc42 100644 --- a/components/pages/settings/profile/DateRangeSetting.js +++ b/components/pages/settings/profile/DateRangeSetting.js @@ -9,11 +9,12 @@ export function DateRangeSetting() { const [dateRange, setDateRange] = useDateRange(); const { startDate, endDate, value } = dateRange; + const handleChange = value => setDateRange(value); const handleReset = () => setDateRange(DEFAULT_DATE_RANGE); return ( - + ); diff --git a/hooks/index.js b/hooks/index.js new file mode 100644 index 00000000..15898eb5 --- /dev/null +++ b/hooks/index.js @@ -0,0 +1,17 @@ +export * from './useApi'; +export * from './useConfig'; +export * from './useCountryNames'; +export * from './useDateRange'; +export * from './useDocumentClick'; +export * from './useEscapeKey'; +export * from './useForceUpdate'; +export * from './useLanguageNames'; +export * from './useLocale'; +export * from './useMessages'; +export * from './usePageQuery'; +export * from './useRequireLogin'; +export * from './useShareToken'; +export * from './useSticky'; +export * from './useTheme'; +export * from './useTimezone'; +export * from './useUser'; diff --git a/hooks/useApi.ts b/hooks/useApi.ts index 9e1e7e20..f41547a9 100644 --- a/hooks/useApi.ts +++ b/hooks/useApi.ts @@ -7,7 +7,7 @@ import useStore from 'store/app'; const selector = state => state.shareToken; -export default function useApi() { +export function useApi() { const { basePath } = useRouter(); const shareToken = useStore(selector); @@ -18,3 +18,5 @@ export default function useApi() { return { get, post, put, del, ...reactQuery }; } + +export default useApi; diff --git a/hooks/useConfig.js b/hooks/useConfig.js index b395829c..6dda7b74 100644 --- a/hooks/useConfig.js +++ b/hooks/useConfig.js @@ -4,7 +4,7 @@ import useApi from 'hooks/useApi'; let loading = false; -export default function useConfig() { +export function useConfig() { const { config } = useStore(); const { get } = useApi(); @@ -23,3 +23,5 @@ export default function useConfig() { return config || {}; } + +export default useConfig; diff --git a/hooks/useCountryNames.js b/hooks/useCountryNames.js index 0834202b..60c992a5 100644 --- a/hooks/useCountryNames.js +++ b/hooks/useCountryNames.js @@ -7,7 +7,7 @@ const countryNames = { 'en-US': enUS, }; -export default function useCountryNames(locale) { +export function useCountryNames(locale) { const [list, setList] = useState(countryNames[locale] || enUS); const { basePath } = useRouter(); @@ -32,3 +32,5 @@ export default function useCountryNames(locale) { return list; } + +export default useCountryNames; diff --git a/hooks/useDateRange.js b/hooks/useDateRange.js index a9896065..e8a542f4 100644 --- a/hooks/useDateRange.js +++ b/hooks/useDateRange.js @@ -5,7 +5,7 @@ import useLocale from './useLocale'; import websiteStore, { setWebsiteDateRange } from 'store/websites'; import appStore, { setDateRange } from 'store/app'; -export default function useDateRange(websiteId) { +export function useDateRange(websiteId) { const { locale } = useLocale(); const websiteConfig = websiteStore(state => state[websiteId]?.dateRange); const defaultConfig = DEFAULT_DATE_RANGE; @@ -23,3 +23,5 @@ export default function useDateRange(websiteId) { return [dateRange, saveDateRange]; } + +export default useDateRange; diff --git a/hooks/useDocumentClick.js b/hooks/useDocumentClick.js index e1baae7e..be3d09be 100644 --- a/hooks/useDocumentClick.js +++ b/hooks/useDocumentClick.js @@ -1,6 +1,6 @@ import { useEffect } from 'react'; -export default function useDocumentClick(handler) { +export function useDocumentClick(handler) { useEffect(() => { document.addEventListener('click', handler); @@ -11,3 +11,5 @@ export default function useDocumentClick(handler) { return null; } + +export default useDocumentClick; diff --git a/hooks/useEscapeKey.js b/hooks/useEscapeKey.js index b8020c31..1a17f18f 100644 --- a/hooks/useEscapeKey.js +++ b/hooks/useEscapeKey.js @@ -1,6 +1,6 @@ import { useEffect, useCallback } from 'react'; -export default function useEscapeKey(handler) { +export function useEscapeKey(handler) { const escFunction = useCallback(event => { if (event.keyCode === 27) { handler(event); @@ -17,3 +17,5 @@ export default function useEscapeKey(handler) { return null; } + +export default useEscapeKey; diff --git a/hooks/useForceUpdate.js b/hooks/useForceUpdate.js index 2b8d6101..35f7fe16 100644 --- a/hooks/useForceUpdate.js +++ b/hooks/useForceUpdate.js @@ -1,9 +1,11 @@ import { useCallback, useState } from 'react'; -export default function useForceUpdate() { +export function useForceUpdate() { const [, update] = useState(Object.create(null)); return useCallback(() => { update(Object.create(null)); }, [update]); } + +export default useForceUpdate; diff --git a/hooks/useLanguageNames.js b/hooks/useLanguageNames.js index 3b153f28..aa5cbe39 100644 --- a/hooks/useLanguageNames.js +++ b/hooks/useLanguageNames.js @@ -7,7 +7,7 @@ const languageNames = { 'en-US': enUS, }; -export default function useLanguageNames(locale) { +export function useLanguageNames(locale) { const [list, setList] = useState(languageNames[locale] || enUS); const { basePath } = useRouter(); @@ -32,3 +32,5 @@ export default function useLanguageNames(locale) { return list; } + +export default useLanguageNames; diff --git a/hooks/useLocale.js b/hooks/useLocale.js index 5cece347..1279cea9 100644 --- a/hooks/useLocale.js +++ b/hooks/useLocale.js @@ -13,7 +13,7 @@ const messages = { const selector = state => state.locale; -export default function useLocale() { +export function useLocale() { const locale = useStore(selector); const { basePath } = useRouter(); const forceUpdate = useForceUpdate(); @@ -61,3 +61,5 @@ export default function useLocale() { return { locale, saveLocale, messages, dir, dateLocale }; } + +export default useLocale; diff --git a/hooks/useMessages.js b/hooks/useMessages.js index 1bb65778..0719afd8 100644 --- a/hooks/useMessages.js +++ b/hooks/useMessages.js @@ -1,7 +1,7 @@ import { useIntl, FormattedMessage } from 'react-intl'; import { messages, labels } from 'components/messages'; -export default function useMessages() { +export function useMessages() { const { formatMessage } = useIntl(); function getMessage(id) { @@ -12,3 +12,5 @@ export default function useMessages() { return { formatMessage, FormattedMessage, messages, labels, getMessage }; } + +export default useMessages; diff --git a/hooks/usePageQuery.js b/hooks/usePageQuery.js index b2f0acf1..b275d580 100644 --- a/hooks/usePageQuery.js +++ b/hooks/usePageQuery.js @@ -2,7 +2,7 @@ import { useMemo } from 'react'; import { useRouter } from 'next/router'; import { buildUrl } from 'next-basics'; -export default function usePageQuery() { +export function usePageQuery() { const router = useRouter(); const { pathname, search } = location; const { asPath } = router; @@ -29,3 +29,5 @@ export default function usePageQuery() { return { pathname, query, resolveUrl, router }; } + +export default usePageQuery; diff --git a/hooks/useRequireLogin.js b/hooks/useRequireLogin.js index 24cfdf0b..3a95c988 100644 --- a/hooks/useRequireLogin.js +++ b/hooks/useRequireLogin.js @@ -3,7 +3,7 @@ import { useRouter } from 'next/router'; import useApi from 'hooks/useApi'; import useUser from 'hooks/useUser'; -export default function useRequireLogin() { +export function useRequireLogin() { const router = useRouter(); const { get } = useApi(); const { user, setUser } = useUser(); @@ -26,3 +26,5 @@ export default function useRequireLogin() { return { user }; } + +export default useRequireLogin; diff --git a/hooks/useShareToken.js b/hooks/useShareToken.js index bac7ec97..3d6b9698 100644 --- a/hooks/useShareToken.js +++ b/hooks/useShareToken.js @@ -4,7 +4,7 @@ import useApi from './useApi'; const selector = state => state.shareToken; -export default function useShareToken(shareId) { +export function useShareToken(shareId) { const shareToken = useStore(selector); const { get } = useApi(); @@ -24,3 +24,5 @@ export default function useShareToken(shareId) { return shareToken; } + +export default useShareToken; diff --git a/hooks/useSticky.js b/hooks/useSticky.js index ae4dce72..be33f6ed 100644 --- a/hooks/useSticky.js +++ b/hooks/useSticky.js @@ -1,6 +1,6 @@ import { useState, useEffect, useRef } from 'react'; -export default function useSticky({ enabled = true, threshold = 1 }) { +export function useSticky({ enabled = true, threshold = 1 }) { const [isSticky, setIsSticky] = useState(false); const ref = useRef(null); @@ -21,3 +21,5 @@ export default function useSticky({ enabled = true, threshold = 1 }) { return { ref, isSticky }; } + +export default useSticky; diff --git a/hooks/useTheme.js b/hooks/useTheme.js index c50f442f..a10894e5 100644 --- a/hooks/useTheme.js +++ b/hooks/useTheme.js @@ -5,7 +5,7 @@ import { THEME_CONFIG } from 'lib/constants'; const selector = state => state.theme; -export default function useTheme() { +export function useTheme() { const defaultTheme = typeof window !== 'undefined' ? window?.matchMedia('(prefers-color-scheme: dark)')?.matches @@ -34,3 +34,5 @@ export default function useTheme() { return [theme, saveTheme]; } + +export default useTheme; diff --git a/hooks/useTimezone.js b/hooks/useTimezone.js index 8eb5d5f8..fb347c4d 100644 --- a/hooks/useTimezone.js +++ b/hooks/useTimezone.js @@ -3,7 +3,7 @@ import { getTimezone } from 'lib/date'; import { getItem, setItem } from 'next-basics'; import { TIMEZONE_CONFIG } from 'lib/constants'; -export default function useTimezone() { +export function useTimezone() { const [timezone, setTimezone] = useState(getItem(TIMEZONE_CONFIG) || getTimezone()); const saveTimezone = useCallback( @@ -16,3 +16,5 @@ export default function useTimezone() { return [timezone, saveTimezone]; } + +export default useTimezone; diff --git a/hooks/useUser.js b/hooks/useUser.js index 6b73c113..c5f1a826 100644 --- a/hooks/useUser.js +++ b/hooks/useUser.js @@ -2,8 +2,10 @@ import useStore, { setUser } from 'store/app'; const selector = state => state.user; -export default function useUser() { +export function useUser() { const user = useStore(selector); return { user, setUser }; } + +export default useUser; diff --git a/pages/api/websites/[id]/data.ts b/pages/api/websites/[id]/data.ts new file mode 100644 index 00000000..93e97067 --- /dev/null +++ b/pages/api/websites/[id]/data.ts @@ -0,0 +1,13 @@ +import { NextApiResponse } from 'next'; +import { useAuth } from 'lib/middleware'; +import { NextApiRequestQueryBody, User } from 'lib/types'; +import { ok } from 'next-basics'; + +export default async ( + req: NextApiRequestQueryBody, + res: NextApiResponse, +) => { + await useAuth(req, res); + + return ok(res, req.auth.user); +}; diff --git a/pages/reports/event-data/index.js b/pages/reports/event-data/index.js new file mode 100644 index 00000000..154c79be --- /dev/null +++ b/pages/reports/event-data/index.js @@ -0,0 +1,5 @@ +import EventDataReport from 'components/pages/reports/EventDataReport'; + +export default function Report() { + return ; +} diff --git a/pages/reports/index.js b/pages/reports/index.js new file mode 100644 index 00000000..b26023cc --- /dev/null +++ b/pages/reports/index.js @@ -0,0 +1,13 @@ +import AppLayout from 'components/layout/AppLayout'; +import ReportsList from 'components/pages/reports/ReportsList'; +import useMessages from 'hooks/useMessages'; + +export default function ReportsPage() { + const { formatMessage, labels } = useMessages(); + + return ( + + + + ); +}