create-freakpunch 0.0.0-stage → 0.1.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 (220) hide show
  1. package/README.md +22 -2
  2. package/bin/create-freakpunch.mjs +227 -0
  3. package/files/README.md +15 -0
  4. package/files/app.config.ts +58 -0
  5. package/package.json +21 -4
  6. package/template/.dockerignore +17 -0
  7. package/template/.oxlintrc.json +9 -0
  8. package/template/AGENTS.md +6 -0
  9. package/template/BACKEND.md +86 -0
  10. package/template/CLAUDE.md +292 -0
  11. package/template/Dockerfile +19 -0
  12. package/template/_gitignore +26 -0
  13. package/template/components.json +27 -0
  14. package/template/design/COMPONENTS.md +145 -0
  15. package/template/design/DESIGN.md +212 -0
  16. package/template/design/PATTERNS.md +110 -0
  17. package/template/index.html +25 -0
  18. package/template/nginx.conf +32 -0
  19. package/template/package-lock.json +6484 -0
  20. package/template/package.json +51 -0
  21. package/template/public/favicon.svg +1 -0
  22. package/template/public/robots.txt +2 -0
  23. package/template/src/app.config.ts +61 -0
  24. package/template/src/app.roles.ts +24 -0
  25. package/template/src/components/ui/alert-dialog.tsx +181 -0
  26. package/template/src/components/ui/avatar.tsx +116 -0
  27. package/template/src/components/ui/badge.tsx +57 -0
  28. package/template/src/components/ui/breadcrumb.tsx +130 -0
  29. package/template/src/components/ui/button.tsx +58 -0
  30. package/template/src/components/ui/calendar.tsx +277 -0
  31. package/template/src/components/ui/card.tsx +99 -0
  32. package/template/src/components/ui/chart.tsx +370 -0
  33. package/template/src/components/ui/checkbox.tsx +28 -0
  34. package/template/src/components/ui/combobox.tsx +329 -0
  35. package/template/src/components/ui/command.tsx +191 -0
  36. package/template/src/components/ui/dialog.tsx +162 -0
  37. package/template/src/components/ui/dropdown-menu.tsx +271 -0
  38. package/template/src/components/ui/empty.tsx +103 -0
  39. package/template/src/components/ui/field.tsx +236 -0
  40. package/template/src/components/ui/input-group.tsx +263 -0
  41. package/template/src/components/ui/input.tsx +45 -0
  42. package/template/src/components/ui/kbd.tsx +31 -0
  43. package/template/src/components/ui/label.tsx +17 -0
  44. package/template/src/components/ui/pagination.tsx +134 -0
  45. package/template/src/components/ui/popover.tsx +87 -0
  46. package/template/src/components/ui/radio-group.tsx +35 -0
  47. package/template/src/components/ui/select.tsx +204 -0
  48. package/template/src/components/ui/separator.tsx +25 -0
  49. package/template/src/components/ui/sheet.tsx +139 -0
  50. package/template/src/components/ui/sidebar.tsx +741 -0
  51. package/template/src/components/ui/skeleton.tsx +13 -0
  52. package/template/src/components/ui/sonner.tsx +78 -0
  53. package/template/src/components/ui/spinner.tsx +42 -0
  54. package/template/src/components/ui/switch.tsx +29 -0
  55. package/template/src/components/ui/table.tsx +121 -0
  56. package/template/src/components/ui/tabs.tsx +191 -0
  57. package/template/src/components/ui/textarea.tsx +17 -0
  58. package/template/src/components/ui/toggle-group.tsx +94 -0
  59. package/template/src/components/ui/toggle.tsx +48 -0
  60. package/template/src/components/ui/tooltip.tsx +91 -0
  61. package/template/src/core/api/http.ts +76 -0
  62. package/template/src/core/api/mock.ts +88 -0
  63. package/template/src/core/api/rest.ts +67 -0
  64. package/template/src/core/api/types.ts +21 -0
  65. package/template/src/core/audit/audit.service.ts +125 -0
  66. package/template/src/core/audit/hooks.ts +19 -0
  67. package/template/src/core/auth/auth-store.ts +52 -0
  68. package/template/src/core/auth/auth.service.ts +75 -0
  69. package/template/src/core/auth/can.tsx +39 -0
  70. package/template/src/core/auth/dev-role-switcher.tsx +64 -0
  71. package/template/src/core/auth/permissions.ts +115 -0
  72. package/template/src/core/auth/require-auth.tsx +16 -0
  73. package/template/src/core/auth/require-permission.tsx +24 -0
  74. package/template/src/core/auth/roles.ts +9 -0
  75. package/template/src/core/auth/types.ts +20 -0
  76. package/template/src/core/auth/use-auth.ts +19 -0
  77. package/template/src/core/company/company.service.ts +92 -0
  78. package/template/src/core/company/hooks.ts +40 -0
  79. package/template/src/core/company/password-policy.ts +40 -0
  80. package/template/src/core/company/types.ts +46 -0
  81. package/template/src/core/components/button-link.tsx +14 -0
  82. package/template/src/core/components/confirm-dialog.tsx +116 -0
  83. package/template/src/core/components/data-table/data-table.tsx +297 -0
  84. package/template/src/core/components/data-table/features.ts +24 -0
  85. package/template/src/core/components/data-table/table-header-cell.tsx +13 -0
  86. package/template/src/core/components/data-table/toolbar.tsx +100 -0
  87. package/template/src/core/components/data-table/use-list-url-state.ts +108 -0
  88. package/template/src/core/components/date-picker.tsx +71 -0
  89. package/template/src/core/components/file-attachments.tsx +84 -0
  90. package/template/src/core/components/form-error.tsx +9 -0
  91. package/template/src/core/components/form-page.tsx +93 -0
  92. package/template/src/core/components/image-upload.tsx +86 -0
  93. package/template/src/core/components/page.tsx +74 -0
  94. package/template/src/core/components/phone-input.tsx +58 -0
  95. package/template/src/core/components/search-select.tsx +57 -0
  96. package/template/src/core/components/segmented.tsx +41 -0
  97. package/template/src/core/components/states.tsx +97 -0
  98. package/template/src/core/components/status-badge.tsx +26 -0
  99. package/template/src/core/components/user-avatar.tsx +23 -0
  100. package/template/src/core/config/runtime.ts +14 -0
  101. package/template/src/core/config/types.ts +36 -0
  102. package/template/src/core/i18n/i18n.ts +69 -0
  103. package/template/src/core/i18n/i18next.d.ts +12 -0
  104. package/template/src/core/i18n/keys.ts +37 -0
  105. package/template/src/core/i18n/language-switcher.tsx +45 -0
  106. package/template/src/core/i18n/types.ts +17 -0
  107. package/template/src/core/i18n/use-format.ts +31 -0
  108. package/template/src/core/layout/app-shell.tsx +50 -0
  109. package/template/src/core/layout/app-sidebar.tsx +268 -0
  110. package/template/src/core/layout/command-menu.tsx +117 -0
  111. package/template/src/core/layout/top-bar.tsx +100 -0
  112. package/template/src/core/lib/csv.ts +27 -0
  113. package/template/src/core/lib/format.ts +87 -0
  114. package/template/src/core/lib/phone.ts +50 -0
  115. package/template/src/core/lib/storage.ts +27 -0
  116. package/template/src/core/modules/lazy-page.ts +9 -0
  117. package/template/src/core/modules/navigation.ts +39 -0
  118. package/template/src/core/modules/types.ts +55 -0
  119. package/template/src/core/notifications/hooks.ts +84 -0
  120. package/template/src/core/notifications/notification-center.tsx +251 -0
  121. package/template/src/core/notifications/notifications.service.ts +237 -0
  122. package/template/src/core/notifications/types.ts +65 -0
  123. package/template/src/core/preferences/preferences-store.ts +70 -0
  124. package/template/src/core/theme/brand-context.tsx +53 -0
  125. package/template/src/core/theme/brand.ts +29 -0
  126. package/template/src/core/theme/theme-context.tsx +62 -0
  127. package/template/src/core/theme/theme-options.ts +8 -0
  128. package/template/src/core/theme/theme-toggle.tsx +45 -0
  129. package/template/src/core/users/hooks.ts +51 -0
  130. package/template/src/core/users/types.ts +37 -0
  131. package/template/src/core/users/users.mock.ts +24 -0
  132. package/template/src/core/users/users.service.ts +167 -0
  133. package/template/src/hooks/use-mobile.ts +19 -0
  134. package/template/src/index.css +321 -0
  135. package/template/src/lib/utils.ts +1 -0
  136. package/template/src/locales/en/account.ts +180 -0
  137. package/template/src/locales/en/auth.ts +17 -0
  138. package/template/src/locales/en/common.ts +150 -0
  139. package/template/src/locales/en/company.ts +378 -0
  140. package/template/src/locales/en/dashboard.ts +41 -0
  141. package/template/src/locales/en/errors.ts +18 -0
  142. package/template/src/locales/en/landing.ts +15 -0
  143. package/template/src/locales/en/notifications.ts +43 -0
  144. package/template/src/locales/en/settings.ts +25 -0
  145. package/template/src/locales/index.ts +58 -0
  146. package/template/src/locales/tr/account.ts +184 -0
  147. package/template/src/locales/tr/auth.ts +21 -0
  148. package/template/src/locales/tr/common.ts +153 -0
  149. package/template/src/locales/tr/company.ts +382 -0
  150. package/template/src/locales/tr/dashboard.ts +45 -0
  151. package/template/src/locales/tr/errors.ts +22 -0
  152. package/template/src/locales/tr/landing.ts +19 -0
  153. package/template/src/locales/tr/notifications.ts +47 -0
  154. package/template/src/locales/tr/settings.ts +29 -0
  155. package/template/src/main.tsx +16 -0
  156. package/template/src/modules/locales.ts +14 -0
  157. package/template/src/modules/products/api/products.mock.ts +46 -0
  158. package/template/src/modules/products/api/products.service.ts +84 -0
  159. package/template/src/modules/products/components/product-status.tsx +21 -0
  160. package/template/src/modules/products/hooks.ts +73 -0
  161. package/template/src/modules/products/index.ts +50 -0
  162. package/template/src/modules/products/locales/en.ts +78 -0
  163. package/template/src/modules/products/locales/tr.ts +82 -0
  164. package/template/src/modules/products/pages/product-detail-page.tsx +108 -0
  165. package/template/src/modules/products/pages/product-form-page.tsx +211 -0
  166. package/template/src/modules/products/pages/products-list-page.tsx +286 -0
  167. package/template/src/modules/products/types.ts +61 -0
  168. package/template/src/modules/registry.ts +19 -0
  169. package/template/src/pages/dashboard/dashboard-page.tsx +270 -0
  170. package/template/src/pages/dashboard/dashboard.service.ts +92 -0
  171. package/template/src/pages/landing-page.tsx +79 -0
  172. package/template/src/pages/login-page.tsx +140 -0
  173. package/template/src/pages/no-access-page.tsx +11 -0
  174. package/template/src/pages/not-found-page.tsx +62 -0
  175. package/template/src/pages/settings/account/account.service.ts +147 -0
  176. package/template/src/pages/settings/account/components/mock-qr.tsx +50 -0
  177. package/template/src/pages/settings/account/components/password-strength.tsx +38 -0
  178. package/template/src/pages/settings/account/components/two-factor-dialog.tsx +140 -0
  179. package/template/src/pages/settings/account/hooks.ts +73 -0
  180. package/template/src/pages/settings/account/notifications-page.tsx +103 -0
  181. package/template/src/pages/settings/account/preferences-page.tsx +157 -0
  182. package/template/src/pages/settings/account/profile-page.tsx +242 -0
  183. package/template/src/pages/settings/account/security-page.tsx +364 -0
  184. package/template/src/pages/settings/account/types.ts +37 -0
  185. package/template/src/pages/settings/company/audit-log-page.tsx +126 -0
  186. package/template/src/pages/settings/company/branding-page.tsx +278 -0
  187. package/template/src/pages/settings/company/components/invite-dialog.tsx +106 -0
  188. package/template/src/pages/settings/company/components/read-only-notice.tsx +13 -0
  189. package/template/src/pages/settings/company/danger-zone-page.tsx +127 -0
  190. package/template/src/pages/settings/company/general-page.tsx +232 -0
  191. package/template/src/pages/settings/company/integrations/api-keys-panel.tsx +170 -0
  192. package/template/src/pages/settings/company/integrations/hooks.ts +37 -0
  193. package/template/src/pages/settings/company/integrations/integrations-page.tsx +57 -0
  194. package/template/src/pages/settings/company/integrations/integrations.service.ts +173 -0
  195. package/template/src/pages/settings/company/integrations/settings-forms.tsx +299 -0
  196. package/template/src/pages/settings/company/integrations/webhooks-panel.tsx +179 -0
  197. package/template/src/pages/settings/company/modules-page.tsx +57 -0
  198. package/template/src/pages/settings/company/roles-page.tsx +115 -0
  199. package/template/src/pages/settings/company/security-policies-page.tsx +164 -0
  200. package/template/src/pages/settings/company/users-page.tsx +262 -0
  201. package/template/src/pages/settings/settings-layout.tsx +57 -0
  202. package/template/src/pages/settings/settings-nav.ts +69 -0
  203. package/template/src/pages/settings/settings-routes.tsx +49 -0
  204. package/template/src/providers.tsx +68 -0
  205. package/template/src/router.tsx +54 -0
  206. package/template/tools/check-design.mjs +73 -0
  207. package/template/tools/new-module.mjs +90 -0
  208. package/template/tools/templates/module/api/__id__.mock.ts.tpl +17 -0
  209. package/template/tools/templates/module/api/__id__.service.ts.tpl +51 -0
  210. package/template/tools/templates/module/hooks.ts.tpl +57 -0
  211. package/template/tools/templates/module/index.ts.tpl +37 -0
  212. package/template/tools/templates/module/locales/en.ts.tpl +45 -0
  213. package/template/tools/templates/module/locales/tr.ts.tpl +49 -0
  214. package/template/tools/templates/module/pages/__id__-form-page.tsx.tpl +121 -0
  215. package/template/tools/templates/module/pages/__id__-list-page.tsx.tpl +237 -0
  216. package/template/tools/templates/module/types.ts.tpl +32 -0
  217. package/template/tsconfig.app.json +32 -0
  218. package/template/tsconfig.json +12 -0
  219. package/template/tsconfig.node.json +23 -0
  220. package/template/vite.config.ts +44 -0
package/README.md CHANGED
@@ -1,3 +1,23 @@
1
- # Temporary Holding Version
1
+ # create-freakpunch
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Adds the freakpunch admin panel to any project as a self-contained `fp-admin/` folder. It has its own
4
+ `package.json`, talks to your backend over HTTP, and works next to PHP, Laravel, Next.js or anything else.
5
+
6
+ ```bash
7
+ cd my-project
8
+ npm create freakpunch@latest
9
+ cd fp-admin
10
+ npm run dev
11
+ ```
12
+
13
+ Options: `npm create freakpunch@latest my-folder`, `-- --no-install`, `-- --no-agent-files`.
14
+
15
+ The command also adds a short pointer to `fp-admin/` in your project's `CLAUDE.md` and `AGENTS.md`
16
+ (creating them if needed), so AI agents started at the project root follow the panel's design rules.
17
+
18
+ Get newer freakpunch files later (your modules and config are never touched; `--dry-run` previews):
19
+
20
+ ```bash
21
+ cd fp-admin
22
+ npm run fp:update
23
+ ```
@@ -0,0 +1,227 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * npm create freakpunch@latest adds ./fp-admin to the current project
4
+ * npx create-freakpunch@latest update refreshes freakpunch's own files inside fp-admin
5
+ */
6
+ import { spawnSync } from 'node:child_process'
7
+ import { createHash } from 'node:crypto'
8
+ import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs'
9
+ import path from 'node:path'
10
+
11
+ const pkgDir = path.resolve(import.meta.dirname, '..')
12
+ const templateDir = path.join(pkgDir, 'template')
13
+ const { version } = JSON.parse(readFileSync(path.join(pkgDir, 'package.json'), 'utf8'))
14
+ const MANIFEST = '.freakpunch/manifest.json'
15
+
16
+ /** These belong to the project: created on install, never touched by update. */
17
+ const PROJECT_OWNED = [
18
+ /^src\/app\.config\.ts$/,
19
+ /^src\/app\.roles\.ts$/,
20
+ /^src\/modules\//,
21
+ /^public\//,
22
+ /^README\.md$/,
23
+ /^\.gitignore$/,
24
+ /^package\.json$/,
25
+ /^package-lock\.json$/,
26
+ ]
27
+ const isProjectOwned = (file) => PROJECT_OWNED.some((r) => r.test(file))
28
+
29
+ const AGENT_BLOCK_START = '<!-- freakpunch:start -->'
30
+ const AGENT_BLOCK_END = '<!-- freakpunch:end -->'
31
+ const agentBlock = (dir) => `${AGENT_BLOCK_START}
32
+ ## Admin panel (\`${dir}/\`)
33
+
34
+ The admin panel lives in \`${dir}/\` and is built on freakpunch. Before changing anything there, read
35
+ \`${dir}/CLAUDE.md\` and \`${dir}/design/\` and follow them exactly. New features are modules
36
+ (\`cd ${dir} && npm run new-module -- <id>\`) and must use the existing design and components. Run
37
+ \`npm run check\` inside \`${dir}/\` before finishing.
38
+ ${AGENT_BLOCK_END}
39
+ `
40
+
41
+ function fail(message) {
42
+ console.error(`\n✖ ${message}\n`)
43
+ process.exit(1)
44
+ }
45
+
46
+ const hash = (buffer) => createHash('sha256').update(buffer).digest('hex')
47
+ /** Text compares with LF so Windows checkouts (CRLF) don't look edited. */
48
+ const hashFile = (abs) => hash(readFileSync(abs, 'utf8').replaceAll('\r\n', '\n'))
49
+
50
+ function walk(dir, base = dir) {
51
+ return readdirSync(dir).flatMap((name) => {
52
+ const abs = path.join(dir, name)
53
+ return statSync(abs).isDirectory() ? walk(abs, base) : [path.relative(base, abs).replaceAll('\\', '/')]
54
+ })
55
+ }
56
+
57
+ /** Template files by their installed path (`_gitignore` → `.gitignore`). */
58
+ const templateFiles = () => walk(templateDir).map((file) => ({ source: file, target: file === '_gitignore' ? '.gitignore' : file }))
59
+
60
+ function copyInto(root, source, target) {
61
+ const abs = path.join(root, target)
62
+ mkdirSync(path.dirname(abs), { recursive: true })
63
+ copyFileSync(path.join(templateDir, source), abs)
64
+ }
65
+
66
+ function writeManifest(root, files) {
67
+ mkdirSync(path.join(root, '.freakpunch'), { recursive: true })
68
+ writeFileSync(path.join(root, MANIFEST), `${JSON.stringify({ version, files }, null, 2)}\n`)
69
+ }
70
+
71
+ function addAgentNotes(project, dir) {
72
+ const changed = []
73
+ for (const name of ['CLAUDE.md', 'AGENTS.md']) {
74
+ const abs = path.join(project, name)
75
+ const current = existsSync(abs) ? readFileSync(abs, 'utf8') : ''
76
+ if (current.includes(AGENT_BLOCK_START)) continue
77
+ writeFileSync(abs, current ? `${current.trimEnd()}\n\n${agentBlock(dir)}` : agentBlock(dir))
78
+ changed.push(name)
79
+ }
80
+ return changed
81
+ }
82
+
83
+ function create(args) {
84
+ const flags = new Set(args.filter((a) => a.startsWith('--')))
85
+ const dirName = args.find((a) => !a.startsWith('--')) ?? 'fp-admin'
86
+ const project = process.cwd()
87
+ const root = path.resolve(project, dirName)
88
+ if (existsSync(root) && readdirSync(root).length > 0) {
89
+ fail(`${dirName}/ already exists. To get newer freakpunch files, run: cd ${dirName} && npm run fp:update`)
90
+ }
91
+
92
+ console.log(`\nAdding freakpunch ${version} to ${dirName}/`)
93
+ const files = {}
94
+ for (const { source, target } of templateFiles()) {
95
+ copyInto(root, source, target)
96
+ if (!isProjectOwned(target)) files[target] = hashFile(path.join(root, target))
97
+ }
98
+
99
+ // Installed defaults: start clean instead of with the template's demo content.
100
+ const appName = path.basename(project).replace(/[^\w .-]/g, '') || 'Admin'
101
+ const config = readFileSync(path.join(pkgDir, 'files/app.config.ts'), 'utf8').replaceAll('__APP_NAME__', appName)
102
+ writeFileSync(path.join(root, 'src/app.config.ts'), config)
103
+ copyFileSync(path.join(pkgDir, 'files/README.md'), path.join(root, 'README.md'))
104
+ writeManifest(root, files)
105
+
106
+ const notes = flags.has('--no-agent-files') ? [] : addAgentNotes(project, dirName)
107
+ if (notes.length > 0) console.log(`Added a pointer to ${dirName}/ in ${notes.join(' and ')} so AI agents follow its rules.`)
108
+
109
+ if (!flags.has('--no-install')) {
110
+ console.log('Installing dependencies…')
111
+ const result = spawnSync('npm install --no-fund --no-audit', { cwd: root, stdio: 'inherit', shell: true })
112
+ if (result.status !== 0) console.error(`npm install failed; run it yourself inside ${dirName}/.`)
113
+ }
114
+
115
+ if (existsSync(path.join(project, 'tsconfig.json'))) {
116
+ console.log(`\n! This project has a tsconfig.json. Add "${dirName}" to its "exclude" so the host's build ignores the panel.`)
117
+ }
118
+ console.log(`
119
+ Done. Next:
120
+ cd ${dirName}
121
+ npm run dev
122
+
123
+ Sign in with owner@freakpunch.site and any password (demo mode).
124
+ Add a feature: npm run new-module -- assets --en "Assets" --tr "Zimmet"
125
+ Connect a backend, serve under /admin: see ${dirName}/BACKEND.md
126
+ `)
127
+ }
128
+
129
+ function mergeDependencies(root) {
130
+ const localPath = path.join(root, 'package.json')
131
+ const local = JSON.parse(readFileSync(localPath, 'utf8'))
132
+ const next = JSON.parse(readFileSync(path.join(templateDir, 'package.json'), 'utf8'))
133
+ let changed = false
134
+ for (const key of ['dependencies', 'devDependencies', 'scripts']) {
135
+ for (const [name, value] of Object.entries(next[key] ?? {})) {
136
+ if (local[key]?.[name] !== value) {
137
+ local[key] = { ...local[key], [name]: value }
138
+ changed = true
139
+ }
140
+ }
141
+ }
142
+ if (changed) writeFileSync(localPath, `${JSON.stringify(local, null, 2)}\n`)
143
+ return changed
144
+ }
145
+
146
+ function update(args) {
147
+ const dryRun = args.includes('--dry-run')
148
+ const root = [process.cwd(), path.resolve('fp-admin')].find((dir) => existsSync(path.join(dir, MANIFEST)))
149
+ if (!root) fail('No freakpunch install here. Run this inside fp-admin/ (or the folder that contains it).')
150
+
151
+ const old = JSON.parse(readFileSync(path.join(root, MANIFEST), 'utf8'))
152
+ const nextFiles = {}
153
+ const updated = []
154
+ const added = []
155
+ const conflicts = []
156
+ const conflictDir = path.join(root, '.freakpunch/conflicts')
157
+ if (!dryRun) rmSync(conflictDir, { recursive: true, force: true })
158
+
159
+ for (const { source, target } of templateFiles()) {
160
+ if (isProjectOwned(target)) continue
161
+ const incoming = hashFile(path.join(templateDir, source))
162
+ const abs = path.join(root, target)
163
+ const base = old.files[target]
164
+ if (!existsSync(abs)) {
165
+ added.push(target)
166
+ } else {
167
+ const local = hashFile(abs)
168
+ if (local === incoming) {
169
+ nextFiles[target] = incoming
170
+ continue
171
+ }
172
+ if (local !== base) {
173
+ // Edited here and changed upstream: keep the local file, leave the new one beside it.
174
+ conflicts.push(target)
175
+ nextFiles[target] = base ?? incoming
176
+ if (!dryRun) {
177
+ const copy = path.join(conflictDir, target)
178
+ mkdirSync(path.dirname(copy), { recursive: true })
179
+ copyFileSync(path.join(templateDir, source), copy)
180
+ }
181
+ continue
182
+ }
183
+ updated.push(target)
184
+ }
185
+ nextFiles[target] = incoming
186
+ if (!dryRun) copyInto(root, source, target)
187
+ }
188
+
189
+ // Files freakpunch no longer ships: remove them unless they were edited here.
190
+ const removed = []
191
+ for (const [target, base] of Object.entries(old.files)) {
192
+ if (target in nextFiles) continue
193
+ const abs = path.join(root, target)
194
+ if (!existsSync(abs)) continue
195
+ if (hashFile(abs) !== base) {
196
+ conflicts.push(`${target} (removed upstream, kept because it was edited)`)
197
+ continue
198
+ }
199
+ removed.push(target)
200
+ if (!dryRun) rmSync(abs)
201
+ }
202
+
203
+ let installNeeded = false
204
+ if (!dryRun) {
205
+ installNeeded = mergeDependencies(root)
206
+ writeManifest(root, nextFiles)
207
+ }
208
+
209
+ const list = (title, items) => items.length > 0 && console.log(`${title} (${items.length}):\n${items.map((f) => ` ${f}`).join('\n')}`)
210
+ console.log(`\nfreakpunch ${old.version} → ${version}${dryRun ? ' (dry run, nothing changed)' : ''}`)
211
+ list('Updated', updated)
212
+ list('Added', added)
213
+ list('Removed', removed)
214
+ list('Needs your decision: edited here and changed in freakpunch', conflicts)
215
+ if (conflicts.length > 0 && !dryRun) console.log('New versions are in .freakpunch/conflicts/. Merge what you need, then delete that folder.')
216
+ if (updated.length + added.length + removed.length + conflicts.length === 0) console.log('Already up to date.')
217
+ if (installNeeded) {
218
+ const result = spawnSync('npm install --no-fund --no-audit', { cwd: root, stdio: 'inherit', shell: true })
219
+ if (result.status !== 0) console.error('npm install failed; run it yourself.')
220
+ }
221
+ if (!dryRun) console.log('\nRun npm run check, then review the changes with git diff.\n')
222
+ }
223
+
224
+ if (!existsSync(templateDir)) fail('This package has no template. Run: npm run build:template')
225
+ const [command, ...rest] = process.argv.slice(2)
226
+ if (command === 'update') update(rest)
227
+ else create(process.argv.slice(2))
@@ -0,0 +1,15 @@
1
+ # Admin panel
2
+
3
+ Built on [freakpunch](https://www.npmjs.com/package/create-freakpunch).
4
+
5
+ ```bash
6
+ npm run dev # start it
7
+ npm run new-module -- assets --en "Assets" --tr "Zimmet" # add a feature
8
+ npm run check # design rules + lint + build
9
+ npm run fp:update # get newer freakpunch files
10
+ ```
11
+
12
+ - Your code: `src/modules/`, `src/app.config.ts`, `src/app.roles.ts`, `public/`.
13
+ - freakpunch's code: everything else. `npm run fp:update` refreshes it, so don't edit it here.
14
+ - Rules for people and AI agents: [CLAUDE.md](./CLAUDE.md) and [design/](./design).
15
+ - Connecting a backend and deploying under `/admin`: [BACKEND.md](./BACKEND.md).
@@ -0,0 +1,58 @@
1
+ import type { AppConfig, AuthMode } from '@/core/config/types'
2
+
3
+ /** This project's panel: name, brand, sidebar groups, modules and which built-in areas are on. */
4
+ export const appConfig = {
5
+ name: '__APP_NAME__',
6
+ /** A preset key from src/core/theme/brand.ts, or a custom { color, foreground } pair. */
7
+ brand: 'indigo',
8
+
9
+ /** Workspaces shown in the sidebar switcher. */
10
+ workspaces: [{ id: 'main', name: '__APP_NAME__', plan: 'common:plans.business' }],
11
+
12
+ /** Sidebar group order; labels are translation keys. Modules attach nav items to these ids. */
13
+ navGroups: [
14
+ { id: 'overview', label: 'common:nav.groups.overview' },
15
+ { id: 'catalog', label: 'common:nav.groups.catalog' },
16
+ { id: 'content', label: 'common:nav.groups.content' },
17
+ { id: 'system', label: 'common:nav.groups.system' },
18
+ ],
19
+
20
+ /**
21
+ * Turn modules on/off. Keys must exist in src/modules/registry.ts. `products` is the
22
+ * reference module agents copy patterns from; it stays off unless you want it.
23
+ */
24
+ modules: {
25
+ products: false,
26
+ // fp:modules
27
+ },
28
+
29
+ /** Marketing page at `/`; off, so `/` opens the panel. */
30
+ landing: false,
31
+
32
+ /** Built-in areas. Switch off what this project doesn't need. */
33
+ features: {
34
+ /** Off until connected: its demo feed talks about products and stock. */
35
+ notifications: false,
36
+ users: true,
37
+ roles: true,
38
+ securityPolicies: true,
39
+ modules: true,
40
+ integrations: true,
41
+ auditLog: true,
42
+ dangerZone: true,
43
+ /** The sample KPI/revenue/activity cards on the dashboard. */
44
+ demoDashboard: false,
45
+ },
46
+
47
+ /** Default ISO 4217 currency for formatting (Settings → Company → General can override it). */
48
+ currency: 'USD',
49
+
50
+ /** Real REST API base URL; mock services ignore it. */
51
+ apiBaseUrl: import.meta.env.VITE_API_URL ?? '/api',
52
+
53
+ /** mock (demo data) | bearer (JWT) | cookie (Laravel Sanctum). See BACKEND.md. */
54
+ auth: {
55
+ mode: (import.meta.env.VITE_AUTH_MODE ?? 'mock') as AuthMode,
56
+ csrfUrl: import.meta.env.VITE_CSRF_URL ?? '/sanctum/csrf-cookie',
57
+ },
58
+ } satisfies AppConfig
package/package.json CHANGED
@@ -1,6 +1,23 @@
1
1
  {
2
2
  "name": "create-freakpunch",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Add the freakpunch admin panel to any project as an fp-admin folder.",
5
+ "keywords": ["admin", "admin-panel", "dashboard", "react", "vite", "template"],
6
+ "license": "UNLICENSED",
7
+ "type": "module",
8
+ "bin": {
9
+ "create-freakpunch": "bin/create-freakpunch.mjs"
10
+ },
11
+ "files": [
12
+ "bin",
13
+ "files",
14
+ "template"
15
+ ],
16
+ "engines": {
17
+ "node": ">=20.11"
18
+ },
19
+ "scripts": {
20
+ "build:template": "node scripts/build-template.mjs",
21
+ "prepack": "node scripts/build-template.mjs"
22
+ }
23
+ }
@@ -0,0 +1,17 @@
1
+ node_modules
2
+ dist
3
+ .git
4
+ .gitignore
5
+ .vscode
6
+ .idea
7
+ *.log
8
+ npm-debug.log*
9
+ Dockerfile
10
+ .dockerignore
11
+ README.md
12
+ .env
13
+ .env.*
14
+ assets
15
+ packages
16
+ .claude
17
+ CLAUDE.md
@@ -0,0 +1,9 @@
1
+ {
2
+ "$schema": "./node_modules/oxlint/configuration_schema.json",
3
+ "plugins": ["react", "typescript", "oxc"],
4
+ "ignorePatterns": ["dist", "src/components/ui/**", "src/hooks/**", "packages/**", "tools/**"],
5
+ "rules": {
6
+ "react/rules-of-hooks": "error",
7
+ "react/only-export-components": ["warn", { "allowConstantExport": true }]
8
+ }
9
+ }
@@ -0,0 +1,6 @@
1
+ # Agent instructions
2
+
3
+ Read [CLAUDE.md](./CLAUDE.md) and [design/](./design) completely before changing anything, and follow them
4
+ exactly. They apply to every AI agent working here, not only Claude.
5
+
6
+ Before you finish, run `npm run check` and fix everything it reports.
@@ -0,0 +1,86 @@
1
+ # Connecting a backend
2
+
3
+ The panel is a static single-page app. It talks to your backend over HTTP, so the backend can be
4
+ Laravel, Next.js route handlers, Spring Boot or anything else. Until you switch it, everything runs on demo data.
5
+
6
+ ## 1. Settings
7
+
8
+ Put these in `.env` (build time, not secret: never put secrets in `VITE_` variables).
9
+
10
+ | Variable | Example | Meaning |
11
+ | --- | --- | --- |
12
+ | `VITE_AUTH_MODE` | `bearer` or `cookie` | Default `mock` (demo data). `bearer`: token in the `Authorization` header. `cookie`: session cookie + `X-XSRF-TOKEN` (Laravel Sanctum). |
13
+ | `VITE_API_URL` | `/api` or `https://api.example.com` | API base URL. |
14
+ | `VITE_CSRF_URL` | `/sanctum/csrf-cookie` | Cookie mode: fetched before login. |
15
+ | `VITE_BASE_PATH` | `/admin/` | Where the panel is served. Keep the trailing slash. Default `/`. |
16
+ | `VITE_DEV_API_PROXY` | `http://localhost:8000` | `npm run dev` only: forwards `VITE_API_URL` calls to your backend. |
17
+
18
+ ## 2. Auth endpoints
19
+
20
+ ```
21
+ POST {API}/auth/login { email, password } → { user, token?, expiresAt? }
22
+ POST {API}/auth/logout
23
+ ```
24
+
25
+ - `token` is required in bearer mode and omitted in cookie mode. `expiresAt` is epoch milliseconds (default 7 days).
26
+ - `user` matches `User` in `src/core/users/types.ts`. `role` must be a role the panel knows: `owner`, `admin`, `editor`, `viewer` or one from `src/app.roles.ts`.
27
+ - Any `401` signs the user out. Errors may return `{ "message": "…" }`, which is shown to the user.
28
+
29
+ **UI permission checks are not security.** Enforce every permission on the server, using the same strings (`assets.delete`).
30
+
31
+ ## 3. Module endpoints
32
+
33
+ Modules created with `npm run new-module` switch to these automatically when `VITE_AUTH_MODE` is not `mock`:
34
+
35
+ ```
36
+ GET {API}/assets?page=1&pageSize=10&search=x&sort=name.asc&status=active
37
+ GET {API}/assets/:id
38
+ POST {API}/assets
39
+ PUT {API}/assets/:id
40
+ DELETE {API}/assets/:id
41
+ ```
42
+
43
+ Pages are 1-based. List responses may use any of these shapes (`src/core/api/rest.ts`):
44
+
45
+ ```jsonc
46
+ { "items": [], "total": 0, "page": 1, "pageSize": 10 } // freakpunch
47
+ { "data": [], "total": 0, "current_page": 1, "per_page": 10 } // Laravel paginate()
48
+ { "data": [], "meta": { "total": 0, "current_page": 1, "per_page": 10 } } // Laravel API resource
49
+ { "content": [], "totalElements": 0, "number": 0, "size": 10 } // Spring Data Page
50
+ ```
51
+
52
+ Spring Data expects a 0-based `page` in the request, and a backend may use other parameter names. For those,
53
+ or for module-specific calls, write the method by hand in the module's `api/*.service.ts` using `http`.
54
+
55
+ ## 4. Built-in areas that are still demo data
56
+
57
+ These services run on mock data in every auth mode. For each one you use, implement its interface with
58
+ `http` and change its one `export const xService = …` line. Switch off the rest in `src/app.config.ts`
59
+ → `features` (their pages and menu items disappear). Don't leave a demo area visible on a real site.
60
+
61
+ | Area | Service | Feature flag |
62
+ | --- | --- | --- |
63
+ | Users, profile | `src/core/users/users.service.ts` | `users` (Profile always shows) |
64
+ | Company settings, branding, security policies | `src/core/company/company.service.ts` | `securityPolicies`, `dangerZone` (General and Branding always show) |
65
+ | Audit log | `src/core/audit/audit.service.ts` | `auditLog` |
66
+ | Notifications | `src/core/notifications/notifications.service.ts` | `notifications` |
67
+ | Integrations, webhooks, API keys | `src/pages/settings/company/integrations/integrations.service.ts` | `integrations` |
68
+ | Password, 2FA, sessions | `src/pages/settings/account/account.service.ts` | none |
69
+
70
+ Updating a core service means editing a freakpunch file, which `npm run check` reports. Until there is a
71
+ built-in way to replace them from a project, fix them in freakpunch itself so every project benefits.
72
+
73
+ ## 5. Deploying
74
+
75
+ `npm run build` writes static files to `dist/`. Any URL inside the panel must fall back to `index.html`.
76
+
77
+ **Subdomain (`admin.example.com`):** deploy `dist/` anywhere static, or use the included `Dockerfile`.
78
+
79
+ **Same domain (`example.com/admin`):** set `VITE_BASE_PATH=/admin/` and build, then:
80
+
81
+ - *Laravel / PHP:* copy `dist/` to `public/admin/` and rewrite unknown `/admin/*` paths to `public/admin/index.html`.
82
+ - *Next.js:* copy `dist/` to `public/admin/` and add a rewrite from `/admin/:path*` to `/admin/index.html`.
83
+ - *nginx:* `location /admin/ { alias /var/www/admin/; try_files $uri $uri/ /admin/index.html; }`
84
+
85
+ Same-domain hosting is the simplest route for cookie mode, because it avoids cross-site cookie and CORS setup.
86
+ If the host project has its own `tsconfig.json`, add `fp-admin` to its `exclude`.