@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,176 @@
1
+ import { useState } from 'react';
2
+ import { Link } from 'react-router-dom';
3
+ import {
4
+ Alert,
5
+ Button,
6
+ Card,
7
+ CardContent,
8
+ Input,
9
+ Label,
10
+ } from '@imansi/ui';
11
+ import { AuthLayout } from '../layouts/AuthLayout.jsx';
12
+
13
+ export function ForgotPasswordPage({
14
+ title = '¿Olvidaste tu contraseña?',
15
+ subtitle = 'Ingresá tu correo y te enviamos un link para restablecerla.',
16
+ submitText = 'Enviar link',
17
+ submitLoadingText = 'Enviando...',
18
+ emailLabel = 'Correo electrónico',
19
+ successTitle = 'Revisá tu correo',
20
+ successMessage = 'Si el correo está registrado, te enviamos un link para restablecer tu contraseña. Revisá tu bandeja de entrada y la carpeta de spam.',
21
+
22
+ backToLoginHref = '/login',
23
+ backToLoginText = 'Volver al inicio de sesión',
24
+
25
+ brandName,
26
+ brandTagline,
27
+ brandLogo,
28
+
29
+ onSubmit,
30
+ error: externalError,
31
+ success: externalSuccess = false,
32
+ loading = false,
33
+
34
+ footer,
35
+ className,
36
+ }) {
37
+ const [correo, setCorreo] = useState('');
38
+ const [localError, setLocalError] = useState(null);
39
+ const [localSuccess, setLocalSuccess] = useState(false);
40
+
41
+ const showingSuccess = externalSuccess || localSuccess;
42
+ const error = localError || externalError;
43
+
44
+ async function handleSubmit(e) {
45
+ e.preventDefault();
46
+ setLocalError(null);
47
+
48
+ if (!correo) {
49
+ setLocalError('Ingresá tu correo electrónico.');
50
+ return;
51
+ }
52
+
53
+ if (onSubmit) {
54
+ const result = await onSubmit({ correo });
55
+ if (result?.ok) setLocalSuccess(true);
56
+ } else {
57
+ setLocalSuccess(true);
58
+ }
59
+ }
60
+
61
+ return (
62
+ <AuthLayout
63
+ brandName={brandName}
64
+ brandTagline={brandTagline}
65
+ brandLogo={brandLogo}
66
+ className={className}
67
+ >
68
+ <div className="lg:hidden flex items-center gap-3 justify-center mb-6">
69
+ {brandLogo || (
70
+ <div className="h-10 w-10 rounded-lg bg-primary text-primary-foreground flex items-center justify-center font-bold">
71
+ {brandName?.charAt(0) || 'I'}
72
+ </div>
73
+ )}
74
+ <span className="text-h4 font-semibold">{brandName}</span>
75
+ </div>
76
+
77
+ <Card className="shadow-lg border-border/50">
78
+ <CardContent className="p-6 sm:p-7">
79
+ <div className="space-y-5">
80
+ {showingSuccess ? (
81
+ <>
82
+ <div className="space-y-1.5 text-center sm:text-left">
83
+ <div className="inline-flex h-12 w-12 items-center justify-center rounded-full bg-success/10 text-success">
84
+ <svg
85
+ width="22"
86
+ height="22"
87
+ viewBox="0 0 24 24"
88
+ fill="none"
89
+ stroke="currentColor"
90
+ strokeWidth="2.5"
91
+ strokeLinecap="round"
92
+ strokeLinejoin="round"
93
+ >
94
+ <polyline points="4,12 10,18 20,6" />
95
+ </svg>
96
+ </div>
97
+ <h1 className="text-h3 font-semibold tracking-tight">
98
+ {successTitle}
99
+ </h1>
100
+ <p className="text-small text-muted-foreground leading-snug">
101
+ {successMessage}
102
+ </p>
103
+ </div>
104
+
105
+ {backToLoginHref && (
106
+ <Button variant="outline" fullWidth asChild>
107
+ <Link to={backToLoginHref}>{backToLoginText}</Link>
108
+ </Button>
109
+ )}
110
+ </>
111
+ ) : (
112
+ <>
113
+ <div className="space-y-1.5">
114
+ <h1 className="text-h3 font-semibold tracking-tight">
115
+ {title}
116
+ </h1>
117
+ <p className="text-small text-muted-foreground leading-snug">
118
+ {subtitle}
119
+ </p>
120
+ </div>
121
+
122
+ {error && (
123
+ <Alert variant="destructive" title="Error">
124
+ {error}
125
+ </Alert>
126
+ )}
127
+
128
+ <form onSubmit={handleSubmit} className="space-y-4">
129
+ <div className="space-y-1.5">
130
+ <Label
131
+ htmlFor="forgot-correo"
132
+ className="text-small font-medium"
133
+ >
134
+ {emailLabel}
135
+ </Label>
136
+ <Input
137
+ id="forgot-correo"
138
+ type="email"
139
+ placeholder="correo@ejemplo.com"
140
+ value={correo}
141
+ onChange={(e) => setCorreo(e.target.value)}
142
+ autoComplete="email"
143
+ autoFocus
144
+ disabled={loading}
145
+ />
146
+ </div>
147
+
148
+ <Button type="submit" fullWidth disabled={loading}>
149
+ {loading ? submitLoadingText : submitText}
150
+ </Button>
151
+ </form>
152
+ </>
153
+ )}
154
+ </div>
155
+ </CardContent>
156
+ </Card>
157
+
158
+ {!showingSuccess && backToLoginHref && (
159
+ <p className="text-center text-small text-muted-foreground mt-5">
160
+ <Link
161
+ to={backToLoginHref}
162
+ className="text-primary font-medium hover:underline underline-offset-4"
163
+ >
164
+ {backToLoginText}
165
+ </Link>
166
+ </p>
167
+ )}
168
+
169
+ {footer && (
170
+ <p className="text-center text-caption text-muted-foreground mt-3 px-2">
171
+ {footer}
172
+ </p>
173
+ )}
174
+ </AuthLayout>
175
+ );
176
+ }
@@ -0,0 +1,265 @@
1
+ import { useState } from 'react';
2
+ import { Link } from 'react-router-dom';
3
+ import {
4
+ Alert,
5
+ Button,
6
+ Card,
7
+ CardContent,
8
+ Checkbox,
9
+ Input,
10
+ Label,
11
+ } from '@imansi/ui';
12
+ import { AuthLayout } from '../layouts/AuthLayout.jsx';
13
+
14
+ export function LoginPage({
15
+ title = 'Iniciar sesión',
16
+ subtitle = 'Bienvenido de nuevo. Ingresá tus credenciales para continuar.',
17
+ submitText = 'Entrar',
18
+ submitLoadingText = 'Entrando...',
19
+ emailLabel = 'Correo electrónico',
20
+ passwordLabel = 'Contraseña',
21
+ forgotPasswordText = '¿La olvidaste?',
22
+ registerText = '¿No tenés cuenta?',
23
+ registerLinkText = 'Crear una gratis',
24
+ rememberText = 'Recordarme por 30 días',
25
+
26
+ registerHref = '/registro',
27
+ forgotPasswordHref = '/recuperar',
28
+
29
+ brandName,
30
+ brandTagline,
31
+ brandLogo,
32
+
33
+ oauthProviders = [],
34
+ oauthTexts = {
35
+ github: 'Continuar con GitHub',
36
+ google: 'Continuar con Google',
37
+ o: 'o continuá con',
38
+ },
39
+
40
+ onSubmit,
41
+ onOAuthClick,
42
+ error: externalError,
43
+ loading = false,
44
+ showRememberMe = true,
45
+
46
+ footer,
47
+ className,
48
+ }) {
49
+ const [correo, setCorreo] = useState('');
50
+ const [contrasena, setContrasena] = useState('');
51
+ const [recordarme, setRecordarme] = useState(false);
52
+ const [localError, setLocalError] = useState(null);
53
+
54
+ async function handleSubmit(e) {
55
+ e.preventDefault();
56
+ setLocalError(null);
57
+
58
+ if (!correo || !contrasena) {
59
+ setLocalError('Completá todos los campos.');
60
+ return;
61
+ }
62
+
63
+ if (onSubmit) {
64
+ await onSubmit({ correo, contrasena, recordarme });
65
+ }
66
+ }
67
+
68
+ function handleOAuth(provider) {
69
+ if (onOAuthClick) onOAuthClick(provider);
70
+ }
71
+
72
+ const error = localError || externalError;
73
+
74
+ return (
75
+ <AuthLayout
76
+ brandName={brandName}
77
+ brandTagline={brandTagline}
78
+ brandLogo={brandLogo}
79
+ className={className}
80
+ >
81
+ {/* Logo mobile */}
82
+ <div className="lg:hidden flex items-center gap-3 justify-center mb-6">
83
+ {brandLogo || (
84
+ <div className="h-10 w-10 rounded-lg bg-primary text-primary-foreground flex items-center justify-center font-bold">
85
+ {brandName?.charAt(0) || 'I'}
86
+ </div>
87
+ )}
88
+ <span className="text-h4 font-semibold">{brandName}</span>
89
+ </div>
90
+
91
+ <Card className="shadow-lg border-border/50">
92
+ <CardContent className="p-6 sm:p-7">
93
+ <div className="space-y-5">
94
+ {/* Encabezado */}
95
+ <div className="space-y-1.5">
96
+ <h1 className="text-h3 font-semibold tracking-tight">{title}</h1>
97
+ <p className="text-small text-muted-foreground leading-snug">
98
+ {subtitle}
99
+ </p>
100
+ </div>
101
+
102
+ {/* Error */}
103
+ {error && (
104
+ <Alert variant="destructive" title="Error">
105
+ {error}
106
+ </Alert>
107
+ )}
108
+
109
+ {/* OAuth */}
110
+ {oauthProviders.length > 0 && (
111
+ <div className="space-y-4">
112
+ <div className="grid gap-2.5">
113
+ {oauthProviders.includes('github') && (
114
+ <Button
115
+ type="button"
116
+ variant="outline"
117
+ fullWidth
118
+ onClick={() => handleOAuth('github')}
119
+ disabled={loading}
120
+ >
121
+ <GitHubIcon />
122
+ {oauthTexts.github}
123
+ </Button>
124
+ )}
125
+ {oauthProviders.includes('google') && (
126
+ <Button
127
+ type="button"
128
+ variant="outline"
129
+ fullWidth
130
+ onClick={() => handleOAuth('google')}
131
+ disabled={loading}
132
+ >
133
+ <GoogleIcon />
134
+ {oauthTexts.google}
135
+ </Button>
136
+ )}
137
+ </div>
138
+
139
+ {/* Divisor */}
140
+ <div className="relative">
141
+ <div className="absolute inset-0 flex items-center">
142
+ <span className="w-full border-t border-border" />
143
+ </div>
144
+ <div className="relative flex justify-center text-caption uppercase">
145
+ <span className="bg-card px-3 text-muted-foreground tracking-widest text-[10px]">
146
+ {oauthTexts.o}
147
+ </span>
148
+ </div>
149
+ </div>
150
+ </div>
151
+ )}
152
+
153
+ {/* Formulario */}
154
+ <form onSubmit={handleSubmit} className="space-y-4">
155
+ <div className="space-y-1.5">
156
+ <Label htmlFor="login-correo" className="text-small font-medium">
157
+ {emailLabel}
158
+ </Label>
159
+ <Input
160
+ id="login-correo"
161
+ type="email"
162
+ placeholder="correo@ejemplo.com"
163
+ value={correo}
164
+ onChange={(e) => setCorreo(e.target.value)}
165
+ autoComplete="email"
166
+ autoFocus
167
+ disabled={loading}
168
+ />
169
+ </div>
170
+
171
+ <div className="space-y-1.5">
172
+ <div className="flex items-center justify-between">
173
+ <Label
174
+ htmlFor="login-contrasena"
175
+ className="text-small font-medium"
176
+ >
177
+ {passwordLabel}
178
+ </Label>
179
+ {forgotPasswordHref && (
180
+ <Link
181
+ to={forgotPasswordHref}
182
+ className="text-caption text-primary hover:underline underline-offset-4 font-medium"
183
+ >
184
+ {forgotPasswordText}
185
+ </Link>
186
+ )}
187
+ </div>
188
+ <Input
189
+ id="login-contrasena"
190
+ type="password"
191
+ placeholder="••••••••"
192
+ value={contrasena}
193
+ onChange={(e) => setContrasena(e.target.value)}
194
+ autoComplete="current-password"
195
+ disabled={loading}
196
+ />
197
+ </div>
198
+
199
+ {showRememberMe && (
200
+ <div className="flex items-center gap-2.5">
201
+ <Checkbox
202
+ id="login-recordarme"
203
+ checked={recordarme}
204
+ onCheckedChange={setRecordarme}
205
+ disabled={loading}
206
+ />
207
+ <Label
208
+ htmlFor="login-recordarme"
209
+ className="cursor-pointer font-normal text-small text-muted-foreground"
210
+ >
211
+ {rememberText}
212
+ </Label>
213
+ </div>
214
+ )}
215
+
216
+ <Button type="submit" fullWidth disabled={loading}>
217
+ {loading ? submitLoadingText : submitText}
218
+ </Button>
219
+ </form>
220
+ </div>
221
+ </CardContent>
222
+ </Card>
223
+
224
+ {/* Link a registro */}
225
+ {registerHref && (
226
+ <p className="text-center text-small text-muted-foreground mt-5">
227
+ {registerText}{' '}
228
+ <Link
229
+ to={registerHref}
230
+ className="text-primary font-medium hover:underline underline-offset-4"
231
+ >
232
+ {registerLinkText}
233
+ </Link>
234
+ </p>
235
+ )}
236
+
237
+ {/* Footer legal */}
238
+ {footer && (
239
+ <p className="text-center text-caption text-muted-foreground mt-3 px-2">
240
+ {footer}
241
+ </p>
242
+ )}
243
+ </AuthLayout>
244
+ );
245
+ }
246
+
247
+ /* Iconos */
248
+ function GitHubIcon() {
249
+ return (
250
+ <svg viewBox="0 0 24 24" fill="currentColor" className="size-4">
251
+ <path d="M12 .5C5.65.5.5 5.65.5 12c0 5.08 3.29 9.39 7.86 10.91.58.1.79-.25.79-.55v-1.94c-3.2.7-3.87-1.54-3.87-1.54-.52-1.33-1.28-1.68-1.28-1.68-1.05-.72.08-.7.08-.7 1.16.08 1.77 1.19 1.77 1.19 1.03 1.77 2.7 1.26 3.36.96.1-.75.4-1.26.73-1.55-2.55-.29-5.23-1.28-5.23-5.7 0-1.26.45-2.29 1.19-3.1-.12-.29-.52-1.46.11-3.05 0 0 .97-.31 3.18 1.18a11.05 11.05 0 0 1 5.8 0c2.2-1.49 3.17-1.18 3.17-1.18.63 1.59.23 2.76.11 3.05.74.81 1.19 1.84 1.19 3.1 0 4.43-2.69 5.4-5.25 5.69.41.35.78 1.05.78 2.12v3.15c0 .3.21.66.8.55C20.21 21.39 23.5 17.08 23.5 12 23.5 5.65 18.35.5 12 .5Z" />
252
+ </svg>
253
+ );
254
+ }
255
+
256
+ function GoogleIcon() {
257
+ return (
258
+ <svg viewBox="0 0 24 24" className="size-4">
259
+ <path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 0 1-2.2 3.32v2.77h3.57c2.08-1.92 3.27-4.74 3.27-8.1Z" />
260
+ <path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84A11 11 0 0 0 12 23Z" />
261
+ <path fill="#FBBC05" d="M5.84 14.1a6.6 6.6 0 0 1 0-4.2V7.06H2.18a11 11 0 0 0 0 9.88l3.66-2.84Z" />
262
+ <path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.06l3.66 2.84C6.71 7.31 9.14 5.38 12 5.38Z" />
263
+ </svg>
264
+ );
265
+ }