Files
Temp_MSSPLASHPage/onboarding_arquitecture.md
Jose Andres f73ab48a46 changes: - Onboarding setuo added
- Onboarding AI setup files
- Register dependencies
2025-10-29 16:17:13 -06:00

47 KiB

Onboarding Module - Architecture Documentation

1. Executive Summary

The Onboarding Module is a critical component of SplashPage that guides new users through the initial setup process for Cisco Meraki network integration. It enables users to connect their Meraki organization, select networks, and automatically provision the system with necessary configurations including dashboards and captive portals.

Key Metrics

  • Legacy System: ASP.NET Core MVC + Tabler UI
  • Target System: Next.js 14 + React + shadcn/ui
  • User Flow: 5-step wizard (6 with welcome, 7 with success)
  • Average Completion Time: ~3-5 minutes
  • Database Entities Created: 6 tables populated during setup

2. Technology Stack

Current Implementation (Legacy MVC)

Layer Technology Version Purpose
Frontend ASP.NET Core MVC .NET 6+ Server-side rendering
UI Framework Tabler UI Latest Bootstrap-based admin template
CSS Framework Bootstrap 5.x Component styling
Icons Font Awesome 6.4.0 Icon library
JavaScript Vanilla JS ES6+ Client-side logic
HTTP Client ABP Service Proxies - Dynamic service generation

Target Implementation (Next.js)

Layer Technology Version Purpose
Frontend Framework Next.js 14 App Router, SSR/CSR
UI Components shadcn/ui Latest Radix UI + Tailwind
Styling Tailwind CSS 3.4+ Utility-first CSS
Animations Framer Motion 12.x Advanced animations
Forms React Hook Form 7.x Form validation
Validation Zod 3.x Schema validation
State Management TanStack Query 5.x Server state management
API Client Kubb Latest Auto-generated from Swagger
Icons Lucide React Latest Icon components

Backend (Shared)

Component Technology Purpose
Framework ASP.NET Core + ABP Business logic layer
ORM Entity Framework Core Database access
Database MySQL/PostgreSQL Data persistence
Caching IMemoryCache Performance optimization
API Integration Meraki Dashboard API External network data
Storage MinIO Object storage for assets

3. File Structure

Legacy MVC Files

src/SplashPage.Web.Mvc/
├── Views/Onboarding/
│   ├── Index.cshtml                          (672 lines) - Main onboarding view
│   ├── _PreScripts.cshtml                    - Script dependencies
│   └── _Scripts.cshtml                       - Script references
├── wwwroot/view-resources/Views/Onboarding/
│   └── Onboarding.js                         (515 lines) - Client-side logic
└── Controllers/
    └── OnboardingController.cs               (65 lines) - MVC controller

src/SplashPage.Application/
└── Onboarding/
    ├── OnboardingService.cs                  (398 lines) - Business logic
    ├── IOnboardingService.cs                 (20 lines) - Service interface
    └── Dto/
        ├── OnboardingApiKeyDto.cs            - API key input
        ├── OnboardingFinishSetupDto.cs       - Final setup data
        ├── OnboardingOrganizationDto.cs      - Organization request
        ├── OnboardingOrganizationOverviewDto.cs - Org with device counts
        └── OnboardingOrganizationNetworksDto.cs - Network data

Target Next.js Files

src/SplashPage.Web.Ui/
└── _examples/onboarding/                     - Reference implementation
    ├── src/
    │   ├── components/
    │   │   ├── onboarding/
    │   │   │   ├── OnboardingWizard.tsx      - Main wizard container
    │   │   │   ├── ProgressBar.tsx           - Liquid progress indicator
    │   │   │   ├── StepCard.tsx              - Animated step wrapper
    │   │   │   ├── RippleButton.tsx          - Interactive button with ripple
    │   │   │   └── steps/
    │   │   │       ├── WelcomeStep.tsx       - Initial landing
    │   │   │       ├── SelectTechStep.tsx    - Integration selection
    │   │   │       ├── ApiKeyStep.tsx        - API key validation
    │   │   │       ├── PickOrgStep.tsx       - Organization selection
    │   │   │       ├── PickNetworksStep.tsx  - Network multi-select
    │   │   │       ├── CreateGroupStep.tsx   - (Summary equivalent)
    │   │   │       └── SuccessStep.tsx       - Completion celebration
    │   │   └── ui/                            - shadcn/ui components
    │   ├── hooks/
    │   │   ├── use-toast.ts                  - Toast notifications
    │   │   └── use-mobile.tsx                - Responsive detection
    │   ├── lib/
    │   │   └── utils.ts                      - Utility functions
    │   ├── pages/
    │   │   ├── Index.tsx                     - Onboarding entry
    │   │   └── Dashboard.tsx                 - Post-onboarding redirect
    │   └── index.css                          - Design system & animations
    └── package.json                           - Dependencies

Proposed Next.js Integration Structure

src/SplashPage.Web.Ui/src/
├── app/
│   └── dashboard/
│       └── onboarding/
│           └── page.tsx                       - Onboarding route
├── components/
│   └── onboarding/
│       ├── OnboardingWizard.tsx
│       ├── ProgressBar.tsx
│       ├── StepCard.tsx
│       ├── RippleButton.tsx
│       └── steps/
│           ├── WelcomeStep.tsx
│           ├── SelectTechStep.tsx
│           ├── ApiKeyStep.tsx
│           ├── PickOrgStep.tsx
│           ├── PickNetworksStep.tsx
│           ├── SummaryStep.tsx
│           └── SuccessStep.tsx
├── hooks/
│   └── api/
│       └── useOnboardingApi.ts               - API integration hooks
└── styles/
    └── onboarding.css                         - Component-specific styles

4. Frontend Architecture

Legacy MVC Views (Index.cshtml)

Structure Overview:

  • Single-file monolithic view with embedded styles
  • 6 view sections rendered conditionally with JavaScript
  • Bootstrap grid system + Tabler components
  • Font Awesome icons for visual elements

View Sections:

  1. Progress Stepper (Lines 124-155)

    • Horizontal step indicator (hidden on step 1)
    • 5 circles with connectors
    • Active/completed state styling
  2. Integration View (Lines 161-282)

    • 3 integration cards (Meraki, Mist, Catalyst)
    • Only Meraki enabled with primary badge
    • Coming soon badges for disabled options
  3. API Key View (Lines 284-364)

    • Single text input with validation
    • Minimum 16 characters requirement
    • Real-time error feedback
    • Link to Meraki dashboard documentation
  4. Organization Selection View (Lines 366-414)

    • Dynamic card grid populated by JavaScript
    • Shows device inventory counts (APs, Switches, SD-WAN, Cameras)
    • Click to select organization
  5. Network Selection View (Lines 416-487)

    • Multi-select checkboxes for networks
    • Displays AP count per network
    • Select All / Clear Selection actions
    • Disabled continue button until selection
  6. Summary View (Lines 489-608)

    • Configuration review
    • License bracket calculation based on total APs
    • Selected networks list
    • Finish setup action

Legacy JavaScript (Onboarding.js)

State Management:

let currentStep = 1;           // Current wizard step
let selectedOrg = null;        // Selected organization object
let selectedNetworks = [];     // Array of network IDs
let apiKey = "";               // Validated API key
let organizations = [];        // Cached organizations
let networks = [];             // Cached networks

Key Functions:

Function Purpose API Call
showView(viewId) Toggle view visibility None
updateProgressSteps(step) Update progress indicator None
validateApiKey() Validate and store API key onboardingService.validateApiKey()
populateOrganizations() Fetch and render organizations onboardingService.getOrganizations()
populateNetworks(orgId) Fetch networks for org onboardingService.getOrganizationsNetworks()
selectOrganization(org) Handle org selection None
populateSummary() Build summary view None
getLicenseBracket(apCount) Calculate license tier None
parseOrganizationDevices() Parse device overview None

ABP Service Proxy Usage:

let service = abp.services.app.onboardingService;

service.validateApiKey({ apiKey: key })
  .done(() => { /* Success */ })
  .fail(() => { /* Error */ })
  .always(() => { /* Cleanup */ });

Target Next.js Architecture

Component Hierarchy:

OnboardingWizard (State Container)
├── ProgressBar (Fixed, conditional)
├── WelcomeStep (Step 0)
├── SelectTechStep (Step 1)
├── ApiKeyStep (Step 2)
├── PickOrgStep (Step 3)
├── PickNetworksStep (Step 4)
├── SummaryStep (Step 5)
└── SuccessStep (Step 6)

State Management Pattern:

  • Local state in OnboardingWizard for step navigation
  • TanStack Query for API data caching and mutations
  • React Hook Form for form validation in steps
  • Context API for shared onboarding data (optional)

Animation System:

// StepCard.tsx - 60% slide animation
<motion.div
  initial={{ x: "60%", opacity: 0 }}
  animate={{ x: 0, opacity: 1 }}
  exit={{ x: "-60%", opacity: 0 }}
  transition={{ duration: 0.4, ease: [0.4, 0, 0.2, 1] }}
>

Key Features:

  • Liquid progress bar with Meraki gradient
  • Full-screen card layout with slide transitions
  • Breath animations on selected elements
  • Glow pulse effects on primary actions
  • Morphing buttons (text → spinner → check)
  • Spring animations on accordions

5. Backend Architecture

Controller Layer (OnboardingController.cs)

Purpose: MVC controller serving the onboarding view

[AbpMvcAuthorize]
public class OnboardingController : AbpController
{
    private readonly IMerakiService _merakiService;
    private readonly IRepository<SplashTenantDetails> _splashTenantDetailsRepository;

    public IActionResult Index()
    {
        var model = new OnboardingViewModel
        {
            AvailableIntegrations = new List<IntegrationViewModel>
            {
                new IntegrationViewModel
                {
                    Name = "Cisco Meraki",
                    Description = "Solución de redes administradas en la nube",
                    IconClass = "fa-cloud",
                    IsEnabled = true
                },
                // Mist AI (disabled)
                // Catalyst (disabled)
            }
        };
        return View(model);
    }
}

Application Service Layer (OnboardingService.cs)

Purpose: Business logic orchestration and API integration

Dependencies:

  • IMerakiService - Meraki API wrapper
  • IMemoryCache - Response caching
  • IRepository<T> - Data persistence
  • IMinioStorageService - File storage

Key Methods:

1. ValidateApiKey

Task<bool> ValidateApiKey(OnboardingApiKeyDto input)
  • Validates API key by attempting to fetch organizations
  • Throws UserFriendlyException if invalid
  • No caching (security)

2. GetOrganizations

Task<List<OnboardingOrganizationOverviewDto>> GetOrganizations(OnboardingApiKeyDto input)
  • Fetches all organizations for API key
  • Retrieves device inventory overview for each org
  • Groups devices by type (switch, appliance, wireless, camera)
  • Cache: 5 min absolute, 30s sliding

Device Mapping:

private readonly Dictionary<string, string> productMap = new()
{
    ["switch"] = "switches",
    ["appliance"] = "sdwan",
    ["wireless"] = "accessPoints",
    ["camera"] = "cameras"
};

3. GetOrganizationsNetworks

Task<List<OnboardingOrganizationNetworksDto>> GetOrganizationsNetworks(OnboardingOrganizationDto input)
  • Fetches wireless networks for organization
  • Counts APs per network via device API
  • Filters out networks with 0 APs
  • Cache: 5 min absolute, 20 min sliding

4. FinishSetup

Task FinishSetup(OnboardingFinishSetupDto input)

Transaction Steps:

  1. Validate organization exists in API response
  2. Create/Update SplashTenantDetails with API key + org ID
  3. Insert/Update SplashMerakiOrganization record
  4. Insert selected SplashMerakiNetwork records
  5. Fetch all devices for organization (single API call)
  6. Filter devices by network and insert SplashAccessPoint records
  7. Create default SplashDashboard with network IDs
  8. Create default SplashCaptivePortal if none exists

Captive Portal Creation:

  • Reads default logo and background from App_Data/CaptivePortal/
  • Reads default configuration JSON
  • Uploads assets to MinIO: {appCodeName}-{customer}/captive-portal/{id}/
  • Updates configuration with MinIO URLs
  • Stores in both Configuration and ProdConfiguration fields

5. CreateDefaultCaptivePortal (Private)

Task CreateDefaultCaptivePortal(string name, string description)

Assets:

  • DefaultLogo.png
  • DefaultBackground.png
  • DefaultConfiguration.json

MinIO Structure:

bucket: {appCodeName}-{customer}
├── captive-portal/{portalId}/
    ├── Logo/{uuid}.png
    └── Background/{uuid}.png

Data Transfer Objects (DTOs)

DTO Properties Purpose
OnboardingApiKeyDto string ApiKey API key input
OnboardingOrganizationDto string OrganizationId
string ApiKey
Org request
OnboardingOrganizationOverviewDto string OrganizationName
string OrganizationId
object Overwiew
Org with device counts
OnboardingOrganizationNetworksDto string Id
string Name
string OrganizationId
int ApCount
Network data
OnboardingFinishSetupDto string OrganizationId
List<string> Networks
string ApiKey
Final setup payload

Repository Layer

Entity Repositories:

  • IRepository<SplashTenantDetails> - Tenant configuration
  • IRepository<SplashMerakiOrganization> - Organization records
  • IRepository<SplashMerakiNetwork> - Network records
  • IRepository<SplashAccessPoint> - Access point inventory
  • IRepository<SplashDashboard> - Dashboard configurations
  • IRepository<SplashCaptivePortal> - Portal configurations

Bulk Operations:

await _repository.InsertRangeAsync(entities);

6. API Integration

Meraki Dashboard API

Authentication: API Key in X-Cisco-Meraki-API-Key header

Endpoints Used:

Endpoint Purpose Called By
GET /organizations List organizations GetOrganizations()
GET /organizations/{id}/devices Get org device inventory GetOrganizations()
GET /organizations/{id}/networks List networks GetOrganizationsNetworks()
GET /organizations/{id}/devices?networkId={id} Count APs per network GetOrganizationsNetworks()
GET /organizations/{id}/devices All devices for AP sync FinishSetup()

Rate Limiting:

  • Meraki API: 5 requests/second per org
  • SplashPage implements caching to reduce API calls

Caching Strategy

Purpose: Reduce Meraki API calls and improve performance

Implementation:

private readonly TimeSpan _cacheExpiration = TimeSpan.FromSeconds(30);

var cacheOptions = new MemoryCacheEntryOptions
{
    AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5),
    SlidingExpiration = TimeSpan.FromSeconds(30)
};

_cache.Set(cacheKey, data, cacheOptions);

Cache Keys:

  • OrganizationOverview_{apiKey} - Organization list with device counts
  • OrganizationNetworks_{apiKey}_{organizationId} - Networks for org

Cache Duration:

Data Type Absolute Sliding Reasoning
Organizations 5 min 30 sec Rarely changes
Networks 5 min 20 min Static during onboarding

7. User Flow Diagram

┌─────────────────────────────────────────────────────────────────┐
│ START: User navigates to /Onboarding                            │
└───────────────────────┬─────────────────────────────────────────┘
                        │
                        ▼
┌─────────────────────────────────────────────────────────────────┐
│ STEP 0: Welcome Screen (Next.js only)                           │
│ - Animated gradient background                                  │
│ - Brand introduction                                             │
│ - "Comenzar" button                                             │
└───────────────────────┬─────────────────────────────────────────┘
                        │
                        ▼
┌─────────────────────────────────────────────────────────────────┐
│ STEP 1: Integration Selection                                   │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐               │
│ │  Cisco      │ │  Juniper    │ │  Cisco      │               │
│ │  Meraki     │ │  Mist AI    │ │  Catalyst   │               │
│ │  [ENABLED]  │ │  [DISABLED] │ │  [DISABLED] │               │
│ └─────────────┘ └─────────────┘ └─────────────┘               │
│ User clicks: Meraki card                                        │
└───────────────────────┬─────────────────────────────────────────┘
                        │
                        ▼
┌─────────────────────────────────────────────────────────────────┐
│ STEP 2: API Key Input                                           │
│ - Input field with show/hide toggle                             │
│ - Real-time validation (min 16 chars in legacy, 40 in new)     │
│ - "Validar y Continuar" button                                  │
│                                                                  │
│ API Call: POST /api/services/app/OnboardingService/            │
│                ValidateApiKey                                    │
│ Request: { apiKey: "***" }                                      │
│ Response: { result: true }                                      │
└───────────────────────┬─────────────────────────────────────────┘
                        │
                        ▼
┌─────────────────────────────────────────────────────────────────┐
│ STEP 3: Organization Selection                                  │
│ - Cards grid with organizations                                 │
│ - Shows device inventory per org                                │
│                                                                  │
│ API Call: POST /api/services/app/OnboardingService/            │
│                GetOrganizations                                  │
│ Request: { apiKey: "***" }                                      │
│ Response: [                                                      │
│   {                                                              │
│     organizationName: "Acme Corp",                              │
│     organizationId: "123456",                                   │
│     overwiew: [                                                  │
│       { productType: "wireless", count: 15 },                   │
│       { productType: "switch", count: 8 }                       │
│     ]                                                            │
│   }                                                              │
│ ]                                                                │
│                                                                  │
│ User clicks: Organization card → selectOrganization(org)        │
└───────────────────────┬─────────────────────────────────────────┘
                        │
                        ▼
┌─────────────────────────────────────────────────────────────────┐
│ STEP 4: Network Selection                                       │
│ - Multi-select checkboxes                                       │
│ - Display AP count per network                                  │
│ - Select All / Clear Selection                                  │
│ - Continue button (disabled until selection)                    │
│                                                                  │
│ API Call: POST /api/services/app/OnboardingService/            │
│                GetOrganizationsNetworks                          │
│ Request: {                                                       │
│   organizationId: "123456",                                     │
│   apiKey: "***"                                                  │
│ }                                                                │
│ Response: [                                                      │
│   {                                                              │
│     id: "N_123",                                                │
│     name: "Oficina Central",                                    │
│     organizationId: "123456",                                   │
│     apCount: 8                                                   │
│   }                                                              │
│ ]                                                                │
│                                                                  │
│ User selects: Multiple networks → selectedNetworks[]            │
└───────────────────────┬─────────────────────────────────────────┘
                        │
                        ▼
┌─────────────────────────────────────────────────────────────────┐
│ STEP 5: Summary / Review                                        │
│ - Organization name                                              │
│ - Selected networks count                                        │
│ - Total APs                                                      │
│ - Calculated license bracket                                     │
│ - Network list with AP counts                                    │
│ - "Finalizar Configuración" button                             │
│                                                                  │
│ User clicks: Finalizar → FinishSetup()                          │
└───────────────────────┬─────────────────────────────────────────┘
                        │
                        ▼
┌─────────────────────────────────────────────────────────────────┐
│ Backend Processing (FinishSetup)                                │
│                                                                  │
│ 1. Validate organization exists                                 │
│ 2. Create/Update SplashTenantDetails                            │
│    - TenantId: 1                                                │
│    - APIKey: "***"                                              │
│    - OrganizationId: "123456"                                   │
│                                                                  │
│ 3. Insert SplashMerakiOrganization                              │
│    - MerakiId: "123456"                                         │
│    - Name: "Acme Corp"                                          │
│                                                                  │
│ 4. Insert SplashMerakiNetwork (multiple)                        │
│    - MerakiId: "N_123"                                          │
│    - Name: "Oficina Central"                                    │
│    - OrganizationId: FK to org                                  │
│                                                                  │
│ 5. Fetch all devices from Meraki API                            │
│    GET /organizations/123456/devices                            │
│                                                                  │
│ 6. Insert SplashAccessPoint (filtered by networks)              │
│    - Name: "AP-Floor1"                                          │
│    - Serial: "Q2XX-XXXX-XXXX"                                   │
│    - MAC: "xx:xx:xx:xx:xx:xx"                                   │
│    - NetworkId: FK to network                                   │
│                                                                  │
│ 7. Create SplashDashboard                                       │
│    - Name: "🚀 Main Dashboard"                                  │
│    - NetworkIds: ["N_123", "N_456"]                            │
│                                                                  │
│ 8. Create SplashCaptivePortal (if none exists)                  │
│    - Read DefaultLogo.png, DefaultBackground.png                │
│    - Read DefaultConfiguration.json                             │
│    - Upload to MinIO: captive-portal/{id}/Logo/{uuid}.png      │
│    - Store URLs in configuration                                │
│                                                                  │
│ API Call: POST /api/services/app/OnboardingService/            │
│                FinishSetup                                       │
│ Request: {                                                       │
│   organizationId: "123456",                                     │
│   networks: ["N_123", "N_456"],                                 │
│   apiKey: "***"                                                  │
│ }                                                                │
│ Response: { success: true }                                     │
└───────────────────────┬─────────────────────────────────────────┘
                        │
                        ▼
┌─────────────────────────────────────────────────────────────────┐
│ STEP 6: Success Screen                                          │
│ - Confetti animation (Next.js)                                  │
│ - Success message                                               │
│ - Auto-redirect to /dashboard?id=1                              │
│ - Redirect delay: 2 seconds                                     │
└───────────────────────┬─────────────────────────────────────────┘
                        │
                        ▼
┌─────────────────────────────────────────────────────────────────┐
│ END: User lands on Main Dashboard                               │
│ - Dashboard pre-configured with selected networks               │
│ - Captive portal ready for customization                        │
└─────────────────────────────────────────────────────────────────┘

8. Data Model

Database Schema

SplashTenantDetails

Purpose: Store Meraki API credentials per tenant

CREATE TABLE SplashTenantDetails (
    Id INT PRIMARY KEY AUTO_INCREMENT,
    TenantId INT NOT NULL,
    APIKey VARCHAR(255) NOT NULL,
    OrganizationId VARCHAR(255) NOT NULL,
    CreationTime DATETIME NOT NULL,
    -- ABP audit fields
    UNIQUE INDEX IX_TenantId (TenantId)
);

SplashMerakiOrganization

Purpose: Cache Meraki organization data

CREATE TABLE SplashMerakiOrganization (
    Id INT PRIMARY KEY AUTO_INCREMENT,
    MerakiId VARCHAR(255) NOT NULL,
    Name VARCHAR(500) NOT NULL,
    TenantId INT NOT NULL,
    CreationTime DATETIME NOT NULL,
    -- ABP audit fields
    UNIQUE INDEX IX_MerakiId_TenantId (MerakiId, TenantId)
);

SplashMerakiNetwork

Purpose: Store selected networks

CREATE TABLE SplashMerakiNetwork (
    Id INT PRIMARY KEY AUTO_INCREMENT,
    MerakiId VARCHAR(255) NOT NULL,
    Name VARCHAR(500) NOT NULL,
    OrganizationId INT NOT NULL,
    TenantId INT NOT NULL,
    CreationTime DATETIME NOT NULL,
    -- ABP audit fields
    FOREIGN KEY (OrganizationId) REFERENCES SplashMerakiOrganization(Id),
    INDEX IX_OrganizationId (OrganizationId)
);

SplashAccessPoint

Purpose: Inventory of wireless access points

CREATE TABLE SplashAccessPoint (
    Id INT PRIMARY KEY AUTO_INCREMENT,
    Name VARCHAR(255) NOT NULL,
    Serial VARCHAR(255) NOT NULL,
    Mac VARCHAR(17) NOT NULL,
    LanIP VARCHAR(50),
    Model VARCHAR(100),
    Lat DECIMAL(10, 8),
    Lng DECIMAL(11, 8),
    NetworkId INT NOT NULL,
    CreationTime DATETIME NOT NULL,
    -- ABP audit fields
    FOREIGN KEY (NetworkId) REFERENCES SplashMerakiNetwork(Id),
    UNIQUE INDEX IX_Serial (Serial),
    INDEX IX_NetworkId (NetworkId)
);

SplashDashboard

Purpose: User-configurable dashboards

CREATE TABLE SplashDashboard (
    Id INT PRIMARY KEY AUTO_INCREMENT,
    Name VARCHAR(255) NOT NULL,
    TenantId INT NOT NULL,
    NetworkIds TEXT NOT NULL,  -- JSON array of network IDs
    CreationTime DATETIME NOT NULL,
    -- ABP audit fields
    INDEX IX_TenantId (TenantId)
);

SplashCaptivePortal

Purpose: Captive portal configurations

CREATE TABLE SplashCaptivePortal (
    Id INT PRIMARY KEY AUTO_INCREMENT,
    Name VARCHAR(255) NOT NULL,
    Description TEXT,
    TenantId INT NOT NULL,
    Configuration TEXT,        -- JSON config (dev/preview)
    ProdConfiguration TEXT,    -- JSON config (production)
    IsActive TINYINT(1) NOT NULL DEFAULT 1,
    IsDeleted TINYINT(1) NOT NULL DEFAULT 0,
    CreationTime DATETIME NOT NULL,
    -- ABP audit fields
    INDEX IX_TenantId (TenantId)
);

Entity Relationships

SplashTenantDetails (1) ─┬─ Stores API key for tenant
                          │
SplashMerakiOrganization (1) ─┬─ Linked via TenantId
                               │
                               ├─ SplashMerakiNetwork (*) ─┬─ Selected networks
                               │                            │
                               │                            └─ SplashAccessPoint (*) ─ APs per network
                               │
                               └─ Referenced by Dashboard NetworkIds

SplashDashboard (*) ─ Contains network ID array (many-to-many via JSON)

SplashCaptivePortal (*) ─ Per-tenant portal configs

Sample Data After Onboarding

SplashTenantDetails:

{
  "Id": 1,
  "TenantId": 1,
  "APIKey": "6bec40cf957de430a6f1f2baa056b99a4fac9ea0",
  "OrganizationId": "537758"
}

SplashMerakiOrganization:

{
  "Id": 1,
  "MerakiId": "537758",
  "Name": "Acme Corporation",
  "TenantId": 1
}

SplashMerakiNetwork:

[
  {
    "Id": 1,
    "MerakiId": "N_123456789",
    "Name": "Oficina Central - CDMX",
    "OrganizationId": 1,
    "TenantId": 1
  },
  {
    "Id": 2,
    "MerakiId": "N_987654321",
    "Name": "Sucursal Monterrey",
    "OrganizationId": 1,
    "TenantId": 1
  }
]

SplashAccessPoint:

[
  {
    "Id": 1,
    "Name": "AP-Floor1-A",
    "Serial": "Q2XX-AAAA-BBBB",
    "Mac": "88:15:44:aa:bb:cc",
    "LanIP": "192.168.1.10",
    "Model": "MR36",
    "Lat": 19.432608,
    "Lng": -99.133209,
    "NetworkId": 1
  }
]

SplashDashboard:

{
  "Id": 1,
  "Name": "🚀 Main Dashboard",
  "TenantId": 1,
  "NetworkIds": "[1,2]"
}

SplashCaptivePortal:

{
  "Id": 1,
  "Name": "Default Captive Portal",
  "Description": "Default Captive Portal",
  "TenantId": 1,
  "Configuration": "{\"selectedLogoImagePath\":\"https://minio.../logo.png\",\"selectedBackgroundImagePath\":\"https://minio.../bg.png\",\"primaryColor\":\"#00B898\",...}",
  "ProdConfiguration": "{...same as Configuration...}",
  "IsActive": true,
  "IsDeleted": false
}

9. UI/UX Components

Current vs. Desired Design System

Color Palette

Legacy (Tabler):

  • Primary: #206bc4 (Blue)
  • Secondary: #64748b (Gray)
  • Success: #2fb344 (Green)
  • Border: #e2e8f0 (Light Gray)

Target (Meraki Theme):

:root {
  --primary: hsl(169, 100%, 36%);        /* #00B898 - Meraki Green */
  --primary-glow: hsl(169, 100%, 42%);   /* #00D4AA - Lighter Green */
  --success: hsl(145, 63%, 49%);         /* Green */
  --warning: hsl(25, 95%, 63%);          /* Orange */
  --destructive: hsl(0, 84.2%, 60.2%);   /* Red */
  --gradient-meraki: linear-gradient(135deg, #00B898 0%, #00D4AA 100%);
}

Typography

Legacy:

  • Font: System fonts (Segoe UI, Arial)
  • Sizes: Bootstrap default scale

Target:

body {
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}

h1, h2, h3, h4, h5, h6 {
  font-weight: 600;
  letter-spacing: -0.02em;
}

Shadows

Legacy: Bootstrap shadows

box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);

Target: Custom elevation system

--shadow-elegant: 0 10px 30px -10px hsl(169 100% 36% / 0.2);
--shadow-lift: 0 20px 40px -15px hsl(169 100% 36% / 0.3);
--shadow-card: 0 2px 8px -2px hsl(220 20% 15% / 0.08);

Animations

Legacy:

.integration-card {
  transition: all 0.3s ease;
}

.integration-card:hover:not(.disabled) {
  transform: translateY(-5px);
}

Target:

Animation Duration Easing Usage
breath 2s ease-in-out Selected cards
float 4s ease-in-out Icon elements
glow-pulse 2s ease-in-out Primary buttons
shake 0.3s ease-in-out Validation errors
ripple 0.6s ease-out Button interactions

Custom Animations:

@keyframes breath {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.02); }
}

@keyframes glow-pulse {
  0%, 100% {
    filter: drop-shadow(0 0 8px hsl(var(--primary-glow) / 0.4));
  }
  50% {
    filter: drop-shadow(0 0 16px hsl(var(--primary-glow) / 0.6));
  }
}

Progress Indicators

Legacy: Step circles with connectors

<div class="step-item step-active">
  <div class="step-number">1</div>
  <div class="step-connector"></div>
  <div class="step-label">Integración</div>
</div>

Target: Liquid progress bar

<motion.div
  className="h-full gradient-meraki"
  initial={{ width: 0 }}
  animate={{ width: `${progress}%` }}
  transition={{ duration: 0.5, ease: "easeInOut" }}
/>

Card Transitions

Legacy: Fade in/out

views[key].classList.add("d-none");
views[key].classList.remove("d-none");

Target: 60% slide with opacity

<motion.div
  initial={{ x: "60%", opacity: 0 }}
  animate={{ x: 0, opacity: 1 }}
  exit={{ x: "-60%", opacity: 0 }}
  transition={{ duration: 0.4, ease: [0.4, 0, 0.2, 1] }}
>

Button States

Legacy: Simple spinner

validateBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span> Validando...';

Target: Morphing button with icon transitions

<RippleButton variant={isValidating ? "default" : isValid ? "success" : "hero"}>
  {isValidating ? (
    <>
      <Loader2 className="mr-2 h-5 w-5 animate-spin" />
      Validando...
    </>
  ) : isValid ? (
    <>
      <Check className="mr-2 h-5 w-5" />
      Validado
    </>
  ) : (
    "Validar"
  )}
</RippleButton>

Interactive Feedback

Legacy:

  • Bootstrap form validation classes (is-invalid, is-valid)
  • Toast notifications (basic)
  • Simple hover effects

Target:

  • Real-time icon morphing (AlertCircle → Loader2 → Check)
  • Spring animations on expand/collapse
  • Breath effect on selected items
  • Ripple effect on button clicks
  • Shake animation on errors
  • Glow pulse on primary actions

10. Dependencies Map

Legacy MVC Dependencies

Frontend:

{
  "Tabler UI": "latest",
  "Bootstrap": "5.x",
  "Font Awesome": "6.4.0",
  "jQuery": "via ABP",
  "ABP Service Proxies": "Dynamic"
}

Backend:

<PackageReference Include="Abp" Version="*" />
<PackageReference Include="Abp.AspNetCore" Version="*" />
<PackageReference Include="Abp.EntityFrameworkCore" Version="*" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="*" />

Target Next.js Dependencies

Production Dependencies:

{
  "@hookform/resolvers": "^3.10.0",
  "@radix-ui/react-*": "^1.x",
  "@tanstack/react-query": "^5.83.0",
  "framer-motion": "^12.23.24",
  "react": "^18.3.1",
  "react-dom": "^18.3.1",
  "react-hook-form": "^7.61.1",
  "lucide-react": "^0.462.0",
  "canvas-confetti": "^1.9.3",
  "zod": "^3.25.76",
  "clsx": "^2.1.1",
  "tailwind-merge": "^2.6.0",
  "next-themes": "^0.3.0"
}

Development Dependencies:

{
  "@types/react": "^18.3.23",
  "@types/node": "^22.16.5",
  "autoprefixer": "^10.4.21",
  "postcss": "^8.5.6",
  "tailwindcss": "^3.4.17",
  "typescript": "^5.8.3"
}

Dependency Purpose Matrix

Dependency Purpose Critical Migration Required
Radix UI Unstyled accessible components Yes New
Framer Motion Advanced animations Yes New
React Hook Form Form state management Yes New
TanStack Query Server state & caching Yes New
Zod Schema validation Yes New
Lucide React Icon library No Replace FA
Canvas Confetti Success celebration No New
Next Themes Dark mode support No New
shadcn/ui Pre-built components Yes New

11. Security & Best Practices

Current Security Measures

API Key Handling

  • Stored in database after validation
  • Not exposed in client-side state
  • Sent via ABP service proxies (POST body)
  • No client-side encryption

Input Validation

  • Client-side: Minimum length check (16 chars)
  • Server-side: API key validated via Meraki API
  • DTOs use [Required] attributes
  • User-friendly exceptions for invalid input

File Upload Security

  • Default assets read from App_Data/CaptivePortal/
  • File path validation prevents directory traversal
  • UUID-based filenames in MinIO
  • Bucket isolation per customer
private string GetValidatedFilePath(string fileName)
{
    if (fileName.Contains("..") ||
        Path.GetInvalidFileNameChars().Any(c => fileName.Contains(c)))
    {
        throw new UserFriendlyException("Invalid file name");
    }
    return Path.Combine(_env.ContentRootPath, "App_Data", "CaptivePortal", fileName);
}

Authorization

  • [AbpMvcAuthorize] on controller
  • Tenant isolation enforced by ABP
  • No cross-tenant data access possible

1. Environment Variables

NEXT_PUBLIC_API_URL=https://api.splashpage.com
API_KEY_ENCRYPTION_SECRET=***
MINIO_ENDPOINT=***
MINIO_ACCESS_KEY=***

2. API Key Encryption

// Encrypt before storing in React Query cache
const encryptApiKey = (key: string) => {
  // Use Web Crypto API
  return encryptedKey;
};

3. CSRF Protection

// Add CSRF token to all mutations
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content;
headers: {
  'X-CSRF-Token': csrfToken
}

4. Rate Limiting (Client-Side)

const { mutate, isLoading } = useMutation({
  mutationFn: validateApiKey,
  retry: 1,
  retryDelay: 5000, // Prevent API spam
});

5. Input Sanitization

const apiKeySchema = z.string()
  .min(40)
  .max(100)
  .regex(/^[a-zA-Z0-9]+$/); // Alphanumeric only

6. Content Security Policy

// next.config.js
headers: [
  {
    key: 'Content-Security-Policy',
    value: "default-src 'self'; script-src 'self' 'unsafe-inline';"
  }
]

12. Migration Considerations

Breaking Changes

1. Step Count Difference

  • Legacy: 5 steps (Integration → Summary)
  • Target: 7 steps (Welcome → Success)
  • Impact: Progress calculation changed
  • Solution: Map legacy step numbers to new system

2. API Key Validation Length

  • Legacy: Minimum 16 characters
  • Target: Minimum 40 characters (Meraki standard)
  • Impact: Legacy keys may fail new validation
  • Solution: Update backend validation to match

3. License Tier Calculation

  • Legacy: 5 tiers based on AP count
  • Target: Not implemented in example
  • Impact: Summary step missing license info
  • Solution: Port getLicenseBracket() function

4. Network Selection UX

  • Legacy: Simple checkboxes
  • Target: Expandable accordion with device details
  • Impact: More complex but better UX
  • Solution: Fetch device details per network

5. Caching Strategy

  • Legacy: IMemoryCache (server-side)
  • Target: TanStack Query (client-side)
  • Impact: Cache keys and TTL need adaptation
  • Solution: Configure React Query defaults
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5 * 60 * 1000, // 5 minutes
      cacheTime: 30 * 60 * 1000, // 30 minutes
    },
  },
});

Data Migration

No database migration required - backend API remains unchanged.

Feature Parity Checklist

Feature Legacy Target Status
Integration selection Port
API key validation Port
Organization listing Port
Device inventory display Port
Network selection Port
Multi-select networks Port
Select All / Clear Add
License bracket display Add
Summary review Port
Finish setup Port
Success redirect Port
Welcome screen New
Animated progress bar New
Confetti celebration New

API Compatibility

No changes required to backend services. All DTOs and endpoints remain the same:

  • ValidateApiKey(OnboardingApiKeyDto)
  • GetOrganizations(OnboardingApiKeyDto)
  • GetOrganizationsNetworks(OnboardingOrganizationDto)
  • FinishSetup(OnboardingFinishSetupDto)

Testing Strategy

Unit Tests (New)

describe('OnboardingWizard', () => {
  it('should navigate through all steps', () => {});
  it('should validate API key format', () => {});
  it('should calculate total APs correctly', () => {});
});

Integration Tests (New)

describe('Onboarding API Integration', () => {
  it('should fetch organizations with cache', () => {});
  it('should create all database records', () => {});
});
describe('Onboarding Flow', () => {
  it('should complete full onboarding', () => {
    cy.visit('/dashboard/onboarding');
    cy.findByText('Comenzar').click();
    cy.findByLabelText('API Key').type('validApiKey123');
    // ... complete all steps
    cy.url().should('include', '/dashboard?id=1');
  });
});

Performance Optimization

1. Code Splitting

const OnboardingWizard = lazy(() => import('@/components/onboarding/OnboardingWizard'));

2. Image Optimization

import Image from 'next/image';

<Image
  src="/meraki-logo.png"
  width={200}
  height={200}
  priority
/>

3. Prefetch Organizations

// Prefetch on API key validation success
await queryClient.prefetchQuery({
  queryKey: ['organizations', apiKey],
  queryFn: () => fetchOrganizations(apiKey),
});

4. Memoize Calculations

const totalAPs = useMemo(() =>
  selectedNetworks.reduce((sum, net) => sum + net.apCount, 0),
  [selectedNetworks]
);

Deployment Considerations

1. Environment Variables

# .env.production
NEXT_PUBLIC_API_URL=https://api.splashpage.com
NEXT_PUBLIC_MERAKI_DASHBOARD_URL=https://dashboard.meraki.com

2. Build Configuration

// next.config.js
module.exports = {
  output: 'standalone',
  images: {
    domains: ['minio.splashpage.com'],
  },
  async rewrites() {
    return [
      {
        source: '/api/:path*',
        destination: 'https://api.splashpage.com/api/:path*',
      },
    ];
  },
};

3. Nginx Configuration (MVC Reverse Proxy)

location /dashboard/onboarding {
  proxy_pass http://nextjs:3000;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

location /Onboarding {
  # Redirect old MVC route to Next.js
  return 301 /dashboard/onboarding;
}

Conclusion

This document provides a comprehensive architectural overview of the Onboarding module, covering both the legacy MVC implementation and the target Next.js design. The migration preserves all business logic and API contracts while delivering a modern, animated user experience.

Next Steps: See onboarding_plan.md for detailed implementation roadmap.