| name | new-page |
| description | Scaffold a complete new Next.js dashboard page for the WealthWise web app. Triggers when asked to "add a page", "create a dashboard screen", "build a UI for <feature>", or scaffold any new frontend feature end-to-end. Does not trigger for API-only or backend tasks.
|
Scaffold a complete new dashboard page for the WealthWise web app following all project conventions.
The page/feature name is provided in the task prompt.
Scope
Create all four layers in this order:
1. TanStack Query hooks — apps/web/src/hooks/use-<entity>.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { apiClient } from '@/lib/api-client';
import { toast } from 'sonner';
export const use<Entity>s = () =>
useQuery({
queryKey: ['<entities>'],
queryFn: () => apiClient.get<{ data: <Entity>Response[] }>('<entities>'),
});
export const use<Entity> = (id: string) =>
useQuery({
queryKey: ['<entities>', id],
queryFn: () => apiClient.get<{ data: <Entity>Response }>(`<entities>/${id}`),
enabled: !!id,
});
export const useCreate<Entity> = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: Create<Entity>Input) =>
apiClient.post<{ data: <Entity>Response }>('<entities>', data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['<entities>'] });
toast.success('<Entity> created successfully');
},
onError: () => toast.error('Failed to create <entity>'),
});
};
Rules:
- Always use
apiClient from @/lib/api-client — never raw fetch
- TanStack Query v5 syntax (
useQuery, useMutation)
- Sonner toast on every mutation success AND error
- Proper cache invalidation in
onSuccess
2. Components — apps/web/src/components/<entity>/
Create at minimum:
<Entity>List.tsx — table or card list with loading skeleton, error state, empty state
<Entity>Form.tsx — create/edit form
All components must:
- Use React Hook Form +
zodResolver with schema from @wealthwise/shared-types
- Use shadcn/ui from
components/ui/ before building custom elements
- Handle loading (use
<Skeleton />), error, and empty states explicitly
- Work in both light and dark themes (always check
.dark class)
- Named exports only (no default exports)
- Tailwind CSS only — no inline styles except dynamic CSS custom properties
3. Page — apps/web/src/app/(dashboard)/<route>/page.tsx
import { Metadata } from 'next';
import { Suspense } from 'react';
export const metadata: Metadata = {
title: '<Feature> | WealthWise',
};
export default function <Feature>Page() {
return (
<Suspense fallback={<SkeletonFallback />}>
<Feature>Content />
</Suspense>
);
}
Default export required by Next.js.
4. Sidebar link — apps/web/src/components/layout/sidebar.tsx
- Add nav item with appropriate
lucide-react icon
- Place in correct section (main nav vs settings)
- Use the correct route path
Standards
- No
any types. No raw fetch. No useEffect + fetch patterns.
- Named exports everywhere except the page itself.
- Every form uses
zodResolver with schemas from @wealthwise/shared-types.
- Strict null checks — handle
undefined from queries explicitly.
Verification
After scaffolding, run:
npx turbo lint --filter=@wealthwise/web
npx turbo test --filter=@wealthwise/web
Report all created file paths and the URL path for the new page.