diff --git a/web/src/app/router.tsx b/web/src/app/router.tsx index 1a2d175b..64df10cf 100644 --- a/web/src/app/router.tsx +++ b/web/src/app/router.tsx @@ -393,6 +393,9 @@ const dashboardStarsRoute = createRoute({ getParentRoute: () => rootRoute, path: 'dashboard/stars', beforeLoad: requireAuth, + validateSearch: (search: Record): { page?: number } => ({ + page: typeof search.page === 'number' && search.page > 0 ? search.page : undefined, + }), component: MyStarsPage, }) @@ -400,6 +403,9 @@ const dashboardSubscriptionsRoute = createRoute({ getParentRoute: () => rootRoute, path: 'dashboard/subscriptions', beforeLoad: requireAuth, + validateSearch: (search: Record): { page?: number } => ({ + page: typeof search.page === 'number' && search.page > 0 ? search.page : undefined, + }), component: MySubscriptionsPage, }) diff --git a/web/src/pages/dashboard/stars.test.ts b/web/src/pages/dashboard/stars.test.ts index e54171b5..907e5935 100644 --- a/web/src/pages/dashboard/stars.test.ts +++ b/web/src/pages/dashboard/stars.test.ts @@ -5,9 +5,21 @@ import { fireEvent, render, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' const navigate = vi.fn() +const { useMyStarsPage } = vi.hoisted(() => ({ + useMyStarsPage: vi.fn(() => ({ + data: { + items: [{ id: 1, namespace: 'team-a', slug: 'demo skill' }], + total: 1, + page: 0, + size: 12, + }, + isLoading: false, + })), +})) vi.mock('@tanstack/react-router', () => ({ useNavigate: () => navigate, + useSearch: () => ({ page: 2 }), useLocation: () => ({ pathname: '/dashboard/stars', searchStr: '?page=2', @@ -34,15 +46,7 @@ vi.mock('@/shared/components/pagination', () => ({ })) vi.mock('@/shared/hooks/use-user-queries', () => ({ - useMyStarsPage: () => ({ - data: { - items: [{ id: 1, namespace: 'team-a', slug: 'demo skill' }], - total: 1, - page: 0, - size: 12, - }, - isLoading: false, - }), + useMyStarsPage, })) vi.mock('@/shared/ui/card', () => ({ @@ -72,4 +76,10 @@ describe('MyStarsPage', () => { search: { returnTo: '/dashboard/stars?page=2#saved' }, }) }) + + it('uses the URL page as the query source', () => { + render(createElement(MyStarsPage)) + + expect(useMyStarsPage).toHaveBeenCalledWith({ page: 2, size: 12 }) + }) }) diff --git a/web/src/pages/dashboard/stars.tsx b/web/src/pages/dashboard/stars.tsx index 7c07bef6..cbaacab5 100644 --- a/web/src/pages/dashboard/stars.tsx +++ b/web/src/pages/dashboard/stars.tsx @@ -1,5 +1,4 @@ -import { useState } from 'react' -import { useLocation, useNavigate } from '@tanstack/react-router' +import { useLocation, useNavigate, useSearch } from '@tanstack/react-router' import { useTranslation } from 'react-i18next' import { SkillCard } from '@/features/skill/skill-card' import { Pagination } from '@/shared/components/pagination' @@ -14,7 +13,8 @@ export function MyStarsPage() { const { t } = useTranslation() const navigate = useNavigate() const location = useLocation() - const [page, setPage] = useState(0) + const search = useSearch({ from: '/dashboard/stars' }) + const page = search.page ?? 0 const { data, isLoading } = useMyStarsPage({ page, size: PAGE_SIZE }) const skills = data?.items ?? [] const totalPages = data ? Math.max(Math.ceil(data.total / data.size), 1) : 1 @@ -50,7 +50,14 @@ export function MyStarsPage() { ))} {data && data.total > PAGE_SIZE ? ( - + navigate({ + to: '/dashboard/stars', + search: { page: nextPage > 0 ? nextPage : undefined }, + })} + /> ) : null} )} diff --git a/web/src/pages/dashboard/subscriptions.test.ts b/web/src/pages/dashboard/subscriptions.test.ts index 9dd3b2c1..922278ed 100644 --- a/web/src/pages/dashboard/subscriptions.test.ts +++ b/web/src/pages/dashboard/subscriptions.test.ts @@ -5,9 +5,21 @@ import { fireEvent, render, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' const navigate = vi.fn() +const { useMySubscriptionsPage } = vi.hoisted(() => ({ + useMySubscriptionsPage: vi.fn(() => ({ + data: { + items: [{ id: 1, namespace: 'team-a', slug: 'demo-skill' }], + total: 1, + page: 0, + size: 12, + }, + isLoading: false, + })), +})) vi.mock('@tanstack/react-router', () => ({ useNavigate: () => navigate, + useSearch: () => ({ page: 1 }), useLocation: () => ({ pathname: '/dashboard/subscriptions', searchStr: '?page=1', @@ -29,15 +41,7 @@ vi.mock('@/features/skill/skill-card', () => ({ vi.mock('@/shared/components/pagination', () => ({ Pagination: () => null })) vi.mock('@/shared/hooks/use-user-queries', () => ({ - useMySubscriptionsPage: () => ({ - data: { - items: [{ id: 1, namespace: 'team-a', slug: 'demo-skill' }], - total: 1, - page: 0, - size: 12, - }, - isLoading: false, - }), + useMySubscriptionsPage, })) vi.mock('@/shared/ui/card', () => ({ Card: ({ children }: { children: unknown }) => children })) vi.mock('@/shared/components/dashboard-page-header', () => ({ DashboardPageHeader: () => null })) @@ -57,4 +61,10 @@ describe('MySubscriptionsPage', () => { search: { returnTo: '/dashboard/subscriptions?page=1' }, }) }) + + it('uses the URL page as the query source', () => { + render(createElement(MySubscriptionsPage)) + + expect(useMySubscriptionsPage).toHaveBeenCalledWith({ page: 1, size: 12 }) + }) }) diff --git a/web/src/pages/dashboard/subscriptions.tsx b/web/src/pages/dashboard/subscriptions.tsx index d128ccc1..85955a28 100644 --- a/web/src/pages/dashboard/subscriptions.tsx +++ b/web/src/pages/dashboard/subscriptions.tsx @@ -1,5 +1,4 @@ -import { useState } from 'react' -import { useLocation, useNavigate } from '@tanstack/react-router' +import { useLocation, useNavigate, useSearch } from '@tanstack/react-router' import { useTranslation } from 'react-i18next' import { SkillCard } from '@/features/skill/skill-card' import { Pagination } from '@/shared/components/pagination' @@ -14,7 +13,8 @@ export function MySubscriptionsPage() { const { t } = useTranslation() const navigate = useNavigate() const location = useLocation() - const [page, setPage] = useState(0) + const search = useSearch({ from: '/dashboard/subscriptions' }) + const page = search.page ?? 0 const { data, isLoading } = useMySubscriptionsPage({ page, size: PAGE_SIZE }) const skills = data?.items ?? [] const totalPages = data ? Math.max(Math.ceil(data.total / data.size), 1) : 1 @@ -50,7 +50,14 @@ export function MySubscriptionsPage() { ))} {data && data.total > PAGE_SIZE ? ( - + navigate({ + to: '/dashboard/subscriptions', + search: { page: nextPage > 0 ? nextPage : undefined }, + })} + /> ) : null} )}