diff --git a/.env.example b/.env.example index 50a9283..424d9b9 100644 --- a/.env.example +++ b/.env.example @@ -11,6 +11,7 @@ # Prisma # https://www.prisma.io/docs/reference/database-reference/connection-urls#env +# Relative SQLite paths are relative to the project root for both migrations and the app. DATABASE_URL="file:./db.sqlite" # Next Auth @@ -42,4 +43,4 @@ LDAP_ADMIN_GROUP="" # Keycloak KEYCLOAK_ISSUER="" KEYCLOAK_CLIENT_ID="" -KEYCLOAK_CLIENT_SECRET="" \ No newline at end of file +KEYCLOAK_CLIENT_SECRET="" diff --git a/README.md b/README.md index 5a060df..b09e57d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # LabEats **LabEats allows a group of people to manage money in combination with item-sales, procurement and group-orders in a moderated prepaid fashion.** -A tool to manage (food)(orders) in the Aerospace Lab (an awesome club in Germany).\ +A tool to manage food orders in the Aerospace Lab (an awesome club in Germany).\ It is designed with our club in mind, but can surely be adapted to fit more needs. @@ -46,11 +46,20 @@ Only secured by basic HTTP auth (set in `./nginx/prismaStudio-htpasswd`), this p ## Development - This is a NextJS project. Some file-conventions apply. +Getting started: + +Install dependencies: +`npm install` \ +copy `.env.example` to `.env` and set `NEXTAUTH_SECRET` (e.g. `openssl rand -base64 32`) \ +create a small database to test edits: +`npm run db:seed` \ +run (PowerShell): +`$env:DATABASE_URL="file:./screenshots.db"; npm run dev` \ +run (bash/zsh): +`DATABASE_URL="file:./screenshots.db" npm run dev` -Start Developing: -`npm run dev` +Log in via "Server Konsole" with `sally.ride@example.com` (admin). The magic link is printed in the terminal. Update types based on current schema: `npx prisma generate` @@ -64,23 +73,17 @@ Create DB Migrations (for production) `npx prisma migrate dev` ### Open ToDos -- Automatisches abmelden aller Accounts nach update (via changes) -- Android Zahlentastatur Komma ausgeblendet - Kostenanpassung bei Gruppenbestellung greift für alle der gleichen Kategorien #### Nice Improvements: -- Item-Page: Kategorie-filter für user persistieren -- Gruppen-Split - Verrechnungskonten-Auszahlung für Admins auch selbst möglich -- Gruppen-Kauf auf eine/mehrere Kategorien beschränken +- Gruppen-Kauf auf mehrere Kategorien beschränken (eine Kategorie geht bereits) - Personen einen "Projekt-Tag" geben - Verrechnungkonten global anzeigen -- Vorbesteller-Items mit Fix-Preis (Default-Feld vorausfüllen) - Ofengebühr für mehrfachbestellungen optional deaktivieren - write test-cases (https://www.prisma.io/docs/orm/prisma-client/testing/integration-testing) --- ## Questions & Contact - Stuck somewhere? Or you want to adopt LabEats to your needs? \ You can contact me at coding@13bytes.de or create a [issue](https://github.com/13Bytes/LabFoodDesk/issues). diff --git a/eslint.config.mts b/eslint.config.mts index 25d7a26..e89f106 100644 --- a/eslint.config.mts +++ b/eslint.config.mts @@ -5,7 +5,7 @@ import nextVitals from "eslint-config-next/core-web-vitals" export default defineConfig([ { - ignores: ["src/generated/prisma/**"], + ignores: ["src/generated/prisma/**", ".next/**"], }, { files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], diff --git a/next.config.mjs b/next.config.mjs index 9ef624f..e1b85b9 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -8,6 +8,10 @@ const config = { reactStrictMode: true, output: 'standalone', + // new value on every build - sessions of older builds are rejected (src/server/sessionBuild.ts) + env: { + SESSION_BUILD_ID: String(Date.now()), + }, /** * If you have the "experimental: { appDir: true }" setting enabled, then you * must comment the below `i18n` config out. diff --git a/package-lock.json b/package-lock.json index aa475d1..6323d9e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -262,14 +262,14 @@ } }, "node_modules/@babel/helpers": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", - "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.10.tgz", + "integrity": "sha512-EMCx3AGZ3WfAl6ht7AyQoPm2Lh+BGYEMdc4Q6Hf7uSCDOZFuLq44qNwL+iaim8VlyYhRAo4X43+weadK8ZB4Yg==", "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7" + "@babel/types": "^7.29.8" }, "engines": { "node": ">=6.9.0" @@ -332,9 +332,9 @@ } }, "node_modules/@babel/types": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", - "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.29.7", @@ -762,29 +762,43 @@ } }, "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, "engines": { "node": ">=18.18.0" } }, "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanfs/core": "^0.19.1", + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" } }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -824,9 +838,9 @@ } }, "node_modules/@img/sharp-darwin-arm64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.3.tgz", - "integrity": "sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.5.tgz", + "integrity": "sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==", "cpu": [ "arm64" ], @@ -842,13 +856,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.3.2" + "@img/sharp-libvips-darwin-arm64": "1.3.4" } }, "node_modules/@img/sharp-darwin-x64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.3.tgz", - "integrity": "sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.5.tgz", + "integrity": "sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==", "cpu": [ "x64" ], @@ -864,20 +878,20 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.3.2" + "@img/sharp-libvips-darwin-x64": "1.3.4" } }, "node_modules/@img/sharp-freebsd-wasm32": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.3.tgz", - "integrity": "sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.5.tgz", + "integrity": "sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==", "license": "Apache-2.0", "optional": true, "os": [ "freebsd" ], "dependencies": { - "@img/sharp-wasm32": "0.35.3" + "@img/sharp-wasm32": "0.35.5" }, "engines": { "node": ">=20.9.0" @@ -887,9 +901,9 @@ } }, "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.2.tgz", - "integrity": "sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.4.tgz", + "integrity": "sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==", "cpu": [ "arm64" ], @@ -903,9 +917,9 @@ } }, "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.2.tgz", - "integrity": "sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.4.tgz", + "integrity": "sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==", "cpu": [ "x64" ], @@ -919,9 +933,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.2.tgz", - "integrity": "sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.4.tgz", + "integrity": "sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==", "cpu": [ "arm" ], @@ -938,9 +952,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.2.tgz", - "integrity": "sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.4.tgz", + "integrity": "sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==", "cpu": [ "arm64" ], @@ -957,9 +971,9 @@ } }, "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.2.tgz", - "integrity": "sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.4.tgz", + "integrity": "sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==", "cpu": [ "ppc64" ], @@ -976,9 +990,9 @@ } }, "node_modules/@img/sharp-libvips-linux-riscv64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.2.tgz", - "integrity": "sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.4.tgz", + "integrity": "sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==", "cpu": [ "riscv64" ], @@ -995,9 +1009,9 @@ } }, "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.2.tgz", - "integrity": "sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.4.tgz", + "integrity": "sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==", "cpu": [ "s390x" ], @@ -1014,9 +1028,9 @@ } }, "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.2.tgz", - "integrity": "sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.4.tgz", + "integrity": "sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==", "cpu": [ "x64" ], @@ -1033,9 +1047,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.2.tgz", - "integrity": "sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.4.tgz", + "integrity": "sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==", "cpu": [ "arm64" ], @@ -1052,9 +1066,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.2.tgz", - "integrity": "sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.4.tgz", + "integrity": "sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==", "cpu": [ "x64" ], @@ -1071,9 +1085,9 @@ } }, "node_modules/@img/sharp-linux-arm": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.3.tgz", - "integrity": "sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.5.tgz", + "integrity": "sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==", "cpu": [ "arm" ], @@ -1092,13 +1106,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.3.2" + "@img/sharp-libvips-linux-arm": "1.3.4" } }, "node_modules/@img/sharp-linux-arm64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.3.tgz", - "integrity": "sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.5.tgz", + "integrity": "sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==", "cpu": [ "arm64" ], @@ -1117,13 +1131,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.3.2" + "@img/sharp-libvips-linux-arm64": "1.3.4" } }, "node_modules/@img/sharp-linux-ppc64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.3.tgz", - "integrity": "sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.5.tgz", + "integrity": "sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==", "cpu": [ "ppc64" ], @@ -1142,13 +1156,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.3.2" + "@img/sharp-libvips-linux-ppc64": "1.3.4" } }, "node_modules/@img/sharp-linux-riscv64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.3.tgz", - "integrity": "sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.5.tgz", + "integrity": "sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==", "cpu": [ "riscv64" ], @@ -1167,13 +1181,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-riscv64": "1.3.2" + "@img/sharp-libvips-linux-riscv64": "1.3.4" } }, "node_modules/@img/sharp-linux-s390x": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.3.tgz", - "integrity": "sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.5.tgz", + "integrity": "sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==", "cpu": [ "s390x" ], @@ -1192,13 +1206,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.3.2" + "@img/sharp-libvips-linux-s390x": "1.3.4" } }, "node_modules/@img/sharp-linux-x64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.3.tgz", - "integrity": "sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.5.tgz", + "integrity": "sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==", "cpu": [ "x64" ], @@ -1217,13 +1231,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.3.2" + "@img/sharp-libvips-linux-x64": "1.3.4" } }, "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.3.tgz", - "integrity": "sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.5.tgz", + "integrity": "sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==", "cpu": [ "arm64" ], @@ -1242,13 +1256,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.3.2" + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4" } }, "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.3.tgz", - "integrity": "sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.5.tgz", + "integrity": "sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==", "cpu": [ "x64" ], @@ -1267,17 +1281,17 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.3.2" + "@img/sharp-libvips-linuxmusl-x64": "1.3.4" } }, "node_modules/@img/sharp-wasm32": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.3.tgz", - "integrity": "sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.5.tgz", + "integrity": "sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==", "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, "dependencies": { - "@emnapi/runtime": "^1.11.1" + "@emnapi/runtime": "^1.11.3" }, "engines": { "node": ">=20.9.0" @@ -1297,16 +1311,16 @@ } }, "node_modules/@img/sharp-webcontainers-wasm32": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.3.tgz", - "integrity": "sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.5.tgz", + "integrity": "sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==", "cpu": [ "wasm32" ], "license": "Apache-2.0", "optional": true, "dependencies": { - "@img/sharp-wasm32": "0.35.3" + "@img/sharp-wasm32": "0.35.5" }, "engines": { "node": ">=20.9.0" @@ -1316,9 +1330,9 @@ } }, "node_modules/@img/sharp-win32-arm64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.3.tgz", - "integrity": "sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.5.tgz", + "integrity": "sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==", "cpu": [ "arm64" ], @@ -1335,9 +1349,9 @@ } }, "node_modules/@img/sharp-win32-ia32": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.3.tgz", - "integrity": "sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.5.tgz", + "integrity": "sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==", "cpu": [ "ia32" ], @@ -1354,9 +1368,9 @@ } }, "node_modules/@img/sharp-win32-x64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.3.tgz", - "integrity": "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.5.tgz", + "integrity": "sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==", "cpu": [ "x64" ], @@ -1442,15 +1456,15 @@ } }, "node_modules/@next/env": { - "version": "16.3.1", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.3.1.tgz", - "integrity": "sha512-35G3xwkQUb2oETSDjFXGrVugknoayLFBh7vSE+yAcl9IP2zT9wyGwq7297AYHR11kJld807t5f8AJBs6WBzXsQ==", + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.4.2.tgz", + "integrity": "sha512-nTUTFH7G5/J/fmpkjVLegOkqyKOP0CEsJcMsyO4UCYeN1su+YMW/MfaSErnJskL7XM+UDK6aTvslxsRCyya/sA==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { - "version": "16.3.1", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.3.1.tgz", - "integrity": "sha512-B4SznlXwVpaLDa7Tbi6zLuueria2d/PmFDhXyDymPGrk2r1n/RMJmcn5FZq1L64k+Jsyte1lKvOr7lP9Xo80mQ==", + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.4.2.tgz", + "integrity": "sha512-gCTWlw0e+Y6kajpFyKKgA195jUJCRhlzJE0lV7ufe8a+ghIsANWZ0/FiJwg6DuOG1+OvRbcZDcFBoRh3mNJx0g==", "dev": true, "license": "MIT", "dependencies": { @@ -1459,9 +1473,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "16.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.3.1.tgz", - "integrity": "sha512-ABMIu2zQ7cnNIHm5ivKGwZwUrm0pAai3yiJ/gK/rF1c1VP9UOnj7XECbMKFdVKp9I9eMYq9NoDs1WXOoowxzJw==", + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.4.2.tgz", + "integrity": "sha512-ONkhFvZNFWlsEHOM/81AtxdyuJP2KRYKYR0JxPwvcu1AuPMMMgcL+wW2luldJvMtZwKtUxoSkCychW5wIeoYSw==", "cpu": [ "arm64" ], @@ -1475,9 +1489,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "16.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.3.1.tgz", - "integrity": "sha512-gNG21e/UnrroeScbY/QndUEdl0mF1FRibW7BBeYUz/5ABCepjqDdEdgr592vpzMtCn/m7FTjYq3TN4TpyDnutw==", + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.4.2.tgz", + "integrity": "sha512-luPCMbOsCoaF+kXJEUjmjmOZg1lC3BflhmUu+ZmW4tBu2vW/4AXak8JBFvePizGkxVmNiAw/r9ra+6UviCWtWQ==", "cpu": [ "x64" ], @@ -1491,9 +1505,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.3.1.tgz", - "integrity": "sha512-6B6Lw016iwNUQuaJoraMMTLh6TwHzFUtxipSScD1F3YyymcrRWkobodRS2ftIOkF5vrs4zNlyUrTC5YZQ9Lz5w==", + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.4.2.tgz", + "integrity": "sha512-xhwFTAncJyaCNy5KqKKuKaIIdCy1/1ZV1s5S1G0hhT7wGMkDTlfJ6aQbfRFnIG/s3/kDrxQ8aycVe+c5QnP8ow==", "cpu": [ "arm64" ], @@ -1510,9 +1524,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.3.1.tgz", - "integrity": "sha512-JUiPXZKK9wOhjf4MgDiH29GZLxfqOesbLtHq2pDxwH/WwscTRV2ToymnOTh1egzaZf0ueUf8T2+CeYTGHjW0Iw==", + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.4.2.tgz", + "integrity": "sha512-T1PNmfbPcxr5ndtWUL/3wQtLXsz808lq6sc2GrNT5k2oquF7UyqZddHSuAewHVsWsGXe6nsl9HoN1YNriVjuSg==", "cpu": [ "arm64" ], @@ -1529,9 +1543,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.3.1.tgz", - "integrity": "sha512-Uog9jsrmIRIL/lfvIp9htmskSNC7JcQsMVucXL2V2YY1y/D9IUN3LPEafqy0zRJ2cIU1SQ0V6F6TlffQ+pLAGg==", + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.4.2.tgz", + "integrity": "sha512-N9cDUOCGtkJG5PNKDFZUF3xXndrJJcpLp121uAGjIGvO/U2p9tfdNOM9htj7+5f72l0m8rMw/cvYacUJBQoudg==", "cpu": [ "x64" ], @@ -1548,9 +1562,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "16.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.3.1.tgz", - "integrity": "sha512-6yy3FT13KgUFOj5H8bl8w/6nKiJwHIvbtwh1V+1acsu+7y4tJjnemSa6mhsh53BeoVrlozE+fMgZhXH46WmjMA==", + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.4.2.tgz", + "integrity": "sha512-yY2uh1YhX6ufuYy2FCbw1dqblbhI57GLYAJfCkqD9auTv6pLrH67Uccjc3QMKBeCYAG6lkgu08ExmX1bbjejjQ==", "cpu": [ "x64" ], @@ -1567,9 +1581,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.3.1.tgz", - "integrity": "sha512-iOoN1QecUoGNZik536U/vtK43YwgyrCsGIkth52yIkl612n+0C9MjSnJbQAikISpb+WYRooBVhaDlUW7iZoKog==", + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.4.2.tgz", + "integrity": "sha512-j/ArWRIHpMMB6wYLMhr2puTguBt70lNU/Cmjl9p6dTDE8DGknYJMP9PdwPk2dqpCNL/KIX9yfngDvIesxJPmsg==", "cpu": [ "arm64" ], @@ -1583,9 +1597,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.3.1", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.3.1.tgz", - "integrity": "sha512-d/k+PpAriUPaeMJJOG7HUSdqfEX46FEPWU1p3/nm2ACmXhj9hFEWdFODUBIpkuijXYkfL90qZzTqVPRp4BW/hw==", + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.4.2.tgz", + "integrity": "sha512-AA4edyWHDko3NCSo0x6GomsdgFb0oR1J3MXU1b5xt6FRFg4AucrMPW3S0hCe/9onU0O1BbpGQBsqo1r0Gneu1g==", "cpu": [ "x64" ], @@ -1722,14 +1736,14 @@ "license": "Apache-2.0" }, "node_modules/@prisma/config": { - "version": "7.9.1", - "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.9.1.tgz", - "integrity": "sha512-4znKhxTmXmuPye9Z6pbIyYb5VZlkZ05qG1L6Dr4g+7oTwc6V50Bs9XirFBDdjWt+H/AabMn9aUnxBcvj8z05aA==", + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.10.1.tgz", + "integrity": "sha512-+0swF5ELUa3P/9cIR26+gpYjjCbGE0qvEBZhV4zMDkYsrtHjZ884MQbOnWFWXj600N0sSJNnHbtBqe12yDngWg==", "devOptional": true, "license": "Apache-2.0", "dependencies": { "c12": "3.3.4", - "deepmerge-ts": "7.1.5", + "deepmerge-ts": "8.0.2", "effect": "3.20.0", "empathic": "2.0.0" } @@ -1781,56 +1795,70 @@ } }, "node_modules/@prisma/engines": { - "version": "7.9.1", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.9.1.tgz", - "integrity": "sha512-UprXSMNXx2NF5ow4pqaQtE8OuBz6K78B0wc0tn2L28G5r933iWp1DR9Do2qWrsNvvFIP3x6mpEWnQtckMO0Uhg==", + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.10.1.tgz", + "integrity": "sha512-R6/ghv1Bu36FhCdQLFof1pNtyoWoeDzJLYeTKjgqb4eGtOAJx7ZqJtcm4pzkahLfZ9rYz8AkmsMDwkyiYbXPCQ==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.9.1", - "@prisma/engines-version": "7.9.0-1.e922089b7d7502aff4249d5da3420f6fa55fc6ad", - "@prisma/fetch-engine": "7.9.1", - "@prisma/get-platform": "7.9.1" + "@prisma/debug": "7.10.1", + "@prisma/engines-version": "8.1.0-1.d1b3f377ca835d73fd50954a5e22128b9ebdd6b8", + "@prisma/fetch-engine": "7.10.1", + "@prisma/get-platform": "7.10.1" } }, "node_modules/@prisma/engines-version": { - "version": "7.9.0-1.e922089b7d7502aff4249d5da3420f6fa55fc6ad", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.9.0-1.e922089b7d7502aff4249d5da3420f6fa55fc6ad.tgz", - "integrity": "sha512-2BsPPFksz3CQUXG6af3rVCtJKg6+JJGJTtfgu2fU8DdXhOfkBjulCq8mwybCd6ge0/jhZq2kOtLAbmUDMyI1nA==", + "version": "8.1.0-1.d1b3f377ca835d73fd50954a5e22128b9ebdd6b8", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-8.1.0-1.d1b3f377ca835d73fd50954a5e22128b9ebdd6b8.tgz", + "integrity": "sha512-XjXmwjN2NE340RpXoXv8ZF8aNAvr0FE+7gNfkKeW6vyjXs7F8uxvEbWQoVrhioXDQoIsnNmv7r+wxvzfdEZaWA==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/engines/node_modules/@prisma/debug": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.10.1.tgz", + "integrity": "sha512-eFCAkGZ13D/qPg48JBlG5IWi9cSBBOC7oG3abDPcAkYioDWlIjh0YJJLK2J7qo5S+xWWD8I6egAN/8KbCaiMCA==", "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/engines/node_modules/@prisma/get-platform": { - "version": "7.9.1", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.9.1.tgz", - "integrity": "sha512-PK8R60YZRQvYxBrGG9i7l2/rFyzy+2MuI1dKtmtrCqPH8YpiJx/MfiC7LRzX5786rZDEv7BngcjfIJW4/9ADuw==", + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.10.1.tgz", + "integrity": "sha512-iVnghHne5vcaobBjPVCMEjh5r/1UCj394EIGTUclasFRatpR/MQwcM2veLv5kgoFSyYP54nch/lNFCQRYcDxvg==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.9.1" + "@prisma/debug": "7.10.1" } }, "node_modules/@prisma/fetch-engine": { - "version": "7.9.1", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.9.1.tgz", - "integrity": "sha512-9DwxrNTeT25Orbu9CWh0CZvVlyY1lmscpbaeLZcOnuR7zcuFrt91YSmmOfIm7zJ08YOZ6mVzURKwLoMwEBcK8w==", + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.10.1.tgz", + "integrity": "sha512-qZezeaExLWG2ptbHOrMxdiWluKHgqEclTdJbvY6pk6+Wg7EiVkeq5U3cXz9ti8nd0bngNvsDgledLHVRg5LF1w==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.9.1", - "@prisma/engines-version": "7.9.0-1.e922089b7d7502aff4249d5da3420f6fa55fc6ad", - "@prisma/get-platform": "7.9.1" + "@prisma/debug": "7.10.1", + "@prisma/engines-version": "8.1.0-1.d1b3f377ca835d73fd50954a5e22128b9ebdd6b8", + "@prisma/get-platform": "7.10.1" } }, + "node_modules/@prisma/fetch-engine/node_modules/@prisma/debug": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.10.1.tgz", + "integrity": "sha512-eFCAkGZ13D/qPg48JBlG5IWi9cSBBOC7oG3abDPcAkYioDWlIjh0YJJLK2J7qo5S+xWWD8I6egAN/8KbCaiMCA==", + "devOptional": true, + "license": "Apache-2.0" + }, "node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": { - "version": "7.9.1", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.9.1.tgz", - "integrity": "sha512-PK8R60YZRQvYxBrGG9i7l2/rFyzy+2MuI1dKtmtrCqPH8YpiJx/MfiC7LRzX5786rZDEv7BngcjfIJW4/9ADuw==", + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.10.1.tgz", + "integrity": "sha512-iVnghHne5vcaobBjPVCMEjh5r/1UCj394EIGTUclasFRatpR/MQwcM2veLv5kgoFSyYP54nch/lNFCQRYcDxvg==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "7.9.1" + "@prisma/debug": "7.10.1" } }, "node_modules/@prisma/get-platform": { @@ -3077,7 +3105,7 @@ "version": "24.13.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz", "integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~7.18.0" @@ -4288,9 +4316,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.37", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.37.tgz", - "integrity": "sha512-girxaJ7WZssDOFhzCGZTDKoTa1gk6A1TbflaYTpykLJ4UU9Fz9kx1aREM8JCuoVHbL8X8T/mJg7w2oYSq72Oig==", + "version": "2.11.29", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.29.tgz", + "integrity": "sha512-YeesxhUmR4h5y5V8KF8iBCqJb8YFrBJ++XBTeGcWIaQOYXGN19JM0FoiOjAhwuDoln8owMgXcWBmgHtYSkky2w==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.cjs" @@ -4341,9 +4369,9 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4367,9 +4395,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", - "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "version": "4.29.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.3.tgz", + "integrity": "sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA==", "dev": true, "funding": [ { @@ -4387,11 +4415,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.10.12", - "caniuse-lite": "^1.0.30001782", - "electron-to-chromium": "^1.5.328", - "node-releases": "^2.0.36", - "update-browserslist-db": "^1.2.3" + "baseline-browser-mapping": "^2.11.26", + "caniuse-lite": "^1.0.30001813", + "electron-to-chromium": "^1.5.439", + "node-releases": "^2.0.57", + "update-browserslist-db": "^1.3.3" }, "bin": { "browserslist": "cli.js" @@ -4513,9 +4541,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001799", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", - "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", + "version": "1.0.30001816", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001816.tgz", + "integrity": "sha512-+8Yx7BXCoqk6IF8yTicjfUR6D/ldJzdlsodlEOiOSPmhK844WIgk67/zIQaGAOxNkZyyvBlrRFnLvqKIoqbAFw==", "funding": [ { "type": "opencollective", @@ -4983,9 +5011,9 @@ "license": "MIT" }, "node_modules/deepmerge-ts": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-8.0.1.tgz", - "integrity": "sha512-szCXE7YLCvLKR9bFPJcvsezOShdalctSvrgN/LM/QGUEPZQajwjmsMObZ6/DuANT5lxzM/wtO8Feubwdkz8myA==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-8.0.2.tgz", + "integrity": "sha512-uqbvqLUMrc6p0MO+WBRtTxY55hmyh94WRwI5a++PZe54X+bfVh59FSN7uWCBCW1CCVjzjnrwzfI8zidE2obMMw==", "devOptional": true, "funding": [ { @@ -4999,7 +5027,7 @@ ], "license": "BSD-3-Clause", "engines": { - "node": ">=16.0.0" + "node": ">=16.9.0" } }, "node_modules/define-data-property": { @@ -5055,16 +5083,6 @@ "robust-predicates": "^3.0.2" } }, - "node_modules/denque": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", - "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", - "devOptional": true, - "license": "Apache-2.0", - "engines": { - "node": ">=0.10" - } - }, "node_modules/destr": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", @@ -5143,9 +5161,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.372", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.372.tgz", - "integrity": "sha512-M3yhbAlilnwqC8D21t28UCDGHyitShTmmLRU/H+b74P6Ski16Nb9HONYEaVpMj/pwC7BEo5B95FpjODLCWbtfA==", + "version": "1.5.459", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.459.tgz", + "integrity": "sha512-4SC9HHSFjO/liAhifpdOL5hG9b4BP7/5G3y3UPZVO8trlAXr1CaUEEJL0gWK3kOcYcZrOA5rkwG45Gra/lTIgg==", "dev": true, "license": "ISC" }, @@ -5485,21 +5503,21 @@ } }, "node_modules/eslint-config-next": { - "version": "16.3.1", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.3.1.tgz", - "integrity": "sha512-0vtrpwFVHFEkycUgV/DyrG29OS+HSRdah5Yu8YuZoiBMtlAT6NIiWzaLwDkJZxr2kGfx+9LIvfQ7KHAlEs0VsA==", + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.4.2.tgz", + "integrity": "sha512-P33vjcETMXc+qBjpF77TeJHZLzk6cPcTy8KjpxLucYB3mCIFIORMcZ+cBXcAiIYjE5RM7jyknEEOwMNdnx/ADQ==", "dev": true, "license": "MIT", "dependencies": { - "@next/eslint-plugin-next": "16.3.1", + "@next/eslint-plugin-next": "16.4.2", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.32.0", "eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-react": "^7.37.0", - "eslint-plugin-react-hooks": "^7.0.0", + "eslint-plugin-react-hooks": "^7.1.0", "globals": "16.4.0", - "typescript-eslint": "^8.46.0" + "typescript-eslint": "^8.56.0" }, "peerDependencies": { "eslint": ">=9.0.0", @@ -5620,26 +5638,6 @@ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" } }, - "node_modules/eslint-config-next/node_modules/eslint-plugin-react-hooks": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", - "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.24.4", - "@babel/parser": "^7.24.4", - "hermes-parser": "^0.25.1", - "zod": "^3.25.0 || ^4.0.0", - "zod-validation-error": "^3.5.0 || ^4.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" - } - }, "node_modules/eslint-config-next/node_modules/globals": { "version": "16.4.0", "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", @@ -5746,6 +5744,26 @@ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + } + }, "node_modules/eslint-plugin-react/node_modules/resolve": { "version": "2.0.0-next.6", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz", @@ -5940,9 +5958,9 @@ } }, "node_modules/exsolve": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.1.tgz", - "integrity": "sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.3.tgz", + "integrity": "sha512-kuzL9zherLQoqDeQaS55AiiIg1kb4KYXaSusTtgZTPlvKP2gGCI82LNmBNh5RjDVUXIcmgx+/Mx7vPwRzBbhJA==", "devOptional": true, "license": "MIT" }, @@ -6038,9 +6056,9 @@ } }, "node_modules/fast-uri": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", - "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", + "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", "devOptional": true, "funding": [ { @@ -6055,9 +6073,9 @@ "license": "BSD-3-Clause" }, "node_modules/fastq": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", - "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.3.tgz", + "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==", "dev": true, "license": "ISC", "dependencies": { @@ -6569,9 +6587,9 @@ } }, "node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", "devOptional": true, "license": "MIT", "dependencies": { @@ -7162,9 +7180,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", - "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", "dev": true, "funding": [ { @@ -7635,9 +7653,9 @@ } }, "node_modules/lru.min": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz", - "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.5.tgz", + "integrity": "sha512-5J9ysMYUpYIg9RF2vJpy9SinEmSviFSe0GyPpCQ4L5QSkLAgeLXlTAOu2ZwWUU5m+0SBl6gUU1R1ZQB3aKypfA==", "devOptional": true, "license": "MIT", "engines": { @@ -7759,24 +7777,25 @@ "license": "MIT" }, "node_modules/mysql2": { - "version": "3.15.3", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.3.tgz", - "integrity": "sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==", + "version": "3.24.4", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.24.4.tgz", + "integrity": "sha512-A2olluVlj0mvgyIRRISMEzXc51m+21mRtcMVjJyIpt2GG98+XrC9m9HzsqcMsX2LcnfccJvY5NB22g8fENBnOA==", "devOptional": true, "license": "MIT", "dependencies": { - "aws-ssl-profiles": "^1.1.1", - "denque": "^2.1.0", + "aws-ssl-profiles": "^1.1.2", "generate-function": "^2.3.1", - "iconv-lite": "^0.7.0", - "long": "^5.2.1", - "lru.min": "^1.0.0", - "named-placeholders": "^1.1.3", - "seq-queue": "^0.0.5", - "sqlstring": "^2.3.2" + "iconv-lite": "^0.7.3", + "long": "^5.3.2", + "lru.min": "^1.1.4", + "named-placeholders": "^1.1.6", + "sql-escaper": "^1.5.1" }, "engines": { "node": ">= 8.0" + }, + "peerDependencies": { + "@types/node": ">= 8" } }, "node_modules/named-placeholders": { @@ -7840,12 +7859,12 @@ "license": "MIT" }, "node_modules/next": { - "version": "16.3.1", - "resolved": "https://registry.npmjs.org/next/-/next-16.3.1.tgz", - "integrity": "sha512-hsAp0i7Rh+/dhe7DGIeN2YlpLM1DP4MNxti9EtDMtqcO612X81MvvEj388/oTce9U1EcEIOWDlGq0zRwrBKvuA==", + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/next/-/next-16.4.2.tgz", + "integrity": "sha512-r+oBJojfefUjOiWkBhXgLS+igxX/7BWyK59uHnyOyplMSLtBZ+QLP3okrdYTbrfrbrmNTVWLBPaHDX9KRaIt3w==", "license": "MIT", "dependencies": { - "@next/env": "16.3.1", + "@next/env": "16.4.2", "@swc/helpers": "0.5.23", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", @@ -7859,15 +7878,15 @@ "node": ">=20.9.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "16.3.1", - "@next/swc-darwin-x64": "16.3.1", - "@next/swc-linux-arm64-gnu": "16.3.1", - "@next/swc-linux-arm64-musl": "16.3.1", - "@next/swc-linux-x64-gnu": "16.3.1", - "@next/swc-linux-x64-musl": "16.3.1", - "@next/swc-win32-arm64-msvc": "16.3.1", - "@next/swc-win32-x64-msvc": "16.3.1", - "sharp": "^0.35.3" + "@next/swc-darwin-arm64": "16.4.2", + "@next/swc-darwin-x64": "16.4.2", + "@next/swc-linux-arm64-gnu": "16.4.2", + "@next/swc-linux-arm64-musl": "16.4.2", + "@next/swc-linux-x64-gnu": "16.4.2", + "@next/swc-linux-x64-musl": "16.4.2", + "@next/swc-win32-arm64-msvc": "16.4.2", + "@next/swc-win32-x64-msvc": "16.4.2", + "sharp": "^0.35.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", @@ -7979,9 +7998,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.47", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", - "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", + "version": "2.0.59", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.59.tgz", + "integrity": "sha512-5qr2okC4ItAI0hG5SaV0HoYMpnM/Y6+Oqr7AmPYy/2uEoGvS+GbSKLeRxSS1uAdu59yz0ouzcuXievSgg///WA==", "dev": true, "license": "MIT", "engines": { @@ -7990,9 +8009,9 @@ }, "node_modules/nodemailer9": { "name": "nodemailer", - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-9.0.5.tgz", - "integrity": "sha512-wvjiKvjczmsN7U/8006JOdXubgBk2XFAbioDMbT+sM7cPs0QrhJTa6KBRX7P5REGGkDcLUz/EarWidb8G8C1jQ==", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-9.1.1.tgz", + "integrity": "sha512-izw9mVKFix6YSnC9eLgV6g1opl9DUlRio9ZNcq+Wu9Ujn2UwF+8Nl0B8nz22kEC+CTZCvinkxwJ0DeFbb6NwcQ==", "license": "MIT-0", "engines": { "node": ">=6.0.0" @@ -8377,17 +8396,24 @@ } }, "node_modules/pkg-types": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz", - "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.3.tgz", + "integrity": "sha512-j/lCFdcppV0JxWpCEITdbDltBxPP6cHT+yNJ6Go2OgoSA9518X847X9z0p6LtA4Nc16+eQzCZjRrWanTGvHJ5w==", "devOptional": true, "license": "MIT", "dependencies": { - "confbox": "^0.2.4", - "exsolve": "^1.0.8", + "confbox": "^0.3.1", + "exsolve": "^1.1.1", "pathe": "^2.0.3" } }, + "node_modules/pkg-types/node_modules/confbox": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.3.1.tgz", + "integrity": "sha512-cKUSoKa8YxFZZSmraVi7onONx3amu77ngK3kGpsYHDH7drPwCRkQE1RYMPlLRrMtnciRj274XNRxcHxnKmDSnA==", + "devOptional": true, + "license": "MIT" + }, "node_modules/playwright": { "version": "1.61.0", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.0.tgz", @@ -8661,18 +8687,18 @@ "license": "MIT" }, "node_modules/prisma": { - "version": "7.9.1", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.9.1.tgz", - "integrity": "sha512-aPqePoZIqwlAchbgbFDO/wHqGB+7H1nj9gaM+OsL9h77S5S3TnLd9BgD3LnoeDikULo7cl2HSUrEyQ55Z7DYbg==", + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.10.1.tgz", + "integrity": "sha512-GoVeErHGGtsSjsflQ1r2BA3RKTXtxNraQJz8OLkLhoLr7OgCscdx4qw4WMM4HnERYc+a1cq2VnMNbep+vOjNcQ==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/config": "7.9.1", + "@prisma/config": "7.10.1", "@prisma/dev": "0.24.17", - "@prisma/engines": "7.9.1", + "@prisma/engines": "7.10.1", "@prisma/studio-core": "0.33.0", - "mysql2": "3.15.3", + "mysql2": "3.24.4", "postgres": "3.4.7" }, "bin": { @@ -8813,13 +8839,13 @@ } }, "node_modules/rc9": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/rc9/-/rc9-3.0.1.tgz", - "integrity": "sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-3.1.0.tgz", + "integrity": "sha512-ufjkNVzbRHKcCOmTahZkmVsyc3W+MSk3jY03m+a7tGHkIsdVMG9l10/3HvFbWkkKzY5VFp3pkRsIo/UYgmFL7Q==", "devOptional": true, "license": "MIT", "dependencies": { - "defu": "^6.1.6", + "defu": "^6.1.7", "destr": "^2.0.5" } }, @@ -9253,12 +9279,6 @@ "node": ">=10" } }, - "node_modules/seq-queue": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", - "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==", - "devOptional": true - }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -9309,9 +9329,9 @@ } }, "node_modules/sharp": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.3.tgz", - "integrity": "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.5.tgz", + "integrity": "sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -9326,31 +9346,31 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.35.3", - "@img/sharp-darwin-x64": "0.35.3", - "@img/sharp-freebsd-wasm32": "0.35.3", - "@img/sharp-libvips-darwin-arm64": "1.3.2", - "@img/sharp-libvips-darwin-x64": "1.3.2", - "@img/sharp-libvips-linux-arm": "1.3.2", - "@img/sharp-libvips-linux-arm64": "1.3.2", - "@img/sharp-libvips-linux-ppc64": "1.3.2", - "@img/sharp-libvips-linux-riscv64": "1.3.2", - "@img/sharp-libvips-linux-s390x": "1.3.2", - "@img/sharp-libvips-linux-x64": "1.3.2", - "@img/sharp-libvips-linuxmusl-arm64": "1.3.2", - "@img/sharp-libvips-linuxmusl-x64": "1.3.2", - "@img/sharp-linux-arm": "0.35.3", - "@img/sharp-linux-arm64": "0.35.3", - "@img/sharp-linux-ppc64": "0.35.3", - "@img/sharp-linux-riscv64": "0.35.3", - "@img/sharp-linux-s390x": "0.35.3", - "@img/sharp-linux-x64": "0.35.3", - "@img/sharp-linuxmusl-arm64": "0.35.3", - "@img/sharp-linuxmusl-x64": "0.35.3", - "@img/sharp-webcontainers-wasm32": "0.35.3", - "@img/sharp-win32-arm64": "0.35.3", - "@img/sharp-win32-ia32": "0.35.3", - "@img/sharp-win32-x64": "0.35.3" + "@img/sharp-darwin-arm64": "0.35.5", + "@img/sharp-darwin-x64": "0.35.5", + "@img/sharp-freebsd-wasm32": "0.35.5", + "@img/sharp-libvips-darwin-arm64": "1.3.4", + "@img/sharp-libvips-darwin-x64": "1.3.4", + "@img/sharp-libvips-linux-arm": "1.3.4", + "@img/sharp-libvips-linux-arm64": "1.3.4", + "@img/sharp-libvips-linux-ppc64": "1.3.4", + "@img/sharp-libvips-linux-riscv64": "1.3.4", + "@img/sharp-libvips-linux-s390x": "1.3.4", + "@img/sharp-libvips-linux-x64": "1.3.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4", + "@img/sharp-libvips-linuxmusl-x64": "1.3.4", + "@img/sharp-linux-arm": "0.35.5", + "@img/sharp-linux-arm64": "0.35.5", + "@img/sharp-linux-ppc64": "0.35.5", + "@img/sharp-linux-riscv64": "0.35.5", + "@img/sharp-linux-s390x": "0.35.5", + "@img/sharp-linux-x64": "0.35.5", + "@img/sharp-linuxmusl-arm64": "0.35.5", + "@img/sharp-linuxmusl-x64": "0.35.5", + "@img/sharp-webcontainers-wasm32": "0.35.5", + "@img/sharp-win32-arm64": "0.35.5", + "@img/sharp-win32-ia32": "0.35.5", + "@img/sharp-win32-x64": "0.35.5" }, "peerDependenciesMeta": { "@types/node": { @@ -9532,22 +9552,28 @@ } }, "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, - "node_modules/sqlstring": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", - "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "node_modules/sql-escaper": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/sql-escaper/-/sql-escaper-1.5.2.tgz", + "integrity": "sha512-6CKD38c31SENivxOADeMNLdukOnUxUcflKtzVWzace7Riv1v7cAEym5Cx9Q7gYZ3ezIJI7ZpqecQq8cqNeSSFg==", "devOptional": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "bun": ">=1.0.0", + "deno": ">=2.0.0", + "node": ">=12.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/mysqljs/sql-escaper?sponsor=1" } }, "node_modules/stable-hash": { @@ -10153,7 +10179,7 @@ "version": "7.18.2", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/unrs-resolver": { @@ -10192,9 +10218,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.4.tgz", + "integrity": "sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==", "dev": true, "funding": [ { diff --git a/prisma/migrations/20261017200000_add_orders_starts_at_to_template/migration.sql b/prisma/migrations/20261017200000_add_orders_starts_at_to_template/migration.sql new file mode 100644 index 0000000..91687c5 --- /dev/null +++ b/prisma/migrations/20261017200000_add_orders_starts_at_to_template/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "GroupOrderTemplate" ADD COLUMN "ordersStartsAt" INTEGER NOT NULL DEFAULT 72; diff --git a/prisma/migrations/20261018121500_add_item_deposit/migration.sql b/prisma/migrations/20261018121500_add_item_deposit/migration.sql new file mode 100644 index 0000000..609f77a --- /dev/null +++ b/prisma/migrations/20261018121500_add_item_deposit/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Item" ADD COLUMN "deposit" REAL NOT NULL DEFAULT 0; diff --git a/prisma/migrations/20261018140000_add_grouporder_shopper_and_filter/migration.sql b/prisma/migrations/20261018140000_add_grouporder_shopper_and_filter/migration.sql new file mode 100644 index 0000000..5ca82e5 --- /dev/null +++ b/prisma/migrations/20261018140000_add_grouporder_shopper_and_filter/migration.sql @@ -0,0 +1,32 @@ +-- RedefineTables +PRAGMA defer_foreign_keys=ON; +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_GroupOrder" ( + "id" TEXT NOT NULL PRIMARY KEY, + "name" TEXT, + "ordersCloseAt" DATETIME NOT NULL, + "ordersClosedAt" DATETIME, + "closedById" TEXT, + "revertedById" TEXT, + "shopperId" TEXT, + "filterCategoryId" TEXT, + "status" INTEGER NOT NULL DEFAULT 0, + "groupOrderTemplateId" TEXT, + CONSTRAINT "GroupOrder_closedById_fkey" FOREIGN KEY ("closedById") REFERENCES "User" ("id") ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT "GroupOrder_revertedById_fkey" FOREIGN KEY ("revertedById") REFERENCES "User" ("id") ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT "GroupOrder_shopperId_fkey" FOREIGN KEY ("shopperId") REFERENCES "User" ("id") ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT "GroupOrder_filterCategoryId_fkey" FOREIGN KEY ("filterCategoryId") REFERENCES "Category" ("id") ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT "GroupOrder_groupOrderTemplateId_fkey" FOREIGN KEY ("groupOrderTemplateId") REFERENCES "GroupOrderTemplate" ("id") ON DELETE SET NULL ON UPDATE CASCADE +); +INSERT INTO "new_GroupOrder" ("closedById", "groupOrderTemplateId", "id", "name", "ordersCloseAt", "ordersClosedAt", "revertedById", "status") SELECT "closedById", "groupOrderTemplateId", "id", "name", "ordersCloseAt", "ordersClosedAt", "revertedById", "status" FROM "GroupOrder"; +DROP TABLE "GroupOrder"; +ALTER TABLE "new_GroupOrder" RENAME TO "GroupOrder"; +PRAGMA foreign_keys=ON; +PRAGMA defer_foreign_keys=OFF; + +-- closed group orders: the shopper is the user who got the compensation +UPDATE "GroupOrder" SET "shopperId" = ( + SELECT "moneyDestinationUserId" FROM "Transaction" + WHERE "Transaction"."groupOrderId" = "GroupOrder"."id" AND "Transaction"."type" = 3 AND "Transaction"."canceled" = false + LIMIT 1 +) WHERE "status" = 6; diff --git a/prisma/migrations/20261018160000_add_user_retractions_seen_at/migration.sql b/prisma/migrations/20261018160000_add_user_retractions_seen_at/migration.sql new file mode 100644 index 0000000..481fc2c --- /dev/null +++ b/prisma/migrations/20261018160000_add_user_retractions_seen_at/migration.sql @@ -0,0 +1,5 @@ +-- AlterTable +ALTER TABLE "User" ADD COLUMN "retractionsSeenAt" DATETIME; + +-- existing users have already noticed past retractions - only new ones are marked +UPDATE "User" SET "retractionsSeenAt" = strftime('%Y-%m-%dT%H:%M:%f+00:00', 'now'); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 784d0eb..bbda453 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -24,6 +24,7 @@ model Item { name String categories Category[] price Float + deposit Float @default(0) // optional deposit (Pfand) in €, bought separately account ClearingAccount @relation(fields: [accountId], references: [id]) purchases ItemCategoryMapping[] is_active Boolean @default(true) @@ -63,20 +64,26 @@ model GroupOrder { ordersClosedAt DateTime? closedBy User? @relation("closedBy", fields: [closedById], references: [id]) revertedBy User? @relation("revertedBy", fields: [revertedById], references: [id]) + shopper User? @relation("shopper", fields: [shopperId], references: [id]) closedById String? revertedById String? + shopperId String? + // only show items of this category when ordering (set by admins or the shopper) + filterCategory Category? @relation("groupOrderFilter", fields: [filterCategoryId], references: [id], onDelete: SetNull) + filterCategoryId String? status Int @default(0) // 0: open, 5: incoming orders stoped, 6: orders fulfilled, 99: aborted (orders not fulfilled) GroupOrderTemplate GroupOrderTemplate? @relation(fields: [groupOrderTemplateId], references: [id]) groupOrderTemplateId String? } model GroupOrderTemplate { - id String @id @default(cuid()) - name String - weekday Int //0:sun, 1: mon, ..., 6:sat - ordersCloseAt DateTime // only time relevant - date not important - active Boolean @default(true) - GroupOrders GroupOrder[] + id String @id @default(cuid()) + name String + weekday Int //0:sun, 1: mon, ..., 6:sat + ordersCloseAt DateTime // only time relevant - date not important + ordersStartsAt Int @default(72) // hours before ordersCloseAt when the grouporder gets created + active Boolean @default(true) + GroupOrders GroupOrder[] } model Category { @@ -95,6 +102,7 @@ model Category { markupDestinationId String? ItemPurchase ItemCategoryMapping? @relation(fields: [itemPurchaseId], references: [id]) itemPurchaseId String? + groupOrdersFiltered GroupOrder[] @relation("groupOrderFilter") } model ClearingAccount { @@ -141,7 +149,7 @@ model Transaction { amountWithoutFees Float? // without category-markup totalAmount Float // without category-markup createdAt DateTime @default(now()) - type Int @default(0) // 0: buy, 1: sell, 2: transfer, 3: procurement (of grouporder) | 90: rescinded buy, 91: rescinded sell + type Int @default(0) // 0: buy, 1: sell, 2: transfer, 3: procurement (of grouporder), 4: reserved buy in grouporder (booked on close) | 90: rescinded buy, 91: rescinded sell groupOrder GroupOrder? @relation(fields: [groupOrderId], references: [id]) groupOrderId String? // --- if buy or transfer (type=0|2) @@ -195,12 +203,14 @@ model User { is_admin Boolean @default(false) allowOverdraw Boolean @default(false) balance Float @default(0) + retractionsSeenAt DateTime? // newest money retraction by others the user has seen on the account page transactions Transaction[] @relation("user") is_transfer_destiantion Transaction[] @relation("moneyDestination") ProcurementWish ProcurementWish[] Transaction Transaction[] @relation("canceledBy") groupOrdersClosed GroupOrder[] @relation("closedBy") groupOrdersReverted GroupOrder[] @relation("revertedBy") + groupOrdersShopped GroupOrder[] @relation("shopper") } model VerificationToken { diff --git a/scripts/screenshots/seed.ts b/scripts/screenshots/seed.ts index d173441..6639edf 100644 --- a/scripts/screenshots/seed.ts +++ b/scripts/screenshots/seed.ts @@ -8,14 +8,10 @@ import path from "node:path" const defaultScreenshotDatabaseUrl = "file:./screenshots.db" const databaseUrl = process.env.SCREENSHOT_DATABASE_URL ?? defaultScreenshotDatabaseUrl -const databaseUrlServer = databaseUrl.startsWith("file:./") - ? `file:./prisma/${databaseUrl.slice("file:./".length)}` - : databaseUrl; - -process.env.DATABASE_URL = databaseUrlServer +process.env.DATABASE_URL = databaseUrl if (databaseUrl === defaultScreenshotDatabaseUrl) { - const databasePath = path.resolve("prisma", "screenshots.db") + const databasePath = path.resolve("screenshots.db") rmSync(databasePath, { force: true }) rmSync(`${databasePath}-journal`, { force: true }) writeFileSync(databasePath, "") @@ -47,7 +43,7 @@ if (push.status !== 0) { const prisma = new PrismaClient({ adapter: new PrismaBetterSqlite3({ - url: databaseUrlServer, + url: databaseUrl, }), }) diff --git a/src/components/FormElements/GroupOrderDetailView.tsx b/src/components/FormElements/GroupOrderDetailView.tsx index e70f02f..c9dfa14 100644 --- a/src/components/FormElements/GroupOrderDetailView.tsx +++ b/src/components/FormElements/GroupOrderDetailView.tsx @@ -1,5 +1,5 @@ import type { inferRouterOutputs } from "@trpc/server" -import { ListTodo } from "lucide-react" +import { Coins, ListTodo, Undo2 } from "lucide-react" import { useState } from "react" import { localStringOptions } from "~/helper/globalTypes" import type { AppRouter } from "../../server/api/root" @@ -15,13 +15,35 @@ const GroupOrderDetailView = (props: Props) => { const { group } = props const [openOrderSummaryModal, setOpenOrderSummaryModal] = useState(false) + const [showSplit, setShowSplit] = useState(false) + const itemBuys = group.orders.filter((o) => !o.canceled && o.items.length > 0) + + const actions = ( +
+ + {!showSplit && ( + + )} +
+ ) return ( <>
-
+
-

{group.name}

+
+

{group.name}

+

+ Einkauf: {group.shopper?.name ?? "noch offen"} +

+
{!!group.closedBy && (

{group.revertedBy?.name} hat Abrechnung von{" "} @@ -33,62 +55,48 @@ const GroupOrderDetailView = (props: Props) => {

-
-
-

Gruppen-Artikel

- - - {group.procurementWishes.map((o) => - o.items.map((item, id) => ( - - - - - )), - )} - -
{o.user?.name}{item.name}
-
+ {showSplit ? ( + + ) : ( + <> +
+ {group.procurementWishes.length > 0 && ( +
+
Gruppen-Artikel
+ + + {group.procurementWishes.map((o) => + o.items.map((item, id) => ( + + + + + )), + )} + +
{o.user?.name}{item.name}
+
+ )} - {group.orders.length >= 1 && ( -
-

Gekaufte Einzel-Artikel

-
- - - {group.orders - .filter((o) => !o.canceled) - .map((o) => ( + {itemBuys.length > 0 && ( +
+
Einzel-Artikel
+
+ + {itemBuys.map((o) => ( - + ))} - -
{o.user?.name}{o.items.map((mpng) => mpng.item.name)}{o.items.map((mpng) => mpng.item.name).join(", ")}
-
-
- )} -
- -
-
- -
- -
- -

Gruppen-Artikel Abrechnung

- +
{actions}
+ + )}
diff --git a/src/components/FormElements/GroupOrderSplit.tsx b/src/components/FormElements/GroupOrderSplit.tsx index a9372f2..8f15b17 100644 --- a/src/components/FormElements/GroupOrderSplit.tsx +++ b/src/components/FormElements/GroupOrderSplit.tsx @@ -1,13 +1,14 @@ import type { ProcurementItem } from "~/generated/prisma/client" import { Coins } from "lucide-react" import { useSession } from "next-auth/react" -import { type ChangeEvent, useMemo, useRef, useState } from "react" +import { type ReactNode, useMemo, useRef, useState } from "react" import { z } from "zod" import { calculateAdditionalPricing } from "~/helper/dataProcessing" import { type Tid, id } from "~/helper/zodTypes" import { type RouterOutputs, api } from "~/utils/api" import ActionResponsePopup, { type AnimationHandle, animate } from "../General/ActionResponsePopup" import { ConfirmationModal } from "../General/ConfirmationModal" +import DecimalInput from "../General/DecimalInput" type UserItemList = { [user: Tid]: { @@ -25,7 +26,7 @@ export const splitSubmitSchema = z.array( items: z.array( z.object({ id: id, - price: z.number(), + price: z.number().min(0), }), ), }), @@ -54,6 +55,8 @@ type Split = { type Props = { group: RouterOutputs["groupOrders"]["getInProgress"][number] + /** further buttons, shown right in the row of the "Abrechnen" button */ + actions?: ReactNode } const buildDerivedGroupData = (group: RouterOutputs["groupOrders"]["getInProgress"][number]) => { @@ -109,7 +112,7 @@ const buildDerivedGroupData = (group: RouterOutputs["groupOrders"]["getInProgres } const GroupOrderSplit = (props: Props) => { - const { group } = props + const { group, actions } = props const trpcUtils = api.useUtils() const animationRef = useRef(null) @@ -120,32 +123,36 @@ const GroupOrderSplit = (props: Props) => { const { itemList, userItemList, baseSplit } = useMemo(() => buildDerivedGroupData(group), [group]) const [overwrittenCosts, setOverwrittenCosts] = useState>({}) + const [itemBuyPrices, setItemBuyPrices] = useState>({}) + const pendingItemBuys = group.orders.filter((order) => order.type === 4 && !order.canceled) const [closeGroupOrderModalOpen, setCloseGroupOrderModalOpen] = useState(false) - const [selectedDestination, setSelectedDestination] = useState(sessionData?.user.id.toString() ?? "") + const [selectedDestination, setSelectedDestination] = useState( + group.shopperId ?? sessionData?.user.id.toString() ?? "", + ) + // the session may not be loaded on the first render + const destination = selectedDestination || sessionData?.user.id || "" const [destinationError, setDestinationError] = useState(false) const totalItems = itemList.length const [totalAmount, setTotalAmount] = useState(0) - const typeTotalAmount = (e: ChangeEvent) => { - const userInput = e.currentTarget.value - const value = parseFloat(userInput) - setTotalAmount(value) - } - - const overwriteUserExpence = ( - e: ChangeEvent, - itemId: Tid, - ) => { - const cost = parseFloat(e.currentTarget.value) + const [costMode, setCostMode] = useState<"total" | "group">("total") + const itemBuysSum = pendingItemBuys.reduce( + (sum, order) => sum + (itemBuyPrices[order.id] ?? order.amountWithoutFees ?? 0), + 0, + ) + const groupAmount = costMode === "total" ? Math.max(0, totalAmount - itemBuysSum) : totalAmount + // the total has to cover at least the single items + const totalAmountTooLow = costMode === "total" && totalAmount > 0 && totalAmount < itemBuysSum + const overwriteUserExpence = (cost: number, key: string) => { setOverwrittenCosts((previous) => ({ ...previous, - [itemId]: Number.isNaN(cost) ? undefined : cost, + [key]: Number.isNaN(cost) ? undefined : cost, })) } const split = useMemo(() => { - const defaultCost = totalItems > 0 ? totalAmount / totalItems : 0 + const defaultCost = totalItems > 0 ? groupAmount / totalItems : 0 const splitWithDefaults = baseSplit.map((userContent) => ({ ...userContent, @@ -154,7 +161,7 @@ const GroupOrderSplit = (props: Props) => { items: proc.items.map((item) => ({ ...item, defaultCost, - overwritenCost: overwrittenCosts[item.item.id], + overwritenCost: overwrittenCosts[`${proc.id}-${item.item.id}`], finalCost: defaultCost, })), })), @@ -176,8 +183,8 @@ const GroupOrderSplit = (props: Props) => { const remainingItems = totalItems - overwrittenItems const pricePerRemainingItem = - totalAmount - overwrittenItemsSum > 0 && remainingItems > 0 - ? (totalAmount - overwrittenItemsSum) / remainingItems + groupAmount - overwrittenItemsSum > 0 && remainingItems > 0 + ? (groupAmount - overwrittenItemsSum) / remainingItems : 0 return splitWithDefaults.map((userContent) => ({ @@ -190,7 +197,7 @@ const GroupOrderSplit = (props: Props) => { })), })), })) - }, [baseSplit, overwrittenCosts, totalAmount, totalItems]) + }, [baseSplit, overwrittenCosts, groupAmount, totalItems]) const allUsersOverwritten = useMemo(() => { let overwrittenItemsSum = 0 @@ -203,13 +210,13 @@ const GroupOrderSplit = (props: Props) => { } } } - return overwrittenItemsSum > totalAmount ? overwrittenItemsSum : undefined - }, [split, totalAmount]) + return overwrittenItemsSum > groupAmount ? overwrittenItemsSum : undefined + }, [split, groupAmount]) const closeGroupOrderRequest = api.groupOrders.close.useMutation() const closeGroupOrder = async () => { - if (!selectedDestination) { + if (!destination) { setDestinationError(true) return } else { @@ -229,8 +236,12 @@ const GroupOrderSplit = (props: Props) => { await closeGroupOrderRequest.mutateAsync( { groupId: group.id, - destination: selectedDestination, + destination, split: splitSubmit ?? {}, + itemBuyPrices: pendingItemBuys.flatMap((order) => { + const price = itemBuyPrices[order.id] + return price === undefined ? [] : [{ id: order.id, price }] + }), }, { onError: (error) => { @@ -249,104 +260,178 @@ const GroupOrderSplit = (props: Props) => { return ( <>
- - -
- - - - - - - - - - - {split.map((userContent) => - userContent.procurementWishs.map((proc) => - proc.items.map((item) => ( - - - + {totalItems > 0 && ( + <> +
+

Kosten

+
+ + +
+ + {allUsersOverwritten !== undefined &&

(verändert)

} + {costMode === "total" && itemBuysSum > 0 && allUsersOverwritten === undefined && ( +

+ − {itemBuysSum.toFixed(2)}€ Einzel-Artikel = {groupAmount.toFixed(2)}€ +

+ )} +
+
Gruppen-Artikel
+
+
NameGerichteKostenKosten anpassen
{userItemList[userContent.user]?.username}{item.item.name}
+ + + + + + + + + + {split.map((userContent) => + userContent.procurementWishs.map((proc) => + proc.items.map((item, index) => ( + + + + + + + )), + ), + )} + +
NameArtikelKostenKosten anpassen
{userItemList[userContent.user]?.username}{item.item.name} + {item.finalCost.toFixed(2)}€ + + {" "} + +{" "} + {calculateAdditionalPricing( + item.finalCost ?? 0, + item.item.categories, + ).toFixed(2)} + € + + + overwriteUserExpence(cost, `${proc.id}-${item.item.id}`)} + className="input input-sm max-w-24" + /> +
+
+ + )} + + {pendingItemBuys.length > 0 && ( +
+
Einzel-Artikel
+ + + + + + + + + + + {pendingItemBuys.map((order) => { + const price = itemBuyPrices[order.id] ?? order.amountWithoutFees ?? 0 + return ( + + + - )), - ), - )} - -
NameArtikelKostenKosten anpassen
{order.user.name}{order.items.map((mapping) => mapping.item.name).join(", ")} - {item.finalCost.toFixed(2)}€ + {price.toFixed(2)}€ {" "} +{" "} {calculateAdditionalPricing( - item.finalCost ?? 0, - item.item.categories, + price, + order.items.flatMap((mapping) => mapping.item.categories), ).toFixed(2)} € - overwriteUserExpence(e, item.item.id)} + value={itemBuyPrices[order.id]} + placeholder={(order.amountWithoutFees ?? 0).toFixed(2)} + onChange={(cost) => + setItemBuyPrices((previous) => ({ + ...previous, + [order.id]: Number.isNaN(cost) ? undefined : cost, + })) + } className="input input-sm max-w-24" />
-
- -
-

Gezahlt von:

- -
- -
- + ) + })} + + +
+ )} + +
+
+

Gezahlt von:

+ + +
+ {actions}
diff --git a/src/components/Forms/CategoryForm.tsx b/src/components/Forms/CategoryForm.tsx index 06c11c1..ad09715 100644 --- a/src/components/Forms/CategoryForm.tsx +++ b/src/components/Forms/CategoryForm.tsx @@ -1,8 +1,9 @@ import { zodResolver } from "@hookform/resolvers/zod" import { useEffect, useMemo } from "react" import type { SubmitHandler } from "react-hook-form" -import { useForm } from "react-hook-form" +import { Controller, useForm } from "react-hook-form" import { z } from "zod" +import DecimalInput from "~/components/General/DecimalInput" import { type Tid } from "~/helper/zodTypes" import { api } from "~/utils/api" @@ -32,6 +33,7 @@ const CategoryForm = (props: Props) => { const { register: addItemRegister, handleSubmit: addItemSubmit, + control, reset, formState: { errors }, } = useForm({ @@ -117,12 +119,19 @@ const CategoryForm = (props: Props) => { - ( + + )} /> {errors.markupFixed &&

{errors.markupFixed.message}

}
diff --git a/src/components/Forms/GrouporderTemplateForm.tsx b/src/components/Forms/GrouporderTemplateForm.tsx index d88c116..63eddb7 100644 --- a/src/components/Forms/GrouporderTemplateForm.tsx +++ b/src/components/Forms/GrouporderTemplateForm.tsx @@ -13,11 +13,13 @@ export const validationSchema = z.object({ weekday: z.number().min(0).max(6), ordersCloseAt_h: z.number().nonnegative(), ordersCloseAt_min: z.number().nonnegative(), + ordersStartsAt: z.number().min(1).max(160), }) export const formSchema = z.object({ name: z.string(), weekday: z.number().min(0).max(6), ordersCloseAt: z.string(), + ordersStartsAt: z.number().min(1).max(160), }) type Props = { @@ -27,8 +29,8 @@ type Props = { const AddGrouporderTemplateForm = (props: Props) => { const trpcUtils = api.useUtils() const createGrouporderTemplate = api.groupOrders.createTemplate.useMutation() - const updateGrouporderTempate = api.groupOrders.updateTemplate.useMutation() - const deleteGrouporderTempate = api.groupOrders.deleteTemplate.useMutation() + const updateGrouporderTemplate = api.groupOrders.updateTemplate.useMutation() + const deleteGrouporderTemplate = api.groupOrders.deleteTemplate.useMutation() const currentGroupOrderTemplate = api.groupOrders.getTemplate.useQuery( { id: props.id! }, @@ -57,7 +59,7 @@ const AddGrouporderTemplateForm = (props: Props) => { ), } : {} - : { name: "", weekday: undefined, ordersCloseAt: undefined }, + : { name: "", weekday: undefined, ordersCloseAt: undefined, ordersStartsAt: 72 }, [currentGroupOrderTemplate.data, props.id], ) @@ -77,7 +79,7 @@ const AddGrouporderTemplateForm = (props: Props) => { ...data, } if (!!props.id) { - await updateGrouporderTempate.mutateAsync({ id: props.id, ...dataToSend}) + await updateGrouporderTemplate.mutateAsync({ id: props.id, ...dataToSend}) } else { await createGrouporderTemplate.mutateAsync(dataToSend) } @@ -87,7 +89,7 @@ const AddGrouporderTemplateForm = (props: Props) => { const onDelete = async () => { if (!!props.id) { - await deleteGrouporderTempate.mutateAsync({ id: props.id }) + await deleteGrouporderTemplate.mutateAsync({ id: props.id }) props.finishAction() await trpcUtils.groupOrders.invalidate() } @@ -117,9 +119,10 @@ const AddGrouporderTemplateForm = (props: Props) => { className="select select-bordered select-primary w-full max-w-xs" {...register("weekday", { required: true, valueAsNumber: true })} > - {weekdays.map((day, index) => ( + {/* show monday first - value stays the JS weekday (0: sunday) */} + {[1, 2, 3, 4, 5, 6, 0].map((index) => ( ))} @@ -134,6 +137,16 @@ const AddGrouporderTemplateForm = (props: Props) => { className="input input-primary w-full max-w-md" /> +
+ + +
+ )} } {!itemWithPrice && @@ -110,15 +135,15 @@ const BuyItemCard = ({ item, buyAction, buttonName, userBalance }: Props) => { open={showConfirmModal} close={() => setShowConfirmModal(false)} proceed={handleConfirmBuy} - title="Kauf bestätigen" + title={buyDeposit ? "Pfand-Kauf bestätigen" : "Kauf bestätigen"} cancelText="Abbrechen" - proceedText="Jetzt kaufen" + proceedText={buyDeposit ? "Pfand kaufen" : "Jetzt kaufen"} proceedButtonClass="btn btn-primary" className="z-1000" >

- Möchtest du {item.name} wirklich kaufen? + Möchtest du {buyDeposit && "den Pfand für "}{item.name} wirklich kaufen?

{/* Quantity Selector */} @@ -156,10 +181,10 @@ const BuyItemCard = ({ item, buyAction, buttonName, userBalance }: Props) => { <>
- Preis pro Stück: - {totalPrice.toFixed(2)}€ + {buyDeposit ? "Pfand pro Stück:" : "Preis pro Stück:"} + {unitPrice.toFixed(2)}€
- {additionalPricing > 0 && ( + {!buyDeposit && additionalPricing > 0 && (
(Grundpreis: {item.price}€ + Gebühren: {additionalPricing.toFixed(2)}€)
@@ -167,7 +192,7 @@ const BuyItemCard = ({ item, buyAction, buttonName, userBalance }: Props) => { {quantity > 1 && (
Gesamtpreis: - {(totalPrice * quantity).toFixed(2)}€ + {(unitPrice * quantity).toFixed(2)}€
)}
@@ -179,8 +204,8 @@ const BuyItemCard = ({ item, buyAction, buttonName, userBalance }: Props) => {
Nach Kauf: - - {(userBalance - (totalPrice * quantity)).toFixed(2)}€ + + {(userBalance - (unitPrice * quantity)).toFixed(2)}€
diff --git a/src/components/General/BuyItemCardBig.tsx b/src/components/General/BuyItemCardBig.tsx index 7458037..62417cd 100644 --- a/src/components/General/BuyItemCardBig.tsx +++ b/src/components/General/BuyItemCardBig.tsx @@ -12,7 +12,7 @@ const BuyItemCardBig = ({ item, buyAction }: Props) => { const additionalPricing = calculateAdditionalItemPricing(item, item.categories) return ( -
+

{item.name}

diff --git a/src/components/General/DecimalInput.tsx b/src/components/General/DecimalInput.tsx new file mode 100644 index 0000000..577892a --- /dev/null +++ b/src/components/General/DecimalInput.tsx @@ -0,0 +1,61 @@ +import { type InputHTMLAttributes, useState } from "react" + +/** + * Parses "1,5" as well as "1.5" - independent of the browser language. Empty or invalid text is NaN. + * Numbers are passed through (e.g. default values of react-hook-form's `setValueAs`). + */ +export const parseDecimal = (text: string | number) => { + if (typeof text === "number") return text + const trimmed = text.trim().replace(",", ".") + return trimmed === "" || trimmed === "." || trimmed === "-" ? NaN : Number(trimmed) +} + +const toText = (value: number | undefined) => + // at most 4 decimals, e.g. for calculated values like 5.900000000000001 + value === undefined || Number.isNaN(value) ? "" : String(+value.toFixed(4)) + +type Props = Omit, "type" | "value" | "onChange" | "min" | "max"> & { + value: number | undefined + /** NaN if the field is empty */ + onChange: (value: number) => void + min?: number + max?: number +} + +/** + * Number input that accepts "," and "." as decimal separator. + * (`type="number"` only accepts the separator of the browser language and clears the field otherwise.) + * Input that isn't a number or lies outside of min/max is ignored. + */ +const DecimalInput = ({ value, onChange, min, max, ...props }: Props) => { + const [text, setText] = useState(toText(value)) + const [prevValue, setPrevValue] = useState(value) + + // take over changes from outside (e.g. a reset), but keep the text as typed while it means the same number ("1," = 1) + if (!Object.is(value, prevValue)) { + setPrevValue(value) + if (!Object.is(parseDecimal(text), value ?? NaN)) setText(toText(value)) + } + + const allowNegative = min === undefined || min < 0 + const pattern = allowNegative ? /^-?\d*[.,]?\d*$/ : /^\d*[.,]?\d*$/ + + return ( + { + const newText = e.target.value.trim() + if (!pattern.test(newText)) return + const newValue = parseDecimal(newText) + if ((min !== undefined && newValue < min) || (max !== undefined && newValue > max)) return + setText(newText) + onChange(newValue) + }} + /> + ) +} + +export default DecimalInput diff --git a/src/components/Layout/Header.tsx b/src/components/Layout/Header.tsx index 2d20036..a0e6e6d 100644 --- a/src/components/Layout/Header.tsx +++ b/src/components/Layout/Header.tsx @@ -1,5 +1,6 @@ import { Archive, + BarChart3, CreditCard, DollarSign, FileText, @@ -29,6 +30,19 @@ export default function Header() { const { data: userData } = api.user.getMe.useQuery(undefined, { enabled: loggedIn, }) + const { data: unseenRetractionCount } = api.user.getUnseenRetractionCount.useQuery(undefined, { + enabled: loggedIn, + refetchInterval: 60_000, + }) + const hasUnseenRetractions = (unseenRetractionCount ?? 0) > 0 + const unseenRetractionsDot = ( + + ) const isActive = (path: string) => { // Handle exact matches for root paths @@ -40,12 +54,8 @@ export default function Header() { path: string, baseClasses = "hover:bg-primary hover:text-primary-content transition-colors duration-200 font-medium", ) => { - if (path === "/top-up") { - const activeClasses = "bg-success text-success-content" - return isActive(path) ? `${baseClasses} ${activeClasses}` : baseClasses - } - - const activeClasses = "bg-primary text-primary-content" + const activeClasses = + path === "/admin" ? "bg-warning text-warning-content" : "bg-primary text-primary-content" return isActive(path) ? `${baseClasses} ${activeClasses}` : baseClasses } @@ -70,13 +80,7 @@ export default function Header() {
  • - + Aufladen @@ -85,6 +89,7 @@ export default function Header() { Konto + {hasUnseenRetractions && unseenRetractionsDot}
  • {" "} {sessionData?.user.is_admin && ( @@ -163,6 +168,15 @@ export default function Header() { Alle Transaktionen +
  • + + + Statistik + +
  • @@ -176,8 +190,9 @@ export default function Header() {
    {" "} -
    +
    + {hasUnseenRetractions && {unseenRetractionsDot}}
      -
      -
      +
      +
      {children}
      diff --git a/src/components/PageComponents/GetMoney.tsx b/src/components/PageComponents/GetMoney.tsx index 83252a2..a2aa60d 100644 --- a/src/components/PageComponents/GetMoney.tsx +++ b/src/components/PageComponents/GetMoney.tsx @@ -1,53 +1,154 @@ import { useSession } from "next-auth/react" -import { useRef, useState } from "react" -import { AlertCircle, ArrowDown } from "lucide-react" +import { type ReactNode, useRef, useState } from "react" +import { AlertCircle, AlertTriangle, ArrowDown, Undo, X } from "lucide-react" import ActionResponsePopup, { animate, type AnimationHandle, } from "~/components/General/ActionResponsePopup" +import DecimalInput from "~/components/General/DecimalInput" +import { calculateSplit, type SplitMode } from "~/helper/generalFunctions" import { api } from "~/utils/api" type Props = { comment?: string sendDescription?: string + groupMode?: boolean +} + +type MoneyRequest = { moneySourceUserId: string; amount: number } + +// amount and note are shared by both forms, so they survive switching between them +type FormProps = { + sendDescription?: string + amount: number + setAmount: (amount: number) => void + note: string + setNote: (note: string) => void + submit: (requests: MoneyRequest[], onSuccess: () => void) => Promise + isPending: boolean } const GetMoney = (props: Props) => { - const allUserRequest = api.user.getAllUsers.useQuery() const animationRef = useRef(null) - const trpcUtils = api.useUtils() + const [amount, setAmount] = useState(1) + const [note, setNote] = useState(props.comment || "") + + const apiRetractMoney = api.transaction.retractMoney.useMutation() + + const submit = async (requests: MoneyRequest[], onSuccess: () => void) => { + await apiRetractMoney.mutateAsync( + { requests, note }, + { + onError: (error) => { + console.error(error) + animate(animationRef, "failure", error.message) + }, + onSuccess: async () => { + animate(animationRef, "success") + setAmount(1) + setNote("") + onSuccess() + await trpcUtils.user.invalidate() + }, + } + ) + } + + const formProps: FormProps = { + sendDescription: props.sendDescription, + amount, + setAmount, + note, + setNote, + submit, + isPending: apiRetractMoney.isPending, + } + + return ( + <> +
      +
      + {props.groupMode ? : } +
      +
      + + + + ) +} + +const AmountInput = (props: { + label: string + value: number + onChange: (value: number) => void + disabled?: boolean + hint?: string +}) => ( +
      + +
      + + + € + +
      + {props.hint && ( + + )} +
      +) + +const NoteInput = (props: { value: string; onChange: (value: string) => void }) => ( +
      + +