@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,182 @@
1
+ import { useState } from 'react';
2
+ import { Link } from 'react-router-dom';
3
+ import { Badge, Button, Input, cn } from '@imansi/ui';
4
+ import { MarketingLayout } from '../layouts/MarketingLayout.jsx';
5
+
6
+ export function BlogPage({
7
+ brandName = 'Imansi',
8
+ brandLogo,
9
+ navLinks = [],
10
+ headerCTA,
11
+ headerSecondaryCTA,
12
+ footerColumns = [],
13
+
14
+ title = 'Blog',
15
+ subtitle = 'Ideas, tutoriales y novedades del ecosistema Imansi.',
16
+ searchPlaceholder = 'Buscar artículos...',
17
+
18
+ categories = [],
19
+ posts = [],
20
+ categoriesLabel = 'Todas',
21
+ emptyMessage = 'No hay artículos que coincidan con tu búsqueda.',
22
+
23
+ // Post fields: { id, title, excerpt, category, author, date, readTime, image, href }
24
+ }) {
25
+ const [selectedCategory, setSelectedCategory] = useState('all');
26
+ const [search, setSearch] = useState('');
27
+
28
+ const filtered = posts.filter((p) => {
29
+ const matchCategory =
30
+ selectedCategory === 'all' || p.category === selectedCategory;
31
+ const matchSearch =
32
+ !search ||
33
+ p.title.toLowerCase().includes(search.toLowerCase()) ||
34
+ p.excerpt?.toLowerCase().includes(search.toLowerCase());
35
+ return matchCategory && matchSearch;
36
+ });
37
+
38
+ const featured = filtered[0];
39
+ const rest = filtered.slice(1);
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
+ {/* Header */}
51
+ <section className="imansi-container py-16 sm:py-20 text-center max-w-2xl mx-auto space-y-4">
52
+ <h1 className="text-display font-bold tracking-tight">{title}</h1>
53
+ <p className="text-body text-muted-foreground">{subtitle}</p>
54
+ </section>
55
+
56
+ {/* Filtros */}
57
+ <section className="imansi-container pb-10">
58
+ <div className="flex flex-col lg:flex-row gap-4 items-start lg:items-center justify-between">
59
+ <div className="flex flex-wrap gap-2">
60
+ <button
61
+ onClick={() => setSelectedCategory('all')}
62
+ className={cn(
63
+ 'rounded-full px-4 py-1.5 text-small font-medium transition-colors',
64
+ selectedCategory === 'all'
65
+ ? 'bg-primary text-primary-foreground'
66
+ : 'bg-muted text-muted-foreground hover:bg-secondary hover:text-secondary-foreground'
67
+ )}
68
+ >
69
+ {categoriesLabel}
70
+ </button>
71
+ {categories.map((cat) => (
72
+ <button
73
+ key={cat}
74
+ onClick={() => setSelectedCategory(cat)}
75
+ className={cn(
76
+ 'rounded-full px-4 py-1.5 text-small font-medium transition-colors',
77
+ selectedCategory === cat
78
+ ? 'bg-primary text-primary-foreground'
79
+ : 'bg-muted text-muted-foreground hover:bg-secondary hover:text-secondary-foreground'
80
+ )}
81
+ >
82
+ {cat}
83
+ </button>
84
+ ))}
85
+ </div>
86
+
87
+ <div className="w-full lg:w-72">
88
+ <Input
89
+ type="search"
90
+ placeholder={searchPlaceholder}
91
+ value={search}
92
+ onChange={(e) => setSearch(e.target.value)}
93
+ />
94
+ </div>
95
+ </div>
96
+ </section>
97
+
98
+ {/* Post destacado */}
99
+ {featured && (
100
+ <section className="imansi-container pb-12">
101
+ <Link to={featured.href || '#'} className="group block">
102
+ <div className="grid gap-6 md:grid-cols-2 items-center rounded-2xl border border-border bg-card overflow-hidden transition-shadow hover:shadow-lg">
103
+ <div className="aspect-[16/10] md:aspect-square bg-muted flex items-center justify-center text-muted-foreground">
104
+ {featured.image ? (
105
+ <img src={featured.image} alt="" className="w-full h-full object-cover" />
106
+ ) : (
107
+ <span className="text-caption">Sin imagen</span>
108
+ )}
109
+ </div>
110
+ <div className="p-8 space-y-4">
111
+ <div className="flex items-center gap-3">
112
+ <Badge variant="primary">Destacado</Badge>
113
+ {featured.category && (
114
+ <Badge variant="outline">{featured.category}</Badge>
115
+ )}
116
+ </div>
117
+ <h2 className="text-h2 font-bold tracking-tight group-hover:text-primary transition-colors">
118
+ {featured.title}
119
+ </h2>
120
+ <p className="text-small text-muted-foreground leading-relaxed">
121
+ {featured.excerpt}
122
+ </p>
123
+ <div className="flex items-center gap-3 text-caption text-muted-foreground pt-2">
124
+ {featured.author && <span>{featured.author}</span>}
125
+ {featured.date && <span>·</span>}
126
+ {featured.date && <span>{featured.date}</span>}
127
+ {featured.readTime && <span>·</span>}
128
+ {featured.readTime && <span>{featured.readTime}</span>}
129
+ </div>
130
+ </div>
131
+ </div>
132
+ </Link>
133
+ </section>
134
+ )}
135
+
136
+ {/* Grid de posts */}
137
+ <section className="imansi-container pb-20">
138
+ {rest.length > 0 ? (
139
+ <div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
140
+ {rest.map((post) => (
141
+ <Link key={post.id} to={post.href || '#'} className="group">
142
+ <article className="rounded-xl border border-border bg-card overflow-hidden h-full flex flex-col transition-shadow hover:shadow-lg">
143
+ <div className="aspect-[16/9] bg-muted flex items-center justify-center text-muted-foreground">
144
+ {post.image ? (
145
+ <img src={post.image} alt="" className="w-full h-full object-cover" />
146
+ ) : (
147
+ <span className="text-caption">Sin imagen</span>
148
+ )}
149
+ </div>
150
+ <div className="p-5 flex-1 flex flex-col gap-3">
151
+ {post.category && (
152
+ <Badge variant="outline" className="self-start">
153
+ {post.category}
154
+ </Badge>
155
+ )}
156
+ <h3 className="text-h4 font-semibold leading-tight group-hover:text-primary transition-colors">
157
+ {post.title}
158
+ </h3>
159
+ <p className="text-small text-muted-foreground line-clamp-2 flex-1">
160
+ {post.excerpt}
161
+ </p>
162
+ <div className="flex items-center gap-2 text-caption text-muted-foreground pt-2 border-t border-border">
163
+ {post.author && <span>{post.author}</span>}
164
+ {post.date && <span>·</span>}
165
+ {post.date && <span>{post.date}</span>}
166
+ </div>
167
+ </div>
168
+ </article>
169
+ </Link>
170
+ ))}
171
+ </div>
172
+ ) : (
173
+ !featured && (
174
+ <div className="text-center py-16 text-muted-foreground">
175
+ <p className="text-small">{emptyMessage}</p>
176
+ </div>
177
+ )
178
+ )}
179
+ </section>
180
+ </MarketingLayout>
181
+ );
182
+ }
@@ -0,0 +1,192 @@
1
+ import { Link } from 'react-router-dom';
2
+ import { Avatar, AvatarFallback, Badge, Button } from '@imansi/ui';
3
+ import { MarketingLayout } from '../layouts/MarketingLayout.jsx';
4
+
5
+ export function BlogPostPage({
6
+ brandName = 'Imansi',
7
+ brandLogo,
8
+ navLinks = [],
9
+ headerCTA,
10
+ headerSecondaryCTA,
11
+ footerColumns = [],
12
+
13
+ post = { title: '', excerpt: '', content: null },
14
+ author,
15
+ categories = [],
16
+ readTime,
17
+ date,
18
+ backLink = { href: '/blog', label: 'Volver al blog' },
19
+ tocItems = [],
20
+ sidebarCta,
21
+ relatedPosts = [],
22
+
23
+ children,
24
+ }) {
25
+ return (
26
+ <MarketingLayout
27
+ brandName={brandName}
28
+ brandLogo={brandLogo}
29
+ navLinks={navLinks}
30
+ headerCTA={headerCTA}
31
+ headerSecondaryCTA={headerSecondaryCTA}
32
+ footerColumns={footerColumns}
33
+ >
34
+ {/* Back link */}
35
+ <div className="imansi-container pt-8">
36
+ <Link
37
+ to={backLink.href}
38
+ className="inline-flex items-center gap-1.5 text-small text-muted-foreground hover:text-foreground transition-colors"
39
+ >
40
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
41
+ <path d="M8.75 3.5L5.25 7L8.75 10.5" />
42
+ </svg>
43
+ {backLink.label}
44
+ </Link>
45
+ </div>
46
+
47
+ {/* Header del post */}
48
+ <article className="imansi-container pt-6 pb-16">
49
+ <header className="max-w-3xl mx-auto space-y-6 text-center">
50
+ {categories.length > 0 && (
51
+ <div className="flex justify-center gap-2 flex-wrap">
52
+ {categories.map((cat) => (
53
+ <Badge key={cat} variant="outline">{cat}</Badge>
54
+ ))}
55
+ </div>
56
+ )}
57
+
58
+ <h1 className="text-display font-bold tracking-tight leading-[1.1]">
59
+ {post.title}
60
+ </h1>
61
+
62
+ {post.excerpt && (
63
+ <p className="text-body text-muted-foreground leading-relaxed">
64
+ {post.excerpt}
65
+ </p>
66
+ )}
67
+
68
+ <div className="flex flex-wrap items-center justify-center gap-4 pt-2">
69
+ {author && (
70
+ <div className="flex items-center gap-3">
71
+ <Avatar size="sm">
72
+ {author.avatar ? (
73
+ <img src={author.avatar} alt="" />
74
+ ) : (
75
+ <AvatarFallback>
76
+ {author.name?.charAt(0) || 'A'}
77
+ </AvatarFallback>
78
+ )}
79
+ </Avatar>
80
+ <div className="text-left">
81
+ <p className="text-small font-medium">{author.name}</p>
82
+ {author.role && (
83
+ <p className="text-caption text-muted-foreground">
84
+ {author.role}
85
+ </p>
86
+ )}
87
+ </div>
88
+ </div>
89
+ )}
90
+
91
+ {(date || readTime) && (
92
+ <div className="flex items-center gap-2 text-caption text-muted-foreground">
93
+ {date && <span>{date}</span>}
94
+ {date && readTime && <span>·</span>}
95
+ {readTime && <span>{readTime}</span>}
96
+ </div>
97
+ )}
98
+ </div>
99
+ </header>
100
+
101
+ {/* Imagen de portada */}
102
+ {post.image && (
103
+ <div className="mt-12 max-w-5xl mx-auto">
104
+ <div className="aspect-[21/9] rounded-2xl overflow-hidden bg-muted">
105
+ <img src={post.image} alt="" className="w-full h-full object-cover" />
106
+ </div>
107
+ </div>
108
+ )}
109
+
110
+ {/* Contenido + Sidebar */}
111
+ <div className="mt-16 grid gap-12 lg:grid-cols-[1fr_280px] max-w-6xl mx-auto">
112
+ <div className="prose-imansi min-w-0">
113
+ {post.content || children}
114
+ </div>
115
+
116
+ <aside className="hidden lg:block">
117
+ <div className="sticky top-24 space-y-8">
118
+ {tocItems.length > 0 && (
119
+ <div className="space-y-3">
120
+ <h4 className="text-caption uppercase tracking-wider font-semibold text-muted-foreground">
121
+ En esta página
122
+ </h4>
123
+ <nav className="space-y-2 border-l border-border">
124
+ {tocItems.map((item, i) => (
125
+ <a
126
+ key={i}
127
+ href={item.href}
128
+ className="block pl-4 text-small text-muted-foreground hover:text-foreground transition-colors border-l-2 border-transparent hover:border-primary -ml-px"
129
+ >
130
+ {item.label}
131
+ </a>
132
+ ))}
133
+ </nav>
134
+ </div>
135
+ )}
136
+
137
+ {sidebarCta && (
138
+ <div className="rounded-lg border border-border bg-card p-5 space-y-3">
139
+ <h4 className="text-small font-semibold">
140
+ {sidebarCta.title}
141
+ </h4>
142
+ <p className="text-caption text-muted-foreground">
143
+ {sidebarCta.description}
144
+ </p>
145
+ <Link to={sidebarCta.href || '#'}>
146
+ <Button size="sm" fullWidth>
147
+ {sidebarCta.label}
148
+ </Button>
149
+ </Link>
150
+ </div>
151
+ )}
152
+ </div>
153
+ </aside>
154
+ </div>
155
+ </article>
156
+
157
+ {/* Posts relacionados */}
158
+ {relatedPosts.length > 0 && (
159
+ <section className="border-t border-border bg-muted/20">
160
+ <div className="imansi-container py-16">
161
+ <h2 className="text-h2 font-bold tracking-tight mb-8">
162
+ Artículos relacionados
163
+ </h2>
164
+ <div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
165
+ {relatedPosts.map((post) => (
166
+ <Link key={post.id} to={post.href || '#'} className="group">
167
+ <article className="rounded-xl border border-border bg-card overflow-hidden transition-shadow hover:shadow-lg">
168
+ <div className="aspect-[16/9] bg-muted flex items-center justify-center text-muted-foreground">
169
+ {post.image ? (
170
+ <img src={post.image} alt="" className="w-full h-full object-cover" />
171
+ ) : (
172
+ <span className="text-caption">Sin imagen</span>
173
+ )}
174
+ </div>
175
+ <div className="p-5 space-y-2">
176
+ <h3 className="text-h4 font-semibold leading-tight group-hover:text-primary transition-colors">
177
+ {post.title}
178
+ </h3>
179
+ <p className="text-caption text-muted-foreground">
180
+ {post.excerpt}
181
+ </p>
182
+ </div>
183
+ </article>
184
+ </Link>
185
+ ))}
186
+ </div>
187
+ </div>
188
+ </section>
189
+ )}
190
+ </MarketingLayout>
191
+ );
192
+ }