diff --git a/next-env.d.ts b/next-env.d.ts index fd36f94..0c7fad7 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,6 +1,7 @@ /// /// /// +import "./.next/dev/types/routes.d.ts"; // NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/next.config.js b/next.config.js index dc96abb..c5ebd50 100644 --- a/next.config.js +++ b/next.config.js @@ -4,11 +4,13 @@ const nextConfig = { eslint: { ignoreDuringBuilds: true, }, + turbopack: {}, images: { unoptimized: true }, webpack: (config) => { config.ignoreWarnings = [ { module: /@supabase\/realtime-js/, + }, ]; return config; diff --git a/package-lock.json b/package-lock.json index 055222c..220c3b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -59,7 +59,7 @@ "eslint-config-next": "13.5.1", "input-otp": "^1.2.4", "lucide-react": "^0.446.0", - "next": "13.5.1", + "next": "^16.1.6", "next-themes": "^0.3.0", "postcss": "^8.4.30", "react": "18.2.0", @@ -273,9 +273,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz", - "integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", "license": "MIT", "optional": true, "dependencies": { @@ -1065,6 +1065,472 @@ "deprecated": "Use @eslint/object-schema instead", "license": "BSD-3-Clause" }, + "node_modules/@img/colour": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1364,9 +1830,9 @@ } }, "node_modules/@next/env": { - "version": "13.5.1", - "resolved": "https://registry.npmjs.org/@next/env/-/env-13.5.1.tgz", - "integrity": "sha512-CIMWiOTyflFn/GFx33iYXkgLSQsMQZV4jB91qaj/TfxGaGOXxn8C1j72TaUSPIyN7ziS/AYG46kGmnvuk1oOpg==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.6.tgz", + "integrity": "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { @@ -1379,9 +1845,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "13.5.1", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.5.1.tgz", - "integrity": "sha512-Bcd0VFrLHZnMmJy6LqV1CydZ7lYaBao8YBEdQUVzV8Ypn/l5s//j5ffjfvMzpEQ4mzlAj3fIY+Bmd9NxpWhACw==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.6.tgz", + "integrity": "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==", "cpu": [ "arm64" ], @@ -1395,9 +1861,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "13.5.1", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.5.1.tgz", - "integrity": "sha512-uvTZrZa4D0bdWa1jJ7X1tBGIxzpqSnw/ATxWvoRO9CVBvXSx87JyuISY+BWsfLFF59IRodESdeZwkWM2l6+Kjg==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.6.tgz", + "integrity": "sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==", "cpu": [ "x64" ], @@ -1411,9 +1877,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "13.5.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.5.1.tgz", - "integrity": "sha512-/52ThlqdORPQt3+AlMoO+omicdYyUEDeRDGPAj86ULpV4dg+/GCFCKAmFWT0Q4zChFwsAoZUECLcKbRdcc0SNg==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.6.tgz", + "integrity": "sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==", "cpu": [ "arm64" ], @@ -1427,9 +1893,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "13.5.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.5.1.tgz", - "integrity": "sha512-L4qNXSOHeu1hEAeeNsBgIYVnvm0gg9fj2O2Yx/qawgQEGuFBfcKqlmIE/Vp8z6gwlppxz5d7v6pmHs1NB6R37w==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.6.tgz", + "integrity": "sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==", "cpu": [ "arm64" ], @@ -1443,9 +1909,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "13.5.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.5.1.tgz", - "integrity": "sha512-QVvMrlrFFYvLtABk092kcZ5Mzlmsk2+SV3xYuAu8sbTuIoh0U2+HGNhVklmuYCuM3DAAxdiMQTNlRQmNH11udw==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.6.tgz", + "integrity": "sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==", "cpu": [ "x64" ], @@ -1459,9 +1925,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "13.5.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.5.1.tgz", - "integrity": "sha512-bBnr+XuWc28r9e8gQ35XBtyi5KLHLhTbEvrSgcWna8atI48sNggjIK8IyiEBO3KIrcUVXYkldAzGXPEYMnKt1g==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.6.tgz", + "integrity": "sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==", "cpu": [ "x64" ], @@ -1480,9 +1946,9 @@ "integrity": "sha512-aDH8VVNfzv2UvwMMw8LOdzlWu514TOprKWZt+5CPiCeGhN0N5uqVpj5oysQKY/WUkeVzOM+Mk9fg8GxRTSjBcw==" }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "13.5.1", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.5.1.tgz", - "integrity": "sha512-EQGeE4S5c9v06jje9gr4UlxqUEA+zrsgPi6kg9VwR+dQHirzbnVJISF69UfKVkmLntknZJJI9XpWPB6q0Z7mTg==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.6.tgz", + "integrity": "sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==", "cpu": [ "arm64" ], @@ -1495,26 +1961,10 @@ "node": ">= 10" } }, - "node_modules/@next/swc-win32-ia32-msvc": { - "version": "13.5.1", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.5.1.tgz", - "integrity": "sha512-1y31Q6awzofVjmbTLtRl92OX3s+W0ZfO8AP8fTnITcIo9a6ATDc/eqa08fd6tSpFu6IFpxOBbdevOjwYTGx/AQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "13.5.1", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.5.1.tgz", - "integrity": "sha512-+9XBQizy7X/GuwNegq+5QkkxAPV7SBsIwapVRQd9WSvvU20YO23B3bZUpevdabi4fsd25y9RJDDncljy/V54ww==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.6.tgz", + "integrity": "sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==", "cpu": [ "x64" ], @@ -4304,6 +4754,15 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -4370,17 +4829,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.16.0" - } - }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -4927,6 +5375,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/detect-node-es": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", @@ -4940,9 +5398,9 @@ "license": "Apache-2.0" }, "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", "devOptional": true, "license": "BSD-3-Clause", "engines": { @@ -6071,12 +6529,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", @@ -6149,12 +6601,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -6818,9 +7264,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -6966,9 +7412,9 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "license": "MIT" }, "node_modules/lodash.merge": { @@ -7129,47 +7575,53 @@ "license": "MIT" }, "node_modules/next": { - "version": "13.5.1", - "resolved": "https://registry.npmjs.org/next/-/next-13.5.1.tgz", - "integrity": "sha512-GIudNR7ggGUZoIL79mSZcxbXK9f5pwAIPZxEM8+j2yLqv5RODg4TkmUlaKSYVqE1bPQueamXSqdC3j7axiTSEg==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/next/-/next-16.1.6.tgz", + "integrity": "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==", "license": "MIT", "dependencies": { - "@next/env": "13.5.1", - "@swc/helpers": "0.5.2", - "busboy": "1.6.0", - "caniuse-lite": "^1.0.30001406", - "postcss": "8.4.14", - "styled-jsx": "5.1.1", - "watchpack": "2.4.0", - "zod": "3.21.4" + "@next/env": "16.1.6", + "@swc/helpers": "0.5.15", + "baseline-browser-mapping": "^2.8.3", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" }, "bin": { "next": "dist/bin/next" }, "engines": { - "node": ">=16.14.0" + "node": ">=20.9.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "13.5.1", - "@next/swc-darwin-x64": "13.5.1", - "@next/swc-linux-arm64-gnu": "13.5.1", - "@next/swc-linux-arm64-musl": "13.5.1", - "@next/swc-linux-x64-gnu": "13.5.1", - "@next/swc-linux-x64-musl": "13.5.1", - "@next/swc-win32-arm64-msvc": "13.5.1", - "@next/swc-win32-ia32-msvc": "13.5.1", - "@next/swc-win32-x64-msvc": "13.5.1" + "@next/swc-darwin-arm64": "16.1.6", + "@next/swc-darwin-x64": "16.1.6", + "@next/swc-linux-arm64-gnu": "16.1.6", + "@next/swc-linux-arm64-musl": "16.1.6", + "@next/swc-linux-x64-gnu": "16.1.6", + "@next/swc-linux-x64-musl": "16.1.6", + "@next/swc-win32-arm64-msvc": "16.1.6", + "@next/swc-win32-x64-msvc": "16.1.6", + "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "peerDependenciesMeta": { "@opentelemetry/api": { "optional": true }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, "sass": { "optional": true } @@ -7186,18 +7638,18 @@ } }, "node_modules/next/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.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", "license": "Apache-2.0", "dependencies": { - "tslib": "^2.4.0" + "tslib": "^2.8.0" } }, "node_modules/next/node_modules/postcss": { - "version": "8.4.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", - "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "funding": [ { "type": "opencollective", @@ -7206,11 +7658,15 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.4", + "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, @@ -7218,15 +7674,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/next/node_modules/zod": { - "version": "3.21.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz", - "integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", @@ -8327,9 +8774,9 @@ } }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -8384,6 +8831,51 @@ "node": ">= 0.4" } }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -8545,14 +9037,6 @@ "node": ">= 0.4" } }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -8772,9 +9256,9 @@ } }, "node_modules/styled-jsx": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", - "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", "license": "MIT", "dependencies": { "client-only": "0.0.1" @@ -8783,7 +9267,7 @@ "node": ">= 12.0.0" }, "peerDependencies": { - "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" }, "peerDependenciesMeta": { "@babel/core": { @@ -8832,9 +9316,10 @@ } }, "node_modules/sucrase/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -9481,19 +9966,6 @@ "d3-timer": "^3.0.1" } }, - "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/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", diff --git a/package.json b/package.json index 7d03d34..9902e63 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "eslint-config-next": "13.5.1", "input-otp": "^1.2.4", "lucide-react": "^0.446.0", - "next": "13.5.1", + "next": "^16.1.6", "next-themes": "^0.3.0", "postcss": "^8.4.30", "react": "18.2.0", diff --git a/src/app/auth/signin/page.tsx b/src/app/auth/signin/page.tsx index 8d97ae5..e84d087 100644 --- a/src/app/auth/signin/page.tsx +++ b/src/app/auth/signin/page.tsx @@ -62,7 +62,7 @@ export default function SignIn() { {/* Magic link form (optional) */} - {/* +
or
@@ -107,7 +107,7 @@ export default function SignIn() { {message.text}
)} - */} +
diff --git a/src/app/courses/[courseId]/page.tsx b/src/app/courses/[courseId]/page.tsx index 0842b1e..d27876e 100644 --- a/src/app/courses/[courseId]/page.tsx +++ b/src/app/courses/[courseId]/page.tsx @@ -8,14 +8,18 @@ import CoursePageStats from "@/components/courses/course_page/CoursePageStats"; import CoursePageReviews from "@/components/courses/course_page/CoursePageReviews"; import RateThisCourse from "@/components/courses/course_page/RateThisCourse"; import Example from "@/components/courses/course_page/CoursePageLoader"; +import { use } from 'react'; +// -export default function CoursePage({ params }: { params: { courseId: string } }) { + +export default function CoursePage({ params }: { params: Promise<{ courseId: string }> }) { + const { courseId } = use(params); const { courses, isLoading } = useCourses(); const [averageRating, setAverageRating] = useState(0); const [reviewCount, setReviewCount] = useState(0); const [courseUUID, setCourseUUID] = useState(null); - const course = courses.find((course) => course.id === params.courseId); + const course = courses.find((course) => course.id === courseId); /* ---------- Fetch Course UUID from Supabase ---------- */ useEffect(() => { diff --git a/src/components/common/VoteButton.tsx b/src/components/common/VoteButton.tsx new file mode 100644 index 0000000..90d9c00 --- /dev/null +++ b/src/components/common/VoteButton.tsx @@ -0,0 +1,202 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { ChevronUp, ChevronDown } from 'lucide-react'; +import { VoteType } from '@/hooks/useVote'; + +interface VoteButtonProps { + reviewId: string; + initialVoteType?: VoteType; + initialVoteCount?: number; + onVote?: (reviewId: string, voteType: VoteType) => void; + size?: 'sm' | 'md' | 'lg'; +} + +export function VoteButton({ + reviewId, + initialVoteType = null, + initialVoteCount = 0, + onVote, + size = 'md', +}: VoteButtonProps) { + const [currentVote, setCurrentVote] = useState(initialVoteType); + const [voteCount, setVoteCount] = useState(initialVoteCount); + const [isLoading, setIsLoading] = useState(false); + + // Update state when props change (important for late-loading vote data) + useEffect(() => { + setCurrentVote(initialVoteType); + }, [initialVoteType]); + + useEffect(() => { + setVoteCount(initialVoteCount); + }, [initialVoteCount]); + + // Size variants (Reddit-style vertical layout) + const sizes = { + sm: { + container: 'gap-0', + icon: 'w-3.5 h-3.5', + text: 'text-[10px]', + padding: 'p-0.5', + }, + md: { + container: 'gap-1', + icon: 'w-5 h-5', + text: 'text-sm', + padding: 'p-1', + }, + lg: { + container: 'gap-1.5', + icon: 'w-6 h-6', + text: 'text-base', + padding: 'p-1.5', + }, + }; + + const handleVote = async (voteType: 'helpful' | 'unhelpful') => { + if (isLoading) return; + + setIsLoading(true); + + try { + const oldVote = currentVote; + const oldCount = voteCount; + + // Optimistic update + let newVote: VoteType; + let newCount = voteCount; + + if (currentVote === voteType) { + // Toggle off - remove vote + newVote = null; + if (voteType === 'helpful') newCount--; + else newCount++; + } else { + // Switch vote or add new vote + newVote = voteType; + + // Remove old vote effect + if (oldVote === 'helpful') newCount--; + else if (oldVote === 'unhelpful') newCount++; + + // Add new vote effect + if (voteType === 'helpful') newCount++; + else newCount--; + } + + setCurrentVote(newVote); + setVoteCount(newCount); + + // API call + const response = await fetch('/api/ratings/vote', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ review_id: reviewId, vote_type: voteType }), + }); + + if (!response.ok) { + throw new Error('Failed to vote'); + } + + const data = await response.json(); + + if (!data.success) { + throw new Error(data.error || 'Failed to vote'); + } + + // Update with server response + setCurrentVote(data.vote_type); + + // Callback + if (onVote) { + onVote(reviewId, data.vote_type); + } + + } catch (error) { + console.error('Error voting:', error); + // Rollback on error + setCurrentVote(currentVote); + setVoteCount(voteCount); + } finally { + setIsLoading(false); + } + }; + + return ( +
+ {/* Upvote button */} + + + {/* Vote count display */} + 0 + ? 'text-gray-700 dark:text-gray-300' + : voteCount < 0 + ? 'text-gray-500 dark:text-gray-400' + : 'text-gray-500 dark:text-gray-400' + } + `} + > + {voteCount > 0 && '+'}{voteCount} + + + {/* Downvote button */} + +
+ ); +} \ No newline at end of file diff --git a/src/components/courses/course_page/CoursePageReviews.tsx b/src/components/courses/course_page/CoursePageReviews.tsx index 26d3b0f..ae8ac90 100644 --- a/src/components/courses/course_page/CoursePageReviews.tsx +++ b/src/components/courses/course_page/CoursePageReviews.tsx @@ -4,6 +4,7 @@ import React, { useEffect, useState } from "react"; import AddReviewButton from "../AddReviewButton"; import { ChevronRight, ChevronDown } from "lucide-react"; import { supabase } from "@/lib/supabase"; +import { VoteButton } from "@/components/common/VoteButton"; interface CoursePageReviewsProps { id: string; // Course ID @@ -11,7 +12,7 @@ interface CoursePageReviewsProps { } /* Single Review Card (vertical format) */ -const CourseReviewItem = ({ review }: { review: any }) => { +const CourseReviewItem = ({ review, userVote }: { review: any; userVote?: string | null }) => { const formattedDate = new Date(review.created_at).toLocaleString("en-IN", { dateStyle: "medium", timeStyle: "short", @@ -27,17 +28,34 @@ const CourseReviewItem = ({ review }: { review: any }) => { return (
-
-

- {getAnonymousName(review.anonymous_id)} -

- - {formattedDate} - +
+ {/* Left side - Review content */} +
+
+

+ {getAnonymousName(review.anonymous_id)} +

+ + {formattedDate} + +
+ +

+ {review.comment || "No comment provided."} +

+
+ + {/* Right side - Vote Button */} +
+ +
-

- {review.comment || "No comment provided."} -

); }; @@ -45,36 +63,56 @@ const CourseReviewItem = ({ review }: { review: any }) => { /* Main Reviews Component */ const CoursePageReviews = ({ id, reviewCount }: CoursePageReviewsProps) => { const [reviews, setReviews] = useState([]); + const [userVotes, setUserVotes] = useState>({}); const [loading, setLoading] = useState(true); - const [showAll, setShowAll] = useState(false); // Toggle for View All + const [showAll, setShowAll] = useState(false); useEffect(() => { - const fetchReviews = async () => { + const fetchReviewsAndVotes = async () => { setLoading(true); // Fetch all reviews - const { data, error } = await supabase + const { data: reviewsData, error: reviewsError } = await supabase .from("reviews") .select(` id, anonymous_id, comment, + votes, created_at `) .eq("target_id", id) .eq("target_type", "course") .order("created_at", { ascending: false }); - if (error) { - console.error("Error fetching reviews:", error.message); - } else { - setReviews(data || []); + if (reviewsError) { + console.error("Error fetching reviews:", reviewsError.message); + setLoading(false); + return; + } + + setReviews(reviewsData || []); + + // Fetch user's votes for these reviews + if (reviewsData && reviewsData.length > 0) { + const reviewIds = reviewsData.map(r => r.id).join(','); + + try { + const response = await fetch(`/api/ratings/vote?review_ids=${reviewIds}`); + const votesData = await response.json(); + + if (votesData.success) { + setUserVotes(votesData.votes || {}); + } + } catch (error) { + console.error("Error fetching user votes:", error); + } } setLoading(false); }; - fetchReviews(); + fetchReviewsAndVotes(); }, [id]); // Show only 3 unless expanded @@ -102,7 +140,11 @@ const CoursePageReviews = ({ id, reviewCount }: CoursePageReviewsProps) => {

) : ( displayedReviews.map((review) => ( - + )) )}
@@ -127,4 +169,4 @@ const CoursePageReviews = ({ id, reviewCount }: CoursePageReviewsProps) => { ); }; -export default CoursePageReviews; +export default CoursePageReviews; \ No newline at end of file diff --git a/src/hooks/useVote.ts b/src/hooks/useVote.ts new file mode 100644 index 0000000..347e457 --- /dev/null +++ b/src/hooks/useVote.ts @@ -0,0 +1,332 @@ +import { useState, useEffect, useCallback, useRef } from 'react'; +import { toast } from 'sonner'; + +export type VoteType = 'helpful' | 'unhelpful' | null; + +interface VoteState { + [reviewId: string]: VoteType; +} + +interface VoteCounts { + [reviewId: string]: { + helpful: number; + unhelpful: number; + }; +} + +interface UseVotesOptions { + reviewIds?: string[]; + initialCounts?: VoteCounts; + onVoteSuccess?: (reviewId: string, voteType: VoteType) => void; + onVoteError?: (error: Error) => void; +} + +interface UseVotesReturn { + votes: VoteState; + voteCounts: VoteCounts; + isLoading: boolean; + castVote: (reviewId: string, voteType: 'helpful' | 'unhelpful') => Promise; + removeVote: (reviewId: string) => Promise; + toggleVote: (reviewId: string, voteType: 'helpful' | 'unhelpful') => Promise; + getUserVote: (reviewId: string) => VoteType; + refreshVotes: (reviewIds?: string[]) => Promise; +} + +export function useVotes(options: UseVotesOptions = {}): UseVotesReturn { + const { reviewIds = [], initialCounts = {}, onVoteSuccess, onVoteError } = options; + + const [votes, setVotes] = useState({}); + const [voteCounts, setVoteCounts] = useState(initialCounts); + const [isLoading, setIsLoading] = useState(false); + + // Track pending operations to prevent race conditions + const pendingOperations = useRef>(new Set()); + + // Cache to store previous states for rollback + const rollbackCache = useRef>(new Map()); + + + //Fetch user votes for specified review IDs + + const fetchUserVotes = useCallback(async (ids: string[]) => { + if (ids.length === 0) return; + + try { + setIsLoading(true); + const response = await fetch(`/api/ratings/vote?review_ids=${ids.join(',')}`); + + if (!response.ok) { + throw new Error('Failed to fetch votes'); + } + + const data = await response.json(); + + if (data.success) { + setVotes(prev => ({ + ...prev, + ...data.votes, + })); + } + } catch (error) { + console.error('Error fetching votes:', error); + if (onVoteError) { + onVoteError(error instanceof Error ? error : new Error('Failed to fetch votes')); + } + } finally { + setIsLoading(false); + } + }, [onVoteError]); + + + //Initial fetch on mount or when reviewIds change + + useEffect(() => { + if (reviewIds.length > 0) { + fetchUserVotes(reviewIds); + } + }, [reviewIds.join(',')]); // eslint-disable-line react-hooks/exhaustive-deps + + + //Update vote counts optimistically + + const updateVoteCountsOptimistically = useCallback( + (reviewId: string, oldVote: VoteType, newVote: VoteType) => { + setVoteCounts(prev => { + const current = prev[reviewId] || { helpful: 0, unhelpful: 0 }; + let helpful = current.helpful; + let unhelpful = current.unhelpful; + + // Remove old vote + if (oldVote === 'helpful') helpful--; + if (oldVote === 'unhelpful') unhelpful--; + + // Add new vote + if (newVote === 'helpful') helpful++; + if (newVote === 'unhelpful') unhelpful++; + + return { + ...prev, + [reviewId]: { helpful, unhelpful }, + }; + }); + }, + [] + ); + + + //Rollback optimistic updates on error + + const rollbackVote = useCallback((reviewId: string) => { + const cached = rollbackCache.current.get(reviewId); + if (cached) { + setVotes(prev => ({ + ...prev, + [reviewId]: cached.vote, + })); + setVoteCounts(prev => ({ + ...prev, + [reviewId]: cached.counts, + })); + rollbackCache.current.delete(reviewId); + } + }, []); + + + //Cast or update a vote + + const castVote = useCallback( + async (reviewId: string, voteType: 'helpful' | 'unhelpful') => { + // Prevent concurrent operations on the same review + if (pendingOperations.current.has(reviewId)) { + return; + } + + pendingOperations.current.add(reviewId); + + try { + const oldVote = votes[reviewId] || null; + const oldCounts = voteCounts[reviewId] || { helpful: 0, unhelpful: 0 }; + + // Save state for potential rollback + rollbackCache.current.set(reviewId, { + vote: oldVote, + counts: oldCounts, + }); + + // Optimistic update + setVotes(prev => ({ + ...prev, + [reviewId]: voteType, + })); + updateVoteCountsOptimistically(reviewId, oldVote, voteType); + + // API call + const response = await fetch('/api/ratings/vote', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ review_id: reviewId, vote_type: voteType }), + }); + + if (!response.ok) { + throw new Error('Failed to cast vote'); + } + + const data = await response.json(); + + if (!data.success) { + throw new Error(data.error || 'Failed to cast vote'); + } + + // Update state based on server response + setVotes(prev => ({ + ...prev, + [reviewId]: data.vote_type, + })); + + // Clear rollback cache on success + rollbackCache.current.delete(reviewId); + + if (onVoteSuccess) { + onVoteSuccess(reviewId, data.vote_type); + } + + } catch (error) { + console.error('Error casting vote:', error); + + // Rollback on error + rollbackVote(reviewId); + + toast.error('Failed to cast vote. Please try again.'); + + if (onVoteError) { + onVoteError(error instanceof Error ? error : new Error('Failed to cast vote')); + } + } finally { + pendingOperations.current.delete(reviewId); + } + }, + [votes, voteCounts, updateVoteCountsOptimistically, rollbackVote, onVoteSuccess, onVoteError] + ); + + //Remove a vote + const removeVote = useCallback( + async (reviewId: string) => { + if (pendingOperations.current.has(reviewId)) { + return; + } + + pendingOperations.current.add(reviewId); + + try { + const oldVote = votes[reviewId] || null; + const oldCounts = voteCounts[reviewId] || { helpful: 0, unhelpful: 0 }; + + // Save state for rollback + rollbackCache.current.set(reviewId, { + vote: oldVote, + counts: oldCounts, + }); + + // Optimistic update + setVotes(prev => ({ + ...prev, + [reviewId]: null, + })); + updateVoteCountsOptimistically(reviewId, oldVote, null); + + // API call + const response = await fetch('/api/ratings/vote', { + method: 'DELETE', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ review_id: reviewId }), + }); + + if (!response.ok) { + throw new Error('Failed to remove vote'); + } + + const data = await response.json(); + + if (!data.success) { + throw new Error(data.error || 'Failed to remove vote'); + } + + // Clear rollback cache + rollbackCache.current.delete(reviewId); + + if (onVoteSuccess) { + onVoteSuccess(reviewId, null); + } + + } catch (error) { + console.error('Error removing vote:', error); + + // Rollback + rollbackVote(reviewId); + + toast.error('Failed to remove vote. Please try again.'); + + if (onVoteError) { + onVoteError(error instanceof Error ? error : new Error('Failed to remove vote')); + } + } finally { + pendingOperations.current.delete(reviewId); + } + }, + [votes, voteCounts, updateVoteCountsOptimistically, rollbackVote, onVoteSuccess, onVoteError] + ); + + //Toggle vote - if same type, remove; otherwise update + + const toggleVote = useCallback( + async (reviewId: string, voteType: 'helpful' | 'unhelpful') => { + const currentVote = votes[reviewId]; + + if (currentVote === voteType) { + // Same vote - remove it + await removeVote(reviewId); + } else { + // Different vote or no vote - cast it + await castVote(reviewId, voteType); + } + }, + [votes, castVote, removeVote] + ); + + /** + * Get user's vote for a specific review + */ + const getUserVote = useCallback( + (reviewId: string): VoteType => { + return votes[reviewId] || null; + }, + [votes] + ); + + /** + * Manually refresh votes + */ + const refreshVotes = useCallback( + async (ids?: string[]) => { + const idsToFetch = ids || reviewIds; + if (idsToFetch.length > 0) { + await fetchUserVotes(idsToFetch); + } + }, + [reviewIds, fetchUserVotes] + ); + + return { + votes, + voteCounts, + isLoading, + castVote, + removeVote, + toggleVote, + getUserVote, + refreshVotes, + }; +} \ No newline at end of file diff --git a/src/migrations/migration.sql b/src/migrations/migration.sql index e33b914..c3b6326 100644 --- a/src/migrations/migration.sql +++ b/src/migrations/migration.sql @@ -120,7 +120,7 @@ CREATE TABLE flags ( CREATE INDEX idx_reviews_target ON reviews(target_id, target_type); CREATE INDEX idx_reviews_anonymous_id ON reviews(anonymous_id); CREATE INDEX idx_votes_review_id ON votes(review_id); -CREATE INDEX idx_flagsMathematics-I_review_id ON flags(review_id); +CREATE INDEX idx_flags_review_id ON flags(review_id); CREATE INDEX idx_flags_status ON flags(status); -- Create function to update course ratings @@ -252,8 +252,13 @@ END; $$ LANGUAGE plpgsql; -- Create function to update review votes +-- IMPORTANT: SECURITY DEFINER allows this function to bypass RLS policies +-- This is necessary so the trigger can update the reviews table CREATE OR REPLACE FUNCTION update_review_votes() -RETURNS TRIGGER AS $$ +RETURNS TRIGGER +SECURITY DEFINER +SET search_path = public +AS $$ BEGIN IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN UPDATE reviews @@ -481,15 +486,6 @@ CREATE POLICY flag_update ON flags CREATE POLICY flag_delete ON flags FOR DELETE USING (is_admin()); --- --- --- --- --- THIS IS THE CORRECTED FUNCTION --- --- --- --- -- Create function to create an anonymous user on signup CREATE OR REPLACE FUNCTION handle_new_user() RETURNS TRIGGER AS $$ @@ -516,5 +512,4 @@ $$ LANGUAGE plpgsql SECURITY DEFINER; -- Trigger to create user profile after auth user is created CREATE TRIGGER on_auth_user_created AFTER INSERT ON auth.users - FOR EACH ROW EXECUTE FUNCTION handle_new_user(); - \ No newline at end of file + FOR EACH ROW EXECUTE FUNCTION handle_new_user(); \ No newline at end of file diff --git a/src/pages/api/ratings/vote/index.ts b/src/pages/api/ratings/vote/index.ts new file mode 100644 index 0000000..8477b61 --- /dev/null +++ b/src/pages/api/ratings/vote/index.ts @@ -0,0 +1,215 @@ +import { NextApiRequest, NextApiResponse } from 'next'; +import { createClient } from '@/utils/supabase/server-pages'; + +/** + * Vote API Route + * Handles helpful/unhelpful functionality for course and professor reviews + */ + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + const supabase = createClient(req, res); + // Handle POST - Cast or update vote + if (req.method === 'POST') { + try { + const { review_id, vote_type } = req.body; + + // Validate input + if (!review_id || !vote_type) { + return res.status(400).json({ error: 'review_id and vote_type are required' }); + } + + if (!['helpful', 'unhelpful'].includes(vote_type)) { + return res.status(400).json({ error: 'vote_type must be "helpful" or "unhelpful"' }); + } + + // Get user session + const { data: { user }, error: authError } = await supabase.auth.getUser(); + + if (authError && authError.message !== 'Auth session missing!') { + console.error('Auth error:', authError); + return res.status(401).json({ error: 'Authentication error' }); + } + + // Get anonymous ID + const { data: anonData, error: anonError } = await supabase.rpc('get_anonymous_id'); + + if (anonError || !anonData) { + console.error('Error getting anonymous ID:', anonError); + return res.status(500).json({ error: 'Failed to get user identifier' }); + } + + const anonymous_id = anonData; + + // Check if user already voted + const { data: existingVote, error: checkError } = await supabase + .from('votes') + .select('id, vote_type') + .eq('review_id', review_id) + .eq('anonymous_id', anonymous_id) + .single(); + + if (checkError && checkError.code !== 'PGRST116') { + console.error('Error checking existing vote:', checkError); + return res.status(500).json({ error: 'Failed to check existing vote' }); + } + + // Case 1: Same vote type - remove vote (toggle off) + if (existingVote && existingVote.vote_type === vote_type) { + const { error: deleteError } = await supabase + .from('votes') + .delete() + .eq('id', existingVote.id); + + if (deleteError) { + console.error('Error deleting vote:', deleteError); + return res.status(500).json({ error: 'Failed to remove vote' }); + } + + return res.status(200).json({ + success: true, + action: 'removed', + vote_type: null, + }); + } + + // Case 2: Different vote type - update vote + if (existingVote && existingVote.vote_type !== vote_type) { + const { error: updateError } = await supabase + .from('votes') + .update({ vote_type, created_at: new Date().toISOString() }) + .eq('id', existingVote.id); + + if (updateError) { + console.error('Error updating vote:', updateError); + return res.status(500).json({ error: 'Failed to update vote' }); + } + + return res.status(200).json({ + success: true, + action: 'updated', + vote_type, + }); + } + + // Case 3: New vote - insert + const { error: insertError } = await supabase + .from('votes') + .insert({ + review_id, + anonymous_id, + vote_type, + }); + + if (insertError) { + console.error('Error inserting vote:', insertError); + return res.status(500).json({ error: 'Failed to cast vote' }); + } + + return res.status(200).json({ + success: true, + action: 'created', + vote_type, + }); + + } catch (error) { + console.error('Unexpected error in vote route:', error); + return res.status(500).json({ error: 'Internal server error' }); + } + } + + // Handle GET - Fetch user votes + else if (req.method === 'GET') { + try { + const { review_ids } = req.query; + + if (!review_ids || typeof review_ids !== 'string') { + return res.status(400).json({ error: 'review_ids parameter is required' }); + } + + // Get anonymous ID + const { data: anonData, error: anonError } = await supabase.rpc('get_anonymous_id'); + + if (anonError || !anonData) { + console.error('Error getting anonymous ID:', anonError); + return res.status(500).json({ error: 'Failed to get user identifier' }); + } + + const anonymous_id = anonData; + const reviewIdArray = review_ids.split(',').map(id => id.trim()); + + // Batch fetch votes + const { data: votes, error: fetchError } = await supabase + .from('votes') + .select('review_id, vote_type') + .eq('anonymous_id', anonymous_id) + .in('review_id', reviewIdArray); + + if (fetchError) { + console.error('Error fetching votes:', fetchError); + return res.status(500).json({ error: 'Failed to fetch votes' }); + } + + // Transform to object map + const votesMap = (votes || []).reduce((acc, vote) => { + acc[vote.review_id] = vote.vote_type; + return acc; + }, {} as Record); + + return res.status(200).json({ + success: true, + votes: votesMap, + }); + + } catch (error) { + console.error('Unexpected error in vote GET route:', error); + return res.status(500).json({ error: 'Internal server error' }); + } + } + + // Handle DELETE - Remove vote + else if (req.method === 'DELETE') { + try { + const { review_id } = req.body; + + if (!review_id) { + return res.status(400).json({ error: 'review_id is required' }); + } + + // Get anonymous ID + const { data: anonData, error: anonError } = await supabase.rpc('get_anonymous_id'); + + if (anonError || !anonData) { + console.error('Error getting anonymous ID:', anonError); + return res.status(500).json({ error: 'Failed to get user identifier' }); + } + + const anonymous_id = anonData; + + // Delete the vote + const { error: deleteError } = await supabase + .from('votes') + .delete() + .eq('review_id', review_id) + .eq('anonymous_id', anonymous_id); + + if (deleteError) { + console.error('Error deleting vote:', deleteError); + return res.status(500).json({ error: 'Failed to delete vote' }); + } + + return res.status(200).json({ + success: true, + action: 'deleted', + }); + + } catch (error) { + console.error('Unexpected error in vote DELETE route:', error); + return res.status(500).json({ error: 'Internal server error' }); + } + } + + // Method not allowed + else { + return res.status(405).json({ error: 'Method not allowed' }); + } +} \ No newline at end of file diff --git a/src/pages/api/ratings/vote/route.ts b/src/pages/api/ratings/vote/route.ts deleted file mode 100644 index a211f6b..0000000 --- a/src/pages/api/ratings/vote/route.ts +++ /dev/null @@ -1,178 +0,0 @@ -import { NextResponse } from 'next/server'; -import { supabase } from '@/lib/supabase'; -import { supabaseAdmin } from '@/lib/supabase-admin'; -import { VoteInsert } from '@/types/supabase'; - -// POST /api/ratings/vote - Vote on a rating (helpful/unhelpful) -export async function POST(request: Request) { - try { - // Get session to verify authentication - const { data: { session } } = await supabase.auth.getSession(); - - if (!session?.user?.id) { - return NextResponse.json( - { error: 'Authentication required' }, - { status: 401 } - ); - } - - // Get JSON data from request - const json = await request.json(); - const { ratingId, voteType } = json; - - // Validate required fields - if (!ratingId) { - return NextResponse.json( - { error: 'Missing rating ID' }, - { status: 400 } - ); - } - - // Validate vote type - if (voteType !== 'helpful' && voteType !== 'unhelpful') { - return NextResponse.json( - { error: 'Invalid vote type' }, - { status: 400 } - ); - } - - // Get the anonymous ID for the authenticated user - const { data: anonymousData, error: anonymousError } = await supabase - .from('users') - .select('anonymous_id') - .eq('auth_id', session.user.id) - .single(); - - if (anonymousError || !anonymousData?.anonymous_id) { - console.error('Error fetching anonymous ID:', anonymousError); - return NextResponse.json( - { error: 'Failed to verify anonymous identity' }, - { status: 500 } - ); - } - - // Check if user has already voted on this rating - const { data: existingVote, error: checkError } = await supabase - .from('rating_votes') - .select('id, vote_type') - .eq('rating_id', ratingId) - .eq('anonymous_id', anonymousData.anonymous_id) - .single(); - - // Transaction to handle vote logic - const { data, error } = await supabaseAdmin.rpc('handle_rating_vote', { - p_rating_id: ratingId, - p_anonymous_id: anonymousData.anonymous_id, - p_vote_type: voteType, - p_existing_vote_type: existingVote?.vote_type || null - }); - - if (error) { - console.error('Error processing vote:', error); - return NextResponse.json( - { error: 'Failed to process vote' }, - { status: 500 } - ); - } - - return NextResponse.json({ - success: true, - data: { - voteType, - helpfulnessScore: data.new_helpfulness_score - } - }); - - } catch (error) { - console.error('Unexpected error in rating vote API:', error); - return NextResponse.json( - { error: 'An unexpected error occurred' }, - { status: 500 } - ); - } -} - -// DELETE /api/ratings/vote - Remove a vote from a rating -export async function DELETE(request: Request) { - try { - // Get session to verify authentication - const { data: { session } } = await supabase.auth.getSession(); - - if (!session?.user?.id) { - return NextResponse.json( - { error: 'Authentication required' }, - { status: 401 } - ); - } - - const { searchParams } = new URL(request.url); - const ratingId = searchParams.get('ratingId'); - - // Validate required fields - if (!ratingId) { - return NextResponse.json( - { error: 'Missing rating ID' }, - { status: 400 } - ); - } - - // Get the anonymous ID for the authenticated user - const { data: anonymousData, error: anonymousError } = await supabase - .from('users') - .select('anonymous_id') - .eq('auth_id', session.user.id) - .single(); - - if (anonymousError || !anonymousData?.anonymous_id) { - console.error('Error fetching anonymous ID:', anonymousError); - return NextResponse.json( - { error: 'Failed to verify anonymous identity' }, - { status: 500 } - ); - } - - // Check if user has a vote on this rating - const { data: existingVote, error: checkError } = await supabase - .from('rating_votes') - .select('id, vote_type') - .eq('rating_id', ratingId) - .eq('anonymous_id', anonymousData.anonymous_id) - .single(); - - if (!existingVote) { - return NextResponse.json( - { error: 'No vote found to remove' }, - { status: 404 } - ); - } - - // Transaction to remove vote and update helpfulness score - const { data, error } = await supabaseAdmin.rpc('remove_rating_vote', { - p_rating_id: ratingId, - p_anonymous_id: anonymousData.anonymous_id, - p_vote_type: existingVote.vote_type - }); - - if (error) { - console.error('Error removing vote:', error); - return NextResponse.json( - { error: 'Failed to remove vote' }, - { status: 500 } - ); - } - - return NextResponse.json({ - success: true, - data: { - helpfulnessScore: data.new_helpfulness_score - } - }); - - } catch (error) { - console.error('Unexpected error in rating vote API:', error); - return NextResponse.json( - { error: 'An unexpected error occurred' }, - { status: 500 } - ); - } -} \ No newline at end of file diff --git a/src/utils/supabase/server-pages.ts b/src/utils/supabase/server-pages.ts new file mode 100644 index 0000000..71baf8f --- /dev/null +++ b/src/utils/supabase/server-pages.ts @@ -0,0 +1,24 @@ +import { createServerClient } from '@supabase/ssr' +import { NextApiRequest, NextApiResponse } from 'next' + +export function createClient(req: NextApiRequest, res: NextApiResponse) { + return createServerClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, + { + cookies: { + getAll() { + return Object.keys(req.cookies).map((name) => ({ + name, + value: req.cookies[name] || '', + })) + }, + setAll(cookiesToSet) { + cookiesToSet.forEach(({ name, value, options }) => { + res.setHeader('Set-Cookie', `${name}=${value}; Path=/; ${options ? Object.entries(options).map(([k, v]) => `${k}=${v}`).join('; ') : ''}`) + }) + }, + }, + } + ) +} \ No newline at end of file diff --git a/src/utils/supabase/server.ts b/src/utils/supabase/server.ts index ac3942f..87a6ba7 100644 --- a/src/utils/supabase/server.ts +++ b/src/utils/supabase/server.ts @@ -2,7 +2,7 @@ import { createServerClient } from '@supabase/ssr' import { cookies } from 'next/headers' export async function createClient() { - const cookieStore = cookies() + const cookieStore = await cookies() return createServerClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, diff --git a/tsconfig.json b/tsconfig.json index 2639f89..50d9095 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,11 @@ { "compilerOptions": { "target": "ES2020", - "lib": ["dom", "dom.iterable", "esnext"], + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], "allowJs": true, "skipLibCheck": true, "strict": true, @@ -11,7 +15,7 @@ "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, - "jsx": "preserve", + "jsx": "react-jsx", "incremental": true, "plugins": [ { @@ -19,13 +23,23 @@ } ], "paths": { - "@/*": ["./src/*"] + "@/*": [ + "./src/*" + ] } }, "ts-node": { "esm": true, "experimentalSpecifierResolution": "node" }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] }