From 02c3d2bab3ed58d6b7cdbcb585af51394f7e9ae7 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Mon, 30 Oct 2023 17:18:48 -0500 Subject: [PATCH 01/17] Moved files. New components build. --- src/app/(main)/NavBar.js | 37 +++++++++++++++++- src/app/(main)/Shell.tsx | 3 +- .../common => app/(main)}/UpdateNotice.js | 0 .../(main)}/UpdateNotice.module.css | 0 src/components/common/HamburgerButton.js | 39 +------------------ .../{common => metrics}/WorldMap.js | 0 .../{common => metrics}/WorldMap.module.css | 0 src/index.ts | 23 ++++++++--- 8 files changed, 56 insertions(+), 46 deletions(-) rename src/{components/common => app/(main)}/UpdateNotice.js (100%) rename src/{components/common => app/(main)}/UpdateNotice.module.css (100%) rename src/components/{common => metrics}/WorldMap.js (100%) rename src/components/{common => metrics}/WorldMap.module.css (100%) diff --git a/src/app/(main)/NavBar.js b/src/app/(main)/NavBar.js index 211adf5f..e241a059 100644 --- a/src/app/(main)/NavBar.js +++ b/src/app/(main)/NavBar.js @@ -14,6 +14,7 @@ import styles from './NavBar.module.css'; export function NavBar() { const pathname = usePathname(); const { formatMessage, labels } = useMessages(); + const cloudMode = Boolean(process.env.cloudMode); const links = [ { label: formatMessage(labels.dashboard), url: '/dashboard' }, @@ -22,6 +23,40 @@ export function NavBar() { { label: formatMessage(labels.settings), url: '/settings' }, ].filter(n => n); + const menuItems = [ + { + label: formatMessage(labels.dashboard), + url: '/dashboard', + }, + !cloudMode && { + label: formatMessage(labels.settings), + url: '/settings', + children: [ + { + label: formatMessage(labels.websites), + url: '/settings/websites', + }, + { + label: formatMessage(labels.teams), + url: '/settings/teams', + }, + { + label: formatMessage(labels.users), + url: '/settings/users', + }, + { + label: formatMessage(labels.profile), + url: '/settings/profile', + }, + ], + }, + cloudMode && { + label: formatMessage(labels.profile), + url: '/settings/profile', + }, + !cloudMode && { label: formatMessage(labels.logout), url: '/logout' }, + ].filter(n => n); + return (
@@ -49,7 +84,7 @@ export function NavBar() {
- +
); diff --git a/src/app/(main)/Shell.tsx b/src/app/(main)/Shell.tsx index 980abb62..69ceff1f 100644 --- a/src/app/(main)/Shell.tsx +++ b/src/app/(main)/Shell.tsx @@ -1,9 +1,10 @@ 'use client'; import Script from 'next/script'; import { usePathname } from 'next/navigation'; -import UpdateNotice from 'components/common/UpdateNotice'; import { useRequireLogin, useConfig } from 'components/hooks'; +import UpdateNotice from './UpdateNotice'; + export function Shell({ children }) { const { user } = useRequireLogin(); const config = useConfig(); diff --git a/src/components/common/UpdateNotice.js b/src/app/(main)/UpdateNotice.js similarity index 100% rename from src/components/common/UpdateNotice.js rename to src/app/(main)/UpdateNotice.js diff --git a/src/components/common/UpdateNotice.module.css b/src/app/(main)/UpdateNotice.module.css similarity index 100% rename from src/components/common/UpdateNotice.module.css rename to src/app/(main)/UpdateNotice.module.css diff --git a/src/components/common/HamburgerButton.js b/src/components/common/HamburgerButton.js index f97006ef..0eddad0f 100644 --- a/src/components/common/HamburgerButton.js +++ b/src/components/common/HamburgerButton.js @@ -2,46 +2,9 @@ import { Button, Icon } from 'react-basics'; import { useState } from 'react'; import MobileMenu from './MobileMenu'; import Icons from 'components/icons'; -import useMessages from 'components/hooks/useMessages'; -export function HamburgerButton() { - const { formatMessage, labels } = useMessages(); +export function HamburgerButton({ menuItems }) { const [active, setActive] = useState(false); - const cloudMode = Boolean(process.env.cloudMode); - - const menuItems = [ - { - label: formatMessage(labels.dashboard), - url: '/dashboard', - }, - !cloudMode && { - label: formatMessage(labels.settings), - url: '/settings', - children: [ - { - label: formatMessage(labels.websites), - url: '/settings/websites', - }, - { - label: formatMessage(labels.teams), - url: '/settings/teams', - }, - { - label: formatMessage(labels.users), - url: '/settings/users', - }, - { - label: formatMessage(labels.profile), - url: '/settings/profile', - }, - ], - }, - cloudMode && { - label: formatMessage(labels.profile), - url: '/settings/profile', - }, - !cloudMode && { label: formatMessage(labels.logout), url: '/logout' }, - ].filter(n => n); const handleClick = () => setActive(state => !state); const handleClose = () => setActive(false); diff --git a/src/components/common/WorldMap.js b/src/components/metrics/WorldMap.js similarity index 100% rename from src/components/common/WorldMap.js rename to src/components/metrics/WorldMap.js diff --git a/src/components/common/WorldMap.module.css b/src/components/metrics/WorldMap.module.css similarity index 100% rename from src/components/common/WorldMap.module.css rename to src/components/metrics/WorldMap.module.css diff --git a/src/index.ts b/src/index.ts index a6bb4c6c..01ceb7d1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,18 +1,15 @@ export * from 'components/hooks/useApi'; export * from 'components/hooks/useConfig'; -export * from 'components/hooks/useCountryNames'; export * from 'components/hooks/useDateRange'; export * from 'components/hooks/useDocumentClick'; export * from 'components/hooks/useEscapeKey'; +export * from 'components/hooks/useFilterQuery'; export * from 'components/hooks/useFilters'; export * from 'components/hooks/useForceUpdate'; export * from 'components/hooks/useFormat'; -export * from 'components/hooks/useLanguageNames'; export * from 'components/hooks/useLocale'; export * from 'components/hooks/useMessages'; export * from 'components/hooks/useNavigation'; -export * from 'components/hooks/useReport'; -export * from 'components/hooks/useReports'; export * from 'components/hooks/useRequireLogin'; export * from 'components/hooks/useShareToken'; export * from 'components/hooks/useSticky'; @@ -21,7 +18,7 @@ export * from 'components/hooks/useTimezone'; export * from 'components/hooks/useUser'; export * from 'components/hooks/useWebsite'; -export * from './app/(main)/settings/teams/[id]/TeamWebsiteAddForm'; +export * from 'app/(main)/settings/teams/[id]/TeamWebsiteAddForm'; export * from 'app/(main)/settings/teams/[id]/TeamEditForm'; export * from 'app/(main)/settings/teams/[id]/TeamMemberRemoveButton'; export * from 'app/(main)/settings/teams/[id]/TeamMembers'; @@ -44,8 +41,22 @@ export * from 'app/(main)/settings/websites/[id]/TrackingCode'; export * from 'app/(main)/settings/websites/[id]/WebsiteDeleteForm'; export * from 'app/(main)/settings/websites/[id]/WebsiteEditForm'; export * from 'app/(main)/settings/websites/[id]/WebsiteResetForm'; + export * from 'app/(main)/settings/websites/WebsiteAddForm'; export * from 'app/(main)/settings/websites/WebsitesHeader'; export * from 'app/(main)/settings/websites/WebsiteSettings'; -export * from './app/(main)/settings/websites/WebsitesDataTable'; +export * from 'app/(main)/settings/websites/WebsitesDataTable'; export * from 'app/(main)/settings/websites/WebsitesTable'; + +export * from 'components/common/ConfirmDeleteForm'; +export * from 'components/common/DataTable'; +export * from 'components/common/Empty'; +export * from 'components/common/ErrorBoundary'; +export * from 'components/common/Favicon'; +export * from 'components/common/FilterButtons'; +export * from 'components/common/FilterLink'; +export * from 'components/common/HamburgerButton'; +export * from 'components/common/HoverTooltip'; +export * from 'components/common/LinkButton'; +export * from 'components/common/MobileMenu'; +export * from 'components/common/Pager'; From 92f32ce7faa59e4420473009608fd817a1f48868 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Fri, 3 Nov 2023 14:23:48 -0700 Subject: [PATCH 02/17] rename url filter to url_path --- src/lib/constants.ts | 2 +- src/lib/types.ts | 2 +- src/pages/api/websites/[id]/events.ts | 8 ++++---- src/pages/api/websites/[id]/metrics.ts | 8 ++++---- src/pages/api/websites/[id]/pageviews.ts | 8 ++++---- src/pages/api/websites/[id]/stats.ts | 8 ++++---- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 4c468c1c..ca9cc5b0 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -45,7 +45,7 @@ export const SESSION_COLUMNS = [ ]; export const FILTER_COLUMNS = { - url: 'url_path', + url_path: 'url_path', referrer: 'referrer_domain', title: 'page_title', query: 'url_query', diff --git a/src/lib/types.ts b/src/lib/types.ts index 98fbc29b..c3d07b52 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -189,7 +189,7 @@ export interface QueryFilters { timezone?: string; unit?: string; eventType?: number; - url?: string; + url_path?: string; referrer?: string; title?: string; query?: string; diff --git a/src/pages/api/websites/[id]/events.ts b/src/pages/api/websites/[id]/events.ts index 05a651ab..98b92d31 100644 --- a/src/pages/api/websites/[id]/events.ts +++ b/src/pages/api/websites/[id]/events.ts @@ -14,7 +14,7 @@ export interface WebsiteEventsRequestQuery { endAt: string; unit?: string; timezone?: string; - url: string; + url_path: string; } const schema = { @@ -24,7 +24,7 @@ const schema = { endAt: yup.number().integer().moreThan(yup.ref('startAt')).required(), unit: UnitTypeTest, timezone: TimezoneTest, - url: yup.string(), + url_path: yup.string(), }), }; @@ -36,7 +36,7 @@ export default async ( await useAuth(req, res); await useValidate(schema, req, res); - const { id: websiteId, timezone, url } = req.query; + const { id: websiteId, timezone, url_path } = req.query; const { startDate, endDate, unit } = await parseDateRangeQuery(req); if (req.method === 'GET') { @@ -49,7 +49,7 @@ export default async ( endDate, timezone, unit, - url, + url_path, }); return ok(res, events); diff --git a/src/pages/api/websites/[id]/metrics.ts b/src/pages/api/websites/[id]/metrics.ts index 56b0b066..cf3ef06a 100644 --- a/src/pages/api/websites/[id]/metrics.ts +++ b/src/pages/api/websites/[id]/metrics.ts @@ -13,7 +13,7 @@ export interface WebsiteMetricsRequestQuery { type: string; startAt: number; endAt: number; - url?: string; + url_path?: string; referrer?: string; title?: string; query?: string; @@ -33,7 +33,7 @@ const schema = { type: yup.string().required(), startAt: yup.number().required(), endAt: yup.number().required(), - url: yup.string(), + url_path: yup.string(), referrer: yup.string(), title: yup.string(), query: yup.string(), @@ -59,7 +59,7 @@ export default async ( const { id: websiteId, type, - url, + url_path, referrer, title, query, @@ -83,7 +83,7 @@ export default async ( const filters = { startDate, endDate, - url, + url_path, referrer, title, query, diff --git a/src/pages/api/websites/[id]/pageviews.ts b/src/pages/api/websites/[id]/pageviews.ts index 7356c504..428913a5 100644 --- a/src/pages/api/websites/[id]/pageviews.ts +++ b/src/pages/api/websites/[id]/pageviews.ts @@ -12,7 +12,7 @@ export interface WebsitePageviewRequestQuery { endAt: number; unit?: string; timezone?: string; - url?: string; + url_path?: string; referrer?: string; title?: string; os?: string; @@ -32,7 +32,7 @@ const schema = { endAt: yup.number().required(), unit: UnitTypeTest, timezone: TimezoneTest, - url: yup.string(), + url_path: yup.string(), referrer: yup.string(), title: yup.string(), os: yup.string(), @@ -55,7 +55,7 @@ export default async ( const { id: websiteId, timezone, - url, + url_path, referrer, title, os, @@ -78,7 +78,7 @@ export default async ( endDate, timezone, unit, - url, + url_path, referrer, title, os, diff --git a/src/pages/api/websites/[id]/stats.ts b/src/pages/api/websites/[id]/stats.ts index 4e8d2a88..094e860d 100644 --- a/src/pages/api/websites/[id]/stats.ts +++ b/src/pages/api/websites/[id]/stats.ts @@ -11,7 +11,7 @@ export interface WebsiteStatsRequestQuery { id: string; startAt: number; endAt: number; - url?: string; + url_path?: string; referrer?: string; title?: string; query?: string; @@ -30,7 +30,7 @@ const schema = { id: yup.string().uuid().required(), startAt: yup.number().required(), endAt: yup.number().required(), - url: yup.string(), + url_path: yup.string(), referrer: yup.string(), title: yup.string(), query: yup.string(), @@ -54,7 +54,7 @@ export default async ( const { id: websiteId, - url, + url_path, referrer, title, query, @@ -78,7 +78,7 @@ export default async ( const prevEndDate = subMinutes(endDate, diff); const filters = { - url, + url_path, referrer, title, query, From cd1a98f51b5a3f81e5ddc94d8848fa7b310f3afd Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Fri, 3 Nov 2023 15:46:12 -0700 Subject: [PATCH 03/17] Fix worldmap mapping --- src/app/(main)/websites/[id]/WebsiteTableView.js | 2 +- src/app/(main)/websites/[id]/realtime/Realtime.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/(main)/websites/[id]/WebsiteTableView.js b/src/app/(main)/websites/[id]/WebsiteTableView.js index 7c71b84b..28a8fad6 100644 --- a/src/app/(main)/websites/[id]/WebsiteTableView.js +++ b/src/app/(main)/websites/[id]/WebsiteTableView.js @@ -5,7 +5,7 @@ import ReferrersTable from 'components/metrics/ReferrersTable'; import BrowsersTable from 'components/metrics/BrowsersTable'; import OSTable from 'components/metrics/OSTable'; import DevicesTable from 'components/metrics/DevicesTable'; -import WorldMap from 'components/common/WorldMap'; +import WorldMap from 'components/metrics/WorldMap'; import CountriesTable from 'components/metrics/CountriesTable'; import EventsTable from 'components/metrics/EventsTable'; import EventsChart from 'components/metrics/EventsChart'; diff --git a/src/app/(main)/websites/[id]/realtime/Realtime.js b/src/app/(main)/websites/[id]/realtime/Realtime.js index b4219b0a..737bcd1b 100644 --- a/src/app/(main)/websites/[id]/realtime/Realtime.js +++ b/src/app/(main)/websites/[id]/realtime/Realtime.js @@ -5,7 +5,7 @@ import firstBy from 'thenby'; import { Grid, GridRow } from 'components/layout/Grid'; import Page from 'components/layout/Page'; import RealtimeChart from 'components/metrics/RealtimeChart'; -import WorldMap from 'components/common/WorldMap'; +import WorldMap from 'components/metrics/WorldMap'; import RealtimeLog from './RealtimeLog'; import RealtimeHeader from './RealtimeHeader'; import RealtimeUrls from './RealtimeUrls'; From d6ee8ee86966a03429bcb136f0d14d7730da5f58 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Fri, 3 Nov 2023 15:48:25 -0700 Subject: [PATCH 04/17] Revert "rename url filter to url_path" This reverts commit 92f32ce7faa59e4420473009608fd817a1f48868. --- src/lib/constants.ts | 2 +- src/lib/types.ts | 2 +- src/pages/api/websites/[id]/events.ts | 8 ++++---- src/pages/api/websites/[id]/metrics.ts | 8 ++++---- src/pages/api/websites/[id]/pageviews.ts | 8 ++++---- src/pages/api/websites/[id]/stats.ts | 8 ++++---- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/lib/constants.ts b/src/lib/constants.ts index ca9cc5b0..4c468c1c 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -45,7 +45,7 @@ export const SESSION_COLUMNS = [ ]; export const FILTER_COLUMNS = { - url_path: 'url_path', + url: 'url_path', referrer: 'referrer_domain', title: 'page_title', query: 'url_query', diff --git a/src/lib/types.ts b/src/lib/types.ts index c3d07b52..98fbc29b 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -189,7 +189,7 @@ export interface QueryFilters { timezone?: string; unit?: string; eventType?: number; - url_path?: string; + url?: string; referrer?: string; title?: string; query?: string; diff --git a/src/pages/api/websites/[id]/events.ts b/src/pages/api/websites/[id]/events.ts index 98b92d31..05a651ab 100644 --- a/src/pages/api/websites/[id]/events.ts +++ b/src/pages/api/websites/[id]/events.ts @@ -14,7 +14,7 @@ export interface WebsiteEventsRequestQuery { endAt: string; unit?: string; timezone?: string; - url_path: string; + url: string; } const schema = { @@ -24,7 +24,7 @@ const schema = { endAt: yup.number().integer().moreThan(yup.ref('startAt')).required(), unit: UnitTypeTest, timezone: TimezoneTest, - url_path: yup.string(), + url: yup.string(), }), }; @@ -36,7 +36,7 @@ export default async ( await useAuth(req, res); await useValidate(schema, req, res); - const { id: websiteId, timezone, url_path } = req.query; + const { id: websiteId, timezone, url } = req.query; const { startDate, endDate, unit } = await parseDateRangeQuery(req); if (req.method === 'GET') { @@ -49,7 +49,7 @@ export default async ( endDate, timezone, unit, - url_path, + url, }); return ok(res, events); diff --git a/src/pages/api/websites/[id]/metrics.ts b/src/pages/api/websites/[id]/metrics.ts index cf3ef06a..56b0b066 100644 --- a/src/pages/api/websites/[id]/metrics.ts +++ b/src/pages/api/websites/[id]/metrics.ts @@ -13,7 +13,7 @@ export interface WebsiteMetricsRequestQuery { type: string; startAt: number; endAt: number; - url_path?: string; + url?: string; referrer?: string; title?: string; query?: string; @@ -33,7 +33,7 @@ const schema = { type: yup.string().required(), startAt: yup.number().required(), endAt: yup.number().required(), - url_path: yup.string(), + url: yup.string(), referrer: yup.string(), title: yup.string(), query: yup.string(), @@ -59,7 +59,7 @@ export default async ( const { id: websiteId, type, - url_path, + url, referrer, title, query, @@ -83,7 +83,7 @@ export default async ( const filters = { startDate, endDate, - url_path, + url, referrer, title, query, diff --git a/src/pages/api/websites/[id]/pageviews.ts b/src/pages/api/websites/[id]/pageviews.ts index 428913a5..7356c504 100644 --- a/src/pages/api/websites/[id]/pageviews.ts +++ b/src/pages/api/websites/[id]/pageviews.ts @@ -12,7 +12,7 @@ export interface WebsitePageviewRequestQuery { endAt: number; unit?: string; timezone?: string; - url_path?: string; + url?: string; referrer?: string; title?: string; os?: string; @@ -32,7 +32,7 @@ const schema = { endAt: yup.number().required(), unit: UnitTypeTest, timezone: TimezoneTest, - url_path: yup.string(), + url: yup.string(), referrer: yup.string(), title: yup.string(), os: yup.string(), @@ -55,7 +55,7 @@ export default async ( const { id: websiteId, timezone, - url_path, + url, referrer, title, os, @@ -78,7 +78,7 @@ export default async ( endDate, timezone, unit, - url_path, + url, referrer, title, os, diff --git a/src/pages/api/websites/[id]/stats.ts b/src/pages/api/websites/[id]/stats.ts index 094e860d..4e8d2a88 100644 --- a/src/pages/api/websites/[id]/stats.ts +++ b/src/pages/api/websites/[id]/stats.ts @@ -11,7 +11,7 @@ export interface WebsiteStatsRequestQuery { id: string; startAt: number; endAt: number; - url_path?: string; + url?: string; referrer?: string; title?: string; query?: string; @@ -30,7 +30,7 @@ const schema = { id: yup.string().uuid().required(), startAt: yup.number().required(), endAt: yup.number().required(), - url_path: yup.string(), + url: yup.string(), referrer: yup.string(), title: yup.string(), query: yup.string(), @@ -54,7 +54,7 @@ export default async ( const { id: websiteId, - url_path, + url, referrer, title, query, @@ -78,7 +78,7 @@ export default async ( const prevEndDate = subMinutes(endDate, diff); const filters = { - url_path, + url, referrer, title, query, From eda2c07ea3480c5e5f5e7833a4f447c8f9406dce Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sat, 11 Nov 2023 20:45:09 -0800 Subject: [PATCH 05/17] Allow embedding of share page. --- next.config.js | 51 +++++++++++-------- src/app/(main)/{Shell.tsx => App.tsx} | 4 +- src/app/(main)/dashboard/page.tsx | 2 +- src/app/(main)/layout.tsx | 8 +-- ...portsDataTable.js => ReportsDataTable.tsx} | 4 +- src/app/(main)/reports/page.tsx | 2 +- src/app/layout.tsx | 14 ++--- src/app/logout/page.tsx | 5 ++ src/app/share/[...id]/page.tsx | 5 ++ src/pages/api/send.ts | 2 +- 10 files changed, 58 insertions(+), 39 deletions(-) rename src/app/(main)/{Shell.tsx => App.tsx} (90%) rename src/app/(main)/reports/{ReportsDataTable.js => ReportsDataTable.tsx} (80%) diff --git a/next.config.js b/next.config.js index cf7dce7f..03c30c55 100644 --- a/next.config.js +++ b/next.config.js @@ -3,29 +3,32 @@ require('dotenv').config(); const path = require('path'); const pkg = require('./package.json'); -const contentSecurityPolicy = ` - default-src 'self'; - img-src *; - script-src 'self' 'unsafe-eval' 'unsafe-inline'; - style-src 'self' 'unsafe-inline'; - connect-src 'self' api.umami.is; - frame-ancestors 'self' ${process.env.ALLOWED_FRAME_URLS}; -`; +const contentSecurityPolicy = [ + `default-src 'self'`, + `img-src *`, + `script-src 'self' 'unsafe-eval' 'unsafe-inline'`, + `style-src 'self' 'unsafe-inline'`, + `connect-src 'self' api.umami.is`, +]; const headers = [ { key: 'X-DNS-Prefetch-Control', value: 'on', }, - { + !process.env.ALLOWED_FRAME_URLS && { key: 'X-Frame-Options', value: 'SAMEORIGIN', }, - { - key: 'Content-Security-Policy', - value: contentSecurityPolicy.replace(/\s{2,}/g, ' ').trim(), - }, -]; +].filter(n => n); + +const cspHeader = (values = []) => ({ + key: 'Content-Security-Policy', + value: [...contentSecurityPolicy, ...values] + .join(';') + .replace(/\s{2,}/g, ' ') + .trim(), +}); if (process.env.FORCE_SSL) { headers.push({ @@ -81,14 +84,13 @@ const config = { reactStrictMode: false, env: { basePath: basePath || '', - cloudMode: !!process.env.CLOUD_MODE, - cloudUrl: process.env.CLOUD_URL, + cloudMode: process.env.CLOUD_MODE || '', + cloudUrl: process.env.CLOUD_URL || '', configUrl: '/config', currentVersion: pkg.version, - defaultLocale: process.env.DEFAULT_LOCALE, - disableLogin: process.env.DISABLE_LOGIN, - disableUI: process.env.DISABLE_UI, - isProduction: process.env.NODE_ENV === 'production', + defaultLocale: process.env.DEFAULT_LOCALE || '', + disableLogin: process.env.DISABLE_LOGIN || '', + disableUI: process.env.DISABLE_UI || '', }, basePath, output: 'standalone', @@ -125,7 +127,14 @@ const config = { return [ { source: '/:path*', - headers, + headers: [ + ...headers, + cspHeader([`frame-ancestors 'self' ${process.env.ALLOWED_FRAME_URLS || ''}`]), + ], + }, + { + source: '/share/:path*', + headers: [...headers, cspHeader()], }, ]; }, diff --git a/src/app/(main)/Shell.tsx b/src/app/(main)/App.tsx similarity index 90% rename from src/app/(main)/Shell.tsx rename to src/app/(main)/App.tsx index 980abb62..daf98fb1 100644 --- a/src/app/(main)/Shell.tsx +++ b/src/app/(main)/App.tsx @@ -4,7 +4,7 @@ import { usePathname } from 'next/navigation'; import UpdateNotice from 'components/common/UpdateNotice'; import { useRequireLogin, useConfig } from 'components/hooks'; -export function Shell({ children }) { +export function App({ children }) { const { user } = useRequireLogin(); const config = useConfig(); const pathname = usePathname(); @@ -24,4 +24,4 @@ export function Shell({ children }) { ); } -export default Shell; +export default App; diff --git a/src/app/(main)/dashboard/page.tsx b/src/app/(main)/dashboard/page.tsx index 91cc9c6e..1853a9f5 100644 --- a/src/app/(main)/dashboard/page.tsx +++ b/src/app/(main)/dashboard/page.tsx @@ -1,7 +1,7 @@ import Dashboard from 'app/(main)/dashboard/Dashboard'; import { Metadata } from 'next'; -export default function DashboardPage() { +export default function () { return ; } diff --git a/src/app/(main)/layout.tsx b/src/app/(main)/layout.tsx index 1c9cc277..f5aeab67 100644 --- a/src/app/(main)/layout.tsx +++ b/src/app/(main)/layout.tsx @@ -1,11 +1,11 @@ -import Shell from './Shell'; +import App from './App'; import NavBar from './NavBar'; import Page from 'components/layout/Page'; import styles from './layout.module.css'; -export default function AppLayout({ children }) { +export default function ({ children }) { return ( - +
-
+ ); } diff --git a/src/app/(main)/reports/ReportsDataTable.js b/src/app/(main)/reports/ReportsDataTable.tsx similarity index 80% rename from src/app/(main)/reports/ReportsDataTable.js rename to src/app/(main)/reports/ReportsDataTable.tsx index 0daa3d06..0ca853dc 100644 --- a/src/app/(main)/reports/ReportsDataTable.js +++ b/src/app/(main)/reports/ReportsDataTable.tsx @@ -5,9 +5,9 @@ import useFilterQuery from 'components/hooks/useFilterQuery'; import DataTable from 'components/common/DataTable'; import useCache from 'store/cache'; -export default function ReportsDataTable({ websiteId }) { +export default function ReportsDataTable({ websiteId }: { websiteId?: string }) { const { get } = useApi(); - const modified = useCache(state => state?.reports); + const modified = useCache(state => (state as any)?.reports); const queryResult = useFilterQuery(['reports', { websiteId, modified }], params => get(websiteId ? `/websites/${websiteId}/reports` : `/reports`, params), ); diff --git a/src/app/(main)/reports/page.tsx b/src/app/(main)/reports/page.tsx index aba59db2..22e6e2a7 100644 --- a/src/app/(main)/reports/page.tsx +++ b/src/app/(main)/reports/page.tsx @@ -1,7 +1,7 @@ import ReportsHeader from './ReportsHeader'; import ReportsDataTable from './ReportsDataTable'; -export default function ReportsPage() { +export default function () { return ( <> diff --git a/src/app/layout.tsx b/src/app/layout.tsx index e2478a95..970c33f0 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -8,16 +8,16 @@ import 'styles/locale.css'; import 'styles/index.css'; import 'styles/variables.css'; -export default function RootLayout({ children }) { +export default function ({ children }) { return ( - - - - - - + + + + + + diff --git a/src/app/logout/page.tsx b/src/app/logout/page.tsx index bce24736..89a3bce9 100644 --- a/src/app/logout/page.tsx +++ b/src/app/logout/page.tsx @@ -1,5 +1,10 @@ import Logout from './Logout'; +import { Metadata } from 'next'; export default function () { return ; } + +export const metadata: Metadata = { + title: 'Logout | umami', +}; diff --git a/src/app/share/[...id]/page.tsx b/src/app/share/[...id]/page.tsx index ca154165..2a69f406 100644 --- a/src/app/share/[...id]/page.tsx +++ b/src/app/share/[...id]/page.tsx @@ -1,5 +1,10 @@ import Share from './Share'; +import { Metadata } from 'next'; export default function ({ params: { id } }) { return ; } + +export const metadata: Metadata = { + title: 'umami', +}; diff --git a/src/pages/api/send.ts b/src/pages/api/send.ts index e8d3e386..cf3004f3 100644 --- a/src/pages/api/send.ts +++ b/src/pages/api/send.ts @@ -74,7 +74,7 @@ export default async (req: NextApiRequestCollect, res: NextApiResponse) => { await useCors(req, res); if (req.method === 'POST') { - if (isbot(req.headers['user-agent']) && !process.env.DISABLE_BOT_CHECK) { + if (!process.env.DISABLE_BOT_CHECK && isbot(req.headers['user-agent'])) { return ok(res); } From bdf2fa4f05d0c1b39e11df2e9f0f336721834398 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sat, 11 Nov 2023 20:47:06 -0800 Subject: [PATCH 06/17] Fixed import. --- src/app/(main)/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/(main)/App.tsx b/src/app/(main)/App.tsx index daf98fb1..01da9a6a 100644 --- a/src/app/(main)/App.tsx +++ b/src/app/(main)/App.tsx @@ -1,7 +1,7 @@ 'use client'; import Script from 'next/script'; import { usePathname } from 'next/navigation'; -import UpdateNotice from 'components/common/UpdateNotice'; +import UpdateNotice from './UpdateNotice'; import { useRequireLogin, useConfig } from 'components/hooks'; export function App({ children }) { From 366ef35d3d86e409419ca7f84740dc07b5460a2a Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Mon, 13 Nov 2023 14:12:05 -0800 Subject: [PATCH 07/17] Updated types. --- next.config.js | 37 +++++++++------- package.json | 8 ++-- .../{WebsiteAddForm.js => WebsiteAddForm.tsx} | 18 ++++---- ...irmDeleteForm.js => ConfirmDeleteForm.tsx} | 8 +++- src/components/common/Empty.tsx | 2 +- ...ptyPlaceholder.js => EmptyPlaceholder.tsx} | 6 +++ .../{ErrorBoundary.js => ErrorBoundary.tsx} | 9 +++- .../{ErrorMessage.js => ErrorMessage.tsx} | 0 .../common/{Favicon.js => Favicon.tsx} | 2 +- src/components/common/FilterButtons.js | 13 ------ src/components/common/FilterButtons.tsx | 20 +++++++++ .../common/{FilterLink.js => FilterLink.tsx} | 19 ++++++++- .../common/{LinkButton.js => LinkButton.tsx} | 11 ++++- yarn.lock | 42 +++++++++++++------ 14 files changed, 136 insertions(+), 59 deletions(-) rename src/app/(main)/settings/websites/{WebsiteAddForm.js => WebsiteAddForm.tsx} (76%) rename src/components/common/{ConfirmDeleteForm.js => ConfirmDeleteForm.tsx} (80%) rename src/components/common/{EmptyPlaceholder.js => EmptyPlaceholder.tsx} (78%) rename src/components/common/{ErrorBoundary.js => ErrorBoundary.tsx} (78%) rename src/components/common/{ErrorMessage.js => ErrorMessage.tsx} (100%) rename src/components/common/{Favicon.js => Favicon.tsx} (93%) delete mode 100644 src/components/common/FilterButtons.js create mode 100644 src/components/common/FilterButtons.tsx rename src/components/common/{FilterLink.js => FilterLink.tsx} (79%) rename src/components/common/{LinkButton.js => LinkButton.tsx} (69%) diff --git a/next.config.js b/next.config.js index 03c30c55..a1e30c36 100644 --- a/next.config.js +++ b/next.config.js @@ -11,24 +11,36 @@ const contentSecurityPolicy = [ `connect-src 'self' api.umami.is`, ]; +const cspHeader = (values = []) => ({ + key: 'Content-Security-Policy', + value: values + .join(';') + .replace(/\s{2,}/g, ' ') + .trim(), +}); + const headers = [ { key: 'X-DNS-Prefetch-Control', value: 'on', }, - !process.env.ALLOWED_FRAME_URLS && { + { key: 'X-Frame-Options', value: 'SAMEORIGIN', }, -].filter(n => n); + cspHeader(contentSecurityPolicy), +]; -const cspHeader = (values = []) => ({ - key: 'Content-Security-Policy', - value: [...contentSecurityPolicy, ...values] - .join(';') - .replace(/\s{2,}/g, ' ') - .trim(), -}); +const shareHeaders = [ + { + key: 'X-DNS-Prefetch-Control', + value: 'on', + }, + cspHeader([ + ...contentSecurityPolicy, + `frame-ancestors 'self' ${process.env.ALLOWED_FRAME_URLS || ''}`, + ]), +]; if (process.env.FORCE_SSL) { headers.push({ @@ -127,14 +139,11 @@ const config = { return [ { source: '/:path*', - headers: [ - ...headers, - cspHeader([`frame-ancestors 'self' ${process.env.ALLOWED_FRAME_URLS || ''}`]), - ], + headers, }, { source: '/share/:path*', - headers: [...headers, cspHeader()], + headers: shareHeaders, }, ]; }, diff --git a/package.json b/package.json index 08038ed7..5d8fff5e 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ "npm-run-all": "^4.1.5", "prisma": "5.4.2", "react": "^18.2.0", - "react-basics": "^0.105.0", + "react-basics": "^0.106.0", "react-beautiful-dnd": "^13.1.0", "react-dom": "^18.2.0", "react-error-boundary": "^4.0.4", @@ -125,9 +125,9 @@ "@rollup/plugin-replace": "^5.0.2", "@svgr/rollup": "^8.1.0", "@svgr/webpack": "^8.1.0", - "@types/node": "^18.11.9", - "@types/react": "^18.0.25", - "@types/react-dom": "^18.0.8", + "@types/node": "^20.9.0", + "@types/react": "^18.2.37", + "@types/react-dom": "^18.2.15", "@typescript-eslint/eslint-plugin": "^6.7.3", "@typescript-eslint/parser": "^6.7.3", "cross-env": "^7.0.3", diff --git a/src/app/(main)/settings/websites/WebsiteAddForm.js b/src/app/(main)/settings/websites/WebsiteAddForm.tsx similarity index 76% rename from src/app/(main)/settings/websites/WebsiteAddForm.js rename to src/app/(main)/settings/websites/WebsiteAddForm.tsx index 371343ba..99624103 100644 --- a/src/app/(main)/settings/websites/WebsiteAddForm.js +++ b/src/app/(main)/settings/websites/WebsiteAddForm.tsx @@ -11,22 +11,22 @@ import useApi from 'components/hooks/useApi'; import { DOMAIN_REGEX } from 'lib/constants'; import useMessages from 'components/hooks/useMessages'; -export function WebsiteAddForm({ onSave, onClose }) { +export function WebsiteAddForm({ onSave, onClose }: { onSave?: () => void; onClose?: () => void }) { const { formatMessage, labels, messages } = useMessages(); const { post, useMutation } = useApi(); const { mutate, error, isLoading } = useMutation(data => post('/websites', data)); - const handleSubmit = async data => { + const handleSubmit = async (data: any) => { mutate(data, { onSuccess: async () => { - onSave(); - onClose(); + onSave?.(); + onClose?.(); }, }); }; return ( -
+ @@ -47,9 +47,11 @@ export function WebsiteAddForm({ onSave, onClose }) { {formatMessage(labels.save)} - + {onClose && ( + + )} ); diff --git a/src/components/common/ConfirmDeleteForm.js b/src/components/common/ConfirmDeleteForm.tsx similarity index 80% rename from src/components/common/ConfirmDeleteForm.js rename to src/components/common/ConfirmDeleteForm.tsx index 3d2c383d..d4cbf203 100644 --- a/src/components/common/ConfirmDeleteForm.js +++ b/src/components/common/ConfirmDeleteForm.tsx @@ -2,7 +2,13 @@ import { useState } from 'react'; import { Button, LoadingButton, Form, FormButtons } from 'react-basics'; import useMessages from 'components/hooks/useMessages'; -export function ConfirmDeleteForm({ name, onConfirm, onClose }) { +export interface ConfirmDeleteFormProps { + name: string; + onConfirm?: () => void; + onClose?: () => void; +} + +export function ConfirmDeleteForm({ name, onConfirm, onClose }: ConfirmDeleteFormProps) { const [loading, setLoading] = useState(false); const { formatMessage, labels, messages, FormattedMessage } = useMessages(); diff --git a/src/components/common/Empty.tsx b/src/components/common/Empty.tsx index 2c7fcd4a..4e2677f8 100644 --- a/src/components/common/Empty.tsx +++ b/src/components/common/Empty.tsx @@ -1,6 +1,6 @@ import classNames from 'classnames'; -import styles from './Empty.module.css'; import useMessages from 'components/hooks/useMessages'; +import styles from './Empty.module.css'; export interface EmptyProps { message?: string; diff --git a/src/components/common/EmptyPlaceholder.js b/src/components/common/EmptyPlaceholder.tsx similarity index 78% rename from src/components/common/EmptyPlaceholder.js rename to src/components/common/EmptyPlaceholder.tsx index 8834a1db..27282edc 100644 --- a/src/components/common/EmptyPlaceholder.js +++ b/src/components/common/EmptyPlaceholder.tsx @@ -1,6 +1,12 @@ +import { ReactNode } from 'react'; import { Icon, Text, Flexbox } from 'react-basics'; import Logo from 'assets/logo.svg'; +export interface EmptyPlaceholderProps { + message: string; + children?: ReactNode; +} + export function EmptyPlaceholder({ message, children }) { return ( diff --git a/src/components/common/ErrorBoundary.js b/src/components/common/ErrorBoundary.tsx similarity index 78% rename from src/components/common/ErrorBoundary.js rename to src/components/common/ErrorBoundary.tsx index 32cedb39..4eb2700f 100644 --- a/src/components/common/ErrorBoundary.js +++ b/src/components/common/ErrorBoundary.tsx @@ -1,14 +1,19 @@ /* eslint-disable no-console */ +import { ErrorInfo, ReactNode } from 'react'; import { ErrorBoundary as Boundary } from 'react-error-boundary'; import { Button } from 'react-basics'; import useMessages from 'components/hooks/useMessages'; import styles from './ErrorBoundry.module.css'; -const logError = (error, info) => { +const logError = (error: Error, info: ErrorInfo) => { console.error(error, info.componentStack); }; -export function ErrorBoundary({ children }) { +export interface ErrorBoundaryProps { + children: ReactNode; +} + +export function ErrorBoundary({ children }: ErrorBoundaryProps) { const { formatMessage, messages } = useMessages(); const fallbackRender = ({ error, resetErrorBoundary }) => { diff --git a/src/components/common/ErrorMessage.js b/src/components/common/ErrorMessage.tsx similarity index 100% rename from src/components/common/ErrorMessage.js rename to src/components/common/ErrorMessage.tsx diff --git a/src/components/common/Favicon.js b/src/components/common/Favicon.tsx similarity index 93% rename from src/components/common/Favicon.js rename to src/components/common/Favicon.tsx index 55059cc0..2bf43c77 100644 --- a/src/components/common/Favicon.js +++ b/src/components/common/Favicon.tsx @@ -1,6 +1,6 @@ import styles from './Favicon.module.css'; -function getHostName(url) { +function getHostName(url: string) { const match = url.match(/^(?:https?:\/\/)?(?:[^@\n]+@)?(?:www\.)?([^:/\n?=]+)/im); return match && match.length > 1 ? match[1] : null; } diff --git a/src/components/common/FilterButtons.js b/src/components/common/FilterButtons.js deleted file mode 100644 index f5a54fb6..00000000 --- a/src/components/common/FilterButtons.js +++ /dev/null @@ -1,13 +0,0 @@ -import { ButtonGroup, Button, Flexbox } from 'react-basics'; - -export function FilterButtons({ items, selectedKey, onSelect }) { - return ( - - - {({ key, label }) => } - - - ); -} - -export default FilterButtons; diff --git a/src/components/common/FilterButtons.tsx b/src/components/common/FilterButtons.tsx new file mode 100644 index 00000000..e1860c78 --- /dev/null +++ b/src/components/common/FilterButtons.tsx @@ -0,0 +1,20 @@ +import { Key } from 'react'; +import { ButtonGroup, Button, Flexbox } from 'react-basics'; + +export interface FilterButtonsProps { + items: any[]; + selectedKey?: Key; + onSelect: () => void; +} + +export function FilterButtons({ items, selectedKey, onSelect }: FilterButtonsProps) { + return ( + + + {({ key, label }) => } + + + ); +} + +export default FilterButtons; diff --git a/src/components/common/FilterLink.js b/src/components/common/FilterLink.tsx similarity index 79% rename from src/components/common/FilterLink.js rename to src/components/common/FilterLink.tsx index 89648255..f91e1459 100644 --- a/src/components/common/FilterLink.js +++ b/src/components/common/FilterLink.tsx @@ -1,3 +1,4 @@ +import { ReactNode } from 'react'; import { Icon, Icons } from 'react-basics'; import classNames from 'classnames'; import Link from 'next/link'; @@ -6,7 +7,23 @@ import useNavigation from 'components/hooks/useNavigation'; import useMessages from 'components/hooks/useMessages'; import styles from './FilterLink.module.css'; -export function FilterLink({ id, value, label, externalUrl, children, className }) { +export interface FilterLinkProps { + id: string; + value: string; + label: string; + externalUrl: string; + className: string; + children: ReactNode; +} + +export function FilterLink({ + id, + value, + label, + externalUrl, + children, + className, +}: FilterLinkProps) { const { formatMessage, labels } = useMessages(); const { makeUrl, query } = useNavigation(); const active = query[id] !== undefined; diff --git a/src/components/common/LinkButton.js b/src/components/common/LinkButton.tsx similarity index 69% rename from src/components/common/LinkButton.js rename to src/components/common/LinkButton.tsx index a9a8562d..c9366e5c 100644 --- a/src/components/common/LinkButton.js +++ b/src/components/common/LinkButton.tsx @@ -2,8 +2,17 @@ import classNames from 'classnames'; import Link from 'next/link'; import { useLocale } from 'components/hooks'; import styles from './LinkButton.module.css'; +import { ReactNode } from 'react'; -export function LinkButton({ href, className, variant, scroll = true, children }) { +export interface LinkButtonProps { + href: string; + className: string; + variant?: string; + scroll?: boolean; + children?: ReactNode; +} + +export function LinkButton({ href, className, variant, scroll = true, children }: LinkButtonProps) { const { dir } = useLocale(); return ( diff --git a/yarn.lock b/yarn.lock index 6dd8b93b..da4ce1ca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2393,10 +2393,12 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190" integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw== -"@types/node@^18.11.9": - version "18.18.6" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.18.6.tgz#26da694f75cdb057750f49d099da5e3f3824cb3e" - integrity sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w== +"@types/node@^20.9.0": + version "20.9.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.9.0.tgz#bfcdc230583aeb891cf51e73cfdaacdd8deae298" + integrity sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw== + dependencies: + undici-types "~5.26.4" "@types/normalize-package-data@^2.4.0": version "2.4.3" @@ -2408,10 +2410,10 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.9.tgz#b6f785caa7ea1fe4414d9df42ee0ab67f23d8a6d" integrity sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g== -"@types/react-dom@^18.0.8": - version "18.2.14" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.14.tgz#c01ba40e5bb57fc1dc41569bb3ccdb19eab1c539" - integrity sha512-V835xgdSVmyQmI1KLV2BEIUgqEuinxp9O4G6g3FqO/SqLac049E53aysv0oEFD2kHfejeKU+ZqL2bcFWj9gLAQ== +"@types/react-dom@^18.2.15": + version "18.2.15" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.15.tgz#921af67f9ee023ac37ea84b1bc0cc40b898ea522" + integrity sha512-HWMdW+7r7MR5+PZqJF6YFNSCtjz1T0dsvo/f1BV6HkV+6erD/nA7wd9NM00KVG83zf2nJ7uATPO9ttdIPvi3gg== dependencies: "@types/react" "*" @@ -2425,7 +2427,7 @@ hoist-non-react-statics "^3.3.0" redux "^4.0.0" -"@types/react@*", "@types/react@16 || 17 || 18", "@types/react@^18.0.25": +"@types/react@*", "@types/react@16 || 17 || 18": version "18.2.30" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.30.tgz#b84f786864fc46f18545364a54d5e1316308e59b" integrity sha512-OfqdJnDsSo4UNw0bqAjFCuBpLYQM7wvZidz0hVxHRjrEkzRlvZL1pJVyOSY55HMiKvRNEo9DUBRuEl7FNlJ/Vg== @@ -2434,6 +2436,15 @@ "@types/scheduler" "*" csstype "^3.0.2" +"@types/react@^18.2.37": + version "18.2.37" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.37.tgz#0f03af69e463c0f19a356c2660dbca5d19c44cae" + integrity sha512-RGAYMi2bhRgEXT3f4B92WTohopH6bIXw05FuGlmJEnv/omEn190+QYEIYxIAuIBdKgboYYdVved2p1AxZVQnaw== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + "@types/resolve@1.20.2": version "1.20.2" resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.2.tgz#97d26e00cd4a0423b4af620abecf3e6f442b7975" @@ -7455,10 +7466,10 @@ rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-basics@^0.105.0: - version "0.105.0" - resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.105.0.tgz#94eda703b3c0728e817b6e9d086e5d1c6c68f25c" - integrity sha512-iKYtfB0A2vsmO+X4jaX64XdmHE836w8TG2jFQ0pi5Qp0ktL0lAL9/q0IrWUjNr86hi0apg46aeJWxY+qQO+T1g== +react-basics@^0.106.0: + version "0.106.0" + resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.106.0.tgz#28ba95a06e6d36adcdb303e1556e6c731b505991" + integrity sha512-CD1qxFu4wrBeNubNo/SkBfWH0BuTErBueNJCCk04IC3qM9poUr3evYfs2S4sfql7dlorcOJ2GflKC1NJ8qPvmw== dependencies: "@react-spring/web" "^9.7.3" classnames "^2.3.1" @@ -8893,6 +8904,11 @@ undici-types@~5.25.1: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.25.3.tgz#e044115914c85f0bcbb229f346ab739f064998c3" integrity sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA== +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + unenv@^1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/unenv/-/unenv-1.7.4.tgz#a0e5a78de2c7c3c4563c06ba9763c96c59db3333" From 8775d696b8d0bcece7c667cf892526da7a7449ad Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Mon, 13 Nov 2023 21:36:52 -0800 Subject: [PATCH 08/17] Typescript conversion. --- package.json | 2 +- ...HamburgerButton.js => HamburgerButton.tsx} | 2 +- .../{HoverTooltip.js => HoverTooltip.tsx} | 4 ++-- src/components/common/LinkButton.tsx | 2 +- .../common/{MobileMenu.js => MobileMenu.tsx} | 13 +++++++++--- src/components/common/{Pager.js => Pager.tsx} | 12 +++++++++-- src/components/declarations.d.ts | 1 + .../hooks/{useConfig.js => useConfig.ts} | 0 ...{useCountryNames.js => useCountryNames.ts} | 4 ++-- .../{useDateRange.js => useDateRange.ts} | 4 ++-- ...seDocumentClick.js => useDocumentClick.ts} | 2 +- src/components/hooks/useEscapeKey.js | 21 ------------------- src/components/hooks/useEscapeKey.ts | 21 +++++++++++++++++++ .../hooks/{useFilters.js => useFilters.ts} | 0 .../{useForceUpdate.js => useForceUpdate.ts} | 0 .../hooks/{useFormat.js => useFormat.ts} | 10 ++++----- ...seLanguageNames.js => useLanguageNames.ts} | 0 .../hooks/{useLocale.js => useLocale.ts} | 0 .../hooks/{useMessages.js => useMessages.ts} | 4 ++-- .../{useNavigation.js => useNavigation.ts} | 0 .../hooks/{useReport.js => useReport.ts} | 6 +++--- .../hooks/{useReports.js => useReports.ts} | 0 .../{useShareToken.js => useShareToken.ts} | 4 ++-- .../hooks/{useSticky.js => useSticky.ts} | 5 +++-- .../hooks/{useTheme.js => useTheme.ts} | 2 +- .../hooks/{useTimezone.js => useTimezone.ts} | 0 .../hooks/{useWebsite.js => useWebsite.ts} | 2 +- tsconfig.json | 2 +- yarn.lock | 8 +++---- 29 files changed, 74 insertions(+), 57 deletions(-) rename src/components/common/{HamburgerButton.js => HamburgerButton.tsx} (89%) rename src/components/common/{HoverTooltip.js => HoverTooltip.tsx} (82%) rename src/components/common/{MobileMenu.js => MobileMenu.tsx} (74%) rename src/components/common/{Pager.js => Pager.tsx} (86%) rename src/components/hooks/{useConfig.js => useConfig.ts} (100%) rename src/components/hooks/{useCountryNames.js => useCountryNames.ts} (87%) rename src/components/hooks/{useDateRange.js => useDateRange.ts} (91%) rename src/components/hooks/{useDocumentClick.js => useDocumentClick.ts} (77%) delete mode 100644 src/components/hooks/useEscapeKey.js create mode 100644 src/components/hooks/useEscapeKey.ts rename src/components/hooks/{useFilters.js => useFilters.ts} (100%) rename src/components/hooks/{useForceUpdate.js => useForceUpdate.ts} (100%) rename src/components/hooks/{useFormat.js => useFormat.ts} (81%) rename src/components/hooks/{useLanguageNames.js => useLanguageNames.ts} (100%) rename src/components/hooks/{useLocale.js => useLocale.ts} (100%) rename src/components/hooks/{useMessages.js => useMessages.ts} (81%) rename src/components/hooks/{useNavigation.js => useNavigation.ts} (100%) rename src/components/hooks/{useReport.js => useReport.ts} (94%) rename src/components/hooks/{useReports.js => useReports.ts} (100%) rename src/components/hooks/{useShareToken.js => useShareToken.ts} (77%) rename src/components/hooks/{useSticky.js => useSticky.ts} (76%) rename src/components/hooks/{useTheme.js => useTheme.ts} (97%) rename src/components/hooks/{useTimezone.js => useTimezone.ts} (100%) rename src/components/hooks/{useWebsite.js => useWebsite.ts} (81%) diff --git a/package.json b/package.json index 5d8fff5e..a483362e 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ "npm-run-all": "^4.1.5", "prisma": "5.4.2", "react": "^18.2.0", - "react-basics": "^0.106.0", + "react-basics": "^0.107.0", "react-beautiful-dnd": "^13.1.0", "react-dom": "^18.2.0", "react-error-boundary": "^4.0.4", diff --git a/src/components/common/HamburgerButton.js b/src/components/common/HamburgerButton.tsx similarity index 89% rename from src/components/common/HamburgerButton.js rename to src/components/common/HamburgerButton.tsx index 0eddad0f..380392c8 100644 --- a/src/components/common/HamburgerButton.js +++ b/src/components/common/HamburgerButton.tsx @@ -3,7 +3,7 @@ import { useState } from 'react'; import MobileMenu from './MobileMenu'; import Icons from 'components/icons'; -export function HamburgerButton({ menuItems }) { +export function HamburgerButton({ menuItems }: { menuItems: any[] }) { const [active, setActive] = useState(false); const handleClick = () => setActive(state => !state); diff --git a/src/components/common/HoverTooltip.js b/src/components/common/HoverTooltip.tsx similarity index 82% rename from src/components/common/HoverTooltip.js rename to src/components/common/HoverTooltip.tsx index 614841df..e5e31219 100644 --- a/src/components/common/HoverTooltip.js +++ b/src/components/common/HoverTooltip.tsx @@ -1,8 +1,8 @@ -import { useEffect, useState } from 'react'; +import { ReactNode, useEffect, useState } from 'react'; import { Tooltip } from 'react-basics'; import styles from './HoverTooltip.module.css'; -export function HoverTooltip({ children }) { +export function HoverTooltip({ children }: { children: ReactNode }) { const [position, setPosition] = useState({ x: -1000, y: -1000 }); useEffect(() => { diff --git a/src/components/common/LinkButton.tsx b/src/components/common/LinkButton.tsx index c9366e5c..83d95151 100644 --- a/src/components/common/LinkButton.tsx +++ b/src/components/common/LinkButton.tsx @@ -6,7 +6,7 @@ import { ReactNode } from 'react'; export interface LinkButtonProps { href: string; - className: string; + className?: string; variant?: string; scroll?: boolean; children?: ReactNode; diff --git a/src/components/common/MobileMenu.js b/src/components/common/MobileMenu.tsx similarity index 74% rename from src/components/common/MobileMenu.js rename to src/components/common/MobileMenu.tsx index 83a05dff..251085a4 100644 --- a/src/components/common/MobileMenu.js +++ b/src/components/common/MobileMenu.tsx @@ -4,12 +4,19 @@ import { usePathname } from 'next/navigation'; import Link from 'next/link'; import styles from './MobileMenu.module.css'; -export function MobileMenu({ items = [], onClose }) { +export function MobileMenu({ + items = [], + onClose, +}: { + items: any[]; + className?: string; + onClose: () => void; +}) { const pathname = usePathname(); - const Items = ({ items, className }) => ( + const Items = ({ items, className }: { items: any[]; className?: string }) => (
- {items.map(({ label, url, children }) => { + {items.map(({ label, url, children }: { label: string; url: string; children: any[] }) => { const selected = pathname.startsWith(url); return ( diff --git a/src/components/common/Pager.js b/src/components/common/Pager.tsx similarity index 86% rename from src/components/common/Pager.js rename to src/components/common/Pager.tsx index a21d35d9..2fe7c6db 100644 --- a/src/components/common/Pager.js +++ b/src/components/common/Pager.tsx @@ -3,7 +3,15 @@ import { Button, Icon, Icons } from 'react-basics'; import useMessages from 'components/hooks/useMessages'; import styles from './Pager.module.css'; -export function Pager({ page, pageSize, count, onPageChange, className }) { +export interface PagerProps { + page: number; + pageSize: number; + count: number; + onPageChange: (nextPage: number) => void; + className?: string; +} + +export function Pager({ page, pageSize, count, onPageChange, className }: PagerProps) { const { formatMessage, labels } = useMessages(); const maxPage = pageSize && count ? Math.ceil(count / pageSize) : 0; const lastPage = page === maxPage; @@ -13,7 +21,7 @@ export function Pager({ page, pageSize, count, onPageChange, className }) { return null; } - const handlePageChange = value => { + const handlePageChange = (value: number) => { const nextPage = page + value; if (nextPage > 0 && nextPage <= maxPage) { onPageChange(nextPage); diff --git a/src/components/declarations.d.ts b/src/components/declarations.d.ts index 31e44ff3..81533301 100644 --- a/src/components/declarations.d.ts +++ b/src/components/declarations.d.ts @@ -1,2 +1,3 @@ declare module '*.css'; declare module '*.svg'; +declare module '*.json'; diff --git a/src/components/hooks/useConfig.js b/src/components/hooks/useConfig.ts similarity index 100% rename from src/components/hooks/useConfig.js rename to src/components/hooks/useConfig.ts diff --git a/src/components/hooks/useCountryNames.js b/src/components/hooks/useCountryNames.ts similarity index 87% rename from src/components/hooks/useCountryNames.js rename to src/components/hooks/useCountryNames.ts index 40611865..22f20666 100644 --- a/src/components/hooks/useCountryNames.js +++ b/src/components/hooks/useCountryNames.ts @@ -6,10 +6,10 @@ const countryNames = { 'en-US': enUS, }; -export function useCountryNames(locale) { +export function useCountryNames(locale: string) { const [list, setList] = useState(countryNames[locale] || enUS); - async function loadData(locale) { + async function loadData(locale: string) { const { data } = await httpGet(`${process.env.basePath}/intl/country/${locale}.json`); if (data) { diff --git a/src/components/hooks/useDateRange.js b/src/components/hooks/useDateRange.ts similarity index 91% rename from src/components/hooks/useDateRange.js rename to src/components/hooks/useDateRange.ts index 1e1b0616..6e70a368 100644 --- a/src/components/hooks/useDateRange.js +++ b/src/components/hooks/useDateRange.ts @@ -6,7 +6,7 @@ import websiteStore, { setWebsiteDateRange } from 'store/websites'; import appStore, { setDateRange } from 'store/app'; import useApi from './useApi'; -export function useDateRange(websiteId) { +export function useDateRange(websiteId: string) { const { get } = useApi(); const { locale } = useLocale(); const websiteConfig = websiteStore(state => state[websiteId]?.dateRange); @@ -20,7 +20,7 @@ export function useDateRange(websiteId) { if (typeof value === 'string') { if (value === 'all') { - const result = await get(`/websites/${websiteId}/daterange`); + const result: any = await get(`/websites/${websiteId}/daterange`); const { mindate, maxdate } = result; const startDate = new Date(mindate); diff --git a/src/components/hooks/useDocumentClick.js b/src/components/hooks/useDocumentClick.ts similarity index 77% rename from src/components/hooks/useDocumentClick.js rename to src/components/hooks/useDocumentClick.ts index be3d09be..eefd9366 100644 --- a/src/components/hooks/useDocumentClick.js +++ b/src/components/hooks/useDocumentClick.ts @@ -1,6 +1,6 @@ import { useEffect } from 'react'; -export function useDocumentClick(handler) { +export function useDocumentClick(handler: (event: MouseEvent) => any) { useEffect(() => { document.addEventListener('click', handler); diff --git a/src/components/hooks/useEscapeKey.js b/src/components/hooks/useEscapeKey.js deleted file mode 100644 index 1a17f18f..00000000 --- a/src/components/hooks/useEscapeKey.js +++ /dev/null @@ -1,21 +0,0 @@ -import { useEffect, useCallback } from 'react'; - -export function useEscapeKey(handler) { - const escFunction = useCallback(event => { - if (event.keyCode === 27) { - handler(event); - } - }, []); - - useEffect(() => { - document.addEventListener('keydown', escFunction, false); - - return () => { - document.removeEventListener('keydown', escFunction, false); - }; - }, [escFunction]); - - return null; -} - -export default useEscapeKey; diff --git a/src/components/hooks/useEscapeKey.ts b/src/components/hooks/useEscapeKey.ts new file mode 100644 index 00000000..5c3350e7 --- /dev/null +++ b/src/components/hooks/useEscapeKey.ts @@ -0,0 +1,21 @@ +import { useEffect, useCallback, KeyboardEvent } from 'react'; + +export function useEscapeKey(handler: (event: KeyboardEvent) => void) { + const escFunction = useCallback((event: KeyboardEvent) => { + if (event.key === 'Escape') { + handler(event); + } + }, []); + + useEffect(() => { + document.addEventListener('keydown', escFunction as any, false); + + return () => { + document.removeEventListener('keydown', escFunction as any, false); + }; + }, [escFunction]); + + return null; +} + +export default useEscapeKey; diff --git a/src/components/hooks/useFilters.js b/src/components/hooks/useFilters.ts similarity index 100% rename from src/components/hooks/useFilters.js rename to src/components/hooks/useFilters.ts diff --git a/src/components/hooks/useForceUpdate.js b/src/components/hooks/useForceUpdate.ts similarity index 100% rename from src/components/hooks/useForceUpdate.js rename to src/components/hooks/useForceUpdate.ts diff --git a/src/components/hooks/useFormat.js b/src/components/hooks/useFormat.ts similarity index 81% rename from src/components/hooks/useFormat.js rename to src/components/hooks/useFormat.ts index 0e609c48..c1160162 100644 --- a/src/components/hooks/useFormat.js +++ b/src/components/hooks/useFormat.ts @@ -9,23 +9,23 @@ export function useFormat() { const { locale } = useLocale(); const countryNames = useCountryNames(locale); - const formatBrowser = value => { + const formatBrowser = (value: string) => { return BROWSERS[value] || value; }; - const formatCountry = value => { + const formatCountry = (value: string) => { return countryNames[value] || value; }; - const formatRegion = value => { + const formatRegion = (value: string) => { return regions[value] ? regions[value] : value; }; - const formatDevice = value => { + const formatDevice = (value: string) => { return formatMessage(labels[value] || labels.unknown); }; - const formatValue = (value, type) => { + const formatValue = (value: string, type: string) => { switch (type) { case 'browser': return formatBrowser(value); diff --git a/src/components/hooks/useLanguageNames.js b/src/components/hooks/useLanguageNames.ts similarity index 100% rename from src/components/hooks/useLanguageNames.js rename to src/components/hooks/useLanguageNames.ts diff --git a/src/components/hooks/useLocale.js b/src/components/hooks/useLocale.ts similarity index 100% rename from src/components/hooks/useLocale.js rename to src/components/hooks/useLocale.ts diff --git a/src/components/hooks/useMessages.js b/src/components/hooks/useMessages.ts similarity index 81% rename from src/components/hooks/useMessages.js rename to src/components/hooks/useMessages.ts index e3a6c20b..0801c7d9 100644 --- a/src/components/hooks/useMessages.js +++ b/src/components/hooks/useMessages.ts @@ -4,13 +4,13 @@ import { messages, labels } from 'components/messages'; export function useMessages() { const intl = useIntl(); - const getMessage = id => { + const getMessage = (id: string) => { const message = Object.values(messages).find(value => value.id === id); return message ? formatMessage(message) : id; }; - const formatMessage = (descriptor, values, opts) => { + const formatMessage = (descriptor: any, values?: any, opts?: any) => { return descriptor ? intl.formatMessage(descriptor, values, opts) : null; }; diff --git a/src/components/hooks/useNavigation.js b/src/components/hooks/useNavigation.ts similarity index 100% rename from src/components/hooks/useNavigation.js rename to src/components/hooks/useNavigation.ts diff --git a/src/components/hooks/useReport.js b/src/components/hooks/useReport.ts similarity index 94% rename from src/components/hooks/useReport.js rename to src/components/hooks/useReport.ts index 7c698b4e..1686e222 100644 --- a/src/components/hooks/useReport.js +++ b/src/components/hooks/useReport.ts @@ -18,7 +18,7 @@ export function useReport(reportId, defaultParameters) { }; const loadReport = async id => { - const data = await get(`/reports/${id}`); + const data: any = await get(`/reports/${id}`); const { dateRange } = data?.parameters || {}; const { startDate, endDate } = dateRange || {}; @@ -40,7 +40,7 @@ export function useReport(reportId, defaultParameters) { const data = await post(`/reports/${type}`, { ...parameters, timezone }); setReport( - produce(state => { + produce((state: any) => { state.parameters = parameters; state.data = data; @@ -56,7 +56,7 @@ export function useReport(reportId, defaultParameters) { const updateReport = useCallback( async data => { setReport( - produce(state => { + produce((state: any) => { const { parameters, ...rest } = data; if (parameters) { diff --git a/src/components/hooks/useReports.js b/src/components/hooks/useReports.ts similarity index 100% rename from src/components/hooks/useReports.js rename to src/components/hooks/useReports.ts diff --git a/src/components/hooks/useShareToken.js b/src/components/hooks/useShareToken.ts similarity index 77% rename from src/components/hooks/useShareToken.js rename to src/components/hooks/useShareToken.ts index 5062c73e..088f643e 100644 --- a/src/components/hooks/useShareToken.js +++ b/src/components/hooks/useShareToken.ts @@ -1,9 +1,9 @@ import useStore, { setShareToken } from 'store/app'; import useApi from './useApi'; -const selector = state => state.shareToken; +const selector = (state: { shareToken: string }) => state.shareToken; -export function useShareToken(shareId) { +export function useShareToken(shareId: string) { const shareToken = useStore(selector); const { get, useQuery } = useApi(); const { isLoading, error } = useQuery(['share', shareId], async () => { diff --git a/src/components/hooks/useSticky.js b/src/components/hooks/useSticky.ts similarity index 76% rename from src/components/hooks/useSticky.js rename to src/components/hooks/useSticky.ts index be33f6ed..459c489a 100644 --- a/src/components/hooks/useSticky.js +++ b/src/components/hooks/useSticky.ts @@ -5,8 +5,9 @@ export function useSticky({ enabled = true, threshold = 1 }) { const ref = useRef(null); useEffect(() => { - let observer; - const handler = ([entry]) => setIsSticky(entry.intersectionRatio < threshold); + let observer: IntersectionObserver | undefined; + const handler: IntersectionObserverCallback = ([entry]) => + setIsSticky(entry.intersectionRatio < threshold); if (enabled && ref.current) { observer = new IntersectionObserver(handler, { threshold: [threshold] }); diff --git a/src/components/hooks/useTheme.js b/src/components/hooks/useTheme.ts similarity index 97% rename from src/components/hooks/useTheme.js rename to src/components/hooks/useTheme.ts index 7e40f601..099bf962 100644 --- a/src/components/hooks/useTheme.js +++ b/src/components/hooks/useTheme.ts @@ -4,7 +4,7 @@ import { getItem, setItem } from 'next-basics'; import { THEME_COLORS, THEME_CONFIG } from 'lib/constants'; import { colord } from 'colord'; -const selector = state => state.theme; +const selector = (state: { theme: string }) => state.theme; export function useTheme() { const defaultTheme = diff --git a/src/components/hooks/useTimezone.js b/src/components/hooks/useTimezone.ts similarity index 100% rename from src/components/hooks/useTimezone.js rename to src/components/hooks/useTimezone.ts diff --git a/src/components/hooks/useWebsite.js b/src/components/hooks/useWebsite.ts similarity index 81% rename from src/components/hooks/useWebsite.js rename to src/components/hooks/useWebsite.ts index 5315f0dc..7b68335a 100644 --- a/src/components/hooks/useWebsite.js +++ b/src/components/hooks/useWebsite.ts @@ -1,6 +1,6 @@ import useApi from './useApi'; -export function useWebsite(websiteId) { +export function useWebsite(websiteId: string) { const { get, useQuery } = useApi(); return useQuery(['websites', websiteId], () => get(`/websites/${websiteId}`), { enabled: !!websiteId, diff --git a/tsconfig.json b/tsconfig.json index 9b8b6033..1807e947 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es5", + "target": "es2021", "outDir": "./build", "module": "esnext", "moduleResolution": "node", diff --git a/yarn.lock b/yarn.lock index da4ce1ca..f2ed9264 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7466,10 +7466,10 @@ rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-basics@^0.106.0: - version "0.106.0" - resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.106.0.tgz#28ba95a06e6d36adcdb303e1556e6c731b505991" - integrity sha512-CD1qxFu4wrBeNubNo/SkBfWH0BuTErBueNJCCk04IC3qM9poUr3evYfs2S4sfql7dlorcOJ2GflKC1NJ8qPvmw== +react-basics@^0.107.0: + version "0.107.0" + resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.107.0.tgz#e5615792cbb3e4707ba5c8f438b29d6a88cf38b3" + integrity sha512-jYnP1z2LTotxXWYwxOBvF26vXxSUBJB0x62YPKkEr1vmJGeg8iOLr8JGF8KE3R6E+NTqzRt6Bmdtt93mjaog4A== dependencies: "@react-spring/web" "^9.7.3" classnames "^2.3.1" From a78d11e352ffae77a57aad9845ccc2ad6923214c Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Mon, 13 Nov 2023 21:58:23 -0800 Subject: [PATCH 09/17] Fixed types. --- package.json | 2 +- src/components/common/MobileMenu.tsx | 4 +- src/components/hooks/useMessages.ts | 16 +++- src/components/hooks/useNavigation.ts | 2 +- yarn.lock | 114 +++++++++++++------------- 5 files changed, 74 insertions(+), 64 deletions(-) diff --git a/package.json b/package.json index a483362e..fe3fa664 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ "react-beautiful-dnd": "^13.1.0", "react-dom": "^18.2.0", "react-error-boundary": "^4.0.4", - "react-intl": "^6.4.7", + "react-intl": "^6.5.5", "react-simple-maps": "^2.3.0", "react-use-measure": "^2.0.4", "react-window": "^1.8.6", diff --git a/src/components/common/MobileMenu.tsx b/src/components/common/MobileMenu.tsx index 251085a4..e14f0b83 100644 --- a/src/components/common/MobileMenu.tsx +++ b/src/components/common/MobileMenu.tsx @@ -11,10 +11,10 @@ export function MobileMenu({ items: any[]; className?: string; onClose: () => void; -}) { +}): any { const pathname = usePathname(); - const Items = ({ items, className }: { items: any[]; className?: string }) => ( + const Items = ({ items, className }: { items: any[]; className?: string }): any => (
{items.map(({ label, url, children }: { label: string; url: string; children: any[] }) => { const selected = pathname.startsWith(url); diff --git a/src/components/hooks/useMessages.ts b/src/components/hooks/useMessages.ts index 0801c7d9..594a3c61 100644 --- a/src/components/hooks/useMessages.ts +++ b/src/components/hooks/useMessages.ts @@ -1,7 +1,8 @@ -import { useIntl, FormattedMessage } from 'react-intl'; +import { useIntl, FormattedMessage, MessageDescriptor, PrimitiveType } from 'react-intl'; import { messages, labels } from 'components/messages'; +import { FormatXMLElementFn, Options } from 'intl-messageformat'; -export function useMessages() { +export function useMessages(): any { const intl = useIntl(); const getMessage = (id: string) => { @@ -10,7 +11,16 @@ export function useMessages() { return message ? formatMessage(message) : id; }; - const formatMessage = (descriptor: any, values?: any, opts?: any) => { + const formatMessage = ( + descriptor: + | MessageDescriptor + | { + id: string; + defaultMessage: string; + }, + values?: Record>, + opts?: Options, + ) => { return descriptor ? intl.formatMessage(descriptor, values, opts) : null; }; diff --git a/src/components/hooks/useNavigation.ts b/src/components/hooks/useNavigation.ts index 658e81ed..9f01cd80 100644 --- a/src/components/hooks/useNavigation.ts +++ b/src/components/hooks/useNavigation.ts @@ -17,7 +17,7 @@ export function useNavigation() { return obj; }, [params]); - function makeUrl(params, reset) { + function makeUrl(params: any, reset?: boolean) { return reset ? pathname : buildUrl(pathname, { ...query, ...params }); } diff --git a/yarn.lock b/yarn.lock index f2ed9264..d7596fa4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1353,12 +1353,12 @@ "@formatjs/intl-localematcher" "0.2.25" tslib "^2.1.0" -"@formatjs/ecma402-abstract@1.17.2": - version "1.17.2" - resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.17.2.tgz#d197c6e26b9fd96ff7ba3b3a0cc2f25f1f2dcac3" - integrity sha512-k2mTh0m+IV1HRdU0xXM617tSQTi53tVR2muvYOsBeYcUgEAyxV1FOC7Qj279th3fBVQ+Dj6muvNJZcHSPNdbKg== +"@formatjs/ecma402-abstract@1.18.0": + version "1.18.0" + resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.18.0.tgz#e2120e7101020140661b58430a7ff4262705a2f2" + integrity sha512-PEVLoa3zBevWSCZzPIM/lvPCi8P5l4G+NXQMc/CjEiaCWgyHieUoo0nM7Bs0n/NbuQ6JpXEolivQ9pKSBHaDlA== dependencies: - "@formatjs/intl-localematcher" "0.4.2" + "@formatjs/intl-localematcher" "0.5.2" tslib "^2.4.0" "@formatjs/ecma402-abstract@1.4.0": @@ -1391,13 +1391,13 @@ "@formatjs/icu-skeleton-parser" "1.3.6" tslib "^2.1.0" -"@formatjs/icu-messageformat-parser@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.0.tgz#9b13f2710a3b4efddfeb544480f684f27a53483b" - integrity sha512-7uqC4C2RqOaBQtcjqXsSpGRYVn+ckjhNga5T/otFh6MgxRrCJQqvjfbrGLpX1Lcbxdm5WH3Z2WZqt1+Tm/cn/Q== +"@formatjs/icu-messageformat-parser@2.7.3": + version "2.7.3" + resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.3.tgz#c8c95e7c9f8141bdb93bea0e92e4fcace19d3c9f" + integrity sha512-X/jy10V9S/vW+qlplqhMUxR8wErQ0mmIYSq4mrjpjDl9mbuGcCILcI1SUYkL5nlM4PJqpc0KOS0bFkkJNPxYRw== dependencies: - "@formatjs/ecma402-abstract" "1.17.2" - "@formatjs/icu-skeleton-parser" "1.6.2" + "@formatjs/ecma402-abstract" "1.18.0" + "@formatjs/icu-skeleton-parser" "1.7.0" tslib "^2.4.0" "@formatjs/icu-skeleton-parser@1.3.6": @@ -1408,30 +1408,30 @@ "@formatjs/ecma402-abstract" "1.11.4" tslib "^2.1.0" -"@formatjs/icu-skeleton-parser@1.6.2": - version "1.6.2" - resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.6.2.tgz#00303034dc08583973c8aa67b96534c49c0bad8d" - integrity sha512-VtB9Slo4ZL6QgtDFJ8Injvscf0xiDd4bIV93SOJTBjUF4xe2nAWOoSjLEtqIG+hlIs1sNrVKAaFo3nuTI4r5ZA== +"@formatjs/icu-skeleton-parser@1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.7.0.tgz#796938d6d0ba8fc75bb9edee038d1350bfee32cb" + integrity sha512-Cfdo/fgbZzpN/jlN/ptQVe0lRHora+8ezrEeg2RfrNjyp+YStwBy7cqDY8k5/z2LzXg6O0AdzAV91XS0zIWv+A== dependencies: - "@formatjs/ecma402-abstract" "1.17.2" + "@formatjs/ecma402-abstract" "1.18.0" tslib "^2.4.0" -"@formatjs/intl-displaynames@6.6.0": - version "6.6.0" - resolved "https://registry.yarnpkg.com/@formatjs/intl-displaynames/-/intl-displaynames-6.6.0.tgz#6f590784b1bcdc1b96d4dba158bdce350f876804" - integrity sha512-bskUou9boZOzTqI8JdNCNkDavXf8uWWz/6NG1og/XJKpn4zsfiLdQ9EYKhVe/CfbCjlSyieJYn7/NztdoprHjw== +"@formatjs/intl-displaynames@6.6.4": + version "6.6.4" + resolved "https://registry.yarnpkg.com/@formatjs/intl-displaynames/-/intl-displaynames-6.6.4.tgz#dd9ca9bb2d1f4b140cc8814667bc830802621674" + integrity sha512-ET8KQ+L9Q0K8x1SnJQa4DNssUcbATlMopWqYvGGR8yAvw5qwAQc1fv+DshCoZNIE9pbcue0IGC4kWNAkWqlFag== dependencies: - "@formatjs/ecma402-abstract" "1.17.2" - "@formatjs/intl-localematcher" "0.4.2" + "@formatjs/ecma402-abstract" "1.18.0" + "@formatjs/intl-localematcher" "0.5.2" tslib "^2.4.0" -"@formatjs/intl-listformat@7.5.0": - version "7.5.0" - resolved "https://registry.yarnpkg.com/@formatjs/intl-listformat/-/intl-listformat-7.5.0.tgz#dbccf2e0f07792aa1c273702796bdad061dc27ae" - integrity sha512-n9FsXGl1T2ZbX6wSyrzCDJHrbJR0YJ9ZNsAqUvHXfbY3nsOmGnSTf5+bkuIp1Xiywu7m1X1Pfm/Ngp/yK1H84A== +"@formatjs/intl-listformat@7.5.3": + version "7.5.3" + resolved "https://registry.yarnpkg.com/@formatjs/intl-listformat/-/intl-listformat-7.5.3.tgz#c6f028471839cd1014760498f783fdfe011422d5" + integrity sha512-l7EOr0Yh1m8KagytukB90yw81uyzrM7amKFrgxXqphz4KeSIL0KPa68lPsdtZ+JmQB73GaDQRwLOwUKFZ1VZPQ== dependencies: - "@formatjs/ecma402-abstract" "1.17.2" - "@formatjs/intl-localematcher" "0.4.2" + "@formatjs/ecma402-abstract" "1.18.0" + "@formatjs/intl-localematcher" "0.5.2" tslib "^2.4.0" "@formatjs/intl-localematcher@0.2.25": @@ -1441,10 +1441,10 @@ dependencies: tslib "^2.1.0" -"@formatjs/intl-localematcher@0.4.2": - version "0.4.2" - resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.4.2.tgz#7e6e596dbaf2f0c5a7c22da5a01d5c55f4c37e9a" - integrity sha512-BGdtJFmaNJy5An/Zan4OId/yR9Ih1OojFjcduX/xOvq798OgWSyDtd6Qd5jqJXwJs1ipe4Fxu9+cshic5Ox2tA== +"@formatjs/intl-localematcher@0.5.2": + version "0.5.2" + resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.5.2.tgz#5fcf029fd218905575e5080fa33facdcb623d532" + integrity sha512-txaaE2fiBMagLrR4jYhxzFO6wEdEG4TPMqrzBAcbr4HFUYzH/YC+lg6OIzKCHm8WgDdyQevxbAAV1OgcXctuGw== dependencies: tslib "^2.4.0" @@ -1456,17 +1456,17 @@ "@formatjs/ecma402-abstract" "1.4.0" tslib "^2.0.1" -"@formatjs/intl@2.9.4": - version "2.9.4" - resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-2.9.4.tgz#6f97a8e6e282086c39c8e502face5b2839f47b6f" - integrity sha512-hY0UlbDz8jY12RkQtkzxe3OfUmsIcUcsvVYyr1TFue6oTrUHqpkmYLdQ626V3BCSLc90EZDXdvmsPfMd3hTcYQ== +"@formatjs/intl@2.9.9": + version "2.9.9" + resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-2.9.9.tgz#866629b565e20dd7490f9e77ee41a00748913e8f" + integrity sha512-JI3CNgL2Zdg5lv9ncT2sYKqbAj2RGrCbdzaCckIxMPxn4QuHuOVvYUGmBAXVusBmfG/0sxLmMrnwnBioz+QKdA== dependencies: - "@formatjs/ecma402-abstract" "1.17.2" + "@formatjs/ecma402-abstract" "1.18.0" "@formatjs/fast-memoize" "2.2.0" - "@formatjs/icu-messageformat-parser" "2.7.0" - "@formatjs/intl-displaynames" "6.6.0" - "@formatjs/intl-listformat" "7.5.0" - intl-messageformat "10.5.4" + "@formatjs/icu-messageformat-parser" "2.7.3" + "@formatjs/intl-displaynames" "6.6.4" + "@formatjs/intl-listformat" "7.5.3" + intl-messageformat "10.5.8" tslib "^2.4.0" "@formatjs/ts-transformer@3.9.4": @@ -5169,14 +5169,14 @@ intl-messageformat-parser@^5.3.7: dependencies: "@formatjs/intl-numberformat" "^5.5.2" -intl-messageformat@10.5.4: - version "10.5.4" - resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-10.5.4.tgz#7b212b083f1b354d7e282518e78057e025134af9" - integrity sha512-z+hrFdiJ/heRYlzegrdFYqU1m/KOMOVMqNilIArj+PbsuU8TNE7v4TWdQgSoxlxbT4AcZH3Op3/Fu15QTp+W1w== +intl-messageformat@10.5.8: + version "10.5.8" + resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-10.5.8.tgz#7184da425f360a53a5483a6194e16d666b011fc0" + integrity sha512-NRf0jpBWV0vd671G5b06wNofAN8tp7WWDogMZyaU8GUAsmbouyvgwmFJI7zLjfAMpm3zK+vSwRP3jzaoIcMbaA== dependencies: - "@formatjs/ecma402-abstract" "1.17.2" + "@formatjs/ecma402-abstract" "1.18.0" "@formatjs/fast-memoize" "2.2.0" - "@formatjs/icu-messageformat-parser" "2.7.0" + "@formatjs/icu-messageformat-parser" "2.7.3" tslib "^2.4.0" ioredis@^5.3.2: @@ -7510,20 +7510,20 @@ react-hook-form@^7.34.2: resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.47.0.tgz#a42f07266bd297ddf1f914f08f4b5f9783262f31" integrity sha512-F/TroLjTICipmHeFlMrLtNLceO2xr1jU3CyiNla5zdwsGUGu2UOxxR4UyJgLlhMwLW/Wzp4cpJ7CPfgJIeKdSg== -react-intl@^6.4.7: - version "6.5.0" - resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-6.5.0.tgz#0ff04170f91e1bcbcd3301dfb2ae39a258827ec7" - integrity sha512-ZnBYFlFUU1ivhvWBA87XJLAr9nR8yeC1/83e6AL7yiHbWH7xQE7tyMyIyw6or78EvU9Hx8Sh8LUDC4bGrNxXOA== +react-intl@^6.5.5: + version "6.5.5" + resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-6.5.5.tgz#d2de7bfd79718a7e3d8031e2599e94e0c8638377" + integrity sha512-cI5UKvBh4tc1zxLIziHBYGMX3dhYWDEFlvUDVN6NfT2i96zTXz/zH2AmM8+2waqgOhwkFUzd+7kK1G9q7fiC2g== dependencies: - "@formatjs/ecma402-abstract" "1.17.2" - "@formatjs/icu-messageformat-parser" "2.7.0" - "@formatjs/intl" "2.9.4" - "@formatjs/intl-displaynames" "6.6.0" - "@formatjs/intl-listformat" "7.5.0" + "@formatjs/ecma402-abstract" "1.18.0" + "@formatjs/icu-messageformat-parser" "2.7.3" + "@formatjs/intl" "2.9.9" + "@formatjs/intl-displaynames" "6.6.4" + "@formatjs/intl-listformat" "7.5.3" "@types/hoist-non-react-statics" "^3.3.1" "@types/react" "16 || 17 || 18" hoist-non-react-statics "^3.3.2" - intl-messageformat "10.5.4" + intl-messageformat "10.5.8" tslib "^2.4.0" react-is@^16.13.1, react-is@^16.7.0: From 4dedc57d0a9b6ff1847ea0951d376698e637270a Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Wed, 22 Nov 2023 18:03:48 -0800 Subject: [PATCH 10/17] Updated clients. --- .eslintrc.json | 16 ++++++++-------- package.json | 4 ++-- .../settings/teams/[id]/TeamWebsiteAddForm.js | 6 ++++-- src/app/(main)/settings/websites/Websites.tsx | 15 +++++++++++++++ .../settings/websites/WebsitesDataTable.tsx | 12 ++++++------ src/app/(main)/settings/websites/page.tsx | 10 ++-------- src/app/(main)/websites/WebsitesBrowse.js | 12 +++++++++--- src/components/hooks/useRequireLogin.ts | 6 +++--- src/declaration.d.ts | 1 + src/lib/auth.ts | 9 +++++---- src/lib/client.ts | 2 +- src/lib/constants.ts | 2 +- src/pages/api/auth/verify.ts | 2 +- yarn.lock | 16 ++++++++-------- 14 files changed, 66 insertions(+), 47 deletions(-) create mode 100644 src/app/(main)/settings/websites/Websites.tsx create mode 100644 src/declaration.d.ts diff --git a/.eslintrc.json b/.eslintrc.json index a77ed5bd..9d747b87 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -4,14 +4,6 @@ "es2020": true, "node": true }, - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaFeatures": { - "jsx": true - }, - "ecmaVersion": 11, - "sourceType": "module" - }, "extends": [ "eslint:recommended", "plugin:prettier/recommended", @@ -19,6 +11,14 @@ "plugin:@typescript-eslint/recommended", "next" ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaFeatures": { + "jsx": true + }, + "ecmaVersion": 11, + "sourceType": "module" + }, "plugins": ["@typescript-eslint", "prettier"], "settings": { "import/resolver": { diff --git a/package.json b/package.json index fe3fa664..edc6b1e0 100644 --- a/package.json +++ b/package.json @@ -66,8 +66,8 @@ "@prisma/client": "5.4.2", "@react-spring/web": "^9.7.3", "@tanstack/react-query": "^4.33.0", - "@umami/prisma-client": "^0.3.0", - "@umami/redis-client": "^0.16.0", + "@umami/prisma-client": "^0.5.0", + "@umami/redis-client": "^0.17.0", "chalk": "^4.1.1", "chart.js": "^4.2.1", "chartjs-adapter-date-fns": "^3.0.0", diff --git a/src/app/(main)/settings/teams/[id]/TeamWebsiteAddForm.js b/src/app/(main)/settings/teams/[id]/TeamWebsiteAddForm.js index 9c2ae7bd..c83ec3d0 100644 --- a/src/app/(main)/settings/teams/[id]/TeamWebsiteAddForm.js +++ b/src/app/(main)/settings/teams/[id]/TeamWebsiteAddForm.js @@ -2,11 +2,13 @@ import useApi from 'components/hooks/useApi'; import { useState } from 'react'; import { Button, Form, FormButtons, GridColumn, Loading, SubmitButton, Toggle } from 'react-basics'; import useMessages from 'components/hooks/useMessages'; -import WebsitesDataTable from '../../websites/WebsitesDataTable'; +import WebsitesDataTable from 'app/(main)/settings/websites/WebsitesDataTable'; import Empty from 'components/common/Empty'; import { setValue } from 'store/cache'; +import { useUser } from 'components/hooks'; export function TeamWebsiteAddForm({ teamId, onSave, onClose }) { + const { user } = useUser(); const { formatMessage, labels } = useMessages(); const { get, post, useQuery, useMutation } = useApi(); const { mutate, error } = useMutation(data => post(`/teams/${teamId}/websites`, data)); @@ -37,7 +39,7 @@ export function TeamWebsiteAddForm({ teamId, onSave, onClose }) { {!isLoading && !hasData && } {hasData && (
- + {row => ( + + + + ); +} diff --git a/src/app/(main)/settings/websites/WebsitesDataTable.tsx b/src/app/(main)/settings/websites/WebsitesDataTable.tsx index 441ae56d..fc6dd0c0 100644 --- a/src/app/(main)/settings/websites/WebsitesDataTable.tsx +++ b/src/app/(main)/settings/websites/WebsitesDataTable.tsx @@ -1,13 +1,13 @@ 'use client'; import { ReactNode } from 'react'; import WebsitesTable from 'app/(main)/settings/websites/WebsitesTable'; -import useUser from 'components/hooks/useUser'; import useApi from 'components/hooks/useApi'; import DataTable from 'components/common/DataTable'; import useFilterQuery from 'components/hooks/useFilterQuery'; import useCache from 'store/cache'; export interface WebsitesDataTableProps { + userId: string; allowEdit?: boolean; allowView?: boolean; showActions?: boolean; @@ -17,25 +17,25 @@ export interface WebsitesDataTableProps { children?: ReactNode; } -function useWebsites({ includeTeams, onlyTeams }) { - const { user } = useUser(); +function useWebsites(userId: string, { includeTeams, onlyTeams }) { const { get } = useApi(); const modified = useCache((state: any) => state?.websites); return useFilterQuery( ['websites', { includeTeams, onlyTeams, modified }], (params: any) => { - return get(`/users/${user?.id}/websites`, { + return get(`/users/${userId}/websites`, { includeTeams, onlyTeams, ...params, }); }, - { enabled: !!user }, + { enabled: !!userId }, ); } export function WebsitesDataTable({ + userId, allowEdit = true, allowView = true, showActions = true, @@ -44,7 +44,7 @@ export function WebsitesDataTable({ onlyTeams, children, }: WebsitesDataTableProps) { - const queryResult = useWebsites({ includeTeams, onlyTeams }); + const queryResult = useWebsites(userId, { includeTeams, onlyTeams }); return ( diff --git a/src/app/(main)/settings/websites/page.tsx b/src/app/(main)/settings/websites/page.tsx index 2c83dce0..d6d11898 100644 --- a/src/app/(main)/settings/websites/page.tsx +++ b/src/app/(main)/settings/websites/page.tsx @@ -1,14 +1,8 @@ -import WebsitesDataTable from './WebsitesDataTable'; -import WebsitesHeader from './WebsitesHeader'; import { Metadata } from 'next'; +import Websites from './Websites'; export default function () { - return ( - <> - - - - ); + return ; } export const metadata: Metadata = { diff --git a/src/app/(main)/websites/WebsitesBrowse.js b/src/app/(main)/websites/WebsitesBrowse.js index f1bab7bf..3e8df2b2 100644 --- a/src/app/(main)/websites/WebsitesBrowse.js +++ b/src/app/(main)/websites/WebsitesBrowse.js @@ -1,6 +1,6 @@ 'use client'; import WebsitesDataTable from '../settings/websites/WebsitesDataTable'; -import { useMessages } from 'components/hooks'; +import { useMessages, useUser } from 'components/hooks'; import { useState } from 'react'; import { Item, Tabs } from 'react-basics'; @@ -10,6 +10,7 @@ const TABS = { }; export function WebsitesBrowse() { + const { user } = useUser(); const { formatMessage, labels } = useMessages(); const [tab, setTab] = useState(TABS.myWebsites); const allowEdit = !process.env.cloudMode; @@ -20,9 +21,14 @@ export function WebsitesBrowse() { {formatMessage(labels.myWebsites)} {formatMessage(labels.teamWebsites)} - {tab === TABS.myWebsites && } + {tab === TABS.myWebsites && } {tab === TABS.teamWebsites && ( - + )} ); diff --git a/src/components/hooks/useRequireLogin.ts b/src/components/hooks/useRequireLogin.ts index 76460a55..68de411b 100644 --- a/src/components/hooks/useRequireLogin.ts +++ b/src/components/hooks/useRequireLogin.ts @@ -2,7 +2,7 @@ import { useEffect } from 'react'; import useApi from 'components/hooks/useApi'; import useUser from 'components/hooks/useUser'; -export function useRequireLogin(handler?: (data?: object) => void) { +export function useRequireLogin() { const { get } = useApi(); const { user, setUser } = useUser(); @@ -11,9 +11,9 @@ export function useRequireLogin(handler?: (data?: object) => void) { try { const data = await get('/auth/verify'); - setUser(typeof handler === 'function' ? handler(data) : (data as any)?.user); + setUser(data); } catch { - location.href = `${process.env.basePath || ''}/login`; + window.location.href = `${process.env.basePath || ''}/login`; } } diff --git a/src/declaration.d.ts b/src/declaration.d.ts new file mode 100644 index 00000000..42a5eeed --- /dev/null +++ b/src/declaration.d.ts @@ -0,0 +1 @@ +declare module 'debug'; diff --git a/src/lib/auth.ts b/src/lib/auth.ts index 4a42d85d..c218cef9 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -7,14 +7,15 @@ import { createSecureToken, ensureArray, getRandomChars, parseToken } from 'next import { findTeamWebsiteByUserId, getTeamUser, getTeamWebsite } from 'queries'; import { loadWebsite } from './load'; import { Auth } from './types'; +import { NextApiRequest } from 'next'; const log = debug('umami:auth'); const cloudMode = process.env.CLOUD_MODE; -export async function setAuthKey(user, expire = 0) { +export async function setAuthKey(data: any, expire = 0) { const authKey = `auth:${getRandomChars(32)}`; - await redis.set(authKey, user); + await redis.set(authKey, data); if (expire) { await redis.expire(authKey, expire); @@ -23,7 +24,7 @@ export async function setAuthKey(user, expire = 0) { return createSecureToken({ authKey }, secret()); } -export function getAuthToken(req) { +export function getAuthToken(req: NextApiRequest) { try { return req.headers.authorization.split(' ')[1]; } catch { @@ -31,7 +32,7 @@ export function getAuthToken(req) { } } -export function parseShareToken(req) { +export function parseShareToken(req: Request) { try { return parseToken(req.headers[SHARE_TOKEN_HEADER], secret()); } catch (e) { diff --git a/src/lib/client.ts b/src/lib/client.ts index 8c69d23d..7810c44a 100644 --- a/src/lib/client.ts +++ b/src/lib/client.ts @@ -5,7 +5,7 @@ export function getClientAuthToken() { return getItem(AUTH_TOKEN); } -export function setClientAuthToken(token) { +export function setClientAuthToken(token: string) { setItem(AUTH_TOKEN, token); } diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 4c468c1c..0c894634 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -13,7 +13,7 @@ export const REPO_URL = 'https://github.com/umami-software/umami'; export const UPDATES_URL = 'https://api.umami.is/v1/updates'; export const TELEMETRY_PIXEL = 'https://i.umami.is/a.png'; -export const DEFAULT_LOCALE = process.env.defaultLocale ?? 'en-US'; +export const DEFAULT_LOCALE = process.env.defaultLocale || 'en-US'; export const DEFAULT_THEME = 'light'; export const DEFAULT_ANIMATION_DURATION = 300; export const DEFAULT_DATE_RANGE = '24hour'; diff --git a/src/pages/api/auth/verify.ts b/src/pages/api/auth/verify.ts index 9eb9ea48..a302c69b 100644 --- a/src/pages/api/auth/verify.ts +++ b/src/pages/api/auth/verify.ts @@ -6,5 +6,5 @@ import { ok } from 'next-basics'; export default async (req: NextApiRequestAuth, res: NextApiResponse) => { await useAuth(req, res); - return ok(res, req.auth); + return ok(res, req.auth.user); }; diff --git a/yarn.lock b/yarn.lock index d7596fa4..aa13ffda 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2596,18 +2596,18 @@ "@typescript-eslint/types" "6.8.0" eslint-visitor-keys "^3.4.1" -"@umami/prisma-client@^0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@umami/prisma-client/-/prisma-client-0.3.0.tgz#fea35a44c76af0e4ce58288107cda3ee76fc80ba" - integrity sha512-88y/WJX2TEZaUfP+PTretGUL6YdwZCBbhaoeC87eTF3l1aG0Lv3TsmW0lJy5rbKpVqrFJ8zrtvCMP/vt7WeIjg== +"@umami/prisma-client@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@umami/prisma-client/-/prisma-client-0.5.0.tgz#e2287debbf21f9344c989b9e7192491df88513bf" + integrity sha512-BkStMrvxYZQPwEIyy30JJPucTTsmQqb4jD8+ciSHxcBc7039cW0XyX3TL/u9ebZmANzIuNO0XiBArwjWulGIjg== dependencies: chalk "^4.1.2" debug "^4.3.4" -"@umami/redis-client@^0.16.0": - version "0.16.0" - resolved "https://registry.yarnpkg.com/@umami/redis-client/-/redis-client-0.16.0.tgz#0050d1f93338d88691c983f3c0cd4a62da20212b" - integrity sha512-fE08lkMvhXbkXSdSRpG0R/9a3xIiTvwD6f+hKERFZrpfvJJlH3Uf4Jod8Ahg/+TmD03ihSQPooUT3T9Ig3dfaQ== +"@umami/redis-client@^0.17.0": + version "0.17.0" + resolved "https://registry.yarnpkg.com/@umami/redis-client/-/redis-client-0.17.0.tgz#a24db0cb561a9c18b0ecaf6a3342c28525abe1e5" + integrity sha512-rX0xB+QkhMoHnKcj8jzbp1CUMKB/qk2HaYWpNP0Stztkvw7wjFYXsLTLidW3t1a06H5qApx6mJvUjxefLUsX7w== dependencies: debug "^4.3.4" redis "^4.5.1" From 4c0beaee14cb1482428f10f0cfe61bd70c0bd1e8 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Mon, 27 Nov 2023 16:11:09 -0800 Subject: [PATCH 11/17] add extension-read-replicas package --- package.json | 1 + yarn.lock | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/package.json b/package.json index edc6b1e0..367bcf9b 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "@clickhouse/client": "^0.2.2", "@fontsource/inter": "^4.5.15", "@prisma/client": "5.4.2", + "@prisma/extension-read-replicas": "^0.3.0", "@react-spring/web": "^9.7.3", "@tanstack/react-query": "^4.33.0", "@umami/prisma-client": "^0.5.0", diff --git a/yarn.lock b/yarn.lock index aa13ffda..9927b8ca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1958,6 +1958,11 @@ resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-5.4.2.tgz#ba2b7faeb227c76e423e88f962afe6a031319f3f" integrity sha512-fqeucJ3LH0e1eyFdT0zRx+oETLancu5+n4lhiYECyEz6H2RDskPJHJYHkVc0LhkU4Uv7fuEnppKU3nVKNzMh8g== +"@prisma/extension-read-replicas@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@prisma/extension-read-replicas/-/extension-read-replicas-0.3.0.tgz#2842a7c928f957c1dd58a6256104797596d43426" + integrity sha512-F9+rSmYday6GT2qjhJtkZcBOpLO5LtpvFcMGqrBDHf+78LEdSuxfFjOxYlNuqk4B+th62yxpbhfpmB9/Mca14Q== + "@react-spring/animated@~9.7.3": version "9.7.3" resolved "https://registry.yarnpkg.com/@react-spring/animated/-/animated-9.7.3.tgz#4211b1a6d48da0ff474a125e93c0f460ff816e0f" From f7dd0464e07c23c8c837847a4b8c0fd929a7ac1b Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Mon, 27 Nov 2023 20:47:35 -0800 Subject: [PATCH 12/17] Updated redis calls. --- package.json | 2 +- src/declaration.d.ts | 1 + src/lib/auth.ts | 20 ++++++-------------- src/lib/cache.ts | 32 ++++++++++++++++++-------------- src/lib/middleware.ts | 10 ++++++---- src/pages/api/auth/login.ts | 6 +++--- src/pages/api/auth/logout.ts | 4 ++-- src/pages/api/auth/sso.ts | 6 +++--- yarn.lock | 8 ++++---- 9 files changed, 44 insertions(+), 45 deletions(-) diff --git a/package.json b/package.json index 367bcf9b..cf0398f7 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "@react-spring/web": "^9.7.3", "@tanstack/react-query": "^4.33.0", "@umami/prisma-client": "^0.5.0", - "@umami/redis-client": "^0.17.0", + "@umami/redis-client": "^0.18.0", "chalk": "^4.1.1", "chart.js": "^4.2.1", "chartjs-adapter-date-fns": "^3.0.0", diff --git a/src/declaration.d.ts b/src/declaration.d.ts index 42a5eeed..3523e9fa 100644 --- a/src/declaration.d.ts +++ b/src/declaration.d.ts @@ -1 +1,2 @@ +declare module 'cors'; declare module 'debug'; diff --git a/src/lib/auth.ts b/src/lib/auth.ts index c218cef9..1757f05e 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -1,6 +1,6 @@ import { Report } from '@prisma/client'; -import redis from '@umami/redis-client'; import debug from 'debug'; +import redis from '@umami/redis-client'; import { PERMISSIONS, ROLE_PERMISSIONS, SHARE_TOKEN_HEADER } from 'lib/constants'; import { secret } from 'lib/crypto'; import { createSecureToken, ensureArray, getRandomChars, parseToken } from 'next-basics'; @@ -12,13 +12,13 @@ import { NextApiRequest } from 'next'; const log = debug('umami:auth'); const cloudMode = process.env.CLOUD_MODE; -export async function setAuthKey(data: any, expire = 0) { +export async function saveAuth(data: any, expire = 0) { const authKey = `auth:${getRandomChars(32)}`; - await redis.set(authKey, data); + await redis.client.set(authKey, data); if (expire) { - await redis.expire(authKey, expire); + await redis.client.expire(authKey, expire); } return createSecureToken({ authKey }, secret()); @@ -61,11 +61,7 @@ export async function canViewWebsite({ user, shareToken }: Auth, websiteId: stri export async function canCreateWebsite({ user, grant }: Auth) { if (cloudMode) { - if (grant?.find(a => a === PERMISSIONS.websiteCreate)) { - return true; - } - - return false; + return !!grant?.find(a => a === PERMISSIONS.websiteCreate); } if (user.isAdmin) { @@ -121,11 +117,7 @@ export async function canDeleteReport(auth: Auth, report: Report) { export async function canCreateTeam({ user, grant }: Auth) { if (cloudMode) { - if (grant?.find(a => a === PERMISSIONS.teamCreate)) { - return true; - } - - return false; + return !!grant?.find(a => a === PERMISSIONS.teamCreate); } if (user.isAdmin) { diff --git a/src/lib/cache.ts b/src/lib/cache.ts index 2b577bf2..11fd9f5c 100644 --- a/src/lib/cache.ts +++ b/src/lib/cache.ts @@ -3,67 +3,71 @@ import redis from '@umami/redis-client'; import { getSession, getUserById, getWebsiteById } from '../queries'; async function fetchWebsite(id): Promise { - return redis.getCache(`website:${id}`, () => getWebsiteById(id), 86400); + return redis.client.getCache(`website:${id}`, () => getWebsiteById(id), 86400); } async function storeWebsite(data) { const { id } = data; const key = `website:${id}`; - const obj = await redis.setCache(key, data); - await redis.expire(key, 86400); + const obj = await redis.client.setCache(key, data); + await redis.client.expire(key, 86400); return obj; } async function deleteWebsite(id) { - return redis.deleteCache(`website:${id}`); + return redis.client.deleteCache(`website:${id}`); } async function fetchUser(id): Promise { - return redis.getCache(`user:${id}`, () => getUserById(id, { includePassword: true }), 86400); + return redis.client.getCache( + `user:${id}`, + () => getUserById(id, { includePassword: true }), + 86400, + ); } async function storeUser(data) { const { id } = data; const key = `user:${id}`; - const obj = await redis.setCache(key, data); - await redis.expire(key, 86400); + const obj = await redis.client.setCache(key, data); + await redis.client.expire(key, 86400); return obj; } async function deleteUser(id) { - return redis.deleteCache(`user:${id}`); + return redis.client.deleteCache(`user:${id}`); } async function fetchSession(id) { - return redis.getCache(`session:${id}`, () => getSession(id), 86400); + return redis.client.getCache(`session:${id}`, () => getSession(id), 86400); } async function storeSession(data) { const { id } = data; const key = `session:${id}`; - const obj = await redis.setCache(key, data); - await redis.expire(key, 86400); + const obj = await redis.client.setCache(key, data); + await redis.client.expire(key, 86400); return obj; } async function deleteSession(id) { - return redis.deleteCache(`session:${id}`); + return redis.client.deleteCache(`session:${id}`); } async function fetchUserBlock(userId: string) { const key = `user:block:${userId}`; - return redis.get(key); + return redis.client.get(key); } async function incrementUserBlock(userId: string) { const key = `user:block:${userId}`; - return redis.incr(key); + return redis.client.incr(key); } export default { diff --git a/src/lib/middleware.ts b/src/lib/middleware.ts index 5a12eb6a..b54f6d3a 100644 --- a/src/lib/middleware.ts +++ b/src/lib/middleware.ts @@ -1,6 +1,6 @@ -import redis from '@umami/redis-client'; import cors from 'cors'; import debug from 'debug'; +import redis from '@umami/redis-client'; import { getAuthToken, parseShareToken } from 'lib/auth'; import { ROLES } from 'lib/constants'; import { isUuid, secret } from 'lib/crypto'; @@ -47,15 +47,17 @@ export const useSession = createMiddleware(async (req, res, next) => { export const useAuth = createMiddleware(async (req, res, next) => { const token = getAuthToken(req); const payload = parseSecureToken(token, secret()); - const shareToken = await parseShareToken(req); + const shareToken = await parseShareToken(req as any); let user = null; const { userId, authKey, grant } = payload || {}; if (isUuid(userId)) { user = await getUserById(userId); - } else if (redis && authKey) { - user = await redis.get(authKey); + } else if (redis.enabled && authKey) { + const key = await redis.client.get(authKey); + + user = await getUserById(key.userId); } if (process.env.NODE_ENV === 'development') { diff --git a/src/pages/api/auth/login.ts b/src/pages/api/auth/login.ts index 0946ae75..e1007b3c 100644 --- a/src/pages/api/auth/login.ts +++ b/src/pages/api/auth/login.ts @@ -1,6 +1,6 @@ import redis from '@umami/redis-client'; import debug from 'debug'; -import { setAuthKey } from 'lib/auth'; +import { saveAuth } from 'lib/auth'; import { secret } from 'lib/crypto'; import { useValidate } from 'lib/middleware'; import { NextApiRequestQueryBody, User } from 'lib/types'; @@ -52,8 +52,8 @@ export default async ( const user = await getUserByUsername(username, { includePassword: true }); if (user && checkPassword(password, user.password)) { - if (redis) { - const token = await setAuthKey(user); + if (redis.enabled) { + const token = await saveAuth({ userId: user.id }); return ok(res, { token, user }); } diff --git a/src/pages/api/auth/logout.ts b/src/pages/api/auth/logout.ts index e6222e49..715fda62 100644 --- a/src/pages/api/auth/logout.ts +++ b/src/pages/api/auth/logout.ts @@ -8,8 +8,8 @@ export default async (req: NextApiRequest, res: NextApiResponse) => { await useAuth(req, res); if (req.method === 'POST') { - if (redis) { - await redis.del(getAuthToken(req)); + if (redis.enabled) { + await redis.client.del(getAuthToken(req)); } return ok(res); diff --git a/src/pages/api/auth/sso.ts b/src/pages/api/auth/sso.ts index a7992666..7b1eef60 100644 --- a/src/pages/api/auth/sso.ts +++ b/src/pages/api/auth/sso.ts @@ -3,13 +3,13 @@ import { useAuth } from 'lib/middleware'; import { NextApiResponse } from 'next'; import { badRequest, ok } from 'next-basics'; import redis from '@umami/redis-client'; -import { setAuthKey } from 'lib/auth'; +import { saveAuth } from 'lib/auth'; export default async (req: NextApiRequestAuth, res: NextApiResponse) => { await useAuth(req, res); - if (redis && req.auth.user) { - const token = await setAuthKey(req.auth.user, 86400); + if (redis.enabled && req.auth.user) { + const token = await saveAuth({ userId: req.auth.user.id }, 86400); return ok(res, { user: req.auth.user, token }); } diff --git a/yarn.lock b/yarn.lock index 9927b8ca..2c95f0d3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2609,10 +2609,10 @@ chalk "^4.1.2" debug "^4.3.4" -"@umami/redis-client@^0.17.0": - version "0.17.0" - resolved "https://registry.yarnpkg.com/@umami/redis-client/-/redis-client-0.17.0.tgz#a24db0cb561a9c18b0ecaf6a3342c28525abe1e5" - integrity sha512-rX0xB+QkhMoHnKcj8jzbp1CUMKB/qk2HaYWpNP0Stztkvw7wjFYXsLTLidW3t1a06H5qApx6mJvUjxefLUsX7w== +"@umami/redis-client@^0.18.0": + version "0.18.0" + resolved "https://registry.yarnpkg.com/@umami/redis-client/-/redis-client-0.18.0.tgz#6a2315a878f2688dae162d93e88dfc4e097fc48e" + integrity sha512-uDuX5w7ydlOZWrq0h6fADG3XWOhto9fAqrUVu85FUhdijWoGlv5f8adaL8FAah5jD+/Byw2VyGQaZO4VhboEZw== dependencies: debug "^4.3.4" redis "^4.5.1" From ea28511b3c9bc5bec48e9612dfa9691dc74d1f8d Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Mon, 27 Nov 2023 22:20:47 -0800 Subject: [PATCH 13/17] fix redis always being enabled --- src/lib/cache.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/cache.ts b/src/lib/cache.ts index 11fd9f5c..69d749d0 100644 --- a/src/lib/cache.ts +++ b/src/lib/cache.ts @@ -82,5 +82,5 @@ export default { deleteSession, fetchUserBlock, incrementUserBlock, - enabled: !!redis, + enabled: !!redis.enabled, }; From 02c9e0115ef52fdfee80b337734280d53ea0a0aa Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Tue, 28 Nov 2023 10:22:24 -0800 Subject: [PATCH 14/17] Updated user get. --- src/lib/middleware.ts | 6 +++--- src/queries/admin/user.ts | 14 +++++++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/lib/middleware.ts b/src/lib/middleware.ts index b54f6d3a..a6da65c0 100644 --- a/src/lib/middleware.ts +++ b/src/lib/middleware.ts @@ -3,7 +3,7 @@ import debug from 'debug'; import redis from '@umami/redis-client'; import { getAuthToken, parseShareToken } from 'lib/auth'; import { ROLES } from 'lib/constants'; -import { isUuid, secret } from 'lib/crypto'; +import { secret } from 'lib/crypto'; import { findSession } from 'lib/session'; import { badRequest, @@ -52,7 +52,7 @@ export const useAuth = createMiddleware(async (req, res, next) => { let user = null; const { userId, authKey, grant } = payload || {}; - if (isUuid(userId)) { + if (userId) { user = await getUserById(userId); } else if (redis.enabled && authKey) { const key = await redis.client.get(authKey); @@ -61,7 +61,7 @@ export const useAuth = createMiddleware(async (req, res, next) => { } if (process.env.NODE_ENV === 'development') { - log({ token, shareToken, payload, user, grant }); + log('useAuth:', { token, shareToken, payload, user, grant }); } if (!user?.id && !shareToken) { diff --git a/src/queries/admin/user.ts b/src/queries/admin/user.ts index b7319942..11f1c846 100644 --- a/src/queries/admin/user.ts +++ b/src/queries/admin/user.ts @@ -11,13 +11,17 @@ export interface GetUserOptions { } async function getUser( - where: Prisma.UserWhereInput | Prisma.UserWhereUniqueInput, + where: Prisma.UserWhereUniqueInput, options: GetUserOptions = {}, ): Promise { const { includePassword = false, showDeleted = false } = options; - return prisma.client.user.findFirst({ - where: { ...where, ...(showDeleted ? {} : { deletedAt: null }) }, + if (showDeleted) { + where.deletedAt = null; + } + + return prisma.client.user.findUnique({ + where, select: { id: true, username: true, @@ -28,8 +32,8 @@ async function getUser( }); } -export async function getUserById(userId: string, options: GetUserOptions = {}) { - return getUser({ id: userId }, options); +export async function getUserById(id: string, options: GetUserOptions = {}) { + return getUser({ id }, options); } export async function getUserByUsername(username: string, options: GetUserOptions = {}) { From e074394b134f7b04ca32fe7019b5f1978d1ee18b Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Tue, 28 Nov 2023 11:03:55 -0800 Subject: [PATCH 15/17] Removed hostname lookup. --- src/pages/api/send.ts | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/src/pages/api/send.ts b/src/pages/api/send.ts index cf3004f3..1698d858 100644 --- a/src/pages/api/send.ts +++ b/src/pages/api/send.ts @@ -1,4 +1,3 @@ -import { Resolver } from 'dns/promises'; import ipaddr from 'ipaddr.js'; import isbot from 'isbot'; import { COLLECTION_TYPE, HOSTNAME_REGEX } from 'lib/constants'; @@ -78,14 +77,14 @@ export default async (req: NextApiRequestCollect, res: NextApiResponse) => { return ok(res); } - const { type, payload } = req.body; - await useValidate(schema, req, res); - if (await hasBlockedIp(req)) { + if (hasBlockedIp(req)) { return forbidden(res); } + const { type, payload } = req.body; + const { url, referrer, name: eventName, data: eventData, title: pageTitle } = payload; await useSession(req, res); @@ -143,28 +142,16 @@ export default async (req: NextApiRequestCollect, res: NextApiResponse) => { return methodNotAllowed(res); }; -async function hasBlockedIp(req: NextApiRequestCollect) { +function hasBlockedIp(req: NextApiRequestCollect) { const ignoreIps = process.env.IGNORE_IP; - const ignoreHostnames = process.env.IGNORE_HOSTNAME; - if (ignoreIps || ignoreHostnames) { + if (ignoreIps) { const ips = []; if (ignoreIps) { ips.push(...ignoreIps.split(',').map(n => n.trim())); } - if (ignoreHostnames) { - const resolver = new Resolver(); - const promises = ignoreHostnames - .split(',') - .map(n => resolver.resolve4(n.trim()).catch(() => {})); - - await Promise.all(promises).then(resolvedIps => { - ips.push(...resolvedIps.filter(n => n).flatMap(n => n as string[])); - }); - } - const clientIp = getIpAddress(req); return ips.find(ip => { @@ -177,8 +164,8 @@ async function hasBlockedIp(req: NextApiRequestCollect) { if (addr.kind() === range[0].kind() && addr.match(range)) return true; } - - return false; }); } + + return false; } From c5fc16f2415f2694a39f2b2ee50e09c3794be576 Mon Sep 17 00:00:00 2001 From: Brian Cao Date: Tue, 28 Nov 2023 22:44:11 -0800 Subject: [PATCH 16/17] Auto stash before merge of "dev" and "origin/dev" --- src/lib/middleware.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/middleware.ts b/src/lib/middleware.ts index a6da65c0..38a29876 100644 --- a/src/lib/middleware.ts +++ b/src/lib/middleware.ts @@ -8,6 +8,7 @@ import { findSession } from 'lib/session'; import { badRequest, createMiddleware, + forbidden, parseSecureToken, tooManyRequest, unauthorized, @@ -38,6 +39,9 @@ export const useSession = createMiddleware(async (req, res, next) => { if (e.message === 'Usage Limit.') { return tooManyRequest(res, e.message); } + if (e.message.startsWith('Website not found:')) { + return forbidden(res, e.message); + } return badRequest(res, e.message); } From c24a0c87dcea1443284bd0c261175214b6b2323a Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Wed, 29 Nov 2023 14:03:10 -0800 Subject: [PATCH 17/17] Updated login check. --- src/app/(main)/App.tsx | 13 ++++++++++-- src/components/hooks/index.js | 2 +- src/components/hooks/useLogin.ts | 22 +++++++++++++++++++ src/components/hooks/useRequireLogin.ts | 28 ------------------------- src/index.ts | 2 +- src/lib/middleware.ts | 2 +- 6 files changed, 36 insertions(+), 33 deletions(-) create mode 100644 src/components/hooks/useLogin.ts delete mode 100644 src/components/hooks/useRequireLogin.ts diff --git a/src/app/(main)/App.tsx b/src/app/(main)/App.tsx index 01da9a6a..4b093165 100644 --- a/src/app/(main)/App.tsx +++ b/src/app/(main)/App.tsx @@ -1,14 +1,23 @@ 'use client'; +import { Loading } from 'react-basics'; import Script from 'next/script'; import { usePathname } from 'next/navigation'; +import { useLogin, useConfig } from 'components/hooks'; import UpdateNotice from './UpdateNotice'; -import { useRequireLogin, useConfig } from 'components/hooks'; export function App({ children }) { - const { user } = useRequireLogin(); + const { user, isLoading, error } = useLogin(); const config = useConfig(); const pathname = usePathname(); + if (isLoading) { + return ; + } + + if (error) { + window.location.href = `${process.env.basePath || ''}/login`; + } + if (!user || !config) { return null; } diff --git a/src/components/hooks/index.js b/src/components/hooks/index.js index 697d54c3..b851eeb7 100644 --- a/src/components/hooks/index.js +++ b/src/components/hooks/index.js @@ -13,7 +13,7 @@ export * from './useMessages'; export * from './useNavigation'; export * from './useReport'; export * from './useReports'; -export * from './useRequireLogin'; +export * from './useLogin'; export * from './useShareToken'; export * from './useSticky'; export * from './useTheme'; diff --git a/src/components/hooks/useLogin.ts b/src/components/hooks/useLogin.ts new file mode 100644 index 00000000..a4ac9d3b --- /dev/null +++ b/src/components/hooks/useLogin.ts @@ -0,0 +1,22 @@ +import useApi from 'components/hooks/useApi'; +import useUser from 'components/hooks/useUser'; + +export function useLogin() { + const { get, useQuery } = useApi(); + const { user, setUser } = useUser(); + + const query = useQuery({ + queryKey: ['login'], + queryFn: async () => { + const data = await get('/auth/verify'); + + setUser(data); + + return data; + }, + }); + + return { user, ...query }; +} + +export default useLogin; diff --git a/src/components/hooks/useRequireLogin.ts b/src/components/hooks/useRequireLogin.ts deleted file mode 100644 index 68de411b..00000000 --- a/src/components/hooks/useRequireLogin.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { useEffect } from 'react'; -import useApi from 'components/hooks/useApi'; -import useUser from 'components/hooks/useUser'; - -export function useRequireLogin() { - const { get } = useApi(); - const { user, setUser } = useUser(); - - useEffect(() => { - async function loadUser() { - try { - const data = await get('/auth/verify'); - - setUser(data); - } catch { - window.location.href = `${process.env.basePath || ''}/login`; - } - } - - if (!user) { - loadUser(); - } - }, [user]); - - return { user }; -} - -export default useRequireLogin; diff --git a/src/index.ts b/src/index.ts index 01ceb7d1..de555051 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,7 +10,7 @@ export * from 'components/hooks/useFormat'; export * from 'components/hooks/useLocale'; export * from 'components/hooks/useMessages'; export * from 'components/hooks/useNavigation'; -export * from 'components/hooks/useRequireLogin'; +export * from 'components/hooks/useLogin'; export * from 'components/hooks/useShareToken'; export * from 'components/hooks/useSticky'; export * from 'components/hooks/useTheme'; diff --git a/src/lib/middleware.ts b/src/lib/middleware.ts index a6da65c0..865a6f3d 100644 --- a/src/lib/middleware.ts +++ b/src/lib/middleware.ts @@ -57,7 +57,7 @@ export const useAuth = createMiddleware(async (req, res, next) => { } else if (redis.enabled && authKey) { const key = await redis.client.get(authKey); - user = await getUserById(key.userId); + user = await getUserById(key?.userId); } if (process.env.NODE_ENV === 'development') {