@wikex/admin-kit
v0.12.0
Published
Reusable Payload CMS admin shell, visual editor and typography tools for Wikex projects
Downloads
2,602
Readme
@wikex/admin-kit
Admin shell dùng chung cho các website Payload CMS của Wikex.
Package sở hữu:
- navigation, header, đăng nhập, account menu, đổi theme và branding động bằng MUI;
- Minimal UI skin và các bản vá layout Payload;
- Live Preview editor, drawer thuộc tính, block picker và resize text;
- font family, line-height và runtime áp dụng typography;
- chuẩn hóa tiếng Việt và hướng dẫn kích thước upload ảnh.
Schema, block catalog, route registry, dashboard và seed data vẫn thuộc ứng dụng sử dụng package vì chúng thay đổi theo từng dự án.
@wikex/admin-kit phụ thuộc @wikex/content-kit, nên chỉ cần cài Admin Kit là có luôn
schema Page/Post/Media, SEO, redirects, robots và sitemap chuẩn. Schema, block catalog và
frontend renderer cụ thể vẫn thuộc dự án consumer.
Dùng trong workspace
{
"dependencies": {
"@wikex/admin-kit": "workspace:*"
}
}// next.config.ts
const nextConfig = {
transpilePackages: ['@wikex/admin-kit'],
}// Payload custom.scss
@use '@wikex/admin-kit/styles';// Payload plugins
import { adminExperiencePlugin, wikexAdminPlugin } from '@wikex/admin-kit'
export const plugins = [
adminExperiencePlugin,
wikexAdminPlugin({
components: {
beforeDashboard: '@/components/BeforeDashboard',
provider: '@/components/LivePreviewResizer',
},
}),
]wikexAdminPlugin tự đăng ký MUI/Emotion provider của Admin Kit và đồng bộ light/dark
mode với Payload. Các màn hình custom có thể dùng trực tiếp các primitive Minimal thật:
'use client'
import { MinimalBlockCard, MinimalDataTable, MinimalSelect } from '@wikex/admin-kit/minimal'
export const ProductPanel = () => (
<MinimalBlockCard label="Catalog" title="Hàng hóa">
<MinimalSelect
label="Trạng thái"
onChange={(value) => console.log(value)}
options={[{ label: 'Đang bán', value: 'active' }]}
value="active"
/>
<MinimalDataTable
caption="Danh sách hàng hóa"
columns={[{ id: 'name', header: 'Tên', render: (row) => row.name }]}
rowKey="id"
rows={[{ id: 'gold', name: 'Vàng' }]}
/>
</MinimalBlockCard>
)Theme dùng palette gốc của Minimal UI (primary.main = #00A76F) và không chèn
CssBaseline, nhờ vậy không reset CSS form/document có sẵn của Payload.
Admin shell dùng trực tiếp component MUI của Minimal; các control nội bộ của Payload
(table, filter, select, block, editor) được ánh xạ sang cùng token và kích thước để giữ
nguyên sorting, permission, localization và Live Preview.
Live Preview provider của dự án là adapter khai báo route và component khả dụng:
'use client'
import { configureAdminBrand } from '@wikex/admin-kit/admin/brand'
import LivePreviewEditor, { configureLivePreview } from '@wikex/admin-kit/live-preview'
configureLivePreview({
getVisualEditorRoute,
pageBlocks,
postBlocks,
})
configureAdminBrand({
logo: { alt: projectName, height: 64, url: '/logo.svg', width: 64 },
siteName: projectName,
})
export default LivePreviewEditorTrong frontend, dùng useTargetedLivePreview cho từng Page/Header/Footer để các message
chung một iframe không ghi đè dữ liệu của nhau:
import { useTargetedLivePreview } from '@wikex/admin-kit/live-preview'
const page = useTargetedLivePreview(initialPage, { collectionSlug: 'pages' })
const header = useTargetedLivePreview(initialHeader, { globalSlug: 'header' })Trong visual editor, nhấp đúp text sẽ mở ô nội dung trong inspector. Admin Kit không đặt
contentEditable lên DOM của website, vì field có thể nằm trên wrapper chứa markup/style con.
Thay đổi được gửi qua field Payload thật và Live Preview chịu trách nhiệm render lại component.
Tự động hóa component, text và ảnh
Khai báo nội dung editable ngay trong component. Không cần tạo file *.wikex.ts riêng:
import { defineWikexComponent, wikex } from '@wikex/admin-kit/content'
export default defineWikexComponent({
slug: 'hero',
label: 'Hero',
description: 'Hero của trang chủ',
image: '/thumbnails/hero.webp',
fields: {
eyebrow: wikex.text({ defaultValue: 'Wikex', label: 'Nhãn nhỏ' }),
title: wikex.text({ defaultValue: 'Nền tảng hàng hóa', label: 'Tiêu đề' }),
artwork: wikex.image({
defaultSrc: '/images/hero.webp',
label: 'Ảnh hero',
alt: { defaultValue: 'Biểu đồ hàng hóa' },
}),
actions: wikex.list({
label: 'Nút hành động',
fields: {
label: wikex.text({ required: true }),
url: wikex.text({ required: true }),
},
defaultValue: [{ label: 'Bắt đầu', url: '/register' }],
}),
},
})(({ content, bind }) => (
<section {...bind.component()}>
<p {...bind.text('eyebrow')}>{content.eyebrow}</p>
<h1 {...bind.text('title')}>{content.title}</h1>
<img {...bind.image('artwork')} src={content.artwork.url} alt={content.artwork.alt} />
{content.actions.map((action, index) => (
<a key={action.url} href={action.url} {...bind.text(`actions.${index}.label`)}>
{action.label}
</a>
))}
</section>
))wikex.image() tự sinh ba phần dữ liệu: Media upload, đường dẫn asset mặc định và alt.
Khi editor chọn ảnh mới, content.artwork.url ưu tiên URL Media; nếu chưa chọn thì dùng
defaultSrc. Các factory hiện có gồm text, textarea, richText, number, boolean,
select, relationship, link, group, list và image.
Thêm generator vào lifecycle của project:
{
"scripts": {
"predev": "wikex generate",
"prebuild": "wikex generate"
}
}Lệnh này scan src/**/*.tsx bằng TypeScript AST mà không chạy/import component, sau đó
sinh theo convention:
src/cms/wikexBlocks.generated.ts— Payload schema, Add Layout catalog và defaults;src/cms/wikexBlockRenderers.generated.ts— mappingblockTypesang component frontend.
Metadata phải là object tĩnh; có thể dùng literal, array, as const và local const tĩnh.
Không dùng giá trị lấy từ API, hook hoặc function runtime bên trong object annotation.
Đưa registry vào starter để không phải khai báo block lần hai:
import { createWikexStarter } from '@wikex/admin-kit/starter'
import { wikexBlockRegistry } from './cms/wikexBlocks.generated'
export const admin = createWikexStarter({
blocks: wikexBlockRegistry,
brand,
content: { page: { previewURL: '/' } },
id: 'acme',
provider: '@/cms/LivePreviewAdapter',
})Starter tự dùng pageBlocks, metadata Add Layout và hydrateLayout() cho Page cũ chưa
có field mới. Giá trị editor đã lưu không bị ghi đè. Scanner cũng đánh dấu component
annotated là editable và liệt kê các route đang import trực tiếp hoặc gián tiếp component.
Quy trình scaffold và import 0.12
Với dự án mới, có thể dựng trước một sườn admin độc lập rồi đưa page/component vào sau:
pnpm exec wikex init --full
cp .env.example .env
pnpm exec wikex generate
pnpm exec payload generate:importmapinit --full tạo các file còn thiếu cho Payload Admin tại /admin, REST API tại
/api, Users, Settings, Header/Footer, SQLite config và adapter Live Preview. Lệnh
không ghi đè source có sẵn. Cài các peer cần cho sườn Payload, tối thiểu
@payloadcms/db-sqlite, @payloadcms/next, payload, Next và React.
Nếu project đang có src/app/layout.tsx, giữ layout đó và tích hợp Payload
RootLayout theo cấu trúc route group hiện tại của project thay vì tạo root layout thứ hai.
Discovery dựa trên cấu trúc thư mục, không ghi database:
pnpm exec wikex discover
pnpm exec wikex discover --apply- route lấy từ
src/app/**/page.tsx; route group được bỏ khỏi URL và dynamic route được đánh dấu để xử lý riêng; - component lấy từ export trong
src/components/**/*.{tsx,jsx}; - catalog phân biệt
registeredvàneeds-mapping.
Import là một bước tường minh. Chọn component theo tên, file hoặc catalog id:
pnpm exec wikex import-component Hero
pnpm exec wikex import-component components:Hero.tsx#default --apply
pnpm exec wikex generateSidecar mới giữ nguyên component React làm renderer và bắt đầu với fields: []. Hãy
khai báo các field Payload tương ứng text/ảnh/list rồi chạy lại wikex generate.
Admin Kit không đoán ý nghĩa prop từ JSX, vì đoán sai sẽ làm Live Preview hiển thị được
nhưng lưu dữ liệu sai. Component chỉ được xem là sẵn sàng bàn giao khi field mapping đã
được khai báo.
Sau khi registry đã sẵn sàng, import các route tĩnh vào collection pages:
pnpm exec wikex import-pages
pnpm exec wikex import-pages --apply
pnpm exec wikex import-pages --apply --overwriteimport-pages mặc định dry-run. --apply chỉ tạo page còn thiếu; record editor đang
có được giữ nguyên. --overwrite là lựa chọn riêng để cập nhật title/layout từ source.
Có thể dùng --collection <slug> và --config <payload-config> cho project khác
convention. Dynamic route không được tự tạo vì cần nguồn dữ liệu/slug cụ thể.
React component vẫn thuộc source code; Payload DB chỉ giữ metadata, content và layout.
Để Live Preview hoạt động như editor hiện tại, frontend route phải đọc Page từ Payload
và render layout bằng src/cms/WikexLayout.tsx do init --full tạo; mỗi component
cần sidecar field mapping hoặc defineWikexComponent().
Quy trình tự động hóa 0.11
Khởi tạo dự án mới bằng một lệnh. init chỉ tạo file còn thiếu, giữ nguyên source đã
tồn tại và bổ sung lifecycle script trong package.json:
pnpm exec wikex init
pnpm exec wikex generate
pnpm exec wikex doctorTrong lúc phát triển có thể generate liên tục và quét inventory dưới dạng dễ đọc hoặc JSON:
pnpm exec wikex generate --watch
pnpm exec wikex scan
pnpm exec wikex scan --jsonĐồng bộ Page và content luôn là dry-run mặc định. sync-pages --apply chỉ ghi manifest
local để review/import, không tự kết nối hay ghi database Payload. Dynamic route được bỏ
qua và phải cấu hình thủ công:
pnpm exec wikex sync-pages
pnpm exec wikex sync-pages --applysync-content hydrate default vào Page JSON đã export, không ghi đè field có nội dung.
Khi dùng --apply, CLI tạo backup trước khi ghi và có thể restore bằng --rollback:
pnpm exec wikex sync-content --input ./pages.json
pnpm exec wikex sync-content --input ./pages.json --apply
pnpm exec wikex sync-content --input ./pages.json --rollback ./pages.json.backup-... --applydoctor kiểm tra dependency, lifecycle scripts, Next transpilation, Payload styles,
generated registry và block slug trùng. Lỗi bắt buộc trả exit code 1 để dùng trong CI.
API defineWikexBlock() cùng sidecar *.wikex.ts vẫn được hỗ trợ để migrate dần các
dự án cũ.
Project config và route resolver cũng dùng API chung, nhưng tên module/collection vẫn do từng website quyết định:
import { createVisualEditorRouteResolver, defineWikexProject } from '@wikex/admin-kit/project'
export const project = defineWikexProject({
brand: {
description: 'Website doanh nghiệp',
logo: { height: 64, url: '/logo.svg', width: 64 },
name: 'Wikex Corporate',
},
id: 'wikex-corporate',
modules: ['website', 'services'],
})
export const getVisualEditorRoute = createVisualEditorRouteResolver({
collections: ['pages'],
globals: { footer: 'footer', header: 'header', homepage: 'content' },
})Xem consumer không thuộc lĩnh vực bất động sản tại examples/corporate-demo.
Ứng dụng cần cung cấp các file font tại public/fonts/google, hoặc ghi đè các biến --font-* nếu dùng font riêng.
Cài vào dự án khác — một package, một cấu hình
pnpm add @wikex/[email protected]Không cần .npmrc, GitLab token hay npm token ở dự án consumer. Các peer dependency
Payload/Next/React phải đã có trong dự án.
// src/wikex.ts — server-only config, dùng một lần trong Payload config
import { createWikexStarter } from '@wikex/admin-kit/starter'
import { pageBlocks, postBlocks } from './blocks'
export const wikex = createWikexStarter({
brand: {
description: 'Website công ty Acme',
logo: { height: 64, url: '/logo.svg', width: 64 },
name: 'Acme',
},
content: {
page: { blocks: pageBlocks, previewURL: '/' },
post: { blocks: postBlocks, previewURL: '/news' },
seo: {
siteName: 'Acme',
pathFor: ({ collection, slug }) =>
collection === 'pages' ? (slug === 'home' ? '/' : `/${slug}`) : `/news/${slug}`,
},
},
globals: { footer: 'footer', header: 'header' },
id: 'acme',
provider: '@/LivePreviewAdapter',
})// payload.config.ts
import { wikex } from './src/wikex'
export default buildConfig({
collections: [Users, ...wikex.collections, ...projectCollections],
globals: projectGlobals,
plugins: wikex.plugins,
})Giữ LivePreviewAdapter.tsx nhỏ ở client vì block renderer của mỗi website là riêng.
Adapter gọi createWikexLivePreviewAdapter từ @wikex/admin-kit/starter/client với
branding, block picker và route resolver tương ứng; xem implementation hoàn chỉnh tại
examples/corporate-demo.
Phát hành npm public
Khi phát hành:
- tăng version theo SemVer và cập nhật
CHANGELOG.md; - chạy test/typecheck/build trong starter;
- tạo tag
admin-kit-v<version>để GitLab CI publish npm; - ở từng dự án consumer đổi
workspace:*thành version cố định, ví dụ0.2.0; - nâng version có chủ đích, không dùng
latesttrong production.
GitLab CI cần một CI/CD variable bảo mật NPM_TOKEN có quyền publish scope @wikex.
Đây là credential duy nhất của phía phát hành; người dùng package công khai không cần
credential nào.
