create-ng-tailwind 3.1.0 → 4.0.0

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