create-vexcms 0.0.20 → 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.
- package/LICENSE +202 -0
- package/README.md +93 -83
- package/dist/index.js +246 -81
- package/dist/index.mjs +30976 -0
- package/package.json +35 -15
- package/templates/base-nextjs/README.md +53 -205
- package/templates/base-nextjs/_env.example +7 -9
- package/templates/base-nextjs/_gitignore +41 -1
- package/templates/base-nextjs/convex/_generated/api.d.ts +6 -62
- package/templates/base-nextjs/convex/_generated/server.d.ts +19 -0
- package/templates/base-nextjs/convex/_generated/server.js +8 -0
- package/templates/base-nextjs/convex/auth/api.ts +49 -3
- package/templates/base-nextjs/convex/auth/db.ts +42 -300
- package/templates/base-nextjs/convex/auth/index.ts +11 -7
- package/templates/base-nextjs/convex/auth/options.ts +5 -29
- package/templates/base-nextjs/convex/auth/plugins/index.ts +15 -5
- package/templates/base-nextjs/convex/auth/sessions.ts +9 -15
- package/templates/base-nextjs/convex/http.ts +18 -3
- package/templates/base-nextjs/convex/schema.ts +104 -2
- package/templates/base-nextjs/convex/vex/firstUser.ts +10 -84
- package/templates/base-nextjs/convex/vex/globals.ts +19 -82
- package/templates/base-nextjs/convex/vex/media.ts +19 -246
- package/templates/base-nextjs/convex/vex.schema.ts +138 -0
- package/templates/base-nextjs/convex/vex.ts +19 -0
- package/templates/base-nextjs/convex.json +6 -0
- package/templates/base-nextjs/eslint.config.mjs +1 -21
- package/templates/base-nextjs/next.config.ts +10 -0
- package/templates/base-nextjs/package.json +47 -48
- package/templates/base-nextjs/src/app/(frontend)/@auth/(...)auth/[pathname]/view.tsx +17 -5
- package/templates/base-nextjs/src/app/(frontend)/auth/[pathname]/view.tsx +2 -4
- package/templates/base-nextjs/src/app/(frontend)/layout.tsx +12 -2
- package/templates/base-nextjs/src/app/(frontend)/page.tsx +2 -113
- package/templates/base-nextjs/src/app/(vexcms)/admin/[[...path]]/page.tsx +40 -0
- package/templates/base-nextjs/src/app/(vexcms)/admin/clientProviders.tsx +22 -0
- package/templates/base-nextjs/src/app/(vexcms)/admin/layout.tsx +19 -0
- package/templates/base-nextjs/src/app/globals.css +240 -104
- package/templates/base-nextjs/src/app/layout.tsx +23 -13
- package/templates/base-nextjs/src/app/unauthorized/page.tsx +24 -0
- package/templates/base-nextjs/src/auth/access.ts +46 -0
- package/templates/base-nextjs/src/auth/client.tsx +17 -5
- package/templates/base-nextjs/src/auth/hasPermission.ts +39 -0
- package/templates/base-nextjs/src/auth/serverUtils.ts +15 -25
- package/templates/base-nextjs/src/components/WelcomePage.tsx +116 -0
- package/templates/base-nextjs/src/components/component-example.tsx +56 -464
- package/templates/base-nextjs/src/components/providers/auth.tsx +13 -0
- package/templates/base-nextjs/src/components/providers/client.tsx +6 -1
- package/templates/base-nextjs/src/components/providers/convex.tsx +53 -21
- package/templates/base-nextjs/src/components/providers/server.tsx +10 -7
- package/templates/{marketing-site → base-nextjs}/src/components/ui/accordion.tsx +30 -32
- package/templates/base-nextjs/src/components/ui/badge.tsx +10 -13
- package/templates/base-nextjs/src/components/ui/input.tsx +35 -5
- package/templates/base-nextjs/src/context/AuthContext.tsx +29 -0
- package/templates/base-nextjs/src/db/constants/auth.ts +0 -1
- package/templates/base-nextjs/src/db/constants/index.ts +22 -35
- package/templates/base-nextjs/src/lib/utils.ts +0 -12
- package/templates/base-nextjs/src/proxy.ts +97 -12
- package/templates/base-nextjs/src/vex.config.ts +35 -0
- package/templates/base-nextjs/src/vex.types.ts +293 -0
- package/templates/base-nextjs/src/vexcms/collections/images.ts +15 -0
- package/templates/base-nextjs/src/vexcms/collections/index.ts +2 -1
- package/templates/base-nextjs/src/vexcms/collections/users.ts +7 -35
- package/templates/base-nextjs/tsconfig.json +3 -0
- package/templates/marketing-site/convex/_generated/api.d.ts +81 -0
- package/templates/marketing-site/convex/_generated/api.js +23 -0
- package/templates/marketing-site/convex/_generated/dataModel.d.ts +60 -0
- package/templates/marketing-site/convex/_generated/server.d.ts +162 -0
- package/templates/marketing-site/convex/_generated/server.js +101 -0
- package/templates/marketing-site/convex/footers.ts +11 -7
- package/templates/marketing-site/convex/headers.ts +15 -5
- package/templates/marketing-site/convex/pages.ts +20 -35
- package/templates/marketing-site/convex/schema.ts +119 -0
- package/templates/marketing-site/convex/seed.ts +582 -35
- package/templates/marketing-site/convex/siteSettings.ts +19 -5
- package/templates/marketing-site/convex/theme.ts +55 -61
- package/templates/marketing-site/convex/vex.schema.ts +355 -0
- package/templates/marketing-site/src/app/(frontend)/PageContent.tsx +27 -160
- package/templates/marketing-site/src/app/(frontend)/[slug]/page.tsx +6 -9
- package/templates/marketing-site/src/app/(frontend)/layout.tsx +4 -2
- package/templates/marketing-site/src/app/(frontend)/page.tsx +2 -5
- package/templates/marketing-site/src/components/SiteFooter.tsx +6 -3
- package/templates/marketing-site/src/components/SiteHeader.tsx +6 -3
- package/templates/marketing-site/src/components/ThemeLive.tsx +59 -0
- package/templates/marketing-site/src/components/ThemeStyle.tsx +45 -89
- package/templates/marketing-site/src/lib/metadata.ts +51 -72
- package/templates/marketing-site/src/vex.config.ts +35 -0
- package/templates/marketing-site/src/vex.types.ts +634 -0
- package/templates/marketing-site/src/vexcms/api.ts +23 -0
- package/templates/marketing-site/src/vexcms/blocks/CTA/config.ts +3 -3
- package/templates/marketing-site/src/vexcms/blocks/CTA/index.tsx +2 -2
- package/templates/marketing-site/src/vexcms/blocks/FAQ/config.ts +4 -4
- package/templates/marketing-site/src/vexcms/blocks/FAQ/index.tsx +2 -2
- package/templates/marketing-site/src/vexcms/blocks/Features/config.ts +4 -8
- package/templates/marketing-site/src/vexcms/blocks/Features/index.tsx +2 -2
- package/templates/marketing-site/src/vexcms/blocks/Footer/config.ts +7 -11
- package/templates/marketing-site/src/vexcms/blocks/Footer/index.tsx +1 -1
- package/templates/marketing-site/src/vexcms/blocks/Header/config.ts +9 -9
- package/templates/marketing-site/src/vexcms/blocks/Header/index.tsx +4 -5
- package/templates/marketing-site/src/vexcms/blocks/Hero/config.ts +3 -4
- package/templates/marketing-site/src/vexcms/blocks/Hero/index.tsx +10 -19
- package/templates/marketing-site/src/vexcms/blocks/HowItWorks/config.ts +4 -9
- package/templates/marketing-site/src/vexcms/blocks/HowItWorks/index.tsx +3 -6
- package/templates/marketing-site/src/vexcms/blocks/Roadmap/config.ts +28 -68
- package/templates/marketing-site/src/vexcms/blocks/Roadmap/index.tsx +13 -43
- package/templates/marketing-site/src/vexcms/blocks/config.ts +7 -22
- package/templates/marketing-site/src/vexcms/blocks/constants.ts +2 -3
- package/templates/marketing-site/src/vexcms/blocks/index.ts +9 -8
- package/templates/marketing-site/src/vexcms/collections/footers.ts +1 -1
- package/templates/marketing-site/src/vexcms/collections/headers.ts +1 -1
- package/templates/marketing-site/src/vexcms/collections/index.ts +2 -1
- package/templates/marketing-site/src/vexcms/collections/pages.ts +12 -21
- package/templates/marketing-site/src/vexcms/collections/themeColors.ts +118 -0
- package/templates/marketing-site/src/vexcms/collections/themes.ts +29 -111
- package/templates/marketing-site/src/vexcms/globals/index.ts +1 -1
- package/templates/marketing-site/src/vexcms/globals/siteSettings.ts +43 -27
- package/templates/base-nextjs/convex/auth/adapter/index.ts +0 -230
- package/templates/base-nextjs/convex/auth/adapter/utils.ts +0 -547
- package/templates/base-nextjs/convex/auth/config.ts +0 -10
- package/templates/base-nextjs/convex/vex/auth.ts +0 -38
- package/templates/base-nextjs/convex/vex/collections.ts +0 -333
- package/templates/base-nextjs/convex/vex/helpers.ts +0 -33
- package/templates/base-nextjs/convex/vex/impersonation.ts +0 -51
- package/templates/base-nextjs/convex/vex/migrate.ts +0 -74
- package/templates/base-nextjs/convex/vex/model/collections.ts +0 -196
- package/templates/base-nextjs/convex/vex/model/media.ts +0 -87
- package/templates/base-nextjs/convex/vex/model/versions.ts +0 -254
- package/templates/base-nextjs/convex/vex/previewSnapshot.ts +0 -53
- package/templates/base-nextjs/convex/vex/versions.ts +0 -648
- package/templates/base-nextjs/src/app/admin/AdminLayoutWrapper.tsx +0 -33
- package/templates/base-nextjs/src/app/admin/AdminPageWrapper.tsx +0 -38
- package/templates/base-nextjs/src/app/admin/RichTextFieldWithMedia.tsx +0 -101
- package/templates/base-nextjs/src/app/admin/[[...path]]/page.tsx +0 -15
- package/templates/base-nextjs/src/app/admin/layout.tsx +0 -51
- package/templates/base-nextjs/src/app/favicon.ico +0 -0
- package/templates/base-nextjs/src/components/providers/theme.tsx +0 -11
- package/templates/base-nextjs/src/components/ui/theme-toggle.tsx +0 -67
- package/templates/base-nextjs/src/db/types.ts +0 -6
- package/templates/base-nextjs/src/vexcms/access.ts +0 -44
- package/templates/base-nextjs/src/vexcms/auth.ts +0 -4
- package/templates/base-nextjs/src/vexcms/collections/media.ts +0 -15
- package/templates/base-nextjs/vex.config.ts +0 -25
- package/templates/marketing-site/public/favicons/favicon.ico +0 -0
- package/templates/marketing-site/src/app/(frontend)/preview/[slug]/PreviewPageContent.tsx +0 -55
- package/templates/marketing-site/src/app/(frontend)/preview/[slug]/page.tsx +0 -27
- package/templates/marketing-site/src/app/(frontend)/preview/layout.tsx +0 -18
- package/templates/marketing-site/src/app/admin/AdminLayoutWrapper.tsx +0 -71
- package/templates/marketing-site/src/components/ThemeInjector.tsx +0 -128
- package/templates/marketing-site/src/components/admin/IconPickerField.tsx +0 -134
- package/templates/marketing-site/src/components/admin/ThemeImport.tsx +0 -174
- package/templates/marketing-site/src/components/admin/ThemeImportField.tsx +0 -33
- package/templates/marketing-site/src/db/constants/index.ts +0 -52
- package/templates/marketing-site/src/lib/colorConvert.ts +0 -171
- package/templates/marketing-site/src/vexcms/access.ts +0 -36
- package/templates/marketing-site/vex.config.ts +0 -32
|
@@ -1,474 +1,66 @@
|
|
|
1
|
-
"use client"
|
|
1
|
+
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
<
|
|
110
|
-
<
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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'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
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
|
9
|
+
return (
|
|
10
|
+
<ConvexClientProvider>
|
|
11
|
+
<BetterAuthClientProvider>{children}</BetterAuthClientProvider>
|
|
12
|
+
</ConvexClientProvider>
|
|
13
|
+
)
|
|
9
14
|
}
|
|
@@ -1,32 +1,64 @@
|
|
|
1
|
-
"use client"
|
|
1
|
+
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import { ConvexQueryClient } from "@convex-dev/react-query"
|
|
5
|
-
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
|
|
6
|
-
import { ConvexReactClient } from "convex/react"
|
|
7
|
-
import { type ReactNode } from "react"
|
|
3
|
+
import type { ReactNode } from "react";
|
|
8
4
|
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
5
|
+
import { ConvexBetterAuthProvider } from "@convex-dev/better-auth/react";
|
|
6
|
+
import { ConvexQueryClient } from "@convex-dev/react-query";
|
|
7
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
8
|
+
import { ConvexReactClient } from "convex/react";
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
10
|
+
import { authClient } from "~/auth/client";
|
|
11
|
+
import { env } from "~/env.mjs";
|
|
12
|
+
|
|
13
|
+
function makeClients() {
|
|
14
|
+
const convex = new ConvexReactClient(env.NEXT_PUBLIC_CONVEX_URL, { expectAuth: true });
|
|
15
|
+
const convexQueryClient = new ConvexQueryClient(convex);
|
|
16
|
+
const queryClient = new QueryClient({
|
|
17
|
+
defaultOptions: {
|
|
18
|
+
queries: {
|
|
19
|
+
queryFn: convexQueryClient.queryFn(),
|
|
20
|
+
queryKeyHashFn: convexQueryClient.hashFn(),
|
|
21
|
+
// Deterministic: a thrown ConvexError re-fails identically, so retrying
|
|
22
|
+
// only delays the error reaching `error` (it sits in `failureReason`
|
|
23
|
+
// while retrying).
|
|
24
|
+
retry: false,
|
|
25
|
+
// Convex queries ride the Convex WebSocket, not `fetch`, so TanStack's
|
|
26
|
+
// `onlineManager` must never gate them: a paused query stays
|
|
27
|
+
// `status: "pending"` / `error: null` forever and ignores `retry`.
|
|
28
|
+
networkMode: "always",
|
|
29
|
+
},
|
|
21
30
|
},
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
31
|
+
});
|
|
32
|
+
convexQueryClient.connect(queryClient);
|
|
33
|
+
return { convex, queryClient };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let browserClients: ReturnType<typeof makeClients> | undefined;
|
|
25
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Wires Convex, TanStack Query, and Better Auth together for the app.
|
|
40
|
+
*
|
|
41
|
+
* Clients are module-level singletons connected synchronously so Convex
|
|
42
|
+
* reactive subscriptions are established before any query is added to the
|
|
43
|
+
* TanStack Query cache. See module-level comment above.
|
|
44
|
+
*
|
|
45
|
+
* @param props.children - The subtree that needs Convex and query access.
|
|
46
|
+
*/
|
|
26
47
|
export default function ConvexClientProvider({ children }: { children: ReactNode }) {
|
|
48
|
+
const { convex, queryClient } = getClients();
|
|
27
49
|
return (
|
|
28
50
|
<ConvexBetterAuthProvider authClient={authClient} client={convex}>
|
|
29
51
|
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
|
30
52
|
</ConvexBetterAuthProvider>
|
|
31
|
-
)
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function getClients() {
|
|
57
|
+
if (typeof window === "undefined") {
|
|
58
|
+
// Server: brand-new clients every request → no cross-request cache leak
|
|
59
|
+
return makeClients();
|
|
60
|
+
}
|
|
61
|
+
// Browser: singleton → preserves Convex WebSocket reactivity
|
|
62
|
+
browserClients ??= makeClients();
|
|
63
|
+
return browserClients;
|
|
32
64
|
}
|
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { ThemeProvider } from "@vexcms/react";
|
|
2
|
+
import { NuqsAdapter } from "nuqs/adapters/next/app";
|
|
3
|
+
import { type PropsWithChildren } from "react";
|
|
3
4
|
|
|
4
|
-
import
|
|
5
|
-
import
|
|
5
|
+
import { AuthServerProvider } from "./auth";
|
|
6
|
+
import ConvexClientProvider from "./convex";
|
|
6
7
|
|
|
7
8
|
export default function ServerProviders({ children }: PropsWithChildren) {
|
|
8
9
|
return (
|
|
9
|
-
<ThemeProvider
|
|
10
|
+
<ThemeProvider defaultTheme="system">
|
|
10
11
|
<ConvexClientProvider>
|
|
11
|
-
<
|
|
12
|
+
<AuthServerProvider>
|
|
13
|
+
<NuqsAdapter>{children}</NuqsAdapter>
|
|
14
|
+
</AuthServerProvider>
|
|
12
15
|
</ConvexClientProvider>
|
|
13
16
|
</ThemeProvider>
|
|
14
|
-
)
|
|
17
|
+
);
|
|
15
18
|
}
|