@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,197 @@
1
+ import { useState } from 'react';
2
+ import {
3
+ Button,
4
+ Card,
5
+ CardContent,
6
+ Input,
7
+ Label,
8
+ Progress,
9
+ Separator,
10
+ cn,
11
+ } from '@imansi/ui';
12
+
13
+ export function OnboardingPage({
14
+ brandName = 'Imansi',
15
+ brandLogo,
16
+ steps = [],
17
+ // [{ title, description, content: ({form, setForm}) => JSX, optional }]
18
+ currentStep: externalStep,
19
+ onStepChange,
20
+ onComplete,
21
+ onSkip,
22
+ onBack,
23
+ finishText = 'Finalizar',
24
+ nextText = 'Siguiente',
25
+ backText = 'Atrás',
26
+ skipText = 'Saltar',
27
+ welcomeTitle = '¡Bienvenido a Imansi!',
28
+ welcomeSubtitle = 'Vamos a configurar tu cuenta en pocos pasos.',
29
+ className,
30
+ }) {
31
+ const [internalStep, setInternalStep] = useState(0);
32
+ const [form, setForm] = useState({});
33
+ const step = externalStep ?? internalStep;
34
+ const totalSteps = steps.length;
35
+ const currentData = steps[step] || {};
36
+ const isFirst = step === 0;
37
+ const isLast = step === totalSteps - 1;
38
+ const progress = ((step + 1) / totalSteps) * 100;
39
+
40
+ function goTo(next) {
41
+ if (onStepChange) onStepChange(next);
42
+ else setInternalStep(next);
43
+ }
44
+
45
+ function handleNext() {
46
+ if (isLast) {
47
+ onComplete?.(form);
48
+ } else {
49
+ goTo(step + 1);
50
+ }
51
+ }
52
+
53
+ function handleBack() {
54
+ if (isFirst) {
55
+ onBack?.();
56
+ } else {
57
+ goTo(step - 1);
58
+ }
59
+ }
60
+
61
+ return (
62
+ <div className={cn('min-h-dvh bg-background text-foreground flex flex-col', className)}>
63
+ {/* Header */}
64
+ <header className="border-b border-border bg-card shrink-0">
65
+ <div className="imansi-container h-16 flex items-center justify-between">
66
+ <div className="flex items-center gap-2.5">
67
+ {brandLogo || (
68
+ <div className="h-9 w-9 rounded-lg bg-primary text-primary-foreground flex items-center justify-center font-bold">
69
+ {brandName.charAt(0)}
70
+ </div>
71
+ )}
72
+ <span className="text-h4 font-semibold tracking-tight">{brandName}</span>
73
+ </div>
74
+ {onSkip && (
75
+ <button
76
+ onClick={onSkip}
77
+ className="text-small text-muted-foreground hover:text-foreground transition-colors"
78
+ >
79
+ {skipText}
80
+ </button>
81
+ )}
82
+ </div>
83
+ </header>
84
+
85
+ {/* Progress bar */}
86
+ <div className="h-1 bg-muted shrink-0">
87
+ <div
88
+ className="h-full bg-primary transition-all duration-500"
89
+ style={{ width: `${progress}%` }}
90
+ />
91
+ </div>
92
+
93
+ {/* Contenido */}
94
+ <main className="flex-1 flex items-center justify-center p-6 sm:p-10">
95
+ <div className="w-full max-w-2xl">
96
+ {/* Stepper dots */}
97
+ <div className="flex items-center justify-center gap-2 mb-10">
98
+ {steps.map((s, i) => (
99
+ <div key={i} className="flex items-center gap-2">
100
+ <div
101
+ className={cn(
102
+ 'flex h-8 w-8 items-center justify-center rounded-full text-caption font-semibold transition-colors',
103
+ i < step && 'bg-primary text-primary-foreground',
104
+ i === step && 'bg-primary text-primary-foreground ring-4 ring-primary/20',
105
+ i > step && 'bg-muted text-muted-foreground'
106
+ )}
107
+ >
108
+ {i < step ? (
109
+ <svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
110
+ <polyline points="2,6 5,9 10,3" />
111
+ </svg>
112
+ ) : (
113
+ i + 1
114
+ )}
115
+ </div>
116
+ {i < totalSteps - 1 && (
117
+ <div
118
+ className={cn(
119
+ 'h-0.5 w-8 sm:w-12 transition-colors',
120
+ i < step ? 'bg-primary' : 'bg-border'
121
+ )}
122
+ />
123
+ )}
124
+ </div>
125
+ ))}
126
+ </div>
127
+
128
+ {/* Card */}
129
+ <Card>
130
+ <CardContent className="p-8 sm:p-10 space-y-8">
131
+ {/* Header */}
132
+ <div className="space-y-2 text-center">
133
+ {step === 0 && !currentData.title ? (
134
+ <>
135
+ <h1 className="text-h2 font-bold tracking-tight">
136
+ {welcomeTitle}
137
+ </h1>
138
+ <p className="text-small text-muted-foreground">
139
+ {welcomeSubtitle}
140
+ </p>
141
+ </>
142
+ ) : (
143
+ <>
144
+ <p className="text-caption uppercase tracking-widest text-muted-foreground">
145
+ Paso {step + 1} de {totalSteps}
146
+ </p>
147
+ <h1 className="text-h2 font-bold tracking-tight">
148
+ {currentData.title}
149
+ </h1>
150
+ {currentData.description && (
151
+ <p className="text-small text-muted-foreground leading-relaxed max-w-md mx-auto">
152
+ {currentData.description}
153
+ </p>
154
+ )}
155
+ </>
156
+ )}
157
+ </div>
158
+
159
+ {/* Contenido del paso */}
160
+ {currentData.content && (
161
+ <div className="pt-2">
162
+ {currentData.content({ form, setForm })}
163
+ </div>
164
+ )}
165
+
166
+ <Separator />
167
+
168
+ {/* Navegación */}
169
+ <div className="flex items-center justify-between gap-3">
170
+ <Button
171
+ variant="ghost"
172
+ onClick={handleBack}
173
+ disabled={isFirst && !onBack}
174
+ >
175
+ {backText}
176
+ </Button>
177
+ <Button onClick={handleNext}>
178
+ {isLast ? finishText : nextText}
179
+ {!isLast && (
180
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
181
+ <path d="M5 3L9 7L5 11" />
182
+ </svg>
183
+ )}
184
+ </Button>
185
+ </div>
186
+ </CardContent>
187
+ </Card>
188
+
189
+ {/* Hint de paso actual */}
190
+ <p className="text-center text-caption text-muted-foreground mt-6">
191
+ {step + 1} / {totalSteps} pasos completados
192
+ </p>
193
+ </div>
194
+ </main>
195
+ </div>
196
+ );
197
+ }
@@ -0,0 +1,181 @@
1
+ import {
2
+ Badge,
3
+ Card,
4
+ CardContent,
5
+ Separator,
6
+ cn,
7
+ } from '@imansi/ui';
8
+
9
+ export function StatusPage({
10
+ brandName = 'Imansi',
11
+ brandLogo,
12
+
13
+ title = 'Estado del sistema',
14
+ subtitle = 'Información en tiempo real sobre el funcionamiento de nuestros servicios.',
15
+ overallStatus = 'operational',
16
+ overallMap = {
17
+ operational: { label: 'Todos los sistemas operativos', color: 'bg-success', textColor: 'text-success' },
18
+ degraded: { label: 'Rendimiento degradado', color: 'bg-warning', textColor: 'text-warning' },
19
+ outage: { label: 'Interrupción del servicio', color: 'bg-destructive', textColor: 'text-destructive' },
20
+ maintenance: { label: 'Mantenimiento programado', color: 'bg-info', textColor: 'text-info' },
21
+ },
22
+
23
+ services = [],
24
+ // [{ name, description, status: 'operational'|'degraded'|'outage'|'maintenance', uptime, responseTime }]
25
+
26
+ incidentsTitle = 'Historial de incidentes',
27
+ incidents = [],
28
+ // [{ date, title, status: 'resolved'|'monitoring'|'investigating', description }]
29
+
30
+ statusLabels = {
31
+ operational: { label: 'Operativo', variant: 'success', color: 'bg-success' },
32
+ degraded: { label: 'Degradado', variant: 'warning', color: 'bg-warning' },
33
+ outage: { label: 'Interrumpido', variant: 'destructive', color: 'bg-destructive' },
34
+ maintenance: { label: 'Mantenimiento', variant: 'info', color: 'bg-info' },
35
+ },
36
+ }) {
37
+ const overall = overallMap[overallStatus] || overallMap.operational;
38
+
39
+ return (
40
+ <div className="min-h-dvh bg-background text-foreground">
41
+ {/* Header */}
42
+ <header className="border-b border-border bg-card">
43
+ <div className="imansi-container h-16 flex items-center gap-2.5">
44
+ {brandLogo || (
45
+ <div className="h-9 w-9 rounded-lg bg-primary text-primary-foreground flex items-center justify-center font-bold">
46
+ {brandName.charAt(0)}
47
+ </div>
48
+ )}
49
+ <span className="text-h4 font-semibold tracking-tight">{brandName}</span>
50
+ </div>
51
+ </header>
52
+
53
+ <main className="imansi-container py-12 max-w-4xl">
54
+ {/* Hero */}
55
+ <div className="text-center mb-10 space-y-3">
56
+ <h1 className="text-h1 font-bold tracking-tight">{title}</h1>
57
+ <p className="text-small text-muted-foreground">{subtitle}</p>
58
+ </div>
59
+
60
+ {/* Estado general */}
61
+ <Card className="mb-10">
62
+ <CardContent className="p-6">
63
+ <div className="flex items-center gap-3">
64
+ <span className={cn('relative flex h-3 w-3', overall.color, 'rounded-full')}>
65
+ {overallStatus === 'operational' && (
66
+ <span className={cn('absolute inset-0 rounded-full animate-ping', overall.color, 'opacity-75')} />
67
+ )}
68
+ </span>
69
+ <span className={cn('text-h4 font-semibold', overall.textColor)}>
70
+ {overall.label}
71
+ </span>
72
+ </div>
73
+ </CardContent>
74
+ </Card>
75
+
76
+ {/* Servicios */}
77
+ {services.length > 0 && (
78
+ <section className="mb-12">
79
+ <h2 className="text-h3 font-semibold mb-5">Servicios</h2>
80
+ <Card>
81
+ <CardContent className="p-0">
82
+ <ul className="divide-y divide-border">
83
+ {services.map((service, i) => {
84
+ const statusInfo = statusLabels[service.status] || statusLabels.operational;
85
+ return (
86
+ <li key={i} className="p-5">
87
+ <div className="flex flex-wrap items-center justify-between gap-4">
88
+ <div className="space-y-1 min-w-0 flex-1">
89
+ <div className="flex items-center gap-2">
90
+ <span className={cn('h-2.5 w-2.5 rounded-full', statusInfo.color)} />
91
+ <p className="text-small font-semibold">{service.name}</p>
92
+ </div>
93
+ {service.description && (
94
+ <p className="text-caption text-muted-foreground">
95
+ {service.description}
96
+ </p>
97
+ )}
98
+ </div>
99
+ <div className="flex items-center gap-4 text-right">
100
+ {service.uptime && (
101
+ <div>
102
+ <p className="text-caption text-muted-foreground">Uptime</p>
103
+ <p className="text-small font-medium tabular-nums">{service.uptime}</p>
104
+ </div>
105
+ )}
106
+ {service.responseTime && (
107
+ <div>
108
+ <p className="text-caption text-muted-foreground">Latencia</p>
109
+ <p className="text-small font-medium tabular-nums">{service.responseTime}</p>
110
+ </div>
111
+ )}
112
+ <Badge variant={statusInfo.variant}>{statusInfo.label}</Badge>
113
+ </div>
114
+ </div>
115
+ </li>
116
+ );
117
+ })}
118
+ </ul>
119
+ </CardContent>
120
+ </Card>
121
+ </section>
122
+ )}
123
+
124
+ {/* Incidentes */}
125
+ {incidents.length > 0 && (
126
+ <section>
127
+ <h2 className="text-h3 font-semibold mb-5">{incidentsTitle}</h2>
128
+ <div className="space-y-4">
129
+ {incidents.map((incident, i) => (
130
+ <Card key={i}>
131
+ <CardContent className="p-5 space-y-3">
132
+ <div className="flex flex-wrap items-center justify-between gap-3">
133
+ <h3 className="text-small font-semibold">{incident.title}</h3>
134
+ <Badge
135
+ variant={
136
+ incident.status === 'resolved'
137
+ ? 'success'
138
+ : incident.status === 'monitoring'
139
+ ? 'warning'
140
+ : 'info'
141
+ }
142
+ >
143
+ {incident.status === 'resolved'
144
+ ? 'Resuelto'
145
+ : incident.status === 'monitoring'
146
+ ? 'Monitoreando'
147
+ : 'Investigando'}
148
+ </Badge>
149
+ </div>
150
+ <p className="text-caption text-muted-foreground">
151
+ {incident.date}
152
+ </p>
153
+ {incident.description && (
154
+ <>
155
+ <Separator />
156
+ <p className="text-small text-muted-foreground leading-relaxed">
157
+ {incident.description}
158
+ </p>
159
+ </>
160
+ )}
161
+ </CardContent>
162
+ </Card>
163
+ ))}
164
+ </div>
165
+ </section>
166
+ )}
167
+
168
+ {/* Footer */}
169
+ <footer className="mt-16 pt-8 border-t border-border text-center text-caption text-muted-foreground">
170
+ ¿Problemas? Escribinos a{' '}
171
+ <a
172
+ href="mailto:soporte@imansi.com"
173
+ className="text-primary hover:underline underline-offset-4"
174
+ >
175
+ soporte@imansi.com
176
+ </a>
177
+ </footer>
178
+ </main>
179
+ </div>
180
+ );
181
+ }
package/src/index.js ADDED
@@ -0,0 +1,53 @@
1
+ // ============================================================
2
+ // @imansi/templates — Entry point
3
+ // ============================================================
4
+
5
+ // Layouts
6
+ export { AuthLayout } from './layouts/AuthLayout.jsx';
7
+ export { MarketingLayout } from './layouts/MarketingLayout.jsx';
8
+ export { DashboardLayout } from './layouts/DashboardLayout.jsx';
9
+
10
+ // ============================================================
11
+ // Auth
12
+ // ============================================================
13
+ export { LoginPage } from './auth/LoginPage.jsx';
14
+ export { RegisterPage } from './auth/RegisterPage.jsx';
15
+ export { ForgotPasswordPage } from './auth/ForgotPasswordPage.jsx';
16
+ export { ResetPasswordPage } from './auth/ResetPasswordPage.jsx';
17
+ export { Verify2FAPage } from './auth/Verify2FAPage.jsx';
18
+
19
+ // ============================================================
20
+ // Marketing
21
+ // ============================================================
22
+ export { LandingPage } from './marketing/LandingPage.jsx';
23
+ export { HomePage } from './marketing/HomePage.jsx';
24
+ export { PricingPage } from './marketing/PricingPage.jsx';
25
+ export { BlogPage } from './marketing/BlogPage.jsx';
26
+ export { BlogPostPage } from './marketing/BlogPostPage.jsx';
27
+ export { ContactPage } from './marketing/ContactPage.jsx';
28
+ export { AboutPage } from './marketing/AboutPage.jsx';
29
+ export { DocsPage } from './marketing/DocsPage.jsx';
30
+
31
+ // ============================================================
32
+ // Dashboard
33
+ // ============================================================
34
+ export { DashboardHome } from './dashboard/DashboardHome.jsx';
35
+ export { UsersTablePage } from './dashboard/UsersTablePage.jsx';
36
+ export { SettingsPage } from './dashboard/SettingsPage.jsx';
37
+ export { ProfilePage } from './dashboard/ProfilePage.jsx';
38
+ export { BillingPage } from './dashboard/BillingPage.jsx';
39
+ export { NotificationsPage } from './dashboard/NotificationsPage.jsx';
40
+
41
+ // ============================================================
42
+ // Extra
43
+ // ============================================================
44
+ export { ErrorPage } from './extra/ErrorPage.jsx';
45
+ export { ComingSoonPage } from './extra/ComingSoonPage.jsx';
46
+ export { MaintenancePage } from './extra/MaintenancePage.jsx';
47
+ export { OnboardingPage } from './extra/OnboardingPage.jsx';
48
+ export { CheckoutPage } from './extra/CheckoutPage.jsx';
49
+ export { InvoicePage } from './extra/InvoicePage.jsx';
50
+ export { ChangelogPage } from './extra/ChangelogPage.jsx';
51
+ export { CareersPage } from './extra/CareersPage.jsx';
52
+ export { LegalPage } from './extra/LegalPage.jsx';
53
+ export { StatusPage } from './extra/StatusPage.jsx';
@@ -0,0 +1,104 @@
1
+ import { cn } from '@imansi/ui';
2
+
3
+ export function AuthLayout({
4
+ children,
5
+ brandName = 'Imansi',
6
+ brandTagline = 'Autenticación moderna para tus apps.',
7
+ brandLogo,
8
+ footer,
9
+ className,
10
+ }) {
11
+ return (
12
+ <div
13
+ className={cn(
14
+ 'h-dvh flex bg-background text-foreground overflow-hidden',
15
+ className
16
+ )}
17
+ >
18
+ {/* Panel de branding (izquierda) */}
19
+ <aside className="hidden lg:flex lg:w-1/2 relative overflow-hidden bg-primary text-primary-foreground">
20
+ <div
21
+ className="absolute inset-0 opacity-[0.08]"
22
+ style={{
23
+ backgroundImage: `
24
+ radial-gradient(circle at 20% 30%, currentColor 0, transparent 40%),
25
+ radial-gradient(circle at 80% 70%, currentColor 0, transparent 40%),
26
+ radial-gradient(circle at 50% 90%, currentColor 0, transparent 30%)
27
+ `,
28
+ }}
29
+ aria-hidden="true"
30
+ />
31
+
32
+ <div className="relative z-10 flex flex-col justify-between p-12 xl:p-16 w-full">
33
+ {/* Logo */}
34
+ <div className="flex items-center gap-3">
35
+ {brandLogo || (
36
+ <div className="h-11 w-11 rounded-xl bg-primary-foreground/20 backdrop-blur flex items-center justify-center font-bold text-lg">
37
+ {brandName.charAt(0)}
38
+ </div>
39
+ )}
40
+ <span className="text-h4 font-semibold">{brandName}</span>
41
+ </div>
42
+
43
+ {/* Contenido central */}
44
+ <div className="space-y-6 max-w-lg">
45
+ <h1 className="text-h1 font-bold leading-[1.1] tracking-tight">
46
+ {brandTagline}
47
+ </h1>
48
+ <p className="text-small opacity-80 leading-relaxed">
49
+ Construido sobre Tailwind CSS, con 6 temas y modo oscuro automático.
50
+ Componentes accesibles y una experiencia premium desde el primer clic.
51
+ </p>
52
+
53
+ <ul className="space-y-2.5 pt-2">
54
+ {[
55
+ '6 temas listos para usar',
56
+ 'Componentes accesibles (WCAG AA)',
57
+ 'Modo oscuro automático',
58
+ ].map((item) => (
59
+ <li
60
+ key={item}
61
+ className="flex items-center gap-3 text-small opacity-90"
62
+ >
63
+ <span className="flex h-5 w-5 items-center justify-center rounded-full bg-primary-foreground/20 shrink-0">
64
+ <svg
65
+ width="10"
66
+ height="10"
67
+ viewBox="0 0 12 12"
68
+ fill="none"
69
+ stroke="currentColor"
70
+ strokeWidth="2.5"
71
+ strokeLinecap="round"
72
+ strokeLinejoin="round"
73
+ >
74
+ <polyline points="2,6 5,9 10,3" />
75
+ </svg>
76
+ </span>
77
+ {item}
78
+ </li>
79
+ ))}
80
+ </ul>
81
+ </div>
82
+
83
+ {/* Footer */}
84
+ <div className="text-caption opacity-60">
85
+ © {new Date().getFullYear()} {brandName}. Todos los derechos reservados.
86
+ </div>
87
+ </div>
88
+ </aside>
89
+
90
+ {/* Panel del formulario (derecha) — scroll interno solo si es necesario */}
91
+ <main className="flex-1 relative overflow-y-auto">
92
+ {/* Círculo decorativo */}
93
+ <div
94
+ className="absolute top-1/4 -right-32 h-96 w-96 rounded-full bg-primary/5 blur-3xl pointer-events-none"
95
+ aria-hidden="true"
96
+ />
97
+
98
+ <div className="relative min-h-full flex items-center justify-center p-6 sm:p-8 lg:p-10">
99
+ <div className="w-full max-w-md">{children}</div>
100
+ </div>
101
+ </main>
102
+ </div>
103
+ );
104
+ }