diff --git a/admin/package-lock.json b/admin/package-lock.json index 412b62f..39313a3 100644 --- a/admin/package-lock.json +++ b/admin/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "dependencies": { "@coal-works/ui": "../libs/ui", + "@google-cloud/translate": "^8.5.0", "@hookform/resolvers": "^3.9.1", "@mapbox/mapbox-gl-draw": "^1.5.0", "@radix-ui/react-accordion": "^1.2.1", @@ -28,6 +29,7 @@ "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-switch": "^1.1.1", "@radix-ui/react-tabs": "^1.1.1", + "@radix-ui/react-toast": "^1.2.2", "@radix-ui/react-toggle": "^1.1.0", "@radix-ui/react-toggle-group": "^1.1.0", "@radix-ui/react-tooltip": "^1.1.4", @@ -60,6 +62,7 @@ "react-day-picker": "^8.10.1", "react-dom": "^18.3.1", "react-dropzone": "^14.3.5", + "react-google-multi-lang": "^1.0.7", "react-hook-form": "^7.54.0", "react-map-gl": "^7.1.7", "react-phone-number-input": "^3.4.9", @@ -558,6 +561,200 @@ "version": "0.2.8", "license": "MIT" }, + "node_modules/@google-cloud/common": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-5.0.2.tgz", + "integrity": "sha512-V7bmBKYQyu0eVG2BFejuUjlBt+zrya6vtsKdY+JxMM/dNntPF41vZ9+LhOshEUH01zOHEqBSvI7Dad7ZS6aUeA==", + "license": "Apache-2.0", + "dependencies": { + "@google-cloud/projectify": "^4.0.0", + "@google-cloud/promisify": "^4.0.0", + "arrify": "^2.0.1", + "duplexify": "^4.1.1", + "extend": "^3.0.2", + "google-auth-library": "^9.0.0", + "html-entities": "^2.5.2", + "retry-request": "^7.0.0", + "teeny-request": "^9.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/projectify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz", + "integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/promisify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.0.0.tgz", + "integrity": "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/translate": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@google-cloud/translate/-/translate-8.5.0.tgz", + "integrity": "sha512-avQa3WLkO3PSk2fiV6Af/PmeDnM6XWGDgO+Z+hZ/FZpBRMjCW1Px9MNLbM1sBKGjt/uM8aOGHqow/AAR7lLsUA==", + "license": "Apache-2.0", + "dependencies": { + "@google-cloud/common": "^5.0.0", + "@google-cloud/promisify": "^4.0.0", + "arrify": "^2.0.0", + "extend": "^3.0.2", + "google-gax": "^4.0.3", + "is-html": "^2.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.12.4.tgz", + "integrity": "sha512-NBhrxEWnFh0FxeA0d//YP95lRFsSx2TNLEUQg4/W+5f/BMxcCjgOOIT24iD+ZB/tZw057j44DaIxja7w4XMrhg==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.7.13", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", + "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@grpc/proto-loader/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@grpc/proto-loader/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@grpc/proto-loader/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/@grpc/proto-loader/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@grpc/proto-loader/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@grpc/proto-loader/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@grpc/proto-loader/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/@grpc/proto-loader/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@grpc/proto-loader/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/@hookform/resolvers": { "version": "3.9.1", "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.9.1.tgz", @@ -683,6 +880,16 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, "node_modules/@mapbox/geojson-area": { "version": "0.2.2", "license": "BSD-2-Clause", @@ -838,6 +1045,70 @@ "node": ">=14" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, "node_modules/@radix-ui/number": { "version": "1.1.0", "license": "MIT" @@ -1881,6 +2152,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.2.tgz", "integrity": "sha512-Z6pqSzmAP/bFJoqMAston4eSNa+ud44NSZTiZUmUen+IOZ5nBY8kzuU5WDBVyFXPtcW6yUalOHsxM/BP6Sv8ww==", + "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.0", "@radix-ui/react-collection": "1.1.0", @@ -2412,6 +2684,15 @@ "url": "https://github.com/sponsors/tannerlinsley" } }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/@types/axios": { "version": "0.14.4", "resolved": "https://registry.npmjs.org/@types/axios/-/axios-0.14.4.tgz", @@ -2458,6 +2739,12 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/caseless": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", + "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==", + "license": "MIT" + }, "node_modules/@types/crypto-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz", @@ -2540,6 +2827,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "license": "MIT" + }, "node_modules/@types/mapbox__point-geometry": { "version": "0.1.4", "license": "MIT" @@ -2618,6 +2911,33 @@ "@types/react": "*" } }, + "node_modules/@types/request": { + "version": "2.48.12", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz", + "integrity": "sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw==", + "license": "MIT", + "dependencies": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + } + }, + "node_modules/@types/request/node_modules/form-data": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.2.tgz", + "integrity": "sha512-GgwY0PS7DbXqajuGf4OYlsrIu3zgxD6Vvql43IBhm6MahqA5SK/7mwhtNj2AdH2z35YR34ujJ7BN+3fFC3jP5Q==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" + } + }, "node_modules/@types/stylis": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz", @@ -2630,6 +2950,12 @@ "@types/geojson": "*" } }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.13.0", "dev": true, @@ -2866,6 +3192,18 @@ "vite": "^4.2.0 || ^5.0.0" } }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/abs-svg-path": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/abs-svg-path/-/abs-svg-path-0.1.1.tgz", @@ -2890,6 +3228,15 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/ajv": { "version": "6.12.6", "dev": true, @@ -2969,6 +3316,15 @@ "node": ">=0.10.0" } }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/assign-symbols": { "version": "1.0.0", "license": "MIT", @@ -3084,6 +3440,15 @@ "require-from-string": "^2.0.2" } }, + "node_modules/bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "license": "MIT", @@ -3176,6 +3541,12 @@ "node": ">= 0.4.0" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "node_modules/bundle-require": { "version": "5.0.0", "dev": true, @@ -4145,6 +4516,30 @@ "integrity": "sha512-2q4bEI+coQM8f5ez7kt2xclg1XsecaV9ASJk/54vwlfRRNQfDqJz2pzQ8t0Ix/ToBpXlVjrRIx7pFC/o8itG2Q==", "optional": true }, + "node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/duplexify": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" + } + }, "node_modules/earcut": { "version": "3.0.0", "license": "ISC" @@ -4153,6 +4548,15 @@ "version": "0.2.0", "license": "MIT" }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.55", "dev": true, @@ -4162,6 +4566,15 @@ "version": "9.2.2", "license": "MIT" }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/engine.io-client": { "version": "6.6.2", "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.2.tgz", @@ -4221,7 +4634,6 @@ }, "node_modules/escalade": { "version": "3.2.0", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -4396,6 +4808,15 @@ "node": ">=0.10.0" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/eventemitter3": { "version": "4.0.7", "license": "MIT" @@ -4408,6 +4829,12 @@ "node": ">=0.8.x" } }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, "node_modules/extend-shallow": { "version": "2.0.1", "license": "MIT", @@ -4650,6 +5077,48 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gaxios/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/gcp-metadata": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", + "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^6.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "dev": true, @@ -4765,6 +5234,59 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/google-auth-library": { + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.0.tgz", + "integrity": "sha512-7ccSEJFDFO7exFbO6NRyC+xH8/mZ1GZGG2xxx9iHxZWcjUjJpjWxIMw3cofAKcueZ6DATiukmmprD7yavQHOyQ==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-gax": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.4.1.tgz", + "integrity": "sha512-Phyp9fMfA00J3sZbJxbbB4jC55b7DBjE3F6poyL3wKMEBVKA79q6BGuHcTiM28yOzVql0NDbRL8MLLh8Iwk9Dg==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.10.9", + "@grpc/proto-loader": "^0.7.13", + "@types/long": "^4.0.0", + "abort-controller": "^3.0.0", + "duplexify": "^4.0.0", + "google-auth-library": "^9.3.0", + "node-fetch": "^2.7.0", + "object-hash": "^3.0.0", + "proto3-json-serializer": "^2.0.2", + "protobufjs": "^7.3.2", + "retry-request": "^7.0.0", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-gax/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/graphemer": { "version": "1.4.0", "dev": true, @@ -4774,6 +5296,19 @@ "version": "1.1.0", "license": "ISC" }, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "license": "MIT", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/has-flag": { "version": "4.0.0", "dev": true, @@ -4805,6 +5340,34 @@ "resolved": "https://registry.npmjs.org/hsl-to-rgb-for-reals/-/hsl-to-rgb-for-reals-1.1.1.tgz", "integrity": "sha512-LgOWAkrN0rFaQpfdWBQlv/VhkOxb5AsBjk6NQVx4yEzWS923T07X0M1Y0VNko2H52HeSpZrZNNMJ0aFqsdVzQg==" }, + "node_modules/html-entities": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", + "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, + "node_modules/html-tags": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", + "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/html2canvas": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", @@ -4831,6 +5394,32 @@ "node": ">=8.0.0" } }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-proxy-agent/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/http-proxy-middleware": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.3.tgz", @@ -4857,6 +5446,19 @@ "node": ">=0.10.0" } }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/hyphen": { "version": "1.10.6", "resolved": "https://registry.npmjs.org/hyphen/-/hyphen-1.10.6.tgz", @@ -4996,6 +5598,18 @@ "node": ">=0.10.0" } }, + "node_modules/is-html": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-html/-/is-html-2.0.0.tgz", + "integrity": "sha512-S+OpgB5i7wzIue/YSE5hg0e5ZYfG3hhpNh9KGl6ayJ38p7ED6wxQLd1TV91xHpcTvw90KMJ9EwN3F/iNflHBVg==", + "license": "MIT", + "dependencies": { + "html-tags": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-number": { "version": "7.0.0", "license": "MIT", @@ -5013,6 +5627,18 @@ "node": ">=0.10.0" } }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-url": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", @@ -5091,6 +5717,15 @@ "node": ">=6" } }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "dev": true, @@ -5138,6 +5773,27 @@ "html2canvas": "^1.0.0-rc.5" } }, + "node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "node_modules/kdbush": { "version": "4.0.2", "license": "ISC" @@ -5212,6 +5868,12 @@ "version": "4.17.21", "license": "MIT" }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "dev": true, @@ -5222,6 +5884,12 @@ "dev": true, "license": "MIT" }, + "node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", + "license": "Apache-2.0" + }, "node_modules/loose-envify": { "version": "1.4.0", "license": "MIT", @@ -5429,6 +6097,48 @@ "dev": true, "license": "MIT" }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/node-releases": { "version": "2.0.18", "dev": true, @@ -5500,6 +6210,15 @@ "pbf": "bin/pbf" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/optionator": { "version": "0.9.4", "dev": true, @@ -5831,6 +6550,42 @@ "react-is": "^16.13.1" } }, + "node_modules/proto3-json-serializer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz", + "integrity": "sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==", + "license": "Apache-2.0", + "dependencies": { + "protobufjs": "^7.2.5" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/protobufjs": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", + "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/protocol-buffers-schema": { "version": "3.6.0", "license": "MIT" @@ -6052,6 +6807,30 @@ "react": ">= 16.8 || 18.0.0" } }, + "node_modules/react-google-multi-lang": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/react-google-multi-lang/-/react-google-multi-lang-1.0.7.tgz", + "integrity": "sha512-PgJSAP/W5Kfv5MPi/26YxaIyDme7EjfDXEtT5pE9JnSu1IGhXHGG7HGCwV1M1OlzP690jGpNpiKiwzHsMHjLEg==", + "license": "MIT", + "dependencies": { + "axios": "^0.27.2", + "dotenv": "^16.4.5" + }, + "peerDependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + } + }, + "node_modules/react-google-multi-lang/node_modules/axios": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" + } + }, "node_modules/react-hook-form": { "version": "7.54.0", "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.54.0.tgz", @@ -6264,6 +7043,20 @@ "pify": "^2.3.0" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "3.6.0", "license": "MIT", @@ -6372,6 +7165,20 @@ "resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.2.tgz", "integrity": "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==" }, + "node_modules/retry-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz", + "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==", + "license": "MIT", + "dependencies": { + "@types/request": "^2.48.8", + "extend": "^3.0.2", + "teeny-request": "^9.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/reusify": { "version": "1.0.4", "license": "MIT", @@ -6666,6 +7473,21 @@ "node": ">=0.1.14" } }, + "node_modules/stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "license": "MIT", + "dependencies": { + "stubs": "^3.0.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", + "license": "MIT" + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -6765,6 +7587,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", + "license": "MIT" + }, "node_modules/styled-components": { "version": "6.1.13", "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.13.tgz", @@ -6941,6 +7769,60 @@ "tailwindcss": ">=3.0.0 || insiders" } }, + "node_modules/teeny-request": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", + "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==", + "license": "Apache-2.0", + "dependencies": { + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.9", + "stream-events": "^1.0.5", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/teeny-request/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/teeny-request/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/teeny-request/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/text-segmentation": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", @@ -7705,26 +8587,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/xml-utils": { "version": "1.10.1", "resolved": "https://registry.npmjs.org/xml-utils/-/xml-utils-1.10.1.tgz", diff --git a/admin/package.json b/admin/package.json index 2e65ec9..3c436d4 100644 --- a/admin/package.json +++ b/admin/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@coal-works/ui": "../libs/ui", + "@google-cloud/translate": "^8.5.0", "@hookform/resolvers": "^3.9.1", "@mapbox/mapbox-gl-draw": "^1.5.0", "@radix-ui/react-accordion": "^1.2.1", @@ -30,6 +31,7 @@ "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-switch": "^1.1.1", "@radix-ui/react-tabs": "^1.1.1", + "@radix-ui/react-toast": "^1.2.2", "@radix-ui/react-toggle": "^1.1.0", "@radix-ui/react-toggle-group": "^1.1.0", "@radix-ui/react-tooltip": "^1.1.4", @@ -62,6 +64,7 @@ "react-day-picker": "^8.10.1", "react-dom": "^18.3.1", "react-dropzone": "^14.3.5", + "react-google-multi-lang": "^1.0.7", "react-hook-form": "^7.54.0", "react-map-gl": "^7.1.7", "react-phone-number-input": "^3.4.9", diff --git a/admin/src/App.tsx b/admin/src/App.tsx index bda2019..2a72ea3 100644 --- a/admin/src/App.tsx +++ b/admin/src/App.tsx @@ -45,6 +45,7 @@ import FormPage from './pages/FormPage' import ControlPlanTemplateBuilder from './pages/createControlPlan' import SoftwareIntegration from './pages/SoftwareIntegration' import { RenderSmp } from './pages/RenderSmp' +import Dgms from './pages/Dgms' interface ErrorPageProps { @@ -317,6 +318,10 @@ function App() { { path: '/render-smp', element: + }, + { + path: '/dgms', + element: } // { // path: '/form/:uniqueKey', diff --git a/admin/src/components/custom/ChatBot.tsx b/admin/src/components/custom/ChatBot.tsx index feb0d7e..d540e96 100644 --- a/admin/src/components/custom/ChatBot.tsx +++ b/admin/src/components/custom/ChatBot.tsx @@ -1,131 +1,216 @@ +'use client' + import React, { useState, useRef, ChangeEvent } from 'react' -import { SendHorizontal, X, Paperclip } from 'lucide-react' +import { SendHorizontal, X, Paperclip, Bot } from 'lucide-react' +import { motion, AnimatePresence } from 'framer-motion' +import axios from 'axios' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { ScrollArea } from '@/components/ui/scroll-area' import { Card } from '@/components/ui/card' +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' +import { LoadingDots } from './LoadingDots' +import { MessageBubble } from './MessageBubble' interface Message { - id: number; - content: string; - role: 'user' | 'assistant'; + id: number + content: string + role: 'user' | 'assistant' } interface ChatBotProps { - isOpen: boolean; - onClose: () => void; + isOpen: boolean + onClose: () => void } export default function ChatBot({ isOpen, onClose }: ChatBotProps) { - const [messages, setMessages] = useState([]) - const [input, setInput] = useState('') - const [isLoading, setIsLoading] = useState(false) - const fileInputRef = useRef(null); + const [messages, setMessages] = useState([]) + const [input, setInput] = useState('') + const [isLoading, setIsLoading] = useState(false) + const fileInputRef = useRef(null) + const scrollAreaRef = useRef(null) - const handleInputChange = (e: React.ChangeEvent) => { - setInput(e.target.value) - } + const handleInputChange = (e: React.ChangeEvent) => { + setInput(e.target.value) + } - const onSubmit = async (e: React.FormEvent) => { - e.preventDefault() - - if (input.trim() === '') return; + const scrollToBottom = () => { + if (scrollAreaRef.current) { + scrollAreaRef.current.scrollTop = scrollAreaRef.current.scrollHeight + } + } - const userMessage: Message = { - id: Date.now(), - content: input, - role: 'user' - } - setMessages(prevMessages => [...prevMessages, userMessage]) - - setInput('') - setIsLoading(true) - - setTimeout(() => { - const assistantMessage: Message = { - id: Date.now() + 1, - content: `No. of active shifts: 13`, - role: 'assistant' - } - setMessages(prevMessages => [...prevMessages, assistantMessage]) - setIsLoading(false) - }, 1000) + const onSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + if (input.trim() === '') return; + + const userMessage: Message = { + id: Date.now(), + content: input, + role: 'user', + }; + + setMessages((prevMessages) => [...prevMessages, userMessage]); + setInput(''); + setIsLoading(true); + scrollToBottom(); + + try { + const res = await axios.post('http://192.168.110.223:8000/chatbot-query', { + query: input, + }); + + const assistantReply = res.data.response; + + const assistantMessage: Message = { + id: Date.now() + 1, + content: assistantReply, + role: 'assistant', + }; + + setMessages((prevMessages) => [...prevMessages, assistantMessage]); + } catch (error) { + console.error('Error communicating with chatbot:', error); + + const errorMessage: Message = { + id: Date.now() + 2, + content: 'Something went wrong. Please try again.', + role: 'assistant', + }; + setMessages((prevMessages) => [...prevMessages, errorMessage]); + } finally { + setIsLoading(false); + scrollToBottom(); } + }; + + + const handleFileUpload = async (e: ChangeEvent) => { + const files = e.target.files + if (files) { + const fileNames = Array.from(files).map((file) => file.name).join(', ') + const fileMessage: Message = { + id: Date.now(), + content: `Uploaded files: ${fileNames}`, + role: 'user', + } + + setMessages((prevMessages) => [...prevMessages, fileMessage]) + scrollToBottom() + + const formData = new FormData() + Array.from(files).forEach((file) => { + formData.append('file', file) + }) + + setIsLoading(true) + + try { + await axios.post('http://192.168.110.223:8000/upload', formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }) - const handleFileUpload = (e: ChangeEvent) => { - const files = e.target.files - if (files) { - const fileNames = Array.from(files).map(file => file.name).join(', ') - const fileMessage: Message = { - id: Date.now(), - content: `Uploaded files: ${fileNames}`, - role: 'user' - } - setMessages(prevMessages => [...prevMessages, fileMessage]) + const successMessage: Message = { + id: Date.now() + 1, + content: `Files uploaded successfully.`, + role: 'assistant', } + setMessages((prevMessages) => [...prevMessages, successMessage]) + } catch (error) { + const errorMessage: Message = { + id: Date.now() + 2, + content: `Failed to upload files. Please try again.`, + role: 'assistant', + } + setMessages((prevMessages) => [...prevMessages, errorMessage]) + console.error('File upload failed:', error) + } finally { + setIsLoading(false) + scrollToBottom() + } } + } - if (!isOpen) return null + if (!isOpen) return null - return ( - + return ( + + {isOpen && ( + +
+
+ + + + + +

Chat Assistant

- +
+
- + +
{messages.map((message) => ( -
- - {message.content} - -
+ ))} + {isLoading && ( +
+
+ +
+
+ )} +
-
- - - - -
+
+ + + + +
-
- ) -} \ No newline at end of file +
+ + )} + + ) +} + diff --git a/admin/src/components/custom/LanguageSelector.tsx b/admin/src/components/custom/LanguageSelector.tsx new file mode 100644 index 0000000..cb39189 --- /dev/null +++ b/admin/src/components/custom/LanguageSelector.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { useTranslation } from '@/context/TranslationContext'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" + +const languages = [ + { code: 'en', name: 'English' }, + { code: 'es', name: 'Spanish' }, + { code: 'fr', name: 'French' }, + { code: 'de', name: 'German' }, + { code: 'it', name: 'Italian' }, + // Add more languages as needed +]; + +export const LanguageSelector: React.FC = () => { + const { setLanguage, currentLanguage } = useTranslation(); + + return ( + + ); +}; + diff --git a/admin/src/components/custom/Layout.tsx b/admin/src/components/custom/Layout.tsx index 00a0d62..c6d6181 100644 --- a/admin/src/components/custom/Layout.tsx +++ b/admin/src/components/custom/Layout.tsx @@ -39,6 +39,7 @@ const pageTitles: PageTitle[] = [ { url: '/master-data/permissions', title: "Manage Roles and Permissions"}, { url: '/master-data/mine', title: "Manage Mines"}, { url: '/master-data/user', title: "Manage Users"}, + { url: '/dgms', title: "DGMS Files"}, ] const Layout = () => { diff --git a/admin/src/components/custom/LoadingDots.tsx b/admin/src/components/custom/LoadingDots.tsx new file mode 100644 index 0000000..3ce1e3e --- /dev/null +++ b/admin/src/components/custom/LoadingDots.tsx @@ -0,0 +1,35 @@ +import React from 'react' +import { motion } from 'framer-motion' + +export const LoadingDots: React.FC = () => { + const dotVariants = { + hidden: { opacity: 0.5, y: 0 }, + visible: { opacity: 1, y: -5 }, + } + + const containerVariants = { + start: { transition: { staggerChildren: 0.2 } }, + end: { transition: { staggerChildren: 0.2 } }, + } + + return ( + + {[0, 1, 2].map((_, i) => ( + + ))} + + ) +} + diff --git a/admin/src/components/custom/MessageBubble.tsx b/admin/src/components/custom/MessageBubble.tsx new file mode 100644 index 0000000..4cd6c42 --- /dev/null +++ b/admin/src/components/custom/MessageBubble.tsx @@ -0,0 +1,42 @@ +import React from 'react' +import { motion } from 'framer-motion' +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' +import { User, Bot } from 'lucide-react' + +interface Message { + id: number + content: string + role: 'user' | 'assistant' +} + +interface MessageBubbleProps { + message: Message +} + +export const MessageBubble: React.FC = ({ message }) => { + const isUser = message.role === 'user' + + return ( + +
+ + + {isUser ? : } + +
+ {message.content} +
+
+
+ ) +} + diff --git a/admin/src/components/custom/Navbars.tsx b/admin/src/components/custom/Navbars.tsx index 25dcc1f..b73c576 100644 --- a/admin/src/components/custom/Navbars.tsx +++ b/admin/src/components/custom/Navbars.tsx @@ -3,7 +3,7 @@ import { Avatar, AvatarImage } from "../ui/avatar"; import { Button } from "../ui/button"; import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover"; import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarHeader, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from "../ui/sidebar" -import { Calendar, FileTextIcon, FolderCog2Icon, ForkliftIcon, Gauge, LanguagesIcon, PickaxeIcon, ReplaceAll, Settings, SunIcon, TriangleAlert, UserRoundCog } from "lucide-react"; +import { Book, Calendar, FileTextIcon, FolderCog2Icon, ForkliftIcon, Gauge, LanguagesIcon, PickaxeIcon, ReplaceAll, Settings, SunIcon, TriangleAlert, UserRoundCog } from "lucide-react"; import { Box } from "lucide-react" import { useTheme } from "./theme"; @@ -59,6 +59,11 @@ const menuItems: MenuItems[] = [ url: '/master-data', title: 'Master Config' }, + { + icon: Book, + url: '/dgms', + title: 'DGMS Guidelines' + } ] const SideNavbar = () => { @@ -103,17 +108,17 @@ const SideNavbar = () => { + {/* + + */} -
- - -
+
diff --git a/admin/src/components/custom/Translate.tsx b/admin/src/components/custom/Translate.tsx new file mode 100644 index 0000000..eb37dc6 --- /dev/null +++ b/admin/src/components/custom/Translate.tsx @@ -0,0 +1,19 @@ +import React, { useEffect, useState } from 'react'; +import { useTranslation } from '@/context/TranslationContext'; + +export const Translate: React.FC<{ children: string }> = ({ children }) => { + const { translate, currentLanguage } = useTranslation(); + const [translatedText, setTranslatedText] = useState(children); + + useEffect(() => { + const translateText = async () => { + const translated = await translate(children); + setTranslatedText(translated); + }; + + translateText(); + }, [children, currentLanguage, translate]); + + return <>{translatedText}; +}; + diff --git a/admin/src/components/ui/toast.tsx b/admin/src/components/ui/toast.tsx new file mode 100644 index 0000000..2ddb7c5 --- /dev/null +++ b/admin/src/components/ui/toast.tsx @@ -0,0 +1,127 @@ +import * as React from "react" +import * as ToastPrimitives from "@radix-ui/react-toast" +import { cva, type VariantProps } from "class-variance-authority" +import { X } from "lucide-react" + +import { cn } from "@/lib/utils" + +const ToastProvider = ToastPrimitives.Provider + +const ToastViewport = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ToastViewport.displayName = ToastPrimitives.Viewport.displayName + +const toastVariants = cva( + "group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", + { + variants: { + variant: { + default: "border bg-background text-foreground", + destructive: + "destructive group border-destructive bg-destructive text-destructive-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +const Toast = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, variant, ...props }, ref) => { + return ( + + ) +}) +Toast.displayName = ToastPrimitives.Root.displayName + +const ToastAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ToastAction.displayName = ToastPrimitives.Action.displayName + +const ToastClose = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +ToastClose.displayName = ToastPrimitives.Close.displayName + +const ToastTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ToastTitle.displayName = ToastPrimitives.Title.displayName + +const ToastDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +ToastDescription.displayName = ToastPrimitives.Description.displayName + +type ToastProps = React.ComponentPropsWithoutRef + +type ToastActionElement = React.ReactElement + +export { + type ToastProps, + type ToastActionElement, + ToastProvider, + ToastViewport, + Toast, + ToastTitle, + ToastDescription, + ToastClose, + ToastAction, +} diff --git a/admin/src/components/ui/toaster.tsx b/admin/src/components/ui/toaster.tsx new file mode 100644 index 0000000..6c67edf --- /dev/null +++ b/admin/src/components/ui/toaster.tsx @@ -0,0 +1,33 @@ +import { useToast } from "@/hooks/use-toast" +import { + Toast, + ToastClose, + ToastDescription, + ToastProvider, + ToastTitle, + ToastViewport, +} from "@/components/ui/toast" + +export function Toaster() { + const { toasts } = useToast() + + return ( + + {toasts.map(function ({ id, title, description, action, ...props }) { + return ( + +
+ {title && {title}} + {description && ( + {description} + )} +
+ {action} + +
+ ) + })} + +
+ ) +} diff --git a/admin/src/context/TranslationContext.ts b/admin/src/context/TranslationContext.ts new file mode 100644 index 0000000..e69de29 diff --git a/admin/src/hooks/use-toast.ts b/admin/src/hooks/use-toast.ts new file mode 100644 index 0000000..02e111d --- /dev/null +++ b/admin/src/hooks/use-toast.ts @@ -0,0 +1,194 @@ +"use client" + +// Inspired by react-hot-toast library +import * as React from "react" + +import type { + ToastActionElement, + ToastProps, +} from "@/components/ui/toast" + +const TOAST_LIMIT = 1 +const TOAST_REMOVE_DELAY = 1000000 + +type ToasterToast = ToastProps & { + id: string + title?: React.ReactNode + description?: React.ReactNode + action?: ToastActionElement +} + +const actionTypes = { + ADD_TOAST: "ADD_TOAST", + UPDATE_TOAST: "UPDATE_TOAST", + DISMISS_TOAST: "DISMISS_TOAST", + REMOVE_TOAST: "REMOVE_TOAST", +} as const + +let count = 0 + +function genId() { + count = (count + 1) % Number.MAX_SAFE_INTEGER + return count.toString() +} + +type ActionType = typeof actionTypes + +type Action = + | { + type: ActionType["ADD_TOAST"] + toast: ToasterToast + } + | { + type: ActionType["UPDATE_TOAST"] + toast: Partial + } + | { + type: ActionType["DISMISS_TOAST"] + toastId?: ToasterToast["id"] + } + | { + type: ActionType["REMOVE_TOAST"] + toastId?: ToasterToast["id"] + } + +interface State { + toasts: ToasterToast[] +} + +const toastTimeouts = new Map>() + +const addToRemoveQueue = (toastId: string) => { + if (toastTimeouts.has(toastId)) { + return + } + + const timeout = setTimeout(() => { + toastTimeouts.delete(toastId) + dispatch({ + type: "REMOVE_TOAST", + toastId: toastId, + }) + }, TOAST_REMOVE_DELAY) + + toastTimeouts.set(toastId, timeout) +} + +export const reducer = (state: State, action: Action): State => { + switch (action.type) { + case "ADD_TOAST": + return { + ...state, + toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), + } + + case "UPDATE_TOAST": + return { + ...state, + toasts: state.toasts.map((t) => + t.id === action.toast.id ? { ...t, ...action.toast } : t + ), + } + + case "DISMISS_TOAST": { + const { toastId } = action + + // ! Side effects ! - This could be extracted into a dismissToast() action, + // but I'll keep it here for simplicity + if (toastId) { + addToRemoveQueue(toastId) + } else { + state.toasts.forEach((toast) => { + addToRemoveQueue(toast.id) + }) + } + + return { + ...state, + toasts: state.toasts.map((t) => + t.id === toastId || toastId === undefined + ? { + ...t, + open: false, + } + : t + ), + } + } + case "REMOVE_TOAST": + if (action.toastId === undefined) { + return { + ...state, + toasts: [], + } + } + return { + ...state, + toasts: state.toasts.filter((t) => t.id !== action.toastId), + } + } +} + +const listeners: Array<(state: State) => void> = [] + +let memoryState: State = { toasts: [] } + +function dispatch(action: Action) { + memoryState = reducer(memoryState, action) + listeners.forEach((listener) => { + listener(memoryState) + }) +} + +type Toast = Omit + +function toast({ ...props }: Toast) { + const id = genId() + + const update = (props: ToasterToast) => + dispatch({ + type: "UPDATE_TOAST", + toast: { ...props, id }, + }) + const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }) + + dispatch({ + type: "ADD_TOAST", + toast: { + ...props, + id, + open: true, + onOpenChange: (open) => { + if (!open) dismiss() + }, + }, + }) + + return { + id: id, + dismiss, + update, + } +} + +function useToast() { + const [state, setState] = React.useState(memoryState) + + React.useEffect(() => { + listeners.push(setState) + return () => { + const index = listeners.indexOf(setState) + if (index > -1) { + listeners.splice(index, 1) + } + } + }, [state]) + + return { + ...state, + toast, + dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), + } +} + +export { useToast, toast } diff --git a/admin/src/pages/Dgms.tsx b/admin/src/pages/Dgms.tsx new file mode 100644 index 0000000..1a080dd --- /dev/null +++ b/admin/src/pages/Dgms.tsx @@ -0,0 +1,226 @@ +import React, { useEffect, useState } from 'react'; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle +} from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow +} from "@/components/ui/table"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger +} from "@/components/ui/dialog"; +import { + FileUp, + Download, + Trash2, + FileText +} from "lucide-react"; +import { Progress } from "@/components/ui/progress"; +import { toast } from 'sonner'; +import axios from 'axios'; + +// Types for DGMS File +interface DgmsFile { + id: number; + name: string; + path: string; + uploadedAt?: Date; +} + +const Dgms: React.FC = () => { + const [files, setFiles] = useState([]); + const [selectedFile, setSelectedFile] = useState(null); + const [uploadProgress, setUploadProgress] = useState(0); + const [books, setBooks] = useState([]); + + const handleFileSelect = (event: React.ChangeEvent) => { + const file = event.target.files?.[0]; + if (file) { + setSelectedFile(file); + } + }; + + const handleFileUpload = async () => { + if (!selectedFile) { + toast({ + title: "Error", + description: "Please select a file first", + variant: "destructive" + }); + return; + } + + const formData = new FormData(); + formData.append('file', selectedFile); + + try { + setUploadProgress(0); + const response = await axios.post('/api/data/dgms', formData) + if (!response.status) { + throw new Error('File upload failed'); + } + + const newFile = response.data; + setFiles(prevFiles => [...prevFiles, newFile]); + + + for (let i = 0; i <= 100; i += 10) { + await new Promise(resolve => setTimeout(resolve, 100)); + setUploadProgress(i); + } + + toast.success("Uploaded successfully!") + + setSelectedFile(null); + } catch (error) { + toast.error("Something went wrong!") + } + }; + + + const handleFileDownload = async (fileId: number) => { + try { + const response = await fetch(`/api/data/dgms/${fileId}/download`); + if (!response.ok) { + throw new Error('Download failed'); + } + const blob = await response.blob(); + const downloadUrl = window.URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = downloadUrl; + link.download = 'downloaded-file'; + document.body.appendChild(link); + link.click(); + link.remove(); + } catch (error) { + toast.error("Something went wrong!") + } + }; + + const handleFileDelete = async (fileId: number) => { + try { + const response = await fetch(`/api/data/dgms/${fileId}`, { + method: 'DELETE' + }); + + if (!response.ok) { + throw new Error('File deletion failed'); + } + + setFiles(prevFiles => prevFiles.filter(file => file.id !== fileId)); + + toast.success("File deleted successfully!") + } catch (error) { + toast.error("Something went wrong!") + } + }; + + useEffect(() => { + const fetchAndSetBooks = async () => { + try { + const res = await axios.get('/api/data/dgms'); + if(res.status === 200) { + setFiles(res.data); + } + } catch (error) { + console.log(error); + } + } + + fetchAndSetBooks(); + }, []) + + return ( +
+ + + DGMS File Management + + Upload, manage, and download your DGMS files + + + + {/* File Upload Section */} +
+ + +
+ + {/* Upload Progress */} + {uploadProgress > 0 && ( +
+ +
+ )} + + {/* File List */} + + + + + File Name + Actions + + + + {files.map((file) => ( + + + + {file.name} + + +
+ + +
+
+
+ ))} +
+
+
+
+
+
+ ); +} + +export default Dgms; \ No newline at end of file diff --git a/admin/src/utils/translationContext.ts b/admin/src/utils/translationContext.ts new file mode 100644 index 0000000..5664efc --- /dev/null +++ b/admin/src/utils/translationContext.ts @@ -0,0 +1,153 @@ +import React, { createContext, useState, useContext, useEffect, ComponentType } from 'react'; +import { Button } from '@/components/ui/button'; + +// Define types for translation context +interface TranslationContextType { + language: string; + setLanguage: (lang: string) => void; + translate: (text: string) => Promise; +} + +// Create a translation context with type +const TranslationContext = createContext({ + language: 'en', + setLanguage: () => {}, + translate: async (text) => text +}); + +// Typed Higher-Order Component for auto-translating text +function withAutoTranslation

( + WrappedComponent: ComponentType

+): ComponentType

{ + return function TranslatedComponent(props: P) { + const { translate } = useContext(TranslationContext); + + // Recursively translate props + const translateProps = async (obj: any): Promise => { + if (typeof obj === 'string') { + return await translate(obj); + } + + if (obj && typeof obj === 'object') { + const translatedObj: any = {}; + for (const key of Object.keys(obj)) { + translatedObj[key] = await translateProps(obj[key]); + } + return translatedObj; + } + + return obj; + }; + + // Use state to handle async translations + const [translatedProps, setTranslatedProps] = useState

(props); + + useEffect(() => { + const translateAsyncProps = async () => { + const translated = await translateProps(props); + setTranslatedProps(translated); + }; + + translateAsyncProps(); + }, [props]); + + return ; + }; +} + +// Translation Provider Component +const TranslationProvider: React.FC<{children: React.ReactNode}> = ({ children }) => { + const [language, setLanguage] = useState('en'); + const [translations, setTranslations] = useState>({}); + + // Function to translate text using Google Translate API + const translateText = async (text: string, targetLanguage: string): Promise => { + // Check if translation exists in cache first + const cacheKey = `${text}_${targetLanguage}`; + const cachedTranslation = translations[cacheKey]; + + if (cachedTranslation) { + return cachedTranslation; + } + + try { + const response = await fetch('https://translation.googleapis.com/language/translate/v2', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer YOUR_GOOGLE_TRANSLATE_API_KEY' + }, + body: JSON.stringify({ + q: text, + target: targetLanguage + }) + }); + + const data = await response.json(); + const translatedText = data.data.translations[0].translatedText; + + // Cache the translation + setTranslations(prev => ({ + ...prev, + [cacheKey]: translatedText + })); + + return translatedText; + } catch (error) { + console.error('Translation error:', error); + return text; + } + }; + + // Memoized translation function + const translate = React.useCallback(async (text: string): Promise => { + if (language === 'en') return text; + return await translateText(text, language); + }, [language]); + + return ( + + {children} + + ); +}; + +// Example Interface for Component Props +interface WelcomeProps { + title: string; + description: string; +} + +// Example Component using Auto-Translation +const WelcomeComponent = withAutoTranslation(({ title, description }) => { + return ( +

+

{title}

+

{description}

+
+ ); +}); + +// Language Selector Component +const LanguageSelector: React.FC = () => { + const { language, setLanguage } = useContext(TranslationContext); + + return ( +
+ +
+ ); +}; diff --git a/docker-compose.yml b/docker-compose.yml index 5a7a567..f2a9664 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -66,52 +66,52 @@ services: # - POSTGRESQL_MASTER_PORT_NUMBER=5432 # - ALLOW_EMPTY_PASSWORD=yes - timescale-master: - image: timescale/timescaledb:latest-pg14 - restart: always - ports: - - '5434:5432' - volumes: - - timescaledb_master_data:/var/lib/postgresql/data - - ./db.sql:/docker-entrypoint-initdb.d/db.sql - environment: - - POSTGRES_USER=postgres - - POSTGRES_PASSWORD=postgres - - POSTGRES_DB=development_database - - TIMESCALEDB_TELEMETRY=off - - PGDATA=/var/lib/postgresql/data/pgdata - command: > - -c wal_level=replica - -c max_wal_senders=5 - -c max_replication_slots=5 - -c hot_standby=on + # timescale-master: + # image: timescale/timescaledb:latest-pg14 + # restart: always + # ports: + # - '5434:5432' + # volumes: + # - timescaledb_master_data:/var/lib/postgresql/data + # - ./db.sql:/docker-entrypoint-initdb.d/db.sql + # environment: + # - POSTGRES_USER=postgres + # - POSTGRES_PASSWORD=postgres + # - POSTGRES_DB=development_database + # - TIMESCALEDB_TELEMETRY=off + # - PGDATA=/var/lib/postgresql/data/pgdata + # command: > + # -c wal_level=replica + # -c max_wal_senders=5 + # -c max_replication_slots=5 + # -c hot_standby=on - zookeeper: - image: bitnami/zookeeper:latest - container_name: zookeeper - environment: - - ALLOW_ANONYMOUS_LOGIN=yes - ports: - - "2181:2181" + # zookeeper: + # image: bitnami/zookeeper:latest + # container_name: zookeeper + # environment: + # - ALLOW_ANONYMOUS_LOGIN=yes + # ports: + # - "2181:2181" - kafka: - image: bitnami/kafka:latest - container_name: kafka - environment: - - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181 - - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT,EXTERNAL1:PLAINTEXT - - KAFKA_CFG_LISTENERS=INTERNAL://0.0.0.0:9092,EXTERNAL://0.0.0.0:9093,EXTERNAL1://0.0.0.0:9094 - - KAFKA_CFG_ADVERTISED_LISTENERS=INTERNAL://kafka:9092,EXTERNAL://192.168.110.53:9093,EXTERNAL1://192.168.110.53:9094 - - KAFKA_CFG_INTER_BROKER_LISTENER_NAME=INTERNAL - - ALLOW_PLAINTEXT_LISTENER=yes - ports: - - "9092:9092" - - "9093:9093" - - "9094:9094" - depends_on: - - zookeeper + # kafka: + # image: bitnami/kafka:latest + # container_name: kafka + # environment: + # - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181 + # - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT,EXTERNAL1:PLAINTEXT + # - KAFKA_CFG_LISTENERS=INTERNAL://0.0.0.0:9092,EXTERNAL://0.0.0.0:9093,EXTERNAL1://0.0.0.0:9094 + # - KAFKA_CFG_ADVERTISED_LISTENERS=INTERNAL://kafka:9092,EXTERNAL://192.168.110.53:9093,EXTERNAL1://192.168.110.53:9094 + # - KAFKA_CFG_INTER_BROKER_LISTENER_NAME=INTERNAL + # - ALLOW_PLAINTEXT_LISTENER=yes + # ports: + # - "9092:9092" + # - "9093:9093" + # - "9094:9094" + # depends_on: + # - zookeeper volumes: postgresql_master_data: driver: local - timescaledb_master_data: \ No newline at end of file + # timescaledb_master_data: \ No newline at end of file diff --git a/server2/db/prisma/migrations/20241212073635_/migration.sql b/server2/db/prisma/migrations/20241212073635_/migration.sql new file mode 100644 index 0000000..69e55f7 --- /dev/null +++ b/server2/db/prisma/migrations/20241212073635_/migration.sql @@ -0,0 +1,17 @@ +/* + Warnings: + + - Added the required column `apiKey` to the `software_integrations` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "software_integrations" ADD COLUMN "apiKey" TEXT NOT NULL; + +-- CreateTable +CREATE TABLE "DgmsFiles" ( + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + "path" TEXT NOT NULL, + + CONSTRAINT "DgmsFiles_pkey" PRIMARY KEY ("id") +); diff --git a/server2/db/prisma/schema.prisma b/server2/db/prisma/schema.prisma index 5ca1411..6902c11 100644 --- a/server2/db/prisma/schema.prisma +++ b/server2/db/prisma/schema.prisma @@ -454,3 +454,9 @@ model SoftwareIntegration { @@map("software_integrations") } + +model DgmsFiles { + id Int @id @default(autoincrement()) + name String + path String +} \ No newline at end of file diff --git a/server2/package-lock.json b/server2/package-lock.json index 05b2321..4e340f2 100644 --- a/server2/package-lock.json +++ b/server2/package-lock.json @@ -18,6 +18,7 @@ "express": "^4.21.2", "jsonwebtoken": "^9.0.2", "morgan": "^1.10.0", + "multer": "^1.4.5-lts.1", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", "zod": "^3.23.8" @@ -27,6 +28,7 @@ "@types/express": "^5.0.0", "@types/jsonwebtoken": "^9.0.7", "@types/morgan": "^1.9.9", + "@types/multer": "^1.4.12", "@types/node": "^22.10.1", "@types/swagger-jsdoc": "^6.0.4", "@types/swagger-ui-express": "^4.1.7", @@ -709,6 +711,16 @@ "@types/node": "*" } }, + "node_modules/@types/multer": { + "version": "1.4.12", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.12.tgz", + "integrity": "sha512-pQ2hoqvXiJt2FP9WQVLPRO+AmiIm/ZYkavPlIQnx282u4ZrVdztx0pkh3jjpQt0Kz+YI0YhSG264y08UJKoUQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/node": { "version": "22.10.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", @@ -826,6 +838,12 @@ "node": ">= 8" } }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT" + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -956,6 +974,23 @@ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", "license": "BSD-3-Clause" }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -1030,6 +1065,21 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "license": "MIT" }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -1066,6 +1116,12 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "license": "MIT" }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -1681,6 +1737,12 @@ "node": ">=0.12.0" } }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -1874,6 +1936,27 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/morgan": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", @@ -1923,6 +2006,24 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/multer": { + "version": "1.4.5-lts.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", + "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -2083,6 +2184,12 @@ "fsevents": "2.3.3" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -2142,6 +2249,27 @@ "node": ">= 0.8" } }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -2328,6 +2456,29 @@ "node": ">= 0.8" } }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -2505,6 +2656,12 @@ "node": ">= 0.6" } }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, "node_modules/typescript": { "version": "5.7.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", @@ -2540,6 +2697,12 @@ "node": ">= 0.8" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -2580,6 +2743,15 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/yaml": { "version": "2.0.0-1", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz", diff --git a/server2/package.json b/server2/package.json index 9d8ae5f..d034cf8 100644 --- a/server2/package.json +++ b/server2/package.json @@ -16,6 +16,7 @@ "@types/express": "^5.0.0", "@types/jsonwebtoken": "^9.0.7", "@types/morgan": "^1.9.9", + "@types/multer": "^1.4.12", "@types/node": "^22.10.1", "@types/swagger-jsdoc": "^6.0.4", "@types/swagger-ui-express": "^4.1.7", @@ -35,6 +36,7 @@ "express": "^4.21.2", "jsonwebtoken": "^9.0.2", "morgan": "^1.10.0", + "multer": "^1.4.5-lts.1", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", "zod": "^3.23.8" diff --git a/server2/src/libs/dgms/dgms.ts b/server2/src/libs/dgms/dgms.ts new file mode 100644 index 0000000..b826e1c --- /dev/null +++ b/server2/src/libs/dgms/dgms.ts @@ -0,0 +1,115 @@ +import { Request, Response, NextFunction } from 'express' +import multer from "multer"; +import path from 'path' +import fs from 'fs/promises' +import { prisma } from '../../utils/prisma'; + +// Configure multer for file upload +const storage = multer.diskStorage({ + destination: (req, file, cb) => { + const uploadPath = path.join(__dirname, '../uploads/dgms') + // Ensure upload directory exists + fs.mkdir(uploadPath, { recursive: true }) + .then(() => cb(null, uploadPath)) + .catch((err) => cb(err, uploadPath)) + }, + filename: (req, file, cb) => { + const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9) + cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname)) + } +}) + +const upload = multer({ + storage: storage, + limits: { fileSize: 50 * 1024 * 1024 }, // 50MB file size limit + fileFilter: (req, file, cb) => { + const allowedFileTypes = /dgms|txt|pdf|docx?/i + const extname = allowedFileTypes.test(path.extname(file.originalname).toLowerCase()) + const mimetype = allowedFileTypes.test(file.mimetype) + + if (extname && mimetype) { + return cb(null, true) + } else { + cb(new Error('Invalid file type. Only DGMS, TXT, PDF, and DOCX files are allowed.')) + } + } +}) + +export const createDgmsFile = [ + upload.single('file'), + async (req: Request, res: Response, next: NextFunction) => { + try { + if (!req.file) { + return res.status(400).json({ error: 'No file uploaded' }); + } + + const { originalname, filename, path: filePath } = req.file; + + const dgmsFile = await prisma.dgmsFiles.create({ + data: { + name: originalname, + path: filePath, + }, + }); + + res.status(201).json({ + message: 'File uploaded successfully', + file: dgmsFile, + }); + } catch (error) { + next(error); + } + }, + ]; + + +// GET: Retrieve all DGMS files +export const getAllDgmsFiles = async (req: Request, res: Response, next: NextFunction) => { + try { + const files = await prisma.dgmsFiles.findMany() + res.json(files) + } catch (error) { + next(error) + } +} + +// GET: Retrieve a specific DGMS file by ID +export const getDgmsFileById = async (req: Request, res: Response, next: NextFunction) => { + try { + const { id } = req.params + const file = await prisma.dgmsFiles.findUnique({ + where: { id: parseInt(id) } + }) + + if (!file) { + return res.status(404).json({ error: 'File not found' }) + } + + res.json(file) + } catch (error) { + next(error) + } +} + +// Serve static files +export const serveDgmsFile = async (req: Request, res: Response, next: NextFunction) => { + try { + const { id } = req.params + const file = await prisma.dgmsFiles.findUnique({ + where: { id: parseInt(id) } + }) + + if (!file) { + return res.status(404).json({ error: 'File not found' }) + } + + res.download(file.path, file.name, (err) => { + if (err) { + // Handle download error + res.status(500).json({ error: 'Could not download file' }) + } + }) + } catch (error) { + next(error) + } +} \ No newline at end of file diff --git a/server2/src/libs/uploads/dgms/file-1733989887141-999734075.pdf b/server2/src/libs/uploads/dgms/file-1733989887141-999734075.pdf new file mode 100644 index 0000000..0d73a0f Binary files /dev/null and b/server2/src/libs/uploads/dgms/file-1733989887141-999734075.pdf differ diff --git a/server2/src/router/dgmsRouter.ts b/server2/src/router/dgmsRouter.ts new file mode 100644 index 0000000..82f32bc --- /dev/null +++ b/server2/src/router/dgmsRouter.ts @@ -0,0 +1,22 @@ +// @ts-nocheck + +import { Router } from "express"; +import { + createDgmsFile, + getAllDgmsFiles, + getDgmsFileById, + serveDgmsFile +} from "../libs/dgms/dgms"; +import { asyncHandler } from "../utils/asyncHandler"; + +const dgmsRouter = Router(); + +dgmsRouter.post('/', createDgmsFile); + +dgmsRouter.get('/', asyncHandler(getAllDgmsFiles)); + +dgmsRouter.get('/:id', asyncHandler(getDgmsFileById)); + +dgmsRouter.get('/:id/download', asyncHandler(serveDgmsFile)); + +export { dgmsRouter }; \ No newline at end of file diff --git a/server2/src/router/router.ts b/server2/src/router/router.ts index b7ae8f8..0cb5c8d 100644 --- a/server2/src/router/router.ts +++ b/server2/src/router/router.ts @@ -20,6 +20,7 @@ import { shiftTemplateRouter } from "./shiftTemplateRouter"; import { smpRouter } from "./smpRouter"; import { hazardRouter } from "./hazardRouter"; import { riskAssesmentResponseRouter } from "./riskAssesmentResponseRouter"; +import { dgmsRouter } from "./dgmsRouter"; const router = Router(); @@ -72,4 +73,6 @@ router.use('/riskresponse', riskAssesmentResponseRouter); router.use('/hazard', hazardRouter); +router.use('/dgms', dgmsRouter); + export { router };