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:
-
Progress Stepper (Lines 124-155)
- Horizontal step indicator (hidden on step 1)
- 5 circles with connectors
- Active/completed state styling
-
Integration View (Lines 161-282)
- 3 integration cards (Meraki, Mist, Catalyst)
- Only Meraki enabled with primary badge
- Coming soon badges for disabled options
-
API Key View (Lines 284-364)
- Single text input with validation
- Minimum 16 characters requirement
- Real-time error feedback
- Link to Meraki dashboard documentation
-
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
-
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
-
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
OnboardingWizardfor 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 wrapperIMemoryCache- Response cachingIRepository<T>- Data persistenceIMinioStorageService- File storage
Key Methods:
1. ValidateApiKey
Task<bool> ValidateApiKey(OnboardingApiKeyDto input)
- Validates API key by attempting to fetch organizations
- Throws
UserFriendlyExceptionif 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:
- Validate organization exists in API response
- Create/Update
SplashTenantDetailswith API key + org ID - Insert/Update
SplashMerakiOrganizationrecord - Insert selected
SplashMerakiNetworkrecords - Fetch all devices for organization (single API call)
- Filter devices by network and insert
SplashAccessPointrecords - Create default
SplashDashboardwith network IDs - Create default
SplashCaptivePortalif 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
ConfigurationandProdConfigurationfields
5. CreateDefaultCaptivePortal (Private)
Task CreateDefaultCaptivePortal(string name, string description)
Assets:
DefaultLogo.pngDefaultBackground.pngDefaultConfiguration.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 OrganizationIdstring ApiKey |
Org request |
OnboardingOrganizationOverviewDto |
string OrganizationNamestring OrganizationIdobject Overwiew |
Org with device counts |
OnboardingOrganizationNetworksDto |
string Idstring Namestring OrganizationIdint ApCount |
Network data |
OnboardingFinishSetupDto |
string OrganizationIdList<string> Networksstring ApiKey |
Final setup payload |
Repository Layer
Entity Repositories:
IRepository<SplashTenantDetails>- Tenant configurationIRepository<SplashMerakiOrganization>- Organization recordsIRepository<SplashMerakiNetwork>- Network recordsIRepository<SplashAccessPoint>- Access point inventoryIRepository<SplashDashboard>- Dashboard configurationsIRepository<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 countsOrganizationNetworks_{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
Recommended Enhancements for Next.js
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', () => {});
});
E2E Tests (Recommended)
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.