This is a starter template using the following stack:
- Framework - Next.js 15
- Language - TypeScript
- Styling - Tailwind CSS v4
- Components - Shadcn-ui
- Schema Validations - Zod
- State Management - Zustand
- Search params state manager - Nuqs
- Auth - Clerk
- Tables - Tanstack Data Tables • Dice UI
- Forms - React Hook Form
- Command+k interface - kbar
- Linting - ESLint
- Pre-commit Hooks - Husky
- Formatting - Prettier
| Pages | Specifications | 
|---|---|
| Signup / Signin | Authentication with Clerk provides secure authentication and user management with multiple sign-in options including passwordless authentication, social logins, and enterprise SSO - all designed to enhance security while delivering a seamless user experience. | 
| Dashboard (Overview) | Cards with recharts graphs for analytics.Parallel routes in the overview sections with independent loading, error handling, and isolated component rendering . | 
| Product | Tanstack tables with server side searching, filter, pagination by Nuqs which is a Type-safe search params state manager in nextjs | 
| Product/new | A Product Form with shadcn form (react-hook-form + zod). | 
| Profile | Clerk's full-featured account management UI that allows users to manage their profile and security settings | 
| Kanban Board | A Drag n Drop task management board with dnd-kit and zustand to persist state locally. | 
| Not Found | Not Found Page Added in the root level | 
| - | - | 
src/
├── app/ # Next.js App Router directory
│ ├── (auth)/ # Auth route group
│ │ ├── (signin)/
│ ├── (dashboard)/ # Dashboard route group
│ │ ├── layout.tsx
│ │ ├── loading.tsx
│ │ └── page.tsx
│ └── api/ # API routes
│
├── components/ # Shared components
│ ├── ui/ # UI components (buttons, inputs, etc.)
│ └── layout/ # Layout components (header, sidebar, etc.)
│
├── features/ # Feature-based modules
│ ├── feature/
│ │ ├── components/ # Feature-specific components
│ │ ├── actions/ # Server actions
│ │ ├── schemas/ # Form validation schemas
│ │ └── utils/ # Feature-specific utilities
│ │
├── lib/ # Core utilities and configurations
│ ├── auth/ # Auth configuration
│ ├── db/ # Database utilities
│ └── utils/ # Shared utilities
│
├── hooks/ # Custom hooks
│ └── use-debounce.ts
│
├── stores/ # Zustand stores
│ └── dashboard-store.ts
│
└── types/ # TypeScript types
└── index.ts
Note
We are using Next 15 with React 19, follow these steps:
- pnpm install( we have legacy-peer-deps=true added in the .npmrc)
- Create a .env.localfile by copying the example environment file:cp env.example.txt .env.local
- Add the required environment variables to the .env.localfile.
- pnpm run dev
You should now be able to access the application at http://localhost:3000.
Warning
After cloning or forking the repository, be cautious when pulling or syncing with the latest changes, as this may result in breaking conflicts.
Cheers! 🥂
The project documentation has been reorganized into a clear, structured format located in the docs/ directory:
docs/
├── MIGRATION-GUIDE.md       # Main entry point and overview
├── guides/                  # Detailed development guides
│   ├── API-GUIDE.md         # API integration guidelines
│   ├── ANGULAR-TO-REACT-PATTERNS.md  # Migration patterns
│   ├── CODING-STANDARDS.md  # Coding standards & best practices
│   └── ...
├── tracking/                # Progress tracking documents
│   ├── IMPLEMENTATION-PLAN.md # Overall implementation plan
│   ├── PROGRESS-TRACKER.md  # Detailed status tracking
│   └── ...
└── reference/               # Reference documentation
    ├── COMPONENT-REGISTRY.md # Component library catalog
    └── ...
If you're new to the project or returning after a break, start with these documents:
- Migration Guide - Overview of the migration project and current status
- Implementation Plan - Current phase and scheduled tasks
- Progress Tracker - Detailed status of components and features
We've provided helper scripts to streamline the migration process:
# View migration status
./docs/migration.sh status
# Show component implementation status
./docs/migration.sh component-status
# Show API integration status
./docs/migration.sh api-status
# Create a new component from template
./docs/migration.sh create-component ComponentName
# Create a new page from template
./docs/migration.sh create-page path/to/pageThe Mixcore dashboard implements a micro frontend architecture where specialized mini applications operate within a central dashboard shell. This architecture enables modular development while maintaining a unified user experience across the platform. Each mini app is a self-contained module that can be developed, tested, and deployed independently while integrating seamlessly with the master shell.
// Dashboard shell layout structure
export default async function DashboardLayout({
  children
}: {
  children: React.ReactNode;
}) {
  return (
    <KBar>
      <NavigationContextProvider>
        <SidebarProvider defaultOpen={true}>
          <LayoutContextProvider>
            <AppSidebar />
            <SidebarInset className='flex h-screen flex-col'>
              <Header />
              <main className='flex-1 overflow-auto' data-app-view="default">
                <LayoutContainer>
                  {children}
                </LayoutContainer>
              </main>
            </SidebarInset>
          </LayoutContextProvider>
        </SidebarProvider>
      </NavigationContextProvider>
    </KBar>
  );
}- Purpose: Provides the master container with consistent layout elements
- Components:
- KBar: Command palette for global shortcuts
- NavigationContextProvider: Provides navigation state to all children
- SidebarProvider: Controls sidebar visibility and state
- LayoutContextProvider: Provides layout configuration and responsive state
- AppSidebar: Primary navigation sidebar
- Header: Top navigation bar
- LayoutContainer: Standardized padding and layout constraints
 
// App registry with dynamic imports
const APPS = {
  cms: () => import('@/app/dashboard/apps/cms'),
  mixdb: () => import('@/app/dashboard/apps/mixdb'),
  projects: () => import('@/app/dashboard/apps/projects'),
  workflow: () => import('@/app/dashboard/apps/workflow'),
  blogs: () => import('@/app/dashboard/apps/blogs'),
  'mini-app': () => import('@/app/dashboard/apps/mini-app')
};
export function AppLoader({ appId }: AppLoaderProps) {
  // Implementation details for dynamically loading apps
  // ...
  return <AppComponent />;
}- Purpose: Dynamically imports and renders mini apps based on URL parameters
- Key Functions:
- App Registry: Maps app IDs to their module paths
- Dynamic Importing: Lazy-loads app code only when requested
- Error Handling: Provides fallbacks for missing or errored apps
- Loading States: Shows appropriate loading UI during import
 
export function MiniApp(props: MiniAppProps) {
  // Router and URL parameters
  const router = useRouter();
  const pathname = usePathname();
  const searchParams = useSearchParams();
  
  // App state
  const [activeView, setActiveView] = useState<ViewType>(getInitialView());
  const [selectedItemId, setSelectedItemId] = useState<string | null>(itemIdParam || null);
  const isFluidLayout = useContainerStatus();
  
  // Initialization logic
  // State management
  // View rendering
  // Event handling
  
  return (
    <AppShell
      title={appConfig.displayName}
      navigation={appNavigation}
      activeView={activeView}
      onViewChange={handleViewChange}
      standalone={props.standalone}
      fluidLayout={isFluidLayout}
    >
      {renderView()}
    </AppShell>
  );
}
export default function MiniAppDefault() {
  return <MiniApp />;
}- Purpose: Main entry point for the mini app
- Responsibilities:
- App Initialization: Sets up app data and state
- View Management: Controls which view is rendered
- URL Management: Syncs app state with URL parameters
- Layout Detection: Adapts to container environment
 
Mini apps use standardized URL parameter patterns:
/dashboard/apps?app=cms&view=list&id=123
- ?app=cms: Specifies which mini app to load
- ?view=list: Controls which view to display
- ?id=123: Identifies the specific item in detail view
// Example event definitions
const EVENTS = {
  BREADCRUMB_UPDATE: 'mixcore:breadcrumbs:update',
  CONTEXT_SET: 'mixcore:context:set',
  LAYOUT_CHANGE: 'mixcore:layout:change'
};
// Emitting events
window.dispatchEvent(new CustomEvent(EVENTS.BREADCRUMB_UPDATE, {
  detail: {
    items: [
      { label: 'Dashboard', path: '/dashboard' },
      { label: 'CMS', path: '/dashboard/apps?app=cms' },
      { label: 'Posts', path: '/dashboard/apps?app=cms&view=list' }
    ]
  }
}));
// Listening for events
window.addEventListener(EVENTS.LAYOUT_CHANGE, (event) => {
  // Handle layout change
  const { isFluid } = event.detail;
  setFluidLayout(isFluid);
});// Custom hook to detect container environment
export function useContainerStatus() {
  const [isFluid, setIsFluid] = useState(false);
  
  useEffect(() => {
    // Check if parent container has fluid layout attribute
    const container = document.querySelector('[data-layout="fluid"]');
    setIsFluid(!!container);
    
    // Listen for layout change events
    const handleLayoutChange = (event: CustomEvent) => {
      setIsFluid(event.detail.isFluid);
    };
    
    window.addEventListener(EVENTS.LAYOUT_CHANGE, handleLayoutChange as EventListener);
    return () => {
      window.removeEventListener(EVENTS.LAYOUT_CHANGE, handleLayoutChange as EventListener);
    };
  }, []);
  
  return isFluid;
}async function registerApp(): Promise<void> {
  try {
    // Register app metadata with Mixcore system
    const response = await fetch('/api/apps/register', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        appId: appConfig.appId,
        version: appConfig.version,
        displayName: appConfig.displayName,
        description: appConfig.description,
        category: appConfig.category,
        icon: appConfig.icon,
        entryPoint: appConfig.entryPoint,
        navigation: appConfig.navigation
      }),
    });
    
    if (!response.ok) {
      throw new Error(`Failed to register app: ${response.statusText}`);
    }
    
    console.log('App registered successfully');
  } catch (error) {
    console.error('Error registering app:', error);
    throw error;
  }
}async function setupMixDBCollections(): Promise<void> {
  try {
    // Create the required collections in MixDB
    const response = await fetch('/api/mixdb/collections/create-many', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(schemaConfig.collections),
    });
    
    if (!response.ok) {
      throw new Error(`Failed to create collections: ${response.statusText}`);
    }
    
    console.log('MixDB collections created successfully');
  } catch (error) {
    console.error('Error setting up MixDB collections:', error);
    throw error;
  }
}async function registerPermissions(): Promise<void> {
  try {
    // Register permissions with the auth system
    const response = await fetch('/api/auth/permissions/create-many', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(appConfig.permissions),
    });
    
    if (!response.ok) {
      throw new Error(`Failed to register permissions: ${response.statusText}`);
    }
    
    console.log('Permissions registered successfully');
  } catch (error) {
    console.error('Error registering permissions:', error);
    throw error;
  }
}async function loadDemoData(): Promise<void> {
  try {
    // For each collection in the demo data, insert the records
    for (const [collectionName, records] of Object.entries(demoData.data)) {
      const response = await fetch(`/api/mixdb/collections/${collectionName}/records/create-many`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(records),
      });
      
      if (!response.ok) {
        throw new Error(`Failed to insert records for ${collectionName}: ${response.statusText}`);
      }
    }
    
    console.log('Demo data loaded successfully');
  } catch (error) {
    console.error('Error loading demo data:', error);
    throw error;
  }
}{
  "appId": "mini-app",
  "version": "1.0.0",
  "displayName": "Mini App",
  "description": "A template application for the Mixcore dashboard",
  "category": "tools",
  "icon": "AppWindow",
  "entryPoint": "/dashboard/apps?app=mini-app",
  "navigation": {
    "primary": [
      {
        "id": "dashboard",
        "label": "Dashboard",
        "path": "?app=mini-app&view=dashboard",
        "icon": "LayoutDashboard"
      },
      {
        "id": "list",
        "label": "Items",
        "path": "?app=mini-app&view=list",
        "icon": "List"
      },
      {
        "id": "settings",
        "label": "Settings", 
        "path": "?app=mini-app&view=settings",
        "icon": "Settings"
      }
    ]
  },
  "permissions": [
    {
      "name": "mini-app:read",
      "description": "View mini app data"
    },
    {
      "name": "mini-app:write",
      "description": "Create and update mini app data"
    },
    {
      "name": "mini-app:admin",
      "description": "Full administrative access to mini app"
    }
  ],
  "init": {
    "initOnInstall": true,
    "createDefaultPermissions": true
  },
  "ui": {
    "layout": {
      "fluid": false
    },
    "theme": {
      "primaryColor": "#0090FF"
    }
  }
}{
  "collections": [
    {
      "name": "mini_app_items",
      "displayName": "Mini App Items",
      "fields": [
        {
          "name": "title",
          "type": "string",
          "required": true,
          "displayName": "Title",
          "searchable": true
        },
        {
          "name": "description",
          "type": "text",
          "required": false,
          "displayName": "Description"
        },
        {
          "name": "status",
          "type": "string",
          "required": true,
          "displayName": "Status",
          "default": "draft",
          "options": ["draft", "published", "archived"]
        },
        {
          "name": "created_at",
          "type": "datetime",
          "required": true,
          "displayName": "Created At",
          "default": "NOW()"
        },
        {
          "name": "updated_at",
          "type": "datetime",
          "required": true,
          "displayName": "Updated At",
          "default": "NOW()"
        }
      ],
      "indexes": [
        {
          "fields": ["title"],
          "type": "fulltext"
        },
        {
          "fields": ["status"],
          "type": "index"
        }
      ]
    }
  ]
}{
  "data": {
    "mini_app_items": [
      {
        "title": "First Item",
        "description": "This is the first demo item",
        "status": "published",
        "created_at": "2023-01-01T00:00:00Z",
        "updated_at": "2023-01-01T00:00:00Z"
      },
      {
        "title": "Second Item",
        "description": "This is the second demo item",
        "status": "draft",
        "created_at": "2023-01-02T00:00:00Z",
        "updated_at": "2023-01-02T00:00:00Z"
      }
    ]
  }
}mini-app/
├── app-globals.css        # App-specific styles
├── app-loader.ts          # Initialization and registration
├── index.tsx              # Main entry point
├── index.ts               # Public API exports
├── components/            # UI components
│   ├── Dashboard.tsx      # Main dashboard component
│   ├── ItemList.tsx       # List view component
│   ├── ItemDetail.tsx     # Detail view component
│   ├── ItemForm.tsx       # Form for creating/editing items
│   └── Settings.tsx       # Settings view component
├── config/                # Configuration files
│   ├── app.config.json    # App configuration
│   ├── demo-data.json     # Demo data
│   └── mixdb.schema.json  # Database schema
├── hooks/                 # Custom hooks
│   ├── useBreadcrumb.ts   # Breadcrumb integration
│   ├── useContainerStatus.ts # Layout detection
│   ├── useItems.ts        # Data fetching for items
│   └── useAppSettings.ts  # Settings management
├── layouts/               # Layout components
│   ├── AppShell.tsx       # Main app shell layout
│   └── ContentLayout.tsx  # Content layout with standard padding/margins
├── lib/                   # Utility functions and types
│   ├── mixdb-api.ts       # MixDB API client
│   ├── auth.ts            # Authentication utilities
│   ├── culture.ts         # Localization utilities
│   ├── types.ts           # TypeScript type definitions
│   └── validation.ts      # Form validation schemas
└── assets/                # Static assets
    ├── icons/             # App-specific icons
    └── images/            # App-specific images
// lib/mixdb-api.ts
export class MixDBClient {
  private baseUrl: string;
  private authToken?: string;
  
  constructor(config: { baseUrl: string, authToken?: string }) {
    this.baseUrl = config.baseUrl;
    this.authToken = config.authToken;
  }
  
  async getItems(collection: string, params: { page?: number, pageSize?: number, filter?: any } = {}) {
    const queryParams = new URLSearchParams();
    if (params.page) queryParams.set('page', params.page.toString());
    if (params.pageSize) queryParams.set('pageSize', params.pageSize.toString());
    if (params.filter) queryParams.set('filter', JSON.stringify(params.filter));
    
    const response = await fetch(`${this.baseUrl}/collections/${collection}/records?${queryParams.toString()}`, {
      headers: this.getHeaders(),
    });
    
    if (!response.ok) {
      throw new Error(`Failed to fetch items: ${response.statusText}`);
    }
    
    return await response.json();
  }
  
  async getItem(collection: string, id: string) {
    const response = await fetch(`${this.baseUrl}/collections/${collection}/records/${id}`, {
      headers: this.getHeaders(),
    });
    
    if (!response.ok) {
      throw new Error(`Failed to fetch item: ${response.statusText}`);
    }
    
    return await response.json();
  }
  
  async createItem(collection: string, data: any) {
    const response = await fetch(`${this.baseUrl}/collections/${collection}/records`, {
      method: 'POST',
      headers: this.getHeaders(),
      body: JSON.stringify(data),
    });
    
    if (!response.ok) {
      throw new Error(`Failed to create item: ${response.statusText}`);
    }
    
    return await response.json();
  }
  
  async updateItem(collection: string, id: string, data: any) {
    const response = await fetch(`${this.baseUrl}/collections/${collection}/records/${id}`, {
      method: 'PATCH',
      headers: this.getHeaders(),
      body: JSON.stringify(data),
    });
    
    if (!response.ok) {
      throw new Error(`Failed to update item: ${response.statusText}`);
    }
    
    return await response.json();
  }
  
  async deleteItem(collection: string, id: string) {
    const response = await fetch(`${this.baseUrl}/collections/${collection}/records/${id}`, {
      method: 'DELETE',
      headers: this.getHeaders(),
    });
    
    if (!response.ok) {
      throw new Error(`Failed to delete item: ${response.statusText}`);
    }
    
    return true;
  }
  
  private getHeaders() {
    const headers: Record<string, string> = {
      'Content-Type': 'application/json',
    };
    
    if (this.authToken) {
      headers['Authorization'] = `Bearer ${this.authToken}`;
    }
    
    return headers;
  }
}// hooks/useItems.ts
import { useState, useEffect } from 'react';
import { MixDBClient } from '../lib/mixdb-api';
const api = new MixDBClient({
  baseUrl: '/api/mixdb',
});
export function useItems(collection: string, params: { page?: number, pageSize?: number, filter?: any } = {}) {
  const [items, setItems] = useState<any[]>([]);
  const [totalItems, setTotalItems] = useState(0);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);
  
  useEffect(() => {
    let isMounted = true;
    
    const fetchItems = async () => {
      try {
        setLoading(true);
        const result = await api.getItems(collection, params);
        
        if (isMounted) {
          setItems(result.items);
          setTotalItems(result.total);
          setError(null);
        }
      } catch (err) {
        if (isMounted) {
          setError(err instanceof Error ? err : new Error(String(err)));
        }
      } finally {
        if (isMounted) {
          setLoading(false);
        }
      }
    };
    
    fetchItems();
    
    return () => {
      isMounted = false;
    };
  }, [collection, params.page, params.pageSize, JSON.stringify(params.filter)]);
  
  const refetch = async () => {
    setLoading(true);
    try {
      const result = await api.getItems(collection, params);
      setItems(result.items);
      setTotalItems(result.total);
      setError(null);
    } catch (err) {
      setError(err instanceof Error ? err : new Error(String(err)));
    } finally {
      setLoading(false);
    }
  };
  
  return { items, totalItems, loading, error, refetch };
}- 
Copy the Template: cp -r src/templates/mini-app src/app/dashboard/apps/your-app-name 
- 
Update Package Information: // package.json { "name": "@mixcore/your-app-name", "version": "1.0.0", "description": "Your app description", "author": "Your Name" } 
- 
Configure App Settings: - Edit config/app.config.jsonwith your app details
- Update app ID, display name, description, icons, etc.
- Define navigation items for your app's main views
 
- Edit 
- 
Define Database Schema: - Modify config/mixdb.schema.jsonwith your data model
- Define collections, fields, relationships, and indexes
 
- Modify 
- 
Register in App Loader: // src/components/app/AppLoader.tsx const APPS = { // Existing apps... 'your-app-name': () => import('@/app/dashboard/apps/your-app-name') }; 
- 
Implement Views and Components: - Customize Dashboard.tsx for your main view
- Create list, detail, and form components as needed
- Add settings and configuration UI
 
- 
Implement Data Integration: - Use the MixDB API client for CRUD operations
- Create custom hooks for specific data needs
- Handle loading, error, and empty states
 
- 
Test the App: - Navigate to /dashboard/apps?app=your-app-name
- Verify all views and functionality work as expected
- Test in both standalone and integrated modes
 
- Navigate to 
- Use URL parameters for main navigation state
- Keep local state contained within components when possible
- Use context for shared state within the app
- Persist configuration in localStorage or database
- Lazy load components and views
- Implement virtual scrolling for large lists
- Use optimistic UI updates for faster perceived performance
- Cache API responses when appropriate
- Implement proper loading and error states
- Follow the Mixcore design system
- Use responsive layouts that adapt to container size
- Support both light and dark modes
- Use common UI components from the shared library
- Implement proper loading and skeleton states
- Implement comprehensive error boundaries
- Provide meaningful error messages
- Add retry functionality for failed operations
- Log errors to monitoring service
- Implement graceful degradation
- Use proper authentication for all API requests
- Implement permission checks for sensitive operations
- Sanitize all user inputs
- Validate data on both client and server
- Follow OWASP security guidelines
- Check AppLoader.tsxfor proper registration
- Verify import path is correct
- Check console for JavaScript errors
- Ensure all dependencies are installed
- Verify API endpoints are correct
- Check authentication status
- Inspect network requests in browser dev tools
- Verify permission configuration
- Test in both fluid and contained layouts
- Check responsive design breakpoints
- Verify CSS isolation is working properly
- Check for conflicting styles with the parent application
- Verify URL parameter handling
- Check state initialization logic
- Use React DevTools to inspect component state
- Verify event listeners are properly cleaned up
This architecture allows Mixcore to maintain a unified dashboard experience while enabling diverse functionality through specialized micro applications.