diff --git a/eslint.config.mjs b/eslint.config.mjs index e03a0cc..8ed791a 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -6,7 +6,14 @@ const config = [ ...nextWebVitals, ...nextTypescript, { - ignores: [".next/**", "node_modules/**", "coverage/**", "next-env.d.ts"], + ignores: [ + ".next/**", + "node_modules/**", + "coverage/**", + "next-env.d.ts", + // Auto-generated by `msw init` — patched on every MSW upgrade. + "public/mockServiceWorker.js", + ], }, ]; diff --git a/next-env.d.ts b/next-env.d.ts index 9edff1c..c4b7818 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/types/routes.d.ts"; +import "./.next/dev/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/package.json b/package.json index 1042349..13de8c1 100644 --- a/package.json +++ b/package.json @@ -19,20 +19,34 @@ "e2e:install": "playwright install --with-deps chromium" }, "dependencies": { + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "geist": "^1.7.2", + "lucide-react": "^1.17.0", "next": "16.2.6", "next-auth": "5.0.0-beta.25", "react": "19.0.0", - "react-dom": "19.0.0" + "react-dom": "19.0.0", + "tailwind-merge": "^3.6.0" }, "devDependencies": { "@playwright/test": "^1.60.0", + "@tailwindcss/postcss": "^4.3.0", "@types/node": "20.16.10", "@types/react": "19.0.1", "@types/react-dom": "19.0.1", "@vitest/coverage-v8": "2.1.8", "eslint": "9.15.0", "eslint-config-next": "16.2.6", + "msw": "^2.14.6", + "postcss": "^8.5.15", + "tailwindcss": "^4.3.0", "typescript": "5.7.2", "vitest": "2.1.8" + }, + "msw": { + "workerDirectory": [ + "public" + ] } -} +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bf1b65e..601f08a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,18 @@ importers: .: dependencies: + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + geist: + specifier: ^1.7.2 + version: 1.7.2(next@16.2.6(@babel/core@7.29.0)(@playwright/test@1.60.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)) + lucide-react: + specifier: ^1.17.0 + version: 1.17.0(react@19.0.0) next: specifier: 16.2.6 version: 16.2.6(@babel/core@7.29.0)(@playwright/test@1.60.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -20,10 +32,16 @@ importers: react-dom: specifier: 19.0.0 version: 19.0.0(react@19.0.0) + tailwind-merge: + specifier: ^3.6.0 + version: 3.6.0 devDependencies: '@playwright/test': specifier: ^1.60.0 version: 1.60.0 + '@tailwindcss/postcss': + specifier: ^4.3.0 + version: 4.3.0 '@types/node': specifier: 20.16.10 version: 20.16.10 @@ -35,22 +53,35 @@ importers: version: 19.0.1 '@vitest/coverage-v8': specifier: 2.1.8 - version: 2.1.8(vitest@2.1.8(@types/node@20.16.10)) + version: 2.1.8(vitest@2.1.8(@types/node@20.16.10)(lightningcss@1.32.0)(msw@2.14.6(@types/node@20.16.10)(typescript@5.7.2))) eslint: specifier: 9.15.0 - version: 9.15.0 + version: 9.15.0(jiti@2.7.0) eslint-config-next: specifier: 16.2.6 - version: 16.2.6(@typescript-eslint/parser@8.59.4(eslint@9.15.0)(typescript@5.7.2))(eslint@9.15.0)(typescript@5.7.2) + version: 16.2.6(@typescript-eslint/parser@8.59.4(eslint@9.15.0(jiti@2.7.0))(typescript@5.7.2))(eslint@9.15.0(jiti@2.7.0))(typescript@5.7.2) + msw: + specifier: ^2.14.6 + version: 2.14.6(@types/node@20.16.10)(typescript@5.7.2) + postcss: + specifier: ^8.5.15 + version: 8.5.15 + tailwindcss: + specifier: ^4.3.0 + version: 4.3.0 typescript: specifier: 5.7.2 version: 5.7.2 vitest: specifier: 2.1.8 - version: 2.1.8(@types/node@20.16.10) + version: 2.1.8(@types/node@20.16.10)(lightningcss@1.32.0)(msw@2.14.6(@types/node@20.16.10)(typescript@5.7.2)) packages: + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + '@ampproject/remapping@2.3.0': resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} @@ -481,6 +512,41 @@ packages: cpu: [x64] os: [win32] + '@inquirer/ansi@2.0.7': + resolution: {integrity: sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + + '@inquirer/confirm@6.1.1': + resolution: {integrity: sha512-eb8DBZcz/2qHWQda4rk2JiQk5h9QV/cVHi1yjt0f69WFZMRFn0sJTye3EAP8icut8UDMjQPsaH5KbcOogefrFQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@11.2.1': + resolution: {integrity: sha512-Qd6GJT1yVyrZZCfN8W2qKF5ApmqryXRhRKCuip8h01x2w/esJQ2XIYc6f9abMIHgKQdBfFTSOdbHRLAhuM09UA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@2.0.7': + resolution: {integrity: sha512-aJ8TBPOGB6f/2qziPfElISTCEd5XOYTFckA2SGjhNmiKzfK/u4ot3v0DUzGVdUnKjN10EqnnEPck36BkyfLnJw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + + '@inquirer/type@4.0.7': + resolution: {integrity: sha512-t28inv14nMQ1PhKpsJPY+kEs/c00qzeCOS2gTNRyTjG5d6qsVA2fItxW4hkvGZ5lvanGLdtCzVIx5dwdRpN1+g==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -505,6 +571,10 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@mswjs/interceptors@0.41.9': + resolution: {integrity: sha512-VVPPgHyQ6ShqnrmDWuxjmUIsO9gWyOZFmuOfLd9LfBGQJwZfy0gvv9pbHSJuoFNIYC7ZDX9aoFwowjcdSC4E8w==} + engines: {node: '>=18'} + '@napi-rs/wasm-runtime@1.1.4': resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} peerDependencies: @@ -581,6 +651,18 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + '@open-draft/deferred-promise@2.2.0': + resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} + + '@open-draft/deferred-promise@3.0.0': + resolution: {integrity: sha512-XW375UK8/9SqUVNVa6M0yEy8+iTi4QN5VZ7aZuRFQmy76LRwI9wy5F4YIBU6T+eTe2/DNDo8tqu8RHlwLHM6RA==} + + '@open-draft/logger@0.3.0': + resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} + + '@open-draft/until@2.1.0': + resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + '@panva/hkdf@1.2.1': resolution: {integrity: sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==} @@ -724,6 +806,94 @@ packages: '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + '@tailwindcss/node@4.3.0': + resolution: {integrity: sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==} + + '@tailwindcss/oxide-android-arm64@4.3.0': + resolution: {integrity: sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.3.0': + resolution: {integrity: sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.3.0': + resolution: {integrity: sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==} + engines: {node: '>= 20'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.3.0': + resolution: {integrity: sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==} + engines: {node: '>= 20'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0': + resolution: {integrity: sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==} + engines: {node: '>= 20'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.3.0': + resolution: {integrity: sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.3.0': + resolution: {integrity: sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.3.0': + resolution: {integrity: sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.3.0': + resolution: {integrity: sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-wasm32-wasi@4.3.0': + resolution: {integrity: sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.3.0': + resolution: {integrity: sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.3.0': + resolution: {integrity: sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==} + engines: {node: '>= 20'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.3.0': + resolution: {integrity: sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==} + engines: {node: '>= 20'} + + '@tailwindcss/postcss@4.3.0': + resolution: {integrity: sha512-Jm05Tjx+9yCLGv5qw1c+84Psds8MnyrEQYCB+FFk2lgGiUjlRqdxke4mVTuYrj2xnVZqKim2Apr5ySuQRYAw/w==} + '@tybys/wasm-util@0.10.2': resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} @@ -751,6 +921,12 @@ packages: '@types/react@19.0.1': resolution: {integrity: sha512-YW6614BDhqbpR5KtUYzTA+zlA7nayzJRA9ljz9CQoxthR0sDisYZLuvSMsil36t4EH/uAt8T52Xb4sVw17G+SQ==} + '@types/set-cookie-parser@2.4.10': + resolution: {integrity: sha512-GGmQVGpQWUe5qglJozEjZV/5dyxbOOZ0LHe/lqyWssB88Y4svNfst0uqBVscdDeIKl5Jy5+aPSvy7mI9tYRguw==} + + '@types/statuses@2.0.6': + resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} + '@typescript-eslint/eslint-plugin@8.59.4': resolution: {integrity: sha512-PegsU+XfyJJNjd4+u/k6f9yTyp0lEXXiPopUNobZcIAUJFGICFLN+sP0Rb3JehVmiij1Ph0dFGYqODoRo/2+6A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1108,9 +1284,24 @@ packages: resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} engines: {node: '>= 16'} + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -1128,6 +1319,10 @@ packages: resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} engines: {node: '>= 0.6'} + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -1206,6 +1401,10 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + enhanced-resolve@5.22.2: + resolution: {integrity: sha512-0rxICaFZ7NQho/sHely2bvOPRP0Eu2B0NZ9zM54YvRvWMn7jfz3DmnOZDR9LlXDdDcqntAVc6Hfy4gr/tdH/Ag==} + engines: {node: '>=10.13.0'} + es-abstract@1.24.2: resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==} engines: {node: '>= 0.4'} @@ -1394,6 +1593,15 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-string-truncated-width@3.0.3: + resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} + + fast-string-width@3.0.2: + resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + + fast-wrap-ansi@0.2.2: + resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} + fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} @@ -1453,6 +1661,11 @@ packages: functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + geist@1.7.2: + resolution: {integrity: sha512-Gu5lDFa3pLRyoBlBPf0QIFHVdWAnpco7fS1bJm41jyLPFoguBgiubseUN2oLXMgqZ7uxAxDoXcHMhCY/fOTTgg==} + peerDependencies: + next: '>=13.2.0' + generator-function@2.0.1: resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} engines: {node: '>= 0.4'} @@ -1461,6 +1674,10 @@ packages: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -1505,6 +1722,13 @@ packages: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphql@16.14.1: + resolution: {integrity: sha512-cQOsSMS/IrDz82PVyRDvf/Q1F/bRbBVjJlh+xYOkI1qw2bWRvWGiWc+m2O0d6l4Bt1fyY+8kzJ8JFWGJqNeDBg==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + has-bigints@1.1.0: resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} engines: {node: '>= 0.4'} @@ -1532,6 +1756,9 @@ packages: resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} engines: {node: '>= 0.4'} + headers-polyfill@5.0.1: + resolution: {integrity: sha512-1TJ6Fih/b8h5TIcv+1+Hw0PDQWJTKDKzFZzcKOiW1wJza3XoAQlkCuXLbymPYB8+ZQyw8mHvdw560e8zVFIWyA==} + hermes-estree@0.25.1: resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} @@ -1624,6 +1851,9 @@ packages: resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} engines: {node: '>= 0.4'} + is-node-process@1.2.0: + resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} + is-number-object@1.1.1: resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} engines: {node: '>= 0.4'} @@ -1697,6 +1927,10 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + jose@5.10.0: resolution: {integrity: sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==} @@ -1748,6 +1982,76 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -1768,6 +2072,11 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lucide-react@1.17.0: + resolution: {integrity: sha512-9FA9evdox/JQL5PT57fdA1x/yg8T7knJ98+zjTL3UfKza6pflQUUh3XtaQIHKvnsJw1lmsEyHVlt5jchYxOQ5w==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -1811,6 +2120,20 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + msw@2.14.6: + resolution: {integrity: sha512-ALe+N10S72cyx94cMcy3Zs4HhXCj35sgeAL4c+WTvKi0zWnbd8/h0lcFqv0mb2P+aSgAdD7p9HzvA0DiUPxsyg==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + typescript: '>= 4.8.x' + peerDependenciesMeta: + typescript: + optional: true + + mute-stream@3.0.0: + resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} + engines: {node: ^20.17.0 || >=22.9.0} + nanoid@3.3.12: resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -1907,6 +2230,9 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + outvariant@1.4.3: + resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + own-keys@1.0.1: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} @@ -1941,6 +2267,9 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} @@ -1977,8 +2306,8 @@ packages: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} - postcss@8.5.14: - resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==} + postcss@8.5.15: + resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} engines: {node: ^10 || ^12 || >=14} preact-render-to-string@5.2.3: @@ -2026,6 +2355,10 @@ packages: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -2038,6 +2371,9 @@ packages: engines: {node: '>= 0.4'} hasBin: true + rettime@0.11.11: + resolution: {integrity: sha512-ILJRqVWBCTlg9r42fFgwVZx1gnFAcQF8mRoMkbgQfIrjEDf9nbBFDFx00oloOa+Q869FUtaYDXZvEfnecQSCoQ==} + reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -2074,6 +2410,9 @@ packages: engines: {node: '>=10'} hasBin: true + set-cookie-parser@3.1.0: + resolution: {integrity: sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -2131,6 +2470,10 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} @@ -2138,6 +2481,9 @@ packages: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} + strict-event-emitter@0.5.1: + resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -2206,6 +2552,20 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + tagged-tag@1.0.0: + resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} + engines: {node: '>=20'} + + tailwind-merge@3.6.0: + resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==} + + tailwindcss@4.3.0: + resolution: {integrity: sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==} + + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} + engines: {node: '>=6'} + test-exclude@7.0.2: resolution: {integrity: sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==} engines: {node: '>=18'} @@ -2232,10 +2592,21 @@ packages: resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} engines: {node: '>=14.0.0'} + tldts-core@7.4.2: + resolution: {integrity: sha512-nwEyF4vl4RSJjwSjBUmOSxc3BFPoIFdlRthJ6e+5v9P3bHNsoD06UjuqMUspqp7vsEZ1beaHi1km+optiE17yA==} + + tldts@7.4.2: + resolution: {integrity: sha512-kCwffuaH8ntKtygnWe1b4BJKWiCUH30n5KfoTr6IchcXOwR7chAOFJxFrH3vjANafUYrIA4a7SDL+nn7SiR4Sw==} + hasBin: true + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + tough-cookie@6.0.1: + resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} + engines: {node: '>=16'} + ts-api-utils@2.5.0: resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} engines: {node: '>=18.12'} @@ -2252,6 +2623,10 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + type-fest@5.7.0: + resolution: {integrity: sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg==} + engines: {node: '>=20'} + typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -2290,6 +2665,9 @@ packages: unrs-resolver@1.12.0: resolution: {integrity: sha512-hiJjN9x3O/SF2yGpNX7swfg24bi4t+uqEww16EeH9LT2I6mkGNf8uZvAS9PL1pvkA/fBagTaxbgHfYQPRfahuQ==} + until-async@3.0.2: + resolution: {integrity: sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==} + update-browserslist-db@1.2.3: resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true @@ -2398,9 +2776,21 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -2416,6 +2806,8 @@ packages: snapshots: + '@alloc/quick-lru@5.2.0': {} + '@ampproject/remapping@2.3.0': dependencies: '@jridgewell/gen-mapping': 0.3.13 @@ -2618,9 +3010,9 @@ snapshots: '@esbuild/win32-x64@0.21.5': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@9.15.0)': + '@eslint-community/eslint-utils@4.9.1(eslint@9.15.0(jiti@2.7.0))': dependencies: - eslint: 9.15.0 + eslint: 9.15.0(jiti@2.7.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -2777,6 +3169,33 @@ snapshots: '@img/sharp-win32-x64@0.34.5': optional: true + '@inquirer/ansi@2.0.7': {} + + '@inquirer/confirm@6.1.1(@types/node@20.16.10)': + dependencies: + '@inquirer/core': 11.2.1(@types/node@20.16.10) + '@inquirer/type': 4.0.7(@types/node@20.16.10) + optionalDependencies: + '@types/node': 20.16.10 + + '@inquirer/core@11.2.1(@types/node@20.16.10)': + dependencies: + '@inquirer/ansi': 2.0.7 + '@inquirer/figures': 2.0.7 + '@inquirer/type': 4.0.7(@types/node@20.16.10) + cli-width: 4.1.0 + fast-wrap-ansi: 0.2.2 + mute-stream: 3.0.0 + signal-exit: 4.1.0 + optionalDependencies: + '@types/node': 20.16.10 + + '@inquirer/figures@2.0.7': {} + + '@inquirer/type@4.0.7(@types/node@20.16.10)': + optionalDependencies: + '@types/node': 20.16.10 + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -2807,6 +3226,15 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@mswjs/interceptors@0.41.9': + dependencies: + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/logger': 0.3.0 + '@open-draft/until': 2.1.0 + is-node-process: 1.2.0 + outvariant: 1.4.3 + strict-event-emitter: 0.5.1 + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: '@emnapi/core': 1.10.0 @@ -2858,6 +3286,17 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} + '@open-draft/deferred-promise@2.2.0': {} + + '@open-draft/deferred-promise@3.0.0': {} + + '@open-draft/logger@0.3.0': + dependencies: + is-node-process: 1.2.0 + outvariant: 1.4.3 + + '@open-draft/until@2.1.0': {} + '@panva/hkdf@1.2.1': {} '@pkgjs/parseargs@0.11.0': @@ -2948,6 +3387,75 @@ snapshots: dependencies: tslib: 2.8.1 + '@tailwindcss/node@4.3.0': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.22.2 + jiti: 2.7.0 + lightningcss: 1.32.0 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.3.0 + + '@tailwindcss/oxide-android-arm64@4.3.0': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.3.0': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.3.0': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.3.0': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.3.0': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.3.0': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.3.0': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.3.0': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.3.0': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.3.0': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.3.0': + optional: true + + '@tailwindcss/oxide@4.3.0': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.3.0 + '@tailwindcss/oxide-darwin-arm64': 4.3.0 + '@tailwindcss/oxide-darwin-x64': 4.3.0 + '@tailwindcss/oxide-freebsd-x64': 4.3.0 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.0 + '@tailwindcss/oxide-linux-arm64-gnu': 4.3.0 + '@tailwindcss/oxide-linux-arm64-musl': 4.3.0 + '@tailwindcss/oxide-linux-x64-gnu': 4.3.0 + '@tailwindcss/oxide-linux-x64-musl': 4.3.0 + '@tailwindcss/oxide-wasm32-wasi': 4.3.0 + '@tailwindcss/oxide-win32-arm64-msvc': 4.3.0 + '@tailwindcss/oxide-win32-x64-msvc': 4.3.0 + + '@tailwindcss/postcss@4.3.0': + dependencies: + '@alloc/quick-lru': 5.2.0 + '@tailwindcss/node': 4.3.0 + '@tailwindcss/oxide': 4.3.0 + postcss: 8.5.15 + tailwindcss: 4.3.0 + '@tybys/wasm-util@0.10.2': dependencies: tslib: 2.8.1 @@ -2975,15 +3483,21 @@ snapshots: dependencies: csstype: 3.2.3 - '@typescript-eslint/eslint-plugin@8.59.4(@typescript-eslint/parser@8.59.4(eslint@9.15.0)(typescript@5.7.2))(eslint@9.15.0)(typescript@5.7.2)': + '@types/set-cookie-parser@2.4.10': + dependencies: + '@types/node': 20.16.10 + + '@types/statuses@2.0.6': {} + + '@typescript-eslint/eslint-plugin@8.59.4(@typescript-eslint/parser@8.59.4(eslint@9.15.0(jiti@2.7.0))(typescript@5.7.2))(eslint@9.15.0(jiti@2.7.0))(typescript@5.7.2)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.59.4(eslint@9.15.0)(typescript@5.7.2) + '@typescript-eslint/parser': 8.59.4(eslint@9.15.0(jiti@2.7.0))(typescript@5.7.2) '@typescript-eslint/scope-manager': 8.59.4 - '@typescript-eslint/type-utils': 8.59.4(eslint@9.15.0)(typescript@5.7.2) - '@typescript-eslint/utils': 8.59.4(eslint@9.15.0)(typescript@5.7.2) + '@typescript-eslint/type-utils': 8.59.4(eslint@9.15.0(jiti@2.7.0))(typescript@5.7.2) + '@typescript-eslint/utils': 8.59.4(eslint@9.15.0(jiti@2.7.0))(typescript@5.7.2) '@typescript-eslint/visitor-keys': 8.59.4 - eslint: 9.15.0 + eslint: 9.15.0(jiti@2.7.0) ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@5.7.2) @@ -2991,14 +3505,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.59.4(eslint@9.15.0)(typescript@5.7.2)': + '@typescript-eslint/parser@8.59.4(eslint@9.15.0(jiti@2.7.0))(typescript@5.7.2)': dependencies: '@typescript-eslint/scope-manager': 8.59.4 '@typescript-eslint/types': 8.59.4 '@typescript-eslint/typescript-estree': 8.59.4(typescript@5.7.2) '@typescript-eslint/visitor-keys': 8.59.4 debug: 4.4.3 - eslint: 9.15.0 + eslint: 9.15.0(jiti@2.7.0) typescript: 5.7.2 transitivePeerDependencies: - supports-color @@ -3021,13 +3535,13 @@ snapshots: dependencies: typescript: 5.7.2 - '@typescript-eslint/type-utils@8.59.4(eslint@9.15.0)(typescript@5.7.2)': + '@typescript-eslint/type-utils@8.59.4(eslint@9.15.0(jiti@2.7.0))(typescript@5.7.2)': dependencies: '@typescript-eslint/types': 8.59.4 '@typescript-eslint/typescript-estree': 8.59.4(typescript@5.7.2) - '@typescript-eslint/utils': 8.59.4(eslint@9.15.0)(typescript@5.7.2) + '@typescript-eslint/utils': 8.59.4(eslint@9.15.0(jiti@2.7.0))(typescript@5.7.2) debug: 4.4.3 - eslint: 9.15.0 + eslint: 9.15.0(jiti@2.7.0) ts-api-utils: 2.5.0(typescript@5.7.2) typescript: 5.7.2 transitivePeerDependencies: @@ -3050,13 +3564,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.59.4(eslint@9.15.0)(typescript@5.7.2)': + '@typescript-eslint/utils@8.59.4(eslint@9.15.0(jiti@2.7.0))(typescript@5.7.2)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.15.0) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.15.0(jiti@2.7.0)) '@typescript-eslint/scope-manager': 8.59.4 '@typescript-eslint/types': 8.59.4 '@typescript-eslint/typescript-estree': 8.59.4(typescript@5.7.2) - eslint: 9.15.0 + eslint: 9.15.0(jiti@2.7.0) typescript: 5.7.2 transitivePeerDependencies: - supports-color @@ -3130,7 +3644,7 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.12.0': optional: true - '@vitest/coverage-v8@2.1.8(vitest@2.1.8(@types/node@20.16.10))': + '@vitest/coverage-v8@2.1.8(vitest@2.1.8(@types/node@20.16.10)(lightningcss@1.32.0)(msw@2.14.6(@types/node@20.16.10)(typescript@5.7.2)))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 0.2.3 @@ -3144,7 +3658,7 @@ snapshots: std-env: 3.10.0 test-exclude: 7.0.2 tinyrainbow: 1.2.0 - vitest: 2.1.8(@types/node@20.16.10) + vitest: 2.1.8(@types/node@20.16.10)(lightningcss@1.32.0)(msw@2.14.6(@types/node@20.16.10)(typescript@5.7.2)) transitivePeerDependencies: - supports-color @@ -3155,13 +3669,14 @@ snapshots: chai: 5.3.3 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.8(vite@5.4.21(@types/node@20.16.10))': + '@vitest/mocker@2.1.8(msw@2.14.6(@types/node@20.16.10)(typescript@5.7.2))(vite@5.4.21(@types/node@20.16.10)(lightningcss@1.32.0))': dependencies: '@vitest/spy': 2.1.8 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 5.4.21(@types/node@20.16.10) + msw: 2.14.6(@types/node@20.16.10)(typescript@5.7.2) + vite: 5.4.21(@types/node@20.16.10)(lightningcss@1.32.0) '@vitest/pretty-format@2.1.8': dependencies: @@ -3369,8 +3884,22 @@ snapshots: check-error@2.1.3: {} + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + + cli-width@4.1.0: {} + client-only@0.0.1: {} + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clsx@2.1.1: {} + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -3383,6 +3912,8 @@ snapshots: cookie@0.7.1: {} + cookie@1.1.1: {} + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -3435,8 +3966,7 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 - detect-libc@2.1.2: - optional: true + detect-libc@2.1.2: {} doctrine@2.1.0: dependencies: @@ -3456,6 +3986,11 @@ snapshots: emoji-regex@9.2.2: {} + enhanced-resolve@5.22.2: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.3 + es-abstract@1.24.2: dependencies: array-buffer-byte-length: 1.0.2 @@ -3589,18 +4124,18 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-next@16.2.6(@typescript-eslint/parser@8.59.4(eslint@9.15.0)(typescript@5.7.2))(eslint@9.15.0)(typescript@5.7.2): + eslint-config-next@16.2.6(@typescript-eslint/parser@8.59.4(eslint@9.15.0(jiti@2.7.0))(typescript@5.7.2))(eslint@9.15.0(jiti@2.7.0))(typescript@5.7.2): dependencies: '@next/eslint-plugin-next': 16.2.6 - eslint: 9.15.0 + eslint: 9.15.0(jiti@2.7.0) eslint-import-resolver-node: 0.3.10 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.4(eslint@9.15.0)(typescript@5.7.2))(eslint@9.15.0))(eslint@9.15.0) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.4(eslint@9.15.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.4(eslint@9.15.0)(typescript@5.7.2))(eslint@9.15.0))(eslint@9.15.0))(eslint@9.15.0) - eslint-plugin-jsx-a11y: 6.10.2(eslint@9.15.0) - eslint-plugin-react: 7.37.5(eslint@9.15.0) - eslint-plugin-react-hooks: 7.1.1(eslint@9.15.0) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.15.0(jiti@2.7.0)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.4(eslint@9.15.0(jiti@2.7.0))(typescript@5.7.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.15.0(jiti@2.7.0)) + eslint-plugin-jsx-a11y: 6.10.2(eslint@9.15.0(jiti@2.7.0)) + eslint-plugin-react: 7.37.5(eslint@9.15.0(jiti@2.7.0)) + eslint-plugin-react-hooks: 7.1.1(eslint@9.15.0(jiti@2.7.0)) globals: 16.4.0 - typescript-eslint: 8.59.4(eslint@9.15.0)(typescript@5.7.2) + typescript-eslint: 8.59.4(eslint@9.15.0(jiti@2.7.0))(typescript@5.7.2) optionalDependencies: typescript: 5.7.2 transitivePeerDependencies: @@ -3617,33 +4152,33 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.4(eslint@9.15.0)(typescript@5.7.2))(eslint@9.15.0))(eslint@9.15.0): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.15.0(jiti@2.7.0)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.3 - eslint: 9.15.0 + eslint: 9.15.0(jiti@2.7.0) get-tsconfig: 4.14.0 is-bun-module: 2.0.0 stable-hash: 0.0.5 tinyglobby: 0.2.16 unrs-resolver: 1.12.0 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.4(eslint@9.15.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.4(eslint@9.15.0)(typescript@5.7.2))(eslint@9.15.0))(eslint@9.15.0))(eslint@9.15.0) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.4(eslint@9.15.0(jiti@2.7.0))(typescript@5.7.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.15.0(jiti@2.7.0)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.59.4(eslint@9.15.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.4(eslint@9.15.0)(typescript@5.7.2))(eslint@9.15.0))(eslint@9.15.0))(eslint@9.15.0): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.59.4(eslint@9.15.0(jiti@2.7.0))(typescript@5.7.2))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.15.0(jiti@2.7.0)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.59.4(eslint@9.15.0)(typescript@5.7.2) - eslint: 9.15.0 + '@typescript-eslint/parser': 8.59.4(eslint@9.15.0(jiti@2.7.0))(typescript@5.7.2) + eslint: 9.15.0(jiti@2.7.0) eslint-import-resolver-node: 0.3.10 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.4(eslint@9.15.0)(typescript@5.7.2))(eslint@9.15.0))(eslint@9.15.0) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.15.0(jiti@2.7.0)) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.4(eslint@9.15.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.4(eslint@9.15.0)(typescript@5.7.2))(eslint@9.15.0))(eslint@9.15.0))(eslint@9.15.0): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.4(eslint@9.15.0(jiti@2.7.0))(typescript@5.7.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.15.0(jiti@2.7.0)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -3652,9 +4187,9 @@ snapshots: array.prototype.flatmap: 1.3.3 debug: 3.2.7 doctrine: 2.1.0 - eslint: 9.15.0 + eslint: 9.15.0(jiti@2.7.0) eslint-import-resolver-node: 0.3.10 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.59.4(eslint@9.15.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.4(eslint@9.15.0)(typescript@5.7.2))(eslint@9.15.0))(eslint@9.15.0))(eslint@9.15.0) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.59.4(eslint@9.15.0(jiti@2.7.0))(typescript@5.7.2))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.15.0(jiti@2.7.0)) hasown: 2.0.3 is-core-module: 2.16.2 is-glob: 4.0.3 @@ -3666,13 +4201,13 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.59.4(eslint@9.15.0)(typescript@5.7.2) + '@typescript-eslint/parser': 8.59.4(eslint@9.15.0(jiti@2.7.0))(typescript@5.7.2) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-jsx-a11y@6.10.2(eslint@9.15.0): + eslint-plugin-jsx-a11y@6.10.2(eslint@9.15.0(jiti@2.7.0)): dependencies: aria-query: 5.3.2 array-includes: 3.1.9 @@ -3682,7 +4217,7 @@ snapshots: axobject-query: 4.1.0 damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 - eslint: 9.15.0 + eslint: 9.15.0(jiti@2.7.0) hasown: 2.0.3 jsx-ast-utils: 3.3.5 language-tags: 1.0.9 @@ -3691,18 +4226,18 @@ snapshots: safe-regex-test: 1.1.0 string.prototype.includes: 2.0.1 - eslint-plugin-react-hooks@7.1.1(eslint@9.15.0): + eslint-plugin-react-hooks@7.1.1(eslint@9.15.0(jiti@2.7.0)): dependencies: '@babel/core': 7.29.0 '@babel/parser': 7.29.3 - eslint: 9.15.0 + eslint: 9.15.0(jiti@2.7.0) hermes-parser: 0.25.1 zod: 4.4.3 zod-validation-error: 4.0.2(zod@4.4.3) transitivePeerDependencies: - supports-color - eslint-plugin-react@7.37.5(eslint@9.15.0): + eslint-plugin-react@7.37.5(eslint@9.15.0(jiti@2.7.0)): dependencies: array-includes: 3.1.9 array.prototype.findlast: 1.2.5 @@ -3710,7 +4245,7 @@ snapshots: array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 es-iterator-helpers: 1.3.2 - eslint: 9.15.0 + eslint: 9.15.0(jiti@2.7.0) estraverse: 5.3.0 hasown: 2.0.3 jsx-ast-utils: 3.3.5 @@ -3735,9 +4270,9 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@9.15.0: + eslint@9.15.0(jiti@2.7.0): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.15.0) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.15.0(jiti@2.7.0)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.19.2 '@eslint/core': 0.9.1 @@ -3771,6 +4306,8 @@ snapshots: minimatch: 3.1.5 natural-compare: 1.4.0 optionator: 0.9.4 + optionalDependencies: + jiti: 2.7.0 transitivePeerDependencies: - supports-color @@ -3812,6 +4349,16 @@ snapshots: fast-levenshtein@2.0.6: {} + fast-string-truncated-width@3.0.3: {} + + fast-string-width@3.0.2: + dependencies: + fast-string-truncated-width: 3.0.3 + + fast-wrap-ansi@0.2.2: + dependencies: + fast-string-width: 3.0.2 + fastq@1.20.1: dependencies: reusify: 1.1.0 @@ -3868,10 +4415,16 @@ snapshots: functions-have-names@1.2.3: {} + geist@1.7.2(next@16.2.6(@babel/core@7.29.0)(@playwright/test@1.60.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)): + dependencies: + next: 16.2.6(@babel/core@7.29.0)(@playwright/test@1.60.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + generator-function@2.0.1: {} gensync@1.0.0-beta.2: {} + get-caller-file@2.0.5: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -3928,6 +4481,10 @@ snapshots: gopd@1.2.0: {} + graceful-fs@4.2.11: {} + + graphql@16.14.1: {} + has-bigints@1.1.0: {} has-flag@4.0.0: {} @@ -3950,6 +4507,11 @@ snapshots: dependencies: function-bind: 1.1.2 + headers-polyfill@5.0.1: + dependencies: + '@types/set-cookie-parser': 2.4.10 + set-cookie-parser: 3.1.0 + hermes-estree@0.25.1: {} hermes-parser@0.25.1: @@ -4043,6 +4605,8 @@ snapshots: is-negative-zero@2.0.3: {} + is-node-process@1.2.0: {} + is-number-object@1.1.1: dependencies: call-bound: 1.0.4 @@ -4129,6 +4693,8 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 + jiti@2.7.0: {} + jose@5.10.0: {} js-tokens@4.0.0: {} @@ -4173,6 +4739,55 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -4191,6 +4806,10 @@ snapshots: dependencies: yallist: 3.1.1 + lucide-react@1.17.0(react@19.0.0): + dependencies: + react: 19.0.0 + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -4232,6 +4851,33 @@ snapshots: ms@2.1.3: {} + msw@2.14.6(@types/node@20.16.10)(typescript@5.7.2): + dependencies: + '@inquirer/confirm': 6.1.1(@types/node@20.16.10) + '@mswjs/interceptors': 0.41.9 + '@open-draft/deferred-promise': 3.0.0 + '@types/statuses': 2.0.6 + cookie: 1.1.1 + graphql: 16.14.1 + headers-polyfill: 5.0.1 + is-node-process: 1.2.0 + outvariant: 1.4.3 + path-to-regexp: 6.3.0 + picocolors: 1.1.1 + rettime: 0.11.11 + statuses: 2.0.2 + strict-event-emitter: 0.5.1 + tough-cookie: 6.0.1 + type-fest: 5.7.0 + until-async: 3.0.2 + yargs: 17.7.2 + optionalDependencies: + typescript: 5.7.2 + transitivePeerDependencies: + - '@types/node' + + mute-stream@3.0.0: {} + nanoid@3.3.12: {} napi-postinstall@0.3.4: {} @@ -4331,6 +4977,8 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + outvariant@1.4.3: {} + own-keys@1.0.1: dependencies: get-intrinsic: 1.3.0 @@ -4362,6 +5010,8 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.3 + path-to-regexp@6.3.0: {} + pathe@1.1.2: {} pathval@2.0.1: {} @@ -4388,7 +5038,7 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - postcss@8.5.14: + postcss@8.5.15: dependencies: nanoid: 3.3.12 picocolors: 1.1.1 @@ -4444,6 +5094,8 @@ snapshots: gopd: 1.2.0 set-function-name: 2.0.2 + require-directory@2.1.1: {} + resolve-from@4.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -4457,6 +5109,8 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + rettime@0.11.11: {} + reusify@1.1.0: {} rollup@4.60.4: @@ -4519,6 +5173,8 @@ snapshots: semver@7.8.0: {} + set-cookie-parser@3.1.0: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -4617,6 +5273,8 @@ snapshots: stackback@0.0.2: {} + statuses@2.0.2: {} + std-env@3.10.0: {} stop-iteration-iterator@1.1.0: @@ -4624,6 +5282,8 @@ snapshots: es-errors: 1.3.0 internal-slot: 1.1.0 + strict-event-emitter@0.5.1: {} + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -4711,6 +5371,14 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + tagged-tag@1.0.0: {} + + tailwind-merge@3.6.0: {} + + tailwindcss@4.3.0: {} + + tapable@2.3.3: {} + test-exclude@7.0.2: dependencies: '@istanbuljs/schema': 0.1.6 @@ -4732,10 +5400,20 @@ snapshots: tinyspy@3.0.2: {} + tldts-core@7.4.2: {} + + tldts@7.4.2: + dependencies: + tldts-core: 7.4.2 + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 + tough-cookie@6.0.1: + dependencies: + tldts: 7.4.2 + ts-api-utils@2.5.0(typescript@5.7.2): dependencies: typescript: 5.7.2 @@ -4753,6 +5431,10 @@ snapshots: dependencies: prelude-ls: 1.2.1 + type-fest@5.7.0: + dependencies: + tagged-tag: 1.0.0 + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 @@ -4786,13 +5468,13 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript-eslint@8.59.4(eslint@9.15.0)(typescript@5.7.2): + typescript-eslint@8.59.4(eslint@9.15.0(jiti@2.7.0))(typescript@5.7.2): dependencies: - '@typescript-eslint/eslint-plugin': 8.59.4(@typescript-eslint/parser@8.59.4(eslint@9.15.0)(typescript@5.7.2))(eslint@9.15.0)(typescript@5.7.2) - '@typescript-eslint/parser': 8.59.4(eslint@9.15.0)(typescript@5.7.2) + '@typescript-eslint/eslint-plugin': 8.59.4(@typescript-eslint/parser@8.59.4(eslint@9.15.0(jiti@2.7.0))(typescript@5.7.2))(eslint@9.15.0(jiti@2.7.0))(typescript@5.7.2) + '@typescript-eslint/parser': 8.59.4(eslint@9.15.0(jiti@2.7.0))(typescript@5.7.2) '@typescript-eslint/typescript-estree': 8.59.4(typescript@5.7.2) - '@typescript-eslint/utils': 8.59.4(eslint@9.15.0)(typescript@5.7.2) - eslint: 9.15.0 + '@typescript-eslint/utils': 8.59.4(eslint@9.15.0(jiti@2.7.0))(typescript@5.7.2) + eslint: 9.15.0(jiti@2.7.0) typescript: 5.7.2 transitivePeerDependencies: - supports-color @@ -4833,6 +5515,8 @@ snapshots: '@unrs/resolver-binding-win32-ia32-msvc': 1.12.0 '@unrs/resolver-binding-win32-x64-msvc': 1.12.0 + until-async@3.0.2: {} + update-browserslist-db@1.2.3(browserslist@4.28.2): dependencies: browserslist: 4.28.2 @@ -4843,13 +5527,13 @@ snapshots: dependencies: punycode: 2.3.1 - vite-node@2.1.8(@types/node@20.16.10): + vite-node@2.1.8(@types/node@20.16.10)(lightningcss@1.32.0): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 1.1.2 - vite: 5.4.21(@types/node@20.16.10) + vite: 5.4.21(@types/node@20.16.10)(lightningcss@1.32.0) transitivePeerDependencies: - '@types/node' - less @@ -4861,19 +5545,20 @@ snapshots: - supports-color - terser - vite@5.4.21(@types/node@20.16.10): + vite@5.4.21(@types/node@20.16.10)(lightningcss@1.32.0): dependencies: esbuild: 0.21.5 - postcss: 8.5.14 + postcss: 8.5.15 rollup: 4.60.4 optionalDependencies: '@types/node': 20.16.10 fsevents: 2.3.3 + lightningcss: 1.32.0 - vitest@2.1.8(@types/node@20.16.10): + vitest@2.1.8(@types/node@20.16.10)(lightningcss@1.32.0)(msw@2.14.6(@types/node@20.16.10)(typescript@5.7.2)): dependencies: '@vitest/expect': 2.1.8 - '@vitest/mocker': 2.1.8(vite@5.4.21(@types/node@20.16.10)) + '@vitest/mocker': 2.1.8(msw@2.14.6(@types/node@20.16.10)(typescript@5.7.2))(vite@5.4.21(@types/node@20.16.10)(lightningcss@1.32.0)) '@vitest/pretty-format': 2.1.9 '@vitest/runner': 2.1.8 '@vitest/snapshot': 2.1.8 @@ -4889,8 +5574,8 @@ snapshots: tinyexec: 0.3.2 tinypool: 1.1.1 tinyrainbow: 1.2.0 - vite: 5.4.21(@types/node@20.16.10) - vite-node: 2.1.8(@types/node@20.16.10) + vite: 5.4.21(@types/node@20.16.10)(lightningcss@1.32.0) + vite-node: 2.1.8(@types/node@20.16.10)(lightningcss@1.32.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 20.16.10 @@ -4969,8 +5654,22 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.2.0 + y18n@5.0.8: {} + yallist@3.1.1: {} + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + yocto-queue@0.1.0: {} zod-validation-error@4.0.2(zod@4.4.3): diff --git a/postcss.config.mjs b/postcss.config.mjs new file mode 100644 index 0000000..61e3684 --- /dev/null +++ b/postcss.config.mjs @@ -0,0 +1,7 @@ +const config = { + plugins: { + "@tailwindcss/postcss": {}, + }, +}; + +export default config; diff --git a/public/mockServiceWorker.js b/public/mockServiceWorker.js new file mode 100644 index 0000000..33dde9e --- /dev/null +++ b/public/mockServiceWorker.js @@ -0,0 +1,349 @@ +/* eslint-disable */ +/* tslint:disable */ + +/** + * Mock Service Worker. + * @see https://github.com/mswjs/msw + * - Please do NOT modify this file. + */ + +const PACKAGE_VERSION = '2.14.6' +const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82' +const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') +const activeClientIds = new Set() + +addEventListener('install', function () { + self.skipWaiting() +}) + +addEventListener('activate', function (event) { + event.waitUntil(self.clients.claim()) +}) + +addEventListener('message', async function (event) { + const clientId = Reflect.get(event.source || {}, 'id') + + if (!clientId || !self.clients) { + return + } + + const client = await self.clients.get(clientId) + + if (!client) { + return + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + switch (event.data) { + case 'KEEPALIVE_REQUEST': { + sendToClient(client, { + type: 'KEEPALIVE_RESPONSE', + }) + break + } + + case 'INTEGRITY_CHECK_REQUEST': { + sendToClient(client, { + type: 'INTEGRITY_CHECK_RESPONSE', + payload: { + packageVersion: PACKAGE_VERSION, + checksum: INTEGRITY_CHECKSUM, + }, + }) + break + } + + case 'MOCK_ACTIVATE': { + activeClientIds.add(clientId) + + sendToClient(client, { + type: 'MOCKING_ENABLED', + payload: { + client: { + id: client.id, + frameType: client.frameType, + }, + }, + }) + break + } + + case 'CLIENT_CLOSED': { + activeClientIds.delete(clientId) + + const remainingClients = allClients.filter((client) => { + return client.id !== clientId + }) + + // Unregister itself when there are no more clients + if (remainingClients.length === 0) { + self.registration.unregister() + } + + break + } + } +}) + +addEventListener('fetch', function (event) { + const requestInterceptedAt = Date.now() + + // Bypass navigation requests. + if (event.request.mode === 'navigate') { + return + } + + // Opening the DevTools triggers the "only-if-cached" request + // that cannot be handled by the worker. Bypass such requests. + if ( + event.request.cache === 'only-if-cached' && + event.request.mode !== 'same-origin' + ) { + return + } + + // Bypass all requests when there are no active clients. + // Prevents the self-unregistered worked from handling requests + // after it's been terminated (still remains active until the next reload). + if (activeClientIds.size === 0) { + return + } + + const requestId = crypto.randomUUID() + event.respondWith(handleRequest(event, requestId, requestInterceptedAt)) +}) + +/** + * @param {FetchEvent} event + * @param {string} requestId + * @param {number} requestInterceptedAt + */ +async function handleRequest(event, requestId, requestInterceptedAt) { + const client = await resolveMainClient(event) + const requestCloneForEvents = event.request.clone() + const response = await getResponse( + event, + client, + requestId, + requestInterceptedAt, + ) + + // Send back the response clone for the "response:*" life-cycle events. + // Ensure MSW is active and ready to handle the message, otherwise + // this message will pend indefinitely. + if (client && activeClientIds.has(client.id)) { + const serializedRequest = await serializeRequest(requestCloneForEvents) + + // Clone the response so both the client and the library could consume it. + const responseClone = response.clone() + + sendToClient( + client, + { + type: 'RESPONSE', + payload: { + isMockedResponse: IS_MOCKED_RESPONSE in response, + request: { + id: requestId, + ...serializedRequest, + }, + response: { + type: responseClone.type, + status: responseClone.status, + statusText: responseClone.statusText, + headers: Object.fromEntries(responseClone.headers.entries()), + body: responseClone.body, + }, + }, + }, + responseClone.body ? [serializedRequest.body, responseClone.body] : [], + ) + } + + return response +} + +/** + * Resolve the main client for the given event. + * Client that issues a request doesn't necessarily equal the client + * that registered the worker. It's with the latter the worker should + * communicate with during the response resolving phase. + * @param {FetchEvent} event + * @returns {Promise} + */ +async function resolveMainClient(event) { + const client = await self.clients.get(event.clientId) + + if (activeClientIds.has(event.clientId)) { + return client + } + + if (client?.frameType === 'top-level') { + return client + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + return allClients + .filter((client) => { + // Get only those clients that are currently visible. + return client.visibilityState === 'visible' + }) + .find((client) => { + // Find the client ID that's recorded in the + // set of clients that have registered the worker. + return activeClientIds.has(client.id) + }) +} + +/** + * @param {FetchEvent} event + * @param {Client | undefined} client + * @param {string} requestId + * @param {number} requestInterceptedAt + * @returns {Promise} + */ +async function getResponse(event, client, requestId, requestInterceptedAt) { + // Clone the request because it might've been already used + // (i.e. its body has been read and sent to the client). + const requestClone = event.request.clone() + + function passthrough() { + // Cast the request headers to a new Headers instance + // so the headers can be manipulated with. + const headers = new Headers(requestClone.headers) + + // Remove the "accept" header value that marked this request as passthrough. + // This prevents request alteration and also keeps it compliant with the + // user-defined CORS policies. + const acceptHeader = headers.get('accept') + if (acceptHeader) { + const values = acceptHeader.split(',').map((value) => value.trim()) + const filteredValues = values.filter( + (value) => value !== 'msw/passthrough', + ) + + if (filteredValues.length > 0) { + headers.set('accept', filteredValues.join(', ')) + } else { + headers.delete('accept') + } + } + + return fetch(requestClone, { headers }) + } + + // Bypass mocking when the client is not active. + if (!client) { + return passthrough() + } + + // Bypass initial page load requests (i.e. static assets). + // The absence of the immediate/parent client in the map of the active clients + // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet + // and is not ready to handle requests. + if (!activeClientIds.has(client.id)) { + return passthrough() + } + + // Notify the client that a request has been intercepted. + const serializedRequest = await serializeRequest(event.request) + const clientMessage = await sendToClient( + client, + { + type: 'REQUEST', + payload: { + id: requestId, + interceptedAt: requestInterceptedAt, + ...serializedRequest, + }, + }, + [serializedRequest.body], + ) + + switch (clientMessage.type) { + case 'MOCK_RESPONSE': { + return respondWithMock(clientMessage.data) + } + + case 'PASSTHROUGH': { + return passthrough() + } + } + + return passthrough() +} + +/** + * @param {Client} client + * @param {any} message + * @param {Array} transferrables + * @returns {Promise} + */ +function sendToClient(client, message, transferrables = []) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel() + + channel.port1.onmessage = (event) => { + if (event.data && event.data.error) { + return reject(event.data.error) + } + + resolve(event.data) + } + + client.postMessage(message, [ + channel.port2, + ...transferrables.filter(Boolean), + ]) + }) +} + +/** + * @param {Response} response + * @returns {Response} + */ +function respondWithMock(response) { + // Setting response status code to 0 is a no-op. + // However, when responding with a "Response.error()", the produced Response + // instance will have status code set to 0. Since it's not possible to create + // a Response instance with status code 0, handle that use-case separately. + if (response.status === 0) { + return Response.error() + } + + const mockedResponse = new Response(response.body, response) + + Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { + value: true, + enumerable: true, + }) + + return mockedResponse +} + +/** + * @param {Request} request + */ +async function serializeRequest(request) { + return { + url: request.url, + mode: request.mode, + method: request.method, + headers: Object.fromEntries(request.headers.entries()), + cache: request.cache, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body: await request.arrayBuffer(), + keepalive: request.keepalive, + } +} diff --git a/src/app/[slug]/audit/page.tsx b/src/app/[slug]/audit/page.tsx index 12d9d49..83a5a53 100644 --- a/src/app/[slug]/audit/page.tsx +++ b/src/app/[slug]/audit/page.tsx @@ -1,207 +1,101 @@ -import Link from "next/link"; -import { redirect } from "next/navigation"; -import { auth } from "@/auth"; -import { NotAuthorized } from "@/components/ShellEmpty"; -import { formatDateTime, formatRelative, truncate } from "@/lib/format"; -import type { SessionWithExtras } from "@/lib/session"; import { canSee } from "@/lib/session"; -import { fetchAudit, fetchTenantBySlug } from "@/lib/tenant-registry"; +import { getPortalSession } from "@/lib/get-session"; +import { loadTenantForShell } from "@/lib/portal-data"; +import { Panel } from "@/components/portal/Panel"; +import { NotAllowed } from "@/components/portal/NotAllowed"; -const PAGE_SIZE = 50; +const EVENT_FILTERS = ["all", "auth", "scan", "finding", "evidence", "billing", "settings"]; export default async function AuditPage({ params, searchParams, }: { params: Promise<{ slug: string }>; - searchParams: Promise<{ cursor?: string; action?: string; actor_id?: string }>; + searchParams: Promise<{ q?: string; type?: string; product?: string }>; }) { const { slug } = await params; - const q = await searchParams; - const session = (await auth()) as SessionWithExtras | null; - if (!canSee(session, "audit")) return ; + const sp = await searchParams; + const session = await getPortalSession(); + if (!canSee(session, "audit")) return ; + const t = await loadTenantForShell(slug); + if (!t) return null; - const tenant = await fetchTenantBySlug(slug); - if (!tenant) redirect(`/${slug}/dashboard`); + const q = sp.q?.toLowerCase() ?? ""; + const type = sp.type ?? "all"; + const product = sp.product ?? "all"; - const cursor = q.cursor ? Number(q.cursor) : undefined; - const page = await fetchAudit({ - tenant_id: tenant.id, - action: q.action || undefined, - actor_id: q.actor_id || undefined, - limit: PAGE_SIZE, - cursor: cursor && !Number.isNaN(cursor) ? cursor : undefined, + const rows = t.audit.filter((r) => { + if (q && !`${r.event} ${r.actor} ${r.product}`.toLowerCase().includes(q)) return false; + if (type !== "all" && !r.event.startsWith(type)) return false; + if (product !== "all" && r.product !== product) return false; + return true; }); - const nextHref = page.next_cursor - ? buildHref(slug, { ...q, cursor: String(page.next_cursor) }) - : null; - const resetHref = (q.action || q.actor_id || q.cursor) ? `/${slug}/audit` : null; - return ( -
-

Audit log

-

- Every state-changing action emitted by the portal and the products.{" "} - - Retraced-shape schema - {" "} - — CSV / PDF export lands in M10.2. -

- - - - {page.items.length === 0 ? ( -

- No events match the current filter. -

- ) : ( -
- - - - - - - - - - - - - {page.items.map((ev) => ( - - - - - - - - - ))} - -
WhenActionActorTargetProductMeta
- {formatRelative(ev.created_at)} - - {ev.action} - - {ev.actor_name || ev.actor_id || ( - system - )} - - {ev.target_type && ( - {ev.target_type}: - )}{" "} - {ev.target_name || ev.target_id || ( - - )} - - {ev.product || portal} - - {ev.metadata && Object.keys(ev.metadata).length > 0 - ? truncate(JSON.stringify(ev.metadata), 50) - : ""} -
-
- )} - -
+
+
- {resetHref && ( - - ← Clear filters - - )} +
Audit log
+
+ {rows.length} of {t.audit.length}{" "} + events · retention 365 days · hash-chained +
-
- {nextHref && ( - - Next page → - - )} +
+
-
+ + +
+ + + {EVENT_FILTERS.map((f) => ( + + ))} + + + + + + + + + + + + + + + + + + {rows.slice(0, 50).map((r, i) => ( + + + + + + + + + ))} + +
WhenEventActorProductSource IPResult
{r.date} {r.time}{r.event}{r.actor}{r.product}{r.ip} + + + {r.result === "denied" ? "DENIED" : "OK"} + +
+
+ ); } - -function Filters({ - slug, - active, -}: { - slug: string; - active: { action?: string; actor_id?: string }; -}) { - return ( -
- - - -
- ); -} - -function buildHref(slug: string, q: Record): string { - const qs = new URLSearchParams(); - for (const [k, v] of Object.entries(q)) { - if (v) qs.set(k, v); - } - const s = qs.toString(); - return s ? `/${slug}/audit?${s}` : `/${slug}/audit`; -} - -const inputStyle: React.CSSProperties = { - padding: "8px 10px", - border: "1px solid #ddd", - borderRadius: 6, - fontSize: 14, -}; -const btnLink: React.CSSProperties = { - color: "#0070f3", - fontSize: 13, - textDecoration: "none", -}; -const btnSmall: React.CSSProperties = { - padding: "4px 10px", - background: "white", - color: "#0070f3", - border: "1px solid #0070f3", - borderRadius: 4, - fontSize: 12, - cursor: "pointer", -}; -const th: React.CSSProperties = { padding: "8px 10px", color: "#666", fontWeight: 500 }; -const td: React.CSSProperties = { padding: "8px 10px" }; diff --git a/src/app/[slug]/billing/page.tsx b/src/app/[slug]/billing/page.tsx index 21b1f32..887823a 100644 --- a/src/app/[slug]/billing/page.tsx +++ b/src/app/[slug]/billing/page.tsx @@ -1,31 +1,160 @@ -import Link from "next/link"; -import { auth } from "@/auth"; -import { NotAuthorized, ShellEmpty } from "@/components/ShellEmpty"; -import type { SessionWithExtras } from "@/lib/session"; import { canSee } from "@/lib/session"; +import { getPortalSession } from "@/lib/get-session"; +import { loadTenantForShell } from "@/lib/portal-data"; +import { Panel } from "@/components/portal/Panel"; +import { NotAllowed } from "@/components/portal/NotAllowed"; +import { CreditCard, AlertTriangle } from "lucide-react"; -export default async function Page({ +const EUR = new Intl.NumberFormat("de-DE", { style: "currency", currency: "EUR" }); + +export default async function BillingPage({ params, }: { params: Promise<{ slug: string }>; }) { const { slug } = await params; - const session = (await auth()) as SessionWithExtras | null; - if (!canSee(session, "billing")) return ; + const session = await getPortalSession(); + if (!canSee(session, "billing")) return ; + const t = await loadTenantForShell(slug); + if (!t) return null; + + const monthlyVAT = Math.round(t.monthly * 0.19); + const monthlyGross = t.monthly + monthlyVAT; + const seatsPct = t.seats.total > 0 ? (t.seats.used / t.seats.total) * 100 : 0; + const evidenceStorageUsed = t.metrics.evidence; + const evidenceStorageMax = 1000; + const evidencePct = (evidenceStorageUsed / evidenceStorageMax) * 100; + const frozen = t.status === "frozen"; + return ( - - Browse the catalog → - - } - /> +
+
+
+
Billing
+
Plan, usage, payment method and invoice history
+
+
+ +
+
+ +
+
+ +
+
+
PLAN
+
{t.plan}
+
{t.planCode}
+
+
+
MONTHLY
+
+ {EUR.format(t.monthly)} +
+
+ + 19% VAT = {EUR.format(monthlyGross)} +
+
+
+
+
+ SEATS + {t.seats.used} / {t.seats.total} +
+
+
+ EVIDENCE STORAGE + + {evidenceStorageUsed} / {evidenceStorageMax} GB + +
+
90 ? "warn" : ""}`}> + +
+ + + + {frozen ? ( +
+ + Payment failed + + + +
+ ) : ( +
+ + + +
+
SEPA Direct Debit
+
+ DE89 •••• •••• 7421 · {t.contact} +
+
+ + +
+ )} +
+
+ +
+ +
+
Renews on
+
+ {t.renewal} +
+
Billing email
{t.contactEmail}
+
Currency
EUR
+
VAT applied
19% (DE)
+
+
+
+
+ + {t.invoices.length} entries} pad={false}> + + + + + + + + + + + + + + + + {t.invoices.map((inv) => ( + + + + + + + + + + + + ))} + +
InvoicePeriodIssuedSeatsNetVATTotalStatus
{inv.id}{inv.period}{inv.issued}{inv.seats}{EUR.format(inv.net)}{EUR.format(inv.vat)}{EUR.format(inv.total)} + + + {inv.status === "due" ? "Due" : "Paid"} + + + +
+
+
); } diff --git a/src/app/[slug]/dashboard/page.tsx b/src/app/[slug]/dashboard/page.tsx index 59e11b1..9b4330b 100644 --- a/src/app/[slug]/dashboard/page.tsx +++ b/src/app/[slug]/dashboard/page.tsx @@ -1,7 +1,24 @@ -import { auth, signIn, signOut } from "@/auth"; -import { ShellEmpty } from "@/components/ShellEmpty"; -import type { SessionWithExtras } from "@/lib/session"; -import { fetchTenantBySlug } from "@/lib/tenant-registry"; +import Link from "next/link"; +import { ArrowRight, Download, Play, ShieldAlert } from "lucide-react"; +import { signIn } from "@/auth"; +import { getPortalSession } from "@/lib/get-session"; +import { loadTenantForShell } from "@/lib/portal-data"; +import { Panel } from "@/components/portal/Panel"; +import { Monogram } from "@/components/portal/Monogram"; +import { Sev } from "@/components/portal/Sev"; +import { Sparkbars } from "@/components/portal/charts/Sparkbars"; +import { Sparkline } from "@/components/portal/charts/Sparkline"; +import { Ring } from "@/components/portal/charts/Ring"; +import { StackBar } from "@/components/portal/charts/StackBar"; +import { Heatmap, HeatLegend } from "@/components/portal/charts/Heatmap"; +import { productById, type Severity } from "@/lib/fixtures"; + +const SEV_LABEL: Record = { + critical: "Critical", + high: "High", + medium: "Medium", + low: "Low", +}; export default async function Dashboard({ params, @@ -9,7 +26,9 @@ export default async function Dashboard({ params: Promise<{ slug: string }>; }) { const { slug } = await params; - const session = (await auth()) as SessionWithExtras | null; + const session = await getPortalSession(); + const tenant = await loadTenantForShell(slug); + if (!tenant) return null; if (!session) { async function login() { @@ -17,156 +36,339 @@ export default async function Dashboard({ await signIn("keycloak", { redirectTo: `/${slug}/dashboard` }); } return ( -
-

Sign in to {slug}

-
- -
-
+
+
+
+
Sign in to {tenant.name}
+
+ Authenticate via Keycloak to view the {tenant.short} control plane. +
+
+
+ +
+ +
+
+
); } - async function logout() { - "use server"; - await signOut({ redirectTo: `/${slug}/dashboard` }); - } - - const tenant = await fetchTenantBySlug(slug); - const products = session.products ?? []; - const trialDaysLeft = computeTrialDaysLeft(tenant?.trial_ends_at); + const m = tenant.metrics; + const userOnly = + (session.org_roles ?? []).every((r) => r === "USER") || session.org_roles?.length === 0; + const f30 = tenant.series.findings30; + const lastWindow = f30.slice(-14); + const findingsDelta = m.findingsDelta; + const ctrlPct = Math.round((m.controlsPassing / m.controlsTotal) * 100); + const sparkEvidence = tenant.series.evidence30; return ( -
- {tenant?.status === "trial" && tenant.trial_ends_at && ( - - )} +
+
+
+
+ {userOnly ? "Workspace" : "Overview"} +
+
+ {tenant.name} ·{" "} + + {new Date().toISOString().slice(0, 10)} + +
+
+
+ + + + +
+
-

Dashboard

-

- Welcome, {session.user?.name ?? session.user?.email ?? "user"}. Signed in - as {session.org_roles?.join(", ") ?? "(no roles)"}. -

+ {!userOnly ? ( +
+
+
+ Open findings +
+
+ {m.openFindings} + 0 ? "delta-up" : "delta-down"}`} + > + {findingsDelta > 0 ? "+" : ""} + {findingsDelta} · 7d + +
+
+ +
+
-

Your products

- {products.length === 0 ? ( - - ) : ( -
    - {products.map((p) => ( -
  • + Critical open +
    + + {m.critical} + + of {m.openFindings} +
    +
    + +
    +
+ +
+ Controls passing +
+ +
+ + {ctrlPct} + % + + + {m.controlsPassing} / {m.controlsTotal} + +
+
+
+ +
+ Evidence +
+ {m.evidence} + +{m.resolved7} · 7d +
+
+ +
+
+ +
+ Last scan +
+ + {m.lastScan} + +
+
+ +
+
+
+ ) : null} + +
+ {!userOnly ? ( +
+ + {f30.length}d window + + } + pad={false} + bracket + > +
+ +
+
+
+ +
+ {(["critical", "high", "medium", "low"] as Severity[]).map((k) => ( + + + {SEV_LABEL[k]} + {m.severity[k]} + + ))} +
+
+ + + + View all + + } + pad={false} + > + + + + + + + + + + + + {tenant.findings + .filter((f) => f.status === "open") + .slice(0, 5) + .map((f) => ( + + + + + + + + ))} + +
SevIDTitleControlAge
+ + {f.id} + {f.title} + {f.control}{f.ageDays}d
+
+
+ ) : null} + +
+ + {tenant.products + .filter( + (p) => tenant.entitled.includes(p.id) || tenant.trialing.includes(p.id), + ) + .map((p) => { + const arr = tenant.series.prodSeries[p.id] ?? []; + const open = tenant.findings.filter( + (f) => f.product === p.id && f.status === "open", + ).length; + return ( + + +
+ {p.name} + {p.slug} +
+
+ +
+
+ {open} + open +
+ + ); + })} +
+ + + 5 weeks + + } + > +
+ +
+
- {p} -

- Tile content lands in M10.1. -

- - ))} - - )} + +
+
-
- -
-
- ); -} - -// Pure compute, lives outside any render path so react-hooks/purity is satisfied. -function computeTrialDaysLeft(endsAt: string | null | undefined): number { - if (!endsAt) return 0; - const ms = new Date(endsAt).getTime() - Date.now(); - return Math.max(0, Math.ceil(ms / (24 * 3600 * 1000))); -} - -function TrialBanner({ - endsAt, - slug, - daysLeft, -}: { - endsAt: string; - slug: string; - daysLeft: number; -}) { - const ends = new Date(endsAt); - const urgent = daysLeft <= 3; - return ( -
- - Trial — {daysLeft} day{daysLeft === 1 ? "" : "s"} left - {" "}(ends {ends.toLocaleDateString()}). - - - Upgrade → - + +
+ {tenant.activity.slice(0, 5).map((a, i) => { + const prod = productById(a.product); + return ( +
+ {a.when} +
+ {a.actor}{" "} + {a.verb}{" "} + {a.target} +
+ + {prod?.mono ?? a.product.slice(0, 2).toUpperCase()} + +
+ ); + })} +
+
+
+
); } + +// Wraps any write CTA in the frozen-write hovercard guard. On `frozen` +// tenants the button is disabled and a tooltip explains the 402. +function WriteGuarded({ + status, + children, +}: { + status: string; + children: React.ReactNode; +}) { + if (status !== "frozen") return <>{children}; + return ( + + {children} + + + + Tenant is read-only + + HTTP 402 · payment required +
+ + Re-activate to continue → + +
+
+ ); +} diff --git a/src/app/[slug]/layout.tsx b/src/app/[slug]/layout.tsx index c75fb97..04a117b 100644 --- a/src/app/[slug]/layout.tsx +++ b/src/app/[slug]/layout.tsx @@ -1,9 +1,15 @@ import { notFound, redirect } from "next/navigation"; import type { ReactNode } from "react"; -import { auth } from "@/auth"; -import { Nav } from "@/components/Nav"; -import type { SessionWithExtras } from "@/lib/session"; -import { fetchTenantBySlug } from "@/lib/tenant-registry"; +import { getPortalSession } from "@/lib/get-session"; +import { loadTenantForShell } from "@/lib/portal-data"; +import { Lifeline } from "@/components/portal/Lifeline"; +import { NavRail } from "@/components/portal/NavRail"; +import { Topbar } from "@/components/portal/Topbar"; +import { ArchivedLockout } from "@/components/portal/ArchivedLockout"; +import { MockWorker } from "@/components/portal/MockWorker"; +import { ToastHost } from "@/components/portal/ToastHost"; + +const MOCK_API = !!process.env.BP_DEV_FIXTURE; export default async function TenantLayout({ children, @@ -13,10 +19,10 @@ export default async function TenantLayout({ params: Promise<{ slug: string }>; }) { const { slug } = await params; - const tenant = await fetchTenantBySlug(slug); + const tenant = await loadTenantForShell(slug); if (!tenant) notFound(); - const session = (await auth()) as SessionWithExtras | null; + const session = await getPortalSession(); // Tenant mismatch guard — a JWT scoped to tenant A must not be allowed // to view tenant B. If the slug in the path doesn't match the session @@ -25,27 +31,71 @@ export default async function TenantLayout({ redirect(`/${session.tenant_slug}/dashboard`); } - return ( -
- {session ?