diff --git a/package.components.json b/package.components.json index e0f8a105b..09c0cf52c 100644 --- a/package.components.json +++ b/package.components.json @@ -1,24 +1,10 @@ { "name": "@umami/components", - "version": "0.116.0", + "version": "0.121.0", "description": "Umami React components.", "author": "Mike Cao ", "license": "MIT", "type": "module", "main": "./index.js", - "types": "./index.d.ts", - "dependencies": { - "chart.js": "^4.5.0", - "chartjs-adapter-date-fns": "^3.0.0", - "colord": "^2.9.2", - "jsonwebtoken": "^9.0.2", - "lucide-react": "^0.542.0", - "pure-rand": "^7.0.1", - "react-simple-maps": "^2.3.0", - "react-use-measure": "^2.0.4", - "react-window": "^1.8.6", - "serialize-error": "^12.0.0", - "thenby": "^1.3.4", - "uuid": "^11.1.0" - } + "types": "./index.d.ts" } diff --git a/src/app/(main)/dashboard/DashboardPage.tsx b/src/app/(main)/dashboard/DashboardPage.tsx new file mode 100644 index 000000000..c05e411d6 --- /dev/null +++ b/src/app/(main)/dashboard/DashboardPage.tsx @@ -0,0 +1,17 @@ +'use client'; +import { Column } from '@umami/react-zen'; +import { PageHeader } from '@/components/common/PageHeader'; +import { useMessages } from '@/components/hooks'; +import { PageBody } from '@/components/common/PageBody'; + +export function DashboardPage() { + const { formatMessage, labels } = useMessages(); + + return ( + + + + + + ); +} diff --git a/src/app/(main)/dashboard/page.tsx b/src/app/(main)/dashboard/page.tsx new file mode 100644 index 000000000..e934e6ada --- /dev/null +++ b/src/app/(main)/dashboard/page.tsx @@ -0,0 +1,10 @@ +import { Metadata } from 'next'; +import { DashboardPage } from './DashboardPage'; + +export default async function () { + return ; +} + +export const metadata: Metadata = { + title: 'Dashboard', +}; diff --git a/src/app/(main)/websites/WebsitesDataTable.tsx b/src/app/(main)/websites/WebsitesDataTable.tsx index 612dc502b..93b1a7c79 100644 --- a/src/app/(main)/websites/WebsitesDataTable.tsx +++ b/src/app/(main)/websites/WebsitesDataTable.tsx @@ -1,20 +1,23 @@ import Link from 'next/link'; import { WebsitesTable } from './WebsitesTable'; import { DataGrid } from '@/components/common/DataGrid'; -import { useNavigation, useUserWebsitesQuery } from '@/components/hooks'; +import { useLoginQuery, useNavigation, useUserWebsitesQuery } from '@/components/hooks'; export function WebsitesDataTable({ + userId, teamId, allowEdit = true, allowView = true, showActions = true, }: { + userId?: string; teamId?: string; allowEdit?: boolean; allowView?: boolean; showActions?: boolean; }) { - const queryResult = useUserWebsitesQuery({ teamId }); + const { user } = useLoginQuery(); + const queryResult = useUserWebsitesQuery({ userId: userId || user?.id, teamId }); const { renderUrl } = useNavigation(); const renderLink = (row: any) => ( diff --git a/src/app/(main)/websites/WebsitesTable.tsx b/src/app/(main)/websites/WebsitesTable.tsx index 3fc22a988..3f781cb7c 100644 --- a/src/app/(main)/websites/WebsitesTable.tsx +++ b/src/app/(main)/websites/WebsitesTable.tsx @@ -3,6 +3,7 @@ import { Row, Text, Icon, DataTable, DataColumn, MenuItem } from '@umami/react-z import { useMessages, useNavigation } from '@/components/hooks'; import { MenuButton } from '@/components/input/MenuButton'; import { Eye, SquarePen } from '@/components/icons'; +import { Empty } from '@/components/common/Empty'; export function WebsitesTable({ data = [], @@ -10,20 +11,18 @@ export function WebsitesTable({ allowEdit, allowView, renderLink, - children, }: { data: Record[]; showActions?: boolean; allowEdit?: boolean; allowView?: boolean; renderLink?: (row: any) => ReactNode; - children?: ReactNode; }) { const { formatMessage, labels } = useMessages(); const { renderUrl } = useNavigation(); - if (!data?.length) { - return children; + if (data.length === 0) { + return ; } return ( diff --git a/src/app/(main)/websites/[websiteId]/WebsiteNav.tsx b/src/app/(main)/websites/[websiteId]/WebsiteNav.tsx index e0d5ffb00..8db82a585 100644 --- a/src/app/(main)/websites/[websiteId]/WebsiteNav.tsx +++ b/src/app/(main)/websites/[websiteId]/WebsiteNav.tsx @@ -18,10 +18,11 @@ import { import { useMessages, useNavigation } from '@/components/hooks'; import { SideMenu } from '@/components/common/SideMenu'; import { WebsiteSelect } from '@/components/input/WebsiteSelect'; +import { Text } from '@umami/react-zen'; export function WebsiteNav({ websiteId }: { websiteId: string }) { const { formatMessage, labels } = useMessages(); - const { pathname, renderUrl, teamId } = useNavigation(); + const { pathname, renderUrl, teamId, router } = useNavigation(); const renderPath = (path: string) => renderUrl(`/websites/${websiteId}${path}`, { @@ -143,13 +144,31 @@ export function WebsiteNav({ websiteId }: { websiteId: string }) { }, ]; + const handleChange = (value: string) => { + router.push(renderUrl(`/websites/${value}`)); + }; + + const renderValue = (value: any) => { + return ( + + {value?.selectedItem?.name} + + ); + }; + const selectedKey = items .flatMap(e => e.items) .find(({ path }) => path && pathname.endsWith(path.split('?')[0]))?.id; return ( - + ); } diff --git a/src/app/api/me/websites/route.ts b/src/app/api/me/websites/route.ts index 41a8756d2..2851dab46 100644 --- a/src/app/api/me/websites/route.ts +++ b/src/app/api/me/websites/route.ts @@ -1,12 +1,13 @@ import { z } from 'zod'; import { pagingParams } from '@/lib/schema'; -import { getUserWebsites } from '@/queries'; +import { getAllUserWebsitesIncludingTeamOwner, getUserWebsites } from '@/queries'; import { json } from '@/lib/response'; import { parseRequest, getQueryFilters } from '@/lib/request'; export async function GET(request: Request) { const schema = z.object({ ...pagingParams, + includeTeams: z.string().optional(), }); const { auth, query, error } = await parseRequest(request, schema); @@ -17,7 +18,9 @@ export async function GET(request: Request) { const filters = await getQueryFilters(query); - const websites = await getUserWebsites(auth.user.id, filters); + if (query.includeTeams) { + return json(await getAllUserWebsitesIncludingTeamOwner(auth.user.id, filters)); + } - return json(websites); + return json(await getUserWebsites(auth.user.id, filters)); } diff --git a/src/app/api/users/[userId]/websites/route.ts b/src/app/api/users/[userId]/websites/route.ts index 0c10682d8..3e81f4f4c 100644 --- a/src/app/api/users/[userId]/websites/route.ts +++ b/src/app/api/users/[userId]/websites/route.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; import { unauthorized, json } from '@/lib/response'; -import { getUserWebsites } from '@/queries/prisma/website'; +import { getAllUserWebsitesIncludingTeamOwner, getUserWebsites } from '@/queries/prisma/website'; import { pagingParams, searchParams } from '@/lib/schema'; import { getQueryFilters, parseRequest } from '@/lib/request'; @@ -8,6 +8,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ user const schema = z.object({ ...pagingParams, ...searchParams, + includeTeams: z.string().optional(), }); const { auth, query, error } = await parseRequest(request, schema); @@ -24,7 +25,9 @@ export async function GET(request: Request, { params }: { params: Promise<{ user const filters = await getQueryFilters(query); - const websites = await getUserWebsites(userId, filters); + if (query.includeTeams) { + return json(await getAllUserWebsitesIncludingTeamOwner(auth.user.id, filters)); + } - return json(websites); + return json(await getUserWebsites(userId, filters)); } diff --git a/src/app/api/websites/[websiteId]/stats/route.ts b/src/app/api/websites/[websiteId]/stats/route.ts index 7041f484c..f3b1a7081 100644 --- a/src/app/api/websites/[websiteId]/stats/route.ts +++ b/src/app/api/websites/[websiteId]/stats/route.ts @@ -40,5 +40,5 @@ export async function GET( endDate, }); - return json({ ...data[0], comparison }); + return json({ ...data, comparison }); } diff --git a/src/components/input/WebsiteSelect.tsx b/src/components/input/WebsiteSelect.tsx index 0df4c1af8..103e5c592 100644 --- a/src/components/input/WebsiteSelect.tsx +++ b/src/components/input/WebsiteSelect.tsx @@ -1,30 +1,26 @@ import { useState } from 'react'; -import { Select, SelectProps, ListItem, Text } from '@umami/react-zen'; -import { - useUserWebsitesQuery, - useWebsiteQuery, - useNavigation, - useMessages, -} from '@/components/hooks'; +import { Select, SelectProps, ListItem } from '@umami/react-zen'; +import { useUserWebsitesQuery, useMessages, useLoginQuery } from '@/components/hooks'; import { Empty } from '@/components/common/Empty'; export function WebsiteSelect({ websiteId, teamId, + onChange, + includeTeams, ...props }: { websiteId?: string; teamId?: string; + includeTeams?: boolean; } & SelectProps) { const { formatMessage, messages } = useMessages(); - const { router, renderUrl } = useNavigation(); const [search, setSearch] = useState(''); - const { data: website } = useWebsiteQuery(websiteId); - const { data, isLoading } = useUserWebsitesQuery({ teamId }, { search, pageSize: 5 }); - - const handleSelect = (value: any) => { - router.push(renderUrl(`/websites/${value}`)); - }; + const { user } = useLoginQuery(); + const { data, isLoading } = useUserWebsitesQuery( + { userId: user?.id, teamId }, + { search, pageSize: 5, includeTeams }, + ); const handleSearch = (value: string) => { setSearch(value); @@ -37,24 +33,17 @@ export function WebsiteSelect({ return ( diff --git a/src/index.ts b/src/index.ts index f72bece02..ab6f0946c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -64,6 +64,10 @@ export * from '@/components/common/SectionHeader'; export * from '@/components/common/SideMenu'; export * from '@/components/common/TypeConfirmationForm'; +export * from '@/components/input/ActionButton'; +export * from '@/components/input/DateFilter'; +export * from '@/components/input/DownloadButton'; +export * from '@/components/input/ExportButton'; export * from '@/components/input/FilterButtons'; export * from '@/components/input/TeamsButton'; export * from '@/components/input/ProfileButton'; diff --git a/src/queries/prisma/website.ts b/src/queries/prisma/website.ts index fc10bcfd8..6a0c0913c 100644 --- a/src/queries/prisma/website.ts +++ b/src/queries/prisma/website.ts @@ -46,46 +46,34 @@ export async function getWebsites( return pagedQuery('website', { ...criteria, where }, filters); } -export async function getAllWebsites(userId: string) { - return prisma.client.website.findMany({ - where: { - OR: [ - { userId }, - { - team: { - deletedAt: null, - teamUser: { - some: { - userId, +export async function getAllUserWebsitesIncludingTeamOwner( + userId: string, + filters?: QueryFilters, +): Promise> { + return getWebsites( + { + where: { + OR: [ + { userId }, + { + team: { + deletedAt: null, + members: { + some: { + role: ROLES.teamOwner, + userId, + }, }, }, }, - }, - ], - deletedAt: null, + ], + }, }, - }); -} - -export async function getAllUserWebsitesIncludingTeamOwner(userId: string) { - return prisma.client.website.findMany({ - where: { - OR: [ - { userId }, - { - team: { - deletedAt: null, - teamUser: { - some: { - role: ROLES.teamOwner, - userId, - }, - }, - }, - }, - ], + { + orderBy: 'name', + ...filters, }, - }); + ); } export async function getUserWebsites( diff --git a/src/queries/sql/getWebsiteStats.ts b/src/queries/sql/getWebsiteStats.ts index 925c40818..79fb8bbe9 100644 --- a/src/queries/sql/getWebsiteStats.ts +++ b/src/queries/sql/getWebsiteStats.ts @@ -58,7 +58,7 @@ async function relationalQuery( ) as t `, queryParams, - ); + ).then(result => result?.[0]); } async function clickhouseQuery(