create-ng-tailwind 2.0.2 → 3.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.
- package/CHANGELOG.md +120 -0
- package/README.md +115 -177
- package/lib/managers/ProjectManager.js +19 -27
- package/lib/templates/starter/advanced-features.js +9 -10
- package/lib/templates/starter/features.js +228 -61
- package/lib/templates/starter/index.js +272 -216
- package/lib/templates/starter/seo-assets.js +141 -0
- package/lib/templates/starter/seo-features.js +290 -0
- package/lib/templates/starter/ui-features.js +1 -2
- package/lib/utils/ai-config.js +97 -58
- package/package.json +3 -4
- package/CLAUDE.md +0 -178
|
@@ -2,8 +2,7 @@ const fs = require('fs-extra');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
|
|
4
4
|
async function createContactComponent(config) {
|
|
5
|
-
const contactComponentTs = `import { Component, inject } from '@angular/core';
|
|
6
|
-
import { CommonModule } from '@angular/common';
|
|
5
|
+
const contactComponentTs = `import { Component, OnInit, inject } from '@angular/core';
|
|
7
6
|
import { ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms';
|
|
8
7
|
import { NgIconComponent, provideIcons } from '@ng-icons/core';
|
|
9
8
|
import { TranslateModule } from '@ngx-translate/core';
|
|
@@ -16,11 +15,12 @@ import {
|
|
|
16
15
|
heroPaperAirplane,
|
|
17
16
|
} from '@ng-icons/heroicons/outline';
|
|
18
17
|
import { ButtonComponent } from '@shared/components/button/button.component';
|
|
18
|
+
import { SeoService } from '@core/services/seo.service';
|
|
19
19
|
|
|
20
20
|
@Component({
|
|
21
21
|
selector: 'app-contact',
|
|
22
22
|
standalone: true,
|
|
23
|
-
imports: [
|
|
23
|
+
imports: [ReactiveFormsModule, NgIconComponent, ButtonComponent, TranslateModule],
|
|
24
24
|
viewProviders: [provideIcons({
|
|
25
25
|
heroEnvelope,
|
|
26
26
|
heroMapPin,
|
|
@@ -30,12 +30,23 @@ import { ButtonComponent } from '@shared/components/button/button.component';
|
|
|
30
30
|
})],
|
|
31
31
|
templateUrl: './contact.component.html'
|
|
32
32
|
})
|
|
33
|
-
export class ContactComponent {
|
|
33
|
+
export class ContactComponent implements OnInit {
|
|
34
34
|
private fb = inject(FormBuilder);
|
|
35
|
+
private seo = inject(SeoService);
|
|
35
36
|
|
|
36
37
|
isSubmitting = false;
|
|
37
38
|
submitted = false;
|
|
38
39
|
|
|
40
|
+
ngOnInit(): void {
|
|
41
|
+
// Set SEO meta tags for Contact page
|
|
42
|
+
this.seo.updateMeta({
|
|
43
|
+
title: 'Contact Us',
|
|
44
|
+
description: 'Get in touch with us. We would love to hear from you and answer any questions you may have.',
|
|
45
|
+
keywords: 'contact, support, get in touch, email',
|
|
46
|
+
ogType: 'website'
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
39
50
|
contactForm = this.fb.group({
|
|
40
51
|
name: ['', [Validators.required, Validators.minLength(2)]],
|
|
41
52
|
email: ['', [Validators.required, Validators.email]],
|
|
@@ -68,11 +79,11 @@ export class ContactComponent {
|
|
|
68
79
|
}`;
|
|
69
80
|
|
|
70
81
|
const contactComponentHtml = `<!-- Hero Section -->
|
|
71
|
-
<section class="relative py-20 bg-linear-to-br from-
|
|
82
|
+
<section class="relative py-20 bg-linear-to-br from-primary-600 via-secondary-600 to-accent-600 overflow-hidden">
|
|
72
83
|
<div class="absolute inset-0 opacity-10">
|
|
73
84
|
<div class="absolute top-0 -left-4 w-96 h-96 bg-white rounded-full mix-blend-multiply filter blur-3xl animate-blob"></div>
|
|
74
|
-
<div class="absolute top-0 -right-4 w-96 h-96 bg-
|
|
75
|
-
<div class="absolute bottom-0 left-1/2 w-96 h-96 bg-
|
|
85
|
+
<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>
|
|
86
|
+
<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>
|
|
76
87
|
</div>
|
|
77
88
|
|
|
78
89
|
<div class="relative container mx-auto px-4 text-center">
|
|
@@ -83,7 +94,7 @@ export class ContactComponent {
|
|
|
83
94
|
<h1 class="text-5xl md:text-6xl lg:text-7xl font-bold text-white mb-6 drop-shadow-lg">
|
|
84
95
|
{{ 'contact.title' | translate }}
|
|
85
96
|
</h1>
|
|
86
|
-
<p class="text-xl md:text-2xl text-
|
|
97
|
+
<p class="text-xl md:text-2xl text-secondary-50 max-w-3xl mx-auto leading-relaxed">
|
|
87
98
|
{{ 'contact.subtitle' | translate }}
|
|
88
99
|
</p>
|
|
89
100
|
</div>
|
|
@@ -91,14 +102,14 @@ export class ContactComponent {
|
|
|
91
102
|
</section>
|
|
92
103
|
|
|
93
104
|
<!-- Main Content -->
|
|
94
|
-
<div class="bg-linear-to-br from-gray-50 to-
|
|
105
|
+
<div class="bg-linear-to-br from-gray-50 to-primary-50 py-16 -mt-8">
|
|
95
106
|
<div class="container mx-auto px-4 sm:px-6 lg:px-8 max-w-6xl">
|
|
96
107
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-12">
|
|
97
108
|
|
|
98
109
|
<!-- Contact Form Card -->
|
|
99
110
|
<div class="bg-white rounded-2xl shadow-xl p-8 border border-gray-100 hover:shadow-2xl transition-shadow animate-fade-in">
|
|
100
111
|
<div class="mb-6">
|
|
101
|
-
<div class="inline-flex items-center justify-center w-12 h-12 rounded-xl bg-linear-to-br from-
|
|
112
|
+
<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">
|
|
102
113
|
<ng-icon name="heroPaperAirplane" size="24" style="color: white;"></ng-icon>
|
|
103
114
|
</div>
|
|
104
115
|
<h2 class="text-2xl font-bold text-gray-900">{{ 'contact.form.title' | translate }}</h2>
|
|
@@ -115,12 +126,13 @@ export class ContactComponent {
|
|
|
115
126
|
id="name"
|
|
116
127
|
type="text"
|
|
117
128
|
formControlName="name"
|
|
129
|
+
autocomplete="name"
|
|
118
130
|
placeholder="John Doe"
|
|
119
|
-
class="w-full px-4 py-3 border-2 border-gray-200 rounded-xl focus:ring-2 focus:ring-
|
|
120
|
-
[class.border-
|
|
121
|
-
[class.focus:ring-
|
|
131
|
+
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"
|
|
132
|
+
[class.border-danger-500]="contactForm.get('name')?.invalid && contactForm.get('name')?.touched"
|
|
133
|
+
[class.focus:ring-danger-500]="contactForm.get('name')?.invalid && contactForm.get('name')?.touched">
|
|
122
134
|
@if (contactForm.get('name')?.invalid && contactForm.get('name')?.touched) {
|
|
123
|
-
<div class="mt-2 text-sm text-
|
|
135
|
+
<div class="mt-2 text-sm text-danger-600 flex items-center gap-1">
|
|
124
136
|
<svg class="h-4 w-4" fill="currentColor" viewBox="0 0 20 20">
|
|
125
137
|
<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"/>
|
|
126
138
|
</svg>
|
|
@@ -143,12 +155,13 @@ export class ContactComponent {
|
|
|
143
155
|
id="email"
|
|
144
156
|
type="email"
|
|
145
157
|
formControlName="email"
|
|
158
|
+
autocomplete="email"
|
|
146
159
|
placeholder="john@example.com"
|
|
147
|
-
class="w-full px-4 py-3 border-2 border-gray-200 rounded-xl focus:ring-2 focus:ring-
|
|
148
|
-
[class.border-
|
|
149
|
-
[class.focus:ring-
|
|
160
|
+
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"
|
|
161
|
+
[class.border-danger-500]="contactForm.get('email')?.invalid && contactForm.get('email')?.touched"
|
|
162
|
+
[class.focus:ring-danger-500]="contactForm.get('email')?.invalid && contactForm.get('email')?.touched">
|
|
150
163
|
@if (contactForm.get('email')?.invalid && contactForm.get('email')?.touched) {
|
|
151
|
-
<div class="mt-2 text-sm text-
|
|
164
|
+
<div class="mt-2 text-sm text-danger-600 flex items-center gap-1">
|
|
152
165
|
<svg class="h-4 w-4" fill="currentColor" viewBox="0 0 20 20">
|
|
153
166
|
<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"/>
|
|
154
167
|
</svg>
|
|
@@ -172,11 +185,11 @@ export class ContactComponent {
|
|
|
172
185
|
type="text"
|
|
173
186
|
formControlName="subject"
|
|
174
187
|
placeholder="How can we help?"
|
|
175
|
-
class="w-full px-4 py-3 border-2 border-gray-200 rounded-xl focus:ring-2 focus:ring-
|
|
176
|
-
[class.border-
|
|
177
|
-
[class.focus:ring-
|
|
188
|
+
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"
|
|
189
|
+
[class.border-danger-500]="contactForm.get('subject')?.invalid && contactForm.get('subject')?.touched"
|
|
190
|
+
[class.focus:ring-danger-500]="contactForm.get('subject')?.invalid && contactForm.get('subject')?.touched">
|
|
178
191
|
@if (contactForm.get('subject')?.invalid && contactForm.get('subject')?.touched) {
|
|
179
|
-
<div class="mt-2 text-sm text-
|
|
192
|
+
<div class="mt-2 text-sm text-danger-600 flex items-center gap-1">
|
|
180
193
|
<svg class="h-4 w-4" fill="currentColor" viewBox="0 0 20 20">
|
|
181
194
|
<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"/>
|
|
182
195
|
</svg>
|
|
@@ -195,12 +208,11 @@ export class ContactComponent {
|
|
|
195
208
|
formControlName="message"
|
|
196
209
|
rows="5"
|
|
197
210
|
placeholder="Tell us more about your inquiry..."
|
|
198
|
-
class="w-full px-4 py-3 border-2 border-gray-200 rounded-xl focus:ring-2 focus:ring-
|
|
199
|
-
[class.border-
|
|
200
|
-
[class.focus:ring-
|
|
201
|
-
</textarea>
|
|
211
|
+
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"
|
|
212
|
+
[class.border-danger-500]="contactForm.get('message')?.invalid && contactForm.get('message')?.touched"
|
|
213
|
+
[class.focus:ring-danger-500]="contactForm.get('message')?.invalid && contactForm.get('message')?.touched"></textarea>
|
|
202
214
|
@if (contactForm.get('message')?.invalid && contactForm.get('message')?.touched) {
|
|
203
|
-
<div class="mt-2 text-sm text-
|
|
215
|
+
<div class="mt-2 text-sm text-danger-600 flex items-center gap-1">
|
|
204
216
|
<svg class="h-4 w-4" fill="currentColor" viewBox="0 0 20 20">
|
|
205
217
|
<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"/>
|
|
206
218
|
</svg>
|
|
@@ -216,10 +228,10 @@ export class ContactComponent {
|
|
|
216
228
|
|
|
217
229
|
<!-- Success Message -->
|
|
218
230
|
@if (submitted && !contactForm.invalid) {
|
|
219
|
-
<div class="p-4 bg-linear-to-r from-
|
|
231
|
+
<div class="p-4 bg-linear-to-r from-success-50 to-success-100 border-2 border-success-300 rounded-xl animate-fade-in">
|
|
220
232
|
<div class="flex items-center gap-2">
|
|
221
|
-
<ng-icon name="heroCheckCircle" size="20" style="color:
|
|
222
|
-
<p class="text-
|
|
233
|
+
<ng-icon name="heroCheckCircle" size="20" style="color: var(--color-success-600);"></ng-icon>
|
|
234
|
+
<p class="text-success-700 font-medium">
|
|
223
235
|
{{ 'contact.form.success' | translate }}
|
|
224
236
|
</p>
|
|
225
237
|
</div>
|
|
@@ -245,13 +257,13 @@ export class ContactComponent {
|
|
|
245
257
|
<!-- Contact Info & Resources -->
|
|
246
258
|
<div class="space-y-6 animate-fade-in animation-delay-200">
|
|
247
259
|
<!-- Contact Information Card -->
|
|
248
|
-
<div class="bg-linear-to-br from-
|
|
260
|
+
<div class="bg-linear-to-br from-primary-600 to-secondary-600 rounded-2xl shadow-xl p-8 text-white">
|
|
249
261
|
<div class="mb-6">
|
|
250
262
|
<div class="inline-flex items-center justify-center w-12 h-12 rounded-xl bg-white/20 backdrop-blur-sm mb-4">
|
|
251
263
|
<ng-icon name="heroMapPin" size="24" style="color: white;"></ng-icon>
|
|
252
264
|
</div>
|
|
253
265
|
<h2 class="text-2xl font-bold">{{ 'contact.info.title' | translate }}</h2>
|
|
254
|
-
<p class="text-
|
|
266
|
+
<p class="text-primary-100 mt-1">{{ 'contact.info.description' | translate }}</p>
|
|
255
267
|
</div>
|
|
256
268
|
|
|
257
269
|
<div class="space-y-6">
|
|
@@ -263,8 +275,8 @@ export class ContactComponent {
|
|
|
263
275
|
</div>
|
|
264
276
|
<div>
|
|
265
277
|
<h3 class="text-lg font-semibold mb-1">{{ 'contact.info.email.label' | translate }}</h3>
|
|
266
|
-
<p class="text-
|
|
267
|
-
<p class="text-sm text-
|
|
278
|
+
<p class="text-primary-100">hello@example.com</p>
|
|
279
|
+
<p class="text-sm text-primary-200 mt-1">{{ 'contact.info.email.description' | translate }}</p>
|
|
268
280
|
</div>
|
|
269
281
|
</div>
|
|
270
282
|
|
|
@@ -276,8 +288,8 @@ export class ContactComponent {
|
|
|
276
288
|
</div>
|
|
277
289
|
<div>
|
|
278
290
|
<h3 class="text-lg font-semibold mb-1">{{ 'contact.info.location.label' | translate }}</h3>
|
|
279
|
-
<p class="text-
|
|
280
|
-
<p class="text-sm text-
|
|
291
|
+
<p class="text-primary-100">{{ 'contact.info.location.value' | translate }}</p>
|
|
292
|
+
<p class="text-sm text-primary-200 mt-1">{{ 'contact.info.location.description' | translate }}</p>
|
|
281
293
|
</div>
|
|
282
294
|
</div>
|
|
283
295
|
|
|
@@ -289,8 +301,8 @@ export class ContactComponent {
|
|
|
289
301
|
</div>
|
|
290
302
|
<div>
|
|
291
303
|
<h3 class="text-lg font-semibold mb-1">{{ 'contact.info.responseTime.label' | translate }}</h3>
|
|
292
|
-
<p class="text-
|
|
293
|
-
<p class="text-sm text-
|
|
304
|
+
<p class="text-primary-100">{{ 'contact.info.responseTime.value' | translate }}</p>
|
|
305
|
+
<p class="text-sm text-primary-200 mt-1">{{ 'contact.info.responseTime.description' | translate }}</p>
|
|
294
306
|
</div>
|
|
295
307
|
</div>
|
|
296
308
|
</div>
|
|
@@ -299,7 +311,7 @@ export class ContactComponent {
|
|
|
299
311
|
<!-- Resources Card -->
|
|
300
312
|
<div class="bg-white rounded-2xl shadow-xl p-8 border border-gray-100">
|
|
301
313
|
<div class="mb-6">
|
|
302
|
-
<div class="inline-flex items-center justify-center w-12 h-12 rounded-xl bg-linear-to-br from-
|
|
314
|
+
<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">
|
|
303
315
|
<svg class="h-6 w-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
304
316
|
<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>
|
|
305
317
|
</svg>
|
|
@@ -310,31 +322,31 @@ export class ContactComponent {
|
|
|
310
322
|
|
|
311
323
|
<ul class="space-y-3">
|
|
312
324
|
<li>
|
|
313
|
-
<a href="https://angular.dev/" target="_blank" class="group flex items-center justify-between p-3 rounded-xl hover:bg-
|
|
325
|
+
<a href="https://angular.dev/" target="_blank" class="group flex items-center justify-between p-3 rounded-xl hover:bg-primary-50 transition-colors">
|
|
314
326
|
<div class="flex items-center gap-3">
|
|
315
|
-
<div class="h-10 w-10 bg-
|
|
316
|
-
<svg class="h-5 w-5 text-
|
|
327
|
+
<div class="h-10 w-10 bg-danger-100 rounded-lg flex items-center justify-center group-hover:scale-110 transition-transform">
|
|
328
|
+
<svg class="h-5 w-5 text-danger-600" fill="currentColor" viewBox="0 0 24 24">
|
|
317
329
|
<path d="M12 2L2 6l10 5 10-5-10-4zm0 18.5l-7-3.5v-6l7 3.5 7-3.5v6l-7 3.5z"/>
|
|
318
330
|
</svg>
|
|
319
331
|
</div>
|
|
320
|
-
<span class="font-medium text-gray-700 group-hover:text-
|
|
332
|
+
<span class="font-medium text-gray-700 group-hover:text-primary-600 transition-colors">{{ 'contact.help.links.angular' | translate }}</span>
|
|
321
333
|
</div>
|
|
322
|
-
<svg class="h-5 w-5 text-gray-400 group-hover:text-
|
|
334
|
+
<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">
|
|
323
335
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
|
324
336
|
</svg>
|
|
325
337
|
</a>
|
|
326
338
|
</li>
|
|
327
339
|
<li>
|
|
328
|
-
<a href="https://tailwindcss.com/" target="_blank" class="group flex items-center justify-between p-3 rounded-xl hover:bg-
|
|
340
|
+
<a href="https://tailwindcss.com/" target="_blank" class="group flex items-center justify-between p-3 rounded-xl hover:bg-primary-50 transition-colors">
|
|
329
341
|
<div class="flex items-center gap-3">
|
|
330
|
-
<div class="h-10 w-10 bg-
|
|
331
|
-
<svg class="h-5 w-5 text-
|
|
342
|
+
<div class="h-10 w-10 bg-secondary-100 rounded-lg flex items-center justify-center group-hover:scale-110 transition-transform">
|
|
343
|
+
<svg class="h-5 w-5 text-secondary-600" fill="currentColor" viewBox="0 0 24 24">
|
|
332
344
|
<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"/>
|
|
333
345
|
</svg>
|
|
334
346
|
</div>
|
|
335
|
-
<span class="font-medium text-gray-700 group-hover:text-
|
|
347
|
+
<span class="font-medium text-gray-700 group-hover:text-primary-600 transition-colors">{{ 'contact.help.links.tailwind' | translate }}</span>
|
|
336
348
|
</div>
|
|
337
|
-
<svg class="h-5 w-5 text-gray-400 group-hover:text-
|
|
349
|
+
<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">
|
|
338
350
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
|
339
351
|
</svg>
|
|
340
352
|
</a>
|
|
@@ -499,18 +511,19 @@ export const appConfig: ApplicationConfig = {
|
|
|
499
511
|
}
|
|
500
512
|
|
|
501
513
|
async function createAppComponent(config) {
|
|
502
|
-
const app = `import { Component, inject } from '@angular/core';
|
|
503
|
-
import { CommonModule } from '@angular/common';
|
|
514
|
+
const app = `import { Component, OnInit, inject } from '@angular/core';
|
|
504
515
|
import { RouterOutlet } from '@angular/router';
|
|
505
516
|
import { HeaderComponent } from './layout/header/header.component';
|
|
506
517
|
import { FooterComponent } from './layout/footer/footer.component';
|
|
507
518
|
import { ToastComponent } from '@shared/components/toast/toast.component';
|
|
508
519
|
import { ModalComponent } from '@shared/components/modal/modal.component';
|
|
509
520
|
import { TranslationService } from '@core/i18n/translation.service';
|
|
521
|
+
import { SeoService } from '@core/services/seo.service';
|
|
522
|
+
import { createOrganizationSchema, createWebSiteSchema } from '@core/utils/structured-data';
|
|
510
523
|
|
|
511
524
|
@Component({
|
|
512
525
|
selector: 'app-root',
|
|
513
|
-
imports: [
|
|
526
|
+
imports: [RouterOutlet, HeaderComponent, FooterComponent, ToastComponent, ModalComponent],
|
|
514
527
|
template: \`
|
|
515
528
|
<div class="min-h-screen flex flex-col bg-white">
|
|
516
529
|
<app-header />
|
|
@@ -529,10 +542,39 @@ import { TranslationService } from '@core/i18n/translation.service';
|
|
|
529
542
|
</div>
|
|
530
543
|
\`
|
|
531
544
|
})
|
|
532
|
-
export class App {
|
|
533
|
-
// Inject
|
|
545
|
+
export class App implements OnInit {
|
|
546
|
+
// Inject services
|
|
534
547
|
private translationService = inject(TranslationService);
|
|
548
|
+
private seo = inject(SeoService);
|
|
549
|
+
|
|
535
550
|
title = '${config.projectName}';
|
|
551
|
+
|
|
552
|
+
ngOnInit(): void {
|
|
553
|
+
// Add global Organization structured data
|
|
554
|
+
const organizationData = createOrganizationSchema({
|
|
555
|
+
name: '${config.projectName}',
|
|
556
|
+
url: 'https://example.com', // TODO: Update with your production URL
|
|
557
|
+
logo: '/assets/images/logo.svg',
|
|
558
|
+
description: 'A modern Angular application built with best practices',
|
|
559
|
+
sameAs: [
|
|
560
|
+
// 'https://twitter.com/yourhandle',
|
|
561
|
+
// 'https://facebook.com/yourpage',
|
|
562
|
+
// 'https://linkedin.com/company/yourcompany'
|
|
563
|
+
]
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
const websiteData = createWebSiteSchema({
|
|
567
|
+
name: '${config.projectName}',
|
|
568
|
+
url: 'https://example.com', // TODO: Update with your production URL
|
|
569
|
+
description: 'A modern Angular application with Tailwind CSS, SEO optimization, and i18n support'
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
// Add structured data to the page
|
|
573
|
+
this.seo.addStructuredData({
|
|
574
|
+
'@context': 'https://schema.org',
|
|
575
|
+
'@graph': [organizationData, websiteData]
|
|
576
|
+
});
|
|
577
|
+
}
|
|
536
578
|
}`;
|
|
537
579
|
|
|
538
580
|
const appHtml = `<!-- This file is not used in standalone components -->
|
|
@@ -544,15 +586,140 @@ export class App {
|
|
|
544
586
|
}
|
|
545
587
|
|
|
546
588
|
async function createStyles(config) {
|
|
547
|
-
// Main styles
|
|
589
|
+
// Main styles with Tailwind v4 theme system
|
|
548
590
|
const mainStyles = `@import "tailwindcss";
|
|
549
591
|
|
|
550
|
-
/*
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
592
|
+
/* ============================================
|
|
593
|
+
TAILWIND V4 THEME CONFIGURATION
|
|
594
|
+
============================================
|
|
595
|
+
|
|
596
|
+
This section defines the theme colors for your application.
|
|
597
|
+
Tailwind v4 uses the @theme directive to create custom color scales
|
|
598
|
+
that automatically generate utility classes (bg-*, text-*, border-*, etc.)
|
|
599
|
+
|
|
600
|
+
To customize your theme:
|
|
601
|
+
1. Change the color values below to match your brand
|
|
602
|
+
2. All components will automatically use the new colors
|
|
603
|
+
3. Color format: Use hex colors (e.g., #3b82f6) or rgb (e.g., rgb(59 130 246))
|
|
604
|
+
|
|
605
|
+
Example: Change primary-500 from #3b82f6 to #8b5cf6 for purple theme
|
|
606
|
+
*/
|
|
607
|
+
|
|
608
|
+
@theme {
|
|
609
|
+
/* PRIMARY COLOR SCALE - Main brand color */
|
|
610
|
+
--color-primary-50: #eff6ff;
|
|
611
|
+
--color-primary-100: #dbeafe;
|
|
612
|
+
--color-primary-200: #bfdbfe;
|
|
613
|
+
--color-primary-300: #93c5fd;
|
|
614
|
+
--color-primary-400: #60a5fa;
|
|
615
|
+
--color-primary-500: #3b82f6; /* Main primary color - CHANGE THIS for your brand */
|
|
616
|
+
--color-primary-600: #2563eb; /* Primary hover state */
|
|
617
|
+
--color-primary-700: #1d4ed8;
|
|
618
|
+
--color-primary-800: #1e40af;
|
|
619
|
+
--color-primary-900: #1e3a8a;
|
|
620
|
+
--color-primary-950: #172554;
|
|
621
|
+
|
|
622
|
+
/* SECONDARY COLOR SCALE - Secondary brand color */
|
|
623
|
+
--color-secondary-50: #ecfeff;
|
|
624
|
+
--color-secondary-100: #cffafe;
|
|
625
|
+
--color-secondary-200: #a5f3fc;
|
|
626
|
+
--color-secondary-300: #67e8f9;
|
|
627
|
+
--color-secondary-400: #22d3ee;
|
|
628
|
+
--color-secondary-500: #06b6d4; /* Main secondary color - CHANGE THIS for your brand */
|
|
629
|
+
--color-secondary-600: #0891b2; /* Secondary hover state */
|
|
630
|
+
--color-secondary-700: #0e7490;
|
|
631
|
+
--color-secondary-800: #155e75;
|
|
632
|
+
--color-secondary-900: #164e63;
|
|
633
|
+
--color-secondary-950: #083344;
|
|
634
|
+
|
|
635
|
+
/* ACCENT COLOR SCALE - Accent/highlight color */
|
|
636
|
+
--color-accent-50: #faf5ff;
|
|
637
|
+
--color-accent-100: #f3e8ff;
|
|
638
|
+
--color-accent-200: #e9d5ff;
|
|
639
|
+
--color-accent-300: #d8b4fe;
|
|
640
|
+
--color-accent-400: #c084fc;
|
|
641
|
+
--color-accent-500: #a855f7; /* Main accent color */
|
|
642
|
+
--color-accent-600: #9333ea; /* Accent hover state */
|
|
643
|
+
--color-accent-700: #7e22ce;
|
|
644
|
+
--color-accent-800: #6b21a8;
|
|
645
|
+
--color-accent-900: #581c87;
|
|
646
|
+
--color-accent-950: #3b0764;
|
|
647
|
+
|
|
648
|
+
/* SUCCESS COLOR SCALE - For success states */
|
|
649
|
+
--color-success-50: #f0fdf4;
|
|
650
|
+
--color-success-100: #dcfce7;
|
|
651
|
+
--color-success-200: #bbf7d0;
|
|
652
|
+
--color-success-300: #86efac;
|
|
653
|
+
--color-success-400: #4ade80;
|
|
654
|
+
--color-success-500: #22c55e; /* Main success color */
|
|
655
|
+
--color-success-600: #16a34a;
|
|
656
|
+
--color-success-700: #15803d;
|
|
657
|
+
--color-success-800: #166534;
|
|
658
|
+
--color-success-900: #14532d;
|
|
659
|
+
--color-success-950: #052e16;
|
|
660
|
+
|
|
661
|
+
/* DANGER COLOR SCALE - For error/danger states */
|
|
662
|
+
--color-danger-50: #fef2f2;
|
|
663
|
+
--color-danger-100: #fee2e2;
|
|
664
|
+
--color-danger-200: #fecaca;
|
|
665
|
+
--color-danger-300: #fca5a5;
|
|
666
|
+
--color-danger-400: #f87171;
|
|
667
|
+
--color-danger-500: #ef4444; /* Main danger color */
|
|
668
|
+
--color-danger-600: #dc2626;
|
|
669
|
+
--color-danger-700: #b91c1c;
|
|
670
|
+
--color-danger-800: #991b1b;
|
|
671
|
+
--color-danger-900: #7f1d1d;
|
|
672
|
+
--color-danger-950: #450a0a;
|
|
673
|
+
|
|
674
|
+
/* WARNING COLOR SCALE - For warning states */
|
|
675
|
+
--color-warning-50: #fffbeb;
|
|
676
|
+
--color-warning-100: #fef3c7;
|
|
677
|
+
--color-warning-200: #fde68a;
|
|
678
|
+
--color-warning-300: #fcd34d;
|
|
679
|
+
--color-warning-400: #fbbf24;
|
|
680
|
+
--color-warning-500: #f59e0b; /* Main warning color */
|
|
681
|
+
--color-warning-600: #d97706;
|
|
682
|
+
--color-warning-700: #b45309;
|
|
683
|
+
--color-warning-800: #92400e;
|
|
684
|
+
--color-warning-900: #78350f;
|
|
685
|
+
--color-warning-950: #451a03;
|
|
686
|
+
|
|
687
|
+
/* INFO COLOR SCALE - For informational states */
|
|
688
|
+
--color-info-50: #eff6ff;
|
|
689
|
+
--color-info-100: #dbeafe;
|
|
690
|
+
--color-info-200: #bfdbfe;
|
|
691
|
+
--color-info-300: #93c5fd;
|
|
692
|
+
--color-info-400: #60a5fa;
|
|
693
|
+
--color-info-500: #3b82f6; /* Main info color */
|
|
694
|
+
--color-info-600: #2563eb;
|
|
695
|
+
--color-info-700: #1d4ed8;
|
|
696
|
+
--color-info-800: #1e40af;
|
|
697
|
+
--color-info-900: #1e3a8a;
|
|
698
|
+
--color-info-950: #172554;
|
|
554
699
|
}
|
|
555
700
|
|
|
701
|
+
/* ============================================
|
|
702
|
+
USAGE EXAMPLES
|
|
703
|
+
============================================
|
|
704
|
+
|
|
705
|
+
After defining colors in @theme, you can use them in your templates:
|
|
706
|
+
|
|
707
|
+
- bg-primary-500 (primary background)
|
|
708
|
+
- text-primary-600 (primary text)
|
|
709
|
+
- border-primary-500 (primary border)
|
|
710
|
+
- hover:bg-primary-600 (primary hover)
|
|
711
|
+
- focus:ring-primary-500 (primary focus ring)
|
|
712
|
+
|
|
713
|
+
- bg-secondary-500 (secondary background)
|
|
714
|
+
- bg-accent-500 (accent background)
|
|
715
|
+
- bg-success-500 (success background)
|
|
716
|
+
- bg-danger-500 (danger background)
|
|
717
|
+
- bg-warning-500 (warning background)
|
|
718
|
+
- bg-info-500 (info background)
|
|
719
|
+
|
|
720
|
+
All Tailwind utilities work: bg-*, text-*, border-*, ring-*, etc.
|
|
721
|
+
*/
|
|
722
|
+
|
|
556
723
|
/* RTL Support */
|
|
557
724
|
html[dir="rtl"] {
|
|
558
725
|
direction: rtl;
|
|
@@ -615,9 +782,9 @@ html[dir="rtl"] .text-right {
|
|
|
615
782
|
@apply bg-gray-400;
|
|
616
783
|
}
|
|
617
784
|
|
|
618
|
-
/* Focus styles for accessibility */
|
|
785
|
+
/* Focus styles for accessibility - now using theme colors */
|
|
619
786
|
.focus-visible {
|
|
620
|
-
@apply focus:outline-none focus:ring-2 focus:ring-
|
|
787
|
+
@apply focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2;
|
|
621
788
|
}
|
|
622
789
|
|
|
623
790
|
/* Animation classes */
|