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.
Files changed (94) hide show
  1. package/README.md +2 -2
  2. package/biome.monorepo.json +67 -0
  3. package/dist/index.js +239 -0
  4. package/package.json +11 -4
  5. package/templates/README.md +37 -0
  6. package/templates/admin-shell/.cursor/rules/commitlint-conventional.mdc +47 -0
  7. package/templates/admin-shell/.env.example +2 -0
  8. package/templates/admin-shell/biome.json +12 -0
  9. package/templates/admin-shell/index.html +12 -0
  10. package/templates/admin-shell/package.json +45 -0
  11. package/templates/admin-shell/postcss.config.js +6 -0
  12. package/templates/admin-shell/src/components/AppShell.tsx +68 -0
  13. package/templates/admin-shell/src/i18n.ts +12 -0
  14. package/templates/admin-shell/src/lib/quadro-client.ts +4 -0
  15. package/templates/admin-shell/src/locales/en.json +15 -0
  16. package/templates/admin-shell/src/main.tsx +15 -0
  17. package/templates/admin-shell/src/pages/AgenciesPage.tsx +69 -0
  18. package/templates/admin-shell/src/pages/HomePage.tsx +67 -0
  19. package/templates/admin-shell/src/router.tsx +15 -0
  20. package/templates/admin-shell/src/stores/useSidebarHint.ts +12 -0
  21. package/templates/admin-shell/src/vite-env.d.ts +9 -0
  22. package/templates/admin-shell/tailwind.config.ts +7 -0
  23. package/templates/admin-shell/tsconfig.app.json +16 -0
  24. package/templates/admin-shell/tsconfig.json +4 -0
  25. package/templates/admin-shell/tsconfig.node.json +10 -0
  26. package/templates/admin-shell/vite.config.ts +25 -0
  27. package/templates/dashboard/.cursor/rules/commitlint-conventional.mdc +47 -0
  28. package/templates/dashboard/.env.example +2 -0
  29. package/templates/dashboard/biome.json +12 -0
  30. package/templates/dashboard/index.html +12 -0
  31. package/templates/dashboard/package.json +45 -0
  32. package/templates/dashboard/postcss.config.js +6 -0
  33. package/templates/dashboard/src/components/AppShell.tsx +44 -0
  34. package/templates/dashboard/src/i18n.ts +12 -0
  35. package/templates/dashboard/src/lib/quadro-client.ts +4 -0
  36. package/templates/dashboard/src/locales/en.json +15 -0
  37. package/templates/dashboard/src/main.tsx +15 -0
  38. package/templates/dashboard/src/pages/AgenciesPage.tsx +69 -0
  39. package/templates/dashboard/src/pages/HomePage.tsx +67 -0
  40. package/templates/dashboard/src/router.tsx +15 -0
  41. package/templates/dashboard/src/stores/useSidebarHint.ts +12 -0
  42. package/templates/dashboard/src/vite-env.d.ts +9 -0
  43. package/templates/dashboard/tailwind.config.ts +7 -0
  44. package/templates/dashboard/tsconfig.app.json +16 -0
  45. package/templates/dashboard/tsconfig.json +4 -0
  46. package/templates/dashboard/tsconfig.node.json +10 -0
  47. package/templates/dashboard/vite.config.ts +25 -0
  48. package/templates/ecommerce/.cursor/rules/commitlint-conventional.mdc +47 -0
  49. package/templates/ecommerce/.env.example +2 -0
  50. package/templates/ecommerce/biome.json +12 -0
  51. package/templates/ecommerce/index.html +12 -0
  52. package/templates/ecommerce/package.json +45 -0
  53. package/templates/ecommerce/postcss.config.js +6 -0
  54. package/templates/ecommerce/src/components/AppShell.tsx +44 -0
  55. package/templates/ecommerce/src/i18n.ts +12 -0
  56. package/templates/ecommerce/src/lib/quadro-client.ts +4 -0
  57. package/templates/ecommerce/src/locales/en.json +20 -0
  58. package/templates/ecommerce/src/main.tsx +15 -0
  59. package/templates/ecommerce/src/pages/AgenciesPage.tsx +69 -0
  60. package/templates/ecommerce/src/pages/HomePage.tsx +52 -0
  61. package/templates/ecommerce/src/router.tsx +15 -0
  62. package/templates/ecommerce/src/stores/useSidebarHint.ts +12 -0
  63. package/templates/ecommerce/src/vite-env.d.ts +9 -0
  64. package/templates/ecommerce/tailwind.config.ts +7 -0
  65. package/templates/ecommerce/tsconfig.app.json +16 -0
  66. package/templates/ecommerce/tsconfig.json +4 -0
  67. package/templates/ecommerce/tsconfig.node.json +10 -0
  68. package/templates/ecommerce/vite.config.ts +25 -0
  69. package/templates/website/.cursor/rules/commitlint-conventional.mdc +47 -0
  70. package/templates/website/.env.example +2 -0
  71. package/templates/website/biome.json +12 -0
  72. package/templates/website/index.html +12 -0
  73. package/templates/website/package.json +45 -0
  74. package/templates/website/postcss.config.js +6 -0
  75. package/templates/website/src/components/AppShell.tsx +44 -0
  76. package/templates/website/src/i18n.ts +12 -0
  77. package/templates/website/src/lib/quadro-client.ts +4 -0
  78. package/templates/website/src/locales/en.json +21 -0
  79. package/templates/website/src/main.tsx +15 -0
  80. package/templates/website/src/pages/AgenciesPage.tsx +69 -0
  81. package/templates/website/src/pages/HomePage.tsx +83 -0
  82. package/templates/website/src/router.tsx +15 -0
  83. package/templates/website/src/stores/useSidebarHint.ts +12 -0
  84. package/templates/website/src/vite-env.d.ts +9 -0
  85. package/templates/website/tailwind.config.ts +7 -0
  86. package/templates/website/tsconfig.app.json +16 -0
  87. package/templates/website/tsconfig.json +4 -0
  88. package/templates/website/tsconfig.node.json +10 -0
  89. package/templates/website/vite.config.ts +25 -0
  90. package/vendor/generated/client.gen.ts +2725 -0
  91. package/vendor/generated/meta.json +5 -0
  92. package/vendor/generated/types.gen.ts +991 -0
  93. package/src/index.ts +0 -237
  94. 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.1.0` (unless `--keep-workspace`).
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.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Scaffold a QuadroKit Vite + React app from a template",
5
5
  "type": "module",
6
- "bin": "./src/index.ts",
6
+ "bin": "./dist/index.js",
7
+ "files": [
8
+ "dist",
9
+ "templates",
10
+ "vendor",
11
+ "biome.monorepo.json"
12
+ ],
7
13
  "scripts": {
8
- "typecheck": "tsc -p tsconfig.json --noEmit"
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,2 @@
1
+ # Origin of your 4D web server (REST). The Vite dev server proxies /rest here.
2
+ VITE_4D_ORIGIN=http://127.0.0.1:7080
@@ -0,0 +1,12 @@
1
+ {
2
+ "root": false,
3
+ "extends": "//",
4
+ "vcs": {
5
+ "enabled": true,
6
+ "clientKind": "git",
7
+ "useIgnoreFile": false
8
+ },
9
+ "files": {
10
+ "includes": ["**", "!**/node_modules", "!**/dist", "!**/.quadrokit"]
11
+ }
12
+ }
@@ -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,6 @@
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
@@ -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,4 @@
1
+ import { createClient } from '@quadrokit/sample-client'
2
+
3
+ /** Same-origin `/rest` in dev (Vite proxy) and production (reverse proxy). */
4
+ export const quadro = createClient({ baseURL: '/rest' })
@@ -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
+ )