@kurly-growth/growthman 0.1.13 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +58 -0
- package/app/api/endpoints/[id]/route.ts +63 -0
- package/app/api/endpoints/bulk/route.ts +23 -0
- package/app/api/endpoints/import/route.ts +62 -0
- package/app/api/endpoints/route.ts +39 -0
- package/app/api/endpoints/sync/route.ts +89 -0
- package/app/api/mock/[...slug]/route.ts +82 -0
- package/app/globals.css +125 -0
- package/app/layout.tsx +36 -0
- package/app/page.tsx +213 -0
- package/bin/cli.js +12 -2
- package/components/api-test-dialog.tsx +222 -0
- package/components/endpoint-edit-dialog.tsx +181 -0
- package/components/endpoint-table.tsx +147 -0
- package/components/openapi-upload-dialog.tsx +213 -0
- package/components/ui/button.tsx +62 -0
- package/components/ui/checkbox.tsx +32 -0
- package/components/ui/dialog.tsx +143 -0
- package/components/ui/input.tsx +21 -0
- package/components/ui/label.tsx +24 -0
- package/components/ui/sonner.tsx +37 -0
- package/components/ui/table.tsx +116 -0
- package/components/ui/textarea.tsx +18 -0
- package/components.json +22 -0
- package/lib/openapi-parser.ts +270 -0
- package/lib/prisma.ts +9 -0
- package/lib/utils.ts +6 -0
- package/lib/validation.ts +19 -0
- package/next-env.d.ts +6 -0
- package/next.config.ts +7 -0
- package/package.json +10 -19
- package/pnpm-workspace.yaml +4 -0
- package/postcss.config.mjs +7 -0
- package/prisma/seed.ts +29 -0
- package/tsconfig.json +34 -0
- package/types/endpoint.ts +10 -0
- package/.next/BUILD_ID +0 -1
- package/.next/app-path-routes-manifest.json +0 -12
- package/.next/build/chunks/[root-of-the-server]__51225daf._.js +0 -206
- package/.next/build/chunks/[root-of-the-server]__51225daf._.js.map +0 -8
- package/.next/build/chunks/[root-of-the-server]__974941ed._.js +0 -500
- package/.next/build/chunks/[root-of-the-server]__974941ed._.js.map +0 -11
- package/.next/build/chunks/[turbopack-node]_transforms_postcss_ts_6920245c._.js +0 -13
- package/.next/build/chunks/[turbopack-node]_transforms_postcss_ts_6920245c._.js.map +0 -5
- package/.next/build/chunks/[turbopack]_runtime.js +0 -795
- package/.next/build/chunks/[turbopack]_runtime.js.map +0 -10
- package/.next/build/chunks/node_modules_fe693df6._.js +0 -6758
- package/.next/build/chunks/node_modules_fe693df6._.js.map +0 -47
- package/.next/build/package.json +0 -1
- package/.next/build/postcss.js +0 -6
- package/.next/build/postcss.js.map +0 -5
- package/.next/build-manifest.json +0 -19
- package/.next/diagnostics/build-diagnostics.json +0 -6
- package/.next/diagnostics/framework.json +0 -1
- package/.next/export-marker.json +0 -6
- package/.next/fallback-build-manifest.json +0 -12
- package/.next/images-manifest.json +0 -66
- package/.next/next-minimal-server.js.nft.json +0 -1
- package/.next/next-server.js.nft.json +0 -1
- package/.next/package.json +0 -1
- package/.next/prerender-manifest.json +0 -114
- package/.next/required-server-files.js +0 -163
- package/.next/required-server-files.json +0 -163
- package/.next/routes-manifest.json +0 -109
- package/.next/server/app/_global-error/page/app-paths-manifest.json +0 -3
- package/.next/server/app/_global-error/page/build-manifest.json +0 -16
- package/.next/server/app/_global-error/page/next-font-manifest.json +0 -6
- package/.next/server/app/_global-error/page/react-loadable-manifest.json +0 -1
- package/.next/server/app/_global-error/page/server-reference-manifest.json +0 -4
- package/.next/server/app/_global-error/page.js +0 -11
- package/.next/server/app/_global-error/page.js.map +0 -5
- package/.next/server/app/_global-error/page.js.nft.json +0 -1
- package/.next/server/app/_global-error/page_client-reference-manifest.js +0 -2
- package/.next/server/app/_global-error.html +0 -2
- package/.next/server/app/_global-error.meta +0 -15
- package/.next/server/app/_global-error.rsc +0 -13
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +0 -5
- package/.next/server/app/_global-error.segments/_full.segment.rsc +0 -13
- package/.next/server/app/_global-error.segments/_head.segment.rsc +0 -6
- package/.next/server/app/_global-error.segments/_index.segment.rsc +0 -4
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +0 -1
- package/.next/server/app/_not-found/page/app-paths-manifest.json +0 -3
- package/.next/server/app/_not-found/page/build-manifest.json +0 -16
- package/.next/server/app/_not-found/page/next-font-manifest.json +0 -11
- package/.next/server/app/_not-found/page/react-loadable-manifest.json +0 -1
- package/.next/server/app/_not-found/page/server-reference-manifest.json +0 -4
- package/.next/server/app/_not-found/page.js +0 -14
- package/.next/server/app/_not-found/page.js.map +0 -5
- package/.next/server/app/_not-found/page.js.nft.json +0 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +0 -2
- package/.next/server/app/_not-found.html +0 -1
- package/.next/server/app/_not-found.meta +0 -16
- package/.next/server/app/_not-found.rsc +0 -15
- package/.next/server/app/_not-found.segments/_full.segment.rsc +0 -15
- package/.next/server/app/_not-found.segments/_head.segment.rsc +0 -6
- package/.next/server/app/_not-found.segments/_index.segment.rsc +0 -6
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +0 -5
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +0 -4
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +0 -2
- package/.next/server/app/api/endpoints/[id]/route/app-paths-manifest.json +0 -3
- package/.next/server/app/api/endpoints/[id]/route/build-manifest.json +0 -11
- package/.next/server/app/api/endpoints/[id]/route/server-reference-manifest.json +0 -4
- package/.next/server/app/api/endpoints/[id]/route.js +0 -7
- package/.next/server/app/api/endpoints/[id]/route.js.map +0 -5
- package/.next/server/app/api/endpoints/[id]/route.js.nft.json +0 -1
- package/.next/server/app/api/endpoints/[id]/route_client-reference-manifest.js +0 -2
- package/.next/server/app/api/endpoints/bulk/route/app-paths-manifest.json +0 -3
- package/.next/server/app/api/endpoints/bulk/route/build-manifest.json +0 -11
- package/.next/server/app/api/endpoints/bulk/route/server-reference-manifest.json +0 -4
- package/.next/server/app/api/endpoints/bulk/route.js +0 -7
- package/.next/server/app/api/endpoints/bulk/route.js.map +0 -5
- package/.next/server/app/api/endpoints/bulk/route.js.nft.json +0 -1
- package/.next/server/app/api/endpoints/bulk/route_client-reference-manifest.js +0 -2
- package/.next/server/app/api/endpoints/import/route/app-paths-manifest.json +0 -3
- package/.next/server/app/api/endpoints/import/route/build-manifest.json +0 -11
- package/.next/server/app/api/endpoints/import/route/server-reference-manifest.json +0 -4
- package/.next/server/app/api/endpoints/import/route.js +0 -7
- package/.next/server/app/api/endpoints/import/route.js.map +0 -5
- package/.next/server/app/api/endpoints/import/route.js.nft.json +0 -1
- package/.next/server/app/api/endpoints/import/route_client-reference-manifest.js +0 -2
- package/.next/server/app/api/endpoints/route/app-paths-manifest.json +0 -3
- package/.next/server/app/api/endpoints/route/build-manifest.json +0 -11
- package/.next/server/app/api/endpoints/route/server-reference-manifest.json +0 -4
- package/.next/server/app/api/endpoints/route.js +0 -7
- package/.next/server/app/api/endpoints/route.js.map +0 -5
- package/.next/server/app/api/endpoints/route.js.nft.json +0 -1
- package/.next/server/app/api/endpoints/route_client-reference-manifest.js +0 -2
- package/.next/server/app/api/endpoints/sync/route/app-paths-manifest.json +0 -3
- package/.next/server/app/api/endpoints/sync/route/build-manifest.json +0 -11
- package/.next/server/app/api/endpoints/sync/route/server-reference-manifest.json +0 -4
- package/.next/server/app/api/endpoints/sync/route.js +0 -7
- package/.next/server/app/api/endpoints/sync/route.js.map +0 -5
- package/.next/server/app/api/endpoints/sync/route.js.nft.json +0 -1
- package/.next/server/app/api/endpoints/sync/route_client-reference-manifest.js +0 -2
- package/.next/server/app/api/mock/[...slug]/route/app-paths-manifest.json +0 -3
- package/.next/server/app/api/mock/[...slug]/route/build-manifest.json +0 -11
- package/.next/server/app/api/mock/[...slug]/route/server-reference-manifest.json +0 -4
- package/.next/server/app/api/mock/[...slug]/route.js +0 -8
- package/.next/server/app/api/mock/[...slug]/route.js.map +0 -5
- package/.next/server/app/api/mock/[...slug]/route.js.nft.json +0 -1
- package/.next/server/app/api/mock/[...slug]/route_client-reference-manifest.js +0 -2
- package/.next/server/app/favicon.ico/route/app-paths-manifest.json +0 -3
- package/.next/server/app/favicon.ico/route/build-manifest.json +0 -11
- package/.next/server/app/favicon.ico/route.js +0 -7
- package/.next/server/app/favicon.ico/route.js.map +0 -5
- package/.next/server/app/favicon.ico/route.js.nft.json +0 -1
- package/.next/server/app/favicon.ico.meta +0 -1
- package/.next/server/app/index.html +0 -1
- package/.next/server/app/index.meta +0 -14
- package/.next/server/app/index.rsc +0 -21
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +0 -9
- package/.next/server/app/index.segments/_full.segment.rsc +0 -21
- package/.next/server/app/index.segments/_head.segment.rsc +0 -6
- package/.next/server/app/index.segments/_index.segment.rsc +0 -6
- package/.next/server/app/index.segments/_tree.segment.rsc +0 -4
- package/.next/server/app/page/app-paths-manifest.json +0 -3
- package/.next/server/app/page/build-manifest.json +0 -16
- package/.next/server/app/page/next-font-manifest.json +0 -11
- package/.next/server/app/page/react-loadable-manifest.json +0 -8
- package/.next/server/app/page/server-reference-manifest.json +0 -4
- package/.next/server/app/page.js +0 -16
- package/.next/server/app/page.js.map +0 -5
- package/.next/server/app/page.js.nft.json +0 -1
- package/.next/server/app/page_client-reference-manifest.js +0 -2
- package/.next/server/app-paths-manifest.json +0 -12
- package/.next/server/chunks/1629d_next_dist_esm_build_templates_app-route_498527d5.js +0 -3
- package/.next/server/chunks/1629d_next_dist_esm_build_templates_app-route_498527d5.js.map +0 -1
- package/.next/server/chunks/[externals]__cf2ccb51._.js +0 -3
- package/.next/server/chunks/[externals]__cf2ccb51._.js.map +0 -1
- package/.next/server/chunks/[externals]_next_dist_8dbe5856._.js +0 -3
- package/.next/server/chunks/[externals]_next_dist_8dbe5856._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__3534fecc._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__3534fecc._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__461ea613._.js +0 -21
- package/.next/server/chunks/[root-of-the-server]__461ea613._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__4929acb0._.js +0 -129
- package/.next/server/chunks/[root-of-the-server]__4929acb0._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__909218aa._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__909218aa._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__a6c01a13._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__a6c01a13._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__db1127cf._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__db1127cf._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__e46f3e25._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__e46f3e25._.js.map +0 -1
- package/.next/server/chunks/[turbopack]_runtime.js +0 -795
- package/.next/server/chunks/[turbopack]_runtime.js.map +0 -10
- package/.next/server/chunks/_next-internal_server_app_api_endpoints_[id]_route_actions_b91cfc4c.js +0 -3
- package/.next/server/chunks/_next-internal_server_app_api_endpoints_[id]_route_actions_b91cfc4c.js.map +0 -1
- package/.next/server/chunks/_next-internal_server_app_api_endpoints_bulk_route_actions_560cc6cd.js +0 -3
- package/.next/server/chunks/_next-internal_server_app_api_endpoints_bulk_route_actions_560cc6cd.js.map +0 -1
- package/.next/server/chunks/_next-internal_server_app_api_endpoints_import_route_actions_f2444950.js +0 -3
- package/.next/server/chunks/_next-internal_server_app_api_endpoints_import_route_actions_f2444950.js.map +0 -1
- package/.next/server/chunks/_next-internal_server_app_api_endpoints_route_actions_49d8ad56.js +0 -3
- package/.next/server/chunks/_next-internal_server_app_api_endpoints_route_actions_49d8ad56.js.map +0 -1
- package/.next/server/chunks/_next-internal_server_app_api_endpoints_sync_route_actions_0f446550.js +0 -3
- package/.next/server/chunks/_next-internal_server_app_api_endpoints_sync_route_actions_0f446550.js.map +0 -1
- package/.next/server/chunks/_next-internal_server_app_api_mock_[___slug]_route_actions_be875f77.js +0 -3
- package/.next/server/chunks/_next-internal_server_app_api_mock_[___slug]_route_actions_be875f77.js.map +0 -1
- package/.next/server/chunks/_next-internal_server_app_favicon_ico_route_actions_353150a5.js +0 -3
- package/.next/server/chunks/_next-internal_server_app_favicon_ico_route_actions_353150a5.js.map +0 -1
- package/.next/server/chunks/node_modules__pnpm_a61fb769._.js +0 -3
- package/.next/server/chunks/node_modules__pnpm_a61fb769._.js.map +0 -1
- package/.next/server/chunks/ssr/1629d_next_dist_1a21bde7._.js +0 -6
- package/.next/server/chunks/ssr/1629d_next_dist_1a21bde7._.js.map +0 -1
- package/.next/server/chunks/ssr/1629d_next_dist_8dc31fba._.js +0 -3
- package/.next/server/chunks/ssr/1629d_next_dist_8dc31fba._.js.map +0 -1
- package/.next/server/chunks/ssr/1629d_next_dist_client_components_b01b33e4._.js +0 -3
- package/.next/server/chunks/ssr/1629d_next_dist_client_components_b01b33e4._.js.map +0 -1
- package/.next/server/chunks/ssr/1629d_next_dist_client_components_builtin_forbidden_4cab2078.js +0 -3
- package/.next/server/chunks/ssr/1629d_next_dist_client_components_builtin_forbidden_4cab2078.js.map +0 -1
- package/.next/server/chunks/ssr/1629d_next_dist_client_components_builtin_global-error_0d5cb623.js +0 -3
- package/.next/server/chunks/ssr/1629d_next_dist_client_components_builtin_global-error_0d5cb623.js.map +0 -1
- package/.next/server/chunks/ssr/1629d_next_dist_client_components_builtin_unauthorized_b8fbdcad.js +0 -3
- package/.next/server/chunks/ssr/1629d_next_dist_client_components_builtin_unauthorized_b8fbdcad.js.map +0 -1
- package/.next/server/chunks/ssr/1629d_next_dist_d78dee57._.js +0 -4
- package/.next/server/chunks/ssr/1629d_next_dist_d78dee57._.js.map +0 -1
- package/.next/server/chunks/ssr/1629d_next_dist_esm_build_templates_app-page_d4e9464b.js +0 -4
- package/.next/server/chunks/ssr/1629d_next_dist_esm_build_templates_app-page_d4e9464b.js.map +0 -1
- package/.next/server/chunks/ssr/67049_lucide-react_dist_esm_createLucideIcon_22fe2e14.js +0 -3
- package/.next/server/chunks/ssr/67049_lucide-react_dist_esm_createLucideIcon_22fe2e14.js.map +0 -1
- package/.next/server/chunks/ssr/[externals]_next_dist_server_app-render_work-async-storage_external_1f8eeae7.js +0 -3
- package/.next/server/chunks/ssr/[externals]_next_dist_server_app-render_work-async-storage_external_1f8eeae7.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__14f4396a._.js +0 -10
- package/.next/server/chunks/ssr/[root-of-the-server]__14f4396a._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__3064bf15._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__3064bf15._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__4ff41ba3._.js +0 -4
- package/.next/server/chunks/ssr/[root-of-the-server]__4ff41ba3._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__57c5da8e._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__57c5da8e._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__67653b38._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__67653b38._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__7d48410d._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__7d48410d._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__a49eaf36._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__a49eaf36._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__b0617f51._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__b0617f51._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__cc026bde._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__cc026bde._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__d079898e._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__d079898e._.js.map +0 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__d3649e47._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__d3649e47._.js.map +0 -1
- package/.next/server/chunks/ssr/[turbopack]_runtime.js +0 -795
- package/.next/server/chunks/ssr/[turbopack]_runtime.js.map +0 -10
- package/.next/server/chunks/ssr/_9b6e3dc4._.js +0 -3
- package/.next/server/chunks/ssr/_9b6e3dc4._.js.map +0 -1
- package/.next/server/chunks/ssr/_a437ac52._.js +0 -7
- package/.next/server/chunks/ssr/_a437ac52._.js.map +0 -1
- package/.next/server/chunks/ssr/_b62b070d._.js +0 -4
- package/.next/server/chunks/ssr/_b62b070d._.js.map +0 -1
- package/.next/server/chunks/ssr/_next-internal_server_app__global-error_page_actions_75761787.js +0 -3
- package/.next/server/chunks/ssr/_next-internal_server_app__global-error_page_actions_75761787.js.map +0 -1
- package/.next/server/chunks/ssr/_next-internal_server_app__not-found_page_actions_554ec2bf.js +0 -3
- package/.next/server/chunks/ssr/_next-internal_server_app__not-found_page_actions_554ec2bf.js.map +0 -1
- package/.next/server/chunks/ssr/_next-internal_server_app_page_actions_39d4fc33.js +0 -3
- package/.next/server/chunks/ssr/_next-internal_server_app_page_actions_39d4fc33.js.map +0 -1
- package/.next/server/chunks/ssr/app_b9b1292a._.js +0 -3
- package/.next/server/chunks/ssr/app_b9b1292a._.js.map +0 -1
- package/.next/server/functions-config-manifest.json +0 -4
- package/.next/server/interception-route-rewrite-manifest.js +0 -1
- package/.next/server/middleware-build-manifest.js +0 -20
- package/.next/server/middleware-manifest.json +0 -6
- package/.next/server/next-font-manifest.js +0 -1
- package/.next/server/next-font-manifest.json +0 -15
- package/.next/server/pages/404.html +0 -1
- package/.next/server/pages/500.html +0 -2
- package/.next/server/pages-manifest.json +0 -4
- package/.next/server/server-reference-manifest.js +0 -1
- package/.next/server/server-reference-manifest.json +0 -5
- package/.next/static/chunks/16403d658c649f0f.js +0 -1
- package/.next/static/chunks/2422cfacfdb28c2c.js +0 -5
- package/.next/static/chunks/42572c067be8ea0f.js +0 -1
- package/.next/static/chunks/5045da71379799ce.js +0 -1
- package/.next/static/chunks/6e04dfc4035d7150.js +0 -5
- package/.next/static/chunks/8155485116e3ff24.js +0 -1
- package/.next/static/chunks/a66e09a7c2336f67.js +0 -1
- package/.next/static/chunks/a6dad97d9634a72d.js +0 -1
- package/.next/static/chunks/a6dad97d9634a72d.js.map +0 -1
- package/.next/static/chunks/da3f3e4f37f68cee.css +0 -3
- package/.next/static/chunks/f1cfb69226717279.js +0 -1
- package/.next/static/chunks/turbopack-7027959231bb432a.js +0 -4
- package/.next/static/media/4fa387ec64143e14-s.c1fdd6c2.woff2 +0 -0
- package/.next/static/media/7178b3e590c64307-s.b97b3418.woff2 +0 -0
- package/.next/static/media/797e433ab948586e-s.p.dbea232f.woff2 +0 -0
- package/.next/static/media/8a480f0b521d4e75-s.8e0177b5.woff2 +0 -0
- package/.next/static/media/bbc41e54d2fcbd21-s.799d8ef8.woff2 +0 -0
- package/.next/static/media/caa3a2e1cccd8315-s.p.853070df.woff2 +0 -0
- package/.next/static/media/favicon.de6b30f1.ico +0 -0
- package/.next/static/rD3QdLXPN9C3vquucAUu6/_buildManifest.js +0 -11
- package/.next/static/rD3QdLXPN9C3vquucAUu6/_clientMiddlewareManifest.json +0 -1
- package/.next/static/rD3QdLXPN9C3vquucAUu6/_ssgManifest.js +0 -1
- package/.next/trace +0 -1
- package/.next/trace-build +0 -1
- package/.next/turbopack +0 -0
- package/.next/types/routes.d.ts +0 -78
- package/.next/types/validator.ts +0 -124
- /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,63 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
2
|
+
import { prisma } from '@/lib/prisma'
|
|
3
|
+
import { validatePath, validateMethod, validateStatusCode } from '@/lib/validation'
|
|
4
|
+
|
|
5
|
+
type RouteParams = {
|
|
6
|
+
params: Promise<{ id: string }>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// GET: 단일 엔드포인트 조회
|
|
10
|
+
export async function GET(_request: NextRequest, { params }: RouteParams) {
|
|
11
|
+
const { id } = await params
|
|
12
|
+
|
|
13
|
+
const endpoint = await prisma.mockEndpoint.findUnique({
|
|
14
|
+
where: { id },
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
if (!endpoint) {
|
|
18
|
+
return NextResponse.json({ error: 'Endpoint not found' }, { status: 404 })
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return NextResponse.json(endpoint)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// PUT: 엔드포인트 수정
|
|
25
|
+
export async function PUT(request: NextRequest, { params }: RouteParams) {
|
|
26
|
+
const { id } = await params
|
|
27
|
+
const body = await request.json()
|
|
28
|
+
const { path, method, description, statusCode, responseBody } = body
|
|
29
|
+
|
|
30
|
+
if (path !== undefined && !validatePath(path)) {
|
|
31
|
+
return NextResponse.json({ error: 'Invalid path' }, { status: 400 })
|
|
32
|
+
}
|
|
33
|
+
if (method !== undefined && !validateMethod(method)) {
|
|
34
|
+
return NextResponse.json({ error: 'Invalid method' }, { status: 400 })
|
|
35
|
+
}
|
|
36
|
+
if (!validateStatusCode(statusCode)) {
|
|
37
|
+
return NextResponse.json({ error: 'Invalid status code' }, { status: 400 })
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const endpoint = await prisma.mockEndpoint.update({
|
|
41
|
+
where: { id },
|
|
42
|
+
data: {
|
|
43
|
+
path,
|
|
44
|
+
method: method?.toUpperCase(),
|
|
45
|
+
description,
|
|
46
|
+
statusCode,
|
|
47
|
+
responseBody,
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
return NextResponse.json(endpoint)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// DELETE: 엔드포인트 삭제
|
|
55
|
+
export async function DELETE(_request: NextRequest, { params }: RouteParams) {
|
|
56
|
+
const { id } = await params
|
|
57
|
+
|
|
58
|
+
await prisma.mockEndpoint.delete({
|
|
59
|
+
where: { id },
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
return NextResponse.json({ success: true })
|
|
63
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
2
|
+
import { prisma } from '@/lib/prisma'
|
|
3
|
+
|
|
4
|
+
// DELETE: 여러 엔드포인트 일괄 삭제
|
|
5
|
+
export async function DELETE(request: NextRequest) {
|
|
6
|
+
const body = await request.json()
|
|
7
|
+
const { ids } = body
|
|
8
|
+
|
|
9
|
+
if (!Array.isArray(ids) || ids.length === 0) {
|
|
10
|
+
return NextResponse.json(
|
|
11
|
+
{ error: 'ids must be a non-empty array' },
|
|
12
|
+
{ status: 400 }
|
|
13
|
+
)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const result = await prisma.mockEndpoint.deleteMany({
|
|
17
|
+
where: {
|
|
18
|
+
id: { in: ids },
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
return NextResponse.json({ deleted: result.count })
|
|
23
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
2
|
+
import { Prisma } from '@/generated/prisma'
|
|
3
|
+
import { prisma } from '@/lib/prisma'
|
|
4
|
+
import { parseOpenAPISpec } from '@/lib/openapi-parser'
|
|
5
|
+
|
|
6
|
+
export async function POST(request: NextRequest) {
|
|
7
|
+
const body = await request.json()
|
|
8
|
+
const { spec, pathPrefix } = body
|
|
9
|
+
|
|
10
|
+
// Handle both old format (spec directly) and new format ({ spec, pathPrefix })
|
|
11
|
+
const actualSpec = spec ?? body
|
|
12
|
+
const endpoints = parseOpenAPISpec(actualSpec)
|
|
13
|
+
|
|
14
|
+
// Apply path prefix if provided
|
|
15
|
+
const normalizedPrefix = pathPrefix ? pathPrefix.replace(/\/+$/, '') : ''
|
|
16
|
+
const processedEndpoints = endpoints.map((ep) => ({
|
|
17
|
+
...ep,
|
|
18
|
+
path: normalizedPrefix ? `${normalizedPrefix}${ep.path}` : ep.path,
|
|
19
|
+
}))
|
|
20
|
+
|
|
21
|
+
if (processedEndpoints.length === 0) {
|
|
22
|
+
return NextResponse.json(
|
|
23
|
+
{ error: 'No valid endpoints found in OpenAPI spec' },
|
|
24
|
+
{ status: 400 }
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const results = await Promise.allSettled(
|
|
29
|
+
processedEndpoints.map((ep) =>
|
|
30
|
+
prisma.mockEndpoint.upsert({
|
|
31
|
+
where: {
|
|
32
|
+
path_method: {
|
|
33
|
+
path: ep.path,
|
|
34
|
+
method: ep.method,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
update: {
|
|
38
|
+
description: ep.description,
|
|
39
|
+
statusCode: ep.statusCode,
|
|
40
|
+
responseBody: ep.responseBody as Prisma.InputJsonValue,
|
|
41
|
+
},
|
|
42
|
+
create: {
|
|
43
|
+
path: ep.path,
|
|
44
|
+
method: ep.method,
|
|
45
|
+
description: ep.description,
|
|
46
|
+
statusCode: ep.statusCode,
|
|
47
|
+
responseBody: ep.responseBody as Prisma.InputJsonValue,
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
)
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
const created = results.filter((r) => r.status === 'fulfilled').length
|
|
54
|
+
const failed = results.filter((r) => r.status === 'rejected').length
|
|
55
|
+
|
|
56
|
+
return NextResponse.json({
|
|
57
|
+
message: `Imported ${created} endpoints`,
|
|
58
|
+
created,
|
|
59
|
+
failed,
|
|
60
|
+
total: processedEndpoints.length,
|
|
61
|
+
})
|
|
62
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
2
|
+
import { prisma } from '@/lib/prisma'
|
|
3
|
+
import { validatePath, validateMethod, validateStatusCode } from '@/lib/validation'
|
|
4
|
+
|
|
5
|
+
// GET: 모든 엔드포인트 조회
|
|
6
|
+
export async function GET() {
|
|
7
|
+
const endpoints = await prisma.mockEndpoint.findMany({
|
|
8
|
+
orderBy: { createdAt: 'desc' },
|
|
9
|
+
})
|
|
10
|
+
return NextResponse.json(endpoints)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// POST: 새 엔드포인트 생성
|
|
14
|
+
export async function POST(request: NextRequest) {
|
|
15
|
+
const body = await request.json()
|
|
16
|
+
const { path, method, description, statusCode, responseBody } = body
|
|
17
|
+
|
|
18
|
+
if (!validatePath(path)) {
|
|
19
|
+
return NextResponse.json({ error: 'Invalid path' }, { status: 400 })
|
|
20
|
+
}
|
|
21
|
+
if (!validateMethod(method)) {
|
|
22
|
+
return NextResponse.json({ error: 'Invalid method' }, { status: 400 })
|
|
23
|
+
}
|
|
24
|
+
if (!validateStatusCode(statusCode)) {
|
|
25
|
+
return NextResponse.json({ error: 'Invalid status code' }, { status: 400 })
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const endpoint = await prisma.mockEndpoint.create({
|
|
29
|
+
data: {
|
|
30
|
+
path,
|
|
31
|
+
method: method.toUpperCase(),
|
|
32
|
+
description,
|
|
33
|
+
statusCode: statusCode ?? 200,
|
|
34
|
+
responseBody,
|
|
35
|
+
},
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
return NextResponse.json(endpoint, { status: 201 })
|
|
39
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
2
|
+
import { Prisma } from '@/generated/prisma'
|
|
3
|
+
import { prisma } from '@/lib/prisma'
|
|
4
|
+
import { parseOpenAPISpec } from '@/lib/openapi-parser'
|
|
5
|
+
|
|
6
|
+
export async function POST(request: NextRequest) {
|
|
7
|
+
const { url, pathPrefix } = await request.json()
|
|
8
|
+
|
|
9
|
+
if (!url) {
|
|
10
|
+
return NextResponse.json({ error: 'URL is required' }, { status: 400 })
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Normalize path prefix (remove trailing slashes)
|
|
14
|
+
const normalizedPrefix = pathPrefix ? pathPrefix.replace(/\/+$/, '') : ''
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
// OpenAPI JSON 가져오기
|
|
18
|
+
const response = await fetch(url, {
|
|
19
|
+
headers: {
|
|
20
|
+
Accept: 'application/json',
|
|
21
|
+
},
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
return NextResponse.json(
|
|
26
|
+
{ error: `Failed to fetch OpenAPI spec: ${response.statusText}` },
|
|
27
|
+
{ status: 400 }
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const spec = await response.json()
|
|
32
|
+
const endpoints = parseOpenAPISpec(spec)
|
|
33
|
+
|
|
34
|
+
// Apply path prefix if provided
|
|
35
|
+
const processedEndpoints = endpoints.map((ep) => ({
|
|
36
|
+
...ep,
|
|
37
|
+
path: normalizedPrefix ? `${normalizedPrefix}${ep.path}` : ep.path,
|
|
38
|
+
}))
|
|
39
|
+
|
|
40
|
+
if (processedEndpoints.length === 0) {
|
|
41
|
+
return NextResponse.json(
|
|
42
|
+
{ error: 'No valid endpoints found in OpenAPI spec' },
|
|
43
|
+
{ status: 400 }
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Upsert로 새로 추가하거나 업데이트
|
|
48
|
+
const results = await Promise.allSettled(
|
|
49
|
+
processedEndpoints.map((ep) =>
|
|
50
|
+
prisma.mockEndpoint.upsert({
|
|
51
|
+
where: {
|
|
52
|
+
path_method: {
|
|
53
|
+
path: ep.path,
|
|
54
|
+
method: ep.method,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
update: {
|
|
58
|
+
description: ep.description,
|
|
59
|
+
statusCode: ep.statusCode,
|
|
60
|
+
responseBody: ep.responseBody as Prisma.InputJsonValue,
|
|
61
|
+
},
|
|
62
|
+
create: {
|
|
63
|
+
path: ep.path,
|
|
64
|
+
method: ep.method,
|
|
65
|
+
description: ep.description,
|
|
66
|
+
statusCode: ep.statusCode,
|
|
67
|
+
responseBody: ep.responseBody as Prisma.InputJsonValue,
|
|
68
|
+
},
|
|
69
|
+
})
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
const succeeded = results.filter((r) => r.status === 'fulfilled').length
|
|
74
|
+
const failed = results.filter((r) => r.status === 'rejected').length
|
|
75
|
+
|
|
76
|
+
return NextResponse.json({
|
|
77
|
+
message: `Synced ${succeeded} endpoints`,
|
|
78
|
+
succeeded,
|
|
79
|
+
failed,
|
|
80
|
+
total: processedEndpoints.length,
|
|
81
|
+
})
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error('Sync error:', error)
|
|
84
|
+
return NextResponse.json(
|
|
85
|
+
{ error: error instanceof Error ? error.message : 'Sync failed' },
|
|
86
|
+
{ status: 500 }
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
2
|
+
import { match } from 'path-to-regexp'
|
|
3
|
+
import { prisma } from '@/lib/prisma'
|
|
4
|
+
|
|
5
|
+
type RouteParams = {
|
|
6
|
+
params: Promise<{ slug: string[] }>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async function findMatchingEndpoint(requestPath: string, method: string) {
|
|
10
|
+
// DB에서 해당 method의 모든 엔드포인트 조회
|
|
11
|
+
const endpoints = await prisma.mockEndpoint.findMany({
|
|
12
|
+
where: { method },
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
// 정확히 일치하는 경로 먼저 확인
|
|
16
|
+
const exactMatch = endpoints.find((ep) => ep.path === requestPath)
|
|
17
|
+
if (exactMatch) {
|
|
18
|
+
return { endpoint: exactMatch, params: {} }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// path-to-regexp를 사용한 패턴 매칭
|
|
22
|
+
for (const endpoint of endpoints) {
|
|
23
|
+
try {
|
|
24
|
+
const matchFn = match(endpoint.path, { decode: decodeURIComponent })
|
|
25
|
+
const result = matchFn(requestPath)
|
|
26
|
+
|
|
27
|
+
if (result) {
|
|
28
|
+
return { endpoint, params: result.params }
|
|
29
|
+
}
|
|
30
|
+
} catch {
|
|
31
|
+
// 잘못된 패턴은 무시
|
|
32
|
+
continue
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function handleRequest(request: NextRequest, { params }: RouteParams) {
|
|
40
|
+
const { slug } = await params
|
|
41
|
+
const requestPath = '/' + slug.join('/')
|
|
42
|
+
const method = request.method
|
|
43
|
+
|
|
44
|
+
const result = await findMatchingEndpoint(requestPath, method)
|
|
45
|
+
|
|
46
|
+
if (!result) {
|
|
47
|
+
return NextResponse.json(
|
|
48
|
+
{ error: 'Endpoint not found', path: requestPath, method },
|
|
49
|
+
{ status: 404 }
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const { endpoint, params: pathParams } = result
|
|
54
|
+
|
|
55
|
+
// responseBody에 pathParams를 포함하여 반환 (선택적)
|
|
56
|
+
const responseData =
|
|
57
|
+
Object.keys(pathParams).length > 0
|
|
58
|
+
? { ...endpoint.responseBody as object, _pathParams: pathParams }
|
|
59
|
+
: endpoint.responseBody
|
|
60
|
+
|
|
61
|
+
return NextResponse.json(responseData, { status: endpoint.statusCode })
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function GET(request: NextRequest, context: RouteParams) {
|
|
65
|
+
return handleRequest(request, context)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function POST(request: NextRequest, context: RouteParams) {
|
|
69
|
+
return handleRequest(request, context)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function PUT(request: NextRequest, context: RouteParams) {
|
|
73
|
+
return handleRequest(request, context)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function PATCH(request: NextRequest, context: RouteParams) {
|
|
77
|
+
return handleRequest(request, context)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function DELETE(request: NextRequest, context: RouteParams) {
|
|
81
|
+
return handleRequest(request, context)
|
|
82
|
+
}
|
package/app/globals.css
ADDED
|
@@ -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
|
+
}
|