From f733690d385f93ce499843a7fe49f581fb62404d Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sun, 5 Oct 2025 23:15:36 -0700 Subject: [PATCH] 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 df476e79..bbbb0614 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 48e8b015..b918ec4d 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 31044982..c36ee5e7 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 00000000..d2240d84 --- /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 ae0d4104..8a096aa7 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 776f23b0..994e22bd 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 315dca03..1c84f527 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, }, }); }