---
name: kiranism-shadcn-dashboard
description: |
  Migration-oriented guide for building and evolving features in this Next.js 16 shadcn dashboard repo. Use this skill whenever the user wants to add dashboard pages, tables, forms, navigation, route handlers, or feature modules in this project, especially when moving from mock APIs to real persistence, replacing Clerk with Auth.js, adding Drizzle-backed CRUD, or implementing organization-aware RBAC. Even if the user only mentions a page, sidebar item, or "connect this to a real backend", use this skill because the repo's feature, auth, and data-layer conventions are tightly coupled.
---

# Dashboard Migration Guide

This skill is for evolving this starter from a demo template into an app-owned architecture.

Default target architecture:

- `Auth.js` for authentication and session access
- `Drizzle ORM` for persistence
- `src/app/api/**/route.ts` as the boundary for CRUD or BFF access
- app-owned `organization`, `membership`, `role`, and optional `permissions` data

Do not treat Clerk or `src/constants/mock-api*.ts` as the happy path. They are legacy template scaffolding that may still exist in the repo and should be migrated deliberately.

## Current Repo Reality

Before changing anything, ground the work in the repo's current state:

- `src/features/products/api/service.ts` and `src/features/users/api/service.ts` still call mock data
- `src/app/api/products/**` and `src/app/api/users/**` already exist as route-handler shells
- `src/hooks/use-nav.ts`, `src/proxy.ts`, and multiple pages/components are still coupled to Clerk
- there is no committed `auth.ts`, Drizzle schema, or shared app RBAC model yet

Work incrementally. Prefer a migration plan that:

1. introduces the auth shell
2. introduces the shared org and RBAC model
3. migrates one feature at a time from mock data to route handlers plus Drizzle

## Quick Reference

| Task | Default location |
| --- | --- |
| Auth.js config | `src/auth.ts` or project root `auth.ts` |
| Route protection | `src/proxy.ts` or `middleware.ts` |
| Drizzle schema | `src/db/schema/*.ts` or `src/db/schema.ts` |
| DB client | `src/lib/db.ts` or `src/db/index.ts` |
| Feature types | `src/features/<name>/api/types.ts` |
| Feature service | `src/features/<name>/api/service.ts` |
| Query options | `src/features/<name>/api/queries.ts` |
| Mutation options | `src/features/<name>/api/mutations.ts` |
| Route handlers | `src/app/api/<name>/route.ts` and `[id]/route.ts` |
| Feature components | `src/features/<name>/components/` |
| Dashboard page | `src/app/dashboard/<name>/page.tsx` |
| Nav config | `src/config/nav-config.ts` |
| RBAC types | `src/types/index.ts` plus app auth or membership types |

## Default Build Order For A Feature

When a user asks for a new feature or a migration from mocks, use this order unless the repo already has a stronger pattern:

### 1. Define persistence first

Create or extend Drizzle tables for:

- the feature entity
- organization ownership if data is tenant-scoped
- membership or permission tables if access depends on role

Keep entity rules close to the schema so later route handlers are straightforward.

### 2. Define feature API types

In `src/features/<name>/api/types.ts`, define:

- list filters
- list response shape
- detail response shape
- mutation payloads

Do not re-export types from mock files. Types should reflect the route contract or normalized entity shape.

Example:

```tsx
export interface Order {
  id: number;
  organizationId: string;
  customerName: string;
  status: 'pending' | 'paid' | 'cancelled';
  totalCents: number;
  createdAt: string;
  updatedAt: string;
}

export interface OrderFilters {
  page?: number;
  limit?: number;
  search?: string;
  status?: string;
  sort?: string;
}

export interface OrdersResponse {
  items: Order[];
  totalItems: number;
}

export interface OrderMutationPayload {
  customerName: string;
  status: Order['status'];
  totalCents: number;
}
```

### 3. Implement route handlers as the app boundary

Use `src/app/api/<feature>/route.ts` for list and create, and `src/app/api/<feature>/[id]/route.ts` for detail, update, and delete.

Route handlers should:

- read Auth.js session or server auth helper
- resolve active `organization`
- verify `membership` or `role`
- read and write via Drizzle
- return JSON contracts that match `types.ts`

Keep them thin. Put reusable query logic in a server-side helper when it grows beyond one route.

Example shape:

```tsx
import { NextRequest, NextResponse } from 'next/server';
import { auth } from '@/auth';
import { db } from '@/lib/db';

export async function GET(request: NextRequest) {
  const session = await auth();
  if (!session?.user) {
    return NextResponse.json({ message: 'Unauthorized' }, { status: 401 });
  }

  const { searchParams } = request.nextUrl;
  const page = Number(searchParams.get('page') ?? 1);
  const limit = Number(searchParams.get('limit') ?? 10);

  // Resolve active organization and role before querying.
  const data = await listOrdersForOrganization({
    db,
    userId: session.user.id,
    page,
    limit
  });

  return NextResponse.json(data);
}
```

### 4. Implement feature service with `apiClient`

`service.ts` should call the local route handlers, not Drizzle directly and not mock stores.

```tsx
import { apiClient } from '@/lib/api-client';
import type {
  OrderFilters,
  OrdersResponse,
  Order,
  OrderMutationPayload
} from './types';

export async function getOrders(filters: OrderFilters): Promise<OrdersResponse> {
  const searchParams = new URLSearchParams();
  if (filters.page) searchParams.set('page', String(filters.page));
  if (filters.limit) searchParams.set('limit', String(filters.limit));
  if (filters.search) searchParams.set('search', filters.search);
  if (filters.status) searchParams.set('status', filters.status);
  if (filters.sort) searchParams.set('sort', filters.sort);

  return apiClient<OrdersResponse>(`/orders?${searchParams.toString()}`);
}

export async function getOrderById(id: number): Promise<Order> {
  return apiClient<Order>(`/orders/${id}`);
}

export async function createOrder(data: OrderMutationPayload): Promise<Order> {
  return apiClient<Order>('/orders', { method: 'POST', body: JSON.stringify(data) });
}
```

### 5. Implement React Query options

Use `queryOptions` and `mutationOptions` as the base abstraction.

- `queries.ts` defines key factories and query options
- `mutations.ts` defines shared mutation options and cache invalidation
- components compose on top with `useSuspenseQuery` and `useMutation`

For every create, update, delete, assign, transfer, or return flow, treat post-mutation freshness as part of the feature contract, not as a nice-to-have. After a successful mutation, make sure all affected list views, detail views, timelines, counters, and dashboard summaries are synchronized immediately via `invalidateQueries`, `setQueryData`, optimistic updates, or a deliberate combination of them. Do not leave the user on stale data and do not rely on manual refresh as the normal recovery path.

See [references/query-abstractions.md](references/query-abstractions.md).

### 6. Build UI and pages

After the contracts are stable:

- build feature components under `src/features/<name>/components/`
- use server-prefetch plus `HydrationBoundary`
- add page route under `src/app/dashboard/<name>/page.tsx`
- wire navigation in `src/config/nav-config.ts`

## Auth.js And App-Owned RBAC

Use this vocabulary consistently:

- `user`: authenticated human actor
- `organization`: active tenant or workspace
- `membership`: relation between user and organization
- `role`: membership level such as `owner`, `admin`, or `member`
- `permissions`: optional fine-grained capabilities derived from role or stored explicitly

Do not use Clerk terms as if they still define the architecture.

### Auth boundary

The skill should guide agents to centralize auth in:

- `auth.ts` for Auth.js config and exported helpers
- route protection in `src/proxy.ts` or `middleware.ts`
- shared server helper for `requireSession`, `requireOrganization`, or `requireRole`

Example page protection:

```tsx
import { auth } from '@/auth';
import { redirect } from 'next/navigation';

export default async function Page() {
  const session = await auth();
  if (!session?.user) redirect('/auth/sign-in');

  return <div>Protected dashboard page</div>;
}
```

### Navigation visibility

Navigation should be filtered from app-owned session or membership data, not Clerk hooks.

If a nav item requires access, model it around app semantics:

```tsx
access: {
  requireOrganization: true,
  role: 'admin'
}
```

If the repo still uses `requireOrg`, `role`, or `permission` in `src/types/index.ts`, treat that as a migration seam. Update the types and filtering logic only when the task reaches navigation or RBAC.

### Plan and feature gates

Do not promise a drop-in replacement for Clerk Billing.

When a page is gated by plan or feature:

- describe it as app-owned entitlement data
- keep the check server-side
- use placeholders until a billing provider exists

Good language: "read the organization's active plan from Drizzle and gate the page accordingly."

Bad language: "use Clerk Billing" or "use `<Protect plan='pro'>`".

## Feature Page Pattern

Use the repo's existing feature layout, but keep the new data path:

```tsx
import { HydrationBoundary, dehydrate } from '@tanstack/react-query';
import { Suspense } from 'react';
import { getQueryClient } from '@/lib/query-client';
import { searchParamsCache } from '@/lib/searchparams';
import { ordersQueryOptions } from '../api/queries';
import { OrdersTable, OrdersTableSkeleton } from './orders-table';

export default function OrderListingPage() {
  const page = searchParamsCache.get('page');
  const perPage = searchParamsCache.get('perPage');
  const search = searchParamsCache.get('name');
  const sort = searchParamsCache.get('sort');

  const filters = {
    page,
    limit: perPage,
    ...(search && { search }),
    ...(sort && { sort })
  };

  const queryClient = getQueryClient();
  void queryClient.prefetchQuery(ordersQueryOptions(filters));

  return (
    <HydrationBoundary state={dehydrate(queryClient)}>
      <Suspense fallback={<OrdersTableSkeleton />}>
        <OrdersTable />
      </Suspense>
    </HydrationBoundary>
  );
}
```

The page flow stays the same. The difference is the backing source:

- before: component -> service -> mock store
- target: component -> service -> route handler -> Drizzle

## Forms

Forms still use the existing project pattern:

- `useAppForm`
- `useFormFields<T>()`
- Zod schema for submit validation
- `useMutation({ ...sharedMutationOptions })`

See [references/forms-guide.md](references/forms-guide.md).

When you show form examples, submit to service functions that hit route handlers. Do not wire forms straight to mock data and do not bypass auth or org checks.

## Charts, Themes, And UI

Keep the existing UI conventions unless the task says otherwise:

- charts use Recharts plus `ChartContainer`
- themes use CSS variables and `theme.config.ts`
- page headers go through `PageContainer`
- icons come only from `@/components/icons`

For chart work, prefer data fetched through a real server path or a clearly labeled placeholder fetch. Avoid new examples that import `delay` from mock utilities as the default approach.

## Legacy Migration Notes

Use these notes when the user is touching existing template code:

- `src/constants/mock-api.ts` and `src/constants/mock-api-users.ts` are demo-only sources
- `src/features/*/api/service.ts` is the intended seam for migrating consumers off mock data
- `src/app/api/products/**` and `src/app/api/users/**` are good first route-handler migrations
- `src/hooks/use-nav.ts`, `src/proxy.ts`, `src/components/layout/providers.tsx`, and auth-facing pages are likely Clerk hot spots

Recommend migrating feature-by-feature instead of flipping everything at once.

## Reference Files

Open only the references needed for the task:

- [references/route-handlers-drizzle-guide.md](references/route-handlers-drizzle-guide.md) for API boundary, Drizzle flow, and CRUD migration
- [references/authjs-org-rbac-guide.md](references/authjs-org-rbac-guide.md) for Auth.js, org membership, and RBAC
- [references/migration-from-clerk-and-mocks.md](references/migration-from-clerk-and-mocks.md) when the task is explicitly a migration
- [references/forms-guide.md](references/forms-guide.md) for TanStack Form usage
- [references/query-abstractions.md](references/query-abstractions.md) for React Query patterns
- [references/theming-guide.md](references/theming-guide.md) for theme work
- [references/charts-guide.md](references/charts-guide.md) for overview pages and chart slots

`references/mock-api-guide.md` is now legacy context only. Do not load it unless the user explicitly asks how the old demo data works.

## Code Conventions

- Use server components by default
- Use `cn()` for class merging
- Keep feature code inside `src/features/<name>/`
- Keep the API layer split as `types.ts` -> `service.ts` -> `queries.ts` -> `mutations.ts`
- Prefer `useSuspenseQuery` with server prefetch
- Use `mutationOptions` with `getQueryClient()`
- Every CRUD mutation must define explicit post-success cache sync for affected queries
- Use `apiClient` for local route-handler calls
- Keep auth and role checks server-side for real protection
- Avoid direct imports from `src/constants/mock-api*`
- Avoid direct use of Clerk helpers or components in new examples

## What Good Guidance Looks Like

When this skill is working well, it should guide the agent toward:

- Auth.js session helpers instead of Clerk auth primitives
- Drizzle schema and route handlers instead of mock stores
- organization-aware server checks instead of client-only vendor hooks
- incremental migration steps that match the repo's real starting point
