create-ng-tailwind 3.1.0 → 4.1.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.

Potentially problematic release.


This version of create-ng-tailwind might be problematic. Click here for more details.

Files changed (48) hide show
  1. package/CHANGELOG.md +96 -341
  2. package/README.md +111 -157
  3. package/lib/cli/index.js +74 -3
  4. package/lib/cli/interactive.js +26 -1
  5. package/lib/managers/ProjectManager.js +2 -5
  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/lib/utils/nodeCompat.js +85 -0
  42. package/package.json +1 -1
  43. package/lib/templates/starter/features.js +0 -867
  44. package/lib/utils/ai-config.js +0 -641
  45. /package/lib/templates/{starter → base}/advanced-features.js +0 -0
  46. /package/lib/templates/{starter → base}/seo-assets.js +0 -0
  47. /package/lib/templates/{starter → base}/seo-features.js +0 -0
  48. /package/lib/templates/{starter → base}/ui-features.js +0 -0
@@ -0,0 +1,1241 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Starter Page Components
6
+ * - Home page with feature showcase
7
+ * - About page with project info
8
+ * - Contact page with form
9
+ */
10
+
11
+ async function createPages(config) {
12
+ await createHomeComponent(config);
13
+ await createAboutComponent(config);
14
+ await createContactComponent(config);
15
+ }
16
+
17
+ async function createHomeComponent(config) {
18
+ const homeComponentTs = `import { Component, OnInit, inject } from '@angular/core';
19
+ import { RouterModule } from '@angular/router';
20
+ import { NgIconComponent, provideIcons } from '@ng-icons/core';
21
+ import {
22
+ heroRocketLaunch,
23
+ heroPaintBrush,
24
+ heroShieldCheck,
25
+ heroBolt,
26
+ heroCodeBracket,
27
+ heroUsers,
28
+ heroGlobeAlt,
29
+ heroCog,
30
+ heroCheckCircle,
31
+ heroServer,
32
+ heroFolder,
33
+ heroCube
34
+ } from '@ng-icons/heroicons/outline';
35
+ import { TranslateModule } from '@ngx-translate/core';
36
+ import { ButtonComponent } from '@shared/components/button/button.component';
37
+ import { CardComponent } from '@shared/components/card/card.component';
38
+ import { ToastService } from '@core/services/toast.service';
39
+ import { ModalService } from '@core/services/modal.service';
40
+ import { SeoService } from '@core/services/seo.service';
41
+
42
+ @Component({
43
+ selector: 'app-home',
44
+ standalone: true,
45
+ imports: [RouterModule, NgIconComponent, TranslateModule, ButtonComponent, CardComponent],
46
+ viewProviders: [provideIcons({
47
+ heroRocketLaunch,
48
+ heroPaintBrush,
49
+ heroShieldCheck,
50
+ heroBolt,
51
+ heroCodeBracket,
52
+ heroUsers,
53
+ heroGlobeAlt,
54
+ heroCog,
55
+ heroCheckCircle,
56
+ heroServer,
57
+ heroFolder,
58
+ heroCube
59
+ })],
60
+ templateUrl: './home.component.html'
61
+ })
62
+ export class HomeComponent implements OnInit {
63
+ protected readonly projectName = '${config.projectName}';
64
+
65
+ private toast = inject(ToastService);
66
+ private modal = inject(ModalService);
67
+ private seo = inject(SeoService);
68
+
69
+ ngOnInit(): void {
70
+ // Set SEO meta tags for Home page
71
+ this.seo.updateMeta({
72
+ title: 'Home',
73
+ description: 'Welcome to ${config.projectName} - A modern Angular application with Tailwind CSS, SEO optimization, and i18n support.',
74
+ keywords: 'angular, tailwind, seo, i18n, typescript',
75
+ ogType: 'website'
76
+ });
77
+ }
78
+
79
+ // Toast Examples
80
+ showSuccessToast(): void {
81
+ this.toast.success('Operation completed successfully!');
82
+ }
83
+
84
+ showErrorToast(): void {
85
+ this.toast.error('Something went wrong. Please try again.');
86
+ }
87
+
88
+ showWarningToast(): void {
89
+ this.toast.warning('This action requires your attention.');
90
+ }
91
+
92
+ showInfoToast(): void {
93
+ this.toast.info('Here is some useful information.');
94
+ }
95
+
96
+ // Modal Examples
97
+ async showConfirmModal(): Promise<void> {
98
+ const confirmed = await this.modal.confirm(
99
+ 'Are you sure you want to proceed with this action?',
100
+ 'Confirm Action'
101
+ );
102
+
103
+ if (confirmed) {
104
+ this.toast.success('Action confirmed!');
105
+ } else {
106
+ this.toast.info('Action cancelled');
107
+ }
108
+ }
109
+
110
+ async showAlertModal(): Promise<void> {
111
+ await this.modal.alert(
112
+ 'This is an alert message. Click OK to close.',
113
+ 'Alert'
114
+ );
115
+ this.toast.info('Alert closed');
116
+ }
117
+ }`;
118
+
119
+ const homeComponentHtml = `<!-- Hero Section -->
120
+ <section class="relative py-20 bg-linear-to-br from-primary-600 via-secondary-600 to-accent-700 overflow-hidden">
121
+ <!-- Animated Background Elements -->
122
+ <div class="absolute inset-0 opacity-20">
123
+ <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>
124
+ <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>
125
+ <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>
126
+ </div>
127
+
128
+ <div class="relative container mx-auto px-4 text-center">
129
+ <div class="animate-fade-in">
130
+ <h1 class="text-4xl md:text-6xl lg:text-7xl font-bold text-white mb-6 drop-shadow-lg">
131
+ {{ 'home.hero.title' | translate:{ projectName: projectName } }}
132
+ </h1>
133
+ <p class="text-lg md:text-xl lg:text-2xl text-primary-50 mb-8 max-w-3xl mx-auto leading-relaxed">
134
+ {{ 'home.hero.subtitle' | translate }}
135
+ </p>
136
+ <div class="flex flex-col sm:flex-row gap-4 justify-center animate-slide-up">
137
+ <app-button size="lg" class="transform hover:scale-105 transition-transform duration-200">
138
+ <span class="flex items-center gap-2">
139
+ <ng-icon name="heroRocketLaunch" size="20"></ng-icon>
140
+ {{ 'home.hero.button' | translate }}
141
+ </span>
142
+ </app-button>
143
+ <app-button size="lg" variant="secondary" [routerLink]="['/about']" class="transform hover:scale-105 transition-transform duration-200">
144
+ <span class="flex items-center gap-2">
145
+ {{ 'home.hero.learnMore' | translate }}
146
+ <ng-icon name="heroShieldCheck" size="20"></ng-icon>
147
+ </span>
148
+ </app-button>
149
+ </div>
150
+ </div>
151
+ </div>
152
+ </section>
153
+
154
+ <!-- What's Included Section -->
155
+ <section class="py-20 bg-white">
156
+ <div class="container mx-auto px-4">
157
+ <div class="text-center mb-16">
158
+ <h2 class="text-3xl md:text-4xl font-bold text-gray-900 mb-4">
159
+ {{ 'home.productionReady.sectionTitle' | translate }}
160
+ </h2>
161
+ <p class="text-xl text-gray-600 max-w-3xl mx-auto">
162
+ {{ 'home.productionReady.title' | translate }}
163
+ </p>
164
+ </div>
165
+
166
+ <!-- Core Features Grid -->
167
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-12">
168
+ <!-- Modern Architecture -->
169
+ <app-card [hover]="true" [shadow]="true">
170
+ <div class="flex items-start space-x-4">
171
+ <div class="shrink-0">
172
+ <div class="w-12 h-12 rounded-lg bg-primary-100 flex items-center justify-center">
173
+ <ng-icon name="heroRocketLaunch" size="24" style="color: var(--color-primary-600);"></ng-icon>
174
+ </div>
175
+ </div>
176
+ <div>
177
+ <h3 class="text-lg font-semibold text-gray-900 mb-2">Modern Architecture</h3>
178
+ <p class="text-sm text-gray-600">Standalone components, Signals, Zoneless support, Angular 20+</p>
179
+ </div>
180
+ </div>
181
+ </app-card>
182
+
183
+ <!-- i18n System -->
184
+ <app-card [hover]="true" [shadow]="true">
185
+ <div class="flex items-start space-x-4">
186
+ <div class="shrink-0">
187
+ <div class="w-12 h-12 rounded-lg bg-accent-100 flex items-center justify-center">
188
+ <ng-icon name="heroGlobeAlt" size="24" style="color: var(--color-accent-600);"></ng-icon>
189
+ </div>
190
+ </div>
191
+ <div>
192
+ <h3 class="text-lg font-semibold text-gray-900 mb-2">i18n Translation</h3>
193
+ <p class="text-sm text-gray-600">English & Arabic with RTL support, language switcher in header</p>
194
+ </div>
195
+ </div>
196
+ </app-card>
197
+
198
+ <!-- HTTP Interceptors -->
199
+ <app-card [hover]="true" [shadow]="true">
200
+ <div class="flex items-start space-x-4">
201
+ <div class="shrink-0">
202
+ <div class="w-12 h-12 rounded-lg bg-success-100 flex items-center justify-center">
203
+ <ng-icon name="heroServer" size="24" style="color: var(--color-success-600);"></ng-icon>
204
+ </div>
205
+ </div>
206
+ <div>
207
+ <h3 class="text-lg font-semibold text-gray-900 mb-2">HTTP Interceptors</h3>
208
+ <p class="text-sm text-gray-600">Auth, Error handling, Loading state, Response caching (5min TTL)</p>
209
+ </div>
210
+ </div>
211
+ </app-card>
212
+
213
+ <!-- Tailwind CSS -->
214
+ <app-card [hover]="true" [shadow]="true">
215
+ <div class="flex items-start space-x-4">
216
+ <div class="shrink-0">
217
+ <div class="w-12 h-12 rounded-lg bg-secondary-100 flex items-center justify-center">
218
+ <ng-icon name="heroPaintBrush" size="24" style="color: var(--color-secondary-600);"></ng-icon>
219
+ </div>
220
+ </div>
221
+ <div>
222
+ <h3 class="text-lg font-semibold text-gray-900 mb-2">Tailwind CSS v4</h3>
223
+ <p class="text-sm text-gray-600">Modern PostCSS setup, responsive utilities, dark mode ready</p>
224
+ </div>
225
+ </div>
226
+ </app-card>
227
+
228
+ <!-- UI Components -->
229
+ <app-card [hover]="true" [shadow]="true">
230
+ <div class="flex items-start space-x-4">
231
+ <div class="shrink-0">
232
+ <div class="w-12 h-12 rounded-lg bg-accent-100 flex items-center justify-center">
233
+ <ng-icon name="heroCube" size="24" style="color: var(--color-accent-600);"></ng-icon>
234
+ </div>
235
+ </div>
236
+ <div>
237
+ <h3 class="text-lg font-semibold text-gray-900 mb-2">UI Components</h3>
238
+ <p class="text-sm text-gray-600">Button, Card, Spinner, Toast, Modal with full customization</p>
239
+ </div>
240
+ </div>
241
+ </app-card>
242
+
243
+ <!-- TypeScript -->
244
+ <app-card [hover]="true" [shadow]="true">
245
+ <div class="flex items-start space-x-4">
246
+ <div class="shrink-0">
247
+ <div class="w-12 h-12 rounded-lg bg-primary-100 flex items-center justify-center">
248
+ <ng-icon name="heroShieldCheck" size="24" style="color: var(--color-primary-600);"></ng-icon>
249
+ </div>
250
+ </div>
251
+ <div>
252
+ <h3 class="text-lg font-semibold text-gray-900 mb-2">Type-Safe</h3>
253
+ <p class="text-sm text-gray-600">TypeScript interfaces, models, strongly-typed services</p>
254
+ </div>
255
+ </div>
256
+ </app-card>
257
+ </div>
258
+
259
+ <!-- Features Breakdown -->
260
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
261
+ <!-- Core Services -->
262
+ <app-card [title]="'home.coreServices.title' | translate" [shadow]="true">
263
+ <ul class="space-y-2 text-gray-700">
264
+ <li class="flex items-start">
265
+ <ng-icon name="heroCheckCircle" size="20" class="mr-2 mt-0.5" style="color: var(--color-success-500);"></ng-icon>
266
+ <span>{{ 'home.coreServices.authService' | translate }}</span>
267
+ </li>
268
+ <li class="flex items-start">
269
+ <ng-icon name="heroCheckCircle" size="20" class="mr-2 mt-0.5" style="color: var(--color-success-500);"></ng-icon>
270
+ <span>{{ 'home.coreServices.apiService' | translate }}</span>
271
+ </li>
272
+ <li class="flex items-start">
273
+ <ng-icon name="heroCheckCircle" size="20" class="mr-2 mt-0.5" style="color: var(--color-success-500);"></ng-icon>
274
+ <span>{{ 'home.coreServices.toastService' | translate }}</span>
275
+ </li>
276
+ <li class="flex items-start">
277
+ <ng-icon name="heroCheckCircle" size="20" class="mr-2 mt-0.5" style="color: var(--color-success-500);"></ng-icon>
278
+ <span>{{ 'home.coreServices.modalService' | translate }}</span>
279
+ </li>
280
+ <li class="flex items-start">
281
+ <ng-icon name="heroCheckCircle" size="20" class="mr-2 mt-0.5" style="color: var(--color-success-500);"></ng-icon>
282
+ <span>{{ 'home.coreServices.loadingService' | translate }}</span>
283
+ </li>
284
+ <li class="flex items-start">
285
+ <ng-icon name="heroCheckCircle" size="20" class="mr-2 mt-0.5" style="color: var(--color-success-500);"></ng-icon>
286
+ <span>{{ 'home.coreServices.cacheService' | translate }}</span>
287
+ </li>
288
+ <li class="flex items-start">
289
+ <ng-icon name="heroCheckCircle" size="20" class="mr-2 mt-0.5" style="color: var(--color-success-500);"></ng-icon>
290
+ <span>{{ 'home.coreServices.storageService' | translate }}</span>
291
+ </li>
292
+ <li class="flex items-start">
293
+ <ng-icon name="heroCheckCircle" size="20" class="mr-2 mt-0.5" style="color: var(--color-success-500);"></ng-icon>
294
+ <span>{{ 'home.coreServices.i18nService' | translate }}</span>
295
+ </li>
296
+ </ul>
297
+ </app-card>
298
+
299
+ <!-- Shared Components & Utils -->
300
+ <app-card [title]="'home.sharedComponents.title' | translate" [shadow]="true">
301
+ <ul class="space-y-2 text-gray-700">
302
+ <li class="flex items-start">
303
+ <ng-icon name="heroCheckCircle" size="20" class="mr-2 mt-0.5" style="color: var(--color-primary-500);"></ng-icon>
304
+ <span>{{ 'home.sharedComponents.button' | translate }}</span>
305
+ </li>
306
+ <li class="flex items-start">
307
+ <ng-icon name="heroCheckCircle" size="20" class="mr-2 mt-0.5" style="color: var(--color-primary-500);"></ng-icon>
308
+ <span>{{ 'home.sharedComponents.card' | translate }}</span>
309
+ </li>
310
+ <li class="flex items-start">
311
+ <ng-icon name="heroCheckCircle" size="20" class="mr-2 mt-0.5" style="color: var(--color-primary-500);"></ng-icon>
312
+ <span>{{ 'home.sharedComponents.spinner' | translate }}</span>
313
+ </li>
314
+ <li class="flex items-start">
315
+ <ng-icon name="heroCheckCircle" size="20" class="mr-2 mt-0.5" style="color: var(--color-primary-500);"></ng-icon>
316
+ <span>{{ 'home.sharedComponents.toast' | translate }}</span>
317
+ </li>
318
+ <li class="flex items-start">
319
+ <ng-icon name="heroCheckCircle" size="20" class="mr-2 mt-0.5" style="color: var(--color-primary-500);"></ng-icon>
320
+ <span>{{ 'home.sharedComponents.modal' | translate }}</span>
321
+ </li>
322
+ <li class="flex items-start">
323
+ <ng-icon name="heroCheckCircle" size="20" class="mr-2 mt-0.5" style="color: var(--color-primary-500);"></ng-icon>
324
+ <span>{{ 'home.sharedComponents.pipes' | translate }}</span>
325
+ </li>
326
+ <li class="flex items-start">
327
+ <ng-icon name="heroCheckCircle" size="20" class="mr-2 mt-0.5" style="color: var(--color-primary-500);"></ng-icon>
328
+ <span>{{ 'home.sharedComponents.directives' | translate }}</span>
329
+ </li>
330
+ </ul>
331
+ </app-card>
332
+ </div>
333
+ </div>
334
+ </section>
335
+
336
+ <!-- Package & Dependencies Section -->
337
+ <section class="py-20 bg-gray-50">
338
+ <div class="container mx-auto px-4">
339
+ <div class="text-center mb-12">
340
+ <h2 class="text-3xl md:text-4xl font-bold text-gray-900 mb-4">
341
+ {{ 'home.preConfigured.title' | translate }}
342
+ </h2>
343
+ <p class="text-xl text-gray-600 max-w-2xl mx-auto">
344
+ {{ 'home.preConfigured.subtitle' | translate }}
345
+ </p>
346
+ </div>
347
+
348
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-8 max-w-6xl mx-auto">
349
+ <!-- Core Dependencies -->
350
+ <app-card [title]="'home.preConfigured.coreDependencies' | translate" [shadow]="true">
351
+ <div class="space-y-3 text-sm">
352
+ <div class="flex justify-between items-center p-2 bg-gray-50 rounded">
353
+ <span class="font-mono text-gray-700">&#64;angular/core</span>
354
+ <span class="text-gray-500">{{ 'home.preConfigured.latest' | translate }}</span>
355
+ </div>
356
+ <div class="flex justify-between items-center p-2 bg-gray-50 rounded">
357
+ <span class="font-mono text-gray-700">tailwindcss</span>
358
+ <span class="text-gray-500">^4.0.0</span>
359
+ </div>
360
+ <div class="flex justify-between items-center p-2 bg-gray-50 rounded">
361
+ <span class="font-mono text-gray-700">&#64;tailwindcss/postcss</span>
362
+ <span class="text-gray-500">{{ 'home.preConfigured.latest' | translate }}</span>
363
+ </div>
364
+ <div class="flex justify-between items-center p-2 bg-gray-50 rounded">
365
+ <span class="font-mono text-gray-700">&#64;ngx-translate/core</span>
366
+ <span class="text-gray-500">{{ 'home.preConfigured.i18n' | translate }}</span>
367
+ </div>
368
+ <div class="flex justify-between items-center p-2 bg-gray-50 rounded">
369
+ <span class="font-mono text-gray-700">&#64;ng-icons/heroicons</span>
370
+ <span class="text-gray-500">{{ 'home.preConfigured.iconLibrary' | translate }}</span>
371
+ </div>
372
+ </div>
373
+ </app-card>
374
+
375
+ <!-- Dev Tools -->
376
+ <app-card [title]="'home.preConfigured.developmentTools' | translate" [shadow]="true">
377
+ <div class="space-y-3 text-sm">
378
+ <div class="flex justify-between items-center p-2 bg-gray-50 rounded">
379
+ <span class="font-mono text-gray-700">ESLint</span>
380
+ <span class="text-gray-500">{{ 'home.preConfigured.linting' | translate }}</span>
381
+ </div>
382
+ <div class="flex justify-between items-center p-2 bg-gray-50 rounded">
383
+ <span class="font-mono text-gray-700">Prettier</span>
384
+ <span class="text-gray-500">{{ 'home.preConfigured.formatting' | translate }}</span>
385
+ </div>
386
+ <div class="flex justify-between items-center p-2 bg-gray-50 rounded">
387
+ <span class="font-mono text-gray-700">simple-git-hooks</span>
388
+ <span class="text-gray-500">{{ 'home.preConfigured.gitHooks' | translate }}</span>
389
+ </div>
390
+ <div class="flex justify-between items-center p-2 bg-gray-50 rounded">
391
+ <span class="font-mono text-gray-700">TypeScript</span>
392
+ <span class="text-gray-500">{{ 'home.preConfigured.typeSafety' | translate }}</span>
393
+ </div>
394
+ <div class="flex justify-between items-center p-2 bg-gray-50 rounded">
395
+ <span class="font-mono text-gray-700">PWA Config</span>
396
+ <span class="text-gray-500">{{ 'home.preConfigured.serviceWorker' | translate }}</span>
397
+ </div>
398
+ </div>
399
+ </app-card>
400
+ </div>
401
+
402
+ <!-- Path Aliases -->
403
+ <div class="mt-8 max-w-4xl mx-auto">
404
+ <app-card [title]="'home.pathAliases.title' | translate" [shadow]="true">
405
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
406
+ <div class="bg-gray-50 p-3 rounded font-mono">
407
+ <span style="color: var(--color-accent-600);">{{ 'home.pathAliases.core' | translate }}</span>
408
+ </div>
409
+ <div class="bg-gray-50 p-3 rounded font-mono">
410
+ <span style="color: var(--color-accent-600);">{{ 'home.pathAliases.shared' | translate }}</span>
411
+ </div>
412
+ <div class="bg-gray-50 p-3 rounded font-mono">
413
+ <span style="color: var(--color-accent-600);">{{ 'home.pathAliases.features' | translate }}</span>
414
+ </div>
415
+ <div class="bg-gray-50 p-3 rounded font-mono">
416
+ <span style="color: var(--color-accent-600);">{{ 'home.pathAliases.environments' | translate }}</span>
417
+ </div>
418
+ </div>
419
+ </app-card>
420
+ </div>
421
+ </div>
422
+ </section>
423
+
424
+ <!-- Project Structure Section -->
425
+ <section class="py-20 bg-white">
426
+ <div class="container mx-auto px-4">
427
+ <div class="text-center mb-12">
428
+ <h2 class="text-3xl md:text-4xl font-bold text-gray-900 mb-4">
429
+ {{ 'home.projectStructure.title' | translate }}
430
+ </h2>
431
+ <p class="text-xl text-gray-600 max-w-2xl mx-auto">
432
+ {{ 'home.projectStructure.subtitle' | translate }}
433
+ </p>
434
+ </div>
435
+
436
+ <div class="max-w-4xl mx-auto">
437
+ <app-card [shadow]="true">
438
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6 text-sm font-mono">
439
+ <div>
440
+ <div class="flex items-center mb-2">
441
+ <ng-icon name="heroFolder" size="18" class="mr-2" style="color: var(--color-primary-500);"></ng-icon>
442
+ <span class="font-semibold">src/app/core/</span>
443
+ </div>
444
+ <ul class="ml-6 space-y-1 text-gray-600">
445
+ <li>├─ services/ (8 services)</li>
446
+ <li>├─ guards/ (auth guard)</li>
447
+ <li>├─ interceptors/ (4 types)</li>
448
+ <li>└─ i18n/ (translation system)</li>
449
+ </ul>
450
+ </div>
451
+
452
+ <div>
453
+ <div class="flex items-center mb-2">
454
+ <ng-icon name="heroFolder" size="18" class="mr-2" style="color: var(--color-accent-500);"></ng-icon>
455
+ <span class="font-semibold">src/app/shared/</span>
456
+ </div>
457
+ <ul class="ml-6 space-y-1 text-gray-600">
458
+ <li>├─ components/ (5 components)</li>
459
+ <li>├─ pipes/ (4 pipes)</li>
460
+ <li>├─ directives/ (2 directives)</li>
461
+ <li>└─ models/ (TypeScript interfaces)</li>
462
+ </ul>
463
+ </div>
464
+
465
+ <div>
466
+ <div class="flex items-center mb-2">
467
+ <ng-icon name="heroFolder" size="18" class="mr-2" style="color: var(--color-success-500);"></ng-icon>
468
+ <span class="font-semibold">src/app/features/</span>
469
+ </div>
470
+ <ul class="ml-6 space-y-1 text-gray-600">
471
+ <li>├─ home/</li>
472
+ <li>├─ about/</li>
473
+ <li>├─ contact/</li>
474
+ <li>└─ auth/ (login, register, forgot)</li>
475
+ </ul>
476
+ </div>
477
+
478
+ <div>
479
+ <div class="flex items-center mb-2">
480
+ <ng-icon name="heroFolder" size="18" class="mr-2" style="color: var(--color-warning-500);"></ng-icon>
481
+ <span class="font-semibold">src/app/layout/</span>
482
+ </div>
483
+ <ul class="ml-6 space-y-1 text-gray-600">
484
+ <li>├─ main-layout/ (header + footer)</li>
485
+ <li>└─ auth-layout/ (auth pages)</li>
486
+ </ul>
487
+ </div>
488
+ </div>
489
+ </app-card>
490
+ </div>
491
+ </div>
492
+ </section>
493
+
494
+ <!-- Interactive Examples Section -->
495
+ <section class="py-20 bg-gray-50">
496
+ <div class="container mx-auto px-4">
497
+ <div class="text-center mb-12 animate-fade-in">
498
+ <div class="inline-flex items-center justify-center w-14 h-14 rounded-xl bg-primary-600 mb-4">
499
+ <ng-icon name="heroBolt" size="28" style="color: white;"></ng-icon>
500
+ </div>
501
+ <h2 class="text-3xl md:text-4xl font-bold text-gray-900 mb-3">
502
+ {{ 'home.interactiveExamples.title' | translate }}
503
+ </h2>
504
+ <p class="text-lg text-gray-600 max-w-2xl mx-auto">
505
+ {{ 'home.interactiveExamples.subtitle' | translate }}
506
+ </p>
507
+ </div>
508
+
509
+ <div class="max-w-5xl mx-auto">
510
+ <div class="bg-white rounded-xl shadow-lg p-8 md:p-10 border border-gray-200">
511
+ <div class="space-y-10">
512
+ <!-- Toast Examples -->
513
+ <div class="animate-slide-up">
514
+ <div class="flex items-center gap-3 mb-5">
515
+ <div class="w-10 h-10 rounded-lg bg-gray-100 flex items-center justify-center">
516
+ <ng-icon name="heroCheckCircle" size="20" class="text-gray-700"></ng-icon>
517
+ </div>
518
+ <div>
519
+ <h3 class="text-xl font-semibold text-gray-900">
520
+ {{ 'home.interactiveExamples.toastNotifications.title' | translate }}
521
+ </h3>
522
+ <p class="text-sm text-gray-500">{{ 'home.interactiveExamples.toastNotifications.subtitle' | translate }}</p>
523
+ </div>
524
+ </div>
525
+ <p class="text-gray-600 mb-5">
526
+ {{ 'home.interactiveExamples.toastNotifications.description' | translate }}
527
+ </p>
528
+ <div class="grid grid-cols-2 md:grid-cols-4 gap-3">
529
+ <app-button (click)="showSuccessToast()">
530
+ <span class="flex items-center gap-2">
531
+ <ng-icon name="heroCheckCircle" size="16"></ng-icon>
532
+ {{ 'home.interactiveExamples.toastNotifications.success' | translate }}
533
+ </span>
534
+ </app-button>
535
+ <app-button variant="danger" (click)="showErrorToast()">
536
+ <span class="flex items-center gap-2">
537
+ <ng-icon name="heroCheckCircle" size="16"></ng-icon>
538
+ {{ 'home.interactiveExamples.toastNotifications.error' | translate }}
539
+ </span>
540
+ </app-button>
541
+ <app-button variant="secondary" (click)="showWarningToast()">
542
+ <span class="flex items-center gap-2">
543
+ <ng-icon name="heroCheckCircle" size="16"></ng-icon>
544
+ {{ 'home.interactiveExamples.toastNotifications.warning' | translate }}
545
+ </span>
546
+ </app-button>
547
+ <app-button variant="outline" (click)="showInfoToast()">
548
+ <span class="flex items-center gap-2">
549
+ <ng-icon name="heroCheckCircle" size="16"></ng-icon>
550
+ {{ 'home.interactiveExamples.toastNotifications.info' | translate }}
551
+ </span>
552
+ </app-button>
553
+ </div>
554
+ </div>
555
+
556
+ <div class="border-t border-gray-200"></div>
557
+
558
+ <!-- Modal Examples -->
559
+ <div class="animate-slide-up animation-delay-200">
560
+ <div class="flex items-center gap-3 mb-5">
561
+ <div class="w-10 h-10 rounded-lg bg-gray-100 flex items-center justify-center">
562
+ <ng-icon name="heroCube" size="20" class="text-gray-700"></ng-icon>
563
+ </div>
564
+ <div>
565
+ <h3 class="text-xl font-semibold text-gray-900">
566
+ {{ 'home.interactiveExamples.modalDialogs.title' | translate }}
567
+ </h3>
568
+ <p class="text-sm text-gray-500">{{ 'home.interactiveExamples.modalDialogs.subtitle' | translate }}</p>
569
+ </div>
570
+ </div>
571
+ <p class="text-gray-600 mb-5">
572
+ {{ 'home.interactiveExamples.modalDialogs.description' | translate }}
573
+ </p>
574
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-3">
575
+ <app-button (click)="showConfirmModal()" size="lg">
576
+ <span class="flex items-center gap-2 justify-center">
577
+ <ng-icon name="heroShieldCheck" size="18"></ng-icon>
578
+ {{ 'home.interactiveExamples.modalDialogs.showConfirm' | translate }}
579
+ </span>
580
+ </app-button>
581
+ <app-button variant="secondary" (click)="showAlertModal()" size="lg">
582
+ <span class="flex items-center gap-2 justify-center">
583
+ <ng-icon name="heroBolt" size="18"></ng-icon>
584
+ {{ 'home.interactiveExamples.modalDialogs.showAlert' | translate }}
585
+ </span>
586
+ </app-button>
587
+ </div>
588
+ </div>
589
+
590
+ <!-- Pro Tip -->
591
+ <div class="bg-info-50 rounded-lg p-5 border border-info-100">
592
+ <div class="flex items-start gap-3">
593
+ <div class="shrink-0">
594
+ <div class="w-8 h-8 rounded-lg bg-primary-600 flex items-center justify-center">
595
+ <ng-icon name="heroBolt" size="16" style="color: white;"></ng-icon>
596
+ </div>
597
+ </div>
598
+ <div>
599
+ <h4 class="font-semibold text-gray-900 mb-1 text-sm">{{ 'home.interactiveExamples.proTip.title' | translate }}</h4>
600
+ <p class="text-sm text-gray-600">
601
+ {{ 'home.interactiveExamples.proTip.description' | translate }}
602
+ </p>
603
+ </div>
604
+ </div>
605
+ </div>
606
+ </div>
607
+ </div>
608
+ </div>
609
+ </div>
610
+ </section>
611
+
612
+ <!-- Getting Started Section -->
613
+ <section class="py-20 bg-white">
614
+ <div class="container mx-auto px-4 text-center">
615
+ <h2 class="text-3xl md:text-4xl font-bold text-gray-900 mb-6">
616
+ {{ 'home.readyToBuild.title' | translate }}
617
+ </h2>
618
+ <p class="text-xl text-gray-600 mb-8 max-w-2xl mx-auto">
619
+ {{ 'home.readyToBuild.subtitle' | translate }}
620
+ </p>
621
+ <div class="flex flex-col sm:flex-row gap-4 justify-center">
622
+ <app-button [routerLink]="['/about']" size="lg">
623
+ {{ 'home.readyToBuild.learnMore' | translate }}
624
+ </app-button>
625
+ <app-button [routerLink]="['/contact']" variant="secondary" size="lg">
626
+ {{ 'home.readyToStart.contactUs' | translate }}
627
+ </app-button>
628
+ </div>
629
+ </div>
630
+ </section>
631
+ `;
632
+
633
+ await fs.writeFile(
634
+ path.join(config.fullPath, 'src/app/features/home/home.component.ts'),
635
+ homeComponentTs
636
+ );
637
+
638
+ await fs.writeFile(
639
+ path.join(config.fullPath, 'src/app/features/home/home.component.html'),
640
+ homeComponentHtml
641
+ );
642
+ }
643
+
644
+ async function createAboutComponent(config) {
645
+ const aboutComponentTs = `import { Component, OnInit, inject } from '@angular/core';
646
+ import { RouterModule } from '@angular/router';
647
+ import { NgIconComponent, provideIcons } from '@ng-icons/core';
648
+ import {
649
+ heroRocketLaunch,
650
+ heroPaintBrush,
651
+ heroShieldCheck,
652
+ heroBolt,
653
+ heroCodeBracket,
654
+ heroCheckCircle,
655
+ } from '@ng-icons/heroicons/outline';
656
+ import { TranslateModule } from '@ngx-translate/core';
657
+ import { ButtonComponent } from '@shared/components/button/button.component';
658
+ import { SeoService } from '@core/services/seo.service';
659
+
660
+ @Component({
661
+ selector: 'app-about',
662
+ standalone: true,
663
+ imports: [RouterModule, NgIconComponent, TranslateModule, ButtonComponent],
664
+ viewProviders: [provideIcons({
665
+ heroRocketLaunch,
666
+ heroPaintBrush,
667
+ heroShieldCheck,
668
+ heroBolt,
669
+ heroCodeBracket,
670
+ heroCheckCircle,
671
+ })],
672
+ templateUrl: './about.component.html'
673
+ })
674
+ export class AboutComponent implements OnInit {
675
+ protected readonly projectName = '${config.projectName}';
676
+ private seo = inject(SeoService);
677
+
678
+ ngOnInit(): void {
679
+ // Set SEO meta tags for About page
680
+ this.seo.updateMeta({
681
+ title: 'About Us',
682
+ description: 'Learn more about ${config.projectName} and our mission to create modern web applications.',
683
+ keywords: 'about, team, mission, company',
684
+ ogType: 'website'
685
+ });
686
+ }
687
+ }`;
688
+
689
+ const aboutComponentHtml = `<!-- Hero Section -->
690
+ <section class="relative py-16 bg-linear-to-br from-primary-600 via-accent-600 to-secondary-600 overflow-hidden">
691
+ <div class="absolute inset-0 opacity-10">
692
+ <div class="absolute top-0 left-0 w-96 h-96 bg-white rounded-full mix-blend-multiply filter blur-3xl animate-blob"></div>
693
+ <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>
694
+ <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>
695
+ </div>
696
+
697
+ <div class="relative container mx-auto px-4 text-center">
698
+ <div class="animate-fade-in">
699
+ <h1 class="text-4xl md:text-5xl lg:text-6xl font-bold text-white mb-4 drop-shadow-lg">
700
+ {{ 'about.title' | translate:{ projectName: projectName } }}
701
+ </h1>
702
+ <p class="text-lg md:text-xl text-accent-50 max-w-3xl mx-auto leading-relaxed">
703
+ {{ 'about.subtitle' | translate }}
704
+ </p>
705
+ </div>
706
+ </div>
707
+ </section>
708
+
709
+ <!-- Overview Section -->
710
+ <section class="py-20 bg-white">
711
+ <div class="container mx-auto px-4">
712
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-12 max-w-6xl mx-auto">
713
+ <!-- Project Overview Card -->
714
+ <div class="animate-slide-up">
715
+ <div class="bg-linear-to-br from-primary-50 to-accent-50 rounded-2xl p-8 border border-primary-100 h-full">
716
+ <div class="flex items-center gap-3 mb-6">
717
+ <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">
718
+ <ng-icon name="heroRocketLaunch" size="24" style="color: white;"></ng-icon>
719
+ </div>
720
+ <h2 class="text-2xl font-bold text-gray-900">
721
+ {{ 'about.overview.title' | translate }}
722
+ </h2>
723
+ </div>
724
+ <p class="text-gray-700 mb-4 leading-relaxed">
725
+ {{ 'about.overview.paragraph1' | translate }}
726
+ </p>
727
+ <p class="text-gray-700 leading-relaxed">
728
+ {{ 'about.overview.paragraph2' | translate }}
729
+ </p>
730
+ </div>
731
+ </div>
732
+
733
+ <!-- Features Card -->
734
+ <div class="animate-slide-up animation-delay-200">
735
+ <div class="bg-linear-to-br from-accent-50 to-secondary-50 rounded-2xl p-8 border border-accent-100 h-full">
736
+ <div class="flex items-center gap-3 mb-6">
737
+ <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">
738
+ <ng-icon name="heroCheckCircle" size="24" style="color: white;"></ng-icon>
739
+ </div>
740
+ <h2 class="text-2xl font-bold text-gray-900">
741
+ {{ 'about.features.title' | translate }}
742
+ </h2>
743
+ </div>
744
+ <ul class="space-y-4">
745
+ <li class="flex items-start gap-3">
746
+ <div class="shrink-0 w-6 h-6 rounded-full bg-success-100 flex items-center justify-center mt-0.5">
747
+ <ng-icon name="heroCheckCircle" size="16" style="color: var(--color-success-600);"></ng-icon>
748
+ </div>
749
+ <span class="text-gray-700">{{ 'about.features.standalone' | translate }}</span>
750
+ </li>
751
+ <li class="flex items-start gap-3">
752
+ <div class="shrink-0 w-6 h-6 rounded-full bg-success-100 flex items-center justify-center mt-0.5">
753
+ <ng-icon name="heroCheckCircle" size="16" style="color: var(--color-success-600);"></ng-icon>
754
+ </div>
755
+ <span class="text-gray-700">{{ 'about.features.tailwind' | translate }}</span>
756
+ </li>
757
+ <li class="flex items-start gap-3">
758
+ <div class="shrink-0 w-6 h-6 rounded-full bg-success-100 flex items-center justify-center mt-0.5">
759
+ <ng-icon name="heroCheckCircle" size="16" style="color: var(--color-success-600);"></ng-icon>
760
+ </div>
761
+ <span class="text-gray-700">{{ 'about.features.responsive' | translate }}</span>
762
+ </li>
763
+ <li class="flex items-start gap-3">
764
+ <div class="shrink-0 w-6 h-6 rounded-full bg-success-100 flex items-center justify-center mt-0.5">
765
+ <ng-icon name="heroCheckCircle" size="16" style="color: var(--color-success-600);"></ng-icon>
766
+ </div>
767
+ <span class="text-gray-700">{{ 'about.features.typescript' | translate }}</span>
768
+ </li>
769
+ <li class="flex items-start gap-3">
770
+ <div class="shrink-0 w-6 h-6 rounded-full bg-success-100 flex items-center justify-center mt-0.5">
771
+ <ng-icon name="heroCheckCircle" size="16" style="color: var(--color-success-600);"></ng-icon>
772
+ </div>
773
+ <span class="text-gray-700">{{ 'about.features.production' | translate }}</span>
774
+ </li>
775
+ </ul>
776
+ </div>
777
+ </div>
778
+ </div>
779
+ </div>
780
+ </section>
781
+
782
+ <!-- Tech Stack Section -->
783
+ <section class="py-20 bg-linear-to-br from-gray-50 to-gray-100">
784
+ <div class="container mx-auto px-4">
785
+ <div class="text-center mb-12 animate-fade-in">
786
+ <h2 class="text-3xl md:text-4xl font-bold text-gray-900 mb-4">
787
+ {{ 'about.techStack.title' | translate }}
788
+ </h2>
789
+ <p class="text-xl text-gray-600 max-w-2xl mx-auto">
790
+ {{ 'home.techStack.title' | translate }}
791
+ </p>
792
+ </div>
793
+
794
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 max-w-6xl mx-auto">
795
+ <!-- Angular -->
796
+ <div class="bg-white rounded-xl p-6 shadow-lg hover:shadow-xl border border-gray-100 transform hover:-translate-y-1 transition-all">
797
+ <div class="flex items-center gap-4 mb-4">
798
+ <div class="w-12 h-12 rounded-lg bg-danger-100 flex items-center justify-center">
799
+ <ng-icon name="heroCodeBracket" size="24" style="color: var(--color-danger-600);"></ng-icon>
800
+ </div>
801
+ <h3 class="text-xl font-bold text-gray-900">{{ 'home.techStack.angular.title' | translate }}</h3>
802
+ </div>
803
+ <p class="text-gray-600 text-sm">
804
+ {{ 'home.techStack.angular.description' | translate }}
805
+ </p>
806
+ </div>
807
+
808
+ <!-- Tailwind CSS -->
809
+ <div class="bg-white rounded-xl p-6 shadow-lg hover:shadow-xl border border-gray-100 transform hover:-translate-y-1 transition-all">
810
+ <div class="flex items-center gap-4 mb-4">
811
+ <div class="w-12 h-12 rounded-lg bg-secondary-100 flex items-center justify-center">
812
+ <ng-icon name="heroPaintBrush" size="24" style="color: var(--color-secondary-600);"></ng-icon>
813
+ </div>
814
+ <h3 class="text-xl font-bold text-gray-900">{{ 'home.techStack.tailwind.title' | translate }}</h3>
815
+ </div>
816
+ <p class="text-gray-600 text-sm">
817
+ {{ 'home.techStack.tailwind.description' | translate }}
818
+ </p>
819
+ </div>
820
+
821
+ <!-- TypeScript -->
822
+ <div class="bg-white rounded-xl p-6 shadow-lg hover:shadow-xl border border-gray-100 transform hover:-translate-y-1 transition-all">
823
+ <div class="flex items-center gap-4 mb-4">
824
+ <div class="w-12 h-12 rounded-lg bg-primary-100 flex items-center justify-center">
825
+ <ng-icon name="heroShieldCheck" size="24" style="color: var(--color-primary-600);"></ng-icon>
826
+ </div>
827
+ <h3 class="text-xl font-bold text-gray-900">{{ 'home.techStack.typescript.title' | translate }}</h3>
828
+ </div>
829
+ <p class="text-gray-600 text-sm">
830
+ {{ 'home.techStack.typescript.description' | translate }}
831
+ </p>
832
+ </div>
833
+ </div>
834
+ </div>
835
+ </section>
836
+
837
+ <!-- CTA Section -->
838
+ <section class="py-20 bg-white">
839
+ <div class="container mx-auto px-4 text-center">
840
+ <div class="max-w-3xl mx-auto animate-slide-up">
841
+ <h2 class="text-3xl md:text-4xl font-bold text-gray-900 mb-6">
842
+ {{ 'home.readyToStart.title' | translate }}
843
+ </h2>
844
+ <p class="text-xl text-gray-600 mb-8">
845
+ {{ 'home.readyToStart.subtitle' | translate }}
846
+ </p>
847
+ <div class="flex flex-col sm:flex-row gap-4 justify-center">
848
+ <app-button [routerLink]="['/']" size="lg">
849
+ <span class="flex items-center gap-2">
850
+ <ng-icon name="heroRocketLaunch" size="20"></ng-icon>
851
+ {{ 'home.readyToStart.viewHome' | translate }}
852
+ </span>
853
+ </app-button>
854
+ <app-button [routerLink]="['/contact']" variant="secondary" size="lg">
855
+ <span class="flex items-center gap-2">
856
+ {{ 'home.readyToStart.contactUs' | translate }}
857
+ <ng-icon name="heroBolt" size="20"></ng-icon>
858
+ </span>
859
+ </app-button>
860
+ </div>
861
+ </div>
862
+ </div>
863
+ </section>
864
+ `;
865
+
866
+ await fs.writeFile(
867
+ path.join(config.fullPath, 'src/app/features/about/about.component.ts'),
868
+ aboutComponentTs
869
+ );
870
+
871
+ await fs.writeFile(
872
+ path.join(config.fullPath, 'src/app/features/about/about.component.html'),
873
+ aboutComponentHtml
874
+ );
875
+ }
876
+
877
+ async function createContactComponent(config) {
878
+ const contactComponentTs = `import { Component, OnInit, inject } from '@angular/core';
879
+ import { ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms';
880
+ import { NgIconComponent, provideIcons } from '@ng-icons/core';
881
+ import { TranslateModule } from '@ngx-translate/core';
882
+
883
+ import {
884
+ heroEnvelope,
885
+ heroMapPin,
886
+ heroClock,
887
+ heroCheckCircle,
888
+ heroPaperAirplane,
889
+ } from '@ng-icons/heroicons/outline';
890
+ import { ButtonComponent } from '@shared/components/button/button.component';
891
+ import { SeoService } from '@core/services/seo.service';
892
+
893
+ @Component({
894
+ selector: 'app-contact',
895
+ standalone: true,
896
+ imports: [ReactiveFormsModule, NgIconComponent, ButtonComponent, TranslateModule],
897
+ viewProviders: [provideIcons({
898
+ heroEnvelope,
899
+ heroMapPin,
900
+ heroClock,
901
+ heroCheckCircle,
902
+ heroPaperAirplane,
903
+ })],
904
+ templateUrl: './contact.component.html'
905
+ })
906
+ export class ContactComponent implements OnInit {
907
+ private fb = inject(FormBuilder);
908
+ private seo = inject(SeoService);
909
+
910
+ isSubmitting = false;
911
+ submitted = false;
912
+
913
+ ngOnInit(): void {
914
+ this.seo.updateMeta({
915
+ title: 'Contact Us',
916
+ description: 'Get in touch with us. We would love to hear from you and answer any questions you may have.',
917
+ keywords: 'contact, support, get in touch, email',
918
+ ogType: 'website'
919
+ });
920
+ }
921
+
922
+ contactForm = this.fb.group({
923
+ name: ['', [Validators.required, Validators.minLength(2)]],
924
+ email: ['', [Validators.required, Validators.email]],
925
+ subject: ['', Validators.required],
926
+ message: ['', [Validators.required, Validators.minLength(10)]]
927
+ });
928
+
929
+ onSubmit(): void {
930
+ if (this.contactForm.valid) {
931
+ this.isSubmitting = true;
932
+ setTimeout(() => {
933
+ this.isSubmitting = false;
934
+ this.submitted = true;
935
+ this.contactForm.reset();
936
+ setTimeout(() => {
937
+ this.submitted = false;
938
+ }, 5000);
939
+ }, 1500);
940
+ } else {
941
+ Object.keys(this.contactForm.controls).forEach(key => {
942
+ this.contactForm.get(key)?.markAsTouched();
943
+ });
944
+ }
945
+ }
946
+ }`;
947
+
948
+ const contactComponentHtml = `<!-- Hero Section -->
949
+ <section class="relative py-20 bg-linear-to-br from-primary-600 via-secondary-600 to-accent-600 overflow-hidden">
950
+ <div class="absolute inset-0 opacity-10">
951
+ <div class="absolute top-0 -left-4 w-96 h-96 bg-white rounded-full mix-blend-multiply filter blur-3xl animate-blob"></div>
952
+ <div class="absolute top-0 -right-4 w-96 h-96 bg-warning-200 rounded-full mix-blend-multiply filter blur-3xl animate-blob animation-delay-2000"></div>
953
+ <div class="absolute bottom-0 left-1/2 w-96 h-96 bg-success-200 rounded-full mix-blend-multiply filter blur-3xl animate-blob animation-delay-4000"></div>
954
+ </div>
955
+
956
+ <div class="relative container mx-auto px-4 text-center">
957
+ <div class="animate-fade-in">
958
+ <div class="inline-flex items-center justify-center w-20 h-20 rounded-full bg-white/20 backdrop-blur-sm mb-6 shadow-xl">
959
+ <ng-icon name="heroEnvelope" size="40" style="color: white;"></ng-icon>
960
+ </div>
961
+ <h1 class="text-5xl md:text-6xl lg:text-7xl font-bold text-white mb-6 drop-shadow-lg">
962
+ {{ 'contact.title' | translate }}
963
+ </h1>
964
+ <p class="text-xl md:text-2xl text-secondary-50 max-w-3xl mx-auto leading-relaxed">
965
+ {{ 'contact.subtitle' | translate }}
966
+ </p>
967
+ </div>
968
+ </div>
969
+ </section>
970
+
971
+ <!-- Main Content -->
972
+ <div class="bg-linear-to-br from-gray-50 to-primary-50 py-16 -mt-8">
973
+ <div class="container mx-auto px-4 sm:px-6 lg:px-8 max-w-6xl">
974
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-12">
975
+
976
+ <!-- Contact Form Card -->
977
+ <div class="bg-white rounded-2xl shadow-xl p-8 border border-gray-100 hover:shadow-2xl transition-shadow animate-fade-in">
978
+ <div class="mb-6">
979
+ <div class="inline-flex items-center justify-center w-12 h-12 rounded-xl bg-linear-to-br from-primary-500 to-secondary-500 mb-4">
980
+ <ng-icon name="heroPaperAirplane" size="24" style="color: white;"></ng-icon>
981
+ </div>
982
+ <h2 class="text-2xl font-bold text-gray-900">{{ 'contact.form.title' | translate }}</h2>
983
+ <p class="text-gray-600 mt-1">{{ 'contact.form.description' | translate }}</p>
984
+ </div>
985
+
986
+ <form [formGroup]="contactForm" (ngSubmit)="onSubmit()" class="space-y-5">
987
+ <!-- Name Field -->
988
+ <div>
989
+ <label for="name" class="block text-sm font-semibold text-gray-700 mb-2">
990
+ {{ 'contact.form.name' | translate }} *
991
+ </label>
992
+ <input
993
+ id="name"
994
+ type="text"
995
+ formControlName="name"
996
+ autocomplete="name"
997
+ placeholder="John Doe"
998
+ class="w-full px-4 py-3 border-2 border-gray-200 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-primary-500 transition-all"
999
+ [class.border-danger-500]="contactForm.get('name')?.invalid && contactForm.get('name')?.touched">
1000
+ @if (contactForm.get('name')?.invalid && contactForm.get('name')?.touched) {
1001
+ <div class="mt-2 text-sm text-danger-600 flex items-center gap-1">
1002
+ <svg class="h-4 w-4" fill="currentColor" viewBox="0 0 20 20">
1003
+ <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
1004
+ </svg>
1005
+ @if (contactForm.get('name')?.errors?.['required']) {
1006
+ <span>{{ 'contact.form.errors.nameRequired' | translate }}</span>
1007
+ }
1008
+ @if (contactForm.get('name')?.errors?.['minlength']) {
1009
+ <span>{{ 'contact.form.errors.nameMinLength' | translate }}</span>
1010
+ }
1011
+ </div>
1012
+ }
1013
+ </div>
1014
+
1015
+ <!-- Email Field -->
1016
+ <div>
1017
+ <label for="email" class="block text-sm font-semibold text-gray-700 mb-2">
1018
+ {{ 'contact.form.email' | translate }} *
1019
+ </label>
1020
+ <input
1021
+ id="email"
1022
+ type="email"
1023
+ formControlName="email"
1024
+ autocomplete="email"
1025
+ placeholder="john@example.com"
1026
+ class="w-full px-4 py-3 border-2 border-gray-200 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-primary-500 transition-all"
1027
+ [class.border-danger-500]="contactForm.get('email')?.invalid && contactForm.get('email')?.touched">
1028
+ @if (contactForm.get('email')?.invalid && contactForm.get('email')?.touched) {
1029
+ <div class="mt-2 text-sm text-danger-600 flex items-center gap-1">
1030
+ <svg class="h-4 w-4" fill="currentColor" viewBox="0 0 20 20">
1031
+ <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
1032
+ </svg>
1033
+ @if (contactForm.get('email')?.errors?.['required']) {
1034
+ <span>{{ 'contact.form.errors.emailRequired' | translate }}</span>
1035
+ }
1036
+ @if (contactForm.get('email')?.errors?.['email']) {
1037
+ <span>{{ 'contact.form.errors.emailInvalid' | translate }}</span>
1038
+ }
1039
+ </div>
1040
+ }
1041
+ </div>
1042
+
1043
+ <!-- Subject Field -->
1044
+ <div>
1045
+ <label for="subject" class="block text-sm font-semibold text-gray-700 mb-2">
1046
+ {{ 'contact.form.subject' | translate }} *
1047
+ </label>
1048
+ <input
1049
+ id="subject"
1050
+ type="text"
1051
+ formControlName="subject"
1052
+ placeholder="How can we help?"
1053
+ class="w-full px-4 py-3 border-2 border-gray-200 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-primary-500 transition-all"
1054
+ [class.border-danger-500]="contactForm.get('subject')?.invalid && contactForm.get('subject')?.touched">
1055
+ @if (contactForm.get('subject')?.invalid && contactForm.get('subject')?.touched) {
1056
+ <div class="mt-2 text-sm text-danger-600 flex items-center gap-1">
1057
+ <svg class="h-4 w-4" fill="currentColor" viewBox="0 0 20 20">
1058
+ <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
1059
+ </svg>
1060
+ <span>{{ 'contact.form.errors.subjectRequired' | translate }}</span>
1061
+ </div>
1062
+ }
1063
+ </div>
1064
+
1065
+ <!-- Message Field -->
1066
+ <div>
1067
+ <label for="message" class="block text-sm font-semibold text-gray-700 mb-2">
1068
+ {{ 'contact.form.message' | translate }} *
1069
+ </label>
1070
+ <textarea
1071
+ id="message"
1072
+ formControlName="message"
1073
+ rows="5"
1074
+ placeholder="Tell us more about your inquiry..."
1075
+ class="w-full px-4 py-3 border-2 border-gray-200 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-primary-500 transition-all resize-vertical"
1076
+ [class.border-danger-500]="contactForm.get('message')?.invalid && contactForm.get('message')?.touched"></textarea>
1077
+ @if (contactForm.get('message')?.invalid && contactForm.get('message')?.touched) {
1078
+ <div class="mt-2 text-sm text-danger-600 flex items-center gap-1">
1079
+ <svg class="h-4 w-4" fill="currentColor" viewBox="0 0 20 20">
1080
+ <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
1081
+ </svg>
1082
+ @if (contactForm.get('message')?.errors?.['required']) {
1083
+ <span>{{ 'contact.form.errors.messageRequired' | translate }}</span>
1084
+ }
1085
+ @if (contactForm.get('message')?.errors?.['minlength']) {
1086
+ <span>{{ 'contact.form.errors.messageMinLength' | translate }}</span>
1087
+ }
1088
+ </div>
1089
+ }
1090
+ </div>
1091
+
1092
+ <!-- Success Message -->
1093
+ @if (submitted && !contactForm.invalid) {
1094
+ <div class="p-4 bg-linear-to-r from-success-50 to-success-100 border-2 border-success-300 rounded-xl animate-fade-in">
1095
+ <div class="flex items-center gap-2">
1096
+ <ng-icon name="heroCheckCircle" size="20" style="color: var(--color-success-600);"></ng-icon>
1097
+ <p class="text-success-700 font-medium">
1098
+ {{ 'contact.form.success' | translate }}
1099
+ </p>
1100
+ </div>
1101
+ </div>
1102
+ }
1103
+
1104
+ <!-- Submit Button -->
1105
+ <app-button
1106
+ type="submit"
1107
+ [fullWidth]="true"
1108
+ [loading]="isSubmitting"
1109
+ [disabled]="contactForm.invalid">
1110
+ <span class="flex items-center justify-center gap-2">
1111
+ @if (!isSubmitting) {
1112
+ <ng-icon name="heroPaperAirplane" size="18"></ng-icon>
1113
+ }
1114
+ {{ isSubmitting ? ('contact.form.sending' | translate) : ('contact.form.submit' | translate) }}
1115
+ </span>
1116
+ </app-button>
1117
+ </form>
1118
+ </div>
1119
+
1120
+ <!-- Contact Info & Resources -->
1121
+ <div class="space-y-6 animate-fade-in animation-delay-200">
1122
+ <!-- Contact Information Card -->
1123
+ <div class="bg-linear-to-br from-primary-600 to-secondary-600 rounded-2xl shadow-xl p-8 text-white">
1124
+ <div class="mb-6">
1125
+ <div class="inline-flex items-center justify-center w-12 h-12 rounded-xl bg-white/20 backdrop-blur-sm mb-4">
1126
+ <ng-icon name="heroMapPin" size="24" style="color: white;"></ng-icon>
1127
+ </div>
1128
+ <h2 class="text-2xl font-bold">{{ 'contact.info.title' | translate }}</h2>
1129
+ <p class="text-primary-100 mt-1">{{ 'contact.info.description' | translate }}</p>
1130
+ </div>
1131
+
1132
+ <div class="space-y-6">
1133
+ <div class="flex items-start gap-4">
1134
+ <div class="shrink-0">
1135
+ <div class="h-12 w-12 bg-white/20 backdrop-blur-sm rounded-xl flex items-center justify-center">
1136
+ <ng-icon name="heroEnvelope" size="24" style="color: white;"></ng-icon>
1137
+ </div>
1138
+ </div>
1139
+ <div>
1140
+ <h3 class="text-lg font-semibold mb-1">{{ 'contact.info.email.label' | translate }}</h3>
1141
+ <p class="text-primary-100">hello&#64;example.com</p>
1142
+ <p class="text-sm text-primary-200 mt-1">{{ 'contact.info.email.description' | translate }}</p>
1143
+ </div>
1144
+ </div>
1145
+
1146
+ <div class="flex items-start gap-4">
1147
+ <div class="shrink-0">
1148
+ <div class="h-12 w-12 bg-white/20 backdrop-blur-sm rounded-xl flex items-center justify-center">
1149
+ <ng-icon name="heroMapPin" size="24" style="color: white;"></ng-icon>
1150
+ </div>
1151
+ </div>
1152
+ <div>
1153
+ <h3 class="text-lg font-semibold mb-1">{{ 'contact.info.location.label' | translate }}</h3>
1154
+ <p class="text-primary-100">{{ 'contact.info.location.value' | translate }}</p>
1155
+ <p class="text-sm text-primary-200 mt-1">{{ 'contact.info.location.description' | translate }}</p>
1156
+ </div>
1157
+ </div>
1158
+
1159
+ <div class="flex items-start gap-4">
1160
+ <div class="shrink-0">
1161
+ <div class="h-12 w-12 bg-white/20 backdrop-blur-sm rounded-xl flex items-center justify-center">
1162
+ <ng-icon name="heroClock" size="24" style="color: white;"></ng-icon>
1163
+ </div>
1164
+ </div>
1165
+ <div>
1166
+ <h3 class="text-lg font-semibold mb-1">{{ 'contact.info.responseTime.label' | translate }}</h3>
1167
+ <p class="text-primary-100">{{ 'contact.info.responseTime.value' | translate }}</p>
1168
+ <p class="text-sm text-primary-200 mt-1">{{ 'contact.info.responseTime.description' | translate }}</p>
1169
+ </div>
1170
+ </div>
1171
+ </div>
1172
+ </div>
1173
+
1174
+ <!-- Resources Card -->
1175
+ <div class="bg-white rounded-2xl shadow-xl p-8 border border-gray-100">
1176
+ <div class="mb-6">
1177
+ <div class="inline-flex items-center justify-center w-12 h-12 rounded-xl bg-linear-to-br from-accent-500 to-secondary-500 mb-4">
1178
+ <svg class="h-6 w-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1179
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"></path>
1180
+ </svg>
1181
+ </div>
1182
+ <h2 class="text-2xl font-bold text-gray-900">{{ 'contact.help.title' | translate }}</h2>
1183
+ <p class="text-gray-600 mt-1">{{ 'contact.help.subtitle' | translate }}</p>
1184
+ </div>
1185
+
1186
+ <ul class="space-y-3">
1187
+ <li>
1188
+ <a href="https://angular.dev/" target="_blank" class="group flex items-center justify-between p-3 rounded-xl hover:bg-primary-50 transition-colors">
1189
+ <div class="flex items-center gap-3">
1190
+ <div class="h-10 w-10 bg-danger-100 rounded-lg flex items-center justify-center group-hover:scale-110 transition-transform">
1191
+ <svg class="h-5 w-5 text-danger-600" fill="currentColor" viewBox="0 0 24 24">
1192
+ <path d="M12 2L2 6l10 5 10-5-10-4zm0 18.5l-7-3.5v-6l7 3.5 7-3.5v6l-7 3.5z"/>
1193
+ </svg>
1194
+ </div>
1195
+ <span class="font-medium text-gray-700 group-hover:text-primary-600 transition-colors">{{ 'contact.help.links.angular' | translate }}</span>
1196
+ </div>
1197
+ <svg class="h-5 w-5 text-gray-400 group-hover:text-primary-600 group-hover:translate-x-1 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1198
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
1199
+ </svg>
1200
+ </a>
1201
+ </li>
1202
+ <li>
1203
+ <a href="https://tailwindcss.com/" target="_blank" class="group flex items-center justify-between p-3 rounded-xl hover:bg-primary-50 transition-colors">
1204
+ <div class="flex items-center gap-3">
1205
+ <div class="h-10 w-10 bg-secondary-100 rounded-lg flex items-center justify-center group-hover:scale-110 transition-transform">
1206
+ <svg class="h-5 w-5 text-secondary-600" fill="currentColor" viewBox="0 0 24 24">
1207
+ <path d="M12 6c-2.67 0-4.33 1.33-5 4 1-1.33 2.17-1.83 3.5-1.5.76.19 1.31.75 1.91 1.36.98 1 2.09 2.14 4.59 2.14 2.67 0 4.33-1.33 5-4-1 1.33-2.17 1.83-3.5 1.5-.76-.19-1.3-.75-1.91-1.36C15.61 7.14 14.5 6 12 6zM7 12c-2.67 0-4.33 1.33-5 4 1-1.33 2.17-1.83 3.5-1.5.76.19 1.3.75 1.91 1.36C8.39 16.86 9.5 18 12 18c2.67 0 4.33-1.33 5-4-1 1.33-2.17 1.83-3.5 1.5-.76-.19-1.3-.75-1.91-1.36C10.61 13.14 9.5 12 7 12z"/>
1208
+ </svg>
1209
+ </div>
1210
+ <span class="font-medium text-gray-700 group-hover:text-primary-600 transition-colors">{{ 'contact.help.links.tailwind' | translate }}</span>
1211
+ </div>
1212
+ <svg class="h-5 w-5 text-gray-400 group-hover:text-primary-600 group-hover:translate-x-1 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1213
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
1214
+ </svg>
1215
+ </a>
1216
+ </li>
1217
+ </ul>
1218
+ </div>
1219
+ </div>
1220
+ </div>
1221
+ </div>
1222
+ </div>
1223
+ `;
1224
+
1225
+ await fs.writeFile(
1226
+ path.join(config.fullPath, 'src/app/features/contact/contact.component.ts'),
1227
+ contactComponentTs
1228
+ );
1229
+
1230
+ await fs.writeFile(
1231
+ path.join(config.fullPath, 'src/app/features/contact/contact.component.html'),
1232
+ contactComponentHtml
1233
+ );
1234
+ }
1235
+
1236
+ module.exports = {
1237
+ createPages,
1238
+ createHomeComponent,
1239
+ createAboutComponent,
1240
+ createContactComponent,
1241
+ };