Skip to content

mixcore/mix.portal.react

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Next.js Admin Dashboard Starter Template With Shadcn-ui
Built with the Next.js 15 App Router

Overview

This is a starter template using the following stack:

Pages

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
- -

Feature based organization

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

Getting Started

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.local file by copying the example environment file: cp env.example.txt .env.local
  • Add the required environment variables to the .env.local file.
  • 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! 🥂

Documentation

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
    └── ...

Getting Started with Documentation

If you're new to the project or returning after a break, start with these documents:

  1. Migration Guide - Overview of the migration project and current status
  2. Implementation Plan - Current phase and scheduled tasks
  3. Progress Tracker - Detailed status of components and features

Helper Scripts

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/page

Mixcore Micro Mini Apps Architecture

Overview

The 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.

Technical Architecture

Core Components

1. Dashboard Shell (layout.tsx)

// 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

2. App Loader System (AppLoader.tsx)

// 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

3. Mini App Entry Point (apps/[app-id]/index.tsx)

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

Integration Points

1. URL-Based Navigation

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

2. Event Communication

// 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);
});

3. Container Status Detection

// 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;
}

App Initialization Process

1. Registration Phase

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;
  }
}

2. Database Setup

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;
  }
}

3. Permissions Setup

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;
  }
}

4. Demo Data Loading

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;
  }
}

Configuration Files

1. App Configuration (app.config.json)

{
  "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"
    }
  }
}

2. Database Schema (mixdb.schema.json)

{
  "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"
        }
      ]
    }
  ]
}

3. Demo Data (demo-data.json)

{
  "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"
      }
    ]
  }
}

Complete Mini App Directory Structure

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

API Integration

MixDB API Client

// 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;
  }
}

Data Fetching Hook

// 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 };
}

Detailed Implementation Guidelines

Creating a New Mini App

  1. Copy the Template:

    cp -r src/templates/mini-app src/app/dashboard/apps/your-app-name
  2. Update Package Information:

    // package.json
    {
      "name": "@mixcore/your-app-name",
      "version": "1.0.0",
      "description": "Your app description",
      "author": "Your Name"
    }
  3. Configure App Settings:

    • Edit config/app.config.json with your app details
    • Update app ID, display name, description, icons, etc.
    • Define navigation items for your app's main views
  4. Define Database Schema:

    • Modify config/mixdb.schema.json with your data model
    • Define collections, fields, relationships, and indexes
  5. Register in App Loader:

    // src/components/app/AppLoader.tsx
    const APPS = {
      // Existing apps...
      'your-app-name': () => import('@/app/dashboard/apps/your-app-name')
    };
  6. Implement Views and Components:

    • Customize Dashboard.tsx for your main view
    • Create list, detail, and form components as needed
    • Add settings and configuration UI
  7. Implement Data Integration:

    • Use the MixDB API client for CRUD operations
    • Create custom hooks for specific data needs
    • Handle loading, error, and empty states
  8. 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

Best Practices

State Management

  • 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

Performance Optimization

  • 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

Layout and UI

  • 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

Error Handling

  • Implement comprehensive error boundaries
  • Provide meaningful error messages
  • Add retry functionality for failed operations
  • Log errors to monitoring service
  • Implement graceful degradation

Security

  • 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

Troubleshooting

Common Issues

App Not Loading

  • Check AppLoader.tsx for proper registration
  • Verify import path is correct
  • Check console for JavaScript errors
  • Ensure all dependencies are installed

API Errors

  • Verify API endpoints are correct
  • Check authentication status
  • Inspect network requests in browser dev tools
  • Verify permission configuration

Layout Issues

  • 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

State Management Issues

  • 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.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •