create-quadrokit 0.1.0 → 0.2.0
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 +2 -2
- package/biome.monorepo.json +67 -0
- package/dist/index.js +239 -0
- package/package.json +11 -4
- package/templates/README.md +37 -0
- package/templates/admin-shell/.cursor/rules/commitlint-conventional.mdc +47 -0
- package/templates/admin-shell/.env.example +2 -0
- package/templates/admin-shell/biome.json +12 -0
- package/templates/admin-shell/index.html +12 -0
- package/templates/admin-shell/package.json +45 -0
- package/templates/admin-shell/postcss.config.js +6 -0
- package/templates/admin-shell/src/components/AppShell.tsx +68 -0
- package/templates/admin-shell/src/i18n.ts +12 -0
- package/templates/admin-shell/src/lib/quadro-client.ts +4 -0
- package/templates/admin-shell/src/locales/en.json +15 -0
- package/templates/admin-shell/src/main.tsx +15 -0
- package/templates/admin-shell/src/pages/AgenciesPage.tsx +69 -0
- package/templates/admin-shell/src/pages/HomePage.tsx +67 -0
- package/templates/admin-shell/src/router.tsx +15 -0
- package/templates/admin-shell/src/stores/useSidebarHint.ts +12 -0
- package/templates/admin-shell/src/vite-env.d.ts +9 -0
- package/templates/admin-shell/tailwind.config.ts +7 -0
- package/templates/admin-shell/tsconfig.app.json +16 -0
- package/templates/admin-shell/tsconfig.json +4 -0
- package/templates/admin-shell/tsconfig.node.json +10 -0
- package/templates/admin-shell/vite.config.ts +25 -0
- package/templates/dashboard/.cursor/rules/commitlint-conventional.mdc +47 -0
- package/templates/dashboard/.env.example +2 -0
- package/templates/dashboard/biome.json +12 -0
- package/templates/dashboard/index.html +12 -0
- package/templates/dashboard/package.json +45 -0
- package/templates/dashboard/postcss.config.js +6 -0
- package/templates/dashboard/src/components/AppShell.tsx +44 -0
- package/templates/dashboard/src/i18n.ts +12 -0
- package/templates/dashboard/src/lib/quadro-client.ts +4 -0
- package/templates/dashboard/src/locales/en.json +15 -0
- package/templates/dashboard/src/main.tsx +15 -0
- package/templates/dashboard/src/pages/AgenciesPage.tsx +69 -0
- package/templates/dashboard/src/pages/HomePage.tsx +67 -0
- package/templates/dashboard/src/router.tsx +15 -0
- package/templates/dashboard/src/stores/useSidebarHint.ts +12 -0
- package/templates/dashboard/src/vite-env.d.ts +9 -0
- package/templates/dashboard/tailwind.config.ts +7 -0
- package/templates/dashboard/tsconfig.app.json +16 -0
- package/templates/dashboard/tsconfig.json +4 -0
- package/templates/dashboard/tsconfig.node.json +10 -0
- package/templates/dashboard/vite.config.ts +25 -0
- package/templates/ecommerce/.cursor/rules/commitlint-conventional.mdc +47 -0
- package/templates/ecommerce/.env.example +2 -0
- package/templates/ecommerce/biome.json +12 -0
- package/templates/ecommerce/index.html +12 -0
- package/templates/ecommerce/package.json +45 -0
- package/templates/ecommerce/postcss.config.js +6 -0
- package/templates/ecommerce/src/components/AppShell.tsx +44 -0
- package/templates/ecommerce/src/i18n.ts +12 -0
- package/templates/ecommerce/src/lib/quadro-client.ts +4 -0
- package/templates/ecommerce/src/locales/en.json +20 -0
- package/templates/ecommerce/src/main.tsx +15 -0
- package/templates/ecommerce/src/pages/AgenciesPage.tsx +69 -0
- package/templates/ecommerce/src/pages/HomePage.tsx +52 -0
- package/templates/ecommerce/src/router.tsx +15 -0
- package/templates/ecommerce/src/stores/useSidebarHint.ts +12 -0
- package/templates/ecommerce/src/vite-env.d.ts +9 -0
- package/templates/ecommerce/tailwind.config.ts +7 -0
- package/templates/ecommerce/tsconfig.app.json +16 -0
- package/templates/ecommerce/tsconfig.json +4 -0
- package/templates/ecommerce/tsconfig.node.json +10 -0
- package/templates/ecommerce/vite.config.ts +25 -0
- package/templates/website/.cursor/rules/commitlint-conventional.mdc +47 -0
- package/templates/website/.env.example +2 -0
- package/templates/website/biome.json +12 -0
- package/templates/website/index.html +12 -0
- package/templates/website/package.json +45 -0
- package/templates/website/postcss.config.js +6 -0
- package/templates/website/src/components/AppShell.tsx +44 -0
- package/templates/website/src/i18n.ts +12 -0
- package/templates/website/src/lib/quadro-client.ts +4 -0
- package/templates/website/src/locales/en.json +21 -0
- package/templates/website/src/main.tsx +15 -0
- package/templates/website/src/pages/AgenciesPage.tsx +69 -0
- package/templates/website/src/pages/HomePage.tsx +83 -0
- package/templates/website/src/router.tsx +15 -0
- package/templates/website/src/stores/useSidebarHint.ts +12 -0
- package/templates/website/src/vite-env.d.ts +9 -0
- package/templates/website/tailwind.config.ts +7 -0
- package/templates/website/tsconfig.app.json +16 -0
- package/templates/website/tsconfig.json +4 -0
- package/templates/website/tsconfig.node.json +10 -0
- package/templates/website/vite.config.ts +25 -0
- package/vendor/generated/client.gen.ts +2725 -0
- package/vendor/generated/meta.json +5 -0
- package/vendor/generated/types.gen.ts +991 -0
- package/src/index.ts +0 -237
- package/tsconfig.json +0 -9
package/README.md
CHANGED
|
@@ -21,10 +21,10 @@ Interactive mode: run without `--template` / `--dir` to be prompted.
|
|
|
21
21
|
|
|
22
22
|
## What it does
|
|
23
23
|
|
|
24
|
-
1. Copies the chosen template from `packages/templates/<name>` (skips `node_modules`, `dist`).
|
|
24
|
+
1. Copies the chosen template from `create-quadrokit/templates/<name>` when installed from npm, or `packages/templates/<name>` when you run the CLI from the monorepo (skips `node_modules`, `dist`).
|
|
25
25
|
2. Copies `packages/sample-client/generated` → `<dir>/.quadrokit/generated`.
|
|
26
26
|
3. Rewrites `src/lib/quadro-client.ts` to import from `../../.quadrokit/generated/client.gen.js`.
|
|
27
|
-
4. Removes `@quadrokit/sample-client` and rewrites other `workspace:*` deps to `^0.
|
|
27
|
+
4. Removes `@quadrokit/sample-client` and rewrites other `workspace:*` deps to `^0.2.0` (unless `--keep-workspace`).
|
|
28
28
|
5. Writes `QUADROKIT.md` with proxy and `quadrokit-client generate` instructions.
|
|
29
29
|
|
|
30
30
|
## Published usage (future)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/2.4.10/schema.json",
|
|
3
|
+
"vcs": {
|
|
4
|
+
"enabled": true,
|
|
5
|
+
"clientKind": "git",
|
|
6
|
+
"useIgnoreFile": true
|
|
7
|
+
},
|
|
8
|
+
"files": {
|
|
9
|
+
"includes": [
|
|
10
|
+
"**",
|
|
11
|
+
"!**/node_modules",
|
|
12
|
+
"!**/dist",
|
|
13
|
+
"!**/.quadrokit",
|
|
14
|
+
"!**/packages/sample-client/generated",
|
|
15
|
+
"!**/*.tsbuildinfo"
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
"formatter": {
|
|
19
|
+
"indentStyle": "space",
|
|
20
|
+
"indentWidth": 2,
|
|
21
|
+
"lineWidth": 100
|
|
22
|
+
},
|
|
23
|
+
"linter": {
|
|
24
|
+
"enabled": true,
|
|
25
|
+
"rules": {
|
|
26
|
+
"recommended": true,
|
|
27
|
+
"correctness": {
|
|
28
|
+
"noUnusedImports": {
|
|
29
|
+
"level": "error"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"style": {
|
|
33
|
+
"noNonNullAssertion": "off"
|
|
34
|
+
},
|
|
35
|
+
"suspicious": {
|
|
36
|
+
"noUnknownAtRules": {
|
|
37
|
+
"level": "off"
|
|
38
|
+
},
|
|
39
|
+
"noExplicitAny": {
|
|
40
|
+
"level": "warn"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"nursery": {
|
|
44
|
+
"useSortedClasses": {
|
|
45
|
+
"level": "off"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"javascript": {
|
|
51
|
+
"formatter": {
|
|
52
|
+
"quoteStyle": "single",
|
|
53
|
+
"trailingCommas": "es5",
|
|
54
|
+
"semicolons": "asNeeded"
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"html": {
|
|
58
|
+
"parser": {
|
|
59
|
+
"interpolation": true
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"css": {
|
|
63
|
+
"parser": {
|
|
64
|
+
"tailwindDirectives": true
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { cp, mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import prompts from 'prompts';
|
|
6
|
+
const TEMPLATES = ['dashboard', 'website', 'ecommerce', 'admin-shell'];
|
|
7
|
+
/** Directory of the create-quadrokit package (works when installed under node_modules). */
|
|
8
|
+
function packageRoot() {
|
|
9
|
+
return path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
10
|
+
}
|
|
11
|
+
async function resolveTemplateDir(template) {
|
|
12
|
+
const pkg = packageRoot();
|
|
13
|
+
const bundled = path.join(pkg, 'templates', template);
|
|
14
|
+
if (await pathExists(bundled)) {
|
|
15
|
+
return bundled;
|
|
16
|
+
}
|
|
17
|
+
const monorepo = path.join(pkg, '..', 'packages', 'templates', template);
|
|
18
|
+
if (await pathExists(monorepo)) {
|
|
19
|
+
return monorepo;
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
async function resolveBiomeJsonPath() {
|
|
24
|
+
const pkg = packageRoot();
|
|
25
|
+
const bundled = path.join(pkg, 'biome.monorepo.json');
|
|
26
|
+
if (await pathExists(bundled)) {
|
|
27
|
+
return bundled;
|
|
28
|
+
}
|
|
29
|
+
const monorepo = path.join(pkg, '..', 'biome.json');
|
|
30
|
+
if (await pathExists(monorepo)) {
|
|
31
|
+
return monorepo;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
async function resolveGeneratedClientDir() {
|
|
36
|
+
const pkg = packageRoot();
|
|
37
|
+
const bundled = path.join(pkg, 'vendor', 'generated');
|
|
38
|
+
if (await pathExists(bundled)) {
|
|
39
|
+
return bundled;
|
|
40
|
+
}
|
|
41
|
+
const monorepo = path.join(pkg, '..', 'packages', 'sample-client', 'generated');
|
|
42
|
+
if (await pathExists(monorepo)) {
|
|
43
|
+
return monorepo;
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
function parseArgs(argv) {
|
|
48
|
+
let template;
|
|
49
|
+
let dir;
|
|
50
|
+
let keepWorkspace = false;
|
|
51
|
+
for (let i = 0; i < argv.length; i++) {
|
|
52
|
+
const a = argv[i];
|
|
53
|
+
if (a === '--template' && argv[i + 1]) {
|
|
54
|
+
template = argv[++i];
|
|
55
|
+
}
|
|
56
|
+
else if (a === '--dir' && argv[i + 1]) {
|
|
57
|
+
dir = argv[++i];
|
|
58
|
+
}
|
|
59
|
+
else if (a === '--keep-workspace') {
|
|
60
|
+
keepWorkspace = true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return { template, dir, keepWorkspace };
|
|
64
|
+
}
|
|
65
|
+
async function pathExists(p) {
|
|
66
|
+
try {
|
|
67
|
+
await stat(p);
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async function copyTemplate(src, dest) {
|
|
75
|
+
await mkdir(dest, { recursive: true });
|
|
76
|
+
const entries = await readdir(src, { withFileTypes: true });
|
|
77
|
+
for (const e of entries) {
|
|
78
|
+
if (e.name === 'node_modules' || e.name === 'dist' || e.name === '.turbo') {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const from = path.join(src, e.name);
|
|
82
|
+
const to = path.join(dest, e.name);
|
|
83
|
+
if (e.isDirectory()) {
|
|
84
|
+
await copyTemplate(from, to);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
await cp(from, to);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function rewriteWorkspaceDeps(pkg) {
|
|
92
|
+
for (const key of ['dependencies', 'devDependencies']) {
|
|
93
|
+
const deps = pkg[key];
|
|
94
|
+
if (!deps) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
for (const name of Object.keys(deps)) {
|
|
98
|
+
if (deps[name] === 'workspace:*') {
|
|
99
|
+
deps[name] = '^0.2.0';
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async function patchPackageJson(dest, projectName, keepWorkspace) {
|
|
105
|
+
const pkgPath = path.join(dest, 'package.json');
|
|
106
|
+
const raw = await readFile(pkgPath, 'utf8');
|
|
107
|
+
const pkg = JSON.parse(raw);
|
|
108
|
+
pkg.name = projectName;
|
|
109
|
+
if (pkg.dependencies) {
|
|
110
|
+
pkg.dependencies = Object.fromEntries(Object.entries(pkg.dependencies).filter(([name]) => name !== '@quadrokit/sample-client'));
|
|
111
|
+
}
|
|
112
|
+
if (!keepWorkspace) {
|
|
113
|
+
rewriteWorkspaceDeps(pkg);
|
|
114
|
+
}
|
|
115
|
+
await writeFile(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`, 'utf8');
|
|
116
|
+
}
|
|
117
|
+
/** Standalone Biome config for projects outside the monorepo (no `extends` to repo root). */
|
|
118
|
+
async function writeStandaloneBiome(dest, keepWorkspace) {
|
|
119
|
+
if (keepWorkspace) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const rootBiomePath = await resolveBiomeJsonPath();
|
|
123
|
+
if (!rootBiomePath) {
|
|
124
|
+
console.warn('Warning: biome.json not found — skipped writing standalone biome.json for the new project.');
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const rootCfg = JSON.parse(await readFile(rootBiomePath, 'utf8'));
|
|
128
|
+
const { extends: _ext, ...rest } = rootCfg;
|
|
129
|
+
const projectBiome = {
|
|
130
|
+
...rest,
|
|
131
|
+
vcs: {
|
|
132
|
+
enabled: true,
|
|
133
|
+
clientKind: 'git',
|
|
134
|
+
useIgnoreFile: false,
|
|
135
|
+
},
|
|
136
|
+
files: {
|
|
137
|
+
includes: ['**', '!**/node_modules', '!**/dist', '!**/.quadrokit'],
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
await writeFile(path.join(dest, 'biome.json'), `${JSON.stringify(projectBiome, null, 2)}\n`, 'utf8');
|
|
141
|
+
}
|
|
142
|
+
async function writeQuadroClientImport(dest) {
|
|
143
|
+
const p = path.join(dest, 'src', 'lib', 'quadro-client.ts');
|
|
144
|
+
const body = `import { createClient } from '../../.quadrokit/generated/client.gen.js';
|
|
145
|
+
|
|
146
|
+
/** Same-origin \`/rest\` in dev (Vite proxy) and production (reverse proxy). */
|
|
147
|
+
export const quadro = createClient({ baseURL: '/rest' });
|
|
148
|
+
`;
|
|
149
|
+
await writeFile(p, body, 'utf8');
|
|
150
|
+
}
|
|
151
|
+
async function seedGenerated(dest) {
|
|
152
|
+
const genSrc = await resolveGeneratedClientDir();
|
|
153
|
+
const genDest = path.join(dest, '.quadrokit', 'generated');
|
|
154
|
+
if (!genSrc) {
|
|
155
|
+
console.warn('Warning: sample generated client not found — run `quadrokit-client generate` after install.');
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
await mkdir(path.join(dest, '.quadrokit'), { recursive: true });
|
|
159
|
+
await cp(genSrc, genDest, { recursive: true });
|
|
160
|
+
}
|
|
161
|
+
async function writeReadme(dest, template) {
|
|
162
|
+
const text = `# ${path.basename(dest)}
|
|
163
|
+
|
|
164
|
+
Created with **create-quadrokit** (template: \`${template}\`).
|
|
165
|
+
|
|
166
|
+
## Next steps
|
|
167
|
+
|
|
168
|
+
1. \`cd ${path.basename(dest)}\` and \`bun install\`
|
|
169
|
+
2. Copy \`.env.example\` to \`.env\` and set \`VITE_4D_ORIGIN\` to your 4D web server.
|
|
170
|
+
3. Regenerate the typed client when your REST catalog changes:
|
|
171
|
+
|
|
172
|
+
\`\`\`bash
|
|
173
|
+
bunx quadrokit-client generate --url "http://localhost:7080/rest/\\$catalog" --token YOUR_TOKEN --out .quadrokit/generated
|
|
174
|
+
\`\`\`
|
|
175
|
+
|
|
176
|
+
4. \`bun run dev\` — the dev server proxies \`/rest\` to \`VITE_4D_ORIGIN\` so **4DSID_** cookies stay same-origin.
|
|
177
|
+
|
|
178
|
+
Production: serve the SPA and reverse-proxy \`/rest\` to 4D on the **same host** as the UI.
|
|
179
|
+
`;
|
|
180
|
+
await writeFile(path.join(dest, 'QUADROKIT.md'), text, 'utf8');
|
|
181
|
+
}
|
|
182
|
+
async function main() {
|
|
183
|
+
const argv = process.argv.slice(2);
|
|
184
|
+
const { template: tArg, dir: dirArg, keepWorkspace } = parseArgs(argv);
|
|
185
|
+
const template = tArg && TEMPLATES.includes(tArg)
|
|
186
|
+
? tArg
|
|
187
|
+
: (await prompts({
|
|
188
|
+
type: 'select',
|
|
189
|
+
name: 'template',
|
|
190
|
+
message: 'Template',
|
|
191
|
+
choices: TEMPLATES.map((value) => ({ title: value, value })),
|
|
192
|
+
})).template;
|
|
193
|
+
if (!template) {
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
const dirAns = dirArg ??
|
|
197
|
+
(await prompts({
|
|
198
|
+
type: 'text',
|
|
199
|
+
name: 'dir',
|
|
200
|
+
message: 'Project directory',
|
|
201
|
+
initial: `quadro-${template}`,
|
|
202
|
+
})).dir;
|
|
203
|
+
if (!dirAns || typeof dirAns !== 'string') {
|
|
204
|
+
process.exit(1);
|
|
205
|
+
}
|
|
206
|
+
const dest = path.resolve(process.cwd(), dirAns);
|
|
207
|
+
if (await pathExists(dest)) {
|
|
208
|
+
const files = await readdir(dest);
|
|
209
|
+
if (files.length > 0) {
|
|
210
|
+
console.error(`Directory not empty: ${dest}`);
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const src = await resolveTemplateDir(template);
|
|
215
|
+
if (!src) {
|
|
216
|
+
console.error(`Template not found: ${template} (expected under create-quadrokit/templates or packages/templates)`);
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
await copyTemplate(src, dest);
|
|
220
|
+
await writeStandaloneBiome(dest, keepWorkspace);
|
|
221
|
+
await seedGenerated(dest);
|
|
222
|
+
await patchPackageJson(dest, path
|
|
223
|
+
.basename(dest)
|
|
224
|
+
.replace(/[^a-z0-9-]/gi, '-')
|
|
225
|
+
.toLowerCase() || 'quadro-app', keepWorkspace);
|
|
226
|
+
await writeQuadroClientImport(dest);
|
|
227
|
+
await writeReadme(dest, template);
|
|
228
|
+
console.log(`
|
|
229
|
+
Created QuadroKit project at ${dest}
|
|
230
|
+
|
|
231
|
+
cd ${path.basename(dest)}
|
|
232
|
+
bun install
|
|
233
|
+
bun run dev
|
|
234
|
+
`);
|
|
235
|
+
}
|
|
236
|
+
main().catch((e) => {
|
|
237
|
+
console.error(e);
|
|
238
|
+
process.exit(1);
|
|
239
|
+
});
|
package/package.json
CHANGED
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-quadrokit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Scaffold a QuadroKit Vite + React app from a template",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"bin": "./
|
|
6
|
+
"bin": "./dist/index.js",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"templates",
|
|
10
|
+
"vendor",
|
|
11
|
+
"biome.monorepo.json"
|
|
12
|
+
],
|
|
7
13
|
"scripts": {
|
|
8
|
-
"
|
|
14
|
+
"build": "tsc -p tsconfig.build.json",
|
|
15
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
16
|
+
"prepublishOnly": "bun run scripts/sync-for-publish.ts && bun run build"
|
|
9
17
|
},
|
|
10
18
|
"dependencies": {
|
|
11
19
|
"prompts": "^2.4.2"
|
|
12
20
|
},
|
|
13
21
|
"devDependencies": {
|
|
14
|
-
"@types/bun": "^1.3.11",
|
|
15
22
|
"@types/node": "^25.5.0",
|
|
16
23
|
"@types/prompts": "^2.4.9",
|
|
17
24
|
"typescript": "^5.9.3"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# QuadroKit templates
|
|
2
|
+
|
|
3
|
+
Vite + React 19 + React Router 7 + **Tailwind 3.4** + **i18next** + **react-hook-form** + **zod** + **zustand** (minimal example).
|
|
4
|
+
|
|
5
|
+
| Template | Use case |
|
|
6
|
+
|----------|----------|
|
|
7
|
+
| `dashboard` | Default shell, agencies sample page, form demo |
|
|
8
|
+
| `website` | Marketing-style hero + same stack |
|
|
9
|
+
| `ecommerce` | Product-card style placeholder |
|
|
10
|
+
| `admin-shell` | Sidebar + main content layout |
|
|
11
|
+
|
|
12
|
+
## Monorepo
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
cd packages/templates/dashboard # or website / ecommerce / admin-shell
|
|
16
|
+
bun install # from repo root once
|
|
17
|
+
bun run dev
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Copy [`.env.example`](dashboard/.env.example) to `.env` and set `VITE_4D_ORIGIN`.
|
|
21
|
+
|
|
22
|
+
## Biome (per template)
|
|
23
|
+
|
|
24
|
+
Each template has a [`biome.json`](dashboard/biome.json) with `"root": false` and `"extends": "//"` (Biome v2 monorepo pattern: inherit repo root, scope files to that template). `vcs.useIgnoreFile` is off so Biome does not require a `.gitignore` next to the template.
|
|
25
|
+
|
|
26
|
+
| Script | Purpose |
|
|
27
|
+
|--------|---------|
|
|
28
|
+
| `bun run format` | `biome format --write .` |
|
|
29
|
+
| `bun run lint` | `biome lint .` (read-only) |
|
|
30
|
+
| `bun run lint:fix` | `biome lint --write .` |
|
|
31
|
+
| `bun run check` / `biome:check` | `biome check .` (read-only) |
|
|
32
|
+
| `bun run check:fix` | `biome check --write .` |
|
|
33
|
+
| `bun run ci:quick` | format → lint:fix → check:fix → build |
|
|
34
|
+
|
|
35
|
+
## Scaffolding outside the monorepo
|
|
36
|
+
|
|
37
|
+
Use [`create-quadrokit`](../../create-quadrokit/README.md); it copies a template, seeds `.quadrokit/generated`, and rewrites `workspace:*` to published semver.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Commit messages must follow Conventional Commits (commitlint @commitlint/config-conventional)
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Git commits (Conventional Commits / commitlint)
|
|
7
|
+
|
|
8
|
+
When proposing or writing **commit messages** (including one-line suggestions, full messages, or PR squash titles), use **Conventional Commits** compatible with **@commitlint/config-conventional**.
|
|
9
|
+
|
|
10
|
+
## Header format
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
<type>(<optional-scope>): <short description>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
- **type** (pick one): `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `revert`
|
|
17
|
+
- **scope** (optional): lowercase, short package or area (e.g. `client`, `templates`, `ui`, `create-quadrokit`)
|
|
18
|
+
- **description**: imperative mood, **lowercase**, **no trailing period**, **≤ ~72 characters** for the subject line
|
|
19
|
+
|
|
20
|
+
## Breaking changes
|
|
21
|
+
|
|
22
|
+
- Add `!` after type or scope: `feat(api)!: remove legacy login endpoint`
|
|
23
|
+
- Or explain in the body with a line starting with `BREAKING CHANGE:`
|
|
24
|
+
|
|
25
|
+
## Body and footer (when needed)
|
|
26
|
+
|
|
27
|
+
- Separate body from subject with a blank line.
|
|
28
|
+
- Wrap body at ~100 chars.
|
|
29
|
+
- Footer: `Refs: #123` / `Fixes: #123` if applicable.
|
|
30
|
+
|
|
31
|
+
## Examples
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
feat(client): add entity set release helper
|
|
35
|
+
|
|
36
|
+
fix(templates): correct vite proxy target env var
|
|
37
|
+
|
|
38
|
+
chore: bump biome to 2.4.10
|
|
39
|
+
|
|
40
|
+
docs(readme): document ci:quick script
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Do not
|
|
44
|
+
|
|
45
|
+
- Vague subjects: `update`, `fix stuff`, `wip`
|
|
46
|
+
- End the subject line with a period
|
|
47
|
+
- Use title case for the description (use sentence case / lowercase)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>QuadroKit — Admin shell</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@quadrokit/template-admin-shell",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "tsc -b && vite build",
|
|
9
|
+
"preview": "vite preview",
|
|
10
|
+
"typecheck": "tsc -b --noEmit",
|
|
11
|
+
"format": "biome format --write .",
|
|
12
|
+
"lint": "biome lint .",
|
|
13
|
+
"lint:fix": "biome lint --write .",
|
|
14
|
+
"check": "biome check .",
|
|
15
|
+
"check:fix": "biome check --write .",
|
|
16
|
+
"biome:check": "biome check .",
|
|
17
|
+
"ci:quick": "bun run format && bun run lint:fix && bun run check:fix && bun run build"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@hookform/resolvers": "^5.2.2",
|
|
21
|
+
"@quadrokit/client": "workspace:*",
|
|
22
|
+
"@quadrokit/sample-client": "workspace:*",
|
|
23
|
+
"@quadrokit/ui": "workspace:*",
|
|
24
|
+
"i18next": "^26.0.3",
|
|
25
|
+
"react": "^19.2.4",
|
|
26
|
+
"react-dom": "^19.2.4",
|
|
27
|
+
"react-hook-form": "^7.72.0",
|
|
28
|
+
"react-i18next": "^17.0.2",
|
|
29
|
+
"react-router-dom": "^7.13.2",
|
|
30
|
+
"zod": "^4.3.6",
|
|
31
|
+
"zustand": "^5.0.12"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@biomejs/biome": "2.4.10",
|
|
35
|
+
"@types/node": "^25.5.0",
|
|
36
|
+
"@types/react": "^19.2.14",
|
|
37
|
+
"@types/react-dom": "^19.2.3",
|
|
38
|
+
"@vitejs/plugin-react": "^6.0.1",
|
|
39
|
+
"autoprefixer": "^10.4.27",
|
|
40
|
+
"postcss": "^8.5.8",
|
|
41
|
+
"tailwindcss": "^3.4.17",
|
|
42
|
+
"typescript": "^5.9.3",
|
|
43
|
+
"vite": "^8.0.3"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { ThemeToolbar } from '@quadrokit/ui'
|
|
2
|
+
import { useTranslation } from 'react-i18next'
|
|
3
|
+
import { Link, NavLink, Outlet } from 'react-router-dom'
|
|
4
|
+
|
|
5
|
+
export function AppShell() {
|
|
6
|
+
const { t } = useTranslation()
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<div className="min-h-dvh bg-background text-foreground">
|
|
10
|
+
<div className="flex min-h-dvh">
|
|
11
|
+
<aside className="hidden w-56 shrink-0 border-r border-border bg-card/40 p-4 md:block">
|
|
12
|
+
<Link to="/" className="block text-lg font-semibold tracking-tight">
|
|
13
|
+
{t('app.title')}
|
|
14
|
+
</Link>
|
|
15
|
+
<nav className="mt-6 flex flex-col gap-2 text-sm text-muted-foreground">
|
|
16
|
+
<NavLink
|
|
17
|
+
to="/"
|
|
18
|
+
end
|
|
19
|
+
className={({ isActive }) =>
|
|
20
|
+
isActive ? 'font-medium text-foreground' : 'hover:text-foreground'
|
|
21
|
+
}
|
|
22
|
+
>
|
|
23
|
+
{t('app.nav_home')}
|
|
24
|
+
</NavLink>
|
|
25
|
+
<NavLink
|
|
26
|
+
to="/agencies"
|
|
27
|
+
className={({ isActive }) =>
|
|
28
|
+
isActive ? 'font-medium text-foreground' : 'hover:text-foreground'
|
|
29
|
+
}
|
|
30
|
+
>
|
|
31
|
+
{t('app.nav_agencies')}
|
|
32
|
+
</NavLink>
|
|
33
|
+
</nav>
|
|
34
|
+
</aside>
|
|
35
|
+
<div className="flex min-w-0 flex-1 flex-col">
|
|
36
|
+
<header className="border-b border-border bg-card/40 px-4 py-3 backdrop-blur md:hidden">
|
|
37
|
+
<div className="flex flex-wrap items-center justify-between gap-2">
|
|
38
|
+
<Link to="/" className="font-semibold">
|
|
39
|
+
{t('app.title')}
|
|
40
|
+
</Link>
|
|
41
|
+
<nav className="flex gap-3 text-sm text-muted-foreground">
|
|
42
|
+
<NavLink
|
|
43
|
+
to="/"
|
|
44
|
+
end
|
|
45
|
+
className={({ isActive }) => (isActive ? 'text-foreground' : '')}
|
|
46
|
+
>
|
|
47
|
+
{t('app.nav_home')}
|
|
48
|
+
</NavLink>
|
|
49
|
+
<NavLink
|
|
50
|
+
to="/agencies"
|
|
51
|
+
className={({ isActive }) => (isActive ? 'text-foreground' : '')}
|
|
52
|
+
>
|
|
53
|
+
{t('app.nav_agencies')}
|
|
54
|
+
</NavLink>
|
|
55
|
+
</nav>
|
|
56
|
+
</div>
|
|
57
|
+
</header>
|
|
58
|
+
<div className="border-b border-border px-4 py-3">
|
|
59
|
+
<ThemeToolbar />
|
|
60
|
+
</div>
|
|
61
|
+
<main className="mx-auto w-full max-w-5xl flex-1 px-4 py-8">
|
|
62
|
+
<Outlet />
|
|
63
|
+
</main>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import i18n from 'i18next'
|
|
2
|
+
import { initReactI18next } from 'react-i18next'
|
|
3
|
+
import en from './locales/en.json'
|
|
4
|
+
|
|
5
|
+
void i18n.use(initReactI18next).init({
|
|
6
|
+
resources: { en: { translation: en } },
|
|
7
|
+
lng: 'en',
|
|
8
|
+
fallbackLng: 'en',
|
|
9
|
+
interpolation: { escapeValue: false },
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
export default i18n
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"app": {
|
|
3
|
+
"title": "QuadroKit",
|
|
4
|
+
"tagline": "Admin shell with sidebar — same stack, denser layout.",
|
|
5
|
+
"nav_home": "Home",
|
|
6
|
+
"nav_agencies": "Agencies",
|
|
7
|
+
"session_cookie": "Expected 4D session cookie: {{name}}"
|
|
8
|
+
},
|
|
9
|
+
"form": {
|
|
10
|
+
"demo_title": "react-hook-form + zod",
|
|
11
|
+
"label": "Label",
|
|
12
|
+
"placeholder": "Type something…",
|
|
13
|
+
"submit": "Submit"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { StrictMode } from 'react'
|
|
2
|
+
import { createRoot } from 'react-dom/client'
|
|
3
|
+
import { RouterProvider } from 'react-router-dom'
|
|
4
|
+
import '@quadrokit/ui/styles.css'
|
|
5
|
+
import './i18n'
|
|
6
|
+
import { ThemeProvider } from '@quadrokit/ui'
|
|
7
|
+
import { router } from './router'
|
|
8
|
+
|
|
9
|
+
createRoot(document.getElementById('root')!).render(
|
|
10
|
+
<StrictMode>
|
|
11
|
+
<ThemeProvider>
|
|
12
|
+
<RouterProvider router={router} />
|
|
13
|
+
</ThemeProvider>
|
|
14
|
+
</StrictMode>
|
|
15
|
+
)
|