Gograb Admin Implementation Design

Documentation for GoGrab Admin

GoGrab Admin - Implementation Design

Document: Technical Implementation Design Date: 2025-10-02 Version: 1.0.0 Status: MVP Implementation Ready

Table of Contents

  1. Architecture Overview
  2. Component Design
  3. Routing System
  4. State Management
  5. Authentication Flow
  6. Module Integration
  7. Data Flow
  8. Interfaces & Types
  9. Service Layer
  10. Implementation Guidelines

Architecture Overview

System Context

┌─────────────────────────────────────────────────────────────────┐
│                        GoGrab Admin MVP                         │
│                                                                 │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐        │
│  │   Browser    │  │  Angular 20  │  │  Mock API    │        │
│  │   SPA        │←→│  Standalone  │←→│  (Dev Mode)  │        │
│  └──────────────┘  └──────────────┘  └──────────────┘        │
│                            ↓                                    │
│                    ┌───────────────┐                           │
│                    │ Module System │                           │
│                    │  (Dynamic     │                           │
│                    │   Loading)    │                           │
│                    └───────────────┘                           │
│                            ↓                                    │
│            ┌───────────────────────────────┐                   │
│            │  @gograb-space/user-management │                  │
│            │  (CRUD Components & Routes)   │                  │
│            └───────────────────────────────┘                   │
└─────────────────────────────────────────────────────────────────┘

Architectural Principles

  1. Standalone Components Only: No NgModules, 100% standalone architecture
  2. OnPush Change Detection: All components use OnPush strategy for performance
  3. Signal-First: Prefer Signals over Observables for local state
  4. RxJS for Async: All async operations use RxJS (no Promises)
  5. Module System: Dynamic module loading via @libs-core/module-integrator
  6. SOLID Principles: Strict adherence to Single Responsibility, DI, etc.

Layer Architecture

┌─────────────────────────────────────────────────────────────┐
│ PRESENTATION LAYER                                          │
│ ├─ Components (LoginComponent, DashboardComponent)         │
│ ├─ Layout (AppShellComponent, HeaderComponent, Footer)     │
│ └─ Guards (authGuard, permissionGuard, roleGuard)          │
└─────────────────────────────────────────────────────────────┘
                           ↓
┌─────────────────────────────────────────────────────────────┐
│ SERVICE LAYER                                               │
│ ├─ AuthService (login, logout, token management)           │
│ ├─ DashboardService (stats fetching)                       │
│ └─ IntegratorService (module lifecycle orchestration)      │
└─────────────────────────────────────────────────────────────┘
                           ↓
┌─────────────────────────────────────────────────────────────┐
│ STATE LAYER                                                 │
│ ├─ NGXS Stores (UserState, AuthState from libraries)       │
│ ├─ Component Signals (local reactive state)                │
│ └─ Store Selectors (reactive state subscriptions)          │
└─────────────────────────────────────────────────────────────┘
                           ↓
┌─────────────────────────────────────────────────────────────┐
│ DATA LAYER                                                  │
│ ├─ Mock API (@libs-core/mock-api)                       │
│ ├─ HTTP Interceptors (auth, context, mock)                 │
│ └─ Models/DTOs (@libs-core/shared-models)               │
└─────────────────────────────────────────────────────────────┘

Component Design

Component Hierarchy

AppComponent (root)
└── <router-outlet>
    ├── LoginComponent (/login)
    │   ├── Reactive Form (email, password)
    │   ├── Validation Messages
    │   └── Submit Button
    │
    └── AppShellComponent (/ - authenticated)
        ├── HeaderComponent
        │   ├── Logo
        │   ├── User Profile Display
        │   └── Logout Button
        │
        ├── <main> (router-outlet for content)
        │   ├── DashboardComponent (/dashboard)
        │   │   ├── Stats Cards (4x)
        │   │   └── Quick Actions
        │   │
        │   └── UserManagementModule (/users/*)
        │       ├── UserListComponent (/users/list)
        │       ├── UserFormComponent (/users/create, /users/:id/edit)
        │       ├── UserDetailComponent (/users/:id)
        │       └── UserDeleteConfirmComponent (modal)
        │
        └── FooterComponent
            ├── Copyright
            ├── Version
            └── Credits

Component Specifications

1. AppComponent

Purpose: Root application component Type: Container (dumb) Responsibility: Bootstrap router-outlet only

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet],
  template: '<router-outlet />',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent {}

Complexity: Minimal (~10 lines)


2. LoginComponent

Purpose: User authentication interface Type: Smart component (form handling + service interaction) Location: apps/gograb-admin/src/app/features/auth/login.component.ts

Interface:

interface LoginForm {
  email: FormControl<string>;
  password: FormControl<string>;
}

interface LoginComponent {
  // Form
  loginForm: FormGroup<LoginForm>;

  // State
  isLoading: WritableSignal<boolean>;
  errorMessage: WritableSignal<string | null>;

  // Methods
  onSubmit(): void;
  private handleLoginSuccess(result: LoginResult): void;
  private handleLoginError(error: Error): void;
}

Dependencies:

State Management:

Validation Rules:

Flow:

  1. User enters credentials
  2. Form validation on blur
  3. Submit button disabled until valid
  4. On submit → show loading spinner
  5. AuthService.login() → Observable
  6. Success → navigate to /dashboard
  7. Error → display error message, hide spinner

Template Structure:

<div class="login-container">
  <div class="login-card">
    <h1>GoGrab Admin</h1>
    <form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
      <div class="form-field">
        <label>Email</label>
        <input type="email" formControlName="email" />
        <span class="error" *ngIf="email validation error">...</span>
      </div>

      <div class="form-field">
        <label>Password</label>
        <input type="password" formControlName="password" />
        <span class="error" *ngIf="password validation error">...</span>
      </div>

      <button type="submit" [disabled]="!loginForm.valid || isLoading()">
        @if (isLoading()) {
          <span>Logging in...</span>
        } @else {
          <span>Login</span>
        }
      </button>

      @if (errorMessage(); as error) {
        <div class="error-alert">{{ error }}</div>
      }
    </form>
  </div>
</div>

Complexity: ~120 líneas TS + 40 líneas HTML


3. AppShellComponent

Purpose: Main layout structure for authenticated routes Type: Container (layout/dumb) Location: apps/gograb-admin/src/app/core/layout/app-shell.component.ts

Interface:

interface AppShellComponent {
  // No state needed - pure layout
}

Dependencies:

Template Structure:

<div class="app-shell">
  <app-header />

  <main class="main-content">
    <router-outlet />
  </main>

  <app-footer />
</div>

Styling:

.app-shell {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

.main-content {
  flex: 1;
  padding: 2rem;
  max-width: 1400px;
  margin: 0 auto;
  width: 100%;
}

Complexity: ~100 líneas (TS + HTML + SCSS)


4. HeaderComponent

Purpose: Top navigation bar with user profile and logout Type: Smart component (user profile access) Location: apps/gograb-admin/src/app/core/layout/header.component.ts

Interface:

interface HeaderComponent {
  // State
  userProfile: Signal<UserProfile | null>;
  isMenuOpen: WritableSignal<boolean>;

  // Methods
  toggleMenu(): void;
  logout(): void;
}

Dependencies:

State Management:

Template Structure:

<header class="app-header">
  <div class="header-content">
    <div class="logo">
      <span>GoGrab Admin</span>
    </div>

    <nav class="main-nav">
      <a routerLink="/dashboard" routerLinkActive="active">Dashboard</a>
      <a routerLink="/users/list" routerLinkActive="active">Users</a>
    </nav>

    <div class="user-menu">
      @if (userProfile(); as user) {
        <button (click)="toggleMenu()">
          <span>{{ user.displayName }}</span>
          <span class="role-badge">{{ user.role.name }}</span>
        </button>

        @if (isMenuOpen()) {
          <div class="dropdown-menu">
            <button (click)="logout()">Logout</button>
          </div>
        }
      }
    </div>
  </div>
</header>

Complexity: ~80 líneas TS + 20 líneas HTML


5. FooterComponent

Purpose: Bottom page footer with copyright and version Type: Dumb component (pure presentation) Location: apps/gograb-admin/src/app/core/layout/footer.component.ts

Specification: See /docs/app/gograb-admin-footer-spec.md

Complexity: ~40 líneas TS + 15 líneas HTML


6. DashboardComponent

Purpose: Main dashboard with statistics overview Type: Smart component (data fetching) Location: apps/gograb-admin/src/app/features/dashboard/dashboard.component.ts

Interface:

interface DashboardStats {
  totalUsers: number;
  activeUsers: number;
  newToday: number;
  lastLoginAt: string;
}

interface DashboardComponent {
  // State
  stats: Signal<DashboardStats | null>;
  isLoading: WritableSignal<boolean>;
  error: WritableSignal<string | null>;

  // Lifecycle
  ngOnInit(): void;

  // Methods
  private loadStats(): void;
}

Dependencies:

State Management:

Template Structure:

<div class="dashboard">
  <h1>Dashboard</h1>

  @if (isLoading()) {
    <div class="loading">Loading statistics...</div>
  } @else if (error(); as err) {
    <div class="error">{{ err }}</div>
  } @else if (stats(); as data) {
    <div class="stats-grid">
      <div class="stat-card">
        <h3>Total Users</h3>
        <p class="stat-value">{{ data.totalUsers }}</p>
      </div>

      <div class="stat-card">
        <h3>Active Users</h3>
        <p class="stat-value">{{ data.activeUsers }}</p>
      </div>

      <div class="stat-card">
        <h3>New Today</h3>
        <p class="stat-value">{{ data.newToday }}</p>
      </div>

      <div class="stat-card">
        <h3>Last Login</h3>
        <p class="stat-value">{{ data.lastLoginAt | date }}</p>
      </div>
    </div>

    <div class="quick-actions">
      <h2>Quick Actions</h2>
      <button routerLink="/users/create">Create New User</button>
      <button routerLink="/users/list">View All Users</button>
    </div>
  }
</div>

Complexity: ~100 líneas TS + 50 líneas HTML


Routing System

Route Configuration

app.routes.ts

import { Routes } from '@angular/router';
import { authGuard } from '@libs-core/security';
import { LoginComponent } from './features/auth/login.component';
import { DashboardComponent } from './features/dashboard/dashboard.component';
import { AppShellComponent } from './core/layout/app-shell.component';

export const routes: Routes = [
  // Public routes
  {
    path: 'login',
    component: LoginComponent,
    title: 'Login - GoGrab Admin'
  },

  // Protected routes (authenticated)
  {
    path: '',
    component: AppShellComponent,
    canActivate: [authGuard],
    children: [
      {
        path: '',
        redirectTo: 'dashboard',
        pathMatch: 'full'
      },
      {
        path: 'dashboard',
        component: DashboardComponent,
        title: 'Dashboard - GoGrab Admin'
      },
      // Module routes registered dynamically by ModuleIntegratorService
      // /users/* routes will be added at runtime
    ]
  },

  // Fallback
  {
    path: '**',
    redirectTo: 'dashboard'
  }
];

Dynamic Route Registration

Process:

  1. provideModuleIntegrator initializes module system
  2. ModuleDiscoveryService finds UserManagementModule
  3. Module lifecycle: configure() → initialize() → start()
  4. During start(), IntegratorService calls Router.resetConfig()
  5. New config = original routes + module routes (e.g., userManagementRoutes)
  6. Navigation to /users/* now works

Module Routes (from @gograb-space/user-management):

// These routes are exported from user-management library
export const userManagementRoutes: Routes = [
  { path: '', redirectTo: 'list', pathMatch: 'full' },
  {
    path: 'list',
    component: UserListComponent,
    canActivate: [authGuard, permissionGuard('users:read')]
  },
  {
    path: 'create',
    component: UserFormComponent,
    canActivate: [authGuard, permissionGuard('users:create')],
    data: { mode: 'create' }
  },
  {
    path: ':id',
    component: UserDetailComponent,
    canActivate: [authGuard, permissionGuard('users:read')]
  },
  {
    path: ':id/edit',
    component: UserFormComponent,
    canActivate: [authGuard, permissionGuard('users:update')],
    data: { mode: 'edit' }
  }
];

Guards

authGuard

export const authGuard: CanActivateFn = (route, state) => {
  const authService = inject(AuthService);
  const router = inject(Router);

  const hasToken = authService.hasValidToken();

  if (!hasToken) {
    router.navigate(['/login'], {
      queryParams: { returnUrl: state.url }
    });
    return false;
  }

  return true;
};

permissionGuard (Factory)

export function permissionGuard(permission: string): CanActivateFn {
  return (route, state) => {
    const permissionService = inject(PermissionService);
    const router = inject(Router);

    const hasPermission = permissionService.hasPermission(permission)();

    if (!hasPermission) {
      router.navigate(['/unauthorized']);
      return false;
    }

    return true;
  };
}

roleGuard (Factory)

export function roleGuard(role: SystemRoles): CanActivateFn {
  return (route, state) => {
    const authService = inject(AuthService);
    const router = inject(Router);

    const userRole = authService.getCurrentUser()?.role;

    if (userRole?.id !== role) {
      router.navigate(['/unauthorized']);
      return false;
    }

    return true;
  };
}

State Management

NGXS Store Architecture

┌──────────────────────────────────────────────────────────┐
│ NGXS STORES (from libraries)                            │
├──────────────────────────────────────────────────────────┤
│ AuthState                                                │
│ ├─ userProfile: UserProfile | null                      │
│ ├─ isAuthenticated: boolean                             │
│ └─ token: string | null                                 │
│                                                          │
│ UserState (from @gograb-space/user-management)          │
│ ├─ users: User[]                                        │
│ ├─ selectedUser: User | null                            │
│ ├─ isLoading: boolean                                   │
│ └─ error: string | null                                 │
└──────────────────────────────────────────────────────────┘

Component State Pattern

Smart Components (LoginComponent, DashboardComponent, HeaderComponent):

Dumb Components (AppShellComponent, FooterComponent):

Signal Usage Patterns

WritableSignal (local state):

class LoginComponent {
  isLoading = signal(false);
  errorMessage = signal<string | null>(null);

  onSubmit() {
    this.isLoading.set(true);
    this.errorMessage.set(null);

    this.authService.login(...).subscribe({
      next: () => {
        this.isLoading.set(false);
        this.router.navigate(['/dashboard']);
      },
      error: (err) => {
        this.isLoading.set(false);
        this.errorMessage.set(err.message);
      }
    });
  }
}

Computed Signal (derived state):

class DashboardComponent {
  private statsSignal = signal<DashboardStats | null>(null);

  // Computed signal for user-friendly display
  formattedStats = computed(() => {
    const stats = this.statsSignal();
    if (!stats) return null;

    return {
      ...stats,
      lastLoginAt: formatDistanceToNow(new Date(stats.lastLoginAt))
    };
  });
}

Signal from Observable (NGXS selector):

class HeaderComponent {
  private store = inject(Store);

  // Convert NGXS selector to Signal
  userProfile = toSignal(
    this.store.select(AuthState.userProfile),
    { initialValue: null }
  );
}

Authentication Flow

Login Sequence Diagram

User                LoginComponent      AuthService         MockAPI         Store           Router
  │                       │                  │                 │               │               │
  │   Enter credentials  │                  │                 │               │               │
  │─────────────────────>│                  │                 │               │               │
  │                       │                  │                 │               │               │
  │   Click Submit       │                  │                 │               │               │
  │─────────────────────>│                  │                 │               │               │
  │                       │                  │                 │               │               │
  │                       │   login(email, pw)                 │               │               │
  │                       │─────────────────>│                 │               │               │
  │                       │                  │                 │               │               │
  │                       │                  │  POST /api/auth/login           │               │
  │                       │                  │────────────────>│               │               │
  │                       │                  │                 │               │               │
  │                       │                  │  { token, profile }             │               │
  │                       │                  │<────────────────│               │               │
  │                       │                  │                 │               │               │
  │                       │                  │  storeToken()   │               │               │
  │                       │                  │──┐              │               │               │
  │                       │                  │<─┘              │               │               │
  │                       │                  │                 │               │               │
  │                       │                  │  dispatch(SetAuthProfile)       │               │
  │                       │                  │────────────────────────────────>│               │
  │                       │                  │                 │               │               │
  │                       │   LoginResult    │                 │               │               │
  │                       │<─────────────────│                 │               │               │
  │                       │                  │                 │               │               │
  │                       │  navigate('/dashboard')            │               │               │
  │                       │────────────────────────────────────────────────────────────────────>│
  │                       │                  │                 │               │               │
  │   Dashboard loads    │                  │                 │               │               │
  │<─────────────────────────────────────────────────────────────────────────────────────────────│

Token Management

Storage:

Token Structure (Mock):

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "profile": {
    "id": "1",
    "tenantId": "default-tenant",
    "email": "[email protected]",
    "displayName": "Admin User",
    "role": {
      "id": "admin",
      "name": "Administrator",
      "level": 100
    },
    "permissions": [
      { "id": "1", "name": "users:read", "resource": "users", "action": "read" },
      { "id": "2", "name": "users:create", "resource": "users", "action": "create" },
      { "id": "3", "name": "users:update", "resource": "users", "action": "update" },
      { "id": "4", "name": "users:delete", "resource": "users", "action": "delete" },
      { "id": "5", "name": "users:manage", "resource": "users", "action": "manage" }
    ]
  }
}

Validation:


Module Integration

Bootstrap Sequence

main.ts
  │
  ├─> bootstrapApplication(AppComponent, appConfig)
  │
  └─> appConfig.providers[]
       │
       ├─> 1. provideRouter(routes, withComponentInputBinding())
       │      └─> Registers app base routes
       │
       ├─> 2. provideModuleIntegrator({
       │           autoDiscovery: true,
       │           validateDependencies: true,
       │           enableLifecycleLogging: true,
       │           tenantId: 'default-tenant'
       │         })
       │      └─> Initializes IntegratorService
       │          └─> ModuleDiscoveryService.discoverAndRegister()
       │              └─> Finds @ModuleDecorator decorated modules
       │                  └─> UserManagementModule discovered
       │
       ├─> 3. provideHttpClient(withInterceptors([
       │        authInterceptor,
       │        contextInterceptor,
       │        mockHttpInterceptor
       │      ]))
       │
       ├─> 4. provideStore([UserState])
       │      └─> NGXS state initialization
       │
       ├─> 5. provideMockApi([UserMockApi])
       │      └─> Mock API routes registration
       │
       ├─> 6. provideEventBus()
       │      └─> Event system initialization
       │
       └─> 7. provideLogger({
                appenders: [ConsoleAppender],
                level: LogLevel.DEBUG,
                context: { appName: 'gograb-admin', tenantId: 'default-tenant' }
              })

Module Lifecycle

IntegratorService.initializeApplication()
  │
  ├─> 1. DISCOVERY
  │      ModuleDiscoveryService.discoverAndRegister()
  │      └─> Scans for @ModuleDecorator
  │          └─> Finds UserManagementModule
  │              └─> Adds to ModuleRegistry
  │
  ├─> 2. CONFIGURATION (from API)
  │      GET /api/tenants/default-tenant/modules/config
  │      └─> Returns: [{ id: 'user-management', enabled: true, config: {...} }]
  │
  ├─> 3. DEPENDENCY RESOLUTION
  │      DependencySolverService.validateDependencies()
  │      └─> Builds dependency graph
  │          └─> Validates no circular dependencies
  │              └─> Returns execution order
  │
  ├─> 4. LIFECYCLE EXECUTION
  │      For each module in execution order:
  │
  │      ├─> 4a. CONFIGURE
  │      │      UserManagementModule.configure(config)
  │      │      └─> Log: "Module 'user-management' configured"
  │      │
  │      ├─> 4b. INITIALIZE
  │      │      UserManagementModule.initialize()
  │      │      └─> Sets up services, state, event listeners
  │      │      └─> Log: "Module 'user-management' initialized"
  │      │
  │      └─> 4c. START
  │             UserManagementModule.start()
  │             └─> Module activates
  │             └─> Log: "Module 'user-management' started"
  │
  └─> 5. ROUTE REGISTRATION
         IntegratorService.registerModuleRoutes()
         └─> Router.resetConfig(baseRoutes + userManagementRoutes)
             └─> /users/* routes now available
             └─> Log: "Routes registered for user-management"

Console Logging Output

Expected console logs during bootstrap:

[LOG] IntegratorService initialized
[LOG] Module discovery started
[LOG] Module 'user-management' discovered
[LOG] Fetching module configuration for tenant: default-tenant
[LOG] Module configuration loaded: 1 modules
[LOG] Validating module dependencies
[LOG] Dependency validation passed
[LOG] Starting module lifecycle for: user-management
[LOG] Module 'user-management' configured
[LOG] Module 'user-management' initialized
[LOG] Module 'user-management' started
[LOG] Routes registered for user-management: 5 routes
[LOG] Application initialization complete

Data Flow

Read Operation Flow (Dashboard Stats)

DashboardComponent
  │
  │ ngOnInit()
  │
  ├─> DashboardService.getStats()
  │      │
  │      ├─> HTTP GET /api/dashboard/stats
  │      │      │
  │      │      └─> MockAPI intercepts
  │      │             │
  │      │             └─> Returns mock stats
  │      │
  │      └─> Observable<DashboardStats>
  │
  └─> Subscribe
       │
       ├─> Success
       │      └─> stats.set(data)
       │             └─> Template updates (OnPush)
       │
       └─> Error
              └─> error.set(errorMessage)
                     └─> Template shows error

Write Operation Flow (User Login)

LoginComponent
  │
  │ onSubmit()
  │
  ├─> AuthService.login(email, password)
  │      │
  │      ├─> HTTP POST /api/auth/login
  │      │      │
  │      │      └─> MockAPI intercepts
  │      │             │
  │      │             ├─> Validates credentials
  │      │             └─> Returns { token, profile }
  │      │
  │      ├─> SecureStorageService.setItem('auth_token', token)
  │      │
  │      ├─> Store.dispatch(new SetAuthProfile(profile))
  │      │      │
  │      │      └─> AuthState reducer
  │      │             └─> Updates state.userProfile
  │      │
  │      └─> Observable<LoginResult>
  │
  └─> Subscribe
       │
       ├─> Success
       │      └─> Router.navigate(['/dashboard'])
       │
       └─> Error
              └─> errorMessage.set(error)

CRUD Operation Flow (User Management)

UserListComponent (from @gograb-space/user-management)
  │
  │ ngOnInit()
  │
  ├─> Store.dispatch(new LoadUsers())
  │      │
  │      └─> UserState
  │             │
  │             ├─> UserService.getAll()
  │             │      │
  │             │      └─> HTTP GET /api/users
  │             │             │
  │             │             └─> MockAPI returns users array
  │             │
  │             └─> Reducer updates state.users
  │
  └─> users$ = Store.select(UserState.users)
       │
       └─> Template displays via AsyncPipe
              │
              @for (user of users$ | async) {
                <user-card [user]="user" />
              }

Interfaces & Types

Core Types

// Auth Types
interface LoginCredentials {
  email: string;
  password: string;
}

interface LoginResult {
  success: boolean;
  token?: string;
  profile?: UserProfile;
  error?: string;
}

interface UserProfile {
  id: string;
  tenantId: string;
  email: string;
  displayName: string;
  role: Role;
  permissions: Permission[];
  createdAt: string;
  updatedAt: string;
}

interface Role {
  id: string;
  name: string;
  level: number;
  permissions?: Permission[];
}

interface Permission {
  id: string;
  tenantId: string;
  name: string;          // Format: "resource:action" (e.g., "users:create")
  resource: string;      // e.g., "users", "settings"
  action: string;        // e.g., "create", "read", "update", "delete"
  description: string;
}

// Dashboard Types
interface DashboardStats {
  totalUsers: number;
  activeUsers: number;
  newToday: number;
  lastLoginAt: string;  // ISO 8601 datetime
}

// Module Integration Types
interface ModuleConfig {
  autoDiscovery: boolean;
  validateDependencies: boolean;
  enableLifecycleLogging: boolean;
  tenantId: string;
}

interface ModuleManifest {
  readonly id: string;
  readonly version: string;
  readonly displayName?: string;
  readonly permissions?: readonly string[];
}

// HTTP Response Types (from @libs-core/shared-models)
interface ApiResponse<T> {
  success: boolean;
  data?: T;
  error?: ErrorResponse;
  meta?: Record<string, unknown>;
}

interface ErrorResponse {
  code: string;
  message: string;
  details?: Record<string, unknown>;
}

Form Types

// Login Form
interface LoginFormValue {
  email: string;
  password: string;
}

type LoginForm = FormGroup<{
  email: FormControl<string>;
  password: FormControl<string>;
}>;

Service Layer

AuthService

Location: apps/gograb-admin/src/app/core/services/auth.service.ts

Interface:

@Injectable({ providedIn: 'root' })
export class AuthService {
  private http = inject(HttpClient);
  private store = inject(Store);
  private router = inject(Router);
  private storage = inject(SecureStorageService);

  /**
   * Authenticate user with credentials
   * @param email User email
   * @param password User password
   * @returns Observable<LoginResult>
   */
  login(email: string, password: string): Observable<LoginResult>;

  /**
   * Logout current user
   * Clears token and navigates to login
   */
  logout(): void;

  /**
   * Check if user has valid authentication token
   * @returns boolean
   */
  hasValidToken(): boolean;

  /**
   * Get current authenticated user profile
   * @returns UserProfile | null
   */
  getCurrentUser(): UserProfile | null;

  /**
   * Get authentication token
   * @returns string | null
   */
  getToken(): string | null;
}

Implementation:

@Injectable({ providedIn: 'root' })
export class AuthService {
  private http = inject(HttpClient);
  private store = inject(Store);
  private router = inject(Router);
  private storage = inject(SecureStorageService);

  login(email: string, password: string): Observable<LoginResult> {
    return this.http.post<ApiResponse<LoginResult>>(
      '/api/auth/login',
      { email, password }
    ).pipe(
      map(response => {
        if (response.success && response.data) {
          const { token, profile } = response.data;

          // Store token
          this.storage.setItem('auth_token', token);

          // Update NGXS state
          this.store.dispatch(new SetAuthProfile(profile));

          return { success: true, token, profile };
        }

        return {
          success: false,
          error: response.error?.message || 'Login failed'
        };
      }),
      catchError(error => {
        return of({
          success: false,
          error: error.message || 'Network error'
        });
      })
    );
  }

  logout(): void {
    this.storage.removeItem('auth_token');
    this.store.dispatch(new ClearAuthProfile());
    this.router.navigate(['/login']);
  }

  hasValidToken(): boolean {
    const token = this.getToken();
    // MVP: Just check existence
    // Post-MVP: Validate JWT signature and expiry
    return token !== null && token.length > 0;
  }

  getCurrentUser(): UserProfile | null {
    return this.store.selectSnapshot(AuthState.userProfile);
  }

  getToken(): string | null {
    return this.storage.getItem('auth_token');
  }
}

DashboardService

Location: apps/gograb-admin/src/app/features/dashboard/dashboard.service.ts

Interface:

@Injectable({ providedIn: 'root' })
export class DashboardService {
  private http = inject(HttpClient);

  /**
   * Fetch dashboard statistics
   * @returns Observable<DashboardStats>
   */
  getStats(): Observable<DashboardStats>;
}

Implementation:

@Injectable({ providedIn: 'root' })
export class DashboardService {
  private http = inject(HttpClient);

  getStats(): Observable<DashboardStats> {
    return this.http.get<ApiResponse<DashboardStats>>(
      '/api/dashboard/stats'
    ).pipe(
      map(response => {
        if (response.success && response.data) {
          return response.data;
        }
        throw new Error(response.error?.message || 'Failed to load stats');
      })
    );
  }
}

Implementation Guidelines

Component Development Checklist

For each component:

Service Development Checklist

For each service:

State Management Checklist

For state integration:

Routing Checklist

For routing setup:

Module Integration Checklist

For module system:

Testing Checklist (Manual for MVP)

For each feature:


Next Steps

This implementation design provides the complete blueprint for gograb-admin MVP. The next phase is execution:

Day 1 - Foundation

  1. Run Nx generator to create app
  2. Setup app.config.ts with all providers
  3. Create basic app.routes.ts
  4. Verify build and serve

Day 2 - Authentication

  1. Create LoginComponent with reactive form
  2. Implement AuthService
  3. Setup mock auth endpoint
  4. Create AppShellComponent
  5. Test login flow

Day 3 - Dashboard & Layout

  1. Create DashboardComponent
  2. Implement DashboardService
  3. Create HeaderComponent
  4. Create FooterComponent
  5. Configure module integrator
  6. Verify lifecycle logs

Day 4 - Module Integration

  1. Configure module routes registration
  2. Test /users/* navigation
  3. Verify CRUD operations work
  4. Test permissions (admin vs editor)
  5. Validate lifecycle logs

Day 5 - Polish & Validation

  1. Apply basic styling
  2. Add error handling
  3. Add loading states
  4. Manual end-to-end testing
  5. Documentation and demo prep

Document Version: 1.0.0 Last Updated: 2025-10-02 Status: Ready for Implementation