@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.
- package/LICENSE +21 -0
- package/README.md +557 -0
- package/package.json +35 -0
- package/src/auth/ForgotPasswordPage.jsx +176 -0
- package/src/auth/LoginPage.jsx +265 -0
- package/src/auth/RegisterPage.jsx +302 -0
- package/src/auth/ResetPasswordPage.jsx +158 -0
- package/src/auth/Verify2FAPage.jsx +175 -0
- package/src/dashboard/BillingPage.jsx +345 -0
- package/src/dashboard/DashboardHome.jsx +246 -0
- package/src/dashboard/NotificationsPage.jsx +230 -0
- package/src/dashboard/ProfilePage.jsx +272 -0
- package/src/dashboard/SettingsPage.jsx +415 -0
- package/src/dashboard/UsersTablePage.jsx +317 -0
- package/src/extra/CareersPage.jsx +166 -0
- package/src/extra/ChangelogPage.jsx +127 -0
- package/src/extra/CheckoutPage.jsx +383 -0
- package/src/extra/ComingSoonPage.jsx +147 -0
- package/src/extra/ErrorPage.jsx +91 -0
- package/src/extra/InvoicePage.jsx +241 -0
- package/src/extra/LegalPage.jsx +115 -0
- package/src/extra/MaintenancePage.jsx +172 -0
- package/src/extra/OnboardingPage.jsx +197 -0
- package/src/extra/StatusPage.jsx +181 -0
- package/src/index.js +53 -0
- package/src/layouts/AuthLayout.jsx +104 -0
- package/src/layouts/DashboardLayout.jsx +249 -0
- package/src/layouts/MarketingLayout.jsx +184 -0
- package/src/marketing/AboutPage.jsx +189 -0
- package/src/marketing/BlogPage.jsx +182 -0
- package/src/marketing/BlogPostPage.jsx +192 -0
- package/src/marketing/ContactPage.jsx +546 -0
- package/src/marketing/DocsPage.jsx +256 -0
- package/src/marketing/HomePage.jsx +702 -0
- package/src/marketing/LandingPage.jsx +755 -0
- 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
|
+
}
|