From cee05d762c7c1c50ae6bf8c26d251e99755cd705 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 6 Jun 2025 19:44:09 -0700 Subject: [PATCH] Added journey page. Removed dashboard. --- next.config.ts | 2 +- package.json | 9 +- pnpm-lock.yaml | 10 +- src/app/(main)/SideNav.tsx | 6 - .../(main)/dashboard/DashboardEdit.module.css | 57 ------- src/app/(main)/dashboard/DashboardEdit.tsx | 158 ------------------ src/app/(main)/dashboard/DashboardPage.tsx | 69 -------- .../DashboardSettingsButton.module.css | 5 - .../dashboard/DashboardSettingsButton.tsx | 35 ---- src/app/(main)/dashboard/page.tsx | 10 -- .../websites/[websiteId]/WebsiteChartList.tsx | 54 ------ .../websites/[websiteId]/WebsiteNav.tsx | 2 +- .../websites/[websiteId]/funnels/Funnel.tsx | 38 +++-- .../websites/[websiteId]/journeys/Journey.tsx | 99 +++++++++++ .../[websiteId]/journeys/JourneyAddButton.tsx | 28 ++++ .../[websiteId]/journeys/JourneyEditForm.tsx | 96 +++++++++++ .../[websiteId]/journeys/JourneysPage.tsx | 38 +++++ .../websites/[websiteId]/journeys/page.tsx | 12 ++ src/app/page.tsx | 2 +- src/components/common/FilterEditForm.tsx | 5 +- src/components/input/TeamsButton.tsx | 2 +- src/components/messages.ts | 1 + src/components/metrics/ChangeLabel.module.css | 2 +- src/lib/schema.ts | 10 ++ 24 files changed, 328 insertions(+), 422 deletions(-) delete mode 100644 src/app/(main)/dashboard/DashboardEdit.module.css delete mode 100644 src/app/(main)/dashboard/DashboardEdit.tsx delete mode 100644 src/app/(main)/dashboard/DashboardPage.tsx delete mode 100644 src/app/(main)/dashboard/DashboardSettingsButton.module.css delete mode 100644 src/app/(main)/dashboard/DashboardSettingsButton.tsx delete mode 100644 src/app/(main)/dashboard/page.tsx delete mode 100644 src/app/(main)/websites/[websiteId]/WebsiteChartList.tsx create mode 100644 src/app/(main)/websites/[websiteId]/journeys/Journey.tsx create mode 100644 src/app/(main)/websites/[websiteId]/journeys/JourneyAddButton.tsx create mode 100644 src/app/(main)/websites/[websiteId]/journeys/JourneyEditForm.tsx create mode 100644 src/app/(main)/websites/[websiteId]/journeys/JourneysPage.tsx create mode 100644 src/app/(main)/websites/[websiteId]/journeys/page.tsx diff --git a/next.config.ts b/next.config.ts index 02851c82..b63abebb 100644 --- a/next.config.ts +++ b/next.config.ts @@ -130,7 +130,7 @@ const redirects = [ }, { source: '/teams/:id', - destination: '/teams/:id/dashboard', + destination: '/teams/:id/websites', permanent: true, }, { diff --git a/package.json b/package.json index 516732c5..7a06fdfa 100644 --- a/package.json +++ b/package.json @@ -21,9 +21,10 @@ "start-server": "node server.js", "build-app": "next build", "build-icons": "svgr ./src/assets --out-dir src/components/svg --typescript", - "build-components": "rollup -c rollup.components.config.mjs", - "build-tracker": "rollup -c rollup.tracker.config.mjs", - "build-db": "npm-run-all copy-db-files build-db-client", + "build-components": "rollup -c rollup.components.config.js", + "build-tracker": "rollup -c rollup.tracker.config.js", + "build-prisma-client": "node scripts/build-prisma-client.js", + "build-db": "npm-run-all copy-db-files build-db-client build-prisma-client", "build-lang": "npm-run-all format-lang compile-lang download-country-names download-language-names clean-lang", "build-geo": "node scripts/build-geo.js", "build-db-schema": "prisma db pull", @@ -80,7 +81,7 @@ "@react-spring/web": "^9.7.3", "@svgr/cli": "^8.1.0", "@tanstack/react-query": "^5.28.6", - "@umami/react-zen": "^0.133.0", + "@umami/react-zen": "^0.134.0", "@umami/redis-client": "^0.27.0", "bcryptjs": "^2.4.3", "chalk": "^4.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 269c7c15..6d7a3926 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,8 +45,8 @@ importers: specifier: ^5.28.6 version: 5.77.2(react@19.1.0) '@umami/react-zen': - specifier: ^0.133.0 - version: 0.133.0(@babel/core@7.27.1)(@types/react@19.1.5)(immer@9.0.21)(use-sync-external-store@1.5.0(react@19.1.0)) + specifier: ^0.134.0 + version: 0.134.0(@babel/core@7.27.1)(@types/react@19.1.5)(immer@9.0.21)(use-sync-external-store@1.5.0(react@19.1.0)) '@umami/redis-client': specifier: ^0.27.0 version: 0.27.0 @@ -2585,8 +2585,8 @@ packages: resolution: {integrity: sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@umami/react-zen@0.133.0': - resolution: {integrity: sha512-AAhtYdmLwVZ4i5lzcr5mylc5IQIirlxEL0bMRktlFHbX73wiBrTnk2RrYjmRhCe2KVRkCw2EF8ORXQ7GFrfAOg==} + '@umami/react-zen@0.134.0': + resolution: {integrity: sha512-RBSD50mTw2YKY0Z73OSxVtjrMvIq3nGtWYtcZHPXl/4oYj3Ph0cKTKto14Jx2qs2kHm2DxcS3ND1FR1OrPEknw==} '@umami/redis-client@0.27.0': resolution: {integrity: sha512-SbHTpxhgeZyTBUSp2zdZM+XUtpsaSL4Tad8QXIEhEtjWhvvfoornyT5kLuyYCVtzSAT4daALeGmOO1z6EE1KcA==} @@ -9805,7 +9805,7 @@ snapshots: '@typescript-eslint/types': 8.32.1 eslint-visitor-keys: 4.2.0 - '@umami/react-zen@0.133.0(@babel/core@7.27.1)(@types/react@19.1.5)(immer@9.0.21)(use-sync-external-store@1.5.0(react@19.1.0))': + '@umami/react-zen@0.134.0(@babel/core@7.27.1)(@types/react@19.1.5)(immer@9.0.21)(use-sync-external-store@1.5.0(react@19.1.0))': dependencies: '@fontsource/jetbrains-mono': 5.2.5 '@internationalized/date': 3.8.2 diff --git a/src/app/(main)/SideNav.tsx b/src/app/(main)/SideNav.tsx index c6f99d96..cdc90cec 100644 --- a/src/app/(main)/SideNav.tsx +++ b/src/app/(main)/SideNav.tsx @@ -1,7 +1,6 @@ import Link from 'next/link'; import { Sidebar, SidebarHeader, SidebarSection, SidebarItem } from '@umami/react-zen'; import { - Copy, Globe, LayoutDashboard, Link as LinkIcon, @@ -17,11 +16,6 @@ export function SideNav(props: any) { const [isCollapsed] = useGlobalState('sidenav-collapsed'); const links = [ - { - label: formatMessage(labels.dashboard), - href: renderTeamUrl('/dashboard'), - icon: , - }, { label: formatMessage(labels.websites), href: renderTeamUrl('/websites'), diff --git a/src/app/(main)/dashboard/DashboardEdit.module.css b/src/app/(main)/dashboard/DashboardEdit.module.css deleted file mode 100644 index 19266d17..00000000 --- a/src/app/(main)/dashboard/DashboardEdit.module.css +++ /dev/null @@ -1,57 +0,0 @@ -.buttons { - display: flex; - align-items: center; - justify-content: flex-end; - gap: 10px; -} - -.item { - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - padding: 20px; - border-radius: 5px; - border: 1px solid var(--base400); - background: var(--base50); - margin-bottom: 10px; -} - -.text { - position: relative; -} - -.name { - font-weight: 600; - font-size: 16px; -} - -.domain { - font-size: 14px; - color: var(--base700); -} - -.dragActive { - cursor: grab; -} - -.dragActive:active { - cursor: grabbing; -} - -.header { - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 20px; - gap: 20px; -} - -.search { - max-width: 360px; -} - -.active { - border-color: var(--base600); - box-shadow: 4px 4px 4px var(--base100); -} diff --git a/src/app/(main)/dashboard/DashboardEdit.tsx b/src/app/(main)/dashboard/DashboardEdit.tsx deleted file mode 100644 index 7dabdf64..00000000 --- a/src/app/(main)/dashboard/DashboardEdit.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import { useState, useMemo, useEffect } from 'react'; -import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd'; -import classNames from 'classnames'; -import { Button, Loading, Toggle, SearchField } from '@umami/react-zen'; -import { firstBy } from 'thenby'; -import { useDashboard, saveDashboard } from '@/store/dashboard'; -import { useMessages, useWebsites } from '@/components/hooks'; -import styles from './DashboardEdit.module.css'; - -const DRAG_ID = 'dashboard-website-ordering'; - -export function DashboardEdit({ teamId }: { teamId: string }) { - const settings = useDashboard(); - const { websiteOrder, websiteActive, isEdited } = settings; - const { formatMessage, labels } = useMessages(); - const [order, setOrder] = useState(websiteOrder || []); - const [active, setActive] = useState(websiteActive || []); - const [edited, setEdited] = useState(isEdited); - const [websites, setWebsites] = useState([]); - const [search, setSearch] = useState(''); - - const { - result, - query: { isLoading }, - setParams, - } = useWebsites({ teamId }); - - useEffect(() => { - if (result?.data) { - setWebsites(prevWebsites => { - const newWebsites = [...prevWebsites, ...result.data]; - if (newWebsites.length < result.count) { - setParams(prevParams => ({ ...prevParams, page: prevParams.page + 1 })); - } - return newWebsites; - }); - } - }, [result]); - - const ordered = useMemo(() => { - if (websites) { - return websites - .map((website: { id: any; name: string; domain: string }) => ({ - ...website, - order: order.indexOf(website.id), - })) - .sort(firstBy('order')); - } - return []; - }, [websites, order]); - - function handleWebsiteDrag({ destination, source }) { - if (!destination || destination.index === source.index) return; - - const orderedWebsites = [...ordered]; - const [removed] = orderedWebsites.splice(source.index, 1); - orderedWebsites.splice(destination.index, 0, removed); - - setOrder(orderedWebsites.map(website => website?.id || 0)); - setEdited(true); - } - - function handleActiveWebsites(id: string) { - setActive(prevActive => - prevActive.includes(id) ? prevActive.filter(a => a !== id) : [...prevActive, id], - ); - setEdited(true); - } - - function handleSave() { - saveDashboard({ - editing: false, - isEdited: edited, - websiteOrder: order, - websiteActive: active, - }); - } - - function handleCancel() { - saveDashboard({ editing: false, websiteOrder, websiteActive, isEdited }); - } - - function handleReset() { - setOrder([]); - setActive([]); - setEdited(false); - } - - if (isLoading) { - return ; - } - - return ( - <> -
- -
- - - -
-
-
- - - {(provided, snapshot) => ( -
- {ordered.map(({ id, name, domain }, index) => { - if ( - search && - !`${name.toLowerCase()}${domain.toLowerCase()}`.includes(search.toLowerCase()) - ) { - return null; - } - - return ( - - {(provided, snapshot) => ( -
-
-
{name}
-
{domain}
-
- handleActiveWebsites(id)} - /> -
- )} -
- ); - })} - {provided.placeholder} -
- )} -
-
-
- - ); -} diff --git a/src/app/(main)/dashboard/DashboardPage.tsx b/src/app/(main)/dashboard/DashboardPage.tsx deleted file mode 100644 index c7306087..00000000 --- a/src/app/(main)/dashboard/DashboardPage.tsx +++ /dev/null @@ -1,69 +0,0 @@ -'use client'; -import { Icon, Loading, Text } from '@umami/react-zen'; -import { SectionHeader } from '@/components/common/SectionHeader'; -import { Pager } from '@/components/common/Pager'; -import { WebsiteChartList } from '../websites/[websiteId]/WebsiteChartList'; -import { DashboardSettingsButton } from '@/app/(main)/dashboard/DashboardSettingsButton'; -import { DashboardEdit } from '@/app/(main)/dashboard/DashboardEdit'; -import { EmptyPlaceholder } from '@/components/common/EmptyPlaceholder'; -import { useMessages, useNavigation, useWebsites } from '@/components/hooks'; -import { Arrow } from '@/components/icons'; -import { useDashboard } from '@/store/dashboard'; -import { LinkButton } from '@/components/common/LinkButton'; - -export function DashboardPage() { - const { formatMessage, labels, messages } = useMessages(); - const { teamId, renderTeamUrl } = useNavigation(); - const { showCharts, editing, isEdited } = useDashboard(); - const pageSize = isEdited ? 200 : 10; - - const { result, query, params, setParams } = useWebsites({ teamId }, { pageSize }); - const { page } = params; - const hasData = !!result?.data?.length; - - const handlePageChange = (page: number) => { - setParams({ ...params, page }); - }; - - if (query.isLoading) { - return ; - } - - return ( -
- - {!editing && hasData && } - - {!hasData && ( - - - - - - {formatMessage(messages.goToSettings)} - - - )} - {hasData && ( - <> - {editing && } - {!editing && ( - <> - - - - )} - - )} -
- ); -} diff --git a/src/app/(main)/dashboard/DashboardSettingsButton.module.css b/src/app/(main)/dashboard/DashboardSettingsButton.module.css deleted file mode 100644 index 6e0d19c2..00000000 --- a/src/app/(main)/dashboard/DashboardSettingsButton.module.css +++ /dev/null @@ -1,5 +0,0 @@ -.buttonGroup { - display: flex; - place-items: center; - gap: 10px; -} diff --git a/src/app/(main)/dashboard/DashboardSettingsButton.tsx b/src/app/(main)/dashboard/DashboardSettingsButton.tsx deleted file mode 100644 index 12b7e913..00000000 --- a/src/app/(main)/dashboard/DashboardSettingsButton.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { Row, TooltipTrigger, Tooltip, Icon, Text, Button } from '@umami/react-zen'; -import { BarChart, Edit } from '@/components/icons'; -import { saveDashboard } from '@/store/dashboard'; -import { useMessages } from '@/components/hooks'; - -export function DashboardSettingsButton() { - const { formatMessage, labels } = useMessages(); - - const handleToggleCharts = () => { - saveDashboard(state => ({ showCharts: !state.showCharts })); - }; - - const handleEdit = () => { - saveDashboard({ editing: true }); - }; - - return ( - - - - {formatMessage(labels.toggleCharts)} - - - - ); -} diff --git a/src/app/(main)/dashboard/page.tsx b/src/app/(main)/dashboard/page.tsx deleted file mode 100644 index 20ac3533..00000000 --- a/src/app/(main)/dashboard/page.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { DashboardPage } from './DashboardPage'; -import { Metadata } from 'next'; - -export default function () { - return ; -} - -export const metadata: Metadata = { - title: 'Dashboard', -}; diff --git a/src/app/(main)/websites/[websiteId]/WebsiteChartList.tsx b/src/app/(main)/websites/[websiteId]/WebsiteChartList.tsx deleted file mode 100644 index 4a211151..00000000 --- a/src/app/(main)/websites/[websiteId]/WebsiteChartList.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { Text, Icon } from '@umami/react-zen'; -import { useMemo } from 'react'; -import { firstBy } from 'thenby'; -import { WebsiteChart } from './WebsiteChart'; -import { useDashboard } from '@/store/dashboard'; -import { WebsiteControls } from './WebsiteControls'; -import { WebsiteMetricsBar } from './WebsiteMetricsBar'; -import { useMessages, useNavigation } from '@/components/hooks'; -import { LinkButton } from '@/components/common/LinkButton'; -import { Arrow } from '@/components/icons'; - -export function WebsiteChartList({ - websites, - showCharts, - limit, -}: { - websites: any[]; - showCharts?: boolean; - limit?: number; -}) { - const { formatMessage, labels } = useMessages(); - const { websiteOrder, websiteActive } = useDashboard(); - const { renderTeamUrl } = useNavigation(); - - const ordered = useMemo(() => { - return websites - .filter(website => (websiteActive.length ? websiteActive.includes(website.id) : true)) - .map(website => ({ ...website, order: websiteOrder.indexOf(website.id) || 0 })) - .sort(firstBy('order')); - }, [websites, websiteOrder, websiteActive]); - - return ( -
- {ordered.map(({ id }: { id: string }, index) => { - return index < limit ? ( -
- - - {formatMessage(labels.viewDetails)} - - - - - - - - - {showCharts && } -
- ) : null; - })} -
- ); -} diff --git a/src/app/(main)/websites/[websiteId]/WebsiteNav.tsx b/src/app/(main)/websites/[websiteId]/WebsiteNav.tsx index 48927023..557aaac4 100644 --- a/src/app/(main)/websites/[websiteId]/WebsiteNav.tsx +++ b/src/app/(main)/websites/[websiteId]/WebsiteNav.tsx @@ -65,7 +65,7 @@ export function WebsiteNav({ websiteId }: { websiteId: string }) { }, { id: 'journeys', - label: formatMessage(labels.journey), + label: formatMessage(labels.journeys), icon: , path: '/journeys', }, diff --git a/src/app/(main)/websites/[websiteId]/funnels/Funnel.tsx b/src/app/(main)/websites/[websiteId]/funnels/Funnel.tsx index a5f6bade..6281b5a2 100644 --- a/src/app/(main)/websites/[websiteId]/funnels/Funnel.tsx +++ b/src/app/(main)/websites/[websiteId]/funnels/Funnel.tsx @@ -1,4 +1,4 @@ -import { Grid, Column, Row, Text, Icon, ProgressBar, Dialog } from '@umami/react-zen'; +import { Grid, Column, Row, Text, Icon, ProgressBar, Dialog, Box } from '@umami/react-zen'; import { useMessages, useResultQuery } from '@/components/hooks'; import { LoadingPanel } from '@/components/common/LoadingPanel'; import { File, Lightning, User } from '@/components/icons'; @@ -13,7 +13,7 @@ type FunnelResult = { visitors: number; previous: number; dropped: number; - droppoff: number; + dropoff: number; remaining: number; }; @@ -53,25 +53,35 @@ export function Funnel({ id, name, type, parameters, websiteId, startDate, endDa {data?.map( ( - { type, value, visitors, previous, dropped, remaining }: FunnelResult, + { type, value, visitors, previous, dropped, dropoff, remaining }: FunnelResult, index: number, ) => { const isPage = type === 'page'; return ( - + - + {index + 1} + {index > 0 && ( + + )} @@ -87,12 +97,16 @@ export function Funnel({ id, name, type, parameters, websiteId, startDate, endDa {index > 0 && ( - {formatLongNumber(dropped)} + + {formatLongNumber(dropped)} + )} - {formatLongNumber(visitors)} + + {`${formatLongNumber(visitors)} ${formatMessage(labels.visitors)}`} + @@ -102,9 +116,11 @@ export function Funnel({ id, name, type, parameters, websiteId, startDate, endDa maxValue={previous || 1} style={{ width: '100%' }} /> - - {Math.round(remaining * 100)}% - + + + {Math.round(remaining * 100)}% + + diff --git a/src/app/(main)/websites/[websiteId]/journeys/Journey.tsx b/src/app/(main)/websites/[websiteId]/journeys/Journey.tsx new file mode 100644 index 00000000..efc04ec6 --- /dev/null +++ b/src/app/(main)/websites/[websiteId]/journeys/Journey.tsx @@ -0,0 +1,99 @@ +import { Grid, Row, Column, Text, Icon, Button, Dialog } from '@umami/react-zen'; +import { ReportEditButton } from '@/components/input/ReportEditButton'; +import { useMessages, useResultQuery } from '@/components/hooks'; +import { Arrow, Eye } from '@/components/icons'; +import { LoadingPanel } from '@/components/common/LoadingPanel'; +import { JourneyEditForm } from './JourneyEditForm'; + +export interface JourneyProps { + id: string; + name: string; + type: string; + parameters: { + steps: string; + startStep: string; + endStep: string; + }; + websiteId: string; + startDate: Date; + endDate: Date; +} + +export type GoalData = { num: number; total: number }; + +export function Journey({ + id, + name, + type, + parameters, + websiteId, + startDate, + endDate, +}: JourneyProps) { + const { formatMessage, labels } = useMessages(); + const { data, error, isLoading } = useResultQuery(type, { + websiteId, + dateRange: { + startDate, + endDate, + }, + parameters, + }); + + return ( + + + + + + + {name} + + + + + + {({ close }) => { + return ( + + + + ); + }} + + + + + + {formatMessage(labels.steps)}: {parameters?.steps} + + + + + + {formatMessage(labels.startStep)}: {parameters?.startStep} + + + + + + {formatMessage(labels.endStep)}: {parameters?.endStep || formatMessage(labels.none)} + + + + + + + ); +} diff --git a/src/app/(main)/websites/[websiteId]/journeys/JourneyAddButton.tsx b/src/app/(main)/websites/[websiteId]/journeys/JourneyAddButton.tsx new file mode 100644 index 00000000..b75de0b2 --- /dev/null +++ b/src/app/(main)/websites/[websiteId]/journeys/JourneyAddButton.tsx @@ -0,0 +1,28 @@ +import { Button, DialogTrigger, Dialog, Icon, Text, Modal } from '@umami/react-zen'; +import { useMessages } from '@/components/hooks'; +import { JourneyEditForm } from './JourneyEditForm'; +import { Plus } from '@/components/icons'; + +export function JourneyAddButton({ websiteId }: { websiteId: string }) { + const { formatMessage, labels } = useMessages(); + + return ( + + + + + {({ close }) => } + + + + ); +} diff --git a/src/app/(main)/websites/[websiteId]/journeys/JourneyEditForm.tsx b/src/app/(main)/websites/[websiteId]/journeys/JourneyEditForm.tsx new file mode 100644 index 00000000..1a07d6b6 --- /dev/null +++ b/src/app/(main)/websites/[websiteId]/journeys/JourneyEditForm.tsx @@ -0,0 +1,96 @@ +import { + Form, + FormField, + TextField, + FormButtons, + FormSubmitButton, + Button, + Select, + ListItem, + Loading, +} from '@umami/react-zen'; +import { useApi, useMessages, useModified, useReportQuery } from '@/components/hooks'; + +const JOURNEY_STEPS = ['3', '4', '5', '6', '7']; + +export function JourneyEditForm({ + id, + websiteId, + onSave, + onClose, +}: { + id?: string; + websiteId: string; + onSave?: () => void; + onClose?: () => void; +}) { + const { formatMessage, labels } = useMessages(); + const { touch } = useModified(); + const { post, useMutation } = useApi(); + const { data } = useReportQuery(id); + const { mutate, error, isPending } = useMutation({ + mutationFn: (params: any) => post(`/reports${id ? `/${id}` : ''}`, params), + }); + + const handleSubmit = async ({ name, ...parameters }) => { + mutate( + { ...data, id, name, type: 'journey', websiteId, parameters }, + { + onSuccess: async () => { + if (id) touch(`report:${id}`); + touch('reports:journey'); + onSave?.(); + onClose?.(); + }, + }, + ); + }; + + if (id && !data) { + return ; + } + + const defaultValues = { + name: data?.name || '', + steps: data?.steps || '5', + startStep: data?.parameters?.startStep || '', + endStep: data?.parameters?.endStep || '', + }; + + return ( +
+ + + + + + + + + + + + + + + {formatMessage(labels.save)} + +
+ ); +} diff --git a/src/app/(main)/websites/[websiteId]/journeys/JourneysPage.tsx b/src/app/(main)/websites/[websiteId]/journeys/JourneysPage.tsx new file mode 100644 index 00000000..604f72ed --- /dev/null +++ b/src/app/(main)/websites/[websiteId]/journeys/JourneysPage.tsx @@ -0,0 +1,38 @@ +'use client'; +import { Grid, Loading } from '@umami/react-zen'; +import { SectionHeader } from '@/components/common/SectionHeader'; +import { Journey } from './Journey'; +import { JourneyAddButton } from './JourneyAddButton'; +import { WebsiteControls } from '../WebsiteControls'; +import { useDateRange, useReportsQuery } from '@/components/hooks'; +import { LoadingPanel } from '@/components/common/LoadingPanel'; +import { Panel } from '@/components/common/Panel'; + +export function JourneysPage({ websiteId }: { websiteId: string }) { + const { result } = useReportsQuery({ websiteId, type: 'journey' }); + const { + dateRange: { startDate, endDate }, + } = useDateRange(websiteId); + + if (!result) { + return ; + } + + return ( + <> + + + + + + + {result?.data?.map((report: any) => ( + + + + ))} + + + + ); +} diff --git a/src/app/(main)/websites/[websiteId]/journeys/page.tsx b/src/app/(main)/websites/[websiteId]/journeys/page.tsx new file mode 100644 index 00000000..c055dacd --- /dev/null +++ b/src/app/(main)/websites/[websiteId]/journeys/page.tsx @@ -0,0 +1,12 @@ +import { Metadata } from 'next'; +import { JourneysPage } from './JourneysPage'; + +export default async function ({ params }: { params: Promise<{ websiteId: string }> }) { + const { websiteId } = await params; + + return ; +} + +export const metadata: Metadata = { + title: 'Journeys', +}; diff --git a/src/app/page.tsx b/src/app/page.tsx index 4977c99b..8249e184 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,5 +1,5 @@ import { redirect } from 'next/navigation'; export default function RootPage() { - redirect('/dashboard'); + redirect('/websites'); } diff --git a/src/components/common/FilterEditForm.tsx b/src/components/common/FilterEditForm.tsx index e630e0bf..c15e6ff0 100644 --- a/src/components/common/FilterEditForm.tsx +++ b/src/components/common/FilterEditForm.tsx @@ -1,7 +1,6 @@ import { useState, Key } from 'react'; -import { Grid, Row, Column, Label, List, ListItem, Button, Heading } from '@umami/react-zen'; +import { Grid, Row, Column, Label, List, ListItem, Button, Heading, Text } from '@umami/react-zen'; import { useFilters, useMessages } from '@/components/hooks'; -import { EmptyPlaceholder } from '@/components/common/EmptyPlaceholder'; import { FilterRecord } from '@/components/common/FilterRecord'; export interface FilterEditFormProps { @@ -73,7 +72,7 @@ export function FilterEditForm({ data = [], onChange, onClose }: FilterEditFormP /> ); })} - {!filters.length && } + {!filters.length && {formatMessage(labels.none)}} diff --git a/src/components/input/TeamsButton.tsx b/src/components/input/TeamsButton.tsx index 3487fd9f..c8151b12 100644 --- a/src/components/input/TeamsButton.tsx +++ b/src/components/input/TeamsButton.tsx @@ -32,7 +32,7 @@ export function TeamsButton({ const selectedKeys = new Set([teamId || user.id]); const handleSelect = (id: Key) => { - router.push(id === user.id ? '/dashboard' : `/teams/${id}/dashboard`); + router.push(id === user.id ? '/websites' : `/teams/${id}/websites`); }; if (!result?.count) { diff --git a/src/components/messages.ts b/src/components/messages.ts index d2e9c2d0..91743807 100644 --- a/src/components/messages.ts +++ b/src/components/messages.ts @@ -282,6 +282,7 @@ export const labels = defineMessages({ defaultMessage: 'Track your goals for pageviews and events.', }, journey: { id: 'label.journey', defaultMessage: 'Journey' }, + journeys: { id: 'label.journeys', defaultMessage: 'Journeys' }, journeyDescription: { id: 'label.journey-description', defaultMessage: 'Understand how users navigate through your website.', diff --git a/src/components/metrics/ChangeLabel.module.css b/src/components/metrics/ChangeLabel.module.css index fb3112de..b3bc7433 100644 --- a/src/components/metrics/ChangeLabel.module.css +++ b/src/components/metrics/ChangeLabel.module.css @@ -2,7 +2,7 @@ display: flex; align-items: center; gap: 5px; - font-size: 12px; + font-size: var(--font-size); padding: 0.1em 0.5em; border-radius: 5px; color: var(--base500); diff --git a/src/lib/schema.ts b/src/lib/schema.ts index 5edf7a46..7402af8c 100644 --- a/src/lib/schema.ts +++ b/src/lib/schema.ts @@ -110,6 +110,15 @@ export const funnelReportSchema = z.object({ }), }); +export const journeyReportSchema = z.object({ + type: z.literal('journey'), + parameters: z.object({ + steps: z.coerce.number().positive(), + startStep: z.string().optional(), + endStep: z.string().optional(), + }), +}); + export const reportBaseSchema = z.object({ websiteId: z.string().uuid(), type: reportTypeParam, @@ -120,6 +129,7 @@ export const reportBaseSchema = z.object({ export const reportTypeSchema = z.discriminatedUnion('type', [ goalReportSchema, funnelReportSchema, + journeyReportSchema, ]); export const reportSchema = z.intersection(reportBaseSchema, reportTypeSchema);