create-z3 0.0.47 → 0.0.49
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 -0
- package/dist/index.js +39 -38
- package/package.json +11 -11
- package/templates/nextjs/convex/auth/adapter/index.ts +7 -22
- package/templates/nextjs/convex/auth/index.ts +1 -1
- package/templates/nextjs/convex/auth/plugins/index.ts +6 -2
- package/templates/nextjs/convex/auth/sessions.ts +1 -1
- package/templates/nextjs/convex/auth.config.ts +7 -0
- package/templates/nextjs/convex/schema.ts +30 -2
- package/templates/nextjs/package.json +3 -2
- package/templates/nextjs/src/app/(frontend)/layout.tsx +21 -27
- package/templates/nextjs/src/auth/client.tsx +2 -4
- package/templates/nextjs/src/auth/permissions.ts +2 -2
- package/templates/nextjs/src/components/component-example.tsx +44 -492
- package/templates/nextjs/src/db/constants/index.ts +1 -0
- package/templates/tanstack-start/convex/auth/adapter/index.ts +7 -22
- package/templates/tanstack-start/convex/auth/index.ts +1 -1
- package/templates/tanstack-start/convex/auth/plugins/index.ts +5 -1
- package/templates/tanstack-start/convex/auth/sessions.ts +1 -1
- package/templates/tanstack-start/convex/auth.config.ts +7 -0
- package/templates/tanstack-start/convex/schema.ts +37 -2
- package/templates/tanstack-start/package.json +3 -2
- package/templates/tanstack-start/src/components/component-example.tsx +41 -460
- package/templates/tanstack-start/src/db/constants/index.ts +1 -0
- package/templates/tanstack-start/src/lib/auth/client.ts +2 -1
- package/templates/tanstack-start/src/lib/auth/permissions.ts +2 -2
- package/templates/tanstack-start/src/routes/auth/$authView.tsx +1 -1
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { defineSchema, defineTable } from "convex/server";
|
|
2
2
|
import { v } from "convex/values"
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
TABLE_SLUG_ACCOUNTS,
|
|
6
|
+
TABLE_SLUG_API_KEYS,
|
|
7
|
+
TABLE_SLUG_JWKS,
|
|
8
|
+
TABLE_SLUG_SESSIONS,
|
|
9
|
+
TABLE_SLUG_USERS,
|
|
10
|
+
TABLE_SLUG_VERIFICATIONS,
|
|
11
|
+
} from "~/db/constants";
|
|
5
12
|
|
|
6
13
|
export default defineSchema({
|
|
7
14
|
// Better Auth component tables (type definitions only - actual tables are in component)
|
|
@@ -22,7 +29,8 @@ export default defineSchema({
|
|
|
22
29
|
banExpires: v.optional(v.number()), // admin plugin
|
|
23
30
|
banned: v.optional(v.boolean()), // admin plugin
|
|
24
31
|
banReason: v.optional(v.string()), // admin plugin
|
|
25
|
-
role: v.
|
|
32
|
+
role: v.optional(v.string()), // admin plugin — single string in BA 1.5
|
|
33
|
+
roles: v.array(v.string()), // our multi-role field via additionalFields
|
|
26
34
|
})
|
|
27
35
|
.index("by_email", ["email"]),
|
|
28
36
|
|
|
@@ -70,4 +78,31 @@ export default defineSchema({
|
|
|
70
78
|
privateKey: v.optional(v.string()),
|
|
71
79
|
publicKey: v.string(),
|
|
72
80
|
}),
|
|
81
|
+
|
|
82
|
+
// Better Auth 1.5 — apiKey plugin (@better-auth/api-key)
|
|
83
|
+
[TABLE_SLUG_API_KEYS]: defineTable({
|
|
84
|
+
configId: v.string(), // new in 1.5, default "default"
|
|
85
|
+
name: v.optional(v.string()),
|
|
86
|
+
start: v.optional(v.string()),
|
|
87
|
+
referenceId: v.string(), // replaces userId from 1.4
|
|
88
|
+
prefix: v.optional(v.string()),
|
|
89
|
+
key: v.string(),
|
|
90
|
+
refillInterval: v.optional(v.number()),
|
|
91
|
+
refillAmount: v.optional(v.number()),
|
|
92
|
+
lastRefillAt: v.optional(v.number()),
|
|
93
|
+
enabled: v.optional(v.boolean()),
|
|
94
|
+
rateLimitEnabled: v.optional(v.boolean()),
|
|
95
|
+
rateLimitTimeWindow: v.optional(v.number()),
|
|
96
|
+
rateLimitMax: v.optional(v.number()),
|
|
97
|
+
requestCount: v.optional(v.number()),
|
|
98
|
+
remaining: v.optional(v.number()),
|
|
99
|
+
lastRequest: v.optional(v.number()),
|
|
100
|
+
expiresAt: v.optional(v.number()),
|
|
101
|
+
createdAt: v.number(),
|
|
102
|
+
updatedAt: v.number(),
|
|
103
|
+
permissions: v.optional(v.string()),
|
|
104
|
+
metadata: v.optional(v.string()),
|
|
105
|
+
})
|
|
106
|
+
.index("by_referenceId", ["referenceId"])
|
|
107
|
+
.index("by_key", ["key"]),
|
|
73
108
|
})
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@base-ui/react": "^1.0.0",
|
|
20
|
-
"@
|
|
20
|
+
"@better-auth/api-key": "^1.0.0",
|
|
21
|
+
"@convex-dev/better-auth": "^0.11.0",
|
|
21
22
|
"@convex-dev/react-query": "^0.1.0",
|
|
22
23
|
"@daveyplate/better-auth-tanstack": "^1.3.6",
|
|
23
24
|
"@daveyplate/better-auth-ui": "^3.3.10",
|
|
@@ -32,7 +33,7 @@
|
|
|
32
33
|
"@tanstack/react-router-with-query": "^1.130.17",
|
|
33
34
|
"@tanstack/react-start": "^1.132.0",
|
|
34
35
|
"@tanstack/router-plugin": "^1.132.0",
|
|
35
|
-
"better-auth": "^1.
|
|
36
|
+
"better-auth": "^1.5.0",
|
|
36
37
|
"class-variance-authority": "^0.7.1",
|
|
37
38
|
"clsx": "^2.1.1",
|
|
38
39
|
"convex": "^1.31.2",
|
|
@@ -1,472 +1,53 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
BellIcon,
|
|
4
|
-
BluetoothIcon,
|
|
5
|
-
CreditCardIcon,
|
|
6
|
-
DownloadIcon,
|
|
7
|
-
EyeIcon,
|
|
8
|
-
FileCodeIcon,
|
|
9
|
-
FileIcon,
|
|
10
|
-
FileTextIcon,
|
|
11
|
-
FolderIcon,
|
|
12
|
-
FolderOpenIcon,
|
|
13
|
-
FolderSearchIcon,
|
|
14
|
-
HelpCircleIcon,
|
|
15
|
-
KeyboardIcon,
|
|
16
|
-
LanguagesIcon,
|
|
17
|
-
LayoutIcon,
|
|
18
|
-
LogOutIcon,
|
|
19
|
-
MailIcon,
|
|
20
|
-
MonitorIcon,
|
|
21
|
-
MoonIcon,
|
|
22
|
-
MoreHorizontalIcon,
|
|
23
|
-
MoreVerticalIcon,
|
|
24
|
-
PaletteIcon,
|
|
25
|
-
PlusIcon,
|
|
26
|
-
SaveIcon,
|
|
27
|
-
SettingsIcon,
|
|
28
|
-
ShieldIcon,
|
|
29
|
-
SunIcon,
|
|
30
|
-
UserIcon,
|
|
31
|
-
} from 'lucide-react'
|
|
1
|
+
import { Link } from '@tanstack/react-router'
|
|
2
|
+
import { TerminalIcon, UserIcon } from 'lucide-react'
|
|
32
3
|
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
AlertDialog,
|
|
36
|
-
AlertDialogAction,
|
|
37
|
-
AlertDialogCancel,
|
|
38
|
-
AlertDialogContent,
|
|
39
|
-
AlertDialogDescription,
|
|
40
|
-
AlertDialogFooter,
|
|
41
|
-
AlertDialogHeader,
|
|
42
|
-
AlertDialogMedia,
|
|
43
|
-
AlertDialogTitle,
|
|
44
|
-
AlertDialogTrigger,
|
|
45
|
-
} from '~/components/ui/alert-dialog'
|
|
46
|
-
import { Badge } from '~/components/ui/badge'
|
|
4
|
+
import { signOut, useSession } from '~/lib/auth/client'
|
|
47
5
|
import { Button } from '~/components/ui/button'
|
|
48
|
-
import {
|
|
49
|
-
Card,
|
|
50
|
-
CardAction,
|
|
51
|
-
CardContent,
|
|
52
|
-
CardDescription,
|
|
53
|
-
CardFooter,
|
|
54
|
-
CardHeader,
|
|
55
|
-
CardTitle,
|
|
56
|
-
} from '~/components/ui/card'
|
|
57
|
-
import {
|
|
58
|
-
Combobox,
|
|
59
|
-
ComboboxContent,
|
|
60
|
-
ComboboxEmpty,
|
|
61
|
-
ComboboxInput,
|
|
62
|
-
ComboboxItem,
|
|
63
|
-
ComboboxList,
|
|
64
|
-
} from '~/components/ui/combobox'
|
|
65
|
-
import {
|
|
66
|
-
DropdownMenu,
|
|
67
|
-
DropdownMenuCheckboxItem,
|
|
68
|
-
DropdownMenuContent,
|
|
69
|
-
DropdownMenuGroup,
|
|
70
|
-
DropdownMenuItem,
|
|
71
|
-
DropdownMenuLabel,
|
|
72
|
-
DropdownMenuPortal,
|
|
73
|
-
DropdownMenuRadioGroup,
|
|
74
|
-
DropdownMenuRadioItem,
|
|
75
|
-
DropdownMenuSeparator,
|
|
76
|
-
DropdownMenuShortcut,
|
|
77
|
-
DropdownMenuSub,
|
|
78
|
-
DropdownMenuSubContent,
|
|
79
|
-
DropdownMenuSubTrigger,
|
|
80
|
-
DropdownMenuTrigger,
|
|
81
|
-
} from '~/components/ui/dropdown-menu'
|
|
82
|
-
import { Field, FieldGroup, FieldLabel } from '~/components/ui/field'
|
|
83
|
-
import { Input } from '~/components/ui/input'
|
|
84
|
-
import {
|
|
85
|
-
Select,
|
|
86
|
-
SelectContent,
|
|
87
|
-
SelectGroup,
|
|
88
|
-
SelectItem,
|
|
89
|
-
SelectTrigger,
|
|
90
|
-
SelectValue,
|
|
91
|
-
} from '~/components/ui/select'
|
|
92
|
-
import { Textarea } from '~/components/ui/textarea'
|
|
93
6
|
import { ThemeToggle } from '~/components/ui/theme-toggle'
|
|
94
7
|
|
|
95
8
|
export function ComponentExample() {
|
|
96
|
-
|
|
97
|
-
<ExampleWrapper>
|
|
98
|
-
<ThemeToggle className="absolute w-md:top-10 right-10 top-2" />
|
|
99
|
-
<CardExample />
|
|
100
|
-
<FormExample />
|
|
101
|
-
</ExampleWrapper>
|
|
102
|
-
)
|
|
103
|
-
}
|
|
9
|
+
const { data: session } = useSession()
|
|
104
10
|
|
|
105
|
-
function CardExample() {
|
|
106
11
|
return (
|
|
107
|
-
<
|
|
108
|
-
<
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
Switch to the improved way to explore your data, with natural language. Monitoring will
|
|
120
|
-
no longer be available on the Pro plan in November, 2025
|
|
121
|
-
</CardDescription>
|
|
122
|
-
</CardHeader>
|
|
123
|
-
<CardFooter>
|
|
124
|
-
<AlertDialog>
|
|
125
|
-
<AlertDialogTrigger render={<Button />}>
|
|
126
|
-
<PlusIcon data-icon="inline-start" />
|
|
127
|
-
Show Dialog
|
|
128
|
-
</AlertDialogTrigger>
|
|
129
|
-
<AlertDialogContent size="sm">
|
|
130
|
-
<AlertDialogHeader>
|
|
131
|
-
<AlertDialogMedia>
|
|
132
|
-
<BluetoothIcon />
|
|
133
|
-
</AlertDialogMedia>
|
|
134
|
-
<AlertDialogTitle>Allow accessory to connect?</AlertDialogTitle>
|
|
135
|
-
<AlertDialogDescription>
|
|
136
|
-
Do you want to allow the USB accessory to connect to this device?
|
|
137
|
-
</AlertDialogDescription>
|
|
138
|
-
</AlertDialogHeader>
|
|
139
|
-
<AlertDialogFooter>
|
|
140
|
-
<AlertDialogCancel>Don't allow</AlertDialogCancel>
|
|
141
|
-
<AlertDialogAction>Allow</AlertDialogAction>
|
|
142
|
-
</AlertDialogFooter>
|
|
143
|
-
</AlertDialogContent>
|
|
144
|
-
</AlertDialog>
|
|
145
|
-
<Badge variant="secondary" className="ml-auto">
|
|
146
|
-
Warning
|
|
147
|
-
</Badge>
|
|
148
|
-
</CardFooter>
|
|
149
|
-
</Card>
|
|
150
|
-
</Example>
|
|
151
|
-
)
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const frameworks = ['Next.js', 'SvelteKit', 'Nuxt.js', 'Remix', 'Astro'] as const
|
|
12
|
+
<div className="bg-background flex min-h-screen flex-col items-center justify-center gap-8 p-6 text-center">
|
|
13
|
+
<div className="absolute top-4 right-4 flex items-center gap-2">
|
|
14
|
+
{session?.user && (
|
|
15
|
+
<>
|
|
16
|
+
<div className="bg-muted flex size-8 items-center justify-center rounded-full">
|
|
17
|
+
<UserIcon className="text-muted-foreground size-4" />
|
|
18
|
+
</div>
|
|
19
|
+
<span className="text-sm font-medium">{session.user.name}</span>
|
|
20
|
+
</>
|
|
21
|
+
)}
|
|
22
|
+
<ThemeToggle />
|
|
23
|
+
</div>
|
|
155
24
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
25
|
+
<div className="flex flex-col items-center gap-4">
|
|
26
|
+
<div className="bg-foreground text-background flex size-14 items-center justify-center rounded-2xl">
|
|
27
|
+
<TerminalIcon className="size-7" />
|
|
28
|
+
</div>
|
|
29
|
+
<div className="flex flex-col gap-1">
|
|
30
|
+
<h1 className="text-3xl font-bold tracking-tight">create-z3-app</h1>
|
|
31
|
+
<p className="text-muted-foreground max-w-sm text-base">
|
|
32
|
+
A full-stack starter with TanStack Start, Convex, and Better Auth — ready to ship.
|
|
33
|
+
</p>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
162
36
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
37
|
+
<div className="flex gap-3">
|
|
38
|
+
{session?.user ? (
|
|
39
|
+
<Button variant="outline" onClick={() => signOut()}>Sign out</Button>
|
|
40
|
+
) : (
|
|
41
|
+
<>
|
|
42
|
+
<Button nativeButton={false} render={<Link to="/auth/$authView" params={{ authView: 'sign-up' }} />}>Sign up</Button>
|
|
43
|
+
<Button nativeButton={false} variant="outline" render={<Link to="/auth/$authView" params={{ authView: 'sign-in' }} />}>Sign in</Button>
|
|
44
|
+
</>
|
|
45
|
+
)}
|
|
46
|
+
</div>
|
|
170
47
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
<CardTitle>User Information</CardTitle>
|
|
176
|
-
<CardDescription>Please fill in your details below</CardDescription>
|
|
177
|
-
<CardAction>
|
|
178
|
-
<DropdownMenu>
|
|
179
|
-
<DropdownMenuTrigger render={<Button variant="ghost" size="icon" />}>
|
|
180
|
-
<MoreVerticalIcon />
|
|
181
|
-
<span className="sr-only">More options</span>
|
|
182
|
-
</DropdownMenuTrigger>
|
|
183
|
-
<DropdownMenuContent align="end" className="w-56">
|
|
184
|
-
<DropdownMenuGroup>
|
|
185
|
-
<DropdownMenuLabel>File</DropdownMenuLabel>
|
|
186
|
-
<DropdownMenuItem>
|
|
187
|
-
<FileIcon />
|
|
188
|
-
New File
|
|
189
|
-
<DropdownMenuShortcut>⌘N</DropdownMenuShortcut>
|
|
190
|
-
</DropdownMenuItem>
|
|
191
|
-
<DropdownMenuItem>
|
|
192
|
-
<FolderIcon />
|
|
193
|
-
New Folder
|
|
194
|
-
<DropdownMenuShortcut>⇧⌘N</DropdownMenuShortcut>
|
|
195
|
-
</DropdownMenuItem>
|
|
196
|
-
<DropdownMenuSub>
|
|
197
|
-
<DropdownMenuSubTrigger>
|
|
198
|
-
<FolderOpenIcon />
|
|
199
|
-
Open Recent
|
|
200
|
-
</DropdownMenuSubTrigger>
|
|
201
|
-
<DropdownMenuPortal>
|
|
202
|
-
<DropdownMenuSubContent>
|
|
203
|
-
<DropdownMenuGroup>
|
|
204
|
-
<DropdownMenuLabel>Recent Projects</DropdownMenuLabel>
|
|
205
|
-
<DropdownMenuItem>
|
|
206
|
-
<FileCodeIcon />
|
|
207
|
-
Project Alpha
|
|
208
|
-
</DropdownMenuItem>
|
|
209
|
-
<DropdownMenuItem>
|
|
210
|
-
<FileCodeIcon />
|
|
211
|
-
Project Beta
|
|
212
|
-
</DropdownMenuItem>
|
|
213
|
-
<DropdownMenuSub>
|
|
214
|
-
<DropdownMenuSubTrigger>
|
|
215
|
-
<MoreHorizontalIcon />
|
|
216
|
-
More Projects
|
|
217
|
-
</DropdownMenuSubTrigger>
|
|
218
|
-
<DropdownMenuPortal>
|
|
219
|
-
<DropdownMenuSubContent>
|
|
220
|
-
<DropdownMenuItem>
|
|
221
|
-
<FileCodeIcon />
|
|
222
|
-
Project Gamma
|
|
223
|
-
</DropdownMenuItem>
|
|
224
|
-
<DropdownMenuItem>
|
|
225
|
-
<FileCodeIcon />
|
|
226
|
-
Project Delta
|
|
227
|
-
</DropdownMenuItem>
|
|
228
|
-
</DropdownMenuSubContent>
|
|
229
|
-
</DropdownMenuPortal>
|
|
230
|
-
</DropdownMenuSub>
|
|
231
|
-
</DropdownMenuGroup>
|
|
232
|
-
<DropdownMenuSeparator />
|
|
233
|
-
<DropdownMenuGroup>
|
|
234
|
-
<DropdownMenuItem>
|
|
235
|
-
<FolderSearchIcon />
|
|
236
|
-
Browse...
|
|
237
|
-
</DropdownMenuItem>
|
|
238
|
-
</DropdownMenuGroup>
|
|
239
|
-
</DropdownMenuSubContent>
|
|
240
|
-
</DropdownMenuPortal>
|
|
241
|
-
</DropdownMenuSub>
|
|
242
|
-
<DropdownMenuSeparator />
|
|
243
|
-
<DropdownMenuItem>
|
|
244
|
-
<SaveIcon />
|
|
245
|
-
Save
|
|
246
|
-
<DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
|
|
247
|
-
</DropdownMenuItem>
|
|
248
|
-
<DropdownMenuItem>
|
|
249
|
-
<DownloadIcon />
|
|
250
|
-
Export
|
|
251
|
-
<DropdownMenuShortcut>⇧⌘E</DropdownMenuShortcut>
|
|
252
|
-
</DropdownMenuItem>
|
|
253
|
-
</DropdownMenuGroup>
|
|
254
|
-
<DropdownMenuSeparator />
|
|
255
|
-
<DropdownMenuGroup>
|
|
256
|
-
<DropdownMenuLabel>View</DropdownMenuLabel>
|
|
257
|
-
<DropdownMenuCheckboxItem
|
|
258
|
-
checked={notifications.email}
|
|
259
|
-
onCheckedChange={(checked) =>
|
|
260
|
-
setNotifications({
|
|
261
|
-
...notifications,
|
|
262
|
-
email: checked === true,
|
|
263
|
-
})
|
|
264
|
-
}
|
|
265
|
-
>
|
|
266
|
-
<EyeIcon />
|
|
267
|
-
Show Sidebar
|
|
268
|
-
</DropdownMenuCheckboxItem>
|
|
269
|
-
<DropdownMenuCheckboxItem
|
|
270
|
-
checked={notifications.sms}
|
|
271
|
-
onCheckedChange={(checked) =>
|
|
272
|
-
setNotifications({
|
|
273
|
-
...notifications,
|
|
274
|
-
sms: checked === true,
|
|
275
|
-
})
|
|
276
|
-
}
|
|
277
|
-
>
|
|
278
|
-
<LayoutIcon />
|
|
279
|
-
Show Status Bar
|
|
280
|
-
</DropdownMenuCheckboxItem>
|
|
281
|
-
<DropdownMenuSub>
|
|
282
|
-
<DropdownMenuSubTrigger>
|
|
283
|
-
<PaletteIcon />
|
|
284
|
-
Theme
|
|
285
|
-
</DropdownMenuSubTrigger>
|
|
286
|
-
<DropdownMenuPortal>
|
|
287
|
-
<DropdownMenuSubContent>
|
|
288
|
-
<DropdownMenuGroup>
|
|
289
|
-
<DropdownMenuLabel>Appearance</DropdownMenuLabel>
|
|
290
|
-
<DropdownMenuRadioGroup value={theme} onValueChange={setTheme}>
|
|
291
|
-
<DropdownMenuRadioItem value="light">
|
|
292
|
-
<SunIcon />
|
|
293
|
-
Light
|
|
294
|
-
</DropdownMenuRadioItem>
|
|
295
|
-
<DropdownMenuRadioItem value="dark">
|
|
296
|
-
<MoonIcon />
|
|
297
|
-
Dark
|
|
298
|
-
</DropdownMenuRadioItem>
|
|
299
|
-
<DropdownMenuRadioItem value="system">
|
|
300
|
-
<MonitorIcon />
|
|
301
|
-
System
|
|
302
|
-
</DropdownMenuRadioItem>
|
|
303
|
-
</DropdownMenuRadioGroup>
|
|
304
|
-
</DropdownMenuGroup>
|
|
305
|
-
</DropdownMenuSubContent>
|
|
306
|
-
</DropdownMenuPortal>
|
|
307
|
-
</DropdownMenuSub>
|
|
308
|
-
</DropdownMenuGroup>
|
|
309
|
-
<DropdownMenuSeparator />
|
|
310
|
-
<DropdownMenuGroup>
|
|
311
|
-
<DropdownMenuLabel>Account</DropdownMenuLabel>
|
|
312
|
-
<DropdownMenuItem>
|
|
313
|
-
<UserIcon />
|
|
314
|
-
Profile
|
|
315
|
-
<DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
|
|
316
|
-
</DropdownMenuItem>
|
|
317
|
-
<DropdownMenuItem>
|
|
318
|
-
<CreditCardIcon />
|
|
319
|
-
Billing
|
|
320
|
-
</DropdownMenuItem>
|
|
321
|
-
<DropdownMenuSub>
|
|
322
|
-
<DropdownMenuSubTrigger>
|
|
323
|
-
<SettingsIcon />
|
|
324
|
-
Settings
|
|
325
|
-
</DropdownMenuSubTrigger>
|
|
326
|
-
<DropdownMenuPortal>
|
|
327
|
-
<DropdownMenuSubContent>
|
|
328
|
-
<DropdownMenuGroup>
|
|
329
|
-
<DropdownMenuLabel>Preferences</DropdownMenuLabel>
|
|
330
|
-
<DropdownMenuItem>
|
|
331
|
-
<KeyboardIcon />
|
|
332
|
-
Keyboard Shortcuts
|
|
333
|
-
</DropdownMenuItem>
|
|
334
|
-
<DropdownMenuItem>
|
|
335
|
-
<LanguagesIcon />
|
|
336
|
-
Language
|
|
337
|
-
</DropdownMenuItem>
|
|
338
|
-
<DropdownMenuSub>
|
|
339
|
-
<DropdownMenuSubTrigger>
|
|
340
|
-
<BellIcon />
|
|
341
|
-
Notifications
|
|
342
|
-
</DropdownMenuSubTrigger>
|
|
343
|
-
<DropdownMenuPortal>
|
|
344
|
-
<DropdownMenuSubContent>
|
|
345
|
-
<DropdownMenuGroup>
|
|
346
|
-
<DropdownMenuLabel>Notification Types</DropdownMenuLabel>
|
|
347
|
-
<DropdownMenuCheckboxItem
|
|
348
|
-
checked={notifications.push}
|
|
349
|
-
onCheckedChange={(checked) =>
|
|
350
|
-
setNotifications({
|
|
351
|
-
...notifications,
|
|
352
|
-
push: checked === true,
|
|
353
|
-
})
|
|
354
|
-
}
|
|
355
|
-
>
|
|
356
|
-
<BellIcon />
|
|
357
|
-
Push Notifications
|
|
358
|
-
</DropdownMenuCheckboxItem>
|
|
359
|
-
<DropdownMenuCheckboxItem
|
|
360
|
-
checked={notifications.email}
|
|
361
|
-
onCheckedChange={(checked) =>
|
|
362
|
-
setNotifications({
|
|
363
|
-
...notifications,
|
|
364
|
-
email: checked === true,
|
|
365
|
-
})
|
|
366
|
-
}
|
|
367
|
-
>
|
|
368
|
-
<MailIcon />
|
|
369
|
-
Email Notifications
|
|
370
|
-
</DropdownMenuCheckboxItem>
|
|
371
|
-
</DropdownMenuGroup>
|
|
372
|
-
</DropdownMenuSubContent>
|
|
373
|
-
</DropdownMenuPortal>
|
|
374
|
-
</DropdownMenuSub>
|
|
375
|
-
</DropdownMenuGroup>
|
|
376
|
-
<DropdownMenuSeparator />
|
|
377
|
-
<DropdownMenuGroup>
|
|
378
|
-
<DropdownMenuItem>
|
|
379
|
-
<ShieldIcon />
|
|
380
|
-
Privacy & Security
|
|
381
|
-
</DropdownMenuItem>
|
|
382
|
-
</DropdownMenuGroup>
|
|
383
|
-
</DropdownMenuSubContent>
|
|
384
|
-
</DropdownMenuPortal>
|
|
385
|
-
</DropdownMenuSub>
|
|
386
|
-
</DropdownMenuGroup>
|
|
387
|
-
<DropdownMenuSeparator />
|
|
388
|
-
<DropdownMenuGroup>
|
|
389
|
-
<DropdownMenuItem>
|
|
390
|
-
<HelpCircleIcon />
|
|
391
|
-
Help & Support
|
|
392
|
-
</DropdownMenuItem>
|
|
393
|
-
<DropdownMenuItem>
|
|
394
|
-
<FileTextIcon />
|
|
395
|
-
Documentation
|
|
396
|
-
</DropdownMenuItem>
|
|
397
|
-
</DropdownMenuGroup>
|
|
398
|
-
<DropdownMenuSeparator />
|
|
399
|
-
<DropdownMenuGroup>
|
|
400
|
-
<DropdownMenuItem variant="destructive">
|
|
401
|
-
<LogOutIcon />
|
|
402
|
-
Sign Out
|
|
403
|
-
<DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
|
|
404
|
-
</DropdownMenuItem>
|
|
405
|
-
</DropdownMenuGroup>
|
|
406
|
-
</DropdownMenuContent>
|
|
407
|
-
</DropdownMenu>
|
|
408
|
-
</CardAction>
|
|
409
|
-
</CardHeader>
|
|
410
|
-
<CardContent>
|
|
411
|
-
<form>
|
|
412
|
-
<FieldGroup>
|
|
413
|
-
<div className="grid grid-cols-2 gap-4">
|
|
414
|
-
<Field>
|
|
415
|
-
<FieldLabel htmlFor="small-form-name">Name</FieldLabel>
|
|
416
|
-
<Input id="small-form-name" placeholder="Enter your name" required />
|
|
417
|
-
</Field>
|
|
418
|
-
<Field>
|
|
419
|
-
<FieldLabel htmlFor="small-form-role">Role</FieldLabel>
|
|
420
|
-
<Select items={roleItems} defaultValue={null}>
|
|
421
|
-
<SelectTrigger id="small-form-role">
|
|
422
|
-
<SelectValue />
|
|
423
|
-
</SelectTrigger>
|
|
424
|
-
<SelectContent>
|
|
425
|
-
<SelectGroup>
|
|
426
|
-
{roleItems.map((item) => (
|
|
427
|
-
<SelectItem key={item.value} value={item.value}>
|
|
428
|
-
{item.label}
|
|
429
|
-
</SelectItem>
|
|
430
|
-
))}
|
|
431
|
-
</SelectGroup>
|
|
432
|
-
</SelectContent>
|
|
433
|
-
</Select>
|
|
434
|
-
</Field>
|
|
435
|
-
</div>
|
|
436
|
-
<Field>
|
|
437
|
-
<FieldLabel htmlFor="small-form-framework">Framework</FieldLabel>
|
|
438
|
-
<Combobox items={frameworks}>
|
|
439
|
-
<ComboboxInput
|
|
440
|
-
id="small-form-framework"
|
|
441
|
-
placeholder="Select a framework"
|
|
442
|
-
required
|
|
443
|
-
/>
|
|
444
|
-
<ComboboxContent>
|
|
445
|
-
<ComboboxEmpty>No frameworks found.</ComboboxEmpty>
|
|
446
|
-
<ComboboxList>
|
|
447
|
-
{(item) => (
|
|
448
|
-
<ComboboxItem key={item} value={item}>
|
|
449
|
-
{item}
|
|
450
|
-
</ComboboxItem>
|
|
451
|
-
)}
|
|
452
|
-
</ComboboxList>
|
|
453
|
-
</ComboboxContent>
|
|
454
|
-
</Combobox>
|
|
455
|
-
</Field>
|
|
456
|
-
<Field>
|
|
457
|
-
<FieldLabel htmlFor="small-form-comments">Comments</FieldLabel>
|
|
458
|
-
<Textarea id="small-form-comments" placeholder="Add any additional comments" />
|
|
459
|
-
</Field>
|
|
460
|
-
<Field orientation="horizontal">
|
|
461
|
-
<Button type="submit">Submit</Button>
|
|
462
|
-
<Button variant="outline" type="button">
|
|
463
|
-
Cancel
|
|
464
|
-
</Button>
|
|
465
|
-
</Field>
|
|
466
|
-
</FieldGroup>
|
|
467
|
-
</form>
|
|
468
|
-
</CardContent>
|
|
469
|
-
</Card>
|
|
470
|
-
</Example>
|
|
48
|
+
<code className="bg-muted text-muted-foreground rounded-lg px-4 py-2 text-sm font-mono">
|
|
49
|
+
npm create z3-app@latest
|
|
50
|
+
</code>
|
|
51
|
+
</div>
|
|
471
52
|
)
|
|
472
53
|
}
|
|
@@ -6,3 +6,4 @@ export const TABLE_SLUG_ACCOUNTS = "account" as const;
|
|
|
6
6
|
export const TABLE_SLUG_SESSIONS = "session" as const;
|
|
7
7
|
export const TABLE_SLUG_VERIFICATIONS = "verification" as const;
|
|
8
8
|
export const TABLE_SLUG_JWKS = "jwks" as const;
|
|
9
|
+
export const TABLE_SLUG_API_KEYS = "apikey" as const;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createAuthClient } from "better-auth/react"
|
|
2
|
-
import { adminClient
|
|
2
|
+
import { adminClient } from "better-auth/client/plugins"
|
|
3
|
+
import { apiKeyClient } from "@better-auth/api-key/client"
|
|
3
4
|
import { env } from '~/env';
|
|
4
5
|
|
|
5
6
|
export const authClient = createAuthClient({
|
|
@@ -80,9 +80,9 @@ export function hasPermission<Resource extends keyof Permissions>({
|
|
|
80
80
|
resource: Resource;
|
|
81
81
|
user?: null | User;
|
|
82
82
|
}): boolean {
|
|
83
|
-
if (!user?.
|
|
83
|
+
if (!user?.roles) { return false; }
|
|
84
84
|
|
|
85
|
-
return user.
|
|
85
|
+
return user.roles.some((role) => {
|
|
86
86
|
const permission = (ROLES as RolesWithPermissions)[role as UserRole][resource]?.[action]
|
|
87
87
|
|
|
88
88
|
if (!permission) { return false }
|
|
@@ -8,7 +8,7 @@ export const Route = createFileRoute('/auth/$authView')({
|
|
|
8
8
|
function RouteComponent() {
|
|
9
9
|
const { authView } = Route.useParams()
|
|
10
10
|
return (
|
|
11
|
-
<main className="
|
|
11
|
+
<main className="mx-auto flex h-[100svh] grow flex-col items-center justify-center gap-3 self-center p-4 md:p-6">
|
|
12
12
|
<AuthView pathname={authView} />
|
|
13
13
|
</main>
|
|
14
14
|
)
|