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
- ✨ Flash-Persistence Integration - Automatic state saving without manual localStorage
- 🎯 Smart Skip Logic - Automatic routing based on preferences and project history
- 🤖 AI Assistant - Help new users get started quickly
- 🎨 Beautiful Animations - Particle system and neural network effects
- 📊 Recent Projects - Quick access to previous work
- ⚙️ User Preferences - Customizable landing behavior
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
// 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
// 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
{{ 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:
- ✅ Guard is added to route:
canActivate: [landingGuard] - ✅ Preferences are set correctly
- ✅ Projects exist in storage
- ✅ Storage is initialized
- ✅ Component uses async ngOnInit
Performance
Optimization Tips
- Lazy Load Animations: Initialize animations in
ngAfterViewInit - Limit Particles: Use 30-50 particles for smooth performance
- Debounce Updates: Use RxJS debounce for frequent updates
- Cache Projects: Recent projects are already cached via Flash-persistence
Performance Metrics
- Initial Load: < 1s
- Particle Animation: 60 FPS
- Flash-persistence Operations: < 5ms
- Guard Execution: < 50ms
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 })); ```