ar-saas 0.4.3 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/package.json +1 -1
  2. package/templates/backend/.env.example +13 -3
  3. package/templates/backend/README.md +22 -3
  4. package/templates/backend/package-lock.json +165 -2
  5. package/templates/backend/package.json +2 -0
  6. package/templates/backend/src/app.module.ts +14 -0
  7. package/templates/backend/src/common/guards/github-auth.guard.ts +5 -0
  8. package/templates/backend/src/main.ts +2 -2
  9. package/templates/backend/src/modules/auth/auth.controller.ts +51 -3
  10. package/templates/backend/src/modules/auth/auth.module.ts +2 -1
  11. package/templates/backend/src/modules/auth/auth.service.ts +96 -11
  12. package/templates/backend/src/modules/auth/strategies/github.strategy.ts +46 -0
  13. package/templates/backend/src/modules/clients/clients.controller.ts +91 -0
  14. package/templates/backend/src/modules/clients/clients.module.ts +16 -0
  15. package/templates/backend/src/modules/clients/clients.repository.ts +14 -0
  16. package/templates/backend/src/modules/clients/clients.service.ts +52 -0
  17. package/templates/backend/src/modules/clients/dto/create-client.dto.ts +40 -0
  18. package/templates/backend/src/modules/clients/dto/query-client.dto.ts +30 -0
  19. package/templates/backend/src/modules/clients/dto/update-client.dto.ts +4 -0
  20. package/templates/backend/src/modules/clients/schemas/client.schema.ts +32 -0
  21. package/templates/backend/src/modules/invoices/dto/create-invoice.dto.ts +79 -0
  22. package/templates/backend/src/modules/invoices/dto/invoice-item.dto.ts +23 -0
  23. package/templates/backend/src/modules/invoices/dto/query-invoice.dto.ts +40 -0
  24. package/templates/backend/src/modules/invoices/dto/update-invoice.dto.ts +4 -0
  25. package/templates/backend/src/modules/invoices/invoices.controller.ts +91 -0
  26. package/templates/backend/src/modules/invoices/invoices.module.ts +18 -0
  27. package/templates/backend/src/modules/invoices/invoices.repository.ts +14 -0
  28. package/templates/backend/src/modules/invoices/invoices.service.ts +104 -0
  29. package/templates/backend/src/modules/invoices/schemas/invoice.schema.ts +75 -0
  30. package/templates/backend/src/modules/notifications/dto/create-notification.dto.ts +45 -0
  31. package/templates/backend/src/modules/notifications/dto/query-notification.dto.ts +30 -0
  32. package/templates/backend/src/modules/notifications/dto/update-notification.dto.ts +4 -0
  33. package/templates/backend/src/modules/notifications/notifications.controller.ts +119 -0
  34. package/templates/backend/src/modules/notifications/notifications.module.ts +16 -0
  35. package/templates/backend/src/modules/notifications/notifications.repository.ts +31 -0
  36. package/templates/backend/src/modules/notifications/notifications.service.ts +64 -0
  37. package/templates/backend/src/modules/notifications/schemas/notification.schema.ts +38 -0
  38. package/templates/backend/src/modules/pipeline/dto/create-deal.dto.ts +40 -0
  39. package/templates/backend/src/modules/pipeline/dto/query-deal.dto.ts +35 -0
  40. package/templates/backend/src/modules/pipeline/dto/update-deal.dto.ts +4 -0
  41. package/templates/backend/src/modules/pipeline/pipeline.controller.ts +91 -0
  42. package/templates/backend/src/modules/pipeline/pipeline.module.ts +18 -0
  43. package/templates/backend/src/modules/pipeline/pipeline.repository.ts +14 -0
  44. package/templates/backend/src/modules/pipeline/pipeline.service.ts +64 -0
  45. package/templates/backend/src/modules/pipeline/schemas/deal.schema.ts +39 -0
  46. package/templates/backend/src/modules/planner/dto/create-planner-block.dto.ts +66 -0
  47. package/templates/backend/src/modules/planner/dto/query-planner-block.dto.ts +48 -0
  48. package/templates/backend/src/modules/planner/dto/update-block-status.dto.ts +10 -0
  49. package/templates/backend/src/modules/planner/dto/update-planner-block.dto.ts +4 -0
  50. package/templates/backend/src/modules/planner/planner.controller.ts +124 -0
  51. package/templates/backend/src/modules/planner/planner.module.ts +16 -0
  52. package/templates/backend/src/modules/planner/planner.repository.ts +45 -0
  53. package/templates/backend/src/modules/planner/planner.service.ts +104 -0
  54. package/templates/backend/src/modules/planner/schemas/planner-block.schema.ts +56 -0
  55. package/templates/backend/src/modules/task-columns/dto/create-task-column.dto.ts +20 -0
  56. package/templates/backend/src/modules/task-columns/dto/reorder-columns.dto.ts +9 -0
  57. package/templates/backend/src/modules/task-columns/dto/update-task-column.dto.ts +4 -0
  58. package/templates/backend/src/modules/task-columns/schemas/task-column.schema.ts +21 -0
  59. package/templates/backend/src/modules/task-columns/task-columns.controller.ts +86 -0
  60. package/templates/backend/src/modules/task-columns/task-columns.module.ts +16 -0
  61. package/templates/backend/src/modules/task-columns/task-columns.repository.ts +15 -0
  62. package/templates/backend/src/modules/task-columns/task-columns.service.ts +49 -0
  63. package/templates/backend/src/modules/tasks/dto/checklist-item.dto.ts +13 -0
  64. package/templates/backend/src/modules/tasks/dto/create-task.dto.ts +67 -0
  65. package/templates/backend/src/modules/tasks/dto/label.dto.ts +12 -0
  66. package/templates/backend/src/modules/tasks/dto/move-task.dto.ts +15 -0
  67. package/templates/backend/src/modules/tasks/dto/query-task.dto.ts +40 -0
  68. package/templates/backend/src/modules/tasks/dto/update-task.dto.ts +4 -0
  69. package/templates/backend/src/modules/tasks/schemas/task.schema.ts +66 -0
  70. package/templates/backend/src/modules/tasks/tasks.controller.ts +104 -0
  71. package/templates/backend/src/modules/tasks/tasks.module.ts +18 -0
  72. package/templates/backend/src/modules/tasks/tasks.repository.ts +14 -0
  73. package/templates/backend/src/modules/tasks/tasks.service.ts +76 -0
  74. package/templates/backend/src/modules/users/schemas/user.schema.ts +3 -0
  75. package/templates/backend/src/modules/users/users.repository.ts +8 -0
  76. package/templates/backend/src/modules/users/users.service.ts +34 -0
  77. package/templates/frontend/.env.local.example +1 -1
  78. package/templates/frontend/README.md +43 -1
  79. package/templates/frontend/package.json +48 -45
  80. package/templates/frontend/pnpm-lock.yaml +5096 -5012
  81. package/templates/frontend/src/app/(auth)/layout.tsx +7 -1
  82. package/templates/frontend/src/app/(auth)/login/page.tsx +13 -0
  83. package/templates/frontend/src/app/(auth)/register/page.tsx +13 -0
  84. package/templates/frontend/src/app/(dashboard)/clients/page.tsx +295 -0
  85. package/templates/frontend/src/app/(dashboard)/invoices/page.tsx +305 -0
  86. package/templates/frontend/src/app/(dashboard)/notifications/page.tsx +173 -0
  87. package/templates/frontend/src/app/(dashboard)/pipeline/page.tsx +244 -0
  88. package/templates/frontend/src/app/(dashboard)/planner/page.tsx +287 -0
  89. package/templates/frontend/src/app/(dashboard)/settings/page.tsx +165 -128
  90. package/templates/frontend/src/app/(dashboard)/tasks/page.tsx +366 -0
  91. package/templates/frontend/src/app/auth/github/callback/page.tsx +82 -0
  92. package/templates/frontend/src/app/landing/page.tsx +21 -0
  93. package/templates/frontend/src/app/page.tsx +5 -5
  94. package/templates/frontend/src/app/setup/page.tsx +15 -14
  95. package/templates/frontend/src/components/auth/github-button.tsx +25 -0
  96. package/templates/frontend/src/components/dashboard/sidebar.tsx +90 -71
  97. package/templates/frontend/src/components/ui/alert-dialog.tsx +141 -0
  98. package/templates/frontend/src/components/ui/button.tsx +56 -52
  99. package/templates/frontend/src/components/ui/popover.tsx +31 -0
  100. package/templates/frontend/src/components/ui/select.tsx +160 -0
  101. package/templates/frontend/src/components/ui/sheet.tsx +140 -0
  102. package/templates/frontend/src/lib/api/auth.ts +7 -0
  103. package/templates/frontend/src/lib/api/clients.ts +17 -0
  104. package/templates/frontend/src/lib/api/invoices.ts +18 -0
  105. package/templates/frontend/src/lib/api/notifications.ts +27 -0
  106. package/templates/frontend/src/lib/api/pipeline.ts +18 -0
  107. package/templates/frontend/src/lib/api/planner.ts +26 -0
  108. package/templates/frontend/src/lib/api/task-columns.ts +17 -0
  109. package/templates/frontend/src/lib/api/tasks.ts +21 -0
  110. package/templates/frontend/src/lib/hooks/use-unread-notifications.ts +23 -0
  111. package/templates/frontend/src/providers/auth-provider.tsx +7 -1
  112. package/templates/frontend/src/types/clients.ts +38 -0
  113. package/templates/frontend/src/types/invoices.ts +51 -0
  114. package/templates/frontend/src/types/notifications.ts +30 -0
  115. package/templates/frontend/src/types/pipeline.ts +35 -0
  116. package/templates/frontend/src/types/planner.ts +49 -0
  117. package/templates/frontend/src/types/tasks.ts +65 -0
@@ -1,71 +1,90 @@
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
+ Building2,
13
+ FileText,
14
+ TrendingUp,
15
+ CheckSquare,
16
+ CalendarDays,
17
+ Bell,
18
+ } from 'lucide-react'
19
+ import { siteConfig } from '@/config/site'
20
+ import { useAuth } from '@/lib/hooks/use-auth'
21
+ import { useUnreadNotifications } from '@/lib/hooks/use-unread-notifications'
22
+ import { Button } from '@/components/ui/button'
23
+ import { Separator } from '@/components/ui/separator'
24
+ import { cn } from '@/lib/utils'
25
+
26
+ const navItems = [
27
+ { label: 'Dashboard', href: '/dashboard', icon: LayoutDashboard },
28
+ { label: 'Clientes', href: '/clients', icon: Building2 },
29
+ { label: 'Facturas', href: '/invoices', icon: FileText },
30
+ { label: 'Pipeline', href: '/pipeline', icon: TrendingUp },
31
+ { label: 'Tareas', href: '/tasks', icon: CheckSquare },
32
+ { label: 'Planner', href: '/planner', icon: CalendarDays },
33
+ { label: 'Notificaciones', href: '/notifications', icon: Bell, badge: true },
34
+ { label: 'Perfil', href: '/profile', icon: User },
35
+ { label: 'Equipo', href: '/team', icon: Users },
36
+ { label: 'Facturación', href: '/billing', icon: CreditCard },
37
+ { label: 'Ajustes', href: '/settings', icon: Settings },
38
+ ]
39
+
40
+ export function DashboardSidebar() {
41
+ const pathname = usePathname()
42
+ const { logout } = useAuth()
43
+ const unreadCount = useUnreadNotifications()
44
+
45
+ return (
46
+ <aside className="flex w-60 shrink-0 flex-col border-r bg-card">
47
+ <div className="flex h-14 items-center border-b px-4">
48
+ <Link href="/" className="text-sm font-bold tracking-tight">
49
+ {siteConfig.name}
50
+ </Link>
51
+ </div>
52
+
53
+ <nav className="flex-1 space-y-1 overflow-y-auto p-3">
54
+ {navItems.map(({ label, href, icon: Icon, badge }) => (
55
+ <Button
56
+ key={href}
57
+ asChild
58
+ variant={pathname === href ? 'secondary' : 'ghost'}
59
+ className={cn(
60
+ 'w-full justify-start',
61
+ pathname === href && 'font-medium',
62
+ )}
63
+ >
64
+ <Link href={href}>
65
+ <Icon className="mr-2 size-4 shrink-0" />
66
+ <span className="flex-1 truncate">{label}</span>
67
+ {badge && unreadCount > 0 && (
68
+ <span className="ml-auto rounded-full bg-blue-500 px-1.5 py-0.5 text-[10px] font-semibold leading-none text-white">
69
+ {unreadCount > 99 ? '99+' : unreadCount}
70
+ </span>
71
+ )}
72
+ </Link>
73
+ </Button>
74
+ ))}
75
+ </nav>
76
+
77
+ <div className="p-3">
78
+ <Separator className="mb-3" />
79
+ <Button
80
+ variant="ghost"
81
+ className="w-full justify-start text-muted-foreground hover:text-foreground"
82
+ onClick={logout}
83
+ >
84
+ <LogOut className="mr-2 size-4" />
85
+ Cerrar sesión
86
+ </Button>
87
+ </div>
88
+ </aside>
89
+ )
90
+ }
@@ -0,0 +1,141 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
5
+
6
+ import { cn } from "@/lib/utils"
7
+ import { buttonVariants } from "@/components/ui/button"
8
+
9
+ const AlertDialog = AlertDialogPrimitive.Root
10
+
11
+ const AlertDialogTrigger = AlertDialogPrimitive.Trigger
12
+
13
+ const AlertDialogPortal = AlertDialogPrimitive.Portal
14
+
15
+ const AlertDialogOverlay = React.forwardRef<
16
+ React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
17
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
18
+ >(({ className, ...props }, ref) => (
19
+ <AlertDialogPrimitive.Overlay
20
+ className={cn(
21
+ "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
22
+ className
23
+ )}
24
+ {...props}
25
+ ref={ref}
26
+ />
27
+ ))
28
+ AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
29
+
30
+ const AlertDialogContent = React.forwardRef<
31
+ React.ElementRef<typeof AlertDialogPrimitive.Content>,
32
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
33
+ >(({ className, ...props }, ref) => (
34
+ <AlertDialogPortal>
35
+ <AlertDialogOverlay />
36
+ <AlertDialogPrimitive.Content
37
+ ref={ref}
38
+ className={cn(
39
+ "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
40
+ className
41
+ )}
42
+ {...props}
43
+ />
44
+ </AlertDialogPortal>
45
+ ))
46
+ AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
47
+
48
+ const AlertDialogHeader = ({
49
+ className,
50
+ ...props
51
+ }: React.HTMLAttributes<HTMLDivElement>) => (
52
+ <div
53
+ className={cn(
54
+ "flex flex-col space-y-2 text-center sm:text-left",
55
+ className
56
+ )}
57
+ {...props}
58
+ />
59
+ )
60
+ AlertDialogHeader.displayName = "AlertDialogHeader"
61
+
62
+ const AlertDialogFooter = ({
63
+ className,
64
+ ...props
65
+ }: React.HTMLAttributes<HTMLDivElement>) => (
66
+ <div
67
+ className={cn(
68
+ "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
69
+ className
70
+ )}
71
+ {...props}
72
+ />
73
+ )
74
+ AlertDialogFooter.displayName = "AlertDialogFooter"
75
+
76
+ const AlertDialogTitle = React.forwardRef<
77
+ React.ElementRef<typeof AlertDialogPrimitive.Title>,
78
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
79
+ >(({ className, ...props }, ref) => (
80
+ <AlertDialogPrimitive.Title
81
+ ref={ref}
82
+ className={cn("text-lg font-semibold", className)}
83
+ {...props}
84
+ />
85
+ ))
86
+ AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
87
+
88
+ const AlertDialogDescription = React.forwardRef<
89
+ React.ElementRef<typeof AlertDialogPrimitive.Description>,
90
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
91
+ >(({ className, ...props }, ref) => (
92
+ <AlertDialogPrimitive.Description
93
+ ref={ref}
94
+ className={cn("text-sm text-muted-foreground", className)}
95
+ {...props}
96
+ />
97
+ ))
98
+ AlertDialogDescription.displayName =
99
+ AlertDialogPrimitive.Description.displayName
100
+
101
+ const AlertDialogAction = React.forwardRef<
102
+ React.ElementRef<typeof AlertDialogPrimitive.Action>,
103
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
104
+ >(({ className, ...props }, ref) => (
105
+ <AlertDialogPrimitive.Action
106
+ ref={ref}
107
+ className={cn(buttonVariants(), className)}
108
+ {...props}
109
+ />
110
+ ))
111
+ AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
112
+
113
+ const AlertDialogCancel = React.forwardRef<
114
+ React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
115
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
116
+ >(({ className, ...props }, ref) => (
117
+ <AlertDialogPrimitive.Cancel
118
+ ref={ref}
119
+ className={cn(
120
+ buttonVariants({ variant: "outline" }),
121
+ "mt-2 sm:mt-0",
122
+ className
123
+ )}
124
+ {...props}
125
+ />
126
+ ))
127
+ AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
128
+
129
+ export {
130
+ AlertDialog,
131
+ AlertDialogPortal,
132
+ AlertDialogOverlay,
133
+ AlertDialogTrigger,
134
+ AlertDialogContent,
135
+ AlertDialogHeader,
136
+ AlertDialogFooter,
137
+ AlertDialogTitle,
138
+ AlertDialogDescription,
139
+ AlertDialogAction,
140
+ AlertDialogCancel,
141
+ }
@@ -1,52 +1,56 @@
1
- import * as React from 'react'
2
- import { Slot } from '@radix-ui/react-slot'
3
- import { cva, type VariantProps } from 'class-variance-authority'
4
- import { cn } from '@/lib/utils'
5
-
6
- const buttonVariants = cva(
7
- 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*="size-"])]:size-4 shrink-0',
8
- {
9
- variants: {
10
- variant: {
11
- default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90',
12
- destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
13
- outline: 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
14
- secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
15
- ghost: 'hover:bg-accent hover:text-accent-foreground',
16
- link: 'text-primary underline-offset-4 hover:underline',
17
- },
18
- size: {
19
- default: 'h-9 px-4 py-2',
20
- sm: 'h-8 rounded-md px-3 text-xs',
21
- lg: 'h-10 rounded-md px-8',
22
- icon: 'h-9 w-9',
23
- },
24
- },
25
- defaultVariants: {
26
- variant: 'default',
27
- size: 'default',
28
- },
29
- },
30
- )
31
-
32
- export interface ButtonProps
33
- extends React.ButtonHTMLAttributes<HTMLButtonElement>,
34
- VariantProps<typeof buttonVariants> {
35
- asChild?: boolean
36
- }
37
-
38
- const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
39
- ({ className, variant, size, asChild = false, ...props }, ref) => {
40
- const Comp = asChild ? Slot : 'button'
41
- return (
42
- <Comp
43
- className={cn(buttonVariants({ variant, size, className }))}
44
- ref={ref}
45
- {...props}
46
- />
47
- )
48
- },
49
- )
50
- Button.displayName = 'Button'
51
-
52
- export { Button, buttonVariants }
1
+ import * as React from "react"
2
+ import { Slot } from "@radix-ui/react-slot"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const buttonVariants = cva(
8
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
13
+ destructive:
14
+ "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15
+ outline:
16
+ "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17
+ secondary:
18
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19
+ ghost: "hover:bg-accent hover:text-accent-foreground",
20
+ link: "text-primary underline-offset-4 hover:underline",
21
+ },
22
+ size: {
23
+ default: "h-10 px-4 py-2",
24
+ sm: "h-9 rounded-md px-3",
25
+ lg: "h-11 rounded-md px-8",
26
+ icon: "h-10 w-10",
27
+ },
28
+ },
29
+ defaultVariants: {
30
+ variant: "default",
31
+ size: "default",
32
+ },
33
+ }
34
+ )
35
+
36
+ export interface ButtonProps
37
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
38
+ VariantProps<typeof buttonVariants> {
39
+ asChild?: boolean
40
+ }
41
+
42
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
43
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
44
+ const Comp = asChild ? Slot : "button"
45
+ return (
46
+ <Comp
47
+ className={cn(buttonVariants({ variant, size, className }))}
48
+ ref={ref}
49
+ {...props}
50
+ />
51
+ )
52
+ }
53
+ )
54
+ Button.displayName = "Button"
55
+
56
+ export { Button, buttonVariants }
@@ -0,0 +1,31 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as PopoverPrimitive from "@radix-ui/react-popover"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const Popover = PopoverPrimitive.Root
9
+
10
+ const PopoverTrigger = PopoverPrimitive.Trigger
11
+
12
+ const PopoverContent = React.forwardRef<
13
+ React.ElementRef<typeof PopoverPrimitive.Content>,
14
+ React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
15
+ >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
16
+ <PopoverPrimitive.Portal>
17
+ <PopoverPrimitive.Content
18
+ ref={ref}
19
+ align={align}
20
+ sideOffset={sideOffset}
21
+ className={cn(
22
+ "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-popover-content-transform-origin]",
23
+ className
24
+ )}
25
+ {...props}
26
+ />
27
+ </PopoverPrimitive.Portal>
28
+ ))
29
+ PopoverContent.displayName = PopoverPrimitive.Content.displayName
30
+
31
+ export { Popover, PopoverTrigger, PopoverContent }
@@ -0,0 +1,160 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as SelectPrimitive from "@radix-ui/react-select"
5
+ import { Check, ChevronDown, ChevronUp } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ const Select = SelectPrimitive.Root
10
+
11
+ const SelectGroup = SelectPrimitive.Group
12
+
13
+ const SelectValue = SelectPrimitive.Value
14
+
15
+ const SelectTrigger = React.forwardRef<
16
+ React.ElementRef<typeof SelectPrimitive.Trigger>,
17
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
18
+ >(({ className, children, ...props }, ref) => (
19
+ <SelectPrimitive.Trigger
20
+ ref={ref}
21
+ className={cn(
22
+ "flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
23
+ className
24
+ )}
25
+ {...props}
26
+ >
27
+ {children}
28
+ <SelectPrimitive.Icon asChild>
29
+ <ChevronDown className="h-4 w-4 opacity-50" />
30
+ </SelectPrimitive.Icon>
31
+ </SelectPrimitive.Trigger>
32
+ ))
33
+ SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
34
+
35
+ const SelectScrollUpButton = React.forwardRef<
36
+ React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
37
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
38
+ >(({ className, ...props }, ref) => (
39
+ <SelectPrimitive.ScrollUpButton
40
+ ref={ref}
41
+ className={cn(
42
+ "flex cursor-default items-center justify-center py-1",
43
+ className
44
+ )}
45
+ {...props}
46
+ >
47
+ <ChevronUp className="h-4 w-4" />
48
+ </SelectPrimitive.ScrollUpButton>
49
+ ))
50
+ SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
51
+
52
+ const SelectScrollDownButton = React.forwardRef<
53
+ React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
54
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
55
+ >(({ className, ...props }, ref) => (
56
+ <SelectPrimitive.ScrollDownButton
57
+ ref={ref}
58
+ className={cn(
59
+ "flex cursor-default items-center justify-center py-1",
60
+ className
61
+ )}
62
+ {...props}
63
+ >
64
+ <ChevronDown className="h-4 w-4" />
65
+ </SelectPrimitive.ScrollDownButton>
66
+ ))
67
+ SelectScrollDownButton.displayName =
68
+ SelectPrimitive.ScrollDownButton.displayName
69
+
70
+ const SelectContent = React.forwardRef<
71
+ React.ElementRef<typeof SelectPrimitive.Content>,
72
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
73
+ >(({ className, children, position = "popper", ...props }, ref) => (
74
+ <SelectPrimitive.Portal>
75
+ <SelectPrimitive.Content
76
+ ref={ref}
77
+ className={cn(
78
+ "relative z-50 max-h-[--radix-select-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-select-content-transform-origin]",
79
+ position === "popper" &&
80
+ "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
81
+ className
82
+ )}
83
+ position={position}
84
+ {...props}
85
+ >
86
+ <SelectScrollUpButton />
87
+ <SelectPrimitive.Viewport
88
+ className={cn(
89
+ "p-1",
90
+ position === "popper" &&
91
+ "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
92
+ )}
93
+ >
94
+ {children}
95
+ </SelectPrimitive.Viewport>
96
+ <SelectScrollDownButton />
97
+ </SelectPrimitive.Content>
98
+ </SelectPrimitive.Portal>
99
+ ))
100
+ SelectContent.displayName = SelectPrimitive.Content.displayName
101
+
102
+ const SelectLabel = React.forwardRef<
103
+ React.ElementRef<typeof SelectPrimitive.Label>,
104
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
105
+ >(({ className, ...props }, ref) => (
106
+ <SelectPrimitive.Label
107
+ ref={ref}
108
+ className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
109
+ {...props}
110
+ />
111
+ ))
112
+ SelectLabel.displayName = SelectPrimitive.Label.displayName
113
+
114
+ const SelectItem = React.forwardRef<
115
+ React.ElementRef<typeof SelectPrimitive.Item>,
116
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
117
+ >(({ className, children, ...props }, ref) => (
118
+ <SelectPrimitive.Item
119
+ ref={ref}
120
+ className={cn(
121
+ "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
122
+ className
123
+ )}
124
+ {...props}
125
+ >
126
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
127
+ <SelectPrimitive.ItemIndicator>
128
+ <Check className="h-4 w-4" />
129
+ </SelectPrimitive.ItemIndicator>
130
+ </span>
131
+
132
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
133
+ </SelectPrimitive.Item>
134
+ ))
135
+ SelectItem.displayName = SelectPrimitive.Item.displayName
136
+
137
+ const SelectSeparator = React.forwardRef<
138
+ React.ElementRef<typeof SelectPrimitive.Separator>,
139
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
140
+ >(({ className, ...props }, ref) => (
141
+ <SelectPrimitive.Separator
142
+ ref={ref}
143
+ className={cn("-mx-1 my-1 h-px bg-muted", className)}
144
+ {...props}
145
+ />
146
+ ))
147
+ SelectSeparator.displayName = SelectPrimitive.Separator.displayName
148
+
149
+ export {
150
+ Select,
151
+ SelectGroup,
152
+ SelectValue,
153
+ SelectTrigger,
154
+ SelectContent,
155
+ SelectLabel,
156
+ SelectItem,
157
+ SelectSeparator,
158
+ SelectScrollUpButton,
159
+ SelectScrollDownButton,
160
+ }