diff --git a/src/app/(main)/TopNav.test.tsx b/src/app/(main)/TopNav.test.tsx
new file mode 100644
index 000000000..be6d0f479
--- /dev/null
+++ b/src/app/(main)/TopNav.test.tsx
@@ -0,0 +1,74 @@
+import { beforeEach, expect, test, vi } from 'vitest';
+import { render, screen } from '@/test/render';
+import { TopNav } from './TopNav';
+
+const mockPush = vi.fn();
+const mockRenderUrl = vi.fn((path: string) => path);
+const mockUseNavigation = vi.fn();
+
+vi.mock('@/components/hooks', () => ({
+ useNavigation: () => mockUseNavigation(),
+}));
+
+vi.mock('@/components/input/TeamsButton', () => ({
+ TeamsButton: () =>
TeamsButton
,
+}));
+
+vi.mock('@/components/input/WebsiteSelect', () => ({
+ WebsiteSelect: ({ onChange }: { onChange: (value: string | number | null) => void }) => (
+ <>
+
+
+ >
+ ),
+}));
+
+vi.mock('@/components/input/LinkSelect', () => ({
+ LinkSelect: () => null,
+}));
+
+vi.mock('@/components/input/PixelSelect', () => ({
+ PixelSelect: () => null,
+}));
+
+vi.mock('@/components/input/BoardSelect', () => ({
+ BoardSelect: () => null,
+}));
+
+beforeEach(() => {
+ mockPush.mockReset();
+ mockRenderUrl.mockClear();
+ mockUseNavigation.mockReturnValue({
+ websiteId: 'website-1',
+ linkId: undefined,
+ pixelId: undefined,
+ boardId: undefined,
+ teamId: undefined,
+ router: {
+ push: mockPush,
+ },
+ renderUrl: mockRenderUrl,
+ });
+});
+
+test('does not navigate when the website select emits null', async () => {
+ const { user } = render();
+
+ await user.click(screen.getByRole('button', { name: 'clear website' }));
+
+ expect(mockRenderUrl).not.toHaveBeenCalled();
+ expect(mockPush).not.toHaveBeenCalled();
+});
+
+test('navigates when the website select emits a website id', async () => {
+ const { user } = render();
+
+ await user.click(screen.getByRole('button', { name: 'select website' }));
+
+ expect(mockRenderUrl).toHaveBeenCalledWith('/websites/website-2', false);
+ expect(mockPush).toHaveBeenCalledWith('/websites/website-2');
+});
diff --git a/src/app/(main)/TopNav.tsx b/src/app/(main)/TopNav.tsx
index a3f34d075..3d8092377 100644
--- a/src/app/(main)/TopNav.tsx
+++ b/src/app/(main)/TopNav.tsx
@@ -11,20 +11,28 @@ import { WebsiteSelect } from '@/components/input/WebsiteSelect';
export function TopNav() {
const { websiteId, linkId, pixelId, boardId, teamId, router, renderUrl } = useNavigation();
+ const navigateToEntity = (basePath: string, value: string | number | null) => {
+ if (value === null || value === undefined || value === '') {
+ return;
+ }
+
+ router.push(renderUrl(`${basePath}/${value}`, false));
+ };
+
const handleWebsiteChange = (value: string | number | null) => {
- router.push(renderUrl(`/websites/${value}`, false));
+ navigateToEntity('/websites', value);
};
const handleLinkChange = (value: string | number | null) => {
- router.push(renderUrl(`/links/${value}`, false));
+ navigateToEntity('/links', value);
};
const handlePixelChange = (value: string | number | null) => {
- router.push(renderUrl(`/pixels/${value}`, false));
+ navigateToEntity('/pixels', value);
};
const handleBoardChange = (value: string | number | null) => {
- router.push(renderUrl(`/boards/${value}`, false));
+ navigateToEntity('/boards', value);
};
return (
diff --git a/src/queries/prisma/website.ts b/src/queries/prisma/website.ts
index 3b647ba2d..5c2117827 100644
--- a/src/queries/prisma/website.ts
+++ b/src/queries/prisma/website.ts
@@ -4,6 +4,7 @@ import prisma from '@/lib/prisma';
import redis from '@/lib/redis';
import { sanitizeSortFilters } from '@/lib/sort';
import type { QueryFilters } from '@/lib/types';
+import { z } from 'zod';
const WEBSITE_SORT_FIELDS = ['name', 'domain', 'createdAt'] as const;
@@ -12,6 +13,10 @@ export async function findWebsite(criteria: Prisma.WebsiteFindUniqueArgs) {
}
export async function getWebsite(websiteId: string) {
+ if (!z.uuid().safeParse(websiteId).success) {
+ return null;
+ }
+
const website = await findWebsite({
where: {
id: websiteId,