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,76 +1,76 @@
1
- import Link from 'next/link'
2
- import { Twitter, Github, Linkedin } from 'lucide-react'
3
- import { siteConfig } from '@/config/site'
4
-
5
- export function LandingFooter() {
6
- const { footer } = siteConfig
7
-
8
- return (
9
- <footer className="border-t bg-card">
10
- <div className="mx-auto max-w-6xl px-4 py-12">
11
- <div className="grid gap-8 sm:grid-cols-2 md:grid-cols-4">
12
- {/* Brand */}
13
- <div className="sm:col-span-2 md:col-span-1">
14
- <p className="text-lg font-bold">{siteConfig.name}</p>
15
- <p className="mt-2 text-sm text-muted-foreground">{siteConfig.tagline}</p>
16
- <div className="mt-4 flex gap-3">
17
- {footer.social.twitter && (
18
- <a
19
- href={footer.social.twitter}
20
- target="_blank"
21
- rel="noopener noreferrer"
22
- className="text-muted-foreground transition-colors hover:text-foreground"
23
- >
24
- <Twitter className="size-4" />
25
- </a>
26
- )}
27
- {footer.social.github && (
28
- <a
29
- href={footer.social.github}
30
- target="_blank"
31
- rel="noopener noreferrer"
32
- className="text-muted-foreground transition-colors hover:text-foreground"
33
- >
34
- <Github className="size-4" />
35
- </a>
36
- )}
37
- {footer.social.linkedin && (
38
- <a
39
- href={footer.social.linkedin}
40
- target="_blank"
41
- rel="noopener noreferrer"
42
- className="text-muted-foreground transition-colors hover:text-foreground"
43
- >
44
- <Linkedin className="size-4" />
45
- </a>
46
- )}
47
- </div>
48
- </div>
49
-
50
- {/* Link columns */}
51
- {footer.columns.map((col) => (
52
- <div key={col.title}>
53
- <p className="text-sm font-semibold">{col.title}</p>
54
- <ul className="mt-4 space-y-3">
55
- {col.links.map((link) => (
56
- <li key={link.label}>
57
- <Link
58
- href={link.href}
59
- className="text-sm text-muted-foreground transition-colors hover:text-foreground"
60
- >
61
- {link.label}
62
- </Link>
63
- </li>
64
- ))}
65
- </ul>
66
- </div>
67
- ))}
68
- </div>
69
-
70
- <div className="mt-12 border-t pt-6 text-center text-sm text-muted-foreground">
71
- {footer.copyright}
72
- </div>
73
- </div>
74
- </footer>
75
- )
76
- }
1
+ import Link from 'next/link'
2
+ import { Twitter, Github, Linkedin } from 'lucide-react'
3
+ import { siteConfig } from '@/config/site'
4
+
5
+ export function LandingFooter() {
6
+ const { footer } = siteConfig
7
+
8
+ return (
9
+ <footer className="border-t bg-card">
10
+ <div className="mx-auto max-w-6xl px-4 py-12">
11
+ <div className="grid gap-8 sm:grid-cols-2 md:grid-cols-4">
12
+ {/* Brand */}
13
+ <div className="sm:col-span-2 md:col-span-1">
14
+ <p className="text-lg font-bold">{siteConfig.name}</p>
15
+ <p className="mt-2 text-sm text-muted-foreground">{siteConfig.tagline}</p>
16
+ <div className="mt-4 flex gap-3">
17
+ {footer.social.twitter && (
18
+ <a
19
+ href={footer.social.twitter}
20
+ target="_blank"
21
+ rel="noopener noreferrer"
22
+ className="text-muted-foreground transition-colors hover:text-foreground"
23
+ >
24
+ <Twitter className="size-4" />
25
+ </a>
26
+ )}
27
+ {footer.social.github && (
28
+ <a
29
+ href={footer.social.github}
30
+ target="_blank"
31
+ rel="noopener noreferrer"
32
+ className="text-muted-foreground transition-colors hover:text-foreground"
33
+ >
34
+ <Github className="size-4" />
35
+ </a>
36
+ )}
37
+ {footer.social.linkedin && (
38
+ <a
39
+ href={footer.social.linkedin}
40
+ target="_blank"
41
+ rel="noopener noreferrer"
42
+ className="text-muted-foreground transition-colors hover:text-foreground"
43
+ >
44
+ <Linkedin className="size-4" />
45
+ </a>
46
+ )}
47
+ </div>
48
+ </div>
49
+
50
+ {/* Link columns */}
51
+ {footer.columns.map((col) => (
52
+ <div key={col.title}>
53
+ <p className="text-sm font-semibold">{col.title}</p>
54
+ <ul className="mt-4 space-y-3">
55
+ {col.links.map((link) => (
56
+ <li key={link.label}>
57
+ <Link
58
+ href={link.href}
59
+ className="text-sm text-muted-foreground transition-colors hover:text-foreground"
60
+ >
61
+ {link.label}
62
+ </Link>
63
+ </li>
64
+ ))}
65
+ </ul>
66
+ </div>
67
+ ))}
68
+ </div>
69
+
70
+ <div className="mt-12 border-t pt-6 text-center text-sm text-muted-foreground">
71
+ {footer.copyright}
72
+ </div>
73
+ </div>
74
+ </footer>
75
+ )
76
+ }
@@ -1,72 +1,72 @@
1
- import Link from 'next/link'
2
- import { ArrowRight } from 'lucide-react'
3
- import { siteConfig } from '@/config/site'
4
- import { Button } from '@/components/ui/button'
5
- import { Badge } from '@/components/ui/badge'
6
-
7
- export function LandingHero() {
8
- return (
9
- <section className="relative overflow-hidden py-20 md:py-32">
10
- {/* Background gradient */}
11
- <div className="pointer-events-none absolute inset-0 -z-10 bg-[radial-gradient(ellipse_80%_60%_at_50%_-10%,hsl(var(--primary)/0.12),transparent)]" />
12
-
13
- <div className="mx-auto max-w-6xl px-4 text-center">
14
- <Badge variant="outline" className="mb-6 px-4 py-1.5 text-sm">
15
- Listo para producción en minutos
16
- </Badge>
17
-
18
- <h1 className="mx-auto max-w-4xl text-4xl font-bold tracking-tight md:text-6xl lg:text-7xl">
19
- {siteConfig.hero.headline}
20
- </h1>
21
-
22
- <p className="mx-auto mt-6 max-w-2xl text-lg text-muted-foreground md:text-xl">
23
- {siteConfig.hero.description}
24
- </p>
25
-
26
- <div className="mt-10 flex flex-col items-center justify-center gap-4 sm:flex-row">
27
- <Button size="lg" asChild className="gap-2">
28
- <Link href={siteConfig.hero.cta.href}>
29
- {siteConfig.hero.cta.label}
30
- <ArrowRight className="size-4" />
31
- </Link>
32
- </Button>
33
- <Button size="lg" variant="outline" asChild>
34
- <Link href={siteConfig.hero.ctaSecondary.href}>
35
- {siteConfig.hero.ctaSecondary.label}
36
- </Link>
37
- </Button>
38
- </div>
39
-
40
- {/* Social proof */}
41
- <p className="mt-8 text-sm text-muted-foreground">
42
- Sin tarjeta de crédito &middot; Cancelá cuando quieras &middot; Soporte en español
43
- </p>
44
-
45
- {/* App mockup placeholder */}
46
- <div className="mx-auto mt-16 max-w-4xl overflow-hidden rounded-xl border bg-muted/50 shadow-2xl">
47
- <div className="flex h-8 items-center gap-2 border-b bg-muted px-4">
48
- <div className="size-3 rounded-full bg-red-400" />
49
- <div className="size-3 rounded-full bg-yellow-400" />
50
- <div className="size-3 rounded-full bg-green-400" />
51
- <div className="mx-2 h-4 flex-1 rounded bg-muted-foreground/20" />
52
- </div>
53
- <div className="grid grid-cols-[200px_1fr] divide-x" style={{ minHeight: 320 }}>
54
- <div className="space-y-2 bg-card p-4">
55
- {[...Array(6)].map((_, i) => (
56
- <div key={i} className="h-7 rounded-md bg-muted" />
57
- ))}
58
- </div>
59
- <div className="space-y-4 p-6">
60
- <div className="grid grid-cols-3 gap-3">
61
- {[...Array(3)].map((_, i) => (
62
- <div key={i} className="h-20 rounded-lg border bg-card" />
63
- ))}
64
- </div>
65
- <div className="h-40 rounded-lg border bg-card" />
66
- </div>
67
- </div>
68
- </div>
69
- </div>
70
- </section>
71
- )
72
- }
1
+ import Link from 'next/link'
2
+ import { ArrowRight } from 'lucide-react'
3
+ import { siteConfig } from '@/config/site'
4
+ import { Button } from '@/components/ui/button'
5
+ import { Badge } from '@/components/ui/badge'
6
+
7
+ export function LandingHero() {
8
+ return (
9
+ <section className="relative overflow-hidden py-20 md:py-32">
10
+ {/* Background gradient */}
11
+ <div className="pointer-events-none absolute inset-0 -z-10 bg-[radial-gradient(ellipse_80%_60%_at_50%_-10%,hsl(var(--primary)/0.12),transparent)]" />
12
+
13
+ <div className="mx-auto max-w-6xl px-4 text-center">
14
+ <Badge variant="outline" className="mb-6 px-4 py-1.5 text-sm">
15
+ Listo para producción en minutos
16
+ </Badge>
17
+
18
+ <h1 className="mx-auto max-w-4xl text-4xl font-bold tracking-tight md:text-6xl lg:text-7xl">
19
+ {siteConfig.hero.headline}
20
+ </h1>
21
+
22
+ <p className="mx-auto mt-6 max-w-2xl text-lg text-muted-foreground md:text-xl">
23
+ {siteConfig.hero.description}
24
+ </p>
25
+
26
+ <div className="mt-10 flex flex-col items-center justify-center gap-4 sm:flex-row">
27
+ <Button size="lg" asChild className="gap-2">
28
+ <Link href={siteConfig.hero.cta.href}>
29
+ {siteConfig.hero.cta.label}
30
+ <ArrowRight className="size-4" />
31
+ </Link>
32
+ </Button>
33
+ <Button size="lg" variant="outline" asChild>
34
+ <Link href={siteConfig.hero.ctaSecondary.href}>
35
+ {siteConfig.hero.ctaSecondary.label}
36
+ </Link>
37
+ </Button>
38
+ </div>
39
+
40
+ {/* Social proof */}
41
+ <p className="mt-8 text-sm text-muted-foreground">
42
+ Sin tarjeta de crédito &middot; Cancelá cuando quieras &middot; Soporte en español
43
+ </p>
44
+
45
+ {/* App mockup placeholder */}
46
+ <div className="mx-auto mt-16 max-w-4xl overflow-hidden rounded-xl border bg-muted/50 shadow-2xl">
47
+ <div className="flex h-8 items-center gap-2 border-b bg-muted px-4">
48
+ <div className="size-3 rounded-full bg-red-400" />
49
+ <div className="size-3 rounded-full bg-yellow-400" />
50
+ <div className="size-3 rounded-full bg-green-400" />
51
+ <div className="mx-2 h-4 flex-1 rounded bg-muted-foreground/20" />
52
+ </div>
53
+ <div className="grid grid-cols-[200px_1fr] divide-x" style={{ minHeight: 320 }}>
54
+ <div className="space-y-2 bg-card p-4">
55
+ {[...Array(6)].map((_, i) => (
56
+ <div key={i} className="h-7 rounded-md bg-muted" />
57
+ ))}
58
+ </div>
59
+ <div className="space-y-4 p-6">
60
+ <div className="grid grid-cols-3 gap-3">
61
+ {[...Array(3)].map((_, i) => (
62
+ <div key={i} className="h-20 rounded-lg border bg-card" />
63
+ ))}
64
+ </div>
65
+ <div className="h-40 rounded-lg border bg-card" />
66
+ </div>
67
+ </div>
68
+ </div>
69
+ </div>
70
+ </section>
71
+ )
72
+ }
@@ -1,78 +1,78 @@
1
- 'use client'
2
-
3
- import Link from 'next/link'
4
- import { useState } from 'react'
5
- import { Menu, X } from 'lucide-react'
6
- import { siteConfig } from '@/config/site'
7
- import { Button } from '@/components/ui/button'
8
-
9
- export function LandingNavbar() {
10
- const [open, setOpen] = useState(false)
11
-
12
- return (
13
- <header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
14
- <div className="mx-auto flex h-16 max-w-6xl items-center justify-between px-4">
15
- <Link href="/" className="text-lg font-bold tracking-tight">
16
- {siteConfig.name}
17
- </Link>
18
-
19
- {/* Desktop nav */}
20
- <nav className="hidden items-center gap-6 md:flex">
21
- {siteConfig.nav.links.map((link) => (
22
- <Link
23
- key={link.href}
24
- href={link.href}
25
- className="text-sm text-muted-foreground transition-colors hover:text-foreground"
26
- >
27
- {link.label}
28
- </Link>
29
- ))}
30
- </nav>
31
-
32
- <div className="hidden items-center gap-2 md:flex">
33
- <Button variant="ghost" asChild size="sm">
34
- <Link href="/login">Iniciar sesión</Link>
35
- </Button>
36
- <Button asChild size="sm">
37
- <Link href="/register">Empezar gratis</Link>
38
- </Button>
39
- </div>
40
-
41
- {/* Mobile toggle */}
42
- <button
43
- className="md:hidden"
44
- onClick={() => setOpen((v) => !v)}
45
- aria-label="Menú"
46
- >
47
- {open ? <X className="size-5" /> : <Menu className="size-5" />}
48
- </button>
49
- </div>
50
-
51
- {/* Mobile menu */}
52
- {open && (
53
- <div className="border-t bg-background px-4 pb-4 md:hidden">
54
- <nav className="flex flex-col gap-3 pt-4">
55
- {siteConfig.nav.links.map((link) => (
56
- <Link
57
- key={link.href}
58
- href={link.href}
59
- className="text-sm text-muted-foreground"
60
- onClick={() => setOpen(false)}
61
- >
62
- {link.label}
63
- </Link>
64
- ))}
65
- <div className="mt-2 flex flex-col gap-2">
66
- <Button variant="outline" asChild>
67
- <Link href="/login">Iniciar sesión</Link>
68
- </Button>
69
- <Button asChild>
70
- <Link href="/register">Empezar gratis</Link>
71
- </Button>
72
- </div>
73
- </nav>
74
- </div>
75
- )}
76
- </header>
77
- )
78
- }
1
+ 'use client'
2
+
3
+ import Link from 'next/link'
4
+ import { useState } from 'react'
5
+ import { Menu, X } from 'lucide-react'
6
+ import { siteConfig } from '@/config/site'
7
+ import { Button } from '@/components/ui/button'
8
+
9
+ export function LandingNavbar() {
10
+ const [open, setOpen] = useState(false)
11
+
12
+ return (
13
+ <header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
14
+ <div className="mx-auto flex h-16 max-w-6xl items-center justify-between px-4">
15
+ <Link href="/" className="text-lg font-bold tracking-tight">
16
+ {siteConfig.name}
17
+ </Link>
18
+
19
+ {/* Desktop nav */}
20
+ <nav className="hidden items-center gap-6 md:flex">
21
+ {siteConfig.nav.links.map((link) => (
22
+ <Link
23
+ key={link.href}
24
+ href={link.href}
25
+ className="text-sm text-muted-foreground transition-colors hover:text-foreground"
26
+ >
27
+ {link.label}
28
+ </Link>
29
+ ))}
30
+ </nav>
31
+
32
+ <div className="hidden items-center gap-2 md:flex">
33
+ <Button variant="ghost" asChild size="sm">
34
+ <Link href="/login">Iniciar sesión</Link>
35
+ </Button>
36
+ <Button asChild size="sm">
37
+ <Link href="/register">Empezar gratis</Link>
38
+ </Button>
39
+ </div>
40
+
41
+ {/* Mobile toggle */}
42
+ <button
43
+ className="md:hidden"
44
+ onClick={() => setOpen((v) => !v)}
45
+ aria-label="Menú"
46
+ >
47
+ {open ? <X className="size-5" /> : <Menu className="size-5" />}
48
+ </button>
49
+ </div>
50
+
51
+ {/* Mobile menu */}
52
+ {open && (
53
+ <div className="border-t bg-background px-4 pb-4 md:hidden">
54
+ <nav className="flex flex-col gap-3 pt-4">
55
+ {siteConfig.nav.links.map((link) => (
56
+ <Link
57
+ key={link.href}
58
+ href={link.href}
59
+ className="text-sm text-muted-foreground"
60
+ onClick={() => setOpen(false)}
61
+ >
62
+ {link.label}
63
+ </Link>
64
+ ))}
65
+ <div className="mt-2 flex flex-col gap-2">
66
+ <Button variant="outline" asChild>
67
+ <Link href="/login">Iniciar sesión</Link>
68
+ </Button>
69
+ <Button asChild>
70
+ <Link href="/register">Empezar gratis</Link>
71
+ </Button>
72
+ </div>
73
+ </nav>
74
+ </div>
75
+ )}
76
+ </header>
77
+ )
78
+ }
@@ -1,90 +1,90 @@
1
- import Link from 'next/link'
2
- import { Check } from 'lucide-react'
3
- import { siteConfig } from '@/config/site'
4
- import { Button } from '@/components/ui/button'
5
- import { Badge } from '@/components/ui/badge'
6
- import { cn } from '@/lib/utils'
7
-
8
- export function LandingPricing() {
9
- return (
10
- <section id="pricing" className="py-20 md:py-28">
11
- <div className="mx-auto max-w-6xl px-4">
12
- <div className="mx-auto max-w-2xl text-center">
13
- <h2 className="text-3xl font-bold tracking-tight md:text-4xl">
14
- Precios claros y sin sorpresas
15
- </h2>
16
- <p className="mt-4 text-muted-foreground">
17
- Elegí el plan que mejor se adapte a tu equipo. Cambiá cuando quieras.
18
- </p>
19
- </div>
20
-
21
- <div className="mt-16 grid gap-8 md:grid-cols-3">
22
- {siteConfig.pricing.map((plan) => (
23
- <div
24
- key={plan.name}
25
- className={cn(
26
- 'relative flex flex-col rounded-xl border p-8',
27
- plan.highlight
28
- ? 'border-primary bg-primary text-primary-foreground shadow-lg'
29
- : 'bg-card',
30
- )}
31
- >
32
- {plan.highlight && (
33
- <Badge className="absolute -top-3 left-1/2 -translate-x-1/2 bg-background text-foreground shadow">
34
- Más popular
35
- </Badge>
36
- )}
37
-
38
- <div>
39
- <h3 className="text-lg font-semibold">{plan.name}</h3>
40
- <p
41
- className={cn(
42
- 'mt-1 text-sm',
43
- plan.highlight ? 'text-primary-foreground/80' : 'text-muted-foreground',
44
- )}
45
- >
46
- {plan.description}
47
- </p>
48
- <div className="mt-4 flex items-end gap-1">
49
- <span className="text-4xl font-bold">{plan.price}</span>
50
- {plan.period && (
51
- <span
52
- className={cn(
53
- 'mb-1 text-sm',
54
- plan.highlight ? 'text-primary-foreground/70' : 'text-muted-foreground',
55
- )}
56
- >
57
- {plan.period}
58
- </span>
59
- )}
60
- </div>
61
- </div>
62
-
63
- <ul className="my-8 flex-1 space-y-3">
64
- {plan.features.map((f) => (
65
- <li key={f} className="flex items-center gap-3 text-sm">
66
- <Check
67
- className={cn(
68
- 'size-4 shrink-0',
69
- plan.highlight ? 'text-primary-foreground' : 'text-primary',
70
- )}
71
- />
72
- {f}
73
- </li>
74
- ))}
75
- </ul>
76
-
77
- <Button
78
- asChild
79
- variant={plan.highlight ? 'secondary' : 'outline'}
80
- className="w-full"
81
- >
82
- <Link href={plan.href}>{plan.cta}</Link>
83
- </Button>
84
- </div>
85
- ))}
86
- </div>
87
- </div>
88
- </section>
89
- )
90
- }
1
+ import Link from 'next/link'
2
+ import { Check } from 'lucide-react'
3
+ import { siteConfig } from '@/config/site'
4
+ import { Button } from '@/components/ui/button'
5
+ import { Badge } from '@/components/ui/badge'
6
+ import { cn } from '@/lib/utils'
7
+
8
+ export function LandingPricing() {
9
+ return (
10
+ <section id="pricing" className="py-20 md:py-28">
11
+ <div className="mx-auto max-w-6xl px-4">
12
+ <div className="mx-auto max-w-2xl text-center">
13
+ <h2 className="text-3xl font-bold tracking-tight md:text-4xl">
14
+ Precios claros y sin sorpresas
15
+ </h2>
16
+ <p className="mt-4 text-muted-foreground">
17
+ Elegí el plan que mejor se adapte a tu equipo. Cambiá cuando quieras.
18
+ </p>
19
+ </div>
20
+
21
+ <div className="mt-16 grid gap-8 md:grid-cols-3">
22
+ {siteConfig.pricing.map((plan) => (
23
+ <div
24
+ key={plan.name}
25
+ className={cn(
26
+ 'relative flex flex-col rounded-xl border p-8',
27
+ plan.highlight
28
+ ? 'border-primary bg-primary text-primary-foreground shadow-lg'
29
+ : 'bg-card',
30
+ )}
31
+ >
32
+ {plan.highlight && (
33
+ <Badge className="absolute -top-3 left-1/2 -translate-x-1/2 bg-background text-foreground shadow">
34
+ Más popular
35
+ </Badge>
36
+ )}
37
+
38
+ <div>
39
+ <h3 className="text-lg font-semibold">{plan.name}</h3>
40
+ <p
41
+ className={cn(
42
+ 'mt-1 text-sm',
43
+ plan.highlight ? 'text-primary-foreground/80' : 'text-muted-foreground',
44
+ )}
45
+ >
46
+ {plan.description}
47
+ </p>
48
+ <div className="mt-4 flex items-end gap-1">
49
+ <span className="text-4xl font-bold">{plan.price}</span>
50
+ {plan.period && (
51
+ <span
52
+ className={cn(
53
+ 'mb-1 text-sm',
54
+ plan.highlight ? 'text-primary-foreground/70' : 'text-muted-foreground',
55
+ )}
56
+ >
57
+ {plan.period}
58
+ </span>
59
+ )}
60
+ </div>
61
+ </div>
62
+
63
+ <ul className="my-8 flex-1 space-y-3">
64
+ {plan.features.map((f) => (
65
+ <li key={f} className="flex items-center gap-3 text-sm">
66
+ <Check
67
+ className={cn(
68
+ 'size-4 shrink-0',
69
+ plan.highlight ? 'text-primary-foreground' : 'text-primary',
70
+ )}
71
+ />
72
+ {f}
73
+ </li>
74
+ ))}
75
+ </ul>
76
+
77
+ <Button
78
+ asChild
79
+ variant={plan.highlight ? 'secondary' : 'outline'}
80
+ className="w-full"
81
+ >
82
+ <Link href={plan.href}>{plan.cta}</Link>
83
+ </Button>
84
+ </div>
85
+ ))}
86
+ </div>
87
+ </div>
88
+ </section>
89
+ )
90
+ }