Skip to content

Commit e5eabaa

Browse files
Merge pull request #790 from aXenDeveloper/feat/homepage-redesign-blue
feat: Add home page by fable
2 parents 4eecf79 + e186fab commit e5eabaa

88 files changed

Lines changed: 7228 additions & 923 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎apps/api/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"dev": "vitnode db:prepare && tsx watch src/index.ts",
1111
"dev:email": "email dev --dir src/emails",
1212
"build": "tsc && tsc-alias -p tsconfig.json",
13-
"start": "node dist/index.js",
13+
"start": "node dist/src/index.js",
1414
"drizzle-kit": "drizzle-kit",
1515
"lint": "eslint .",
1616
"lint:fix": "eslint . --fix",

‎apps/web/content/docs/dev/configuration.mdx‎

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,13 @@ import { blogPlugin } from '@vitnode/blog/config'
7777
plugins: [blogPlugin()]
7878
```
7979

80-
The factory carries the plugin's content types, its AdminCP navigation and its
81-
translations - one declaration, and the same one a Next.js host reads directly.
80+
The factory carries the plugin's identity, its locale barrel and its route
81+
declarations - plain data, and nothing that renders. Its AdminCP navigation and
82+
its Content Engine screens live in the plugin's `admin/nav` and `admin/content`
83+
modules instead, and they never pass through this object.
8284

8385
What a TanStack Start app **renders** comes back through generated projections
84-
rather than out of this object, and that is the part worth knowing:
86+
of those modules, and that is the part worth knowing:
8587

8688
| Generated file | From | Loaded |
8789
| :---------------------------- | :--------------------------- | :---------------------------------------------- |
@@ -92,7 +94,9 @@ rather than out of this object, and that is the part worth knowing:
9294
One literal import per configured plugin, written at build time. So a content
9395
type's editing screen - a Tiptap field, a form layout, a table cell - arrives
9496
with the route that renders it, not with the config that the document shell
95-
imports. See [Plugin routes](/docs/dev/plugins/routes) and
97+
imports. Spreading `admin/content` into the factory undoes exactly that: the
98+
whole editing stack rides along with every public page. See
99+
[Plugin routes](/docs/dev/plugins/routes) and
96100
[Plugin frontend modules](/docs/dev/content-engine/plugin-registration).
97101

98102
<Callout type="info" title="Also register its locale files">
@@ -246,14 +250,19 @@ import { vitnode } from '@vitnode/core/framework/vite'
246250
export default defineConfig({
247251
plugins: [
248252
vitnode({ appRoot: import.meta.dirname }),
249-
nitro(),
253+
nitro({ compressPublicAssets: { brotli: true, gzip: true } }),
250254
tailwindcss(),
251255
tanstackStart(),
252256
viteReact(),
253257
],
254258
})
255259
```
256260

261+
`compressPublicAssets` writes a `.br` and a `.gz` twin next to every built asset,
262+
so the Node server from `pnpm start` serves scripts and styles compressed the
263+
way a CDN would. The server-rendered HTML itself is not compressed by the Node
264+
server; put a reverse proxy or a platform such as Vercel in front of it for that.
265+
257266
`appRoot` has to be `import.meta.dirname`: a Vite config is loaded with the
258267
working directory set to wherever the command ran, which in a monorepo is
259268
regularly the repository root.

‎apps/web/content/docs/dev/content-engine/admincp.mdx‎

Lines changed: 36 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,37 @@ description: Configure zero-code AdminCP management screens, custom form layouts
44
icon: LayoutDashboard
55
---
66

7-
import { TypeTable } from "fumadocs-ui/components/type-table"
7+
import { TypeTable } from 'fumadocs-ui/components/type-table'
88

99
Content Engine automatically builds interactive management screens in the AdminCP without writing hand-rolled pages or form components.
1010

1111
## Quick start
1212

13-
Register your content type in `src/config.tsx` with `contentTypeAdmin`:
14-
15-
```tsx title="plugins/blog/src/config.tsx"
16-
import { buildPlugin, contentTypeAdmin } from "@vitnode/core/lib/plugin"
17-
import { FileTextIcon } from "lucide-react"
18-
import { postContentType } from "./content/post"
19-
20-
export const blogPlugin = () =>
21-
buildPlugin({
22-
pluginId: "blog",
23-
// [!code ++:6]
24-
contentTypes: [
25-
contentTypeAdmin({
26-
definition: postContentType,
27-
icon: <FileTextIcon />,
28-
}),
29-
],
30-
})
13+
Register your content type in `src/admin/content.tsx` with `contentTypeAdmin`:
14+
15+
```tsx title="plugins/blog/src/admin/content.tsx"
16+
import type { ContentFrontendPluginSource } from '@vitnode/core/lib/plugin'
17+
import { contentTypeAdmin } from '@vitnode/core/lib/plugin'
18+
import { FileTextIcon } from 'lucide-react'
19+
import { postContentType } from '@/content/post'
20+
21+
export const adminContent = {
22+
pluginId: 'blog',
23+
// [!code ++:6]
24+
contentTypes: [
25+
contentTypeAdmin({
26+
definition: postContentType,
27+
icon: <FileTextIcon />,
28+
}),
29+
],
30+
} satisfies ContentFrontendPluginSource
3131
```
3232

33-
The screen is immediately accessible at `/admin/content/blog/post`.
33+
The screen is immediately accessible at `/admin/content/blog/post`. The build
34+
imports this module for you through the generated content registry; keep it out
35+
of `config.tsx`, which is bundled with every public page. See
36+
[Plugin frontend modules](/docs/dev/content-engine/plugin-registration) for the
37+
matching `admin/nav.tsx` sidebar entry.
3438

3539
---
3640

@@ -40,17 +44,17 @@ Control which columns, search inputs, and sortable headers appear in the AdminCP
4044

4145
```ts title="plugins/blog/src/content/post.ts"
4246
export const postContentType = defineContentType({
43-
id: "blog.post",
44-
tableName: "blog_posts",
45-
fields: { /* ... */ },
47+
id: 'blog.post',
48+
tableName: 'blog_posts',
49+
fields: {/* ... */},
4650
// [!code ++:9]
4751
admin: {
48-
titleField: "title", // Primary record identifier in dialogs
52+
titleField: 'title', // Primary record identifier in dialogs
4953
list: {
50-
columns: ["title", "status", "author", "createdAt"],
51-
searchableFields: ["title"],
52-
orderableFields: ["title", "createdAt"],
53-
defaultOrderBy: { column: "createdAt", order: "desc" },
54+
columns: ['title', 'status', 'author', 'createdAt'],
55+
searchableFields: ['title'],
56+
orderableFields: ['title', 'createdAt'],
57+
defaultOrderBy: { column: 'createdAt', order: 'desc' },
5458
},
5559
},
5660
})
@@ -94,7 +98,7 @@ replaces it.
9498
The placeholder is built from the content type's definition, so it needs no
9599
configuration - a `text` field becomes an input-sized block, a `boolean` becomes
96100
a switch, a `file` becomes a media box, and a to-many picker becomes a list. If
97-
you gave the content type its own layout, the placeholder renders *that* layout,
101+
you gave the content type its own layout, the placeholder renders _that_ layout,
98102
so the fields land exactly where they will a moment later.
99103

100104
### When an override is a different shape
@@ -109,7 +113,7 @@ contentTypeAdmin({
109113
fields: {
110114
content: {
111115
component: BlogArticleEditorField,
112-
skeleton: "editor", // [!code ++]
116+
skeleton: 'editor', // [!code ++]
113117
},
114118
},
115119
})
@@ -128,10 +132,10 @@ contentTypeAdmin({
128132
The same block is available on its own, for a field that lazy-loads its control:
129133

130134
```tsx title="plugins/blog/src/views/admin/article/editor-field.tsx"
131-
import { ContentFormFieldSkeleton } from "@vitnode/core/content/admin-form"
135+
import { ContentFormFieldSkeleton } from '@vitnode/core/content/admin-form'
132136

133137
;<React.Suspense fallback={<ContentFormFieldSkeleton control="editor" />}>
134-
<AutoFormEditor label={t("content.label")} {...props} />
138+
<AutoFormEditor label={t('content.label')} {...props} />
135139
</React.Suspense>
136140
```
137141

‎apps/web/content/docs/dev/content-engine/defining-a-content-type.mdx‎

Lines changed: 44 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ description: Declare a Content Engine content type once to get a Postgres table,
44
icon: FileCode
55
---
66

7-
import { File, Files, Folder } from "fumadocs-ui/components/files"
8-
import { Tab, Tabs } from "fumadocs-ui/components/tabs"
9-
import { TypeTable } from "fumadocs-ui/components/type-table"
7+
import { File, Files, Folder } from 'fumadocs-ui/components/files'
8+
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
9+
import { TypeTable } from 'fumadocs-ui/components/type-table'
1010

1111
Content Engine generates a complete CRUD ecosystem from one declaration: a PostgreSQL table, Zod validation schemas, API endpoints, staff permissions, and AdminCP management screens.
1212

@@ -15,23 +15,23 @@ Content Engine generates a complete CRUD ecosystem from one declaration: a Postg
1515
### 1. Define the Content Type
1616

1717
```ts title="plugins/example/src/content/category.ts"
18-
import { defineContentType, field } from "@vitnode/core/content"
18+
import { defineContentType, field } from '@vitnode/core/content'
1919

2020
export const categoryContentType = defineContentType({
21-
id: "example.category",
22-
tableName: "example_categories",
21+
id: 'example.category',
22+
tableName: 'example_categories',
2323
fields: {
2424
name: field.text({ required: true, minLength: 2, maxLength: 100 }),
25-
slug: field.slug({ from: "name", required: true }),
25+
slug: field.slug({ from: 'name', required: true }),
2626
},
2727
})
2828
```
2929

3030
### 2. Create the Database Model
3131

3232
```ts title="plugins/example/src/database/categories.ts"
33-
import { createContentModel } from "@vitnode/core/content/server"
34-
import { categoryContentType } from "@/content/category"
33+
import { createContentModel } from '@vitnode/core/content/server'
34+
import { categoryContentType } from '@/content/category'
3535

3636
export const categoryContent = createContentModel(categoryContentType)
3737
export const example_categories = categoryContent.table
@@ -69,16 +69,14 @@ Your table now exists in PostgreSQL.
6969
Add the model to your plugin's `config.api.ts`:
7070

7171
```ts title="plugins/example/src/config.api.ts"
72-
import { buildApiPlugin } from "@vitnode/core/api/lib/plugin"
73-
import { categoryContent } from "./database/categories"
72+
import { buildApiPlugin } from '@vitnode/core/api/lib/plugin'
73+
import { categoryContent } from './database/categories'
7474

7575
export const exampleApiPlugin = () =>
7676
buildApiPlugin({
77-
pluginId: "@vitnode/example",
77+
pluginId: '@vitnode/example',
7878
// [!code ++:3]
79-
contentTypes: [
80-
categoryContent,
81-
],
79+
contentTypes: [categoryContent],
8280
})
8381
```
8482

@@ -89,26 +87,30 @@ This immediately registers full CRUD routes under `/api/@vitnode/example/admin/c
8987

9088
### 2. Register Screens in the AdminCP
9189

92-
Connect the definition in `src/config.tsx`:
93-
94-
```tsx title="plugins/example/src/config.tsx"
95-
import { buildPlugin, contentTypeAdmin } from "@vitnode/core/lib/plugin"
96-
import { FolderIcon } from "lucide-react"
97-
import { categoryContentType } from "./content/category"
98-
99-
export const examplePlugin = () =>
100-
buildPlugin({
101-
pluginId: "@vitnode/example",
102-
// [!code ++:6]
103-
contentTypes: [
104-
contentTypeAdmin({
105-
definition: categoryContentType,
106-
icon: <FolderIcon />,
107-
}),
108-
],
109-
})
90+
Connect the definition in `src/admin/content.tsx`. The build imports this module
91+
through the generated content registry, so it never reaches a public page:
92+
93+
```tsx title="plugins/example/src/admin/content.tsx"
94+
import type { ContentFrontendPluginSource } from '@vitnode/core/lib/plugin'
95+
import { contentTypeAdmin } from '@vitnode/core/lib/plugin'
96+
import { FolderIcon } from 'lucide-react'
97+
import { categoryContentType } from '@/content/category'
98+
99+
export const adminContent = {
100+
pluginId: '@vitnode/example',
101+
// [!code ++:6]
102+
contentTypes: [
103+
contentTypeAdmin({
104+
definition: categoryContentType,
105+
icon: <FolderIcon />,
106+
}),
107+
],
108+
} satisfies ContentFrontendPluginSource
110109
```
111110

111+
The sidebar entry comes from `src/admin/nav.tsx` the same way - see
112+
[Plugin frontend modules](/docs/dev/content-engine/plugin-registration).
113+
112114
</Step>
113115
<Step>
114116

@@ -151,25 +153,26 @@ You have a complete AdminCP data table with search, sorting, pagination, and mod
151153
id: {
152154
description: "Unique identifier formatted as '<plugin>.<name>'.",
153155
required: true,
154-
type: "string",
156+
type: 'string',
155157
},
156158
tableName: {
157159
description: "Postgres table name (e.g. 'example_categories').",
158160
required: true,
159-
type: "string",
161+
type: 'string',
160162
},
161163
fields: {
162-
description: "Record containing field descriptors created with field.*.",
164+
description: 'Record containing field descriptors created with field.*.',
163165
required: true,
164-
type: "Record<string, ContentField>",
166+
type: 'Record<string, ContentField>',
165167
},
166168
publication: {
167-
description: "Enables draft/published workflows and publishedAt timestamps.",
168-
type: "{ enabled: boolean }",
169+
description:
170+
'Enables draft/published workflows and publishedAt timestamps.',
171+
type: '{ enabled: boolean }',
169172
},
170173
indexes: {
171-
description: "Custom single or composite database indexes.",
172-
type: "IndexDefinition[]",
174+
description: 'Custom single or composite database indexes.',
175+
type: 'IndexDefinition[]',
173176
},
174177
}}
175178
/>

0 commit comments

Comments
 (0)