feat(chat): enhance chat functionality with file attachments
- Added file attachment support in chat messages, allowing users to upload and send files. - Implemented a new API endpoint to retrieve message attachments. - Updated chat components to display attachments and handle file uploads. - Improved user experience with loading indicators and toast notifications for file uploads. - Refactored chat bottom bar to integrate file upload functionality seamlessly. - Enhanced chat list to show attachments associated with messages. - Updated database schema to support attachments in chat messages. Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
parent
9886e5042a
commit
ff722eb2a7
13 changed files with 704 additions and 673 deletions
42
apps/db/migrations/48_message_attachments.up.sql
Normal file
42
apps/db/migrations/48_message_attachments.up.sql
Normal file
|
|
@ -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 $$;
|
||||||
|
|
@ -2,6 +2,46 @@ const { makeKyselyHook } = require("kanel-kysely");
|
||||||
const { makePgTsGenerator } = require("kanel");
|
const { makePgTsGenerator } = require("kanel");
|
||||||
const ignoredTables = ["schema_migrations"];
|
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} */
|
/** @type {import('kanel').Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
connection: {
|
connection: {
|
||||||
|
|
@ -19,11 +59,25 @@ module.exports = {
|
||||||
"pg_catalog.bpchar": "string",
|
"pg_catalog.bpchar": "string",
|
||||||
'pg_catalog.numeric': 'number',
|
'pg_catalog.numeric': 'number',
|
||||||
//"pg_catalog.json": "string",
|
//"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,
|
preDeleteOutputFolder: true,
|
||||||
|
|
||||||
|
typescriptConfig: {
|
||||||
|
enumStyle: "enum",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
562
apps/infoalloggi/package-lock.json
generated
562
apps/infoalloggi/package-lock.json
generated
|
|
@ -26,7 +26,7 @@
|
||||||
"@stripe/stripe-js": "^8.8.0",
|
"@stripe/stripe-js": "^8.8.0",
|
||||||
"@t3-oss/env-nextjs": "^0.13.11",
|
"@t3-oss/env-nextjs": "^0.13.11",
|
||||||
"@tailwindcss/forms": "^0.5.11",
|
"@tailwindcss/forms": "^0.5.11",
|
||||||
"@tailwindcss/postcss": "^4.1.18",
|
"@tailwindcss/postcss": "^4.2.2",
|
||||||
"@tailwindcss/typography": "^0.5.19",
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
"@tanstack/react-query": "^5.90.19",
|
"@tanstack/react-query": "^5.90.19",
|
||||||
"@tanstack/react-table": "^8.21.3",
|
"@tanstack/react-table": "^8.21.3",
|
||||||
|
|
@ -61,7 +61,7 @@
|
||||||
"nextjs-progressbar": "^0.0.16",
|
"nextjs-progressbar": "^0.0.16",
|
||||||
"nodemailer": "^8.0.5",
|
"nodemailer": "^8.0.5",
|
||||||
"nodemailer-html-to-text": "^3.2.0",
|
"nodemailer-html-to-text": "^3.2.0",
|
||||||
"nuqs": "^2.8.6",
|
"nuqs": "^2.8.9",
|
||||||
"papaparse": "^5.5.3",
|
"papaparse": "^5.5.3",
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
"pg": "^8.19.0",
|
"pg": "^8.19.0",
|
||||||
|
|
@ -71,7 +71,7 @@
|
||||||
"react-colorful": "^5.6.1",
|
"react-colorful": "^5.6.1",
|
||||||
"react-day-picker": "^9.11.1",
|
"react-day-picker": "^9.11.1",
|
||||||
"react-dom": "^19.2.3",
|
"react-dom": "^19.2.3",
|
||||||
"react-hook-form": "^7.71.1",
|
"react-hook-form": "^7.72.1",
|
||||||
"react-hot-toast": "^2.5.2",
|
"react-hot-toast": "^2.5.2",
|
||||||
"react-is": "^19.2.3",
|
"react-is": "^19.2.3",
|
||||||
"react-leaflet": "^5.0.0",
|
"react-leaflet": "^5.0.0",
|
||||||
|
|
@ -86,10 +86,10 @@
|
||||||
"superjson": "^2.2.6",
|
"superjson": "^2.2.6",
|
||||||
"tailwind-merge": "^3.5.0",
|
"tailwind-merge": "^3.5.0",
|
||||||
"tailwind-scrollbar": "^4.0.2",
|
"tailwind-scrollbar": "^4.0.2",
|
||||||
"tailwindcss": "^4.1.18",
|
"tailwindcss": "^4.2.2",
|
||||||
"tunnel-rat": "^0.1.2",
|
"tunnel-rat": "^0.1.2",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
"type-fest": "^5.4.1",
|
"type-fest": "^5.5.0",
|
||||||
"uuid": "^13.0.0",
|
"uuid": "^13.0.0",
|
||||||
"vaul": "^1.1.2",
|
"vaul": "^1.1.2",
|
||||||
"zod": "^4.3.6"
|
"zod": "^4.3.6"
|
||||||
|
|
@ -1804,6 +1804,8 @@
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/remapping": {
|
"node_modules/@jridgewell/remapping": {
|
||||||
"version": "2.3.5",
|
"version": "2.3.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
|
||||||
|
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/gen-mapping": "^0.3.5",
|
"@jridgewell/gen-mapping": "^0.3.5",
|
||||||
|
|
@ -5193,6 +5195,29 @@
|
||||||
"node": ">=14.0.0"
|
"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": {
|
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
||||||
"version": "1.0.0-rc.15",
|
"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",
|
"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": {
|
"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",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/remapping": "^2.3.4",
|
"@jridgewell/remapping": "^2.3.5",
|
||||||
"enhanced-resolve": "^5.18.3",
|
"enhanced-resolve": "^5.19.0",
|
||||||
"jiti": "^2.6.1",
|
"jiti": "^2.6.1",
|
||||||
"lightningcss": "1.30.2",
|
"lightningcss": "1.32.0",
|
||||||
"magic-string": "^0.30.21",
|
"magic-string": "^0.30.21",
|
||||||
"source-map-js": "^1.2.1",
|
"source-map-js": "^1.2.1",
|
||||||
"tailwindcss": "4.1.18"
|
"tailwindcss": "4.2.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide": {
|
"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",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 20"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@tailwindcss/oxide-android-arm64": "4.1.18",
|
"@tailwindcss/oxide-android-arm64": "4.2.4",
|
||||||
"@tailwindcss/oxide-darwin-arm64": "4.1.18",
|
"@tailwindcss/oxide-darwin-arm64": "4.2.4",
|
||||||
"@tailwindcss/oxide-darwin-x64": "4.1.18",
|
"@tailwindcss/oxide-darwin-x64": "4.2.4",
|
||||||
"@tailwindcss/oxide-freebsd-x64": "4.1.18",
|
"@tailwindcss/oxide-freebsd-x64": "4.2.4",
|
||||||
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18",
|
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.4",
|
||||||
"@tailwindcss/oxide-linux-arm64-gnu": "4.1.18",
|
"@tailwindcss/oxide-linux-arm64-gnu": "4.2.4",
|
||||||
"@tailwindcss/oxide-linux-arm64-musl": "4.1.18",
|
"@tailwindcss/oxide-linux-arm64-musl": "4.2.4",
|
||||||
"@tailwindcss/oxide-linux-x64-gnu": "4.1.18",
|
"@tailwindcss/oxide-linux-x64-gnu": "4.2.4",
|
||||||
"@tailwindcss/oxide-linux-x64-musl": "4.1.18",
|
"@tailwindcss/oxide-linux-x64-musl": "4.2.4",
|
||||||
"@tailwindcss/oxide-wasm32-wasi": "4.1.18",
|
"@tailwindcss/oxide-wasm32-wasi": "4.2.4",
|
||||||
"@tailwindcss/oxide-win32-arm64-msvc": "4.1.18",
|
"@tailwindcss/oxide-win32-arm64-msvc": "4.2.4",
|
||||||
"@tailwindcss/oxide-win32-x64-msvc": "4.1.18"
|
"@tailwindcss/oxide-win32-x64-msvc": "4.2.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-android-arm64": {
|
"node_modules/@tailwindcss/oxide-android-arm64": {
|
||||||
"version": "4.1.18",
|
"version": "4.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.4.tgz",
|
||||||
"integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==",
|
"integrity": "sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -5403,13 +5432,13 @@
|
||||||
"android"
|
"android"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-darwin-arm64": {
|
"node_modules/@tailwindcss/oxide-darwin-arm64": {
|
||||||
"version": "4.1.18",
|
"version": "4.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.4.tgz",
|
||||||
"integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==",
|
"integrity": "sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -5419,13 +5448,13 @@
|
||||||
"darwin"
|
"darwin"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-darwin-x64": {
|
"node_modules/@tailwindcss/oxide-darwin-x64": {
|
||||||
"version": "4.1.18",
|
"version": "4.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.4.tgz",
|
||||||
"integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==",
|
"integrity": "sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -5435,13 +5464,13 @@
|
||||||
"darwin"
|
"darwin"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-freebsd-x64": {
|
"node_modules/@tailwindcss/oxide-freebsd-x64": {
|
||||||
"version": "4.1.18",
|
"version": "4.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.4.tgz",
|
||||||
"integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==",
|
"integrity": "sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -5451,13 +5480,13 @@
|
||||||
"freebsd"
|
"freebsd"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
|
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
|
||||||
"version": "4.1.18",
|
"version": "4.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.4.tgz",
|
||||||
"integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==",
|
"integrity": "sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
|
@ -5467,13 +5496,13 @@
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
|
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
|
||||||
"version": "4.1.18",
|
"version": "4.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.4.tgz",
|
||||||
"integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==",
|
"integrity": "sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -5483,13 +5512,13 @@
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
|
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
|
||||||
"version": "4.1.18",
|
"version": "4.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.4.tgz",
|
||||||
"integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==",
|
"integrity": "sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -5499,13 +5528,13 @@
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
|
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
|
||||||
"version": "4.1.18",
|
"version": "4.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.4.tgz",
|
||||||
"integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==",
|
"integrity": "sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -5515,13 +5544,13 @@
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
|
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
|
||||||
"version": "4.1.18",
|
"version": "4.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.4.tgz",
|
||||||
"integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==",
|
"integrity": "sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -5531,13 +5560,13 @@
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-wasm32-wasi": {
|
"node_modules/@tailwindcss/oxide-wasm32-wasi": {
|
||||||
"version": "4.1.18",
|
"version": "4.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.4.tgz",
|
||||||
"integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==",
|
"integrity": "sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw==",
|
||||||
"bundleDependencies": [
|
"bundleDependencies": [
|
||||||
"@napi-rs/wasm-runtime",
|
"@napi-rs/wasm-runtime",
|
||||||
"@emnapi/core",
|
"@emnapi/core",
|
||||||
|
|
@ -5552,21 +5581,21 @@
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emnapi/core": "^1.7.1",
|
"@emnapi/core": "^1.8.1",
|
||||||
"@emnapi/runtime": "^1.7.1",
|
"@emnapi/runtime": "^1.8.1",
|
||||||
"@emnapi/wasi-threads": "^1.1.0",
|
"@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",
|
"@tybys/wasm-util": "^0.10.1",
|
||||||
"tslib": "^2.4.0"
|
"tslib": "^2.8.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
||||||
"version": "4.1.18",
|
"version": "4.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.4.tgz",
|
||||||
"integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==",
|
"integrity": "sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -5576,11 +5605,13 @@
|
||||||
"win32"
|
"win32"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
|
"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": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -5590,18 +5621,20 @@
|
||||||
"win32"
|
"win32"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/postcss": {
|
"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",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alloc/quick-lru": "^5.2.0",
|
"@alloc/quick-lru": "^5.2.0",
|
||||||
"@tailwindcss/node": "4.1.18",
|
"@tailwindcss/node": "4.2.4",
|
||||||
"@tailwindcss/oxide": "4.1.18",
|
"@tailwindcss/oxide": "4.2.4",
|
||||||
"postcss": "^8.4.41",
|
"postcss": "^8.5.6",
|
||||||
"tailwindcss": "4.1.18"
|
"tailwindcss": "4.2.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/typography": {
|
"node_modules/@tailwindcss/typography": {
|
||||||
|
|
@ -7450,11 +7483,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/enhanced-resolve": {
|
"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",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"graceful-fs": "^4.2.4",
|
"graceful-fs": "^4.2.4",
|
||||||
"tapable": "^2.2.0"
|
"tapable": "^2.3.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.13.0"
|
"node": ">=10.13.0"
|
||||||
|
|
@ -9237,7 +9272,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss": {
|
"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",
|
"license": "MPL-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"detect-libc": "^2.0.3"
|
"detect-libc": "^2.0.3"
|
||||||
|
|
@ -9250,23 +9287,23 @@
|
||||||
"url": "https://opencollective.com/parcel"
|
"url": "https://opencollective.com/parcel"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"lightningcss-android-arm64": "1.30.2",
|
"lightningcss-android-arm64": "1.32.0",
|
||||||
"lightningcss-darwin-arm64": "1.30.2",
|
"lightningcss-darwin-arm64": "1.32.0",
|
||||||
"lightningcss-darwin-x64": "1.30.2",
|
"lightningcss-darwin-x64": "1.32.0",
|
||||||
"lightningcss-freebsd-x64": "1.30.2",
|
"lightningcss-freebsd-x64": "1.32.0",
|
||||||
"lightningcss-linux-arm-gnueabihf": "1.30.2",
|
"lightningcss-linux-arm-gnueabihf": "1.32.0",
|
||||||
"lightningcss-linux-arm64-gnu": "1.30.2",
|
"lightningcss-linux-arm64-gnu": "1.32.0",
|
||||||
"lightningcss-linux-arm64-musl": "1.30.2",
|
"lightningcss-linux-arm64-musl": "1.32.0",
|
||||||
"lightningcss-linux-x64-gnu": "1.30.2",
|
"lightningcss-linux-x64-gnu": "1.32.0",
|
||||||
"lightningcss-linux-x64-musl": "1.30.2",
|
"lightningcss-linux-x64-musl": "1.32.0",
|
||||||
"lightningcss-win32-arm64-msvc": "1.30.2",
|
"lightningcss-win32-arm64-msvc": "1.32.0",
|
||||||
"lightningcss-win32-x64-msvc": "1.30.2"
|
"lightningcss-win32-x64-msvc": "1.32.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss-android-arm64": {
|
"node_modules/lightningcss-android-arm64": {
|
||||||
"version": "1.30.2",
|
"version": "1.32.0",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
|
||||||
"integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==",
|
"integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -9284,9 +9321,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss-darwin-arm64": {
|
"node_modules/lightningcss-darwin-arm64": {
|
||||||
"version": "1.30.2",
|
"version": "1.32.0",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
|
||||||
"integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==",
|
"integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -9304,9 +9341,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss-darwin-x64": {
|
"node_modules/lightningcss-darwin-x64": {
|
||||||
"version": "1.30.2",
|
"version": "1.32.0",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
|
||||||
"integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==",
|
"integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -9324,9 +9361,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss-freebsd-x64": {
|
"node_modules/lightningcss-freebsd-x64": {
|
||||||
"version": "1.30.2",
|
"version": "1.32.0",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
|
||||||
"integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==",
|
"integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -9344,9 +9381,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss-linux-arm-gnueabihf": {
|
"node_modules/lightningcss-linux-arm-gnueabihf": {
|
||||||
"version": "1.30.2",
|
"version": "1.32.0",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
|
||||||
"integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==",
|
"integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
|
@ -9364,9 +9401,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss-linux-arm64-gnu": {
|
"node_modules/lightningcss-linux-arm64-gnu": {
|
||||||
"version": "1.30.2",
|
"version": "1.32.0",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
|
||||||
"integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==",
|
"integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -9384,9 +9421,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss-linux-arm64-musl": {
|
"node_modules/lightningcss-linux-arm64-musl": {
|
||||||
"version": "1.30.2",
|
"version": "1.32.0",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
|
||||||
"integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==",
|
"integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -9404,9 +9441,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss-linux-x64-gnu": {
|
"node_modules/lightningcss-linux-x64-gnu": {
|
||||||
"version": "1.30.2",
|
"version": "1.32.0",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
|
||||||
"integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==",
|
"integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -9424,9 +9461,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss-linux-x64-musl": {
|
"node_modules/lightningcss-linux-x64-musl": {
|
||||||
"version": "1.30.2",
|
"version": "1.32.0",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
|
||||||
"integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==",
|
"integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -9444,9 +9481,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss-win32-arm64-msvc": {
|
"node_modules/lightningcss-win32-arm64-msvc": {
|
||||||
"version": "1.30.2",
|
"version": "1.32.0",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
|
||||||
"integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==",
|
"integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -9464,7 +9501,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss-win32-x64-msvc": {
|
"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": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -10201,7 +10240,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/nuqs": {
|
"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",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@standard-schema/spec": "1.0.0"
|
"@standard-schema/spec": "1.0.0"
|
||||||
|
|
@ -11470,7 +11511,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-hook-form": {
|
"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",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
@ -12668,12 +12711,16 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tailwindcss": {
|
"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",
|
"license": "MIT",
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/tapable": {
|
"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",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
|
|
@ -12873,7 +12920,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/type-fest": {
|
"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)",
|
"license": "(MIT OR CC0-1.0)",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tagged-tag": "^1.0.0"
|
"tagged-tag": "^1.0.0"
|
||||||
|
|
@ -13239,263 +13288,6 @@
|
||||||
"vite": "*"
|
"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": {
|
"node_modules/vitest": {
|
||||||
"version": "4.1.4",
|
"version": "4.1.4",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
|
|
||||||
|
|
@ -158,4 +158,4 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,185 +1,65 @@
|
||||||
import { differenceInDays, differenceInMinutes, format } from "date-fns";
|
import { Download } from "lucide-react";
|
||||||
import { Paperclip } from "lucide-react";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { ExtIcon } from "~/components/area-riservata/allegati";
|
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 { LoadingPage } from "~/components/loading";
|
||||||
import { Button, buttonVariants } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import { UploadComponent } from "~/components/upload_modal";
|
import { getStorageUrl } from "~/lib/storage_utils";
|
||||||
import { cn } from "~/lib/utils";
|
import type { MessagesMessageid } from "~/schemas/public/Messages";
|
||||||
import { useChatContext } from "~/providers/ChatProvider";
|
|
||||||
import { useTranslation } from "~/providers/I18nProvider";
|
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
|
|
||||||
export const ChatAttachments = () => {
|
export const ChatAttachments = ({
|
||||||
const { session, userinfo } = useChatContext();
|
messageId,
|
||||||
const { t } = useTranslation();
|
}: {
|
||||||
const {
|
messageId: MessagesMessageid;
|
||||||
data: attachments,
|
}) => {
|
||||||
isLoading: attachmentsLoading,
|
const { data, isLoading } = api.chat.getMessageAttachments.useQuery(
|
||||||
refetch,
|
{ messageId },
|
||||||
} = api.storage.retrieveUserFileData.useQuery(
|
|
||||||
{ userId: userinfo.id },
|
|
||||||
{ refetchOnMount: true },
|
{ refetchOnMount: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <LoadingPage />;
|
||||||
|
}
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Credenza onOpenChange={(o) => o && refetch()}>
|
<div className="mt-1 flex flex-col items-end gap-1">
|
||||||
<CredenzaTrigger asChild>
|
{data.map((file) => (
|
||||||
<Button
|
<div
|
||||||
aria-label="Allegati Chat"
|
className="flex w-fit items-center justify-end gap-2 rounded-md bg-secondary p-2 text-sm hover:bg-muted/50"
|
||||||
className="relative size-9"
|
key={file.id}
|
||||||
size="icon"
|
|
||||||
variant="ghost"
|
|
||||||
>
|
>
|
||||||
{attachments && attachments.length > 0 && (
|
<div className="flex grow basis-full items-center gap-2 md:col-span-8">
|
||||||
<span className="absolute -top-1 right-0 rounded-full bg-blue-500 px-1 font-bold text-white text-xs">
|
<div className="shrink-0">
|
||||||
{attachments.length}
|
<ExtIcon mimeType={file.mimeType} />
|
||||||
</span>
|
</div>
|
||||||
)}
|
<Link
|
||||||
<Paperclip className="text-muted-foreground" size={20} />
|
aria-label="Allegato"
|
||||||
</Button>
|
className="max-w-md truncate font-medium text-foreground hover:underline"
|
||||||
</CredenzaTrigger>
|
href={`/area-riservata/allegato-view/${file.id}`}
|
||||||
<CredenzaContent className="max-h-[90vh] md:max-w-xl">
|
target="_blank"
|
||||||
<CredenzaHeader>
|
>
|
||||||
<CredenzaTitle className="sr-only">Allegati</CredenzaTitle>
|
{file.originalName}
|
||||||
<CredenzaDescription className="sr-only">
|
</Link>
|
||||||
Chat Attachments
|
|
||||||
</CredenzaDescription>
|
|
||||||
</CredenzaHeader>
|
|
||||||
<CredenzaBody className="max-h-[80vh] space-y-5 overflow-auto pb-5 md:px-1">
|
|
||||||
<UploadComponent isAdmin={session.isAdmin} userId={userinfo.id} />
|
|
||||||
|
|
||||||
<div className="h-auto max-h-160 max-w-lg overflow-auto rounded-lg">
|
|
||||||
{attachmentsLoading ? (
|
|
||||||
<LoadingPage />
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{!attachments || attachments.length === 0 ? (
|
|
||||||
<div className="flex h-full items-center justify-center">
|
|
||||||
<p className="text-center font-semibold text-gray-500 text-lg">
|
|
||||||
{t.allegati.nessun_allegato}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<ul className="px-2">
|
|
||||||
<h3 className="font-semibold text-gray-700 text-lg">
|
|
||||||
{t.allegati.allegati_recenti}
|
|
||||||
</h3>
|
|
||||||
{attachments?.map((file, idx) => {
|
|
||||||
if (idx >= 3) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<li
|
|
||||||
className="flex flex-row items-center justify-between border-b py-1"
|
|
||||||
key={`${file.originalName}-${
|
|
||||||
// biome-ignore lint/suspicious/noArrayIndexKey: <ok>
|
|
||||||
idx
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<Link
|
|
||||||
aria-label="Visualizza Allegato"
|
|
||||||
href={`/area-riservata/allegato-view/${file.id}`}
|
|
||||||
onTouchStart={() => console.log("touching")}
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
className="h-8 truncate p-0 text-base has-[>svg]:px-0"
|
|
||||||
variant="link"
|
|
||||||
>
|
|
||||||
<ExtIcon mimeType={file.mimeType} />
|
|
||||||
<span className="max-w-[60vw] truncate">
|
|
||||||
{file.originalName}
|
|
||||||
</span>
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
<span
|
|
||||||
className="text-muted-foreground text-xs"
|
|
||||||
key={new Date().toString()}
|
|
||||||
>
|
|
||||||
{TimeSince(new Date(file.uploadedAt))}
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<Link
|
|
||||||
aria-label="Visualizza Allegati"
|
<div className="flex justify-end gap-1 md:col-span-1">
|
||||||
className={cn(
|
<Link
|
||||||
buttonVariants({
|
href={getStorageUrl({
|
||||||
variant: "outline",
|
storageId: file.id,
|
||||||
}),
|
params: { mode: "download" },
|
||||||
"relative w-full",
|
})}
|
||||||
)}
|
>
|
||||||
href="/area-riservata/allegati"
|
<Button className="flex size-8 gap-1 text-sm" size="icon">
|
||||||
>
|
<Download className="size-4" />
|
||||||
<Paperclip /> {t.allegati.i_tuoi_allegati}
|
</Button>
|
||||||
{attachments && attachments.length > 0 && (
|
</Link>
|
||||||
<span className="absolute -top-1 -right-1 flex size-5 items-center justify-center rounded-full bg-red-600 text-white text-xs">
|
</div>
|
||||||
{attachments.length}
|
</div>
|
||||||
</span>
|
))}
|
||||||
)}
|
</div>
|
||||||
</Link>
|
|
||||||
</CredenzaBody>
|
|
||||||
<CredenzaFooter className="flex flex-col items-center justify-between gap-2">
|
|
||||||
<CredenzaClose asChild>
|
|
||||||
<Button className="w-full">{t.chiudi}</Button>
|
|
||||||
</CredenzaClose>
|
|
||||||
</CredenzaFooter>
|
|
||||||
</CredenzaContent>
|
|
||||||
</Credenza>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
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");
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,29 @@
|
||||||
import {
|
import {
|
||||||
ArrowDown,
|
ArrowDown,
|
||||||
|
Paperclip,
|
||||||
SendHorizontal,
|
SendHorizontal,
|
||||||
SmilePlusIcon,
|
SmilePlusIcon,
|
||||||
ThumbsUp,
|
ThumbsUp,
|
||||||
X,
|
X,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { type ChangeEvent, useCallback, useEffect, useState } from "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 { AutosizeTextarea } from "~/components/custom_ui/autoResizeTextArea";
|
||||||
import {
|
import {
|
||||||
EmojiPicker,
|
EmojiPicker,
|
||||||
EmojiPickerContent,
|
EmojiPickerContent,
|
||||||
EmojiPickerSearch,
|
EmojiPickerSearch,
|
||||||
} from "~/components/custom_ui/emoji-picker";
|
} 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 { Button } from "~/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
|
|
@ -20,9 +31,11 @@ import {
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "~/components/ui/popover";
|
} from "~/components/ui/popover";
|
||||||
import { useThrottledIsTypingMutation } from "~/hooks/chatHooks";
|
import { useThrottledIsTypingMutation } from "~/hooks/chatHooks";
|
||||||
|
import { uploadHandler } from "~/hooks/storageClienSideHooks";
|
||||||
import { useMediaQuery } from "~/hooks/use-media-query";
|
import { useMediaQuery } from "~/hooks/use-media-query";
|
||||||
import { useChatContext } from "~/providers/ChatProvider";
|
import { useChatContext } from "~/providers/ChatProvider";
|
||||||
import { useTranslation } from "~/providers/I18nProvider";
|
import { useTranslation } from "~/providers/I18nProvider";
|
||||||
|
import type { UsersStorageUserStorageId } from "~/schemas/public/UsersStorage";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
|
|
||||||
export default function ChatBottombar() {
|
export default function ChatBottombar() {
|
||||||
|
|
@ -31,14 +44,17 @@ export default function ChatBottombar() {
|
||||||
session,
|
session,
|
||||||
inputRef,
|
inputRef,
|
||||||
replyTrg,
|
replyTrg,
|
||||||
|
userinfo,
|
||||||
setReplyTrg,
|
setReplyTrg,
|
||||||
messagesContainerRef,
|
messagesContainerRef,
|
||||||
} = useChatContext();
|
} = useChatContext();
|
||||||
const { locale } = useTranslation();
|
const { locale, t } = useTranslation();
|
||||||
const [message, setMessage] = useState("");
|
const [message, setMessage] = useState("");
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [files, setFiles] = useState<File[]>([]);
|
||||||
const [isFocused, setIsFocused] = useState(false);
|
const [isFocused, setIsFocused] = useState(false);
|
||||||
|
|
||||||
const { mutate: addMutation } = api.chatSSE.sendMessage.useMutation({
|
const { mutateAsync: addMutation } = api.chatSSE.sendMessage.useMutation({
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
console.error("Failed to send message", 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 = () => {
|
try {
|
||||||
addMutation({
|
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,
|
reply_to_id: replyTrg?.messageid || null,
|
||||||
chatId,
|
chatId,
|
||||||
message: "👍",
|
message: "👍",
|
||||||
sender: session.id,
|
sender: session.id,
|
||||||
|
attachments,
|
||||||
});
|
});
|
||||||
|
setIsLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSend = () => {
|
const handleSend = async () => {
|
||||||
if (message.length !== 0) {
|
setIsLoading(true);
|
||||||
addMutation({
|
const attachments = await uploadFiles();
|
||||||
reply_to_id: replyTrg?.messageid || null,
|
await addMutation({
|
||||||
chatId,
|
reply_to_id: replyTrg?.messageid || null,
|
||||||
message: message.trim(),
|
chatId,
|
||||||
sender: session.id,
|
message: message.trim(),
|
||||||
});
|
sender: session.id,
|
||||||
}
|
attachments,
|
||||||
|
});
|
||||||
|
setIsLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const isTypingMutation = useThrottledIsTypingMutation({
|
const isTypingMutation = useThrottledIsTypingMutation({
|
||||||
chatId,
|
chatId,
|
||||||
username: session.username,
|
username: session.username,
|
||||||
|
|
@ -112,105 +196,150 @@ export default function ChatBottombar() {
|
||||||
el.addEventListener("scroll", handleScroll, { passive: true });
|
el.addEventListener("scroll", handleScroll, { passive: true });
|
||||||
return () => el.removeEventListener("scroll", handleScroll);
|
return () => el.removeEventListener("scroll", handleScroll);
|
||||||
}, [messagesContainerRef]);
|
}, [messagesContainerRef]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex w-full flex-col border-t">
|
<div className="w-full">
|
||||||
{showScrollDown && (
|
<FileUpload
|
||||||
<Button
|
maxFiles={5}
|
||||||
className="absolute -top-1/2 left-1/2 size-12 -translate-x-1/2 -translate-y-1/2 rounded-full opacity-80"
|
maxSize={5 * 1024 * 1024}
|
||||||
onClick={handleJumpToBottom}
|
multiple
|
||||||
variant="outline"
|
onValueChange={setFiles}
|
||||||
>
|
value={files}
|
||||||
<ArrowDown className="size-6" />
|
>
|
||||||
</Button>
|
<div className="relative flex w-full flex-col border-t">
|
||||||
)}
|
{showScrollDown && (
|
||||||
{replyTrg && (
|
|
||||||
<div className="p-2">
|
|
||||||
<div className="relative flex-1 rounded-lg border bg-muted p-2 text-sm">
|
|
||||||
<span>Rispondi:</span>
|
|
||||||
<div className="line-clamp-2 overflow-hidden text-ellipsis whitespace-pre-wrap text-sm">
|
|
||||||
{replyTrg.message}
|
|
||||||
</div>
|
|
||||||
<Button
|
<Button
|
||||||
className="absolute top-1 right-1"
|
className="absolute -top-1/2 left-1/2 size-12 -translate-x-1/2 rounded-full opacity-80"
|
||||||
onClick={() => setReplyTrg(null)}
|
onClick={handleJumpToBottom}
|
||||||
size="icon"
|
|
||||||
variant="outline"
|
variant="outline"
|
||||||
>
|
>
|
||||||
<X className="size-4" />
|
<ArrowDown className="size-6" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<FileUploadList className="my-2 max-w-[calc(100vw-48px)] px-2">
|
||||||
|
{files.map((file, index) => (
|
||||||
|
// biome-ignore lint/suspicious/noArrayIndexKey: <ok>
|
||||||
|
<FileUploadItem className="w-full p-2" key={index} value={file}>
|
||||||
|
<FileUploadItemPreview className="size-8" />
|
||||||
|
<FileUploadItemMetadata
|
||||||
|
//className="max-w-[60vw] sm:max-w-none"
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<LoadingSpinner className="size-6" />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<FileUploadItemDelete asChild>
|
||||||
|
<Button className="size-6" size="icon" variant="ghost">
|
||||||
|
<X className="size-4" />
|
||||||
|
</Button>
|
||||||
|
</FileUploadItemDelete>
|
||||||
|
)}
|
||||||
|
</FileUploadItem>
|
||||||
|
))}
|
||||||
|
</FileUploadList>
|
||||||
|
{replyTrg && (
|
||||||
|
<div className="p-2">
|
||||||
|
<div className="relative flex-1 rounded-lg border bg-muted p-2 text-sm">
|
||||||
|
<span>Rispondi:</span>
|
||||||
|
<div className="line-clamp-2 overflow-hidden text-ellipsis whitespace-pre-wrap text-sm">
|
||||||
|
{replyTrg.message}
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
className="absolute top-1 right-1"
|
||||||
|
disabled={isLoading}
|
||||||
|
onClick={() => setReplyTrg(null)}
|
||||||
|
size="icon"
|
||||||
|
variant="outline"
|
||||||
|
>
|
||||||
|
<X className="size-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="flex min-h-18.5 w-full items-center justify-between gap-2 p-2">
|
||||||
|
<FileUploadTrigger asChild>
|
||||||
|
<Button className="size-8 shrink-0" size="icon" variant="ghost">
|
||||||
|
<Paperclip className="size-4" />
|
||||||
|
</Button>
|
||||||
|
</FileUploadTrigger>
|
||||||
|
|
||||||
|
<div className="relative w-full">
|
||||||
|
<AutosizeTextarea
|
||||||
|
autoComplete="off"
|
||||||
|
className="flex h-14 min-h-14 w-full resize-none items-center overflow-hidden rounded-xl border bg-background pr-8 outline-hidden ring-0 hover:border-neutral-400 focus:border-neutral-600 focus:ring-0 focus-visible:ring-0 focus-visible:ring-offset-0"
|
||||||
|
minHeight={56}
|
||||||
|
name="message"
|
||||||
|
onBlur={() => setIsFocused(false)}
|
||||||
|
onChange={(e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
|
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 && (
|
||||||
|
<div className="absolute top-0 right-2 flex h-full items-center">
|
||||||
|
<Popover onOpenChange={setOpenEmoji} open={openEmoji}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button variant="ghost">
|
||||||
|
<SmilePlusIcon className="stroke-muted-foreground" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent align="end" className="w-fit p-0">
|
||||||
|
<EmojiPicker
|
||||||
|
className="h-84"
|
||||||
|
locale={locale as "it" | "en"}
|
||||||
|
onEmojiSelect={({ emoji }) => {
|
||||||
|
setMessage(message + emoji);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<EmojiPickerSearch
|
||||||
|
placeholder={
|
||||||
|
locale === "en" ? "Search..." : "Cerca..."
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<EmojiPickerContent />
|
||||||
|
</EmojiPicker>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
aria-label="Send Message"
|
||||||
|
className="shrink-0"
|
||||||
|
disabled={isLoading}
|
||||||
|
onClick={
|
||||||
|
message.length !== 0
|
||||||
|
? () => handleSend()
|
||||||
|
: () => handleThumbsUp()
|
||||||
|
}
|
||||||
|
variant="ghost"
|
||||||
|
>
|
||||||
|
{isLoading ? (
|
||||||
|
<LoadingSpinner className="size-6" />
|
||||||
|
) : message.length !== 0 ? (
|
||||||
|
<SendHorizontal className="text-muted-foreground" size={20} />
|
||||||
|
) : (
|
||||||
|
<ThumbsUp className="text-muted-foreground" size={20} />
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</FileUpload>
|
||||||
<div className="flex min-h-18.5 w-full items-center justify-between gap-2 p-2">
|
|
||||||
<div className="flex">
|
|
||||||
<ChatAttachments />
|
|
||||||
</div>
|
|
||||||
<div className="relative w-full">
|
|
||||||
<AutosizeTextarea
|
|
||||||
autoComplete="off"
|
|
||||||
className="flex h-14 min-h-14 w-full resize-none items-center overflow-hidden rounded-xl border bg-background pr-8 outline-hidden ring-0 hover:border-neutral-400 focus:border-neutral-600 focus:ring-0 focus-visible:ring-0 focus-visible:ring-offset-0"
|
|
||||||
minHeight={56}
|
|
||||||
name="message"
|
|
||||||
onBlur={() => setIsFocused(false)}
|
|
||||||
onChange={(e: ChangeEvent<HTMLTextAreaElement>) => {
|
|
||||||
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 && (
|
|
||||||
<div className="absolute top-0 right-2 flex h-full items-center">
|
|
||||||
<Popover onOpenChange={setOpenEmoji} open={openEmoji}>
|
|
||||||
<PopoverTrigger asChild>
|
|
||||||
<Button variant="ghost">
|
|
||||||
<SmilePlusIcon className="stroke-muted-foreground" />
|
|
||||||
</Button>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent align="end" className="w-fit p-0">
|
|
||||||
<EmojiPicker
|
|
||||||
className="h-84"
|
|
||||||
locale={locale as "it" | "en"}
|
|
||||||
onEmojiSelect={({ emoji }) => {
|
|
||||||
setMessage(message + emoji);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<EmojiPickerSearch
|
|
||||||
placeholder={locale === "en" ? "Search..." : "Cerca..."}
|
|
||||||
/>
|
|
||||||
<EmojiPickerContent />
|
|
||||||
</EmojiPicker>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
aria-label="Send Message"
|
|
||||||
className="shrink-0"
|
|
||||||
onClick={
|
|
||||||
message.length !== 0 ? () => handleSend() : () => handleThumbsUp()
|
|
||||||
}
|
|
||||||
variant="ghost"
|
|
||||||
>
|
|
||||||
{message.length !== 0 ? (
|
|
||||||
<SendHorizontal className="text-muted-foreground" size={20} />
|
|
||||||
) : (
|
|
||||||
<ThumbsUp className="text-muted-foreground" size={20} />
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import {
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
import { ChatAttachments } from "~/components/chat/chat-attachments";
|
||||||
import {
|
import {
|
||||||
ChatBubble,
|
ChatBubble,
|
||||||
ChatBubbleMessage,
|
ChatBubbleMessage,
|
||||||
|
|
@ -245,7 +246,7 @@ export const ChatList = () => {
|
||||||
key={message.messageid}
|
key={message.messageid}
|
||||||
>
|
>
|
||||||
{!isPrevSameDay && (
|
{!isPrevSameDay && (
|
||||||
<span className="mx-auto rounded-md bg-muted px-2 py-1 text-center text-muted-foreground text-xs">
|
<span className="mx-auto mt-1 rounded-md bg-primary px-2 py-1 text-center text-secondary text-xs">
|
||||||
{new Date(message.time).toLocaleDateString("it", {
|
{new Date(message.time).toLocaleDateString("it", {
|
||||||
day: "2-digit",
|
day: "2-digit",
|
||||||
month: "2-digit",
|
month: "2-digit",
|
||||||
|
|
@ -291,7 +292,7 @@ export const ChatList = () => {
|
||||||
<ChatBubble variant={isSender ? "sent" : "received"}>
|
<ChatBubble variant={isSender ? "sent" : "received"}>
|
||||||
<ChatBubbleMessage
|
<ChatBubbleMessage
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-wrap items-end justify-end gap-x-2 gap-y-1",
|
"flex flex-col items-end justify-end gap-x-2 gap-y-1",
|
||||||
isNextSameSender && "rounded-lg",
|
isNextSameSender && "rounded-lg",
|
||||||
"",
|
"",
|
||||||
)}
|
)}
|
||||||
|
|
@ -328,6 +329,9 @@ export const ChatList = () => {
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
{message.has_attachments && (
|
||||||
|
<ChatAttachments messageId={message.messageid} />
|
||||||
|
)}
|
||||||
<span className="flex shrink break-all">
|
<span className="flex shrink break-all">
|
||||||
{message.message}
|
{message.message}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,18 @@ export default function ChatTopbar() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Link
|
||||||
|
aria-label="Visualizza Allegati Utente"
|
||||||
|
className={cn(
|
||||||
|
buttonVariants({ variant: "outline" }),
|
||||||
|
"justify-start gap-2",
|
||||||
|
)}
|
||||||
|
href={`/area-riservata/admin/user-view/allegati/${userinfo.id}`}
|
||||||
|
>
|
||||||
|
<Paperclip /> Allegati
|
||||||
|
</Link>
|
||||||
{session.isAdmin && (
|
{session.isAdmin && (
|
||||||
<AdminPopover
|
<AdminPopover
|
||||||
handleDeleteChat={handleDeleteChat}
|
handleDeleteChat={handleDeleteChat}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import type { Caratteristiche } from '../../utils/kanel-types.ts';
|
||||||
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
||||||
|
|
||||||
/** Identifier type for public.annunci */
|
/** Identifier type for public.annunci */
|
||||||
|
|
@ -85,7 +86,7 @@ export default interface AnnunciTable {
|
||||||
|
|
||||||
web: ColumnType<boolean | null, boolean | null, boolean | null>;
|
web: ColumnType<boolean | null, boolean | null, boolean | null>;
|
||||||
|
|
||||||
caratteristiche: ColumnType<unknown | null, unknown | null, unknown | null>;
|
caratteristiche: ColumnType<Caratteristiche | null, Caratteristiche | null, Caratteristiche | null>;
|
||||||
|
|
||||||
homepage: ColumnType<boolean | null, boolean | null, boolean | null>;
|
homepage: ColumnType<boolean | null, boolean | null, boolean | null>;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,14 @@ import {
|
||||||
getChatEtichette,
|
getChatEtichette,
|
||||||
removeChatEtichette,
|
removeChatEtichette,
|
||||||
} from "~/server/controllers/etichette.controller";
|
} from "~/server/controllers/etichette.controller";
|
||||||
|
import { retrieveChatMessageFiles } from "~/server/controllers/storage.controller";
|
||||||
import { db } from "~/server/db";
|
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({
|
export const chatRouter = createTRPCRouter({
|
||||||
addEtichettaToChat: protectedProcedure
|
addEtichettaToChat: protectedProcedure
|
||||||
|
|
@ -90,4 +96,9 @@ export const chatRouter = createTRPCRouter({
|
||||||
etichettaid: input.etichettaId,
|
etichettaid: input.etichettaId,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
getMessageAttachments: protectedProcedure
|
||||||
|
.input(z.object({ messageId: zMessageId }))
|
||||||
|
.query(async ({ input }) => {
|
||||||
|
return await retrieveChatMessageFiles({ messageId: input.messageId });
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import {
|
||||||
zChatId,
|
zChatId,
|
||||||
zMessageId,
|
zMessageId,
|
||||||
zUserId,
|
zUserId,
|
||||||
|
zUserStorageId,
|
||||||
} from "~/server/utils/zod_types";
|
} from "~/server/utils/zod_types";
|
||||||
import { getKeydbClient, getSubClient } from "~/utils/keydb";
|
import { getKeydbClient, getSubClient } from "~/utils/keydb";
|
||||||
import { withUserColorStr } from "~/utils/kysely-helper";
|
import { withUserColorStr } from "~/utils/kysely-helper";
|
||||||
|
|
@ -110,6 +111,7 @@ export const chatSSERouter = createTRPCRouter({
|
||||||
"messages.reply_to_id",
|
"messages.reply_to_id",
|
||||||
"users.nome as sender_nome",
|
"users.nome as sender_nome",
|
||||||
"users.isAdmin as sender_isAdmin",
|
"users.isAdmin as sender_isAdmin",
|
||||||
|
|
||||||
withUserColorStr(),
|
withUserColorStr(),
|
||||||
jsonObjectFrom(
|
jsonObjectFrom(
|
||||||
eb
|
eb
|
||||||
|
|
@ -128,12 +130,25 @@ export const chatSSERouter = createTRPCRouter({
|
||||||
)
|
)
|
||||||
.limit(1),
|
.limit(1),
|
||||||
).as("reply_source"),
|
).as("reply_source"),
|
||||||
|
eb
|
||||||
|
.exists(
|
||||||
|
eb
|
||||||
|
.selectFrom("messages_attachments")
|
||||||
|
.select("messages_attachments.userStorageId")
|
||||||
|
.whereRef(
|
||||||
|
"messages_attachments.messageId",
|
||||||
|
"=",
|
||||||
|
"messages.messageid",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.$castTo<boolean>()
|
||||||
|
.as("has_attachments"),
|
||||||
])
|
])
|
||||||
.where("messages.chatid", "=", input.chatId)
|
.where("messages.chatid", "=", input.chatId)
|
||||||
.where("messages.messageid", ">=", input.targetMsgId)
|
.where("messages.messageid", ">=", input.targetMsgId)
|
||||||
.orderBy("messages.messageid", "asc")
|
.orderBy("messages.messageid", "asc")
|
||||||
.execute();
|
.execute();
|
||||||
const prevCheck = await db
|
const prevCheck = await db
|
||||||
.selectFrom("messages")
|
.selectFrom("messages")
|
||||||
.where("chatid", "=", input.chatId)
|
.where("chatid", "=", input.chatId)
|
||||||
.where("messageid", "<", input.targetMsgId)
|
.where("messageid", "<", input.targetMsgId)
|
||||||
|
|
@ -141,7 +156,7 @@ export const chatSSERouter = createTRPCRouter({
|
||||||
.orderBy("messageid", "desc")
|
.orderBy("messageid", "desc")
|
||||||
.limit(1)
|
.limit(1)
|
||||||
.executeTakeFirst();
|
.executeTakeFirst();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items: msgs,
|
items: msgs,
|
||||||
nextCursor: prevCheck?.messageid ?? null,
|
nextCursor: prevCheck?.messageid ?? null,
|
||||||
|
|
@ -187,6 +202,19 @@ export const chatSSERouter = createTRPCRouter({
|
||||||
.whereRef("messages.reply_to_id", "=", "reply_source.messageid")
|
.whereRef("messages.reply_to_id", "=", "reply_source.messageid")
|
||||||
.limit(1),
|
.limit(1),
|
||||||
).as("reply_source"),
|
).as("reply_source"),
|
||||||
|
eb
|
||||||
|
.exists(
|
||||||
|
eb
|
||||||
|
.selectFrom("messages_attachments")
|
||||||
|
.select("messages_attachments.userStorageId")
|
||||||
|
.whereRef(
|
||||||
|
"messages_attachments.messageId",
|
||||||
|
"=",
|
||||||
|
"messages.messageid",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.$castTo<boolean>()
|
||||||
|
.as("has_attachments"),
|
||||||
])
|
])
|
||||||
|
|
||||||
.where("users.nome", "is not", null)
|
.where("users.nome", "is not", null)
|
||||||
|
|
@ -225,6 +253,13 @@ export const chatSSERouter = createTRPCRouter({
|
||||||
sender: zUserId,
|
sender: zUserId,
|
||||||
is_forwarded: z.boolean().default(false),
|
is_forwarded: z.boolean().default(false),
|
||||||
reply_to_id: zMessageId.nullable().default(null),
|
reply_to_id: zMessageId.nullable().default(null),
|
||||||
|
attachments: z
|
||||||
|
.object({
|
||||||
|
userStorageId: zUserStorageId,
|
||||||
|
storageId: z.string(),
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.default([]),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
|
|
@ -242,6 +277,20 @@ export const chatSSERouter = createTRPCRouter({
|
||||||
.executeTakeFirstOrThrow(
|
.executeTakeFirstOrThrow(
|
||||||
() => new Error("Failed to insert message into database"),
|
() => 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
|
const rs = await db
|
||||||
.selectFrom("messages")
|
.selectFrom("messages")
|
||||||
.innerJoin("users", "users.id", "messages.sender")
|
.innerJoin("users", "users.id", "messages.sender")
|
||||||
|
|
@ -274,7 +323,7 @@ export const chatSSERouter = createTRPCRouter({
|
||||||
const payload: Message = {
|
const payload: Message = {
|
||||||
...newMessage,
|
...newMessage,
|
||||||
...userInfos,
|
...userInfos,
|
||||||
|
has_attachments: hasAttachments,
|
||||||
reply_source: rs
|
reply_source: rs
|
||||||
? {
|
? {
|
||||||
messageid: rs.messageid,
|
messageid: rs.messageid,
|
||||||
|
|
@ -545,6 +594,19 @@ export const chatSSERouter = createTRPCRouter({
|
||||||
.whereRef("messages.reply_to_id", "=", "reply_source.messageid")
|
.whereRef("messages.reply_to_id", "=", "reply_source.messageid")
|
||||||
.limit(1),
|
.limit(1),
|
||||||
).as("reply_source"),
|
).as("reply_source"),
|
||||||
|
eb
|
||||||
|
.exists(
|
||||||
|
eb
|
||||||
|
.selectFrom("messages_attachments")
|
||||||
|
.select("messages_attachments.userStorageId")
|
||||||
|
.whereRef(
|
||||||
|
"messages_attachments.messageId",
|
||||||
|
"=",
|
||||||
|
"messages.messageid",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.$castTo<boolean>()
|
||||||
|
.as("has_attachments"),
|
||||||
])
|
])
|
||||||
.where("messages.chatid", "=", input.chatId)
|
.where("messages.chatid", "=", input.chatId)
|
||||||
.where("messages.messageid", ">", lastMessageId)
|
.where("messages.messageid", ">", lastMessageId)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
|
import type { MessagesMessageid } from "~/schemas/public/Messages";
|
||||||
import type { UsersId } from "~/schemas/public/Users";
|
import type { UsersId } from "~/schemas/public/Users";
|
||||||
import type { NewUsersStorage } from "~/schemas/public/UsersStorage";
|
import type { NewUsersStorage } from "~/schemas/public/UsersStorage";
|
||||||
import { db, type Querier } from "~/server/db";
|
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 () => {
|
export const getAllWithFromAdmin = async () => {
|
||||||
try {
|
try {
|
||||||
const files = await db
|
const files = await db
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import type { RinnoviId } from "~/schemas/public/Rinnovi";
|
||||||
import type { ServizioServizioId } from "~/schemas/public/Servizio";
|
import type { ServizioServizioId } from "~/schemas/public/Servizio";
|
||||||
import type { TestiEStringheStingaId } from "~/schemas/public/TestiEStringhe";
|
import type { TestiEStringheStingaId } from "~/schemas/public/TestiEStringhe";
|
||||||
import type { UsersId } from "~/schemas/public/Users";
|
import type { UsersId } from "~/schemas/public/Users";
|
||||||
|
import type { UsersStorageUserStorageId } from "~/schemas/public/UsersStorage";
|
||||||
|
|
||||||
export const zUserId = z.custom<UsersId>(
|
export const zUserId = z.custom<UsersId>(
|
||||||
(val) => typeof val === "string",
|
(val) => typeof val === "string",
|
||||||
|
|
@ -64,6 +65,11 @@ export const zTestiEStringheStingaId = z.custom<TestiEStringheStingaId>(
|
||||||
// "falied to validate EventDataTypes",
|
// "falied to validate EventDataTypes",
|
||||||
// );
|
// );
|
||||||
|
|
||||||
|
export const zUserStorageId = z.custom<UsersStorageUserStorageId>(
|
||||||
|
(val) => typeof val === "string",
|
||||||
|
"falied to validate UserStorageId",
|
||||||
|
);
|
||||||
|
|
||||||
export const zBanlistId = z.custom<BanlistId>(
|
export const zBanlistId = z.custom<BanlistId>(
|
||||||
(val) => typeof val === "number",
|
(val) => typeof val === "number",
|
||||||
"falied to validate BanlistId",
|
"falied to validate BanlistId",
|
||||||
|
|
@ -110,6 +116,7 @@ export const MessageDataSchema = z.object({
|
||||||
color_str: z.string(),
|
color_str: z.string(),
|
||||||
is_forwarded: z.boolean(),
|
is_forwarded: z.boolean(),
|
||||||
reply_to_id: zMessageId.nullable(),
|
reply_to_id: zMessageId.nullable(),
|
||||||
|
has_attachments: z.boolean(),
|
||||||
reply_source: z
|
reply_source: z
|
||||||
.object({
|
.object({
|
||||||
messageid: zMessageId,
|
messageid: zMessageId,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue