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.
- package/CHANGELOG.md +81 -344
- package/README.md +93 -157
- package/lib/cli/index.js +29 -3
- package/lib/cli/interactive.js +26 -1
- package/lib/managers/ProjectManager.js +0 -4
- package/lib/templates/base/components.js +243 -0
- package/lib/templates/base/index.js +207 -0
- package/lib/templates/base/infrastructure.js +314 -0
- package/lib/templates/base/linting.js +359 -0
- package/lib/templates/base/pwa.js +103 -0
- package/lib/templates/base/services.js +362 -0
- package/lib/templates/blog/app.js +250 -0
- package/lib/templates/blog/components.js +360 -0
- package/lib/templates/blog/i18n.js +77 -0
- package/lib/templates/blog/index.js +126 -0
- package/lib/templates/blog/pages.js +554 -0
- package/lib/templates/blog/services.js +390 -0
- package/lib/templates/dashboard/app.js +320 -0
- package/lib/templates/dashboard/charts.js +305 -0
- package/lib/templates/dashboard/components.js +410 -0
- package/lib/templates/dashboard/i18n.js +340 -0
- package/lib/templates/dashboard/index.js +141 -0
- package/lib/templates/dashboard/layout.js +310 -0
- package/lib/templates/dashboard/pages.js +681 -0
- package/lib/templates/ecommerce/app.js +315 -0
- package/lib/templates/ecommerce/components.js +496 -0
- package/lib/templates/ecommerce/i18n.js +389 -0
- package/lib/templates/ecommerce/index.js +152 -0
- package/lib/templates/ecommerce/layout.js +270 -0
- package/lib/templates/ecommerce/pages.js +969 -0
- package/lib/templates/ecommerce/services.js +300 -0
- package/lib/templates/index.js +12 -0
- package/lib/templates/landing/index.js +1117 -0
- package/lib/templates/portfolio/index.js +1160 -0
- package/lib/templates/saas/index.js +1371 -0
- package/lib/templates/starter/app.js +364 -0
- package/lib/templates/starter/i18n.js +856 -0
- package/lib/templates/starter/index.js +53 -4055
- package/lib/templates/starter/layout.js +852 -0
- package/lib/templates/starter/pages.js +1241 -0
- package/package.json +1 -1
- package/lib/templates/starter/features.js +0 -867
- package/lib/utils/ai-config.js +0 -641
- /package/lib/templates/{starter → base}/advanced-features.js +0 -0
- /package/lib/templates/{starter → base}/seo-assets.js +0 -0
- /package/lib/templates/{starter → base}/seo-features.js +0 -0
- /package/lib/templates/{starter → base}/ui-features.js +0 -0
|
@@ -0,0 +1,681 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Create dashboard pages (Overview, Analytics, Users, Orders, Settings)
|
|
6
|
+
*/
|
|
7
|
+
async function createPages(config) {
|
|
8
|
+
// Overview Page
|
|
9
|
+
const overviewComponent = `import { Component, inject } from '@angular/core';
|
|
10
|
+
import { TranslateModule } from '@ngx-translate/core';
|
|
11
|
+
import { StatsCardComponent } from '@shared/components/stats-card/stats-card.component';
|
|
12
|
+
import { ChartBarComponent, BarChartData } from '@shared/components/chart-bar/chart-bar.component';
|
|
13
|
+
import { ChartLineComponent, LineChartData } from '@shared/components/chart-line/chart-line.component';
|
|
14
|
+
import { ChartDonutComponent, DonutChartData } from '@shared/components/chart-donut/chart-donut.component';
|
|
15
|
+
import { DataTableComponent, TableColumn } from '@shared/components/data-table/data-table.component';
|
|
16
|
+
import { BreadcrumbComponent } from '@shared/components/breadcrumb/breadcrumb.component';
|
|
17
|
+
import { TranslationService } from '@core/i18n/translation.service';
|
|
18
|
+
|
|
19
|
+
@Component({
|
|
20
|
+
selector: 'app-overview',
|
|
21
|
+
standalone: true,
|
|
22
|
+
imports: [
|
|
23
|
+
TranslateModule,
|
|
24
|
+
StatsCardComponent,
|
|
25
|
+
ChartBarComponent,
|
|
26
|
+
ChartLineComponent,
|
|
27
|
+
ChartDonutComponent,
|
|
28
|
+
DataTableComponent,
|
|
29
|
+
BreadcrumbComponent,
|
|
30
|
+
],
|
|
31
|
+
template: \`
|
|
32
|
+
<div class="space-y-6">
|
|
33
|
+
<!-- Header -->
|
|
34
|
+
<div>
|
|
35
|
+
<app-breadcrumb [items]="[{ label: ('dashboard.overview' | translate) }]"></app-breadcrumb>
|
|
36
|
+
<h1 class="mt-2 text-2xl font-bold text-gray-900">{{ 'dashboard.title' | translate }}</h1>
|
|
37
|
+
<p class="text-gray-500">{{ 'dashboard.welcome' | translate }}</p>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<!-- Stats Cards -->
|
|
41
|
+
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4">
|
|
42
|
+
<app-stats-card
|
|
43
|
+
[label]="'stats.totalRevenue' | translate"
|
|
44
|
+
value="$45,231"
|
|
45
|
+
icon="heroCurrencyDollar"
|
|
46
|
+
iconBgClass="bg-linear-to-br from-emerald-500 to-green-600 shadow-lg shadow-emerald-500/30"
|
|
47
|
+
iconColor="white"
|
|
48
|
+
gradientClass="from-emerald-50/50 to-green-50/50"
|
|
49
|
+
[trend]="12.5">
|
|
50
|
+
</app-stats-card>
|
|
51
|
+
<app-stats-card
|
|
52
|
+
[label]="'stats.totalUsers' | translate"
|
|
53
|
+
value="2,350"
|
|
54
|
+
icon="heroUsers"
|
|
55
|
+
iconBgClass="bg-linear-to-br from-blue-500 to-indigo-600 shadow-lg shadow-blue-500/30"
|
|
56
|
+
iconColor="white"
|
|
57
|
+
gradientClass="from-blue-50/50 to-indigo-50/50"
|
|
58
|
+
[trend]="8.2">
|
|
59
|
+
</app-stats-card>
|
|
60
|
+
<app-stats-card
|
|
61
|
+
[label]="'stats.totalOrders' | translate"
|
|
62
|
+
value="1,247"
|
|
63
|
+
icon="heroShoppingCart"
|
|
64
|
+
iconBgClass="bg-linear-to-br from-amber-500 to-orange-600 shadow-lg shadow-amber-500/30"
|
|
65
|
+
iconColor="white"
|
|
66
|
+
gradientClass="from-amber-50/50 to-orange-50/50"
|
|
67
|
+
[trend]="-2.4">
|
|
68
|
+
</app-stats-card>
|
|
69
|
+
<app-stats-card
|
|
70
|
+
[label]="'stats.conversionRate' | translate"
|
|
71
|
+
value="3.2%"
|
|
72
|
+
icon="heroChartBar"
|
|
73
|
+
iconBgClass="bg-linear-to-br from-violet-500 to-purple-600 shadow-lg shadow-violet-500/30"
|
|
74
|
+
iconColor="white"
|
|
75
|
+
gradientClass="from-violet-50/50 to-purple-50/50"
|
|
76
|
+
[trend]="4.1">
|
|
77
|
+
</app-stats-card>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<!-- Charts Row -->
|
|
81
|
+
<div class="grid grid-cols-1 gap-6 lg:grid-cols-3">
|
|
82
|
+
<!-- Line Chart -->
|
|
83
|
+
<div class="col-span-2 rounded-xl border border-gray-200 bg-white p-6 shadow-sm">
|
|
84
|
+
<app-chart-line
|
|
85
|
+
[title]="'charts.revenueOverTime' | translate"
|
|
86
|
+
[data]="revenueData"
|
|
87
|
+
lineColor="#3b82f6">
|
|
88
|
+
</app-chart-line>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<!-- Donut Chart -->
|
|
92
|
+
<div class="rounded-xl border border-gray-200 bg-white p-6 shadow-sm">
|
|
93
|
+
<app-chart-donut
|
|
94
|
+
[title]="'charts.trafficSources' | translate"
|
|
95
|
+
[data]="trafficData">
|
|
96
|
+
</app-chart-donut>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<!-- Bar Chart & Recent Orders -->
|
|
101
|
+
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
|
102
|
+
<!-- Bar Chart -->
|
|
103
|
+
<div class="rounded-xl border border-gray-200 bg-white p-6 shadow-sm">
|
|
104
|
+
<app-chart-bar
|
|
105
|
+
[title]="'charts.salesByCategory' | translate"
|
|
106
|
+
[data]="salesData">
|
|
107
|
+
</app-chart-bar>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<!-- Recent Orders Table -->
|
|
111
|
+
<div class="rounded-xl border border-gray-200 bg-white p-6 shadow-sm">
|
|
112
|
+
<h3 class="mb-4 text-lg font-semibold text-gray-900">{{ 'table.orderId' | translate }}</h3>
|
|
113
|
+
<app-data-table
|
|
114
|
+
[data]="recentOrders"
|
|
115
|
+
[columns]="orderColumns"
|
|
116
|
+
[searchable]="false"
|
|
117
|
+
[paginate]="false"
|
|
118
|
+
[pageSize]="5">
|
|
119
|
+
</app-data-table>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
\`,
|
|
124
|
+
})
|
|
125
|
+
export class OverviewComponent {
|
|
126
|
+
revenueData: LineChartData[] = [
|
|
127
|
+
{ label: 'Jan', value: 4000 },
|
|
128
|
+
{ label: 'Feb', value: 3000 },
|
|
129
|
+
{ label: 'Mar', value: 5000 },
|
|
130
|
+
{ label: 'Apr', value: 4500 },
|
|
131
|
+
{ label: 'May', value: 6000 },
|
|
132
|
+
{ label: 'Jun', value: 5500 },
|
|
133
|
+
{ label: 'Jul', value: 7000 },
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
trafficData: DonutChartData[] = [
|
|
137
|
+
{ label: 'Direct', value: 4500, color: '#3b82f6' },
|
|
138
|
+
{ label: 'Organic', value: 3200, color: '#10b981' },
|
|
139
|
+
{ label: 'Referral', value: 2100, color: '#f59e0b' },
|
|
140
|
+
{ label: 'Social', value: 1800, color: '#8b5cf6' },
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
salesData: BarChartData[] = [
|
|
144
|
+
{ label: 'Electronics', value: 12500, color: '#3b82f6' },
|
|
145
|
+
{ label: 'Clothing', value: 9800, color: '#10b981' },
|
|
146
|
+
{ label: 'Home', value: 7600, color: '#f59e0b' },
|
|
147
|
+
{ label: 'Books', value: 5400, color: '#8b5cf6' },
|
|
148
|
+
{ label: 'Sports', value: 4200, color: '#ec4899' },
|
|
149
|
+
];
|
|
150
|
+
|
|
151
|
+
orderColumns: TableColumn[] = [
|
|
152
|
+
{ key: 'id', label: 'Order ID', width: '100px' },
|
|
153
|
+
{ key: 'customer', label: 'Customer' },
|
|
154
|
+
{ key: 'amount', label: 'Amount', type: 'number' },
|
|
155
|
+
{ key: 'status', label: 'Status', type: 'status' },
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
recentOrders = [
|
|
159
|
+
{ id: '#3210', customer: 'John Doe', amount: 125.00, status: 'Completed' },
|
|
160
|
+
{ id: '#3209', customer: 'Jane Smith', amount: 89.50, status: 'Processing' },
|
|
161
|
+
{ id: '#3208', customer: 'Bob Wilson', amount: 234.00, status: 'Pending' },
|
|
162
|
+
{ id: '#3207', customer: 'Alice Brown', amount: 156.75, status: 'Completed' },
|
|
163
|
+
{ id: '#3206', customer: 'Charlie Davis', amount: 67.25, status: 'Cancelled' },
|
|
164
|
+
];
|
|
165
|
+
}`;
|
|
166
|
+
|
|
167
|
+
await fs.writeFile(
|
|
168
|
+
path.join(config.fullPath, 'src/app/features/dashboard/overview/overview.component.ts'),
|
|
169
|
+
overviewComponent
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
// Analytics Page
|
|
173
|
+
const analyticsComponent = `import { Component } from '@angular/core';
|
|
174
|
+
import { TranslateModule } from '@ngx-translate/core';
|
|
175
|
+
import { ChartLineComponent, LineChartData } from '@shared/components/chart-line/chart-line.component';
|
|
176
|
+
import { ChartBarComponent, BarChartData } from '@shared/components/chart-bar/chart-bar.component';
|
|
177
|
+
import { StatsCardComponent } from '@shared/components/stats-card/stats-card.component';
|
|
178
|
+
import { BreadcrumbComponent } from '@shared/components/breadcrumb/breadcrumb.component';
|
|
179
|
+
|
|
180
|
+
@Component({
|
|
181
|
+
selector: 'app-analytics',
|
|
182
|
+
standalone: true,
|
|
183
|
+
imports: [
|
|
184
|
+
TranslateModule,
|
|
185
|
+
ChartLineComponent,
|
|
186
|
+
ChartBarComponent,
|
|
187
|
+
StatsCardComponent,
|
|
188
|
+
BreadcrumbComponent,
|
|
189
|
+
],
|
|
190
|
+
template: \`
|
|
191
|
+
<div class="space-y-6">
|
|
192
|
+
<div>
|
|
193
|
+
<app-breadcrumb [items]="[{ label: ('dashboard.analytics' | translate) }]"></app-breadcrumb>
|
|
194
|
+
<h1 class="mt-2 text-2xl font-bold text-gray-900">{{ 'dashboard.analytics' | translate }}</h1>
|
|
195
|
+
<p class="text-gray-500">{{ 'dashboard.welcome' | translate }}</p>
|
|
196
|
+
</div>
|
|
197
|
+
|
|
198
|
+
<!-- Stats -->
|
|
199
|
+
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4">
|
|
200
|
+
<app-stats-card
|
|
201
|
+
[label]="'stats.pageViews' | translate"
|
|
202
|
+
value="124,592"
|
|
203
|
+
icon="heroEye"
|
|
204
|
+
iconBgClass="bg-linear-to-br from-cyan-500 to-blue-600 shadow-lg shadow-cyan-500/30"
|
|
205
|
+
iconColor="white"
|
|
206
|
+
gradientClass="from-cyan-50/50 to-blue-50/50"
|
|
207
|
+
[trend]="15.3">
|
|
208
|
+
</app-stats-card>
|
|
209
|
+
<app-stats-card
|
|
210
|
+
[label]="'stats.uniqueVisitors' | translate"
|
|
211
|
+
value="48,352"
|
|
212
|
+
icon="heroUserGroup"
|
|
213
|
+
iconBgClass="bg-linear-to-br from-teal-500 to-emerald-600 shadow-lg shadow-teal-500/30"
|
|
214
|
+
iconColor="white"
|
|
215
|
+
gradientClass="from-teal-50/50 to-emerald-50/50"
|
|
216
|
+
[trend]="8.7">
|
|
217
|
+
</app-stats-card>
|
|
218
|
+
<app-stats-card
|
|
219
|
+
[label]="'stats.avgSession' | translate"
|
|
220
|
+
value="4m 32s"
|
|
221
|
+
icon="heroClock"
|
|
222
|
+
iconBgClass="bg-linear-to-br from-amber-500 to-yellow-600 shadow-lg shadow-amber-500/30"
|
|
223
|
+
iconColor="white"
|
|
224
|
+
gradientClass="from-amber-50/50 to-yellow-50/50"
|
|
225
|
+
[trend]="2.1">
|
|
226
|
+
</app-stats-card>
|
|
227
|
+
<app-stats-card
|
|
228
|
+
[label]="'stats.bounceRate' | translate"
|
|
229
|
+
value="32.4%"
|
|
230
|
+
icon="heroArrowPath"
|
|
231
|
+
iconBgClass="bg-linear-to-br from-rose-500 to-red-600 shadow-lg shadow-rose-500/30"
|
|
232
|
+
iconColor="white"
|
|
233
|
+
gradientClass="from-rose-50/50 to-red-50/50"
|
|
234
|
+
[trend]="-5.2">
|
|
235
|
+
</app-stats-card>
|
|
236
|
+
</div>
|
|
237
|
+
|
|
238
|
+
<!-- Charts -->
|
|
239
|
+
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
|
240
|
+
<div class="rounded-xl border border-gray-200 bg-white p-6 shadow-sm">
|
|
241
|
+
<app-chart-line
|
|
242
|
+
[title]="'charts.visitorsOverTime' | translate"
|
|
243
|
+
[data]="visitorsData"
|
|
244
|
+
lineColor="#10b981">
|
|
245
|
+
</app-chart-line>
|
|
246
|
+
</div>
|
|
247
|
+
<div class="rounded-xl border border-gray-200 bg-white p-6 shadow-sm">
|
|
248
|
+
<app-chart-bar
|
|
249
|
+
[title]="'charts.topPages' | translate"
|
|
250
|
+
[data]="pagesData">
|
|
251
|
+
</app-chart-bar>
|
|
252
|
+
</div>
|
|
253
|
+
</div>
|
|
254
|
+
</div>
|
|
255
|
+
\`,
|
|
256
|
+
})
|
|
257
|
+
export class AnalyticsComponent {
|
|
258
|
+
visitorsData: LineChartData[] = [
|
|
259
|
+
{ label: 'Mon', value: 1200 },
|
|
260
|
+
{ label: 'Tue', value: 1900 },
|
|
261
|
+
{ label: 'Wed', value: 1500 },
|
|
262
|
+
{ label: 'Thu', value: 2200 },
|
|
263
|
+
{ label: 'Fri', value: 2800 },
|
|
264
|
+
{ label: 'Sat', value: 2100 },
|
|
265
|
+
{ label: 'Sun', value: 1600 },
|
|
266
|
+
];
|
|
267
|
+
|
|
268
|
+
pagesData: BarChartData[] = [
|
|
269
|
+
{ label: 'Homepage', value: 45000, color: '#3b82f6' },
|
|
270
|
+
{ label: 'Products', value: 32000, color: '#10b981' },
|
|
271
|
+
{ label: 'About', value: 18000, color: '#f59e0b' },
|
|
272
|
+
{ label: 'Contact', value: 12000, color: '#8b5cf6' },
|
|
273
|
+
{ label: 'Blog', value: 9500, color: '#ec4899' },
|
|
274
|
+
];
|
|
275
|
+
}`;
|
|
276
|
+
|
|
277
|
+
await fs.writeFile(
|
|
278
|
+
path.join(config.fullPath, 'src/app/features/dashboard/analytics/analytics.component.ts'),
|
|
279
|
+
analyticsComponent
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
// Users Page
|
|
283
|
+
const usersComponent = `import { Component, inject } from '@angular/core';
|
|
284
|
+
import { ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms';
|
|
285
|
+
import { TranslateModule } from '@ngx-translate/core';
|
|
286
|
+
import { DataTableComponent, TableColumn } from '@shared/components/data-table/data-table.component';
|
|
287
|
+
import { BreadcrumbComponent } from '@shared/components/breadcrumb/breadcrumb.component';
|
|
288
|
+
import { ButtonComponent } from '@shared/components/button/button.component';
|
|
289
|
+
import { NgIconComponent, provideIcons } from '@ng-icons/core';
|
|
290
|
+
import { heroPlus, heroXMark } from '@ng-icons/heroicons/outline';
|
|
291
|
+
|
|
292
|
+
@Component({
|
|
293
|
+
selector: 'app-users',
|
|
294
|
+
standalone: true,
|
|
295
|
+
imports: [ReactiveFormsModule, TranslateModule, DataTableComponent, BreadcrumbComponent, ButtonComponent, NgIconComponent],
|
|
296
|
+
viewProviders: [provideIcons({ heroPlus, heroXMark })],
|
|
297
|
+
template: \`
|
|
298
|
+
<div class="space-y-6">
|
|
299
|
+
<div class="flex items-center justify-between">
|
|
300
|
+
<div>
|
|
301
|
+
<app-breadcrumb [items]="[{ label: ('dashboard.users' | translate) }]"></app-breadcrumb>
|
|
302
|
+
<h1 class="mt-2 text-2xl font-bold text-gray-900">{{ 'dashboard.users' | translate }}</h1>
|
|
303
|
+
<p class="text-gray-500">{{ 'dashboard.welcome' | translate }}</p>
|
|
304
|
+
</div>
|
|
305
|
+
<app-button (click)="openAddUserModal()">
|
|
306
|
+
<ng-icon name="heroPlus" size="18" class="me-2"></ng-icon>
|
|
307
|
+
{{ 'actions.addUser' | translate }}
|
|
308
|
+
</app-button>
|
|
309
|
+
</div>
|
|
310
|
+
|
|
311
|
+
<app-data-table
|
|
312
|
+
[data]="users"
|
|
313
|
+
[columns]="columns"
|
|
314
|
+
[pageSize]="10">
|
|
315
|
+
</app-data-table>
|
|
316
|
+
</div>
|
|
317
|
+
|
|
318
|
+
<!-- Add User Modal -->
|
|
319
|
+
@if (showModal) {
|
|
320
|
+
<div class="fixed inset-0 z-50 flex items-center justify-center">
|
|
321
|
+
<div class="absolute inset-0 bg-black/50" (click)="closeModal()"></div>
|
|
322
|
+
<div class="relative w-full max-w-md rounded-xl bg-white p-6 shadow-xl">
|
|
323
|
+
<div class="mb-6 flex items-center justify-between">
|
|
324
|
+
<h2 class="text-xl font-semibold text-gray-900">{{ 'actions.addUser' | translate }}</h2>
|
|
325
|
+
<button type="button" (click)="closeModal()" class="rounded-lg p-1 text-gray-400 hover:bg-gray-100 hover:text-gray-600">
|
|
326
|
+
<ng-icon name="heroXMark" size="24"></ng-icon>
|
|
327
|
+
</button>
|
|
328
|
+
</div>
|
|
329
|
+
<form [formGroup]="userForm" (ngSubmit)="onSubmit()" class="space-y-4">
|
|
330
|
+
<div>
|
|
331
|
+
<label for="userName" class="block text-sm font-medium text-gray-700">{{ 'settings.fullName' | translate }}</label>
|
|
332
|
+
<input id="userName" type="text" formControlName="name"
|
|
333
|
+
class="mt-1 block w-full rounded-lg border border-gray-300 px-3 py-2 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500"
|
|
334
|
+
[class.border-danger-500]="userForm.get('name')?.invalid && userForm.get('name')?.touched" />
|
|
335
|
+
@if (userForm.get('name')?.invalid && userForm.get('name')?.touched) {
|
|
336
|
+
<p class="mt-1 text-sm text-danger-600">Name is required (min 2 characters)</p>
|
|
337
|
+
}
|
|
338
|
+
</div>
|
|
339
|
+
<div>
|
|
340
|
+
<label for="userEmail" class="block text-sm font-medium text-gray-700">{{ 'settings.email' | translate }}</label>
|
|
341
|
+
<input id="userEmail" type="email" formControlName="email"
|
|
342
|
+
class="mt-1 block w-full rounded-lg border border-gray-300 px-3 py-2 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500"
|
|
343
|
+
[class.border-danger-500]="userForm.get('email')?.invalid && userForm.get('email')?.touched" />
|
|
344
|
+
@if (userForm.get('email')?.invalid && userForm.get('email')?.touched) {
|
|
345
|
+
<p class="mt-1 text-sm text-danger-600">Valid email is required</p>
|
|
346
|
+
}
|
|
347
|
+
</div>
|
|
348
|
+
<div>
|
|
349
|
+
<label for="userRole" class="block text-sm font-medium text-gray-700">{{ 'table.role' | translate }}</label>
|
|
350
|
+
<select id="userRole" formControlName="role"
|
|
351
|
+
class="mt-1 block w-full rounded-lg border border-gray-300 px-3 py-2 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500">
|
|
352
|
+
<option value="">Select a role</option>
|
|
353
|
+
<option value="Admin">Admin</option>
|
|
354
|
+
<option value="Editor">Editor</option>
|
|
355
|
+
<option value="User">User</option>
|
|
356
|
+
</select>
|
|
357
|
+
@if (userForm.get('role')?.invalid && userForm.get('role')?.touched) {
|
|
358
|
+
<p class="mt-1 text-sm text-danger-600">Role is required</p>
|
|
359
|
+
}
|
|
360
|
+
</div>
|
|
361
|
+
<div>
|
|
362
|
+
<label for="userStatus" class="block text-sm font-medium text-gray-700">{{ 'table.status' | translate }}</label>
|
|
363
|
+
<select id="userStatus" formControlName="status"
|
|
364
|
+
class="mt-1 block w-full rounded-lg border border-gray-300 px-3 py-2 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500">
|
|
365
|
+
<option value="Active">Active</option>
|
|
366
|
+
<option value="Inactive">Inactive</option>
|
|
367
|
+
<option value="Pending">Pending</option>
|
|
368
|
+
</select>
|
|
369
|
+
</div>
|
|
370
|
+
<div class="mt-6 flex justify-end gap-3">
|
|
371
|
+
<app-button type="button" variant="secondary" (click)="closeModal()">{{ 'actions.cancel' | translate }}</app-button>
|
|
372
|
+
<app-button type="submit" [disabled]="userForm.invalid">{{ 'actions.addUser' | translate }}</app-button>
|
|
373
|
+
</div>
|
|
374
|
+
</form>
|
|
375
|
+
</div>
|
|
376
|
+
</div>
|
|
377
|
+
}
|
|
378
|
+
\`,
|
|
379
|
+
})
|
|
380
|
+
export class UsersComponent {
|
|
381
|
+
private fb = inject(FormBuilder);
|
|
382
|
+
showModal = false;
|
|
383
|
+
|
|
384
|
+
userForm = this.fb.group({
|
|
385
|
+
name: ['', [Validators.required, Validators.minLength(2)]],
|
|
386
|
+
email: ['', [Validators.required, Validators.email]],
|
|
387
|
+
role: ['', Validators.required],
|
|
388
|
+
status: ['Active'],
|
|
389
|
+
});
|
|
390
|
+
columns: TableColumn[] = [
|
|
391
|
+
{ key: 'id', label: 'ID', width: '80px', sortable: true },
|
|
392
|
+
{ key: 'name', label: 'Name', sortable: true },
|
|
393
|
+
{ key: 'email', label: 'Email', sortable: true },
|
|
394
|
+
{ key: 'role', label: 'Role', sortable: true },
|
|
395
|
+
{ key: 'status', label: 'Status', type: 'status' },
|
|
396
|
+
{ key: 'createdAt', label: 'Joined', type: 'date', sortable: true },
|
|
397
|
+
];
|
|
398
|
+
|
|
399
|
+
users = [
|
|
400
|
+
{ id: 1, name: 'John Doe', email: 'john@example.com', role: 'Admin', status: 'Active', createdAt: '2024-01-15' },
|
|
401
|
+
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', role: 'Editor', status: 'Active', createdAt: '2024-02-20' },
|
|
402
|
+
{ id: 3, name: 'Bob Wilson', email: 'bob@example.com', role: 'User', status: 'Inactive', createdAt: '2024-03-10' },
|
|
403
|
+
{ id: 4, name: 'Alice Brown', email: 'alice@example.com', role: 'User', status: 'Active', createdAt: '2024-03-15' },
|
|
404
|
+
{ id: 5, name: 'Charlie Davis', email: 'charlie@example.com', role: 'Editor', status: 'Active', createdAt: '2024-04-01' },
|
|
405
|
+
{ id: 6, name: 'Diana Miller', email: 'diana@example.com', role: 'User', status: 'Pending', createdAt: '2024-04-10' },
|
|
406
|
+
{ id: 7, name: 'Edward Jones', email: 'edward@example.com', role: 'Admin', status: 'Active', createdAt: '2024-04-15' },
|
|
407
|
+
{ id: 8, name: 'Fiona Garcia', email: 'fiona@example.com', role: 'User', status: 'Active', createdAt: '2024-05-01' },
|
|
408
|
+
{ id: 9, name: 'George Martinez', email: 'george@example.com', role: 'User', status: 'Inactive', createdAt: '2024-05-10' },
|
|
409
|
+
{ id: 10, name: 'Helen Anderson', email: 'helen@example.com', role: 'Editor', status: 'Active', createdAt: '2024-05-15' },
|
|
410
|
+
{ id: 11, name: 'Ivan Taylor', email: 'ivan@example.com', role: 'User', status: 'Active', createdAt: '2024-06-01' },
|
|
411
|
+
{ id: 12, name: 'Julia Thomas', email: 'julia@example.com', role: 'User', status: 'Pending', createdAt: '2024-06-10' },
|
|
412
|
+
];
|
|
413
|
+
|
|
414
|
+
openAddUserModal(): void {
|
|
415
|
+
this.userForm.reset({ status: 'Active' });
|
|
416
|
+
this.showModal = true;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
closeModal(): void {
|
|
420
|
+
this.showModal = false;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
onSubmit(): void {
|
|
424
|
+
if (this.userForm.valid) {
|
|
425
|
+
const newUser = {
|
|
426
|
+
id: this.users.length + 1,
|
|
427
|
+
...this.userForm.value,
|
|
428
|
+
createdAt: new Date().toISOString().split('T')[0],
|
|
429
|
+
};
|
|
430
|
+
this.users = [newUser as typeof this.users[0], ...this.users];
|
|
431
|
+
this.closeModal();
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}`;
|
|
435
|
+
|
|
436
|
+
await fs.writeFile(
|
|
437
|
+
path.join(config.fullPath, 'src/app/features/dashboard/users/users.component.ts'),
|
|
438
|
+
usersComponent
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
// Orders Page
|
|
442
|
+
const ordersComponent = `import { Component } from '@angular/core';
|
|
443
|
+
import { TranslateModule } from '@ngx-translate/core';
|
|
444
|
+
import { DataTableComponent, TableColumn } from '@shared/components/data-table/data-table.component';
|
|
445
|
+
import { BreadcrumbComponent } from '@shared/components/breadcrumb/breadcrumb.component';
|
|
446
|
+
import { StatsCardComponent } from '@shared/components/stats-card/stats-card.component';
|
|
447
|
+
|
|
448
|
+
@Component({
|
|
449
|
+
selector: 'app-orders',
|
|
450
|
+
standalone: true,
|
|
451
|
+
imports: [TranslateModule, DataTableComponent, BreadcrumbComponent, StatsCardComponent],
|
|
452
|
+
template: \`
|
|
453
|
+
<div class="space-y-6">
|
|
454
|
+
<div>
|
|
455
|
+
<app-breadcrumb [items]="[{ label: ('dashboard.orders' | translate) }]"></app-breadcrumb>
|
|
456
|
+
<h1 class="mt-2 text-2xl font-bold text-gray-900">{{ 'dashboard.orders' | translate }}</h1>
|
|
457
|
+
<p class="text-gray-500">{{ 'dashboard.welcome' | translate }}</p>
|
|
458
|
+
</div>
|
|
459
|
+
|
|
460
|
+
<!-- Order Stats -->
|
|
461
|
+
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4">
|
|
462
|
+
<app-stats-card
|
|
463
|
+
[label]="'stats.totalOrders' | translate"
|
|
464
|
+
value="1,247"
|
|
465
|
+
icon="heroShoppingCart"
|
|
466
|
+
iconBgClass="bg-linear-to-br from-indigo-500 to-blue-600 shadow-lg shadow-indigo-500/30"
|
|
467
|
+
iconColor="white"
|
|
468
|
+
gradientClass="from-indigo-50/50 to-blue-50/50">
|
|
469
|
+
</app-stats-card>
|
|
470
|
+
<app-stats-card
|
|
471
|
+
[label]="'stats.pending' | translate"
|
|
472
|
+
value="23"
|
|
473
|
+
icon="heroClock"
|
|
474
|
+
iconBgClass="bg-linear-to-br from-amber-500 to-orange-600 shadow-lg shadow-amber-500/30"
|
|
475
|
+
iconColor="white"
|
|
476
|
+
gradientClass="from-amber-50/50 to-orange-50/50">
|
|
477
|
+
</app-stats-card>
|
|
478
|
+
<app-stats-card
|
|
479
|
+
[label]="'stats.completed' | translate"
|
|
480
|
+
value="1,180"
|
|
481
|
+
icon="heroCheck"
|
|
482
|
+
iconBgClass="bg-linear-to-br from-emerald-500 to-green-600 shadow-lg shadow-emerald-500/30"
|
|
483
|
+
iconColor="white"
|
|
484
|
+
gradientClass="from-emerald-50/50 to-green-50/50">
|
|
485
|
+
</app-stats-card>
|
|
486
|
+
<app-stats-card
|
|
487
|
+
[label]="'stats.cancelled' | translate"
|
|
488
|
+
value="44"
|
|
489
|
+
icon="heroXMark"
|
|
490
|
+
iconBgClass="bg-linear-to-br from-rose-500 to-red-600 shadow-lg shadow-rose-500/30"
|
|
491
|
+
iconColor="white"
|
|
492
|
+
gradientClass="from-rose-50/50 to-red-50/50">
|
|
493
|
+
</app-stats-card>
|
|
494
|
+
</div>
|
|
495
|
+
|
|
496
|
+
<app-data-table
|
|
497
|
+
[data]="orders"
|
|
498
|
+
[columns]="columns"
|
|
499
|
+
[pageSize]="10">
|
|
500
|
+
</app-data-table>
|
|
501
|
+
</div>
|
|
502
|
+
\`,
|
|
503
|
+
})
|
|
504
|
+
export class OrdersComponent {
|
|
505
|
+
columns: TableColumn[] = [
|
|
506
|
+
{ key: 'id', label: 'Order ID', width: '100px', sortable: true },
|
|
507
|
+
{ key: 'customer', label: 'Customer', sortable: true },
|
|
508
|
+
{ key: 'items', label: 'Items', type: 'number' },
|
|
509
|
+
{ key: 'total', label: 'Total', sortable: true },
|
|
510
|
+
{ key: 'status', label: 'Status', type: 'status' },
|
|
511
|
+
{ key: 'date', label: 'Date', type: 'date', sortable: true },
|
|
512
|
+
];
|
|
513
|
+
|
|
514
|
+
orders = [
|
|
515
|
+
{ id: '#3210', customer: 'John Doe', items: 3, total: '$125.00', status: 'Completed', date: '2024-06-15' },
|
|
516
|
+
{ id: '#3209', customer: 'Jane Smith', items: 2, total: '$89.50', status: 'Processing', date: '2024-06-15' },
|
|
517
|
+
{ id: '#3208', customer: 'Bob Wilson', items: 5, total: '$234.00', status: 'Pending', date: '2024-06-14' },
|
|
518
|
+
{ id: '#3207', customer: 'Alice Brown', items: 1, total: '$156.75', status: 'Completed', date: '2024-06-14' },
|
|
519
|
+
{ id: '#3206', customer: 'Charlie Davis', items: 4, total: '$67.25', status: 'Cancelled', date: '2024-06-13' },
|
|
520
|
+
{ id: '#3205', customer: 'Diana Miller', items: 2, total: '$198.00', status: 'Completed', date: '2024-06-13' },
|
|
521
|
+
{ id: '#3204', customer: 'Edward Jones', items: 3, total: '$145.50', status: 'Processing', date: '2024-06-12' },
|
|
522
|
+
{ id: '#3203', customer: 'Fiona Garcia', items: 1, total: '$78.00', status: 'Completed', date: '2024-06-12' },
|
|
523
|
+
{ id: '#3202', customer: 'George Martinez', items: 6, total: '$312.00', status: 'Pending', date: '2024-06-11' },
|
|
524
|
+
{ id: '#3201', customer: 'Helen Anderson', items: 2, total: '$95.25', status: 'Completed', date: '2024-06-11' },
|
|
525
|
+
];
|
|
526
|
+
}`;
|
|
527
|
+
|
|
528
|
+
await fs.writeFile(
|
|
529
|
+
path.join(config.fullPath, 'src/app/features/dashboard/orders/orders.component.ts'),
|
|
530
|
+
ordersComponent
|
|
531
|
+
);
|
|
532
|
+
|
|
533
|
+
// Settings Page
|
|
534
|
+
const settingsComponent = `import { Component, inject } from '@angular/core';
|
|
535
|
+
import { FormsModule } from '@angular/forms';
|
|
536
|
+
import { TranslateModule } from '@ngx-translate/core';
|
|
537
|
+
import { BreadcrumbComponent } from '@shared/components/breadcrumb/breadcrumb.component';
|
|
538
|
+
import { ButtonComponent } from '@shared/components/button/button.component';
|
|
539
|
+
import { CardComponent } from '@shared/components/card/card.component';
|
|
540
|
+
import { TranslationService } from '@core/i18n/translation.service';
|
|
541
|
+
|
|
542
|
+
@Component({
|
|
543
|
+
selector: 'app-settings',
|
|
544
|
+
standalone: true,
|
|
545
|
+
imports: [FormsModule, TranslateModule, BreadcrumbComponent, ButtonComponent, CardComponent],
|
|
546
|
+
template: \`
|
|
547
|
+
<div class="space-y-6">
|
|
548
|
+
<div>
|
|
549
|
+
<app-breadcrumb [items]="[{ label: ('dashboard.settings' | translate) }]"></app-breadcrumb>
|
|
550
|
+
<h1 class="mt-2 text-2xl font-bold text-gray-900">{{ 'dashboard.settings' | translate }}</h1>
|
|
551
|
+
<p class="text-gray-500">{{ 'dashboard.welcome' | translate }}</p>
|
|
552
|
+
</div>
|
|
553
|
+
|
|
554
|
+
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
|
555
|
+
<!-- Profile Settings -->
|
|
556
|
+
<app-card [title]="'settings.profile' | translate">
|
|
557
|
+
<div class="space-y-4">
|
|
558
|
+
<div>
|
|
559
|
+
<label for="fullName" class="block text-sm font-medium text-gray-700">{{ 'settings.fullName' | translate }}</label>
|
|
560
|
+
<input
|
|
561
|
+
id="fullName"
|
|
562
|
+
type="text"
|
|
563
|
+
[(ngModel)]="profile.name"
|
|
564
|
+
class="mt-1 block w-full rounded-lg border border-gray-300 px-3 py-2 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500"
|
|
565
|
+
/>
|
|
566
|
+
</div>
|
|
567
|
+
<div>
|
|
568
|
+
<label for="email" class="block text-sm font-medium text-gray-700">{{ 'settings.email' | translate }}</label>
|
|
569
|
+
<input
|
|
570
|
+
id="email"
|
|
571
|
+
type="email"
|
|
572
|
+
[(ngModel)]="profile.email"
|
|
573
|
+
class="mt-1 block w-full rounded-lg border border-gray-300 px-3 py-2 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500"
|
|
574
|
+
/>
|
|
575
|
+
</div>
|
|
576
|
+
<div>
|
|
577
|
+
<label for="bio" class="block text-sm font-medium text-gray-700">{{ 'settings.bio' | translate }}</label>
|
|
578
|
+
<textarea
|
|
579
|
+
id="bio"
|
|
580
|
+
[(ngModel)]="profile.bio"
|
|
581
|
+
rows="3"
|
|
582
|
+
class="mt-1 block w-full rounded-lg border border-gray-300 px-3 py-2 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500">
|
|
583
|
+
</textarea>
|
|
584
|
+
</div>
|
|
585
|
+
<app-button>{{ 'actions.save' | translate }}</app-button>
|
|
586
|
+
</div>
|
|
587
|
+
</app-card>
|
|
588
|
+
|
|
589
|
+
<!-- Notification Settings -->
|
|
590
|
+
<app-card [title]="'settings.notifications' | translate">
|
|
591
|
+
<div class="space-y-4">
|
|
592
|
+
@for (item of notifications; track item.key) {
|
|
593
|
+
<div class="flex items-center justify-between">
|
|
594
|
+
<div>
|
|
595
|
+
<p class="font-medium text-gray-900">{{ item.labelKey | translate }}</p>
|
|
596
|
+
<p class="text-sm text-gray-500">{{ item.descriptionKey | translate }}</p>
|
|
597
|
+
</div>
|
|
598
|
+
<button
|
|
599
|
+
type="button"
|
|
600
|
+
(click)="item.enabled = !item.enabled"
|
|
601
|
+
[attr.aria-pressed]="item.enabled"
|
|
602
|
+
class="relative inline-flex h-6 w-11 cursor-pointer items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-primary-300"
|
|
603
|
+
[class]="item.enabled ? 'bg-primary-600' : 'bg-gray-200'">
|
|
604
|
+
<span
|
|
605
|
+
class="inline-block h-5 w-5 transform rounded-full bg-white border border-gray-300 transition-transform"
|
|
606
|
+
[class]="item.enabled ? 'translate-x-5' : 'translate-x-0.5'">
|
|
607
|
+
</span>
|
|
608
|
+
</button>
|
|
609
|
+
</div>
|
|
610
|
+
}
|
|
611
|
+
</div>
|
|
612
|
+
</app-card>
|
|
613
|
+
|
|
614
|
+
<!-- Security Settings -->
|
|
615
|
+
<app-card [title]="'settings.security' | translate">
|
|
616
|
+
<div class="space-y-4">
|
|
617
|
+
<div>
|
|
618
|
+
<label for="currentPassword" class="block text-sm font-medium text-gray-700">{{ 'settings.currentPassword' | translate }}</label>
|
|
619
|
+
<input
|
|
620
|
+
id="currentPassword"
|
|
621
|
+
type="password"
|
|
622
|
+
class="mt-1 block w-full rounded-lg border border-gray-300 px-3 py-2 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500"
|
|
623
|
+
/>
|
|
624
|
+
</div>
|
|
625
|
+
<div>
|
|
626
|
+
<label for="newPassword" class="block text-sm font-medium text-gray-700">{{ 'settings.newPassword' | translate }}</label>
|
|
627
|
+
<input
|
|
628
|
+
id="newPassword"
|
|
629
|
+
type="password"
|
|
630
|
+
class="mt-1 block w-full rounded-lg border border-gray-300 px-3 py-2 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500"
|
|
631
|
+
/>
|
|
632
|
+
</div>
|
|
633
|
+
<div>
|
|
634
|
+
<label for="confirmPassword" class="block text-sm font-medium text-gray-700">{{ 'settings.confirmPassword' | translate }}</label>
|
|
635
|
+
<input
|
|
636
|
+
id="confirmPassword"
|
|
637
|
+
type="password"
|
|
638
|
+
class="mt-1 block w-full rounded-lg border border-gray-300 px-3 py-2 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500"
|
|
639
|
+
/>
|
|
640
|
+
</div>
|
|
641
|
+
<app-button>{{ 'settings.updatePassword' | translate }}</app-button>
|
|
642
|
+
</div>
|
|
643
|
+
</app-card>
|
|
644
|
+
|
|
645
|
+
<!-- Danger Zone -->
|
|
646
|
+
<app-card [title]="'settings.dangerZone' | translate">
|
|
647
|
+
<div class="space-y-4">
|
|
648
|
+
<p class="text-sm text-gray-500">
|
|
649
|
+
{{ 'settings.deleteWarning' | translate }}
|
|
650
|
+
</p>
|
|
651
|
+
<app-button variant="danger">{{ 'settings.deleteAccount' | translate }}</app-button>
|
|
652
|
+
</div>
|
|
653
|
+
</app-card>
|
|
654
|
+
</div>
|
|
655
|
+
</div>
|
|
656
|
+
\`,
|
|
657
|
+
})
|
|
658
|
+
export class SettingsComponent {
|
|
659
|
+
private translationService = inject(TranslationService);
|
|
660
|
+
|
|
661
|
+
profile = {
|
|
662
|
+
name: 'Admin User',
|
|
663
|
+
email: 'admin@example.com',
|
|
664
|
+
bio: 'Dashboard administrator',
|
|
665
|
+
};
|
|
666
|
+
|
|
667
|
+
notifications = [
|
|
668
|
+
{ key: 'email', labelKey: 'settings.emailNotifications', descriptionKey: 'settings.emailNotifications', enabled: true },
|
|
669
|
+
{ key: 'push', labelKey: 'settings.pushNotifications', descriptionKey: 'settings.pushNotifications', enabled: false },
|
|
670
|
+
{ key: 'weekly', labelKey: 'settings.weeklyReport', descriptionKey: 'settings.weeklyReport', enabled: true },
|
|
671
|
+
{ key: 'marketing', labelKey: 'settings.marketing', descriptionKey: 'settings.marketing', enabled: false },
|
|
672
|
+
];
|
|
673
|
+
}`;
|
|
674
|
+
|
|
675
|
+
await fs.writeFile(
|
|
676
|
+
path.join(config.fullPath, 'src/app/features/dashboard/settings/settings.component.ts'),
|
|
677
|
+
settingsComponent
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
module.exports = { createPages };
|