ar-saas 0.2.1 → 0.3.1
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/README.md +107 -25
- package/dist/cli.js +33 -1
- package/dist/generator.js +12 -3
- package/package.json +7 -3
- package/templates/frontend/package.json +21 -13
- package/templates/frontend/pnpm-lock.yaml +5012 -0
- package/templates/frontend/pnpm-workspace.yaml +3 -0
- package/templates/frontend/src/app/(auth)/register/page.tsx +49 -7
- package/templates/frontend/src/app/(dashboard)/billing/page.tsx +111 -0
- package/templates/frontend/src/app/(dashboard)/dashboard/page.tsx +81 -12
- package/templates/frontend/src/app/(dashboard)/layout.tsx +11 -32
- package/templates/frontend/src/app/(dashboard)/profile/page.tsx +226 -0
- package/templates/frontend/src/app/(dashboard)/settings/page.tsx +156 -0
- package/templates/frontend/src/app/(dashboard)/team/page.tsx +178 -0
- package/templates/frontend/src/app/(legal)/privacy/page.tsx +127 -0
- package/templates/frontend/src/app/(legal)/terms/page.tsx +118 -0
- package/templates/frontend/src/app/page.tsx +43 -3
- package/templates/frontend/src/app/setup/page.tsx +1 -4
- package/templates/frontend/src/components/dashboard/header.tsx +89 -0
- package/templates/frontend/src/components/dashboard/sidebar.tsx +71 -0
- package/templates/frontend/src/components/dashboard/stat-card.tsx +34 -0
- package/templates/frontend/src/components/landing/faq.tsx +39 -0
- package/templates/frontend/src/components/landing/features.tsx +54 -0
- package/templates/frontend/src/components/landing/footer.tsx +76 -0
- package/templates/frontend/src/components/landing/hero.tsx +72 -0
- package/templates/frontend/src/components/landing/navbar.tsx +78 -0
- package/templates/frontend/src/components/landing/pricing.tsx +90 -0
- package/templates/frontend/src/components/ui/accordion.tsx +52 -0
- package/templates/frontend/src/components/ui/avatar.tsx +46 -0
- package/templates/frontend/src/components/ui/badge.tsx +30 -0
- package/templates/frontend/src/components/ui/checkbox.tsx +27 -0
- package/templates/frontend/src/components/ui/dialog.tsx +100 -0
- package/templates/frontend/src/components/ui/dropdown-menu.tsx +173 -0
- package/templates/frontend/src/components/ui/separator.tsx +25 -0
- package/templates/frontend/src/components/ui/skeleton.tsx +7 -0
- package/templates/frontend/src/components/ui/switch.tsx +28 -0
- package/templates/frontend/src/components/ui/tabs.tsx +54 -0
- package/templates/frontend/src/components/ui/textarea.tsx +20 -0
- package/templates/frontend/src/config/site.ts +197 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
|
5
|
+
import { Switch } from '@/components/ui/switch'
|
|
6
|
+
import { Separator } from '@/components/ui/separator'
|
|
7
|
+
import { Button } from '@/components/ui/button'
|
|
8
|
+
import { Label } from '@/components/ui/label'
|
|
9
|
+
import { Badge } from '@/components/ui/badge'
|
|
10
|
+
|
|
11
|
+
interface NotificationSettings {
|
|
12
|
+
emailMarketing: boolean
|
|
13
|
+
emailProduct: boolean
|
|
14
|
+
emailSecurity: boolean
|
|
15
|
+
pushAll: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface WorkspaceSettings {
|
|
19
|
+
publicProfile: boolean
|
|
20
|
+
allowInvites: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default function SettingsPage() {
|
|
24
|
+
const [notifications, setNotifications] = useState<NotificationSettings>({
|
|
25
|
+
emailMarketing: false,
|
|
26
|
+
emailProduct: true,
|
|
27
|
+
emailSecurity: true,
|
|
28
|
+
pushAll: false,
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const [workspace, setWorkspace] = useState<WorkspaceSettings>({
|
|
32
|
+
publicProfile: false,
|
|
33
|
+
allowInvites: true,
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const [saved, setSaved] = useState(false)
|
|
37
|
+
|
|
38
|
+
function toggleNotification(key: keyof NotificationSettings) {
|
|
39
|
+
setNotifications((prev) => ({ ...prev, [key]: !prev[key] }))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function toggleWorkspace(key: keyof WorkspaceSettings) {
|
|
43
|
+
setWorkspace((prev) => ({ ...prev, [key]: !prev[key] }))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function handleSave() {
|
|
47
|
+
// TODO: llamar a PATCH /users/me/settings con notifications y workspace
|
|
48
|
+
console.log('Guardar ajustes:', { notifications, workspace })
|
|
49
|
+
setSaved(true)
|
|
50
|
+
setTimeout(() => setSaved(false), 2000)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div className="max-w-2xl space-y-6">
|
|
55
|
+
{/* Notificaciones */}
|
|
56
|
+
<Card>
|
|
57
|
+
<CardHeader>
|
|
58
|
+
<CardTitle>Notificaciones por email</CardTitle>
|
|
59
|
+
<CardDescription>Elegí qué correos querés recibir de nuestra parte.</CardDescription>
|
|
60
|
+
</CardHeader>
|
|
61
|
+
<CardContent className="space-y-4">
|
|
62
|
+
{[
|
|
63
|
+
{
|
|
64
|
+
key: 'emailSecurity' as const,
|
|
65
|
+
label: 'Seguridad',
|
|
66
|
+
description: 'Alertas de inicio de sesión y cambios de contraseña.',
|
|
67
|
+
badge: 'Recomendado',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
key: 'emailProduct' as const,
|
|
71
|
+
label: 'Actualizaciones del producto',
|
|
72
|
+
description: 'Nuevas funcionalidades, mejoras y fixes importantes.',
|
|
73
|
+
badge: null,
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
key: 'emailMarketing' as const,
|
|
77
|
+
label: 'Marketing y promociones',
|
|
78
|
+
description: 'Ofertas, descuentos y contenido educativo.',
|
|
79
|
+
badge: null,
|
|
80
|
+
},
|
|
81
|
+
].map(({ key, label, description, badge }) => (
|
|
82
|
+
<div key={key} className="flex items-start justify-between gap-4">
|
|
83
|
+
<div className="space-y-0.5">
|
|
84
|
+
<div className="flex items-center gap-2">
|
|
85
|
+
<Label className="text-sm font-medium">{label}</Label>
|
|
86
|
+
{badge && <Badge variant="secondary" className="text-xs">{badge}</Badge>}
|
|
87
|
+
</div>
|
|
88
|
+
<p className="text-xs text-muted-foreground">{description}</p>
|
|
89
|
+
</div>
|
|
90
|
+
<Switch
|
|
91
|
+
checked={notifications[key]}
|
|
92
|
+
onCheckedChange={() => toggleNotification(key)}
|
|
93
|
+
/>
|
|
94
|
+
</div>
|
|
95
|
+
))}
|
|
96
|
+
</CardContent>
|
|
97
|
+
</Card>
|
|
98
|
+
|
|
99
|
+
{/* Workspace */}
|
|
100
|
+
<Card>
|
|
101
|
+
<CardHeader>
|
|
102
|
+
<CardTitle>Workspace</CardTitle>
|
|
103
|
+
<CardDescription>Configuración de tu espacio de trabajo.</CardDescription>
|
|
104
|
+
</CardHeader>
|
|
105
|
+
<CardContent className="space-y-4">
|
|
106
|
+
<div className="flex items-start justify-between gap-4">
|
|
107
|
+
<div className="space-y-0.5">
|
|
108
|
+
<Label className="text-sm font-medium">Perfil público</Label>
|
|
109
|
+
<p className="text-xs text-muted-foreground">
|
|
110
|
+
Permite que otros usuarios encuentren tu perfil.
|
|
111
|
+
</p>
|
|
112
|
+
</div>
|
|
113
|
+
<Switch
|
|
114
|
+
checked={workspace.publicProfile}
|
|
115
|
+
onCheckedChange={() => toggleWorkspace('publicProfile')}
|
|
116
|
+
/>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<Separator />
|
|
120
|
+
|
|
121
|
+
<div className="flex items-start justify-between gap-4">
|
|
122
|
+
<div className="space-y-0.5">
|
|
123
|
+
<Label className="text-sm font-medium">Invitaciones de equipo</Label>
|
|
124
|
+
<p className="text-xs text-muted-foreground">
|
|
125
|
+
Permite que otros te inviten a sus workspaces.
|
|
126
|
+
</p>
|
|
127
|
+
</div>
|
|
128
|
+
<Switch
|
|
129
|
+
checked={workspace.allowInvites}
|
|
130
|
+
onCheckedChange={() => toggleWorkspace('allowInvites')}
|
|
131
|
+
/>
|
|
132
|
+
</div>
|
|
133
|
+
</CardContent>
|
|
134
|
+
</Card>
|
|
135
|
+
|
|
136
|
+
{/* Danger zone */}
|
|
137
|
+
<Card className="border-destructive/40">
|
|
138
|
+
<CardHeader>
|
|
139
|
+
<CardTitle className="text-destructive">Zona peligrosa</CardTitle>
|
|
140
|
+
<CardDescription>Acciones irreversibles sobre tu cuenta.</CardDescription>
|
|
141
|
+
</CardHeader>
|
|
142
|
+
<CardContent className="space-y-3">
|
|
143
|
+
<div className="flex items-center justify-between">
|
|
144
|
+
<div>
|
|
145
|
+
<p className="text-sm font-medium">Exportar mis datos</p>
|
|
146
|
+
<p className="text-xs text-muted-foreground">Descargá toda tu información en formato JSON.</p>
|
|
147
|
+
</div>
|
|
148
|
+
<Button variant="outline" size="sm" disabled>Exportar</Button>
|
|
149
|
+
</div>
|
|
150
|
+
</CardContent>
|
|
151
|
+
</Card>
|
|
152
|
+
|
|
153
|
+
<Button onClick={handleSave}>{saved ? 'Guardado' : 'Guardar ajustes'}</Button>
|
|
154
|
+
</div>
|
|
155
|
+
)
|
|
156
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import { useForm } from 'react-hook-form'
|
|
5
|
+
import { UserPlus, MoreHorizontal, Crown, User } from 'lucide-react'
|
|
6
|
+
import { useAuth } from '@/lib/hooks/use-auth'
|
|
7
|
+
import { Button } from '@/components/ui/button'
|
|
8
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
|
9
|
+
import { Avatar, AvatarFallback } from '@/components/ui/avatar'
|
|
10
|
+
import { Badge } from '@/components/ui/badge'
|
|
11
|
+
import { Input } from '@/components/ui/input'
|
|
12
|
+
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
|
|
13
|
+
import {
|
|
14
|
+
Dialog,
|
|
15
|
+
DialogContent,
|
|
16
|
+
DialogDescription,
|
|
17
|
+
DialogFooter,
|
|
18
|
+
DialogHeader,
|
|
19
|
+
DialogTitle,
|
|
20
|
+
DialogTrigger,
|
|
21
|
+
} from '@/components/ui/dialog'
|
|
22
|
+
import {
|
|
23
|
+
DropdownMenu,
|
|
24
|
+
DropdownMenuContent,
|
|
25
|
+
DropdownMenuItem,
|
|
26
|
+
DropdownMenuTrigger,
|
|
27
|
+
} from '@/components/ui/dropdown-menu'
|
|
28
|
+
|
|
29
|
+
function getInitials(name: string) {
|
|
30
|
+
return name.split(' ').map((n) => n[0]).slice(0, 2).join('').toUpperCase()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface InviteForm {
|
|
34
|
+
email: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default function TeamPage() {
|
|
38
|
+
const { user } = useAuth()
|
|
39
|
+
const [inviteOpen, setInviteOpen] = useState(false)
|
|
40
|
+
const [invited, setInvited] = useState(false)
|
|
41
|
+
|
|
42
|
+
const members = [
|
|
43
|
+
{ name: user?.name ?? 'Vos', email: user?.email ?? '', role: 'owner' as const },
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
const form = useForm<InviteForm>({ defaultValues: { email: '' } })
|
|
47
|
+
|
|
48
|
+
async function onInvite(data: InviteForm) {
|
|
49
|
+
// TODO: llamar a POST /workspaces/invite con data.email
|
|
50
|
+
console.log('Invitar:', data.email)
|
|
51
|
+
setInvited(true)
|
|
52
|
+
setTimeout(() => {
|
|
53
|
+
setInvited(false)
|
|
54
|
+
setInviteOpen(false)
|
|
55
|
+
form.reset()
|
|
56
|
+
}, 1500)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div className="max-w-2xl space-y-6">
|
|
61
|
+
<Card>
|
|
62
|
+
<CardHeader>
|
|
63
|
+
<div className="flex items-start justify-between">
|
|
64
|
+
<div>
|
|
65
|
+
<CardTitle>Miembros del equipo</CardTitle>
|
|
66
|
+
<CardDescription>
|
|
67
|
+
Gestioná quién tiene acceso a tu workspace.
|
|
68
|
+
</CardDescription>
|
|
69
|
+
</div>
|
|
70
|
+
<Dialog open={inviteOpen} onOpenChange={setInviteOpen}>
|
|
71
|
+
<DialogTrigger asChild>
|
|
72
|
+
<Button size="sm" className="gap-2">
|
|
73
|
+
<UserPlus className="size-4" />
|
|
74
|
+
Invitar
|
|
75
|
+
</Button>
|
|
76
|
+
</DialogTrigger>
|
|
77
|
+
<DialogContent>
|
|
78
|
+
<DialogHeader>
|
|
79
|
+
<DialogTitle>Invitar miembro</DialogTitle>
|
|
80
|
+
<DialogDescription>
|
|
81
|
+
Ingresá el email de la persona que querés agregar al workspace.
|
|
82
|
+
</DialogDescription>
|
|
83
|
+
</DialogHeader>
|
|
84
|
+
<Form {...form}>
|
|
85
|
+
<form onSubmit={form.handleSubmit(onInvite)} className="space-y-4">
|
|
86
|
+
<FormField
|
|
87
|
+
control={form.control}
|
|
88
|
+
name="email"
|
|
89
|
+
rules={{
|
|
90
|
+
required: 'El email es requerido',
|
|
91
|
+
pattern: { value: /\S+@\S+\.\S+/, message: 'Email inválido' },
|
|
92
|
+
}}
|
|
93
|
+
render={({ field }) => (
|
|
94
|
+
<FormItem>
|
|
95
|
+
<FormLabel>Email</FormLabel>
|
|
96
|
+
<FormControl>
|
|
97
|
+
<Input type="email" placeholder="colaborador@empresa.com" {...field} />
|
|
98
|
+
</FormControl>
|
|
99
|
+
<FormMessage />
|
|
100
|
+
</FormItem>
|
|
101
|
+
)}
|
|
102
|
+
/>
|
|
103
|
+
<DialogFooter>
|
|
104
|
+
<Button type="button" variant="ghost" onClick={() => setInviteOpen(false)}>
|
|
105
|
+
Cancelar
|
|
106
|
+
</Button>
|
|
107
|
+
<Button type="submit" disabled={form.formState.isSubmitting}>
|
|
108
|
+
{invited ? 'Invitación enviada' : 'Enviar invitación'}
|
|
109
|
+
</Button>
|
|
110
|
+
</DialogFooter>
|
|
111
|
+
</form>
|
|
112
|
+
</Form>
|
|
113
|
+
</DialogContent>
|
|
114
|
+
</Dialog>
|
|
115
|
+
</div>
|
|
116
|
+
</CardHeader>
|
|
117
|
+
<CardContent>
|
|
118
|
+
<div className="space-y-3">
|
|
119
|
+
{members.map((member) => (
|
|
120
|
+
<div
|
|
121
|
+
key={member.email}
|
|
122
|
+
className="flex items-center justify-between rounded-lg border p-3"
|
|
123
|
+
>
|
|
124
|
+
<div className="flex items-center gap-3">
|
|
125
|
+
<Avatar className="size-8">
|
|
126
|
+
<AvatarFallback className="text-xs">{getInitials(member.name)}</AvatarFallback>
|
|
127
|
+
</Avatar>
|
|
128
|
+
<div>
|
|
129
|
+
<p className="text-sm font-medium">{member.name}</p>
|
|
130
|
+
<p className="text-xs text-muted-foreground">{member.email}</p>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
<div className="flex items-center gap-2">
|
|
134
|
+
<Badge variant={member.role === 'owner' ? 'default' : 'secondary'} className="gap-1 text-xs">
|
|
135
|
+
{member.role === 'owner' ? (
|
|
136
|
+
<><Crown className="size-3" /> Owner</>
|
|
137
|
+
) : (
|
|
138
|
+
<><User className="size-3" /> Miembro</>
|
|
139
|
+
)}
|
|
140
|
+
</Badge>
|
|
141
|
+
{member.role !== 'owner' && (
|
|
142
|
+
<DropdownMenu>
|
|
143
|
+
<DropdownMenuTrigger asChild>
|
|
144
|
+
<Button variant="ghost" size="sm" className="size-7 p-0">
|
|
145
|
+
<MoreHorizontal className="size-4" />
|
|
146
|
+
</Button>
|
|
147
|
+
</DropdownMenuTrigger>
|
|
148
|
+
<DropdownMenuContent align="end">
|
|
149
|
+
<DropdownMenuItem className="text-destructive focus:text-destructive">
|
|
150
|
+
Eliminar miembro
|
|
151
|
+
</DropdownMenuItem>
|
|
152
|
+
</DropdownMenuContent>
|
|
153
|
+
</DropdownMenu>
|
|
154
|
+
)}
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
))}
|
|
158
|
+
</div>
|
|
159
|
+
|
|
160
|
+
<p className="mt-4 text-center text-xs text-muted-foreground">
|
|
161
|
+
Plan Free · 1 de 3 usuarios
|
|
162
|
+
</p>
|
|
163
|
+
</CardContent>
|
|
164
|
+
</Card>
|
|
165
|
+
|
|
166
|
+
{/* Pending invitations placeholder */}
|
|
167
|
+
<Card>
|
|
168
|
+
<CardHeader>
|
|
169
|
+
<CardTitle>Invitaciones pendientes</CardTitle>
|
|
170
|
+
<CardDescription>Invitaciones enviadas que aún no fueron aceptadas.</CardDescription>
|
|
171
|
+
</CardHeader>
|
|
172
|
+
<CardContent>
|
|
173
|
+
<p className="text-sm text-muted-foreground">No hay invitaciones pendientes.</p>
|
|
174
|
+
</CardContent>
|
|
175
|
+
</Card>
|
|
176
|
+
</div>
|
|
177
|
+
)
|
|
178
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import Link from 'next/link'
|
|
2
|
+
import { ArrowLeft } from 'lucide-react'
|
|
3
|
+
import { siteConfig } from '@/config/site'
|
|
4
|
+
|
|
5
|
+
export default function PrivacyPage() {
|
|
6
|
+
const { legal } = siteConfig
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<div className="min-h-screen bg-background">
|
|
10
|
+
<div className="mx-auto max-w-3xl px-4 py-12">
|
|
11
|
+
<Link
|
|
12
|
+
href="/"
|
|
13
|
+
className="mb-8 inline-flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground"
|
|
14
|
+
>
|
|
15
|
+
<ArrowLeft className="size-4" />
|
|
16
|
+
Volver al inicio
|
|
17
|
+
</Link>
|
|
18
|
+
|
|
19
|
+
<h1 className="text-3xl font-bold">Política de Privacidad</h1>
|
|
20
|
+
<p className="mt-2 text-sm text-muted-foreground">
|
|
21
|
+
Última actualización: {legal.lastUpdated}
|
|
22
|
+
</p>
|
|
23
|
+
|
|
24
|
+
<div className="mt-10 space-y-8 text-sm leading-relaxed">
|
|
25
|
+
<section>
|
|
26
|
+
<h2 className="text-lg font-semibold">1. Información que recopilamos</h2>
|
|
27
|
+
<p className="mt-3 text-muted-foreground">
|
|
28
|
+
Recopilamos información que nos proporcionás directamente (nombre, email, contraseña)
|
|
29
|
+
al crear una cuenta, así como datos de uso generados al interactuar con la Plataforma
|
|
30
|
+
(logs de acceso, funcionalidades utilizadas, configuraciones).
|
|
31
|
+
</p>
|
|
32
|
+
</section>
|
|
33
|
+
|
|
34
|
+
<section>
|
|
35
|
+
<h2 className="text-lg font-semibold">2. Cómo usamos tu información</h2>
|
|
36
|
+
<ul className="mt-3 list-inside list-disc space-y-2 text-muted-foreground">
|
|
37
|
+
<li>Proveer, mantener y mejorar el servicio.</li>
|
|
38
|
+
<li>Enviarte comunicaciones transaccionales (confirmaciones, alertas de seguridad).</li>
|
|
39
|
+
<li>Responder consultas y brindar soporte técnico.</li>
|
|
40
|
+
<li>Cumplir obligaciones legales aplicables.</li>
|
|
41
|
+
<li>Prevenir fraude y garantizar la seguridad de la plataforma.</li>
|
|
42
|
+
</ul>
|
|
43
|
+
</section>
|
|
44
|
+
|
|
45
|
+
<section>
|
|
46
|
+
<h2 className="text-lg font-semibold">3. Compartir información</h2>
|
|
47
|
+
<p className="mt-3 text-muted-foreground">
|
|
48
|
+
No vendemos ni compartimos tu información personal con terceros, salvo que sea
|
|
49
|
+
necesario para operar el servicio (proveedores de infraestructura, procesadores de
|
|
50
|
+
pago) o cuando lo exija la ley. Todos los subprocesadores están obligados
|
|
51
|
+
contractualmente a proteger tus datos.
|
|
52
|
+
</p>
|
|
53
|
+
</section>
|
|
54
|
+
|
|
55
|
+
<section>
|
|
56
|
+
<h2 className="text-lg font-semibold">4. Seguridad de los datos</h2>
|
|
57
|
+
<p className="mt-3 text-muted-foreground">
|
|
58
|
+
Implementamos medidas técnicas y organizativas razonables para proteger tu información,
|
|
59
|
+
incluyendo encriptación en tránsito (TLS) y en reposo. Sin embargo, ningún sistema
|
|
60
|
+
es 100% seguro; te recomendamos usar contraseñas únicas y fuertes.
|
|
61
|
+
</p>
|
|
62
|
+
</section>
|
|
63
|
+
|
|
64
|
+
<section>
|
|
65
|
+
<h2 className="text-lg font-semibold">5. Retención de datos</h2>
|
|
66
|
+
<p className="mt-3 text-muted-foreground">
|
|
67
|
+
Conservamos tu información mientras tu cuenta esté activa o según sea necesario para
|
|
68
|
+
los fines descritos. Si eliminás tu cuenta, procederemos a borrar tus datos dentro
|
|
69
|
+
de los 30 días hábiles posteriores, salvo que la ley exija conservarlos por más tiempo.
|
|
70
|
+
</p>
|
|
71
|
+
</section>
|
|
72
|
+
|
|
73
|
+
<section>
|
|
74
|
+
<h2 className="text-lg font-semibold">6. Tus derechos</h2>
|
|
75
|
+
<p className="mt-3 text-muted-foreground">
|
|
76
|
+
Tenés derecho a acceder, rectificar, eliminar o portar tus datos personales. También
|
|
77
|
+
podés oponerte o solicitar la limitación del tratamiento. Para ejercer estos derechos,
|
|
78
|
+
contactanos en{' '}
|
|
79
|
+
<a
|
|
80
|
+
href={`mailto:${legal.email}`}
|
|
81
|
+
className="underline underline-offset-4 hover:text-foreground"
|
|
82
|
+
>
|
|
83
|
+
{legal.email}
|
|
84
|
+
</a>
|
|
85
|
+
.
|
|
86
|
+
</p>
|
|
87
|
+
</section>
|
|
88
|
+
|
|
89
|
+
<section>
|
|
90
|
+
<h2 className="text-lg font-semibold">7. Cookies</h2>
|
|
91
|
+
<p className="mt-3 text-muted-foreground">
|
|
92
|
+
Utilizamos cookies estrictamente necesarias para el funcionamiento de la sesión.
|
|
93
|
+
No utilizamos cookies de tracking de terceros ni publicidad comportamental.
|
|
94
|
+
</p>
|
|
95
|
+
</section>
|
|
96
|
+
|
|
97
|
+
<section>
|
|
98
|
+
<h2 className="text-lg font-semibold">8. Cambios en esta política</h2>
|
|
99
|
+
<p className="mt-3 text-muted-foreground">
|
|
100
|
+
Podemos actualizar esta Política periódicamente. Te notificaremos por email ante
|
|
101
|
+
cambios significativos. La fecha de última actualización siempre estará visible
|
|
102
|
+
al inicio de este documento.
|
|
103
|
+
</p>
|
|
104
|
+
</section>
|
|
105
|
+
|
|
106
|
+
<section>
|
|
107
|
+
<h2 className="text-lg font-semibold">9. Contacto</h2>
|
|
108
|
+
<p className="mt-3 text-muted-foreground">
|
|
109
|
+
Para cualquier consulta sobre privacidad escribinos a{' '}
|
|
110
|
+
<a
|
|
111
|
+
href={`mailto:${legal.email}`}
|
|
112
|
+
className="underline underline-offset-4 hover:text-foreground"
|
|
113
|
+
>
|
|
114
|
+
{legal.email}
|
|
115
|
+
</a>
|
|
116
|
+
. También podés consultar nuestros{' '}
|
|
117
|
+
<Link href="/terms" className="underline underline-offset-4 hover:text-foreground">
|
|
118
|
+
Términos y Condiciones
|
|
119
|
+
</Link>
|
|
120
|
+
.
|
|
121
|
+
</p>
|
|
122
|
+
</section>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
)
|
|
127
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import Link from 'next/link'
|
|
2
|
+
import { ArrowLeft } from 'lucide-react'
|
|
3
|
+
import { siteConfig } from '@/config/site'
|
|
4
|
+
|
|
5
|
+
export default function TermsPage() {
|
|
6
|
+
const { legal } = siteConfig
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<div className="min-h-screen bg-background">
|
|
10
|
+
<div className="mx-auto max-w-3xl px-4 py-12">
|
|
11
|
+
<Link
|
|
12
|
+
href="/"
|
|
13
|
+
className="mb-8 inline-flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground"
|
|
14
|
+
>
|
|
15
|
+
<ArrowLeft className="size-4" />
|
|
16
|
+
Volver al inicio
|
|
17
|
+
</Link>
|
|
18
|
+
|
|
19
|
+
<h1 className="text-3xl font-bold">Términos y Condiciones</h1>
|
|
20
|
+
<p className="mt-2 text-sm text-muted-foreground">
|
|
21
|
+
Última actualización: {legal.lastUpdated}
|
|
22
|
+
</p>
|
|
23
|
+
|
|
24
|
+
<div className="prose prose-neutral dark:prose-invert mt-10 max-w-none space-y-8 text-sm leading-relaxed text-foreground">
|
|
25
|
+
<section>
|
|
26
|
+
<h2 className="text-lg font-semibold">1. Aceptación de los términos</h2>
|
|
27
|
+
<p className="mt-3 text-muted-foreground">
|
|
28
|
+
Al acceder y utilizar los servicios de {legal.companyName} ("la Plataforma"),
|
|
29
|
+
aceptás quedar vinculado por estos Términos y Condiciones. Si no estás de acuerdo,
|
|
30
|
+
no utilices la Plataforma.
|
|
31
|
+
</p>
|
|
32
|
+
</section>
|
|
33
|
+
|
|
34
|
+
<section>
|
|
35
|
+
<h2 className="text-lg font-semibold">2. Descripción del servicio</h2>
|
|
36
|
+
<p className="mt-3 text-muted-foreground">
|
|
37
|
+
{legal.companyName} provee una plataforma de software como servicio (SaaS) accesible
|
|
38
|
+
a través de la web. Nos reservamos el derecho de modificar, suspender o discontinuar
|
|
39
|
+
cualquier aspecto del servicio en cualquier momento.
|
|
40
|
+
</p>
|
|
41
|
+
</section>
|
|
42
|
+
|
|
43
|
+
<section>
|
|
44
|
+
<h2 className="text-lg font-semibold">3. Registro y cuenta</h2>
|
|
45
|
+
<p className="mt-3 text-muted-foreground">
|
|
46
|
+
Para acceder a ciertas funcionalidades debés crear una cuenta. Sos responsable de
|
|
47
|
+
mantener la confidencialidad de tus credenciales y de todas las actividades que
|
|
48
|
+
ocurran bajo tu cuenta. Notificanos inmediatamente ante cualquier uso no autorizado.
|
|
49
|
+
</p>
|
|
50
|
+
</section>
|
|
51
|
+
|
|
52
|
+
<section>
|
|
53
|
+
<h2 className="text-lg font-semibold">4. Facturación y pagos</h2>
|
|
54
|
+
<p className="mt-3 text-muted-foreground">
|
|
55
|
+
Los planes de pago se facturan de forma recurrente (mensual o anual) según el plan
|
|
56
|
+
elegido. Los precios están sujetos a cambios con previo aviso de 30 días. Las
|
|
57
|
+
cancelaciones surten efecto al final del período facturado.
|
|
58
|
+
</p>
|
|
59
|
+
</section>
|
|
60
|
+
|
|
61
|
+
<section>
|
|
62
|
+
<h2 className="text-lg font-semibold">5. Propiedad intelectual</h2>
|
|
63
|
+
<p className="mt-3 text-muted-foreground">
|
|
64
|
+
Todo el contenido de la Plataforma (código, diseño, logos, textos) es propiedad
|
|
65
|
+
exclusiva de {legal.companyName} o sus licenciantes. No podés copiar, modificar ni
|
|
66
|
+
distribuir ningún elemento sin autorización expresa y escrita.
|
|
67
|
+
</p>
|
|
68
|
+
</section>
|
|
69
|
+
|
|
70
|
+
<section>
|
|
71
|
+
<h2 className="text-lg font-semibold">6. Datos del usuario</h2>
|
|
72
|
+
<p className="mt-3 text-muted-foreground">
|
|
73
|
+
Sos propietario de los datos que cargás en la Plataforma. Nos otorgás una licencia
|
|
74
|
+
limitada para procesarlos únicamente con el fin de prestar el servicio. Consultá
|
|
75
|
+
nuestra{' '}
|
|
76
|
+
<Link href="/privacy" className="underline underline-offset-4 hover:text-foreground">
|
|
77
|
+
Política de Privacidad
|
|
78
|
+
</Link>{' '}
|
|
79
|
+
para más detalles.
|
|
80
|
+
</p>
|
|
81
|
+
</section>
|
|
82
|
+
|
|
83
|
+
<section>
|
|
84
|
+
<h2 className="text-lg font-semibold">7. Limitación de responsabilidad</h2>
|
|
85
|
+
<p className="mt-3 text-muted-foreground">
|
|
86
|
+
En la máxima medida permitida por la ley, {legal.companyName} no será responsable
|
|
87
|
+
por daños indirectos, incidentales, especiales o consecuentes derivados del uso o
|
|
88
|
+
imposibilidad de uso de la Plataforma.
|
|
89
|
+
</p>
|
|
90
|
+
</section>
|
|
91
|
+
|
|
92
|
+
<section>
|
|
93
|
+
<h2 className="text-lg font-semibold">8. Modificaciones</h2>
|
|
94
|
+
<p className="mt-3 text-muted-foreground">
|
|
95
|
+
Podemos actualizar estos Términos periódicamente. Te notificaremos por email ante
|
|
96
|
+
cambios materiales. El uso continuado de la Plataforma constituye aceptación de los
|
|
97
|
+
nuevos términos.
|
|
98
|
+
</p>
|
|
99
|
+
</section>
|
|
100
|
+
|
|
101
|
+
<section>
|
|
102
|
+
<h2 className="text-lg font-semibold">9. Contacto</h2>
|
|
103
|
+
<p className="mt-3 text-muted-foreground">
|
|
104
|
+
Para consultas sobre estos Términos escribinos a{' '}
|
|
105
|
+
<a
|
|
106
|
+
href={`mailto:${legal.email}`}
|
|
107
|
+
className="underline underline-offset-4 hover:text-foreground"
|
|
108
|
+
>
|
|
109
|
+
{legal.email}
|
|
110
|
+
</a>
|
|
111
|
+
.
|
|
112
|
+
</p>
|
|
113
|
+
</section>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
)
|
|
118
|
+
}
|
|
@@ -1,5 +1,45 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LandingNavbar } from '@/components/landing/navbar'
|
|
2
|
+
import { LandingHero } from '@/components/landing/hero'
|
|
3
|
+
import { LandingFeatures } from '@/components/landing/features'
|
|
4
|
+
import { LandingPricing } from '@/components/landing/pricing'
|
|
5
|
+
import { LandingFaq } from '@/components/landing/faq'
|
|
6
|
+
import { LandingFooter } from '@/components/landing/footer'
|
|
7
|
+
import { siteConfig } from '@/config/site'
|
|
8
|
+
import Link from 'next/link'
|
|
9
|
+
import { Button } from '@/components/ui/button'
|
|
2
10
|
|
|
3
|
-
export default function
|
|
4
|
-
|
|
11
|
+
export default function LandingPage() {
|
|
12
|
+
return (
|
|
13
|
+
<div className="flex min-h-screen flex-col">
|
|
14
|
+
<LandingNavbar />
|
|
15
|
+
<main className="flex-1">
|
|
16
|
+
<LandingHero />
|
|
17
|
+
<LandingFeatures />
|
|
18
|
+
<LandingPricing />
|
|
19
|
+
|
|
20
|
+
{/* CTA final */}
|
|
21
|
+
<section className="py-20 md:py-28">
|
|
22
|
+
<div className="mx-auto max-w-3xl px-4 text-center">
|
|
23
|
+
<h2 className="text-3xl font-bold tracking-tight md:text-4xl">
|
|
24
|
+
¿Listo para empezar?
|
|
25
|
+
</h2>
|
|
26
|
+
<p className="mt-4 text-muted-foreground">
|
|
27
|
+
Creá tu cuenta gratis y tené tu SaaS funcionando en minutos.
|
|
28
|
+
</p>
|
|
29
|
+
<div className="mt-8 flex flex-col items-center justify-center gap-4 sm:flex-row">
|
|
30
|
+
<Button size="lg" asChild>
|
|
31
|
+
<Link href="/register">{siteConfig.hero.cta.label}</Link>
|
|
32
|
+
</Button>
|
|
33
|
+
<Button size="lg" variant="outline" asChild>
|
|
34
|
+
<Link href="/login">Iniciar sesión</Link>
|
|
35
|
+
</Button>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</section>
|
|
39
|
+
|
|
40
|
+
<LandingFaq />
|
|
41
|
+
</main>
|
|
42
|
+
<LandingFooter />
|
|
43
|
+
</div>
|
|
44
|
+
)
|
|
5
45
|
}
|
|
@@ -118,10 +118,7 @@ export default function SetupPage() {
|
|
|
118
118
|
|
|
119
119
|
useEffect(() => {
|
|
120
120
|
setMounted(true)
|
|
121
|
-
|
|
122
|
-
router.replace('/login')
|
|
123
|
-
}
|
|
124
|
-
}, [router])
|
|
121
|
+
}, [])
|
|
125
122
|
|
|
126
123
|
const handleDone = () => {
|
|
127
124
|
localStorage.setItem('setup_dismissed', 'true')
|