@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,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
+ }