@imansi/templates 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +557 -0
  3. package/package.json +35 -0
  4. package/src/auth/ForgotPasswordPage.jsx +176 -0
  5. package/src/auth/LoginPage.jsx +265 -0
  6. package/src/auth/RegisterPage.jsx +302 -0
  7. package/src/auth/ResetPasswordPage.jsx +158 -0
  8. package/src/auth/Verify2FAPage.jsx +175 -0
  9. package/src/dashboard/BillingPage.jsx +345 -0
  10. package/src/dashboard/DashboardHome.jsx +246 -0
  11. package/src/dashboard/NotificationsPage.jsx +230 -0
  12. package/src/dashboard/ProfilePage.jsx +272 -0
  13. package/src/dashboard/SettingsPage.jsx +415 -0
  14. package/src/dashboard/UsersTablePage.jsx +317 -0
  15. package/src/extra/CareersPage.jsx +166 -0
  16. package/src/extra/ChangelogPage.jsx +127 -0
  17. package/src/extra/CheckoutPage.jsx +383 -0
  18. package/src/extra/ComingSoonPage.jsx +147 -0
  19. package/src/extra/ErrorPage.jsx +91 -0
  20. package/src/extra/InvoicePage.jsx +241 -0
  21. package/src/extra/LegalPage.jsx +115 -0
  22. package/src/extra/MaintenancePage.jsx +172 -0
  23. package/src/extra/OnboardingPage.jsx +197 -0
  24. package/src/extra/StatusPage.jsx +181 -0
  25. package/src/index.js +53 -0
  26. package/src/layouts/AuthLayout.jsx +104 -0
  27. package/src/layouts/DashboardLayout.jsx +249 -0
  28. package/src/layouts/MarketingLayout.jsx +184 -0
  29. package/src/marketing/AboutPage.jsx +189 -0
  30. package/src/marketing/BlogPage.jsx +182 -0
  31. package/src/marketing/BlogPostPage.jsx +192 -0
  32. package/src/marketing/ContactPage.jsx +546 -0
  33. package/src/marketing/DocsPage.jsx +256 -0
  34. package/src/marketing/HomePage.jsx +702 -0
  35. package/src/marketing/LandingPage.jsx +755 -0
  36. package/src/marketing/PricingPage.jsx +205 -0
@@ -0,0 +1,345 @@
1
+ import {
2
+ Badge,
3
+ Button,
4
+ Card,
5
+ CardContent,
6
+ CardDescription,
7
+ CardHeader,
8
+ CardTitle,
9
+ Separator,
10
+ Table,
11
+ TableBody,
12
+ TableCell,
13
+ TableHead,
14
+ TableHeader,
15
+ TableRow,
16
+ cn,
17
+ } from '@imansi/ui';
18
+ import { DashboardLayout } from '../layouts/DashboardLayout.jsx';
19
+
20
+ export function BillingPage({
21
+ brandName,
22
+ brandLogo,
23
+ navSections,
24
+ user,
25
+ userMenuItems,
26
+ notificationsCount,
27
+ onLogout,
28
+
29
+ title = 'Facturación',
30
+ subtitle = 'Administrá tu plan, método de pago y facturas.',
31
+
32
+ currentPlan,
33
+ // { name, price, period, features, renewsOn }
34
+
35
+ availablePlans = [],
36
+ // [{ name, price, period, description, features, popular }]
37
+
38
+ paymentMethod,
39
+ // { brand, last4, expires, holder }
40
+
41
+ invoices = [],
42
+ // [{ id, date, amount, status: 'paid'|'pending'|'failed' }]
43
+
44
+ statusMap = {
45
+ paid: { label: 'Pagada', variant: 'success' },
46
+ pending: { label: 'Pendiente', variant: 'warning' },
47
+ failed: { label: 'Fallida', variant: 'destructive' },
48
+ },
49
+
50
+ onUpgrade,
51
+ onCancelPlan,
52
+ onUpdatePayment,
53
+ onDownloadInvoice,
54
+ }) {
55
+ return (
56
+ <DashboardLayout
57
+ brandName={brandName}
58
+ brandLogo={brandLogo}
59
+ navSections={navSections}
60
+ user={user}
61
+ userMenuItems={userMenuItems}
62
+ notificationsCount={notificationsCount}
63
+ onLogout={onLogout}
64
+ title={title}
65
+ >
66
+ <div className="space-y-6 max-w-6xl mx-auto">
67
+ {/* Header */}
68
+ <div className="space-y-1">
69
+ <h1 className="text-h2 font-bold tracking-tight">{title}</h1>
70
+ <p className="text-small text-muted-foreground">{subtitle}</p>
71
+ </div>
72
+
73
+ {/* Plan actual + método de pago */}
74
+ <div className="grid gap-6 lg:grid-cols-3">
75
+ {/* Plan actual */}
76
+ {currentPlan && (
77
+ <Card className="lg:col-span-2">
78
+ <CardHeader>
79
+ <div className="flex items-start justify-between gap-4">
80
+ <div className="space-y-1">
81
+ <CardTitle className="text-h4">
82
+ Plan {currentPlan.name}
83
+ </CardTitle>
84
+ <CardDescription>
85
+ Se renueva el {currentPlan.renewsOn}
86
+ </CardDescription>
87
+ </div>
88
+ <Badge variant="success">Activo</Badge>
89
+ </div>
90
+ </CardHeader>
91
+ <CardContent className="space-y-5">
92
+ <div className="flex items-baseline gap-1">
93
+ <span className="text-display font-bold tracking-tight">
94
+ {currentPlan.price}
95
+ </span>
96
+ {currentPlan.period && (
97
+ <span className="text-small text-muted-foreground">
98
+ /{currentPlan.period}
99
+ </span>
100
+ )}
101
+ </div>
102
+
103
+ {currentPlan.features?.length > 0 && (
104
+ <ul className="grid gap-2 sm:grid-cols-2">
105
+ {currentPlan.features.map((f, i) => (
106
+ <li
107
+ key={i}
108
+ className="flex items-start gap-2 text-small text-muted-foreground"
109
+ >
110
+ <span className="mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-success/10 text-success">
111
+ <svg width="9" height="9" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
112
+ <polyline points="2,6 5,9 10,3" />
113
+ </svg>
114
+ </span>
115
+ {f}
116
+ </li>
117
+ ))}
118
+ </ul>
119
+ )}
120
+
121
+ <Separator />
122
+
123
+ <div className="flex flex-wrap gap-2">
124
+ {onUpgrade && <Button onClick={onUpgrade}>Cambiar plan</Button>}
125
+ {onCancelPlan && (
126
+ <Button variant="ghost" onClick={onCancelPlan}>
127
+ Cancelar suscripción
128
+ </Button>
129
+ )}
130
+ </div>
131
+ </CardContent>
132
+ </Card>
133
+ )}
134
+
135
+ {/* Método de pago */}
136
+ <Card>
137
+ <CardHeader>
138
+ <CardTitle className="text-h4">Método de pago</CardTitle>
139
+ </CardHeader>
140
+ <CardContent className="space-y-5">
141
+ {paymentMethod ? (
142
+ <>
143
+ <div className="rounded-lg border border-border bg-muted/30 p-4 space-y-3">
144
+ <div className="flex items-center justify-between">
145
+ <span className="text-small font-semibold uppercase tracking-wider text-muted-foreground">
146
+ {paymentMethod.brand}
147
+ </span>
148
+ <svg
149
+ width="32"
150
+ height="20"
151
+ viewBox="0 0 32 20"
152
+ fill="none"
153
+ className="text-muted-foreground"
154
+ >
155
+ <rect
156
+ x="0.5"
157
+ y="0.5"
158
+ width="31"
159
+ height="19"
160
+ rx="2.5"
161
+ stroke="currentColor"
162
+ />
163
+ <line
164
+ x1="0"
165
+ y1="7"
166
+ x2="32"
167
+ y2="7"
168
+ stroke="currentColor"
169
+ />
170
+ </svg>
171
+ </div>
172
+ <div className="space-y-0.5">
173
+ <p className="text-small font-mono tracking-wider">
174
+ •••• •••• •••• {paymentMethod.last4}
175
+ </p>
176
+ <p className="text-caption text-muted-foreground">
177
+ Expira {paymentMethod.expires}
178
+ </p>
179
+ </div>
180
+ {paymentMethod.holder && (
181
+ <p className="text-caption text-muted-foreground pt-1 border-t border-border">
182
+ {paymentMethod.holder}
183
+ </p>
184
+ )}
185
+ </div>
186
+ <Button
187
+ variant="outline"
188
+ fullWidth
189
+ onClick={onUpdatePayment}
190
+ >
191
+ Actualizar tarjeta
192
+ </Button>
193
+ </>
194
+ ) : (
195
+ <div className="text-center py-4 space-y-3">
196
+ <p className="text-small text-muted-foreground">
197
+ No hay método de pago configurado.
198
+ </p>
199
+ <Button fullWidth onClick={onUpdatePayment}>
200
+ Agregar tarjeta
201
+ </Button>
202
+ </div>
203
+ )}
204
+ </CardContent>
205
+ </Card>
206
+ </div>
207
+
208
+ {/* Planes disponibles */}
209
+ {availablePlans.length > 0 && (
210
+ <div className="space-y-4">
211
+ <h3 className="text-h4 font-semibold">Planes disponibles</h3>
212
+ <div
213
+ className={cn(
214
+ 'grid gap-5',
215
+ availablePlans.length === 2 && 'md:grid-cols-2',
216
+ availablePlans.length === 3 && 'md:grid-cols-3',
217
+ availablePlans.length >= 4 && 'md:grid-cols-2 lg:grid-cols-4'
218
+ )}
219
+ >
220
+ {availablePlans.map((plan, i) => (
221
+ <div
222
+ key={i}
223
+ className={cn(
224
+ 'rounded-xl border bg-card p-6 space-y-5 flex flex-col',
225
+ plan.popular && 'border-primary ring-1 ring-primary'
226
+ )}
227
+ >
228
+ {plan.popular && (
229
+ <Badge variant="primary" className="self-start">
230
+ Recomendado
231
+ </Badge>
232
+ )}
233
+ <div className="space-y-1">
234
+ <h4 className="text-h4 font-semibold">{plan.name}</h4>
235
+ {plan.description && (
236
+ <p className="text-caption text-muted-foreground">
237
+ {plan.description}
238
+ </p>
239
+ )}
240
+ </div>
241
+ <div className="flex items-baseline gap-1">
242
+ <span className="text-h1 font-bold tracking-tight">
243
+ {plan.price}
244
+ </span>
245
+ {plan.period && (
246
+ <span className="text-small text-muted-foreground">
247
+ /{plan.period}
248
+ </span>
249
+ )}
250
+ </div>
251
+ {plan.features?.length > 0 && (
252
+ <ul className="space-y-2 flex-1">
253
+ {plan.features.map((f, j) => (
254
+ <li
255
+ key={j}
256
+ className="flex items-start gap-2 text-small text-muted-foreground"
257
+ >
258
+ <span className="mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-success/10 text-success">
259
+ <svg width="9" height="9" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
260
+ <polyline points="2,6 5,9 10,3" />
261
+ </svg>
262
+ </span>
263
+ {f}
264
+ </li>
265
+ ))}
266
+ </ul>
267
+ )}
268
+ <Button
269
+ variant={plan.popular ? 'primary' : 'outline'}
270
+ fullWidth
271
+ onClick={() => onUpgrade?.(plan)}
272
+ >
273
+ Elegir plan
274
+ </Button>
275
+ </div>
276
+ ))}
277
+ </div>
278
+ </div>
279
+ )}
280
+
281
+ {/* Historial de facturas */}
282
+ {invoices.length > 0 && (
283
+ <Card>
284
+ <CardHeader>
285
+ <CardTitle className="text-h4">Historial de facturas</CardTitle>
286
+ <CardDescription>
287
+ Descargá tus facturas anteriores.
288
+ </CardDescription>
289
+ </CardHeader>
290
+ <CardContent className="p-0">
291
+ <Table>
292
+ <TableHeader>
293
+ <TableRow>
294
+ <TableHead>Factura</TableHead>
295
+ <TableHead>Fecha</TableHead>
296
+ <TableHead>Monto</TableHead>
297
+ <TableHead>Estado</TableHead>
298
+ <TableHead className="text-right">Acciones</TableHead>
299
+ </TableRow>
300
+ </TableHeader>
301
+ <TableBody>
302
+ {invoices.map((invoice) => {
303
+ const status = statusMap[invoice.status] || statusMap.pending;
304
+ return (
305
+ <TableRow key={invoice.id}>
306
+ <TableCell className="font-mono text-small">
307
+ {invoice.id}
308
+ </TableCell>
309
+ <TableCell className="text-muted-foreground">
310
+ {invoice.date}
311
+ </TableCell>
312
+ <TableCell className="font-medium">
313
+ {invoice.amount}
314
+ </TableCell>
315
+ <TableCell>
316
+ <Badge variant={status.variant}>
317
+ {status.label}
318
+ </Badge>
319
+ </TableCell>
320
+ <TableCell className="text-right">
321
+ <Button
322
+ variant="ghost"
323
+ size="sm"
324
+ onClick={() => onDownloadInvoice?.(invoice)}
325
+ >
326
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
327
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
328
+ <polyline points="7 10 12 15 17 10" />
329
+ <line x1="12" y1="15" x2="12" y2="3" />
330
+ </svg>
331
+ Descargar
332
+ </Button>
333
+ </TableCell>
334
+ </TableRow>
335
+ );
336
+ })}
337
+ </TableBody>
338
+ </Table>
339
+ </CardContent>
340
+ </Card>
341
+ )}
342
+ </div>
343
+ </DashboardLayout>
344
+ );
345
+ }
@@ -0,0 +1,246 @@
1
+ import { Link } from 'react-router-dom';
2
+ import {
3
+ Avatar,
4
+ AvatarFallback,
5
+ Badge,
6
+ Button,
7
+ Card,
8
+ CardContent,
9
+ CardHeader,
10
+ CardTitle,
11
+ Progress,
12
+ cn,
13
+ } from '@imansi/ui';
14
+ import { DashboardLayout } from '../layouts/DashboardLayout.jsx';
15
+
16
+ export function DashboardHome({
17
+ brandName,
18
+ brandLogo,
19
+ navSections,
20
+ user,
21
+ userMenuItems,
22
+ notificationsCount,
23
+ onLogout,
24
+
25
+ title = 'Panel',
26
+ subtitle = 'Resumen general de tu actividad.',
27
+
28
+ // Stats
29
+ stats = [],
30
+ // [{ label, value, change, trend: 'up'|'down'|'flat', icon }]
31
+
32
+ // Gráficos
33
+ chartTitle = 'Ingresos de los últimos 7 días',
34
+ chartData = [],
35
+ // [{ label, value }] con value entre 0-100
36
+
37
+ // Actividad
38
+ activityTitle = 'Actividad reciente',
39
+ activityItems = [],
40
+ // [{ id, user: { name, avatar }, action, target, time }]
41
+
42
+ // Top items
43
+ topTitle = 'Top productos',
44
+ topItems = [],
45
+ // [{ id, name, value, progress }]
46
+
47
+ onViewAllActivity,
48
+ onViewAllTop,
49
+ }) {
50
+ return (
51
+ <DashboardLayout
52
+ brandName={brandName}
53
+ brandLogo={brandLogo}
54
+ navSections={navSections}
55
+ user={user}
56
+ userMenuItems={userMenuItems}
57
+ notificationsCount={notificationsCount}
58
+ onLogout={onLogout}
59
+ title={title}
60
+ >
61
+ <div className="space-y-8 max-w-7xl mx-auto">
62
+ {/* Header */}
63
+ <div className="flex flex-wrap items-start justify-between gap-4">
64
+ <div className="space-y-1">
65
+ <h1 className="text-h2 font-bold tracking-tight">{title}</h1>
66
+ <p className="text-small text-muted-foreground">{subtitle}</p>
67
+ </div>
68
+ <div className="flex gap-2">
69
+ <Button variant="outline" size="sm">
70
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
71
+ <rect x="3" y="4" width="18" height="18" rx="2" />
72
+ <line x1="16" y1="2" x2="16" y2="6" />
73
+ <line x1="8" y1="2" x2="8" y2="6" />
74
+ <line x1="3" y1="10" x2="21" y2="10" />
75
+ </svg>
76
+ Últimos 7 días
77
+ </Button>
78
+ <Button size="sm">
79
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
80
+ <line x1="12" y1="5" x2="12" y2="19" />
81
+ <line x1="5" y1="12" x2="19" y2="12" />
82
+ </svg>
83
+ Nuevo
84
+ </Button>
85
+ </div>
86
+ </div>
87
+
88
+ {/* Stats */}
89
+ {stats.length > 0 && (
90
+ <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
91
+ {stats.map((stat, i) => (
92
+ <Card key={i}>
93
+ <CardContent className="p-5 space-y-3">
94
+ <div className="flex items-start justify-between">
95
+ <div className="space-y-1">
96
+ <p className="text-caption uppercase tracking-wider font-semibold text-muted-foreground">
97
+ {stat.label}
98
+ </p>
99
+ <p className="text-h2 font-bold tracking-tight">
100
+ {stat.value}
101
+ </p>
102
+ </div>
103
+ {stat.icon && (
104
+ <div className="flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10 text-primary shrink-0">
105
+ {stat.icon}
106
+ </div>
107
+ )}
108
+ </div>
109
+ {stat.change && (
110
+ <div className="flex items-center gap-1.5 text-caption">
111
+ <span
112
+ className={cn(
113
+ 'inline-flex items-center gap-0.5 font-medium',
114
+ stat.trend === 'up' && 'text-success',
115
+ stat.trend === 'down' && 'text-destructive',
116
+ (!stat.trend || stat.trend === 'flat') &&
117
+ 'text-muted-foreground'
118
+ )}
119
+ >
120
+ {stat.trend === 'up' && '↑'}
121
+ {stat.trend === 'down' && '↓'}
122
+ {stat.trend === 'flat' && '→'}
123
+ {stat.change}
124
+ </span>
125
+ <span className="text-muted-foreground">
126
+ vs. período anterior
127
+ </span>
128
+ </div>
129
+ )}
130
+ </CardContent>
131
+ </Card>
132
+ ))}
133
+ </div>
134
+ )}
135
+
136
+ {/* Chart + Activity */}
137
+ <div className="grid gap-6 lg:grid-cols-3">
138
+ {/* Chart */}
139
+ {chartData.length > 0 && (
140
+ <Card className="lg:col-span-2">
141
+ <CardHeader>
142
+ <CardTitle className="text-h4">{chartTitle}</CardTitle>
143
+ </CardHeader>
144
+ <CardContent>
145
+ {/* Barras simples */}
146
+ <div className="flex items-end justify-between gap-2 h-56">
147
+ {chartData.map((item, i) => (
148
+ <div
149
+ key={i}
150
+ className="flex-1 flex flex-col items-center gap-2 group"
151
+ >
152
+ <div className="w-full flex items-end justify-center h-full">
153
+ <div
154
+ className="w-full max-w-[40px] rounded-t-md bg-primary/80 transition-all group-hover:bg-primary"
155
+ style={{ height: `${item.value}%` }}
156
+ title={`${item.value}%`}
157
+ />
158
+ </div>
159
+ <span className="text-caption text-muted-foreground">
160
+ {item.label}
161
+ </span>
162
+ </div>
163
+ ))}
164
+ </div>
165
+ </CardContent>
166
+ </Card>
167
+ )}
168
+
169
+ {/* Activity */}
170
+ {activityItems.length > 0 && (
171
+ <Card>
172
+ <CardHeader className="flex flex-row items-center justify-between space-y-0">
173
+ <CardTitle className="text-h4">{activityTitle}</CardTitle>
174
+ {onViewAllActivity && (
175
+ <button
176
+ onClick={onViewAllActivity}
177
+ className="text-caption text-primary hover:underline"
178
+ >
179
+ Ver todo
180
+ </button>
181
+ )}
182
+ </CardHeader>
183
+ <CardContent className="space-y-4">
184
+ {activityItems.slice(0, 5).map((item) => (
185
+ <div key={item.id} className="flex items-start gap-3">
186
+ <Avatar size="sm">
187
+ {item.user?.avatar ? (
188
+ <img src={item.user.avatar} alt="" />
189
+ ) : (
190
+ <AvatarFallback>
191
+ {item.user?.name?.charAt(0) || 'U'}
192
+ </AvatarFallback>
193
+ )}
194
+ </Avatar>
195
+ <div className="flex-1 min-w-0 space-y-0.5">
196
+ <p className="text-small leading-snug">
197
+ <span className="font-medium">
198
+ {item.user?.name}
199
+ </span>{' '}
200
+ <span className="text-muted-foreground">
201
+ {item.action}
202
+ </span>{' '}
203
+ <span className="font-medium">{item.target}</span>
204
+ </p>
205
+ <p className="text-caption text-muted-foreground">
206
+ {item.time}
207
+ </p>
208
+ </div>
209
+ </div>
210
+ ))}
211
+ </CardContent>
212
+ </Card>
213
+ )}
214
+ </div>
215
+
216
+ {/* Top items */}
217
+ {topItems.length > 0 && (
218
+ <Card>
219
+ <CardHeader className="flex flex-row items-center justify-between space-y-0">
220
+ <CardTitle className="text-h4">{topTitle}</CardTitle>
221
+ {onViewAllTop && (
222
+ <button
223
+ onClick={onViewAllTop}
224
+ className="text-caption text-primary hover:underline"
225
+ >
226
+ Ver todo
227
+ </button>
228
+ )}
229
+ </CardHeader>
230
+ <CardContent className="space-y-4">
231
+ {topItems.map((item) => (
232
+ <div key={item.id} className="space-y-2">
233
+ <div className="flex items-center justify-between text-small">
234
+ <span className="font-medium">{item.name}</span>
235
+ <span className="text-muted-foreground">{item.value}</span>
236
+ </div>
237
+ <Progress value={item.progress} size="sm" />
238
+ </div>
239
+ ))}
240
+ </CardContent>
241
+ </Card>
242
+ )}
243
+ </div>
244
+ </DashboardLayout>
245
+ );
246
+ }