create-vexcms 0.1.0-alpha.2 → 0.1.0-alpha.21
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/README.md +48 -14
- package/dist/index.js +1714 -1504
- package/package.json +2 -2
- package/templates/base-nextjs/README.md +18 -5
- package/templates/base-nextjs/convex/auth/index.ts +2 -2
- package/templates/base-nextjs/convex/auth/options.ts +27 -26
- package/templates/base-nextjs/convex/auth/schema.ts +51 -0
- package/templates/base-nextjs/convex/auth/sessions.ts +3 -3
- package/templates/base-nextjs/convex/tsconfig.json +2 -2
- package/templates/base-nextjs/convex/vex/firstUser.ts +2 -2
- package/templates/base-nextjs/convex/vex/globals.ts +3 -2
- package/templates/base-nextjs/convex/vex/media.ts +3 -2
- package/templates/base-nextjs/convex/vex.schema.ts +1 -0
- package/templates/base-nextjs/convex/vex.ts +17 -4
- package/templates/base-nextjs/next.config.ts +52 -6
- package/templates/base-nextjs/package.json +18 -7
- package/templates/base-nextjs/public/favicons/favicon.ico +0 -0
- package/templates/base-nextjs/src/app/(frontend)/@auth/(...)auth/[pathname]/view.tsx +27 -15
- package/templates/base-nextjs/src/app/(vexcms)/admin/[[...path]]/page.tsx +2 -2
- package/templates/base-nextjs/src/app/(vexcms)/admin/clientProviders.tsx +9 -14
- package/templates/base-nextjs/src/app/(vexcms)/admin/layout.tsx +8 -6
- package/templates/base-nextjs/src/app/api/vex/revalidate/route.ts +19 -0
- package/templates/base-nextjs/src/app/globals.css +10 -9
- package/templates/base-nextjs/src/app/layout.tsx +6 -0
- package/templates/base-nextjs/src/auth/access.ts +14 -2
- package/templates/base-nextjs/src/auth/serverUtils.ts +20 -6
- package/templates/base-nextjs/src/components/WelcomePage.tsx +54 -43
- package/templates/base-nextjs/src/components/component-example.tsx +1 -1
- package/templates/base-nextjs/src/components/providers/server.tsx +16 -8
- package/templates/base-nextjs/src/vex.config.server.ts +30 -0
- package/templates/base-nextjs/src/vex.config.ts +14 -11
- package/templates/base-nextjs/src/vex.types.ts +2 -1
- package/templates/base-nextjs/src/vexcms/collections/images.ts +1 -1
- package/templates/base-nextjs/src/vexcms/collections/users.ts +3 -1
- package/templates/marketing-site/convex/pages.ts +20 -1
- package/templates/marketing-site/convex/schema.ts +4 -4
- package/templates/marketing-site/convex/seed.ts +515 -70
- package/templates/marketing-site/convex/siteSettings.ts +1 -1
- package/templates/marketing-site/convex/theme.ts +3 -3
- package/templates/marketing-site/convex/vex.schema.ts +75 -38
- package/templates/marketing-site/public/favicon.svg +14 -0
- package/templates/marketing-site/public/favicons/apple-touch-icon.png +0 -0
- package/templates/marketing-site/public/favicons/favicon.ico +0 -0
- package/templates/marketing-site/src/app/(frontend)/(site)/PageContent.tsx +64 -0
- package/templates/marketing-site/src/app/(frontend)/(site)/SiteLivePreviewProvider.tsx +31 -0
- package/templates/marketing-site/src/app/(frontend)/(site)/[slug]/page.tsx +63 -0
- package/templates/marketing-site/src/app/(frontend)/(site)/layout.tsx +50 -0
- package/templates/marketing-site/src/app/(frontend)/(site)/page.tsx +33 -0
- package/templates/marketing-site/src/app/(frontend)/layout.tsx +10 -24
- package/templates/marketing-site/src/app/(vexcms)/admin/layout.tsx +32 -0
- package/templates/marketing-site/src/app/globals.css +413 -0
- package/templates/marketing-site/src/app/layout.tsx +85 -0
- package/templates/marketing-site/src/app/robots.ts +13 -0
- package/templates/marketing-site/src/app/sitemap.ts +18 -0
- package/templates/marketing-site/src/components/BrandMark.tsx +27 -0
- package/templates/marketing-site/src/components/CodeHighlightContext.tsx +76 -0
- package/templates/marketing-site/src/components/CodePane.tsx +98 -0
- package/templates/marketing-site/src/components/Container.tsx +25 -0
- package/templates/marketing-site/src/components/CopyButton.tsx +80 -0
- package/templates/marketing-site/src/components/FirstAdminBootstrap.tsx +54 -0
- package/templates/marketing-site/src/components/InstallCommand.tsx +29 -0
- package/templates/marketing-site/src/components/MediaImage.tsx +59 -0
- package/templates/marketing-site/src/components/SectionHeader.tsx +51 -0
- package/templates/marketing-site/src/components/SiteFooter.tsx +8 -6
- package/templates/marketing-site/src/components/SiteHeader.tsx +8 -6
- package/templates/marketing-site/src/components/ThemeLive.tsx +1 -2
- package/templates/marketing-site/src/components/ThemeStyle.tsx +3 -3
- package/templates/marketing-site/src/components/motion-primitives/animated-group.tsx +44 -120
- package/templates/marketing-site/src/components/motion-primitives/reveal.module.css +32 -0
- package/templates/marketing-site/src/components/motion-primitives/text-effect.tsx +47 -266
- package/templates/marketing-site/src/lib/codeHighlightKey.ts +27 -0
- package/templates/marketing-site/src/lib/highlight.ts +134 -0
- package/templates/marketing-site/src/lib/highlightClient.ts +37 -0
- package/templates/marketing-site/src/lib/metadata.ts +61 -33
- package/templates/marketing-site/src/lib/resolvePagePath.ts +8 -0
- package/templates/marketing-site/src/lib/vex.ts +18 -0
- package/templates/marketing-site/src/proxy.ts +179 -0
- package/templates/marketing-site/src/vex.config.server.ts +27 -0
- package/templates/marketing-site/src/vex.config.ts +27 -9
- package/templates/marketing-site/src/vex.types.ts +102 -76
- package/templates/marketing-site/src/vexcms/api.ts +2 -2
- package/templates/marketing-site/src/vexcms/blocks/CTA/config.ts +3 -2
- package/templates/marketing-site/src/vexcms/blocks/CTA/index.tsx +40 -24
- package/templates/marketing-site/src/vexcms/blocks/CodeShowcase/config.ts +62 -0
- package/templates/marketing-site/src/vexcms/blocks/CodeShowcase/index.tsx +106 -0
- package/templates/marketing-site/src/vexcms/blocks/FAQ/config.ts +4 -3
- package/templates/marketing-site/src/vexcms/blocks/FAQ/index.tsx +63 -50
- package/templates/marketing-site/src/vexcms/blocks/Features/config.ts +1 -0
- package/templates/marketing-site/src/vexcms/blocks/Features/index.tsx +43 -58
- package/templates/marketing-site/src/vexcms/blocks/Footer/config.ts +6 -3
- package/templates/marketing-site/src/vexcms/blocks/Footer/index.tsx +90 -53
- package/templates/marketing-site/src/vexcms/blocks/Header/config.ts +10 -3
- package/templates/marketing-site/src/vexcms/blocks/Header/index.tsx +205 -138
- package/templates/marketing-site/src/vexcms/blocks/Hero/config.ts +27 -12
- package/templates/marketing-site/src/vexcms/blocks/Hero/index.tsx +174 -119
- package/templates/marketing-site/src/vexcms/blocks/HowItWorks/config.ts +2 -1
- package/templates/marketing-site/src/vexcms/blocks/HowItWorks/index.tsx +59 -51
- package/templates/marketing-site/src/vexcms/blocks/Roadmap/config.ts +26 -5
- package/templates/marketing-site/src/vexcms/blocks/Roadmap/index.tsx +256 -73
- package/templates/marketing-site/src/vexcms/blocks/Split/config.ts +62 -0
- package/templates/marketing-site/src/vexcms/blocks/Split/index.tsx +186 -0
- package/templates/marketing-site/src/vexcms/blocks/Stats/config.ts +35 -0
- package/templates/marketing-site/src/vexcms/blocks/Stats/index.tsx +77 -0
- package/templates/marketing-site/src/vexcms/blocks/config.ts +27 -11
- package/templates/marketing-site/src/vexcms/blocks/constants.ts +3 -0
- package/templates/marketing-site/src/vexcms/blocks/index.ts +9 -0
- package/templates/marketing-site/src/vexcms/collections/footers.ts +1 -0
- package/templates/marketing-site/src/vexcms/collections/headers.ts +1 -0
- package/templates/marketing-site/src/vexcms/collections/pages.ts +23 -1
- package/templates/marketing-site/src/vexcms/collections/themeColors.ts +14 -14
- package/templates/marketing-site/src/vexcms/collections/users.ts +20 -0
- package/dist/index.mjs +0 -30976
- package/templates/marketing-site/src/app/(frontend)/PageContent.tsx +0 -53
- package/templates/marketing-site/src/app/(frontend)/[slug]/page.tsx +0 -25
- package/templates/marketing-site/src/app/(frontend)/page.tsx +0 -21
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { BlockComponentProps } from "@vexcms/react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "@vexcms/react"
|
|
4
|
+
|
|
5
|
+
import type { StatsBlock } from "~/vex.types"
|
|
6
|
+
|
|
7
|
+
import { Container } from "~/components/Container"
|
|
8
|
+
import { SectionHeader } from "~/components/SectionHeader"
|
|
9
|
+
|
|
10
|
+
export { statsBlock } from "./config"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
/** Beyond this length the value stops being a headline number and has to
|
|
14
|
+
* step down a size to keep the cell from breaking. */
|
|
15
|
+
const LONG_VALUE_CHARS = 6
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Stats — four numbers on a raised band.
|
|
19
|
+
*
|
|
20
|
+
* Shorter than a standard block on purpose: this is a caption on the hero,
|
|
21
|
+
* not a section. Cells are separated by 1px dividers rather than gaps, and
|
|
22
|
+
* values are top-aligned per cell rather than baseline-aligned to each other,
|
|
23
|
+
* so `"0"`, `"12"` and `"~30s"` all sit correctly next to one another.
|
|
24
|
+
*/
|
|
25
|
+
export default function StatsBlock({ block }: BlockComponentProps) {
|
|
26
|
+
const { heading, items, subheading } = block as StatsBlock
|
|
27
|
+
const stats = items ?? []
|
|
28
|
+
|
|
29
|
+
if (stats.length === 0) {return null}
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<section className="border-y border-border bg-card py-10 md:py-14 xl:py-16">
|
|
33
|
+
<Container>
|
|
34
|
+
<SectionHeader heading={heading} subheading={subheading} />
|
|
35
|
+
|
|
36
|
+
<dl
|
|
37
|
+
className={cn(
|
|
38
|
+
"grid grid-cols-1 divide-y divide-border md:divide-y-0",
|
|
39
|
+
stats.length > 1 && "md:grid-cols-2 md:divide-x xl:grid-cols-4"
|
|
40
|
+
)}
|
|
41
|
+
>
|
|
42
|
+
{stats.map((stat, index) => (
|
|
43
|
+
<div
|
|
44
|
+
className={cn(
|
|
45
|
+
"flex flex-col items-start py-6 md:px-8 md:py-0",
|
|
46
|
+
// The first cell of each row owns the left edge, so it must not
|
|
47
|
+
// draw a divider; at md that is every other cell, at xl every
|
|
48
|
+
// fourth. Rows after the first take a top rule instead.
|
|
49
|
+
"md:[&:nth-child(odd)]:border-l-0 xl:[&:nth-child(odd)]:border-l xl:[&:nth-child(4n+1)]:border-l-0",
|
|
50
|
+
index >= 2 && "md:border-t md:border-border xl:border-t-0",
|
|
51
|
+
index >= 4 && "xl:border-t xl:border-border"
|
|
52
|
+
)}
|
|
53
|
+
key={`${stat.label}-${index}`}
|
|
54
|
+
>
|
|
55
|
+
<dd
|
|
56
|
+
className={cn(
|
|
57
|
+
"font-extrabold tracking-[-0.04em] tabular-nums text-foreground",
|
|
58
|
+
stat.value.length > LONG_VALUE_CHARS
|
|
59
|
+
? "text-3xl md:text-4xl"
|
|
60
|
+
: "text-[40px] leading-none md:text-5xl xl:text-[56px]"
|
|
61
|
+
)}
|
|
62
|
+
>
|
|
63
|
+
{stat.value}
|
|
64
|
+
</dd>
|
|
65
|
+
<dt className="mt-3.5 text-[15px] font-semibold text-foreground">{stat.label}</dt>
|
|
66
|
+
{stat.description ? (
|
|
67
|
+
<p className="mt-2 max-w-[30ch] text-[13.5px] leading-[1.55] text-muted-foreground">
|
|
68
|
+
{stat.description}
|
|
69
|
+
</p>
|
|
70
|
+
) : null}
|
|
71
|
+
</div>
|
|
72
|
+
))}
|
|
73
|
+
</dl>
|
|
74
|
+
</Container>
|
|
75
|
+
</section>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
@@ -1,25 +1,41 @@
|
|
|
1
|
+
import { codeShowcaseBlock } from "./CodeShowcase/config"
|
|
2
|
+
import { ctaBlock } from "./CTA/config"
|
|
3
|
+
import { faqBlock } from "./FAQ/config"
|
|
4
|
+
import { featuresBlock } from "./Features/config"
|
|
5
|
+
import { footerBlock } from "./Footer/config"
|
|
6
|
+
import { headerBlock } from "./Header/config"
|
|
1
7
|
// Block configs only — no React/motion dependencies. Safe to import from
|
|
2
8
|
// collections, vex.config.ts, and convex/seed.ts.
|
|
3
9
|
import { heroBlock } from "./Hero/config"
|
|
4
|
-
import { featuresBlock } from "./Features/config"
|
|
5
10
|
import { howItWorksBlock } from "./HowItWorks/config"
|
|
6
11
|
import { roadmapBlock } from "./Roadmap/config"
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { headerBlock } from "./Header/config"
|
|
10
|
-
import { footerBlock } from "./Footer/config"
|
|
11
|
-
|
|
12
|
-
export const pageBlocks = [heroBlock, featuresBlock, howItWorksBlock, roadmapBlock, ctaBlock, faqBlock]
|
|
13
|
-
export const headerBlocks = [headerBlock]
|
|
14
|
-
export const footerBlocks = [footerBlock]
|
|
12
|
+
import { splitBlock } from "./Split/config"
|
|
13
|
+
import { statsBlock } from "./Stats/config"
|
|
15
14
|
|
|
16
|
-
export
|
|
15
|
+
export const pageBlocks = [
|
|
17
16
|
heroBlock,
|
|
17
|
+
statsBlock,
|
|
18
18
|
featuresBlock,
|
|
19
|
+
codeShowcaseBlock,
|
|
20
|
+
splitBlock,
|
|
19
21
|
howItWorksBlock,
|
|
20
22
|
roadmapBlock,
|
|
23
|
+
faqBlock,
|
|
24
|
+
ctaBlock,
|
|
25
|
+
]
|
|
26
|
+
export const headerBlocks = [headerBlock]
|
|
27
|
+
export const footerBlocks = [footerBlock]
|
|
28
|
+
|
|
29
|
+
export {
|
|
30
|
+
codeShowcaseBlock,
|
|
21
31
|
ctaBlock,
|
|
22
32
|
faqBlock,
|
|
23
|
-
|
|
33
|
+
featuresBlock,
|
|
24
34
|
footerBlock,
|
|
35
|
+
headerBlock,
|
|
36
|
+
heroBlock,
|
|
37
|
+
howItWorksBlock,
|
|
38
|
+
roadmapBlock,
|
|
39
|
+
splitBlock,
|
|
40
|
+
statsBlock,
|
|
25
41
|
}
|
|
@@ -6,3 +6,6 @@ export const BLOCK_SLUG_CTA = "cta" as const
|
|
|
6
6
|
export const BLOCK_SLUG_FAQ = "faq" as const
|
|
7
7
|
export const BLOCK_SLUG_HEADER = "header" as const
|
|
8
8
|
export const BLOCK_SLUG_FOOTER = "footer" as const
|
|
9
|
+
export const BLOCK_SLUG_CODE_SHOWCASE = "code_showcase" as const
|
|
10
|
+
export const BLOCK_SLUG_STATS = "stats" as const
|
|
11
|
+
export const BLOCK_SLUG_SPLIT = "split" as const
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { BlockComponentProps } from "@vexcms/react"
|
|
2
2
|
import type { ComponentType } from "react"
|
|
3
3
|
|
|
4
|
+
import CodeShowcaseBlock from "./CodeShowcase"
|
|
4
5
|
import {
|
|
6
|
+
BLOCK_SLUG_CODE_SHOWCASE,
|
|
5
7
|
BLOCK_SLUG_CTA,
|
|
6
8
|
BLOCK_SLUG_FAQ,
|
|
7
9
|
BLOCK_SLUG_FEATURES,
|
|
@@ -10,6 +12,8 @@ import {
|
|
|
10
12
|
BLOCK_SLUG_HERO,
|
|
11
13
|
BLOCK_SLUG_HOW_IT_WORKS,
|
|
12
14
|
BLOCK_SLUG_ROADMAP,
|
|
15
|
+
BLOCK_SLUG_SPLIT,
|
|
16
|
+
BLOCK_SLUG_STATS,
|
|
13
17
|
} from "./constants"
|
|
14
18
|
import CTABlock from "./CTA"
|
|
15
19
|
import FAQBlock from "./FAQ"
|
|
@@ -19,6 +23,8 @@ import HeaderBlock from "./Header"
|
|
|
19
23
|
import HeroBlock from "./Hero"
|
|
20
24
|
import HowItWorksBlock from "./HowItWorks"
|
|
21
25
|
import RoadmapBlock from "./Roadmap"
|
|
26
|
+
import SplitBlock from "./Split"
|
|
27
|
+
import StatsBlock from "./Stats"
|
|
22
28
|
|
|
23
29
|
/**
|
|
24
30
|
* Block component map for `RenderBlocks` (Contract 1). Loosely typed as
|
|
@@ -27,6 +33,7 @@ import RoadmapBlock from "./Roadmap"
|
|
|
27
33
|
* against this config, so this file can't reference it.
|
|
28
34
|
*/
|
|
29
35
|
export const blockComponents: Record<string, ComponentType<BlockComponentProps>> = {
|
|
36
|
+
[BLOCK_SLUG_CODE_SHOWCASE]: CodeShowcaseBlock,
|
|
30
37
|
[BLOCK_SLUG_CTA]: CTABlock,
|
|
31
38
|
[BLOCK_SLUG_FAQ]: FAQBlock,
|
|
32
39
|
[BLOCK_SLUG_FEATURES]: FeaturesBlock,
|
|
@@ -35,6 +42,8 @@ export const blockComponents: Record<string, ComponentType<BlockComponentProps>>
|
|
|
35
42
|
[BLOCK_SLUG_HERO]: HeroBlock,
|
|
36
43
|
[BLOCK_SLUG_HOW_IT_WORKS]: HowItWorksBlock,
|
|
37
44
|
[BLOCK_SLUG_ROADMAP]: RoadmapBlock,
|
|
45
|
+
[BLOCK_SLUG_SPLIT]: SplitBlock,
|
|
46
|
+
[BLOCK_SLUG_STATS]: StatsBlock,
|
|
38
47
|
}
|
|
39
48
|
|
|
40
49
|
export { footerBlocks, headerBlocks, pageBlocks } from "./config"
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { DataModel } from "@convex/_generated/dataModel"
|
|
2
2
|
|
|
3
|
+
import { blocks, defineCollection, text, textValidator, upload } from "@vexcms/core"
|
|
4
|
+
|
|
5
|
+
import { resolvePagePath } from "~/lib/resolvePagePath"
|
|
3
6
|
import { TABLE_SLUG_IMAGES, TABLE_SLUG_PAGES } from "~/db/constants"
|
|
4
7
|
import { pageBlocks } from "~/vexcms/blocks/config"
|
|
5
8
|
|
|
@@ -8,6 +11,10 @@ export const pages = defineCollection({
|
|
|
8
11
|
admin: {
|
|
9
12
|
table: { defaultColumns: ["title", "slug", "_id"] },
|
|
10
13
|
useAsTitle: "title",
|
|
14
|
+
icon: "FileText",
|
|
15
|
+
livePreview: {
|
|
16
|
+
url: (doc) => resolvePagePath(doc.slug),
|
|
17
|
+
},
|
|
11
18
|
},
|
|
12
19
|
fields: {
|
|
13
20
|
title: text({
|
|
@@ -19,8 +26,23 @@ export const pages = defineCollection({
|
|
|
19
26
|
index: "by_slug",
|
|
20
27
|
label: "Slug",
|
|
21
28
|
required: true,
|
|
29
|
+
validate: textValidator<typeof TABLE_SLUG_PAGES, DataModel>(
|
|
30
|
+
TABLE_SLUG_PAGES,
|
|
31
|
+
async ({ value, doc, ctx }) => {
|
|
32
|
+
const existing = await ctx.db
|
|
33
|
+
.query("pages")
|
|
34
|
+
.withIndex("by_slug", (q) => q.eq("slug", value))
|
|
35
|
+
.first()
|
|
36
|
+
if (existing && existing._id !== doc._id) {
|
|
37
|
+
return `This slug is already in use by the page '${existing.title}'.`
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
),
|
|
22
41
|
}),
|
|
23
42
|
blocks: blocks({
|
|
43
|
+
admin: {
|
|
44
|
+
defaultCollapsed: true,
|
|
45
|
+
},
|
|
24
46
|
blocks: pageBlocks,
|
|
25
47
|
interfaceName: "PageBlock",
|
|
26
48
|
label: "Content",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
color,
|
|
3
|
-
THEME_COLOR_TOKENS,
|
|
4
3
|
type ColorField,
|
|
4
|
+
THEME_COLOR_TOKENS,
|
|
5
5
|
type ThemeColorTokenKey,
|
|
6
6
|
} from "@vexcms/core";
|
|
7
7
|
|
|
@@ -81,19 +81,6 @@ const EMBER: Record<"dark" | "light", Record<ThemeColorTokenKey, string>> = {
|
|
|
81
81
|
},
|
|
82
82
|
};
|
|
83
83
|
|
|
84
|
-
/**
|
|
85
|
-
* Derives a human label from a token key: `"cardForeground"` → `"Card
|
|
86
|
-
* Foreground"`, `"chart1"` → `"Chart 1"`.
|
|
87
|
-
*
|
|
88
|
-
* @param props - Input props.
|
|
89
|
-
* @param props.key - The camelCase token key.
|
|
90
|
-
* @returns The admin form label.
|
|
91
|
-
*/
|
|
92
|
-
function tokenLabel(props: { key: string }): string {
|
|
93
|
-
const spaced = props.key.replace(/([A-Z])/g, " $1").replace(/(\d+)/g, " $1");
|
|
94
|
-
return spaced.charAt(0).toUpperCase() + spaced.slice(1);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
84
|
/**
|
|
98
85
|
* The shadcn design-token set as `color()` fields for one colour scheme.
|
|
99
86
|
*
|
|
@@ -116,3 +103,16 @@ export function themeColors(mode: "dark" | "light"): Record<ThemeColorTokenKey,
|
|
|
116
103
|
}
|
|
117
104
|
return fields;
|
|
118
105
|
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Derives a human label from a token key: `"cardForeground"` → `"Card
|
|
109
|
+
* Foreground"`, `"chart1"` → `"Chart 1"`.
|
|
110
|
+
*
|
|
111
|
+
* @param props - Input props.
|
|
112
|
+
* @param props.key - The camelCase token key.
|
|
113
|
+
* @returns The admin form label.
|
|
114
|
+
*/
|
|
115
|
+
function tokenLabel(props: { key: string }): string {
|
|
116
|
+
const spaced = props.key.replace(/([A-Z])/g, " $1").replace(/(\d+)/g, " $1");
|
|
117
|
+
return spaced.charAt(0).toUpperCase() + spaced.slice(1);
|
|
118
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { defineCollection, text } from "@vexcms/core"
|
|
2
|
+
|
|
3
|
+
import { TABLE_SLUG_USERS } from "~/db/constants"
|
|
4
|
+
|
|
5
|
+
export const users = defineCollection({
|
|
6
|
+
slug: TABLE_SLUG_USERS,
|
|
7
|
+
admin: {
|
|
8
|
+
useAsTitle: "name",
|
|
9
|
+
icon: "Users",
|
|
10
|
+
},
|
|
11
|
+
labels: {
|
|
12
|
+
singular: "User",
|
|
13
|
+
plural: "Users",
|
|
14
|
+
},
|
|
15
|
+
fields: {
|
|
16
|
+
name: text({ label: "Name", required: true }),
|
|
17
|
+
// Add custom user fields here — the auth adapter already contributes
|
|
18
|
+
// name/email/roles/etc. from Better Auth's schema.
|
|
19
|
+
},
|
|
20
|
+
})
|