@kurly-growth/growthman 0.1.13 → 0.1.14

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 (287) hide show
  1. package/CLAUDE.md +58 -0
  2. package/app/globals.css +125 -0
  3. package/app/layout.tsx +36 -0
  4. package/app/page.tsx +213 -0
  5. package/bin/cli.js +12 -2
  6. package/components/api-test-dialog.tsx +222 -0
  7. package/components/endpoint-edit-dialog.tsx +181 -0
  8. package/components/endpoint-table.tsx +147 -0
  9. package/components/openapi-upload-dialog.tsx +213 -0
  10. package/components/ui/button.tsx +62 -0
  11. package/components/ui/checkbox.tsx +32 -0
  12. package/components/ui/dialog.tsx +143 -0
  13. package/components/ui/input.tsx +21 -0
  14. package/components/ui/label.tsx +24 -0
  15. package/components/ui/sonner.tsx +37 -0
  16. package/components/ui/table.tsx +116 -0
  17. package/components/ui/textarea.tsx +18 -0
  18. package/components.json +22 -0
  19. package/next-env.d.ts +6 -0
  20. package/package.json +10 -19
  21. package/pnpm-workspace.yaml +4 -0
  22. package/postcss.config.mjs +7 -0
  23. package/prisma/prisma/dev.db +0 -0
  24. package/.next/BUILD_ID +0 -1
  25. package/.next/app-path-routes-manifest.json +0 -12
  26. package/.next/build/chunks/[root-of-the-server]__51225daf._.js +0 -206
  27. package/.next/build/chunks/[root-of-the-server]__51225daf._.js.map +0 -8
  28. package/.next/build/chunks/[root-of-the-server]__974941ed._.js +0 -500
  29. package/.next/build/chunks/[root-of-the-server]__974941ed._.js.map +0 -11
  30. package/.next/build/chunks/[turbopack-node]_transforms_postcss_ts_6920245c._.js +0 -13
  31. package/.next/build/chunks/[turbopack-node]_transforms_postcss_ts_6920245c._.js.map +0 -5
  32. package/.next/build/chunks/[turbopack]_runtime.js +0 -795
  33. package/.next/build/chunks/[turbopack]_runtime.js.map +0 -10
  34. package/.next/build/chunks/node_modules_fe693df6._.js +0 -6758
  35. package/.next/build/chunks/node_modules_fe693df6._.js.map +0 -47
  36. package/.next/build/package.json +0 -1
  37. package/.next/build/postcss.js +0 -6
  38. package/.next/build/postcss.js.map +0 -5
  39. package/.next/build-manifest.json +0 -19
  40. package/.next/diagnostics/build-diagnostics.json +0 -6
  41. package/.next/diagnostics/framework.json +0 -1
  42. package/.next/export-marker.json +0 -6
  43. package/.next/fallback-build-manifest.json +0 -12
  44. package/.next/images-manifest.json +0 -66
  45. package/.next/next-minimal-server.js.nft.json +0 -1
  46. package/.next/next-server.js.nft.json +0 -1
  47. package/.next/package.json +0 -1
  48. package/.next/prerender-manifest.json +0 -114
  49. package/.next/required-server-files.js +0 -163
  50. package/.next/required-server-files.json +0 -163
  51. package/.next/routes-manifest.json +0 -109
  52. package/.next/server/app/_global-error/page/app-paths-manifest.json +0 -3
  53. package/.next/server/app/_global-error/page/build-manifest.json +0 -16
  54. package/.next/server/app/_global-error/page/next-font-manifest.json +0 -6
  55. package/.next/server/app/_global-error/page/react-loadable-manifest.json +0 -1
  56. package/.next/server/app/_global-error/page/server-reference-manifest.json +0 -4
  57. package/.next/server/app/_global-error/page.js +0 -11
  58. package/.next/server/app/_global-error/page.js.map +0 -5
  59. package/.next/server/app/_global-error/page.js.nft.json +0 -1
  60. package/.next/server/app/_global-error/page_client-reference-manifest.js +0 -2
  61. package/.next/server/app/_global-error.html +0 -2
  62. package/.next/server/app/_global-error.meta +0 -15
  63. package/.next/server/app/_global-error.rsc +0 -13
  64. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +0 -5
  65. package/.next/server/app/_global-error.segments/_full.segment.rsc +0 -13
  66. package/.next/server/app/_global-error.segments/_head.segment.rsc +0 -6
  67. package/.next/server/app/_global-error.segments/_index.segment.rsc +0 -4
  68. package/.next/server/app/_global-error.segments/_tree.segment.rsc +0 -1
  69. package/.next/server/app/_not-found/page/app-paths-manifest.json +0 -3
  70. package/.next/server/app/_not-found/page/build-manifest.json +0 -16
  71. package/.next/server/app/_not-found/page/next-font-manifest.json +0 -11
  72. package/.next/server/app/_not-found/page/react-loadable-manifest.json +0 -1
  73. package/.next/server/app/_not-found/page/server-reference-manifest.json +0 -4
  74. package/.next/server/app/_not-found/page.js +0 -14
  75. package/.next/server/app/_not-found/page.js.map +0 -5
  76. package/.next/server/app/_not-found/page.js.nft.json +0 -1
  77. package/.next/server/app/_not-found/page_client-reference-manifest.js +0 -2
  78. package/.next/server/app/_not-found.html +0 -1
  79. package/.next/server/app/_not-found.meta +0 -16
  80. package/.next/server/app/_not-found.rsc +0 -15
  81. package/.next/server/app/_not-found.segments/_full.segment.rsc +0 -15
  82. package/.next/server/app/_not-found.segments/_head.segment.rsc +0 -6
  83. package/.next/server/app/_not-found.segments/_index.segment.rsc +0 -6
  84. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +0 -5
  85. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +0 -4
  86. package/.next/server/app/_not-found.segments/_tree.segment.rsc +0 -2
  87. package/.next/server/app/api/endpoints/[id]/route/app-paths-manifest.json +0 -3
  88. package/.next/server/app/api/endpoints/[id]/route/build-manifest.json +0 -11
  89. package/.next/server/app/api/endpoints/[id]/route/server-reference-manifest.json +0 -4
  90. package/.next/server/app/api/endpoints/[id]/route.js +0 -7
  91. package/.next/server/app/api/endpoints/[id]/route.js.map +0 -5
  92. package/.next/server/app/api/endpoints/[id]/route.js.nft.json +0 -1
  93. package/.next/server/app/api/endpoints/[id]/route_client-reference-manifest.js +0 -2
  94. package/.next/server/app/api/endpoints/bulk/route/app-paths-manifest.json +0 -3
  95. package/.next/server/app/api/endpoints/bulk/route/build-manifest.json +0 -11
  96. package/.next/server/app/api/endpoints/bulk/route/server-reference-manifest.json +0 -4
  97. package/.next/server/app/api/endpoints/bulk/route.js +0 -7
  98. package/.next/server/app/api/endpoints/bulk/route.js.map +0 -5
  99. package/.next/server/app/api/endpoints/bulk/route.js.nft.json +0 -1
  100. package/.next/server/app/api/endpoints/bulk/route_client-reference-manifest.js +0 -2
  101. package/.next/server/app/api/endpoints/import/route/app-paths-manifest.json +0 -3
  102. package/.next/server/app/api/endpoints/import/route/build-manifest.json +0 -11
  103. package/.next/server/app/api/endpoints/import/route/server-reference-manifest.json +0 -4
  104. package/.next/server/app/api/endpoints/import/route.js +0 -7
  105. package/.next/server/app/api/endpoints/import/route.js.map +0 -5
  106. package/.next/server/app/api/endpoints/import/route.js.nft.json +0 -1
  107. package/.next/server/app/api/endpoints/import/route_client-reference-manifest.js +0 -2
  108. package/.next/server/app/api/endpoints/route/app-paths-manifest.json +0 -3
  109. package/.next/server/app/api/endpoints/route/build-manifest.json +0 -11
  110. package/.next/server/app/api/endpoints/route/server-reference-manifest.json +0 -4
  111. package/.next/server/app/api/endpoints/route.js +0 -7
  112. package/.next/server/app/api/endpoints/route.js.map +0 -5
  113. package/.next/server/app/api/endpoints/route.js.nft.json +0 -1
  114. package/.next/server/app/api/endpoints/route_client-reference-manifest.js +0 -2
  115. package/.next/server/app/api/endpoints/sync/route/app-paths-manifest.json +0 -3
  116. package/.next/server/app/api/endpoints/sync/route/build-manifest.json +0 -11
  117. package/.next/server/app/api/endpoints/sync/route/server-reference-manifest.json +0 -4
  118. package/.next/server/app/api/endpoints/sync/route.js +0 -7
  119. package/.next/server/app/api/endpoints/sync/route.js.map +0 -5
  120. package/.next/server/app/api/endpoints/sync/route.js.nft.json +0 -1
  121. package/.next/server/app/api/endpoints/sync/route_client-reference-manifest.js +0 -2
  122. package/.next/server/app/api/mock/[...slug]/route/app-paths-manifest.json +0 -3
  123. package/.next/server/app/api/mock/[...slug]/route/build-manifest.json +0 -11
  124. package/.next/server/app/api/mock/[...slug]/route/server-reference-manifest.json +0 -4
  125. package/.next/server/app/api/mock/[...slug]/route.js +0 -8
  126. package/.next/server/app/api/mock/[...slug]/route.js.map +0 -5
  127. package/.next/server/app/api/mock/[...slug]/route.js.nft.json +0 -1
  128. package/.next/server/app/api/mock/[...slug]/route_client-reference-manifest.js +0 -2
  129. package/.next/server/app/favicon.ico/route/app-paths-manifest.json +0 -3
  130. package/.next/server/app/favicon.ico/route/build-manifest.json +0 -11
  131. package/.next/server/app/favicon.ico/route.js +0 -7
  132. package/.next/server/app/favicon.ico/route.js.map +0 -5
  133. package/.next/server/app/favicon.ico/route.js.nft.json +0 -1
  134. package/.next/server/app/favicon.ico.meta +0 -1
  135. package/.next/server/app/index.html +0 -1
  136. package/.next/server/app/index.meta +0 -14
  137. package/.next/server/app/index.rsc +0 -21
  138. package/.next/server/app/index.segments/__PAGE__.segment.rsc +0 -9
  139. package/.next/server/app/index.segments/_full.segment.rsc +0 -21
  140. package/.next/server/app/index.segments/_head.segment.rsc +0 -6
  141. package/.next/server/app/index.segments/_index.segment.rsc +0 -6
  142. package/.next/server/app/index.segments/_tree.segment.rsc +0 -4
  143. package/.next/server/app/page/app-paths-manifest.json +0 -3
  144. package/.next/server/app/page/build-manifest.json +0 -16
  145. package/.next/server/app/page/next-font-manifest.json +0 -11
  146. package/.next/server/app/page/react-loadable-manifest.json +0 -8
  147. package/.next/server/app/page/server-reference-manifest.json +0 -4
  148. package/.next/server/app/page.js +0 -16
  149. package/.next/server/app/page.js.map +0 -5
  150. package/.next/server/app/page.js.nft.json +0 -1
  151. package/.next/server/app/page_client-reference-manifest.js +0 -2
  152. package/.next/server/app-paths-manifest.json +0 -12
  153. package/.next/server/chunks/1629d_next_dist_esm_build_templates_app-route_498527d5.js +0 -3
  154. package/.next/server/chunks/1629d_next_dist_esm_build_templates_app-route_498527d5.js.map +0 -1
  155. package/.next/server/chunks/[externals]__cf2ccb51._.js +0 -3
  156. package/.next/server/chunks/[externals]__cf2ccb51._.js.map +0 -1
  157. package/.next/server/chunks/[externals]_next_dist_8dbe5856._.js +0 -3
  158. package/.next/server/chunks/[externals]_next_dist_8dbe5856._.js.map +0 -1
  159. package/.next/server/chunks/[root-of-the-server]__3534fecc._.js +0 -3
  160. package/.next/server/chunks/[root-of-the-server]__3534fecc._.js.map +0 -1
  161. package/.next/server/chunks/[root-of-the-server]__461ea613._.js +0 -21
  162. package/.next/server/chunks/[root-of-the-server]__461ea613._.js.map +0 -1
  163. package/.next/server/chunks/[root-of-the-server]__4929acb0._.js +0 -129
  164. package/.next/server/chunks/[root-of-the-server]__4929acb0._.js.map +0 -1
  165. package/.next/server/chunks/[root-of-the-server]__909218aa._.js +0 -3
  166. package/.next/server/chunks/[root-of-the-server]__909218aa._.js.map +0 -1
  167. package/.next/server/chunks/[root-of-the-server]__a6c01a13._.js +0 -3
  168. package/.next/server/chunks/[root-of-the-server]__a6c01a13._.js.map +0 -1
  169. package/.next/server/chunks/[root-of-the-server]__db1127cf._.js +0 -3
  170. package/.next/server/chunks/[root-of-the-server]__db1127cf._.js.map +0 -1
  171. package/.next/server/chunks/[root-of-the-server]__e46f3e25._.js +0 -3
  172. package/.next/server/chunks/[root-of-the-server]__e46f3e25._.js.map +0 -1
  173. package/.next/server/chunks/[turbopack]_runtime.js +0 -795
  174. package/.next/server/chunks/[turbopack]_runtime.js.map +0 -10
  175. package/.next/server/chunks/_next-internal_server_app_api_endpoints_[id]_route_actions_b91cfc4c.js +0 -3
  176. package/.next/server/chunks/_next-internal_server_app_api_endpoints_[id]_route_actions_b91cfc4c.js.map +0 -1
  177. package/.next/server/chunks/_next-internal_server_app_api_endpoints_bulk_route_actions_560cc6cd.js +0 -3
  178. package/.next/server/chunks/_next-internal_server_app_api_endpoints_bulk_route_actions_560cc6cd.js.map +0 -1
  179. package/.next/server/chunks/_next-internal_server_app_api_endpoints_import_route_actions_f2444950.js +0 -3
  180. package/.next/server/chunks/_next-internal_server_app_api_endpoints_import_route_actions_f2444950.js.map +0 -1
  181. package/.next/server/chunks/_next-internal_server_app_api_endpoints_route_actions_49d8ad56.js +0 -3
  182. package/.next/server/chunks/_next-internal_server_app_api_endpoints_route_actions_49d8ad56.js.map +0 -1
  183. package/.next/server/chunks/_next-internal_server_app_api_endpoints_sync_route_actions_0f446550.js +0 -3
  184. package/.next/server/chunks/_next-internal_server_app_api_endpoints_sync_route_actions_0f446550.js.map +0 -1
  185. package/.next/server/chunks/_next-internal_server_app_api_mock_[___slug]_route_actions_be875f77.js +0 -3
  186. package/.next/server/chunks/_next-internal_server_app_api_mock_[___slug]_route_actions_be875f77.js.map +0 -1
  187. package/.next/server/chunks/_next-internal_server_app_favicon_ico_route_actions_353150a5.js +0 -3
  188. package/.next/server/chunks/_next-internal_server_app_favicon_ico_route_actions_353150a5.js.map +0 -1
  189. package/.next/server/chunks/node_modules__pnpm_a61fb769._.js +0 -3
  190. package/.next/server/chunks/node_modules__pnpm_a61fb769._.js.map +0 -1
  191. package/.next/server/chunks/ssr/1629d_next_dist_1a21bde7._.js +0 -6
  192. package/.next/server/chunks/ssr/1629d_next_dist_1a21bde7._.js.map +0 -1
  193. package/.next/server/chunks/ssr/1629d_next_dist_8dc31fba._.js +0 -3
  194. package/.next/server/chunks/ssr/1629d_next_dist_8dc31fba._.js.map +0 -1
  195. package/.next/server/chunks/ssr/1629d_next_dist_client_components_b01b33e4._.js +0 -3
  196. package/.next/server/chunks/ssr/1629d_next_dist_client_components_b01b33e4._.js.map +0 -1
  197. package/.next/server/chunks/ssr/1629d_next_dist_client_components_builtin_forbidden_4cab2078.js +0 -3
  198. package/.next/server/chunks/ssr/1629d_next_dist_client_components_builtin_forbidden_4cab2078.js.map +0 -1
  199. package/.next/server/chunks/ssr/1629d_next_dist_client_components_builtin_global-error_0d5cb623.js +0 -3
  200. package/.next/server/chunks/ssr/1629d_next_dist_client_components_builtin_global-error_0d5cb623.js.map +0 -1
  201. package/.next/server/chunks/ssr/1629d_next_dist_client_components_builtin_unauthorized_b8fbdcad.js +0 -3
  202. package/.next/server/chunks/ssr/1629d_next_dist_client_components_builtin_unauthorized_b8fbdcad.js.map +0 -1
  203. package/.next/server/chunks/ssr/1629d_next_dist_d78dee57._.js +0 -4
  204. package/.next/server/chunks/ssr/1629d_next_dist_d78dee57._.js.map +0 -1
  205. package/.next/server/chunks/ssr/1629d_next_dist_esm_build_templates_app-page_d4e9464b.js +0 -4
  206. package/.next/server/chunks/ssr/1629d_next_dist_esm_build_templates_app-page_d4e9464b.js.map +0 -1
  207. package/.next/server/chunks/ssr/67049_lucide-react_dist_esm_createLucideIcon_22fe2e14.js +0 -3
  208. package/.next/server/chunks/ssr/67049_lucide-react_dist_esm_createLucideIcon_22fe2e14.js.map +0 -1
  209. package/.next/server/chunks/ssr/[externals]_next_dist_server_app-render_work-async-storage_external_1f8eeae7.js +0 -3
  210. package/.next/server/chunks/ssr/[externals]_next_dist_server_app-render_work-async-storage_external_1f8eeae7.js.map +0 -1
  211. package/.next/server/chunks/ssr/[root-of-the-server]__14f4396a._.js +0 -10
  212. package/.next/server/chunks/ssr/[root-of-the-server]__14f4396a._.js.map +0 -1
  213. package/.next/server/chunks/ssr/[root-of-the-server]__3064bf15._.js +0 -3
  214. package/.next/server/chunks/ssr/[root-of-the-server]__3064bf15._.js.map +0 -1
  215. package/.next/server/chunks/ssr/[root-of-the-server]__4ff41ba3._.js +0 -4
  216. package/.next/server/chunks/ssr/[root-of-the-server]__4ff41ba3._.js.map +0 -1
  217. package/.next/server/chunks/ssr/[root-of-the-server]__57c5da8e._.js +0 -3
  218. package/.next/server/chunks/ssr/[root-of-the-server]__57c5da8e._.js.map +0 -1
  219. package/.next/server/chunks/ssr/[root-of-the-server]__67653b38._.js +0 -3
  220. package/.next/server/chunks/ssr/[root-of-the-server]__67653b38._.js.map +0 -1
  221. package/.next/server/chunks/ssr/[root-of-the-server]__7d48410d._.js +0 -3
  222. package/.next/server/chunks/ssr/[root-of-the-server]__7d48410d._.js.map +0 -1
  223. package/.next/server/chunks/ssr/[root-of-the-server]__a49eaf36._.js +0 -3
  224. package/.next/server/chunks/ssr/[root-of-the-server]__a49eaf36._.js.map +0 -1
  225. package/.next/server/chunks/ssr/[root-of-the-server]__b0617f51._.js +0 -3
  226. package/.next/server/chunks/ssr/[root-of-the-server]__b0617f51._.js.map +0 -1
  227. package/.next/server/chunks/ssr/[root-of-the-server]__cc026bde._.js +0 -3
  228. package/.next/server/chunks/ssr/[root-of-the-server]__cc026bde._.js.map +0 -1
  229. package/.next/server/chunks/ssr/[root-of-the-server]__d079898e._.js +0 -3
  230. package/.next/server/chunks/ssr/[root-of-the-server]__d079898e._.js.map +0 -1
  231. package/.next/server/chunks/ssr/[root-of-the-server]__d3649e47._.js +0 -3
  232. package/.next/server/chunks/ssr/[root-of-the-server]__d3649e47._.js.map +0 -1
  233. package/.next/server/chunks/ssr/[turbopack]_runtime.js +0 -795
  234. package/.next/server/chunks/ssr/[turbopack]_runtime.js.map +0 -10
  235. package/.next/server/chunks/ssr/_9b6e3dc4._.js +0 -3
  236. package/.next/server/chunks/ssr/_9b6e3dc4._.js.map +0 -1
  237. package/.next/server/chunks/ssr/_a437ac52._.js +0 -7
  238. package/.next/server/chunks/ssr/_a437ac52._.js.map +0 -1
  239. package/.next/server/chunks/ssr/_b62b070d._.js +0 -4
  240. package/.next/server/chunks/ssr/_b62b070d._.js.map +0 -1
  241. package/.next/server/chunks/ssr/_next-internal_server_app__global-error_page_actions_75761787.js +0 -3
  242. package/.next/server/chunks/ssr/_next-internal_server_app__global-error_page_actions_75761787.js.map +0 -1
  243. package/.next/server/chunks/ssr/_next-internal_server_app__not-found_page_actions_554ec2bf.js +0 -3
  244. package/.next/server/chunks/ssr/_next-internal_server_app__not-found_page_actions_554ec2bf.js.map +0 -1
  245. package/.next/server/chunks/ssr/_next-internal_server_app_page_actions_39d4fc33.js +0 -3
  246. package/.next/server/chunks/ssr/_next-internal_server_app_page_actions_39d4fc33.js.map +0 -1
  247. package/.next/server/chunks/ssr/app_b9b1292a._.js +0 -3
  248. package/.next/server/chunks/ssr/app_b9b1292a._.js.map +0 -1
  249. package/.next/server/functions-config-manifest.json +0 -4
  250. package/.next/server/interception-route-rewrite-manifest.js +0 -1
  251. package/.next/server/middleware-build-manifest.js +0 -20
  252. package/.next/server/middleware-manifest.json +0 -6
  253. package/.next/server/next-font-manifest.js +0 -1
  254. package/.next/server/next-font-manifest.json +0 -15
  255. package/.next/server/pages/404.html +0 -1
  256. package/.next/server/pages/500.html +0 -2
  257. package/.next/server/pages-manifest.json +0 -4
  258. package/.next/server/server-reference-manifest.js +0 -1
  259. package/.next/server/server-reference-manifest.json +0 -5
  260. package/.next/static/chunks/16403d658c649f0f.js +0 -1
  261. package/.next/static/chunks/2422cfacfdb28c2c.js +0 -5
  262. package/.next/static/chunks/42572c067be8ea0f.js +0 -1
  263. package/.next/static/chunks/5045da71379799ce.js +0 -1
  264. package/.next/static/chunks/6e04dfc4035d7150.js +0 -5
  265. package/.next/static/chunks/8155485116e3ff24.js +0 -1
  266. package/.next/static/chunks/a66e09a7c2336f67.js +0 -1
  267. package/.next/static/chunks/a6dad97d9634a72d.js +0 -1
  268. package/.next/static/chunks/a6dad97d9634a72d.js.map +0 -1
  269. package/.next/static/chunks/da3f3e4f37f68cee.css +0 -3
  270. package/.next/static/chunks/f1cfb69226717279.js +0 -1
  271. package/.next/static/chunks/turbopack-7027959231bb432a.js +0 -4
  272. package/.next/static/media/4fa387ec64143e14-s.c1fdd6c2.woff2 +0 -0
  273. package/.next/static/media/7178b3e590c64307-s.b97b3418.woff2 +0 -0
  274. package/.next/static/media/797e433ab948586e-s.p.dbea232f.woff2 +0 -0
  275. package/.next/static/media/8a480f0b521d4e75-s.8e0177b5.woff2 +0 -0
  276. package/.next/static/media/bbc41e54d2fcbd21-s.799d8ef8.woff2 +0 -0
  277. package/.next/static/media/caa3a2e1cccd8315-s.p.853070df.woff2 +0 -0
  278. package/.next/static/media/favicon.de6b30f1.ico +0 -0
  279. package/.next/static/rD3QdLXPN9C3vquucAUu6/_buildManifest.js +0 -11
  280. package/.next/static/rD3QdLXPN9C3vquucAUu6/_clientMiddlewareManifest.json +0 -1
  281. package/.next/static/rD3QdLXPN9C3vquucAUu6/_ssgManifest.js +0 -1
  282. package/.next/trace +0 -1
  283. package/.next/trace-build +0 -1
  284. package/.next/turbopack +0 -0
  285. package/.next/types/routes.d.ts +0 -78
  286. package/.next/types/validator.ts +0 -124
  287. /package/{.next/server/app/favicon.ico.body → app/favicon.ico} +0 -0
package/CLAUDE.md ADDED
@@ -0,0 +1,58 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Development Commands
6
+
7
+ ```bash
8
+ pnpm dev # Start dev server (http://localhost:3100)
9
+ pnpm dev:clean # Reset DB with seed data and start dev server
10
+ pnpm build # Build for production
11
+ pnpm lint # Run ESLint
12
+ pnpm db:seed # Seed the database
13
+ pnpm db:reset # Reset database with migrations
14
+ ```
15
+
16
+ ## Architecture Overview
17
+
18
+ This is a **Mock Server Dashboard** - a Next.js 16 application that allows users to create, manage, and serve mock API endpoints stored in PostgreSQL via Prisma.
19
+
20
+ ### Core Concept
21
+
22
+ 1. Users define mock endpoints (path, method, status code, response body) through a web UI
23
+ 2. These endpoints are stored in the `MockEndpoint` table
24
+ 3. Requests to `/api/mock/*` are dynamically matched against stored endpoints using `path-to-regexp`
25
+
26
+ ### Key Components
27
+
28
+ - **`/app/api/mock/[...slug]/route.ts`** - Catch-all route that handles all mock requests. Matches incoming requests against stored endpoints using pattern matching (supports path parameters like `:id`)
29
+
30
+ - **`/app/api/endpoints/`** - CRUD API for managing mock endpoints
31
+ - `route.ts` - GET (list all), POST (create)
32
+ - `[id]/route.ts` - PUT (update), DELETE
33
+ - `import/route.ts` - Bulk import from OpenAPI spec
34
+ - `sync/route.ts` - Sync endpoints with external spec
35
+
36
+ - **`/lib/openapi-parser.ts`** - Parses OpenAPI 3.x specs and generates mock response data based on schema definitions. Handles `$ref`, `allOf`, `oneOf`, `anyOf`, and generates type-appropriate mock values.
37
+
38
+ - **`/components/`** - React components for the dashboard UI
39
+ - `endpoint-table.tsx` - Display list of endpoints
40
+ - `endpoint-edit-dialog.tsx` - Create/edit endpoint with Monaco editor for JSON
41
+ - `openapi-upload-dialog.tsx` - Import endpoints from OpenAPI spec
42
+
43
+ ### Database Schema
44
+
45
+ Single table `MockEndpoint` with unique constraint on `(path, method)`:
46
+ - `path` - Express-style route (e.g., `/v1/users/:id`)
47
+ - `method` - HTTP method
48
+ - `statusCode` - Response status code
49
+ - `responseBody` - JSON response data
50
+
51
+ ### Tech Stack
52
+
53
+ - Next.js 16 (App Router)
54
+ - React 19
55
+ - Prisma with PostgreSQL
56
+ - Tailwind CSS 4
57
+ - Radix UI + shadcn/ui components
58
+ - Monaco Editor for JSON editing
@@ -0,0 +1,125 @@
1
+ @import "tailwindcss";
2
+ @import "tw-animate-css";
3
+
4
+ @custom-variant dark (&:is(.dark *));
5
+
6
+ @theme inline {
7
+ --color-background: var(--background);
8
+ --color-foreground: var(--foreground);
9
+ --font-sans: var(--font-geist-sans);
10
+ --font-mono: var(--font-geist-mono);
11
+ --color-sidebar-ring: var(--sidebar-ring);
12
+ --color-sidebar-border: var(--sidebar-border);
13
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
14
+ --color-sidebar-accent: var(--sidebar-accent);
15
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
16
+ --color-sidebar-primary: var(--sidebar-primary);
17
+ --color-sidebar-foreground: var(--sidebar-foreground);
18
+ --color-sidebar: var(--sidebar);
19
+ --color-chart-5: var(--chart-5);
20
+ --color-chart-4: var(--chart-4);
21
+ --color-chart-3: var(--chart-3);
22
+ --color-chart-2: var(--chart-2);
23
+ --color-chart-1: var(--chart-1);
24
+ --color-ring: var(--ring);
25
+ --color-input: var(--input);
26
+ --color-border: var(--border);
27
+ --color-destructive: var(--destructive);
28
+ --color-accent-foreground: var(--accent-foreground);
29
+ --color-accent: var(--accent);
30
+ --color-muted-foreground: var(--muted-foreground);
31
+ --color-muted: var(--muted);
32
+ --color-secondary-foreground: var(--secondary-foreground);
33
+ --color-secondary: var(--secondary);
34
+ --color-primary-foreground: var(--primary-foreground);
35
+ --color-primary: var(--primary);
36
+ --color-popover-foreground: var(--popover-foreground);
37
+ --color-popover: var(--popover);
38
+ --color-card-foreground: var(--card-foreground);
39
+ --color-card: var(--card);
40
+ --radius-sm: calc(var(--radius) - 4px);
41
+ --radius-md: calc(var(--radius) - 2px);
42
+ --radius-lg: var(--radius);
43
+ --radius-xl: calc(var(--radius) + 4px);
44
+ --radius-2xl: calc(var(--radius) + 8px);
45
+ --radius-3xl: calc(var(--radius) + 12px);
46
+ --radius-4xl: calc(var(--radius) + 16px);
47
+ }
48
+
49
+ :root {
50
+ --radius: 0.625rem;
51
+ --background: oklch(1 0 0);
52
+ --foreground: oklch(0.145 0 0);
53
+ --card: oklch(1 0 0);
54
+ --card-foreground: oklch(0.145 0 0);
55
+ --popover: oklch(1 0 0);
56
+ --popover-foreground: oklch(0.145 0 0);
57
+ --primary: oklch(0.205 0 0);
58
+ --primary-foreground: oklch(0.985 0 0);
59
+ --secondary: oklch(0.97 0 0);
60
+ --secondary-foreground: oklch(0.205 0 0);
61
+ --muted: oklch(0.97 0 0);
62
+ --muted-foreground: oklch(0.556 0 0);
63
+ --accent: oklch(0.97 0 0);
64
+ --accent-foreground: oklch(0.205 0 0);
65
+ --destructive: oklch(0.577 0.245 27.325);
66
+ --border: oklch(0.922 0 0);
67
+ --input: oklch(0.922 0 0);
68
+ --ring: oklch(0.708 0 0);
69
+ --chart-1: oklch(0.646 0.222 41.116);
70
+ --chart-2: oklch(0.6 0.118 184.704);
71
+ --chart-3: oklch(0.398 0.07 227.392);
72
+ --chart-4: oklch(0.828 0.189 84.429);
73
+ --chart-5: oklch(0.769 0.188 70.08);
74
+ --sidebar: oklch(0.985 0 0);
75
+ --sidebar-foreground: oklch(0.145 0 0);
76
+ --sidebar-primary: oklch(0.205 0 0);
77
+ --sidebar-primary-foreground: oklch(0.985 0 0);
78
+ --sidebar-accent: oklch(0.97 0 0);
79
+ --sidebar-accent-foreground: oklch(0.205 0 0);
80
+ --sidebar-border: oklch(0.922 0 0);
81
+ --sidebar-ring: oklch(0.708 0 0);
82
+ }
83
+
84
+ .dark {
85
+ --background: oklch(0.145 0 0);
86
+ --foreground: oklch(0.985 0 0);
87
+ --card: oklch(0.205 0 0);
88
+ --card-foreground: oklch(0.985 0 0);
89
+ --popover: oklch(0.205 0 0);
90
+ --popover-foreground: oklch(0.985 0 0);
91
+ --primary: oklch(0.922 0 0);
92
+ --primary-foreground: oklch(0.205 0 0);
93
+ --secondary: oklch(0.269 0 0);
94
+ --secondary-foreground: oklch(0.985 0 0);
95
+ --muted: oklch(0.269 0 0);
96
+ --muted-foreground: oklch(0.708 0 0);
97
+ --accent: oklch(0.269 0 0);
98
+ --accent-foreground: oklch(0.985 0 0);
99
+ --destructive: oklch(0.704 0.191 22.216);
100
+ --border: oklch(1 0 0 / 10%);
101
+ --input: oklch(1 0 0 / 15%);
102
+ --ring: oklch(0.556 0 0);
103
+ --chart-1: oklch(0.488 0.243 264.376);
104
+ --chart-2: oklch(0.696 0.17 162.48);
105
+ --chart-3: oklch(0.769 0.188 70.08);
106
+ --chart-4: oklch(0.627 0.265 303.9);
107
+ --chart-5: oklch(0.645 0.246 16.439);
108
+ --sidebar: oklch(0.205 0 0);
109
+ --sidebar-foreground: oklch(0.985 0 0);
110
+ --sidebar-primary: oklch(0.488 0.243 264.376);
111
+ --sidebar-primary-foreground: oklch(0.985 0 0);
112
+ --sidebar-accent: oklch(0.269 0 0);
113
+ --sidebar-accent-foreground: oklch(0.985 0 0);
114
+ --sidebar-border: oklch(1 0 0 / 10%);
115
+ --sidebar-ring: oklch(0.556 0 0);
116
+ }
117
+
118
+ @layer base {
119
+ * {
120
+ @apply border-border outline-ring/50;
121
+ }
122
+ body {
123
+ @apply bg-background text-foreground;
124
+ }
125
+ }
package/app/layout.tsx ADDED
@@ -0,0 +1,36 @@
1
+ import type { Metadata } from "next";
2
+ import { Geist, Geist_Mono } from "next/font/google";
3
+ import { Toaster } from "@/components/ui/sonner";
4
+ import "./globals.css";
5
+
6
+ const geistSans = Geist({
7
+ variable: "--font-geist-sans",
8
+ subsets: ["latin"],
9
+ });
10
+
11
+ const geistMono = Geist_Mono({
12
+ variable: "--font-geist-mono",
13
+ subsets: ["latin"],
14
+ });
15
+
16
+ export const metadata: Metadata = {
17
+ title: "Growthman",
18
+ description: "Local mock API server with UI dashboard",
19
+ };
20
+
21
+ export default function RootLayout({
22
+ children,
23
+ }: Readonly<{
24
+ children: React.ReactNode;
25
+ }>) {
26
+ return (
27
+ <html lang="en">
28
+ <body
29
+ className={`${geistSans.variable} ${geistMono.variable} antialiased`}
30
+ >
31
+ {children}
32
+ <Toaster />
33
+ </body>
34
+ </html>
35
+ );
36
+ }
package/app/page.tsx ADDED
@@ -0,0 +1,213 @@
1
+ 'use client'
2
+
3
+ import { useState, useEffect, useCallback } from 'react'
4
+ import { MockEndpoint } from '@/types/endpoint'
5
+ import { EndpointTable } from '@/components/endpoint-table'
6
+ import { EndpointEditDialog } from '@/components/endpoint-edit-dialog'
7
+ import { OpenAPIUploadDialog } from '@/components/openapi-upload-dialog'
8
+ import { ApiTestDialog } from '@/components/api-test-dialog'
9
+ import { Button } from '@/components/ui/button'
10
+ import { toast } from 'sonner'
11
+
12
+ export default function Home() {
13
+ const [endpoints, setEndpoints] = useState<MockEndpoint[]>([])
14
+ const [loading, setLoading] = useState(true)
15
+ const [editDialogOpen, setEditDialogOpen] = useState(false)
16
+ const [uploadDialogOpen, setUploadDialogOpen] = useState(false)
17
+ const [testDialogOpen, setTestDialogOpen] = useState(false)
18
+ const [selectedEndpoint, setSelectedEndpoint] = useState<MockEndpoint | null>(null)
19
+ const [testEndpoint, setTestEndpoint] = useState<MockEndpoint | null>(null)
20
+ const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set())
21
+ const [baseUrl, setBaseUrl] = useState('')
22
+ const [lastModifiedId, setLastModifiedId] = useState<string | null>(null)
23
+
24
+ useEffect(() => {
25
+ setBaseUrl(window.location.origin)
26
+ }, [])
27
+
28
+ const fetchEndpoints = useCallback(async () => {
29
+ try {
30
+ const response = await fetch('/api/endpoints')
31
+ const data = await response.json()
32
+ setEndpoints(data)
33
+ } catch (error) {
34
+ console.error('Failed to fetch endpoints:', error)
35
+ } finally {
36
+ setLoading(false)
37
+ }
38
+ }, [])
39
+
40
+ useEffect(() => {
41
+ fetchEndpoints()
42
+ }, [fetchEndpoints])
43
+
44
+ const handleEdit = (endpoint: MockEndpoint) => {
45
+ setSelectedEndpoint(endpoint)
46
+ setEditDialogOpen(true)
47
+ }
48
+
49
+ const handleCreate = () => {
50
+ setSelectedEndpoint(null)
51
+ setEditDialogOpen(true)
52
+ }
53
+
54
+ const handleTest = (endpoint: MockEndpoint) => {
55
+ setTestEndpoint(endpoint)
56
+ setTestDialogOpen(true)
57
+ }
58
+
59
+ const handleDelete = async (id: string) => {
60
+ if (!confirm('Are you sure you want to delete this endpoint?')) return
61
+
62
+ try {
63
+ const response = await fetch(`/api/endpoints/${id}`, { method: 'DELETE' })
64
+ if (!response.ok) throw new Error('Failed to delete')
65
+ setSelectedIds((prev) => {
66
+ const newSet = new Set(prev)
67
+ newSet.delete(id)
68
+ return newSet
69
+ })
70
+ toast.success('Endpoint deleted successfully')
71
+ fetchEndpoints()
72
+ } catch (error) {
73
+ console.error('Failed to delete endpoint:', error)
74
+ toast.error('Failed to delete endpoint')
75
+ }
76
+ }
77
+
78
+ const handleBulkDelete = async () => {
79
+ if (selectedIds.size === 0) return
80
+ if (!confirm(`Are you sure you want to delete ${selectedIds.size} endpoint(s)?`)) return
81
+
82
+ try {
83
+ const response = await fetch('/api/endpoints/bulk', {
84
+ method: 'DELETE',
85
+ headers: { 'Content-Type': 'application/json' },
86
+ body: JSON.stringify({ ids: Array.from(selectedIds) }),
87
+ })
88
+ if (!response.ok) throw new Error('Failed to delete')
89
+ const count = selectedIds.size
90
+ setSelectedIds(new Set())
91
+ toast.success(`${count} endpoint(s) deleted successfully`)
92
+ fetchEndpoints()
93
+ } catch (error) {
94
+ console.error('Failed to delete endpoints:', error)
95
+ toast.error('Failed to delete endpoints')
96
+ }
97
+ }
98
+
99
+ const handleSave = async (endpoint: Partial<MockEndpoint> & { id?: string }) => {
100
+ try {
101
+ const isUpdate = !!endpoint.id
102
+ const response = isUpdate
103
+ ? await fetch(`/api/endpoints/${endpoint.id}`, {
104
+ method: 'PUT',
105
+ headers: { 'Content-Type': 'application/json' },
106
+ body: JSON.stringify(endpoint),
107
+ })
108
+ : await fetch('/api/endpoints', {
109
+ method: 'POST',
110
+ headers: { 'Content-Type': 'application/json' },
111
+ body: JSON.stringify(endpoint),
112
+ })
113
+
114
+ if (!response.ok) {
115
+ const data = await response.json()
116
+ throw new Error(data.error || 'Failed to save')
117
+ }
118
+
119
+ const savedEndpoint = await response.json()
120
+ setLastModifiedId(savedEndpoint.id)
121
+ toast.success(isUpdate ? 'Endpoint updated successfully' : 'Endpoint created successfully')
122
+ fetchEndpoints()
123
+ } catch (error) {
124
+ console.error('Failed to save endpoint:', error)
125
+ toast.error(error instanceof Error ? error.message : 'Failed to save endpoint')
126
+ }
127
+ }
128
+
129
+ return (
130
+ <div className="min-h-screen bg-gray-50">
131
+ <div className="max-w-7xl mx-auto py-8 px-4 sm:px-6 lg:px-8">
132
+ <div className="flex justify-between items-center mb-8">
133
+ <div>
134
+ <h1 className="text-3xl font-bold text-gray-900">Growthman</h1>
135
+ <p className="mt-1 text-gray-500">
136
+ Manage your mock API endpoints
137
+ </p>
138
+ {baseUrl && (
139
+ <p className="mt-2 text-sm font-mono bg-gray-100 px-3 py-1.5 rounded-md inline-block">
140
+ {baseUrl}/api/mock
141
+ </p>
142
+ )}
143
+ </div>
144
+ <div className="flex gap-3">
145
+ <Button variant="outline" onClick={() => setUploadDialogOpen(true)}>
146
+ Import OpenAPI
147
+ </Button>
148
+ <Button onClick={handleCreate}>
149
+ + New Endpoint
150
+ </Button>
151
+ </div>
152
+ </div>
153
+
154
+ {selectedIds.size > 0 && (
155
+ <div className="mb-4 flex items-center gap-4 p-3 bg-blue-50 border border-blue-200 rounded-lg">
156
+ <span className="text-sm text-blue-800">
157
+ {selectedIds.size} endpoint(s) selected
158
+ </span>
159
+ <Button
160
+ variant="destructive"
161
+ size="sm"
162
+ onClick={handleBulkDelete}
163
+ >
164
+ Delete Selected
165
+ </Button>
166
+ <Button
167
+ variant="ghost"
168
+ size="sm"
169
+ onClick={() => setSelectedIds(new Set())}
170
+ >
171
+ Clear Selection
172
+ </Button>
173
+ </div>
174
+ )}
175
+
176
+ <div className="bg-white shadow rounded-lg">
177
+ {loading ? (
178
+ <div className="p-8 text-center text-gray-500">Loading...</div>
179
+ ) : (
180
+ <EndpointTable
181
+ endpoints={endpoints}
182
+ onEdit={handleEdit}
183
+ onDelete={handleDelete}
184
+ onTest={handleTest}
185
+ selectedIds={selectedIds}
186
+ onSelectionChange={setSelectedIds}
187
+ lastModifiedId={lastModifiedId}
188
+ />
189
+ )}
190
+ </div>
191
+ </div>
192
+
193
+ <EndpointEditDialog
194
+ endpoint={selectedEndpoint}
195
+ open={editDialogOpen}
196
+ onOpenChange={setEditDialogOpen}
197
+ onSave={handleSave}
198
+ />
199
+
200
+ <OpenAPIUploadDialog
201
+ open={uploadDialogOpen}
202
+ onOpenChange={setUploadDialogOpen}
203
+ onImportComplete={fetchEndpoints}
204
+ />
205
+
206
+ <ApiTestDialog
207
+ endpoint={testEndpoint}
208
+ open={testDialogOpen}
209
+ onOpenChange={setTestDialogOpen}
210
+ />
211
+ </div>
212
+ )
213
+ }
package/bin/cli.js CHANGED
@@ -25,14 +25,12 @@ const env = {
25
25
  // CLI 바이너리 경로 찾기
26
26
  function getBinPath(packageName, binName) {
27
27
  try {
28
- // 패키지의 package.json 찾기
29
28
  const pkgJsonPath = require.resolve(`${packageName}/package.json`, {
30
29
  paths: [packageDir],
31
30
  });
32
31
  const pkgDir = path.dirname(pkgJsonPath);
33
32
  const pkgJson = require(pkgJsonPath);
34
33
 
35
- // bin 필드에서 실행 파일 경로 찾기
36
34
  let binRelPath;
37
35
  if (typeof pkgJson.bin === 'string') {
38
36
  binRelPath = pkgJson.bin;
@@ -60,6 +58,18 @@ if (!fs.existsSync(dbPath)) {
60
58
  });
61
59
  }
62
60
 
61
+ // .next 폴더가 없으면 빌드
62
+ const nextDir = path.join(packageDir, '.next');
63
+ if (!fs.existsSync(nextDir)) {
64
+ console.log('Building application (first run)...');
65
+ const nextPath = getBinPath('next', 'next');
66
+ execSync(`node "${nextPath}" build`, {
67
+ cwd: packageDir,
68
+ stdio: 'inherit',
69
+ env,
70
+ });
71
+ }
72
+
63
73
  // 서버 시작
64
74
  const port = process.env.PORT || 3100;
65
75
  const url = `http://localhost:${port}`;
@@ -0,0 +1,222 @@
1
+ 'use client'
2
+
3
+ import { useState } from 'react'
4
+ import dynamic from 'next/dynamic'
5
+ import { MockEndpoint } from '@/types/endpoint'
6
+ import {
7
+ Dialog,
8
+ DialogContent,
9
+ DialogHeader,
10
+ DialogTitle,
11
+ } from '@/components/ui/dialog'
12
+ import { Button } from '@/components/ui/button'
13
+ import { Input } from '@/components/ui/input'
14
+ import { Label } from '@/components/ui/label'
15
+
16
+ const MonacoEditor = dynamic(() => import('@monaco-editor/react'), {
17
+ ssr: false,
18
+ loading: () => <div className="h-[300px] bg-gray-100 animate-pulse rounded" />,
19
+ })
20
+
21
+ interface ApiTestDialogProps {
22
+ endpoint: MockEndpoint | null
23
+ open: boolean
24
+ onOpenChange: (open: boolean) => void
25
+ }
26
+
27
+ const methodColors: Record<string, string> = {
28
+ GET: 'bg-green-100 text-green-800',
29
+ POST: 'bg-blue-100 text-blue-800',
30
+ PUT: 'bg-yellow-100 text-yellow-800',
31
+ PATCH: 'bg-orange-100 text-orange-800',
32
+ DELETE: 'bg-red-100 text-red-800',
33
+ }
34
+
35
+ export function ApiTestDialog({
36
+ endpoint,
37
+ open,
38
+ onOpenChange,
39
+ }: ApiTestDialogProps) {
40
+ const [loading, setLoading] = useState(false)
41
+ const [response, setResponse] = useState<{
42
+ status: number
43
+ statusText: string
44
+ body: string
45
+ time: number
46
+ } | null>(null)
47
+ const [error, setError] = useState<string | null>(null)
48
+ const [pathParams, setPathParams] = useState<Record<string, string>>({})
49
+
50
+ const baseUrl = typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000'
51
+
52
+ // Extract path parameters like :id, :userId from path
53
+ const extractPathParams = (path: string): string[] => {
54
+ const matches = path.match(/:(\w+)/g)
55
+ return matches ? matches.map((m) => m.slice(1)) : []
56
+ }
57
+
58
+ const paramNames = endpoint ? extractPathParams(endpoint.path) : []
59
+
60
+ // Build the actual URL with path parameters replaced
61
+ const buildUrl = (): string => {
62
+ if (!endpoint) return ''
63
+ let path = endpoint.path
64
+ for (const [key, value] of Object.entries(pathParams)) {
65
+ path = path.replace(`:${key}`, value || `:${key}`)
66
+ }
67
+ return `${baseUrl}/api/mock${path}`
68
+ }
69
+
70
+ const handleSend = async () => {
71
+ if (!endpoint) return
72
+
73
+ setLoading(true)
74
+ setError(null)
75
+ setResponse(null)
76
+
77
+ const startTime = performance.now()
78
+
79
+ try {
80
+ const res = await fetch(buildUrl(), {
81
+ method: endpoint.method,
82
+ })
83
+
84
+ const endTime = performance.now()
85
+ const body = await res.text()
86
+
87
+ let formattedBody = body
88
+ try {
89
+ formattedBody = JSON.stringify(JSON.parse(body), null, 2)
90
+ } catch {
91
+ // Not JSON, use as-is
92
+ }
93
+
94
+ setResponse({
95
+ status: res.status,
96
+ statusText: res.statusText,
97
+ body: formattedBody,
98
+ time: Math.round(endTime - startTime),
99
+ })
100
+ } catch (err) {
101
+ setError(err instanceof Error ? err.message : 'Request failed')
102
+ } finally {
103
+ setLoading(false)
104
+ }
105
+ }
106
+
107
+ const handleOpenChange = (isOpen: boolean) => {
108
+ if (!isOpen) {
109
+ setResponse(null)
110
+ setError(null)
111
+ setPathParams({})
112
+ }
113
+ onOpenChange(isOpen)
114
+ }
115
+
116
+ const getStatusColor = (status: number) => {
117
+ if (status >= 200 && status < 300) return 'text-green-600'
118
+ if (status >= 400 && status < 500) return 'text-yellow-600'
119
+ if (status >= 500) return 'text-red-600'
120
+ return 'text-gray-600'
121
+ }
122
+
123
+ if (!endpoint) return null
124
+
125
+ return (
126
+ <Dialog open={open} onOpenChange={handleOpenChange}>
127
+ <DialogContent className="sm:max-w-[700px] max-h-[90vh] flex flex-col">
128
+ <DialogHeader>
129
+ <DialogTitle className="flex items-center gap-3">
130
+ <span
131
+ className={`px-2 py-1 rounded text-xs font-medium ${methodColors[endpoint.method] || 'bg-gray-100'}`}
132
+ >
133
+ {endpoint.method}
134
+ </span>
135
+ <span>Test API</span>
136
+ </DialogTitle>
137
+ </DialogHeader>
138
+
139
+ <div className="flex flex-col gap-4 flex-1 overflow-hidden">
140
+ {/* URL Display */}
141
+ <div className="flex items-center gap-2">
142
+ <Input
143
+ readOnly
144
+ value={buildUrl()}
145
+ className="flex-1 font-mono text-sm bg-gray-50"
146
+ />
147
+ <Button onClick={handleSend} disabled={loading}>
148
+ {loading ? 'Sending...' : 'Send'}
149
+ </Button>
150
+ </div>
151
+
152
+ {/* Path Parameters */}
153
+ {paramNames.length > 0 && (
154
+ <div className="space-y-2 p-3 bg-gray-50 rounded-lg">
155
+ <Label className="text-sm font-medium">Path Parameters</Label>
156
+ <div className="grid gap-2">
157
+ {paramNames.map((param) => (
158
+ <div key={param} className="flex items-center gap-2">
159
+ <Label className="w-24 text-sm text-gray-600">:{param}</Label>
160
+ <Input
161
+ placeholder={`Enter ${param}`}
162
+ value={pathParams[param] || ''}
163
+ onChange={(e) =>
164
+ setPathParams((prev) => ({
165
+ ...prev,
166
+ [param]: e.target.value,
167
+ }))
168
+ }
169
+ className="flex-1"
170
+ />
171
+ </div>
172
+ ))}
173
+ </div>
174
+ </div>
175
+ )}
176
+
177
+ {/* Error Display */}
178
+ {error && (
179
+ <div className="p-3 bg-red-50 border border-red-200 rounded-lg">
180
+ <p className="text-red-600 text-sm">{error}</p>
181
+ </div>
182
+ )}
183
+
184
+ {/* Response Display */}
185
+ {response && (
186
+ <div className="flex-1 flex flex-col overflow-hidden">
187
+ <div className="flex items-center gap-4 mb-2">
188
+ <span className="text-sm font-medium">Response</span>
189
+ <span className={`text-sm font-medium ${getStatusColor(response.status)}`}>
190
+ {response.status} {response.statusText}
191
+ </span>
192
+ <span className="text-sm text-gray-500">{response.time}ms</span>
193
+ </div>
194
+ <div className="border rounded-md overflow-hidden flex-1">
195
+ <MonacoEditor
196
+ height="300px"
197
+ language="json"
198
+ theme="vs-dark"
199
+ value={response.body}
200
+ options={{
201
+ readOnly: true,
202
+ minimap: { enabled: false },
203
+ fontSize: 13,
204
+ scrollBeyondLastLine: false,
205
+ automaticLayout: true,
206
+ }}
207
+ />
208
+ </div>
209
+ </div>
210
+ )}
211
+
212
+ {/* Initial State */}
213
+ {!response && !error && !loading && (
214
+ <div className="flex-1 flex items-center justify-center text-gray-400 border-2 border-dashed rounded-lg">
215
+ Click Send to test the API
216
+ </div>
217
+ )}
218
+ </div>
219
+ </DialogContent>
220
+ </Dialog>
221
+ )
222
+ }