create-ng-tailwind 1.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4117 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const {
4
+ createContactComponent,
5
+ createRouting,
6
+ createAppConfig,
7
+ createAppComponent,
8
+ createStyles,
9
+ } = require('./features');
10
+
11
+ const {
12
+ createAuthInterceptor,
13
+ createErrorInterceptor,
14
+ createLoadingInterceptor,
15
+ createCachingInterceptor,
16
+ createToastService,
17
+ createToastComponent,
18
+ createLoadingService,
19
+ createCacheService,
20
+ } = require('./advanced-features');
21
+
22
+ const {
23
+ createModalService,
24
+ createModalComponent,
25
+ createTruncatePipe,
26
+ createClickOutsideDirective,
27
+ createTooltipDirective,
28
+ } = require('./ui-features');
29
+
30
+ const starter = {
31
+ info: {
32
+ name: 'Starter',
33
+ description: 'Professional Angular foundation with essential components and best practices',
34
+ features: [
35
+ 'Modern standalone Angular 20+ architecture with signals',
36
+ 'Complete routing setup with lazy loading',
37
+ 'HTTP Interceptors (Auth, Error, Loading, Caching)',
38
+ 'i18n translation support (English & Arabic) with RTL',
39
+ 'Language switcher in header (desktop & mobile)',
40
+ 'Toast/Notification system with auto-dismiss',
41
+ 'Modal/Dialog system with confirm & alert',
42
+ 'Essential UI components (Button, Card, Spinner, Toast, Modal)',
43
+ 'Truncate pipe for text truncation',
44
+ 'Useful Directives (ClickOutside, Tooltip)',
45
+ 'Professional layout with header/footer',
46
+ 'Authentication UI (Login, Register, Forgot Password)',
47
+ '3 example pages (Home, About, Contact)',
48
+ 'Reactive forms with validation',
49
+ 'API, Auth, Storage, Loading, Cache services',
50
+ 'TypeScript interfaces and models',
51
+ 'Responsive Tailwind design system',
52
+ 'ESLint + Prettier + Husky pre-commit hooks',
53
+ 'PWA-ready with service worker configuration',
54
+ 'Icons library (@ng-icons/heroicons)',
55
+ ],
56
+ },
57
+
58
+ async apply(config, spinner) {
59
+ // Store spinner reference for progress updates
60
+ this.spinner = spinner;
61
+ const chalk = require('chalk');
62
+
63
+ // Helper function to show completed step with green checkmark
64
+ const completeStep = (message) => {
65
+ if (spinner) {
66
+ spinner.stop();
67
+ console.log(chalk.green(' ✔') + chalk.white(' ' + message));
68
+ spinner.start();
69
+ }
70
+ };
71
+
72
+ // Create all components silently
73
+ if (spinner) spinner.update('Setting up project structure...');
74
+ await this.createDirectoryStructure(config);
75
+ await this.createEnvironments(config);
76
+ await this.createCoreServices(config);
77
+ await this.createHttpInterceptors(config);
78
+ await this.createI18n(config);
79
+ await this.createGuards(config);
80
+ await this.createSharedComponents(config);
81
+ await this.createToastSystem(config);
82
+ await this.createModalSystem(config);
83
+ await this.createPipes(config);
84
+ await this.createAdditionalPipes(config);
85
+ await this.createDirectives(config);
86
+ await this.createModels(config);
87
+ await this.createLayout(config);
88
+ await this.createAuthLayout(config);
89
+ await this.createFeatures(config);
90
+ await this.createRouting(config);
91
+ await this.createAppConfig(config);
92
+ await this.createAppComponent(config);
93
+ await this.createStyles(config);
94
+ await this.createAssets(config);
95
+ await this.createVSCodeSettings(config);
96
+ await this.updateTsConfig(config);
97
+ await this.setupLinting(config);
98
+ await this.setupPWA(config);
99
+ await this.formatCode(config);
100
+
101
+ // Stop spinner and show summary
102
+ if (spinner) spinner.stop();
103
+
104
+ // Show minimal, professional summary
105
+ console.log('');
106
+ completeStep('Angular 20+ project created');
107
+ completeStep('Tailwind CSS v4 configured');
108
+ completeStep('i18n translation support (English & Arabic)');
109
+ completeStep('HTTP interceptors (Auth, Error, Loading, Cache)');
110
+ completeStep('Core services (8 services)');
111
+ completeStep('UI components (Button, Card, Toast, Modal, Spinner)');
112
+ completeStep('Utility pipes & directives');
113
+ completeStep('Authentication pages & guards');
114
+ completeStep('PWA support configured');
115
+ completeStep('ESLint + Prettier + Husky');
116
+ console.log('');
117
+ },
118
+
119
+ async createDirectoryStructure(config) {
120
+ const directories = [
121
+ 'src/environments',
122
+ 'src/app/core/services',
123
+ 'src/app/core/guards',
124
+ 'src/app/core/i18n',
125
+ 'src/app/core/interceptors',
126
+ 'src/app/shared/components/button',
127
+ 'src/app/shared/components/card',
128
+ 'src/app/shared/components/loading-spinner',
129
+ 'src/app/shared/components/toast',
130
+ 'src/app/shared/components/modal',
131
+ 'src/app/shared/pipes',
132
+ 'src/app/shared/directives',
133
+ 'src/app/shared/models',
134
+ 'src/app/features/home',
135
+ 'src/app/features/about',
136
+ 'src/app/features/contact',
137
+ 'src/app/features/auth/login',
138
+ 'src/app/features/auth/register',
139
+ 'src/app/features/auth/forgot-password',
140
+ 'src/app/layout/header',
141
+ 'src/app/layout/footer',
142
+ 'src/app/layout/auth',
143
+ 'public/assets/images',
144
+ 'public/assets/i18n',
145
+ 'public/images',
146
+ 'public/styles',
147
+ ];
148
+
149
+ for (const dir of directories) {
150
+ await fs.ensureDir(path.join(config.fullPath, dir));
151
+ }
152
+ },
153
+
154
+ async createEnvironments(config) {
155
+ // Development environment
156
+ const devEnvironment = `export const environment = {
157
+ production: false,
158
+ apiUrl: 'http://localhost:3000/api',
159
+ appName: '${config.projectName}',
160
+ version: '1.0.0',
161
+ enableDevTools: true,
162
+ logLevel: 'debug' as 'debug' | 'info' | 'warn' | 'error',
163
+ features: {
164
+ enableAnalytics: false,
165
+ enableLogging: true,
166
+ enableMocking: true
167
+ },
168
+ auth: {
169
+ tokenKey: 'auth_token',
170
+ refreshTokenKey: 'refresh_token',
171
+ tokenExpirationBuffer: 300000 // 5 minutes
172
+ },
173
+ api: {
174
+ timeout: 30000,
175
+ retryAttempts: 3
176
+ }
177
+ };`;
178
+
179
+ await fs.writeFile(
180
+ path.join(config.fullPath, 'src/environments/environment.ts'),
181
+ devEnvironment
182
+ );
183
+
184
+ // Production environment
185
+ const prodEnvironment = `export const environment = {
186
+ production: true,
187
+ apiUrl: 'https://api.${config.projectName}.com/api',
188
+ appName: '${config.projectName}',
189
+ version: '1.0.0',
190
+ enableDevTools: false,
191
+ logLevel: 'error' as 'debug' | 'info' | 'warn' | 'error',
192
+ features: {
193
+ enableAnalytics: true,
194
+ enableLogging: false,
195
+ enableMocking: false
196
+ },
197
+ auth: {
198
+ tokenKey: 'auth_token',
199
+ refreshTokenKey: 'refresh_token',
200
+ tokenExpirationBuffer: 300000 // 5 minutes
201
+ },
202
+ api: {
203
+ timeout: 30000,
204
+ retryAttempts: 1
205
+ }
206
+ };`;
207
+
208
+ await fs.writeFile(
209
+ path.join(config.fullPath, 'src/environments/environment.prod.ts'),
210
+ prodEnvironment
211
+ );
212
+ },
213
+
214
+ async createCoreServices(config) {
215
+ // API Service
216
+ const apiService = `import { Injectable, inject } from '@angular/core';
217
+ import { HttpClient, HttpErrorResponse } from '@angular/common/http';
218
+ import { Observable, throwError } from 'rxjs';
219
+ import { catchError, retry } from 'rxjs/operators';
220
+
221
+ import { ApiResponse } from '@shared/models/api-response.interface';
222
+
223
+ @Injectable({
224
+ providedIn: 'root'
225
+ })
226
+ export class ApiService {
227
+ private http = inject(HttpClient);
228
+ private readonly baseUrl = 'https://api.example.com'; // Configure your API URL
229
+
230
+ /**
231
+ * Generic GET request
232
+ */
233
+ get<T>(endpoint: string): Observable<ApiResponse<T>> {
234
+ return this.http.get<ApiResponse<T>>(\`\${this.baseUrl}/\${endpoint}\`)
235
+ .pipe(
236
+ retry(1),
237
+ catchError(this.handleError)
238
+ );
239
+ }
240
+
241
+ /**
242
+ * Generic POST request
243
+ */
244
+ post<T, D = unknown>(endpoint: string, data: D): Observable<ApiResponse<T>> {
245
+ return this.http.post<ApiResponse<T>>(\`\${this.baseUrl}/\${endpoint}\`, data)
246
+ .pipe(
247
+ catchError(this.handleError)
248
+ );
249
+ }
250
+
251
+ /**
252
+ * Generic PUT request
253
+ */
254
+ put<T, D = unknown>(endpoint: string, data: D): Observable<ApiResponse<T>> {
255
+ return this.http.put<ApiResponse<T>>(\`\${this.baseUrl}/\${endpoint}\`, data)
256
+ .pipe(
257
+ catchError(this.handleError)
258
+ );
259
+ }
260
+
261
+ /**
262
+ * Generic DELETE request
263
+ */
264
+ delete<T>(endpoint: string): Observable<ApiResponse<T>> {
265
+ return this.http.delete<ApiResponse<T>>(\`\${this.baseUrl}/\${endpoint}\`)
266
+ .pipe(
267
+ catchError(this.handleError)
268
+ );
269
+ }
270
+
271
+ /**
272
+ * Handle HTTP errors
273
+ */
274
+ private handleError(error: HttpErrorResponse) {
275
+ let errorMessage = 'An unknown error occurred';
276
+
277
+ if (error.error instanceof ErrorEvent) {
278
+ // Client-side error
279
+ errorMessage = error.error.message;
280
+ } else {
281
+ // Server-side error
282
+ errorMessage = error.error?.message || \`Server returned code: \${error.status}, error message is: \${error.message}\`;
283
+ }
284
+
285
+ console.error('API Error:', errorMessage);
286
+ return throwError(() => new Error(errorMessage));
287
+ }
288
+ }`;
289
+
290
+ await fs.writeFile(
291
+ path.join(config.fullPath, 'src/app/core/services/api.service.ts'),
292
+ apiService
293
+ );
294
+
295
+ // Auth Service with Reactive State
296
+ const authService = `import { Injectable, inject } from '@angular/core';
297
+ import { HttpClient } from '@angular/common/http';
298
+ import { Router } from '@angular/router';
299
+ import { BehaviorSubject, Observable, throwError } from 'rxjs';
300
+ import { catchError, tap } from 'rxjs/operators';
301
+ import { environment } from '@environments/environment';
302
+ import { StorageService } from './storage.service';
303
+
304
+ interface User {
305
+ id: string;
306
+ email: string;
307
+ firstName: string;
308
+ lastName: string;
309
+ }
310
+
311
+ interface AuthResponse {
312
+ token: string;
313
+ user: User;
314
+ refreshToken?: string;
315
+ }
316
+
317
+ interface LoginCredentials {
318
+ email: string;
319
+ password: string;
320
+ }
321
+
322
+ interface RegisterData {
323
+ email: string;
324
+ password: string;
325
+ firstName: string;
326
+ lastName: string;
327
+ }
328
+
329
+ @Injectable({
330
+ providedIn: 'root'
331
+ })
332
+ export class AuthService {
333
+ private http = inject(HttpClient);
334
+ private router = inject(Router);
335
+ private storage = inject(StorageService);
336
+
337
+ private readonly API_URL = environment.apiUrl;
338
+
339
+ // Reactive state
340
+ private _isAuthenticated$ = new BehaviorSubject<boolean>(this.hasValidToken());
341
+ private _currentUser$ = new BehaviorSubject<User | null>(this.getCurrentUserFromStorage());
342
+
343
+ // Public observables
344
+ public readonly isAuthenticated$ = this._isAuthenticated$.asObservable();
345
+ public readonly currentUser$ = this._currentUser$.asObservable();
346
+
347
+ constructor() {
348
+ // Initialize authentication state on service creation
349
+ this.checkAuthenticationStatus();
350
+ }
351
+
352
+ login(credentials: LoginCredentials): Observable<AuthResponse> {
353
+ // For demo purposes, simulate login
354
+ if (credentials.email === 'demo@example.com' && credentials.password === 'password') {
355
+ const mockResponse: AuthResponse = {
356
+ token: 'mock_jwt_token_12345',
357
+ user: {
358
+ id: '1',
359
+ email: 'demo@example.com',
360
+ firstName: 'Demo',
361
+ lastName: 'User'
362
+ }
363
+ };
364
+
365
+ // Store auth data
366
+ this.storage.setItem(environment.auth.tokenKey, mockResponse.token);
367
+ this.storage.setItem('current_user', JSON.stringify(mockResponse.user));
368
+
369
+ // Update reactive state
370
+ this._isAuthenticated$.next(true);
371
+ this._currentUser$.next(mockResponse.user);
372
+
373
+ return new Observable(subscriber => {
374
+ setTimeout(() => {
375
+ subscriber.next(mockResponse);
376
+ subscriber.complete();
377
+ }, 1000);
378
+ });
379
+ }
380
+
381
+ // Real API call would be:
382
+ return this.http.post<AuthResponse>(\`\${this.API_URL}/auth/login\`, credentials)
383
+ .pipe(
384
+ tap(response => {
385
+ // Store tokens
386
+ this.storage.setItem(environment.auth.tokenKey, response.token);
387
+ this.storage.setItem('current_user', JSON.stringify(response.user));
388
+ if (response.refreshToken) {
389
+ this.storage.setItem(environment.auth.refreshTokenKey, response.refreshToken);
390
+ }
391
+
392
+ // Update reactive state
393
+ this._isAuthenticated$.next(true);
394
+ this._currentUser$.next(response.user);
395
+ }),
396
+ catchError(error => throwError(() => error))
397
+ );
398
+ }
399
+
400
+ logout(): void {
401
+ this.storage.removeItem(environment.auth.tokenKey);
402
+ this.storage.removeItem(environment.auth.refreshTokenKey);
403
+ this.storage.removeItem('current_user');
404
+
405
+ // Update reactive state
406
+ this._isAuthenticated$.next(false);
407
+ this._currentUser$.next(null);
408
+
409
+ this.router.navigate(['/auth/login']);
410
+ }
411
+
412
+ register(userData: RegisterData): Observable<AuthResponse> {
413
+ return this.http.post<AuthResponse>(\`\${this.API_URL}/auth/register\`, userData)
414
+ .pipe(
415
+ tap(response => {
416
+ // Store auth data after successful registration
417
+ this.storage.setItem(environment.auth.tokenKey, response.token);
418
+ this.storage.setItem('current_user', JSON.stringify(response.user));
419
+
420
+ // Update reactive state
421
+ this._isAuthenticated$.next(true);
422
+ this._currentUser$.next(response.user);
423
+ }),
424
+ catchError(error => throwError(() => error))
425
+ );
426
+ }
427
+
428
+ // Synchronous authentication check
429
+ isAuthenticated(): boolean {
430
+ return this.hasValidToken();
431
+ }
432
+
433
+ private hasValidToken(): boolean {
434
+ const token = this.storage.getItem(environment.auth.tokenKey);
435
+ return !!token && !this.isTokenExpired(token);
436
+ }
437
+
438
+ private isTokenExpired(token: string): boolean {
439
+ // For demo token, always return false
440
+ if (token === 'mock_jwt_token_12345') {
441
+ return false;
442
+ }
443
+
444
+ try {
445
+ const payload = JSON.parse(atob(token.split('.')[1]));
446
+ const now = Date.now() / 1000;
447
+ return payload.exp < now;
448
+ } catch {
449
+ return true;
450
+ }
451
+ }
452
+
453
+ private getCurrentUserFromStorage(): User | null {
454
+ try {
455
+ const userStr = this.storage.getItem('current_user');
456
+ return userStr ? JSON.parse(userStr) : null;
457
+ } catch {
458
+ return null;
459
+ }
460
+ }
461
+
462
+ private checkAuthenticationStatus(): void {
463
+ const isAuth = this.hasValidToken();
464
+ const user = this.getCurrentUserFromStorage();
465
+
466
+ this._isAuthenticated$.next(isAuth);
467
+ this._currentUser$.next(user);
468
+ }
469
+
470
+ getToken(): string | null {
471
+ return this.storage.getItem(environment.auth.tokenKey);
472
+ }
473
+
474
+ // Get current user synchronously
475
+ getCurrentUser(): User | null {
476
+ return this._currentUser$.getValue();
477
+ }
478
+ }`;
479
+
480
+ await fs.writeFile(
481
+ path.join(config.fullPath, 'src/app/core/services/auth.service.ts'),
482
+ authService
483
+ );
484
+
485
+ // Storage Service
486
+ const storageService = `import { Injectable, inject, PLATFORM_ID } from '@angular/core';
487
+ import { isPlatformBrowser } from '@angular/common';
488
+
489
+ @Injectable({
490
+ providedIn: 'root'
491
+ })
492
+ export class StorageService {
493
+ private platformId = inject(PLATFORM_ID);
494
+ private isBrowser = isPlatformBrowser(this.platformId);
495
+
496
+ setItem(key: string, value: string): void {
497
+ if (!this.isBrowser) {
498
+ return;
499
+ }
500
+ try {
501
+ localStorage.setItem(key, value);
502
+ } catch (error) {
503
+ console.error('Error storing to localStorage:', error);
504
+ }
505
+ }
506
+
507
+ getItem(key: string): string | null {
508
+ if (!this.isBrowser) {
509
+ return null;
510
+ }
511
+ try {
512
+ return localStorage.getItem(key);
513
+ } catch (error) {
514
+ console.error('Error retrieving from localStorage:', error);
515
+ return null;
516
+ }
517
+ }
518
+
519
+ removeItem(key: string): void {
520
+ if (!this.isBrowser) {
521
+ return;
522
+ }
523
+ try {
524
+ localStorage.removeItem(key);
525
+ } catch (error) {
526
+ console.error('Error removing from localStorage:', error);
527
+ }
528
+ }
529
+
530
+ clear(): void {
531
+ if (!this.isBrowser) {
532
+ return;
533
+ }
534
+ try {
535
+ localStorage.clear();
536
+ } catch (error) {
537
+ console.error('Error clearing localStorage:', error);
538
+ }
539
+ }
540
+ }`;
541
+
542
+ await fs.writeFile(
543
+ path.join(config.fullPath, 'src/app/core/services/storage.service.ts'),
544
+ storageService
545
+ );
546
+ },
547
+
548
+ async createHttpInterceptors(config) {
549
+ // Create all HTTP interceptors
550
+ await createAuthInterceptor(config);
551
+ await createErrorInterceptor(config);
552
+ await createLoadingInterceptor(config);
553
+ await createCachingInterceptor(config);
554
+
555
+ // Create supporting services
556
+ await createLoadingService(config);
557
+ await createCacheService(config);
558
+ },
559
+
560
+ async createToastSystem(config) {
561
+ await createToastService(config);
562
+ await createToastComponent(config);
563
+ },
564
+
565
+ async createModalSystem(config) {
566
+ await createModalService(config);
567
+ await createModalComponent(config);
568
+ },
569
+
570
+ async createAdditionalPipes(config) {
571
+ await createTruncatePipe(config);
572
+ },
573
+
574
+ async createDirectives(config) {
575
+ await createClickOutsideDirective(config);
576
+ await createTooltipDirective(config);
577
+ },
578
+
579
+ async createI18n(config) {
580
+ // Create Translation Service
581
+ const translationService = `import { Injectable, inject, signal, effect, PLATFORM_ID } from '@angular/core';
582
+ import { isPlatformBrowser } from '@angular/common';
583
+ import { TranslateService } from '@ngx-translate/core';
584
+ import { StorageService } from '@core/services/storage.service';
585
+
586
+ export type SupportedLanguage = 'en' | 'ar';
587
+
588
+ export interface LanguageOption {
589
+ code: SupportedLanguage;
590
+ name: string;
591
+ nativeName: string;
592
+ dir: 'ltr' | 'rtl';
593
+ flag: string;
594
+ }
595
+
596
+ @Injectable({
597
+ providedIn: 'root'
598
+ })
599
+ export class TranslationService {
600
+ private translateService = inject(TranslateService);
601
+ private storage = inject(StorageService);
602
+ private platformId = inject(PLATFORM_ID);
603
+ private isBrowser = isPlatformBrowser(this.platformId);
604
+
605
+ // Reactive current language
606
+ public currentLang = signal<SupportedLanguage>('en');
607
+
608
+ // Reactive text direction
609
+ public textDirection = signal<'ltr' | 'rtl'>('ltr');
610
+
611
+ // Available languages
612
+ public readonly languages: LanguageOption[] = [
613
+ {
614
+ code: 'en',
615
+ name: 'English',
616
+ nativeName: 'English',
617
+ dir: 'ltr',
618
+ flag: '🇺🇸'
619
+ },
620
+ {
621
+ code: 'ar',
622
+ name: 'Arabic',
623
+ nativeName: 'العربية',
624
+ dir: 'rtl',
625
+ flag: '🇸🇦'
626
+ }
627
+ ];
628
+
629
+ constructor() {
630
+ // Initialize translation service
631
+ this.translateService.setDefaultLang('en');
632
+
633
+ // Load saved language or detect browser language
634
+ const savedLang = this.getSavedLanguage();
635
+ const browserLang = this.translateService.getBrowserLang() as SupportedLanguage;
636
+ const initialLang = savedLang || (this.isSupportedLanguage(browserLang) ? browserLang : 'en');
637
+
638
+ this.setLanguage(initialLang);
639
+
640
+ // Effect to update document direction when language changes (browser only)
641
+ if (this.isBrowser) {
642
+ effect(() => {
643
+ const dir = this.textDirection();
644
+ document.documentElement.setAttribute('dir', dir);
645
+ document.documentElement.setAttribute('lang', this.currentLang());
646
+ });
647
+ }
648
+ }
649
+
650
+ /**
651
+ * Change the current language
652
+ */
653
+ setLanguage(lang: SupportedLanguage): void {
654
+ if (!this.isSupportedLanguage(lang)) {
655
+ console.warn(\`Language '\${lang}' is not supported. Falling back to 'en'.\`);
656
+ lang = 'en';
657
+ }
658
+
659
+ this.translateService.use(lang);
660
+ this.currentLang.set(lang);
661
+
662
+ const languageOption = this.languages.find(l => l.code === lang);
663
+ if (languageOption) {
664
+ this.textDirection.set(languageOption.dir);
665
+ }
666
+
667
+ this.saveLanguage(lang);
668
+ }
669
+
670
+ /**
671
+ * Get current language
672
+ */
673
+ getCurrentLanguage(): SupportedLanguage {
674
+ return this.currentLang();
675
+ }
676
+
677
+ /**
678
+ * Get language option by code
679
+ */
680
+ getLanguageOption(code: SupportedLanguage): LanguageOption | undefined {
681
+ return this.languages.find(lang => lang.code === code);
682
+ }
683
+
684
+ /**
685
+ * Check if language is supported
686
+ */
687
+ isSupportedLanguage(lang: string): lang is SupportedLanguage {
688
+ return ['en', 'ar'].includes(lang);
689
+ }
690
+
691
+ /**
692
+ * Toggle between English and Arabic
693
+ */
694
+ toggleLanguage(): void {
695
+ const newLang: SupportedLanguage = this.currentLang() === 'en' ? 'ar' : 'en';
696
+ this.setLanguage(newLang);
697
+ }
698
+
699
+ /**
700
+ * Get instant translation
701
+ */
702
+ instant(key: string, params?: object): string {
703
+ return this.translateService.instant(key, params);
704
+ }
705
+
706
+ /**
707
+ * Get translation as observable
708
+ */
709
+ get(key: string | string[], params?: object) {
710
+ return this.translateService.get(key, params);
711
+ }
712
+
713
+ /**
714
+ * Save language preference to storage
715
+ */
716
+ private saveLanguage(lang: SupportedLanguage): void {
717
+ this.storage.setItem('preferred_language', lang);
718
+ }
719
+
720
+ /**
721
+ * Get saved language from storage
722
+ */
723
+ private getSavedLanguage(): SupportedLanguage | null {
724
+ const saved = this.storage.getItem('preferred_language');
725
+ return saved && this.isSupportedLanguage(saved) ? saved : null;
726
+ }
727
+ }`;
728
+
729
+ await fs.writeFile(
730
+ path.join(config.fullPath, 'src/app/core/i18n/translation.service.ts'),
731
+ translationService
732
+ );
733
+
734
+ // Create English translations
735
+ const enTranslations = {
736
+ app: {
737
+ title: config.projectName,
738
+ welcome: 'Welcome to {{name}}',
739
+ description: 'A professional Angular starter template with Tailwind CSS',
740
+ },
741
+ nav: {
742
+ home: 'Home',
743
+ about: 'About',
744
+ contact: 'Contact',
745
+ login: 'Sign In',
746
+ register: 'Get Started',
747
+ logout: 'Sign Out',
748
+ profile: 'Profile Settings',
749
+ account: 'Account Settings',
750
+ },
751
+ home: {
752
+ hero: {
753
+ title: 'Welcome to {{projectName}}',
754
+ subtitle:
755
+ 'A professional Angular starter template with Tailwind CSS. Built with modern best practices and ready to scale.',
756
+ button: 'Get Started',
757
+ learnMore: 'Learn More',
758
+ },
759
+ features: {
760
+ title: 'Modern Angular Features',
761
+ subtitle: 'Everything you need to build professional web applications.',
762
+ standalone: {
763
+ title: 'Standalone Components',
764
+ description: 'Modern Angular architecture without NgModules',
765
+ },
766
+ tailwind: {
767
+ title: 'Tailwind CSS',
768
+ description: 'Utility-first CSS framework for rapid UI development',
769
+ },
770
+ typescript: {
771
+ title: 'TypeScript',
772
+ description: 'Full type safety and enhanced developer experience',
773
+ },
774
+ },
775
+ techStack: {
776
+ title: 'Leveraging the latest tools and frameworks for optimal performance',
777
+ angular: {
778
+ title: 'Angular 20+',
779
+ description: 'Modern standalone components with signals for reactive state management',
780
+ },
781
+ tailwind: {
782
+ title: 'Tailwind CSS v4',
783
+ description: 'Utility-first CSS framework with modern PostCSS configuration',
784
+ },
785
+ typescript: {
786
+ title: 'TypeScript',
787
+ description: 'Strongly-typed interfaces and models for enhanced developer experience',
788
+ },
789
+ },
790
+ readyToStart: {
791
+ title: 'Ready to Start Building?',
792
+ subtitle: 'Explore other pages or get in touch with us to learn more',
793
+ viewHome: 'View Home',
794
+ contactUs: 'Contact Us',
795
+ },
796
+ productionReady: {
797
+ sectionTitle: "What's Included in This Starter Template",
798
+ title:
799
+ 'A production-ready Angular application with 20+ pre-configured features, services, and components',
800
+ modernArchitecture: {
801
+ title: 'Modern Architecture',
802
+ description: 'Standalone components, Signals, Zoneless support, Angular 20+',
803
+ },
804
+ i18n: {
805
+ title: 'i18n Translation',
806
+ description: 'English & Arabic with RTL support, language switcher in header',
807
+ },
808
+ interceptors: {
809
+ title: 'HTTP Interceptors',
810
+ description: 'Auth, Error handling, Loading state, Response caching (5min TTL)',
811
+ },
812
+ tailwind: {
813
+ title: 'Tailwind CSS v4',
814
+ description: 'Modern PostCSS setup, responsive utilities, dark mode ready',
815
+ },
816
+ uiComponents: {
817
+ title: 'UI Components',
818
+ description: 'Button, Card, Spinner, Toast, Modal with full customization',
819
+ },
820
+ typeSafe: {
821
+ title: 'Type-Safe',
822
+ description: 'TypeScript interfaces, models, strongly-typed services',
823
+ },
824
+ },
825
+ coreServices: {
826
+ title: 'Core Services (src/app/core/)',
827
+ authService: 'AuthService - Authentication & user management',
828
+ apiService: 'ApiService - Centralized HTTP request handling',
829
+ toastService: 'ToastService - Notification system (success, error, warning, info)',
830
+ modalService: 'ModalService - Dialog system with confirm & alert',
831
+ loadingService: 'LoadingService - Global loading state with signals',
832
+ cacheService: 'CacheService - Response caching with TTL',
833
+ storageService: 'StorageService - LocalStorage wrapper with type safety',
834
+ i18nService: 'i18nService - Internationalization management',
835
+ },
836
+ sharedComponents: {
837
+ title: 'Shared Components (src/app/shared/)',
838
+ button: 'ButtonComponent - Variants: primary, secondary, danger',
839
+ card: 'CardComponent - Flexible container with title & shadow',
840
+ spinner: 'LoadingSpinnerComponent - Animated loading indicator',
841
+ toast: 'ToastComponent - Auto-dismiss notifications',
842
+ modal: 'ModalComponent - Accessible dialog with sizes',
843
+ pipes: 'Pipes - Truncate, TimeAgo',
844
+ directives: 'Directives - ClickOutside, Tooltip',
845
+ },
846
+ preConfigured: {
847
+ title: 'Pre-Configured Packages & Tools',
848
+ subtitle: 'Production-ready dependencies and development tools included',
849
+ coreDependencies: 'Core Dependencies',
850
+ developmentTools: 'Development Tools',
851
+ latest: 'Latest',
852
+ i18n: 'i18n',
853
+ iconLibrary: 'Icon library',
854
+ linting: 'Linting',
855
+ formatting: 'Formatting',
856
+ gitHooks: 'Git hooks',
857
+ typeSafety: 'Type safety',
858
+ serviceWorker: 'Service worker',
859
+ },
860
+ pathAliases: {
861
+ title: 'TypeScript Path Aliases (tsconfig.json)',
862
+ core: '@core/* → src/app/core/*',
863
+ shared: '@shared/* → src/app/shared/*',
864
+ features: '@features/* → src/app/features/*',
865
+ environments: '@environments/* → src/environments/*',
866
+ },
867
+ projectStructure: {
868
+ title: 'Well-Organized Project Structure',
869
+ subtitle: 'Clean architecture following Angular best practices',
870
+ coreServices: 'services/ (8 services)',
871
+ guards: 'guards/ (auth guard)',
872
+ interceptors: 'interceptors/ (4 types)',
873
+ i18n: 'i18n/ (translation system)',
874
+ components: 'components/ (5 components)',
875
+ pipes: 'pipes/ (2 pipes)',
876
+ directives: 'directives/ (2 directives)',
877
+ models: 'models/ (TypeScript interfaces)',
878
+ home: 'home/',
879
+ about: 'about/',
880
+ contact: 'contact/',
881
+ auth: 'auth/ (login, register, forgot)',
882
+ mainLayout: 'main-layout/ (header + footer)',
883
+ authLayout: 'auth-layout/ (auth pages)',
884
+ },
885
+ interactiveExamples: {
886
+ title: 'Try Interactive Examples',
887
+ subtitle: 'Test the Toast and Modal services included in this starter template',
888
+ toastNotifications: {
889
+ title: 'Toast Notifications',
890
+ subtitle: 'Auto-dismiss alerts with 4 types',
891
+ description: 'Click any button to see different toast notification types:',
892
+ success: 'Success',
893
+ error: 'Error',
894
+ warning: 'Warning',
895
+ info: 'Info',
896
+ },
897
+ modalDialogs: {
898
+ title: 'Modal Dialogs',
899
+ subtitle: 'Accessible & responsive dialogs',
900
+ description: 'Test confirmation and alert modals with transitions:',
901
+ showConfirm: 'Show Confirm Dialog',
902
+ showAlert: 'Show Alert Dialog',
903
+ },
904
+ proTip: {
905
+ title: 'Pro Tip',
906
+ description:
907
+ 'These services are fully typed and can be injected anywhere in your app. Check src/app/core/services/ to see the implementation.',
908
+ },
909
+ },
910
+ readyToBuild: {
911
+ title: 'Ready to Build Something Amazing?',
912
+ subtitle:
913
+ 'Explore the other pages to see forms, routing, and authentication UI in action',
914
+ learnMore: 'Learn More',
915
+ },
916
+ },
917
+ about: {
918
+ title: 'About {{projectName}}',
919
+ subtitle:
920
+ 'A modern Angular starter template built with best practices and developer experience in mind.',
921
+ overview: {
922
+ title: 'Project Overview',
923
+ paragraph1:
924
+ 'This starter template provides a solid foundation for building modern Angular applications with Tailwind CSS, including essential components, services, and best practices.',
925
+ paragraph2:
926
+ 'Built with the latest Angular features including standalone components, signals, and optimized for performance and developer experience.',
927
+ },
928
+ features: {
929
+ title: 'Features Included',
930
+ standalone: 'Standalone Angular components',
931
+ tailwind: 'Tailwind CSS integration',
932
+ responsive: 'Responsive design',
933
+ typescript: 'TypeScript ready',
934
+ production: 'Production optimized',
935
+ },
936
+ techStack: {
937
+ title: 'Built With Modern Technologies',
938
+ },
939
+ },
940
+ contact: {
941
+ title: 'Get in Touch',
942
+ subtitle:
943
+ "Have questions? We'd love to hear from you. Send us a message and we'll respond as soon as possible.",
944
+ form: {
945
+ title: 'Send us a message',
946
+ description: "Fill out the form below and we'll get back to you soon",
947
+ name: 'Full Name',
948
+ email: 'Email Address',
949
+ subject: 'Subject',
950
+ message: 'Message',
951
+ submit: 'Send Message',
952
+ sending: 'Sending...',
953
+ success: "Thank you for your message! We'll get back to you soon.",
954
+ errors: {
955
+ nameRequired: 'Name is required',
956
+ nameMinLength: 'Name must be at least 2 characters',
957
+ emailRequired: 'Email is required',
958
+ emailInvalid: 'Please enter a valid email',
959
+ subjectRequired: 'Subject is required',
960
+ messageRequired: 'Message is required',
961
+ messageMinLength: 'Message must be at least 10 characters',
962
+ },
963
+ },
964
+ info: {
965
+ title: 'Contact Information',
966
+ description: "We're here to help and answer any question you might have",
967
+ email: {
968
+ label: 'Email',
969
+ description: "We'll respond within 24 hours",
970
+ },
971
+ location: {
972
+ label: 'Location',
973
+ value: 'Remote & Global',
974
+ description: 'Working across all time zones',
975
+ },
976
+ responseTime: {
977
+ label: 'Response Time',
978
+ value: '24 hours',
979
+ description: 'Usually much faster',
980
+ },
981
+ },
982
+ help: {
983
+ title: 'Helpful Resources',
984
+ subtitle: 'Have questions about the starter template? Check out these resources:',
985
+ links: {
986
+ angular: 'Angular Documentation',
987
+ tailwind: 'Tailwind CSS Guide',
988
+ github: 'GitHub Repository',
989
+ },
990
+ },
991
+ },
992
+ auth: {
993
+ layout: {
994
+ welcome: 'Welcome back! Please sign in to continue.',
995
+ copyright: '© {{year}} {{name}}. All rights reserved.',
996
+ },
997
+ login: {
998
+ title: 'Sign In',
999
+ subtitle: 'Enter your credentials to access your account',
1000
+ email: 'Email Address',
1001
+ password: 'Password',
1002
+ rememberMe: 'Remember me',
1003
+ forgotPassword: 'Forgot password?',
1004
+ submit: 'Sign In',
1005
+ signing: 'Signing in...',
1006
+ noAccount: "Don't have an account?",
1007
+ createAccount: 'Create one here',
1008
+ showPassword: 'Show password',
1009
+ hidePassword: 'Hide password',
1010
+ },
1011
+ register: {
1012
+ title: 'Create Account',
1013
+ subtitle: 'Sign up to get started',
1014
+ firstName: 'First Name',
1015
+ lastName: 'Last Name',
1016
+ email: 'Email',
1017
+ password: 'Password',
1018
+ submit: 'Create Account',
1019
+ hasAccount: 'Already have an account?',
1020
+ },
1021
+ forgot: {
1022
+ title: 'Forgot Password',
1023
+ subtitle: 'Enter your email to reset password',
1024
+ email: 'Email Address',
1025
+ submit: 'Send Reset Link',
1026
+ backToLogin: 'Back to Sign In',
1027
+ },
1028
+ validation: {
1029
+ emailRequired: 'Email is required',
1030
+ emailInvalid: 'Please enter a valid email',
1031
+ passwordRequired: 'Password is required',
1032
+ passwordMinLength: 'Password must be at least 6 characters',
1033
+ },
1034
+ },
1035
+ footer: {
1036
+ description:
1037
+ 'Built with Angular and Tailwind CSS. A modern, fast, and responsive web application starter template.',
1038
+ quickLinks: 'Quick Links',
1039
+ builtWith: 'Built With',
1040
+ copyright: '© {{year}} {{name}}. Created with',
1041
+ madeWith: 'Made with ❤️ by developers, for developers',
1042
+ },
1043
+ language: {
1044
+ select: 'Select Language',
1045
+ english: 'English',
1046
+ arabic: 'العربية',
1047
+ },
1048
+ };
1049
+
1050
+ await fs.writeFile(
1051
+ path.join(config.fullPath, 'public/assets/i18n/en.json'),
1052
+ JSON.stringify(enTranslations, null, 2)
1053
+ );
1054
+
1055
+ // Create Arabic translations
1056
+ const arTranslations = {
1057
+ app: {
1058
+ title: config.projectName,
1059
+ welcome: 'مرحباً بك في {{name}}',
1060
+ description: 'قالب احترافي لتطبيقات Angular مع Tailwind CSS',
1061
+ },
1062
+ nav: {
1063
+ home: 'الرئيسية',
1064
+ about: 'حول',
1065
+ contact: 'اتصل بنا',
1066
+ login: 'تسجيل الدخول',
1067
+ register: 'ابدأ الآن',
1068
+ logout: 'تسجيل الخروج',
1069
+ profile: 'إعدادات الملف الشخصي',
1070
+ account: 'إعدادات الحساب',
1071
+ },
1072
+ home: {
1073
+ hero: {
1074
+ title: 'مرحباً بك في {{projectName}}',
1075
+ subtitle:
1076
+ 'قالب احترافي لتطبيقات Angular مع Tailwind CSS. مبني بأحدث الممارسات وجاهز للتوسع.',
1077
+ button: 'ابدأ الآن',
1078
+ learnMore: 'معرفة المزيد',
1079
+ },
1080
+ features: {
1081
+ title: 'ميزات Angular الحديثة',
1082
+ subtitle: 'كل ما تحتاجه لبناء تطبيقات ويب احترافية.',
1083
+ standalone: {
1084
+ title: 'مكونات مستقلة',
1085
+ description: 'بنية Angular الحديثة بدون NgModules',
1086
+ },
1087
+ tailwind: {
1088
+ title: 'Tailwind CSS',
1089
+ description: 'إطار عمل CSS سريع للتطوير',
1090
+ },
1091
+ typescript: {
1092
+ title: 'TypeScript',
1093
+ description: 'الأمان الكامل للأنواع وتجربة تطوير محسّنة',
1094
+ },
1095
+ },
1096
+ techStack: {
1097
+ title: 'الاستفادة من أحدث الأدوات والأطر لتحسين الأداء',
1098
+ angular: {
1099
+ title: 'Angular 20+',
1100
+ description: 'مكونات مستقلة حديثة مع الإشارات لإدارة الحالة التفاعلية',
1101
+ },
1102
+ tailwind: {
1103
+ title: 'Tailwind CSS v4',
1104
+ description: 'إطار عمل CSS مبني على المرافق مع إعداد PostCSS حديث',
1105
+ },
1106
+ typescript: {
1107
+ title: 'TypeScript',
1108
+ description: 'واجهات ونماذج مُكتوبة بقوة لتحسين تجربة المطور',
1109
+ },
1110
+ },
1111
+ readyToStart: {
1112
+ title: 'جاهز لبدء البناء؟',
1113
+ subtitle: 'استكشف الصفحات الأخرى أو تواصل معنا لمعرفة المزيد',
1114
+ viewHome: 'عرض الرئيسية',
1115
+ contactUs: 'تواصل معنا',
1116
+ },
1117
+ productionReady: {
1118
+ sectionTitle: 'ما هو مُضمن في هذا القالب',
1119
+ title: 'تطبيق Angular جاهز للإنتاج مع 20+ ميزة وخدمة ومكون مُعد مسبقاً',
1120
+ modernArchitecture: {
1121
+ title: 'معمارية حديثة',
1122
+ description: 'مكونات مستقلة، إشارات، دعم بدون منطقة، Angular 20+',
1123
+ },
1124
+ i18n: {
1125
+ title: 'ترجمة i18n',
1126
+ description: 'الإنجليزية والعربية مع دعم RTL، مبدل اللغة في الرأس',
1127
+ },
1128
+ interceptors: {
1129
+ title: 'مُعترِضات HTTP',
1130
+ description:
1131
+ 'المصادقة، معالجة الأخطاء، حالة التحميل، تخزين الاستجابة مؤقتاً (5 دقائق TTL)',
1132
+ },
1133
+ tailwind: {
1134
+ title: 'Tailwind CSS v4',
1135
+ description: 'إعداد PostCSS حديث، مرافق متجاوبة، جاهز للوضع المظلم',
1136
+ },
1137
+ uiComponents: {
1138
+ title: 'مكونات واجهة المستخدم',
1139
+ description: 'زر، بطاقة، دوار، تنبيه، نافذة منبثقة مع تخصيص كامل',
1140
+ },
1141
+ typeSafe: {
1142
+ title: 'آمن النوع',
1143
+ description: 'واجهات TypeScript، نماذج، خدمات مُكتوبة بقوة',
1144
+ },
1145
+ },
1146
+ coreServices: {
1147
+ title: 'الخدمات الأساسية (src/app/core/)',
1148
+ authService: 'AuthService - المصادقة وإدارة المستخدمين',
1149
+ apiService: 'ApiService - معالجة طلبات HTTP مركزية',
1150
+ toastService: 'ToastService - نظام الإشعارات (نجاح، خطأ، تحذير، معلومات)',
1151
+ modalService: 'ModalService - نظام الحوار مع التأكيد والتنبيه',
1152
+ loadingService: 'LoadingService - حالة التحميل العامة مع الإشارات',
1153
+ cacheService: 'CacheService - تخزين الاستجابة مؤقتاً مع TTL',
1154
+ storageService: 'StorageService - غلاف LocalStorage مع أمان النوع',
1155
+ i18nService: 'i18nService - إدارة الترجمة',
1156
+ },
1157
+ sharedComponents: {
1158
+ title: 'المكونات المشتركة (src/app/shared/)',
1159
+ button: 'ButtonComponent - متغيرات: أساسي، ثانوي، خطر',
1160
+ card: 'CardComponent - حاوية مرنة مع عنوان وظل',
1161
+ spinner: 'LoadingSpinnerComponent - مؤشر تحميل متحرك',
1162
+ toast: 'ToastComponent - إشعارات إغلاق تلقائي',
1163
+ modal: 'ModalComponent - حوار قابل للوصول بأحجام',
1164
+ pipes: 'الأنابيب - قطع، منذ',
1165
+ directives: 'التوجيهات - خارج النقر، تلميح',
1166
+ },
1167
+ preConfigured: {
1168
+ title: 'الحزم والأدوات المُعدة مسبقاً',
1169
+ subtitle: 'التبعيات وأدوات التطوير الجاهزة للإنتاج مُضمنة',
1170
+ coreDependencies: 'التبعيات الأساسية',
1171
+ developmentTools: 'أدوات التطوير',
1172
+ latest: 'الأحدث',
1173
+ i18n: 'i18n',
1174
+ iconLibrary: 'مكتبة الأيقونات',
1175
+ linting: 'الفحص',
1176
+ formatting: 'التنسيق',
1177
+ gitHooks: 'خطافات Git',
1178
+ typeSafety: 'أمان النوع',
1179
+ serviceWorker: 'عامل الخدمة',
1180
+ },
1181
+ pathAliases: {
1182
+ title: 'أسماء مسارات TypeScript (tsconfig.json)',
1183
+ core: '@core/* → src/app/core/*',
1184
+ shared: '@shared/* → src/app/shared/*',
1185
+ features: '@features/* → src/app/features/*',
1186
+ environments: '@environments/* → src/environments/*',
1187
+ },
1188
+ projectStructure: {
1189
+ title: 'هيكل المشروع منظم جيداً',
1190
+ subtitle: 'معمارية نظيفة تتبع أفضل ممارسات Angular',
1191
+ coreServices: 'services/ (8 خدمات)',
1192
+ guards: 'guards/ (حارس المصادقة)',
1193
+ interceptors: 'interceptors/ (4 أنواع)',
1194
+ i18n: 'i18n/ (نظام الترجمة)',
1195
+ components: 'components/ (5 مكونات)',
1196
+ pipes: 'pipes/ (2 أنابيب)',
1197
+ directives: 'directives/ (2 توجيهات)',
1198
+ models: 'models/ (واجهات TypeScript)',
1199
+ home: 'home/',
1200
+ about: 'about/',
1201
+ contact: 'contact/',
1202
+ auth: 'auth/ (تسجيل دخول، تسجيل، نسيان)',
1203
+ mainLayout: 'main-layout/ (رأس + تذييل)',
1204
+ authLayout: 'auth-layout/ (صفحات المصادقة)',
1205
+ },
1206
+ interactiveExamples: {
1207
+ title: 'جرب الأمثلة التفاعلية',
1208
+ subtitle: 'اختبر خدمات Toast و Modal المُضمنة في هذا القالب',
1209
+ toastNotifications: {
1210
+ title: 'إشعارات Toast',
1211
+ subtitle: 'تنبيهات إغلاق تلقائي مع 4 أنواع',
1212
+ description: 'انقر على أي زر لرؤية أنواع مختلفة من إشعارات Toast:',
1213
+ success: 'نجح',
1214
+ error: 'خطأ',
1215
+ warning: 'تحذير',
1216
+ info: 'معلومات',
1217
+ },
1218
+ modalDialogs: {
1219
+ title: 'حوارات النافذة المنبثقة',
1220
+ subtitle: 'حوارات قابلة للوصول ومتجاوبة',
1221
+ description: 'اختبر حوارات التأكيد والتنبيه مع الانتقالات:',
1222
+ showConfirm: 'إظهار حوار التأكيد',
1223
+ showAlert: 'إظهار حوار التنبيه',
1224
+ },
1225
+ proTip: {
1226
+ title: 'نصيحة احترافية',
1227
+ description:
1228
+ 'هذه الخدمات مُكتوبة بالكامل ويمكن حقنها في أي مكان في تطبيقك. تحقق من src/app/core/services/ لرؤية التنفيذ.',
1229
+ },
1230
+ },
1231
+ readyToBuild: {
1232
+ title: 'جاهز لبناء شيء مذهل؟',
1233
+ subtitle: 'استكشف الصفحات الأخرى لرؤية النماذج والتوجيه وواجهة المصادقة في العمل',
1234
+ learnMore: 'معرفة المزيد',
1235
+ },
1236
+ },
1237
+ about: {
1238
+ title: 'حول {{projectName}}',
1239
+ subtitle: 'قالب Angular حديث مبني بأفضل الممارسات وتجربة المطور في الاعتبار.',
1240
+ overview: {
1241
+ title: 'نظرة عامة على المشروع',
1242
+ paragraph1:
1243
+ 'يوفر هذا القالب أساساً قوياً لبناء تطبيقات Angular الحديثة مع Tailwind CSS، بما في ذلك المكونات الأساسية والخدمات وأفضل الممارسات.',
1244
+ paragraph2:
1245
+ 'مبني بأحدث ميزات Angular بما في ذلك المكونات المستقلة والإشارات، ومُحسّن للأداء وتجربة المطور.',
1246
+ },
1247
+ features: {
1248
+ title: 'الميزات المتضمنة',
1249
+ standalone: 'مكونات Angular المستقلة',
1250
+ tailwind: 'تكامل Tailwind CSS',
1251
+ responsive: 'تصميم متجاوب',
1252
+ typescript: 'جاهز لـ TypeScript',
1253
+ production: 'مُحسّن للإنتاج',
1254
+ },
1255
+ techStack: {
1256
+ title: 'مبني بأحدث التقنيات',
1257
+ },
1258
+ },
1259
+ contact: {
1260
+ title: 'تواصل معنا',
1261
+ subtitle: 'هل لديك أسئلة؟ نحب أن نسمع منك. أرسل لنا رسالة وسنرد في أقرب وقت ممكن.',
1262
+ form: {
1263
+ title: 'أرسل لنا رسالة',
1264
+ description: 'املأ النموذج أدناه وسنعاود الاتصال بك قريباً',
1265
+ name: 'الاسم الكامل',
1266
+ email: 'عنوان البريد الإلكتروني',
1267
+ subject: 'الموضوع',
1268
+ message: 'الرسالة',
1269
+ submit: 'إرسال الرسالة',
1270
+ sending: 'جاري الإرسال...',
1271
+ success: 'شكراً على رسالتك! سنعاود الاتصال بك قريباً.',
1272
+ errors: {
1273
+ nameRequired: 'الاسم مطلوب',
1274
+ nameMinLength: 'يجب أن يكون الاسم على الأقل حرفين',
1275
+ emailRequired: 'البريد الإلكتروني مطلوب',
1276
+ emailInvalid: 'يرجى إدخال بريد إلكتروني صحيح',
1277
+ subjectRequired: 'الموضوع مطلوب',
1278
+ messageRequired: 'الرسالة مطلوبة',
1279
+ messageMinLength: 'يجب أن تكون الرسالة على الأقل 10 أحرف',
1280
+ },
1281
+ },
1282
+ info: {
1283
+ title: 'معلومات الاتصال',
1284
+ description: 'نحن هنا للمساعدة والإجابة على أي سؤال قد يكون لديك',
1285
+ email: {
1286
+ label: 'البريد الإلكتروني',
1287
+ description: 'سنرد خلال 24 ساعة',
1288
+ },
1289
+ location: {
1290
+ label: 'الموقع',
1291
+ value: 'عن بُعد وعالمي',
1292
+ description: 'نعمل عبر جميع المناطق الزمنية',
1293
+ },
1294
+ responseTime: {
1295
+ label: 'وقت الاستجابة',
1296
+ value: '24 ساعة',
1297
+ description: 'عادة أسرع بكثير',
1298
+ },
1299
+ },
1300
+ help: {
1301
+ title: 'موارد مفيدة',
1302
+ subtitle: 'هل لديك أسئلة حول القالب؟ تحقق من هذه الموارد:',
1303
+ links: {
1304
+ angular: 'وثائق Angular',
1305
+ tailwind: 'دليل Tailwind CSS',
1306
+ github: 'مستودع GitHub',
1307
+ },
1308
+ },
1309
+ },
1310
+ auth: {
1311
+ layout: {
1312
+ welcome: 'مرحبًا بعودتك! يرجى تسجيل الدخول للمتابعة.',
1313
+ copyright: '© {{year}} {{name}}. جميع الحقوق محفوظة.',
1314
+ },
1315
+ login: {
1316
+ title: 'تسجيل الدخول',
1317
+ subtitle: 'أدخل بيانات الاعتماد للوصول إلى حسابك',
1318
+ email: 'عنوان البريد الإلكتروني',
1319
+ password: 'كلمة المرور',
1320
+ rememberMe: 'تذكرني',
1321
+ forgotPassword: 'نسيت كلمة المرور؟',
1322
+ submit: 'تسجيل الدخول',
1323
+ signing: 'جاري تسجيل الدخول...',
1324
+ noAccount: 'ليس لديك حساب؟',
1325
+ createAccount: 'أنشئ حساباً هنا',
1326
+ showPassword: 'إظهار كلمة المرور',
1327
+ hidePassword: 'إخفاء كلمة المرور',
1328
+ },
1329
+ register: {
1330
+ title: 'إنشاء حساب',
1331
+ subtitle: 'سجل للبدء',
1332
+ firstName: 'الاسم الأول',
1333
+ lastName: 'اسم العائلة',
1334
+ email: 'البريد الإلكتروني',
1335
+ password: 'كلمة المرور',
1336
+ submit: 'إنشاء حساب',
1337
+ hasAccount: 'لديك حساب بالفعل؟',
1338
+ },
1339
+ forgot: {
1340
+ title: 'نسيت كلمة المرور',
1341
+ subtitle: 'أدخل بريدك الإلكتروني لإعادة تعيين كلمة المرور',
1342
+ email: 'عنوان البريد الإلكتروني',
1343
+ submit: 'إرسال رابط إعادة التعيين',
1344
+ backToLogin: 'العودة إلى تسجيل الدخول',
1345
+ },
1346
+ validation: {
1347
+ emailRequired: 'البريد الإلكتروني مطلوب',
1348
+ emailInvalid: 'يرجى إدخال بريد إلكتروني صحيح',
1349
+ passwordRequired: 'كلمة المرور مطلوبة',
1350
+ passwordMinLength: 'يجب أن تكون كلمة المرور 6 أحرف على الأقل',
1351
+ },
1352
+ },
1353
+ footer: {
1354
+ description: 'مبني بـ Angular و Tailwind CSS. قالب تطبيق ويب حديث وسريع ومتجاوب.',
1355
+ quickLinks: 'روابط سريعة',
1356
+ builtWith: 'مبني بـ',
1357
+ copyright: '© {{year}} {{name}}. تم الإنشاء باستخدام',
1358
+ madeWith: 'صُنع بـ ❤️ من قبل المطورين، للمطورين',
1359
+ },
1360
+ language: {
1361
+ select: 'اختر اللغة',
1362
+ english: 'English',
1363
+ arabic: 'العربية',
1364
+ },
1365
+ };
1366
+
1367
+ await fs.writeFile(
1368
+ path.join(config.fullPath, 'public/assets/i18n/ar.json'),
1369
+ JSON.stringify(arTranslations, null, 2)
1370
+ );
1371
+
1372
+ // Update package.json to include translation dependencies
1373
+ const packageJsonPath = path.join(config.fullPath, 'package.json');
1374
+ const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
1375
+
1376
+ const translationDependencies = {
1377
+ '@ngx-translate/core': '^15.0.0',
1378
+ '@ngx-translate/http-loader': '^8.0.0',
1379
+ };
1380
+
1381
+ packageJson.dependencies = {
1382
+ ...packageJson.dependencies,
1383
+ ...translationDependencies,
1384
+ };
1385
+ await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
1386
+ },
1387
+
1388
+ async createGuards(config) {
1389
+ const authGuard = `import { CanActivateFn, Router } from '@angular/router';
1390
+ import { inject } from '@angular/core';
1391
+
1392
+ /**
1393
+ * Simple authentication guard
1394
+ * Replace with your actual authentication logic
1395
+ */
1396
+ export const authGuard: CanActivateFn = (_route, _state) => {
1397
+ const router = inject(Router);
1398
+
1399
+ // Replace with your actual authentication check
1400
+ const isAuthenticated = localStorage.getItem('auth-token') !== null;
1401
+
1402
+ if (!isAuthenticated) {
1403
+ // Redirect to login page
1404
+ router.navigate(['/auth/login']);
1405
+ return false;
1406
+ }
1407
+
1408
+ return true;
1409
+ };
1410
+
1411
+ /**
1412
+ * Example: Check if user has specific role
1413
+ */
1414
+ export const roleGuard = (requiredRole: string): CanActivateFn => {
1415
+ return (_route, _state) => {
1416
+ const router = inject(Router);
1417
+
1418
+ // Replace with your actual role checking logic
1419
+ const userRole = localStorage.getItem('user-role');
1420
+
1421
+ if (userRole !== requiredRole) {
1422
+ router.navigate(['/unauthorized']);
1423
+ return false;
1424
+ }
1425
+
1426
+ return true;
1427
+ };
1428
+ };`;
1429
+
1430
+ await fs.writeFile(path.join(config.fullPath, 'src/app/core/guards/auth.guard.ts'), authGuard);
1431
+ },
1432
+
1433
+ async createSharedComponents(config) {
1434
+ // Button Component
1435
+ const buttonComponent = `import { Component, Input, Output, EventEmitter } from '@angular/core';
1436
+ import { CommonModule } from '@angular/common';
1437
+
1438
+ export type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger';
1439
+ export type ButtonSize = 'sm' | 'md' | 'lg';
1440
+
1441
+ @Component({
1442
+ selector: 'app-button',
1443
+ standalone: true,
1444
+ imports: [CommonModule],
1445
+ template: \`
1446
+ <button
1447
+ [class]="buttonClasses"
1448
+ [disabled]="disabled || loading"
1449
+ (click)="handleClick($event)"
1450
+ [type]="type">
1451
+
1452
+ @if (loading) {
1453
+ <svg class="animate-spin -ml-1 mr-3 h-4 w-4" fill="none" viewBox="0 0 24 24">
1454
+ <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" class="opacity-25"></circle>
1455
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
1456
+ </svg>
1457
+ }
1458
+
1459
+ <ng-content></ng-content>
1460
+ </button>
1461
+ \`
1462
+ })
1463
+ export class ButtonComponent {
1464
+ @Input() variant: ButtonVariant = 'primary';
1465
+ @Input() size: ButtonSize = 'md';
1466
+ @Input() disabled = false;
1467
+ @Input() loading = false;
1468
+ @Input() type: 'button' | 'submit' | 'reset' = 'button';
1469
+ @Input() fullWidth = false;
1470
+
1471
+ @Output() clicked = new EventEmitter<Event>();
1472
+
1473
+ get buttonClasses(): string {
1474
+ const baseClasses = 'inline-flex items-center justify-center font-medium rounded-lg transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50';
1475
+
1476
+ const sizeClasses = {
1477
+ sm: 'text-sm px-3 py-2 h-9',
1478
+ md: 'text-sm px-4 py-2 h-10',
1479
+ lg: 'text-base px-8 py-3 h-11'
1480
+ };
1481
+
1482
+ const variantClasses = {
1483
+ primary: 'bg-blue-600 text-white hover:bg-blue-700',
1484
+ secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300',
1485
+ outline: 'border-2 border-blue-600 text-blue-600 hover:bg-blue-600 hover:text-white',
1486
+ ghost: 'hover:bg-gray-100 hover:text-gray-900',
1487
+ danger: 'bg-red-600 text-white hover:bg-red-700'
1488
+ };
1489
+
1490
+ const widthClass = this.fullWidth ? 'w-full' : '';
1491
+
1492
+ return \`\${baseClasses} \${sizeClasses[this.size]} \${variantClasses[this.variant]} \${widthClass}\`.trim();
1493
+ }
1494
+
1495
+ handleClick(event: Event): void {
1496
+ if (!this.disabled && !this.loading) {
1497
+ this.clicked.emit(event);
1498
+ }
1499
+ }
1500
+ }`;
1501
+
1502
+ await fs.writeFile(
1503
+ path.join(config.fullPath, 'src/app/shared/components/button/button.component.ts'),
1504
+ buttonComponent
1505
+ );
1506
+
1507
+ // Card Component
1508
+ const cardComponent = `import { Component, Input } from '@angular/core';
1509
+ import { CommonModule } from '@angular/common';
1510
+
1511
+ @Component({
1512
+ selector: 'app-card',
1513
+ standalone: true,
1514
+ imports: [CommonModule],
1515
+ template: \`
1516
+ <div [class]="cardClasses">
1517
+ @if (title || subtitle) {
1518
+ <div class="px-6 py-4 border-b border-gray-200">
1519
+ @if (title) {
1520
+ <h3 class="text-lg font-semibold text-gray-900">
1521
+ {{ title }}
1522
+ </h3>
1523
+ }
1524
+ @if (subtitle) {
1525
+ <p class="text-sm text-gray-600 mt-1">
1526
+ {{ subtitle }}
1527
+ </p>
1528
+ }
1529
+ </div>
1530
+ }
1531
+
1532
+ <div [class]="contentClasses">
1533
+ <ng-content></ng-content>
1534
+ </div>
1535
+
1536
+ @if (hasFooter) {
1537
+ <div class="px-6 py-4 border-t border-gray-200 bg-gray-50">
1538
+ <ng-content select="[slot=footer]"></ng-content>
1539
+ </div>
1540
+ }
1541
+ </div>
1542
+ \`
1543
+ })
1544
+ export class CardComponent {
1545
+ @Input() title?: string;
1546
+ @Input() subtitle?: string;
1547
+ @Input() padding = true;
1548
+ @Input() shadow = true;
1549
+ @Input() hover = false;
1550
+ @Input() hasFooter = false;
1551
+
1552
+ get cardClasses(): string {
1553
+ const baseClasses = 'bg-white border border-gray-200 rounded-lg overflow-hidden';
1554
+ const shadowClasses = this.shadow ? 'shadow-sm' : '';
1555
+ const hoverClasses = this.hover ? 'hover:shadow-md transition-shadow duration-200' : '';
1556
+
1557
+ return \`\${baseClasses} \${shadowClasses} \${hoverClasses}\`.trim();
1558
+ }
1559
+
1560
+ get contentClasses(): string {
1561
+ return this.padding ? 'p-6' : '';
1562
+ }
1563
+ }`;
1564
+
1565
+ await fs.writeFile(
1566
+ path.join(config.fullPath, 'src/app/shared/components/card/card.component.ts'),
1567
+ cardComponent
1568
+ );
1569
+
1570
+ // Loading Spinner Component
1571
+ const spinnerComponent = `import { Component, Input } from '@angular/core';
1572
+ import { CommonModule } from '@angular/common';
1573
+
1574
+ export type SpinnerSize = 'sm' | 'md' | 'lg' | 'xl';
1575
+
1576
+ @Component({
1577
+ selector: 'app-loading-spinner',
1578
+ standalone: true,
1579
+ imports: [CommonModule],
1580
+ template: \`
1581
+ <div [class]="containerClasses">
1582
+ <svg [class]="spinnerClasses" fill="none" viewBox="0 0 24 24">
1583
+ <circle
1584
+ cx="12" cy="12" r="10"
1585
+ stroke="currentColor"
1586
+ stroke-width="4"
1587
+ class="opacity-25">
1588
+ </circle>
1589
+ <path
1590
+ class="opacity-75"
1591
+ fill="currentColor"
1592
+ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z">
1593
+ </path>
1594
+ </svg>
1595
+ @if (text) {
1596
+ <span [class]="textClasses">{{ text }}</span>
1597
+ }
1598
+ </div>
1599
+ \`
1600
+ })
1601
+ export class LoadingSpinnerComponent {
1602
+ @Input() size: SpinnerSize = 'md';
1603
+ @Input() text?: string;
1604
+ @Input() centered = false;
1605
+ @Input() color = 'blue';
1606
+
1607
+ private readonly sizeClasses = {
1608
+ sm: 'h-4 w-4',
1609
+ md: 'h-8 w-8',
1610
+ lg: 'h-12 w-12',
1611
+ xl: 'h-16 w-16'
1612
+ } as const;
1613
+
1614
+ private readonly colorClasses = {
1615
+ blue: 'text-blue-600',
1616
+ gray: 'text-gray-600',
1617
+ white: 'text-white',
1618
+ green: 'text-green-600',
1619
+ red: 'text-red-600'
1620
+ } as const;
1621
+
1622
+ protected readonly textClasses = 'text-sm text-gray-600';
1623
+
1624
+ get containerClasses(): string {
1625
+ const baseClasses = 'flex items-center';
1626
+ const centerClasses = this.centered ? 'justify-center' : '';
1627
+ const directionClasses = this.text ? 'flex-col space-y-2' : '';
1628
+
1629
+ return \`\${baseClasses} \${centerClasses} \${directionClasses}\`.trim();
1630
+ }
1631
+
1632
+ get spinnerClasses(): string {
1633
+ const baseClasses = 'animate-spin';
1634
+ return \`\${baseClasses} \${this.sizeClasses[this.size]} \${this.colorClasses[this.color as keyof typeof this.colorClasses] || this.colorClasses.blue}\`.trim();
1635
+ }
1636
+ }`;
1637
+
1638
+ await fs.writeFile(
1639
+ path.join(
1640
+ config.fullPath,
1641
+ 'src/app/shared/components/loading-spinner/loading-spinner.component.ts'
1642
+ ),
1643
+ spinnerComponent
1644
+ );
1645
+ },
1646
+
1647
+ async createPipes(config) {
1648
+ // Truncate Pipe
1649
+ const truncatePipe = `import { Pipe, PipeTransform } from '@angular/core';
1650
+
1651
+ @Pipe({
1652
+ name: 'truncate',
1653
+ standalone: true
1654
+ })
1655
+ export class TruncatePipe implements PipeTransform {
1656
+ transform(value: string | null | undefined, limit = 50, suffix = '...'): string {
1657
+ if (!value) return '';
1658
+
1659
+ if (value.length <= limit) {
1660
+ return value;
1661
+ }
1662
+
1663
+ return value.substring(0, limit).trim() + suffix;
1664
+ }
1665
+ }`;
1666
+
1667
+ await fs.writeFile(
1668
+ path.join(config.fullPath, 'src/app/shared/pipes/truncate.pipe.ts'),
1669
+ truncatePipe
1670
+ );
1671
+
1672
+ // Time Ago Pipe
1673
+ const timeAgoPipe = `import { Pipe, PipeTransform } from '@angular/core';
1674
+
1675
+ @Pipe({
1676
+ name: 'timeAgo',
1677
+ standalone: true
1678
+ })
1679
+ export class TimeAgoPipe implements PipeTransform {
1680
+ transform(value: string | number | Date | null | undefined): string {
1681
+ if (!value) return '';
1682
+
1683
+ const date = new Date(value);
1684
+ const now = new Date();
1685
+ const secondsAgo = Math.floor((now.getTime() - date.getTime()) / 1000);
1686
+
1687
+ if (secondsAgo < 60) {
1688
+ return 'just now';
1689
+ }
1690
+
1691
+ const minutesAgo = Math.floor(secondsAgo / 60);
1692
+ if (minutesAgo < 60) {
1693
+ return \`\${minutesAgo} minute\${minutesAgo === 1 ? '' : 's'} ago\`;
1694
+ }
1695
+
1696
+ const hoursAgo = Math.floor(minutesAgo / 60);
1697
+ if (hoursAgo < 24) {
1698
+ return \`\${hoursAgo} hour\${hoursAgo === 1 ? '' : 's'} ago\`;
1699
+ }
1700
+
1701
+ const daysAgo = Math.floor(hoursAgo / 24);
1702
+ if (daysAgo < 7) {
1703
+ return \`\${daysAgo} day\${daysAgo === 1 ? '' : 's'} ago\`;
1704
+ }
1705
+
1706
+ const weeksAgo = Math.floor(daysAgo / 7);
1707
+ if (weeksAgo < 4) {
1708
+ return \`\${weeksAgo} week\${weeksAgo === 1 ? '' : 's'} ago\`;
1709
+ }
1710
+
1711
+ const monthsAgo = Math.floor(daysAgo / 30);
1712
+ if (monthsAgo < 12) {
1713
+ return \`\${monthsAgo} month\${monthsAgo === 1 ? '' : 's'} ago\`;
1714
+ }
1715
+
1716
+ const yearsAgo = Math.floor(daysAgo / 365);
1717
+ return \`\${yearsAgo} year\${yearsAgo === 1 ? '' : 's'} ago\`;
1718
+ }
1719
+ }`;
1720
+
1721
+ await fs.writeFile(
1722
+ path.join(config.fullPath, 'src/app/shared/pipes/time-ago.pipe.ts'),
1723
+ timeAgoPipe
1724
+ );
1725
+ },
1726
+
1727
+ async createModels(config) {
1728
+ // User Interface
1729
+ const userInterface = `export interface User {
1730
+ id: string;
1731
+ email: string;
1732
+ firstName: string;
1733
+ lastName: string;
1734
+ avatar?: string;
1735
+ role: 'user' | 'admin' | 'moderator';
1736
+ createdAt: Date;
1737
+ updatedAt: Date;
1738
+ isActive: boolean;
1739
+ }
1740
+
1741
+ export interface UserProfile {
1742
+ user: User;
1743
+ preferences: {
1744
+ notifications: boolean;
1745
+ language: string;
1746
+ };
1747
+ }
1748
+
1749
+ export interface CreateUserRequest {
1750
+ email: string;
1751
+ password: string;
1752
+ firstName: string;
1753
+ lastName: string;
1754
+ }
1755
+
1756
+ export interface UpdateUserRequest {
1757
+ firstName?: string;
1758
+ lastName?: string;
1759
+ avatar?: string;
1760
+ }`;
1761
+
1762
+ await fs.writeFile(
1763
+ path.join(config.fullPath, 'src/app/shared/models/user.interface.ts'),
1764
+ userInterface
1765
+ );
1766
+
1767
+ // API Response Interface
1768
+ const apiResponseInterface = `export interface ApiResponse<T = unknown> {
1769
+ data: T;
1770
+ message: string;
1771
+ success: boolean;
1772
+ errors?: string[];
1773
+ meta?: {
1774
+ total: number;
1775
+ page: number;
1776
+ perPage: number;
1777
+ totalPages: number;
1778
+ };
1779
+ }
1780
+
1781
+ export interface ApiError {
1782
+ message: string;
1783
+ code: string;
1784
+ field?: string;
1785
+ }
1786
+
1787
+ export interface PaginationParams {
1788
+ page: number;
1789
+ perPage: number;
1790
+ sortBy?: string;
1791
+ sortOrder?: 'asc' | 'desc';
1792
+ }
1793
+
1794
+ export interface ContactFormData {
1795
+ name: string;
1796
+ email: string;
1797
+ subject: string;
1798
+ message: string;
1799
+ }`;
1800
+
1801
+ await fs.writeFile(
1802
+ path.join(config.fullPath, 'src/app/shared/models/api-response.interface.ts'),
1803
+ apiResponseInterface
1804
+ );
1805
+ },
1806
+
1807
+ async createLayout(config) {
1808
+ // Header Component with Authentication and Language Switcher
1809
+ const headerComponentTs = `import { Component, inject, OnInit } from '@angular/core';
1810
+ import { CommonModule } from '@angular/common';
1811
+ import { RouterModule, Router } from '@angular/router';
1812
+ import { NgIconComponent, provideIcons } from '@ng-icons/core';
1813
+ import { heroHome, heroInformationCircle, heroEnvelope, heroUser, heroBars3, heroXMark, heroChevronDown, heroLanguage } from '@ng-icons/heroicons/outline';
1814
+ import { TranslateModule } from '@ngx-translate/core';
1815
+ import { AuthService } from '@core/services/auth.service';
1816
+ import { TranslationService, type SupportedLanguage, type LanguageOption } from '@core/i18n/translation.service';
1817
+
1818
+ interface User {
1819
+ id: string;
1820
+ email: string;
1821
+ firstName: string;
1822
+ lastName: string;
1823
+ }
1824
+
1825
+ @Component({
1826
+ selector: 'app-header',
1827
+ standalone: true,
1828
+ imports: [CommonModule, RouterModule, NgIconComponent, TranslateModule],
1829
+ viewProviders: [provideIcons({ heroHome, heroInformationCircle, heroEnvelope, heroUser, heroBars3, heroXMark, heroChevronDown, heroLanguage })],
1830
+ templateUrl: './header.component.html'
1831
+ })
1832
+ export class HeaderComponent implements OnInit {
1833
+ private authService = inject(AuthService);
1834
+ private router = inject(Router);
1835
+ public translationService = inject(TranslationService);
1836
+
1837
+ protected readonly projectName = '${config.projectName}';
1838
+
1839
+ mobileMenuOpen = false;
1840
+ userMenuOpen = false;
1841
+ langMenuOpen = false;
1842
+ isAuthenticated = false;
1843
+ currentUser: User | null = null;
1844
+ currentLang: SupportedLanguage = 'en';
1845
+ currentLanguage: LanguageOption = this.translationService.languages[0];
1846
+
1847
+ ngOnInit() {
1848
+ // Subscribe to authentication state
1849
+ this.authService.isAuthenticated$.subscribe(
1850
+ isAuth => this.isAuthenticated = isAuth
1851
+ );
1852
+
1853
+ this.authService.currentUser$.subscribe(
1854
+ user => this.currentUser = user
1855
+ );
1856
+
1857
+ // Subscribe to language changes
1858
+ this.currentLang = this.translationService.getCurrentLanguage();
1859
+ const lang = this.translationService.getLanguageOption(this.currentLang);
1860
+ if (lang) {
1861
+ this.currentLanguage = lang;
1862
+ }
1863
+ }
1864
+
1865
+ changeLanguage(lang: SupportedLanguage) {
1866
+ this.translationService.setLanguage(lang);
1867
+ this.currentLang = lang;
1868
+ const langOption = this.translationService.getLanguageOption(lang);
1869
+ if (langOption) {
1870
+ this.currentLanguage = langOption;
1871
+ }
1872
+ this.langMenuOpen = false;
1873
+ }
1874
+
1875
+ onLogout() {
1876
+ this.authService.logout();
1877
+ this.userMenuOpen = false;
1878
+ this.router.navigate(['/']);
1879
+ }
1880
+
1881
+ closeAllMenus() {
1882
+ this.mobileMenuOpen = false;
1883
+ this.userMenuOpen = false;
1884
+ this.langMenuOpen = false;
1885
+ }
1886
+ }`;
1887
+
1888
+ const headerComponentHtml = `<header class="sticky top-0 z-50 bg-white/80 backdrop-blur-lg border-b border-gray-200/80">
1889
+ <nav class="container mx-auto px-4 sm:px-6 lg:px-8">
1890
+ <div class="flex items-center justify-between h-16">
1891
+
1892
+ <!-- Logo -->
1893
+ <div class="flex items-center space-x-4">
1894
+ <a routerLink="/" class="flex items-center space-x-3">
1895
+ <div class="h-8 w-8 bg-linear-to-r from-blue-500 to-purple-600 rounded-lg flex items-center justify-center">
1896
+ <span class="text-white font-bold text-sm">{{ projectName.charAt(0).toUpperCase() }}</span>
1897
+ </div>
1898
+ <span class="text-xl font-semibold text-gray-900">
1899
+ {{ projectName }}
1900
+ </span>
1901
+ </a>
1902
+ </div>
1903
+
1904
+ <!-- Desktop Navigation -->
1905
+ <div class="hidden md:block">
1906
+ <div class="ml-10 flex items-center space-x-8">
1907
+ <a
1908
+ routerLink="/"
1909
+ routerLinkActive="text-blue-600"
1910
+ [routerLinkActiveOptions]="{exact: true}"
1911
+ class="flex items-center gap-1.5 text-gray-700 hover:text-blue-600 transition-colors font-medium">
1912
+ <ng-icon name="heroHome" size="18"></ng-icon>
1913
+ {{ 'nav.home' | translate }}
1914
+ </a>
1915
+ <a
1916
+ routerLink="/about"
1917
+ routerLinkActive="text-blue-600"
1918
+ class="flex items-center gap-1.5 text-gray-700 hover:text-blue-600 transition-colors font-medium">
1919
+ <ng-icon name="heroInformationCircle" size="18"></ng-icon>
1920
+ {{ 'nav.about' | translate }}
1921
+ </a>
1922
+ <a
1923
+ routerLink="/contact"
1924
+ routerLinkActive="text-blue-600"
1925
+ class="flex items-center gap-1.5 text-gray-700 hover:text-blue-600 transition-colors font-medium">
1926
+ <ng-icon name="heroEnvelope" size="18"></ng-icon>
1927
+ {{ 'nav.contact' | translate }}
1928
+ </a>
1929
+ </div>
1930
+ </div>
1931
+
1932
+ <!-- Right side actions -->
1933
+ <div class="flex items-center space-x-4">
1934
+
1935
+ <!-- Language Switcher -->
1936
+ <div class="hidden md:block relative">
1937
+ <button
1938
+ (click)="langMenuOpen = !langMenuOpen"
1939
+ class="flex items-center space-x-2 text-gray-700 hover:text-blue-600 transition-colors px-3 py-2 rounded-lg hover:bg-gray-100"
1940
+ [class.text-blue-600]="langMenuOpen"
1941
+ [class.bg-gray-100]="langMenuOpen">
1942
+ <ng-icon name="heroLanguage" size="20"></ng-icon>
1943
+ <span class="font-medium text-sm">{{ currentLanguage.nativeName }}</span>
1944
+ <ng-icon name="heroChevronDown" size="16"></ng-icon>
1945
+ </button>
1946
+
1947
+ <!-- Language dropdown menu -->
1948
+ @if (langMenuOpen) {
1949
+ <div class="absolute right-0 mt-2 w-48 bg-white rounded-lg shadow-lg border border-gray-200 z-50">
1950
+ <div class="py-1">
1951
+ @for (lang of translationService.languages; track lang.code) {
1952
+ <button
1953
+ (click)="changeLanguage(lang.code)"
1954
+ class="w-full text-left px-4 py-2 text-sm transition-colors flex items-center justify-between"
1955
+ [class.bg-blue-50]="currentLang === lang.code"
1956
+ [class.text-blue-600]="currentLang === lang.code"
1957
+ [class.text-gray-700]="currentLang !== lang.code"
1958
+ [class.hover:bg-gray-100]="currentLang !== lang.code">
1959
+ <span class="flex items-center space-x-2">
1960
+ <span class="text-lg">{{ lang.flag }}</span>
1961
+ <span>{{ lang.nativeName }}</span>
1962
+ </span>
1963
+ @if (currentLang === lang.code) {
1964
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1965
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
1966
+ </svg>
1967
+ }
1968
+ </button>
1969
+ }
1970
+ </div>
1971
+ </div>
1972
+ }
1973
+ </div>
1974
+
1975
+ <!-- Authentication Section -->
1976
+ <div class="hidden md:flex items-center space-x-3">
1977
+ <!-- Logged out state -->
1978
+ @if (!isAuthenticated) {
1979
+ <a
1980
+ routerLink="/auth/login"
1981
+ class="text-gray-700 hover:text-blue-600 transition-colors font-medium">
1982
+ {{ 'nav.login' | translate }}
1983
+ </a>
1984
+ <a
1985
+ routerLink="/auth/register"
1986
+ class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg transition-colors font-medium">
1987
+ {{ 'nav.register' | translate }}
1988
+ </a>
1989
+ }
1990
+
1991
+ <!-- Logged in state -->
1992
+ @if (isAuthenticated) {
1993
+ <div class="relative">
1994
+ <button
1995
+ (click)="userMenuOpen = !userMenuOpen"
1996
+ class="flex items-center space-x-2 text-gray-700 hover:text-blue-600 transition-colors"
1997
+ [class.text-blue-600]="userMenuOpen">
1998
+ <div class="h-8 w-8 bg-blue-600 rounded-full flex items-center justify-center">
1999
+ <ng-icon name="heroUser" size="16" style="color: white;"></ng-icon>
2000
+ </div>
2001
+ <span class="font-medium">{{ currentUser?.firstName || 'User' }}</span>
2002
+ <ng-icon name="heroChevronDown" size="16"></ng-icon>
2003
+ </button>
2004
+
2005
+ <!-- User dropdown menu -->
2006
+ @if (userMenuOpen) {
2007
+ <div class="absolute right-0 mt-2 w-48 bg-white rounded-lg shadow-lg border border-gray-200 z-50">
2008
+ <div class="py-1">
2009
+ <div class="px-4 py-2 text-sm text-gray-500 border-b border-gray-100">
2010
+ {{ currentUser?.email }}
2011
+ </div>
2012
+ <button
2013
+ class="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition-colors">
2014
+ {{ 'nav.profile' | translate }}
2015
+ </button>
2016
+ <button
2017
+ class="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition-colors">
2018
+ {{ 'nav.account' | translate }}
2019
+ </button>
2020
+ <hr class="my-1">
2021
+ <button
2022
+ (click)="onLogout()"
2023
+ class="w-full text-left px-4 py-2 text-sm text-red-600 hover:bg-red-50 transition-colors">
2024
+ {{ 'nav.logout' | translate }}
2025
+ </button>
2026
+ </div>
2027
+ </div>
2028
+ }
2029
+ </div>
2030
+ }
2031
+ </div>
2032
+
2033
+ <!-- Mobile menu button -->
2034
+ <button
2035
+ (click)="mobileMenuOpen = !mobileMenuOpen"
2036
+ class="md:hidden p-2 rounded-lg hover:bg-gray-100 transition-colors">
2037
+ @if (!mobileMenuOpen) {
2038
+ <ng-icon name="heroBars3" size="24"></ng-icon>
2039
+ }
2040
+ @if (mobileMenuOpen) {
2041
+ <ng-icon name="heroXMark" size="24"></ng-icon>
2042
+ }
2043
+ </button>
2044
+ </div>
2045
+ </div>
2046
+
2047
+ <!-- Mobile Navigation -->
2048
+ @if (mobileMenuOpen) {
2049
+ <div class="md:hidden pb-4">
2050
+ <div class="flex flex-col space-y-2 mt-4">
2051
+ <!-- Navigation Links -->
2052
+ <a
2053
+ routerLink="/"
2054
+ routerLinkActive="text-blue-600 bg-blue-50"
2055
+ [routerLinkActiveOptions]="{exact: true}"
2056
+ (click)="mobileMenuOpen = false"
2057
+ class="px-3 py-2 rounded-lg text-gray-700 hover:text-blue-600 hover:bg-gray-100 transition-colors font-medium">
2058
+ {{ 'nav.home' | translate }}
2059
+ </a>
2060
+ <a
2061
+ routerLink="/about"
2062
+ routerLinkActive="text-blue-600 bg-blue-50"
2063
+ (click)="mobileMenuOpen = false"
2064
+ class="px-3 py-2 rounded-lg text-gray-700 hover:text-blue-600 hover:bg-gray-100 transition-colors font-medium">
2065
+ {{ 'nav.about' | translate }}
2066
+ </a>
2067
+ <a
2068
+ routerLink="/contact"
2069
+ routerLinkActive="text-blue-600 bg-blue-50"
2070
+ (click)="mobileMenuOpen = false"
2071
+ class="px-3 py-2 rounded-lg text-gray-700 hover:text-blue-600 hover:bg-gray-100 transition-colors font-medium">
2072
+ {{ 'nav.contact' | translate }}
2073
+ </a>
2074
+
2075
+ <!-- Mobile Language Switcher -->
2076
+ <hr class="my-2">
2077
+ <div class="px-3 py-2">
2078
+ <p class="text-xs font-semibold text-gray-500 uppercase mb-2">{{ 'language.select' | translate }}</p>
2079
+ <div class="flex flex-col space-y-1">
2080
+ @for (lang of translationService.languages; track lang.code) {
2081
+ <button
2082
+ (click)="changeLanguage(lang.code)"
2083
+ class="w-full text-left px-3 py-2 rounded-lg text-sm transition-colors flex items-center justify-between"
2084
+ [class.bg-blue-50]="currentLang === lang.code"
2085
+ [class.text-blue-600]="currentLang === lang.code"
2086
+ [class.text-gray-700]="currentLang !== lang.code"
2087
+ [class.hover:bg-gray-100]="currentLang !== lang.code">
2088
+ <span class="flex items-center space-x-2">
2089
+ <span class="text-lg">{{ lang.flag }}</span>
2090
+ <span>{{ lang.nativeName }}</span>
2091
+ </span>
2092
+ @if (currentLang === lang.code) {
2093
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
2094
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
2095
+ </svg>
2096
+ }
2097
+ </button>
2098
+ }
2099
+ </div>
2100
+ </div>
2101
+
2102
+ <!-- Mobile Auth Section -->
2103
+ <hr class="my-2">
2104
+ @if (!isAuthenticated) {
2105
+ <a
2106
+ routerLink="/auth/login"
2107
+ (click)="mobileMenuOpen = false"
2108
+ class="px-3 py-2 rounded-lg text-gray-700 hover:text-blue-600 hover:bg-gray-100 transition-colors font-medium">
2109
+ {{ 'nav.login' | translate }}
2110
+ </a>
2111
+ <a
2112
+ routerLink="/auth/register"
2113
+ (click)="mobileMenuOpen = false"
2114
+ class="px-3 py-2 rounded-lg bg-blue-600 text-white hover:bg-blue-700 transition-colors font-medium text-center">
2115
+ {{ 'nav.register' | translate }}
2116
+ </a>
2117
+ }
2118
+
2119
+ @if (isAuthenticated) {
2120
+ <div class="px-3 py-2 text-sm text-gray-500">
2121
+ Welcome, {{ currentUser?.firstName || 'User' }}!
2122
+ </div>
2123
+ <button
2124
+ class="w-full text-left px-3 py-2 rounded-lg text-gray-700 hover:bg-gray-100 transition-colors">
2125
+ {{ 'nav.profile' | translate }}
2126
+ </button>
2127
+ <button
2128
+ (click)="onLogout(); mobileMenuOpen = false"
2129
+ class="w-full text-left px-3 py-2 rounded-lg text-red-600 hover:bg-red-50 transition-colors">
2130
+ {{ 'nav.logout' | translate }}
2131
+ </button>
2132
+ }
2133
+ </div>
2134
+ </div>
2135
+ }
2136
+ </nav>
2137
+ </header>
2138
+
2139
+ <!-- Backdrop for mobile menu and user dropdown -->
2140
+ @if (mobileMenuOpen || userMenuOpen || langMenuOpen) {
2141
+ <button
2142
+ (click)="closeAllMenus()"
2143
+ (keydown.escape)="closeAllMenus()"
2144
+ type="button"
2145
+ aria-label="Close menu"
2146
+ class="fixed inset-0 z-40 bg-black/20 backdrop-blur-sm cursor-default">
2147
+ </button>
2148
+ }
2149
+ `;
2150
+
2151
+ await fs.writeFile(
2152
+ path.join(config.fullPath, 'src/app/layout/header/header.component.ts'),
2153
+ headerComponentTs
2154
+ );
2155
+
2156
+ await fs.writeFile(
2157
+ path.join(config.fullPath, 'src/app/layout/header/header.component.html'),
2158
+ headerComponentHtml
2159
+ );
2160
+
2161
+ // Footer Component
2162
+ const footerComponentTs = `import { Component } from '@angular/core';
2163
+ import { CommonModule } from '@angular/common';
2164
+ import { TranslateModule } from '@ngx-translate/core';
2165
+
2166
+ @Component({
2167
+ selector: 'app-footer',
2168
+ standalone: true,
2169
+ imports: [CommonModule, TranslateModule],
2170
+ templateUrl: './footer.component.html'
2171
+ })
2172
+ export class FooterComponent {
2173
+ protected readonly projectName = '${config.projectName}';
2174
+ protected readonly currentYear = new Date().getFullYear();
2175
+ }`;
2176
+
2177
+ const footerComponentHtml = `<footer class="bg-white border-t border-gray-200">
2178
+ <div class="container mx-auto px-4 sm:px-6 lg:px-8 py-8">
2179
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-8">
2180
+
2181
+ <!-- Company Info -->
2182
+ <div>
2183
+ <div class="flex items-center space-x-3 mb-4">
2184
+ <div class="h-8 w-8 bg-linear-to-r from-blue-500 to-purple-600 rounded-lg flex items-center justify-center">
2185
+ </div>
2186
+ <span class="text-xl font-semibold text-gray-900">
2187
+ {{ projectName }}
2188
+ </span>
2189
+ </div>
2190
+ <p class="text-gray-600 text-sm max-w-md">
2191
+ {{ 'footer.description' | translate }}
2192
+ </p>
2193
+ </div>
2194
+
2195
+ <!-- Quick Links -->
2196
+ <div>
2197
+ <h3 class="text-sm font-semibold text-gray-900 uppercase tracking-wider mb-4">
2198
+ {{ 'footer.quickLinks' | translate }}
2199
+ </h3>
2200
+ <ul class="space-y-2">
2201
+ <li>
2202
+ <a href="/" class="text-gray-600 hover:text-blue-600 transition-colors text-sm">
2203
+ {{ 'nav.home' | translate }}
2204
+ </a>
2205
+ </li>
2206
+ <li>
2207
+ <a href="/about" class="text-gray-600 hover:text-blue-600 transition-colors text-sm">
2208
+ {{ 'nav.about' | translate }}
2209
+ </a>
2210
+ </li>
2211
+ <li>
2212
+ <a href="/contact" class="text-gray-600 hover:text-blue-600 transition-colors text-sm">
2213
+ {{ 'nav.contact' | translate }}
2214
+ </a>
2215
+ </li>
2216
+ </ul>
2217
+ </div>
2218
+
2219
+ <!-- Tech Stack -->
2220
+ <div>
2221
+ <h3 class="text-sm font-semibold text-gray-900 uppercase tracking-wider mb-4">
2222
+ {{ 'footer.builtWith' | translate }}
2223
+ </h3>
2224
+ <ul class="space-y-2">
2225
+ <li>
2226
+ <a href="https://angular.dev" target="_blank" class="text-gray-600 hover:text-blue-600 transition-colors text-sm">
2227
+ Angular
2228
+ </a>
2229
+ </li>
2230
+ <li>
2231
+ <a href="https://tailwindcss.com" target="_blank" class="text-gray-600 hover:text-blue-600 transition-colors text-sm">
2232
+ Tailwind CSS
2233
+ </a>
2234
+ </li>
2235
+ <li>
2236
+ <a href="https://typescript.org" target="_blank" class="text-gray-600 hover:text-blue-600 transition-colors text-sm">
2237
+ TypeScript
2238
+ </a>
2239
+ </li>
2240
+ </ul>
2241
+ </div>
2242
+ </div>
2243
+
2244
+ <!-- Bottom Bar -->
2245
+ <div class="border-t border-gray-200 mt-8 pt-8 flex flex-col md:flex-row justify-between items-center">
2246
+ <p class="text-gray-500 text-sm">
2247
+ {{ 'footer.copyright' | translate:{ year: currentYear, name: projectName } }}
2248
+ </p>
2249
+ <p class="text-gray-500 text-sm mt-2 md:mt-0">
2250
+ {{ 'footer.madeWith' | translate }}
2251
+ </p>
2252
+ </div>
2253
+ </div>
2254
+ </footer>
2255
+ `;
2256
+
2257
+ await fs.writeFile(
2258
+ path.join(config.fullPath, 'src/app/layout/footer/footer.component.ts'),
2259
+ footerComponentTs
2260
+ );
2261
+
2262
+ await fs.writeFile(
2263
+ path.join(config.fullPath, 'src/app/layout/footer/footer.component.html'),
2264
+ footerComponentHtml
2265
+ );
2266
+ },
2267
+
2268
+ async createAuthLayout(config) {
2269
+ // Auth Layout Component
2270
+ const authLayoutComponent = `import { Component } from '@angular/core';
2271
+ import { CommonModule } from '@angular/common';
2272
+ import { RouterOutlet } from '@angular/router';
2273
+ import { TranslateModule } from '@ngx-translate/core';
2274
+
2275
+ @Component({
2276
+ selector: 'app-auth-layout',
2277
+ standalone: true,
2278
+ imports: [CommonModule, RouterOutlet, TranslateModule],
2279
+ template: \`
2280
+ <div class="min-h-screen bg-linear-to-br from-blue-500 via-purple-600 to-indigo-700 flex items-center justify-center p-4">
2281
+ <div class="w-full max-w-md">
2282
+
2283
+ <!-- Logo/Brand -->
2284
+ <div class="text-center mb-8">
2285
+ <div class="inline-flex items-center justify-center h-16 w-16 bg-white/20 backdrop-blur-lg rounded-2xl mb-4">
2286
+ <span class="text-2xl font-bold text-white">{{ firstLetter }}</span>
2287
+ </div>
2288
+ <h1 class="text-2xl font-bold text-white">{{ projectName }}</h1>
2289
+ <p class="text-blue-100 mt-2">{{ 'auth.layout.welcome' | translate }}</p>
2290
+ </div>
2291
+
2292
+ <!-- Auth Form Container -->
2293
+ <div class="bg-white/95 backdrop-blur-sm rounded-2xl shadow-xl p-8">
2294
+ <router-outlet />
2295
+ </div>
2296
+
2297
+ <!-- Footer -->
2298
+ <div class="text-center mt-8 text-blue-100 text-sm">
2299
+ <p>{{ 'auth.layout.copyright' | translate:{ year: currentYear, name: projectName } }}</p>
2300
+ </div>
2301
+ </div>
2302
+ </div>
2303
+ \`
2304
+ })
2305
+ export class AuthLayoutComponent {
2306
+ protected readonly projectName = '${config.projectName}';
2307
+ protected readonly firstLetter = '${config.projectName}'.charAt(0).toUpperCase();
2308
+ protected readonly currentYear = new Date().getFullYear();
2309
+ }`;
2310
+
2311
+ await fs.writeFile(
2312
+ path.join(config.fullPath, 'src/app/layout/auth/auth-layout.component.ts'),
2313
+ authLayoutComponent
2314
+ );
2315
+
2316
+ // Login Component
2317
+ const loginComponentTs = `import { Component, inject } from '@angular/core';
2318
+ import { CommonModule } from '@angular/common';
2319
+ import { ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms';
2320
+ import { Router, RouterModule } from '@angular/router';
2321
+ import { TranslateModule } from '@ngx-translate/core';
2322
+ import { ButtonComponent } from '@shared/components/button/button.component';
2323
+
2324
+ @Component({
2325
+ selector: 'app-login',
2326
+ standalone: true,
2327
+ imports: [CommonModule, ReactiveFormsModule, RouterModule, TranslateModule, ButtonComponent],
2328
+ templateUrl: './login.component.html'
2329
+ })
2330
+ export class LoginComponent {
2331
+ private fb = inject(FormBuilder);
2332
+ private router = inject(Router);
2333
+
2334
+ showPassword = false;
2335
+ isLoading = false;
2336
+ errorMessage = '';
2337
+
2338
+ loginForm = this.fb.group({
2339
+ email: ['', [Validators.required, Validators.email]],
2340
+ password: ['', [Validators.required, Validators.minLength(6)]],
2341
+ rememberMe: [false]
2342
+ });
2343
+
2344
+ togglePassword(): void {
2345
+ this.showPassword = !this.showPassword;
2346
+ }
2347
+
2348
+ onSubmit(): void {
2349
+ if (this.loginForm.valid) {
2350
+ this.isLoading = true;
2351
+ this.errorMessage = '';
2352
+
2353
+ // Simulate login API call
2354
+ setTimeout(() => {
2355
+ this.isLoading = false;
2356
+
2357
+ // Mock login logic - replace with actual authentication service
2358
+ const { email, password } = this.loginForm.value;
2359
+
2360
+ if (email === 'demo@example.com' && password === 'password') {
2361
+ // Store token (mock)
2362
+ localStorage.setItem('auth_token', 'mock_token_12345');
2363
+ this.router.navigate(['/']);
2364
+ } else {
2365
+ this.errorMessage = 'Invalid email or password. Try demo@example.com / password';
2366
+ }
2367
+ }, 1500);
2368
+ } else {
2369
+ // Mark all fields as touched to show validation errors
2370
+ Object.keys(this.loginForm.controls).forEach(key => {
2371
+ this.loginForm.get(key)?.markAsTouched();
2372
+ });
2373
+ }
2374
+ }
2375
+ }`;
2376
+
2377
+ const loginComponentHtml = `<div class="space-y-6">
2378
+ <div class="text-center">
2379
+ <h2 class="text-2xl font-bold text-gray-900">{{ 'auth.login.title' | translate }}</h2>
2380
+ <p class="text-gray-600 mt-2">{{ 'auth.login.subtitle' | translate }}</p>
2381
+ </div>
2382
+
2383
+ <form [formGroup]="loginForm" (ngSubmit)="onSubmit()" class="space-y-4">
2384
+
2385
+ <!-- Email Field -->
2386
+ <div>
2387
+ <label for="email" class="block text-sm font-medium text-gray-700 mb-2">
2388
+ {{ 'auth.login.email' | translate }}
2389
+ </label>
2390
+ <input
2391
+ id="email"
2392
+ type="email"
2393
+ formControlName="email"
2394
+ [placeholder]="'auth.login.email' | translate"
2395
+ class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors"
2396
+ [class.border-red-500]="loginForm.get('email')?.invalid && loginForm.get('email')?.touched">
2397
+ @if (loginForm.get('email')?.invalid && loginForm.get('email')?.touched) {
2398
+ <div class="mt-1 text-sm text-red-600">
2399
+ @if (loginForm.get('email')?.errors?.['required']) {
2400
+ <span>{{ 'auth.validation.emailRequired' | translate }}</span>
2401
+ }
2402
+ @if (loginForm.get('email')?.errors?.['email']) {
2403
+ <span>{{ 'auth.validation.emailInvalid' | translate }}</span>
2404
+ }
2405
+ </div>
2406
+ }
2407
+ </div>
2408
+
2409
+ <!-- Password Field -->
2410
+ <div>
2411
+ <label for="password" class="block text-sm font-medium text-gray-700 mb-2">
2412
+ {{ 'auth.login.password' | translate }}
2413
+ </label>
2414
+ <div class="relative">
2415
+ <input
2416
+ id="password"
2417
+ [type]="showPassword ? 'text' : 'password'"
2418
+ formControlName="password"
2419
+ [placeholder]="'auth.login.password' | translate"
2420
+ class="w-full px-4 py-3 pr-12 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors"
2421
+ [class.border-red-500]="loginForm.get('password')?.invalid && loginForm.get('password')?.touched">
2422
+ <button
2423
+ type="button"
2424
+ (click)="togglePassword()"
2425
+ [attr.aria-label]="showPassword ? ('auth.login.hidePassword' | translate) : ('auth.login.showPassword' | translate)"
2426
+ class="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-500 hover:text-gray-700">
2427
+ @if (!showPassword) {
2428
+ <svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
2429
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
2430
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path>
2431
+ </svg>
2432
+ }
2433
+ @if (showPassword) {
2434
+ <svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
2435
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.878 9.878L3 3m6.878 6.878L21 21"></path>
2436
+ </svg>
2437
+ }
2438
+ </button>
2439
+ </div>
2440
+ @if (loginForm.get('password')?.invalid && loginForm.get('password')?.touched) {
2441
+ <div class="mt-1 text-sm text-red-600">
2442
+ @if (loginForm.get('password')?.errors?.['required']) {
2443
+ <span>{{ 'auth.validation.passwordRequired' | translate }}</span>
2444
+ }
2445
+ @if (loginForm.get('password')?.errors?.['minlength']) {
2446
+ <span>{{ 'auth.validation.passwordMinLength' | translate }}</span>
2447
+ }
2448
+ </div>
2449
+ }
2450
+ </div>
2451
+
2452
+ <!-- Remember Me & Forgot Password -->
2453
+ <div class="flex items-center justify-between">
2454
+ <label class="flex items-center">
2455
+ <input
2456
+ type="checkbox"
2457
+ formControlName="rememberMe"
2458
+ class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
2459
+ <span class="ml-2 rtl:mr-2 text-sm text-gray-600">{{ 'auth.login.rememberMe' | translate }}</span>
2460
+ </label>
2461
+ <a routerLink="/auth/forgot-password" class="text-sm text-blue-600 hover:text-blue-500">
2462
+ {{ 'auth.login.forgotPassword' | translate }}
2463
+ </a>
2464
+ </div>
2465
+
2466
+ <!-- Submit Button -->
2467
+ <app-button
2468
+ type="submit"
2469
+ [fullWidth]="true"
2470
+ [loading]="isLoading"
2471
+ [disabled]="loginForm.invalid">
2472
+ {{ (isLoading ? 'auth.login.signing' : 'auth.login.submit') | translate }}
2473
+ </app-button>
2474
+
2475
+ <!-- Error Message -->
2476
+ @if (errorMessage) {
2477
+ <div class="p-3 bg-red-100 border border-red-300 text-red-700 rounded-lg text-sm">
2478
+ {{ errorMessage }}
2479
+ </div>
2480
+ }
2481
+ </form>
2482
+
2483
+ <!-- Sign Up Link -->
2484
+ <div class="text-center pt-4 border-t border-gray-200">
2485
+ <p class="text-sm text-gray-600">
2486
+ {{ 'auth.login.noAccount' | translate }}
2487
+ <a routerLink="/auth/register" class="text-blue-600 hover:text-blue-500 font-medium">
2488
+ {{ 'auth.login.createAccount' | translate }}
2489
+ </a>
2490
+ </p>
2491
+ </div>
2492
+ </div>
2493
+ `;
2494
+
2495
+ await fs.writeFile(
2496
+ path.join(config.fullPath, 'src/app/features/auth/login/login.component.ts'),
2497
+ loginComponentTs
2498
+ );
2499
+
2500
+ await fs.writeFile(
2501
+ path.join(config.fullPath, 'src/app/features/auth/login/login.component.html'),
2502
+ loginComponentHtml
2503
+ );
2504
+
2505
+ // Register Component
2506
+ const registerComponentTs = `import { Component, inject } from '@angular/core';
2507
+ import { CommonModule } from '@angular/common';
2508
+ import { ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms';
2509
+ import { RouterModule } from '@angular/router';
2510
+ import { TranslateModule } from '@ngx-translate/core';
2511
+ import { ButtonComponent } from '@shared/components/button/button.component';
2512
+
2513
+ @Component({
2514
+ selector: 'app-register',
2515
+ standalone: true,
2516
+ imports: [CommonModule, ReactiveFormsModule, RouterModule, TranslateModule, ButtonComponent],
2517
+ templateUrl: './register.component.html'
2518
+ })
2519
+ export class RegisterComponent {
2520
+ private fb = inject(FormBuilder);
2521
+ registerForm = this.fb.group({
2522
+ firstName: ['', Validators.required],
2523
+ lastName: ['', Validators.required],
2524
+ email: ['', [Validators.required, Validators.email]],
2525
+ password: ['', [Validators.required, Validators.minLength(6)]]
2526
+ });
2527
+ onSubmit() { /* Implementation */ }
2528
+ }`;
2529
+
2530
+ const registerComponentHtml = `<div class="space-y-6">
2531
+ <div class="text-center">
2532
+ <h2 class="text-2xl font-bold text-gray-900">{{ 'auth.register.title' | translate }}</h2>
2533
+ <p class="text-gray-600 mt-2">{{ 'auth.register.subtitle' | translate }}</p>
2534
+ </div>
2535
+ <form [formGroup]="registerForm" (ngSubmit)="onSubmit()" class="space-y-4">
2536
+ <div class="grid grid-cols-2 gap-4">
2537
+ <input
2538
+ type="text"
2539
+ formControlName="firstName"
2540
+ [placeholder]="'auth.register.firstName' | translate"
2541
+ class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent">
2542
+ <input
2543
+ type="text"
2544
+ formControlName="lastName"
2545
+ [placeholder]="'auth.register.lastName' | translate"
2546
+ class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent">
2547
+ </div>
2548
+ <input
2549
+ type="email"
2550
+ formControlName="email"
2551
+ [placeholder]="'auth.register.email' | translate"
2552
+ class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent">
2553
+ <input
2554
+ type="password"
2555
+ formControlName="password"
2556
+ [placeholder]="'auth.register.password' | translate"
2557
+ class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent">
2558
+ <app-button type="submit" [fullWidth]="true" [disabled]="registerForm.invalid">
2559
+ {{ 'auth.register.submit' | translate }}
2560
+ </app-button>
2561
+ <div class="text-center">
2562
+ <a routerLink="/auth/login" class="text-blue-600 hover:text-blue-500">
2563
+ {{ 'auth.register.hasAccount' | translate }}
2564
+ </a>
2565
+ </div>
2566
+ </form>
2567
+ </div>
2568
+ `;
2569
+
2570
+ await fs.writeFile(
2571
+ path.join(config.fullPath, 'src/app/features/auth/register/register.component.ts'),
2572
+ registerComponentTs
2573
+ );
2574
+
2575
+ await fs.writeFile(
2576
+ path.join(config.fullPath, 'src/app/features/auth/register/register.component.html'),
2577
+ registerComponentHtml
2578
+ );
2579
+
2580
+ // Forgot Password Component
2581
+ const forgotPasswordComponentTs = `import { Component, inject } from '@angular/core';
2582
+ import { CommonModule } from '@angular/common';
2583
+ import { ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms';
2584
+ import { RouterModule } from '@angular/router';
2585
+ import { TranslateModule } from '@ngx-translate/core';
2586
+ import { ButtonComponent } from '@shared/components/button/button.component';
2587
+
2588
+ @Component({
2589
+ selector: 'app-forgot-password',
2590
+ standalone: true,
2591
+ imports: [CommonModule, ReactiveFormsModule, RouterModule, TranslateModule, ButtonComponent],
2592
+ templateUrl: './forgot-password.component.html'
2593
+ })
2594
+ export class ForgotPasswordComponent {
2595
+ private fb = inject(FormBuilder);
2596
+ forgotForm = this.fb.group({ email: ['', [Validators.required, Validators.email]] });
2597
+ onSubmit() { /* Implementation */ }
2598
+ }`;
2599
+
2600
+ const forgotPasswordComponentHtml = `<div class="space-y-6">
2601
+ <div class="text-center">
2602
+ <h2 class="text-2xl font-bold text-gray-900">{{ 'auth.forgot.title' | translate }}</h2>
2603
+ <p class="text-gray-600 mt-2">{{ 'auth.forgot.subtitle' | translate }}</p>
2604
+ </div>
2605
+ <form [formGroup]="forgotForm" (ngSubmit)="onSubmit()" class="space-y-4">
2606
+ <input
2607
+ type="email"
2608
+ formControlName="email"
2609
+ [placeholder]="'auth.forgot.email' | translate"
2610
+ class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent">
2611
+ <app-button type="submit" [fullWidth]="true" [disabled]="forgotForm.invalid">
2612
+ {{ 'auth.forgot.submit' | translate }}
2613
+ </app-button>
2614
+ <div class="text-center">
2615
+ <a routerLink="/auth/login" class="text-blue-600 hover:text-blue-500">
2616
+ {{ 'auth.forgot.backToLogin' | translate }}
2617
+ </a>
2618
+ </div>
2619
+ </form>
2620
+ </div>
2621
+ `;
2622
+
2623
+ await fs.writeFile(
2624
+ path.join(
2625
+ config.fullPath,
2626
+ 'src/app/features/auth/forgot-password/forgot-password.component.ts'
2627
+ ),
2628
+ forgotPasswordComponentTs
2629
+ );
2630
+
2631
+ await fs.writeFile(
2632
+ path.join(
2633
+ config.fullPath,
2634
+ 'src/app/features/auth/forgot-password/forgot-password.component.html'
2635
+ ),
2636
+ forgotPasswordComponentHtml
2637
+ );
2638
+ },
2639
+
2640
+ async createFeatures(config) {
2641
+ await this.createHomeComponent(config);
2642
+ await this.createAboutComponent(config);
2643
+ await createContactComponent(config);
2644
+ },
2645
+
2646
+ async createRouting(config) {
2647
+ await createRouting(config);
2648
+ },
2649
+
2650
+ async createAppConfig(config) {
2651
+ await createAppConfig(config);
2652
+ },
2653
+
2654
+ async createAppComponent(config) {
2655
+ await createAppComponent(config);
2656
+ },
2657
+
2658
+ async createStyles(config) {
2659
+ await createStyles(config);
2660
+ },
2661
+
2662
+ async createAssets(config) {
2663
+ // Create a simple logo placeholder
2664
+ const logoSvg = `<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
2665
+ <rect width="100" height="100" fill="#3B82F6" rx="20"/>
2666
+ <text x="50" y="60" text-anchor="middle" fill="white" font-size="24" font-family="Arial, sans-serif" font-weight="bold">
2667
+ ${config.projectName.charAt(0).toUpperCase()}
2668
+ </text>
2669
+ </svg>`;
2670
+
2671
+ await fs.writeFile(path.join(config.fullPath, 'public/assets/images/logo.svg'), logoSvg);
2672
+ },
2673
+
2674
+ async createVSCodeSettings(config) {
2675
+ // Create .vscode directory
2676
+ await fs.ensureDir(path.join(config.fullPath, '.vscode'));
2677
+
2678
+ // VSCode settings for format on save
2679
+ const vscodeSettings = {
2680
+ 'editor.formatOnSave': true,
2681
+ 'editor.defaultFormatter': 'esbenp.prettier-vscode',
2682
+ 'editor.codeActionsOnSave': {
2683
+ 'source.fixAll.eslint': 'explicit',
2684
+ },
2685
+ '[typescript]': {
2686
+ 'editor.formatOnSave': true,
2687
+ 'editor.defaultFormatter': 'esbenp.prettier-vscode',
2688
+ },
2689
+ '[html]': {
2690
+ 'editor.formatOnSave': true,
2691
+ 'editor.defaultFormatter': 'esbenp.prettier-vscode',
2692
+ },
2693
+ '[css]': {
2694
+ 'editor.formatOnSave': true,
2695
+ 'editor.defaultFormatter': 'esbenp.prettier-vscode',
2696
+ },
2697
+ '[json]': {
2698
+ 'editor.formatOnSave': true,
2699
+ 'editor.defaultFormatter': 'esbenp.prettier-vscode',
2700
+ },
2701
+ '[jsonc]': {
2702
+ 'editor.formatOnSave': true,
2703
+ 'editor.defaultFormatter': 'esbenp.prettier-vscode',
2704
+ },
2705
+ 'files.eol': '\n',
2706
+ 'files.trimTrailingWhitespace': true,
2707
+ 'files.insertFinalNewline': true,
2708
+ 'editor.tabSize': 2,
2709
+ 'prettier.requireConfig': false,
2710
+ 'eslint.validate': ['javascript', 'typescript', 'html'],
2711
+ };
2712
+
2713
+ // VSCode extensions recommendations
2714
+ const vscodeExtensions = {
2715
+ recommendations: [
2716
+ 'esbenp.prettier-vscode',
2717
+ 'dbaeumer.vscode-eslint',
2718
+ 'angular.ng-template',
2719
+ 'bradlc.vscode-tailwindcss',
2720
+ ],
2721
+ };
2722
+
2723
+ // Write settings.json
2724
+ await fs.writeFile(
2725
+ path.join(config.fullPath, '.vscode/settings.json'),
2726
+ JSON.stringify(vscodeSettings, null, 2)
2727
+ );
2728
+
2729
+ // Write extensions.json
2730
+ await fs.writeFile(
2731
+ path.join(config.fullPath, '.vscode/extensions.json'),
2732
+ JSON.stringify(vscodeExtensions, null, 2)
2733
+ );
2734
+ },
2735
+
2736
+ async createHomeComponent(config) {
2737
+ const homeComponentTs = `import { Component, inject } from '@angular/core';
2738
+ import { CommonModule } from '@angular/common';
2739
+ import { RouterModule } from '@angular/router';
2740
+ import { NgIconComponent, provideIcons } from '@ng-icons/core';
2741
+ import {
2742
+ heroRocketLaunch,
2743
+ heroPaintBrush,
2744
+ heroShieldCheck,
2745
+ heroBolt,
2746
+ heroCodeBracket,
2747
+ heroUsers,
2748
+ heroGlobeAlt,
2749
+ heroCog,
2750
+ heroCheckCircle,
2751
+ heroServer,
2752
+ heroFolder,
2753
+ heroCube
2754
+ } from '@ng-icons/heroicons/outline';
2755
+ import { TranslateModule } from '@ngx-translate/core';
2756
+ import { ButtonComponent } from '@shared/components/button/button.component';
2757
+ import { CardComponent } from '@shared/components/card/card.component';
2758
+ import { ToastService } from '@core/services/toast.service';
2759
+ import { ModalService } from '@core/services/modal.service';
2760
+
2761
+ @Component({
2762
+ selector: 'app-home',
2763
+ standalone: true,
2764
+ imports: [CommonModule, RouterModule, NgIconComponent, TranslateModule, ButtonComponent, CardComponent],
2765
+ viewProviders: [provideIcons({
2766
+ heroRocketLaunch,
2767
+ heroPaintBrush,
2768
+ heroShieldCheck,
2769
+ heroBolt,
2770
+ heroCodeBracket,
2771
+ heroUsers,
2772
+ heroGlobeAlt,
2773
+ heroCog,
2774
+ heroCheckCircle,
2775
+ heroServer,
2776
+ heroFolder,
2777
+ heroCube
2778
+ })],
2779
+ templateUrl: './home.component.html'
2780
+ })
2781
+ export class HomeComponent {
2782
+ protected readonly projectName = '${config.projectName}';
2783
+
2784
+ private toast = inject(ToastService);
2785
+ private modal = inject(ModalService);
2786
+
2787
+ // Toast Examples
2788
+ showSuccessToast(): void {
2789
+ this.toast.success('Operation completed successfully!');
2790
+ }
2791
+
2792
+ showErrorToast(): void {
2793
+ this.toast.error('Something went wrong. Please try again.');
2794
+ }
2795
+
2796
+ showWarningToast(): void {
2797
+ this.toast.warning('This action requires your attention.');
2798
+ }
2799
+
2800
+ showInfoToast(): void {
2801
+ this.toast.info('Here is some useful information.');
2802
+ }
2803
+
2804
+ // Modal Examples
2805
+ async showConfirmModal(): Promise<void> {
2806
+ const confirmed = await this.modal.confirm(
2807
+ 'Are you sure you want to proceed with this action?',
2808
+ 'Confirm Action'
2809
+ );
2810
+
2811
+ if (confirmed) {
2812
+ this.toast.success('Action confirmed!');
2813
+ } else {
2814
+ this.toast.info('Action cancelled');
2815
+ }
2816
+ }
2817
+
2818
+ async showAlertModal(): Promise<void> {
2819
+ await this.modal.alert(
2820
+ 'This is an alert message. Click OK to close.',
2821
+ 'Alert'
2822
+ );
2823
+ this.toast.info('Alert closed');
2824
+ }
2825
+ }`;
2826
+
2827
+ const homeComponentHtml = `<!-- Hero Section -->
2828
+ <section class="relative py-20 bg-linear-to-br from-blue-600 via-indigo-600 to-purple-700 overflow-hidden">
2829
+ <!-- Animated Background Elements -->
2830
+ <div class="absolute inset-0 opacity-20">
2831
+ <div class="absolute top-0 -left-4 w-72 h-72 bg-purple-300 rounded-full mix-blend-multiply filter blur-xl animate-blob"></div>
2832
+ <div class="absolute top-0 -right-4 w-72 h-72 bg-yellow-300 rounded-full mix-blend-multiply filter blur-xl animate-blob animation-delay-2000"></div>
2833
+ <div class="absolute -bottom-8 left-20 w-72 h-72 bg-pink-300 rounded-full mix-blend-multiply filter blur-xl animate-blob animation-delay-4000"></div>
2834
+ </div>
2835
+
2836
+ <div class="relative container mx-auto px-4 text-center">
2837
+ <div class="animate-fade-in">
2838
+ <h1 class="text-4xl md:text-6xl lg:text-7xl font-bold text-white mb-6 drop-shadow-lg">
2839
+ {{ 'home.hero.title' | translate:{ projectName: projectName } }}
2840
+ </h1>
2841
+ <p class="text-lg md:text-xl lg:text-2xl text-blue-50 mb-8 max-w-3xl mx-auto leading-relaxed">
2842
+ {{ 'home.hero.subtitle' | translate }}
2843
+ </p>
2844
+ <div class="flex flex-col sm:flex-row gap-4 justify-center animate-slide-up">
2845
+ <app-button size="lg" class="transform hover:scale-105 transition-transform duration-200">
2846
+ <span class="flex items-center gap-2">
2847
+ <ng-icon name="heroRocketLaunch" size="20"></ng-icon>
2848
+ {{ 'home.hero.button' | translate }}
2849
+ </span>
2850
+ </app-button>
2851
+ <app-button size="lg" variant="secondary" [routerLink]="['/about']" class="transform hover:scale-105 transition-transform duration-200">
2852
+ <span class="flex items-center gap-2">
2853
+ {{ 'home.hero.learnMore' | translate }}
2854
+ <ng-icon name="heroShieldCheck" size="20"></ng-icon>
2855
+ </span>
2856
+ </app-button>
2857
+ </div>
2858
+ </div>
2859
+ </div>
2860
+ </section>
2861
+
2862
+ <!-- What's Included Section -->
2863
+ <section class="py-20 bg-white">
2864
+ <div class="container mx-auto px-4">
2865
+ <div class="text-center mb-16">
2866
+ <h2 class="text-3xl md:text-4xl font-bold text-gray-900 mb-4">
2867
+ {{ 'home.productionReady.sectionTitle' | translate }}
2868
+ </h2>
2869
+ <p class="text-xl text-gray-600 max-w-3xl mx-auto">
2870
+ {{ 'home.productionReady.title' | translate }}
2871
+ </p>
2872
+ </div>
2873
+
2874
+ <!-- Core Features Grid -->
2875
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-12">
2876
+ <!-- Modern Architecture -->
2877
+ <app-card [hover]="true" [shadow]="true">
2878
+ <div class="flex items-start space-x-4">
2879
+ <div class="shrink-0">
2880
+ <div class="w-12 h-12 rounded-lg bg-blue-100 flex items-center justify-center">
2881
+ <ng-icon name="heroRocketLaunch" size="24" style="color: #2563eb;"></ng-icon>
2882
+ </div>
2883
+ </div>
2884
+ <div>
2885
+ <h3 class="text-lg font-semibold text-gray-900 mb-2">Modern Architecture</h3>
2886
+ <p class="text-sm text-gray-600">Standalone components, Signals, Zoneless support, Angular 20+</p>
2887
+ </div>
2888
+ </div>
2889
+ </app-card>
2890
+
2891
+ <!-- i18n System -->
2892
+ <app-card [hover]="true" [shadow]="true">
2893
+ <div class="flex items-start space-x-4">
2894
+ <div class="shrink-0">
2895
+ <div class="w-12 h-12 rounded-lg bg-purple-100 flex items-center justify-center">
2896
+ <ng-icon name="heroGlobeAlt" size="24" style="color: #9333ea;"></ng-icon>
2897
+ </div>
2898
+ </div>
2899
+ <div>
2900
+ <h3 class="text-lg font-semibold text-gray-900 mb-2">i18n Translation</h3>
2901
+ <p class="text-sm text-gray-600">English & Arabic with RTL support, language switcher in header</p>
2902
+ </div>
2903
+ </div>
2904
+ </app-card>
2905
+
2906
+ <!-- HTTP Interceptors -->
2907
+ <app-card [hover]="true" [shadow]="true">
2908
+ <div class="flex items-start space-x-4">
2909
+ <div class="shrink-0">
2910
+ <div class="w-12 h-12 rounded-lg bg-green-100 flex items-center justify-center">
2911
+ <ng-icon name="heroServer" size="24" style="color: #16a34a;"></ng-icon>
2912
+ </div>
2913
+ </div>
2914
+ <div>
2915
+ <h3 class="text-lg font-semibold text-gray-900 mb-2">HTTP Interceptors</h3>
2916
+ <p class="text-sm text-gray-600">Auth, Error handling, Loading state, Response caching (5min TTL)</p>
2917
+ </div>
2918
+ </div>
2919
+ </app-card>
2920
+
2921
+ <!-- Tailwind CSS -->
2922
+ <app-card [hover]="true" [shadow]="true">
2923
+ <div class="flex items-start space-x-4">
2924
+ <div class="shrink-0">
2925
+ <div class="w-12 h-12 rounded-lg bg-cyan-100 flex items-center justify-center">
2926
+ <ng-icon name="heroPaintBrush" size="24" style="color: #0891b2;"></ng-icon>
2927
+ </div>
2928
+ </div>
2929
+ <div>
2930
+ <h3 class="text-lg font-semibold text-gray-900 mb-2">Tailwind CSS v4</h3>
2931
+ <p class="text-sm text-gray-600">Modern PostCSS setup, responsive utilities, dark mode ready</p>
2932
+ </div>
2933
+ </div>
2934
+ </app-card>
2935
+
2936
+ <!-- UI Components -->
2937
+ <app-card [hover]="true" [shadow]="true">
2938
+ <div class="flex items-start space-x-4">
2939
+ <div class="shrink-0">
2940
+ <div class="w-12 h-12 rounded-lg bg-indigo-100 flex items-center justify-center">
2941
+ <ng-icon name="heroCube" size="24" style="color: #4f46e5;"></ng-icon>
2942
+ </div>
2943
+ </div>
2944
+ <div>
2945
+ <h3 class="text-lg font-semibold text-gray-900 mb-2">UI Components</h3>
2946
+ <p class="text-sm text-gray-600">Button, Card, Spinner, Toast, Modal with full customization</p>
2947
+ </div>
2948
+ </div>
2949
+ </app-card>
2950
+
2951
+ <!-- TypeScript -->
2952
+ <app-card [hover]="true" [shadow]="true">
2953
+ <div class="flex items-start space-x-4">
2954
+ <div class="shrink-0">
2955
+ <div class="w-12 h-12 rounded-lg bg-blue-100 flex items-center justify-center">
2956
+ <ng-icon name="heroShieldCheck" size="24" style="color: #2563eb;"></ng-icon>
2957
+ </div>
2958
+ </div>
2959
+ <div>
2960
+ <h3 class="text-lg font-semibold text-gray-900 mb-2">Type-Safe</h3>
2961
+ <p class="text-sm text-gray-600">TypeScript interfaces, models, strongly-typed services</p>
2962
+ </div>
2963
+ </div>
2964
+ </app-card>
2965
+ </div>
2966
+
2967
+ <!-- Features Breakdown -->
2968
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
2969
+ <!-- Core Services -->
2970
+ <app-card [title]="'home.coreServices.title' | translate" [shadow]="true">
2971
+ <ul class="space-y-2 text-gray-700">
2972
+ <li class="flex items-start">
2973
+ <ng-icon name="heroCheckCircle" size="20" class="mr-2 mt-0.5" style="color: #22c55e;"></ng-icon>
2974
+ <span>{{ 'home.coreServices.authService' | translate }}</span>
2975
+ </li>
2976
+ <li class="flex items-start">
2977
+ <ng-icon name="heroCheckCircle" size="20" class="mr-2 mt-0.5" style="color: #22c55e;"></ng-icon>
2978
+ <span>{{ 'home.coreServices.apiService' | translate }}</span>
2979
+ </li>
2980
+ <li class="flex items-start">
2981
+ <ng-icon name="heroCheckCircle" size="20" class="mr-2 mt-0.5" style="color: #22c55e;"></ng-icon>
2982
+ <span>{{ 'home.coreServices.toastService' | translate }}</span>
2983
+ </li>
2984
+ <li class="flex items-start">
2985
+ <ng-icon name="heroCheckCircle" size="20" class="mr-2 mt-0.5" style="color: #22c55e;"></ng-icon>
2986
+ <span>{{ 'home.coreServices.modalService' | translate }}</span>
2987
+ </li>
2988
+ <li class="flex items-start">
2989
+ <ng-icon name="heroCheckCircle" size="20" class="mr-2 mt-0.5" style="color: #22c55e;"></ng-icon>
2990
+ <span>{{ 'home.coreServices.loadingService' | translate }}</span>
2991
+ </li>
2992
+ <li class="flex items-start">
2993
+ <ng-icon name="heroCheckCircle" size="20" class="mr-2 mt-0.5" style="color: #22c55e;"></ng-icon>
2994
+ <span>{{ 'home.coreServices.cacheService' | translate }}</span>
2995
+ </li>
2996
+ <li class="flex items-start">
2997
+ <ng-icon name="heroCheckCircle" size="20" class="mr-2 mt-0.5" style="color: #22c55e;"></ng-icon>
2998
+ <span>{{ 'home.coreServices.storageService' | translate }}</span>
2999
+ </li>
3000
+ <li class="flex items-start">
3001
+ <ng-icon name="heroCheckCircle" size="20" class="mr-2 mt-0.5" style="color: #22c55e;"></ng-icon>
3002
+ <span>{{ 'home.coreServices.i18nService' | translate }}</span>
3003
+ </li>
3004
+ </ul>
3005
+ </app-card>
3006
+
3007
+ <!-- Shared Components & Utils -->
3008
+ <app-card [title]="'home.sharedComponents.title' | translate" [shadow]="true">
3009
+ <ul class="space-y-2 text-gray-700">
3010
+ <li class="flex items-start">
3011
+ <ng-icon name="heroCheckCircle" size="20" class="mr-2 mt-0.5" style="color: #3b82f6;"></ng-icon>
3012
+ <span>{{ 'home.sharedComponents.button' | translate }}</span>
3013
+ </li>
3014
+ <li class="flex items-start">
3015
+ <ng-icon name="heroCheckCircle" size="20" class="mr-2 mt-0.5" style="color: #3b82f6;"></ng-icon>
3016
+ <span>{{ 'home.sharedComponents.card' | translate }}</span>
3017
+ </li>
3018
+ <li class="flex items-start">
3019
+ <ng-icon name="heroCheckCircle" size="20" class="mr-2 mt-0.5" style="color: #3b82f6;"></ng-icon>
3020
+ <span>{{ 'home.sharedComponents.spinner' | translate }}</span>
3021
+ </li>
3022
+ <li class="flex items-start">
3023
+ <ng-icon name="heroCheckCircle" size="20" class="mr-2 mt-0.5" style="color: #3b82f6;"></ng-icon>
3024
+ <span>{{ 'home.sharedComponents.toast' | translate }}</span>
3025
+ </li>
3026
+ <li class="flex items-start">
3027
+ <ng-icon name="heroCheckCircle" size="20" class="mr-2 mt-0.5" style="color: #3b82f6;"></ng-icon>
3028
+ <span>{{ 'home.sharedComponents.modal' | translate }}</span>
3029
+ </li>
3030
+ <li class="flex items-start">
3031
+ <ng-icon name="heroCheckCircle" size="20" class="mr-2 mt-0.5" style="color: #3b82f6;"></ng-icon>
3032
+ <span>{{ 'home.sharedComponents.pipes' | translate }}</span>
3033
+ </li>
3034
+ <li class="flex items-start">
3035
+ <ng-icon name="heroCheckCircle" size="20" class="mr-2 mt-0.5" style="color: #3b82f6;"></ng-icon>
3036
+ <span>{{ 'home.sharedComponents.directives' | translate }}</span>
3037
+ </li>
3038
+ </ul>
3039
+ </app-card>
3040
+ </div>
3041
+ </div>
3042
+ </section>
3043
+
3044
+ <!-- Package & Dependencies Section -->
3045
+ <section class="py-20 bg-gray-50">
3046
+ <div class="container mx-auto px-4">
3047
+ <div class="text-center mb-12">
3048
+ <h2 class="text-3xl md:text-4xl font-bold text-gray-900 mb-4">
3049
+ {{ 'home.preConfigured.title' | translate }}
3050
+ </h2>
3051
+ <p class="text-xl text-gray-600 max-w-2xl mx-auto">
3052
+ {{ 'home.preConfigured.subtitle' | translate }}
3053
+ </p>
3054
+ </div>
3055
+
3056
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-8 max-w-6xl mx-auto">
3057
+ <!-- Core Dependencies -->
3058
+ <app-card [title]="'home.preConfigured.coreDependencies' | translate" [shadow]="true">
3059
+ <div class="space-y-3 text-sm">
3060
+ <div class="flex justify-between items-center p-2 bg-gray-50 rounded">
3061
+ <span class="font-mono text-gray-700">&#64;angular/core</span>
3062
+ <span class="text-gray-500">{{ 'home.preConfigured.latest' | translate }}</span>
3063
+ </div>
3064
+ <div class="flex justify-between items-center p-2 bg-gray-50 rounded">
3065
+ <span class="font-mono text-gray-700">tailwindcss</span>
3066
+ <span class="text-gray-500">^4.0.0</span>
3067
+ </div>
3068
+ <div class="flex justify-between items-center p-2 bg-gray-50 rounded">
3069
+ <span class="font-mono text-gray-700">&#64;tailwindcss/postcss</span>
3070
+ <span class="text-gray-500">{{ 'home.preConfigured.latest' | translate }}</span>
3071
+ </div>
3072
+ <div class="flex justify-between items-center p-2 bg-gray-50 rounded">
3073
+ <span class="font-mono text-gray-700">&#64;ngx-translate/core</span>
3074
+ <span class="text-gray-500">{{ 'home.preConfigured.i18n' | translate }}</span>
3075
+ </div>
3076
+ <div class="flex justify-between items-center p-2 bg-gray-50 rounded">
3077
+ <span class="font-mono text-gray-700">&#64;ng-icons/heroicons</span>
3078
+ <span class="text-gray-500">{{ 'home.preConfigured.iconLibrary' | translate }}</span>
3079
+ </div>
3080
+ </div>
3081
+ </app-card>
3082
+
3083
+ <!-- Dev Tools -->
3084
+ <app-card [title]="'home.preConfigured.developmentTools' | translate" [shadow]="true">
3085
+ <div class="space-y-3 text-sm">
3086
+ <div class="flex justify-between items-center p-2 bg-gray-50 rounded">
3087
+ <span class="font-mono text-gray-700">ESLint</span>
3088
+ <span class="text-gray-500">{{ 'home.preConfigured.linting' | translate }}</span>
3089
+ </div>
3090
+ <div class="flex justify-between items-center p-2 bg-gray-50 rounded">
3091
+ <span class="font-mono text-gray-700">Prettier</span>
3092
+ <span class="text-gray-500">{{ 'home.preConfigured.formatting' | translate }}</span>
3093
+ </div>
3094
+ <div class="flex justify-between items-center p-2 bg-gray-50 rounded">
3095
+ <span class="font-mono text-gray-700">Husky</span>
3096
+ <span class="text-gray-500">{{ 'home.preConfigured.gitHooks' | translate }}</span>
3097
+ </div>
3098
+ <div class="flex justify-between items-center p-2 bg-gray-50 rounded">
3099
+ <span class="font-mono text-gray-700">TypeScript</span>
3100
+ <span class="text-gray-500">{{ 'home.preConfigured.typeSafety' | translate }}</span>
3101
+ </div>
3102
+ <div class="flex justify-between items-center p-2 bg-gray-50 rounded">
3103
+ <span class="font-mono text-gray-700">PWA Config</span>
3104
+ <span class="text-gray-500">{{ 'home.preConfigured.serviceWorker' | translate }}</span>
3105
+ </div>
3106
+ </div>
3107
+ </app-card>
3108
+ </div>
3109
+
3110
+ <!-- Path Aliases -->
3111
+ <div class="mt-8 max-w-4xl mx-auto">
3112
+ <app-card [title]="'home.pathAliases.title' | translate" [shadow]="true">
3113
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
3114
+ <div class="bg-gray-50 p-3 rounded font-mono">
3115
+ <span class="text-purple-600">{{ 'home.pathAliases.core' | translate }}</span>
3116
+ </div>
3117
+ <div class="bg-gray-50 p-3 rounded font-mono">
3118
+ <span class="text-purple-600">{{ 'home.pathAliases.shared' | translate }}</span>
3119
+ </div>
3120
+ <div class="bg-gray-50 p-3 rounded font-mono">
3121
+ <span class="text-purple-600">{{ 'home.pathAliases.features' | translate }}</span>
3122
+ </div>
3123
+ <div class="bg-gray-50 p-3 rounded font-mono">
3124
+ <span class="text-purple-600">{{ 'home.pathAliases.environments' | translate }}</span>
3125
+ </div>
3126
+ </div>
3127
+ </app-card>
3128
+ </div>
3129
+ </div>
3130
+ </section>
3131
+
3132
+ <!-- Project Structure Section -->
3133
+ <section class="py-20 bg-white">
3134
+ <div class="container mx-auto px-4">
3135
+ <div class="text-center mb-12">
3136
+ <h2 class="text-3xl md:text-4xl font-bold text-gray-900 mb-4">
3137
+ {{ 'home.projectStructure.title' | translate }}
3138
+ </h2>
3139
+ <p class="text-xl text-gray-600 max-w-2xl mx-auto">
3140
+ {{ 'home.projectStructure.subtitle' | translate }}
3141
+ </p>
3142
+ </div>
3143
+
3144
+ <div class="max-w-4xl mx-auto">
3145
+ <app-card [shadow]="true">
3146
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6 text-sm font-mono">
3147
+ <div>
3148
+ <div class="flex items-center mb-2">
3149
+ <ng-icon name="heroFolder" size="18" class="mr-2" style="color: #3b82f6;"></ng-icon>
3150
+ <span class="font-semibold">src/app/core/</span>
3151
+ </div>
3152
+ <ul class="ml-6 space-y-1 text-gray-600">
3153
+ <li>├─ services/ (8 services)</li>
3154
+ <li>├─ guards/ (auth guard)</li>
3155
+ <li>├─ interceptors/ (4 types)</li>
3156
+ <li>└─ i18n/ (translation system)</li>
3157
+ </ul>
3158
+ </div>
3159
+
3160
+ <div>
3161
+ <div class="flex items-center mb-2">
3162
+ <ng-icon name="heroFolder" size="18" class="mr-2" style="color: #a855f7;"></ng-icon>
3163
+ <span class="font-semibold">src/app/shared/</span>
3164
+ </div>
3165
+ <ul class="ml-6 space-y-1 text-gray-600">
3166
+ <li>├─ components/ (5 components)</li>
3167
+ <li>├─ pipes/ (4 pipes)</li>
3168
+ <li>├─ directives/ (2 directives)</li>
3169
+ <li>└─ models/ (TypeScript interfaces)</li>
3170
+ </ul>
3171
+ </div>
3172
+
3173
+ <div>
3174
+ <div class="flex items-center mb-2">
3175
+ <ng-icon name="heroFolder" size="18" class="mr-2" style="color: #22c55e;"></ng-icon>
3176
+ <span class="font-semibold">src/app/features/</span>
3177
+ </div>
3178
+ <ul class="ml-6 space-y-1 text-gray-600">
3179
+ <li>├─ home/</li>
3180
+ <li>├─ about/</li>
3181
+ <li>├─ contact/</li>
3182
+ <li>└─ auth/ (login, register, forgot)</li>
3183
+ </ul>
3184
+ </div>
3185
+
3186
+ <div>
3187
+ <div class="flex items-center mb-2">
3188
+ <ng-icon name="heroFolder" size="18" class="mr-2" style="color: #f97316;"></ng-icon>
3189
+ <span class="font-semibold">src/app/layout/</span>
3190
+ </div>
3191
+ <ul class="ml-6 space-y-1 text-gray-600">
3192
+ <li>├─ main-layout/ (header + footer)</li>
3193
+ <li>└─ auth-layout/ (auth pages)</li>
3194
+ </ul>
3195
+ </div>
3196
+ </div>
3197
+ </app-card>
3198
+ </div>
3199
+ </div>
3200
+ </section>
3201
+
3202
+ <!-- Interactive Examples Section -->
3203
+ <section class="py-20 bg-gray-50">
3204
+ <div class="container mx-auto px-4">
3205
+ <div class="text-center mb-12 animate-fade-in">
3206
+ <div class="inline-flex items-center justify-center w-14 h-14 rounded-xl bg-blue-600 mb-4">
3207
+ <ng-icon name="heroBolt" size="28" style="color: white;"></ng-icon>
3208
+ </div>
3209
+ <h2 class="text-3xl md:text-4xl font-bold text-gray-900 mb-3">
3210
+ {{ 'home.interactiveExamples.title' | translate }}
3211
+ </h2>
3212
+ <p class="text-lg text-gray-600 max-w-2xl mx-auto">
3213
+ {{ 'home.interactiveExamples.subtitle' | translate }}
3214
+ </p>
3215
+ </div>
3216
+
3217
+ <div class="max-w-5xl mx-auto">
3218
+ <div class="bg-white rounded-xl shadow-lg p-8 md:p-10 border border-gray-200">
3219
+ <div class="space-y-10">
3220
+ <!-- Toast Examples -->
3221
+ <div class="animate-slide-up">
3222
+ <div class="flex items-center gap-3 mb-5">
3223
+ <div class="w-10 h-10 rounded-lg bg-gray-100 flex items-center justify-center">
3224
+ <ng-icon name="heroCheckCircle" size="20" style="color: #374151;"></ng-icon>
3225
+ </div>
3226
+ <div>
3227
+ <h3 class="text-xl font-semibold text-gray-900">
3228
+ {{ 'home.interactiveExamples.toastNotifications.title' | translate }}
3229
+ </h3>
3230
+ <p class="text-sm text-gray-500">{{ 'home.interactiveExamples.toastNotifications.subtitle' | translate }}</p>
3231
+ </div>
3232
+ </div>
3233
+ <p class="text-gray-600 mb-5">
3234
+ {{ 'home.interactiveExamples.toastNotifications.description' | translate }}
3235
+ </p>
3236
+ <div class="grid grid-cols-2 md:grid-cols-4 gap-3">
3237
+ <app-button (click)="showSuccessToast()">
3238
+ <span class="flex items-center gap-2">
3239
+ <ng-icon name="heroCheckCircle" size="16"></ng-icon>
3240
+ {{ 'home.interactiveExamples.toastNotifications.success' | translate }}
3241
+ </span>
3242
+ </app-button>
3243
+ <app-button variant="danger" (click)="showErrorToast()">
3244
+ <span class="flex items-center gap-2">
3245
+ <ng-icon name="heroCheckCircle" size="16"></ng-icon>
3246
+ {{ 'home.interactiveExamples.toastNotifications.error' | translate }}
3247
+ </span>
3248
+ </app-button>
3249
+ <app-button variant="secondary" (click)="showWarningToast()">
3250
+ <span class="flex items-center gap-2">
3251
+ <ng-icon name="heroCheckCircle" size="16"></ng-icon>
3252
+ {{ 'home.interactiveExamples.toastNotifications.warning' | translate }}
3253
+ </span>
3254
+ </app-button>
3255
+ <app-button variant="outline" (click)="showInfoToast()">
3256
+ <span class="flex items-center gap-2">
3257
+ <ng-icon name="heroCheckCircle" size="16"></ng-icon>
3258
+ {{ 'home.interactiveExamples.toastNotifications.info' | translate }}
3259
+ </span>
3260
+ </app-button>
3261
+ </div>
3262
+ </div>
3263
+
3264
+ <div class="border-t border-gray-200"></div>
3265
+
3266
+ <!-- Modal Examples -->
3267
+ <div class="animate-slide-up animation-delay-200">
3268
+ <div class="flex items-center gap-3 mb-5">
3269
+ <div class="w-10 h-10 rounded-lg bg-gray-100 flex items-center justify-center">
3270
+ <ng-icon name="heroCube" size="20" style="color: #374151;"></ng-icon>
3271
+ </div>
3272
+ <div>
3273
+ <h3 class="text-xl font-semibold text-gray-900">
3274
+ {{ 'home.interactiveExamples.modalDialogs.title' | translate }}
3275
+ </h3>
3276
+ <p class="text-sm text-gray-500">{{ 'home.interactiveExamples.modalDialogs.subtitle' | translate }}</p>
3277
+ </div>
3278
+ </div>
3279
+ <p class="text-gray-600 mb-5">
3280
+ {{ 'home.interactiveExamples.modalDialogs.description' | translate }}
3281
+ </p>
3282
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-3">
3283
+ <app-button (click)="showConfirmModal()" size="lg">
3284
+ <span class="flex items-center gap-2 justify-center">
3285
+ <ng-icon name="heroShieldCheck" size="18"></ng-icon>
3286
+ {{ 'home.interactiveExamples.modalDialogs.showConfirm' | translate }}
3287
+ </span>
3288
+ </app-button>
3289
+ <app-button variant="secondary" (click)="showAlertModal()" size="lg">
3290
+ <span class="flex items-center gap-2 justify-center">
3291
+ <ng-icon name="heroBolt" size="18"></ng-icon>
3292
+ {{ 'home.interactiveExamples.modalDialogs.showAlert' | translate }}
3293
+ </span>
3294
+ </app-button>
3295
+ </div>
3296
+ </div>
3297
+
3298
+ <!-- Pro Tip -->
3299
+ <div class="bg-blue-50 rounded-lg p-5 border border-blue-100">
3300
+ <div class="flex items-start gap-3">
3301
+ <div class="shrink-0">
3302
+ <div class="w-8 h-8 rounded-lg bg-blue-600 flex items-center justify-center">
3303
+ <ng-icon name="heroBolt" size="16" style="color: white;"></ng-icon>
3304
+ </div>
3305
+ </div>
3306
+ <div>
3307
+ <h4 class="font-semibold text-gray-900 mb-1 text-sm">{{ 'home.interactiveExamples.proTip.title' | translate }}</h4>
3308
+ <p class="text-sm text-gray-600">
3309
+ {{ 'home.interactiveExamples.proTip.description' | translate }}
3310
+ </p>
3311
+ </div>
3312
+ </div>
3313
+ </div>
3314
+ </div>
3315
+ </div>
3316
+ </div>
3317
+ </div>
3318
+ </section>
3319
+
3320
+ <!-- Getting Started Section -->
3321
+ <section class="py-20 bg-white">
3322
+ <div class="container mx-auto px-4 text-center">
3323
+ <h2 class="text-3xl md:text-4xl font-bold text-gray-900 mb-6">
3324
+ {{ 'home.readyToBuild.title' | translate }}
3325
+ </h2>
3326
+ <p class="text-xl text-gray-600 mb-8 max-w-2xl mx-auto">
3327
+ {{ 'home.readyToBuild.subtitle' | translate }}
3328
+ </p>
3329
+ <div class="flex flex-col sm:flex-row gap-4 justify-center">
3330
+ <app-button [routerLink]="['/about']" size="lg">
3331
+ {{ 'home.readyToBuild.learnMore' | translate }}
3332
+ </app-button>
3333
+ <app-button [routerLink]="['/contact']" variant="secondary" size="lg">
3334
+ {{ 'home.readyToStart.contactUs' | translate }}
3335
+ </app-button>
3336
+ </div>
3337
+ </div>
3338
+ </section>
3339
+ `;
3340
+
3341
+ await fs.writeFile(
3342
+ path.join(config.fullPath, 'src/app/features/home/home.component.ts'),
3343
+ homeComponentTs
3344
+ );
3345
+
3346
+ await fs.writeFile(
3347
+ path.join(config.fullPath, 'src/app/features/home/home.component.html'),
3348
+ homeComponentHtml
3349
+ );
3350
+ },
3351
+
3352
+ async createAboutComponent(config) {
3353
+ const aboutComponentTs = `import { Component } from '@angular/core';
3354
+ import { CommonModule } from '@angular/common';
3355
+ import { RouterModule } from '@angular/router';
3356
+ import { NgIconComponent, provideIcons } from '@ng-icons/core';
3357
+ import {
3358
+ heroRocketLaunch,
3359
+ heroPaintBrush,
3360
+ heroShieldCheck,
3361
+ heroBolt,
3362
+ heroCodeBracket,
3363
+ heroCheckCircle,
3364
+ } from '@ng-icons/heroicons/outline';
3365
+ import { TranslateModule } from '@ngx-translate/core';
3366
+ import { ButtonComponent } from '@shared/components/button/button.component';
3367
+
3368
+ @Component({
3369
+ selector: 'app-about',
3370
+ standalone: true,
3371
+ imports: [CommonModule, RouterModule, NgIconComponent, TranslateModule, ButtonComponent],
3372
+ viewProviders: [provideIcons({
3373
+ heroRocketLaunch,
3374
+ heroPaintBrush,
3375
+ heroShieldCheck,
3376
+ heroBolt,
3377
+ heroCodeBracket,
3378
+ heroCheckCircle,
3379
+ })],
3380
+ templateUrl: './about.component.html'
3381
+ })
3382
+ export class AboutComponent {
3383
+ protected readonly projectName = '${config.projectName}';
3384
+ }`;
3385
+
3386
+ const aboutComponentHtml = `<!-- Hero Section -->
3387
+ <section class="relative py-16 bg-linear-to-br from-indigo-600 via-purple-600 to-pink-600 overflow-hidden">
3388
+ <div class="absolute inset-0 opacity-10">
3389
+ <div class="absolute top-0 left-0 w-96 h-96 bg-white rounded-full mix-blend-multiply filter blur-3xl animate-blob"></div>
3390
+ <div class="absolute top-0 right-0 w-96 h-96 bg-yellow-200 rounded-full mix-blend-multiply filter blur-3xl animate-blob animation-delay-2000"></div>
3391
+ <div class="absolute bottom-0 left-1/2 w-96 h-96 bg-pink-200 rounded-full mix-blend-multiply filter blur-3xl animate-blob animation-delay-4000"></div>
3392
+ </div>
3393
+
3394
+ <div class="relative container mx-auto px-4 text-center">
3395
+ <div class="animate-fade-in">
3396
+ <h1 class="text-4xl md:text-5xl lg:text-6xl font-bold text-white mb-4 drop-shadow-lg">
3397
+ {{ 'about.title' | translate:{ projectName: projectName } }}
3398
+ </h1>
3399
+ <p class="text-lg md:text-xl text-purple-50 max-w-3xl mx-auto leading-relaxed">
3400
+ {{ 'about.subtitle' | translate }}
3401
+ </p>
3402
+ </div>
3403
+ </div>
3404
+ </section>
3405
+
3406
+ <!-- Overview Section -->
3407
+ <section class="py-20 bg-white">
3408
+ <div class="container mx-auto px-4">
3409
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-12 max-w-6xl mx-auto">
3410
+ <!-- Project Overview Card -->
3411
+ <div class="animate-slide-up">
3412
+ <div class="bg-linear-to-br from-blue-50 to-indigo-50 rounded-2xl p-8 border border-blue-100 h-full">
3413
+ <div class="flex items-center gap-3 mb-6">
3414
+ <div class="w-12 h-12 rounded-lg bg-linear-to-br from-blue-500 to-indigo-600 flex items-center justify-center shadow-lg">
3415
+ <ng-icon name="heroRocketLaunch" size="24" style="color: white;"></ng-icon>
3416
+ </div>
3417
+ <h2 class="text-2xl font-bold text-gray-900">
3418
+ {{ 'about.overview.title' | translate }}
3419
+ </h2>
3420
+ </div>
3421
+ <p class="text-gray-700 mb-4 leading-relaxed">
3422
+ {{ 'about.overview.paragraph1' | translate }}
3423
+ </p>
3424
+ <p class="text-gray-700 leading-relaxed">
3425
+ {{ 'about.overview.paragraph2' | translate }}
3426
+ </p>
3427
+ </div>
3428
+ </div>
3429
+
3430
+ <!-- Features Card -->
3431
+ <div class="animate-slide-up animation-delay-200">
3432
+ <div class="bg-linear-to-br from-purple-50 to-pink-50 rounded-2xl p-8 border border-purple-100 h-full">
3433
+ <div class="flex items-center gap-3 mb-6">
3434
+ <div class="w-12 h-12 rounded-lg bg-linear-to-br from-purple-500 to-pink-600 flex items-center justify-center shadow-lg">
3435
+ <ng-icon name="heroCheckCircle" size="24" style="color: white;"></ng-icon>
3436
+ </div>
3437
+ <h2 class="text-2xl font-bold text-gray-900">
3438
+ {{ 'about.features.title' | translate }}
3439
+ </h2>
3440
+ </div>
3441
+ <ul class="space-y-4">
3442
+ <li class="flex items-start gap-3">
3443
+ <div class="shrink-0 w-6 h-6 rounded-full bg-green-100 flex items-center justify-center mt-0.5">
3444
+ <ng-icon name="heroCheckCircle" size="16" style="color: #16a34a;"></ng-icon>
3445
+ </div>
3446
+ <span class="text-gray-700">{{ 'about.features.standalone' | translate }}</span>
3447
+ </li>
3448
+ <li class="flex items-start gap-3">
3449
+ <div class="shrink-0 w-6 h-6 rounded-full bg-green-100 flex items-center justify-center mt-0.5">
3450
+ <ng-icon name="heroCheckCircle" size="16" style="color: #16a34a;"></ng-icon>
3451
+ </div>
3452
+ <span class="text-gray-700">{{ 'about.features.tailwind' | translate }}</span>
3453
+ </li>
3454
+ <li class="flex items-start gap-3">
3455
+ <div class="shrink-0 w-6 h-6 rounded-full bg-green-100 flex items-center justify-center mt-0.5">
3456
+ <ng-icon name="heroCheckCircle" size="16" style="color: #16a34a;"></ng-icon>
3457
+ </div>
3458
+ <span class="text-gray-700">{{ 'about.features.responsive' | translate }}</span>
3459
+ </li>
3460
+ <li class="flex items-start gap-3">
3461
+ <div class="shrink-0 w-6 h-6 rounded-full bg-green-100 flex items-center justify-center mt-0.5">
3462
+ <ng-icon name="heroCheckCircle" size="16" style="color: #16a34a;"></ng-icon>
3463
+ </div>
3464
+ <span class="text-gray-700">{{ 'about.features.typescript' | translate }}</span>
3465
+ </li>
3466
+ <li class="flex items-start gap-3">
3467
+ <div class="shrink-0 w-6 h-6 rounded-full bg-green-100 flex items-center justify-center mt-0.5">
3468
+ <ng-icon name="heroCheckCircle" size="16" style="color: #16a34a;"></ng-icon>
3469
+ </div>
3470
+ <span class="text-gray-700">{{ 'about.features.production' | translate }}</span>
3471
+ </li>
3472
+ </ul>
3473
+ </div>
3474
+ </div>
3475
+ </div>
3476
+ </div>
3477
+ </section>
3478
+
3479
+ <!-- Tech Stack Section -->
3480
+ <section class="py-20 bg-linear-to-br from-gray-50 to-gray-100">
3481
+ <div class="container mx-auto px-4">
3482
+ <div class="text-center mb-12 animate-fade-in">
3483
+ <h2 class="text-3xl md:text-4xl font-bold text-gray-900 mb-4">
3484
+ {{ 'about.techStack.title' | translate }}
3485
+ </h2>
3486
+ <p class="text-xl text-gray-600 max-w-2xl mx-auto">
3487
+ {{ 'home.techStack.title' | translate }}
3488
+ </p>
3489
+ </div>
3490
+
3491
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 max-w-6xl mx-auto">
3492
+ <!-- Angular -->
3493
+ <div class="bg-white rounded-xl p-6 shadow-lg hover:shadow-xl border border-gray-100 transform hover:-translate-y-1 transition-all">
3494
+ <div class="flex items-center gap-4 mb-4">
3495
+ <div class="w-12 h-12 rounded-lg bg-red-100 flex items-center justify-center">
3496
+ <ng-icon name="heroCodeBracket" size="24" style="color: #dc2626;"></ng-icon>
3497
+ </div>
3498
+ <h3 class="text-xl font-bold text-gray-900">{{ 'home.techStack.angular.title' | translate }}</h3>
3499
+ </div>
3500
+ <p class="text-gray-600 text-sm">
3501
+ {{ 'home.techStack.angular.description' | translate }}
3502
+ </p>
3503
+ </div>
3504
+
3505
+ <!-- Tailwind CSS -->
3506
+ <div class="bg-white rounded-xl p-6 shadow-lg hover:shadow-xl border border-gray-100 transform hover:-translate-y-1 transition-all">
3507
+ <div class="flex items-center gap-4 mb-4">
3508
+ <div class="w-12 h-12 rounded-lg bg-cyan-100 flex items-center justify-center">
3509
+ <ng-icon name="heroPaintBrush" size="24" style="color: #0891b2;"></ng-icon>
3510
+ </div>
3511
+ <h3 class="text-xl font-bold text-gray-900">{{ 'home.techStack.tailwind.title' | translate }}</h3>
3512
+ </div>
3513
+ <p class="text-gray-600 text-sm">
3514
+ {{ 'home.techStack.tailwind.description' | translate }}
3515
+ </p>
3516
+ </div>
3517
+
3518
+ <!-- TypeScript -->
3519
+ <div class="bg-white rounded-xl p-6 shadow-lg hover:shadow-xl border border-gray-100 transform hover:-translate-y-1 transition-all">
3520
+ <div class="flex items-center gap-4 mb-4">
3521
+ <div class="w-12 h-12 rounded-lg bg-blue-100 flex items-center justify-center">
3522
+ <ng-icon name="heroShieldCheck" size="24" style="color: #2563eb;"></ng-icon>
3523
+ </div>
3524
+ <h3 class="text-xl font-bold text-gray-900">{{ 'home.techStack.typescript.title' | translate }}</h3>
3525
+ </div>
3526
+ <p class="text-gray-600 text-sm">
3527
+ {{ 'home.techStack.typescript.description' | translate }}
3528
+ </p>
3529
+ </div>
3530
+ </div>
3531
+ </div>
3532
+ </section>
3533
+
3534
+ <!-- CTA Section -->
3535
+ <section class="py-20 bg-white">
3536
+ <div class="container mx-auto px-4 text-center">
3537
+ <div class="max-w-3xl mx-auto animate-slide-up">
3538
+ <h2 class="text-3xl md:text-4xl font-bold text-gray-900 mb-6">
3539
+ {{ 'home.readyToStart.title' | translate }}
3540
+ </h2>
3541
+ <p class="text-xl text-gray-600 mb-8">
3542
+ {{ 'home.readyToStart.subtitle' | translate }}
3543
+ </p>
3544
+ <div class="flex flex-col sm:flex-row gap-4 justify-center">
3545
+ <app-button [routerLink]="['/']" size="lg">
3546
+ <span class="flex items-center gap-2">
3547
+ <ng-icon name="heroRocketLaunch" size="20"></ng-icon>
3548
+ {{ 'home.readyToStart.viewHome' | translate }}
3549
+ </span>
3550
+ </app-button>
3551
+ <app-button [routerLink]="['/contact']" variant="secondary" size="lg">
3552
+ <span class="flex items-center gap-2">
3553
+ {{ 'home.readyToStart.contactUs' | translate }}
3554
+ <ng-icon name="heroBolt" size="20"></ng-icon>
3555
+ </span>
3556
+ </app-button>
3557
+ </div>
3558
+ </div>
3559
+ </div>
3560
+ </section>
3561
+ `;
3562
+
3563
+ await fs.writeFile(
3564
+ path.join(config.fullPath, 'src/app/features/about/about.component.ts'),
3565
+ aboutComponentTs
3566
+ );
3567
+
3568
+ await fs.writeFile(
3569
+ path.join(config.fullPath, 'src/app/features/about/about.component.html'),
3570
+ aboutComponentHtml
3571
+ );
3572
+ },
3573
+
3574
+ async updateTsConfig(config) {
3575
+ // Define path mapping configuration
3576
+ const pathMappingConfig = ` "baseUrl": "./",
3577
+ "paths": {
3578
+ "@/*": ["src/*"],
3579
+ "@app/*": ["src/app/*"],
3580
+ "@core/*": ["src/app/core/*"],
3581
+ "@shared/*": ["src/app/shared/*"],
3582
+ "@features/*": ["src/app/features/*"],
3583
+ "@layout/*": ["src/app/layout/*"],
3584
+ "@environments/*": ["src/environments/*"]
3585
+ },`;
3586
+
3587
+ // Helper function to add path mappings to a tsconfig file
3588
+ const addPathMappings = (content) => {
3589
+ if (content.includes('"compilerOptions": {')) {
3590
+ const compilerOptionsStart =
3591
+ content.indexOf('"compilerOptions": {') + '"compilerOptions": {'.length;
3592
+ const beforeOptions = content.substring(0, compilerOptionsStart);
3593
+ const afterOptions = content.substring(compilerOptionsStart);
3594
+ return beforeOptions + '\n' + pathMappingConfig + afterOptions;
3595
+ }
3596
+ return content;
3597
+ };
3598
+
3599
+ // Update tsconfig.json (base config) - CRITICAL for IDE support
3600
+ const tsConfigPath = path.join(config.fullPath, 'tsconfig.json');
3601
+ let tsConfigContent = await fs.readFile(tsConfigPath, 'utf8');
3602
+ tsConfigContent = addPathMappings(tsConfigContent);
3603
+ await fs.writeFile(tsConfigPath, tsConfigContent);
3604
+
3605
+ // Update tsconfig.app.json
3606
+ const tsConfigAppPath = path.join(config.fullPath, 'tsconfig.app.json');
3607
+ let tsConfigAppContent = await fs.readFile(tsConfigAppPath, 'utf8');
3608
+ tsConfigAppContent = addPathMappings(tsConfigAppContent);
3609
+ await fs.writeFile(tsConfigAppPath, tsConfigAppContent);
3610
+
3611
+ // Update tsconfig.spec.json
3612
+ const tsConfigSpecPath = path.join(config.fullPath, 'tsconfig.spec.json');
3613
+ let tsConfigSpecContent = await fs.readFile(tsConfigSpecPath, 'utf8');
3614
+ tsConfigSpecContent = addPathMappings(tsConfigSpecContent);
3615
+ await fs.writeFile(tsConfigSpecPath, tsConfigSpecContent);
3616
+ },
3617
+
3618
+ async setupLinting(config) {
3619
+ // Read current package.json
3620
+ const packageJsonPath = path.join(config.fullPath, 'package.json');
3621
+ const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
3622
+
3623
+ // Add icons and PWA to regular dependencies
3624
+ if (!packageJson.dependencies) {
3625
+ packageJson.dependencies = {};
3626
+ }
3627
+ packageJson.dependencies['@ng-icons/core'] = '^29.7.0';
3628
+ packageJson.dependencies['@ng-icons/heroicons'] = '^29.7.0';
3629
+ packageJson.dependencies['@angular/service-worker'] = packageJson.dependencies['@angular/core'];
3630
+
3631
+ // Add linting dev dependencies
3632
+ const lintingDependencies = {
3633
+ '@eslint/js': '^9.23.0',
3634
+ eslint: '^9.23.0',
3635
+ 'angular-eslint': '19.3.0',
3636
+ 'typescript-eslint': '^8.20.0',
3637
+ prettier: '^3.5.3',
3638
+ 'eslint-config-prettier': '^10.1.2',
3639
+ 'prettier-plugin-tailwindcss': '^0.6.11',
3640
+ husky: '^9.0.11',
3641
+ 'lint-staged': '^15.2.0',
3642
+ };
3643
+
3644
+ // Add enhanced scripts
3645
+ const enhancedScripts = {
3646
+ lint: 'ng lint',
3647
+ 'lint:fix': 'ng lint --fix',
3648
+ format: 'prettier --write "**/*.{ts,html,css,json,md}"',
3649
+ 'format:check': 'prettier --check "**/*.{ts,html,css,json,md}"',
3650
+ 'format:ts': 'prettier --write "**/*.ts"',
3651
+ 'code:check': 'npm run format:check && npm run lint',
3652
+ 'code:fix': 'npm run format && npm run lint:fix',
3653
+ prepare: 'husky install || true',
3654
+ };
3655
+
3656
+ // Add prettier configuration
3657
+ const prettierConfig = {
3658
+ printWidth: 100,
3659
+ singleQuote: true,
3660
+ trailingComma: 'es5',
3661
+ tabWidth: 2,
3662
+ semi: true,
3663
+ arrowParens: 'always',
3664
+ endOfLine: 'lf',
3665
+ plugins: ['prettier-plugin-tailwindcss'],
3666
+ overrides: [
3667
+ {
3668
+ files: '*.html',
3669
+ options: {
3670
+ parser: 'angular',
3671
+ },
3672
+ },
3673
+ ],
3674
+ };
3675
+
3676
+ // lint-staged configuration
3677
+ const lintStagedConfig = {
3678
+ '*.{ts,html}': ['prettier --write', 'eslint --fix'],
3679
+ '*.css': ['prettier --write'],
3680
+ };
3681
+
3682
+ // Update package.json
3683
+ packageJson.devDependencies = {
3684
+ ...packageJson.devDependencies,
3685
+ ...lintingDependencies,
3686
+ };
3687
+ packageJson.scripts = { ...packageJson.scripts, ...enhancedScripts };
3688
+ packageJson.prettier = prettierConfig;
3689
+ packageJson['lint-staged'] = lintStagedConfig;
3690
+
3691
+ // Write updated package.json
3692
+ await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
3693
+
3694
+ // Configure angular.json for ESLint
3695
+ await this.configureAngularJson(config);
3696
+
3697
+ // Create ESLint configuration (ESLint 9+ flat config)
3698
+ const eslintConfig = `// @ts-check
3699
+ const eslint = require("@eslint/js");
3700
+ const tseslint = require("typescript-eslint");
3701
+ const angular = require("angular-eslint");
3702
+ const eslintConfigPrettier = require("eslint-config-prettier");
3703
+
3704
+ module.exports = tseslint.config(
3705
+ {
3706
+ files: ["**/*.ts"],
3707
+ extends: [
3708
+ eslint.configs.recommended,
3709
+ ...tseslint.configs.recommended,
3710
+ ...tseslint.configs.stylistic,
3711
+ ...angular.configs.tsRecommended,
3712
+ eslintConfigPrettier,
3713
+ ],
3714
+ processor: angular.processInlineTemplates,
3715
+ rules: {
3716
+ "@angular-eslint/directive-selector": [
3717
+ "error",
3718
+ {
3719
+ type: "attribute",
3720
+ prefix: "app",
3721
+ style: "camelCase",
3722
+ },
3723
+ ],
3724
+ "@angular-eslint/component-selector": [
3725
+ "error",
3726
+ {
3727
+ type: "element",
3728
+ prefix: "app",
3729
+ style: "kebab-case",
3730
+ },
3731
+ ],
3732
+ "@angular-eslint/component-class-suffix": [
3733
+ "error",
3734
+ {
3735
+ "suffixes": ["Component", "App"]
3736
+ }
3737
+ ],
3738
+ "@typescript-eslint/no-unused-vars": [
3739
+ "error",
3740
+ {
3741
+ "argsIgnorePattern": "^_",
3742
+ "varsIgnorePattern": "^_"
3743
+ }
3744
+ ],
3745
+ },
3746
+ },
3747
+ {
3748
+ files: ["**/*.html"],
3749
+ extends: [
3750
+ ...angular.configs.templateRecommended,
3751
+ ...angular.configs.templateAccessibility,
3752
+ ],
3753
+ rules: {},
3754
+ }
3755
+ );
3756
+ `;
3757
+
3758
+ await fs.writeFile(path.join(config.fullPath, 'eslint.config.js'), eslintConfig);
3759
+
3760
+ // Create VS Code extensions recommendation
3761
+ await fs.ensureDir(path.join(config.fullPath, '.vscode'));
3762
+
3763
+ const vscodeExtensions = {
3764
+ recommendations: [
3765
+ 'angular.ng-template',
3766
+ 'esbenp.prettier-vscode',
3767
+ 'dbaeumer.vscode-eslint',
3768
+ 'bradlc.vscode-tailwindcss',
3769
+ ],
3770
+ };
3771
+
3772
+ await fs.writeFile(
3773
+ path.join(config.fullPath, '.vscode/extensions.json'),
3774
+ JSON.stringify(vscodeExtensions, null, 2)
3775
+ );
3776
+
3777
+ // Create VS Code settings for format-on-save
3778
+ const vscodeSettings = {
3779
+ 'editor.formatOnSave': true,
3780
+ 'editor.defaultFormatter': 'esbenp.prettier-vscode',
3781
+ 'editor.codeActionsOnSave': {
3782
+ 'source.fixAll.eslint': 'explicit',
3783
+ },
3784
+ '[typescript]': {
3785
+ 'editor.defaultFormatter': 'esbenp.prettier-vscode',
3786
+ 'editor.formatOnSave': true,
3787
+ },
3788
+ '[html]': {
3789
+ 'editor.defaultFormatter': 'esbenp.prettier-vscode',
3790
+ 'editor.formatOnSave': true,
3791
+ },
3792
+ '[css]': {
3793
+ 'editor.defaultFormatter': 'esbenp.prettier-vscode',
3794
+ 'editor.formatOnSave': true,
3795
+ },
3796
+ '[json]': {
3797
+ 'editor.defaultFormatter': 'esbenp.prettier-vscode',
3798
+ 'editor.formatOnSave': true,
3799
+ },
3800
+ '[jsonc]': {
3801
+ 'editor.defaultFormatter': 'esbenp.prettier-vscode',
3802
+ 'editor.formatOnSave': true,
3803
+ },
3804
+ 'prettier.requireConfig': false,
3805
+ 'eslint.validate': ['javascript', 'typescript', 'html'],
3806
+ 'files.eol': '\\n',
3807
+ 'files.trimTrailingWhitespace': true,
3808
+ 'files.insertFinalNewline': true,
3809
+ };
3810
+
3811
+ await fs.writeFile(
3812
+ path.join(config.fullPath, '.vscode/settings.json'),
3813
+ JSON.stringify(vscodeSettings, null, 2)
3814
+ );
3815
+
3816
+ // Create .prettierignore file
3817
+ const prettierIgnore = `# Build outputs
3818
+ dist/
3819
+ coverage/
3820
+ node_modules/
3821
+
3822
+ # Generated files
3823
+ *.d.ts
3824
+ `;
3825
+
3826
+ await fs.writeFile(path.join(config.fullPath, '.prettierignore'), prettierIgnore);
3827
+
3828
+ // Create .prettierrc.json file for better IDE support
3829
+ await fs.writeFile(
3830
+ path.join(config.fullPath, '.prettierrc.json'),
3831
+ JSON.stringify(prettierConfig, null, 2)
3832
+ );
3833
+
3834
+ // Install linting packages if not skipping install
3835
+ if (!config.skipInstall) {
3836
+ await this.installLintingPackages(config);
3837
+ await this.setupHusky(config);
3838
+ }
3839
+ },
3840
+
3841
+ async configureAngularJson(config) {
3842
+ const angularJsonPath = path.join(config.fullPath, 'angular.json');
3843
+ const angularJson = JSON.parse(await fs.readFile(angularJsonPath, 'utf8'));
3844
+
3845
+ // Add lint architect target
3846
+ if (angularJson.projects && angularJson.projects[config.projectName]) {
3847
+ angularJson.projects[config.projectName].architect.lint = {
3848
+ builder: '@angular-eslint/builder:lint',
3849
+ options: {
3850
+ lintFilePatterns: ['src/**/*.ts', 'src/**/*.html'],
3851
+ },
3852
+ };
3853
+ }
3854
+
3855
+ await fs.writeFile(angularJsonPath, JSON.stringify(angularJson, null, 2));
3856
+ },
3857
+
3858
+ async installLintingPackages(config) {
3859
+ const execa = require('execa');
3860
+
3861
+ try {
3862
+ const packages = [
3863
+ '@eslint/js@^9.23.0',
3864
+ 'eslint@^9.23.0',
3865
+ 'angular-eslint@19.3.0',
3866
+ 'typescript-eslint@^8.20.0',
3867
+ 'prettier@^3.5.3',
3868
+ 'eslint-config-prettier@^10.1.2',
3869
+ 'prettier-plugin-tailwindcss@^0.6.11',
3870
+ 'husky@^9.0.11',
3871
+ 'lint-staged@^15.2.0',
3872
+ ];
3873
+
3874
+ await execa.command(`npm install ${packages.join(' ')} --save-dev`, {
3875
+ cwd: config.fullPath,
3876
+ stdio: 'pipe',
3877
+ });
3878
+ } catch (error) {
3879
+ // Silently fail - packages will be installed when user runs npm install
3880
+ // Note: Linting packages will be installed with npm install
3881
+ }
3882
+ },
3883
+
3884
+ async formatCode(config) {
3885
+ const execa = require('execa');
3886
+
3887
+ try {
3888
+ // Run prettier to format all generated files (including config files)
3889
+ await execa.command(
3890
+ 'npx prettier --write "**/*.{ts,html,css,json}" --ignore-path .gitignore --log-level silent',
3891
+ {
3892
+ cwd: config.fullPath,
3893
+ stdio: 'pipe',
3894
+ }
3895
+ );
3896
+ } catch (error) {
3897
+ // If prettier fails, it's not critical - user can run it manually
3898
+ // Silent failure - user can run "npm run format" later
3899
+ }
3900
+ },
3901
+
3902
+ async setupHusky(config) {
3903
+ const execa = require('execa');
3904
+
3905
+ try {
3906
+ // Initialize husky
3907
+ await execa.command('npx husky install', {
3908
+ cwd: config.fullPath,
3909
+ stdio: 'pipe',
3910
+ });
3911
+
3912
+ // Create pre-commit hook
3913
+ const preCommitHook = `#!/usr/bin/env sh
3914
+ . "$(dirname -- "$0")/_/husky.sh"
3915
+
3916
+ npx lint-staged
3917
+ `;
3918
+
3919
+ await fs.ensureDir(path.join(config.fullPath, '.husky'));
3920
+ await fs.writeFile(path.join(config.fullPath, '.husky/pre-commit'), preCommitHook, {
3921
+ mode: 0o755,
3922
+ });
3923
+ } catch (error) {
3924
+ // If husky setup fails, it's not critical
3925
+ // Silent failure - user can run "npm run prepare" later
3926
+ }
3927
+ },
3928
+
3929
+ async setupPWA(config) {
3930
+ // Create manifest.json
3931
+ const manifest = {
3932
+ name: config.projectName,
3933
+ short_name: config.projectName,
3934
+ theme_color: '#3b82f6',
3935
+ background_color: '#ffffff',
3936
+ display: 'standalone',
3937
+ scope: '/',
3938
+ start_url: '/',
3939
+ description: `${config.projectName} - Built with Angular and Tailwind CSS`,
3940
+ icons: [
3941
+ {
3942
+ src: 'assets/icons/icon-72x72.svg',
3943
+ sizes: '72x72',
3944
+ type: 'image/svg+xml',
3945
+ purpose: 'maskable any',
3946
+ },
3947
+ {
3948
+ src: 'assets/icons/icon-96x96.svg',
3949
+ sizes: '96x96',
3950
+ type: 'image/svg+xml',
3951
+ purpose: 'maskable any',
3952
+ },
3953
+ {
3954
+ src: 'assets/icons/icon-128x128.svg',
3955
+ sizes: '128x128',
3956
+ type: 'image/svg+xml',
3957
+ purpose: 'maskable any',
3958
+ },
3959
+ {
3960
+ src: 'assets/icons/icon-144x144.svg',
3961
+ sizes: '144x144',
3962
+ type: 'image/svg+xml',
3963
+ purpose: 'maskable any',
3964
+ },
3965
+ {
3966
+ src: 'assets/icons/icon-152x152.svg',
3967
+ sizes: '152x152',
3968
+ type: 'image/svg+xml',
3969
+ purpose: 'maskable any',
3970
+ },
3971
+ {
3972
+ src: 'assets/icons/icon-192x192.svg',
3973
+ sizes: '192x192',
3974
+ type: 'image/svg+xml',
3975
+ purpose: 'maskable any',
3976
+ },
3977
+ {
3978
+ src: 'assets/icons/icon-384x384.svg',
3979
+ sizes: '384x384',
3980
+ type: 'image/svg+xml',
3981
+ purpose: 'maskable any',
3982
+ },
3983
+ {
3984
+ src: 'assets/icons/icon-512x512.svg',
3985
+ sizes: '512x512',
3986
+ type: 'image/svg+xml',
3987
+ purpose: 'maskable any',
3988
+ },
3989
+ ],
3990
+ };
3991
+
3992
+ // Write manifest to public directory (new Angular structure)
3993
+ await fs.ensureDir(path.join(config.fullPath, 'public'));
3994
+ await fs.writeFile(
3995
+ path.join(config.fullPath, 'public/manifest.webmanifest'),
3996
+ JSON.stringify(manifest, null, 2)
3997
+ );
3998
+
3999
+ // Create ngsw-config.json (Service Worker configuration)
4000
+ const ngswConfig = {
4001
+ $schema: './node_modules/@angular/service-worker/config/schema.json',
4002
+ index: '/index.html',
4003
+ assetGroups: [
4004
+ {
4005
+ name: 'app',
4006
+ installMode: 'prefetch',
4007
+ resources: {
4008
+ files: ['/favicon.ico', '/index.html', '/manifest.webmanifest', '/*.css', '/*.js'],
4009
+ },
4010
+ },
4011
+ {
4012
+ name: 'assets',
4013
+ installMode: 'lazy',
4014
+ updateMode: 'prefetch',
4015
+ resources: {
4016
+ files: [
4017
+ '/assets/**',
4018
+ '/*.(svg|cur|jpg|jpeg|png|apng|webp|avif|gif|otf|ttf|woff|woff2)',
4019
+ ],
4020
+ },
4021
+ },
4022
+ ],
4023
+ dataGroups: [
4024
+ {
4025
+ name: 'api',
4026
+ urls: ['https://api.example.com/**'],
4027
+ cacheConfig: {
4028
+ maxSize: 100,
4029
+ maxAge: '1h',
4030
+ timeout: '10s',
4031
+ strategy: 'freshness',
4032
+ },
4033
+ },
4034
+ ],
4035
+ };
4036
+
4037
+ await fs.writeFile(
4038
+ path.join(config.fullPath, 'ngsw-config.json'),
4039
+ JSON.stringify(ngswConfig, null, 2)
4040
+ );
4041
+
4042
+ // Generate PWA icons (as SVG for now - users can replace with actual PNGs)
4043
+ await fs.ensureDir(path.join(config.fullPath, 'public/assets/icons'));
4044
+
4045
+ const iconSizes = [72, 96, 128, 144, 152, 192, 384, 512];
4046
+ const firstLetter = config.projectName.charAt(0).toUpperCase();
4047
+
4048
+ for (const size of iconSizes) {
4049
+ const iconSvg = `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
4050
+ <rect width="${size}" height="${size}" fill="#3B82F6" rx="${size * 0.2}"/>
4051
+ <text x="50%" y="55%" text-anchor="middle" fill="white" font-size="${
4052
+ size * 0.5
4053
+ }" font-family="Arial, sans-serif" font-weight="bold" dominant-baseline="middle">
4054
+ ${firstLetter}
4055
+ </text>
4056
+ </svg>`;
4057
+
4058
+ // Save as SVG (users can convert to PNG later or use tools)
4059
+ await fs.writeFile(
4060
+ path.join(config.fullPath, `public/assets/icons/icon-${size}x${size}.svg`),
4061
+ iconSvg
4062
+ );
4063
+ }
4064
+
4065
+ // Create a README for icons
4066
+ const iconReadme = `# PWA Icons
4067
+
4068
+ These are placeholder SVG icons for your PWA.
4069
+
4070
+ ## Generating PNG Icons
4071
+
4072
+ You should replace these with actual PNG icons for better compatibility. You can:
4073
+
4074
+ 1. Use online tools like https://realfavicongenerator.net/
4075
+ 2. Use a design tool to export PNG icons
4076
+ 3. Use command-line tools like ImageMagick to convert SVGs to PNGs
4077
+
4078
+ ## Required Sizes
4079
+
4080
+ - 72x72, 96x96, 128x128, 144x144, 152x152, 192x192, 384x384, 512x512
4081
+
4082
+ Update the paths in \`manifest.webmanifest\` if you change the file format.
4083
+ `;
4084
+
4085
+ await fs.writeFile(path.join(config.fullPath, 'public/assets/icons/README.md'), iconReadme);
4086
+
4087
+ // Update index.html to include manifest and theme
4088
+ const indexHtmlPath = path.join(config.fullPath, 'src/index.html');
4089
+ let indexHtml = await fs.readFile(indexHtmlPath, 'utf8');
4090
+
4091
+ if (!indexHtml.includes('manifest.webmanifest')) {
4092
+ indexHtml = indexHtml.replace(
4093
+ '</head>',
4094
+ ` <link rel="manifest" href="manifest.webmanifest">
4095
+ <meta name="theme-color" content="#3b82f6">
4096
+ </head>`
4097
+ );
4098
+ await fs.writeFile(indexHtmlPath, indexHtml);
4099
+ }
4100
+
4101
+ // Update angular.json to include service worker
4102
+ const angularJsonPath = path.join(config.fullPath, 'angular.json');
4103
+ const angularJson = JSON.parse(await fs.readFile(angularJsonPath, 'utf8'));
4104
+
4105
+ const projectName = Object.keys(angularJson.projects)[0];
4106
+ const buildConfigs = angularJson.projects[projectName].architect.build.configurations;
4107
+
4108
+ // Add service worker to production configuration
4109
+ if (buildConfigs && buildConfigs.production && !buildConfigs.production.serviceWorker) {
4110
+ buildConfigs.production.serviceWorker = 'ngsw-config.json';
4111
+ }
4112
+
4113
+ await fs.writeFile(angularJsonPath, JSON.stringify(angularJson, null, 2));
4114
+ },
4115
+ };
4116
+
4117
+ module.exports = starter;