ar-saas 0.3.1 → 0.3.2

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 (114) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +338 -314
  3. package/dist/cli.js +19 -0
  4. package/dist/generator.js +166 -55
  5. package/package.json +52 -50
  6. package/templates/backend/.env.example +67 -67
  7. package/templates/backend/.prettierrc +4 -4
  8. package/templates/backend/README.md +249 -168
  9. package/templates/backend/eslint.config.mjs +35 -35
  10. package/templates/backend/nest-cli.json +8 -8
  11. package/templates/backend/package-lock.json +10979 -10979
  12. package/templates/backend/package.json +88 -88
  13. package/templates/backend/src/app.controller.spec.ts +24 -24
  14. package/templates/backend/src/app.controller.ts +15 -15
  15. package/templates/backend/src/app.module.ts +40 -40
  16. package/templates/backend/src/app.service.ts +11 -11
  17. package/templates/backend/src/common/base/base.repository.ts +221 -221
  18. package/templates/backend/src/common/base/base.schema.ts +24 -24
  19. package/templates/backend/src/common/decorators/cookie.decorator.ts +9 -9
  20. package/templates/backend/src/common/decorators/current-user.decorator.ts +20 -20
  21. package/templates/backend/src/common/decorators/workspace-id.decorator.ts +14 -14
  22. package/templates/backend/src/common/filters/global-exception.filter.ts +61 -61
  23. package/templates/backend/src/common/guards/jwt-auth.guard.ts +5 -5
  24. package/templates/backend/src/common/interceptors/workspace-tenant.interceptor.ts +45 -45
  25. package/templates/backend/src/main.ts +51 -51
  26. package/templates/backend/src/modules/auth/auth.controller.ts +158 -158
  27. package/templates/backend/src/modules/auth/auth.module.ts +20 -20
  28. package/templates/backend/src/modules/auth/auth.service.ts +257 -257
  29. package/templates/backend/src/modules/auth/dto/forgot-password.dto.ts +9 -9
  30. package/templates/backend/src/modules/auth/dto/login.dto.ts +14 -14
  31. package/templates/backend/src/modules/auth/dto/refresh-token.dto.ts +12 -12
  32. package/templates/backend/src/modules/auth/dto/register.dto.ts +26 -26
  33. package/templates/backend/src/modules/auth/dto/reset-password.dto.ts +16 -16
  34. package/templates/backend/src/modules/auth/dto/verify-email.dto.ts +9 -9
  35. package/templates/backend/src/modules/auth/strategies/jwt.strategy.ts +43 -43
  36. package/templates/backend/src/modules/mail/mail.module.ts +9 -9
  37. package/templates/backend/src/modules/mail/mail.service.ts +141 -141
  38. package/templates/backend/src/modules/users/schemas/user.schema.ts +54 -54
  39. package/templates/backend/src/modules/users/users.module.ts +14 -14
  40. package/templates/backend/src/modules/users/users.repository.ts +51 -51
  41. package/templates/backend/src/modules/users/users.service.ts +104 -104
  42. package/templates/backend/src/modules/workspaces/schemas/workspace.schema.ts +26 -26
  43. package/templates/backend/src/modules/workspaces/workspaces.module.ts +16 -16
  44. package/templates/backend/src/modules/workspaces/workspaces.repository.ts +34 -34
  45. package/templates/backend/src/modules/workspaces/workspaces.service.ts +42 -42
  46. package/templates/backend/test/app.e2e-spec.ts +25 -25
  47. package/templates/backend/test/jest-e2e.json +9 -9
  48. package/templates/backend/tsconfig.build.json +4 -4
  49. package/templates/backend/tsconfig.json +26 -26
  50. package/templates/frontend/.env.local.example +1 -1
  51. package/templates/frontend/README.md +152 -0
  52. package/templates/frontend/components.json +20 -20
  53. package/templates/frontend/eslint.config.mjs +14 -14
  54. package/templates/frontend/next.config.ts +5 -5
  55. package/templates/frontend/package-lock.json +6722 -6722
  56. package/templates/frontend/package.json +48 -48
  57. package/templates/frontend/pnpm-lock.yaml +5012 -5012
  58. package/templates/frontend/pnpm-workspace.yaml +3 -3
  59. package/templates/frontend/postcss.config.mjs +7 -7
  60. package/templates/frontend/src/app/(auth)/forgot-password/page.tsx +84 -84
  61. package/templates/frontend/src/app/(auth)/layout.tsx +28 -28
  62. package/templates/frontend/src/app/(auth)/login/page.tsx +111 -111
  63. package/templates/frontend/src/app/(auth)/register/page.tsx +161 -161
  64. package/templates/frontend/src/app/(auth)/reset-password/page.tsx +120 -120
  65. package/templates/frontend/src/app/(auth)/verify-email/page.tsx +78 -78
  66. package/templates/frontend/src/app/(dashboard)/billing/page.tsx +111 -111
  67. package/templates/frontend/src/app/(dashboard)/dashboard/page.tsx +105 -105
  68. package/templates/frontend/src/app/(dashboard)/layout.tsx +38 -38
  69. package/templates/frontend/src/app/(dashboard)/profile/page.tsx +226 -226
  70. package/templates/frontend/src/app/(dashboard)/settings/page.tsx +156 -156
  71. package/templates/frontend/src/app/(dashboard)/team/page.tsx +178 -178
  72. package/templates/frontend/src/app/(legal)/privacy/page.tsx +127 -127
  73. package/templates/frontend/src/app/(legal)/terms/page.tsx +118 -118
  74. package/templates/frontend/src/app/globals.css +81 -81
  75. package/templates/frontend/src/app/layout.tsx +26 -26
  76. package/templates/frontend/src/app/page.tsx +5 -45
  77. package/templates/frontend/src/app/setup/page.tsx +371 -275
  78. package/templates/frontend/src/components/dashboard/header.tsx +89 -89
  79. package/templates/frontend/src/components/dashboard/sidebar.tsx +71 -71
  80. package/templates/frontend/src/components/dashboard/stat-card.tsx +34 -34
  81. package/templates/frontend/src/components/landing/faq.tsx +39 -39
  82. package/templates/frontend/src/components/landing/features.tsx +54 -54
  83. package/templates/frontend/src/components/landing/footer.tsx +76 -76
  84. package/templates/frontend/src/components/landing/hero.tsx +72 -72
  85. package/templates/frontend/src/components/landing/navbar.tsx +78 -78
  86. package/templates/frontend/src/components/landing/pricing.tsx +90 -90
  87. package/templates/frontend/src/components/ui/accordion.tsx +52 -52
  88. package/templates/frontend/src/components/ui/avatar.tsx +46 -46
  89. package/templates/frontend/src/components/ui/badge.tsx +30 -30
  90. package/templates/frontend/src/components/ui/button.tsx +52 -52
  91. package/templates/frontend/src/components/ui/card.tsx +50 -50
  92. package/templates/frontend/src/components/ui/checkbox.tsx +27 -27
  93. package/templates/frontend/src/components/ui/dialog.tsx +100 -100
  94. package/templates/frontend/src/components/ui/dropdown-menu.tsx +173 -173
  95. package/templates/frontend/src/components/ui/form.tsx +158 -158
  96. package/templates/frontend/src/components/ui/input.tsx +21 -21
  97. package/templates/frontend/src/components/ui/label.tsx +22 -22
  98. package/templates/frontend/src/components/ui/separator.tsx +25 -25
  99. package/templates/frontend/src/components/ui/skeleton.tsx +7 -7
  100. package/templates/frontend/src/components/ui/switch.tsx +28 -28
  101. package/templates/frontend/src/components/ui/tabs.tsx +54 -54
  102. package/templates/frontend/src/components/ui/textarea.tsx +20 -20
  103. package/templates/frontend/src/components/ui/toast.tsx +109 -109
  104. package/templates/frontend/src/components/ui/toaster.tsx +30 -30
  105. package/templates/frontend/src/config/site.ts +197 -197
  106. package/templates/frontend/src/hooks/use-toast.ts +116 -116
  107. package/templates/frontend/src/lib/api/auth.ts +39 -39
  108. package/templates/frontend/src/lib/api/client.ts +66 -66
  109. package/templates/frontend/src/lib/hooks/use-auth.ts +1 -1
  110. package/templates/frontend/src/lib/utils.ts +6 -6
  111. package/templates/frontend/src/providers/auth-provider.tsx +60 -60
  112. package/templates/frontend/src/types/api.ts +12 -12
  113. package/templates/frontend/src/types/auth.ts +27 -27
  114. package/templates/frontend/tsconfig.json +23 -23
@@ -1,89 +1,89 @@
1
- 'use client'
2
-
3
- import Link from 'next/link'
4
- import { usePathname } from 'next/navigation'
5
- import { User, Settings, CreditCard, LogOut } from 'lucide-react'
6
- import { useAuth } from '@/lib/hooks/use-auth'
7
- import { Avatar, AvatarFallback } from '@/components/ui/avatar'
8
- import {
9
- DropdownMenu,
10
- DropdownMenuContent,
11
- DropdownMenuItem,
12
- DropdownMenuLabel,
13
- DropdownMenuSeparator,
14
- DropdownMenuTrigger,
15
- } from '@/components/ui/dropdown-menu'
16
-
17
- const breadcrumbMap: Record<string, string> = {
18
- '/dashboard': 'Dashboard',
19
- '/profile': 'Perfil',
20
- '/settings': 'Ajustes',
21
- '/billing': 'Facturación',
22
- '/team': 'Equipo',
23
- }
24
-
25
- function getInitials(name: string) {
26
- return name
27
- .split(' ')
28
- .map((n) => n[0])
29
- .slice(0, 2)
30
- .join('')
31
- .toUpperCase()
32
- }
33
-
34
- export function DashboardHeader() {
35
- const { user, logout } = useAuth()
36
- const pathname = usePathname()
37
- const pageTitle = breadcrumbMap[pathname] ?? 'Dashboard'
38
-
39
- return (
40
- <header className="flex h-14 items-center justify-between border-b bg-background px-6">
41
- <h1 className="text-sm font-semibold">{pageTitle}</h1>
42
-
43
- <DropdownMenu>
44
- <DropdownMenuTrigger asChild>
45
- <button className="flex items-center gap-2 rounded-full outline-none focus-visible:ring-2 focus-visible:ring-ring">
46
- <Avatar className="size-8">
47
- <AvatarFallback className="text-xs">
48
- {user?.name ? getInitials(user.name) : 'U'}
49
- </AvatarFallback>
50
- </Avatar>
51
- </button>
52
- </DropdownMenuTrigger>
53
- <DropdownMenuContent align="end" className="w-52">
54
- <DropdownMenuLabel className="font-normal">
55
- <p className="font-medium leading-none">{user?.name}</p>
56
- <p className="mt-1 text-xs text-muted-foreground">{user?.email}</p>
57
- </DropdownMenuLabel>
58
- <DropdownMenuSeparator />
59
- <DropdownMenuItem asChild>
60
- <Link href="/profile">
61
- <User className="mr-2 size-4" />
62
- Perfil
63
- </Link>
64
- </DropdownMenuItem>
65
- <DropdownMenuItem asChild>
66
- <Link href="/settings">
67
- <Settings className="mr-2 size-4" />
68
- Ajustes
69
- </Link>
70
- </DropdownMenuItem>
71
- <DropdownMenuItem asChild>
72
- <Link href="/billing">
73
- <CreditCard className="mr-2 size-4" />
74
- Facturación
75
- </Link>
76
- </DropdownMenuItem>
77
- <DropdownMenuSeparator />
78
- <DropdownMenuItem
79
- className="text-destructive focus:text-destructive"
80
- onClick={logout}
81
- >
82
- <LogOut className="mr-2 size-4" />
83
- Cerrar sesión
84
- </DropdownMenuItem>
85
- </DropdownMenuContent>
86
- </DropdownMenu>
87
- </header>
88
- )
89
- }
1
+ 'use client'
2
+
3
+ import Link from 'next/link'
4
+ import { usePathname } from 'next/navigation'
5
+ import { User, Settings, CreditCard, LogOut } from 'lucide-react'
6
+ import { useAuth } from '@/lib/hooks/use-auth'
7
+ import { Avatar, AvatarFallback } from '@/components/ui/avatar'
8
+ import {
9
+ DropdownMenu,
10
+ DropdownMenuContent,
11
+ DropdownMenuItem,
12
+ DropdownMenuLabel,
13
+ DropdownMenuSeparator,
14
+ DropdownMenuTrigger,
15
+ } from '@/components/ui/dropdown-menu'
16
+
17
+ const breadcrumbMap: Record<string, string> = {
18
+ '/dashboard': 'Dashboard',
19
+ '/profile': 'Perfil',
20
+ '/settings': 'Ajustes',
21
+ '/billing': 'Facturación',
22
+ '/team': 'Equipo',
23
+ }
24
+
25
+ function getInitials(name: string) {
26
+ return name
27
+ .split(' ')
28
+ .map((n) => n[0])
29
+ .slice(0, 2)
30
+ .join('')
31
+ .toUpperCase()
32
+ }
33
+
34
+ export function DashboardHeader() {
35
+ const { user, logout } = useAuth()
36
+ const pathname = usePathname()
37
+ const pageTitle = breadcrumbMap[pathname] ?? 'Dashboard'
38
+
39
+ return (
40
+ <header className="flex h-14 items-center justify-between border-b bg-background px-6">
41
+ <h1 className="text-sm font-semibold">{pageTitle}</h1>
42
+
43
+ <DropdownMenu>
44
+ <DropdownMenuTrigger asChild>
45
+ <button className="flex items-center gap-2 rounded-full outline-none focus-visible:ring-2 focus-visible:ring-ring">
46
+ <Avatar className="size-8">
47
+ <AvatarFallback className="text-xs">
48
+ {user?.name ? getInitials(user.name) : 'U'}
49
+ </AvatarFallback>
50
+ </Avatar>
51
+ </button>
52
+ </DropdownMenuTrigger>
53
+ <DropdownMenuContent align="end" className="w-52">
54
+ <DropdownMenuLabel className="font-normal">
55
+ <p className="font-medium leading-none">{user?.name}</p>
56
+ <p className="mt-1 text-xs text-muted-foreground">{user?.email}</p>
57
+ </DropdownMenuLabel>
58
+ <DropdownMenuSeparator />
59
+ <DropdownMenuItem asChild>
60
+ <Link href="/profile">
61
+ <User className="mr-2 size-4" />
62
+ Perfil
63
+ </Link>
64
+ </DropdownMenuItem>
65
+ <DropdownMenuItem asChild>
66
+ <Link href="/settings">
67
+ <Settings className="mr-2 size-4" />
68
+ Ajustes
69
+ </Link>
70
+ </DropdownMenuItem>
71
+ <DropdownMenuItem asChild>
72
+ <Link href="/billing">
73
+ <CreditCard className="mr-2 size-4" />
74
+ Facturación
75
+ </Link>
76
+ </DropdownMenuItem>
77
+ <DropdownMenuSeparator />
78
+ <DropdownMenuItem
79
+ className="text-destructive focus:text-destructive"
80
+ onClick={logout}
81
+ >
82
+ <LogOut className="mr-2 size-4" />
83
+ Cerrar sesión
84
+ </DropdownMenuItem>
85
+ </DropdownMenuContent>
86
+ </DropdownMenu>
87
+ </header>
88
+ )
89
+ }
@@ -1,71 +1,71 @@
1
- 'use client'
2
-
3
- import Link from 'next/link'
4
- import { usePathname } from 'next/navigation'
5
- import {
6
- LayoutDashboard,
7
- User,
8
- Settings,
9
- CreditCard,
10
- Users,
11
- LogOut,
12
- } from 'lucide-react'
13
- import { siteConfig } from '@/config/site'
14
- import { useAuth } from '@/lib/hooks/use-auth'
15
- import { Button } from '@/components/ui/button'
16
- import { Separator } from '@/components/ui/separator'
17
- import { cn } from '@/lib/utils'
18
-
19
- const navItems = [
20
- { label: 'Dashboard', href: '/dashboard', icon: LayoutDashboard },
21
- { label: 'Perfil', href: '/profile', icon: User },
22
- { label: 'Ajustes', href: '/settings', icon: Settings },
23
- { label: 'Facturación', href: '/billing', icon: CreditCard },
24
- { label: 'Equipo', href: '/team', icon: Users },
25
- ]
26
-
27
- export function DashboardSidebar() {
28
- const pathname = usePathname()
29
- const { logout } = useAuth()
30
-
31
- return (
32
- <aside className="flex w-60 shrink-0 flex-col border-r bg-card">
33
- <div className="flex h-14 items-center border-b px-4">
34
- <Link href="/" className="text-sm font-bold tracking-tight">
35
- {siteConfig.name}
36
- </Link>
37
- </div>
38
-
39
- <nav className="flex-1 space-y-1 p-3">
40
- {navItems.map(({ label, href, icon: Icon }) => (
41
- <Button
42
- key={href}
43
- asChild
44
- variant={pathname === href ? 'secondary' : 'ghost'}
45
- className={cn(
46
- 'w-full justify-start',
47
- pathname === href && 'font-medium',
48
- )}
49
- >
50
- <Link href={href}>
51
- <Icon className="mr-2 size-4" />
52
- {label}
53
- </Link>
54
- </Button>
55
- ))}
56
- </nav>
57
-
58
- <div className="p-3">
59
- <Separator className="mb-3" />
60
- <Button
61
- variant="ghost"
62
- className="w-full justify-start text-muted-foreground hover:text-foreground"
63
- onClick={logout}
64
- >
65
- <LogOut className="mr-2 size-4" />
66
- Cerrar sesión
67
- </Button>
68
- </div>
69
- </aside>
70
- )
71
- }
1
+ 'use client'
2
+
3
+ import Link from 'next/link'
4
+ import { usePathname } from 'next/navigation'
5
+ import {
6
+ LayoutDashboard,
7
+ User,
8
+ Settings,
9
+ CreditCard,
10
+ Users,
11
+ LogOut,
12
+ } from 'lucide-react'
13
+ import { siteConfig } from '@/config/site'
14
+ import { useAuth } from '@/lib/hooks/use-auth'
15
+ import { Button } from '@/components/ui/button'
16
+ import { Separator } from '@/components/ui/separator'
17
+ import { cn } from '@/lib/utils'
18
+
19
+ const navItems = [
20
+ { label: 'Dashboard', href: '/dashboard', icon: LayoutDashboard },
21
+ { label: 'Perfil', href: '/profile', icon: User },
22
+ { label: 'Ajustes', href: '/settings', icon: Settings },
23
+ { label: 'Facturación', href: '/billing', icon: CreditCard },
24
+ { label: 'Equipo', href: '/team', icon: Users },
25
+ ]
26
+
27
+ export function DashboardSidebar() {
28
+ const pathname = usePathname()
29
+ const { logout } = useAuth()
30
+
31
+ return (
32
+ <aside className="flex w-60 shrink-0 flex-col border-r bg-card">
33
+ <div className="flex h-14 items-center border-b px-4">
34
+ <Link href="/" className="text-sm font-bold tracking-tight">
35
+ {siteConfig.name}
36
+ </Link>
37
+ </div>
38
+
39
+ <nav className="flex-1 space-y-1 p-3">
40
+ {navItems.map(({ label, href, icon: Icon }) => (
41
+ <Button
42
+ key={href}
43
+ asChild
44
+ variant={pathname === href ? 'secondary' : 'ghost'}
45
+ className={cn(
46
+ 'w-full justify-start',
47
+ pathname === href && 'font-medium',
48
+ )}
49
+ >
50
+ <Link href={href}>
51
+ <Icon className="mr-2 size-4" />
52
+ {label}
53
+ </Link>
54
+ </Button>
55
+ ))}
56
+ </nav>
57
+
58
+ <div className="p-3">
59
+ <Separator className="mb-3" />
60
+ <Button
61
+ variant="ghost"
62
+ className="w-full justify-start text-muted-foreground hover:text-foreground"
63
+ onClick={logout}
64
+ >
65
+ <LogOut className="mr-2 size-4" />
66
+ Cerrar sesión
67
+ </Button>
68
+ </div>
69
+ </aside>
70
+ )
71
+ }
@@ -1,34 +1,34 @@
1
- import { type LucideIcon } from 'lucide-react'
2
- import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
3
- import { cn } from '@/lib/utils'
4
-
5
- interface StatCardProps {
6
- title: string
7
- value: string
8
- description?: string
9
- icon: LucideIcon
10
- trend?: { value: string; positive: boolean }
11
- className?: string
12
- }
13
-
14
- export function StatCard({ title, value, description, icon: Icon, trend, className }: StatCardProps) {
15
- return (
16
- <Card className={cn('', className)}>
17
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
18
- <CardTitle className="text-sm font-medium">{title}</CardTitle>
19
- <Icon className="size-4 text-muted-foreground" />
20
- </CardHeader>
21
- <CardContent>
22
- <div className="text-2xl font-bold">{value}</div>
23
- {trend && (
24
- <p className={cn('mt-1 text-xs', trend.positive ? 'text-green-600' : 'text-red-500')}>
25
- {trend.positive ? '+' : ''}{trend.value} vs. mes anterior
26
- </p>
27
- )}
28
- {description && !trend && (
29
- <p className="mt-1 text-xs text-muted-foreground">{description}</p>
30
- )}
31
- </CardContent>
32
- </Card>
33
- )
34
- }
1
+ import { type LucideIcon } from 'lucide-react'
2
+ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
3
+ import { cn } from '@/lib/utils'
4
+
5
+ interface StatCardProps {
6
+ title: string
7
+ value: string
8
+ description?: string
9
+ icon: LucideIcon
10
+ trend?: { value: string; positive: boolean }
11
+ className?: string
12
+ }
13
+
14
+ export function StatCard({ title, value, description, icon: Icon, trend, className }: StatCardProps) {
15
+ return (
16
+ <Card className={cn('', className)}>
17
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
18
+ <CardTitle className="text-sm font-medium">{title}</CardTitle>
19
+ <Icon className="size-4 text-muted-foreground" />
20
+ </CardHeader>
21
+ <CardContent>
22
+ <div className="text-2xl font-bold">{value}</div>
23
+ {trend && (
24
+ <p className={cn('mt-1 text-xs', trend.positive ? 'text-green-600' : 'text-red-500')}>
25
+ {trend.positive ? '+' : ''}{trend.value} vs. mes anterior
26
+ </p>
27
+ )}
28
+ {description && !trend && (
29
+ <p className="mt-1 text-xs text-muted-foreground">{description}</p>
30
+ )}
31
+ </CardContent>
32
+ </Card>
33
+ )
34
+ }
@@ -1,39 +1,39 @@
1
- import { siteConfig } from '@/config/site'
2
- import {
3
- Accordion,
4
- AccordionContent,
5
- AccordionItem,
6
- AccordionTrigger,
7
- } from '@/components/ui/accordion'
8
-
9
- export function LandingFaq() {
10
- return (
11
- <section id="faq" className="py-20 md:py-28">
12
- <div className="mx-auto max-w-3xl px-4">
13
- <div className="text-center">
14
- <h2 className="text-3xl font-bold tracking-tight md:text-4xl">
15
- Preguntas frecuentes
16
- </h2>
17
- <p className="mt-4 text-muted-foreground">
18
- Si no encontrás lo que buscás, escribinos a{' '}
19
- <a
20
- href={`mailto:${siteConfig.supportEmail}`}
21
- className="underline underline-offset-4 hover:text-foreground"
22
- >
23
- {siteConfig.supportEmail}
24
- </a>
25
- </p>
26
- </div>
27
-
28
- <Accordion type="single" collapsible className="mt-12">
29
- {siteConfig.faq.map((item, index) => (
30
- <AccordionItem key={index} value={`item-${index}`}>
31
- <AccordionTrigger className="text-left">{item.question}</AccordionTrigger>
32
- <AccordionContent className="text-muted-foreground">{item.answer}</AccordionContent>
33
- </AccordionItem>
34
- ))}
35
- </Accordion>
36
- </div>
37
- </section>
38
- )
39
- }
1
+ import { siteConfig } from '@/config/site'
2
+ import {
3
+ Accordion,
4
+ AccordionContent,
5
+ AccordionItem,
6
+ AccordionTrigger,
7
+ } from '@/components/ui/accordion'
8
+
9
+ export function LandingFaq() {
10
+ return (
11
+ <section id="faq" className="py-20 md:py-28">
12
+ <div className="mx-auto max-w-3xl px-4">
13
+ <div className="text-center">
14
+ <h2 className="text-3xl font-bold tracking-tight md:text-4xl">
15
+ Preguntas frecuentes
16
+ </h2>
17
+ <p className="mt-4 text-muted-foreground">
18
+ Si no encontrás lo que buscás, escribinos a{' '}
19
+ <a
20
+ href={`mailto:${siteConfig.supportEmail}`}
21
+ className="underline underline-offset-4 hover:text-foreground"
22
+ >
23
+ {siteConfig.supportEmail}
24
+ </a>
25
+ </p>
26
+ </div>
27
+
28
+ <Accordion type="single" collapsible className="mt-12">
29
+ {siteConfig.faq.map((item, index) => (
30
+ <AccordionItem key={index} value={`item-${index}`}>
31
+ <AccordionTrigger className="text-left">{item.question}</AccordionTrigger>
32
+ <AccordionContent className="text-muted-foreground">{item.answer}</AccordionContent>
33
+ </AccordionItem>
34
+ ))}
35
+ </Accordion>
36
+ </div>
37
+ </section>
38
+ )
39
+ }
@@ -1,54 +1,54 @@
1
- import {
2
- Zap,
3
- Shield,
4
- BarChart3,
5
- Users,
6
- Globe,
7
- Headphones,
8
- type LucideIcon,
9
- } from 'lucide-react'
10
- import { siteConfig } from '@/config/site'
11
-
12
- const iconMap: Record<string, LucideIcon> = {
13
- Zap,
14
- Shield,
15
- BarChart3,
16
- Users,
17
- Globe,
18
- Headphones,
19
- }
20
-
21
- export function LandingFeatures() {
22
- return (
23
- <section id="features" className="py-20 md:py-28">
24
- <div className="mx-auto max-w-6xl px-4">
25
- <div className="mx-auto max-w-2xl text-center">
26
- <h2 className="text-3xl font-bold tracking-tight md:text-4xl">
27
- Todo lo que necesitás para lanzar
28
- </h2>
29
- <p className="mt-4 text-muted-foreground">
30
- Funcionalidades pensadas para que te enfoques en el negocio, no en la infraestructura.
31
- </p>
32
- </div>
33
-
34
- <div className="mt-16 grid gap-8 sm:grid-cols-2 lg:grid-cols-3">
35
- {siteConfig.features.map((feature) => {
36
- const Icon = iconMap[feature.icon] ?? Zap
37
- return (
38
- <div
39
- key={feature.title}
40
- className="group rounded-xl border bg-card p-6 transition-shadow hover:shadow-md"
41
- >
42
- <div className="mb-4 inline-flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10 text-primary">
43
- <Icon className="size-5" />
44
- </div>
45
- <h3 className="font-semibold">{feature.title}</h3>
46
- <p className="mt-2 text-sm text-muted-foreground">{feature.description}</p>
47
- </div>
48
- )
49
- })}
50
- </div>
51
- </div>
52
- </section>
53
- )
54
- }
1
+ import {
2
+ Zap,
3
+ Shield,
4
+ BarChart3,
5
+ Users,
6
+ Globe,
7
+ Headphones,
8
+ type LucideIcon,
9
+ } from 'lucide-react'
10
+ import { siteConfig } from '@/config/site'
11
+
12
+ const iconMap: Record<string, LucideIcon> = {
13
+ Zap,
14
+ Shield,
15
+ BarChart3,
16
+ Users,
17
+ Globe,
18
+ Headphones,
19
+ }
20
+
21
+ export function LandingFeatures() {
22
+ return (
23
+ <section id="features" className="py-20 md:py-28">
24
+ <div className="mx-auto max-w-6xl px-4">
25
+ <div className="mx-auto max-w-2xl text-center">
26
+ <h2 className="text-3xl font-bold tracking-tight md:text-4xl">
27
+ Todo lo que necesitás para lanzar
28
+ </h2>
29
+ <p className="mt-4 text-muted-foreground">
30
+ Funcionalidades pensadas para que te enfoques en el negocio, no en la infraestructura.
31
+ </p>
32
+ </div>
33
+
34
+ <div className="mt-16 grid gap-8 sm:grid-cols-2 lg:grid-cols-3">
35
+ {siteConfig.features.map((feature) => {
36
+ const Icon = iconMap[feature.icon] ?? Zap
37
+ return (
38
+ <div
39
+ key={feature.title}
40
+ className="group rounded-xl border bg-card p-6 transition-shadow hover:shadow-md"
41
+ >
42
+ <div className="mb-4 inline-flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10 text-primary">
43
+ <Icon className="size-5" />
44
+ </div>
45
+ <h3 className="font-semibold">{feature.title}</h3>
46
+ <p className="mt-2 text-sm text-muted-foreground">{feature.description}</p>
47
+ </div>
48
+ )
49
+ })}
50
+ </div>
51
+ </div>
52
+ </section>
53
+ )
54
+ }