Landing Page Implementation

Complete guide to the editor landing page with Flash-persistence and AI integration

Landing Page Implementation

Overview

The editor landing page provides a welcoming introduction to new users with automatic skip logic for returning users. It uses Flash-persistence for state management and includes AI assistance for enhanced onboarding.

Features

Architecture

Component Structure

``` landing-editor/ ├── landing-editor.component.ts # Main component logic ├── landing-editor.component.html # Template └── landing-editor.component.scss # Styles ```

Data Flow

``` User Visits Landing ↓ Landing Guard Checks ↓ ├─ First Time → Show Landing ├─ Skip Enabled → Go to Workspace └─ Remember Project → Open Last Project ```

Implementation

Basic Setup

```typescript import { Component, computed, signal } from '@angular/core'; import { Router } from '@angular/router'; import { autoFlashState } from '@storage-system'; import { ProjectDetectionService } from '../../services/project-detection.service';

@Component({ selector: 'editor-landing', standalone: true, templateUrl: './landing-editor.component.html', styleUrls: ['./landing-editor.component.scss'] }) export class LandingComponent { private router = inject(Router); private projectDetection = inject(ProjectDetectionService);

// Flash-persistence for preferences private landingPrefs = autoFlashState( 'editor-preferences/landing', { showLandingByDefault: true, rememberLastProject: false, visits: 0, lastVisit: new Date().toISOString() } );

// Computed signals for UI showLandingByDefault = computed(() => this.landingPrefs().showLandingByDefault ); rememberLastProject = computed(() => this.landingPrefs().rememberLastProject ); } ```

Flash-Persistence Integration

User Preferences

```typescript // Define preferences interface interface LandingPreferences { showLandingByDefault: boolean; rememberLastProject: boolean; visits: number; lastVisit: string; lastProjectId?: string; }

// Create auto-persisted state private landingPrefs = autoFlashState( 'editor-preferences/landing', { showLandingByDefault: true, rememberLastProject: false, visits: 0, lastVisit: new Date().toISOString() } );

// Update preference (automatically persists) toggleLandingPreference() { this.landingPrefs.update(prefs => ({ ...prefs, showLandingByDefault: !prefs.showLandingByDefault })); } ```

Recent Projects

```typescript // Define project interface interface RecentProject { id: string; name: string; description: string; lastModified: Date; thumbnail?: string; }

// Create auto-persisted projects list private recentProjectsState = autoFlashState<RecentProject[]>( 'editor-preferences/recent-projects', [] );

// Computed for UI recentProjects = computed(() => this.recentProjectsState());

// Add project (automatically persists) addRecentProject(project: RecentProject) { this.recentProjectsState.update(projects => [project, ...projects].slice(0, 10) // Keep last 10 ); } ```

Auto-Skip Logic

```typescript async ngOnInit() { // Track visit this.landingPrefs.update(prefs => ({ ...prefs, visits: prefs.visits + 1, lastVisit: new Date().toISOString() }));

// Check if should skip await this.checkShouldSkip();

// Continue initialization this.initializeAnimations(); }

private async checkShouldSkip() { const prefs = this.landingPrefs(); const hasProjects = await this.projectDetection.hasExistingProjects();

if (!prefs.showLandingByDefault && hasProjects) { console.log('⏭️ Auto-skipping landing (preference disabled)'); this.startEditing(); } } ```

Quick Actions

```typescript quickActions = computed<QuickAction[]>(() => [ { id: 'new-widget', title: 'Create Widget', description: 'Start with a blank canvas', icon: '🎨', action: () => this.createNewWidget(), color: 'primary' }, { id: 'template', title: 'Use Template', description: 'Choose from pre-built templates', icon: '📋', action: () => this.browseTemplates(), color: 'secondary' }, { id: 'import', title: 'Import Widget', description: 'Import existing configuration', icon: '📥', action: () => this.importWidget(), color: 'tertiary' }, { id: 'tutorial', title: 'Interactive Tutorial', description: 'Learn the basics', icon: '🎓', action: () => this.startTutorial(), color: 'secondary' } ]); ```

Landing Guard

The landing guard handles automatic skip logic at the route level:

```typescript // guards/landing.guard.ts export const landingGuard: CanActivateFn = async () => { const router = inject(Router); const projectDetection = inject(ProjectDetectionService); const storage = inject(GgStorage);

try { // Load preferences const prefs = await storage.get('flash:editor-preferences/landing'); const hasProjects = await projectDetection.hasExistingProjects();

// Skip if disabled and has projects
if (prefs?.showLandingByDefault === false && hasProjects) {
  await router.navigate(['/editor/workspace']);
  return false;
}

// Remember last project
if (prefs?.rememberLastProject && hasProjects) {
  const lastProject = await projectDetection.getLastProject();
  if (lastProject) {
    await router.navigate(['/editor/workspace'], {
      queryParams: { pageId: lastProject }
    });
    return false;
  }
}

// Show landing
return true;

} catch (error) { console.warn('LandingGuard: Error, showing landing', error); return true; } }; ```

Usage

Navigation

```typescript // Navigate to landing this.router.navigate(['/editor/landing']);

// Skip landing this.router.navigate(['/editor/workspace']);

// Open specific project this.router.navigate(['/editor/workspace'], { queryParams: { pageId: 'project-123' } }); ```

Preference Management

```typescript // Toggle "show landing by default" component.toggleLandingPreference();

// Toggle "remember last project" component.toggleRememberProject();

// Skip landing and save preference component.onSkipLanding(); ```

Project Detection

```typescript // Check if user has projects const hasProjects = await detection.hasExistingProjects();

// Get project count const count = await detection.getProjectCount();

// Get recent projects const recent = await detection.getRecentProjects(5);

// Check specific project const exists = await detection.projectExists('project-123'); ```

Configuration

Storage Keys

Key Type Purpose
flash:editor-preferences/landing LandingPreferences User preferences
flash:editor-preferences/recent-projects RecentProject[] Recent projects list
flash:editor-web/pages Page[] Page data for detection
gg_editor_user_id UserIdentity User identification (localStorage)

Default Preferences

```typescript const defaultPreferences: LandingPreferences = { showLandingByDefault: true, // Show landing for new users rememberLastProject: false, // Don't auto-open last project visits: 0, // Visit counter lastVisit: new Date().toISOString(), // Last visit timestamp lastProjectId: undefined // No last project initially }; ```

Template Examples

Quick Action Cards

```html

@for (action of quickActions(); track action.id) {
{{ action.icon }}

{{ action.title }}

{{ action.description }}

}
\`\`\`

Recent Projects

```html

Recent Projects

@if (recentProjects().length === 0) {

No recent projects yet

}

@for (project of recentProjects(); track project.id) { <div class="project-card" (click)="openProject(project)"> @if (project.thumbnail) { <img [src]="project.thumbnail" [alt]="project.name"> }

{{ project.name }}

{{ project.description }}

{{ formatDate(project.lastModified) }}
}

\`\`\`

Preference Toggles

```html

\`\`\`

Animations

Particle System

```typescript private initializeParticleSystem() { if (!this.particleSystem?.nativeElement) return;

const container = this.particleSystem.nativeElement; const particleCount = 50;

for (let i = 0; i < particleCount; i++) { const particle = document.createElement('div'); particle.className = 'particle';

const size = Math.random() * 4 + 1;
const delay = Math.random() * 5;
const duration = Math.random() * 10 + 15;

particle.style.cssText = \`
  width: \${size}px;
  height: \${size}px;
  left: \${Math.random() * 100}%;
  top: \${Math.random() * 100}%;
  animation: floatParticle \${duration}s linear infinite;
  animation-delay: \${delay}s;
\`;

container.appendChild(particle);

} } ```

Neural Network

```typescript private initializeNeuralNetwork() { // Create animated neural network visualization // See full implementation in component source } ```

Best Practices

1. Always Use Flash-Persistence

```typescript // ❌ Manual localStorage localStorage.setItem('prefs', JSON.stringify(prefs));

// ✅ Flash-persistence const prefs = autoFlashState('editor-preferences/landing', defaultPrefs); prefs.update(p => ({ ...p, visits: p.visits + 1 })); ```

2. Check Projects Before Navigation

```typescript async navigateToWorkspace() { const hasProjects = await this.projectDetection.hasExistingProjects();

if (!hasProjects) { // Show onboarding or create first project } else { this.router.navigate(['/editor/workspace']); } } ```

3. Use Async ngOnInit

```typescript async ngOnInit() { // Auto-skip check requires async await this.checkShouldSkip();

// Continue with other initialization this.initializeAnimations(); } ```

4. Handle Errors Gracefully

```typescript private async checkShouldSkip() { try { const prefs = this.landingPrefs(); const hasProjects = await this.projectDetection.hasExistingProjects();

if (!prefs.showLandingByDefault && hasProjects) {
  this.startEditing();
}

} catch (error) { console.warn('Auto-skip error:', error); // Continue showing landing on error } } ```

Troubleshooting

Landing always shows even when skip is enabled

Problem: Guard doesn't skip landing page

Solution: Check preferences and project detection:

```typescript // Debug preferences const prefs = await storage.get('flash:editor-preferences/landing'); console.log('Preferences:', prefs);

// Debug project detection const hasProjects = await detection.hasExistingProjects(); console.log('Has projects:', hasProjects);

// Check storage keys const pages = await storage.get('flash:editor-web/pages'); const recent = await storage.get('flash:editor-preferences/recent-projects'); console.log('Pages:', pages, 'Recent:', recent); ```

Preferences don't persist

Problem: Settings reset on page refresh

Solution: Ensure Flash-persistence is initialized:

```typescript // Check app.config.ts import { provideGgStorage } from '@storage-system';

export const appConfig = { providers: [ provideGgStorage() // Required ] }; ```

Auto-skip doesn't work

Problem: Landing shows even with skip preference

Checklist:

  1. ✅ Guard is added to route: canActivate: [landingGuard]
  2. ✅ Preferences are set correctly
  3. ✅ Projects exist in storage
  4. ✅ Storage is initialized
  5. ✅ Component uses async ngOnInit

Performance

Optimization Tips

  1. Lazy Load Animations: Initialize animations in ngAfterViewInit
  2. Limit Particles: Use 30-50 particles for smooth performance
  3. Debounce Updates: Use RxJS debounce for frequent updates
  4. Cache Projects: Recent projects are already cached via Flash-persistence

Performance Metrics

See Also

API Reference

Interfaces

```typescript interface LandingPreferences { showLandingByDefault: boolean; rememberLastProject: boolean; visits: number; lastVisit: string; lastProjectId?: string; }

interface RecentProject { id: string; name: string; description: string; lastModified: Date; thumbnail?: string; }

interface QuickAction { id: string; title: string; description: string; icon: string; action: () => void; color: 'primary' | 'secondary' | 'tertiary'; } ```

Methods

```typescript // Preference management toggleLandingPreference(): void toggleRememberProject(): void onSkipLanding(): void

// Navigation startEditing(): void openDashboard(): void openProject(project: RecentProject): void

// Quick actions createNewWidget(): void browseTemplates(): void importWidget(): void startTutorial(): void

// Animations initializeParticleSystem(): void initializeNeuralNetwork(): void ```

Migration Guide

From Manual localStorage

Before: ```typescript // Load preferences const stored = localStorage.getItem('editor-landing-preferences'); if (stored) { const prefs = JSON.parse(stored); this.showLandingByDefault.set(prefs.showLandingByDefault); }

// Save preferences const prefs = { showLandingByDefault: this.showLandingByDefault() }; localStorage.setItem('editor-landing-preferences', JSON.stringify(prefs)); ```

After: ```typescript // Flash-persistence handles everything automatically private landingPrefs = autoFlashState('editor-preferences/landing', defaultPrefs);

// Just update, it auto-saves this.landingPrefs.update(prefs => ({ ...prefs, showLandingByDefault: false })); ```