diff --git a/apps/db/migrations/48_message_attachments.up.sql b/apps/db/migrations/48_message_attachments.up.sql new file mode 100644 index 0000000..3ed13d8 --- /dev/null +++ b/apps/db/migrations/48_message_attachments.up.sql @@ -0,0 +1,42 @@ +CREATE TABLE IF NOT EXISTS public.messages_attachments +( + "messageId" uuid NOT NULL, + "userStorageId" uuid NOT NULL +); + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'messages_attachments_pkey' + AND conrelid = 'public.messages_attachments'::regclass + ) THEN + ALTER TABLE public.messages_attachments + ADD CONSTRAINT messages_attachments_pkey PRIMARY KEY ("messageId", "userStorageId"); + END IF; +END $$; + + +DO $$ BEGIN IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'msg_attach_msgid' + AND conrelid = 'public.messages_attachments'::regclass + ) THEN + ALTER TABLE public.messages_attachments + ADD CONSTRAINT msg_attach_msgid + FOREIGN KEY ("messageId") REFERENCES public.messages (messageid) + ON UPDATE CASCADE ON DELETE CASCADE; + END IF; +END $$; + +DO $$ BEGIN IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'msg_attach_storageid' + AND conrelid = 'public.messages_attachments'::regclass + ) THEN + ALTER TABLE public.messages_attachments + ADD CONSTRAINT msg_attach_storageid + FOREIGN KEY ("userStorageId") REFERENCES public.users_storage (user_storage_id) + ON UPDATE CASCADE ON DELETE CASCADE; + END IF; +END $$; \ No newline at end of file diff --git a/apps/infoalloggi/kanel.config.js b/apps/infoalloggi/kanel.config.js index f32704d..f8da207 100644 --- a/apps/infoalloggi/kanel.config.js +++ b/apps/infoalloggi/kanel.config.js @@ -2,6 +2,46 @@ const { makeKyselyHook } = require("kanel-kysely"); const { makePgTsGenerator } = require("kanel"); const ignoredTables = ["schema_migrations"]; +/** + * Allows for injection of custom types from the mapping of `table.field` + * @param {import("kanel").TypeMap} mappings + * @returns {import("kanel").PreRenderHook} + */ +const specificTypes = (mappings) => { + /** @type {import("kanel").PreRenderHook} */ + return async (outputAcc, instantiatedConfig) => { + const output = { ...outputAcc }; + + for (const path of Object.keys(output)) { + const file = outputAcc[path]; + const declarations = file.declarations.map((decl) => { + const declaration = { ...decl }; + + if (declaration.declarationType === "interface") { + for (const field of declaration.properties) { + const key = `${declaration.name}.${field.name}`; + if (mappings[key]) { + field.typeName = mappings[key].name; + declaration.typeImports = [ + ...(declaration.typeImports || []), + ...mappings[key].typeImports, + ]; + } + } + } + return declaration; + }); + + output[path] = { + ...file, + declarations, + }; + } + + return output; + }; +} + /** @type {import('kanel').Config} */ module.exports = { connection: { @@ -19,11 +59,25 @@ module.exports = { "pg_catalog.bpchar": "string", 'pg_catalog.numeric': 'number', //"pg_catalog.json": "string", + }, - preRenderHooks: [makeKyselyHook()], + preRenderHooks: [ + + specificTypes({ + "Annunci.caratteristiche": { + name: "Caratteristiche", + typeImports: [{ name: "Caratteristiche", path: "src/utils/kanel-types.ts", importAsType: true, isAbsolute: false, isDefault: false }], + }, + }), + makeKyselyHook(), + ], }), ], preDeleteOutputFolder: true, + + typescriptConfig: { + enumStyle: "enum", + }, }; diff --git a/apps/infoalloggi/package-lock.json b/apps/infoalloggi/package-lock.json index 07b2d05..6a2c3a0 100644 --- a/apps/infoalloggi/package-lock.json +++ b/apps/infoalloggi/package-lock.json @@ -26,7 +26,7 @@ "@stripe/stripe-js": "^8.8.0", "@t3-oss/env-nextjs": "^0.13.11", "@tailwindcss/forms": "^0.5.11", - "@tailwindcss/postcss": "^4.1.18", + "@tailwindcss/postcss": "^4.2.2", "@tailwindcss/typography": "^0.5.19", "@tanstack/react-query": "^5.90.19", "@tanstack/react-table": "^8.21.3", @@ -61,7 +61,7 @@ "nextjs-progressbar": "^0.0.16", "nodemailer": "^8.0.5", "nodemailer-html-to-text": "^3.2.0", - "nuqs": "^2.8.6", + "nuqs": "^2.8.9", "papaparse": "^5.5.3", "pdf-lib": "^1.17.1", "pg": "^8.19.0", @@ -71,7 +71,7 @@ "react-colorful": "^5.6.1", "react-day-picker": "^9.11.1", "react-dom": "^19.2.3", - "react-hook-form": "^7.71.1", + "react-hook-form": "^7.72.1", "react-hot-toast": "^2.5.2", "react-is": "^19.2.3", "react-leaflet": "^5.0.0", @@ -86,10 +86,10 @@ "superjson": "^2.2.6", "tailwind-merge": "^3.5.0", "tailwind-scrollbar": "^4.0.2", - "tailwindcss": "^4.1.18", + "tailwindcss": "^4.2.2", "tunnel-rat": "^0.1.2", "tw-animate-css": "^1.4.0", - "type-fest": "^5.4.1", + "type-fest": "^5.5.0", "uuid": "^13.0.0", "vaul": "^1.1.2", "zod": "^4.3.6" @@ -1804,6 +1804,8 @@ }, "node_modules/@jridgewell/remapping": { "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -5193,6 +5195,29 @@ "node": ">=14.0.0" } }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", + "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", + "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@rolldown/binding-win32-arm64-msvc": { "version": "1.0.0-rc.15", "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz", @@ -5357,43 +5382,47 @@ } }, "node_modules/@tailwindcss/node": { - "version": "4.1.18", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.4.tgz", + "integrity": "sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA==", "license": "MIT", "dependencies": { - "@jridgewell/remapping": "^2.3.4", - "enhanced-resolve": "^5.18.3", + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", - "lightningcss": "1.30.2", + "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", - "tailwindcss": "4.1.18" + "tailwindcss": "4.2.4" } }, "node_modules/@tailwindcss/oxide": { - "version": "4.1.18", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.4.tgz", + "integrity": "sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q==", "license": "MIT", "engines": { - "node": ">= 10" + "node": ">= 20" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.18", - "@tailwindcss/oxide-darwin-arm64": "4.1.18", - "@tailwindcss/oxide-darwin-x64": "4.1.18", - "@tailwindcss/oxide-freebsd-x64": "4.1.18", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", - "@tailwindcss/oxide-linux-x64-musl": "4.1.18", - "@tailwindcss/oxide-wasm32-wasi": "4.1.18", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" + "@tailwindcss/oxide-android-arm64": "4.2.4", + "@tailwindcss/oxide-darwin-arm64": "4.2.4", + "@tailwindcss/oxide-darwin-x64": "4.2.4", + "@tailwindcss/oxide-freebsd-x64": "4.2.4", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.4", + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.4", + "@tailwindcss/oxide-linux-arm64-musl": "4.2.4", + "@tailwindcss/oxide-linux-x64-gnu": "4.2.4", + "@tailwindcss/oxide-linux-x64-musl": "4.2.4", + "@tailwindcss/oxide-wasm32-wasi": "4.2.4", + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.4", + "@tailwindcss/oxide-win32-x64-msvc": "4.2.4" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz", - "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.4.tgz", + "integrity": "sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g==", "cpu": [ "arm64" ], @@ -5403,13 +5432,13 @@ "android" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz", - "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.4.tgz", + "integrity": "sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg==", "cpu": [ "arm64" ], @@ -5419,13 +5448,13 @@ "darwin" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz", - "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.4.tgz", + "integrity": "sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg==", "cpu": [ "x64" ], @@ -5435,13 +5464,13 @@ "darwin" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz", - "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.4.tgz", + "integrity": "sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw==", "cpu": [ "x64" ], @@ -5451,13 +5480,13 @@ "freebsd" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz", - "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.4.tgz", + "integrity": "sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA==", "cpu": [ "arm" ], @@ -5467,13 +5496,13 @@ "linux" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz", - "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.4.tgz", + "integrity": "sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw==", "cpu": [ "arm64" ], @@ -5483,13 +5512,13 @@ "linux" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz", - "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.4.tgz", + "integrity": "sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g==", "cpu": [ "arm64" ], @@ -5499,13 +5528,13 @@ "linux" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz", - "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.4.tgz", + "integrity": "sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA==", "cpu": [ "x64" ], @@ -5515,13 +5544,13 @@ "linux" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz", - "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.4.tgz", + "integrity": "sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA==", "cpu": [ "x64" ], @@ -5531,13 +5560,13 @@ "linux" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz", - "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.4.tgz", + "integrity": "sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw==", "bundleDependencies": [ "@napi-rs/wasm-runtime", "@emnapi/core", @@ -5552,21 +5581,21 @@ "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1", + "@emnapi/core": "^1.8.1", + "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", - "@napi-rs/wasm-runtime": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", - "tslib": "^2.4.0" + "tslib": "^2.8.1" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", - "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.4.tgz", + "integrity": "sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ==", "cpu": [ "arm64" ], @@ -5576,11 +5605,13 @@ "win32" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.18", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.4.tgz", + "integrity": "sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw==", "cpu": [ "x64" ], @@ -5590,18 +5621,20 @@ "win32" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/postcss": { - "version": "4.1.18", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.2.4.tgz", + "integrity": "sha512-wgAVj6nUWAolAu8YFvzT2cTBIElWHkjZwFYovF+xsqKsW2ADxM/X2opxj5NsF/qVccAOjRNe8X2IdPzMsWyHTg==", "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", - "@tailwindcss/node": "4.1.18", - "@tailwindcss/oxide": "4.1.18", - "postcss": "^8.4.41", - "tailwindcss": "4.1.18" + "@tailwindcss/node": "4.2.4", + "@tailwindcss/oxide": "4.2.4", + "postcss": "^8.5.6", + "tailwindcss": "4.2.4" } }, "node_modules/@tailwindcss/typography": { @@ -7450,11 +7483,13 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.18.4", + "version": "5.21.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.0.tgz", + "integrity": "sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "tapable": "^2.3.3" }, "engines": { "node": ">=10.13.0" @@ -9237,7 +9272,9 @@ "license": "MIT" }, "node_modules/lightningcss": { - "version": "1.30.2", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", "license": "MPL-2.0", "dependencies": { "detect-libc": "^2.0.3" @@ -9250,23 +9287,23 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "lightningcss-android-arm64": "1.30.2", - "lightningcss-darwin-arm64": "1.30.2", - "lightningcss-darwin-x64": "1.30.2", - "lightningcss-freebsd-x64": "1.30.2", - "lightningcss-linux-arm-gnueabihf": "1.30.2", - "lightningcss-linux-arm64-gnu": "1.30.2", - "lightningcss-linux-arm64-musl": "1.30.2", - "lightningcss-linux-x64-gnu": "1.30.2", - "lightningcss-linux-x64-musl": "1.30.2", - "lightningcss-win32-arm64-msvc": "1.30.2", - "lightningcss-win32-x64-msvc": "1.30.2" + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" } }, "node_modules/lightningcss-android-arm64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", - "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", "cpu": [ "arm64" ], @@ -9284,9 +9321,9 @@ } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", - "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", "cpu": [ "arm64" ], @@ -9304,9 +9341,9 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", - "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", "cpu": [ "x64" ], @@ -9324,9 +9361,9 @@ } }, "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", - "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", "cpu": [ "x64" ], @@ -9344,9 +9381,9 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", - "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", "cpu": [ "arm" ], @@ -9364,9 +9401,9 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", - "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", "cpu": [ "arm64" ], @@ -9384,9 +9421,9 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", - "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", "cpu": [ "arm64" ], @@ -9404,9 +9441,9 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", - "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", "cpu": [ "x64" ], @@ -9424,9 +9461,9 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", - "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", "cpu": [ "x64" ], @@ -9444,9 +9481,9 @@ } }, "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", - "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", "cpu": [ "arm64" ], @@ -9464,7 +9501,9 @@ } }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.2", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", "cpu": [ "x64" ], @@ -10201,7 +10240,9 @@ "license": "MIT" }, "node_modules/nuqs": { - "version": "2.8.6", + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/nuqs/-/nuqs-2.8.9.tgz", + "integrity": "sha512-8ou6AEwsxMWSYo2qkfZtYFVzngwbKmg4c00HVxC1fF6CEJv3Fwm6eoZmfVPALB+vw8Udo7KL5uy96PFcYe1BIQ==", "license": "MIT", "dependencies": { "@standard-schema/spec": "1.0.0" @@ -11470,7 +11511,9 @@ } }, "node_modules/react-hook-form": { - "version": "7.71.1", + "version": "7.74.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.74.0.tgz", + "integrity": "sha512-yR6wHr99p9wFv686jhRWVSFhUvDvNbdUf2dKlbno8/VKOCuoNobDGC6S+M2dua9A9Yo8vpcrp8assIYbsZCQ9g==", "license": "MIT", "peer": true, "engines": { @@ -12668,12 +12711,16 @@ } }, "node_modules/tailwindcss": { - "version": "4.1.18", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.4.tgz", + "integrity": "sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==", "license": "MIT", "peer": true }, "node_modules/tapable": { - "version": "2.3.0", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", "license": "MIT", "engines": { "node": ">=6" @@ -12873,7 +12920,9 @@ } }, "node_modules/type-fest": { - "version": "5.4.1", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.6.0.tgz", + "integrity": "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==", "license": "(MIT OR CC0-1.0)", "dependencies": { "tagged-tag": "^1.0.0" @@ -13239,263 +13288,6 @@ "vite": "*" } }, - "node_modules/vite/node_modules/lightningcss": { - "version": "1.32.0", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" - } - }, - "node_modules/vite/node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/vite/node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/vite/node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/vite/node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/vite/node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/vite/node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/vite/node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/vite/node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/vite/node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/vite/node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/vite/node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/vitest": { "version": "4.1.4", "dev": true, diff --git a/apps/infoalloggi/package.json b/apps/infoalloggi/package.json index 4486d49..e754805 100644 --- a/apps/infoalloggi/package.json +++ b/apps/infoalloggi/package.json @@ -158,4 +158,4 @@ } } } -} \ No newline at end of file +} diff --git a/apps/infoalloggi/src/components/chat/chat-attachments.tsx b/apps/infoalloggi/src/components/chat/chat-attachments.tsx index 743427f..f933503 100644 --- a/apps/infoalloggi/src/components/chat/chat-attachments.tsx +++ b/apps/infoalloggi/src/components/chat/chat-attachments.tsx @@ -1,185 +1,65 @@ -import { differenceInDays, differenceInMinutes, format } from "date-fns"; -import { Paperclip } from "lucide-react"; +import { Download } from "lucide-react"; import Link from "next/link"; import { ExtIcon } from "~/components/area-riservata/allegati"; -import { - Credenza, - CredenzaBody, - CredenzaClose, - CredenzaContent, - CredenzaDescription, - CredenzaFooter, - CredenzaHeader, - CredenzaTitle, - CredenzaTrigger, -} from "~/components/custom_ui/credenza"; + import { LoadingPage } from "~/components/loading"; -import { Button, buttonVariants } from "~/components/ui/button"; -import { UploadComponent } from "~/components/upload_modal"; -import { cn } from "~/lib/utils"; -import { useChatContext } from "~/providers/ChatProvider"; -import { useTranslation } from "~/providers/I18nProvider"; +import { Button } from "~/components/ui/button"; +import { getStorageUrl } from "~/lib/storage_utils"; +import type { MessagesMessageid } from "~/schemas/public/Messages"; import { api } from "~/utils/api"; -export const ChatAttachments = () => { - const { session, userinfo } = useChatContext(); - const { t } = useTranslation(); - const { - data: attachments, - isLoading: attachmentsLoading, - refetch, - } = api.storage.retrieveUserFileData.useQuery( - { userId: userinfo.id }, +export const ChatAttachments = ({ + messageId, +}: { + messageId: MessagesMessageid; +}) => { + const { data, isLoading } = api.chat.getMessageAttachments.useQuery( + { messageId }, { refetchOnMount: true }, ); + if (isLoading) { + return ; + } + if (!data || data.length === 0) { + return null; + } + return ( - o && refetch()}> - - - - - - Allegati - - Chat Attachments - - - - - -
- {attachmentsLoading ? ( - - ) : ( - <> - {!attachments || attachments.length === 0 ? ( -
-

- {t.allegati.nessun_allegato} -

-
- ) : ( -
    -

    - {t.allegati.allegati_recenti} -

    - {attachments?.map((file, idx) => { - if (idx >= 3) return null; - - return ( -
  • - idx - }`} - > - console.log("touching")} - target="_blank" - > - - - - {TimeSince(new Date(file.uploadedAt))} - -
  • - ); - })} -
- )} - - )} +
+
+ +
+ + {file.originalName} +
- - {t.allegati.i_tuoi_allegati} - {attachments && attachments.length > 0 && ( - - {attachments.length} - - )} - - - - - - - - - + +
+ + + +
+
+ ))} + ); }; - -const TimeSince = (date: Date) => { - const minsSince = Math.abs(differenceInMinutes(date, new Date())); - const daysSince = Math.abs(differenceInDays(date, new Date())); - - const relevantMinsIncrements = [1, 3, 5, 10, 15, 30, 60, 120]; - - if (daysSince === 0) { - if (minsSince < 1) { - return "ora"; - } - - const closestToNow = relevantMinsIncrements.reduce((prev, curr) => { - return Math.abs(curr - minsSince) < Math.abs(prev - minsSince) - ? curr - : prev; - }); - - if (closestToNow === 60) { - return "~1 ora fa"; - } - - if (closestToNow === 120 && minsSince < 121) { - return "~2 ore fa"; - } - - return `~${closestToNow} min fa`; - } - - if (daysSince === 1) { - return "ieri"; - } - - if (daysSince < 30) { - return `${daysSince} giorni fa`; - } - - return format(date, "dd/MM/yyyy"); -}; diff --git a/apps/infoalloggi/src/components/chat/chat-bottombar.tsx b/apps/infoalloggi/src/components/chat/chat-bottombar.tsx index bcfc922..2d6befc 100644 --- a/apps/infoalloggi/src/components/chat/chat-bottombar.tsx +++ b/apps/infoalloggi/src/components/chat/chat-bottombar.tsx @@ -1,18 +1,29 @@ import { ArrowDown, + Paperclip, SendHorizontal, SmilePlusIcon, ThumbsUp, X, } from "lucide-react"; import { type ChangeEvent, useCallback, useEffect, useState } from "react"; -import { ChatAttachments } from "~/components/chat/chat-attachments"; +import toast from "react-hot-toast"; import { AutosizeTextarea } from "~/components/custom_ui/autoResizeTextArea"; import { EmojiPicker, EmojiPickerContent, EmojiPickerSearch, } from "~/components/custom_ui/emoji-picker"; +import { + FileUpload, + FileUploadItem, + FileUploadItemDelete, + FileUploadItemMetadata, + FileUploadItemPreview, + FileUploadList, + FileUploadTrigger, +} from "~/components/custom_ui/fileUpload"; +import { LoadingSpinner } from "~/components/spinner"; import { Button } from "~/components/ui/button"; import { Popover, @@ -20,9 +31,11 @@ import { PopoverTrigger, } from "~/components/ui/popover"; import { useThrottledIsTypingMutation } from "~/hooks/chatHooks"; +import { uploadHandler } from "~/hooks/storageClienSideHooks"; import { useMediaQuery } from "~/hooks/use-media-query"; import { useChatContext } from "~/providers/ChatProvider"; import { useTranslation } from "~/providers/I18nProvider"; +import type { UsersStorageUserStorageId } from "~/schemas/public/UsersStorage"; import { api } from "~/utils/api"; export default function ChatBottombar() { @@ -31,14 +44,17 @@ export default function ChatBottombar() { session, inputRef, replyTrg, + userinfo, setReplyTrg, messagesContainerRef, } = useChatContext(); - const { locale } = useTranslation(); + const { locale, t } = useTranslation(); const [message, setMessage] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const [files, setFiles] = useState([]); const [isFocused, setIsFocused] = useState(false); - const { mutate: addMutation } = api.chatSSE.sendMessage.useMutation({ + const { mutateAsync: addMutation } = api.chatSSE.sendMessage.useMutation({ onError: (err) => { console.error("Failed to send message", err); }, @@ -51,26 +67,94 @@ export default function ChatBottombar() { } }, }); + const utils = api.useUtils(); + const { mutateAsync: setUserStorage } = + api.storage.addUserStorageEntry.useMutation({ + onMutate: () => { + toast(t.allegati.allegato_caricato, { + icon: "📤", + toasterId: "uploading-toast", + }); + }, + onSettled: async () => { + await utils.storage.getAll.invalidate(); + await utils.storage.retrieveUserFileData.invalidate({ + userId: userinfo.id, + }); + }, + }); + const uploadFiles = async () => { + if (files.length === 0) { + return []; + } + const uploadedFiles: { + storageId: string; + userStorageId: UsersStorageUserStorageId; + }[] = []; - const handleThumbsUp = () => { - addMutation({ + try { + const from_admin = session.isAdmin; + + const { status, attachments } = await uploadHandler({ + files, + from_admin, + }); + if (!status) { + toast.error(t.allegati.errore_caricamento, { + icon: "❌", + toasterId: "uploading-toast", + }); + + return []; + } + for (const storageId of attachments) { + const res = await setUserStorage({ + storageId, + userId: userinfo.id, + from_admin, + }); + if (!res) continue; + uploadedFiles.push({ storageId, userStorageId: res.user_storage_id }); + } + + return uploadedFiles; + } catch (error) { + toast.error( + error instanceof Error ? error.message : t.allegati.errore_sconosciuto, + { icon: "❌", toasterId: "uploading-toast" }, + ); + return []; + } finally { + setFiles([]); + } + }; + + const handleThumbsUp = async () => { + setIsLoading(true); + const attachments = await uploadFiles(); + await addMutation({ reply_to_id: replyTrg?.messageid || null, chatId, message: "👍", sender: session.id, + attachments, }); + setIsLoading(false); }; - const handleSend = () => { - if (message.length !== 0) { - addMutation({ - reply_to_id: replyTrg?.messageid || null, - chatId, - message: message.trim(), - sender: session.id, - }); - } + const handleSend = async () => { + setIsLoading(true); + const attachments = await uploadFiles(); + await addMutation({ + reply_to_id: replyTrg?.messageid || null, + chatId, + message: message.trim(), + sender: session.id, + attachments, + }); + setIsLoading(false); }; + const isTypingMutation = useThrottledIsTypingMutation({ chatId, username: session.username, @@ -112,105 +196,150 @@ export default function ChatBottombar() { el.addEventListener("scroll", handleScroll, { passive: true }); return () => el.removeEventListener("scroll", handleScroll); }, [messagesContainerRef]); + return ( -
- {showScrollDown && ( - - )} - {replyTrg && ( -
-
- Rispondi: -
- {replyTrg.message} -
+
+ +
+ {showScrollDown && ( + )} + + {files.map((file, index) => ( + // biome-ignore lint/suspicious/noArrayIndexKey: + + + + {isLoading ? ( +
+ +
+ ) : ( + + + + )} +
+ ))} +
+ {replyTrg && ( +
+
+ Rispondi: +
+ {replyTrg.message} +
+ +
+
+ )} +
+ + + + +
+ setIsFocused(false)} + onChange={(e: ChangeEvent) => { + setMessage(e.target.value); + }} + onFocus={() => setIsFocused(true)} + onKeyDown={(e) => { + if (e.key === "Enter") { + e.preventDefault(); + if (e.shiftKey) { + setMessage((prev) => `${prev}\n`); + } else { + handleSend(); + } + } + }} + placeholder="Aa" + ref={inputRef} + value={message} + /> + {isMobile && ( +
+ + + + + + { + setMessage(message + emoji); + }} + > + + + + + +
+ )} +
+
- )} -
-
- -
-
- setIsFocused(false)} - onChange={(e: ChangeEvent) => { - setMessage(e.target.value); - }} - onFocus={() => setIsFocused(true)} - onKeyDown={(e) => { - if (e.key === "Enter") { - e.preventDefault(); - if (e.shiftKey) { - setMessage((prev) => `${prev}\n`); - } else { - handleSend(); - } - } - }} - placeholder="Aa" - ref={inputRef} - value={message} - /> - {isMobile && ( -
- - - - - - { - setMessage(message + emoji); - }} - > - - - - - -
- )} -
- -
+
); } diff --git a/apps/infoalloggi/src/components/chat/chat-list.tsx b/apps/infoalloggi/src/components/chat/chat-list.tsx index 017a943..570524e 100644 --- a/apps/infoalloggi/src/components/chat/chat-list.tsx +++ b/apps/infoalloggi/src/components/chat/chat-list.tsx @@ -10,6 +10,7 @@ import { useState, } from "react"; import toast from "react-hot-toast"; +import { ChatAttachments } from "~/components/chat/chat-attachments"; import { ChatBubble, ChatBubbleMessage, @@ -245,7 +246,7 @@ export const ChatList = () => { key={message.messageid} > {!isPrevSameDay && ( - + {new Date(message.time).toLocaleDateString("it", { day: "2-digit", month: "2-digit", @@ -291,7 +292,7 @@ export const ChatList = () => { {
)} + {message.has_attachments && ( + + )} {message.message} diff --git a/apps/infoalloggi/src/components/chat/chat-topbar.tsx b/apps/infoalloggi/src/components/chat/chat-topbar.tsx index 4c7267b..936d7d3 100644 --- a/apps/infoalloggi/src/components/chat/chat-topbar.tsx +++ b/apps/infoalloggi/src/components/chat/chat-topbar.tsx @@ -83,7 +83,18 @@ export default function ChatTopbar() {
-
+ +
+ + Allegati + {session.isAdmin && ( ; - caratteristiche: ColumnType; + caratteristiche: ColumnType; homepage: ColumnType; diff --git a/apps/infoalloggi/src/server/api/routers/chat.ts b/apps/infoalloggi/src/server/api/routers/chat.ts index 8488d9b..8e541e5 100644 --- a/apps/infoalloggi/src/server/api/routers/chat.ts +++ b/apps/infoalloggi/src/server/api/routers/chat.ts @@ -17,8 +17,14 @@ import { getChatEtichette, removeChatEtichette, } from "~/server/controllers/etichette.controller"; +import { retrieveChatMessageFiles } from "~/server/controllers/storage.controller"; import { db } from "~/server/db"; -import { zChatId, zEtichettaId, zUserId } from "~/server/utils/zod_types"; +import { + zChatId, + zEtichettaId, + zMessageId, + zUserId, +} from "~/server/utils/zod_types"; export const chatRouter = createTRPCRouter({ addEtichettaToChat: protectedProcedure @@ -90,4 +96,9 @@ export const chatRouter = createTRPCRouter({ etichettaid: input.etichettaId, }); }), + getMessageAttachments: protectedProcedure + .input(z.object({ messageId: zMessageId })) + .query(async ({ input }) => { + return await retrieveChatMessageFiles({ messageId: input.messageId }); + }), }); diff --git a/apps/infoalloggi/src/server/api/routers/chat_sse.ts b/apps/infoalloggi/src/server/api/routers/chat_sse.ts index 0c740bb..39b6349 100644 --- a/apps/infoalloggi/src/server/api/routers/chat_sse.ts +++ b/apps/infoalloggi/src/server/api/routers/chat_sse.ts @@ -25,6 +25,7 @@ import { zChatId, zMessageId, zUserId, + zUserStorageId, } from "~/server/utils/zod_types"; import { getKeydbClient, getSubClient } from "~/utils/keydb"; import { withUserColorStr } from "~/utils/kysely-helper"; @@ -110,6 +111,7 @@ export const chatSSERouter = createTRPCRouter({ "messages.reply_to_id", "users.nome as sender_nome", "users.isAdmin as sender_isAdmin", + withUserColorStr(), jsonObjectFrom( eb @@ -128,12 +130,25 @@ export const chatSSERouter = createTRPCRouter({ ) .limit(1), ).as("reply_source"), + eb + .exists( + eb + .selectFrom("messages_attachments") + .select("messages_attachments.userStorageId") + .whereRef( + "messages_attachments.messageId", + "=", + "messages.messageid", + ), + ) + .$castTo() + .as("has_attachments"), ]) .where("messages.chatid", "=", input.chatId) .where("messages.messageid", ">=", input.targetMsgId) .orderBy("messages.messageid", "asc") .execute(); - const prevCheck = await db + const prevCheck = await db .selectFrom("messages") .where("chatid", "=", input.chatId) .where("messageid", "<", input.targetMsgId) @@ -141,7 +156,7 @@ export const chatSSERouter = createTRPCRouter({ .orderBy("messageid", "desc") .limit(1) .executeTakeFirst(); - + return { items: msgs, nextCursor: prevCheck?.messageid ?? null, @@ -187,6 +202,19 @@ export const chatSSERouter = createTRPCRouter({ .whereRef("messages.reply_to_id", "=", "reply_source.messageid") .limit(1), ).as("reply_source"), + eb + .exists( + eb + .selectFrom("messages_attachments") + .select("messages_attachments.userStorageId") + .whereRef( + "messages_attachments.messageId", + "=", + "messages.messageid", + ), + ) + .$castTo() + .as("has_attachments"), ]) .where("users.nome", "is not", null) @@ -225,6 +253,13 @@ export const chatSSERouter = createTRPCRouter({ sender: zUserId, is_forwarded: z.boolean().default(false), reply_to_id: zMessageId.nullable().default(null), + attachments: z + .object({ + userStorageId: zUserStorageId, + storageId: z.string(), + }) + .array() + .default([]), }), ) .mutation(async ({ input }) => { @@ -242,6 +277,20 @@ export const chatSSERouter = createTRPCRouter({ .executeTakeFirstOrThrow( () => new Error("Failed to insert message into database"), ); + let hasAttachments = false; + if (input.attachments.length > 0) { + const newAttachments = await db + .insertInto("messages_attachments") + .values( + input.attachments.map(({ userStorageId }) => ({ + messageId: newMessage.messageid, + userStorageId, + })), + ) + .returning("userStorageId") + .execute(); + hasAttachments = newAttachments.length > 0; + } const rs = await db .selectFrom("messages") .innerJoin("users", "users.id", "messages.sender") @@ -274,7 +323,7 @@ export const chatSSERouter = createTRPCRouter({ const payload: Message = { ...newMessage, ...userInfos, - + has_attachments: hasAttachments, reply_source: rs ? { messageid: rs.messageid, @@ -545,6 +594,19 @@ export const chatSSERouter = createTRPCRouter({ .whereRef("messages.reply_to_id", "=", "reply_source.messageid") .limit(1), ).as("reply_source"), + eb + .exists( + eb + .selectFrom("messages_attachments") + .select("messages_attachments.userStorageId") + .whereRef( + "messages_attachments.messageId", + "=", + "messages.messageid", + ), + ) + .$castTo() + .as("has_attachments"), ]) .where("messages.chatid", "=", input.chatId) .where("messages.messageid", ">", lastMessageId) diff --git a/apps/infoalloggi/src/server/controllers/storage.controller.ts b/apps/infoalloggi/src/server/controllers/storage.controller.ts index 5e66793..4970c23 100644 --- a/apps/infoalloggi/src/server/controllers/storage.controller.ts +++ b/apps/infoalloggi/src/server/controllers/storage.controller.ts @@ -1,4 +1,5 @@ import { TRPCError } from "@trpc/server"; +import type { MessagesMessageid } from "~/schemas/public/Messages"; import type { UsersId } from "~/schemas/public/Users"; import type { NewUsersStorage } from "~/schemas/public/UsersStorage"; import { db, type Querier } from "~/server/db"; @@ -83,6 +84,43 @@ export const retrieveUserFiles = async ({ userId }: { userId: UsersId }) => { } }; +export const retrieveChatMessageFiles = async ({ + messageId, +}: { + messageId: MessagesMessageid; +}) => { + try { + const messageFiles = await db + .selectFrom("messages_attachments") + .innerJoin( + "users_storage", + "users_storage.user_storage_id", + "messages_attachments.userStorageId", + ) + .select("users_storage.storageId") + .orderBy("messages_attachments.messageId", "asc") + .where("messages_attachments.messageId", "=", messageId) + .execute(); + + const { available, unavailable } = await retriveFilesSafely( + messageFiles.map((f) => f.storageId), + ); + + //remove entries in users_storage for files that no longer exist in storage + await db + .deleteFrom("users_storage") + .where("storageId", "in", unavailable) + .execute(); + + return available; + } catch (e) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: `Error while getting chat message files: ${(e as Error).message}`, + }); + } +}; + export const getAllWithFromAdmin = async () => { try { const files = await db diff --git a/apps/infoalloggi/src/server/utils/zod_types.ts b/apps/infoalloggi/src/server/utils/zod_types.ts index 99ce307..4f6d4ce 100644 --- a/apps/infoalloggi/src/server/utils/zod_types.ts +++ b/apps/infoalloggi/src/server/utils/zod_types.ts @@ -13,6 +13,7 @@ import type { RinnoviId } from "~/schemas/public/Rinnovi"; import type { ServizioServizioId } from "~/schemas/public/Servizio"; import type { TestiEStringheStingaId } from "~/schemas/public/TestiEStringhe"; import type { UsersId } from "~/schemas/public/Users"; +import type { UsersStorageUserStorageId } from "~/schemas/public/UsersStorage"; export const zUserId = z.custom( (val) => typeof val === "string", @@ -64,6 +65,11 @@ export const zTestiEStringheStingaId = z.custom( // "falied to validate EventDataTypes", // ); +export const zUserStorageId = z.custom( + (val) => typeof val === "string", + "falied to validate UserStorageId", +); + export const zBanlistId = z.custom( (val) => typeof val === "number", "falied to validate BanlistId", @@ -110,6 +116,7 @@ export const MessageDataSchema = z.object({ color_str: z.string(), is_forwarded: z.boolean(), reply_to_id: zMessageId.nullable(), + has_attachments: z.boolean(), reply_source: z .object({ messageid: zMessageId,