Claude Code Plugins

Community-maintained marketplace

Feedback

nextjs-module-builder

@JKKN-Institutions/Kenavo
0
0

Complete workflow for building new modules in Next.js 15 (App Router) with Supabase backend. Use when creating CRUD features, data management modules, or any new feature module following the 5-layer architecture (Types → Services → Hooks → Components → Pages). Covers TypeScript types, Supabase services, React hooks, Shadcn/UI components, and permission-based routing.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name nextjs-module-builder
description Complete workflow for building new modules in Next.js 15 (App Router) with Supabase backend. Use when creating CRUD features, data management modules, or any new feature module following the 5-layer architecture (Types → Services → Hooks → Components → Pages). Covers TypeScript types, Supabase services, React hooks, Shadcn/UI components, and permission-based routing.

Next.js Module Builder

Build complete feature modules following MyJKKN's standardized 5-layer architecture for Next.js 15 + Supabase applications.

Architecture Overview

Every module follows a strict 5-layer pattern:

  1. Types Layer (types/) - TypeScript interfaces and DTOs
  2. Service Layer (lib/services/) - Supabase database operations and business logic
  3. Hooks Layer (hooks/) - React state management and data fetching
  4. Components Layer (_components/) - Reusable UI components with Shadcn/UI
  5. Pages Layer (app/(routes)/) - Route handlers with Server Components

Workflow Decision Tree

Start here for every new module:

  1. Is database schema defined?

    • NO → Read references/database-patterns.md and create schema first
    • YES → Continue to step 2
  2. Are TypeScript types needed?

    • YES → Follow Step 1: Types Layer below
    • NO → Skip to step 3
  3. Need database operations?

    • YES → Follow Step 2: Service Layer below
    • NO → Skip to step 4
  4. Need state management?

    • YES → Follow Step 3: Hooks Layer below
    • NO → Skip to step 5
  5. Need UI components?

    • YES → Follow Step 4: Components Layer below
    • NO → Skip to step 6
  6. Need pages/routes?

    • YES → Follow Step 5: Pages Layer below
    • Then proceed to step 7
  7. Configure permissions

    • Follow Step 6: Permissions & Navigation

Step 1: Types Layer (20-30 min)

Create types/[module-name].ts with these interfaces:

// Main entity interface - include ALL database fields
export interface Entity {
  id: string;
  institution_id: string;
  name: string;
  // ... your entity fields
  is_active: boolean;
  created_at: string;
  updated_at: string;
  created_by?: string;
  updated_by?: string;
}

// Create DTO - only fields user provides
export interface CreateEntityDto {
  institution_id: string;
  name: string;
  // ... only user-provided fields
}

// Update DTO - all fields optional except id
export interface UpdateEntityDto {
  id: string;
  name?: string;
  // ... optional update fields
  is_active?: boolean;
}

// Filter interface - for search and filtering
export interface EntityFilters {
  institution_id?: string;
  search?: string;
  is_active?: boolean;
  // ... filter fields
}

// Response interface - for paginated lists
export interface EntityResponse {
  data: Entity[];
  total: number;
  page: number;
  pageSize: number;
}

Detailed patterns: See references/typescript-patterns.md

Step 2: Service Layer (45-60 min)

Create lib/services/[module]/[entity]-service.ts:

import { createClientSupabaseClient } from '@/lib/supabase/client';
import type { CreateEntityDto, UpdateEntityDto, EntityFilters } from '@/types/[module]';

export class EntityService {
  private static supabase = createClientSupabaseClient();

  // GET with pagination and filters
  static async getEntities(filters: EntityFilters = {}, page = 1, pageSize = 10) {
    try {
      let query = this.supabase.from('entities').select('*', { count: 'exact' });

      // Apply filters
      if (filters.institution_id) {
        query = query.eq('institution_id', filters.institution_id);
      }
      if (filters.search) {
        query = query.ilike('name', `%${filters.search}%`);
      }
      if (filters.is_active !== undefined) {
        query = query.eq('is_active', filters.is_active);
      }

      // Pagination
      const from = (page - 1) * pageSize;
      const to = from + pageSize - 1;
      query = query.range(from, to);

      const { data, error, count } = await query;
      if (error) throw error;

      return { data: data || [], total: count || 0, page, pageSize };
    } catch (error) {
      console.error('[module/entity] Error fetching:', error);
      throw error;
    }
  }

  // GET by ID
  static async getEntityById(id: string) {
    try {
      const { data, error } = await this.supabase
        .from('entities')
        .select('*')
        .eq('id', id)
        .single();

      if (error) throw error;
      return data;
    } catch (error) {
      console.error('[module/entity] Error fetching by ID:', error);
      throw error;
    }
  }

  // CREATE
  static async createEntity(dto: CreateEntityDto) {
    try {
      const { data, error } = await this.supabase
        .from('entities')
        .insert([dto])
        .select()
        .single();

      if (error) throw error;
      console.log('[module/entity] Created:', data.id);
      return data;
    } catch (error) {
      console.error('[module/entity] Error creating:', error);
      throw error;
    }
  }

  // UPDATE
  static async updateEntity(dto: UpdateEntityDto) {
    try {
      const { id, ...updates } = dto;
      const { data, error } = await this.supabase
        .from('entities')
        .update(updates)
        .eq('id', id)
        .select()
        .single();

      if (error) throw error;
      console.log('[module/entity] Updated:', id);
      return data;
    } catch (error) {
      console.error('[module/entity] Error updating:', error);
      throw error;
    }
  }

  // DELETE (soft delete recommended)
  static async deleteEntity(id: string) {
    try {
      // Soft delete: set is_active = false
      const { error } = await this.supabase
        .from('entities')
        .update({ is_active: false })
        .eq('id', id);

      if (error) throw error;
      console.log('[module/entity] Deleted:', id);
      return true;
    } catch (error) {
      console.error('[module/entity] Error deleting:', error);
      throw error;
    }
  }
}

Detailed patterns: See references/service-patterns.md

Step 3: Hooks Layer (30-45 min)

Create hooks/[module]/use-[entity].ts:

'use client';

import { useState, useEffect, useCallback } from 'react';
import { EntityService } from '@/lib/services/[module]/[entity]-service';
import type { Entity, EntityFilters } from '@/types/[module]';
import { usePermissions } from '@/hooks/use-permissions';

export function useEntities(filters: EntityFilters = {}) {
  const [entities, setEntities] = useState<Entity[]>([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [total, setTotal] = useState(0);
  const [page, setPage] = useState(1);
  const pageSize = 10;

  const { userProfile } = usePermissions();

  const fetchEntities = useCallback(async () => {
    try {
      setLoading(true);
      setError(null);

      // Auto-apply institution filter
      const effectiveFilters = {
        ...filters,
        institution_id: filters.institution_id || userProfile?.institution_id
      };

      const response = await EntityService.getEntities(
        effectiveFilters,
        page,
        pageSize
      );

      setEntities(response.data);
      setTotal(response.total);
    } catch (err) {
      const message = err instanceof Error ? err.message : 'Failed to fetch';
      setError(message);
      console.error('[hooks/entity] Fetch error:', err);
    } finally {
      setLoading(false);
    }
  }, [filters, page, userProfile?.institution_id]);

  useEffect(() => {
    fetchEntities();
  }, [fetchEntities]);

  return {
    entities,
    loading,
    error,
    total,
    page,
    setPage,
    pageSize,
    refetch: fetchEntities
  };
}

Detailed patterns: See references/hooks-patterns.md

Step 4: Components Layer (90-120 min)

Required components in app/(routes)/[module]/_components/:

  1. data-table-schema.ts - Zod validation schema
  2. columns.tsx - TanStack Table column definitions
  3. [entity]-data-table.tsx - Data table wrapper component
  4. [entity]-form.tsx - Create/Edit form with React Hook Form
  5. [entity]-filters.tsx - Search and filter controls
  6. row-actions.tsx - Edit/Delete action menu

Complete component code examples: See references/component-patterns.md

Step 5: Pages Layer (45-60 min)

Required pages:

  1. page.tsx - List view with filters and data table
  2. new/page.tsx - Create form page
  3. [id]/edit/page.tsx - Edit form page
  4. [id]/page.tsx - Detail view page (optional)

Each page must include:

  • ContentLayout wrapper
  • Breadcrumb navigation
  • PermissionGuard for access control
  • Proper error handling and loading states

Complete page code examples: See references/page-patterns.md

Step 6: Permissions & Navigation (20-30 min)

1. Define Permissions

Add to lib/sidebarMenuLink.ts:

export const MENU_PERMISSIONS = {
  '[module].[entity].view': ['super_admin', 'admin', 'faculty'],
  '[module].[entity].create': ['super_admin', 'admin'],
  '[module].[entity].edit': ['super_admin', 'admin'],
  '[module].[entity].delete': ['super_admin'],
};

2. Add Menu Item

{
  groupLabel: 'Module',
  menus: [
    {
      label: 'Entities',
      href: '/[module]/entities',
      permission: '[module].[entity].view'
    }
  ]
}

3. Apply Guards

<PermissionGuard module="[module].[entity]" action="create">
  <Button>Create</Button>
</PermissionGuard>

// Or use shorthand components
<CanCreate module="[module].[entity]">
  <Button>Create</Button>
</CanCreate>

Complete permission setup: See references/permission-patterns.md

File Structure

app/(routes)/[module]/
├── page.tsx                    # List view
├── new/page.tsx               # Create form
├── [id]/
│   ├── page.tsx              # Detail view (optional)
│   └── edit/page.tsx         # Edit form
└── _components/
    ├── data-table-schema.ts
    ├── columns.tsx
    ├── [entity]-data-table.tsx
    ├── [entity]-form.tsx
    ├── [entity]-filters.tsx
    └── row-actions.tsx

lib/services/[module]/
└── [entity]-service.ts

hooks/[module]/
└── use-[entity].ts

types/
└── [module].ts

Development Standards

Naming Conventions

  • Files: kebab-case (entity-name.tsx)
  • Components: PascalCase (EntityForm)
  • Functions: camelCase (fetchEntities)
  • Types: PascalCase (CreateEntityDto)
  • Hooks: use prefix (useEntities)
  • Services: Service suffix (EntityService)

Logging Format

Always use module prefix:

console.log('[module/entity] Action completed:', details);
console.warn('[module/entity] Validation warning:', data);
console.error('[module/entity] Error occurred:', error);

TypeScript Rules

  • Use strict mode (no implicit any)
  • No any types - use unknown with type guards
  • Explicit return types on service methods
  • Use DTOs for all API boundaries
  • Interfaces over types for extensibility

Error Handling

  • Try-catch in all service methods
  • User-friendly messages in UI (toast notifications)
  • Console errors with module prefix for debugging
  • Graceful degradation for failed operations

Quality Checklist

Before marking module as complete:

  • All TypeScript types are strict (no any)
  • Service methods have proper error handling
  • RLS policies tested in Supabase dashboard
  • All CRUD operations tested manually
  • Permissions applied to all pages and actions
  • Loading states display correctly
  • Error messages are user-friendly
  • Console logs use correct module prefix
  • Navigation flows logically between pages
  • Mobile responsive (test on small screens)
  • Forms validate inputs correctly
  • Data table pagination works
  • Search and filters function properly

Time Estimates

  • Database Setup: 30-45 minutes
  • Types Layer: 20-30 minutes
  • Service Layer: 45-60 minutes
  • Hooks Layer: 30-45 minutes
  • Components Layer: 90-120 minutes
  • Pages Layer: 45-60 minutes
  • Permissions: 20-30 minutes
  • Testing: 30-45 minutes

Total: 5-7 hours for a complete module

Reference Files

For detailed implementation patterns and complete code examples:

  • references/architecture-patterns.md - Overall architecture and design patterns
  • references/database-patterns.md - Database schema, indexes, and RLS policies
  • references/typescript-patterns.md - Type definitions and DTO patterns
  • references/service-patterns.md - Service layer with complex queries
  • references/hooks-patterns.md - Custom hooks with advanced patterns
  • references/component-patterns.md - Complete UI component examples
  • references/page-patterns.md - Page structure with all routing patterns
  • references/permission-patterns.md - Permission system integration