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.
Files changed (27) hide show
  1. package/LICENSE +21 -0
  2. package/dist/index.js +39 -38
  3. package/package.json +11 -11
  4. package/templates/nextjs/convex/auth/adapter/index.ts +7 -22
  5. package/templates/nextjs/convex/auth/index.ts +1 -1
  6. package/templates/nextjs/convex/auth/plugins/index.ts +6 -2
  7. package/templates/nextjs/convex/auth/sessions.ts +1 -1
  8. package/templates/nextjs/convex/auth.config.ts +7 -0
  9. package/templates/nextjs/convex/schema.ts +30 -2
  10. package/templates/nextjs/package.json +3 -2
  11. package/templates/nextjs/src/app/(frontend)/layout.tsx +21 -27
  12. package/templates/nextjs/src/auth/client.tsx +2 -4
  13. package/templates/nextjs/src/auth/permissions.ts +2 -2
  14. package/templates/nextjs/src/components/component-example.tsx +44 -492
  15. package/templates/nextjs/src/db/constants/index.ts +1 -0
  16. package/templates/tanstack-start/convex/auth/adapter/index.ts +7 -22
  17. package/templates/tanstack-start/convex/auth/index.ts +1 -1
  18. package/templates/tanstack-start/convex/auth/plugins/index.ts +5 -1
  19. package/templates/tanstack-start/convex/auth/sessions.ts +1 -1
  20. package/templates/tanstack-start/convex/auth.config.ts +7 -0
  21. package/templates/tanstack-start/convex/schema.ts +37 -2
  22. package/templates/tanstack-start/package.json +3 -2
  23. package/templates/tanstack-start/src/components/component-example.tsx +41 -460
  24. package/templates/tanstack-start/src/db/constants/index.ts +1 -0
  25. package/templates/tanstack-start/src/lib/auth/client.ts +2 -1
  26. package/templates/tanstack-start/src/lib/auth/permissions.ts +2 -2
  27. 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 { TABLE_SLUG_ACCOUNTS, TABLE_SLUG_JWKS, TABLE_SLUG_SESSIONS, TABLE_SLUG_USERS, TABLE_SLUG_VERIFICATIONS } from "~/db/constants";
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.array(v.string()), // admin plugin
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
- "@convex-dev/better-auth": "^0.10.9",
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.4.9",
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 * as React from 'react'
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 { Example, ExampleWrapper } from '~/components/example'
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
- return (
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
- <Example title="Card" className="items-center justify-center">
108
- <Card className="relative w-full max-w-sm overflow-hidden pt-0">
109
- <div className="bg-primary absolute inset-0 z-30 aspect-video opacity-50 mix-blend-color" />
110
- <img
111
- src="https://images.unsplash.com/photo-1604076850742-4c7221f3101b?q=80&w=1887&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
112
- alt="Photo by mymind on Unsplash"
113
- title="Photo by mymind on Unsplash"
114
- className="relative z-20 aspect-video w-full object-cover brightness-60 grayscale"
115
- />
116
- <CardHeader>
117
- <CardTitle>Observability Plus is replacing Monitoring</CardTitle>
118
- <CardDescription>
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&apos;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
- const roleItems = [
157
- { label: 'Developer', value: 'developer' },
158
- { label: 'Designer', value: 'designer' },
159
- { label: 'Manager', value: 'manager' },
160
- { label: 'Other', value: 'other' },
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
- function FormExample() {
164
- const [notifications, setNotifications] = React.useState({
165
- email: true,
166
- sms: false,
167
- push: true,
168
- })
169
- const [theme, setTheme] = React.useState('light')
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
- return (
172
- <Example title="Form">
173
- <Card className="w-full max-w-md">
174
- <CardHeader>
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, apiKeyClient } from "better-auth/client/plugins"
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?.role) { return false; }
83
+ if (!user?.roles) { return false; }
84
84
 
85
- return user.role.some((role) => {
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="container mx-auto flex grow flex-col items-center justify-center gap-3 self-center p-4 md:p-6">
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
  )