diff --git a/.claude/settings.local.json b/.claude/settings.local.json index e71e7a2..31407ba 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -2,7 +2,20 @@ "permissions": { "allow": [ "Bash(docker-compose:*)", - "Bash(docker container prune:*)" + "Bash(docker container prune:*)", + "Bash(npx prisma migrate dev:*)", + "Bash(npx prisma:*)", + "Bash(npm run dev)", + "Bash(timeout:*)", + "Bash(taskkill:*)", + "Bash(npx kill-port:*)", + "Bash(docker compose:*)", + "Bash(curl -I https://fonts.googleapis.com)", + "Bash(wsl:*)", + "Read(//c/Users/a931627/.ssh/**)", + "Bash(ssh-keygen:*)", + "Bash(cat:*)", + "Bash(git remote add:*)" ], "deny": [], "ask": [] diff --git a/docker-compose.yml b/docker-compose.yml index 9ee2a36..35a2d16 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,7 +10,7 @@ services: POSTGRES_DB: qrmaster POSTGRES_INITDB_ARGS: "-E UTF8 --locale=en_US.utf8" ports: - - "5432:5432" + - "5435:5432" volumes: - dbdata:/var/lib/postgresql/data - ./docker/init-db.sh:/docker-entrypoint-initdb.d/init-db.sh diff --git a/package-lock.json b/package-lock.json index 00b17ba..ab05b8d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,13 +11,14 @@ "dependencies": { "@auth/prisma-adapter": "^1.0.12", "@prisma/client": "^5.7.0", + "@stripe/stripe-js": "^8.0.0", "bcryptjs": "^2.4.3", "chart.js": "^4.4.0", "clsx": "^2.0.0", "dayjs": "^1.11.10", "i18next": "^23.7.6", "ioredis": "^5.3.2", - "next": "14.0.4", + "next": "14.2.18", "next-auth": "^4.24.5", "papaparse": "^5.4.1", "qrcode": "^1.5.3", @@ -28,6 +29,7 @@ "react-dropzone": "^14.2.3", "react-i18next": "^13.5.0", "sharp": "^0.33.1", + "stripe": "^19.1.0", "tailwind-merge": "^2.2.0", "xlsx": "^0.18.5", "zod": "^3.22.4" @@ -41,7 +43,7 @@ "@types/react-dom": "^18.2.18", "autoprefixer": "^10.4.16", "eslint": "^8.56.0", - "eslint-config-next": "14.0.4", + "eslint-config-next": "14.2.18", "postcss": "^8.4.32", "prettier": "^3.1.1", "prisma": "^5.7.0", @@ -1216,25 +1218,93 @@ } }, "node_modules/@next/env": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.0.4.tgz", - "integrity": "sha512-irQnbMLbUNQpP1wcE5NstJtbuA/69kRfzBrpAD7Gsn8zm/CY6YQYc3HQBz8QPxwISG26tIm5afvvVbu508oBeQ==", + "version": "14.2.18", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.18.tgz", + "integrity": "sha512-2vWLOUwIPgoqMJKG6dt35fVXVhgM09tw4tK3/Q34GFXDrfiHlG7iS33VA4ggnjWxjiz9KV5xzfsQzJX6vGAekA==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.0.4.tgz", - "integrity": "sha512-U3qMNHmEZoVmHA0j/57nRfi3AscXNvkOnxDmle/69Jz/G0o/gWjXTDdlgILZdrxQ0Lw/jv2mPW8PGy0EGIHXhQ==", + "version": "14.2.18", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.18.tgz", + "integrity": "sha512-KyYTbZ3GQwWOjX3Vi1YcQbekyGP0gdammb7pbmmi25HBUCINzDReyrzCMOJIeZisK1Q3U6DT5Rlc4nm2/pQeXA==", "dev": true, "license": "MIT", "dependencies": { - "glob": "7.1.7" + "glob": "10.3.10" + } + }, + "node_modules/@next/eslint-plugin-next/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@next/eslint-plugin-next/node_modules/glob": { + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@next/eslint-plugin-next/node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/@next/eslint-plugin-next/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@next/swc-darwin-arm64": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.0.4.tgz", - "integrity": "sha512-mF05E/5uPthWzyYDyptcwHptucf/jj09i2SXBPwNzbgBNc+XnwzrL0U6BmPjQeOL+FiB+iG1gwBeq7mlDjSRPg==", + "version": "14.2.18", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.18.tgz", + "integrity": "sha512-tOBlDHCjGdyLf0ube/rDUs6VtwNOajaWV+5FV/ajPgrvHeisllEdymY/oDgv2cx561+gJksfMUtqf8crug7sbA==", "cpu": [ "arm64" ], @@ -1248,9 +1318,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.0.4.tgz", - "integrity": "sha512-IZQ3C7Bx0k2rYtrZZxKKiusMTM9WWcK5ajyhOZkYYTCc8xytmwSzR1skU7qLgVT/EY9xtXDG0WhY6fyujnI3rw==", + "version": "14.2.18", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.18.tgz", + "integrity": "sha512-uJCEjutt5VeJ30jjrHV1VIHCsbMYnEqytQgvREx+DjURd/fmKy15NaVK4aR/u98S1LGTnjq35lRTnRyygglxoA==", "cpu": [ "x64" ], @@ -1264,9 +1334,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.4.tgz", - "integrity": "sha512-VwwZKrBQo/MGb1VOrxJ6LrKvbpo7UbROuyMRvQKTFKhNaXjUmKTu7wxVkIuCARAfiI8JpaWAnKR+D6tzpCcM4w==", + "version": "14.2.18", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.18.tgz", + "integrity": "sha512-IL6rU8vnBB+BAm6YSWZewc+qvdL1EaA+VhLQ6tlUc0xp+kkdxQrVqAnh8Zek1ccKHlTDFRyAft0e60gteYmQ4A==", "cpu": [ "arm64" ], @@ -1280,9 +1350,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.4.tgz", - "integrity": "sha512-8QftwPEW37XxXoAwsn+nXlodKWHfpMaSvt81W43Wh8dv0gkheD+30ezWMcFGHLI71KiWmHK5PSQbTQGUiidvLQ==", + "version": "14.2.18", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.18.tgz", + "integrity": "sha512-RCaENbIZqKKqTlL8KNd+AZV/yAdCsovblOpYFp0OJ7ZxgLNbV5w23CUU1G5On+0fgafrsGcW+GdMKdFjaRwyYA==", "cpu": [ "arm64" ], @@ -1296,9 +1366,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.0.4.tgz", - "integrity": "sha512-/s/Pme3VKfZAfISlYVq2hzFS8AcAIOTnoKupc/j4WlvF6GQ0VouS2Q2KEgPuO1eMBwakWPB1aYFIA4VNVh667A==", + "version": "14.2.18", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.18.tgz", + "integrity": "sha512-3kmv8DlyhPRCEBM1Vavn8NjyXtMeQ49ID0Olr/Sut7pgzaQTo4h01S7Z8YNE0VtbowyuAL26ibcz0ka6xCTH5g==", "cpu": [ "x64" ], @@ -1312,9 +1382,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.0.4.tgz", - "integrity": "sha512-m8z/6Fyal4L9Bnlxde5g2Mfa1Z7dasMQyhEhskDATpqr+Y0mjOBZcXQ7G5U+vgL22cI4T7MfvgtrM2jdopqWaw==", + "version": "14.2.18", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.18.tgz", + "integrity": "sha512-mliTfa8seVSpTbVEcKEXGjC18+TDII8ykW4a36au97spm9XMPqQTpdGPNBJ9RySSFw9/hLuaCMByluQIAnkzlw==", "cpu": [ "x64" ], @@ -1328,9 +1398,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.4.tgz", - "integrity": "sha512-7Wv4PRiWIAWbm5XrGz3D8HUkCVDMMz9igffZG4NB1p4u1KoItwx9qjATHz88kwCEal/HXmbShucaslXCQXUM5w==", + "version": "14.2.18", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.18.tgz", + "integrity": "sha512-J5g0UFPbAjKYmqS3Cy7l2fetFmWMY9Oao32eUsBPYohts26BdrMUyfCJnZFQkX9npYaHNDOWqZ6uV9hSDPw9NA==", "cpu": [ "arm64" ], @@ -1344,9 +1414,9 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.4.tgz", - "integrity": "sha512-zLeNEAPULsl0phfGb4kdzF/cAVIfaC7hY+kt0/d+y9mzcZHsMS3hAS829WbJ31DkSlVKQeHEjZHIdhN+Pg7Gyg==", + "version": "14.2.18", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.18.tgz", + "integrity": "sha512-Ynxuk4ZgIpdcN7d16ivJdjsDG1+3hTvK24Pp8DiDmIa2+A4CfhJSEHHVndCHok6rnLUzAZD+/UOKESQgTsAZGg==", "cpu": [ "ia32" ], @@ -1360,9 +1430,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.4.tgz", - "integrity": "sha512-yEh2+R8qDlDCjxVpzOTEpBLQTEFAcP2A8fUFLaWNap9GitYKkKv1//y2S6XY6zsR4rCOPRpU7plYDR+az2n30A==", + "version": "14.2.18", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.18.tgz", + "integrity": "sha512-dtRGMhiU9TN5nyhwzce+7c/4CCeykYS+ipY/4mIrGzJ71+7zNo55ZxCB7cAVuNqdwtYniFNR2c9OFQ6UdFIMcg==", "cpu": [ "x64" ], @@ -1525,12 +1595,28 @@ "dev": true, "license": "MIT" }, + "node_modules/@stripe/stripe-js": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-8.0.0.tgz", + "integrity": "sha512-dLvD55KT1cBmrqzgYRgY42qNcw6zW4HS5oRZs0xRvHw9gBWig5yDnWNop/E+/t2JK+OZO30zsnupVBN2MqW2mg==", + "license": "MIT", + "engines": { + "node": ">=12.16" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "license": "Apache-2.0" + }, "node_modules/@swc/helpers": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz", - "integrity": "sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz", + "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==", "license": "Apache-2.0", "dependencies": { + "@swc/counter": "^0.1.3", "tslib": "^2.4.0" } }, @@ -1623,6 +1709,122 @@ "@types/react": "^18.0.0" } }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.1.tgz", + "integrity": "sha512-rUsLh8PXmBjdiPY+Emjz9NX2yHvhS11v0SR6xNJkm5GM1MO9ea/1GoDKlHHZGrOJclL/cZ2i/vRUYVtjRhrHVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.46.1", + "@typescript-eslint/type-utils": "8.46.1", + "@typescript-eslint/utils": "8.46.1", + "@typescript-eslint/visitor-keys": "8.46.1", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.46.1", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.1.tgz", + "integrity": "sha512-weL9Gg3/5F0pVQKiF8eOXFZp8emqWzZsOJuWRUNtHT+UNV2xSJegmpCNQHy37aEQIbToTq7RHKhWvOsmbM680A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.1", + "@typescript-eslint/visitor-keys": "8.46.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.1.tgz", + "integrity": "sha512-C+soprGBHwWBdkDpbaRC4paGBrkIXxVlNohadL5o0kfhsXqOC6GYH2S/Obmig+I0HTDl8wMaRySwrfrXVP8/pQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.1.tgz", + "integrity": "sha512-ptkmIf2iDkNUjdeu2bQqhFPV1m6qTnFFjg7PPDjxKWaMaP0Z6I9l30Jr3g5QqbZGdw8YdYvLp+XnqnWWZOg/NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.1", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/@typescript-eslint/parser": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", @@ -1652,6 +1854,42 @@ } } }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.1.tgz", + "integrity": "sha512-FOIaFVMHzRskXr5J4Jp8lFVV0gz5ngv3RHmn+E4HYxSJ3DgDzU7fVI1/M7Ijh1zf6S7HIoaIOtln1H5y8V+9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.46.1", + "@typescript-eslint/types": "^8.46.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service/node_modules/@typescript-eslint/types": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.1.tgz", + "integrity": "sha512-C+soprGBHwWBdkDpbaRC4paGBrkIXxVlNohadL5o0kfhsXqOC6GYH2S/Obmig+I0HTDl8wMaRySwrfrXVP8/pQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@typescript-eslint/scope-manager": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", @@ -1670,6 +1908,161 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.1.tgz", + "integrity": "sha512-X88+J/CwFvlJB+mK09VFqx5FE4H5cXD+H/Bdza2aEWkSb8hnWIQorNcscRl4IEo1Cz9VI/+/r/jnGWkbWPx54g==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.1.tgz", + "integrity": "sha512-+BlmiHIiqufBxkVnOtFwjah/vrkF4MtKKvpXrKSPLCkCtAp8H01/VV43sfqA98Od7nJpDcFnkwgyfQbOG0AMvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.1", + "@typescript-eslint/typescript-estree": "8.46.1", + "@typescript-eslint/utils": "8.46.1", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.1.tgz", + "integrity": "sha512-C+soprGBHwWBdkDpbaRC4paGBrkIXxVlNohadL5o0kfhsXqOC6GYH2S/Obmig+I0HTDl8wMaRySwrfrXVP8/pQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.1.tgz", + "integrity": "sha512-uIifjT4s8cQKFQ8ZBXXyoUODtRoAd7F7+G8MKmtzj17+1UbdzFl52AzRyZRyKqPHhgzvXunnSckVu36flGy8cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.46.1", + "@typescript-eslint/tsconfig-utils": "8.46.1", + "@typescript-eslint/types": "8.46.1", + "@typescript-eslint/visitor-keys": "8.46.1", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.1.tgz", + "integrity": "sha512-ptkmIf2iDkNUjdeu2bQqhFPV1m6qTnFFjg7PPDjxKWaMaP0Z6I9l30Jr3g5QqbZGdw8YdYvLp+XnqnWWZOg/NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.1", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/@typescript-eslint/types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", @@ -1739,6 +2132,161 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@typescript-eslint/utils": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.1.tgz", + "integrity": "sha512-vkYUy6LdZS7q1v/Gxb2Zs7zziuXN0wxqsetJdeZdRe/f5dwJFglmuvZBfTUivCtjH725C1jWCDfpadadD95EDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.46.1", + "@typescript-eslint/types": "8.46.1", + "@typescript-eslint/typescript-estree": "8.46.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.1.tgz", + "integrity": "sha512-weL9Gg3/5F0pVQKiF8eOXFZp8emqWzZsOJuWRUNtHT+UNV2xSJegmpCNQHy37aEQIbToTq7RHKhWvOsmbM680A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.1", + "@typescript-eslint/visitor-keys": "8.46.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.1.tgz", + "integrity": "sha512-C+soprGBHwWBdkDpbaRC4paGBrkIXxVlNohadL5o0kfhsXqOC6GYH2S/Obmig+I0HTDl8wMaRySwrfrXVP8/pQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.1.tgz", + "integrity": "sha512-uIifjT4s8cQKFQ8ZBXXyoUODtRoAd7F7+G8MKmtzj17+1UbdzFl52AzRyZRyKqPHhgzvXunnSckVu36flGy8cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.46.1", + "@typescript-eslint/tsconfig-utils": "8.46.1", + "@typescript-eslint/types": "8.46.1", + "@typescript-eslint/visitor-keys": "8.46.1", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.1.tgz", + "integrity": "sha512-ptkmIf2iDkNUjdeu2bQqhFPV1m6qTnFFjg7PPDjxKWaMaP0Z6I9l30Jr3g5QqbZGdw8YdYvLp+XnqnWWZOg/NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.46.1", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/@typescript-eslint/visitor-keys": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", @@ -2549,7 +3097,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -2563,7 +3110,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -3067,7 +3613,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -3172,7 +3717,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3182,7 +3726,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3220,7 +3763,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -3399,15 +3941,16 @@ } }, "node_modules/eslint-config-next": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.0.4.tgz", - "integrity": "sha512-9/xbOHEQOmQtqvQ1UsTQZpnA7SlDMBtuKJ//S4JnoyK3oGLhILKXdBgu/UO7lQo/2xOykQULS1qQ6p2+EpHgAQ==", + "version": "14.2.18", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.2.18.tgz", + "integrity": "sha512-SuDRcpJY5VHBkhz5DijJ4iA4bVnBA0n48Rb+YSJSCDr+h7kKAcb1mZHusLbW+WA8LDB6edSolomXA55eG3eOVA==", "dev": true, "license": "MIT", "dependencies": { - "@next/eslint-plugin-next": "14.0.4", + "@next/eslint-plugin-next": "14.2.18", "@rushstack/eslint-patch": "^1.3.3", - "@typescript-eslint/parser": "^5.4.2 || ^6.0.0", + "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.28.1", @@ -4026,7 +4569,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4086,7 +4628,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -4111,7 +4652,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -4187,12 +4727,6 @@ "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/globals": { "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", @@ -4251,7 +4785,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4329,7 +4862,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4358,7 +4890,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -5197,7 +5728,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5320,19 +5850,18 @@ "license": "MIT" }, "node_modules/next": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/next/-/next-14.0.4.tgz", - "integrity": "sha512-qbwypnM7327SadwFtxXnQdGiKpkuhaRLE2uq62/nRul9cj9KhQ5LhHmlziTNqUidZotw/Q1I9OjirBROdUJNgA==", + "version": "14.2.18", + "resolved": "https://registry.npmjs.org/next/-/next-14.2.18.tgz", + "integrity": "sha512-H9qbjDuGivUDEnK6wa+p2XKO+iMzgVgyr9Zp/4Iv29lKa+DYaxJGjOeEA+5VOvJh/M7HLiskehInSa0cWxVXUw==", "license": "MIT", "dependencies": { - "@next/env": "14.0.4", - "@swc/helpers": "0.5.2", + "@next/env": "14.2.18", + "@swc/helpers": "0.5.5", "busboy": "1.6.0", - "caniuse-lite": "^1.0.30001406", + "caniuse-lite": "^1.0.30001579", "graceful-fs": "^4.2.11", "postcss": "8.4.31", - "styled-jsx": "5.1.1", - "watchpack": "2.4.0" + "styled-jsx": "5.1.1" }, "bin": { "next": "dist/bin/next" @@ -5341,18 +5870,19 @@ "node": ">=18.17.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "14.0.4", - "@next/swc-darwin-x64": "14.0.4", - "@next/swc-linux-arm64-gnu": "14.0.4", - "@next/swc-linux-arm64-musl": "14.0.4", - "@next/swc-linux-x64-gnu": "14.0.4", - "@next/swc-linux-x64-musl": "14.0.4", - "@next/swc-win32-arm64-msvc": "14.0.4", - "@next/swc-win32-ia32-msvc": "14.0.4", - "@next/swc-win32-x64-msvc": "14.0.4" + "@next/swc-darwin-arm64": "14.2.18", + "@next/swc-darwin-x64": "14.2.18", + "@next/swc-linux-arm64-gnu": "14.2.18", + "@next/swc-linux-arm64-musl": "14.2.18", + "@next/swc-linux-x64-gnu": "14.2.18", + "@next/swc-linux-x64-musl": "14.2.18", + "@next/swc-win32-arm64-msvc": "14.2.18", + "@next/swc-win32-ia32-msvc": "14.2.18", + "@next/swc-win32-x64-msvc": "14.2.18" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.41.2", "react": "^18.2.0", "react-dom": "^18.2.0", "sass": "^1.3.0" @@ -5361,6 +5891,9 @@ "@opentelemetry/api": { "optional": true }, + "@playwright/test": { + "optional": true + }, "sass": { "optional": true } @@ -5508,7 +6041,6 @@ "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6175,6 +6707,21 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -6669,7 +7216,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -6689,7 +7235,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -6706,7 +7251,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -6725,7 +7269,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -7034,6 +7577,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/stripe": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-19.1.0.tgz", + "integrity": "sha512-FjgIiE98dMMTNssfdjMvFdD4eZyEzdWAOwPYqzhPRNZeg9ggFWlPXmX1iJKD5pPIwZBaPlC3SayQQkwsPo6/YQ==", + "license": "MIT", + "dependencies": { + "qs": "^6.11.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@types/node": ">=16" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, "node_modules/styled-jsx": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", @@ -7588,19 +8151,6 @@ "node": ">=0.10.0" } }, - "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", - "license": "MIT", - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 2a4d703..a810bcc 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "dependencies": { "@auth/prisma-adapter": "^1.0.12", "@prisma/client": "^5.7.0", + "@stripe/stripe-js": "^8.0.0", "bcryptjs": "^2.4.3", "chart.js": "^4.4.0", "clsx": "^2.0.0", @@ -44,6 +45,7 @@ "react-dropzone": "^14.2.3", "react-i18next": "^13.5.0", "sharp": "^0.33.1", + "stripe": "^19.1.0", "tailwind-merge": "^2.2.0", "xlsx": "^0.18.5", "zod": "^3.22.4" @@ -68,4 +70,4 @@ "engines": { "node": ">=18.0.0" } -} \ No newline at end of file +} diff --git a/src/app/(app)/create/page.tsx b/src/app/(app)/create/page.tsx index 7f4d537..a309066 100644 --- a/src/app/(app)/create/page.tsx +++ b/src/app/(app)/create/page.tsx @@ -1,6 +1,7 @@ 'use client'; import React, { useState, useEffect } from 'react'; +import Link from 'next/link'; import { useRouter } from 'next/navigation'; import { QRCodeSVG } from 'qrcode.react'; import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card'; @@ -16,22 +17,42 @@ export default function CreatePage() { const router = useRouter(); const { t } = useTranslation(); const [loading, setLoading] = useState(false); - + const [userPlan, setUserPlan] = useState('FREE'); + // Form state const [title, setTitle] = useState(''); const [contentType, setContentType] = useState('URL'); const [content, setContent] = useState({ url: '' }); const [isDynamic, setIsDynamic] = useState(true); const [tags, setTags] = useState(''); - + // Style state const [foregroundColor, setForegroundColor] = useState('#000000'); const [backgroundColor, setBackgroundColor] = useState('#FFFFFF'); const [cornerStyle, setCornerStyle] = useState('square'); const [size, setSize] = useState(200); - + // QR preview const [qrDataUrl, setQrDataUrl] = useState(''); + + // Check if user can customize colors (PRO+ only) + const canCustomizeColors = userPlan === 'PRO' || userPlan === 'BUSINESS'; + + // Load user plan + useEffect(() => { + const fetchUserPlan = async () => { + try { + const response = await fetch('/api/user/plan'); + if (response.ok) { + const data = await response.json(); + setUserPlan(data.plan || 'FREE'); + } + } catch (error) { + console.error('Error fetching user plan:', error); + } + }; + fetchUserPlan(); + }, []); const contrast = calculateContrast(foregroundColor, backgroundColor); const hasGoodContrast = contrast >= 4.5; @@ -141,8 +162,9 @@ export default function CreatePage() { isStatic: !isDynamic, // Add this flag tags: tags.split(',').map(t => t.trim()).filter(Boolean), style: { - foregroundColor, - backgroundColor, + // FREE users can only use black/white + foregroundColor: canCustomizeColors ? foregroundColor : '#000000', + backgroundColor: canCustomizeColors ? backgroundColor : '#FFFFFF', cornerStyle, size, }, @@ -354,9 +376,26 @@ export default function CreatePage() { {/* Style Section */} - {t('create.style')} +
+ {t('create.style')} + {!canCustomizeColors && ( + PRO Feature + )} +
+ {!canCustomizeColors && ( +
+

+ Upgrade to PRO to customize colors, add logos, and brand your QR codes. +

+ + + +
+ )}
- +
diff --git a/src/app/(app)/dashboard/page.tsx b/src/app/(app)/dashboard/page.tsx index 3b05f43..14bc68d 100644 --- a/src/app/(app)/dashboard/page.tsx +++ b/src/app/(app)/dashboard/page.tsx @@ -2,13 +2,15 @@ import React, { useState, useEffect } from 'react'; import Link from 'next/link'; -import { useSession } from 'next-auth/react'; +import { useRouter, useSearchParams } from 'next/navigation'; import { StatsGrid } from '@/components/dashboard/StatsGrid'; import { QRCodeCard } from '@/components/dashboard/QRCodeCard'; import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card'; import { Button } from '@/components/ui/Button'; import { Badge } from '@/components/ui/Badge'; import { useTranslation } from '@/hooks/useTranslation'; +import { showToast } from '@/components/ui/Toast'; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/Dialog'; interface QRCodeData { id: string; @@ -24,10 +26,13 @@ interface QRCodeData { export default function DashboardPage() { const { t } = useTranslation(); - const { data: session } = useSession(); + const router = useRouter(); + const searchParams = useSearchParams(); const [qrCodes, setQrCodes] = useState([]); const [loading, setLoading] = useState(true); const [userPlan, setUserPlan] = useState('FREE'); + const [showUpgradeDialog, setShowUpgradeDialog] = useState(false); + const [upgradedPlan, setUpgradedPlan] = useState(''); const [stats, setStats] = useState({ totalScans: 0, activeQRCodes: 0, @@ -118,6 +123,35 @@ export default function DashboardPage() { }, ]; + // Check for successful payment and verify session + useEffect(() => { + const success = searchParams.get('success'); + if (success === 'true') { + const verifySession = async () => { + try { + const response = await fetch('/api/stripe/verify-session', { + method: 'POST', + }); + + if (response.ok) { + const data = await response.json(); + setUserPlan(data.plan); + setUpgradedPlan(data.plan); + setShowUpgradeDialog(true); + // Remove success parameter from URL + router.replace('/dashboard'); + } else { + console.error('Failed to verify session:', await response.text()); + } + } catch (error) { + console.error('Error verifying session:', error); + } + }; + + verifySession(); + } + }, [searchParams, router]); + useEffect(() => { // Load real QR codes and user plan from API const fetchData = async () => { @@ -148,13 +182,11 @@ export default function DashboardPage() { }); } - // Fetch user plan - if (session?.user?.email) { - const userResponse = await fetch('/api/user/plan'); - if (userResponse.ok) { - const userData = await userResponse.json(); - setUserPlan(userData.plan || 'FREE'); - } + // Fetch user plan (using cookie-based auth, no session needed) + const userResponse = await fetch('/api/user/plan'); + if (userResponse.ok) { + const userData = await userResponse.json(); + setUserPlan(userData.plan || 'FREE'); } } catch (error) { console.error('Error fetching data:', error); @@ -170,7 +202,7 @@ export default function DashboardPage() { }; fetchData(); - }, [session]); + }, []); const handleEdit = (id: string) => { console.log('Edit QR:', id); @@ -200,16 +232,8 @@ export default function DashboardPage() { }; const getPlanEmoji = (plan: string) => { - switch (plan) { - case 'FREE': - return '🟢'; - case 'PRO': - return '🔵'; - case 'BUSINESS': - return '🟣'; - default: - return '⚪'; - } + // No emojis anymore + return ''; }; return ( @@ -222,7 +246,7 @@ export default function DashboardPage() {
- {getPlanEmoji(userPlan)} {userPlan} Plan + {userPlan} Plan {userPlan === 'FREE' && ( @@ -302,6 +326,89 @@ export default function DashboardPage() { ))}
+ + {/* Upgrade Success Dialog */} + + + + + Upgrade erfolgreich! + + + Willkommen im {upgradedPlan} Plan! Ihr Konto wurde erfolgreich aktualisiert. + + + +
+
+

Ihre neuen Features:

+
    + {upgradedPlan === 'PRO' && ( + <> +
  • + + 50 dynamische QR-Codes +
  • +
  • + + Branding (Logo, Farben anpassen) +
  • +
  • + + Detaillierte Analytics +
  • +
  • + + CSV-Export +
  • +
  • + + Passwortschutz für QR-Codes +
  • + + )} + {upgradedPlan === 'BUSINESS' && ( + <> +
  • + + 500 dynamische QR-Codes +
  • +
  • + + Team-Zugänge (bis zu 3 User) +
  • +
  • + + Benutzerdefinierte Domains +
  • +
  • + + White-Label +
  • +
  • + + Prioritäts-Support +
  • + + )} +
+
+
+ + + + +
+
); } \ No newline at end of file diff --git a/src/app/(app)/pricing/page.tsx b/src/app/(app)/pricing/page.tsx index 697cf10..b85659d 100644 --- a/src/app/(app)/pricing/page.tsx +++ b/src/app/(app)/pricing/page.tsx @@ -1,8 +1,7 @@ 'use client'; -import React, { useState } from 'react'; -import { useRouter } from 'next/navigation'; -import { useSession } from 'next-auth/react'; +import React, { useState, useEffect } from 'react'; +import { useRouter, useSearchParams } from 'next/navigation'; import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card'; import { Button } from '@/components/ui/Button'; import { Badge } from '@/components/ui/Badge'; @@ -10,15 +9,25 @@ import { showToast } from '@/components/ui/Toast'; export default function PricingPage() { const router = useRouter(); - const { data: session } = useSession(); + const searchParams = useSearchParams(); + const [user, setUser] = useState(null); const [loading, setLoading] = useState(null); const [billingInterval, setBillingInterval] = useState<'monthly' | 'yearly'>('monthly'); + const [hasTriggeredCheckout, setHasTriggeredCheckout] = useState(false); + + // Check for user in localStorage + useEffect(() => { + const storedUser = localStorage.getItem('user'); + if (storedUser) { + setUser(JSON.parse(storedUser)); + } + }, []); const plans = [ { id: 'FREE', name: 'Free / Starter', - icon: '🟢', + icon: '', price: 0, priceYearly: 0, description: 'Privatnutzer & Testkunden', @@ -36,7 +45,7 @@ export default function PricingPage() { { id: 'PRO', name: 'Pro', - icon: '🔵', + icon: '', price: 9, priceYearly: 90, description: 'Selbstständige / kleine Firmen', @@ -55,14 +64,13 @@ export default function PricingPage() { { id: 'BUSINESS', name: 'Business', - icon: '🟣', + icon: '', price: 29, priceYearly: 290, description: 'Agenturen / Startups', features: [ '500 QR-Codes', 'Team-Zugänge (bis 3 User)', - 'API-Zugang', 'Benutzerdefinierte Domains', 'White-Label', 'Prioritäts-Support', @@ -75,8 +83,24 @@ export default function PricingPage() { ]; const handleSubscribe = async (planId: string, priceId: string | null | undefined) => { - if (!session) { - router.push('/login?redirect=/pricing'); + console.log('🔵 handleSubscribe called:', { planId, priceId, hasUser: !!user }); + + if (!user) { + // Save the plan selection in localStorage so we can continue after login + const pendingPlan = { + planId, + interval: billingInterval, + }; + console.log('💾 Saving pending plan to localStorage:', pendingPlan); + localStorage.setItem('pendingPlan', JSON.stringify(pendingPlan)); + + // Verify it was saved + const saved = localStorage.getItem('pendingPlan'); + console.log('✅ Verified saved:', saved); + + // Use window.location instead of router.push to ensure localStorage is written + console.log('🔄 Redirecting to login...'); + window.location.href = '/login?redirect=/pricing'; return; } @@ -99,6 +123,7 @@ export default function PricingPage() { body: JSON.stringify({ priceId, plan: planId, + userEmail: user.email, }), }); @@ -117,6 +142,67 @@ export default function PricingPage() { } }; + // Auto-trigger checkout after login if plan is selected + useEffect(() => { + console.log('Pricing useEffect triggered:', { + hasUser: !!user, + hasTriggeredCheckout, + }); + + // Only run once and only when authenticated + if (hasTriggeredCheckout) { + console.log('Already triggered checkout, skipping...'); + return; + } + + if (!user) { + console.log('Not authenticated - no user in localStorage'); + return; + } + + // Check for pending plan in localStorage + const pendingPlanStr = localStorage.getItem('pendingPlan'); + if (pendingPlanStr) { + try { + const pendingPlan = JSON.parse(pendingPlanStr); + console.log('✅ Found pending plan:', pendingPlan); + + // Clear pending plan immediately + localStorage.removeItem('pendingPlan'); + + // Mark as triggered to prevent re-runs + setHasTriggeredCheckout(true); + + // Set the billing interval + setBillingInterval(pendingPlan.interval); + + // Find the plan + const selectedPlan = plans.find((p) => p.id === pendingPlan.planId); + if (selectedPlan) { + const priceId = + pendingPlan.interval === 'yearly' + ? selectedPlan.priceIdYearly + : selectedPlan.priceIdMonthly; + + console.log('✅ Found plan and priceId:', selectedPlan.name, priceId); + + // Trigger checkout after a short delay + setTimeout(() => { + console.log('🚀 Calling handleSubscribe now...'); + handleSubscribe(selectedPlan.id, priceId); + }, 500); + } else { + console.error('❌ Plan not found:', pendingPlan.planId); + } + } catch (e) { + console.error('Error parsing pending plan:', e); + localStorage.removeItem('pendingPlan'); + } + } else { + console.log('No pending plan in localStorage'); + } + }, [user, hasTriggeredCheckout]); + return (
@@ -180,7 +266,7 @@ export default function PricingPage() { )} -
{plan.icon}
+ {plan.icon &&
{plan.icon}
} {plan.name}

{plan.description}

diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx index da4b057..155bfdc 100644 --- a/src/app/(auth)/login/page.tsx +++ b/src/app/(auth)/login/page.tsx @@ -1,7 +1,7 @@ 'use client'; -import React, { useState } from 'react'; -import { useRouter } from 'next/navigation'; +import React, { useState, useEffect } from 'react'; +import { useRouter, useSearchParams } from 'next/navigation'; import Link from 'next/link'; import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card'; import { Input } from '@/components/ui/Input'; @@ -10,6 +10,7 @@ import { useTranslation } from '@/hooks/useTranslation'; export default function LoginPage() { const router = useRouter(); + const searchParams = useSearchParams(); const { t } = useTranslation(); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); @@ -33,7 +34,10 @@ export default function LoginPage() { if (response.ok && data.success) { // Store user in localStorage for client-side localStorage.setItem('user', JSON.stringify(data.user)); - router.push('/dashboard'); + + // Check for redirect parameter + const redirectUrl = searchParams.get('redirect') || '/dashboard'; + router.push(redirectUrl); router.refresh(); } else { setError(data.error || 'Invalid email or password'); diff --git a/src/app/(marketing)/pricing/page.tsx b/src/app/(marketing)/pricing/page.tsx deleted file mode 100644 index 0e4c96d..0000000 --- a/src/app/(marketing)/pricing/page.tsx +++ /dev/null @@ -1,15 +0,0 @@ -'use client'; - -import React from 'react'; -import { Pricing } from '@/components/marketing/Pricing'; -import { useTranslation } from '@/hooks/useTranslation'; - -export default function PricingPage() { - const { t } = useTranslation(); - - return ( -
- -
- ); -} \ No newline at end of file diff --git a/src/app/api/qrs/route.ts b/src/app/api/qrs/route.ts index 361b7f7..f39a238 100644 --- a/src/app/api/qrs/route.ts +++ b/src/app/api/qrs/route.ts @@ -38,32 +38,67 @@ export async function GET(request: NextRequest) { } } +// Plan limits +const PLAN_LIMITS = { + FREE: 3, + PRO: 50, + BUSINESS: 500, +}; + // POST /api/qrs - Create a new QR code export async function POST(request: NextRequest) { try { const userId = cookies().get('userId')?.value; console.log('POST /api/qrs - userId from cookie:', userId); - + if (!userId) { return NextResponse.json({ error: 'Unauthorized - no userId cookie' }, { status: 401 }); } - // Check if user exists - const userExists = await db.user.findUnique({ - where: { id: userId } + // Check if user exists and get their plan + const user = await db.user.findUnique({ + where: { id: userId }, + select: { plan: true }, }); - - console.log('User exists:', !!userExists); - - if (!userExists) { + + console.log('User exists:', !!user); + + if (!user) { return NextResponse.json({ error: `User not found: ${userId}` }, { status: 404 }); } const body = await request.json(); console.log('Request body:', body); - + // Check if this is a static QR request const isStatic = body.isStatic === true; + + // Only check limits for DYNAMIC QR codes (static QR codes are unlimited) + if (!isStatic) { + // Count existing dynamic QR codes + const dynamicQRCount = await db.qRCode.count({ + where: { + userId, + type: 'DYNAMIC', + }, + }); + + const userPlan = user.plan || 'FREE'; + const limit = PLAN_LIMITS[userPlan as keyof typeof PLAN_LIMITS] || PLAN_LIMITS.FREE; + + if (dynamicQRCount >= limit) { + return NextResponse.json( + { + error: 'Limit reached', + message: `You have reached the limit of ${limit} dynamic QR codes for your ${userPlan} plan. Please upgrade to create more.`, + currentCount: dynamicQRCount, + limit, + plan: userPlan, + }, + { status: 403 } + ); + } + } let enrichedContent = body.content; diff --git a/src/app/api/stripe/checkout/route.ts b/src/app/api/stripe/checkout/route.ts index 80c0d47..a71f84e 100644 --- a/src/app/api/stripe/checkout/route.ts +++ b/src/app/api/stripe/checkout/route.ts @@ -1,19 +1,17 @@ import { NextRequest, NextResponse } from 'next/server'; -import { getServerSession } from 'next-auth'; -import { authOptions } from '@/lib/auth'; import { stripe } from '@/lib/stripe'; import { db } from '@/lib/db'; +import { cookies } from 'next/headers'; export async function POST(request: NextRequest) { try { - const session = await getServerSession(authOptions); + // Get user email from request body (since we're using simple auth, not NextAuth) + const { priceId, plan, userEmail } = await request.json(); - if (!session?.user?.email) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + if (!userEmail) { + return NextResponse.json({ error: 'Unauthorized - No user email provided' }, { status: 401 }); } - const { priceId, plan } = await request.json(); - if (!priceId || !plan) { return NextResponse.json( { error: 'Missing priceId or plan' }, @@ -23,7 +21,7 @@ export async function POST(request: NextRequest) { // Get user from database const user = await db.user.findUnique({ - where: { email: session.user.email }, + where: { email: userEmail }, }); if (!user) { @@ -54,7 +52,7 @@ export async function POST(request: NextRequest) { const checkoutSession = await stripe.checkout.sessions.create({ customer: customerId, mode: 'subscription', - payment_method_types: ['card', 'sepa_debit'], + payment_method_types: ['card'], line_items: [ { price: priceId, diff --git a/src/app/api/stripe/verify-session/route.ts b/src/app/api/stripe/verify-session/route.ts new file mode 100644 index 0000000..cabcd41 --- /dev/null +++ b/src/app/api/stripe/verify-session/route.ts @@ -0,0 +1,76 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { cookies } from 'next/headers'; +import { stripe } from '@/lib/stripe'; +import { db } from '@/lib/db'; + +export async function POST(request: NextRequest) { + try { + // Use cookie-based auth instead of NextAuth + const userId = cookies().get('userId')?.value; + + if (!userId) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + + const user = await db.user.findUnique({ + where: { id: userId }, + }); + + if (!user) { + return NextResponse.json({ error: 'User not found' }, { status: 404 }); + } + + if (!user.stripeCustomerId) { + return NextResponse.json({ error: 'No Stripe customer ID' }, { status: 400 }); + } + + // Get the most recent checkout session for this customer + const checkoutSessions = await stripe.checkout.sessions.list({ + customer: user.stripeCustomerId, + limit: 1, + }); + + if (checkoutSessions.data.length === 0) { + return NextResponse.json({ error: 'No checkout session found' }, { status: 404 }); + } + + const checkoutSession = checkoutSessions.data[0]; + + // Only process if payment was successful + if (checkoutSession.payment_status === 'paid' && checkoutSession.subscription) { + const subscription = await stripe.subscriptions.retrieve( + checkoutSession.subscription as string + ); + + // Determine plan from metadata or price ID + const plan = checkoutSession.metadata?.plan || 'PRO'; + + // Update user in database + await db.user.update({ + where: { id: user.id }, + data: { + stripeSubscriptionId: subscription.id, + stripePriceId: subscription.items.data[0].price.id, + stripeCurrentPeriodEnd: new Date( + subscription.current_period_end * 1000 + ), + plan: plan as any, + }, + }); + + return NextResponse.json({ + success: true, + plan, + subscriptionId: subscription.id, + }); + } + + return NextResponse.json({ error: 'Payment not completed' }, { status: 400 }); + } catch (error) { + console.error('Error verifying session:', error); + return NextResponse.json( + { error: 'Internal server error' }, + { status: 500 } + ); + } +} diff --git a/src/app/api/user/plan/route.ts b/src/app/api/user/plan/route.ts index c0f9dab..8ad03ad 100644 --- a/src/app/api/user/plan/route.ts +++ b/src/app/api/user/plan/route.ts @@ -1,18 +1,18 @@ import { NextRequest, NextResponse } from 'next/server'; -import { getServerSession } from 'next-auth'; -import { authOptions } from '@/lib/auth'; +import { cookies } from 'next/headers'; import { db } from '@/lib/db'; export async function GET(request: NextRequest) { try { - const session = await getServerSession(authOptions); + // Use cookie-based auth instead of NextAuth + const userId = cookies().get('userId')?.value; - if (!session?.user?.email) { + if (!userId) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } const user = await db.user.findUnique({ - where: { email: session.user.email }, + where: { id: userId }, select: { plan: true, stripeCurrentPeriodEnd: true, diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 1513698..9ec7aa0 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,9 +1,7 @@ import type { Metadata } from 'next'; -import { Inter } from 'next/font/google'; import '@/styles/globals.css'; import { ToastContainer } from '@/components/ui/Toast'; - -const inter = Inter({ subsets: ['latin'] }); +import AuthProvider from '@/components/SessionProvider'; export const metadata: Metadata = { title: 'QR Master - Create Custom QR Codes in Seconds', @@ -33,8 +31,10 @@ export default function RootLayout({ }) { return ( - - {children} + + + {children} + diff --git a/src/components/SessionProvider.tsx b/src/components/SessionProvider.tsx new file mode 100644 index 0000000..5c91026 --- /dev/null +++ b/src/components/SessionProvider.tsx @@ -0,0 +1,11 @@ +'use client'; + +import { SessionProvider } from 'next-auth/react'; + +export default function AuthProvider({ + children, +}: { + children: React.ReactNode; +}) { + return {children}; +}