create-ng-tailwind 3.0.1 → 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 -344
  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 +53 -4055
  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
@@ -0,0 +1,1371 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const starter = require('../starter');
4
+
5
+ /**
6
+ * SaaS Template
7
+ * Extends starter template with:
8
+ * - Pricing page with plan comparison
9
+ * - Subscription management
10
+ * - User dashboard
11
+ * - Feature highlights
12
+ * - FAQ section
13
+ * - Testimonials
14
+ */
15
+ const saas = {
16
+ info: {
17
+ name: 'SaaS',
18
+ description: 'Software as a Service template with pricing, subscriptions, and user dashboard',
19
+ features: [
20
+ ...starter.info.features,
21
+ 'Pricing page with plan comparison',
22
+ 'Subscription management UI',
23
+ 'User dashboard with usage stats',
24
+ 'Feature comparison table',
25
+ 'FAQ accordion',
26
+ 'Customer testimonials',
27
+ 'CTA sections',
28
+ 'Usage metrics display',
29
+ ],
30
+ },
31
+
32
+ async apply(config, spinner) {
33
+ const chalk = require('chalk');
34
+
35
+ const completeStep = (message) => {
36
+ if (spinner) {
37
+ spinner.stop();
38
+ console.log(chalk.green(' ✔') + chalk.white(' ' + message));
39
+ spinner.start();
40
+ }
41
+ };
42
+
43
+ // Step 1: Apply starter template
44
+ if (spinner) spinner.update('Setting up starter foundation...');
45
+ await starter.apply(config, null);
46
+
47
+ // Step 2: Create SaaS-specific directories
48
+ if (spinner) spinner.update('Setting up SaaS structure...');
49
+ await this.createDirectories(config);
50
+
51
+ // Step 3: Create services
52
+ await this.createServices(config);
53
+
54
+ // Step 4: Create components
55
+ await this.createComponents(config);
56
+
57
+ // Step 5: Create pages
58
+ await this.createPages(config);
59
+
60
+ // Step 6: Update routing
61
+ await this.createRouting(config);
62
+
63
+ // Step 7: Add i18n translations
64
+ await this.updateI18n(config);
65
+
66
+ // Step 8: Format code
67
+ const base = require('../base');
68
+ await base.formatCode(config);
69
+
70
+ if (spinner) spinner.stop();
71
+
72
+ console.log('');
73
+ completeStep('SaaS template created');
74
+ completeStep('Pricing page with plans');
75
+ completeStep('User dashboard');
76
+ completeStep('Subscription management');
77
+ completeStep('Feature comparison');
78
+ console.log('');
79
+ },
80
+
81
+ async createDirectories(config) {
82
+ const directories = [
83
+ 'src/app/features/pricing',
84
+ 'src/app/features/dashboard',
85
+ 'src/app/features/dashboard/overview',
86
+ 'src/app/features/dashboard/subscription',
87
+ 'src/app/features/dashboard/usage',
88
+ 'src/app/features/features',
89
+ 'src/app/shared/components/pricing-card',
90
+ 'src/app/shared/components/faq',
91
+ 'src/app/shared/components/testimonial',
92
+ 'src/app/shared/components/feature-card',
93
+ 'src/app/shared/components/stats-card',
94
+ 'src/app/shared/components/usage-chart',
95
+ ];
96
+
97
+ for (const dir of directories) {
98
+ await fs.ensureDir(path.join(config.fullPath, dir));
99
+ }
100
+ },
101
+
102
+ async createServices(config) {
103
+ const subscriptionService = `import { Injectable, signal, computed } from '@angular/core';
104
+
105
+ export interface Plan {
106
+ id: string;
107
+ name: string;
108
+ description: string;
109
+ price: number;
110
+ priceYearly: number;
111
+ features: string[];
112
+ highlighted?: boolean;
113
+ cta: string;
114
+ }
115
+
116
+ export interface Subscription {
117
+ planId: string;
118
+ status: 'active' | 'cancelled' | 'past_due' | 'trialing';
119
+ currentPeriodEnd: Date;
120
+ cancelAtPeriodEnd: boolean;
121
+ }
122
+
123
+ export interface UsageStats {
124
+ apiCalls: { used: number; limit: number };
125
+ storage: { used: number; limit: number };
126
+ teamMembers: { used: number; limit: number };
127
+ projects: { used: number; limit: number };
128
+ }
129
+
130
+ @Injectable({ providedIn: 'root' })
131
+ export class SubscriptionService {
132
+ private plansSignal = signal<Plan[]>(this.getPlans());
133
+ private subscriptionSignal = signal<Subscription | null>(this.getMockSubscription());
134
+ private usageSignal = signal<UsageStats>(this.getMockUsage());
135
+
136
+ plans = this.plansSignal.asReadonly();
137
+ subscription = this.subscriptionSignal.asReadonly();
138
+ usage = this.usageSignal.asReadonly();
139
+
140
+ currentPlan = computed(() => {
141
+ const sub = this.subscriptionSignal();
142
+ if (!sub) return null;
143
+ return this.plansSignal().find(p => p.id === sub.planId) || null;
144
+ });
145
+
146
+ isSubscribed = computed(() => {
147
+ const sub = this.subscriptionSignal();
148
+ return sub !== null && sub.status === 'active';
149
+ });
150
+
151
+ daysRemaining = computed(() => {
152
+ const sub = this.subscriptionSignal();
153
+ if (!sub) return 0;
154
+ const now = new Date();
155
+ const end = new Date(sub.currentPeriodEnd);
156
+ const diff = end.getTime() - now.getTime();
157
+ return Math.ceil(diff / (1000 * 60 * 60 * 24));
158
+ });
159
+
160
+ getPlanById(id: string): Plan | undefined {
161
+ return this.plansSignal().find(p => p.id === id);
162
+ }
163
+
164
+ subscribe(planId: string): Promise<boolean> {
165
+ // Mock subscription - in production, integrate with Stripe/Paddle
166
+ return new Promise(resolve => {
167
+ setTimeout(() => {
168
+ this.subscriptionSignal.set({
169
+ planId,
170
+ status: 'active',
171
+ currentPeriodEnd: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
172
+ cancelAtPeriodEnd: false,
173
+ });
174
+ resolve(true);
175
+ }, 1000);
176
+ });
177
+ }
178
+
179
+ cancelSubscription(): Promise<boolean> {
180
+ return new Promise(resolve => {
181
+ setTimeout(() => {
182
+ this.subscriptionSignal.update(sub => sub ? { ...sub, cancelAtPeriodEnd: true } : null);
183
+ resolve(true);
184
+ }, 500);
185
+ });
186
+ }
187
+
188
+ private getPlans(): Plan[] {
189
+ return [
190
+ {
191
+ id: 'free',
192
+ name: 'Free',
193
+ description: 'Perfect for getting started',
194
+ price: 0,
195
+ priceYearly: 0,
196
+ features: [
197
+ '1,000 API calls/month',
198
+ '100MB storage',
199
+ '1 team member',
200
+ '3 projects',
201
+ 'Community support',
202
+ ],
203
+ cta: 'Get Started',
204
+ },
205
+ {
206
+ id: 'pro',
207
+ name: 'Pro',
208
+ description: 'Best for growing teams',
209
+ price: 29,
210
+ priceYearly: 290,
211
+ features: [
212
+ '50,000 API calls/month',
213
+ '10GB storage',
214
+ '5 team members',
215
+ 'Unlimited projects',
216
+ 'Priority support',
217
+ 'Advanced analytics',
218
+ 'Custom integrations',
219
+ ],
220
+ highlighted: true,
221
+ cta: 'Start Free Trial',
222
+ },
223
+ {
224
+ id: 'enterprise',
225
+ name: 'Enterprise',
226
+ description: 'For large organizations',
227
+ price: 99,
228
+ priceYearly: 990,
229
+ features: [
230
+ 'Unlimited API calls',
231
+ '100GB storage',
232
+ 'Unlimited team members',
233
+ 'Unlimited projects',
234
+ '24/7 dedicated support',
235
+ 'Advanced analytics',
236
+ 'Custom integrations',
237
+ 'SLA guarantee',
238
+ 'On-premise option',
239
+ ],
240
+ cta: 'Contact Sales',
241
+ },
242
+ ];
243
+ }
244
+
245
+ private getMockSubscription(): Subscription {
246
+ return {
247
+ planId: 'pro',
248
+ status: 'active',
249
+ currentPeriodEnd: new Date(Date.now() + 15 * 24 * 60 * 60 * 1000),
250
+ cancelAtPeriodEnd: false,
251
+ };
252
+ }
253
+
254
+ private getMockUsage(): UsageStats {
255
+ return {
256
+ apiCalls: { used: 32500, limit: 50000 },
257
+ storage: { used: 4.2, limit: 10 },
258
+ teamMembers: { used: 3, limit: 5 },
259
+ projects: { used: 8, limit: -1 }, // -1 means unlimited
260
+ };
261
+ }
262
+ }`;
263
+
264
+ await fs.writeFile(
265
+ path.join(config.fullPath, 'src/app/core/services/subscription.service.ts'),
266
+ subscriptionService
267
+ );
268
+ },
269
+
270
+ async createComponents(config) {
271
+ // Pricing Card
272
+ const pricingCard = `import { Component, Input, Output, EventEmitter } from '@angular/core';
273
+ import { CurrencyPipe } from '@angular/common';
274
+ import { NgIconComponent, provideIcons } from '@ng-icons/core';
275
+ import { heroCheck } from '@ng-icons/heroicons/outline';
276
+ import { Plan } from '@core/services/subscription.service';
277
+
278
+ @Component({
279
+ selector: 'app-pricing-card',
280
+ standalone: true,
281
+ imports: [CurrencyPipe, NgIconComponent],
282
+ viewProviders: [provideIcons({ heroCheck })],
283
+ template: \`
284
+ <div
285
+ class="relative rounded-2xl border-2 p-8 transition-all"
286
+ [class.border-primary-500]="plan.highlighted"
287
+ [class.border-gray-200]="!plan.highlighted"
288
+ [class.shadow-xl]="plan.highlighted"
289
+ [class.scale-105]="plan.highlighted">
290
+
291
+ @if (plan.highlighted) {
292
+ <div class="absolute -top-4 left-1/2 -translate-x-1/2">
293
+ <span class="rounded-full bg-primary-500 px-4 py-1 text-sm font-semibold text-white">
294
+ Most Popular
295
+ </span>
296
+ </div>
297
+ }
298
+
299
+ <div class="text-center">
300
+ <h3 class="text-xl font-bold text-gray-900">{{ plan.name }}</h3>
301
+ <p class="mt-2 text-gray-600">{{ plan.description }}</p>
302
+
303
+ <div class="mt-6">
304
+ <span class="text-5xl font-bold text-gray-900">
305
+ {{ isYearly ? plan.priceYearly : plan.price | currency:'USD':'symbol':'1.0-0' }}
306
+ </span>
307
+ @if (plan.price > 0) {
308
+ <span class="text-gray-500">/{{ isYearly ? 'year' : 'month' }}</span>
309
+ }
310
+ </div>
311
+
312
+ @if (isYearly && plan.price > 0) {
313
+ <p class="mt-2 text-sm text-success-600">
314
+ Save {{ (plan.price * 12) - plan.priceYearly | currency:'USD':'symbol':'1.0-0' }}/year
315
+ </p>
316
+ }
317
+ </div>
318
+
319
+ <ul class="mt-8 space-y-4">
320
+ @for (feature of plan.features; track feature) {
321
+ <li class="flex items-center gap-3">
322
+ <ng-icon name="heroCheck" size="20" class="text-success-500"></ng-icon>
323
+ <span class="text-gray-700">{{ feature }}</span>
324
+ </li>
325
+ }
326
+ </ul>
327
+
328
+ <button
329
+ (click)="onSelect.emit(plan)"
330
+ class="mt-8 w-full rounded-lg py-3 font-semibold transition-all"
331
+ [class.bg-primary-500]="plan.highlighted"
332
+ [class.text-white]="plan.highlighted"
333
+ [class.hover:bg-primary-600]="plan.highlighted"
334
+ [class.bg-gray-100]="!plan.highlighted"
335
+ [class.text-gray-900]="!plan.highlighted"
336
+ [class.hover:bg-gray-200]="!plan.highlighted">
337
+ {{ plan.cta }}
338
+ </button>
339
+ </div>
340
+ \`,
341
+ })
342
+ export class PricingCardComponent {
343
+ @Input({ required: true }) plan!: Plan;
344
+ @Input() isYearly = false;
345
+ @Output() onSelect = new EventEmitter<Plan>();
346
+ }`;
347
+
348
+ await fs.writeFile(
349
+ path.join(
350
+ config.fullPath,
351
+ 'src/app/shared/components/pricing-card/pricing-card.component.ts'
352
+ ),
353
+ pricingCard
354
+ );
355
+
356
+ // FAQ Component
357
+ const faq = `import { Component, Input, signal } from '@angular/core';
358
+ import { NgIconComponent, provideIcons } from '@ng-icons/core';
359
+ import { heroChevronDown } from '@ng-icons/heroicons/outline';
360
+
361
+ export interface FaqItem {
362
+ question: string;
363
+ answer: string;
364
+ }
365
+
366
+ @Component({
367
+ selector: 'app-faq',
368
+ standalone: true,
369
+ imports: [NgIconComponent],
370
+ viewProviders: [provideIcons({ heroChevronDown })],
371
+ template: \`
372
+ <div class="space-y-4">
373
+ @for (item of items; track item.question; let i = $index) {
374
+ <div class="rounded-xl border border-gray-200 bg-white">
375
+ <button
376
+ (click)="toggle(i)"
377
+ class="flex w-full items-center justify-between p-6 text-left">
378
+ <span class="font-semibold text-gray-900">{{ item.question }}</span>
379
+ <ng-icon
380
+ name="heroChevronDown"
381
+ size="20"
382
+ class="text-gray-500 transition-transform"
383
+ [class.rotate-180]="openIndex() === i">
384
+ </ng-icon>
385
+ </button>
386
+ @if (openIndex() === i) {
387
+ <div class="border-t border-gray-200 px-6 py-4">
388
+ <p class="text-gray-600">{{ item.answer }}</p>
389
+ </div>
390
+ }
391
+ </div>
392
+ }
393
+ </div>
394
+ \`,
395
+ })
396
+ export class FaqComponent {
397
+ @Input({ required: true }) items!: FaqItem[];
398
+
399
+ openIndex = signal<number | null>(null);
400
+
401
+ toggle(index: number): void {
402
+ this.openIndex.set(this.openIndex() === index ? null : index);
403
+ }
404
+ }`;
405
+
406
+ await fs.writeFile(
407
+ path.join(config.fullPath, 'src/app/shared/components/faq/faq.component.ts'),
408
+ faq
409
+ );
410
+
411
+ // Stats Card
412
+ const statsCard = `import { Component, Input } from '@angular/core';
413
+ import { NgIconComponent } from '@ng-icons/core';
414
+
415
+ @Component({
416
+ selector: 'app-stats-card',
417
+ standalone: true,
418
+ imports: [NgIconComponent],
419
+ template: \`
420
+ <div class="rounded-xl border border-gray-200 bg-white p-6">
421
+ <div class="flex items-center justify-between">
422
+ <div>
423
+ <p class="text-sm font-medium text-gray-500">{{ label }}</p>
424
+ <p class="mt-1 text-3xl font-bold text-gray-900">{{ value }}</p>
425
+ @if (subtext) {
426
+ <p class="mt-1 text-sm" [class.text-success-600]="trend === 'up'" [class.text-danger-600]="trend === 'down'" [class.text-gray-500]="!trend">
427
+ {{ subtext }}
428
+ </p>
429
+ }
430
+ </div>
431
+ @if (icon) {
432
+ <div class="flex h-12 w-12 items-center justify-center rounded-xl" [class]="iconBgClass">
433
+ <ng-icon [name]="icon" size="24" [class]="iconClass"></ng-icon>
434
+ </div>
435
+ }
436
+ </div>
437
+
438
+ @if (showProgress) {
439
+ <div class="mt-4">
440
+ <div class="flex justify-between text-sm">
441
+ <span class="text-gray-500">{{ progressLabel }}</span>
442
+ <span class="font-medium text-gray-900">{{ progressPercent }}%</span>
443
+ </div>
444
+ <div class="mt-2 h-2 w-full overflow-hidden rounded-full bg-gray-100">
445
+ <div
446
+ class="h-full rounded-full transition-all"
447
+ [class.bg-primary-500]="progressPercent < 80"
448
+ [class.bg-warning-500]="progressPercent >= 80 && progressPercent < 95"
449
+ [class.bg-danger-500]="progressPercent >= 95"
450
+ [style.width.%]="progressPercent">
451
+ </div>
452
+ </div>
453
+ </div>
454
+ }
455
+ </div>
456
+ \`,
457
+ })
458
+ export class StatsCardComponent {
459
+ @Input({ required: true }) label!: string;
460
+ @Input({ required: true }) value!: string | number;
461
+ @Input() subtext?: string;
462
+ @Input() trend?: 'up' | 'down';
463
+ @Input() icon?: string;
464
+ @Input() iconBgClass = 'bg-primary-100';
465
+ @Input() iconClass = 'text-primary-600';
466
+ @Input() showProgress = false;
467
+ @Input() progressLabel = 'Used';
468
+ @Input() progressPercent = 0;
469
+ }`;
470
+
471
+ await fs.writeFile(
472
+ path.join(config.fullPath, 'src/app/shared/components/stats-card/stats-card.component.ts'),
473
+ statsCard
474
+ );
475
+
476
+ // Feature Card
477
+ const featureCard = `import { Component, Input } from '@angular/core';
478
+ import { NgIconComponent } from '@ng-icons/core';
479
+
480
+ @Component({
481
+ selector: 'app-feature-card',
482
+ standalone: true,
483
+ imports: [NgIconComponent],
484
+ template: \`
485
+ <div class="rounded-xl border border-gray-200 bg-white p-6 transition-all hover:shadow-lg hover:-translate-y-1">
486
+ <div class="flex h-12 w-12 items-center justify-center rounded-xl" [class]="iconBgClass">
487
+ <ng-icon [name]="icon" size="24" [class]="iconClass"></ng-icon>
488
+ </div>
489
+ <h3 class="mt-4 text-lg font-semibold text-gray-900">{{ title }}</h3>
490
+ <p class="mt-2 text-gray-600">{{ description }}</p>
491
+ </div>
492
+ \`,
493
+ })
494
+ export class FeatureCardComponent {
495
+ @Input({ required: true }) icon!: string;
496
+ @Input({ required: true }) title!: string;
497
+ @Input({ required: true }) description!: string;
498
+ @Input() iconBgClass = 'bg-primary-100';
499
+ @Input() iconClass = 'text-primary-600';
500
+ }`;
501
+
502
+ await fs.writeFile(
503
+ path.join(
504
+ config.fullPath,
505
+ 'src/app/shared/components/feature-card/feature-card.component.ts'
506
+ ),
507
+ featureCard
508
+ );
509
+
510
+ // Testimonial Card
511
+ const testimonial = `import { Component, Input } from '@angular/core';
512
+ import { NgIconComponent, provideIcons } from '@ng-icons/core';
513
+ import { heroStarSolid } from '@ng-icons/heroicons/solid';
514
+
515
+ export interface Testimonial {
516
+ content: string;
517
+ author: string;
518
+ role: string;
519
+ company: string;
520
+ avatar: string;
521
+ rating: number;
522
+ }
523
+
524
+ @Component({
525
+ selector: 'app-testimonial',
526
+ standalone: true,
527
+ imports: [NgIconComponent],
528
+ viewProviders: [provideIcons({ heroStarSolid })],
529
+ template: \`
530
+ <div class="rounded-2xl border border-gray-200 bg-white p-8">
531
+ <div class="flex gap-1 mb-4">
532
+ @for (star of stars; track star) {
533
+ <ng-icon name="heroStarSolid" size="20" class="text-amber-400"></ng-icon>
534
+ }
535
+ </div>
536
+
537
+ <p class="text-gray-700 italic">"{{ testimonial.content }}"</p>
538
+
539
+ <div class="mt-6 flex items-center gap-4">
540
+ <img [src]="testimonial.avatar" [alt]="testimonial.author" class="h-12 w-12 rounded-full object-cover" />
541
+ <div>
542
+ <p class="font-semibold text-gray-900">{{ testimonial.author }}</p>
543
+ <p class="text-sm text-gray-500">{{ testimonial.role }}, {{ testimonial.company }}</p>
544
+ </div>
545
+ </div>
546
+ </div>
547
+ \`,
548
+ })
549
+ export class TestimonialComponent {
550
+ @Input({ required: true }) testimonial!: Testimonial;
551
+
552
+ get stars(): number[] {
553
+ return Array.from({ length: this.testimonial.rating }, (_, i) => i);
554
+ }
555
+ }`;
556
+
557
+ await fs.writeFile(
558
+ path.join(config.fullPath, 'src/app/shared/components/testimonial/testimonial.component.ts'),
559
+ testimonial
560
+ );
561
+ },
562
+
563
+ async createPages(config) {
564
+ // Pricing Page
565
+ const pricing = `import { Component, inject, signal } from '@angular/core';
566
+ import { Router } from '@angular/router';
567
+ import { TranslateModule } from '@ngx-translate/core';
568
+ import { PricingCardComponent } from '@shared/components/pricing-card/pricing-card.component';
569
+ import { FaqComponent, FaqItem } from '@shared/components/faq/faq.component';
570
+ import { TestimonialComponent, Testimonial } from '@shared/components/testimonial/testimonial.component';
571
+ import { SubscriptionService, Plan } from '@core/services/subscription.service';
572
+
573
+ @Component({
574
+ selector: 'app-pricing',
575
+ standalone: true,
576
+ imports: [TranslateModule, PricingCardComponent, FaqComponent, TestimonialComponent],
577
+ template: \`
578
+ <div>
579
+ <!-- Hero -->
580
+ <section class="bg-gradient-to-b from-gray-50 to-white py-20">
581
+ <div class="mx-auto max-w-7xl px-4 text-center sm:px-6 lg:px-8">
582
+ <h1 class="text-4xl font-bold text-gray-900 sm:text-5xl">
583
+ {{ 'pricing.title' | translate }}
584
+ </h1>
585
+ <p class="mt-4 text-xl text-gray-600">
586
+ {{ 'pricing.subtitle' | translate }}
587
+ </p>
588
+
589
+ <!-- Billing Toggle -->
590
+ <div class="mt-8 flex items-center justify-center gap-4">
591
+ <span [class.text-gray-900]="!isYearly()" [class.text-gray-500]="isYearly()">Monthly</span>
592
+ <button
593
+ (click)="isYearly.set(!isYearly())"
594
+ class="relative h-8 w-14 rounded-full bg-primary-500 transition-colors">
595
+ <span
596
+ class="absolute top-1 h-6 w-6 rounded-full bg-white transition-all"
597
+ [class.left-1]="!isYearly()"
598
+ [class.left-7]="isYearly()">
599
+ </span>
600
+ </button>
601
+ <span [class.text-gray-900]="isYearly()" [class.text-gray-500]="!isYearly()">
602
+ Yearly
603
+ <span class="ml-1 rounded-full bg-success-100 px-2 py-0.5 text-xs font-medium text-success-700">
604
+ Save 20%
605
+ </span>
606
+ </span>
607
+ </div>
608
+ </div>
609
+ </section>
610
+
611
+ <!-- Pricing Cards -->
612
+ <section class="py-20">
613
+ <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
614
+ <div class="grid gap-8 md:grid-cols-3">
615
+ @for (plan of subscriptionService.plans(); track plan.id) {
616
+ <app-pricing-card
617
+ [plan]="plan"
618
+ [isYearly]="isYearly()"
619
+ (onSelect)="selectPlan($event)">
620
+ </app-pricing-card>
621
+ }
622
+ </div>
623
+ </div>
624
+ </section>
625
+
626
+ <!-- Feature Comparison -->
627
+ <section class="bg-gray-50 py-20">
628
+ <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
629
+ <h2 class="mb-12 text-center text-3xl font-bold text-gray-900">
630
+ {{ 'pricing.compareFeatures' | translate }}
631
+ </h2>
632
+
633
+ <div class="overflow-x-auto">
634
+ <table class="w-full">
635
+ <thead>
636
+ <tr class="border-b border-gray-200">
637
+ <th class="py-4 text-left font-semibold text-gray-900">Feature</th>
638
+ @for (plan of subscriptionService.plans(); track plan.id) {
639
+ <th class="py-4 text-center font-semibold text-gray-900">{{ plan.name }}</th>
640
+ }
641
+ </tr>
642
+ </thead>
643
+ <tbody>
644
+ @for (feature of comparisonFeatures; track feature.name) {
645
+ <tr class="border-b border-gray-100">
646
+ <td class="py-4 text-gray-700">{{ feature.name }}</td>
647
+ @for (value of feature.values; track $index) {
648
+ <td class="py-4 text-center">
649
+ @if (value === true) {
650
+ <span class="text-success-500">✓</span>
651
+ } @else if (value === false) {
652
+ <span class="text-gray-300">—</span>
653
+ } @else {
654
+ <span class="text-gray-700">{{ value }}</span>
655
+ }
656
+ </td>
657
+ }
658
+ </tr>
659
+ }
660
+ </tbody>
661
+ </table>
662
+ </div>
663
+ </div>
664
+ </section>
665
+
666
+ <!-- Testimonials -->
667
+ <section class="py-20">
668
+ <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
669
+ <h2 class="mb-12 text-center text-3xl font-bold text-gray-900">
670
+ {{ 'pricing.trustedBy' | translate }}
671
+ </h2>
672
+
673
+ <div class="grid gap-8 md:grid-cols-3">
674
+ @for (testimonial of testimonials; track testimonial.author) {
675
+ <app-testimonial [testimonial]="testimonial"></app-testimonial>
676
+ }
677
+ </div>
678
+ </div>
679
+ </section>
680
+
681
+ <!-- FAQ -->
682
+ <section class="bg-gray-50 py-20">
683
+ <div class="mx-auto max-w-3xl px-4 sm:px-6 lg:px-8">
684
+ <h2 class="mb-12 text-center text-3xl font-bold text-gray-900">
685
+ {{ 'pricing.faq' | translate }}
686
+ </h2>
687
+ <app-faq [items]="faqItems"></app-faq>
688
+ </div>
689
+ </section>
690
+
691
+ <!-- CTA -->
692
+ <section class="py-20">
693
+ <div class="mx-auto max-w-4xl px-4 text-center sm:px-6 lg:px-8">
694
+ <h2 class="text-3xl font-bold text-gray-900">{{ 'pricing.ctaTitle' | translate }}</h2>
695
+ <p class="mt-4 text-xl text-gray-600">{{ 'pricing.ctaSubtitle' | translate }}</p>
696
+ <button class="mt-8 rounded-lg bg-primary-500 px-8 py-4 font-semibold text-white hover:bg-primary-600">
697
+ {{ 'pricing.startTrial' | translate }}
698
+ </button>
699
+ </div>
700
+ </section>
701
+ </div>
702
+ \`,
703
+ })
704
+ export class PricingComponent {
705
+ subscriptionService = inject(SubscriptionService);
706
+ private router = inject(Router);
707
+
708
+ isYearly = signal(false);
709
+
710
+ comparisonFeatures = [
711
+ { name: 'API Calls', values: ['1,000/mo', '50,000/mo', 'Unlimited'] },
712
+ { name: 'Storage', values: ['100MB', '10GB', '100GB'] },
713
+ { name: 'Team Members', values: ['1', '5', 'Unlimited'] },
714
+ { name: 'Projects', values: ['3', 'Unlimited', 'Unlimited'] },
715
+ { name: 'Analytics', values: [false, true, true] },
716
+ { name: 'Custom Integrations', values: [false, true, true] },
717
+ { name: 'Priority Support', values: [false, true, true] },
718
+ { name: 'SLA Guarantee', values: [false, false, true] },
719
+ { name: 'On-Premise', values: [false, false, true] },
720
+ ];
721
+
722
+ testimonials: Testimonial[] = [
723
+ {
724
+ content: 'This platform has transformed how we handle our data. The API is incredibly well-designed and the support team is amazing.',
725
+ author: 'Sarah Chen',
726
+ role: 'CTO',
727
+ company: 'TechStart Inc',
728
+ avatar: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=150',
729
+ rating: 5,
730
+ },
731
+ {
732
+ content: 'We migrated from a competitor and the difference is night and day. Better performance, better pricing, better everything.',
733
+ author: 'Michael Rodriguez',
734
+ role: 'Lead Developer',
735
+ company: 'DataFlow',
736
+ avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=150',
737
+ rating: 5,
738
+ },
739
+ {
740
+ content: 'The enterprise features are exactly what we needed. Custom integrations and dedicated support made our rollout smooth.',
741
+ author: 'Emily Watson',
742
+ role: 'VP Engineering',
743
+ company: 'Enterprise Co',
744
+ avatar: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=150',
745
+ rating: 5,
746
+ },
747
+ ];
748
+
749
+ faqItems: FaqItem[] = [
750
+ {
751
+ question: 'Can I change plans at any time?',
752
+ answer: 'Yes, you can upgrade or downgrade your plan at any time. Changes take effect immediately and we\\'ll prorate any differences.',
753
+ },
754
+ {
755
+ question: 'What payment methods do you accept?',
756
+ answer: 'We accept all major credit cards (Visa, MasterCard, American Express) as well as PayPal. Enterprise customers can also pay via invoice.',
757
+ },
758
+ {
759
+ question: 'Is there a free trial?',
760
+ answer: 'Yes! All paid plans come with a 14-day free trial. No credit card required to start.',
761
+ },
762
+ {
763
+ question: 'What happens if I exceed my limits?',
764
+ answer: 'We\\'ll notify you when you\\'re approaching your limits. You can upgrade anytime, or we offer overage pricing for occasional spikes.',
765
+ },
766
+ {
767
+ question: 'Can I cancel anytime?',
768
+ answer: 'Absolutely. You can cancel your subscription at any time with no penalties. You\\'ll retain access until the end of your billing period.',
769
+ },
770
+ ];
771
+
772
+ selectPlan(plan: Plan): void {
773
+ if (plan.id === 'enterprise') {
774
+ this.router.navigate(['/contact']);
775
+ } else {
776
+ this.router.navigate(['/auth/register'], { queryParams: { plan: plan.id } });
777
+ }
778
+ }
779
+ }`;
780
+
781
+ await fs.writeFile(
782
+ path.join(config.fullPath, 'src/app/features/pricing/pricing.component.ts'),
783
+ pricing
784
+ );
785
+
786
+ // Dashboard Overview
787
+ const dashboardOverview = `import { Component, inject } from '@angular/core';
788
+ import { RouterModule } from '@angular/router';
789
+ import { DatePipe } from '@angular/common';
790
+ import { TranslateModule } from '@ngx-translate/core';
791
+ import { NgIconComponent, provideIcons } from '@ng-icons/core';
792
+ import {
793
+ heroChartBar,
794
+ heroCloud,
795
+ heroUsers,
796
+ heroFolder,
797
+ heroArrowTrendingUp,
798
+ heroArrowTrendingDown,
799
+ } from '@ng-icons/heroicons/outline';
800
+ import { StatsCardComponent } from '@shared/components/stats-card/stats-card.component';
801
+ import { SubscriptionService } from '@core/services/subscription.service';
802
+
803
+ @Component({
804
+ selector: 'app-dashboard-overview',
805
+ standalone: true,
806
+ imports: [RouterModule, DatePipe, TranslateModule, NgIconComponent, StatsCardComponent],
807
+ viewProviders: [provideIcons({ heroChartBar, heroCloud, heroUsers, heroFolder, heroArrowTrendingUp, heroArrowTrendingDown })],
808
+ template: \`
809
+ <div>
810
+ <div class="mb-8">
811
+ <h1 class="text-2xl font-bold text-gray-900">{{ 'dashboard.welcome' | translate }}</h1>
812
+ <p class="text-gray-600">{{ 'dashboard.overviewSubtitle' | translate }}</p>
813
+ </div>
814
+
815
+ <!-- Current Plan Banner -->
816
+ @if (subscriptionService.currentPlan()) {
817
+ <div class="mb-8 rounded-xl bg-gradient-to-r from-primary-500 to-purple-600 p-6 text-white">
818
+ <div class="flex items-center justify-between">
819
+ <div>
820
+ <p class="text-sm text-primary-100">Current Plan</p>
821
+ <p class="text-2xl font-bold">{{ subscriptionService.currentPlan()!.name }}</p>
822
+ <p class="mt-1 text-primary-100">
823
+ {{ subscriptionService.daysRemaining() }} days remaining
824
+ </p>
825
+ </div>
826
+ <a routerLink="/dashboard/subscription" class="rounded-lg bg-white/20 px-4 py-2 font-medium backdrop-blur-sm hover:bg-white/30">
827
+ Manage Subscription
828
+ </a>
829
+ </div>
830
+ </div>
831
+ }
832
+
833
+ <!-- Usage Stats -->
834
+ <div class="grid gap-6 md:grid-cols-2 lg:grid-cols-4">
835
+ <app-stats-card
836
+ label="API Calls"
837
+ [value]="formatNumber(subscriptionService.usage().apiCalls.used)"
838
+ [subtext]="'of ' + formatNumber(subscriptionService.usage().apiCalls.limit)"
839
+ icon="heroChartBar"
840
+ iconBgClass="bg-blue-100"
841
+ iconClass="text-blue-600"
842
+ [showProgress]="true"
843
+ progressLabel="Used this month"
844
+ [progressPercent]="getPercent(subscriptionService.usage().apiCalls)">
845
+ </app-stats-card>
846
+
847
+ <app-stats-card
848
+ label="Storage"
849
+ [value]="subscriptionService.usage().storage.used + ' GB'"
850
+ [subtext]="'of ' + subscriptionService.usage().storage.limit + ' GB'"
851
+ icon="heroCloud"
852
+ iconBgClass="bg-purple-100"
853
+ iconClass="text-purple-600"
854
+ [showProgress]="true"
855
+ progressLabel="Used"
856
+ [progressPercent]="getPercent(subscriptionService.usage().storage)">
857
+ </app-stats-card>
858
+
859
+ <app-stats-card
860
+ label="Team Members"
861
+ [value]="subscriptionService.usage().teamMembers.used"
862
+ [subtext]="'of ' + subscriptionService.usage().teamMembers.limit + ' seats'"
863
+ icon="heroUsers"
864
+ iconBgClass="bg-green-100"
865
+ iconClass="text-green-600"
866
+ [showProgress]="true"
867
+ [progressPercent]="getPercent(subscriptionService.usage().teamMembers)">
868
+ </app-stats-card>
869
+
870
+ <app-stats-card
871
+ label="Projects"
872
+ [value]="subscriptionService.usage().projects.used"
873
+ [subtext]="subscriptionService.usage().projects.limit === -1 ? 'Unlimited' : 'of ' + subscriptionService.usage().projects.limit"
874
+ icon="heroFolder"
875
+ iconBgClass="bg-amber-100"
876
+ iconClass="text-amber-600">
877
+ </app-stats-card>
878
+ </div>
879
+
880
+ <!-- Recent Activity -->
881
+ <div class="mt-8 rounded-xl border border-gray-200 bg-white p-6">
882
+ <h2 class="mb-4 text-lg font-semibold text-gray-900">{{ 'dashboard.recentActivity' | translate }}</h2>
883
+ <div class="space-y-4">
884
+ @for (activity of recentActivity; track activity.id) {
885
+ <div class="flex items-center gap-4 rounded-lg bg-gray-50 p-4">
886
+ <div class="flex h-10 w-10 items-center justify-center rounded-full" [class]="activity.iconBg">
887
+ <ng-icon [name]="activity.icon" size="20" [class]="activity.iconColor"></ng-icon>
888
+ </div>
889
+ <div class="flex-1">
890
+ <p class="font-medium text-gray-900">{{ activity.title }}</p>
891
+ <p class="text-sm text-gray-500">{{ activity.description }}</p>
892
+ </div>
893
+ <span class="text-sm text-gray-500">{{ activity.time }}</span>
894
+ </div>
895
+ }
896
+ </div>
897
+ </div>
898
+
899
+ <!-- Quick Actions -->
900
+ <div class="mt-8 grid gap-4 md:grid-cols-3">
901
+ <a routerLink="/dashboard/usage" class="rounded-xl border border-gray-200 bg-white p-6 transition-all hover:shadow-lg">
902
+ <ng-icon name="heroChartBar" size="24" class="text-primary-500"></ng-icon>
903
+ <h3 class="mt-3 font-semibold text-gray-900">View Detailed Usage</h3>
904
+ <p class="mt-1 text-sm text-gray-600">See your API usage trends and analytics</p>
905
+ </a>
906
+
907
+ <a routerLink="/dashboard/subscription" class="rounded-xl border border-gray-200 bg-white p-6 transition-all hover:shadow-lg">
908
+ <ng-icon name="heroArrowTrendingUp" size="24" class="text-success-500"></ng-icon>
909
+ <h3 class="mt-3 font-semibold text-gray-900">Upgrade Plan</h3>
910
+ <p class="mt-1 text-sm text-gray-600">Get more features and higher limits</p>
911
+ </a>
912
+
913
+ <a routerLink="/contact" class="rounded-xl border border-gray-200 bg-white p-6 transition-all hover:shadow-lg">
914
+ <ng-icon name="heroUsers" size="24" class="text-purple-500"></ng-icon>
915
+ <h3 class="mt-3 font-semibold text-gray-900">Invite Team</h3>
916
+ <p class="mt-1 text-sm text-gray-600">Add team members to collaborate</p>
917
+ </a>
918
+ </div>
919
+ </div>
920
+ \`,
921
+ })
922
+ export class DashboardOverviewComponent {
923
+ subscriptionService = inject(SubscriptionService);
924
+
925
+ recentActivity = [
926
+ { id: 1, title: 'API Key Generated', description: 'New production API key created', time: '2 hours ago', icon: 'heroChartBar', iconBg: 'bg-blue-100', iconColor: 'text-blue-600' },
927
+ { id: 2, title: 'Storage Increased', description: 'Upgraded storage from 5GB to 10GB', time: '1 day ago', icon: 'heroCloud', iconBg: 'bg-purple-100', iconColor: 'text-purple-600' },
928
+ { id: 3, title: 'New Team Member', description: 'john@example.com joined the team', time: '3 days ago', icon: 'heroUsers', iconBg: 'bg-green-100', iconColor: 'text-green-600' },
929
+ ];
930
+
931
+ formatNumber(num: number): string {
932
+ return num.toLocaleString();
933
+ }
934
+
935
+ getPercent(usage: { used: number; limit: number }): number {
936
+ if (usage.limit === -1) return 0;
937
+ return Math.round((usage.used / usage.limit) * 100);
938
+ }
939
+ }`;
940
+
941
+ await fs.writeFile(
942
+ path.join(config.fullPath, 'src/app/features/dashboard/overview/overview.component.ts'),
943
+ dashboardOverview
944
+ );
945
+
946
+ // Subscription Management Page
947
+ const subscription = `import { Component, inject, signal } from '@angular/core';
948
+ import { RouterModule } from '@angular/router';
949
+ import { DatePipe, CurrencyPipe } from '@angular/common';
950
+ import { TranslateModule } from '@ngx-translate/core';
951
+ import { NgIconComponent, provideIcons } from '@ng-icons/core';
952
+ import { heroCheck, heroExclamationTriangle } from '@ng-icons/heroicons/outline';
953
+ import { SubscriptionService, Plan } from '@core/services/subscription.service';
954
+ import { ToastService } from '@core/services/toast.service';
955
+
956
+ @Component({
957
+ selector: 'app-subscription',
958
+ standalone: true,
959
+ imports: [RouterModule, DatePipe, CurrencyPipe, TranslateModule, NgIconComponent],
960
+ viewProviders: [provideIcons({ heroCheck, heroExclamationTriangle })],
961
+ template: \`
962
+ <div>
963
+ <div class="mb-8">
964
+ <h1 class="text-2xl font-bold text-gray-900">{{ 'subscription.title' | translate }}</h1>
965
+ <p class="text-gray-600">{{ 'subscription.subtitle' | translate }}</p>
966
+ </div>
967
+
968
+ <!-- Current Subscription -->
969
+ @if (subscriptionService.subscription()) {
970
+ <div class="mb-8 rounded-xl border border-gray-200 bg-white p-6">
971
+ <div class="flex items-start justify-between">
972
+ <div>
973
+ <h2 class="text-lg font-semibold text-gray-900">Current Plan</h2>
974
+ <p class="mt-1 text-3xl font-bold text-primary-600">
975
+ {{ subscriptionService.currentPlan()?.name }}
976
+ </p>
977
+ <p class="mt-2 text-gray-600">
978
+ {{ subscriptionService.currentPlan()?.price | currency }}/month
979
+ </p>
980
+ </div>
981
+
982
+ <div class="text-right">
983
+ <span
984
+ class="inline-flex items-center rounded-full px-3 py-1 text-sm font-medium"
985
+ [class.bg-success-100]="subscriptionService.subscription()?.status === 'active'"
986
+ [class.text-success-700]="subscriptionService.subscription()?.status === 'active'"
987
+ [class.bg-warning-100]="subscriptionService.subscription()?.status === 'trialing'"
988
+ [class.text-warning-700]="subscriptionService.subscription()?.status === 'trialing'">
989
+ {{ subscriptionService.subscription()?.status | titlecase }}
990
+ </span>
991
+ <p class="mt-2 text-sm text-gray-500">
992
+ Renews {{ subscriptionService.subscription()?.currentPeriodEnd | date:'mediumDate' }}
993
+ </p>
994
+ </div>
995
+ </div>
996
+
997
+ @if (subscriptionService.subscription()?.cancelAtPeriodEnd) {
998
+ <div class="mt-4 flex items-center gap-2 rounded-lg bg-warning-50 p-4 text-warning-700">
999
+ <ng-icon name="heroExclamationTriangle" size="20"></ng-icon>
1000
+ <span>Your subscription will cancel at the end of the billing period</span>
1001
+ </div>
1002
+ }
1003
+
1004
+ <div class="mt-6 flex gap-4">
1005
+ @if (!subscriptionService.subscription()?.cancelAtPeriodEnd) {
1006
+ <button
1007
+ (click)="cancelSubscription()"
1008
+ [disabled]="loading()"
1009
+ class="rounded-lg border border-gray-300 px-4 py-2 font-medium text-gray-700 hover:bg-gray-50 disabled:opacity-50">
1010
+ Cancel Subscription
1011
+ </button>
1012
+ }
1013
+ <a routerLink="/pricing" class="rounded-lg bg-primary-500 px-4 py-2 font-medium text-white hover:bg-primary-600">
1014
+ Change Plan
1015
+ </a>
1016
+ </div>
1017
+ </div>
1018
+ }
1019
+
1020
+ <!-- Plan Features -->
1021
+ @if (subscriptionService.currentPlan()) {
1022
+ <div class="rounded-xl border border-gray-200 bg-white p-6">
1023
+ <h2 class="mb-4 text-lg font-semibold text-gray-900">Plan Features</h2>
1024
+ <ul class="grid gap-3 md:grid-cols-2">
1025
+ @for (feature of subscriptionService.currentPlan()!.features; track feature) {
1026
+ <li class="flex items-center gap-2 text-gray-700">
1027
+ <ng-icon name="heroCheck" size="20" class="text-success-500"></ng-icon>
1028
+ {{ feature }}
1029
+ </li>
1030
+ }
1031
+ </ul>
1032
+ </div>
1033
+ }
1034
+
1035
+ <!-- Billing History -->
1036
+ <div class="mt-8 rounded-xl border border-gray-200 bg-white p-6">
1037
+ <h2 class="mb-4 text-lg font-semibold text-gray-900">Billing History</h2>
1038
+ <div class="overflow-x-auto">
1039
+ <table class="w-full">
1040
+ <thead>
1041
+ <tr class="border-b border-gray-200 text-left">
1042
+ <th class="pb-3 font-medium text-gray-500">Date</th>
1043
+ <th class="pb-3 font-medium text-gray-500">Description</th>
1044
+ <th class="pb-3 font-medium text-gray-500">Amount</th>
1045
+ <th class="pb-3 font-medium text-gray-500">Status</th>
1046
+ <th class="pb-3 font-medium text-gray-500"></th>
1047
+ </tr>
1048
+ </thead>
1049
+ <tbody>
1050
+ @for (invoice of invoices; track invoice.id) {
1051
+ <tr class="border-b border-gray-100">
1052
+ <td class="py-4 text-gray-900">{{ invoice.date | date:'mediumDate' }}</td>
1053
+ <td class="py-4 text-gray-700">{{ invoice.description }}</td>
1054
+ <td class="py-4 text-gray-900">{{ invoice.amount | currency }}</td>
1055
+ <td class="py-4">
1056
+ <span class="rounded-full bg-success-100 px-2 py-1 text-xs font-medium text-success-700">
1057
+ {{ invoice.status }}
1058
+ </span>
1059
+ </td>
1060
+ <td class="py-4">
1061
+ <button class="text-primary-600 hover:text-primary-700">Download</button>
1062
+ </td>
1063
+ </tr>
1064
+ }
1065
+ </tbody>
1066
+ </table>
1067
+ </div>
1068
+ </div>
1069
+ </div>
1070
+ \`,
1071
+ })
1072
+ export class SubscriptionComponent {
1073
+ subscriptionService = inject(SubscriptionService);
1074
+ private toast = inject(ToastService);
1075
+
1076
+ loading = signal(false);
1077
+
1078
+ invoices = [
1079
+ { id: 1, date: new Date('2025-01-01'), description: 'Pro Plan - Monthly', amount: 29, status: 'Paid' },
1080
+ { id: 2, date: new Date('2024-12-01'), description: 'Pro Plan - Monthly', amount: 29, status: 'Paid' },
1081
+ { id: 3, date: new Date('2024-11-01'), description: 'Pro Plan - Monthly', amount: 29, status: 'Paid' },
1082
+ ];
1083
+
1084
+ async cancelSubscription(): Promise<void> {
1085
+ if (confirm('Are you sure you want to cancel your subscription?')) {
1086
+ this.loading.set(true);
1087
+ try {
1088
+ await this.subscriptionService.cancelSubscription();
1089
+ this.toast.success('Subscription cancelled. You will retain access until the end of your billing period.');
1090
+ } catch {
1091
+ this.toast.error('Failed to cancel subscription');
1092
+ } finally {
1093
+ this.loading.set(false);
1094
+ }
1095
+ }
1096
+ }
1097
+ }`;
1098
+
1099
+ await fs.writeFile(
1100
+ path.join(
1101
+ config.fullPath,
1102
+ 'src/app/features/dashboard/subscription/subscription.component.ts'
1103
+ ),
1104
+ subscription
1105
+ );
1106
+
1107
+ // Features Page
1108
+ const features = `import { Component } from '@angular/core';
1109
+ import { RouterModule } from '@angular/router';
1110
+ import { TranslateModule } from '@ngx-translate/core';
1111
+ import { NgIconComponent, provideIcons } from '@ng-icons/core';
1112
+ import {
1113
+ heroBolt,
1114
+ heroShieldCheck,
1115
+ heroChartBar,
1116
+ heroCloud,
1117
+ heroCog,
1118
+ heroUsers,
1119
+ heroGlobeAlt,
1120
+ heroLockClosed,
1121
+ } from '@ng-icons/heroicons/outline';
1122
+ import { FeatureCardComponent } from '@shared/components/feature-card/feature-card.component';
1123
+
1124
+ @Component({
1125
+ selector: 'app-features',
1126
+ standalone: true,
1127
+ imports: [RouterModule, TranslateModule, NgIconComponent, FeatureCardComponent],
1128
+ viewProviders: [provideIcons({ heroBolt, heroShieldCheck, heroChartBar, heroCloud, heroCog, heroUsers, heroGlobeAlt, heroLockClosed })],
1129
+ template: \`
1130
+ <div>
1131
+ <!-- Hero -->
1132
+ <section class="bg-gradient-to-b from-primary-600 to-primary-800 py-20 text-white">
1133
+ <div class="mx-auto max-w-7xl px-4 text-center sm:px-6 lg:px-8">
1134
+ <h1 class="text-4xl font-bold sm:text-5xl">{{ 'features.title' | translate }}</h1>
1135
+ <p class="mt-4 text-xl text-primary-100">{{ 'features.subtitle' | translate }}</p>
1136
+ </div>
1137
+ </section>
1138
+
1139
+ <!-- Features Grid -->
1140
+ <section class="py-20">
1141
+ <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
1142
+ <div class="grid gap-8 md:grid-cols-2 lg:grid-cols-4">
1143
+ @for (feature of features; track feature.title) {
1144
+ <app-feature-card
1145
+ [icon]="feature.icon"
1146
+ [title]="feature.title"
1147
+ [description]="feature.description"
1148
+ [iconBgClass]="feature.iconBg"
1149
+ [iconClass]="feature.iconColor">
1150
+ </app-feature-card>
1151
+ }
1152
+ </div>
1153
+ </div>
1154
+ </section>
1155
+
1156
+ <!-- Feature Details -->
1157
+ <section class="bg-gray-50 py-20">
1158
+ <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
1159
+ @for (detail of featureDetails; track detail.title; let i = $index) {
1160
+ <div class="mb-20 last:mb-0 grid items-center gap-12 lg:grid-cols-2" [class.lg:flex-row-reverse]="i % 2 === 1">
1161
+ <div [class.lg:order-2]="i % 2 === 1">
1162
+ <h2 class="text-3xl font-bold text-gray-900">{{ detail.title }}</h2>
1163
+ <p class="mt-4 text-lg text-gray-600">{{ detail.description }}</p>
1164
+ <ul class="mt-6 space-y-3">
1165
+ @for (point of detail.points; track point) {
1166
+ <li class="flex items-center gap-3 text-gray-700">
1167
+ <span class="flex h-6 w-6 items-center justify-center rounded-full bg-primary-100 text-primary-600">✓</span>
1168
+ {{ point }}
1169
+ </li>
1170
+ }
1171
+ </ul>
1172
+ </div>
1173
+ <div [class.lg:order-1]="i % 2 === 1" class="rounded-2xl bg-linear-to-br p-8" [class]="detail.gradientClass">
1174
+ <ng-icon [name]="detail.icon" size="120" class="mx-auto text-white/80"></ng-icon>
1175
+ </div>
1176
+ </div>
1177
+ }
1178
+ </div>
1179
+ </section>
1180
+
1181
+ <!-- CTA -->
1182
+ <section class="py-20">
1183
+ <div class="mx-auto max-w-4xl px-4 text-center sm:px-6 lg:px-8">
1184
+ <h2 class="text-3xl font-bold text-gray-900">{{ 'features.ctaTitle' | translate }}</h2>
1185
+ <p class="mt-4 text-xl text-gray-600">{{ 'features.ctaSubtitle' | translate }}</p>
1186
+ <div class="mt-8 flex flex-wrap justify-center gap-4">
1187
+ <a routerLink="/pricing" class="rounded-lg bg-primary-500 px-8 py-4 font-semibold text-white hover:bg-primary-600">
1188
+ View Pricing
1189
+ </a>
1190
+ <a routerLink="/contact" class="rounded-lg border border-gray-300 px-8 py-4 font-semibold text-gray-700 hover:bg-gray-50">
1191
+ Contact Sales
1192
+ </a>
1193
+ </div>
1194
+ </div>
1195
+ </section>
1196
+ </div>
1197
+ \`,
1198
+ })
1199
+ export class FeaturesComponent {
1200
+ features = [
1201
+ { icon: 'heroBolt', title: 'Lightning Fast', description: 'Sub-millisecond response times with our globally distributed infrastructure.', iconBg: 'bg-amber-100', iconColor: 'text-amber-600' },
1202
+ { icon: 'heroShieldCheck', title: 'Enterprise Security', description: 'SOC 2 compliant with end-to-end encryption and advanced access controls.', iconBg: 'bg-green-100', iconColor: 'text-green-600' },
1203
+ { icon: 'heroChartBar', title: 'Advanced Analytics', description: 'Real-time insights and custom dashboards to track your performance.', iconBg: 'bg-blue-100', iconColor: 'text-blue-600' },
1204
+ { icon: 'heroCloud', title: 'Scalable Storage', description: 'Automatically scales with your needs, from megabytes to petabytes.', iconBg: 'bg-purple-100', iconColor: 'text-purple-600' },
1205
+ { icon: 'heroCog', title: 'Custom Integrations', description: 'Connect with 100+ tools or build your own with our flexible API.', iconBg: 'bg-gray-100', iconColor: 'text-gray-600' },
1206
+ { icon: 'heroUsers', title: 'Team Collaboration', description: 'Real-time collaboration with role-based permissions and audit logs.', iconBg: 'bg-pink-100', iconColor: 'text-pink-600' },
1207
+ { icon: 'heroGlobeAlt', title: 'Global CDN', description: 'Content delivery from 200+ edge locations worldwide.', iconBg: 'bg-cyan-100', iconColor: 'text-cyan-600' },
1208
+ { icon: 'heroLockClosed', title: 'SSO & SAML', description: 'Enterprise single sign-on with support for all major providers.', iconBg: 'bg-red-100', iconColor: 'text-red-600' },
1209
+ ];
1210
+
1211
+ featureDetails = [
1212
+ {
1213
+ icon: 'heroChartBar',
1214
+ title: 'Powerful Analytics Dashboard',
1215
+ description: 'Get deep insights into your data with our comprehensive analytics suite. Track trends, identify patterns, and make data-driven decisions.',
1216
+ points: ['Real-time data visualization', 'Custom report builder', 'Automated insights', 'Export to any format'],
1217
+ gradientClass: 'from-blue-500 to-indigo-600',
1218
+ },
1219
+ {
1220
+ icon: 'heroShieldCheck',
1221
+ title: 'Enterprise-Grade Security',
1222
+ description: 'Your data security is our top priority. We employ industry-leading security measures to protect your sensitive information.',
1223
+ points: ['End-to-end encryption', 'SOC 2 Type II certified', 'GDPR compliant', '99.99% uptime SLA'],
1224
+ gradientClass: 'from-green-500 to-emerald-600',
1225
+ },
1226
+ {
1227
+ icon: 'heroCog',
1228
+ title: 'Seamless Integrations',
1229
+ description: 'Connect your favorite tools and automate your workflows with our extensive integration library and flexible API.',
1230
+ points: ['100+ pre-built integrations', 'RESTful API', 'Webhooks support', 'Custom SDK available'],
1231
+ gradientClass: 'from-purple-500 to-pink-600',
1232
+ },
1233
+ ];
1234
+ }`;
1235
+
1236
+ await fs.writeFile(
1237
+ path.join(config.fullPath, 'src/app/features/features/features.component.ts'),
1238
+ features
1239
+ );
1240
+ },
1241
+
1242
+ async createRouting(config) {
1243
+ const routes = `import { Routes } from '@angular/router';
1244
+
1245
+ export const routes: Routes = [
1246
+ // Public routes
1247
+ {
1248
+ path: '',
1249
+ loadComponent: () => import('./features/home/home.component').then(c => c.HomeComponent)
1250
+ },
1251
+ {
1252
+ path: 'about',
1253
+ loadComponent: () => import('./features/about/about.component').then(c => c.AboutComponent)
1254
+ },
1255
+ {
1256
+ path: 'contact',
1257
+ loadComponent: () => import('./features/contact/contact.component').then(c => c.ContactComponent)
1258
+ },
1259
+ {
1260
+ path: 'pricing',
1261
+ loadComponent: () => import('./features/pricing/pricing.component').then(c => c.PricingComponent)
1262
+ },
1263
+ {
1264
+ path: 'features',
1265
+ loadComponent: () => import('./features/features/features.component').then(c => c.FeaturesComponent)
1266
+ },
1267
+
1268
+ // Dashboard routes
1269
+ {
1270
+ path: 'dashboard',
1271
+ children: [
1272
+ { path: '', redirectTo: 'overview', pathMatch: 'full' },
1273
+ { path: 'overview', loadComponent: () => import('./features/dashboard/overview/overview.component').then(c => c.DashboardOverviewComponent) },
1274
+ { path: 'subscription', loadComponent: () => import('./features/dashboard/subscription/subscription.component').then(c => c.SubscriptionComponent) },
1275
+ ]
1276
+ },
1277
+
1278
+ // Auth routes
1279
+ {
1280
+ path: 'auth',
1281
+ loadComponent: () => import('./layout/auth/auth-layout.component').then(c => c.AuthLayoutComponent),
1282
+ children: [
1283
+ { path: '', redirectTo: 'login', pathMatch: 'full' },
1284
+ { path: 'login', loadComponent: () => import('./features/auth/login/login.component').then(c => c.LoginComponent) },
1285
+ { path: 'register', loadComponent: () => import('./features/auth/register/register.component').then(c => c.RegisterComponent) },
1286
+ { path: 'forgot-password', loadComponent: () => import('./features/auth/forgot-password/forgot-password.component').then(c => c.ForgotPasswordComponent) }
1287
+ ]
1288
+ },
1289
+
1290
+ { path: '**', redirectTo: '' }
1291
+ ];`;
1292
+
1293
+ await fs.writeFile(path.join(config.fullPath, 'src/app/app.routes.ts'), routes);
1294
+ },
1295
+
1296
+ async updateI18n(config) {
1297
+ const enPath = path.join(config.fullPath, 'public/assets/i18n/en.json');
1298
+ const arPath = path.join(config.fullPath, 'public/assets/i18n/ar.json');
1299
+
1300
+ let en = {};
1301
+ let ar = {};
1302
+
1303
+ try {
1304
+ en = JSON.parse(await fs.readFile(enPath, 'utf-8'));
1305
+ ar = JSON.parse(await fs.readFile(arPath, 'utf-8'));
1306
+ } catch {
1307
+ // Files don't exist yet
1308
+ }
1309
+
1310
+ const saasEn = {
1311
+ pricing: {
1312
+ title: 'Simple, Transparent Pricing',
1313
+ subtitle: 'Choose the plan that fits your needs. No hidden fees.',
1314
+ compareFeatures: 'Compare Features',
1315
+ trustedBy: 'Trusted by Industry Leaders',
1316
+ faq: 'Frequently Asked Questions',
1317
+ ctaTitle: 'Ready to Get Started?',
1318
+ ctaSubtitle: 'Start your 14-day free trial today. No credit card required.',
1319
+ startTrial: 'Start Free Trial',
1320
+ },
1321
+ subscription: {
1322
+ title: 'Subscription',
1323
+ subtitle: 'Manage your subscription and billing',
1324
+ },
1325
+ dashboard: {
1326
+ welcome: 'Welcome Back!',
1327
+ overviewSubtitle: "Here's what's happening with your account",
1328
+ recentActivity: 'Recent Activity',
1329
+ },
1330
+ features: {
1331
+ title: 'Powerful Features for Modern Teams',
1332
+ subtitle: 'Everything you need to build, deploy, and scale your applications',
1333
+ ctaTitle: 'Ready to Transform Your Workflow?',
1334
+ ctaSubtitle: 'Join thousands of teams already using our platform',
1335
+ },
1336
+ };
1337
+
1338
+ const saasAr = {
1339
+ pricing: {
1340
+ title: 'أسعار بسيطة وشفافة',
1341
+ subtitle: 'اختر الخطة التي تناسب احتياجاتك. بدون رسوم خفية.',
1342
+ compareFeatures: 'مقارنة الميزات',
1343
+ trustedBy: 'موثوق به من قبل رواد الصناعة',
1344
+ faq: 'الأسئلة الشائعة',
1345
+ ctaTitle: 'هل أنت مستعد للبدء؟',
1346
+ ctaSubtitle: 'ابدأ تجربتك المجانية لمدة 14 يومًا اليوم. لا حاجة لبطاقة ائتمان.',
1347
+ startTrial: 'ابدأ التجربة المجانية',
1348
+ },
1349
+ subscription: {
1350
+ title: 'الاشتراك',
1351
+ subtitle: 'إدارة اشتراكك والفواتير',
1352
+ },
1353
+ dashboard: {
1354
+ welcome: 'مرحبًا بعودتك!',
1355
+ overviewSubtitle: 'إليك ما يحدث في حسابك',
1356
+ recentActivity: 'النشاط الأخير',
1357
+ },
1358
+ features: {
1359
+ title: 'ميزات قوية للفرق الحديثة',
1360
+ subtitle: 'كل ما تحتاجه لبناء ونشر وتوسيع تطبيقاتك',
1361
+ ctaTitle: 'هل أنت مستعد لتحويل سير عملك؟',
1362
+ ctaSubtitle: 'انضم إلى آلاف الفرق التي تستخدم منصتنا بالفعل',
1363
+ },
1364
+ };
1365
+
1366
+ await fs.writeFile(enPath, JSON.stringify({ ...en, ...saasEn }, null, 2));
1367
+ await fs.writeFile(arPath, JSON.stringify({ ...ar, ...saasAr }, null, 2));
1368
+ },
1369
+ };
1370
+
1371
+ module.exports = saas;