@jonsoc/web 1.1.34

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 (44) hide show
  1. package/.env.example +3 -0
  2. package/components.json +24 -0
  3. package/index.html +13 -0
  4. package/package.json +48 -0
  5. package/public/apple-touch-icon-v3.png +1 -0
  6. package/public/apple-touch-icon.png +1 -0
  7. package/public/favicon-96x96-v3.png +1 -0
  8. package/public/favicon-96x96.png +1 -0
  9. package/public/favicon-v3.ico +1 -0
  10. package/public/favicon-v3.svg +1 -0
  11. package/public/favicon.ico +1 -0
  12. package/public/favicon.svg +1 -0
  13. package/public/robots.txt +6 -0
  14. package/public/site.webmanifest +1 -0
  15. package/public/social-share-zen.png +1 -0
  16. package/public/social-share.png +1 -0
  17. package/public/theme.json +183 -0
  18. package/public/web-app-manifest-192x192.png +1 -0
  19. package/public/web-app-manifest-512x512.png +1 -0
  20. package/src/components/header.tsx +30 -0
  21. package/src/components/loader.tsx +9 -0
  22. package/src/components/mode-toggle.tsx +24 -0
  23. package/src/components/sign-in-form.tsx +123 -0
  24. package/src/components/sign-up-form.tsx +148 -0
  25. package/src/components/theme-provider.tsx +8 -0
  26. package/src/components/ui/button.tsx +51 -0
  27. package/src/components/ui/card.tsx +72 -0
  28. package/src/components/ui/checkbox.tsx +26 -0
  29. package/src/components/ui/dropdown-menu.tsx +235 -0
  30. package/src/components/ui/input.tsx +20 -0
  31. package/src/components/ui/label.tsx +20 -0
  32. package/src/components/ui/skeleton.tsx +7 -0
  33. package/src/components/ui/sonner.tsx +39 -0
  34. package/src/components/user-menu.tsx +50 -0
  35. package/src/index.css +127 -0
  36. package/src/lib/auth-client.ts +8 -0
  37. package/src/lib/utils.ts +6 -0
  38. package/src/main.tsx +42 -0
  39. package/src/routeTree.gen.ts +77 -0
  40. package/src/routes/__root.tsx +47 -0
  41. package/src/routes/dashboard.tsx +39 -0
  42. package/src/routes/index.tsx +46 -0
  43. package/tsconfig.json +18 -0
  44. package/vite.config.ts +17 -0
@@ -0,0 +1,8 @@
1
+ import { ThemeProvider as NextThemesProvider } from "next-themes"
2
+ import * as React from "react"
3
+
4
+ export function ThemeProvider({ children, ...props }: React.ComponentProps<typeof NextThemesProvider>) {
5
+ return <NextThemesProvider {...props}>{children}</NextThemesProvider>
6
+ }
7
+
8
+ export { useTheme } from "next-themes"
@@ -0,0 +1,51 @@
1
+ import type { VariantProps } from "class-variance-authority"
2
+
3
+ import { Button as ButtonPrimitive } from "@base-ui/react/button"
4
+ import { cva } from "class-variance-authority"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const buttonVariants = cva(
9
+ "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-none border border-transparent bg-clip-padding text-xs font-medium focus-visible:ring-1 aria-invalid:ring-1 [&_svg:not([class*='size-'])]:size-4 inline-flex items-center justify-center whitespace-nowrap transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 outline-none group/button select-none",
10
+ {
11
+ variants: {
12
+ variant: {
13
+ default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
14
+ outline:
15
+ "border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground",
16
+ secondary:
17
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
18
+ ghost:
19
+ "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground",
20
+ destructive:
21
+ "bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30",
22
+ link: "text-primary underline-offset-4 hover:underline",
23
+ },
24
+ size: {
25
+ default: "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
26
+ xs: "h-6 gap-1 rounded-none px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
27
+ sm: "h-7 gap-1 rounded-none px-2.5 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
28
+ lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3",
29
+ icon: "size-8",
30
+ "icon-xs": "size-6 rounded-none [&_svg:not([class*='size-'])]:size-3",
31
+ "icon-sm": "size-7 rounded-none",
32
+ "icon-lg": "size-9",
33
+ },
34
+ },
35
+ defaultVariants: {
36
+ variant: "default",
37
+ size: "default",
38
+ },
39
+ },
40
+ )
41
+
42
+ function Button({
43
+ className,
44
+ variant = "default",
45
+ size = "default",
46
+ ...props
47
+ }: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {
48
+ return <ButtonPrimitive data-slot="button" className={cn(buttonVariants({ variant, size, className }))} {...props} />
49
+ }
50
+
51
+ export { Button, buttonVariants }
@@ -0,0 +1,72 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ function Card({ className, size = "default", ...props }: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
6
+ return (
7
+ <div
8
+ data-slot="card"
9
+ data-size={size}
10
+ className={cn(
11
+ "ring-foreground/10 bg-card text-card-foreground gap-4 overflow-hidden rounded-none py-4 text-xs/relaxed ring-1 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-2 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-none *:[img:last-child]:rounded-none group/card flex flex-col",
12
+ className,
13
+ )}
14
+ {...props}
15
+ />
16
+ )
17
+ }
18
+
19
+ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
20
+ return (
21
+ <div
22
+ data-slot="card-header"
23
+ className={cn(
24
+ "gap-1 rounded-none px-4 group-data-[size=sm]/card:px-3 [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3 group/card-header @container/card-header grid auto-rows-min items-start has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto]",
25
+ className,
26
+ )}
27
+ {...props}
28
+ />
29
+ )
30
+ }
31
+
32
+ function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
33
+ return (
34
+ <div
35
+ data-slot="card-title"
36
+ className={cn("text-sm font-medium group-data-[size=sm]/card:text-sm", className)}
37
+ {...props}
38
+ />
39
+ )
40
+ }
41
+
42
+ function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
43
+ return (
44
+ <div data-slot="card-description" className={cn("text-muted-foreground text-xs/relaxed", className)} {...props} />
45
+ )
46
+ }
47
+
48
+ function CardAction({ className, ...props }: React.ComponentProps<"div">) {
49
+ return (
50
+ <div
51
+ data-slot="card-action"
52
+ className={cn("col-start-2 row-span-2 row-start-1 self-start justify-self-end", className)}
53
+ {...props}
54
+ />
55
+ )
56
+ }
57
+
58
+ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
59
+ return <div data-slot="card-content" className={cn("px-4 group-data-[size=sm]/card:px-3", className)} {...props} />
60
+ }
61
+
62
+ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
63
+ return (
64
+ <div
65
+ data-slot="card-footer"
66
+ className={cn("rounded-none border-t p-4 group-data-[size=sm]/card:p-3 flex items-center", className)}
67
+ {...props}
68
+ />
69
+ )
70
+ }
71
+
72
+ export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent }
@@ -0,0 +1,26 @@
1
+ import { Checkbox as CheckboxPrimitive } from "@base-ui/react/checkbox"
2
+ import { CheckIcon } from "lucide-react"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ function Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) {
7
+ return (
8
+ <CheckboxPrimitive.Root
9
+ data-slot="checkbox"
10
+ className={cn(
11
+ "border-input dark:bg-input/30 data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary data-checked:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 flex size-4 items-center justify-center rounded-none border transition-colors group-has-disabled/field:opacity-50 focus-visible:ring-1 aria-invalid:ring-1 peer relative shrink-0 outline-none after:absolute after:-inset-x-3 after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50",
12
+ className,
13
+ )}
14
+ {...props}
15
+ >
16
+ <CheckboxPrimitive.Indicator
17
+ data-slot="checkbox-indicator"
18
+ className="[&>svg]:size-3.5 grid place-content-center text-current transition-none"
19
+ >
20
+ <CheckIcon />
21
+ </CheckboxPrimitive.Indicator>
22
+ </CheckboxPrimitive.Root>
23
+ )
24
+ }
25
+
26
+ export { Checkbox }
@@ -0,0 +1,235 @@
1
+ import { Menu as MenuPrimitive } from "@base-ui/react/menu"
2
+ import { CheckIcon, ChevronRightIcon } from "lucide-react"
3
+ import * as React from "react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ function DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {
8
+ return <MenuPrimitive.Root data-slot="dropdown-menu" {...props} />
9
+ }
10
+
11
+ function DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {
12
+ return <MenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
13
+ }
14
+
15
+ function DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) {
16
+ return <MenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />
17
+ }
18
+
19
+ function DropdownMenuContent({
20
+ align = "start",
21
+ alignOffset = 0,
22
+ side = "bottom",
23
+ sideOffset = 4,
24
+ className,
25
+ ...props
26
+ }: MenuPrimitive.Popup.Props & Pick<MenuPrimitive.Positioner.Props, "align" | "alignOffset" | "side" | "sideOffset">) {
27
+ return (
28
+ <MenuPrimitive.Portal>
29
+ <MenuPrimitive.Positioner
30
+ className="isolate z-50 outline-none"
31
+ align={align}
32
+ alignOffset={alignOffset}
33
+ side={side}
34
+ sideOffset={sideOffset}
35
+ >
36
+ <MenuPrimitive.Popup
37
+ data-slot="dropdown-menu-content"
38
+ className={cn(
39
+ "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 ring-foreground/10 bg-popover text-popover-foreground min-w-32 rounded-none shadow-md ring-1 duration-100 z-50 max-h-(--available-height) w-(--anchor-width) origin-(--transform-origin) overflow-x-hidden overflow-y-auto outline-none data-closed:overflow-hidden",
40
+ className,
41
+ )}
42
+ {...props}
43
+ />
44
+ </MenuPrimitive.Positioner>
45
+ </MenuPrimitive.Portal>
46
+ )
47
+ }
48
+
49
+ function DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {
50
+ return <MenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
51
+ }
52
+
53
+ function DropdownMenuLabel({
54
+ className,
55
+ inset,
56
+ ...props
57
+ }: MenuPrimitive.GroupLabel.Props & {
58
+ inset?: boolean
59
+ }) {
60
+ return (
61
+ <MenuPrimitive.GroupLabel
62
+ data-slot="dropdown-menu-label"
63
+ data-inset={inset}
64
+ className={cn("text-muted-foreground px-2 py-2 text-xs data-[inset]:pl-8", className)}
65
+ {...props}
66
+ />
67
+ )
68
+ }
69
+
70
+ function DropdownMenuItem({
71
+ className,
72
+ inset,
73
+ variant = "default",
74
+ ...props
75
+ }: MenuPrimitive.Item.Props & {
76
+ inset?: boolean
77
+ variant?: "default" | "destructive"
78
+ }) {
79
+ return (
80
+ <MenuPrimitive.Item
81
+ data-slot="dropdown-menu-item"
82
+ data-inset={inset}
83
+ data-variant={variant}
84
+ className={cn(
85
+ "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-none px-2 py-2 text-xs [&_svg:not([class*='size-'])]:size-4 group/dropdown-menu-item relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0",
86
+ className,
87
+ )}
88
+ {...props}
89
+ />
90
+ )
91
+ }
92
+
93
+ function DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {
94
+ return <MenuPrimitive.SubmenuRoot data-slot="dropdown-menu-sub" {...props} />
95
+ }
96
+
97
+ function DropdownMenuSubTrigger({
98
+ className,
99
+ inset,
100
+ children,
101
+ ...props
102
+ }: MenuPrimitive.SubmenuTrigger.Props & {
103
+ inset?: boolean
104
+ }) {
105
+ return (
106
+ <MenuPrimitive.SubmenuTrigger
107
+ data-slot="dropdown-menu-sub-trigger"
108
+ data-inset={inset}
109
+ className={cn(
110
+ "focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-none px-2 py-2 text-xs [&_svg:not([class*='size-'])]:size-4 flex cursor-default items-center outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0",
111
+ className,
112
+ )}
113
+ {...props}
114
+ >
115
+ {children}
116
+ <ChevronRightIcon className="ml-auto" />
117
+ </MenuPrimitive.SubmenuTrigger>
118
+ )
119
+ }
120
+
121
+ function DropdownMenuSubContent({
122
+ align = "start",
123
+ alignOffset = -3,
124
+ side = "right",
125
+ sideOffset = 0,
126
+ className,
127
+ ...props
128
+ }: React.ComponentProps<typeof DropdownMenuContent>) {
129
+ return (
130
+ <DropdownMenuContent
131
+ data-slot="dropdown-menu-sub-content"
132
+ className={cn(
133
+ "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-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 ring-foreground/10 bg-popover text-popover-foreground min-w-[96px] rounded-none shadow-lg ring-1 duration-100 w-auto",
134
+ className,
135
+ )}
136
+ align={align}
137
+ alignOffset={alignOffset}
138
+ side={side}
139
+ sideOffset={sideOffset}
140
+ {...props}
141
+ />
142
+ )
143
+ }
144
+
145
+ function DropdownMenuCheckboxItem({ className, children, checked, ...props }: MenuPrimitive.CheckboxItem.Props) {
146
+ return (
147
+ <MenuPrimitive.CheckboxItem
148
+ data-slot="dropdown-menu-checkbox-item"
149
+ className={cn(
150
+ "focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-2 rounded-none py-2 pr-8 pl-2 text-xs [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
151
+ className,
152
+ )}
153
+ checked={checked}
154
+ {...props}
155
+ >
156
+ <span
157
+ className="pointer-events-none absolute right-2 flex items-center justify-center pointer-events-none"
158
+ data-slot="dropdown-menu-checkbox-item-indicator"
159
+ >
160
+ <MenuPrimitive.CheckboxItemIndicator>
161
+ <CheckIcon />
162
+ </MenuPrimitive.CheckboxItemIndicator>
163
+ </span>
164
+ {children}
165
+ </MenuPrimitive.CheckboxItem>
166
+ )
167
+ }
168
+
169
+ function DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {
170
+ return <MenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />
171
+ }
172
+
173
+ function DropdownMenuRadioItem({ className, children, ...props }: MenuPrimitive.RadioItem.Props) {
174
+ return (
175
+ <MenuPrimitive.RadioItem
176
+ data-slot="dropdown-menu-radio-item"
177
+ className={cn(
178
+ "focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-2 rounded-none py-2 pr-8 pl-2 text-xs [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
179
+ className,
180
+ )}
181
+ {...props}
182
+ >
183
+ <span
184
+ className="pointer-events-none absolute right-2 flex items-center justify-center pointer-events-none"
185
+ data-slot="dropdown-menu-radio-item-indicator"
186
+ >
187
+ <MenuPrimitive.RadioItemIndicator>
188
+ <CheckIcon />
189
+ </MenuPrimitive.RadioItemIndicator>
190
+ </span>
191
+ {children}
192
+ </MenuPrimitive.RadioItem>
193
+ )
194
+ }
195
+
196
+ function DropdownMenuSeparator({ className, ...props }: MenuPrimitive.Separator.Props) {
197
+ return (
198
+ <MenuPrimitive.Separator
199
+ data-slot="dropdown-menu-separator"
200
+ className={cn("bg-border -mx-1 h-px", className)}
201
+ {...props}
202
+ />
203
+ )
204
+ }
205
+
206
+ function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<"span">) {
207
+ return (
208
+ <span
209
+ data-slot="dropdown-menu-shortcut"
210
+ className={cn(
211
+ "text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground ml-auto text-xs tracking-widest",
212
+ className,
213
+ )}
214
+ {...props}
215
+ />
216
+ )
217
+ }
218
+
219
+ export {
220
+ DropdownMenu,
221
+ DropdownMenuPortal,
222
+ DropdownMenuTrigger,
223
+ DropdownMenuContent,
224
+ DropdownMenuGroup,
225
+ DropdownMenuLabel,
226
+ DropdownMenuItem,
227
+ DropdownMenuCheckboxItem,
228
+ DropdownMenuRadioGroup,
229
+ DropdownMenuRadioItem,
230
+ DropdownMenuSeparator,
231
+ DropdownMenuShortcut,
232
+ DropdownMenuSub,
233
+ DropdownMenuSubTrigger,
234
+ DropdownMenuSubContent,
235
+ }
@@ -0,0 +1,20 @@
1
+ import { Input as InputPrimitive } from "@base-ui/react/input"
2
+ import * as React from "react"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
7
+ return (
8
+ <InputPrimitive
9
+ type={type}
10
+ data-slot="input"
11
+ className={cn(
12
+ "dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 h-8 rounded-none border bg-transparent px-2.5 py-1 text-xs transition-colors file:h-6 file:text-xs file:font-medium focus-visible:ring-1 aria-invalid:ring-1 md:text-xs file:text-foreground placeholder:text-muted-foreground w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
13
+ className,
14
+ )}
15
+ {...props}
16
+ />
17
+ )
18
+ }
19
+
20
+ export { Input }
@@ -0,0 +1,20 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ function Label({ className, ...props }: React.ComponentProps<"label">) {
8
+ return (
9
+ <label
10
+ data-slot="label"
11
+ className={cn(
12
+ "gap-2 text-xs leading-none group-data-[disabled=true]:opacity-50 peer-disabled:opacity-50 flex items-center select-none group-data-[disabled=true]:pointer-events-none peer-disabled:cursor-not-allowed",
13
+ className,
14
+ )}
15
+ {...props}
16
+ />
17
+ )
18
+ }
19
+
20
+ export { Label }
@@ -0,0 +1,7 @@
1
+ import { cn } from "@/lib/utils"
2
+
3
+ function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
4
+ return <div data-slot="skeleton" className={cn("bg-muted rounded-none animate-pulse", className)} {...props} />
5
+ }
6
+
7
+ export { Skeleton }
@@ -0,0 +1,39 @@
1
+ import type { ToasterProps } from "sonner"
2
+
3
+ import { CircleCheckIcon, InfoIcon, Loader2Icon, OctagonXIcon, TriangleAlertIcon } from "lucide-react"
4
+ import { useTheme } from "next-themes"
5
+ import { Toaster as Sonner } from "sonner"
6
+
7
+ const Toaster = ({ ...props }: ToasterProps) => {
8
+ const { theme = "system" } = useTheme()
9
+
10
+ return (
11
+ <Sonner
12
+ theme={theme as ToasterProps["theme"]}
13
+ className="toaster group"
14
+ icons={{
15
+ success: <CircleCheckIcon className="size-4" />,
16
+ info: <InfoIcon className="size-4" />,
17
+ warning: <TriangleAlertIcon className="size-4" />,
18
+ error: <OctagonXIcon className="size-4" />,
19
+ loading: <Loader2Icon className="size-4 animate-spin" />,
20
+ }}
21
+ style={
22
+ {
23
+ "--normal-bg": "var(--popover)",
24
+ "--normal-text": "var(--popover-foreground)",
25
+ "--normal-border": "var(--border)",
26
+ "--border-radius": "var(--radius)",
27
+ } as React.CSSProperties
28
+ }
29
+ toastOptions={{
30
+ classNames: {
31
+ toast: "cn-toast",
32
+ },
33
+ }}
34
+ {...props}
35
+ />
36
+ )
37
+ }
38
+
39
+ export { Toaster }
@@ -0,0 +1,50 @@
1
+ import { api } from "@jonsoc/convex"
2
+ import { useNavigate } from "@tanstack/react-router"
3
+ import { useQuery } from "convex/react"
4
+
5
+ import {
6
+ DropdownMenu,
7
+ DropdownMenuContent,
8
+ DropdownMenuGroup,
9
+ DropdownMenuItem,
10
+ DropdownMenuLabel,
11
+ DropdownMenuSeparator,
12
+ DropdownMenuTrigger,
13
+ } from "@/components/ui/dropdown-menu"
14
+ import { authClient } from "@/lib/auth-client"
15
+
16
+ import { Button } from "./ui/button"
17
+
18
+ export default function UserMenu() {
19
+ const navigate = useNavigate()
20
+ const user = useQuery(api.auth.getCurrentUser)
21
+
22
+ return (
23
+ <DropdownMenu>
24
+ <DropdownMenuTrigger render={<Button variant="outline" />}>{user?.name}</DropdownMenuTrigger>
25
+ <DropdownMenuContent className="bg-card">
26
+ <DropdownMenuGroup>
27
+ <DropdownMenuLabel>My Account</DropdownMenuLabel>
28
+ <DropdownMenuSeparator />
29
+ <DropdownMenuItem>{user?.email}</DropdownMenuItem>
30
+ <DropdownMenuItem
31
+ variant="destructive"
32
+ onClick={() => {
33
+ authClient.signOut({
34
+ fetchOptions: {
35
+ onSuccess: () => {
36
+ navigate({
37
+ to: "/dashboard",
38
+ })
39
+ },
40
+ },
41
+ })
42
+ }}
43
+ >
44
+ Sign Out
45
+ </DropdownMenuItem>
46
+ </DropdownMenuGroup>
47
+ </DropdownMenuContent>
48
+ </DropdownMenu>
49
+ )
50
+ }
package/src/index.css ADDED
@@ -0,0 +1,127 @@
1
+ @import "tailwindcss";
2
+ @import "tw-animate-css";
3
+
4
+ @custom-variant dark (&:is(.dark *));
5
+
6
+ :root {
7
+ --background: oklch(1 0 0);
8
+ --foreground: oklch(0.145 0 0);
9
+ --card: oklch(1 0 0);
10
+ --card-foreground: oklch(0.145 0 0);
11
+ --popover: oklch(1 0 0);
12
+ --popover-foreground: oklch(0.145 0 0);
13
+ --primary: oklch(0.205 0 0);
14
+ --primary-foreground: oklch(0.985 0 0);
15
+ --secondary: oklch(0.97 0 0);
16
+ --secondary-foreground: oklch(0.205 0 0);
17
+ --muted: oklch(0.97 0 0);
18
+ --muted-foreground: oklch(0.556 0 0);
19
+ --accent: oklch(0.97 0 0);
20
+ --accent-foreground: oklch(0.205 0 0);
21
+ --destructive: oklch(0.58 0.22 27);
22
+ --border: oklch(0.922 0 0);
23
+ --input: oklch(0.922 0 0);
24
+ --ring: oklch(0.708 0 0);
25
+ --chart-1: oklch(0.809 0.105 251.813);
26
+ --chart-2: oklch(0.623 0.214 259.815);
27
+ --chart-3: oklch(0.546 0.245 262.881);
28
+ --chart-4: oklch(0.488 0.243 264.376);
29
+ --chart-5: oklch(0.424 0.199 265.638);
30
+ --radius: 0.625rem;
31
+ --sidebar: oklch(0.985 0 0);
32
+ --sidebar-foreground: oklch(0.145 0 0);
33
+ --sidebar-primary: oklch(0.205 0 0);
34
+ --sidebar-primary-foreground: oklch(0.985 0 0);
35
+ --sidebar-accent: oklch(0.97 0 0);
36
+ --sidebar-accent-foreground: oklch(0.205 0 0);
37
+ --sidebar-border: oklch(0.922 0 0);
38
+ --sidebar-ring: oklch(0.708 0 0);
39
+ }
40
+
41
+ .dark {
42
+ --background: oklch(0.145 0 0);
43
+ --foreground: oklch(0.985 0 0);
44
+ --card: oklch(0.205 0 0);
45
+ --card-foreground: oklch(0.985 0 0);
46
+ --popover: oklch(0.205 0 0);
47
+ --popover-foreground: oklch(0.985 0 0);
48
+ --primary: oklch(0.87 0 0);
49
+ --primary-foreground: oklch(0.205 0 0);
50
+ --secondary: oklch(0.269 0 0);
51
+ --secondary-foreground: oklch(0.985 0 0);
52
+ --muted: oklch(0.269 0 0);
53
+ --muted-foreground: oklch(0.708 0 0);
54
+ --accent: oklch(0.371 0 0);
55
+ --accent-foreground: oklch(0.985 0 0);
56
+ --destructive: oklch(0.704 0.191 22.216);
57
+ --border: oklch(1 0 0 / 10%);
58
+ --input: oklch(1 0 0 / 15%);
59
+ --ring: oklch(0.556 0 0);
60
+ --chart-1: oklch(0.809 0.105 251.813);
61
+ --chart-2: oklch(0.623 0.214 259.815);
62
+ --chart-3: oklch(0.546 0.245 262.881);
63
+ --chart-4: oklch(0.488 0.243 264.376);
64
+ --chart-5: oklch(0.424 0.199 265.638);
65
+ --sidebar: oklch(0.205 0 0);
66
+ --sidebar-foreground: oklch(0.985 0 0);
67
+ --sidebar-primary: oklch(0.488 0.243 264.376);
68
+ --sidebar-primary-foreground: oklch(0.985 0 0);
69
+ --sidebar-accent: oklch(0.269 0 0);
70
+ --sidebar-accent-foreground: oklch(0.985 0 0);
71
+ --sidebar-border: oklch(1 0 0 / 10%);
72
+ --sidebar-ring: oklch(0.556 0 0);
73
+ }
74
+
75
+ @theme inline {
76
+ --font-sans: "Inter Variable", sans-serif;
77
+ --color-sidebar-ring: var(--sidebar-ring);
78
+ --color-sidebar-border: var(--sidebar-border);
79
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
80
+ --color-sidebar-accent: var(--sidebar-accent);
81
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
82
+ --color-sidebar-primary: var(--sidebar-primary);
83
+ --color-sidebar-foreground: var(--sidebar-foreground);
84
+ --color-sidebar: var(--sidebar);
85
+ --color-chart-5: var(--chart-5);
86
+ --color-chart-4: var(--chart-4);
87
+ --color-chart-3: var(--chart-3);
88
+ --color-chart-2: var(--chart-2);
89
+ --color-chart-1: var(--chart-1);
90
+ --color-ring: var(--ring);
91
+ --color-input: var(--input);
92
+ --color-border: var(--border);
93
+ --color-destructive: var(--destructive);
94
+ --color-accent-foreground: var(--accent-foreground);
95
+ --color-accent: var(--accent);
96
+ --color-muted-foreground: var(--muted-foreground);
97
+ --color-muted: var(--muted);
98
+ --color-secondary-foreground: var(--secondary-foreground);
99
+ --color-secondary: var(--secondary);
100
+ --color-primary-foreground: var(--primary-foreground);
101
+ --color-primary: var(--primary);
102
+ --color-popover-foreground: var(--popover-foreground);
103
+ --color-popover: var(--popover);
104
+ --color-card-foreground: var(--card-foreground);
105
+ --color-card: var(--card);
106
+ --color-foreground: var(--foreground);
107
+ --color-background: var(--background);
108
+ --radius-sm: calc(var(--radius) - 4px);
109
+ --radius-md: calc(var(--radius) - 2px);
110
+ --radius-lg: var(--radius);
111
+ --radius-xl: calc(var(--radius) + 4px);
112
+ --radius-2xl: calc(var(--radius) + 8px);
113
+ --radius-3xl: calc(var(--radius) + 12px);
114
+ --radius-4xl: calc(var(--radius) + 16px);
115
+ }
116
+
117
+ @layer base {
118
+ * {
119
+ @apply border-border outline-ring/50;
120
+ }
121
+ body {
122
+ @apply font-sans bg-background text-foreground;
123
+ }
124
+ html {
125
+ @apply font-sans;
126
+ }
127
+ }
@@ -0,0 +1,8 @@
1
+ import { convexClient, crossDomainClient } from "@convex-dev/better-auth/client/plugins"
2
+ import { env } from "@jonsoc/env/web"
3
+ import { createAuthClient } from "better-auth/react"
4
+
5
+ export const authClient = createAuthClient({
6
+ baseURL: env.VITE_CONVEX_SITE_URL,
7
+ plugins: [convexClient(), crossDomainClient()],
8
+ })
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from "clsx"
2
+ import { twMerge } from "tailwind-merge"
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }