From a8cd0932d8a250aca5ffb9d8a56097a5f3bbd8f2 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Tue, 21 Mar 2023 11:30:11 -0700 Subject: [PATCH 1/6] fixed send message for new url/referrer columns --- queries/analytics/event/saveEvent.ts | 4 ++-- queries/analytics/pageview/savePageView.ts | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/queries/analytics/event/saveEvent.ts b/queries/analytics/event/saveEvent.ts index 73ecee27..60195960 100644 --- a/queries/analytics/event/saveEvent.ts +++ b/queries/analytics/event/saveEvent.ts @@ -95,8 +95,8 @@ async function clickhouseQuery(data: { subdivision1: subdivision1 ? subdivision1 : null, subdivision2: subdivision2 ? subdivision2 : null, city: city ? city : null, - urlPath: urlPath?.substring(0, URL_LENGTH), - urlQuery: urlQuery?.substring(0, URL_LENGTH), + url_path: urlPath?.substring(0, URL_LENGTH), + url_query: urlQuery?.substring(0, URL_LENGTH), page_title: pageTitle, event_type: EVENT_TYPE.customEvent, event_name: eventName?.substring(0, EVENT_NAME_LENGTH), diff --git a/queries/analytics/pageview/savePageView.ts b/queries/analytics/pageview/savePageView.ts index 9a2a9c9e..e5c39ea7 100644 --- a/queries/analytics/pageview/savePageView.ts +++ b/queries/analytics/pageview/savePageView.ts @@ -114,11 +114,11 @@ async function clickhouseQuery(data: { subdivision1: subdivision1 ? subdivision1 : null, subdivision2: subdivision2 ? subdivision2 : null, city: city ? city : null, - urlPath: urlPath?.substring(0, URL_LENGTH), - urlQuery: urlQuery?.substring(0, URL_LENGTH), - referrerPath: referrerPath?.substring(0, URL_LENGTH), - referrerQuery: referrerQuery?.substring(0, URL_LENGTH), - referrerDomain: referrerDomain?.substring(0, URL_LENGTH), + url_path: urlPath?.substring(0, URL_LENGTH), + url_query: urlQuery?.substring(0, URL_LENGTH), + referrer_path: referrerPath?.substring(0, URL_LENGTH), + referrer_query: referrerQuery?.substring(0, URL_LENGTH), + referrer_domain: referrerDomain?.substring(0, URL_LENGTH), page_title: pageTitle, event_type: EVENT_TYPE.pageView, created_at: getDateFormat(new Date()), From fc2a8f3d9fad7355b1e26f7ac37c76b6dc029ae8 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Tue, 21 Mar 2023 21:28:36 -0700 Subject: [PATCH 2/6] Updated navigation. --- components/input/LanguageButton.js | 21 +++---- components/input/ProfileButton.js | 53 ++++++++++++++++ components/input/ProfileButton.module.css | 9 +++ components/input/ThemeButton.js | 22 +++---- components/layout/AppLayout.module.css | 18 ++++-- components/layout/NavBar.js | 61 +++++++----------- components/layout/NavBar.module.css | 63 ++++++++----------- components/layout/Page.js | 2 +- components/metrics/WebsiteChart.js | 2 +- .../pages/settings/profile/ProfileDetails.js | 9 +-- hooks/useMessages.js | 8 +++ pages/_app.js | 2 +- styles/index.css | 9 +-- 13 files changed, 156 insertions(+), 123 deletions(-) create mode 100644 components/input/ProfileButton.js create mode 100644 components/input/ProfileButton.module.css create mode 100644 hooks/useMessages.js diff --git a/components/input/LanguageButton.js b/components/input/LanguageButton.js index afd447ac..e6da5991 100644 --- a/components/input/LanguageButton.js +++ b/components/input/LanguageButton.js @@ -1,14 +1,11 @@ -import { Icon, Button, PopupTrigger, Popup, Tooltip, Text } from 'react-basics'; -import { useIntl } from 'react-intl'; +import { Icon, Button, PopupTrigger, Popup, Text } from 'react-basics'; 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', menuPosition = 'right' }) { - const { formatMessage } = useIntl(); +export default function LanguageButton() { const { locale, saveLocale } = useLocale(); const items = Object.keys(languages).map(key => ({ ...languages[key], value: key })); @@ -18,14 +15,12 @@ export default function LanguageButton({ tooltipPosition = 'top', menuPosition = return ( - - - - + +
{items.map(({ value, label }) => { return ( diff --git a/components/input/ProfileButton.js b/components/input/ProfileButton.js new file mode 100644 index 00000000..c7b46a8f --- /dev/null +++ b/components/input/ProfileButton.js @@ -0,0 +1,53 @@ +import { Icon, Button, PopupTrigger, Popup, Menu, Item, Text } from 'react-basics'; +import { useRouter } from 'next/router'; +import Icons from 'components/icons'; +import useMessages from 'hooks/useMessages'; +import useUser from 'hooks/useUser'; +import styles from './ProfileButton.module.css'; + +export default function ProfileButton() { + const { formatMessage, labels } = useMessages(); + const { user } = useUser(); + const router = useRouter(); + + const handleSelect = key => { + if (key === 'profile') { + router.push('/settings/profile'); + } + if (key === 'logout') { + router.push('/logout'); + } + }; + + return ( + + + + + + {user.username} + + + + + + {formatMessage(labels.profile)} + + + + + + {formatMessage(labels.logout)} + + + + + ); +} diff --git a/components/input/ProfileButton.module.css b/components/input/ProfileButton.module.css new file mode 100644 index 00000000..2ed5306b --- /dev/null +++ b/components/input/ProfileButton.module.css @@ -0,0 +1,9 @@ +.menu { + width: 200px; +} + +.item { + display: flex; + gap: 12px; + background: var(--base50); +} diff --git a/components/input/ThemeButton.js b/components/input/ThemeButton.js index cca3560a..692e886f 100644 --- a/components/input/ThemeButton.js +++ b/components/input/ThemeButton.js @@ -1,14 +1,12 @@ import { useTransition, animated } from 'react-spring'; -import { Button, Icon, Tooltip } from 'react-basics'; +import { Button, Icon } 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' }) { +export default function ThemeButton() { const [theme, setTheme] = useTheme(); - const { formatMessage } = useIntl(); const transitions = useTransition(theme, { initial: { opacity: 1 }, @@ -28,14 +26,12 @@ export default function ThemeButton({ tooltipPosition = 'top' }) { } return ( - - - + ); } diff --git a/components/layout/AppLayout.module.css b/components/layout/AppLayout.module.css index 746ada39..8ed52c92 100644 --- a/components/layout/AppLayout.module.css +++ b/components/layout/AppLayout.module.css @@ -1,15 +1,21 @@ .layout { display: grid; - grid-template-rows: 1fr; - grid-template-columns: max-content 1fr; + grid-template-rows: max-content 1fr; + grid-template-columns: 1fr; } .nav { - grid-row: 1 / 3; + height: 60px; + width: 100vw; + z-index: 100; + grid-column: 1; + grid-row: 1 / 2; } .body { - grid-area: 1 / 2; - overflow: auto; - height: 100vh; + grid-column: 1; + grid-row: 2 / 3; + min-height: 0; + max-height: calc(100vh - 60px); + overflow-y: auto; } diff --git a/components/layout/NavBar.js b/components/layout/NavBar.js index 723da5df..d4defcb6 100644 --- a/components/layout/NavBar.js +++ b/components/layout/NavBar.js @@ -1,69 +1,50 @@ import { useState } from 'react'; -import { useIntl } from 'react-intl'; import { Icon, Text } from 'react-basics'; +import Link from 'next/link'; import classNames from 'classnames'; import Icons from 'components/icons'; import ThemeButton from 'components/input/ThemeButton'; import LanguageButton from 'components/input/LanguageButton'; -import LogoutButton from 'components/input/LogoutButton'; -import { labels } from 'components/messages'; -import useUser from 'hooks/useUser'; -import NavGroup from './NavGroup'; +import ProfileButton from 'components/input/ProfileButton'; import styles from './NavBar.module.css'; import useConfig from 'hooks/useConfig'; +import useMessages from 'hooks/useMessages'; export default function NavBar() { - const { user } = useUser(); const { cloudMode } = useConfig(); - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); const [minimized, setMinimized] = useState(false); - const tooltipPosition = minimized ? 'right' : 'top'; - const analytics = [ + const links = [ { label: formatMessage(labels.dashboard), url: '/dashboard', icon: }, { label: formatMessage(labels.realtime), url: '/realtime', icon: }, - ]; - - const settings = [ - !cloudMode && { - label: formatMessage(labels.websites), - url: '/settings/websites', - icon: , - }, - user?.isAdmin && { - label: formatMessage(labels.users), - url: '/settings/users', - icon: , - }, - !cloudMode && { - label: formatMessage(labels.teams), - url: '/settings/teams', - icon: , - }, - { label: formatMessage(labels.profile), url: '/settings/profile', icon: }, + !cloudMode && { label: formatMessage(labels.settings), url: '/settings', icon: }, ].filter(n => n); const handleMinimize = () => setMinimized(state => !state); return (
-
+
umami - - -
- - -
-
- - - {!cloudMode && } -
+
+ {links.map(({ url, icon, label }) => { + return ( + + {icon} + {label} + + ); + })} +
+
+ + + {!cloudMode && }
); diff --git a/components/layout/NavBar.module.css b/components/layout/NavBar.module.css index 1d387643..4accadf4 100644 --- a/components/layout/NavBar.module.css +++ b/components/layout/NavBar.module.css @@ -1,62 +1,49 @@ .navbar { position: relative; display: flex; - flex-direction: column; + flex-direction: row; align-items: center; + height: 60px; background: var(--base75); - height: 100%; - width: 200px; - border-right: 2px solid var(--base200); + border-bottom: 1px solid var(--base200); + padding: 0 20px; } -.header { +.logo { display: flex; align-items: center; justify-content: center; gap: 10px; font-size: 16px; font-weight: 700; - padding: 20px 0; cursor: pointer; + min-width: 0; } -.header:hover .icon { - visibility: visible; -} - -.icon { - visibility: hidden; - position: absolute; - right: -10px; - border-radius: 100%; - color: var(--base50); - background: var(--base800); - height: 20px; - width: 20px; -} - -.minimized.navbar { - width: 60px; -} - -.minimized .text { - display: none; -} - -.footer { - flex: 1; +.links { display: flex; - flex-direction: column; - justify-content: flex-end; - padding: 20px; + flex-direction: row; + gap: 20px; + padding: 0 40px; + flex: 1; + font-weight: 700; } -.buttons { +.links a { display: flex; align-items: center; - justify-content: center; + gap: 10px; + color: var(--font-color100); } -.minimized .buttons { - flex-direction: column; +.links a:hover { + color: var(--primary400); +} + +.actions { + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-end; + min-width: 0; } diff --git a/components/layout/Page.js b/components/layout/Page.js index ff6e049d..e8ddc44a 100644 --- a/components/layout/Page.js +++ b/components/layout/Page.js @@ -1,6 +1,6 @@ import classNames from 'classnames'; -import styles from './Page.module.css'; import { Banner, Loading } from 'react-basics'; +import styles from './Page.module.css'; export default function Page({ className, error, loading, children }) { if (error) { diff --git a/components/metrics/WebsiteChart.js b/components/metrics/WebsiteChart.js index 9489767c..f11410d8 100644 --- a/components/metrics/WebsiteChart.js +++ b/components/metrics/WebsiteChart.js @@ -84,7 +84,7 @@ export default function WebsiteChart({ diff --git a/components/pages/settings/profile/ProfileDetails.js b/components/pages/settings/profile/ProfileDetails.js index d8f183c2..5569053c 100644 --- a/components/pages/settings/profile/ProfileDetails.js +++ b/components/pages/settings/profile/ProfileDetails.js @@ -1,15 +1,14 @@ import { Form, FormRow } from 'react-basics'; -import { useIntl } from 'react-intl'; import TimezoneSetting from 'components/pages/settings/profile/TimezoneSetting'; import DateRangeSetting from 'components/pages/settings/profile/DateRangeSetting'; import LanguageSetting from 'components/pages/settings/profile/LanguageSetting'; import ThemeSetting from 'components/pages/settings/profile/ThemeSetting'; import useUser from 'hooks/useUser'; -import { labels } from 'components/messages'; +import useMessages from 'hooks/useMessages'; export default function ProfileDetails() { const { user } = useUser(); - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); if (!user) { return null; @@ -20,7 +19,9 @@ export default function ProfileDetails() { return (
{username} - {role} + + {formatMessage(labels[role] || labels.unknown)} + diff --git a/hooks/useMessages.js b/hooks/useMessages.js new file mode 100644 index 00000000..b0202fce --- /dev/null +++ b/hooks/useMessages.js @@ -0,0 +1,8 @@ +import { useIntl } from 'react-intl'; +import { messages, labels } from 'components/messages'; + +export default function useMessages() { + const { formatMessage } = useIntl(); + + return { formatMessage, messages, labels }; +} diff --git a/pages/_app.js b/pages/_app.js index ad9e71ac..21116467 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -29,7 +29,7 @@ export default function App({ Component, pageProps }) { const Wrapper = ({ children }) => {children}; - if (!config || config.uiDisabled) { + if (config?.uiDisabled) { return null; } diff --git a/styles/index.css b/styles/index.css index 42b2534e..1a57208e 100644 --- a/styles/index.css +++ b/styles/index.css @@ -1,8 +1,3 @@ -html { - overflow-x: hidden; - margin-right: calc(-1 * (100vw - 100%)); -} - html, body { font-family: Inter, -apple-system, system-ui, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, @@ -20,6 +15,7 @@ body { flex: 1; color: var(--font-color100); background: var(--base50); + overflow: hidden; } *, @@ -64,7 +60,8 @@ svg { #__next { display: flex; flex-direction: column; + flex: 1; width: 100%; height: 100%; - flex: 1; + overflow: hidden; } From f3e1f18e1b1f8c19aa6152b8970ecf3dcbc23195 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Wed, 22 Mar 2023 01:53:34 -0700 Subject: [PATCH 3/6] Updated sticky header logic. --- components/common/StickyHeader.js | 32 ------------------ components/metrics/WebsiteChart.js | 33 ++++++++++--------- components/metrics/WebsiteChart.module.css | 15 +++++---- .../pages/realtime/RealtimeDashboard.js | 5 +-- hooks/useSticky.js | 22 ++++++------- package.json | 2 +- yarn.lock | 8 ++--- 7 files changed, 41 insertions(+), 76 deletions(-) delete mode 100644 components/common/StickyHeader.js diff --git a/components/common/StickyHeader.js b/components/common/StickyHeader.js deleted file mode 100644 index 639709f9..00000000 --- a/components/common/StickyHeader.js +++ /dev/null @@ -1,32 +0,0 @@ -import { useMeasure } from 'react-basics'; -import classNames from 'classnames'; -import useSticky from 'hooks/useSticky'; - -export default function StickyHeader({ - className, - stickyClassName, - stickyStyle, - enabled = true, - scrollElement, - children, -}) { - const { ref: scrollRef, isSticky } = useSticky({ scrollElement }); - const { ref: measureRef, dimensions } = useMeasure(); - const active = enabled && isSticky; - - return ( -
-
- {children} -
-
- ); -} diff --git a/components/metrics/WebsiteChart.js b/components/metrics/WebsiteChart.js index f11410d8..c3a1268b 100644 --- a/components/metrics/WebsiteChart.js +++ b/components/metrics/WebsiteChart.js @@ -2,11 +2,11 @@ import { useMemo } from 'react'; import { useIntl } from 'react-intl'; import { Button, Icon, Text, Row, Column } from 'react-basics'; import Link from 'next/link'; +import classNames from 'classnames'; import PageviewsChart from './PageviewsChart'; import MetricsBar from './MetricsBar'; import WebsiteHeader from './WebsiteHeader'; import DateFilter from 'components/input/DateFilter'; -import StickyHeader from 'components/common/StickyHeader'; import ErrorMessage from 'components/common/ErrorMessage'; import FilterTags from 'components/metrics/FilterTags'; import RefreshButton from 'components/input/RefreshButton'; @@ -16,9 +16,9 @@ import useTimezone from 'hooks/useTimezone'; import usePageQuery from 'hooks/usePageQuery'; import { getDateArray, getDateLength } from 'lib/date'; import Icons from 'components/icons'; -import { UI_LAYOUT_BODY } from 'lib/constants'; import { labels } from 'components/messages'; import styles from './WebsiteChart.module.css'; +import useSticky from '../../hooks/useSticky'; export default function WebsiteChart({ websiteId, @@ -37,6 +37,7 @@ export default function WebsiteChart({ query: { url, referrer, os, browser, device, country }, } = 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], @@ -81,21 +82,21 @@ export default function WebsiteChart({ )} - - - - - - - - - - - + + + + + + + + {error && } diff --git a/components/metrics/WebsiteChart.module.css b/components/metrics/WebsiteChart.module.css index 3a3d4718..3db8bc63 100644 --- a/components/metrics/WebsiteChart.module.css +++ b/components/metrics/WebsiteChart.module.css @@ -17,22 +17,23 @@ } .header { - position: relative; display: flex; justify-content: space-between; align-items: center; + padding: 10px 0; min-height: 90px; margin-bottom: 20px; + background: var(--base50); } .sticky { - position: fixed; - top: 0; - background: var(--base50); + position: sticky; + top: -1px; + z-index: 2; +} + +.isSticky { border-bottom: 1px solid var(--base300); - z-index: 3; - width: inherit; - padding-top: 10px; } .actions { diff --git a/components/pages/realtime/RealtimeDashboard.js b/components/pages/realtime/RealtimeDashboard.js index 9c33d2ac..35906c45 100644 --- a/components/pages/realtime/RealtimeDashboard.js +++ b/components/pages/realtime/RealtimeDashboard.js @@ -6,7 +6,6 @@ import firstBy from 'thenby'; import { GridRow, GridColumn } from 'components/layout/Grid'; import Page from 'components/layout/Page'; import RealtimeChart from 'components/metrics/RealtimeChart'; -import StickyHeader from 'components/common/StickyHeader'; import PageHeader from 'components/layout/PageHeader'; import WorldMap from 'components/common/WorldMap'; import RealtimeLog from 'components/pages/realtime/RealtimeLog'; @@ -104,9 +103,7 @@ export default function RealtimeDashboard({ websiteId }) { - - - +
diff --git a/hooks/useSticky.js b/hooks/useSticky.js index dc264f9d..e2f561b3 100644 --- a/hooks/useSticky.js +++ b/hooks/useSticky.js @@ -1,25 +1,23 @@ import { useState, useEffect, useRef } from 'react'; -export default function useSticky({ scrollElement = document, defaultSticky = false }) { +export default function useSticky({ defaultSticky = false, enabled = true }) { const [isSticky, setIsSticky] = useState(defaultSticky); const ref = useRef(null); - const initialTop = useRef(null); useEffect(() => { - const handleScroll = () => { - setIsSticky((scrollElement?.scrollTop ?? window.scrollY) > initialTop.current); - }; + let observer; + const handler = ([entry]) => setIsSticky(entry.intersectionRatio < 1); - if (initialTop.current === null) { - initialTop.current = ref?.current?.offsetTop; + if (enabled && ref.current) { + observer = new IntersectionObserver(handler, { threshold: [1] }); + observer.observe(ref.current); } - - scrollElement.addEventListener('scroll', handleScroll, true); - return () => { - scrollElement.removeEventListener('scroll', handleScroll, true); + if (observer) { + observer.disconnect(); + } }; - }, [ref, setIsSticky, scrollElement]); + }, [ref]); return { ref, isSticky }; } diff --git a/package.json b/package.json index 5153f6cc..71452dca 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "node-fetch": "^3.2.8", "npm-run-all": "^4.1.5", "react": "^18.2.0", - "react-basics": "^0.73.0", + "react-basics": "^0.74.0", "react-beautiful-dnd": "^13.1.0", "react-dom": "^18.2.0", "react-intl": "^5.24.7", diff --git a/yarn.lock b/yarn.lock index ed85f811..28d0d787 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7076,10 +7076,10 @@ rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-basics@^0.73.0: - version "0.73.0" - resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.73.0.tgz#9555563f3407ac417dc833dfca47588123d55535" - integrity sha512-eEK8yWWrXO7JATBlPKBfFQlD1hNZoNeEtlYNx+QjOCLKu1qjClutP5nXWHmX4gHE97XFwUKzbTU35NkNEy5C0w== +react-basics@^0.74.0: + version "0.74.0" + resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.74.0.tgz#153433bc485d6b71d8edf377d1a83f1d55133e24" + integrity sha512-Z9XwgEOSRvcPqFqFZL6HR59t/XrqhIB8uoYwbmon3IFX2W0kOPqkX1Box0c+2BibJoHp4N4mbfuZWK2kSEnq9g== dependencies: classnames "^2.3.1" date-fns "^2.29.3" From 9ddb8b5d25305594ad1ca4872fae5561f02a5efa Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Wed, 22 Mar 2023 14:05:55 -0700 Subject: [PATCH 4/6] Use useMessages hook everywhere. --- components/common/ErrorMessage.js | 5 ++--- components/common/UpdateNotice.js | 7 +++---- components/input/DateFilter.js | 5 ++--- components/input/LogoutButton.js | 5 ++--- components/input/RefreshButton.js | 5 ++--- components/input/SettingsButton.js | 5 ++--- components/input/ThemeButton.js | 1 - components/input/WebsiteSelect.js | 5 ++--- components/messages.js | 8 ++------ components/metrics/ActiveUsers.js | 5 ++--- components/metrics/CountriesTable.js | 16 +++++----------- components/metrics/DatePickerForm.js | 7 +++---- components/metrics/DevicesTable.js | 5 ++--- components/metrics/EventsTable.js | 13 ++++--------- components/metrics/FilterTags.js | 7 +++---- components/metrics/MetricsBar.js | 5 ++--- components/metrics/MetricsTable.js | 10 +++------- components/metrics/OSTable.js | 5 ++--- components/metrics/PagesTable.js | 5 ++--- components/metrics/PageviewsChart.js | 9 ++++----- components/metrics/QueryParametersTable.js | 5 ++--- components/metrics/ReferrersTable.js | 5 ++--- components/metrics/WebsiteChart.js | 7 +++---- components/pages/dashboard/Dashboard.js | 5 ++--- components/pages/dashboard/DashboardEdit.js | 16 +++++----------- .../pages/dashboard/DashboardSettingsButton.js | 14 ++++---------- components/pages/realtime/RealtimeCountries.js | 5 ++--- components/pages/realtime/RealtimeDashboard.js | 5 ++--- components/pages/realtime/RealtimeHeader.js | 5 ++--- components/pages/realtime/RealtimeHome.js | 5 ++--- components/pages/realtime/RealtimeLog.js | 5 ++--- components/pages/realtime/RealtimeUrls.js | 5 ++--- .../pages/settings/profile/DateRangeSetting.js | 5 ++--- .../pages/settings/profile/LanguageSetting.js | 5 ++--- .../settings/profile/PasswordChangeButton.js | 5 ++--- .../pages/settings/profile/PasswordEditForm.js | 5 ++--- .../pages/settings/profile/ProfileSettings.js | 5 ++--- .../pages/settings/profile/TimezoneSetting.js | 5 ++--- components/pages/settings/teams/JoinTeamForm.js | 7 +++---- components/pages/settings/teams/TeamAddForm.js | 5 ++--- .../pages/settings/teams/TeamDeleteForm.js | 5 ++--- components/pages/settings/teams/TeamEditForm.js | 5 ++--- components/pages/settings/teams/TeamMembers.js | 9 ++++----- .../pages/settings/teams/TeamMembersTable.js | 5 ++--- components/pages/settings/teams/TeamSettings.js | 5 ++--- components/pages/settings/teams/TeamWebsites.js | 11 +++++------ .../pages/settings/teams/TeamWebsitesTable.js | 5 ++--- components/pages/settings/teams/TeamsList.js | 7 +++---- components/pages/settings/teams/TeamsTable.js | 9 ++++----- .../pages/settings/teams/WebsiteAddTeamForm.js | 5 ++--- components/pages/settings/users/UserAddButton.js | 5 ++--- components/pages/settings/users/UserAddForm.js | 5 ++--- .../pages/settings/users/UserDeleteForm.js | 7 +++---- components/pages/settings/users/UserEditForm.js | 5 ++--- components/pages/settings/users/UserSettings.js | 5 ++--- components/pages/settings/users/UserWebsites.js | 5 ++--- components/pages/settings/users/UsersList.js | 7 +++---- components/pages/settings/users/UsersTable.js | 5 ++--- components/pages/settings/websites/ShareUrl.js | 5 ++--- .../pages/settings/websites/TrackingCode.js | 5 ++--- .../pages/settings/websites/WebsiteAddForm.js | 11 +++-------- .../pages/settings/websites/WebsiteData.js | 5 ++--- .../pages/settings/websites/WebsiteDeleteForm.js | 5 ++--- .../pages/settings/websites/WebsiteEditForm.js | 5 ++--- .../pages/settings/websites/WebsiteResetForm.js | 5 ++--- .../pages/settings/websites/WebsiteSettings.js | 5 ++--- .../pages/settings/websites/WebsitesList.js | 5 ++--- .../pages/settings/websites/WebsitesTable.js | 5 ++--- components/pages/websites/WebsiteMenuView.js | 5 ++--- hooks/useMessages.js | 10 ++++++++-- hooks/useSticky.js | 10 +++++----- pages/404.js | 5 ++--- styles/index.css | 2 -- 73 files changed, 180 insertions(+), 275 deletions(-) diff --git a/components/common/ErrorMessage.js b/components/common/ErrorMessage.js index 926e64ac..b5d44bb4 100644 --- a/components/common/ErrorMessage.js +++ b/components/common/ErrorMessage.js @@ -1,10 +1,9 @@ -import { useIntl } from 'react-intl'; import { Icon, Icons, Text } from 'react-basics'; -import { messages } from 'components/messages'; import styles from './ErrorMessage.module.css'; +import useMessages from 'hooks/useMessages'; export default function ErrorMessage() { - const { formatMessage } = useIntl(); + const { formatMessage, messages } = useMessages(); return (
diff --git a/components/common/UpdateNotice.js b/components/common/UpdateNotice.js index 732e6f52..c30ab8fb 100644 --- a/components/common/UpdateNotice.js +++ b/components/common/UpdateNotice.js @@ -1,14 +1,13 @@ import { useState, useEffect, useCallback } from 'react'; -import { useIntl } from 'react-intl'; -import { Button, Banner, Row, Column, Flexbox } from 'react-basics'; +import { Button, Row, Column } from 'react-basics'; import { setItem } from 'next-basics'; import useStore, { checkVersion } from 'store/version'; import { REPO_URL, VERSION_CHECK } from 'lib/constants'; -import { labels, messages } from 'components/messages'; import styles from './UpdateNotice.module.css'; +import useMessages from 'hooks/useMessages'; export default function UpdateNotice() { - const { formatMessage } = useIntl(); + const { formatMessage, labels, messages } = useMessages(); const { latest, checked, hasUpdate, releaseUrl } = useStore(); const [dismissed, setDismissed] = useState(false); diff --git a/components/input/DateFilter.js b/components/input/DateFilter.js index b10388e7..e0881bd8 100644 --- a/components/input/DateFilter.js +++ b/components/input/DateFilter.js @@ -1,17 +1,16 @@ import { useState } from 'react'; import { Icon, Modal, Dropdown, Item, Text, Flexbox } from 'react-basics'; -import { useIntl } from 'react-intl'; import { endOfYear, isSameDay } from 'date-fns'; import DatePickerForm from 'components/metrics/DatePickerForm'; import useLocale from 'hooks/useLocale'; import { dateFormat, getDateRangeValues } from 'lib/date'; import Icons from 'components/icons'; -import { labels } from 'components/messages'; import useApi from 'hooks/useApi'; import useDateRange from 'hooks/useDateRange'; +import useMessages from 'hooks/useMessages'; function DateFilter({ websiteId, value, className }) { - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); const { get } = useApi(); const [dateRange, setDateRange] = useDateRange(websiteId); const { startDate, endDate } = dateRange; diff --git a/components/input/LogoutButton.js b/components/input/LogoutButton.js index 9fb9adbf..4dfe7bed 100644 --- a/components/input/LogoutButton.js +++ b/components/input/LogoutButton.js @@ -1,10 +1,9 @@ import { Button, Icon, Icons, Tooltip } from 'react-basics'; import Link from 'next/link'; -import { labels } from 'components/messages'; -import { useIntl } from 'react-intl'; +import useMessages from 'hooks/useMessages'; export default function LogoutButton({ tooltipPosition = 'top' }) { - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); return ( diff --git a/components/input/RefreshButton.js b/components/input/RefreshButton.js index a5c8493b..458b469b 100644 --- a/components/input/RefreshButton.js +++ b/components/input/RefreshButton.js @@ -1,12 +1,11 @@ -import { useIntl } from 'react-intl'; import { LoadingButton, Icon, Tooltip } from 'react-basics'; import { setWebsiteDateRange } from 'store/websites'; import useDateRange from 'hooks/useDateRange'; import Icons from 'components/icons'; -import { labels } from 'components/messages'; +import useMessages from 'hooks/useMessages'; function RefreshButton({ websiteId, isLoading }) { - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); const [dateRange] = useDateRange(websiteId); function handleClick() { diff --git a/components/input/SettingsButton.js b/components/input/SettingsButton.js index 3a6068e3..37e785da 100644 --- a/components/input/SettingsButton.js +++ b/components/input/SettingsButton.js @@ -1,13 +1,12 @@ -import { useIntl } from 'react-intl'; import { Button, Icon, Tooltip, PopupTrigger, Popup, Form, FormRow } from 'react-basics'; import TimezoneSetting from 'components/pages/settings/profile/TimezoneSetting'; import DateRangeSetting from 'components/pages/settings/profile/DateRangeSetting'; import Icons from 'components/icons'; -import { labels } from 'components/messages'; +import useMessages from 'hooks/useMessages'; import styles from './SettingsButton.module.css'; export default function SettingsButton() { - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); return ( diff --git a/components/input/ThemeButton.js b/components/input/ThemeButton.js index 692e886f..0e5706d7 100644 --- a/components/input/ThemeButton.js +++ b/components/input/ThemeButton.js @@ -1,6 +1,5 @@ import { useTransition, animated } from 'react-spring'; import { Button, Icon } from 'react-basics'; -import { useIntl } from 'react-intl'; import useTheme from 'hooks/useTheme'; import Icons from 'components/icons'; import styles from './ThemeButton.module.css'; diff --git a/components/input/WebsiteSelect.js b/components/input/WebsiteSelect.js index 2a1a4e17..62fc1435 100644 --- a/components/input/WebsiteSelect.js +++ b/components/input/WebsiteSelect.js @@ -1,10 +1,9 @@ -import { useIntl } from 'react-intl'; import { Dropdown, Item } from 'react-basics'; -import { labels } from 'components/messages'; import useApi from 'hooks/useApi'; +import useMessages from 'hooks/useMessages'; export default function WebsiteSelect({ websiteId, onSelect }) { - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); const { get, useQuery } = useApi(); const { data } = useQuery(['websites:me'], () => get('/me/websites')); diff --git a/components/messages.js b/components/messages.js index efb267d5..3f888e90 100644 --- a/components/messages.js +++ b/components/messages.js @@ -109,6 +109,8 @@ export const labels = defineMessages({ laptop: { id: 'label.laptop', defaultMessage: 'Laptop' }, tablet: { id: 'label.tablet', defaultMessage: 'Tablet' }, mobile: { id: 'label.mobile', defaultMessage: 'Mobile' }, + toggleCharts: { id: 'label.toggle-charts', defaultMessage: 'Toggle charts' }, + editDashboard: { id: 'label.edit-dashboard', defaultMessage: 'Edit dashboard' }, }); export const messages = defineMessages({ @@ -201,9 +203,3 @@ export const messages = defineMessages({ defaultMessage: '{event} on {url}', }, }); - -export function getMessage(id, formatMessage) { - const message = Object.values(messages).find(value => value.id === id); - - return message ? formatMessage(message) : id; -} diff --git a/components/metrics/ActiveUsers.js b/components/metrics/ActiveUsers.js index cb3afe80..96b67df3 100644 --- a/components/metrics/ActiveUsers.js +++ b/components/metrics/ActiveUsers.js @@ -1,12 +1,11 @@ import { useMemo } from 'react'; import { StatusLight } from 'react-basics'; -import { useIntl } from 'react-intl'; import useApi from 'hooks/useApi'; -import { messages } from 'components/messages'; +import useMessages from 'hooks/useMessages'; import styles from './ActiveUsers.module.css'; export default function ActiveUsers({ websiteId, value, refetchInterval = 60000 }) { - const { formatMessage } = useIntl(); + const { formatMessage, messages } = useMessages(); const { get, useQuery } = useApi(); const { data } = useQuery( ['websites:active', websiteId], diff --git a/components/metrics/CountriesTable.js b/components/metrics/CountriesTable.js index 37b884e0..70d60e50 100644 --- a/components/metrics/CountriesTable.js +++ b/components/metrics/CountriesTable.js @@ -1,20 +1,14 @@ import MetricsTable from './MetricsTable'; import { percentFilter } from 'lib/filters'; -import { useIntl, defineMessages } from 'react-intl'; import FilterLink from 'components/common/FilterLink'; import useCountryNames from 'hooks/useCountryNames'; import useLocale from 'hooks/useLocale'; - -const messages = defineMessages({ - unknown: { id: 'label.unknown', defaultMessage: 'Unknown' }, - countries: { id: 'metrics.countries', defaultMessage: 'Countries' }, - visitors: { id: 'metrics.visitors', defaultMessage: 'Visitors' }, -}); +import useMessages from 'hooks/useMessages'; export default function CountriesTable({ websiteId, onDataLoad, ...props }) { const { locale } = useLocale(); const countryNames = useCountryNames(locale); - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); function renderLink({ x: code }) { return ( @@ -22,7 +16,7 @@ export default function CountriesTable({ websiteId, onDataLoad, ...props }) {
); @@ -31,9 +25,9 @@ export default function CountriesTable({ websiteId, onDataLoad, ...props }) { return ( onDataLoad?.(percentFilter(data))} renderLabel={renderLink} diff --git a/components/metrics/DatePickerForm.js b/components/metrics/DatePickerForm.js index 5235d339..1a6265b7 100644 --- a/components/metrics/DatePickerForm.js +++ b/components/metrics/DatePickerForm.js @@ -1,13 +1,12 @@ import { useState } from 'react'; import { Button, ButtonGroup, Calendar } from 'react-basics'; -import { useIntl } from 'react-intl'; import { isAfter, isBefore, isSameDay } from 'date-fns'; import useLocale from 'hooks/useLocale'; import { getDateRangeValues } from 'lib/date'; import { getDateLocale } from 'lib/lang'; -import { labels } from 'components/messages'; -import styles from './DatePickerForm.module.css'; import { FILTER_DAY, FILTER_RANGE } from 'lib/constants'; +import useMessages from 'hooks/useMessages'; +import styles from './DatePickerForm.module.css'; export default function DatePickerForm({ startDate: defaultStartDate, @@ -24,7 +23,7 @@ export default function DatePickerForm({ const [startDate, setStartDate] = useState(defaultStartDate); const [endDate, setEndDate] = useState(defaultEndDate); const { locale } = useLocale(); - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); const disabled = selected === FILTER_DAY diff --git a/components/metrics/DevicesTable.js b/components/metrics/DevicesTable.js index 997d25cc..53b13148 100644 --- a/components/metrics/DevicesTable.js +++ b/components/metrics/DevicesTable.js @@ -1,10 +1,9 @@ import MetricsTable from './MetricsTable'; -import { useIntl } from 'react-intl'; import FilterLink from 'components/common/FilterLink'; -import { labels } from 'components/messages'; +import useMessages from 'hooks/useMessages'; export default function DevicesTable({ websiteId, ...props }) { - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); function renderLink({ x: device }) { return ( diff --git a/components/metrics/EventsTable.js b/components/metrics/EventsTable.js index 6986388e..3e09a856 100644 --- a/components/metrics/EventsTable.js +++ b/components/metrics/EventsTable.js @@ -1,13 +1,8 @@ -import { defineMessages, useIntl } from 'react-intl'; import MetricsTable from './MetricsTable'; - -const messages = defineMessages({ - events: { id: 'metrics.events', defaultMessage: 'Events' }, - actions: { id: 'metrics.actions', defaultMessage: 'Actions' }, -}); +import useMessages from 'hooks/useMessages'; export default function EventsTable({ websiteId, ...props }) { - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); function handleDataLoad(data) { props.onDataLoad?.(data); @@ -16,9 +11,9 @@ export default function EventsTable({ websiteId, ...props }) { return ( diff --git a/components/metrics/FilterTags.js b/components/metrics/FilterTags.js index 637ea066..bd7749dd 100644 --- a/components/metrics/FilterTags.js +++ b/components/metrics/FilterTags.js @@ -1,12 +1,11 @@ -import { useIntl } from 'react-intl'; import { safeDecodeURI } from 'next-basics'; import { Button, Icon, Icons, Text } from 'react-basics'; -import { labels } from 'components/messages'; import usePageQuery from 'hooks/usePageQuery'; import styles from './FilterTags.module.css'; +import useMessages from 'hooks/useMessages'; -export default function FilterTags({ websiteId, params, onClick }) { - const { formatMessage } = useIntl(); +export default function FilterTags({ websiteId, params }) { + const { formatMessage, labels } = useMessages(); const { router, resolveUrl, diff --git a/components/metrics/MetricsBar.js b/components/metrics/MetricsBar.js index ae9689f3..5eb225c3 100644 --- a/components/metrics/MetricsBar.js +++ b/components/metrics/MetricsBar.js @@ -1,17 +1,16 @@ import { useState } from 'react'; import { Loading } from 'react-basics'; -import { useIntl } from 'react-intl'; import ErrorMessage from 'components/common/ErrorMessage'; import useApi from 'hooks/useApi'; import useDateRange from 'hooks/useDateRange'; import usePageQuery from 'hooks/usePageQuery'; import { formatShortTime, formatNumber, formatLongNumber } from 'lib/format'; import MetricCard from './MetricCard'; -import { labels } from 'components/messages'; import styles from './MetricsBar.module.css'; +import useMessages from 'hooks/useMessages'; export default function MetricsBar({ websiteId }) { - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); const { get, useQuery } = useApi(); const [dateRange] = useDateRange(websiteId); const { startDate, endDate, modified } = dateRange; diff --git a/components/metrics/MetricsTable.js b/components/metrics/MetricsTable.js index 945f7796..2e20dfc6 100644 --- a/components/metrics/MetricsTable.js +++ b/components/metrics/MetricsTable.js @@ -1,6 +1,5 @@ import { useMemo } from 'react'; import { Loading, Icon, Text, Button } from 'react-basics'; -import { defineMessages, useIntl } from 'react-intl'; import Link from 'next/link'; import firstBy from 'thenby'; import classNames from 'classnames'; @@ -12,12 +11,9 @@ import ErrorMessage from 'components/common/ErrorMessage'; import DataTable from './DataTable'; import { DEFAULT_ANIMATION_DURATION } from 'lib/constants'; import Icons from 'components/icons'; +import useMessages from 'hooks/useMessages'; import styles from './MetricsTable.module.css'; -const messages = defineMessages({ - more: { id: 'label.more', defaultMessage: 'More' }, -}); - export default function MetricsTable({ websiteId, type, @@ -35,7 +31,7 @@ export default function MetricsTable({ router, query: { url, referrer, os, browser, device, country }, } = usePageQuery(); - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); const { get, useQuery } = useApi(); const { data, isLoading, isFetched, error } = useQuery( @@ -81,7 +77,7 @@ export default function MetricsTable({ {data && !error && limit && (
diff --git a/components/pages/dashboard/DashboardSettingsButton.js b/components/pages/dashboard/DashboardSettingsButton.js index cdbb771b..d76f5232 100644 --- a/components/pages/dashboard/DashboardSettingsButton.js +++ b/components/pages/dashboard/DashboardSettingsButton.js @@ -1,24 +1,18 @@ -import { defineMessages, useIntl } from 'react-intl'; import { Menu, Icon, Text, PopupTrigger, Popup, Item, Button } from 'react-basics'; import Icons from 'components/icons'; -import { labels } from 'components/messages'; import { saveDashboard } from 'store/dashboard'; - -const messages = defineMessages({ - toggleCharts: { id: 'message.toggle-charts', defaultMessage: 'Toggle charts' }, - editDashboard: { id: 'message.edit-dashboard', defaultMessage: 'Edit dashboard' }, -}); +import useMessages from 'hooks/useMessages'; export default function DashboardSettingsButton() { - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); const menuOptions = [ { - label: formatMessage(messages.toggleCharts), + label: formatMessage(labels.toggleCharts), value: 'charts', }, { - label: formatMessage(messages.editDashboard), + label: formatMessage(labels.editDashboard), value: 'order', }, ]; diff --git a/components/pages/realtime/RealtimeCountries.js b/components/pages/realtime/RealtimeCountries.js index 82d744fc..f6711f1a 100644 --- a/components/pages/realtime/RealtimeCountries.js +++ b/components/pages/realtime/RealtimeCountries.js @@ -1,12 +1,11 @@ import { useCallback } from 'react'; -import { useIntl } from 'react-intl'; -import { labels } from 'components/messages'; import DataTable from 'components/metrics/DataTable'; import useLocale from 'hooks/useLocale'; import useCountryNames from 'hooks/useCountryNames'; +import useMessages from 'hooks/useMessages'; export default function RealtimeCountries({ data }) { - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); const { locale } = useLocale(); const countryNames = useCountryNames(locale); diff --git a/components/pages/realtime/RealtimeDashboard.js b/components/pages/realtime/RealtimeDashboard.js index 35906c45..268a135b 100644 --- a/components/pages/realtime/RealtimeDashboard.js +++ b/components/pages/realtime/RealtimeDashboard.js @@ -1,5 +1,4 @@ import { useState, useEffect, useMemo } from 'react'; -import { useIntl } from 'react-intl'; import { subMinutes, startOfMinute } from 'date-fns'; import { useRouter } from 'next/router'; import firstBy from 'thenby'; @@ -14,8 +13,8 @@ import RealtimeUrls from 'components/pages/realtime/RealtimeUrls'; import RealtimeCountries from 'components/pages/realtime/RealtimeCountries'; import WebsiteSelect from 'components/input/WebsiteSelect'; import useApi from 'hooks/useApi'; +import useMessages from 'hooks/useMessages'; import { percentFilter } from 'lib/filters'; -import { labels } from 'components/messages'; import { REALTIME_RANGE, REALTIME_INTERVAL } from 'lib/constants'; import styles from './RealtimeDashboard.module.css'; @@ -27,7 +26,7 @@ function mergeData(state = [], data = [], time) { } export default function RealtimeDashboard({ websiteId }) { - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); const router = useRouter(); const [currentData, setCurrentData] = useState(); const { get, useQuery } = useApi(); diff --git a/components/pages/realtime/RealtimeHeader.js b/components/pages/realtime/RealtimeHeader.js index 15730d5d..5a7e1c3f 100644 --- a/components/pages/realtime/RealtimeHeader.js +++ b/components/pages/realtime/RealtimeHeader.js @@ -1,10 +1,9 @@ -import { useIntl } from 'react-intl'; import MetricCard from 'components/metrics/MetricCard'; -import { labels } from 'components/messages'; +import useMessages from 'hooks/useMessages'; import styles from './RealtimeHeader.module.css'; export default function RealtimeHeader({ data = {} }) { - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); const { pageviews, visitors, events, countries } = data; return ( diff --git a/components/pages/realtime/RealtimeHome.js b/components/pages/realtime/RealtimeHome.js index 67b34ee9..a893ee84 100644 --- a/components/pages/realtime/RealtimeHome.js +++ b/components/pages/realtime/RealtimeHome.js @@ -1,14 +1,13 @@ import { useEffect } from 'react'; import { useRouter } from 'next/router'; -import { useIntl } from 'react-intl'; import Page from 'components/layout/Page'; import PageHeader from 'components/layout/PageHeader'; import useApi from 'hooks/useApi'; -import { labels, messages } from 'components/messages'; import EmptyPlaceholder from 'components/common/EmptyPlaceholder'; +import useMessages from 'hooks/useMessages'; export default function RealtimeHome() { - const { formatMessage } = useIntl(); + const { formatMessage, labels, messages } = useMessages(); const { get, useQuery } = useApi(); const router = useRouter(); const { data, isLoading, error } = useQuery(['websites:me'], () => get('/me/websites')); diff --git a/components/pages/realtime/RealtimeLog.js b/components/pages/realtime/RealtimeLog.js index be7aeb8d..0ad579c2 100644 --- a/components/pages/realtime/RealtimeLog.js +++ b/components/pages/realtime/RealtimeLog.js @@ -1,11 +1,9 @@ import { useMemo, useState } from 'react'; import { StatusLight, Icon, Text } from 'react-basics'; -import { useIntl, FormattedMessage } from 'react-intl'; import { FixedSizeList } from 'react-window'; import firstBy from 'thenby'; import FilterButtons from 'components/common/FilterButtons'; import NoData from 'components/common/NoData'; -import { labels, messages } from 'components/messages'; import useLocale from 'hooks/useLocale'; import useCountryNames from 'hooks/useCountryNames'; import { BROWSERS } from 'lib/constants'; @@ -14,6 +12,7 @@ import { dateFormat } from 'lib/date'; import { safeDecodeURI } from 'next-basics'; import Icons from 'components/icons'; import styles from './RealtimeLog.module.css'; +import useMessages from 'hooks/useMessages'; const TYPE_ALL = 'all'; const TYPE_PAGEVIEW = 'pageview'; @@ -27,7 +26,7 @@ const icons = { }; export default function RealtimeLog({ data, websiteDomain }) { - const { formatMessage } = useIntl(); + const { formatMessage, labels, messages, FormattedMessage } = useMessages(); const { locale } = useLocale(); const countryNames = useCountryNames(locale); const [filter, setFilter] = useState(TYPE_ALL); diff --git a/components/pages/realtime/RealtimeUrls.js b/components/pages/realtime/RealtimeUrls.js index 8437af40..8f8b8312 100644 --- a/components/pages/realtime/RealtimeUrls.js +++ b/components/pages/realtime/RealtimeUrls.js @@ -1,14 +1,13 @@ import { useMemo, useState } from 'react'; import { ButtonGroup, Button, Flexbox } from 'react-basics'; -import { useIntl } from 'react-intl'; import firstBy from 'thenby'; import { percentFilter } from 'lib/filters'; import DataTable from 'components/metrics/DataTable'; import { FILTER_PAGES, FILTER_REFERRERS } from 'lib/constants'; -import { labels } from 'components/messages'; +import useMessages from 'hooks/useMessages'; export default function RealtimeUrls({ websiteDomain, data = {} }) { - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); const { pageviews } = data; const [filter, setFilter] = useState(FILTER_REFERRERS); diff --git a/components/pages/settings/profile/DateRangeSetting.js b/components/pages/settings/profile/DateRangeSetting.js index 15a3c010..23921d31 100644 --- a/components/pages/settings/profile/DateRangeSetting.js +++ b/components/pages/settings/profile/DateRangeSetting.js @@ -1,12 +1,11 @@ -import { useIntl } from 'react-intl'; import DateFilter from 'components/input/DateFilter'; import { Button, Flexbox } from 'react-basics'; import useDateRange from 'hooks/useDateRange'; import { DEFAULT_DATE_RANGE } from 'lib/constants'; -import { labels } from 'components/messages'; +import useMessages from 'hooks/useMessages'; export default function DateRangeSetting() { - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); const [dateRange, setDateRange] = useDateRange(); const { startDate, endDate, value } = dateRange; diff --git a/components/pages/settings/profile/LanguageSetting.js b/components/pages/settings/profile/LanguageSetting.js index d5aa064f..8130d33a 100644 --- a/components/pages/settings/profile/LanguageSetting.js +++ b/components/pages/settings/profile/LanguageSetting.js @@ -1,12 +1,11 @@ -import { useIntl } from 'react-intl'; import { Button, Dropdown, Item, Flexbox } from 'react-basics'; import useLocale from 'hooks/useLocale'; import { DEFAULT_LOCALE } from 'lib/constants'; import { languages } from 'lib/lang'; -import { labels } from 'components/messages'; +import useMessages from 'hooks/useMessages'; export default function LanguageSetting() { - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); const { locale, saveLocale } = useLocale(); const options = Object.keys(languages); diff --git a/components/pages/settings/profile/PasswordChangeButton.js b/components/pages/settings/profile/PasswordChangeButton.js index 51ac6b9b..466b6896 100644 --- a/components/pages/settings/profile/PasswordChangeButton.js +++ b/components/pages/settings/profile/PasswordChangeButton.js @@ -1,11 +1,10 @@ -import { useIntl } from 'react-intl'; import { Button, Icon, Text, useToast, ModalTrigger, Modal } from 'react-basics'; import PasswordEditForm from 'components/pages/settings/profile/PasswordEditForm'; import Icons from 'components/icons'; -import { labels, messages } from 'components/messages'; +import useMessages from 'hooks/useMessages'; export default function PasswordChangeButton() { - const { formatMessage } = useIntl(); + const { formatMessage, labels, messages } = useMessages(); const { toast, showToast } = useToast(); const handleSave = () => { diff --git a/components/pages/settings/profile/PasswordEditForm.js b/components/pages/settings/profile/PasswordEditForm.js index be3d1048..4ea934b0 100644 --- a/components/pages/settings/profile/PasswordEditForm.js +++ b/components/pages/settings/profile/PasswordEditForm.js @@ -1,11 +1,10 @@ import { useRef } from 'react'; import { Form, FormRow, FormInput, FormButtons, PasswordField, Button } from 'react-basics'; -import { useIntl } from 'react-intl'; import useApi from 'hooks/useApi'; -import { labels, messages } from 'components/messages'; +import useMessages from 'hooks/useMessages'; export default function PasswordEditForm({ onSave, onClose }) { - const { formatMessage } = useIntl(); + const { formatMessage, labels, messages } = useMessages(); const { post, useMutation } = useApi(); const { mutate, error, isLoading } = useMutation(data => post('/me/password', data)); const ref = useRef(null); diff --git a/components/pages/settings/profile/ProfileSettings.js b/components/pages/settings/profile/ProfileSettings.js index d302b6b2..d2b73159 100644 --- a/components/pages/settings/profile/ProfileSettings.js +++ b/components/pages/settings/profile/ProfileSettings.js @@ -1,13 +1,12 @@ -import { useIntl } from 'react-intl'; import Page from 'components/layout/Page'; import PageHeader from 'components/layout/PageHeader'; import ProfileDetails from './ProfileDetails'; import PasswordChangeButton from './PasswordChangeButton'; -import { labels } from 'components/messages'; import useConfig from 'hooks/useConfig'; +import useMessages from 'hooks/useMessages'; export default function ProfileSettings() { - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); const { cloudMode } = useConfig(); return ( diff --git a/components/pages/settings/profile/TimezoneSetting.js b/components/pages/settings/profile/TimezoneSetting.js index 11cc450e..dd69b3c9 100644 --- a/components/pages/settings/profile/TimezoneSetting.js +++ b/components/pages/settings/profile/TimezoneSetting.js @@ -1,12 +1,11 @@ import { Dropdown, Item, Button, Flexbox } from 'react-basics'; -import { useIntl } from 'react-intl'; import { listTimeZones } from 'timezone-support'; import useTimezone from 'hooks/useTimezone'; +import useMessages from 'hooks/useMessages'; import { getTimezone } from 'lib/date'; -import { labels } from 'components/messages'; export default function TimezoneSetting() { - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); const [timezone, saveTimezone] = useTimezone(); const options = listTimeZones(); diff --git a/components/pages/settings/teams/JoinTeamForm.js b/components/pages/settings/teams/JoinTeamForm.js index 387968fd..b3fb95d1 100644 --- a/components/pages/settings/teams/JoinTeamForm.js +++ b/components/pages/settings/teams/JoinTeamForm.js @@ -1,5 +1,4 @@ import { useRef } from 'react'; -import { useIntl } from 'react-intl'; import { Form, FormRow, @@ -10,10 +9,10 @@ import { SubmitButton, } from 'react-basics'; import useApi from 'hooks/useApi'; -import { labels, getMessage } from 'components/messages'; +import useMessages from 'hooks/useMessages'; export default function TeamJoinForm({ onSave, onClose }) { - const { formatMessage } = useIntl(); + const { formatMessage, labels, getMessage } = useMessages(); const { post, useMutation } = useApi(); const { mutate, error } = useMutation(data => post('/teams/join', data)); const ref = useRef(null); @@ -28,7 +27,7 @@ export default function TeamJoinForm({ onSave, onClose }) { }; return ( - + diff --git a/components/pages/settings/teams/TeamAddForm.js b/components/pages/settings/teams/TeamAddForm.js index fd55c93c..382c3bad 100644 --- a/components/pages/settings/teams/TeamAddForm.js +++ b/components/pages/settings/teams/TeamAddForm.js @@ -1,5 +1,4 @@ import { useRef } from 'react'; -import { useIntl } from 'react-intl'; import { Form, FormRow, @@ -10,10 +9,10 @@ import { SubmitButton, } from 'react-basics'; import useApi from 'hooks/useApi'; -import { labels } from 'components/messages'; +import useMessages from 'hooks/useMessages'; export default function TeamAddForm({ onSave, onClose }) { - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); const { post, useMutation } = useApi(); const { mutate, error, isLoading } = useMutation(data => post('/teams', data)); const ref = useRef(null); diff --git a/components/pages/settings/teams/TeamDeleteForm.js b/components/pages/settings/teams/TeamDeleteForm.js index 5f317124..e743cc9c 100644 --- a/components/pages/settings/teams/TeamDeleteForm.js +++ b/components/pages/settings/teams/TeamDeleteForm.js @@ -1,10 +1,9 @@ import { Button, Form, FormButtons, SubmitButton } from 'react-basics'; -import { useIntl, FormattedMessage } from 'react-intl'; -import { labels, messages } from 'components/messages'; import useApi from 'hooks/useApi'; +import useMessages from 'hooks/useMessages'; export default function TeamDeleteForm({ teamId, teamName, onSave, onClose }) { - const { formatMessage } = useIntl(); + const { formatMessage, labels, messages, FormattedMessage } = useMessages(); const { del, useMutation } = useApi(); const { mutate, error, isLoading } = useMutation(data => del(`/teams/${teamId}`, data)); diff --git a/components/pages/settings/teams/TeamEditForm.js b/components/pages/settings/teams/TeamEditForm.js index ba5b21e2..1a9a8baa 100644 --- a/components/pages/settings/teams/TeamEditForm.js +++ b/components/pages/settings/teams/TeamEditForm.js @@ -8,16 +8,15 @@ import { Button, Flexbox, } from 'react-basics'; -import { useIntl } from 'react-intl'; import { getRandomChars } from 'next-basics'; import { useRef, useState } from 'react'; import useApi from 'hooks/useApi'; -import { labels } from 'components/messages'; +import useMessages from 'hooks/useMessages'; const generateId = () => getRandomChars(16); export default function TeamEditForm({ teamId, data, onSave, readOnly }) { - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); const { post, useMutation } = useApi(); const { mutate, error } = useMutation(data => post(`/teams/${teamId}`, data)); const ref = useRef(null); diff --git a/components/pages/settings/teams/TeamMembers.js b/components/pages/settings/teams/TeamMembers.js index 8873a4d3..2e9b87f7 100644 --- a/components/pages/settings/teams/TeamMembers.js +++ b/components/pages/settings/teams/TeamMembers.js @@ -1,13 +1,12 @@ -import { messages } from 'components/messages'; +import { Loading, useToast } from 'react-basics'; import TeamMembersTable from 'components/pages/settings/teams/TeamMembersTable'; import useApi from 'hooks/useApi'; -import { Loading, useToast } from 'react-basics'; -import { useIntl } from 'react-intl'; +import useMessages from 'hooks/useMessages'; export default function TeamMembers({ teamId, readOnly }) { const { toast, showToast } = useToast(); const { get, useQuery } = useApi(); - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); const { data, isLoading, refetch } = useQuery(['teams:users', teamId], () => get(`/teams/${teamId}/users`), ); @@ -18,7 +17,7 @@ export default function TeamMembers({ teamId, readOnly }) { const handleSave = async () => { await refetch(); - showToast({ message: formatMessage(messages.saved), variant: 'success' }); + showToast({ message: formatMessage(labels.saved), variant: 'success' }); }; return ( diff --git a/components/pages/settings/teams/TeamMembersTable.js b/components/pages/settings/teams/TeamMembersTable.js index f0369f06..8498a19d 100644 --- a/components/pages/settings/teams/TeamMembersTable.js +++ b/components/pages/settings/teams/TeamMembersTable.js @@ -11,14 +11,13 @@ import { Flexbox, Text, } from 'react-basics'; -import { useIntl } from 'react-intl'; import { ROLES } from 'lib/constants'; -import { labels } from 'components/messages'; import useUser from 'hooks/useUser'; import useApi from 'hooks/useApi'; +import useMessages from 'hooks/useMessages'; export default function TeamMembersTable({ data = [], onSave, readOnly }) { - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); const { user } = useUser(); const { del, useMutation } = useApi(); const { mutate } = useMutation(data => del(`/teamUsers/${data.teamUserId}`)); diff --git a/components/pages/settings/teams/TeamSettings.js b/components/pages/settings/teams/TeamSettings.js index 6fe5e6f3..3b5c2937 100644 --- a/components/pages/settings/teams/TeamSettings.js +++ b/components/pages/settings/teams/TeamSettings.js @@ -1,19 +1,18 @@ import { useEffect, useState } from 'react'; -import { useIntl } from 'react-intl'; import { Breadcrumbs, Item, Tabs, useToast } from 'react-basics'; import Link from 'next/link'; import Page from 'components/layout/Page'; import PageHeader from 'components/layout/PageHeader'; -import { labels, messages } from 'components/messages'; import { ROLES } from 'lib/constants'; import useUser from 'hooks/useUser'; import useApi from 'hooks/useApi'; +import useMessages from 'hooks/useMessages'; import TeamEditForm from './TeamEditForm'; import TeamMembers from './TeamMembers'; import TeamWebsites from './TeamWebsites'; export default function TeamSettings({ teamId }) { - const { formatMessage } = useIntl(); + const { formatMessage, labels, messages } = useMessages(); const { user } = useUser(); const [values, setValues] = useState(null); const [tab, setTab] = useState('details'); diff --git a/components/pages/settings/teams/TeamWebsites.js b/components/pages/settings/teams/TeamWebsites.js index 494a6eb0..63b46d55 100644 --- a/components/pages/settings/teams/TeamWebsites.js +++ b/components/pages/settings/teams/TeamWebsites.js @@ -1,7 +1,3 @@ -import EmptyPlaceholder from 'components/common/EmptyPlaceholder'; -import { labels, messages } from 'components/messages'; -import TeamWebsitesTable from 'components/pages/settings/teams/TeamWebsitesTable'; -import useApi from 'hooks/useApi'; import { ActionForm, Button, @@ -13,12 +9,15 @@ import { Text, useToast, } from 'react-basics'; -import { useIntl } from 'react-intl'; +import EmptyPlaceholder from 'components/common/EmptyPlaceholder'; +import TeamWebsitesTable from 'components/pages/settings/teams/TeamWebsitesTable'; import WebsiteAddTeamForm from 'components/pages/settings/teams/WebsiteAddTeamForm'; +import useApi from 'hooks/useApi'; +import useMessages from 'hooks/useMessages'; export default function TeamWebsites({ teamId }) { const { toast, showToast } = useToast(); - const { formatMessage } = useIntl(); + const { formatMessage, labels, messages } = useMessages(); const { get, useQuery } = useApi(); const { data, isLoading, refetch } = useQuery(['teams:websites', teamId], () => get(`/teams/${teamId}/websites`), diff --git a/components/pages/settings/teams/TeamWebsitesTable.js b/components/pages/settings/teams/TeamWebsitesTable.js index 52141523..bb85033f 100644 --- a/components/pages/settings/teams/TeamWebsitesTable.js +++ b/components/pages/settings/teams/TeamWebsitesTable.js @@ -12,13 +12,12 @@ import { Icons, Flexbox, } from 'react-basics'; -import { useIntl } from 'react-intl'; -import { labels } from 'components/messages'; import useUser from 'hooks/useUser'; import useApi from 'hooks/useApi'; +import useMessages from 'hooks/useMessages'; export default function TeamWebsitesTable({ data = [], onSave }) { - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); const { user } = useUser(); const { del, useMutation } = useApi(); const { mutate } = useMutation(({ teamWebsiteId }) => del(`/teamWebsites/${teamWebsiteId}`)); diff --git a/components/pages/settings/teams/TeamsList.js b/components/pages/settings/teams/TeamsList.js index 0e6ef9c2..d6ebd291 100644 --- a/components/pages/settings/teams/TeamsList.js +++ b/components/pages/settings/teams/TeamsList.js @@ -1,18 +1,17 @@ import { useState } from 'react'; import { Button, Icon, Modal, ModalTrigger, useToast, Text, Flexbox } from 'react-basics'; -import { useIntl } from 'react-intl'; -import useApi from 'hooks/useApi'; import EmptyPlaceholder from 'components/common/EmptyPlaceholder'; import TeamAddForm from 'components/pages/settings/teams/TeamAddForm'; import PageHeader from 'components/layout/PageHeader'; import TeamsTable from 'components/pages/settings/teams/TeamsTable'; import Page from 'components/layout/Page'; -import { labels, messages } from 'components/messages'; import Icons from 'components/icons'; import TeamJoinForm from './JoinTeamForm'; +import useApi from 'hooks/useApi'; +import useMessages from 'hooks/useMessages'; export default function TeamsList() { - const { formatMessage } = useIntl(); + const { formatMessage, labels, messages } = useMessages(); const [update, setUpdate] = useState(0); const { get, useQuery } = useApi(); const { data, isLoading, error } = useQuery(['teams', update], () => get(`/teams`)); diff --git a/components/pages/settings/teams/TeamsTable.js b/components/pages/settings/teams/TeamsTable.js index 4f70cf6d..ce37bb28 100644 --- a/components/pages/settings/teams/TeamsTable.js +++ b/components/pages/settings/teams/TeamsTable.js @@ -1,6 +1,3 @@ -import { labels } from 'components/messages'; -import useUser from 'hooks/useUser'; -import { ROLES } from 'lib/constants'; import Link from 'next/link'; import { Button, @@ -17,11 +14,13 @@ import { TableRow, Text, } from 'react-basics'; -import { useIntl } from 'react-intl'; import TeamDeleteForm from './TeamDeleteForm'; +import useMessages from 'hooks/useMessages'; +import useUser from 'hooks/useUser'; +import { ROLES } from 'lib/constants'; export default function TeamsTable({ data = [], onDelete }) { - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); const { user } = useUser(); const columns = [ diff --git a/components/pages/settings/teams/WebsiteAddTeamForm.js b/components/pages/settings/teams/WebsiteAddTeamForm.js index 5d85e5de..f1ecd332 100644 --- a/components/pages/settings/teams/WebsiteAddTeamForm.js +++ b/components/pages/settings/teams/WebsiteAddTeamForm.js @@ -1,12 +1,11 @@ -import { labels } from 'components/messages'; import useApi from 'hooks/useApi'; import { useRef, useState } from 'react'; import { Button, Dropdown, Form, FormButtons, FormRow, Item, SubmitButton } from 'react-basics'; -import { useIntl } from 'react-intl'; import WebsiteTags from './WebsiteTags'; +import useMessages from 'hooks/useMessages'; export default function WebsiteAddTeamForm({ teamId, onSave, onClose }) { - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); const { get, post, useQuery, useMutation } = useApi(); const { mutate, error } = useMutation(data => post(`/teams/${teamId}/websites`, data)); const { data: websites } = useQuery(['websites'], () => get('/websites')); diff --git a/components/pages/settings/users/UserAddButton.js b/components/pages/settings/users/UserAddButton.js index 08600a2e..656a388b 100644 --- a/components/pages/settings/users/UserAddButton.js +++ b/components/pages/settings/users/UserAddButton.js @@ -1,10 +1,9 @@ -import { useIntl } from 'react-intl'; import { Button, Icon, Text, Modal, Icons, ModalTrigger } from 'react-basics'; import UserAddForm from './UserAddForm'; -import { labels } from 'components/messages'; +import useMessages from 'hooks/useMessages'; export default function UserAddButton({ onSave }) { - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); const handleSave = () => { onSave(); diff --git a/components/pages/settings/users/UserAddForm.js b/components/pages/settings/users/UserAddForm.js index cba5b7cd..6b159abb 100644 --- a/components/pages/settings/users/UserAddForm.js +++ b/components/pages/settings/users/UserAddForm.js @@ -10,15 +10,14 @@ import { SubmitButton, Button, } from 'react-basics'; -import { useIntl } from 'react-intl'; import useApi from 'hooks/useApi'; import { ROLES } from 'lib/constants'; -import { labels } from 'components/messages'; +import useMessages from 'hooks/useMessages'; export default function UserAddForm({ onSave, onClose }) { const { post, useMutation } = useApi(); const { mutate, error, isLoading } = useMutation(data => post(`/users`, data)); - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); const handleSubmit = async data => { mutate(data, { diff --git a/components/pages/settings/users/UserDeleteForm.js b/components/pages/settings/users/UserDeleteForm.js index ffed7556..596e996b 100644 --- a/components/pages/settings/users/UserDeleteForm.js +++ b/components/pages/settings/users/UserDeleteForm.js @@ -1,11 +1,10 @@ import { useMutation } from '@tanstack/react-query'; -import useApi from 'hooks/useApi'; import { Button, Form, FormButtons, SubmitButton } from 'react-basics'; -import { useIntl, FormattedMessage } from 'react-intl'; -import { labels, messages } from 'components/messages'; +import useApi from 'hooks/useApi'; +import useMessages from 'hooks/useMessages'; export default function UserDeleteForm({ userId, username, onSave, onClose }) { - const { formatMessage } = useIntl(); + const { formatMessage, FormattedMessage, labels, messages } = useMessages(); const { del } = useApi(); const { mutate, error, isLoading } = useMutation(() => del(`/users/${userId}`)); diff --git a/components/pages/settings/users/UserEditForm.js b/components/pages/settings/users/UserEditForm.js index 057ab9b4..e746b18b 100644 --- a/components/pages/settings/users/UserEditForm.js +++ b/components/pages/settings/users/UserEditForm.js @@ -9,13 +9,12 @@ import { SubmitButton, PasswordField, } from 'react-basics'; -import { useIntl } from 'react-intl'; import useApi from 'hooks/useApi'; import { ROLES } from 'lib/constants'; -import { labels, messages } from 'components/messages'; +import useMessages from 'hooks/useMessages'; export default function UserEditForm({ userId, data, onSave }) { - const { formatMessage } = useIntl(); + const { formatMessage, labels, messages } = useMessages(); const { post, useMutation } = useApi(); const { mutate, error } = useMutation(({ username }) => post(`/users/${userId}`, { username })); diff --git a/components/pages/settings/users/UserSettings.js b/components/pages/settings/users/UserSettings.js index e3fad23c..cd27a909 100644 --- a/components/pages/settings/users/UserSettings.js +++ b/components/pages/settings/users/UserSettings.js @@ -1,16 +1,15 @@ import { useEffect, useState } from 'react'; -import { useIntl } from 'react-intl'; import { Breadcrumbs, Item, Tabs, useToast } from 'react-basics'; import Link from 'next/link'; import UserEditForm from 'components/pages/settings/users//UserEditForm'; import Page from 'components/layout/Page'; import PageHeader from 'components/layout/PageHeader'; import useApi from 'hooks/useApi'; -import { labels, messages } from 'components/messages'; import UserWebsites from './UserWebsites'; +import useMessages from 'hooks/useMessages'; export default function UserSettings({ userId }) { - const { formatMessage } = useIntl(); + const { formatMessage, labels, messages } = useMessages(); const [edit, setEdit] = useState(false); const [values, setValues] = useState(null); const [tab, setTab] = useState('details'); diff --git a/components/pages/settings/users/UserWebsites.js b/components/pages/settings/users/UserWebsites.js index 326464f7..b49a6baf 100644 --- a/components/pages/settings/users/UserWebsites.js +++ b/components/pages/settings/users/UserWebsites.js @@ -1,11 +1,10 @@ import { Loading } from 'react-basics'; -import { useIntl } from 'react-intl'; import useApi from 'hooks/useApi'; import WebsitesTable from 'components/pages/settings/websites/WebsitesTable'; -import { messages } from 'components/messages'; +import useMessages from 'hooks/useMessages'; export default function UserWebsites({ userId }) { - const { formatMessage } = useIntl(); + const { formatMessage, messages } = useMessages(); const { get, useQuery } = useApi(); const { data, isLoading } = useQuery(['user:websites', userId], () => get(`/users/${userId}/websites`), diff --git a/components/pages/settings/users/UsersList.js b/components/pages/settings/users/UsersList.js index 70192362..6137427a 100644 --- a/components/pages/settings/users/UsersList.js +++ b/components/pages/settings/users/UsersList.js @@ -1,4 +1,4 @@ -import { useIntl } from 'react-intl'; +import { useToast } from 'react-basics'; import Page from 'components/layout/Page'; import PageHeader from 'components/layout/PageHeader'; import EmptyPlaceholder from 'components/common/EmptyPlaceholder'; @@ -6,11 +6,10 @@ import UsersTable from './UsersTable'; import UserAddButton from './UserAddButton'; import useApi from 'hooks/useApi'; import useUser from 'hooks/useUser'; -import { useToast } from 'react-basics'; -import { labels, messages } from 'components/messages'; +import useMessages from 'hooks/useMessages'; export default function UsersList() { - const { formatMessage } = useIntl(); + const { formatMessage, labels, messages } = useMessages(); const { user } = useUser(); const { get, useQuery } = useApi(); const { data, isLoading, error, refetch } = useQuery(['user'], () => get(`/users`), { diff --git a/components/pages/settings/users/UsersTable.js b/components/pages/settings/users/UsersTable.js index 4b7e0a59..bdabdf1c 100644 --- a/components/pages/settings/users/UsersTable.js +++ b/components/pages/settings/users/UsersTable.js @@ -13,16 +13,15 @@ import { ModalTrigger, Modal, } from 'react-basics'; -import { useIntl } from 'react-intl'; import { formatDistance } from 'date-fns'; import Link from 'next/link'; import useUser from 'hooks/useUser'; import UserDeleteForm from './UserDeleteForm'; -import { labels } from 'components/messages'; import { ROLES } from 'lib/constants'; +import useMessages from 'hooks/useMessages'; export default function UsersTable({ data = [], onDelete }) { - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); const { user } = useUser(); const columns = [ diff --git a/components/pages/settings/websites/ShareUrl.js b/components/pages/settings/websites/ShareUrl.js index b3a5e82d..ea8f7d22 100644 --- a/components/pages/settings/websites/ShareUrl.js +++ b/components/pages/settings/websites/ShareUrl.js @@ -8,16 +8,15 @@ import { Button, Toggle, } from 'react-basics'; -import { useIntl } from 'react-intl'; import { useEffect, useMemo, useRef, useState } from 'react'; import { getRandomChars } from 'next-basics'; import useApi from 'hooks/useApi'; -import { labels, messages } from 'components/messages'; +import useMessages from 'hooks/useMessages'; const generateId = () => getRandomChars(16); export default function ShareUrl({ websiteId, data, onSave }) { - const { formatMessage } = useIntl(); + const { formatMessage, labels, messages } = useMessages(); const { name, shareId } = data; const [id, setId] = useState(shareId); const { post, useMutation } = useApi(); diff --git a/components/pages/settings/websites/TrackingCode.js b/components/pages/settings/websites/TrackingCode.js index 2822cbdf..4f692d21 100644 --- a/components/pages/settings/websites/TrackingCode.js +++ b/components/pages/settings/websites/TrackingCode.js @@ -1,10 +1,9 @@ import { TextArea } from 'react-basics'; import { TRACKER_SCRIPT_URL } from 'lib/constants'; -import { messages } from 'components/messages'; -import { useIntl } from 'react-intl'; +import useMessages from 'hooks/useMessages'; export default function TrackingCode({ websiteId }) { - const { formatMessage } = useIntl(); + const { formatMessage, messages } = useMessages(); const url = TRACKER_SCRIPT_URL.startsWith('http') ? TRACKER_SCRIPT_URL : `${location.origin}${TRACKER_SCRIPT_URL}`; diff --git a/components/pages/settings/websites/WebsiteAddForm.js b/components/pages/settings/websites/WebsiteAddForm.js index 8a864549..d08849e2 100644 --- a/components/pages/settings/websites/WebsiteAddForm.js +++ b/components/pages/settings/websites/WebsiteAddForm.js @@ -7,17 +7,12 @@ import { Button, SubmitButton, } from 'react-basics'; -import { defineMessages, useIntl } from 'react-intl'; import useApi from 'hooks/useApi'; import { DOMAIN_REGEX } from 'lib/constants'; -import { labels } from 'components/messages'; - -const messages = defineMessages({ - invalidDomain: { id: 'label.invalid-domain', defaultMessage: 'Invalid domain' }, -}); +import useMessages from 'hooks/useMessages'; export default function WebsiteAddForm({ onSave, onClose }) { - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); const { post, useMutation } = useApi(); const { mutate, error, isLoading } = useMutation(data => post('/websites', data)); @@ -42,7 +37,7 @@ export default function WebsiteAddForm({ onSave, onClose }) { name="domain" rules={{ required: formatMessage(labels.required), - pattern: { value: DOMAIN_REGEX, message: formatMessage(messages.invalidDomain) }, + pattern: { value: DOMAIN_REGEX, message: formatMessage(labels.invalidDomain) }, }} > diff --git a/components/pages/settings/websites/WebsiteData.js b/components/pages/settings/websites/WebsiteData.js index 64979fe4..7c558542 100644 --- a/components/pages/settings/websites/WebsiteData.js +++ b/components/pages/settings/websites/WebsiteData.js @@ -1,11 +1,10 @@ import { Button, Modal, ModalTrigger, ActionForm } from 'react-basics'; -import { useIntl } from 'react-intl'; import WebsiteDeleteForm from 'components/pages/settings/websites/WebsiteDeleteForm'; import WebsiteResetForm from 'components/pages/settings/websites/WebsiteResetForm'; -import { labels, messages } from 'components/messages'; +import useMessages from 'hooks/useMessages'; export default function WebsiteData({ websiteId, onSave }) { - const { formatMessage } = useIntl(); + const { formatMessage, labels, messages } = useMessages(); const handleReset = async () => { onSave('reset'); diff --git a/components/pages/settings/websites/WebsiteDeleteForm.js b/components/pages/settings/websites/WebsiteDeleteForm.js index c1ec10e0..0661c515 100644 --- a/components/pages/settings/websites/WebsiteDeleteForm.js +++ b/components/pages/settings/websites/WebsiteDeleteForm.js @@ -7,14 +7,13 @@ import { SubmitButton, TextField, } from 'react-basics'; -import { useIntl } from 'react-intl'; -import { labels, messages } from 'components/messages'; import useApi from 'hooks/useApi'; +import useMessages from 'hooks/useMessages'; const CONFIRM_VALUE = 'DELETE'; export default function WebsiteDeleteForm({ websiteId, onSave, onClose }) { - const { formatMessage } = useIntl(); + const { formatMessage, labels, messages } = useMessages(); const { del, useMutation } = useApi(); const { mutate, error } = useMutation(data => del(`/websites/${websiteId}`, data)); diff --git a/components/pages/settings/websites/WebsiteEditForm.js b/components/pages/settings/websites/WebsiteEditForm.js index 5b8f7926..01383f5a 100644 --- a/components/pages/settings/websites/WebsiteEditForm.js +++ b/components/pages/settings/websites/WebsiteEditForm.js @@ -1,12 +1,11 @@ import { SubmitButton, Form, FormInput, FormRow, FormButtons, TextField } from 'react-basics'; import { useRef } from 'react'; -import { useIntl } from 'react-intl'; import useApi from 'hooks/useApi'; import { DOMAIN_REGEX } from 'lib/constants'; -import { labels, messages } from 'components/messages'; +import useMessages from 'hooks/useMessages'; export default function WebsiteEditForm({ websiteId, data, onSave }) { - const { formatMessage } = useIntl(); + const { formatMessage, labels, messages } = useMessages(); const { post, useMutation } = useApi(); const { mutate, error } = useMutation(data => post(`/websites/${websiteId}`, data)); const ref = useRef(null); diff --git a/components/pages/settings/websites/WebsiteResetForm.js b/components/pages/settings/websites/WebsiteResetForm.js index b88de986..038ad59e 100644 --- a/components/pages/settings/websites/WebsiteResetForm.js +++ b/components/pages/settings/websites/WebsiteResetForm.js @@ -8,13 +8,12 @@ import { TextField, } from 'react-basics'; import useApi from 'hooks/useApi'; -import { useIntl } from 'react-intl'; -import { labels, messages } from 'components/messages'; +import useMessages from 'hooks/useMessages'; const CONFIRM_VALUE = 'RESET'; export default function WebsiteResetForm({ websiteId, onSave, onClose }) { - const { formatMessage } = useIntl(); + const { formatMessage, labels, messages } = useMessages(); const { post, useMutation } = useApi(); const { mutate, error } = useMutation(data => post(`/websites/${websiteId}/reset`, data)); diff --git a/components/pages/settings/websites/WebsiteSettings.js b/components/pages/settings/websites/WebsiteSettings.js index 53922a61..dd8b1abe 100644 --- a/components/pages/settings/websites/WebsiteSettings.js +++ b/components/pages/settings/websites/WebsiteSettings.js @@ -1,6 +1,5 @@ import { useEffect, useState } from 'react'; import { Breadcrumbs, Item, Tabs, useToast, Button, Text, Icon, Icons } from 'react-basics'; -import { useIntl } from 'react-intl'; import { useRouter } from 'next/router'; import Link from 'next/link'; import Page from 'components/layout/Page'; @@ -10,11 +9,11 @@ import WebsiteData from 'components/pages/settings/websites/WebsiteData'; import TrackingCode from 'components/pages/settings/websites/TrackingCode'; import ShareUrl from 'components/pages/settings/websites/ShareUrl'; import useApi from 'hooks/useApi'; -import { labels, messages } from 'components/messages'; +import useMessages from 'hooks/useMessages'; export default function WebsiteSettings({ websiteId }) { const router = useRouter(); - const { formatMessage } = useIntl(); + const { formatMessage, labels, messages } = useMessages(); const [values, setValues] = useState(null); const [tab, setTab] = useState('details'); const { get, useQuery } = useApi(); diff --git a/components/pages/settings/websites/WebsitesList.js b/components/pages/settings/websites/WebsitesList.js index 7c70d895..e69babeb 100644 --- a/components/pages/settings/websites/WebsitesList.js +++ b/components/pages/settings/websites/WebsitesList.js @@ -1,5 +1,4 @@ import { Button, Icon, Text, Modal, ModalTrigger, useToast, Icons } from 'react-basics'; -import { useIntl } from 'react-intl'; import Page from 'components/layout/Page'; import PageHeader from 'components/layout/PageHeader'; import EmptyPlaceholder from 'components/common/EmptyPlaceholder'; @@ -7,9 +6,10 @@ import WebsiteAddForm from 'components/pages/settings/websites/WebsiteAddForm'; import WebsitesTable from 'components/pages/settings/websites/WebsitesTable'; import useApi from 'hooks/useApi'; import useUser from 'hooks/useUser'; -import { labels, messages } from 'components/messages'; +import useMessages from 'hooks/useMessages'; export default function WebsitesList() { + const { formatMessage, labels, messages } = useMessages(); const { user } = useUser(); const { get, useQuery } = useApi(); const { data, isLoading, error, refetch } = useQuery( @@ -18,7 +18,6 @@ export default function WebsitesList() { { enabled: !!user }, ); const { toast, showToast } = useToast(); - const { formatMessage } = useIntl(); const hasData = data && data.length !== 0; const handleSave = async () => { diff --git a/components/pages/settings/websites/WebsitesTable.js b/components/pages/settings/websites/WebsitesTable.js index 7a969d94..6f7290a1 100644 --- a/components/pages/settings/websites/WebsitesTable.js +++ b/components/pages/settings/websites/WebsitesTable.js @@ -12,11 +12,10 @@ import { Icons, Flexbox, } from 'react-basics'; -import { useIntl } from 'react-intl'; -import { labels } from 'components/messages'; +import useMessages from 'hooks/useMessages'; export default function WebsitesTable({ data = [] }) { - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); const columns = [ { name: 'name', label: formatMessage(labels.name), style: { flex: 2 } }, diff --git a/components/pages/websites/WebsiteMenuView.js b/components/pages/websites/WebsiteMenuView.js index d8f0b7ff..99e2dc5b 100644 --- a/components/pages/websites/WebsiteMenuView.js +++ b/components/pages/websites/WebsiteMenuView.js @@ -1,5 +1,4 @@ import { Menu, Item, Icon, Button, Flexbox, Text } from 'react-basics'; -import { useIntl } from 'react-intl'; import Link from 'next/link'; import { GridRow, GridColumn } from 'components/layout/Grid'; import BrowsersTable from 'components/metrics/BrowsersTable'; @@ -14,8 +13,8 @@ import ScreenTable from 'components/metrics/ScreenTable'; import EventsTable from 'components/metrics/EventsTable'; import usePageQuery from 'hooks/usePageQuery'; import Icons from 'components/icons'; -import { labels } from 'components/messages'; import styles from './WebsiteMenuView.module.css'; +import useMessages from 'hooks/useMessages'; const views = { url: PagesTable, @@ -31,7 +30,7 @@ const views = { }; export default function WebsiteMenuView({ websiteId, websiteDomain }) { - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); const { resolveUrl, query: { view }, diff --git a/hooks/useMessages.js b/hooks/useMessages.js index b0202fce..1bb65778 100644 --- a/hooks/useMessages.js +++ b/hooks/useMessages.js @@ -1,8 +1,14 @@ -import { useIntl } from 'react-intl'; +import { useIntl, FormattedMessage } from 'react-intl'; import { messages, labels } from 'components/messages'; export default function useMessages() { const { formatMessage } = useIntl(); - return { formatMessage, messages, labels }; + function getMessage(id) { + const message = Object.values(messages).find(value => value.id === id); + + return message ? formatMessage(message) : id; + } + + return { formatMessage, FormattedMessage, messages, labels, getMessage }; } diff --git a/hooks/useSticky.js b/hooks/useSticky.js index e2f561b3..ae4dce72 100644 --- a/hooks/useSticky.js +++ b/hooks/useSticky.js @@ -1,15 +1,15 @@ import { useState, useEffect, useRef } from 'react'; -export default function useSticky({ defaultSticky = false, enabled = true }) { - const [isSticky, setIsSticky] = useState(defaultSticky); +export default function useSticky({ enabled = true, threshold = 1 }) { + const [isSticky, setIsSticky] = useState(false); const ref = useRef(null); useEffect(() => { let observer; - const handler = ([entry]) => setIsSticky(entry.intersectionRatio < 1); + const handler = ([entry]) => setIsSticky(entry.intersectionRatio < threshold); if (enabled && ref.current) { - observer = new IntersectionObserver(handler, { threshold: [1] }); + observer = new IntersectionObserver(handler, { threshold: [threshold] }); observer.observe(ref.current); } return () => { @@ -17,7 +17,7 @@ export default function useSticky({ defaultSticky = false, enabled = true }) { observer.disconnect(); } }; - }, [ref]); + }, [ref, enabled, threshold]); return { ref, isSticky }; } diff --git a/pages/404.js b/pages/404.js index 12cb4113..a5601f35 100644 --- a/pages/404.js +++ b/pages/404.js @@ -1,10 +1,9 @@ import { Row, Column, Flexbox } from 'react-basics'; -import { useIntl } from 'react-intl'; import AppLayout from 'components/layout/AppLayout'; -import { labels } from 'components/messages'; +import useMessages from 'hooks/useMessages'; export default function Custom404() { - const { formatMessage } = useIntl(); + const { formatMessage, labels } = useMessages(); return ( diff --git a/styles/index.css b/styles/index.css index 1a57208e..66e307b8 100644 --- a/styles/index.css +++ b/styles/index.css @@ -15,7 +15,6 @@ body { flex: 1; color: var(--font-color100); background: var(--base50); - overflow: hidden; } *, @@ -63,5 +62,4 @@ svg { flex: 1; width: 100%; height: 100%; - overflow: hidden; } From ea39f5b43120ae9701ac6039cd07e780a3a9a299 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Wed, 22 Mar 2023 23:02:37 -0700 Subject: [PATCH 5/6] add new event data schema --- db/clickhouse/schema.sql | 68 +++++++++++++++++-- db/mysql/migrations/01_init/migration.sql | 28 +++++++- db/mysql/schema.prisma | 37 +++++++++- .../09_add_new_event_data/migration.sql | 38 +++++++++++ db/postgresql/schema.prisma | 39 ++++++++++- 5 files changed, 198 insertions(+), 12 deletions(-) create mode 100644 db/postgresql/migrations/09_add_new_event_data/migration.sql diff --git a/db/clickhouse/schema.sql b/db/clickhouse/schema.sql index c0a2f62d..acae2ac9 100644 --- a/db/clickhouse/schema.sql +++ b/db/clickhouse/schema.sql @@ -1,11 +1,11 @@ SET allow_experimental_object_type = 1; -- Create Event -CREATE TABLE event +CREATE TABLE umami.event ( website_id UUID, session_id UUID, - event_id Nullable(UUID), + event_id UUID, rev_id UInt32, --session hostname LowCardinality(String), @@ -34,10 +34,10 @@ CREATE TABLE event ORDER BY (website_id, session_id, created_at) SETTINGS index_granularity = 8192; -CREATE TABLE event_queue ( +CREATE TABLE umami.event_queue ( website_id UUID, session_id UUID, - event_id Nullable(UUID), + event_id UUID, rev_id UInt32, --session hostname LowCardinality(String), @@ -70,7 +70,7 @@ SETTINGS kafka_broker_list = 'domain:9092,domain:9093,domain:9094', -- input bro kafka_max_block_size = 1048576, kafka_skip_broken_messages = 1; -CREATE MATERIALIZED VIEW event_queue_mv TO event AS +CREATE MATERIALIZED VIEW umami.event_queue_mv TO umami.event AS SELECT website_id, session_id, event_id, @@ -94,4 +94,60 @@ SELECT website_id, event_type, event_name, created_at -FROM event_queue; \ No newline at end of file +FROM umami.event_queue; + +CREATE TABLE umami.event_data +( + website_id UUID, + session_id UUID, + event_id UUID, + rev_id UInt32, + url_path String, + event_name String, + event_key String, + event_string_value Nullable(String), + event_numeric_value Nullable(Decimal64(4)), + event_date_value Nullable(DateTime('UTC')), + event_data_type UInt32, + created_at DateTime('UTC') +) + engine = MergeTree + ORDER BY (website_id, session_id, event_id, event_key, created_at) + SETTINGS index_granularity = 8192; + +CREATE TABLE umami.event_data_queue ( + website_id UUID, + session_id UUID, + event_id UUID, + rev_id UInt32, + url_path String, + event_name String, + event_key String, + event_string_value Nullable(String), + event_numeric_value Nullable(Decimal64(4)), + event_date_value Nullable(DateTime('UTC')), + event_data_type UInt32, + created_at DateTime('UTC') +) +ENGINE = Kafka +SETTINGS kafka_broker_list = 'domain:9092,domain:9093,domain:9094', -- input broker list + kafka_topic_list = 'event_data', + kafka_group_name = 'event_data_consumer_group', + kafka_format = 'JSONEachRow', + kafka_max_block_size = 1048576, + kafka_skip_broken_messages = 1; + +CREATE MATERIALIZED VIEW umami.event_data_queue_mv TO umami.event_data AS +SELECT website_id, + session_id, + event_id, + rev_id, + url_path, + event_name, + event_key, + event_string_value, + event_numeric_value, + event_date_value, + event_data_type, + created_at +FROM umami.event_data_queue; \ No newline at end of file diff --git a/db/mysql/migrations/01_init/migration.sql b/db/mysql/migrations/01_init/migration.sql index eacbc38d..84dfea01 100644 --- a/db/mysql/migrations/01_init/migration.sql +++ b/db/mysql/migrations/01_init/migration.sql @@ -78,6 +78,31 @@ CREATE TABLE `website_event` ( PRIMARY KEY (`event_id`) ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +-- CreateTable +CREATE TABLE `event_data` ( + `event_id` VARCHAR(36) NOT NULL, + `website_event_id` VARCHAR(36) NOT NULL, + `website_id` VARCHAR(36) NOT NULL, + `session_id` VARCHAR(36) NOT NULL, + `url_path` VARCHAR(500) NOT NULL, + `event_name` VARCHAR(500) NOT NULL, + `event_key` VARCHAR(500) NOT NULL, + `event_string_value` VARCHAR(500) NOT NULL, + `event_numeric_value` DECIMAL(19, 4) NOT NULL, + `event_date_value` TIMESTAMP(0) NULL, + `event_data_type` INTEGER UNSIGNED NOT NULL, + `created_at` TIMESTAMP(0) NULL DEFAULT CURRENT_TIMESTAMP(0), + + INDEX `event_data_created_at_idx`(`created_at`), + INDEX `event_data_session_id_idx`(`session_id`), + INDEX `event_data_website_id_idx`(`website_id`), + INDEX `event_data_website_event_id_idx`(`website_event_id`), + INDEX `event_data_website_id_website_event_id_created_at_idx`(`website_id`, `website_event_id`, `created_at`), + INDEX `event_data_website_id_session_id_created_at_idx`(`website_id`, `session_id`, `created_at`), + INDEX `event_data_website_id_session_id_website_event_id_created_at_idx`(`website_id`, `session_id`, `website_event_id`, `created_at`), + PRIMARY KEY (`event_id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + -- CreateTable CREATE TABLE `team` ( `team_id` VARCHAR(36) NOT NULL, @@ -119,6 +144,3 @@ CREATE TABLE `team_website` ( INDEX `team_website_website_id_idx`(`website_id`), PRIMARY KEY (`team_website_id`) ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; - --- AddSystemUser -INSERT INTO "user" (user_id, username, role, password) VALUES ('41e2b680-648e-4b09-bcd7-3e2b10c06264' , 'admin', 'admin', '$2b$10$BUli0c.muyCW1ErNJc3jL.vFRFtFJWrT8/GcR4A.sUdCznaXiqFXa'); \ No newline at end of file diff --git a/db/mysql/schema.prisma b/db/mysql/schema.prisma index 1f182716..7a7a1b15 100644 --- a/db/mysql/schema.prisma +++ b/db/mysql/schema.prisma @@ -17,8 +17,8 @@ model User { updatedAt DateTime? @map("updated_at") @db.Timestamp(0) deletedAt DateTime? @map("deleted_at") @db.Timestamp(0) + website Website[] teamUser TeamUser[] - Website Website[] @@map("user") } @@ -38,6 +38,9 @@ model Session { city String? @db.VarChar(50) createdAt DateTime? @default(now()) @map("created_at") @db.Timestamp(0) + websiteEvent WebsiteEvent[] + eventData EventData[] + @@index([createdAt]) @@index([websiteId]) @@map("session") @@ -56,6 +59,7 @@ model Website { user User? @relation(fields: [userId], references: [id]) teamWebsite TeamWebsite[] + eventData EventData[] @@index([userId]) @@index([createdAt]) @@ -77,6 +81,9 @@ model WebsiteEvent { eventType Int @default(1) @map("event_type") @db.UnsignedInt eventName String? @map("event_name") @db.VarChar(50) + eventData EventData[] + session Session @relation(fields: [sessionId], references: [id]) + @@index([createdAt]) @@index([sessionId]) @@index([websiteId]) @@ -85,6 +92,34 @@ model WebsiteEvent { @@map("website_event") } +model EventData { + id String @id() @map("event_id") @db.VarChar(36) + websiteEventId String @map("website_event_id") @db.VarChar(36) + websiteId String @map("website_id") @db.VarChar(36) + sessionId String @map("session_id") @db.VarChar(36) + urlPath String @map("url_path") @db.VarChar(500) + eventName String @map("event_name") @db.VarChar(500) + eventKey String @map("event_key") @db.VarChar(500) + eventStringValue String @map("event_string_value") @db.VarChar(500) + eventNumericValue Decimal @map("event_numeric_value") @db.Decimal(19,4) + eventDateValue DateTime? @map("event_date_value") @db.Timestamp(0) + eventDataType Int @map("event_data_type") @db.UnsignedInt + createdAt DateTime? @default(now()) @map("created_at")@db.Timestamp(0) + + website Website @relation(fields: [websiteId], references: [id]) + websiteEvent WebsiteEvent @relation(fields: [websiteEventId], references: [id]) + session Session @relation(fields: [sessionId], references: [id]) + + @@index([createdAt]) + @@index([sessionId]) + @@index([websiteId]) + @@index([websiteEventId]) + @@index([websiteId, websiteEventId, createdAt]) + @@index([websiteId, sessionId, createdAt]) + @@index([websiteId, sessionId, websiteEventId, createdAt]) + @@map("event_data") +} + model Team { id String @id() @unique() @map("team_id") @db.VarChar(36) name String @db.VarChar(50) diff --git a/db/postgresql/migrations/09_add_new_event_data/migration.sql b/db/postgresql/migrations/09_add_new_event_data/migration.sql new file mode 100644 index 00000000..932d4f19 --- /dev/null +++ b/db/postgresql/migrations/09_add_new_event_data/migration.sql @@ -0,0 +1,38 @@ +-- CreateTable +CREATE TABLE "event_data" ( + "event_id" UUID NOT NULL, + "website_event_id" UUID NOT NULL, + "website_id" UUID NOT NULL, + "session_id" UUID NOT NULL, + "url_path" VARCHAR(500) NOT NULL, + "event_name" VARCHAR(500) NOT NULL, + "event_key" VARCHAR(500) NOT NULL, + "event_string_value" VARCHAR(500) NOT NULL, + "event_numeric_value" DECIMAL(19,4) NOT NULL, + "event_date_value" TIMESTAMPTZ(6), + "event_data_type" INTEGER NOT NULL, + "created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "event_data_pkey" PRIMARY KEY ("event_id") +); + +-- CreateIndex +CREATE INDEX "event_data_created_at_idx" ON "event_data"("created_at"); + +-- CreateIndex +CREATE INDEX "event_data_session_id_idx" ON "event_data"("session_id"); + +-- CreateIndex +CREATE INDEX "event_data_website_id_idx" ON "event_data"("website_id"); + +-- CreateIndex +CREATE INDEX "event_data_website_event_id_idx" ON "event_data"("website_event_id"); + +-- CreateIndex +CREATE INDEX "event_data_website_id_website_event_id_created_at_idx" ON "event_data"("website_id", "website_event_id", "created_at"); + +-- CreateIndex +CREATE INDEX "event_data_website_id_session_id_created_at_idx" ON "event_data"("website_id", "session_id", "created_at"); + +-- CreateIndex +CREATE INDEX "event_data_website_id_session_id_website_event_id_created_a_idx" ON "event_data"("website_id", "session_id", "website_event_id", "created_at"); diff --git a/db/postgresql/schema.prisma b/db/postgresql/schema.prisma index 82b22c64..a29b96b2 100644 --- a/db/postgresql/schema.prisma +++ b/db/postgresql/schema.prisma @@ -17,7 +17,7 @@ model User { updatedAt DateTime? @map("updated_at") @db.Timestamptz(6) deletedAt DateTime? @map("deleted_at") @db.Timestamptz(6) - Website Website[] + website Website[] teamUser TeamUser[] @@map("user") @@ -38,6 +38,9 @@ model Session { city String? @db.VarChar(50) createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6) + websiteEvent WebsiteEvent[] + eventData EventData[] + @@index([createdAt]) @@index([websiteId]) @@map("session") @@ -56,6 +59,7 @@ model Website { user User? @relation(fields: [userId], references: [id]) teamWebsite TeamWebsite[] + eventData EventData[] @@index([userId]) @@index([createdAt]) @@ -77,6 +81,9 @@ model WebsiteEvent { eventType Int @default(1) @map("event_type") @db.Integer eventName String? @map("event_name") @db.VarChar(50) + eventData EventData[] + session Session @relation(fields: [sessionId], references: [id]) + @@index([createdAt]) @@index([sessionId]) @@index([websiteId]) @@ -85,6 +92,34 @@ model WebsiteEvent { @@map("website_event") } +model EventData { + id String @id() @map("event_id") @db.Uuid + websiteEventId String @map("website_event_id") @db.Uuid + websiteId String @map("website_id") @db.Uuid + sessionId String @map("session_id") @db.Uuid + urlPath String @map("url_path") @db.VarChar(500) + eventName String @map("event_name") @db.VarChar(500) + eventKey String @map("event_key") @db.VarChar(500) + eventStringValue String @map("event_string_value") @db.VarChar(500) + eventNumericValue Decimal @map("event_numeric_value") @db.Decimal(19,4) + eventDateValue DateTime? @map("event_date_value") @db.Timestamptz(6) + eventDataType Int @map("event_data_type") @db.Integer + createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6) + + website Website @relation(fields: [websiteId], references: [id]) + websiteEvent WebsiteEvent @relation(fields: [websiteEventId], references: [id]) + session Session @relation(fields: [sessionId], references: [id]) + + @@index([createdAt]) + @@index([sessionId]) + @@index([websiteId]) + @@index([websiteEventId]) + @@index([websiteId, websiteEventId, createdAt]) + @@index([websiteId, sessionId, createdAt]) + @@index([websiteId, sessionId, websiteEventId, createdAt]) + @@map("event_data") +} + model Team { id String @id() @unique() @map("team_id") @db.Uuid name String @db.VarChar(50) @@ -127,4 +162,4 @@ model TeamWebsite { @@index([teamId]) @@index([websiteId]) @@map("team_website") -} +} \ No newline at end of file From 87545a5648ce920245dd5360f1ab39496bb513bf Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Wed, 22 Mar 2023 23:07:01 -0700 Subject: [PATCH 6/6] add system user insert back for MySQL --- db/mysql/migrations/01_init/migration.sql | 3 +++ 1 file changed, 3 insertions(+) diff --git a/db/mysql/migrations/01_init/migration.sql b/db/mysql/migrations/01_init/migration.sql index 84dfea01..586c604f 100644 --- a/db/mysql/migrations/01_init/migration.sql +++ b/db/mysql/migrations/01_init/migration.sql @@ -144,3 +144,6 @@ CREATE TABLE `team_website` ( INDEX `team_website_website_id_idx`(`website_id`), PRIMARY KEY (`team_website_id`) ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- AddSystemUser +INSERT INTO "user" (user_id, username, role, password) VALUES ('41e2b680-648e-4b09-bcd7-3e2b10c06264' , 'admin', 'admin', '$2b$10$BUli0c.muyCW1ErNJc3jL.vFRFtFJWrT8/GcR4A.sUdCznaXiqFXa'); \ No newline at end of file