create-vexcms 0.0.19 → 0.1.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +93 -83
  3. package/dist/index.js +246 -81
  4. package/dist/index.mjs +30976 -0
  5. package/package.json +35 -15
  6. package/templates/base-nextjs/README.md +53 -205
  7. package/templates/base-nextjs/_env.example +7 -9
  8. package/templates/base-nextjs/_gitignore +41 -1
  9. package/templates/base-nextjs/convex/_generated/api.d.ts +6 -62
  10. package/templates/base-nextjs/convex/_generated/server.d.ts +19 -0
  11. package/templates/base-nextjs/convex/_generated/server.js +8 -0
  12. package/templates/base-nextjs/convex/auth/api.ts +49 -3
  13. package/templates/base-nextjs/convex/auth/db.ts +42 -300
  14. package/templates/base-nextjs/convex/auth/index.ts +11 -7
  15. package/templates/base-nextjs/convex/auth/options.ts +5 -29
  16. package/templates/base-nextjs/convex/auth/plugins/index.ts +15 -5
  17. package/templates/base-nextjs/convex/auth/sessions.ts +9 -15
  18. package/templates/base-nextjs/convex/http.ts +18 -3
  19. package/templates/base-nextjs/convex/schema.ts +104 -2
  20. package/templates/base-nextjs/convex/vex/firstUser.ts +10 -84
  21. package/templates/base-nextjs/convex/vex/globals.ts +19 -82
  22. package/templates/base-nextjs/convex/vex/media.ts +19 -246
  23. package/templates/base-nextjs/convex/vex.schema.ts +138 -0
  24. package/templates/base-nextjs/convex/vex.ts +19 -0
  25. package/templates/base-nextjs/convex.json +6 -0
  26. package/templates/base-nextjs/eslint.config.mjs +1 -21
  27. package/templates/base-nextjs/next.config.ts +10 -0
  28. package/templates/base-nextjs/package.json +47 -48
  29. package/templates/base-nextjs/src/app/(frontend)/@auth/(...)auth/[pathname]/view.tsx +17 -5
  30. package/templates/base-nextjs/src/app/(frontend)/auth/[pathname]/view.tsx +2 -4
  31. package/templates/base-nextjs/src/app/(frontend)/layout.tsx +12 -2
  32. package/templates/base-nextjs/src/app/(frontend)/page.tsx +2 -113
  33. package/templates/base-nextjs/src/app/(vexcms)/admin/[[...path]]/page.tsx +40 -0
  34. package/templates/base-nextjs/src/app/(vexcms)/admin/clientProviders.tsx +22 -0
  35. package/templates/base-nextjs/src/app/(vexcms)/admin/layout.tsx +19 -0
  36. package/templates/base-nextjs/src/app/globals.css +240 -104
  37. package/templates/base-nextjs/src/app/layout.tsx +23 -13
  38. package/templates/base-nextjs/src/app/unauthorized/page.tsx +24 -0
  39. package/templates/base-nextjs/src/auth/access.ts +46 -0
  40. package/templates/base-nextjs/src/auth/client.tsx +17 -5
  41. package/templates/base-nextjs/src/auth/hasPermission.ts +39 -0
  42. package/templates/base-nextjs/src/auth/serverUtils.ts +15 -22
  43. package/templates/base-nextjs/src/components/WelcomePage.tsx +116 -0
  44. package/templates/base-nextjs/src/components/component-example.tsx +56 -464
  45. package/templates/base-nextjs/src/components/providers/auth.tsx +13 -0
  46. package/templates/base-nextjs/src/components/providers/client.tsx +6 -1
  47. package/templates/base-nextjs/src/components/providers/convex.tsx +53 -21
  48. package/templates/base-nextjs/src/components/providers/server.tsx +10 -7
  49. package/templates/{marketing-site → base-nextjs}/src/components/ui/accordion.tsx +30 -32
  50. package/templates/base-nextjs/src/components/ui/badge.tsx +10 -13
  51. package/templates/base-nextjs/src/components/ui/input.tsx +35 -5
  52. package/templates/base-nextjs/src/context/AuthContext.tsx +29 -0
  53. package/templates/base-nextjs/src/db/constants/auth.ts +0 -1
  54. package/templates/base-nextjs/src/db/constants/index.ts +22 -35
  55. package/templates/base-nextjs/src/lib/utils.ts +0 -12
  56. package/templates/base-nextjs/src/proxy.ts +98 -11
  57. package/templates/base-nextjs/src/vex.config.ts +35 -0
  58. package/templates/base-nextjs/src/vex.types.ts +293 -0
  59. package/templates/base-nextjs/src/vexcms/collections/images.ts +15 -0
  60. package/templates/base-nextjs/src/vexcms/collections/index.ts +2 -1
  61. package/templates/base-nextjs/src/vexcms/collections/users.ts +7 -35
  62. package/templates/base-nextjs/tsconfig.json +3 -0
  63. package/templates/marketing-site/convex/_generated/api.d.ts +81 -0
  64. package/templates/marketing-site/convex/_generated/api.js +23 -0
  65. package/templates/marketing-site/convex/_generated/dataModel.d.ts +60 -0
  66. package/templates/marketing-site/convex/_generated/server.d.ts +162 -0
  67. package/templates/marketing-site/convex/_generated/server.js +101 -0
  68. package/templates/marketing-site/convex/footers.ts +11 -7
  69. package/templates/marketing-site/convex/headers.ts +15 -5
  70. package/templates/marketing-site/convex/pages.ts +20 -35
  71. package/templates/marketing-site/convex/schema.ts +119 -0
  72. package/templates/marketing-site/convex/seed.ts +582 -35
  73. package/templates/marketing-site/convex/siteSettings.ts +19 -5
  74. package/templates/marketing-site/convex/theme.ts +55 -61
  75. package/templates/marketing-site/convex/vex.schema.ts +355 -0
  76. package/templates/marketing-site/src/app/(frontend)/PageContent.tsx +27 -160
  77. package/templates/marketing-site/src/app/(frontend)/[slug]/page.tsx +6 -9
  78. package/templates/marketing-site/src/app/(frontend)/layout.tsx +4 -2
  79. package/templates/marketing-site/src/app/(frontend)/page.tsx +2 -5
  80. package/templates/marketing-site/src/components/SiteFooter.tsx +6 -3
  81. package/templates/marketing-site/src/components/SiteHeader.tsx +6 -3
  82. package/templates/marketing-site/src/components/ThemeLive.tsx +59 -0
  83. package/templates/marketing-site/src/components/ThemeStyle.tsx +45 -89
  84. package/templates/marketing-site/src/components/motion-primitives/animated-group.tsx +2 -2
  85. package/templates/marketing-site/src/lib/metadata.ts +51 -72
  86. package/templates/marketing-site/src/vex.config.ts +35 -0
  87. package/templates/marketing-site/src/vex.types.ts +634 -0
  88. package/templates/marketing-site/src/vexcms/api.ts +23 -0
  89. package/templates/marketing-site/src/vexcms/blocks/CTA/config.ts +3 -3
  90. package/templates/marketing-site/src/vexcms/blocks/CTA/index.tsx +2 -2
  91. package/templates/marketing-site/src/vexcms/blocks/FAQ/config.ts +4 -4
  92. package/templates/marketing-site/src/vexcms/blocks/FAQ/index.tsx +2 -2
  93. package/templates/marketing-site/src/vexcms/blocks/Features/config.ts +4 -8
  94. package/templates/marketing-site/src/vexcms/blocks/Features/index.tsx +2 -2
  95. package/templates/marketing-site/src/vexcms/blocks/Footer/config.ts +7 -11
  96. package/templates/marketing-site/src/vexcms/blocks/Footer/index.tsx +1 -1
  97. package/templates/marketing-site/src/vexcms/blocks/Header/config.ts +9 -9
  98. package/templates/marketing-site/src/vexcms/blocks/Header/index.tsx +4 -5
  99. package/templates/marketing-site/src/vexcms/blocks/Hero/config.ts +3 -4
  100. package/templates/marketing-site/src/vexcms/blocks/Hero/index.tsx +10 -19
  101. package/templates/marketing-site/src/vexcms/blocks/HowItWorks/config.ts +4 -9
  102. package/templates/marketing-site/src/vexcms/blocks/HowItWorks/index.tsx +3 -6
  103. package/templates/marketing-site/src/vexcms/blocks/Roadmap/config.ts +28 -68
  104. package/templates/marketing-site/src/vexcms/blocks/Roadmap/index.tsx +13 -43
  105. package/templates/marketing-site/src/vexcms/blocks/config.ts +7 -22
  106. package/templates/marketing-site/src/vexcms/blocks/constants.ts +2 -3
  107. package/templates/marketing-site/src/vexcms/blocks/index.ts +9 -8
  108. package/templates/marketing-site/src/vexcms/collections/footers.ts +1 -1
  109. package/templates/marketing-site/src/vexcms/collections/headers.ts +1 -1
  110. package/templates/marketing-site/src/vexcms/collections/index.ts +2 -1
  111. package/templates/marketing-site/src/vexcms/collections/pages.ts +12 -21
  112. package/templates/marketing-site/src/vexcms/collections/themeColors.ts +118 -0
  113. package/templates/marketing-site/src/vexcms/collections/themes.ts +29 -111
  114. package/templates/marketing-site/src/vexcms/globals/index.ts +1 -1
  115. package/templates/marketing-site/src/vexcms/globals/siteSettings.ts +43 -27
  116. package/templates/base-nextjs/convex/auth/adapter/index.ts +0 -230
  117. package/templates/base-nextjs/convex/auth/adapter/utils.ts +0 -547
  118. package/templates/base-nextjs/convex/auth/config.ts +0 -10
  119. package/templates/base-nextjs/convex/vex/auth.ts +0 -38
  120. package/templates/base-nextjs/convex/vex/collections.ts +0 -321
  121. package/templates/base-nextjs/convex/vex/helpers.ts +0 -33
  122. package/templates/base-nextjs/convex/vex/impersonation.ts +0 -51
  123. package/templates/base-nextjs/convex/vex/migrate.ts +0 -74
  124. package/templates/base-nextjs/convex/vex/model/collections.ts +0 -196
  125. package/templates/base-nextjs/convex/vex/model/media.ts +0 -87
  126. package/templates/base-nextjs/convex/vex/model/versions.ts +0 -254
  127. package/templates/base-nextjs/convex/vex/previewSnapshot.ts +0 -53
  128. package/templates/base-nextjs/convex/vex/versions.ts +0 -648
  129. package/templates/base-nextjs/src/app/admin/AdminLayoutWrapper.tsx +0 -33
  130. package/templates/base-nextjs/src/app/admin/AdminPageWrapper.tsx +0 -38
  131. package/templates/base-nextjs/src/app/admin/RichTextFieldWithMedia.tsx +0 -101
  132. package/templates/base-nextjs/src/app/admin/[[...path]]/page.tsx +0 -15
  133. package/templates/base-nextjs/src/app/admin/layout.tsx +0 -51
  134. package/templates/base-nextjs/src/app/favicon.ico +0 -0
  135. package/templates/base-nextjs/src/components/providers/theme.tsx +0 -11
  136. package/templates/base-nextjs/src/components/ui/theme-toggle.tsx +0 -67
  137. package/templates/base-nextjs/src/db/types.ts +0 -6
  138. package/templates/base-nextjs/src/vexcms/access.ts +0 -44
  139. package/templates/base-nextjs/src/vexcms/auth.ts +0 -4
  140. package/templates/base-nextjs/src/vexcms/collections/media.ts +0 -15
  141. package/templates/base-nextjs/vex.config.ts +0 -25
  142. package/templates/marketing-site/public/favicons/favicon.ico +0 -0
  143. package/templates/marketing-site/src/app/(frontend)/preview/[slug]/PreviewPageContent.tsx +0 -55
  144. package/templates/marketing-site/src/app/(frontend)/preview/[slug]/page.tsx +0 -27
  145. package/templates/marketing-site/src/app/(frontend)/preview/layout.tsx +0 -18
  146. package/templates/marketing-site/src/app/admin/AdminLayoutWrapper.tsx +0 -71
  147. package/templates/marketing-site/src/components/ThemeInjector.tsx +0 -128
  148. package/templates/marketing-site/src/components/admin/IconPickerField.tsx +0 -134
  149. package/templates/marketing-site/src/components/admin/ThemeImport.tsx +0 -174
  150. package/templates/marketing-site/src/components/admin/ThemeImportField.tsx +0 -33
  151. package/templates/marketing-site/src/db/constants/index.ts +0 -52
  152. package/templates/marketing-site/src/lib/colorConvert.ts +0 -171
  153. package/templates/marketing-site/src/vexcms/access.ts +0 -36
  154. package/templates/marketing-site/vex.config.ts +0 -32
@@ -0,0 +1,116 @@
1
+ "use client"
2
+
3
+ import { useMutation, useQuery } from "convex/react"
4
+ import { usePathname, useRouter } from "next/navigation"
5
+ import { useEffect, useState } from "react"
6
+
7
+ import { api } from "@convex/_generated/api"
8
+ import { useSession } from "~/auth/client"
9
+
10
+ function LoadingSpinner() {
11
+ return (
12
+ <svg className="animate-spin h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
13
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
14
+ <path
15
+ className="opacity-75"
16
+ fill="currentColor"
17
+ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
18
+ />
19
+ </svg>
20
+ )
21
+ }
22
+
23
+ /**
24
+ * Landing UI for a project that has not been bootstrapped yet (no admin user),
25
+ * and the "Go to Admin" / "Sign in" entry point once it has.
26
+ *
27
+ * Renders directly on `templates/base-nextjs`'s home route (no `pages`
28
+ * collection exists there). The marketing overlay's `PageContent` renders it
29
+ * as a fallback instead, when `pages.getBySlug("home")` returns no document.
30
+ *
31
+ * Promotes the first signed-in user to admin exactly once: `promoteFirstAdmin`
32
+ * is a Convex mutation, so concurrent signups cannot both win — Convex
33
+ * serializes mutations, and the loser observes `isBootstrapped` already true.
34
+ */
35
+ export function WelcomePage() {
36
+ const isBootstrapped = useQuery(api.vex.firstUser.isBootstrapped)
37
+ const promoteFirstAdmin = useMutation(api.vex.firstUser.promoteFirstAdmin)
38
+ const { data: session } = useSession()
39
+ const router = useRouter()
40
+ const pathname = usePathname()
41
+ const [promoting, setPromoting] = useState(false)
42
+ const [navigating, setNavigating] = useState(false)
43
+
44
+ // Reset navigating state when the user comes back to the home page
45
+ // (e.g. closing the intercepting auth dialog)
46
+ useEffect(() => {
47
+ if (pathname === "/") {
48
+ setNavigating(false)
49
+ }
50
+ }, [pathname])
51
+
52
+ // If the user is signed in and this is a fresh project, try to promote them
53
+ useEffect(() => {
54
+ if (session?.user && isBootstrapped === false && !promoting) {
55
+ setPromoting(true)
56
+ setNavigating(true)
57
+ promoteFirstAdmin()
58
+ .then((result) => {
59
+ if (result.promoted) {
60
+ router.push("/admin")
61
+ } else {
62
+ setNavigating(false)
63
+ }
64
+ })
65
+ .catch(() => {
66
+ setPromoting(false)
67
+ setNavigating(false)
68
+ })
69
+ }
70
+ }, [session, isBootstrapped, promoting, promoteFirstAdmin, router])
71
+
72
+ // Treat undefined (query still loading) as not bootstrapped — show the welcome page immediately
73
+ const bootstrapped = isBootstrapped === true
74
+
75
+ const buttonClass =
76
+ "inline-flex items-center justify-center gap-2 rounded-md bg-primary px-6 py-3 text-sm font-medium text-primary-foreground shadow hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed transition-opacity"
77
+
78
+ const handleNavigate = (path: string) => {
79
+ setNavigating(true)
80
+ router.push(path)
81
+ }
82
+
83
+ return (
84
+ <div className="flex min-h-screen flex-col items-center justify-center gap-8 p-8">
85
+ <div className="text-center">
86
+ <h1 className="text-4xl font-bold tracking-tight">VexCMS</h1>
87
+ <p className="mt-3 text-lg text-muted-foreground">
88
+ {bootstrapped
89
+ ? "Your content management system is ready."
90
+ : "Welcome! Create your admin account to get started."}
91
+ </p>
92
+ </div>
93
+
94
+ <div className="flex gap-4">
95
+ {session?.user ? (
96
+ <button onClick={() => handleNavigate("/admin")} disabled={navigating} className={buttonClass}>
97
+ {navigating && <LoadingSpinner />}
98
+ {navigating ? "Loading..." : "Go to Admin Panel"}
99
+ </button>
100
+ ) : bootstrapped ? (
101
+ <button onClick={() => handleNavigate("/auth/sign-in")} disabled={navigating} className={buttonClass}>
102
+ {navigating && <LoadingSpinner />}
103
+ {navigating ? "Loading..." : "Sign In"}
104
+ </button>
105
+ ) : (
106
+ <button onClick={() => handleNavigate("/auth/sign-up")} disabled={navigating} className={buttonClass}>
107
+ {navigating && <LoadingSpinner />}
108
+ {navigating ? "Loading..." : "Create Admin Account"}
109
+ </button>
110
+ )}
111
+ </div>
112
+
113
+ {promoting && <p className="text-sm text-muted-foreground animate-pulse">Setting up your admin account...</p>}
114
+ </div>
115
+ )
116
+ }
@@ -1,474 +1,66 @@
1
- "use client"
1
+ "use client";
2
2
 
3
- import {
4
- BellIcon,
5
- BluetoothIcon,
6
- CreditCardIcon,
7
- DownloadIcon,
8
- EyeIcon,
9
- FileCodeIcon,
10
- FileIcon,
11
- FileTextIcon,
12
- FolderIcon,
13
- FolderOpenIcon,
14
- FolderSearchIcon,
15
- HelpCircleIcon,
16
- KeyboardIcon,
17
- LanguagesIcon,
18
- LayoutIcon,
19
- LogOutIcon,
20
- MailIcon,
21
- MonitorIcon,
22
- MoonIcon,
23
- MoreHorizontalIcon,
24
- MoreVerticalIcon,
25
- PaletteIcon,
26
- PlusIcon,
27
- SaveIcon,
28
- SettingsIcon,
29
- ShieldIcon,
30
- SunIcon,
31
- UserIcon,
32
- } from "lucide-react"
33
- import * as React from "react"
3
+ import { ThemeToggle } from "@vexcms/react";
4
+ import { TerminalIcon, UserIcon } from "lucide-react";
5
+ import Link from "next/link";
34
6
 
35
- import { Example, ExampleWrapper } from "~/components/example"
36
- import {
37
- AlertDialog,
38
- AlertDialogAction,
39
- AlertDialogCancel,
40
- AlertDialogContent,
41
- AlertDialogDescription,
42
- AlertDialogFooter,
43
- AlertDialogHeader,
44
- AlertDialogMedia,
45
- AlertDialogTitle,
46
- AlertDialogTrigger,
47
- } from "~/components/ui/alert-dialog"
48
- import { Badge } from "~/components/ui/badge"
49
- import { Button } from "~/components/ui/button"
50
- import {
51
- Card,
52
- CardAction,
53
- CardContent,
54
- CardDescription,
55
- CardFooter,
56
- CardHeader,
57
- CardTitle,
58
- } from "~/components/ui/card"
59
- import {
60
- Combobox,
61
- ComboboxContent,
62
- ComboboxEmpty,
63
- ComboboxInput,
64
- ComboboxItem,
65
- ComboboxList,
66
- } from "~/components/ui/combobox"
67
- import {
68
- DropdownMenu,
69
- DropdownMenuCheckboxItem,
70
- DropdownMenuContent,
71
- DropdownMenuGroup,
72
- DropdownMenuItem,
73
- DropdownMenuLabel,
74
- DropdownMenuPortal,
75
- DropdownMenuRadioGroup,
76
- DropdownMenuRadioItem,
77
- DropdownMenuSeparator,
78
- DropdownMenuShortcut,
79
- DropdownMenuSub,
80
- DropdownMenuSubContent,
81
- DropdownMenuSubTrigger,
82
- DropdownMenuTrigger,
83
- } from "~/components/ui/dropdown-menu"
84
- import { Field, FieldGroup, FieldLabel } from "~/components/ui/field"
85
- import { Input } from "~/components/ui/input"
86
- import {
87
- Select,
88
- SelectContent,
89
- SelectGroup,
90
- SelectItem,
91
- SelectTrigger,
92
- SelectValue,
93
- } from "~/components/ui/select"
94
- import { Textarea } from "~/components/ui/textarea"
95
- import { ThemeToggle } from "~/components/ui/theme-toggle"
7
+ import { signOut, useSession } from "~/auth/client";
8
+ import { Button } from "~/components/ui/button";
96
9
 
97
10
  export function ComponentExample() {
98
- return (
99
- <ExampleWrapper>
100
- <ThemeToggle className="absolute w-md:top-10 right-10 top-2" />
101
- <CardExample />
102
- <FormExample />
103
- </ExampleWrapper>
104
- )
105
- }
11
+ const { data: session } = useSession();
106
12
 
107
- function CardExample() {
108
13
  return (
109
- <Example className="items-center justify-center" title="Card">
110
- <Card className="relative w-full max-w-sm overflow-hidden pt-0">
111
- <div className="bg-primary absolute inset-0 z-30 aspect-video opacity-50 mix-blend-color" />
112
- <img
113
- alt="Photo by mymind on Unsplash"
114
- className="relative z-20 aspect-video w-full object-cover brightness-60 grayscale"
115
- 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"
116
- title="Photo by mymind on Unsplash"
117
- />
118
- <CardHeader>
119
- <CardTitle>Observability Plus is replacing Monitoring</CardTitle>
120
- <CardDescription>
121
- Switch to the improved way to explore your data, with natural language. Monitoring will
122
- no longer be available on the Pro plan in November, 2025
123
- </CardDescription>
124
- </CardHeader>
125
- <CardFooter>
126
- <AlertDialog>
127
- <AlertDialogTrigger render={<Button />}>
128
- <PlusIcon data-icon="inline-start" />
129
- Show Dialog
130
- </AlertDialogTrigger>
131
- <AlertDialogContent size="sm">
132
- <AlertDialogHeader>
133
- <AlertDialogMedia>
134
- <BluetoothIcon />
135
- </AlertDialogMedia>
136
- <AlertDialogTitle>Allow accessory to connect?</AlertDialogTitle>
137
- <AlertDialogDescription>
138
- Do you want to allow the USB accessory to connect to this device?
139
- </AlertDialogDescription>
140
- </AlertDialogHeader>
141
- <AlertDialogFooter>
142
- <AlertDialogCancel>Don&apos;t allow</AlertDialogCancel>
143
- <AlertDialogAction>Allow</AlertDialogAction>
144
- </AlertDialogFooter>
145
- </AlertDialogContent>
146
- </AlertDialog>
147
- <Badge className="ml-auto" variant="secondary">
148
- Warning
149
- </Badge>
150
- </CardFooter>
151
- </Card>
152
- </Example>
153
- )
154
- }
14
+ <div className="bg-background flex min-h-screen flex-col items-center justify-center gap-8 p-6 text-center">
15
+ <div className="absolute top-4 right-4 flex items-center gap-2">
16
+ {session?.user && (
17
+ <>
18
+ <div className="bg-muted flex size-8 items-center justify-center rounded-full">
19
+ <UserIcon className="text-muted-foreground size-4" />
20
+ </div>
21
+ <span className="text-sm font-medium">{session.user.name}</span>
22
+ </>
23
+ )}
24
+ <ThemeToggle />
25
+ </div>
155
26
 
156
- const frameworks = ["Next.js", "SvelteKit", "Nuxt.js", "Remix", "Astro"] as const
27
+ <div className="flex flex-col items-center gap-4">
28
+ <div className="bg-foreground text-background flex size-14 items-center justify-center rounded-2xl">
29
+ <TerminalIcon className="size-7" />
30
+ </div>
31
+ <div className="flex flex-col gap-1">
32
+ <h1 className="text-3xl font-bold tracking-tight">Vex CMS</h1>
33
+ <p className="text-muted-foreground max-w-sm text-base">
34
+ The CMS built for Convex. Real-time, type-safe, developer first.
35
+ </p>
36
+ </div>
37
+ </div>
157
38
 
158
- const roleItems = [
159
- { label: "Developer", value: "developer" },
160
- { label: "Designer", value: "designer" },
161
- { label: "Manager", value: "manager" },
162
- { label: "Other", value: "other" },
163
- ]
39
+ <div className="flex gap-3">
40
+ {session?.user ? (
41
+ <div className="flex gap-4">
42
+ <Link href="/admin">
43
+ <Button>Admin</Button>
44
+ </Link>
45
+ <Button onClick={() => signOut()} variant="outline">
46
+ Sign out
47
+ </Button>
48
+ </div>
49
+ ) : (
50
+ <>
51
+ <Button nativeButton={false} render={<Link href="/auth/sign-up" />}>
52
+ Sign up
53
+ </Button>
54
+ <Button nativeButton={false} render={<Link href="/auth/sign-in" />} variant="outline">
55
+ Sign in
56
+ </Button>
57
+ </>
58
+ )}
59
+ </div>
164
60
 
165
- function FormExample() {
166
- const [notifications, setNotifications] = React.useState({
167
- email: true,
168
- push: true,
169
- sms: false,
170
- })
171
- const [theme, setTheme] = React.useState("light")
172
-
173
- return (
174
- <Example title="Form">
175
- <Card className="w-full max-w-md">
176
- <CardHeader>
177
- <CardTitle>User Information</CardTitle>
178
- <CardDescription>Please fill in your details below</CardDescription>
179
- <CardAction>
180
- <DropdownMenu>
181
- <DropdownMenuTrigger render={<Button size="icon" variant="ghost" />}>
182
- <MoreVerticalIcon />
183
- <span className="sr-only">More options</span>
184
- </DropdownMenuTrigger>
185
- <DropdownMenuContent align="end" className="w-56">
186
- <DropdownMenuGroup>
187
- <DropdownMenuLabel>File</DropdownMenuLabel>
188
- <DropdownMenuItem>
189
- <FileIcon />
190
- New File
191
- <DropdownMenuShortcut>⌘N</DropdownMenuShortcut>
192
- </DropdownMenuItem>
193
- <DropdownMenuItem>
194
- <FolderIcon />
195
- New Folder
196
- <DropdownMenuShortcut>⇧⌘N</DropdownMenuShortcut>
197
- </DropdownMenuItem>
198
- <DropdownMenuSub>
199
- <DropdownMenuSubTrigger>
200
- <FolderOpenIcon />
201
- Open Recent
202
- </DropdownMenuSubTrigger>
203
- <DropdownMenuPortal>
204
- <DropdownMenuSubContent>
205
- <DropdownMenuGroup>
206
- <DropdownMenuLabel>Recent Projects</DropdownMenuLabel>
207
- <DropdownMenuItem>
208
- <FileCodeIcon />
209
- Project Alpha
210
- </DropdownMenuItem>
211
- <DropdownMenuItem>
212
- <FileCodeIcon />
213
- Project Beta
214
- </DropdownMenuItem>
215
- <DropdownMenuSub>
216
- <DropdownMenuSubTrigger>
217
- <MoreHorizontalIcon />
218
- More Projects
219
- </DropdownMenuSubTrigger>
220
- <DropdownMenuPortal>
221
- <DropdownMenuSubContent>
222
- <DropdownMenuItem>
223
- <FileCodeIcon />
224
- Project Gamma
225
- </DropdownMenuItem>
226
- <DropdownMenuItem>
227
- <FileCodeIcon />
228
- Project Delta
229
- </DropdownMenuItem>
230
- </DropdownMenuSubContent>
231
- </DropdownMenuPortal>
232
- </DropdownMenuSub>
233
- </DropdownMenuGroup>
234
- <DropdownMenuSeparator />
235
- <DropdownMenuGroup>
236
- <DropdownMenuItem>
237
- <FolderSearchIcon />
238
- Browse...
239
- </DropdownMenuItem>
240
- </DropdownMenuGroup>
241
- </DropdownMenuSubContent>
242
- </DropdownMenuPortal>
243
- </DropdownMenuSub>
244
- <DropdownMenuSeparator />
245
- <DropdownMenuItem>
246
- <SaveIcon />
247
- Save
248
- <DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
249
- </DropdownMenuItem>
250
- <DropdownMenuItem>
251
- <DownloadIcon />
252
- Export
253
- <DropdownMenuShortcut>⇧⌘E</DropdownMenuShortcut>
254
- </DropdownMenuItem>
255
- </DropdownMenuGroup>
256
- <DropdownMenuSeparator />
257
- <DropdownMenuGroup>
258
- <DropdownMenuLabel>View</DropdownMenuLabel>
259
- <DropdownMenuCheckboxItem
260
- checked={notifications.email}
261
- onCheckedChange={(checked) =>
262
- setNotifications({
263
- ...notifications,
264
- email: checked === true,
265
- })
266
- }
267
- >
268
- <EyeIcon />
269
- Show Sidebar
270
- </DropdownMenuCheckboxItem>
271
- <DropdownMenuCheckboxItem
272
- checked={notifications.sms}
273
- onCheckedChange={(checked) =>
274
- setNotifications({
275
- ...notifications,
276
- sms: checked === true,
277
- })
278
- }
279
- >
280
- <LayoutIcon />
281
- Show Status Bar
282
- </DropdownMenuCheckboxItem>
283
- <DropdownMenuSub>
284
- <DropdownMenuSubTrigger>
285
- <PaletteIcon />
286
- Theme
287
- </DropdownMenuSubTrigger>
288
- <DropdownMenuPortal>
289
- <DropdownMenuSubContent>
290
- <DropdownMenuGroup>
291
- <DropdownMenuLabel>Appearance</DropdownMenuLabel>
292
- <DropdownMenuRadioGroup onValueChange={setTheme} value={theme}>
293
- <DropdownMenuRadioItem value="light">
294
- <SunIcon />
295
- Light
296
- </DropdownMenuRadioItem>
297
- <DropdownMenuRadioItem value="dark">
298
- <MoonIcon />
299
- Dark
300
- </DropdownMenuRadioItem>
301
- <DropdownMenuRadioItem value="system">
302
- <MonitorIcon />
303
- System
304
- </DropdownMenuRadioItem>
305
- </DropdownMenuRadioGroup>
306
- </DropdownMenuGroup>
307
- </DropdownMenuSubContent>
308
- </DropdownMenuPortal>
309
- </DropdownMenuSub>
310
- </DropdownMenuGroup>
311
- <DropdownMenuSeparator />
312
- <DropdownMenuGroup>
313
- <DropdownMenuLabel>Account</DropdownMenuLabel>
314
- <DropdownMenuItem>
315
- <UserIcon />
316
- Profile
317
- <DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
318
- </DropdownMenuItem>
319
- <DropdownMenuItem>
320
- <CreditCardIcon />
321
- Billing
322
- </DropdownMenuItem>
323
- <DropdownMenuSub>
324
- <DropdownMenuSubTrigger>
325
- <SettingsIcon />
326
- Settings
327
- </DropdownMenuSubTrigger>
328
- <DropdownMenuPortal>
329
- <DropdownMenuSubContent>
330
- <DropdownMenuGroup>
331
- <DropdownMenuLabel>Preferences</DropdownMenuLabel>
332
- <DropdownMenuItem>
333
- <KeyboardIcon />
334
- Keyboard Shortcuts
335
- </DropdownMenuItem>
336
- <DropdownMenuItem>
337
- <LanguagesIcon />
338
- Language
339
- </DropdownMenuItem>
340
- <DropdownMenuSub>
341
- <DropdownMenuSubTrigger>
342
- <BellIcon />
343
- Notifications
344
- </DropdownMenuSubTrigger>
345
- <DropdownMenuPortal>
346
- <DropdownMenuSubContent>
347
- <DropdownMenuGroup>
348
- <DropdownMenuLabel>Notification Types</DropdownMenuLabel>
349
- <DropdownMenuCheckboxItem
350
- checked={notifications.push}
351
- onCheckedChange={(checked) =>
352
- setNotifications({
353
- ...notifications,
354
- push: checked === true,
355
- })
356
- }
357
- >
358
- <BellIcon />
359
- Push Notifications
360
- </DropdownMenuCheckboxItem>
361
- <DropdownMenuCheckboxItem
362
- checked={notifications.email}
363
- onCheckedChange={(checked) =>
364
- setNotifications({
365
- ...notifications,
366
- email: checked === true,
367
- })
368
- }
369
- >
370
- <MailIcon />
371
- Email Notifications
372
- </DropdownMenuCheckboxItem>
373
- </DropdownMenuGroup>
374
- </DropdownMenuSubContent>
375
- </DropdownMenuPortal>
376
- </DropdownMenuSub>
377
- </DropdownMenuGroup>
378
- <DropdownMenuSeparator />
379
- <DropdownMenuGroup>
380
- <DropdownMenuItem>
381
- <ShieldIcon />
382
- Privacy & Security
383
- </DropdownMenuItem>
384
- </DropdownMenuGroup>
385
- </DropdownMenuSubContent>
386
- </DropdownMenuPortal>
387
- </DropdownMenuSub>
388
- </DropdownMenuGroup>
389
- <DropdownMenuSeparator />
390
- <DropdownMenuGroup>
391
- <DropdownMenuItem>
392
- <HelpCircleIcon />
393
- Help & Support
394
- </DropdownMenuItem>
395
- <DropdownMenuItem>
396
- <FileTextIcon />
397
- Documentation
398
- </DropdownMenuItem>
399
- </DropdownMenuGroup>
400
- <DropdownMenuSeparator />
401
- <DropdownMenuGroup>
402
- <DropdownMenuItem variant="destructive">
403
- <LogOutIcon />
404
- Sign Out
405
- <DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
406
- </DropdownMenuItem>
407
- </DropdownMenuGroup>
408
- </DropdownMenuContent>
409
- </DropdownMenu>
410
- </CardAction>
411
- </CardHeader>
412
- <CardContent>
413
- <form>
414
- <FieldGroup>
415
- <div className="grid grid-cols-2 gap-4">
416
- <Field>
417
- <FieldLabel htmlFor="small-form-name">Name</FieldLabel>
418
- <Input id="small-form-name" placeholder="Enter your name" required />
419
- </Field>
420
- <Field>
421
- <FieldLabel htmlFor="small-form-role">Role</FieldLabel>
422
- <Select defaultValue={null} items={roleItems}>
423
- <SelectTrigger id="small-form-role">
424
- <SelectValue />
425
- </SelectTrigger>
426
- <SelectContent>
427
- <SelectGroup>
428
- {roleItems.map((item) => (
429
- <SelectItem key={item.value} value={item.value}>
430
- {item.label}
431
- </SelectItem>
432
- ))}
433
- </SelectGroup>
434
- </SelectContent>
435
- </Select>
436
- </Field>
437
- </div>
438
- <Field>
439
- <FieldLabel htmlFor="small-form-framework">Framework</FieldLabel>
440
- <Combobox items={frameworks}>
441
- <ComboboxInput
442
- id="small-form-framework"
443
- placeholder="Select a framework"
444
- required
445
- />
446
- <ComboboxContent>
447
- <ComboboxEmpty>No frameworks found.</ComboboxEmpty>
448
- <ComboboxList>
449
- {(item) => (
450
- <ComboboxItem key={item} value={item}>
451
- {item}
452
- </ComboboxItem>
453
- )}
454
- </ComboboxList>
455
- </ComboboxContent>
456
- </Combobox>
457
- </Field>
458
- <Field>
459
- <FieldLabel htmlFor="small-form-comments">Comments</FieldLabel>
460
- <Textarea id="small-form-comments" placeholder="Add any additional comments" />
461
- </Field>
462
- <Field orientation="horizontal">
463
- <Button type="submit">Submit</Button>
464
- <Button type="button" variant="outline">
465
- Cancel
466
- </Button>
467
- </Field>
468
- </FieldGroup>
469
- </form>
470
- </CardContent>
471
- </Card>
472
- </Example>
473
- )
61
+ <code className="bg-muted text-muted-foreground rounded-lg px-4 py-2 font-mono text-sm">
62
+ npx create-vexcms@latest
63
+ </code>
64
+ </div>
65
+ );
474
66
  }
@@ -0,0 +1,13 @@
1
+ import { api } from "@convex/_generated/api";
2
+
3
+ import { fetchAuthQuery, getToken } from "~/auth/server";
4
+ import { AuthProvider } from "~/context/AuthContext";
5
+
6
+ export async function AuthServerProvider(props: { children: React.ReactNode }) {
7
+ const sessionToken = await getToken();
8
+ if (!sessionToken) {
9
+ return <AuthProvider value={{ user: null }}>{props.children}</AuthProvider>;
10
+ }
11
+ const auth = await fetchAuthQuery(api.auth.api.getUserOrg, {});
12
+ return <AuthProvider value={auth}>{props.children}</AuthProvider>;
13
+ }
@@ -3,7 +3,12 @@
3
3
  import { type PropsWithChildren } from "react"
4
4
 
5
5
  import BetterAuthClientProvider from "~/auth/client"
6
+ import ConvexClientProvider from "~/components/providers/convex"
6
7
 
7
8
  export default function ClientProviders({ children }: PropsWithChildren) {
8
- return <BetterAuthClientProvider>{children}</BetterAuthClientProvider>
9
+ return (
10
+ <ConvexClientProvider>
11
+ <BetterAuthClientProvider>{children}</BetterAuthClientProvider>
12
+ </ConvexClientProvider>
13
+ )
9
14
  }