diff --git a/apps/infoalloggi/package-lock.json b/apps/infoalloggi/package-lock.json index 92fc076..d40e1ab 100644 --- a/apps/infoalloggi/package-lock.json +++ b/apps/infoalloggi/package-lock.json @@ -49,9 +49,11 @@ "embla-carousel-react": "^8.6.0", "framer-motion": "^12.8.0", "frimousse": "^0.3.0", + "html2canvas-pro": "^1.5.13", "ioredis": "^5.7.0", "jose": "^6.0.12", "js-cookie": "^3.0.5", + "jspdf": "^3.0.4", "kysely": "^0.28.2", "kysely-plugin-serialize": "^0.8.2", "leaflet": "^1.9.4", @@ -76,7 +78,7 @@ "react-is": "^19.1.1", "react-leaflet": "^5.0.0", "react-number-format": "^5.4.4", - "react-pdf": "^10.1.0", + "react-pdf": "^10.2.0", "react-phone-number-input": "^3.4.12", "react-reverse-portal": "^2.3.0", "react-select": "^5.10.2", @@ -997,9 +999,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz", - "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -2576,9 +2578,9 @@ "license": "MIT" }, "node_modules/@napi-rs/canvas": { - "version": "0.1.79", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.79.tgz", - "integrity": "sha512-0SkvRRjyxY35eniEsQsjPYUMWunKlAWvionJOzJJADZF5ZDf/sL+ncJbMTV5LUiHg1iHOvVjWcuDOx/GNXr/lA==", + "version": "0.1.82", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.82.tgz", + "integrity": "sha512-FGjyUBoF0sl1EenSiE4UV2WYu76q6F9GSYedq5EiOCOyGYoQ/Owulcv6rd7v/tWOpljDDtefXXIaOCJrVKem4w==", "license": "MIT", "optional": true, "workspaces": [ @@ -2588,22 +2590,22 @@ "node": ">= 10" }, "optionalDependencies": { - "@napi-rs/canvas-android-arm64": "0.1.79", - "@napi-rs/canvas-darwin-arm64": "0.1.79", - "@napi-rs/canvas-darwin-x64": "0.1.79", - "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.79", - "@napi-rs/canvas-linux-arm64-gnu": "0.1.79", - "@napi-rs/canvas-linux-arm64-musl": "0.1.79", - "@napi-rs/canvas-linux-riscv64-gnu": "0.1.79", - "@napi-rs/canvas-linux-x64-gnu": "0.1.79", - "@napi-rs/canvas-linux-x64-musl": "0.1.79", - "@napi-rs/canvas-win32-x64-msvc": "0.1.79" + "@napi-rs/canvas-android-arm64": "0.1.82", + "@napi-rs/canvas-darwin-arm64": "0.1.82", + "@napi-rs/canvas-darwin-x64": "0.1.82", + "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.82", + "@napi-rs/canvas-linux-arm64-gnu": "0.1.82", + "@napi-rs/canvas-linux-arm64-musl": "0.1.82", + "@napi-rs/canvas-linux-riscv64-gnu": "0.1.82", + "@napi-rs/canvas-linux-x64-gnu": "0.1.82", + "@napi-rs/canvas-linux-x64-musl": "0.1.82", + "@napi-rs/canvas-win32-x64-msvc": "0.1.82" } }, "node_modules/@napi-rs/canvas-android-arm64": { - "version": "0.1.79", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.79.tgz", - "integrity": "sha512-ih6ZIztNDEXl7axvC4swOwLFrM9lOyJa9VAMq7xIBtEZhR/8IVDa0ZTup2fZEiTCmnjmXolzv7uDviHkOTEMKQ==", + "version": "0.1.82", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.82.tgz", + "integrity": "sha512-bvZhN0iI54ouaQOrgJV96H2q7J3ZoufnHf4E1fUaERwW29Rz4rgicohnAg4venwBJZYjGl5Yl3CGmlAl1LZowQ==", "cpu": [ "arm64" ], @@ -2617,9 +2619,9 @@ } }, "node_modules/@napi-rs/canvas-darwin-arm64": { - "version": "0.1.79", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.79.tgz", - "integrity": "sha512-REMz1Fac2VlOYJDg+JjmQWSJc459cCgVom6GvKwWkDqzSjvG9BSo72MDmQY3uhb7r49Xuz5gTFcLYTfNcm4MoA==", + "version": "0.1.82", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.82.tgz", + "integrity": "sha512-InuBHKCyuFqhNwNr4gpqazo5Xp6ltKflqOLiROn4hqAS8u21xAHyYCJRgHwd+a5NKmutFTaRWeUIT/vxWbU/iw==", "cpu": [ "arm64" ], @@ -2633,9 +2635,9 @@ } }, "node_modules/@napi-rs/canvas-darwin-x64": { - "version": "0.1.79", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.79.tgz", - "integrity": "sha512-uQxLg6Bll7zv/ljp/YIeiUFWfV9C/ESv+2ioUh60hIAypuhtg6hhtWE/KnoW7G48wQls5VUStvEnJbnJ7bPKlA==", + "version": "0.1.82", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.82.tgz", + "integrity": "sha512-aQGV5Ynn96onSXcuvYb2y7TRXD/t4CL2EGmnGqvLyeJX1JLSNisKQlWN/1bPDDXymZYSdUqbXehj5qzBlOx+RQ==", "cpu": [ "x64" ], @@ -2649,9 +2651,9 @@ } }, "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { - "version": "0.1.79", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.79.tgz", - "integrity": "sha512-X37B//TVIipL/3RyvyfNlbQK2uyIaK3PJ2bH7ZeU+jpkaYprBsV15GCN/LHTYAi6R0F/c53zK3aSFNKkGHM/Og==", + "version": "0.1.82", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.82.tgz", + "integrity": "sha512-YIUpmHWeHGGRhWitT1KJkgj/JPXPfc9ox8oUoyaGPxolLGPp5AxJkq8wIg8CdFGtutget968dtwmx71m8o3h5g==", "cpu": [ "arm" ], @@ -2665,9 +2667,9 @@ } }, "node_modules/@napi-rs/canvas-linux-arm64-gnu": { - "version": "0.1.79", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.79.tgz", - "integrity": "sha512-+T1fuau1heabE6zGXiqZBGPH5fTIQF+xEu/u4fuugxEiChRYlhnPjkw26MBi8ePg/jmzxLfJEij6LMJQ4AQa2A==", + "version": "0.1.82", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.82.tgz", + "integrity": "sha512-AwLzwLBgmvk7kWeUgItOUor/QyG31xqtD26w1tLpf4yE0hiXTGp23yc669aawjB6FzgIkjh1NKaNS52B7/qEBQ==", "cpu": [ "arm64" ], @@ -2681,9 +2683,9 @@ } }, "node_modules/@napi-rs/canvas-linux-arm64-musl": { - "version": "0.1.79", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.79.tgz", - "integrity": "sha512-KsrsR3+6uXv70W/1/kY0yRK4/bbdJgA1Vuxw4KyfSc6mjl1DMoYXDAjpBT/5w7AXy6cGG44jm3upvvt/y/dPfg==", + "version": "0.1.82", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.82.tgz", + "integrity": "sha512-moZWuqepAwWBffdF4JDadt8TgBD02iMhG6I1FHZf8xO20AsIp9rB+p0B8Zma2h2vAF/YMjeFCDmW5un6+zZz9g==", "cpu": [ "arm64" ], @@ -2697,9 +2699,9 @@ } }, "node_modules/@napi-rs/canvas-linux-riscv64-gnu": { - "version": "0.1.79", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.79.tgz", - "integrity": "sha512-EXaENnSJD6au6z4aKN2PpU9eVNWUsRI2cApm8gCa0WSRMaiYXZsFkXQmhB+Vz2pXahOS8BN2Zd8S1IeML/LCtg==", + "version": "0.1.82", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.82.tgz", + "integrity": "sha512-w9++2df2kG9eC9LWYIHIlMLuhIrKGQYfUxs97CwgxYjITeFakIRazI9LYWgVzEc98QZ9x9GQvlicFsrROV59MQ==", "cpu": [ "riscv64" ], @@ -2713,9 +2715,9 @@ } }, "node_modules/@napi-rs/canvas-linux-x64-gnu": { - "version": "0.1.79", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.79.tgz", - "integrity": "sha512-3xZhHlE9e3cd9D7Comy6/TTSs/8PUGXEXymIwYQrA1QxHojAlAOFlVai4rffzXd0bHylZu+/wD76LodvYqF1Yw==", + "version": "0.1.82", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.82.tgz", + "integrity": "sha512-lZulOPwrRi6hEg/17CaqdwWEUfOlIJuhXxincx1aVzsVOCmyHf+xFq4i6liJl1P+x2v6Iz2Z/H5zHvXJCC7Bwg==", "cpu": [ "x64" ], @@ -2729,9 +2731,9 @@ } }, "node_modules/@napi-rs/canvas-linux-x64-musl": { - "version": "0.1.79", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.79.tgz", - "integrity": "sha512-4yv550uCjIEoTFgrpxYZK67nFlDMCQa3LAheM2QrO+B8w1p5w04usIQSCHqHe6aPWlbLQCIqfVcew6/7Q4KuHg==", + "version": "0.1.82", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.82.tgz", + "integrity": "sha512-Be9Wf5RTv1w6GXlTph55K3PH3vsAh1Ax4T1FQY1UYM0QfD0yrwGdnJ8/fhqw7dEgMjd59zIbjJQC8C3msbGn5g==", "cpu": [ "x64" ], @@ -2745,9 +2747,9 @@ } }, "node_modules/@napi-rs/canvas-win32-x64-msvc": { - "version": "0.1.79", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.79.tgz", - "integrity": "sha512-sD5qP2njBRnhNlTNFJDdpeCN6aR3qVamLySTwhX3ec8sdfeT/chf/x2dw2UXoIGMoVaVk/y2ifwxBj/h2a2jug==", + "version": "0.1.82", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.82.tgz", + "integrity": "sha512-LN/i8VrvxTDmEEK1c10z2cdOTkWT76LlTGtyZe5Kr1sqoSomKeExAjbilnu1+oee5lZUgS5yfZ2LNlVhCeARuw==", "cpu": [ "x64" ], @@ -9207,6 +9209,12 @@ "integrity": "sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA==", "license": "MIT" }, + "node_modules/@types/pako": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz", + "integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==", + "license": "MIT" + }, "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", @@ -9231,6 +9239,13 @@ "integrity": "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==", "license": "MIT" }, + "node_modules/@types/raf": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", + "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==", + "license": "MIT", + "optional": true + }, "node_modules/@types/react": { "version": "19.1.10", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.10.tgz", @@ -9271,6 +9286,13 @@ "@types/react": "*" } }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, "node_modules/@types/use-sync-external-store": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", @@ -9795,6 +9817,15 @@ "dev": true, "license": "MIT" }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -10041,6 +10072,26 @@ ], "license": "CC-BY-4.0" }, + "node_modules/canvg": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz", + "integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "@types/raf": "^3.4.0", + "core-js": "^3.8.3", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.7", + "rgbcolor": "^1.0.1", + "stackblur-canvas": "^2.0.0", + "svg-pathdata": "^6.0.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -10351,6 +10402,18 @@ "toggle-selection": "^1.0.6" } }, + "node_modules/core-js": { + "version": "3.47.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.47.0.tgz", + "integrity": "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -10442,6 +10505,15 @@ "hyphenate-style-name": "^1.0.3" } }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/css-select": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", @@ -10917,6 +10989,16 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, + "node_modules/dompurify": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.0.tgz", + "integrity": "sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optional": true, + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/domutils": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", @@ -11469,6 +11551,17 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-png": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/fast-png/-/fast-png-6.4.0.tgz", + "integrity": "sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==", + "license": "MIT", + "dependencies": { + "@types/pako": "^2.0.3", + "iobuffer": "^5.3.2", + "pako": "^2.1.0" + } + }, "node_modules/fast-shallow-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fast-shallow-equal/-/fast-shallow-equal-1.0.0.tgz", @@ -11536,6 +11629,12 @@ "walk-up-path": "^4.0.0" } }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -12118,6 +12217,33 @@ "node": ">=14" } }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "license": "MIT", + "optional": true, + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/html2canvas-pro": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/html2canvas-pro/-/html2canvas-pro-1.5.13.tgz", + "integrity": "sha512-//ksb3JLA+ewSxFODyb5YH6xqRLpe6Kt1i7Zyr5eqHmfcNt0bm8MpzSQjOK2qu6cCv13wqWtOZrCOpA+//EhaA==", + "license": "MIT", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/htmlparser2": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", @@ -12251,6 +12377,12 @@ "node": ">= 0.10" } }, + "node_modules/iobuffer": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.4.0.tgz", + "integrity": "sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==", + "license": "MIT" + }, "node_modules/ioredis": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.7.0.tgz", @@ -12879,6 +13011,23 @@ "node": ">=6" } }, + "node_modules/jspdf": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.4.tgz", + "integrity": "sha512-dc6oQ8y37rRcHn316s4ngz/nOjayLF/FFxBF4V9zamQKRqXxyiH1zagkCdktdWhtoQId5K20xt1lB90XzkB+hQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "fast-png": "^6.2.0", + "fflate": "^0.8.1" + }, + "optionalDependencies": { + "canvg": "^3.0.11", + "core-js": "^3.6.0", + "dompurify": "^3.2.4", + "html2canvas": "^1.0.0-rc.5" + } + }, "node_modules/kanel": { "version": "3.14.4", "resolved": "https://registry.npmjs.org/kanel/-/kanel-3.14.4.tgz", @@ -14713,6 +14862,12 @@ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "license": "BlueOak-1.0.0" }, + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", + "license": "(MIT AND Zlib)" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -14812,15 +14967,15 @@ "license": "MIT" }, "node_modules/pdfjs-dist": { - "version": "5.3.93", - "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.3.93.tgz", - "integrity": "sha512-w3fQKVL1oGn8FRyx5JUG5tnbblggDqyx2XzA5brsJ5hSuS+I0NdnJANhmeWKLjotdbPQucLBug5t0MeWr0AAdg==", + "version": "5.4.296", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.4.296.tgz", + "integrity": "sha512-DlOzet0HO7OEnmUmB6wWGJrrdvbyJKftI1bhMitK7O2N8W2gc757yyYBbINy9IDafXAV9wmKr9t7xsTaNKRG5Q==", "license": "Apache-2.0", "engines": { "node": ">=20.16.0 || >=22.3.0" }, "optionalDependencies": { - "@napi-rs/canvas": "^0.1.71" + "@napi-rs/canvas": "^0.1.80" } }, "node_modules/peberminta": { @@ -14832,6 +14987,13 @@ "url": "https://ko-fi.com/killymxi" } }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT", + "optional": true + }, "node_modules/pg": { "version": "8.16.3", "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", @@ -15701,6 +15863,16 @@ } } }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "license": "MIT", + "optional": true, + "dependencies": { + "performance-now": "^2.1.0" + } + }, "node_modules/ramda": { "version": "0.30.1", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", @@ -16124,9 +16296,9 @@ } }, "node_modules/react-pdf": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-10.1.0.tgz", - "integrity": "sha512-iUI1YqWgwwZcsXjrehTp3Yi8nT/bvTaWULaRMMyJWvoqqSlopk4LQQ9GDqUnDtX3gzT2glrqrLbjIPl56a+Q3w==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-10.2.0.tgz", + "integrity": "sha512-zk0DIL31oCh8cuQycM0SJKfwh4Onz0/Nwi6wTOjgtEjWGUY6eM+/vuzvOP3j70qtEULn7m1JtaeGzud1w5fY2Q==", "license": "MIT", "dependencies": { "clsx": "^2.0.0", @@ -16134,7 +16306,7 @@ "make-cancellable-promise": "^2.0.0", "make-event-props": "^2.0.0", "merge-refs": "^2.0.0", - "pdfjs-dist": "5.3.93", + "pdfjs-dist": "5.4.296", "tiny-invariant": "^1.0.0", "warning": "^4.0.0" }, @@ -16552,6 +16724,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT", + "optional": true + }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", @@ -16650,6 +16829,16 @@ "node": ">=0.10.0" } }, + "node_modules/rgbcolor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", + "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==", + "license": "MIT OR SEE LICENSE IN FEEL-FREE.md", + "optional": true, + "engines": { + "node": ">= 0.8.15" + } + }, "node_modules/rimraf": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", @@ -17407,6 +17596,16 @@ "stackframe": "^1.3.4" } }, + "node_modules/stackblur-canvas": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz", + "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.14" + } + }, "node_modules/stackframe": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", @@ -17880,6 +18079,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svg-pathdata": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", + "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/tagged-comment-parser": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/tagged-comment-parser/-/tagged-comment-parser-1.3.8.tgz", @@ -18025,6 +18234,15 @@ "dev": true, "license": "MIT" }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -18427,6 +18645,15 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "license": "MIT", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, "node_modules/uuid": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", diff --git a/apps/infoalloggi/package.json b/apps/infoalloggi/package.json index bf94708..70bb94f 100644 --- a/apps/infoalloggi/package.json +++ b/apps/infoalloggi/package.json @@ -61,9 +61,11 @@ "embla-carousel-react": "^8.6.0", "framer-motion": "^12.8.0", "frimousse": "^0.3.0", + "html2canvas-pro": "^1.5.13", "ioredis": "^5.7.0", "jose": "^6.0.12", "js-cookie": "^3.0.5", + "jspdf": "^3.0.4", "kysely": "^0.28.2", "kysely-plugin-serialize": "^0.8.2", "leaflet": "^1.9.4", @@ -88,7 +90,7 @@ "react-is": "^19.1.1", "react-leaflet": "^5.0.0", "react-number-format": "^5.4.4", - "react-pdf": "^10.1.0", + "react-pdf": "^10.2.0", "react-phone-number-input": "^3.4.12", "react-reverse-portal": "^2.3.0", "react-select": "^5.10.2", diff --git a/apps/infoalloggi/src/components/schedaAnnuncioStampabile.tsx b/apps/infoalloggi/src/components/schedaAnnuncioStampabile.tsx index 01af10e..4ee4073 100644 --- a/apps/infoalloggi/src/components/schedaAnnuncioStampabile.tsx +++ b/apps/infoalloggi/src/components/schedaAnnuncioStampabile.tsx @@ -1,5 +1,7 @@ "use client"; +import html2canvas from "html2canvas-pro"; +import jsPDF from "jspdf"; import { useEffect, useRef, useState } from "react"; import { useReactToPrint } from "react-to-print"; import { Button } from "~/components/ui/button"; @@ -10,6 +12,7 @@ import { handleConsegna } from "~/lib/annuncio_details"; import { replaceWithBr } from "~/lib/newlineToBr"; import { formatCurrency } from "~/lib/utils"; import type { Annunci } from "~/schemas/public/Annunci"; +import { api } from "~/utils/api"; import { IconMatrix } from "./IconComponents"; import { GoogleMapsIcon, LogoSvg, WhatsAppIcon } from "./svgs"; @@ -20,6 +23,119 @@ export function SchedaAnnuncioStampabile({ data }: { data: Annunci }) { //print: async (target) => {console.log("Printing...", target.contentDocument);}, }); + const { mutate } = api.comunicazioni.sendSchedaAnnuncioEmail.useMutation(); + const reactToPrintFn2 = useReactToPrint({ + contentRef, + print: async (target) => { + try { + const htmlContent = target.contentDocument?.documentElement; + if (!htmlContent) return; + htmlContent.querySelectorAll(".print\\:border-none").forEach((el) => { + el.classList.remove("border"); + }); + htmlContent.querySelectorAll(".print\\:shadow-none").forEach((el) => { + el.classList.remove("shadow-sm"); + }); + + const canvas = await html2canvas(htmlContent, { + //scale: 2, + useCORS: true, + logging: false, + backgroundColor: "#ffffff", + }); + + const imgData = canvas.toDataURL("image/png", 0.85); + const pdf = new jsPDF("p", "mm", "a4"); + + // A4 dimensions in mm + const pdfWidth = pdf.internal.pageSize.getWidth(); + const pdfHeight = pdf.internal.pageSize.getHeight(); + + // Define margins (in mm) + const marginTop = 5; + const marginRight = 5; + const marginBottom = 5; + const marginLeft = 5; + + // Calculate available space + const availableWidth = pdfWidth - marginLeft - marginRight; + const availableHeight = pdfHeight - marginTop - marginBottom; + + // Calculate image dimensions + const imgProps = pdf.getImageProperties(imgData); + const imgWidth = imgProps.width; + const imgHeight = imgProps.height; + + // Calculate how the image should fit with margins + const ratio = availableWidth / imgWidth; + const scaledHeight = imgHeight * ratio; + + // Check if content needs multiple pages + if (scaledHeight <= availableHeight) { + // Single page - content fits + pdf.addImage( + imgData, + "PNG", + marginLeft, + marginTop, + availableWidth, + scaledHeight, + undefined, + "FAST", + ); + } else { + // Multiple pages needed + let heightLeft = scaledHeight; + let position = marginTop; + + // Add first page + pdf.addImage( + imgData, + "PNG", + marginLeft, + position, + availableWidth, + scaledHeight, + undefined, + "FAST", + ); + heightLeft -= availableHeight; + + // Add remaining pages + while (heightLeft > 0) { + position = marginTop - (scaledHeight - heightLeft); + pdf.addPage(); + pdf.addImage( + imgData, + "PNG", + marginLeft, + position, + availableWidth, + scaledHeight, + undefined, + "FAST", + ); + heightLeft -= availableHeight; + } + } + const pdfBase64 = pdf.output("dataurlstring"); + console.log( + "PDF size:", + ((pdfBase64.length * 0.75) / 1024 / 1024).toFixed(2), + "MB", + ); + + mutate({ + pdf: pdfBase64, + to: "m.pedone98@gmail.com", + codice: data.codice, + }); + } catch (e) { + console.error("Error during print:", e); + } + }, + }); + return (
@@ -29,6 +145,9 @@ export function SchedaAnnuncioStampabile({ data }: { data: Annunci }) { +
@@ -38,7 +157,7 @@ export function SchedaAnnuncioStampabile({ data }: { data: Annunci }) { ); } -function SchedaAnnuncio({ data }: { data: Annunci }) { +export function SchedaAnnuncio({ data }: { data: Annunci }) { const companyAddress = "Via Beata Giovanna, 1 a Bassano del Grappa (VI)\nTel. +39 0424529869\nEmail: arca@infoalloggi.it"; diff --git a/apps/infoalloggi/src/server/api/routers/comunicazioni.ts b/apps/infoalloggi/src/server/api/routers/comunicazioni.ts index b739d3b..2c41e72 100644 --- a/apps/infoalloggi/src/server/api/routers/comunicazioni.ts +++ b/apps/infoalloggi/src/server/api/routers/comunicazioni.ts @@ -1,6 +1,11 @@ import { z } from "zod/v4"; -import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc"; +import { + adminProcedure, + createTRPCRouter, + protectedProcedure, +} from "~/server/api/trpc"; import { GetEmails } from "~/server/services/email.service"; +import { NewMail } from "~/server/services/mailer"; import { zUserId } from "~/server/utils/zod_types"; export const comunicazioniRouter = createTRPCRouter({ @@ -35,4 +40,45 @@ export const comunicazioniRouter = createTRPCRouter({ // userId: input.userId, // }); // }), + sendSchedaAnnuncioEmail: adminProcedure + .input( + z.object({ + pdf: z.string(), + codice: z.string(), + to: z.email(), + }), + ) + .mutation(async ({ input }) => { + try { + const base64Content = input.pdf.includes("base64,") + ? input.pdf.split("base64,")[1] + : input.pdf; + await NewMail({ + template: { + mailType: "generic", + props: { + noreply: true, + testo: `In allegato la scheda annuncio stampabile per l'annuncio con codice ${input.codice}.`, + title: "Scheda Annuncio Stampabile", + }, + }, + mail: { + subject: `Scheda Annuncio ${input.codice} - Infoalloggi.it`, + to: input.to, + attachments: [ + { + filename: `scheda-annuncio-${input.codice}.pdf`, + content: base64Content, + encoding: "base64", + contentType: "application/pdf", + }, + ], + }, + }); + } catch (e) { + throw new Error( + `Errore nell'invio dell'email: ${(e as Error).message}`, + ); + } + }), }); diff --git a/apps/infoalloggi/src/server/api/routers/contact.ts b/apps/infoalloggi/src/server/api/routers/contact.ts index 4105590..cd175af 100644 --- a/apps/infoalloggi/src/server/api/routers/contact.ts +++ b/apps/infoalloggi/src/server/api/routers/contact.ts @@ -22,16 +22,20 @@ export const contactRouter = createTRPCRouter({ ) .mutation(async ({ input }) => { await NewMail({ - mailType: "contattoAdminEmail", - props: { - cognome: input.cognome, - email: input.email, - messaggio: input.messaggio, - nome: input.nome, - telefono: input.telefono, + template: { + mailType: "contattoAdminEmail", + props: { + cognome: input.cognome, + email: input.email, + messaggio: input.messaggio, + nome: input.nome, + telefono: input.telefono, + }, + }, + mail: { + subject: `Richiesta di contatto da ${input.nome}`, + to: "web@infoalloggi.it", }, - subject: `Richiesta di contatto da ${input.nome}`, - to: "web@infoalloggi.it", }); return { success: true }; }), diff --git a/apps/infoalloggi/src/server/api/routers/interests.ts b/apps/infoalloggi/src/server/api/routers/interests.ts index bcc41f3..311e8ef 100644 --- a/apps/infoalloggi/src/server/api/routers/interests.ts +++ b/apps/infoalloggi/src/server/api/routers/interests.ts @@ -50,17 +50,21 @@ export const intrestsRouter = createTRPCRouter({ }); } await NewMail({ - mailType: "generic", - props: { - link: { - href: `${env.BASE_URL}/area-riservata/admin/user-view/ricerca/${userId}`, - label: "Visualizza utente", + template: { + mailType: "generic", + props: { + link: { + href: `${env.BASE_URL}/area-riservata/admin/user-view/ricerca/${userId}`, + label: "Visualizza utente", + }, + testo: `L'utente ${user.username} (${user.email}) ha mostrato interesse per l'annuncio "${annuncio.titolo_it}" (Codice: ${annuncio.codice}).`, + title: "Nuovo interesse per un annuncio", }, - testo: `L'utente ${user.username} (${user.email}) ha mostrato interesse per l'annuncio "${annuncio.titolo_it}" (Codice: ${annuncio.codice}).`, - title: "Nuovo interesse per un annuncio", }, - subject: "Utente interessato ad un annuncio", - to: "web@infoalloggi.it", + mail: { + subject: "Utente interessato ad un annuncio", + to: "web@infoalloggi.it", + }, userId, }); return { success: true }; diff --git a/apps/infoalloggi/src/server/controllers/event_queue.controller.ts b/apps/infoalloggi/src/server/controllers/event_queue.controller.ts index b8dad1d..b24f1b6 100644 --- a/apps/infoalloggi/src/server/controllers/event_queue.controller.ts +++ b/apps/infoalloggi/src/server/controllers/event_queue.controller.ts @@ -76,11 +76,12 @@ export const processEvents = async () => { const data = e.data as EventDataTypes; switch (data.tipo) { - case "email": + case "email": { await NewMail({ ...data.data, }); break; + } case "sms": await SendMessage(data.data); diff --git a/apps/infoalloggi/src/server/controllers/servizio.controller.ts b/apps/infoalloggi/src/server/controllers/servizio.controller.ts index cc0da22..fc41f3c 100644 --- a/apps/infoalloggi/src/server/controllers/servizio.controller.ts +++ b/apps/infoalloggi/src/server/controllers/servizio.controller.ts @@ -929,32 +929,40 @@ export const SbloccaContatti = async ({ const EmailProprietario: NewMailProps | null = data.annuncio.email ? { - mailType: "emailContattoInteressato", - props: { - indirizzo: data.annuncio.indirizzo, - nome_cognome: data.annuncio.locatore || "", - nome_cognome_utente: data.user.username, - sesso: data.anagrafica.sesso, - telefono: data.user.telefono, + template: { + mailType: "emailContattoInteressato", + props: { + indirizzo: data.annuncio.indirizzo, + nome_cognome: data.annuncio.locatore || "", + nome_cognome_utente: data.user.username, + sesso: data.anagrafica.sesso, + telefono: data.user.telefono, + }, + }, + mail: { + subject: "Un interessato ha ricevuto i Suoi contatti", + to: data.annuncio.email, }, - subject: "Un interessato ha ricevuto i Suoi contatti", - to: data.annuncio.email, } : null; const EmailInteressato: NewMailProps = { - mailType: "generic", - props: { - link: { - href: `${env.BASE_URL}/area-riservata/dashboard#${servizioId}-${annuncioId}`, - label: "Vai al servizio", + template: { + mailType: "generic", + props: { + link: { + href: `${env.BASE_URL}/area-riservata/dashboard#${servizioId}-${annuncioId}`, + label: "Vai al servizio", + }, + noreply: true, + testo: `I contatti per l'annuncio ${data.annuncio.codice} sono stati sbloccati con successo.`, + title: "Contatti sbloccati per il servizio di ricerca affitto", }, - noreply: true, - testo: `I contatti per l'annuncio ${data.annuncio.codice} sono stati sbloccati con successo.`, - title: "Contatti sbloccati per il servizio di ricerca affitto", }, - subject: "Contatti sbloccati per il servizio di ricerca affitto", - to: data.user.email, + mail: { + subject: "Contatti sbloccati per il servizio di ricerca affitto", + to: data.user.email, + }, }; const numeroProprietario = data.annuncio.numero ? data.annuncio.numero.replace(/\s+/g, "").split(",")[0] || null @@ -1053,30 +1061,38 @@ export const interruzioneServizio = async (servizioId: ServizioServizioId) => { // NOTIFICHE EMAIL INTERRUZIONE SERVIZIO const utente = await getUser({ db, userId: servizio.user_id }); await NewMail({ - mailType: "generic", - props: { - noreply: true, - testo: `Abbiamo ricevuto la tua richiesta di interruzione del servizio di ricerca affitto per la tipologia ${servizio.tipologia}. Il servizio è stato interrotto con successo.`, - title: `Servizio Ricerca Affitto ${servizio.tipologia} interrotto`, + template: { + mailType: "generic", + props: { + noreply: true, + testo: `Abbiamo ricevuto la tua richiesta di interruzione del servizio di ricerca affitto per la tipologia ${servizio.tipologia}. Il servizio è stato interrotto con successo.`, + title: `Servizio Ricerca Affitto ${servizio.tipologia} interrotto`, + }, + }, + mail: { + subject: `Servizio Ricerca Affitto ${servizio.tipologia} interrotto`, + to: utente.email, }, - subject: `Servizio Ricerca Affitto ${servizio.tipologia} interrotto`, - to: utente.email, userId: utente.id, }); await NewMail({ - mailType: "generic", - props: { - link: { - href: `${env.BASE_URL}/area-riservata/admin/user-view/ricerca/${servizio.user_id}#${servizioId}`, - label: "Vai al servizio", + template: { + mailType: "generic", + props: { + link: { + href: `${env.BASE_URL}/area-riservata/admin/user-view/ricerca/${servizio.user_id}#${servizioId}`, + label: "Vai al servizio", + }, + noreply: true, + testo: `L'utente ha richiesto l'interruzione del servizio di ricerca affitto per la tipologia ${servizio.tipologia}. Puoi procedere con le prossime azioni.`, + title: `Utente ${utente.username} ha richieto l'interruzione del servizio di ricerca affitto`, }, - noreply: true, - testo: `L'utente ha richiesto l'interruzione del servizio di ricerca affitto per la tipologia ${servizio.tipologia}. Puoi procedere con le prossime azioni.`, - title: `Utente ${utente.username} ha richieto l'interruzione del servizio di ricerca affitto`, }, - subject: `Utente ${utente.username} ha richieto l'interruzione del servizio di ricerca affitto`, - to: "web@infoalloggi.it", + mail: { + subject: `Utente ${utente.username} ha richieto l'interruzione del servizio di ricerca affitto`, + to: "web@infoalloggi.it", + }, }); return true; @@ -1174,21 +1190,25 @@ export const sendServizioEmail = async ({ const { email, nome } = user; await NewMail({ - mailType: "onboardingServizio", - props: { - annunci: annunci.map((annuncio) => ({ - codice: annuncio.codice, - immagine: annuncio?.images - ? annuncio.images[0]?.img || "fallback" - : "fallback", - prezzo: annuncio.prezzo, - titolo: annuncio.titolo_it, - })), - nome, - token: servizioId, + template: { + mailType: "onboardingServizio", + props: { + annunci: annunci.map((annuncio) => ({ + codice: annuncio.codice, + immagine: annuncio?.images + ? annuncio.images[0]?.img || "fallback" + : "fallback", + prezzo: annuncio.prezzo, + titolo: annuncio.titolo_it, + })), + nome, + token: servizioId, + }, + }, + mail: { + subject: "Procedi ora su Infoalloggi.it", + to: email, }, - subject: "Procedi ora su Infoalloggi.it", - to: email, userId, }); await db @@ -1247,29 +1267,37 @@ export const userSendConfermaIntent = async ({ .executeTakeFirstOrThrow(); await NewMail({ - mailType: "generic", - props: { - link: { - href: `${env.BASE_URL}/area-riservata/admin/user-view/ricerca/${servizio.user_id}#${servizioId}-${annuncioId}`, - label: "Visualizza Annuncio", + template: { + mailType: "generic", + props: { + link: { + href: `${env.BASE_URL}/area-riservata/admin/user-view/ricerca/${servizio.user_id}#${servizioId}-${annuncioId}`, + label: "Visualizza Annuncio", + }, + noreply: true, + testo: `L'utente ${utente.username} ha confermato l'immobile con codice ${annuncio_codice.codice}.`, + title: `Nuova conferma immobile ${annuncio_codice.codice}`, }, - noreply: true, - testo: `L'utente ${utente.username} ha confermato l'immobile con codice ${annuncio_codice.codice}.`, - title: `Nuova conferma immobile ${annuncio_codice.codice}`, }, - subject: `Nuova conferma immobile ${annuncio_codice.codice}`, - to: "web@infoalloggi.it", + mail: { + subject: `Nuova conferma immobile ${annuncio_codice.codice}`, + to: "web@infoalloggi.it", + }, }); await NewMail({ - mailType: "generic", - props: { - noreply: true, - testo: `La tua conferma per l'immobile con codice ${annuncio_codice.codice} è stata ricevuta. Il nostro team la esaminerà a breve. Riceverai una notifica via email per procedere.`, - title: `Conferma immobile ${annuncio_codice.codice} ricevuta`, + template: { + mailType: "generic", + props: { + noreply: true, + testo: `La tua conferma per l'immobile con codice ${annuncio_codice.codice} è stata ricevuta. Il nostro team la esaminerà a breve. Riceverai una notifica via email per procedere.`, + title: `Conferma immobile ${annuncio_codice.codice} ricevuta`, + }, + }, + mail: { + subject: `Conferma immobile ${annuncio_codice.codice} ricevuta`, + to: utente.email, }, - subject: `Conferma immobile ${annuncio_codice.codice} ricevuta`, - to: utente.email, userId: utente.id, }); return true; @@ -1305,18 +1333,22 @@ export const adminUpdateConfermaStatus = async ({ //ADMIN CONFERMA LAVORATA EMAIL if (status) { await NewMail({ - mailType: "generic", - props: { - link: { - href: `${env.BASE_URL}/area-riservata/dashboard#${servizioId}-${annuncioId}`, - label: "Vai al servizio", + template: { + mailType: "generic", + props: { + link: { + href: `${env.BASE_URL}/area-riservata/dashboard#${servizioId}-${annuncioId}`, + label: "Vai al servizio", + }, + noreply: true, + testo: `La conferma è stata lavorata con successo. Puoi procedere con le prossime azioni.`, + title: "Conferma lavorata per il servizio di ricerca affitto", }, - noreply: true, - testo: `La conferma è stata lavorata con successo. Puoi procedere con le prossime azioni.`, - title: "Conferma lavorata per il servizio di ricerca affitto", }, - subject: "Conferma lavorata per il servizio di ricerca affitto", - to: utente.email, + mail: { + subject: "Conferma lavorata per il servizio di ricerca affitto", + to: utente.email, + }, userId: utente.id, }); } @@ -1353,18 +1385,22 @@ export const userAccettaConferma = async ({ // USER ACCETTA CONFERMA EMAIL await NewMail({ - mailType: "generic", - props: { - link: { - href: `${env.BASE_URL}/area-riservata/admin/user-view/ricerca/${servizio.user_id}#${servizioId}-${annuncioId}`, - label: "Vai al servizio", + template: { + mailType: "generic", + props: { + link: { + href: `${env.BASE_URL}/area-riservata/admin/user-view/ricerca/${servizio.user_id}#${servizioId}-${annuncioId}`, + label: "Vai al servizio", + }, + noreply: true, + testo: `L'utente ha accettato le condizioni di conferma per la locazione. Puoi procedere con le prossime azioni.`, + title: `Utente ${utente.username} ha accettato le condizioni di conferma`, }, - noreply: true, - testo: `L'utente ha accettato le condizioni di conferma per la locazione. Puoi procedere con le prossime azioni.`, - title: `Utente ${utente.username} ha accettato le condizioni di conferma`, }, - subject: `Utente ${utente.username} ha accettato le condizioni di conferma`, - to: "web@infoalloggi.it", + mail: { + subject: `Utente ${utente.username} ha accettato le condizioni di conferma`, + to: "web@infoalloggi.it", + }, }); return true; @@ -1552,16 +1588,20 @@ export const SendContactEmail = async ({ }) => { try { await NewMail({ - mailType: "contattoAnnuncioEmail", - props: { - codice, - email, - messaggio, - nome, - telefono, + template: { + mailType: "contattoAnnuncioEmail", + props: { + codice, + email, + messaggio, + nome, + telefono, + }, + }, + mail: { + subject: "Contatto per annuncio", + to: "web@infoalloggi.it", }, - subject: "Contatto per annuncio", - to: "web@infoalloggi.it", }); return { email, diff --git a/apps/infoalloggi/src/server/controllers/skebby.controller.ts b/apps/infoalloggi/src/server/controllers/skebby.controller.ts index 7300893..38d342a 100644 --- a/apps/infoalloggi/src/server/controllers/skebby.controller.ts +++ b/apps/infoalloggi/src/server/controllers/skebby.controller.ts @@ -155,13 +155,17 @@ export const SendMessage = async (data: MinimalSMSData) => { console.error("Error:", error); await NewMail({ - mailType: "generic", - props: { - testo: `Errore durante l'invio dell'SMS: to:${data.recipient.join(", ")} Mgs:${data.message}; ${error instanceof Error ? error.message : String(error)}`, - title: "Errore invio SMS", + template: { + mailType: "generic", + props: { + testo: `Errore durante l'invio dell'SMS: to:${data.recipient.join(", ")} Mgs:${data.message}; ${error instanceof Error ? error.message : String(error)}`, + title: "Errore invio SMS", + }, + }, + mail: { + subject: "Errore invio SMS", + to: "web@infoalloggi.it", }, - subject: "Errore invio SMS", - to: "web@infoalloggi.it", }); } }; diff --git a/apps/infoalloggi/src/server/controllers/stripe.controller.ts b/apps/infoalloggi/src/server/controllers/stripe.controller.ts index 5f3ad67..c29e178 100644 --- a/apps/infoalloggi/src/server/controllers/stripe.controller.ts +++ b/apps/infoalloggi/src/server/controllers/stripe.controller.ts @@ -135,19 +135,23 @@ export const whIntentSucceededHandler = async ({ .execute(); await NewMail({ - mailType: "generic", + template: { + mailType: "generic", - props: { - link: { - href: `${env.BASE_URL}/area-riservata/payment-recap/${ordine.ordine_id}`, - label: "Vai alla ricevuta", + props: { + link: { + href: `${env.BASE_URL}/area-riservata/payment-recap/${ordine.ordine_id}`, + label: "Vai alla ricevuta", + }, + noreply: true, + testo: `Il pagamento dell'acconto per il servizio ${ordine.packid} è stato confermato.`, + title: "Pagamento Confermato ", }, - noreply: true, - testo: `Il pagamento dell'acconto per il servizio ${ordine.packid} è stato confermato.`, - title: "Pagamento Confermato ", }, - subject: "Pagamento Confermato - Acconto Infoalloggi.it", - to: utente.email, + mail: { + subject: "Pagamento Confermato - Acconto Infoalloggi.it", + to: utente.email, + }, userId: utente.id, }); break; @@ -163,18 +167,22 @@ export const whIntentSucceededHandler = async ({ }) .execute(); await NewMail({ - mailType: "generic", - props: { - link: { - href: `${env.BASE_URL}/area-riservata/payment-recap/${ordine.ordine_id}`, - label: "Vai alla ricevuta", + template: { + mailType: "generic", + props: { + link: { + href: `${env.BASE_URL}/area-riservata/payment-recap/${ordine.ordine_id}`, + label: "Vai alla ricevuta", + }, + noreply: true, + testo: `Il pagamento del saldo per il servizio ${ordine.packid} è stato confermato.`, + title: "Pagamento Confermato ", }, - noreply: true, - testo: `Il pagamento del saldo per il servizio ${ordine.packid} è stato confermato.`, - title: "Pagamento Confermato ", }, - subject: "Pagamento Confermato - Saldo Infoalloggi.it", - to: utente.email, + mail: { + subject: "Pagamento Confermato - Saldo Infoalloggi.it", + to: utente.email, + }, userId: utente.id, }); break; @@ -187,30 +195,38 @@ export const whIntentSucceededHandler = async ({ }) .execute(); await NewMail({ - mailType: "generic", - props: { - link: { - href: `${env.BASE_URL}/area-riservata/payment-recap/${ordine.ordine_id}`, - label: "Vai alla ricevuta", + template: { + mailType: "generic", + props: { + link: { + href: `${env.BASE_URL}/area-riservata/payment-recap/${ordine.ordine_id}`, + label: "Vai alla ricevuta", + }, + noreply: true, + testo: `Il pagamento per la consulenza è stato confermato.`, + title: "Pagamento Confermato ", }, - noreply: true, - testo: `Il pagamento per la consulenza è stato confermato.`, - title: "Pagamento Confermato ", }, - subject: "Pagamento Confermato - Consulenza Infoalloggi.it", - to: utente.email, + mail: { + subject: "Pagamento Confermato - Consulenza Infoalloggi.it", + to: utente.email, + }, userId: utente.id, }); break; case OrderTypeEnum.Altro: // TODO: Handle other types await NewMail({ - mailType: "pagamentoConferma", - props: { - receiptHref: `${env.BASE_URL}/area-riservata/payment-recap/${ordine.ordine_id}`, + template: { + mailType: "pagamentoConferma", + props: { + receiptHref: `${env.BASE_URL}/area-riservata/payment-recap/${ordine.ordine_id}`, + }, + }, + mail: { + subject: "Pagamento Confermato", + to: utente.email, }, - subject: "Pagamento Confermato", - to: utente.email, userId: utente.id, }); break; @@ -246,9 +262,13 @@ export const whIntentFailedHandler = async ({ }); await NewMail({ - mailType: "pagamentoErrore", - subject: "Pagamento Fallito", - to: utente.email, + template: { + mailType: "pagamentoErrore", + }, + mail: { + subject: "Pagamento Fallito", + to: utente.email, + }, userId: utente.id, }); } diff --git a/apps/infoalloggi/src/server/services/auth.service.ts b/apps/infoalloggi/src/server/services/auth.service.ts index bea889a..fc39853 100644 --- a/apps/infoalloggi/src/server/services/auth.service.ts +++ b/apps/infoalloggi/src/server/services/auth.service.ts @@ -115,12 +115,16 @@ export const resetPasswordFromTokenHandler = async ({ export const pwResetSendLinkHandler = async ({ email }: { email: string }) => { const { token, userId } = await pwResetTokenFromMailHandler({ email }); await NewMail({ - mailType: "pwResetLink", - props: { - resetlink: `${env.BASE_URL}/auth/password-reset/${token}`, + template: { + mailType: "pwResetLink", + props: { + resetlink: `${env.BASE_URL}/auth/password-reset/${token}`, + }, + }, + mail: { + subject: `Richiesta di reset password Infoalloggi.it`, + to: email, }, - subject: `Richiesta di reset password Infoalloggi.it`, - to: email, userId, }); }; diff --git a/apps/infoalloggi/src/server/services/mailer.ts b/apps/infoalloggi/src/server/services/mailer.ts index bc21af6..9dd287b 100644 --- a/apps/infoalloggi/src/server/services/mailer.ts +++ b/apps/infoalloggi/src/server/services/mailer.ts @@ -38,12 +38,7 @@ import { GetFlagValueHandler } from "~/server/controllers/flags.controller"; import { db } from "~/server/db"; import { StoreEmail } from "~/server/services/email.service"; -type MailerProps = { - to: string; - subject: string; - html: string; -}; -const mailer = async ({ to, subject, html }: MailerProps) => { +const mailer = async (options: Options) => { const smtpTransport = createTransport({ auth: { pass: env.ARUBA_PASS, @@ -63,9 +58,7 @@ const mailer = async ({ to, subject, html }: MailerProps) => { const message: Options = { from: `"InfoAlloggi" <${env.ARUBA_USER}>`, - html: html, - subject: subject, - to: to, + ...options, }; smtpTransport.sendMail(message, (err, info) => { if (err) { @@ -75,13 +68,11 @@ const mailer = async ({ to, subject, html }: MailerProps) => { }); }; -type base_NewMailProps = { +export type NewMailProps = { + template?: MailsTemplates; userId?: UsersId; - to: string; - subject: string; + mail: Options; }; - -export type NewMailProps = base_NewMailProps & MailsTemplates; export type MailsTemplates = | ContattoAdmin_NewMail | ContattoAnnuncio_NewMail @@ -185,24 +176,24 @@ export const genMail = async ({ ...mail }: MailsTemplates) => { return { html: await render(mailComponent), title }; }; -export const NewMail = async ({ - userId, - to, - subject, - ...mail -}: NewMailProps) => { +export const NewMail = async ({ template, userId, mail }: NewMailProps) => { const emailFlag = await GetFlagValueHandler({ db: db, id: "EMAIL_OFF", }); if (emailFlag === "false") { try { - const { html } = await genMail({ ...mail }); - await mailer({ - html, - subject: subject, - to: to, - }); + if (template) { + const { html } = await genMail({ ...template }); + await mailer({ + html, + ...mail, + }); + } else { + await mailer({ + ...mail, + }); + } } catch (e) { throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", @@ -211,12 +202,12 @@ export const NewMail = async ({ } } else { console.warn( - `Email sending is disabled to:${to} subj:${subject} msgType: ${mail.mailType}`, + `Email sending is disabled to:${mail.to} subj:${mail.subject} msgType: ${template?.mailType}`, ); } - if (userId) { + if (userId && template) { // Store the email in the database if userId is provided - await StoreEmail({ data: mail, userId }); + await StoreEmail({ data: template, userId }); } };