@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,249 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { Link, useLocation } from 'react-router-dom';
|
|
3
|
+
import {
|
|
4
|
+
Avatar,
|
|
5
|
+
AvatarFallback,
|
|
6
|
+
Badge,
|
|
7
|
+
Button,
|
|
8
|
+
DropdownMenu,
|
|
9
|
+
DropdownMenuTrigger,
|
|
10
|
+
DropdownMenuContent,
|
|
11
|
+
DropdownMenuItem,
|
|
12
|
+
DropdownMenuLabel,
|
|
13
|
+
DropdownMenuSeparator,
|
|
14
|
+
Input,
|
|
15
|
+
cn,
|
|
16
|
+
} from '@imansi/ui';
|
|
17
|
+
|
|
18
|
+
export function DashboardLayout({
|
|
19
|
+
children,
|
|
20
|
+
brandName = 'Imansi',
|
|
21
|
+
brandLogo,
|
|
22
|
+
navSections = [],
|
|
23
|
+
// [{ title, items: [{ href, label, icon, badge }] }]
|
|
24
|
+
user,
|
|
25
|
+
// { name, email, avatar }
|
|
26
|
+
userMenuItems = [],
|
|
27
|
+
// [{ label, href, icon, variant }]
|
|
28
|
+
title,
|
|
29
|
+
breadcrumb = [],
|
|
30
|
+
actions,
|
|
31
|
+
notificationsCount = 0,
|
|
32
|
+
onLogout,
|
|
33
|
+
className,
|
|
34
|
+
}) {
|
|
35
|
+
const [sidebarOpen, setSidebarOpen] = useState(false);
|
|
36
|
+
const location = useLocation();
|
|
37
|
+
|
|
38
|
+
const SidebarContent = (
|
|
39
|
+
<div className="flex h-full flex-col bg-card border-r border-border">
|
|
40
|
+
{/* Logo */}
|
|
41
|
+
<div className="flex h-16 items-center gap-2.5 px-5 border-b border-border shrink-0">
|
|
42
|
+
{brandLogo || (
|
|
43
|
+
<div className="h-9 w-9 rounded-lg bg-primary text-primary-foreground flex items-center justify-center font-bold">
|
|
44
|
+
{brandName.charAt(0)}
|
|
45
|
+
</div>
|
|
46
|
+
)}
|
|
47
|
+
<span className="text-h4 font-semibold tracking-tight">{brandName}</span>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
{/* Nav sections */}
|
|
51
|
+
<nav className="flex-1 overflow-y-auto p-4 space-y-6">
|
|
52
|
+
{navSections.map((section, i) => (
|
|
53
|
+
<div key={i} className="space-y-1">
|
|
54
|
+
{section.title && (
|
|
55
|
+
<p className="px-3 pb-2 text-caption uppercase tracking-wider font-semibold text-muted-foreground">
|
|
56
|
+
{section.title}
|
|
57
|
+
</p>
|
|
58
|
+
)}
|
|
59
|
+
{section.items.map((item) => {
|
|
60
|
+
const isActive = location.pathname === item.href;
|
|
61
|
+
return (
|
|
62
|
+
<Link
|
|
63
|
+
key={item.href}
|
|
64
|
+
to={item.href}
|
|
65
|
+
onClick={() => setSidebarOpen(false)}
|
|
66
|
+
className={cn(
|
|
67
|
+
'flex items-center gap-3 rounded-md px-3 py-2 text-small transition-colors',
|
|
68
|
+
isActive
|
|
69
|
+
? 'bg-primary/10 text-primary font-semibold'
|
|
70
|
+
: 'text-muted-foreground hover:bg-muted hover:text-foreground'
|
|
71
|
+
)}
|
|
72
|
+
>
|
|
73
|
+
{item.icon && (
|
|
74
|
+
<span className="shrink-0 [&_svg]:size-4">
|
|
75
|
+
{item.icon}
|
|
76
|
+
</span>
|
|
77
|
+
)}
|
|
78
|
+
<span className="flex-1 truncate">{item.label}</span>
|
|
79
|
+
{item.badge && (
|
|
80
|
+
<Badge variant="primary" size="sm">
|
|
81
|
+
{item.badge}
|
|
82
|
+
</Badge>
|
|
83
|
+
)}
|
|
84
|
+
</Link>
|
|
85
|
+
);
|
|
86
|
+
})}
|
|
87
|
+
</div>
|
|
88
|
+
))}
|
|
89
|
+
</nav>
|
|
90
|
+
|
|
91
|
+
{/* User */}
|
|
92
|
+
{user && (
|
|
93
|
+
<div className="border-t border-border p-3 shrink-0">
|
|
94
|
+
<DropdownMenu>
|
|
95
|
+
<DropdownMenuTrigger asChild>
|
|
96
|
+
<button className="w-full flex items-center gap-3 rounded-md p-2 text-left transition-colors hover:bg-muted">
|
|
97
|
+
<Avatar size="sm">
|
|
98
|
+
{user.avatar ? (
|
|
99
|
+
<img src={user.avatar} alt={user.name} />
|
|
100
|
+
) : (
|
|
101
|
+
<AvatarFallback>
|
|
102
|
+
{user.name?.charAt(0)?.toUpperCase() || 'U'}
|
|
103
|
+
</AvatarFallback>
|
|
104
|
+
)}
|
|
105
|
+
</Avatar>
|
|
106
|
+
<div className="flex-1 min-w-0">
|
|
107
|
+
<p className="text-small font-medium truncate">{user.name}</p>
|
|
108
|
+
<p className="text-caption text-muted-foreground truncate">
|
|
109
|
+
{user.email}
|
|
110
|
+
</p>
|
|
111
|
+
</div>
|
|
112
|
+
</button>
|
|
113
|
+
</DropdownMenuTrigger>
|
|
114
|
+
<DropdownMenuContent align="end" side="top" className="w-56">
|
|
115
|
+
<DropdownMenuLabel>Mi cuenta</DropdownMenuLabel>
|
|
116
|
+
<DropdownMenuSeparator />
|
|
117
|
+
{userMenuItems.map((item, i) => (
|
|
118
|
+
<DropdownMenuItem key={i} variant={item.variant} asChild>
|
|
119
|
+
<Link to={item.href || '#'}>
|
|
120
|
+
{item.icon && (
|
|
121
|
+
<span className="shrink-0 [&_svg]:size-4">{item.icon}</span>
|
|
122
|
+
)}
|
|
123
|
+
{item.label}
|
|
124
|
+
</Link>
|
|
125
|
+
</DropdownMenuItem>
|
|
126
|
+
))}
|
|
127
|
+
{onLogout && (
|
|
128
|
+
<>
|
|
129
|
+
<DropdownMenuSeparator />
|
|
130
|
+
<DropdownMenuItem
|
|
131
|
+
variant="destructive"
|
|
132
|
+
onClick={onLogout}
|
|
133
|
+
>
|
|
134
|
+
Cerrar sesión
|
|
135
|
+
</DropdownMenuItem>
|
|
136
|
+
</>
|
|
137
|
+
)}
|
|
138
|
+
</DropdownMenuContent>
|
|
139
|
+
</DropdownMenu>
|
|
140
|
+
</div>
|
|
141
|
+
)}
|
|
142
|
+
</div>
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<div className={cn('h-dvh flex bg-background text-foreground overflow-hidden', className)}>
|
|
147
|
+
{/* Sidebar desktop */}
|
|
148
|
+
<aside className="hidden lg:block w-64 shrink-0">{SidebarContent}</aside>
|
|
149
|
+
|
|
150
|
+
{/* Sidebar mobile */}
|
|
151
|
+
{sidebarOpen && (
|
|
152
|
+
<>
|
|
153
|
+
<div
|
|
154
|
+
className="fixed inset-0 z-40 bg-overlay backdrop-blur-sm lg:hidden"
|
|
155
|
+
onClick={() => setSidebarOpen(false)}
|
|
156
|
+
aria-hidden="true"
|
|
157
|
+
/>
|
|
158
|
+
<aside className="fixed inset-y-0 left-0 z-50 w-64 lg:hidden">
|
|
159
|
+
{SidebarContent}
|
|
160
|
+
</aside>
|
|
161
|
+
</>
|
|
162
|
+
)}
|
|
163
|
+
|
|
164
|
+
{/* Main */}
|
|
165
|
+
<div className="flex-1 flex flex-col min-w-0 overflow-hidden">
|
|
166
|
+
{/* Topbar */}
|
|
167
|
+
<header className="h-16 flex items-center gap-3 border-b border-border bg-card px-4 sm:px-6 shrink-0">
|
|
168
|
+
{/* Hamburger mobile */}
|
|
169
|
+
<button
|
|
170
|
+
type="button"
|
|
171
|
+
onClick={() => setSidebarOpen(true)}
|
|
172
|
+
className="lg:hidden inline-flex h-9 w-9 items-center justify-center rounded-md hover:bg-muted"
|
|
173
|
+
aria-label="Abrir menú"
|
|
174
|
+
>
|
|
175
|
+
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
176
|
+
<line x1="3" y1="6" x2="17" y2="6" />
|
|
177
|
+
<line x1="3" y1="10" x2="17" y2="10" />
|
|
178
|
+
<line x1="3" y1="14" x2="17" y2="14" />
|
|
179
|
+
</svg>
|
|
180
|
+
</button>
|
|
181
|
+
|
|
182
|
+
{/* Título / breadcrumb */}
|
|
183
|
+
<div className="flex-1 min-w-0">
|
|
184
|
+
{breadcrumb.length > 0 ? (
|
|
185
|
+
<nav className="flex items-center gap-1.5 text-small text-muted-foreground">
|
|
186
|
+
{breadcrumb.map((crumb, i) => (
|
|
187
|
+
<span key={i} className="flex items-center gap-1.5">
|
|
188
|
+
{i > 0 && <span className="opacity-50">/</span>}
|
|
189
|
+
{crumb.href ? (
|
|
190
|
+
<Link
|
|
191
|
+
to={crumb.href}
|
|
192
|
+
className="hover:text-foreground transition-colors"
|
|
193
|
+
>
|
|
194
|
+
{crumb.label}
|
|
195
|
+
</Link>
|
|
196
|
+
) : (
|
|
197
|
+
<span className="text-foreground font-medium truncate">
|
|
198
|
+
{crumb.label}
|
|
199
|
+
</span>
|
|
200
|
+
)}
|
|
201
|
+
</span>
|
|
202
|
+
))}
|
|
203
|
+
</nav>
|
|
204
|
+
) : title ? (
|
|
205
|
+
<h1 className="text-h4 font-semibold truncate">{title}</h1>
|
|
206
|
+
) : null}
|
|
207
|
+
</div>
|
|
208
|
+
|
|
209
|
+
{/* Search desktop */}
|
|
210
|
+
<div className="hidden md:block w-72">
|
|
211
|
+
<Input
|
|
212
|
+
type="search"
|
|
213
|
+
placeholder="Buscar..."
|
|
214
|
+
className="h-9"
|
|
215
|
+
/>
|
|
216
|
+
</div>
|
|
217
|
+
|
|
218
|
+
{/* Actions */}
|
|
219
|
+
<div className="flex items-center gap-1">
|
|
220
|
+
{/* Notificaciones */}
|
|
221
|
+
<Link
|
|
222
|
+
to="/panel/notificaciones"
|
|
223
|
+
className="relative inline-flex h-9 w-9 items-center justify-center rounded-md hover:bg-muted transition-colors"
|
|
224
|
+
aria-label="Notificaciones"
|
|
225
|
+
>
|
|
226
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
227
|
+
<path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9" />
|
|
228
|
+
<path d="M10.3 21a1.94 1.94 0 0 0 3.4 0" />
|
|
229
|
+
</svg>
|
|
230
|
+
{notificationsCount > 0 && (
|
|
231
|
+
<span className="absolute top-1 right-1 flex h-4 min-w-4 items-center justify-center rounded-full bg-destructive px-1 text-[10px] font-bold text-destructive-foreground">
|
|
232
|
+
{notificationsCount > 9 ? '9+' : notificationsCount}
|
|
233
|
+
</span>
|
|
234
|
+
)}
|
|
235
|
+
</Link>
|
|
236
|
+
|
|
237
|
+
{/* Actions custom */}
|
|
238
|
+
{actions}
|
|
239
|
+
</div>
|
|
240
|
+
</header>
|
|
241
|
+
|
|
242
|
+
{/* Content */}
|
|
243
|
+
<main className="flex-1 overflow-y-auto">
|
|
244
|
+
<div className="p-4 sm:p-6 lg:p-8">{children}</div>
|
|
245
|
+
</main>
|
|
246
|
+
</div>
|
|
247
|
+
</div>
|
|
248
|
+
);
|
|
249
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { Link } from 'react-router-dom';
|
|
3
|
+
import { cn } from '@imansi/ui';
|
|
4
|
+
|
|
5
|
+
export function MarketingLayout({
|
|
6
|
+
children,
|
|
7
|
+
brandName = 'Imansi',
|
|
8
|
+
brandLogo,
|
|
9
|
+
navLinks = [],
|
|
10
|
+
headerCTA,
|
|
11
|
+
headerSecondaryCTA,
|
|
12
|
+
footer,
|
|
13
|
+
footerColumns = [],
|
|
14
|
+
footerNote,
|
|
15
|
+
className,
|
|
16
|
+
}) {
|
|
17
|
+
const [menuOpen, setMenuOpen] = useState(false);
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div className={cn('min-h-screen flex flex-col bg-background text-foreground', className)}>
|
|
21
|
+
{/* Header */}
|
|
22
|
+
<header className="sticky top-0 z-40 w-full border-b border-border bg-background/80 backdrop-blur-lg">
|
|
23
|
+
<div className="imansi-container flex h-16 items-center justify-between gap-6">
|
|
24
|
+
{/* Logo */}
|
|
25
|
+
<Link to="/" className="flex items-center gap-2.5 shrink-0">
|
|
26
|
+
{brandLogo || (
|
|
27
|
+
<div className="h-9 w-9 rounded-lg bg-primary text-primary-foreground flex items-center justify-center font-bold">
|
|
28
|
+
{brandName.charAt(0)}
|
|
29
|
+
</div>
|
|
30
|
+
)}
|
|
31
|
+
<span className="text-h4 font-semibold tracking-tight">{brandName}</span>
|
|
32
|
+
</Link>
|
|
33
|
+
|
|
34
|
+
{/* Nav desktop */}
|
|
35
|
+
<nav className="hidden md:flex items-center gap-1">
|
|
36
|
+
{navLinks.map((link) => (
|
|
37
|
+
<Link
|
|
38
|
+
key={link.href}
|
|
39
|
+
to={link.href}
|
|
40
|
+
className="rounded-md px-3 py-2 text-small font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
|
|
41
|
+
>
|
|
42
|
+
{link.label}
|
|
43
|
+
</Link>
|
|
44
|
+
))}
|
|
45
|
+
</nav>
|
|
46
|
+
|
|
47
|
+
{/* CTAs desktop */}
|
|
48
|
+
<div className="hidden md:flex items-center gap-2">
|
|
49
|
+
{headerSecondaryCTA && (
|
|
50
|
+
<Link
|
|
51
|
+
to={headerSecondaryCTA.href}
|
|
52
|
+
className="rounded-md px-3 py-2 text-small font-medium text-muted-foreground hover:text-foreground transition-colors"
|
|
53
|
+
>
|
|
54
|
+
{headerSecondaryCTA.label}
|
|
55
|
+
</Link>
|
|
56
|
+
)}
|
|
57
|
+
{headerCTA && (
|
|
58
|
+
<Link
|
|
59
|
+
to={headerCTA.href}
|
|
60
|
+
className="rounded-md bg-primary text-primary-foreground px-4 py-2 text-small font-medium transition-opacity hover:opacity-90"
|
|
61
|
+
>
|
|
62
|
+
{headerCTA.label}
|
|
63
|
+
</Link>
|
|
64
|
+
)}
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
{/* Botón menú mobile */}
|
|
68
|
+
<button
|
|
69
|
+
type="button"
|
|
70
|
+
onClick={() => setMenuOpen((v) => !v)}
|
|
71
|
+
className="md:hidden inline-flex h-9 w-9 items-center justify-center rounded-md hover:bg-muted"
|
|
72
|
+
aria-label="Abrir menú"
|
|
73
|
+
>
|
|
74
|
+
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
75
|
+
{menuOpen ? (
|
|
76
|
+
<>
|
|
77
|
+
<line x1="5" y1="5" x2="15" y2="15" />
|
|
78
|
+
<line x1="15" y1="5" x2="5" y2="15" />
|
|
79
|
+
</>
|
|
80
|
+
) : (
|
|
81
|
+
<>
|
|
82
|
+
<line x1="3" y1="6" x2="17" y2="6" />
|
|
83
|
+
<line x1="3" y1="10" x2="17" y2="10" />
|
|
84
|
+
<line x1="3" y1="14" x2="17" y2="14" />
|
|
85
|
+
</>
|
|
86
|
+
)}
|
|
87
|
+
</svg>
|
|
88
|
+
</button>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
{/* Nav mobile */}
|
|
92
|
+
{menuOpen && (
|
|
93
|
+
<div className="md:hidden border-t border-border bg-background">
|
|
94
|
+
<nav className="imansi-container py-4 flex flex-col gap-1">
|
|
95
|
+
{navLinks.map((link) => (
|
|
96
|
+
<Link
|
|
97
|
+
key={link.href}
|
|
98
|
+
to={link.href}
|
|
99
|
+
onClick={() => setMenuOpen(false)}
|
|
100
|
+
className="rounded-md px-3 py-2 text-small font-medium hover:bg-muted"
|
|
101
|
+
>
|
|
102
|
+
{link.label}
|
|
103
|
+
</Link>
|
|
104
|
+
))}
|
|
105
|
+
<div className="flex flex-col gap-2 pt-3 border-t border-border mt-2">
|
|
106
|
+
{headerSecondaryCTA && (
|
|
107
|
+
<Link
|
|
108
|
+
to={headerSecondaryCTA.href}
|
|
109
|
+
className="rounded-md px-3 py-2 text-small font-medium text-center hover:bg-muted"
|
|
110
|
+
>
|
|
111
|
+
{headerSecondaryCTA.label}
|
|
112
|
+
</Link>
|
|
113
|
+
)}
|
|
114
|
+
{headerCTA && (
|
|
115
|
+
<Link
|
|
116
|
+
to={headerCTA.href}
|
|
117
|
+
className="rounded-md bg-primary text-primary-foreground px-3 py-2 text-small font-medium text-center"
|
|
118
|
+
>
|
|
119
|
+
{headerCTA.label}
|
|
120
|
+
</Link>
|
|
121
|
+
)}
|
|
122
|
+
</div>
|
|
123
|
+
</nav>
|
|
124
|
+
</div>
|
|
125
|
+
)}
|
|
126
|
+
</header>
|
|
127
|
+
|
|
128
|
+
{/* Main */}
|
|
129
|
+
<main className="flex-1">{children}</main>
|
|
130
|
+
|
|
131
|
+
{/* Footer */}
|
|
132
|
+
<footer className="border-t border-border bg-muted/30">
|
|
133
|
+
<div className="imansi-container py-12">
|
|
134
|
+
{footerColumns.length > 0 && (
|
|
135
|
+
<div className="grid gap-8 sm:grid-cols-2 md:grid-cols-4 mb-10">
|
|
136
|
+
{/* Columna de marca */}
|
|
137
|
+
<div className="space-y-3">
|
|
138
|
+
<div className="flex items-center gap-2.5">
|
|
139
|
+
{brandLogo || (
|
|
140
|
+
<div className="h-8 w-8 rounded-lg bg-primary text-primary-foreground flex items-center justify-center font-bold text-small">
|
|
141
|
+
{brandName.charAt(0)}
|
|
142
|
+
</div>
|
|
143
|
+
)}
|
|
144
|
+
<span className="text-small font-semibold">{brandName}</span>
|
|
145
|
+
</div>
|
|
146
|
+
<p className="text-caption text-muted-foreground max-w-xs">
|
|
147
|
+
Plataforma moderna para construir aplicaciones SaaS.
|
|
148
|
+
</p>
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
{/* Columnas de links */}
|
|
152
|
+
{footerColumns.map((col) => (
|
|
153
|
+
<div key={col.title} className="space-y-3">
|
|
154
|
+
<h4 className="text-caption uppercase tracking-wider font-semibold text-foreground">
|
|
155
|
+
{col.title}
|
|
156
|
+
</h4>
|
|
157
|
+
<ul className="space-y-2">
|
|
158
|
+
{col.links.map((link) => (
|
|
159
|
+
<li key={link.href}>
|
|
160
|
+
<Link
|
|
161
|
+
to={link.href}
|
|
162
|
+
className="text-small text-muted-foreground hover:text-foreground transition-colors"
|
|
163
|
+
>
|
|
164
|
+
{link.label}
|
|
165
|
+
</Link>
|
|
166
|
+
</li>
|
|
167
|
+
))}
|
|
168
|
+
</ul>
|
|
169
|
+
</div>
|
|
170
|
+
))}
|
|
171
|
+
</div>
|
|
172
|
+
)}
|
|
173
|
+
|
|
174
|
+
{footer}
|
|
175
|
+
|
|
176
|
+
<div className="pt-6 border-t border-border flex flex-col sm:flex-row justify-between gap-3 text-caption text-muted-foreground">
|
|
177
|
+
<span>© {new Date().getFullYear()} {brandName}. Todos los derechos reservados.</span>
|
|
178
|
+
{footerNote && <span>{footerNote}</span>}
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
</footer>
|
|
182
|
+
</div>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { Link } from 'react-router-dom';
|
|
2
|
+
import { Avatar, AvatarFallback, AvatarImage, Badge, Button } from '@imansi/ui';
|
|
3
|
+
import { MarketingLayout } from '../layouts/MarketingLayout.jsx';
|
|
4
|
+
|
|
5
|
+
export function AboutPage({
|
|
6
|
+
brandName = 'Imansi',
|
|
7
|
+
brandLogo,
|
|
8
|
+
navLinks = [],
|
|
9
|
+
headerCTA,
|
|
10
|
+
headerSecondaryCTA,
|
|
11
|
+
footerColumns = [],
|
|
12
|
+
|
|
13
|
+
// Hero
|
|
14
|
+
heroBadge = 'Sobre nosotros',
|
|
15
|
+
heroTitle = 'Construimos herramientas para creadores',
|
|
16
|
+
heroSubtitle = 'Creemos que construir software debería ser simple, rápido y accesible para todos.',
|
|
17
|
+
|
|
18
|
+
// Historia
|
|
19
|
+
storyTitle = 'Nuestra historia',
|
|
20
|
+
storyParagraphs = [],
|
|
21
|
+
|
|
22
|
+
// Valores
|
|
23
|
+
valuesTitle = 'Nuestros valores',
|
|
24
|
+
valuesSubtitle = 'Lo que nos guía en cada decisión.',
|
|
25
|
+
values = [],
|
|
26
|
+
|
|
27
|
+
// Stats
|
|
28
|
+
stats = [],
|
|
29
|
+
|
|
30
|
+
// Equipo
|
|
31
|
+
teamTitle = 'Nuestro equipo',
|
|
32
|
+
teamSubtitle = 'Un grupo pequeño con una gran misión.',
|
|
33
|
+
team = [],
|
|
34
|
+
|
|
35
|
+
// CTA
|
|
36
|
+
ctaTitle = '¿Querés trabajar con nosotros?',
|
|
37
|
+
ctaSubtitle = 'Estamos buscando gente talentosa que quiera cambiar la forma de construir software.',
|
|
38
|
+
ctaPrimary = { label: 'Ver posiciones', href: '/contacto' },
|
|
39
|
+
ctaSecondary = { label: 'Contactanos', href: '/contacto' },
|
|
40
|
+
}) {
|
|
41
|
+
return (
|
|
42
|
+
<MarketingLayout
|
|
43
|
+
brandName={brandName}
|
|
44
|
+
brandLogo={brandLogo}
|
|
45
|
+
navLinks={navLinks}
|
|
46
|
+
headerCTA={headerCTA}
|
|
47
|
+
headerSecondaryCTA={headerSecondaryCTA}
|
|
48
|
+
footerColumns={footerColumns}
|
|
49
|
+
>
|
|
50
|
+
{/* Hero */}
|
|
51
|
+
<section className="imansi-container py-20 sm:py-24 text-center max-w-3xl mx-auto space-y-6">
|
|
52
|
+
{heroBadge && <Badge variant="outline" className="px-4 py-1.5">{heroBadge}</Badge>}
|
|
53
|
+
<h1 className="text-display font-bold tracking-tight leading-[1.1]">
|
|
54
|
+
{heroTitle}
|
|
55
|
+
</h1>
|
|
56
|
+
<p className="text-body text-muted-foreground leading-relaxed">
|
|
57
|
+
{heroSubtitle}
|
|
58
|
+
</p>
|
|
59
|
+
</section>
|
|
60
|
+
|
|
61
|
+
{/* Stats */}
|
|
62
|
+
{stats.length > 0 && (
|
|
63
|
+
<section className="imansi-container pb-16">
|
|
64
|
+
<div className="grid grid-cols-2 md:grid-cols-4 gap-6 max-w-4xl mx-auto">
|
|
65
|
+
{stats.map((stat, i) => (
|
|
66
|
+
<div key={i} className="text-center space-y-1">
|
|
67
|
+
<p className="text-display font-bold tracking-tight text-primary">
|
|
68
|
+
{stat.value}
|
|
69
|
+
</p>
|
|
70
|
+
<p className="text-caption text-muted-foreground uppercase tracking-wider">
|
|
71
|
+
{stat.label}
|
|
72
|
+
</p>
|
|
73
|
+
</div>
|
|
74
|
+
))}
|
|
75
|
+
</div>
|
|
76
|
+
</section>
|
|
77
|
+
)}
|
|
78
|
+
|
|
79
|
+
{/* Historia */}
|
|
80
|
+
{storyParagraphs.length > 0 && (
|
|
81
|
+
<section className="imansi-container py-16 border-t border-border">
|
|
82
|
+
<div className="max-w-3xl mx-auto space-y-6">
|
|
83
|
+
<h2 className="text-h1 font-bold tracking-tight">{storyTitle}</h2>
|
|
84
|
+
<div className="space-y-4">
|
|
85
|
+
{storyParagraphs.map((p, i) => (
|
|
86
|
+
<p key={i} className="text-body text-muted-foreground leading-relaxed">
|
|
87
|
+
{p}
|
|
88
|
+
</p>
|
|
89
|
+
))}
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</section>
|
|
93
|
+
)}
|
|
94
|
+
|
|
95
|
+
{/* Valores */}
|
|
96
|
+
{values.length > 0 && (
|
|
97
|
+
<section className="border-y border-border bg-muted/20">
|
|
98
|
+
<div className="imansi-container py-20">
|
|
99
|
+
<div className="text-center max-w-2xl mx-auto space-y-4 mb-14">
|
|
100
|
+
<h2 className="text-h1 font-bold tracking-tight">{valuesTitle}</h2>
|
|
101
|
+
<p className="text-body text-muted-foreground">{valuesSubtitle}</p>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3 max-w-5xl mx-auto">
|
|
105
|
+
{values.map((value, i) => (
|
|
106
|
+
<div
|
|
107
|
+
key={i}
|
|
108
|
+
className="rounded-lg border border-border bg-card p-6 space-y-3"
|
|
109
|
+
>
|
|
110
|
+
{value.icon && (
|
|
111
|
+
<div className="flex h-11 w-11 items-center justify-center rounded-lg bg-primary/10 text-primary">
|
|
112
|
+
{value.icon}
|
|
113
|
+
</div>
|
|
114
|
+
)}
|
|
115
|
+
<h3 className="text-h4 font-semibold">{value.title}</h3>
|
|
116
|
+
<p className="text-small text-muted-foreground leading-relaxed">
|
|
117
|
+
{value.description}
|
|
118
|
+
</p>
|
|
119
|
+
</div>
|
|
120
|
+
))}
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
</section>
|
|
124
|
+
)}
|
|
125
|
+
|
|
126
|
+
{/* Equipo */}
|
|
127
|
+
{team.length > 0 && (
|
|
128
|
+
<section className="imansi-container py-20">
|
|
129
|
+
<div className="text-center max-w-2xl mx-auto space-y-4 mb-14">
|
|
130
|
+
<h2 className="text-h1 font-bold tracking-tight">{teamTitle}</h2>
|
|
131
|
+
<p className="text-body text-muted-foreground">{teamSubtitle}</p>
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
<div className="grid gap-8 sm:grid-cols-2 lg:grid-cols-4 max-w-5xl mx-auto">
|
|
135
|
+
{team.map((member, i) => (
|
|
136
|
+
<div key={i} className="text-center space-y-3">
|
|
137
|
+
<Avatar size="2xl" className="mx-auto">
|
|
138
|
+
{member.image ? (
|
|
139
|
+
<AvatarImage src={member.image} alt={member.name} />
|
|
140
|
+
) : null}
|
|
141
|
+
<AvatarFallback>
|
|
142
|
+
{member.name?.split(' ').map((n) => n[0]).join('').slice(0, 2)}
|
|
143
|
+
</AvatarFallback>
|
|
144
|
+
</Avatar>
|
|
145
|
+
<div>
|
|
146
|
+
<p className="text-small font-semibold">{member.name}</p>
|
|
147
|
+
<p className="text-caption text-muted-foreground">
|
|
148
|
+
{member.role}
|
|
149
|
+
</p>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
))}
|
|
153
|
+
</div>
|
|
154
|
+
</section>
|
|
155
|
+
)}
|
|
156
|
+
|
|
157
|
+
{/* CTA */}
|
|
158
|
+
<section className="imansi-container pb-20">
|
|
159
|
+
<div className="rounded-2xl bg-primary text-primary-foreground p-10 sm:p-14 text-center space-y-5">
|
|
160
|
+
<h2 className="text-h1 font-bold tracking-tight">{ctaTitle}</h2>
|
|
161
|
+
<p className="text-body opacity-80 max-w-2xl mx-auto">{ctaSubtitle}</p>
|
|
162
|
+
<div className="flex flex-wrap justify-center gap-3 pt-2">
|
|
163
|
+
{ctaPrimary && (
|
|
164
|
+
<Link to={ctaPrimary.href}>
|
|
165
|
+
<Button
|
|
166
|
+
size="lg"
|
|
167
|
+
className="!bg-primary-foreground !text-primary hover:opacity-90"
|
|
168
|
+
>
|
|
169
|
+
{ctaPrimary.label}
|
|
170
|
+
</Button>
|
|
171
|
+
</Link>
|
|
172
|
+
)}
|
|
173
|
+
{ctaSecondary && (
|
|
174
|
+
<Link to={ctaSecondary.href}>
|
|
175
|
+
<Button
|
|
176
|
+
size="lg"
|
|
177
|
+
variant="outline"
|
|
178
|
+
className="!bg-transparent !text-primary-foreground !border-primary-foreground/30 hover:!bg-primary-foreground/10"
|
|
179
|
+
>
|
|
180
|
+
{ctaSecondary.label}
|
|
181
|
+
</Button>
|
|
182
|
+
</Link>
|
|
183
|
+
)}
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
</section>
|
|
187
|
+
</MarketingLayout>
|
|
188
|
+
);
|
|
189
|
+
}
|