create-freakpunch 0.0.0-stage → 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 (216) hide show
  1. package/README.md +24 -3
  2. package/bin/create-freakpunch.mjs +227 -0
  3. package/files/README.md +19 -0
  4. package/files/app.config.ts +50 -0
  5. package/files/locales.ts +10 -0
  6. package/files/registry.ts +17 -0
  7. package/package.json +23 -6
  8. package/template/.dockerignore +17 -0
  9. package/template/.oxlintrc.json +9 -0
  10. package/template/AGENTS.md +6 -0
  11. package/template/BACKEND.md +108 -0
  12. package/template/CLAUDE.md +297 -0
  13. package/template/Dockerfile +19 -0
  14. package/template/_gitignore +26 -0
  15. package/template/components.json +27 -0
  16. package/template/design/COMPONENTS.md +145 -0
  17. package/template/design/DESIGN.md +212 -0
  18. package/template/design/PATTERNS.md +110 -0
  19. package/template/index.html +25 -0
  20. package/template/nginx.conf +32 -0
  21. package/template/package-lock.json +6484 -0
  22. package/template/package.json +51 -0
  23. package/template/public/favicon.svg +1 -0
  24. package/template/public/robots.txt +2 -0
  25. package/template/src/app.config.ts +50 -0
  26. package/template/src/app.roles.ts +24 -0
  27. package/template/src/components/ui/alert-dialog.tsx +181 -0
  28. package/template/src/components/ui/avatar.tsx +116 -0
  29. package/template/src/components/ui/badge.tsx +57 -0
  30. package/template/src/components/ui/breadcrumb.tsx +130 -0
  31. package/template/src/components/ui/button.tsx +58 -0
  32. package/template/src/components/ui/calendar.tsx +277 -0
  33. package/template/src/components/ui/card.tsx +99 -0
  34. package/template/src/components/ui/chart.tsx +370 -0
  35. package/template/src/components/ui/checkbox.tsx +28 -0
  36. package/template/src/components/ui/combobox.tsx +329 -0
  37. package/template/src/components/ui/command.tsx +191 -0
  38. package/template/src/components/ui/dialog.tsx +162 -0
  39. package/template/src/components/ui/dropdown-menu.tsx +271 -0
  40. package/template/src/components/ui/empty.tsx +103 -0
  41. package/template/src/components/ui/field.tsx +236 -0
  42. package/template/src/components/ui/input-group.tsx +263 -0
  43. package/template/src/components/ui/input.tsx +45 -0
  44. package/template/src/components/ui/kbd.tsx +31 -0
  45. package/template/src/components/ui/label.tsx +17 -0
  46. package/template/src/components/ui/pagination.tsx +134 -0
  47. package/template/src/components/ui/popover.tsx +87 -0
  48. package/template/src/components/ui/radio-group.tsx +35 -0
  49. package/template/src/components/ui/select.tsx +204 -0
  50. package/template/src/components/ui/separator.tsx +25 -0
  51. package/template/src/components/ui/sheet.tsx +139 -0
  52. package/template/src/components/ui/sidebar.tsx +741 -0
  53. package/template/src/components/ui/skeleton.tsx +13 -0
  54. package/template/src/components/ui/sonner.tsx +78 -0
  55. package/template/src/components/ui/spinner.tsx +42 -0
  56. package/template/src/components/ui/switch.tsx +29 -0
  57. package/template/src/components/ui/table.tsx +121 -0
  58. package/template/src/components/ui/tabs.tsx +191 -0
  59. package/template/src/components/ui/textarea.tsx +17 -0
  60. package/template/src/components/ui/toggle-group.tsx +94 -0
  61. package/template/src/components/ui/toggle.tsx +48 -0
  62. package/template/src/components/ui/tooltip.tsx +91 -0
  63. package/template/src/core/api/demo.ts +8 -0
  64. package/template/src/core/api/http.ts +76 -0
  65. package/template/src/core/api/mock.ts +88 -0
  66. package/template/src/core/api/rest.ts +67 -0
  67. package/template/src/core/api/types.ts +21 -0
  68. package/template/src/core/audit/audit.service.ts +94 -0
  69. package/template/src/core/audit/hooks.ts +19 -0
  70. package/template/src/core/auth/auth-store.ts +52 -0
  71. package/template/src/core/auth/auth.service.ts +86 -0
  72. package/template/src/core/auth/can.tsx +39 -0
  73. package/template/src/core/auth/dev-role-switcher.tsx +64 -0
  74. package/template/src/core/auth/local-credentials.ts +46 -0
  75. package/template/src/core/auth/permissions.ts +115 -0
  76. package/template/src/core/auth/require-auth.tsx +16 -0
  77. package/template/src/core/auth/require-permission.tsx +24 -0
  78. package/template/src/core/auth/roles.ts +9 -0
  79. package/template/src/core/auth/types.ts +20 -0
  80. package/template/src/core/auth/use-auth.ts +19 -0
  81. package/template/src/core/company/company.service.ts +94 -0
  82. package/template/src/core/company/hooks.ts +40 -0
  83. package/template/src/core/company/password-policy.ts +40 -0
  84. package/template/src/core/company/types.ts +46 -0
  85. package/template/src/core/company/use-app-name.ts +7 -0
  86. package/template/src/core/components/button-link.tsx +14 -0
  87. package/template/src/core/components/confirm-dialog.tsx +116 -0
  88. package/template/src/core/components/data-table/data-table.tsx +297 -0
  89. package/template/src/core/components/data-table/features.ts +24 -0
  90. package/template/src/core/components/data-table/table-header-cell.tsx +13 -0
  91. package/template/src/core/components/data-table/toolbar.tsx +100 -0
  92. package/template/src/core/components/data-table/use-list-url-state.ts +108 -0
  93. package/template/src/core/components/date-picker.tsx +71 -0
  94. package/template/src/core/components/file-attachments.tsx +84 -0
  95. package/template/src/core/components/form-error.tsx +9 -0
  96. package/template/src/core/components/form-page.tsx +93 -0
  97. package/template/src/core/components/image-upload.tsx +86 -0
  98. package/template/src/core/components/page.tsx +75 -0
  99. package/template/src/core/components/phone-input.tsx +58 -0
  100. package/template/src/core/components/search-select.tsx +57 -0
  101. package/template/src/core/components/segmented.tsx +41 -0
  102. package/template/src/core/components/states.tsx +97 -0
  103. package/template/src/core/components/status-badge.tsx +26 -0
  104. package/template/src/core/components/user-avatar.tsx +23 -0
  105. package/template/src/core/config/runtime.ts +16 -0
  106. package/template/src/core/config/types.ts +37 -0
  107. package/template/src/core/i18n/i18n.ts +69 -0
  108. package/template/src/core/i18n/i18next.d.ts +12 -0
  109. package/template/src/core/i18n/keys.ts +37 -0
  110. package/template/src/core/i18n/language-switcher.tsx +45 -0
  111. package/template/src/core/i18n/types.ts +17 -0
  112. package/template/src/core/i18n/use-format.ts +31 -0
  113. package/template/src/core/layout/app-shell.tsx +50 -0
  114. package/template/src/core/layout/app-sidebar.tsx +285 -0
  115. package/template/src/core/layout/command-menu.tsx +117 -0
  116. package/template/src/core/layout/top-bar.tsx +100 -0
  117. package/template/src/core/lib/csv.ts +27 -0
  118. package/template/src/core/lib/format.ts +87 -0
  119. package/template/src/core/lib/phone.ts +50 -0
  120. package/template/src/core/lib/storage.ts +27 -0
  121. package/template/src/core/modules/lazy-page.ts +9 -0
  122. package/template/src/core/modules/navigation.ts +39 -0
  123. package/template/src/core/modules/types.ts +55 -0
  124. package/template/src/core/notifications/hooks.ts +84 -0
  125. package/template/src/core/notifications/notification-center.tsx +251 -0
  126. package/template/src/core/notifications/notifications.service.ts +132 -0
  127. package/template/src/core/notifications/types.ts +70 -0
  128. package/template/src/core/preferences/preferences-store.ts +70 -0
  129. package/template/src/core/setup/setup-gate.tsx +20 -0
  130. package/template/src/core/setup/setup.service.ts +101 -0
  131. package/template/src/core/theme/brand-context.tsx +53 -0
  132. package/template/src/core/theme/brand.ts +29 -0
  133. package/template/src/core/theme/theme-context.tsx +62 -0
  134. package/template/src/core/theme/theme-options.ts +8 -0
  135. package/template/src/core/theme/theme-toggle.tsx +45 -0
  136. package/template/src/core/users/hooks.ts +51 -0
  137. package/template/src/core/users/types.ts +37 -0
  138. package/template/src/core/users/users.service.ts +167 -0
  139. package/template/src/hooks/use-mobile.ts +19 -0
  140. package/template/src/index.css +321 -0
  141. package/template/src/lib/utils.ts +1 -0
  142. package/template/src/locales/en/account.ts +180 -0
  143. package/template/src/locales/en/auth.ts +18 -0
  144. package/template/src/locales/en/common.ts +151 -0
  145. package/template/src/locales/en/company.ts +380 -0
  146. package/template/src/locales/en/dashboard.ts +46 -0
  147. package/template/src/locales/en/errors.ts +18 -0
  148. package/template/src/locales/en/notifications.ts +43 -0
  149. package/template/src/locales/en/settings.ts +25 -0
  150. package/template/src/locales/en/setup.ts +45 -0
  151. package/template/src/locales/index.ts +58 -0
  152. package/template/src/locales/tr/account.ts +184 -0
  153. package/template/src/locales/tr/auth.ts +22 -0
  154. package/template/src/locales/tr/common.ts +154 -0
  155. package/template/src/locales/tr/company.ts +384 -0
  156. package/template/src/locales/tr/dashboard.ts +50 -0
  157. package/template/src/locales/tr/errors.ts +22 -0
  158. package/template/src/locales/tr/notifications.ts +47 -0
  159. package/template/src/locales/tr/settings.ts +29 -0
  160. package/template/src/locales/tr/setup.ts +49 -0
  161. package/template/src/main.tsx +16 -0
  162. package/template/src/modules/locales.ts +10 -0
  163. package/template/src/modules/registry.ts +17 -0
  164. package/template/src/pages/dashboard/dashboard-page.tsx +55 -0
  165. package/template/src/pages/dashboard/quick-actions.tsx +37 -0
  166. package/template/src/pages/login-page.tsx +152 -0
  167. package/template/src/pages/no-access-page.tsx +11 -0
  168. package/template/src/pages/not-found-page.tsx +61 -0
  169. package/template/src/pages/settings/account/account.service.ts +128 -0
  170. package/template/src/pages/settings/account/components/mock-qr.tsx +50 -0
  171. package/template/src/pages/settings/account/components/password-strength.tsx +38 -0
  172. package/template/src/pages/settings/account/components/two-factor-dialog.tsx +140 -0
  173. package/template/src/pages/settings/account/hooks.ts +73 -0
  174. package/template/src/pages/settings/account/notifications-page.tsx +103 -0
  175. package/template/src/pages/settings/account/preferences-page.tsx +157 -0
  176. package/template/src/pages/settings/account/profile-page.tsx +242 -0
  177. package/template/src/pages/settings/account/security-page.tsx +364 -0
  178. package/template/src/pages/settings/account/types.ts +37 -0
  179. package/template/src/pages/settings/company/audit-log-page.tsx +126 -0
  180. package/template/src/pages/settings/company/branding-page.tsx +278 -0
  181. package/template/src/pages/settings/company/components/invite-dialog.tsx +106 -0
  182. package/template/src/pages/settings/company/components/read-only-notice.tsx +13 -0
  183. package/template/src/pages/settings/company/danger-zone-page.tsx +127 -0
  184. package/template/src/pages/settings/company/general-page.tsx +234 -0
  185. package/template/src/pages/settings/company/integrations/api-keys-panel.tsx +170 -0
  186. package/template/src/pages/settings/company/integrations/hooks.ts +37 -0
  187. package/template/src/pages/settings/company/integrations/integrations-page.tsx +57 -0
  188. package/template/src/pages/settings/company/integrations/integrations.service.ts +153 -0
  189. package/template/src/pages/settings/company/integrations/settings-forms.tsx +299 -0
  190. package/template/src/pages/settings/company/integrations/webhooks-panel.tsx +179 -0
  191. package/template/src/pages/settings/company/modules-page.tsx +65 -0
  192. package/template/src/pages/settings/company/roles-page.tsx +116 -0
  193. package/template/src/pages/settings/company/security-policies-page.tsx +164 -0
  194. package/template/src/pages/settings/company/users-page.tsx +262 -0
  195. package/template/src/pages/settings/settings-layout.tsx +57 -0
  196. package/template/src/pages/settings/settings-nav.ts +69 -0
  197. package/template/src/pages/settings/settings-routes.tsx +49 -0
  198. package/template/src/pages/setup-page.tsx +387 -0
  199. package/template/src/providers.tsx +68 -0
  200. package/template/src/router.tsx +59 -0
  201. package/template/tools/check-design.mjs +73 -0
  202. package/template/tools/new-module.mjs +90 -0
  203. package/template/tools/templates/module/api/__id__.mock.ts.tpl +17 -0
  204. package/template/tools/templates/module/api/__id__.service.ts.tpl +51 -0
  205. package/template/tools/templates/module/hooks.ts.tpl +57 -0
  206. package/template/tools/templates/module/index.ts.tpl +44 -0
  207. package/template/tools/templates/module/locales/en.ts.tpl +51 -0
  208. package/template/tools/templates/module/locales/tr.ts.tpl +55 -0
  209. package/template/tools/templates/module/pages/__id__-detail-page.tsx.tpl +103 -0
  210. package/template/tools/templates/module/pages/__id__-form-page.tsx.tpl +121 -0
  211. package/template/tools/templates/module/pages/__id__-list-page.tsx.tpl +234 -0
  212. package/template/tools/templates/module/types.ts.tpl +32 -0
  213. package/template/tsconfig.app.json +32 -0
  214. package/template/tsconfig.json +12 -0
  215. package/template/tsconfig.node.json +23 -0
  216. package/template/vite.config.ts +47 -0
package/README.md CHANGED
@@ -1,3 +1,24 @@
1
- # Temporary Holding Version
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.
1
+ # create-freakpunch
2
+
3
+ Adds a clean 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
+ The first time you open it, a setup wizard creates your company and admin account.
6
+
7
+ ```bash
8
+ cd my-project
9
+ npm create freakpunch@latest
10
+ cd fp-admin
11
+ npm run dev
12
+ ```
13
+
14
+ Options: `npm create freakpunch@latest my-folder`, `-- --no-install`, `-- --no-agent-files`.
15
+
16
+ The command also adds a short pointer to `fp-admin/` in your project's `CLAUDE.md` and `AGENTS.md`
17
+ (creating them if needed), so AI agents started at the project root follow the panel's design rules.
18
+
19
+ Get newer freakpunch files later (your modules and config are never touched; `--dry-run` previews):
20
+
21
+ ```bash
22
+ cd fp-admin
23
+ npm run fp:update
24
+ ```
@@ -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
+ /^\.env/,
27
+ ]
28
+ const isProjectOwned = (file) => PROJECT_OWNED.some((r) => r.test(file))
29
+
30
+ const AGENT_BLOCK_START = '<!-- freakpunch:start -->'
31
+ const AGENT_BLOCK_END = '<!-- freakpunch:end -->'
32
+ const agentBlock = (dir) => `${AGENT_BLOCK_START}
33
+ ## Admin panel (\`${dir}/\`)
34
+
35
+ The admin panel lives in \`${dir}/\` and is built on freakpunch. Before changing anything there, read
36
+ \`${dir}/CLAUDE.md\` and \`${dir}/design/\` and follow them exactly. New features are modules
37
+ (\`cd ${dir} && npm run new-module -- <id>\`) and must use the existing design and components. Run
38
+ \`npm run check\` inside \`${dir}/\` before finishing.
39
+ ${AGENT_BLOCK_END}
40
+ `
41
+
42
+ function fail(message) {
43
+ console.error(`\n✖ ${message}\n`)
44
+ process.exit(1)
45
+ }
46
+
47
+ const hash = (buffer) => createHash('sha256').update(buffer).digest('hex')
48
+ /** Text compares with LF so Windows checkouts (CRLF) don't look edited. */
49
+ const hashFile = (abs) => hash(readFileSync(abs, 'utf8').replaceAll('\r\n', '\n'))
50
+
51
+ function walk(dir, base = dir) {
52
+ return readdirSync(dir).flatMap((name) => {
53
+ const abs = path.join(dir, name)
54
+ return statSync(abs).isDirectory() ? walk(abs, base) : [path.relative(base, abs).replaceAll('\\', '/')]
55
+ })
56
+ }
57
+
58
+ /** Template files by their installed path (`_gitignore` → `.gitignore`). */
59
+ const templateFiles = () => walk(templateDir).map((file) => ({ source: file, target: file === '_gitignore' ? '.gitignore' : file }))
60
+
61
+ function copyInto(root, source, target) {
62
+ const abs = path.join(root, target)
63
+ mkdirSync(path.dirname(abs), { recursive: true })
64
+ copyFileSync(path.join(templateDir, source), abs)
65
+ }
66
+
67
+ function writeManifest(root, files) {
68
+ mkdirSync(path.join(root, '.freakpunch'), { recursive: true })
69
+ writeFileSync(path.join(root, MANIFEST), `${JSON.stringify({ version, files }, null, 2)}\n`)
70
+ }
71
+
72
+ function addAgentNotes(project, dir) {
73
+ const changed = []
74
+ for (const name of ['CLAUDE.md', 'AGENTS.md']) {
75
+ const abs = path.join(project, name)
76
+ const current = existsSync(abs) ? readFileSync(abs, 'utf8') : ''
77
+ if (current.includes(AGENT_BLOCK_START)) continue
78
+ writeFileSync(abs, current ? `${current.trimEnd()}\n\n${agentBlock(dir)}` : agentBlock(dir))
79
+ changed.push(name)
80
+ }
81
+ return changed
82
+ }
83
+
84
+ function create(args) {
85
+ const flags = new Set(args.filter((a) => a.startsWith('--')))
86
+ const dirName = args.find((a) => !a.startsWith('--')) ?? 'fp-admin'
87
+ const project = process.cwd()
88
+ const root = path.resolve(project, dirName)
89
+ if (existsSync(root) && readdirSync(root).length > 0) {
90
+ fail(`${dirName}/ already exists. To get newer freakpunch files, run: cd ${dirName} && npm run fp:update`)
91
+ }
92
+
93
+ console.log(`\nAdding freakpunch ${version} to ${dirName}/`)
94
+ const files = {}
95
+ for (const { source, target } of templateFiles()) {
96
+ copyInto(root, source, target)
97
+ if (!isProjectOwned(target)) files[target] = hashFile(path.join(root, target))
98
+ }
99
+
100
+ copyFileSync(path.join(pkgDir, 'files/README.md'), path.join(root, 'README.md'))
101
+ // Browser data is per address, so each project gets its own dev port and panels never share accounts.
102
+ const port = 5200 + (parseInt(hash(root).slice(0, 6), 16) % 700)
103
+ writeFileSync(path.join(root, '.env.development'), `VITE_DEV_PORT=${port}\n`)
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
+ The first time you open it, a setup wizard creates your company and admin account.
124
+ Add a feature: npm run new-module -- orders --en "Orders" --tr "Siparişler"
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,19 @@
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 -- orders --en "Orders" --tr "Siparişler" # add a feature
8
+ npm run check # design rules + lint + build
9
+ npm run fp:update # get newer freakpunch files
10
+ ```
11
+
12
+ The first time you open it, a setup wizard creates your company and admin account. To start over without a
13
+ backend, clear this site's data in your browser. Built-in pages such as Users and Audit log are off:
14
+ turn them on in `src/app.config.ts` → `features`.
15
+
16
+ - Your code: `src/modules/`, `src/app.config.ts`, `src/app.roles.ts`, `public/`.
17
+ - freakpunch's code: everything else. `npm run fp:update` refreshes it, so don't edit it here.
18
+ - Rules for people and AI agents: [CLAUDE.md](./CLAUDE.md) and [design/](./design).
19
+ - Connecting a backend and deploying under `/admin`: [BACKEND.md](./BACKEND.md).
@@ -0,0 +1,50 @@
1
+ import type { AppConfig, AuthMode } from '@/core/config/types'
2
+
3
+ /** This panel's settings. The company name and logo come from the setup wizard and Settings. */
4
+ export const appConfig = {
5
+ name: 'Admin',
6
+ /** A preset key from src/core/theme/brand.ts, or a custom { color, foreground } pair. */
7
+ brand: 'indigo',
8
+
9
+ workspaces: [{ id: 'main', name: 'Admin' }],
10
+
11
+ /** Sidebar group order; labels are translation keys. Modules attach nav items to these ids. */
12
+ navGroups: [
13
+ { id: 'overview', label: 'common:nav.groups.overview' },
14
+ { id: 'catalog', label: 'common:nav.groups.catalog' },
15
+ { id: 'content', label: 'common:nav.groups.content' },
16
+ { id: 'system', label: 'common:nav.groups.system' },
17
+ ],
18
+
19
+ /** Turn modules on/off. `npm run new-module` adds them here. */
20
+ modules: {
21
+ // fp:modules
22
+ },
23
+
24
+ /** No marketing page: `/` opens the panel. */
25
+ landing: false,
26
+
27
+ /** Built-in areas, off until a project needs them. Setting one to true adds its pages and menu items. */
28
+ features: {
29
+ notifications: false,
30
+ users: false,
31
+ roles: false,
32
+ securityPolicies: false,
33
+ modules: false,
34
+ integrations: false,
35
+ auditLog: false,
36
+ dangerZone: false,
37
+ },
38
+
39
+ /** Default ISO 4217 currency for the setup wizard. */
40
+ currency: 'USD',
41
+
42
+ /** Real REST API base URL. */
43
+ apiBaseUrl: import.meta.env.VITE_API_URL ?? '/api',
44
+
45
+ /** mock (no backend, data stays in this browser) | bearer (JWT) | cookie (Laravel Sanctum). See BACKEND.md. */
46
+ auth: {
47
+ mode: (import.meta.env.VITE_AUTH_MODE ?? 'mock') as AuthMode,
48
+ csrfUrl: import.meta.env.VITE_CSRF_URL ?? '/sanctum/csrf-cookie',
49
+ },
50
+ } satisfies AppConfig
@@ -0,0 +1,10 @@
1
+ // fp:locale-imports
2
+
3
+ /** Translations owned by modules; each key is the module's namespace. */
4
+ export const moduleLocalesEn = {
5
+ // fp:locale-en
6
+ }
7
+
8
+ export const moduleLocalesTr: typeof moduleLocalesEn = {
9
+ // fp:locale-tr
10
+ }
@@ -0,0 +1,17 @@
1
+ import { appConfig } from '@/app.config'
2
+ import type { AppModule } from '@/core/modules/types'
3
+ // fp:module-imports
4
+
5
+ /**
6
+ * Every module in this panel. `npm run new-module` registers new ones here and
7
+ * enables them in src/app.config.ts → `modules`.
8
+ */
9
+ export const moduleRegistry = {
10
+ // fp:module-registry
11
+ } satisfies Record<string, AppModule>
12
+
13
+ export type ModuleId = keyof typeof moduleRegistry
14
+
15
+ export const enabledModules: AppModule[] = (Object.keys(moduleRegistry) as ModuleId[])
16
+ .filter((id) => appConfig.modules[id])
17
+ .map((id) => moduleRegistry[id])
package/package.json CHANGED
@@ -1,6 +1,23 @@
1
- {
2
- "name": "create-freakpunch",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
1
+ {
2
+ "name": "create-freakpunch",
3
+ "version": "0.2.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,108 @@
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` (`.env.development` holds this panel's own dev port) (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 (`orders.delete`).
30
+
31
+ ## 3. First-run setup
32
+
33
+ Without a backend the setup wizard keeps the Owner account in this browser (salted hash, not real security),
34
+ so it is for building only. With a backend it uses:
35
+
36
+ ```
37
+ GET {API}/setup/status → { needsSetup: boolean }
38
+ POST {API}/setup → same response as /auth/login
39
+ ```
40
+
41
+ ```jsonc
42
+ {
43
+ "company": { "name": "", "language": "en", "timeZone": "Europe/Istanbul", "currency": "USD" },
44
+ "admin": { "firstName": "", "lastName": "", "email": "", "password": "" },
45
+ "branding": { "brand": "indigo", "logo": "data:image/png;base64,…" } // logo is optional
46
+ }
47
+ ```
48
+
49
+ **`POST /setup` must return 403 once an Owner exists**, or anyone could take over the panel. Check that, validate the
50
+ password and hash it on the server. If you create the first admin another way (seeder, CLI), skip these
51
+ endpoints: when `/setup/status` fails, the panel goes straight to login.
52
+
53
+ ## 4. Module endpoints
54
+
55
+ Modules created with `npm run new-module` switch to these automatically when `VITE_AUTH_MODE` is not `mock`:
56
+
57
+ ```
58
+ GET {API}/orders?page=1&pageSize=10&search=x&sort=name.asc&status=active
59
+ GET {API}/orders/:id
60
+ POST {API}/orders
61
+ PUT {API}/orders/:id
62
+ DELETE {API}/orders/:id
63
+ ```
64
+
65
+ Pages are 1-based. List responses may use any of these shapes (`src/core/api/rest.ts`):
66
+
67
+ ```jsonc
68
+ { "items": [], "total": 0, "page": 1, "pageSize": 10 } // freakpunch
69
+ { "data": [], "total": 0, "current_page": 1, "per_page": 10 } // Laravel paginate()
70
+ { "data": [], "meta": { "total": 0, "current_page": 1, "per_page": 10 } } // Laravel API resource
71
+ { "content": [], "totalElements": 0, "number": 0, "size": 10 } // Spring Data Page
72
+ ```
73
+
74
+ Spring Data expects a 0-based `page` in the request, and a backend may use other parameter names. For those,
75
+ or for module-specific calls, write the method by hand in the module's `api/*.service.ts` using `http`.
76
+
77
+ ## 5. Built-in areas that still save locally
78
+
79
+ These services keep their data in the browser in every auth mode, and they start empty. Most are switched off in a new panel. For each one you use, implement its interface with
80
+ `http` and change its one `export const xService = …` line. Switch off the rest in `src/app.config.ts`
81
+ → `features` (their pages and menu items disappear). Don't leave an unconnected area on for a real site.
82
+
83
+ | Area | Service | Feature flag |
84
+ | --- | --- | --- |
85
+ | Users, profile | `src/core/users/users.service.ts` | `users` (Profile always shows) |
86
+ | Company settings, branding, security policies | `src/core/company/company.service.ts` | `securityPolicies`, `dangerZone` (General and Branding always show) |
87
+ | Audit log | `src/core/audit/audit.service.ts` | `auditLog` |
88
+ | Notifications | `src/core/notifications/notifications.service.ts` | `notifications` |
89
+ | Integrations, webhooks, API keys | `src/pages/settings/company/integrations/integrations.service.ts` | `integrations` |
90
+ | Password, 2FA, sessions | `src/pages/settings/account/account.service.ts` | none |
91
+
92
+ Updating a core service means editing a freakpunch file, which `npm run check` reports. Until there is a
93
+ built-in way to replace them from a project, fix them in freakpunch itself so every project benefits.
94
+
95
+ ## 6. Deploying
96
+
97
+ `npm run build` writes static files to `dist/`. Any URL inside the panel must fall back to `index.html`.
98
+
99
+ **Subdomain (`admin.example.com`):** deploy `dist/` anywhere static, or use the included `Dockerfile`.
100
+
101
+ **Same domain (`example.com/admin`):** set `VITE_BASE_PATH=/admin/` and build, then:
102
+
103
+ - *Laravel / PHP:* copy `dist/` to `public/admin/` and rewrite unknown `/admin/*` paths to `public/admin/index.html`.
104
+ - *Next.js:* copy `dist/` to `public/admin/` and add a rewrite from `/admin/:path*` to `/admin/index.html`.
105
+ - *nginx:* `location /admin/ { alias /var/www/admin/; try_files $uri $uri/ /admin/index.html; }`
106
+
107
+ Same-domain hosting is the simplest route for cookie mode, because it avoids cross-site cookie and CORS setup.
108
+ If the host project has its own `tsconfig.json`, add `fp-admin` to its `exclude`.