ar-saas 0.3.0 → 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.
- package/LICENSE +21 -21
- package/README.md +338 -314
- package/dist/cli.js +19 -0
- package/dist/generator.js +166 -55
- package/package.json +52 -50
- package/templates/backend/.env.example +67 -67
- package/templates/backend/.prettierrc +4 -4
- package/templates/backend/README.md +249 -168
- package/templates/backend/eslint.config.mjs +35 -35
- package/templates/backend/nest-cli.json +8 -8
- package/templates/backend/package-lock.json +10979 -10979
- package/templates/backend/package.json +88 -88
- package/templates/backend/src/app.controller.spec.ts +24 -24
- package/templates/backend/src/app.controller.ts +15 -15
- package/templates/backend/src/app.module.ts +40 -40
- package/templates/backend/src/app.service.ts +11 -11
- package/templates/backend/src/common/base/base.repository.ts +221 -221
- package/templates/backend/src/common/base/base.schema.ts +24 -24
- package/templates/backend/src/common/decorators/cookie.decorator.ts +9 -9
- package/templates/backend/src/common/decorators/current-user.decorator.ts +20 -20
- package/templates/backend/src/common/decorators/workspace-id.decorator.ts +14 -14
- package/templates/backend/src/common/filters/global-exception.filter.ts +61 -61
- package/templates/backend/src/common/guards/jwt-auth.guard.ts +5 -5
- package/templates/backend/src/common/interceptors/workspace-tenant.interceptor.ts +45 -45
- package/templates/backend/src/main.ts +51 -51
- package/templates/backend/src/modules/auth/auth.controller.ts +158 -158
- package/templates/backend/src/modules/auth/auth.module.ts +20 -20
- package/templates/backend/src/modules/auth/auth.service.ts +257 -257
- package/templates/backend/src/modules/auth/dto/forgot-password.dto.ts +9 -9
- package/templates/backend/src/modules/auth/dto/login.dto.ts +14 -14
- package/templates/backend/src/modules/auth/dto/refresh-token.dto.ts +12 -12
- package/templates/backend/src/modules/auth/dto/register.dto.ts +26 -26
- package/templates/backend/src/modules/auth/dto/reset-password.dto.ts +16 -16
- package/templates/backend/src/modules/auth/dto/verify-email.dto.ts +9 -9
- package/templates/backend/src/modules/auth/strategies/jwt.strategy.ts +43 -43
- package/templates/backend/src/modules/mail/mail.module.ts +9 -9
- package/templates/backend/src/modules/mail/mail.service.ts +141 -141
- package/templates/backend/src/modules/users/schemas/user.schema.ts +54 -54
- package/templates/backend/src/modules/users/users.module.ts +14 -14
- package/templates/backend/src/modules/users/users.repository.ts +51 -51
- package/templates/backend/src/modules/users/users.service.ts +104 -104
- package/templates/backend/src/modules/workspaces/schemas/workspace.schema.ts +26 -26
- package/templates/backend/src/modules/workspaces/workspaces.module.ts +16 -16
- package/templates/backend/src/modules/workspaces/workspaces.repository.ts +34 -34
- package/templates/backend/src/modules/workspaces/workspaces.service.ts +42 -42
- package/templates/backend/test/app.e2e-spec.ts +25 -25
- package/templates/backend/test/jest-e2e.json +9 -9
- package/templates/backend/tsconfig.build.json +4 -4
- package/templates/backend/tsconfig.json +26 -26
- package/templates/frontend/.env.local.example +1 -1
- package/templates/frontend/README.md +152 -0
- package/templates/frontend/components.json +20 -20
- package/templates/frontend/eslint.config.mjs +14 -14
- package/templates/frontend/next.config.ts +5 -5
- package/templates/frontend/package-lock.json +6722 -6722
- package/templates/frontend/package.json +48 -48
- package/templates/frontend/pnpm-lock.yaml +5012 -5012
- package/templates/frontend/pnpm-workspace.yaml +3 -3
- package/templates/frontend/postcss.config.mjs +7 -7
- package/templates/frontend/src/app/(auth)/forgot-password/page.tsx +84 -84
- package/templates/frontend/src/app/(auth)/layout.tsx +28 -28
- package/templates/frontend/src/app/(auth)/login/page.tsx +111 -111
- package/templates/frontend/src/app/(auth)/register/page.tsx +161 -161
- package/templates/frontend/src/app/(auth)/reset-password/page.tsx +120 -120
- package/templates/frontend/src/app/(auth)/verify-email/page.tsx +78 -78
- package/templates/frontend/src/app/(dashboard)/billing/page.tsx +111 -111
- package/templates/frontend/src/app/(dashboard)/dashboard/page.tsx +105 -105
- package/templates/frontend/src/app/(dashboard)/layout.tsx +38 -38
- package/templates/frontend/src/app/(dashboard)/profile/page.tsx +226 -226
- package/templates/frontend/src/app/(dashboard)/settings/page.tsx +156 -156
- package/templates/frontend/src/app/(dashboard)/team/page.tsx +178 -178
- package/templates/frontend/src/app/(legal)/privacy/page.tsx +127 -127
- package/templates/frontend/src/app/(legal)/terms/page.tsx +118 -118
- package/templates/frontend/src/app/globals.css +81 -81
- package/templates/frontend/src/app/layout.tsx +26 -26
- package/templates/frontend/src/app/page.tsx +5 -45
- package/templates/frontend/src/app/setup/page.tsx +371 -275
- package/templates/frontend/src/components/dashboard/header.tsx +89 -89
- package/templates/frontend/src/components/dashboard/sidebar.tsx +71 -71
- package/templates/frontend/src/components/dashboard/stat-card.tsx +34 -34
- package/templates/frontend/src/components/landing/faq.tsx +39 -39
- package/templates/frontend/src/components/landing/features.tsx +54 -54
- package/templates/frontend/src/components/landing/footer.tsx +76 -76
- package/templates/frontend/src/components/landing/hero.tsx +72 -72
- package/templates/frontend/src/components/landing/navbar.tsx +78 -78
- package/templates/frontend/src/components/landing/pricing.tsx +90 -90
- package/templates/frontend/src/components/ui/accordion.tsx +52 -52
- package/templates/frontend/src/components/ui/avatar.tsx +46 -46
- package/templates/frontend/src/components/ui/badge.tsx +30 -30
- package/templates/frontend/src/components/ui/button.tsx +52 -52
- package/templates/frontend/src/components/ui/card.tsx +50 -50
- package/templates/frontend/src/components/ui/checkbox.tsx +27 -27
- package/templates/frontend/src/components/ui/dialog.tsx +100 -100
- package/templates/frontend/src/components/ui/dropdown-menu.tsx +173 -173
- package/templates/frontend/src/components/ui/form.tsx +158 -158
- package/templates/frontend/src/components/ui/input.tsx +21 -21
- package/templates/frontend/src/components/ui/label.tsx +22 -22
- package/templates/frontend/src/components/ui/separator.tsx +25 -25
- package/templates/frontend/src/components/ui/skeleton.tsx +7 -7
- package/templates/frontend/src/components/ui/switch.tsx +28 -28
- package/templates/frontend/src/components/ui/tabs.tsx +54 -54
- package/templates/frontend/src/components/ui/textarea.tsx +20 -20
- package/templates/frontend/src/components/ui/toast.tsx +109 -109
- package/templates/frontend/src/components/ui/toaster.tsx +30 -30
- package/templates/frontend/src/config/site.ts +197 -197
- package/templates/frontend/src/hooks/use-toast.ts +116 -116
- package/templates/frontend/src/lib/api/auth.ts +39 -39
- package/templates/frontend/src/lib/api/client.ts +66 -66
- package/templates/frontend/src/lib/hooks/use-auth.ts +1 -1
- package/templates/frontend/src/lib/utils.ts +6 -6
- package/templates/frontend/src/providers/auth-provider.tsx +60 -60
- package/templates/frontend/src/types/api.ts +12 -12
- package/templates/frontend/src/types/auth.ts +27 -27
- package/templates/frontend/tsconfig.json +23 -23
|
@@ -1,178 +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
|
-
}
|
|
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
|
+
}
|
|
@@ -1,127 +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
|
-
}
|
|
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
|
+
}
|