diff --git a/package-lock.json b/package-lock.json index ff2a8d0..251645b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,24 @@ { - "name": "mockapi.io", + "name": "mock-api", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "mockapi.io", + "name": "mock-api", "version": "0.1.0", + "hasInstallScript": true, "dependencies": { + "@clerk/nextjs": "^6.12.0", "@faker-js/faker": "^9.5.0", "@paralleldrive/cuid2": "^2.2.2", "@prisma/client": "^6.3.1", + "clsx": "^2.1.1", + "framer-motion": "^12.4.7", "next": "15.1.7", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "zod": "^3.24.2" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -41,6 +46,123 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@clerk/backend": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/@clerk/backend/-/backend-1.24.2.tgz", + "integrity": "sha512-O5W3AlZ/g5dcdadXbBJfq+JqXoTimR2vm8bMTEJ2MEaCDXOyFoGvlv+93XxS3TgcuWFrVPgzdBPfaE/95G5Xxw==", + "license": "MIT", + "dependencies": { + "@clerk/shared": "^2.22.0", + "@clerk/types": "^4.46.1", + "cookie": "1.0.2", + "snakecase-keys": "8.0.1", + "tslib": "2.4.1" + }, + "engines": { + "node": ">=18.17.0" + } + }, + "node_modules/@clerk/backend/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "license": "0BSD" + }, + "node_modules/@clerk/clerk-react": { + "version": "5.23.0", + "resolved": "https://registry.npmjs.org/@clerk/clerk-react/-/clerk-react-5.23.0.tgz", + "integrity": "sha512-2BMT3+KOWRJsAPDuhFoVdee2solukUcHw8BuKFfiOw6XXbU01H7WBezQCgF8gGFwrygEzZ0P5MBIz3L6MC/LYQ==", + "license": "MIT", + "dependencies": { + "@clerk/shared": "^2.22.0", + "@clerk/types": "^4.46.1", + "tslib": "2.4.1" + }, + "engines": { + "node": ">=18.17.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-0", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-0" + } + }, + "node_modules/@clerk/clerk-react/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "license": "0BSD" + }, + "node_modules/@clerk/nextjs": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@clerk/nextjs/-/nextjs-6.12.0.tgz", + "integrity": "sha512-fA+M09d9KdZd0L0yDpQZJ0LscKwrZkJVa+4Zkk9LVdeanhbrLokQljS9ZbZvAS+jGKTzjX0SPhTeJdoq3NTFHg==", + "license": "MIT", + "dependencies": { + "@clerk/backend": "^1.24.2", + "@clerk/clerk-react": "^5.23.0", + "@clerk/shared": "^2.22.0", + "@clerk/types": "^4.46.1", + "crypto-js": "4.2.0", + "server-only": "0.0.1", + "tslib": "2.4.1" + }, + "engines": { + "node": ">=18.17.0" + }, + "peerDependencies": { + "next": "^13.5.4 || ^14.0.3 || ^15.0.0", + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-0", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-0" + } + }, + "node_modules/@clerk/nextjs/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "license": "0BSD" + }, + "node_modules/@clerk/shared": { + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/@clerk/shared/-/shared-2.22.0.tgz", + "integrity": "sha512-VWBeddOJVa3sqUPdvquaaQYw4h5hACSG3EUDOW7eSu2F6W3BXUozyLJQPBJ9C0MuoeHhOe/DeV8x2KqOgxVZaQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@clerk/types": "^4.46.1", + "dequal": "2.0.3", + "glob-to-regexp": "0.4.1", + "js-cookie": "3.0.5", + "std-env": "^3.7.0", + "swr": "^2.2.0" + }, + "engines": { + "node": ">=18.17.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-0", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@clerk/types": { + "version": "4.46.1", + "resolved": "https://registry.npmjs.org/@clerk/types/-/types-4.46.1.tgz", + "integrity": "sha512-QwomMjG1v2GqOITU2/rQUC11LFFsqNFUA92VKDo8dS1nN9iQadT6cNk7MZWqEYTxAfAM/IgX/gB00aGsLKaV8g==", + "license": "MIT", + "dependencies": { + "csstype": "3.1.3" + }, + "engines": { + "node": ">=18.17.0" + } + }, "node_modules/@emnapi/runtime": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", @@ -1848,6 +1970,15 @@ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -1910,6 +2041,15 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1925,6 +2065,12 @@ "node": ">= 8" } }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -1942,7 +2088,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, "license": "MIT" }, "node_modules/damerau-levenshtein": { @@ -2067,6 +2212,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/detect-libc": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", @@ -2104,6 +2258,16 @@ "node": ">=0.10.0" } }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -2917,6 +3081,33 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/framer-motion": { + "version": "12.4.7", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.4.7.tgz", + "integrity": "sha512-VhrcbtcAMXfxlrjeHPpWVu2+mkcoR31e02aNSR7OUS/hZAciKa8q6o3YN2mA1h+jjscRsSyKvX6E1CiY/7OLMw==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.4.5", + "motion-utils": "^12.0.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -3077,6 +3268,12 @@ "node": ">=10.13.0" } }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "license": "BSD-2-Clause" + }, "node_modules/glob/node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -3782,6 +3979,15 @@ "jiti": "bin/jiti.js" } }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3952,6 +4158,15 @@ "loose-envify": "cli.js" } }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, "node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", @@ -3959,6 +4174,18 @@ "dev": true, "license": "ISC" }, + "node_modules/map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -4026,6 +4253,21 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/motion-dom": { + "version": "12.4.5", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.4.5.tgz", + "integrity": "sha512-Q2xmhuyYug1CGTo0jdsL05EQ4RhIYXlggFS/yPhQQRNzbrhjKQ1tbjThx5Plv68aX31LsUQRq4uIkuDxdO5vRQ==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.0.0" + } + }, + "node_modules/motion-utils": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.0.0.tgz", + "integrity": "sha512-MNFiBKbbqnmvOjkPyOKgHUp3Q6oiokLkI1bEwm5QA28cxMZrv0CbbBGDNmhF6DIXsi1pCQBSs0dX8xjeER1tmA==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -4152,6 +4394,16 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -4951,6 +5203,12 @@ "node": ">=10" } }, + "node_modules/server-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/server-only/-/server-only-0.0.1.tgz", + "integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==", + "license": "MIT" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -5162,6 +5420,30 @@ "is-arrayish": "^0.3.1" } }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/snakecase-keys": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/snakecase-keys/-/snakecase-keys-8.0.1.tgz", + "integrity": "sha512-Sj51kE1zC7zh6TDlNNz0/Jn1n5HiHdoQErxO8jLtnyrkJW/M5PrI7x05uDgY3BO7OUQYKCvmeMurW6BPUdwEOw==", + "license": "MIT", + "dependencies": { + "map-obj": "^4.1.0", + "snake-case": "^3.0.4", + "type-fest": "^4.15.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -5178,6 +5460,12 @@ "dev": true, "license": "MIT" }, + "node_modules/std-env": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz", + "integrity": "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==", + "license": "MIT" + }, "node_modules/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -5498,6 +5786,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swr": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.2.tgz", + "integrity": "sha512-RosxFpiabojs75IwQ316DGoDRmOqtiAj0tg8wCcbEu4CiLZBs/a9QNtHV7TUfDXmmlgqij/NqzKq/eLelyv9xA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/tailwindcss": { "version": "3.4.17", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", @@ -5706,6 +6007,18 @@ "node": ">= 0.8.0" } }, + "node_modules/type-fest": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.35.0.tgz", + "integrity": "sha512-2/AwEFQDFEy30iOLjrvHDIH7e4HEWH+f1Yl1bI5XMqzuoCUqwYCdxachgsgv0og/JdVZUhbfjcJAoHj5L1753A==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typed-array-buffer": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", @@ -5834,6 +6147,15 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", + "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -6075,6 +6397,15 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.24.2", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", + "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 7fb03cf..178e952 100644 --- a/package.json +++ b/package.json @@ -10,12 +10,16 @@ "postinstall": "prisma generate" }, "dependencies": { + "@clerk/nextjs": "^6.12.0", "@faker-js/faker": "^9.5.0", "@paralleldrive/cuid2": "^2.2.2", "@prisma/client": "^6.3.1", + "clsx": "^2.1.1", + "framer-motion": "^12.4.7", "next": "15.1.7", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "zod": "^3.24.2" }, "devDependencies": { "@eslint/eslintrc": "^3", diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 8ff1092..6ca5a54 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -2,6 +2,8 @@ import type { Metadata } from "next"; import { Inter } from "next/font/google"; import "./globals.css"; +import { ClerkProvider, SignedOut, SignInButton, SignedIn, UserButton } from '@clerk/nextjs'; + const inter = Inter({ subsets: ["latin"] }); export const metadata: Metadata = { @@ -11,16 +13,24 @@ export const metadata: Metadata = { export default function RootLayout({ children, -}: Readonly<{ +}: { children: React.ReactNode; -}>) { +}) { return ( - - - {children} - - + + + +
+ + + + + + +
+ {children} + + +
); } diff --git a/src/app/page.tsx b/src/app/page.tsx index 755c13b..3096412 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -7,67 +7,111 @@ const ROOT_URL = process.env.ROOT_SITE; export default async function Home() { const projects = (await getProjects()) ?? []; - console.log(projects); - const baseUrl = ROOT_URL; return ( -
-
-
-

- Create Your Mock API +
+
+ {/* Hero Section with Nextra-style */} +
+

+ MockAPI

-

- Design, create and manage your mock APIs with ease. Generate - realistic data for your frontend development. +

+ Build and test your frontend with realistic mock APIs. Generate code for Next.js, Express, and FastAPI.

-
-
- - Create New Project - +
+ + Create New Project + + + + + + Documentation + + + + +
+
+ + {/* Features Grid with Nextra-style cards */} +
+
+
+ + + +
+

Instant API Generation

+

Generate RESTful APIs with realistic mock data in seconds.

+
+ +
+
+ + +
+

Realistic Data

+

Powered by Faker.js for realistic and customizable mock data.

+
+ +
+
+ + + +
+

Code Generation

+

Get ready-to-use code for your favorite frameworks.

-
-
    + {/* Projects List with Nextra-style */} +
    +
    +

    Your Projects

    +
    +
    {projects.map((project) => ( -
  • - -
    -
    -

    - {project.name} -

    -

    - {project.resources.length} resources -

    -

    - Base URL: {baseUrl}/{project.id} -

    -
    + +
    +
    - - - +

    {project.name}

    +
    +

    + {project.resources.length} resources +

    + + {ROOT_URL}/{project.id} + +
    + + +
    - -
  • +
    + ))} -
+ {projects.length === 0 && ( +
+

No projects yet. Create your first project to get started.

+
+ )} +
diff --git a/src/app/projects/[id]/page.tsx b/src/app/projects/[id]/page.tsx index abb6121..e20acc3 100644 --- a/src/app/projects/[id]/page.tsx +++ b/src/app/projects/[id]/page.tsx @@ -3,13 +3,15 @@ import { notFound } from "next/navigation"; import { getProject } from "@/server/actions/projects"; import { DeleteResourceButton } from "@/app/resources/DeleteResourceButton"; import { DeleteProjectButton } from "@/app/projects/DeleteProjectButton"; +import { CopyButton } from "@/components/common/CopyButton"; +import type { Resource as PrismaResource } from "@prisma/client"; const ROOT_URL = process.env.ROOT_SITE; -interface Resource { - id: string; - name: string; - endpoint: string; +interface Resource extends PrismaResource { + _count?: { + data: number; + }; } interface PageProps { @@ -33,60 +35,94 @@ export default async function ProjectPage({ params }: PageProps) { }; return ( -
-
-
-
-

- {project.name} -

-

Base URL: {apiBaseUrl}

-
-
- - - Create Resource +
+
+ {/* Project Header */} +
+
+ + Projects + / + {project.name} +
+
+
+

{project.name}

+
+ + {apiBaseUrl} + + +
+
+
+ + + + + + New Resource + +
-
-
    - {project.resources.map((resource) => ( -
  • -
    -
    -

    - {resource.name} -

    -

    - Endpoint: {apiBaseUrl}/{getResourceEndpoint(resource)} -

    -
    -
    - - View - - + {/* Resources List */} +
    + {project.resources.map((resource) => ( +
    +
    +
    +

    + {resource.name} +

    +
    + + {apiBaseUrl}/{getResourceEndpoint(resource)} + +
    + + {resource._count?.data || 0} records +
    -
  • - ))} - - {project.resources.length === 0 && ( -
  • -
    - No resources yet. Create your first resource to get started. +
    + + View Resource + + + + +
    -
  • - )} -
+
+
+ ))} + + {project.resources.length === 0 && ( +
+ + + +

No resources yet

+ + Create your first resource + +
+ )}
diff --git a/src/app/projects/[id]/resources/[resourceId]/page.tsx b/src/app/projects/[id]/resources/[resourceId]/page.tsx index b60b71b..36d8f42 100644 --- a/src/app/projects/[id]/resources/[resourceId]/page.tsx +++ b/src/app/projects/[id]/resources/[resourceId]/page.tsx @@ -1,13 +1,13 @@ import { notFound } from "next/navigation"; import Link from "next/link"; import { getProject } from "@/server/actions/projects"; -import { getResource, TemplateObject } from "@/server/actions/resources"; +import { getResource} from "@/server/actions/resources"; import { DeleteResourceButton } from "@/app/resources/DeleteResourceButton"; import { GenerateDataSection } from "./GenerateDataSection"; -import { EditableTemplate } from "@/app/components/EditableTemplate"; -import { IdTypeToggle } from "@/app/components/IdTypeToggle"; import { ApiEndpointsSection } from "@/app/components/ApiEndpointsSection"; -import { EndpointTemplateEditor } from "@/app/components/EndpointTemplateEditor"; +import { CodeGenerator } from "@/components/resource/CodeGenerator"; +import { ResourceTemplateWrapper } from "@/components/resource/ResourceTemplateWrapper"; +import { CopyButton } from "@/components/common/CopyButton"; const ROOT_URL = process.env.ROOT_SITE; @@ -29,77 +29,101 @@ export default async function ResourcePage({ params }: PageProps) { const apiBaseUrl = `${ROOT_URL}/${project.id}`; const resourceEndpoint = `api/${resource.endpoint}`; - const currentCount = resource._count?.data ?? 0; - const fullUrl = `${apiBaseUrl}/${resourceEndpoint}`; return ( -
-
-
-
-
- - {project.name} - - / - {resource.name} -
-
-

+
+
+ {/* Resource Header */} +
+
+ + Projects + + / + + {project.name} + + / + {resource.name} +
+
+
+

{resource.name}

- +
+ + {fullUrl} + + +
+ + {resource._count?.data || 0} records +
+
-

- Base URL: {apiBaseUrl}/{resourceEndpoint} -

+
-
- + {/* Main Content */} +
+ {/* Data Generation Section */} +
+ +
-
- } - /> -
-
- -
+ {/* API Endpoints Section */} +
+ } + /> +
+ + {/* Template Editor */} +
+ } + /> +
+ + {/* Code Generator */} +
+

+ API Code Generator +

+ } + initialEndpoints={{ + get: resource.allowGet, + getById: resource.allowGetById, + post: resource.allowPost, + put: resource.allowPut, + delete: resource.allowDelete + }} + />
- - } - />
diff --git a/src/app/projects/[id]/resources/new/NewResourceForm.tsx b/src/app/projects/[id]/resources/new/NewResourceForm.tsx index 44daf07..07ed998 100644 --- a/src/app/projects/[id]/resources/new/NewResourceForm.tsx +++ b/src/app/projects/[id]/resources/new/NewResourceForm.tsx @@ -1,8 +1,14 @@ "use client"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; +import { ResourceNameInput } from "@/components/resource/ResourceNameInput"; +import { VersionInput } from "@/components/resource/VersionInput"; +import { TemplateEditor } from "@/components/template/TemplateEditor"; +import { generateTemplate } from "@/utils/templateUtils"; import { createResource } from "@/server/actions/resources"; +import type { TemplateField } from "@/types/template"; +import { EditorStateSchema, type EditorState } from "@/schemas/template"; interface NewResourceFormProps { projectId: string; @@ -12,38 +18,120 @@ export default function NewResourceForm({ projectId }: NewResourceFormProps) { const router = useRouter(); const [name, setName] = useState(""); const [version, setVersion] = useState("v1"); - const [templateText, setTemplateText] = useState(""); + const [fields, setFields] = useState([]); const [error, setError] = useState(null); + const [editorState, setEditorState] = useState({ + mode: "visual", + template: "{}", + }); - // Function to convert string to slug - const toSlug = (str: string) => { - return str - .toLowerCase() - .trim() - .replace(/[^\w\s-]/g, "") // Remove non-word chars - .replace(/[\s_-]+/g, "-") // Replace spaces and _ with - - .replace(/^-+|-+$/g, ""); // Remove leading/trailing - + // Sync fields with manual template when switching modes + useEffect(() => { + if (editorState.mode === "manual" && fields.length > 0) { + const template = JSON.stringify(generateTemplate(fields), null, 2); + setEditorState(prev => ({ ...prev, template })); + } + }, [editorState.mode, fields]); + + const handleUpdateField = (field: TemplateField, updates: Partial) => { + const updateFieldInArray = (fieldsArray: TemplateField[]): TemplateField[] => { + return fieldsArray.map(f => { + if (f === field) { + return { ...f, ...updates }; + } + if (f.type === "object" && f.fields) { + return { ...f, fields: updateFieldInArray(f.fields) }; + } + if (f.type === "array" && f.items) { + if (f.items === field) { + return { ...f, items: { ...f.items, ...updates } }; + } + if (f.items.fields) { + return { ...f, items: { ...f.items, fields: updateFieldInArray(f.items.fields) } }; + } + } + return f; + }); + }; + + setFields(updateFieldInArray(fields)); + }; + + const handleRemoveField = (fieldToRemove: TemplateField) => { + const removeFieldFromArray = (fieldsArray: TemplateField[]): TemplateField[] => { + return fieldsArray.filter(f => { + if (f === fieldToRemove) return false; + if (f.type === "object" && f.fields) { + f.fields = removeFieldFromArray(f.fields); + } + if (f.type === "array" && f.items && f.items.fields) { + f.items.fields = removeFieldFromArray(f.items.fields); + } + return true; + }); + }; + + setFields(removeFieldFromArray(fields)); + }; + + const handleAddNestedField = (parentField: TemplateField) => { + const updateFieldInArray = (fieldsArray: TemplateField[]): TemplateField[] => { + return fieldsArray.map(f => { + if (f === parentField) { + return { + ...f, + fields: [...(f.fields || []), { key: "", type: "simple" }] + }; + } + if (f.type === "object" && f.fields) { + return { ...f, fields: updateFieldInArray(f.fields) }; + } + return f; + }); + }; + + setFields(updateFieldInArray(fields)); + }; + + const handleModeChange = (mode: "visual" | "manual") => { + try { + const newState = { mode, template: editorState.template }; + EditorStateSchema.parse(newState); + setEditorState(newState); + } catch (error) { + console.error(error); + setError("Invalid editor state"); + } }; - const handleNameChange = (e: React.ChangeEvent) => { - const sluggedName = toSlug(e.target.value); - setName(sluggedName); + const handleTemplateChange = (value: string) => { + try { + JSON.parse(value); // Validate JSON + const newState = { ...editorState, template: value }; + EditorStateSchema.parse(newState); + setEditorState(newState); + setError(null); + } catch { + setError("Invalid JSON format"); + } }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { - const template = JSON.parse(templateText); - const endpoint = `${version}/${name}`; + const template = editorState.mode === "visual" + ? generateTemplate(fields) + : JSON.parse(editorState.template); + await createResource(projectId, { name, - endpoint, + endpoint: `${version}/${name}`, template, }); router.push(`/projects/${projectId}`); } catch (error) { console.error(error); - setError("Invalid JSON format. Please check your template."); + setError("Failed to create resource. Please try again."); } }; @@ -53,89 +141,31 @@ export default function NewResourceForm({ projectId }: NewResourceFormProps) { Create New Resource

-
- - -

- Use lowercase letters, numbers, and hyphens only (e.g., - "blog-posts", "users", "api-keys") -

-
- -
- -
- - api/ - - setVersion(e.target.value)} - className="flex-1 block w-full rounded-none rounded-r-md border border-gray-600 bg-gray-700 text-gray-100 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm p-2" - placeholder="v1" - required - /> -
-

- Example: v1, v2, beta, etc. -

-
- -
- -