From e64a01d8f1f27c71dbaa9cf8b5e3040b6c4221f6 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Sun, 5 Oct 2025 22:37:06 -0700 Subject: [PATCH 1/3] fix redis set for resetWebsite cloudMode --- src/lib/request.ts | 4 ++-- src/queries/prisma/website.ts | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/lib/request.ts b/src/lib/request.ts index afd2b1300..da247e20b 100644 --- a/src/lib/request.ts +++ b/src/lib/request.ts @@ -1,12 +1,12 @@ -import { z } from 'zod'; import { checkAuth } from '@/lib/auth'; import { DEFAULT_PAGE_SIZE, FILTER_COLUMNS } from '@/lib/constants'; import { getAllowedUnits, getMinimumUnit, maxDate, parseDateRange } from '@/lib/date'; import { fetchWebsite } from '@/lib/load'; +import { filtersArrayToObject } from '@/lib/params'; import { badRequest, unauthorized } from '@/lib/response'; import { QueryFilters } from '@/lib/types'; import { getWebsiteSegment } from '@/queries/prisma'; -import { filtersArrayToObject } from '@/lib/params'; +import { z } from 'zod'; export async function parseRequest( request: Request, diff --git a/src/queries/prisma/website.ts b/src/queries/prisma/website.ts index 315dca038..ade8893c8 100644 --- a/src/queries/prisma/website.ts +++ b/src/queries/prisma/website.ts @@ -156,7 +156,10 @@ export async function resetWebsite(websiteId: string) { }), ]).then(async data => { if (cloudMode) { - await redis.client.set(`website:${websiteId}`, data[3]); + await redis.client.set( + `website:${websiteId}`, + data.find(website => website.id), + ); } return data; From f733690d385f93ce499843a7fe49f581fb62404d Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sun, 5 Oct 2025 23:15:36 -0700 Subject: [PATCH 2/3] Added lookup for cloud account. Added SessionModal component. --- .../websites/[websiteId]/WebsiteNav.tsx | 15 ++++-- .../[websiteId]/events/EventsPage.tsx | 2 + .../[websiteId]/events/EventsTable.tsx | 4 +- .../[websiteId]/sessions/SessionModal.tsx | 49 +++++++++++++++++++ .../[websiteId]/sessions/SessionsPage.tsx | 43 ++-------------- src/app/api/websites/route.ts | 13 +++-- src/queries/prisma/website.ts | 1 + 7 files changed, 79 insertions(+), 48 deletions(-) create mode 100644 src/app/(main)/websites/[websiteId]/sessions/SessionModal.tsx diff --git a/src/app/(main)/websites/[websiteId]/WebsiteNav.tsx b/src/app/(main)/websites/[websiteId]/WebsiteNav.tsx index df476e79b..bbbb06147 100644 --- a/src/app/(main)/websites/[websiteId]/WebsiteNav.tsx +++ b/src/app/(main)/websites/[websiteId]/WebsiteNav.tsx @@ -1,5 +1,14 @@ import { Text } from '@umami/react-zen'; -import { Eye, User, Clock, Ungroup, Tag, ChartPie, UserPlus, GitCompare } from '@/components/icons'; +import { + Eye, + User, + Clock, + Sheet, + Tag, + ChartPie, + UserPlus, + AlignEndHorizontal, +} from '@/components/icons'; import { Lightning, Path, Money, Target, Funnel, Magnet, Network } from '@/components/svg'; import { useMessages, useNavigation } from '@/components/hooks'; import { SideMenu } from '@/components/common/SideMenu'; @@ -47,13 +56,13 @@ export function WebsiteNav({ websiteId }: { websiteId: string }) { { id: 'compare', label: formatMessage(labels.compare), - icon: , + icon: , path: renderPath('/compare'), }, { id: 'breakdown', label: formatMessage(labels.breakdown), - icon: , + icon: , path: renderPath('/breakdown'), }, ], diff --git a/src/app/(main)/websites/[websiteId]/events/EventsPage.tsx b/src/app/(main)/websites/[websiteId]/events/EventsPage.tsx index 48e8b0158..b918ec4df 100644 --- a/src/app/(main)/websites/[websiteId]/events/EventsPage.tsx +++ b/src/app/(main)/websites/[websiteId]/events/EventsPage.tsx @@ -9,6 +9,7 @@ import { useMessages } from '@/components/hooks'; import { EventProperties } from './EventProperties'; import { WebsiteControls } from '@/app/(main)/websites/[websiteId]/WebsiteControls'; import { getItem, setItem } from '@/lib/storage'; +import { SessionModal } from '@/app/(main)/websites/[websiteId]/sessions/SessionModal'; const KEY_NAME = 'umami.events.tab'; @@ -52,6 +53,7 @@ export function EventsPage({ websiteId }) { + ); } diff --git a/src/app/(main)/websites/[websiteId]/events/EventsTable.tsx b/src/app/(main)/websites/[websiteId]/events/EventsTable.tsx index 31044982b..c36ee5e73 100644 --- a/src/app/(main)/websites/[websiteId]/events/EventsTable.tsx +++ b/src/app/(main)/websites/[websiteId]/events/EventsTable.tsx @@ -10,7 +10,7 @@ import { TypeIcon } from '@/components/common/TypeIcon'; export function EventsTable({ data = [] }) { const { formatMessage, labels } = useMessages(); - const { renderUrl } = useNavigation(); + const { updateParams } = useNavigation(); const { formatValue } = useFormat(); if (data.length === 0) { @@ -23,7 +23,7 @@ export function EventsTable({ data = [] }) { {(row: any) => { return ( - + {row.eventName ? : } diff --git a/src/app/(main)/websites/[websiteId]/sessions/SessionModal.tsx b/src/app/(main)/websites/[websiteId]/sessions/SessionModal.tsx new file mode 100644 index 000000000..d2240d84e --- /dev/null +++ b/src/app/(main)/websites/[websiteId]/sessions/SessionModal.tsx @@ -0,0 +1,49 @@ +import { Dialog, Modal, ModalProps } from '@umami/react-zen'; +import { SessionProfile } from '@/app/(main)/websites/[websiteId]/sessions/SessionProfile'; +import { useNavigation } from '@/components/hooks'; + +export interface SessionModalProps extends ModalProps { + websiteId: string; +} + +export function SessionModal({ websiteId, ...props }: SessionModalProps) { + const { + router, + query: { session }, + updateParams, + } = useNavigation(); + + const handleClose = (close: () => void) => { + router.push(updateParams({ session: undefined })); + close(); + }; + + const handleOpenChange = (isOpen: boolean) => { + if (!isOpen) { + router.push(updateParams({ session: undefined })); + } + }; + + return ( + + + {({ close }) => { + return ( + handleClose(close)} + /> + ); + }} + + + ); +} diff --git a/src/app/(main)/websites/[websiteId]/sessions/SessionsPage.tsx b/src/app/(main)/websites/[websiteId]/sessions/SessionsPage.tsx index ae0d41043..8a096aa78 100644 --- a/src/app/(main)/websites/[websiteId]/sessions/SessionsPage.tsx +++ b/src/app/(main)/websites/[websiteId]/sessions/SessionsPage.tsx @@ -1,35 +1,19 @@ 'use client'; import { Key, useState } from 'react'; -import { TabList, Tab, Tabs, TabPanel, Column, Modal, Dialog } from '@umami/react-zen'; +import { TabList, Tab, Tabs, TabPanel, Column } from '@umami/react-zen'; import { SessionsDataTable } from './SessionsDataTable'; import { SessionProperties } from './SessionProperties'; -import { useMessages, useNavigation } from '@/components/hooks'; +import { useMessages } from '@/components/hooks'; import { Panel } from '@/components/common/Panel'; import { WebsiteControls } from '@/app/(main)/websites/[websiteId]/WebsiteControls'; import { getItem, setItem } from '@/lib/storage'; -import { SessionProfile } from '@/app/(main)/websites/[websiteId]/sessions/SessionProfile'; +import { SessionModal } from '@/app/(main)/websites/[websiteId]/sessions/SessionModal'; const KEY_NAME = 'umami.sessions.tab'; export function SessionsPage({ websiteId }) { const [tab, setTab] = useState(getItem(KEY_NAME) || 'activity'); const { formatMessage, labels } = useMessages(); - const { - router, - query: { session }, - updateParams, - } = useNavigation(); - - const handleClose = (close: () => void) => { - router.push(updateParams({ session: undefined })); - close(); - }; - - const handleOpenChange = (isOpen: boolean) => { - if (!isOpen) { - router.push(updateParams({ session: undefined })); - } - }; const handleSelect = (value: Key) => { setItem(KEY_NAME, value); @@ -53,26 +37,7 @@ export function SessionsPage({ websiteId }) { - - - {({ close }) => { - return ( - handleClose(close)} - /> - ); - }} - - + ); } diff --git a/src/app/api/websites/route.ts b/src/app/api/websites/route.ts index 776f23b0a..994e22bd7 100644 --- a/src/app/api/websites/route.ts +++ b/src/app/api/websites/route.ts @@ -1,4 +1,5 @@ import { z } from 'zod'; +import redis from '@/lib/redis'; import { canCreateTeamWebsite, canCreateWebsite } from '@/permissions'; import { json, unauthorized } from '@/lib/response'; import { uuid } from '@/lib/crypto'; @@ -50,11 +51,15 @@ export async function POST(request: Request) { const { id, name, domain, shareId, teamId } = body; - if (process.env.CLOUD_MODE && !teamId && !auth.user.hasSubscription) { - const count = await getWebsiteCount(auth.user.id); + if (process.env.CLOUD_MODE && !teamId) { + const account = await redis.client.get(`account:${auth.user.id}`); - if (count >= CLOUD_WEBSITE_LIMIT) { - return unauthorized({ message: 'Website limit reached.' }); + if (!account?.hasSubscription) { + const count = await getWebsiteCount(auth.user.id); + + if (count >= CLOUD_WEBSITE_LIMIT) { + return unauthorized({ message: 'Website limit reached.' }); + } } } diff --git a/src/queries/prisma/website.ts b/src/queries/prisma/website.ts index 315dca038..1c84f527c 100644 --- a/src/queries/prisma/website.ts +++ b/src/queries/prisma/website.ts @@ -208,6 +208,7 @@ export async function getWebsiteCount(userId: string) { return prisma.client.website.count({ where: { userId, + deletedAt: null, }, }); } From e7f565f143d6d920667fe1860cec19b8ec3bbba9 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sun, 5 Oct 2025 23:55:17 -0700 Subject: [PATCH 3/3] Don't allow saving segments on share page. --- src/components/input/FilterBar.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/input/FilterBar.tsx b/src/components/input/FilterBar.tsx index 96ad51b4f..b81f77e30 100644 --- a/src/components/input/FilterBar.tsx +++ b/src/components/input/FilterBar.tsx @@ -25,13 +25,14 @@ export function FilterBar({ websiteId }: { websiteId: string }) { const { formatValue } = useFormat(); const { router, + pathname, updateParams, replaceParams, query: { segment, cohort }, } = useNavigation(); const { filters, operatorLabels } = useFilters(); const { data, isLoading } = useWebsiteSegmentQuery(websiteId, segment || cohort); - const canSaveSegment = filters.length > 0 && !segment && !cohort; + const canSaveSegment = filters.length > 0 && !segment && !cohort && !pathname.includes('/share'); const handleCloseFilter = (param: string) => { router.push(updateParams({ [param]: undefined }));