From 88b7ef89bcaa07ff52a1f1dfd9505ebf2b338a0a Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 21 Dec 2023 18:11:06 +0100 Subject: [PATCH 001/565] connect local react-fast-pdf --- package-lock.json | 429 +++++++++++++++++++++++++++++--- package.json | 1 + src/components/PDFView/index.js | 266 ++------------------ src/styles/index.ts | 19 -- 4 files changed, 426 insertions(+), 289 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1c2ae325575d..cf30d9f6fb3f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -69,6 +69,7 @@ "react-content-loader": "^6.1.0", "react-dom": "18.1.0", "react-error-boundary": "^4.0.11", + "react-fast-pdf": "git+https://github.com/rezkiy37/react-fast-pdf#ecb6779e8442d433ca421b7e05acedaa0725a30a", "react-map-gl": "^7.1.3", "react-native": "0.72.4", "react-native-android-location-enabler": "^1.2.2", @@ -5380,6 +5381,50 @@ "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-2.0.1.tgz", "integrity": "sha512-HP6XvfNIzfoMVfyGjBckjiAOQK9WfX0ywdLubuPMPv+Vqf5fj0uCbgBQYpiqcWZT6cbyyRnTSXDheT1ugvF6UQ==" }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "optional": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "optional": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@mapbox/point-geometry": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz", @@ -20854,6 +20899,12 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "optional": true + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -20964,7 +21015,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "debug": "4" @@ -21416,7 +21467,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true + "devOptional": true }, "node_modules/are-docs-informative": { "version": "0.0.2", @@ -21431,7 +21482,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "delegates": "^1.0.0", @@ -21445,7 +21496,7 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, + "devOptional": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -24072,6 +24123,21 @@ } ] }, + "node_modules/canvas": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz", + "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.0", + "nan": "^2.17.0", + "simple-get": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/canvas-size": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/canvas-size/-/canvas-size-1.2.6.tgz", @@ -24746,7 +24812,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true, + "devOptional": true, "license": "ISC", "bin": { "color-support": "bin.js" @@ -25081,7 +25147,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/constants-browserify": { @@ -26630,7 +26696,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/denodeify": { @@ -26706,6 +26772,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -31212,7 +31287,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "aproba": "^1.0.3 || ^2.0.0", @@ -31729,7 +31804,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/has-value": { @@ -32467,7 +32542,7 @@ "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==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "agent-base": "6", @@ -37782,8 +37857,9 @@ } }, "node_modules/make-cancellable-promise": { - "version": "1.1.0", - "license": "MIT", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/make-cancellable-promise/-/make-cancellable-promise-1.3.2.tgz", + "integrity": "sha512-GCXh3bq/WuMbS+Ky4JBPW1hYTOU+znU+Q5m9Pu+pI8EoUqIHk9+tviOKC6/qhHh8C4/As3tzJ69IF32kdz85ww==", "funding": { "url": "https://github.com/wojtekmaj/make-cancellable-promise?sponsor=1" } @@ -41068,7 +41144,6 @@ "version": "2.17.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==", - "dev": true, "optional": true }, "node_modules/nanoid": { @@ -41330,6 +41405,21 @@ "url": "https://github.com/sponsors/antelle" } }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "optional": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -41412,7 +41502,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "are-we-there-yet": "^2.0.0", @@ -42615,6 +42705,15 @@ "node": ">=8" } }, + "node_modules/path2d-polyfill": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path2d-polyfill/-/path2d-polyfill-2.0.1.tgz", + "integrity": "sha512-ad/3bsalbbWhmBo0D6FZ4RNMwsLsPpL6gnvhuSaU5Vm7b06Kr5ubSltQQ0T7YKsiJQO+g22zJ4dJKNTXIyOXtA==", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/pause-stream": { "version": "0.0.11", "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", @@ -44004,6 +44103,74 @@ "react": ">=16.13.1" } }, + "node_modules/react-fast-pdf": { + "version": "1.0.3", + "resolved": "git+ssh://git@github.com/rezkiy37/react-fast-pdf.git#ecb6779e8442d433ca421b7e05acedaa0725a30a", + "integrity": "sha512-kmdkx9Oh6gvMyt6Jiv+xSi5fQEybxXFD6l+woscs6Lt6m3nLweYP1KUSUn03+ocpVzLKIbLiMbW8D/D9R18AJA==", + "license": "MIT", + "dependencies": { + "react-pdf": "^7.3.3", + "react-window": "^1.8.9" + }, + "engines": { + "node": "20.9.0", + "npm": "10.1.0" + }, + "peerDependencies": { + "lodash": "4.x", + "prop-types": "15.x", + "react": "18.x", + "react-dom": "18.x" + } + }, + "node_modules/react-fast-pdf/node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "engines": { + "node": ">=6" + } + }, + "node_modules/react-fast-pdf/node_modules/pdfjs-dist": { + "version": "3.11.174", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-3.11.174.tgz", + "integrity": "sha512-TdTZPf1trZ8/UFu5Cx/GXB7GZM30LT+wWUNfsi6Bq8ePLnb+woNKtDymI2mxZYBpMbonNFqKmiz684DIfnd8dA==", + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "canvas": "^2.11.2", + "path2d-polyfill": "^2.0.1" + } + }, + "node_modules/react-fast-pdf/node_modules/react-pdf": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-7.6.0.tgz", + "integrity": "sha512-b2/8V6xhe5pn4Y01ELKTQZ+RhdZl1KpSAMGbN+HCZ/kwhYTIc4Pn5ctz1wRQUu1gOJbIEG4CcjMD9vTCzNdwjw==", + "dependencies": { + "clsx": "^2.0.0", + "make-cancellable-promise": "^1.3.1", + "make-event-props": "^1.6.0", + "merge-refs": "^1.2.1", + "pdfjs-dist": "3.11.174", + "prop-types": "^15.6.2", + "tiny-invariant": "^1.0.0", + "tiny-warning": "^1.0.0" + }, + "funding": { + "url": "https://github.com/wojtekmaj/react-pdf?sponsor=1" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-freeze": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/react-freeze/-/react-freeze-1.0.3.tgz", @@ -47571,6 +47738,61 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "license": "ISC" }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true + }, + "node_modules/simple-get": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "optional": true, + "dependencies": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-get/node_modules/decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "optional": true, + "dependencies": { + "mimic-response": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/simple-get/node_modules/mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "optional": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/simple-git": { "version": "3.19.0", "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.19.0.tgz", @@ -52409,7 +52631,7 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" @@ -56611,6 +56833,42 @@ "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-2.0.1.tgz", "integrity": "sha512-HP6XvfNIzfoMVfyGjBckjiAOQK9WfX0ywdLubuPMPv+Vqf5fj0uCbgBQYpiqcWZT6cbyyRnTSXDheT1ugvF6UQ==" }, + "@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "optional": true, + "requires": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "dependencies": { + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "optional": true, + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "optional": true + } + } + } + } + }, "@mapbox/point-geometry": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz", @@ -67822,6 +68080,12 @@ "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", "dev": true }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "optional": true + }, "abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -67896,7 +68160,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, + "devOptional": true, "requires": { "debug": "4" } @@ -68235,7 +68499,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true + "devOptional": true }, "are-docs-informative": { "version": "0.0.2", @@ -68247,7 +68511,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "dev": true, + "devOptional": true, "requires": { "delegates": "^1.0.0", "readable-stream": "^3.6.0" @@ -68257,7 +68521,7 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, + "devOptional": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -70191,6 +70455,17 @@ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001505.tgz", "integrity": "sha512-jaAOR5zVtxHfL0NjZyflVTtXm3D3J9P15zSJ7HmQF8dSKGA6tqzQq+0ZI3xkjyQj46I4/M0K2GbMpcAFOcbr3A==" }, + "canvas": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz", + "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", + "optional": true, + "requires": { + "@mapbox/node-pre-gyp": "^1.0.0", + "nan": "^2.17.0", + "simple-get": "^3.0.3" + } + }, "canvas-size": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/canvas-size/-/canvas-size-1.2.6.tgz", @@ -70668,7 +70943,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true + "devOptional": true }, "colorette": { "version": "1.4.0", @@ -70910,7 +71185,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "dev": true + "devOptional": true }, "constants-browserify": { "version": "1.0.0", @@ -72010,7 +72285,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "dev": true + "devOptional": true }, "denodeify": { "version": "1.2.1", @@ -72065,6 +72340,12 @@ "repeat-string": "^1.5.4" } }, + "detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "optional": true + }, "detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -75344,7 +75625,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", - "dev": true, + "devOptional": true, "requires": { "aproba": "^1.0.3 || ^2.0.0", "color-support": "^1.1.2", @@ -75694,7 +75975,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "dev": true + "devOptional": true }, "has-value": { "version": "1.0.0", @@ -76234,7 +76515,7 @@ "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==", - "dev": true, + "devOptional": true, "requires": { "agent-base": "6", "debug": "4" @@ -79943,7 +80224,9 @@ } }, "make-cancellable-promise": { - "version": "1.1.0" + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/make-cancellable-promise/-/make-cancellable-promise-1.3.2.tgz", + "integrity": "sha512-GCXh3bq/WuMbS+Ky4JBPW1hYTOU+znU+Q5m9Pu+pI8EoUqIHk9+tviOKC6/qhHh8C4/As3tzJ69IF32kdz85ww==" }, "make-dir": { "version": "2.1.0", @@ -82337,7 +82620,6 @@ "version": "2.17.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==", - "dev": true, "optional": true }, "nanoid": { @@ -82537,6 +82819,15 @@ "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==" }, + "nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "optional": true, + "requires": { + "abbrev": "1" + } + }, "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -82594,7 +82885,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "dev": true, + "devOptional": true, "requires": { "are-we-there-yet": "^2.0.0", "console-control-strings": "^1.1.0", @@ -83434,6 +83725,12 @@ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true }, + "path2d-polyfill": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path2d-polyfill/-/path2d-polyfill-2.0.1.tgz", + "integrity": "sha512-ad/3bsalbbWhmBo0D6FZ4RNMwsLsPpL6gnvhuSaU5Vm7b06Kr5ubSltQQ0T7YKsiJQO+g22zJ4dJKNTXIyOXtA==", + "optional": true + }, "pause-stream": { "version": "0.0.11", "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", @@ -84415,6 +84712,46 @@ "@babel/runtime": "^7.12.5" } }, + "react-fast-pdf": { + "version": "git+ssh://git@github.com/rezkiy37/react-fast-pdf.git#ecb6779e8442d433ca421b7e05acedaa0725a30a", + "integrity": "sha512-kmdkx9Oh6gvMyt6Jiv+xSi5fQEybxXFD6l+woscs6Lt6m3nLweYP1KUSUn03+ocpVzLKIbLiMbW8D/D9R18AJA==", + "from": "react-fast-pdf@git+https://github.com/rezkiy37/react-fast-pdf#ecb6779e8442d433ca421b7e05acedaa0725a30a", + "requires": { + "react-pdf": "^7.3.3", + "react-window": "^1.8.9" + }, + "dependencies": { + "clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==" + }, + "pdfjs-dist": { + "version": "3.11.174", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-3.11.174.tgz", + "integrity": "sha512-TdTZPf1trZ8/UFu5Cx/GXB7GZM30LT+wWUNfsi6Bq8ePLnb+woNKtDymI2mxZYBpMbonNFqKmiz684DIfnd8dA==", + "requires": { + "canvas": "^2.11.2", + "path2d-polyfill": "^2.0.1" + } + }, + "react-pdf": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-7.6.0.tgz", + "integrity": "sha512-b2/8V6xhe5pn4Y01ELKTQZ+RhdZl1KpSAMGbN+HCZ/kwhYTIc4Pn5ctz1wRQUu1gOJbIEG4CcjMD9vTCzNdwjw==", + "requires": { + "clsx": "^2.0.0", + "make-cancellable-promise": "^1.3.1", + "make-event-props": "^1.6.0", + "merge-refs": "^1.2.1", + "pdfjs-dist": "3.11.174", + "prop-types": "^15.6.2", + "tiny-invariant": "^1.0.0", + "tiny-warning": "^1.0.0" + } + } + } + }, "react-freeze": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/react-freeze/-/react-freeze-1.0.3.tgz", @@ -86952,6 +87289,40 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, + "simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "optional": true + }, + "simple-get": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "optional": true, + "requires": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + }, + "dependencies": { + "decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "optional": true, + "requires": { + "mimic-response": "^2.0.0" + } + }, + "mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "optional": true + } + } + }, "simple-git": { "version": "3.19.0", "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.19.0.tgz", @@ -90443,7 +90814,7 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dev": true, + "devOptional": true, "requires": { "string-width": "^1.0.2 || 2 || 3 || 4" } diff --git a/package.json b/package.json index 7281ab12cfa7..cde7a9460002 100644 --- a/package.json +++ b/package.json @@ -117,6 +117,7 @@ "react-content-loader": "^6.1.0", "react-dom": "18.1.0", "react-error-boundary": "^4.0.11", + "react-fast-pdf": "git+https://github.com/rezkiy37/react-fast-pdf#ecb6779e8442d433ca421b7e05acedaa0725a30a", "react-map-gl": "^7.1.3", "react-native": "0.72.4", "react-native-android-location-enabler": "^1.2.2", diff --git a/src/components/PDFView/index.js b/src/components/PDFView/index.js index e18c52b06972..7f74b919469c 100644 --- a/src/components/PDFView/index.js +++ b/src/components/PDFView/index.js @@ -1,10 +1,8 @@ import 'core-js/features/array/at'; -import pdfWorkerSource from 'pdfjs-dist/legacy/build/pdf.worker'; import React, {Component} from 'react'; +import {PDFPreviewer} from 'react-fast-pdf'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import {Document, Page, pdfjs} from 'react-pdf/dist/esm/entry.webpack'; -import {VariableSizeList as List} from 'react-window'; import _ from 'underscore'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; @@ -13,7 +11,6 @@ import withLocalize from '@components/withLocalize'; import withThemeStyles from '@components/withThemeStyles'; import withWindowDimensions from '@components/withWindowDimensions'; import compose from '@libs/compose'; -import Log from '@libs/Log'; import variables from '@styles/variables'; import * as CanvasSize from '@userActions/CanvasSize'; import CONST from '@src/CONST'; @@ -21,41 +18,15 @@ import ONYXKEYS from '@src/ONYXKEYS'; import PDFPasswordForm from './PDFPasswordForm'; import * as pdfViewPropTypes from './pdfViewPropTypes'; -/** - * Each page has a default border. The app should take this size into account - * when calculates the page width and height. - */ -const PAGE_BORDER = 9; -/** - * Pages should be more narrow than the container on large screens. The app should take this size into account - * when calculates the page width. - */ -const LARGE_SCREEN_SIDE_SPACING = 40; - class PDFView extends Component { constructor(props) { super(props); this.state = { - numPages: null, - pageViewports: [], - containerWidth: props.windowWidth, - containerHeight: props.windowHeight, - shouldRequestPassword: false, - isPasswordInvalid: false, isKeyboardOpen: false, }; - this.onDocumentLoadSuccess = this.onDocumentLoadSuccess.bind(this); - this.initiatePasswordChallenge = this.initiatePasswordChallenge.bind(this); - this.attemptPDFLoad = this.attemptPDFLoad.bind(this); this.toggleKeyboardOnSmallScreens = this.toggleKeyboardOnSmallScreens.bind(this); - this.calculatePageHeight = this.calculatePageHeight.bind(this); - this.calculatePageWidth = this.calculatePageWidth.bind(this); - this.renderPage = this.renderPage.bind(this); this.getDevicePixelRatio = _.memoize(this.getDevicePixelRatio); - this.setListAttributes = this.setListAttributes.bind(this); - const workerBlob = new Blob([pdfWorkerSource], {type: 'text/javascript'}); - pdfjs.GlobalWorkerOptions.workerSrc = URL.createObjectURL(workerBlob); this.retrieveCanvasLimits(); } @@ -73,136 +44,6 @@ class PDFView extends Component { } } - /** - * Upon successful document load, combine an array of page viewports, - * set the number of pages on PDF, - * hide/reset PDF password form, and notify parent component that - * user input is no longer required. - * - * @param {Object} pdf - The PDF file instance - * @param {Number} pdf.numPages - Number of pages of the PDF file - * @param {Function} pdf.getPage - A method to get page by its number. It requires to have the context. It should be the pdf itself. - * @memberof PDFView - */ - onDocumentLoadSuccess(pdf) { - const {numPages} = pdf; - - Promise.all( - _.times(numPages, (index) => { - const pageNumber = index + 1; - - return pdf.getPage(pageNumber).then((page) => page.getViewport({scale: 1})); - }), - ).then((pageViewports) => { - this.setState({ - pageViewports, - numPages, - shouldRequestPassword: false, - isPasswordInvalid: false, - }); - }); - } - - /** - * Sets attributes to list container. - * It unblocks a default scroll by keyboard of browsers. - * @param {Object|undefined} ref - */ - setListAttributes(ref) { - if (!ref) { - return; - } - - // Useful for elements that should not be navigated to directly using the "Tab" key, - // but need to have keyboard focus set to them. - // eslint-disable-next-line no-param-reassign - ref.tabIndex = -1; - } - - /** - * Calculate the devicePixelRatio the page should be rendered with - * Each platform has a different default devicePixelRatio and different canvas limits, we need to verify that - * with the default devicePixelRatio it will be able to diplay the pdf correctly, if not we must change the devicePixelRatio. - * @param {Number} width of the page - * @param {Number} height of the page - * @returns {Number} devicePixelRatio for this page on this platform - */ - getDevicePixelRatio(width, height) { - const nbPixels = width * height; - const ratioHeight = this.props.maxCanvasHeight / height; - const ratioWidth = this.props.maxCanvasWidth / width; - const ratioArea = Math.sqrt(this.props.maxCanvasArea / nbPixels); - const ratio = Math.min(ratioHeight, ratioArea, ratioWidth); - return ratio > window.devicePixelRatio ? undefined : ratio; - } - - /** - * Calculates a proper page height. The method should be called only when there are page viewports. - * It is based on a ratio between the specific page viewport width and provided page width. - * Also, the app should take into account the page borders. - * @param {Number} pageIndex - * @returns {Number} - */ - calculatePageHeight(pageIndex) { - if (this.state.pageViewports.length === 0) { - Log.warn('Dev error: calculatePageHeight() in PDFView called too early'); - - return 0; - } - - const pageViewport = this.state.pageViewports[pageIndex]; - const pageWidth = this.calculatePageWidth(); - const scale = pageWidth / pageViewport.width; - const actualHeight = pageViewport.height * scale + PAGE_BORDER * 2; - - return actualHeight; - } - - /** - * Calculates a proper page width. - * It depends on a screen size. Also, the app should take into account the page borders. - * @returns {Number} - */ - calculatePageWidth() { - const pdfContainerWidth = this.state.containerWidth; - const pageWidthOnLargeScreen = Math.min(pdfContainerWidth - LARGE_SCREEN_SIDE_SPACING * 2, variables.pdfPageMaxWidth); - const pageWidth = this.props.isSmallScreenWidth ? this.state.containerWidth : pageWidthOnLargeScreen; - - return pageWidth + PAGE_BORDER * 2; - } - - /** - * Initiate password challenge process. The react-pdf/Document - * component calls this handler to indicate that a PDF requires a - * password, or to indicate that a previously provided password was - * invalid. - * - * The PasswordResponses constants used below were copied from react-pdf - * because they're not exported in entry.webpack. - * - * @param {Function} callback Callback used to send password to react-pdf - * @param {Number} reason Reason code for password request - */ - initiatePasswordChallenge(callback, reason) { - this.onPasswordCallback = callback; - - if (reason === CONST.PDF_PASSWORD_FORM.REACT_PDF_PASSWORD_RESPONSES.NEED_PASSWORD) { - this.setState({shouldRequestPassword: true}); - } else if (reason === CONST.PDF_PASSWORD_FORM.REACT_PDF_PASSWORD_RESPONSES.INCORRECT_PASSWORD) { - this.setState({shouldRequestPassword: true, isPasswordInvalid: true}); - } - } - - /** - * Send password to react-pdf via its callback so that it can attempt to load - * the PDF. - * - * @param {String} password Password to send via callback to react-pdf - */ - attemptPDFLoad(password) { - this.onPasswordCallback(password); - } - /** * On small screens notify parent that the keyboard has opened or closed. * @@ -233,92 +74,35 @@ class PDFView extends Component { } } - /** - * Render a specific page based on its index. - * The method includes a wrapper to apply virtualized styles. - * @param {Object} page item object of the List - * @param {Number} page.index index of the page - * @param {Object} page.style virtualized styles - * @returns {JSX.Element} - */ - renderPage({index, style}) { - const pageWidth = this.calculatePageWidth(); - const pageHeight = this.calculatePageHeight(index); - const devicePixelRatio = this.getDevicePixelRatio(pageWidth, pageHeight); - - return ( - - - - ); - } - renderPDFView() { const styles = this.props.themeStyles; - const pageWidth = this.calculatePageWidth(); const outerContainerStyle = [styles.w100, styles.h100, styles.justifyContentCenter, styles.alignItemsCenter]; - // If we're requesting a password then we need to hide - but still render - - // the PDF component. - const pdfContainerStyle = this.state.shouldRequestPassword - ? [styles.PDFView, styles.noSelect, this.props.style, styles.invisible] - : [styles.PDFView, styles.noSelect, this.props.style]; - return ( - - this.setState({containerWidth: width, containerHeight: height})} - > - {this.props.translate('attachmentView.failedToLoadPDF')}} - loading={} - file={this.props.sourceURL} - options={{ - cMapUrl: 'cmaps/', - cMapPacked: true, - }} - externalLinkTarget="_blank" - onLoadSuccess={this.onDocumentLoadSuccess} - onPassword={this.initiatePasswordChallenge} - > - {this.state.pageViewports.length > 0 && ( - - {this.renderPage} - - )} - - - {this.state.shouldRequestPassword && ( - this.setState({isPasswordInvalid: false})} - isPasswordInvalid={this.state.isPasswordInvalid} - onPasswordFieldFocused={this.toggleKeyboardOnSmallScreens} - /> - )} + + } + ErrorComponent={{this.props.translate('attachmentView.failedToLoadPDF')}} + renderPasswordForm={({isPasswordInvalid, onSubmit, onPasswordChange}) => ( + + )} + /> ); } diff --git a/src/styles/index.ts b/src/styles/index.ts index aececf93beb9..2f34c91d3fa6 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -2208,25 +2208,6 @@ const styles = (theme: ThemeColors) => backgroundColor: theme.modalBackground, }, - PDFView: { - // `display: grid` is not supported in native platforms! - // It's being used on Web/Desktop only to vertically center short PDFs, - // while preventing the overflow of the top of long PDF files. - ...display.dGrid, - width: '100%', - height: '100%', - justifyContent: 'center', - overflow: 'hidden', - alignItems: 'center', - }, - - PDFViewList: { - overflowX: 'hidden', - // There properties disable "focus" effect on list - boxShadow: 'none', - outline: 'none', - }, - getPDFPasswordFormStyle: (isSmallScreenWidth: boolean) => ({ width: isSmallScreenWidth ? '100%' : 350, From 2c725ca00033fbc698998ea06d64a96c612d83e8 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 21 Dec 2023 18:25:03 +0100 Subject: [PATCH 002/565] align password block --- src/styles/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/styles/index.ts b/src/styles/index.ts index 1a029f2f4240..ad791838f125 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -2205,6 +2205,7 @@ const styles = (theme: ThemeColors) => ({ width: isSmallScreenWidth ? '100%' : 350, ...(isSmallScreenWidth && flex.flex1), + alignSelf: 'flex-start', } satisfies ViewStyle), centeredModalStyles: (isSmallScreenWidth: boolean, isFullScreenWhenSmall: boolean) => From 183f9621101cb83b011f6cad5998f1ac4c325553 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 21 Dec 2023 19:20:45 +0100 Subject: [PATCH 003/565] align password form --- src/styles/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/styles/index.ts b/src/styles/index.ts index ad791838f125..aefbd47f4fa4 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -2203,8 +2203,8 @@ const styles = (theme: ThemeColors) => getPDFPasswordFormStyle: (isSmallScreenWidth: boolean) => ({ - width: isSmallScreenWidth ? '100%' : 350, - ...(isSmallScreenWidth && flex.flex1), + flexBasis: isSmallScreenWidth ? '100%' : 350, + flexGrow: 0, alignSelf: 'flex-start', } satisfies ViewStyle), From e90e0085e5e300fd841c6cf4cfafd7d56b71f288 Mon Sep 17 00:00:00 2001 From: Dylan Date: Mon, 29 Jan 2024 16:04:33 +0700 Subject: [PATCH 004/565] remove NewDistanceRequestPage and EditRequestDistancePage --- src/ROUTES.ts | 10 +- .../MoneyRequestConfirmationList.js | 12 -- ...oraryForRefactorRequestConfirmationList.js | 10 +- .../ReportActionItem/MoneyRequestView.js | 12 +- .../AppNavigator/ModalStackNavigators.tsx | 1 - src/libs/Navigation/linkingConfig.ts | 1 - src/libs/Navigation/types.ts | 5 +- src/pages/EditRequestDistancePage.js | 122 ------------------ src/pages/EditRequestPage.js | 11 -- src/pages/iou/MoneyRequestSelectorPage.js | 12 -- src/pages/iou/NewDistanceRequestPage.js | 85 ------------ 11 files changed, 27 insertions(+), 254 deletions(-) delete mode 100644 src/pages/EditRequestDistancePage.js delete mode 100644 src/pages/iou/NewDistanceRequestPage.js diff --git a/src/ROUTES.ts b/src/ROUTES.ts index deabdc0ac853..b985f993367a 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -295,10 +295,6 @@ const ROUTES = { route: ':iouType/new/receipt/:reportID?', getRoute: (iouType: string, reportID = '') => `${iouType}/new/receipt/${reportID}` as const, }, - MONEY_REQUEST_DISTANCE: { - route: ':iouType/new/address/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/address/${reportID}` as const, - }, MONEY_REQUEST_DISTANCE_TAB: { route: ':iouType/new/:reportID?/distance', getRoute: (iouType: string, reportID = '') => `${iouType}/new/${reportID}/distance` as const, @@ -350,9 +346,9 @@ const ROUTES = { getUrlWithBackToParam(`create/${iouType}/description/${transactionID}/${reportID}`, backTo), }, MONEY_REQUEST_STEP_DISTANCE: { - route: 'create/:iouType/distance/:transactionID/:reportID', - getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => - getUrlWithBackToParam(`create/${iouType}/distance/${transactionID}/${reportID}`, backTo), + route: ':action/:iouType/distance/:transactionID/:reportID', + getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`${action}/${iouType}/distance/${transactionID}/${reportID}`, backTo), }, MONEY_REQUEST_STEP_MERCHANT: { route: 'create/:iouType/merchant/:transactionID/:reportID', diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index d967d04ab94b..101e135d36e1 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -703,18 +703,6 @@ function MoneyRequestConfirmationList(props) { error={shouldDisplayFieldError && TransactionUtils.isCreatedMissing(transaction) ? translate('common.error.enterDate') : ''} /> )} - {props.isDistanceRequest && ( - Navigation.navigate(ROUTES.MONEY_REQUEST_DISTANCE.getRoute(props.iouType, props.reportID))} - disabled={didConfirm || !isTypeRequest} - interactive={!props.isReadOnly} - /> - )} {shouldShowMerchant && ( - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams())) + Navigation.navigate( + ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute( + CONST.IOU.ACTION.CREATE, + CONST.IOU.TYPE.REQUEST, + transaction.transactionID, + reportID, + Navigation.getActiveRouteWithoutParams(), + ), + ) } disabled={didConfirm || !isTypeRequest} interactive={!isReadOnly} diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 3121328138ee..6cc2119a9044 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -301,7 +301,17 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate interactive={canEditDistance} shouldShowRightIcon={canEditDistance} titleStyle={styles.flex1} - onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DISTANCE))} + onPress={() => + Navigation.navigate( + ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute( + CONST.IOU.ACTION.EDIT, + CONST.IOU.TYPE.REQUEST, + transaction.transactionID, + report.reportID, + Navigation.getActiveRouteWithoutParams(), + ), + ) + } /> ) : ( diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index 3a843e400409..9c2361916934 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -103,7 +103,6 @@ const MoneyRequestModalStackNavigator = createModalStackNavigator require('../../../pages/settings/Wallet/AddDebitCardPage').default as React.ComponentType, [SCREENS.IOU_SEND.ENABLE_PAYMENTS]: () => require('../../../pages/EnablePayments/EnablePaymentsPage').default as React.ComponentType, [SCREENS.MONEY_REQUEST.WAYPOINT]: () => require('../../../pages/iou/MoneyRequestWaypointPage').default as React.ComponentType, - [SCREENS.MONEY_REQUEST.DISTANCE]: () => require('../../../pages/iou/NewDistanceRequestPage').default as React.ComponentType, [SCREENS.MONEY_REQUEST.RECEIPT]: () => require('../../../pages/EditRequestReceiptPage').default as React.ComponentType, }); diff --git a/src/libs/Navigation/linkingConfig.ts b/src/libs/Navigation/linkingConfig.ts index d4e04d5402e2..715b14c4cb90 100644 --- a/src/libs/Navigation/linkingConfig.ts +++ b/src/libs/Navigation/linkingConfig.ts @@ -435,7 +435,6 @@ const linkingConfig: LinkingOptions = { [SCREENS.MONEY_REQUEST.TAG]: ROUTES.MONEY_REQUEST_TAG.route, [SCREENS.MONEY_REQUEST.MERCHANT]: ROUTES.MONEY_REQUEST_MERCHANT.route, [SCREENS.MONEY_REQUEST.RECEIPT]: ROUTES.MONEY_REQUEST_RECEIPT.route, - [SCREENS.MONEY_REQUEST.DISTANCE]: ROUTES.MONEY_REQUEST_DISTANCE.route, [SCREENS.IOU_SEND.ENABLE_PAYMENTS]: ROUTES.IOU_SEND_ENABLE_PAYMENTS, [SCREENS.IOU_SEND.ADD_BANK_ACCOUNT]: ROUTES.IOU_SEND_ADD_BANK_ACCOUNT, [SCREENS.IOU_SEND.ADD_DEBIT_CARD]: ROUTES.IOU_SEND_ADD_DEBIT_CARD, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index b4a77f96cc74..ea2b48df4ed2 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -238,9 +238,12 @@ type MoneyRequestNavigatorParamList = { waypointIndex: string; threadReportID: number; }; - [SCREENS.MONEY_REQUEST.DISTANCE]: { + [SCREENS.MONEY_REQUEST.STEP_DISTANCE]: { + action: string; iouType: ValueOf; + transactionID: string; reportID: string; + backTo: string; }; [SCREENS.MONEY_REQUEST.RECEIPT]: { iouType: string; diff --git a/src/pages/EditRequestDistancePage.js b/src/pages/EditRequestDistancePage.js deleted file mode 100644 index f3ea76a3390a..000000000000 --- a/src/pages/EditRequestDistancePage.js +++ /dev/null @@ -1,122 +0,0 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; -import React, {useEffect, useRef} from 'react'; -import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; -import DistanceRequest from '@components/DistanceRequest'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import ScreenWrapper from '@components/ScreenWrapper'; -import transactionPropTypes from '@components/transactionPropTypes'; -import useLocalize from '@hooks/useLocalize'; -import useNetwork from '@hooks/useNetwork'; -import usePrevious from '@hooks/usePrevious'; -import Navigation from '@libs/Navigation/Navigation'; -import * as IOU from '@userActions/IOU'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import reportPropTypes from './reportPropTypes'; - -const propTypes = { - /** The transactionID we're currently editing */ - transactionID: PropTypes.string.isRequired, - - /** The report to with which the distance request is associated */ - report: reportPropTypes.isRequired, - - /** Passed from the navigator */ - route: PropTypes.shape({ - /** Parameters the route gets */ - params: PropTypes.shape({ - /** Type of IOU */ - iouType: PropTypes.oneOf(_.values(CONST.IOU.TYPE)), - - /** Id of the report on which the distance request is being created */ - reportID: PropTypes.string, - }), - }).isRequired, - - /* Onyx props */ - /** The original transaction that is being edited */ - transaction: transactionPropTypes, - - /** backup version of the original transaction */ - transactionBackup: transactionPropTypes, -}; - -const defaultProps = { - transaction: {}, - transactionBackup: {}, -}; - -function EditRequestDistancePage({report, route, transaction, transactionBackup}) { - const {isOffline} = useNetwork(); - const {translate} = useLocalize(); - const hasWaypointError = useRef(false); - const prevIsLoading = usePrevious(transaction.isLoading); - - useEffect(() => { - hasWaypointError.current = Boolean(lodashGet(transaction, 'errorFields.route') || lodashGet(transaction, 'errorFields.waypoints')); - - // When the loading goes from true to false, then we know the transaction has just been - // saved to the server. Check for errors. If there are no errors, then the modal can be closed. - if (prevIsLoading && !transaction.isLoading && !hasWaypointError.current) { - Navigation.dismissModal(report.reportID); - } - }, [transaction, prevIsLoading, report]); - - /** - * Save the changes to the original transaction object - * @param {Object} waypoints - */ - const saveTransaction = (waypoints) => { - // If nothing was changed, simply go to transaction thread - // We compare only addresses because numbers are rounded while backup - const oldWaypoints = lodashGet(transactionBackup, 'comment.waypoints', {}); - const oldAddresses = _.mapObject(oldWaypoints, (waypoint) => _.pick(waypoint, 'address')); - const addresses = _.mapObject(waypoints, (waypoint) => _.pick(waypoint, 'address')); - if (_.isEqual(oldAddresses, addresses)) { - Navigation.dismissModal(report.reportID); - return; - } - - IOU.updateMoneyRequestDistance(transaction.transactionID, report.reportID, waypoints); - - // If the client is offline, then the modal can be closed as well (because there are no errors or other feedback to show them - // until they come online again and sync with the server). - if (isOffline) { - Navigation.dismissModal(report.reportID); - } - }; - - return ( - - Navigation.goBack()} - /> - - - ); -} - -EditRequestDistancePage.propTypes = propTypes; -EditRequestDistancePage.defaultProps = defaultProps; -EditRequestDistancePage.displayName = 'EditRequestDistancePage'; -export default withOnyx({ - transaction: { - key: (props) => `${ONYXKEYS.COLLECTION.TRANSACTION}${props.transactionID}`, - }, - transactionBackup: { - key: (props) => `${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${props.transactionID}`, - }, -})(EditRequestDistancePage); diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index 3eb9d88f1120..9f1ba51806e5 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -23,7 +23,6 @@ import EditRequestAmountPage from './EditRequestAmountPage'; import EditRequestCategoryPage from './EditRequestCategoryPage'; import EditRequestCreatedPage from './EditRequestCreatedPage'; import EditRequestDescriptionPage from './EditRequestDescriptionPage'; -import EditRequestDistancePage from './EditRequestDistancePage'; import EditRequestMerchantPage from './EditRequestMerchantPage'; import EditRequestReceiptPage from './EditRequestReceiptPage'; import EditRequestTagPage from './EditRequestTagPage'; @@ -264,16 +263,6 @@ function EditRequestPage({report, route, policyCategories, policyTags, parentRep ); } - if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.DISTANCE) { - return ( - - ); - } - return ( { const moneyRequestID = `${iouType}${reportID}`; @@ -133,13 +128,6 @@ function MoneyRequestSelectorPage(props) { initialParams={{reportID, iouType}} /> {() => } - {shouldDisplayDistanceRequest && ( - - )} ) : ( diff --git a/src/pages/iou/NewDistanceRequestPage.js b/src/pages/iou/NewDistanceRequestPage.js deleted file mode 100644 index 750ac5d0141e..000000000000 --- a/src/pages/iou/NewDistanceRequestPage.js +++ /dev/null @@ -1,85 +0,0 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; -import React, {useCallback, useEffect} from 'react'; -import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; -import DistanceRequest from '@components/DistanceRequest'; -import Navigation from '@libs/Navigation/Navigation'; -import reportPropTypes from '@pages/reportPropTypes'; -import * as IOU from '@userActions/IOU'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; -import {iouPropTypes} from './propTypes'; - -const propTypes = { - /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ - iou: iouPropTypes, - - /** The report on which the request is initiated on */ - report: reportPropTypes, - - /** Passed from the navigator */ - route: PropTypes.shape({ - /** Parameters the route gets */ - params: PropTypes.shape({ - /** Type of IOU */ - iouType: PropTypes.oneOf(_.values(CONST.IOU.TYPE)), - /** Id of the report on which the distance request is being created */ - reportID: PropTypes.string, - }), - }), -}; - -const defaultProps = { - iou: {}, - report: {}, - route: { - params: { - iouType: '', - reportID: '', - }, - }, -}; - -// This component is responsible for getting the transactionID from the IOU key, or creating the transaction if it doesn't exist yet, and then passing the transactionID. -// You can't use Onyx props in the withOnyx mapping, so we need to set up and access the transactionID here, and then pass it down so that DistanceRequest can subscribe to the transaction. -function NewDistanceRequestPage({iou, report, route}) { - const iouType = lodashGet(route, 'params.iouType', 'request'); - const isEditingNewRequest = Navigation.getActiveRoute().includes('address'); - - useEffect(() => { - if (iou.transactionID) { - return; - } - IOU.setUpDistanceTransaction(); - }, [iou.transactionID]); - - const onSubmit = useCallback(() => { - if (isEditingNewRequest) { - Navigation.goBack(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, report.reportID)); - return; - } - IOU.navigateToNextPage(iou, iouType, report); - }, [iou, iouType, isEditingNewRequest, report]); - - return ( - - ); -} - -NewDistanceRequestPage.displayName = 'NewDistanceRequestPage'; -NewDistanceRequestPage.propTypes = propTypes; -NewDistanceRequestPage.defaultProps = defaultProps; -export default withOnyx({ - iou: {key: ONYXKEYS.IOU}, - report: { - key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${lodashGet(route, 'params.reportID')}`, - }, -})(NewDistanceRequestPage); From 44c41df41b81ab483fe0c3a13505b01fab0084fa Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 31 Jan 2024 15:34:26 +0100 Subject: [PATCH 005/565] back changes --- src/components/PDFView/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/PDFView/index.js b/src/components/PDFView/index.js index 5d505bd318fe..4e748669df1b 100644 --- a/src/components/PDFView/index.js +++ b/src/components/PDFView/index.js @@ -4,7 +4,9 @@ import {PDFPreviewer} from 'react-fast-pdf'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; +import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; +import Text from '@components/Text'; import withLocalize from '@components/withLocalize'; import withThemeStyles from '@components/withThemeStyles'; import withWindowDimensions from '@components/withWindowDimensions'; @@ -93,6 +95,7 @@ class PDFView extends Component { maxCanvasWidth={this.props.maxCanvasWidth} maxCanvasHeight={this.props.maxCanvasHeight} maxCanvasArea={this.props.maxCanvasArea} + LoadingComponent={} ErrorComponent={{this.props.translate('attachmentView.failedToLoadPDF')}} renderPasswordForm={({isPasswordInvalid, onSubmit, onPasswordChange}) => ( Date: Wed, 31 Jan 2024 18:29:12 +0100 Subject: [PATCH 006/565] use latest react-fast-pdf versions --- package-lock.json | 34 +++++++++++++++++++++------------- package.json | 2 +- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 04409926e0b1..b829a7208f72 100644 --- a/package-lock.json +++ b/package-lock.json @@ -73,7 +73,7 @@ "react-content-loader": "^6.1.0", "react-dom": "18.1.0", "react-error-boundary": "^4.0.11", - "react-fast-pdf": "git+https://github.com/rezkiy37/react-fast-pdf#b7b8ccb6a21a2322d0567bd448d57e54d71238fb", + "react-fast-pdf": "git+https://github.com/rezkiy37/react-fast-pdf#b925199ddbc94eb17197bfbe285e764d95832206", "react-map-gl": "^7.1.3", "react-native": "0.73.2", "react-native-android-location-enabler": "^1.2.2", @@ -28041,7 +28041,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, "engines": { "node": ">=6" } @@ -44622,12 +44621,12 @@ }, "node_modules/react-fast-pdf": { "version": "1.0.3", - "resolved": "git+ssh://git@github.com/rezkiy37/react-fast-pdf.git#b7b8ccb6a21a2322d0567bd448d57e54d71238fb", - "integrity": "sha512-qTePMIJJX1fKe/o83pu/zHYkGforwT2HnHK3pRBaZ1xe5CD3xCM5iQ6K0xRNTxQ/F4saLD0oQtiIjaKZdmdIBg==", + "resolved": "git+ssh://git@github.com/rezkiy37/react-fast-pdf.git#b925199ddbc94eb17197bfbe285e764d95832206", + "integrity": "sha512-CRPbAXZkIYc9e3Dk93qzGD63jfqAvzg57ay2S36Do1V1UwKUFTkwwM+kbbC+kwejVq05tnIC5CRthU75PnlP1A==", "license": "MIT", "dependencies": { - "react-pdf": "^7.3.3", - "react-window": "^1.8.9" + "react-pdf": "^7.7.0", + "react-window": "^1.8.10" }, "engines": { "node": "20.10.0", @@ -44653,18 +44652,19 @@ } }, "node_modules/react-fast-pdf/node_modules/react-pdf": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-7.6.0.tgz", - "integrity": "sha512-b2/8V6xhe5pn4Y01ELKTQZ+RhdZl1KpSAMGbN+HCZ/kwhYTIc4Pn5ctz1wRQUu1gOJbIEG4CcjMD9vTCzNdwjw==", + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-7.7.0.tgz", + "integrity": "sha512-704ObLnRDm5lixL4e6NXNLaincBHGNLo+NGdbO3rEXE963NlNzwLxFpmKcbdXHAMQL4rYJQWb1L0w5IL6y8Osw==", "dependencies": { "clsx": "^2.0.0", + "dequal": "^2.0.3", "make-cancellable-promise": "^1.3.1", "make-event-props": "^1.6.0", "merge-refs": "^1.2.1", "pdfjs-dist": "3.11.174", "prop-types": "^15.6.2", "tiny-invariant": "^1.0.0", - "tiny-warning": "^1.0.0" + "warning": "^4.0.0" }, "funding": { "url": "https://github.com/wojtekmaj/react-pdf?sponsor=1" @@ -44680,6 +44680,14 @@ } } }, + "node_modules/react-fast-pdf/node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/react-freeze": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/react-freeze/-/react-freeze-1.0.3.tgz", @@ -46301,9 +46309,9 @@ } }, "node_modules/react-window": { - "version": "1.8.9", - "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.9.tgz", - "integrity": "sha512-+Eqx/fj1Aa5WnhRfj9dJg4VYATGwIUP2ItwItiJ6zboKWA6EX3lYDAXfGF2hyNqplEprhbtjbipiADEcwQ823Q==", + "version": "1.8.10", + "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.10.tgz", + "integrity": "sha512-Y0Cx+dnU6NLa5/EvoHukUD0BklJ8qITCtVEPY1C/nL8wwoZ0b5aEw8Ff1dOVHw7fCzMt55XfJDd8S8W8LCaUCg==", "dependencies": { "@babel/runtime": "^7.0.0", "memoize-one": ">=3.1.1 <6" diff --git a/package.json b/package.json index 56d74250d929..2ce9f9bbe492 100644 --- a/package.json +++ b/package.json @@ -121,7 +121,7 @@ "react-content-loader": "^6.1.0", "react-dom": "18.1.0", "react-error-boundary": "^4.0.11", - "react-fast-pdf": "git+https://github.com/rezkiy37/react-fast-pdf#b7b8ccb6a21a2322d0567bd448d57e54d71238fb", + "react-fast-pdf": "git+https://github.com/rezkiy37/react-fast-pdf#b925199ddbc94eb17197bfbe285e764d95832206", "react-map-gl": "^7.1.3", "react-native": "0.73.2", "react-native-android-location-enabler": "^1.2.2", From 55a4a1115a0124c585b6f991f5e59371c245206b Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 31 Jan 2024 18:37:56 +0100 Subject: [PATCH 007/565] use latest react-fast-pdf versions --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index b829a7208f72..df4c333af633 100644 --- a/package-lock.json +++ b/package-lock.json @@ -73,7 +73,7 @@ "react-content-loader": "^6.1.0", "react-dom": "18.1.0", "react-error-boundary": "^4.0.11", - "react-fast-pdf": "git+https://github.com/rezkiy37/react-fast-pdf#b925199ddbc94eb17197bfbe285e764d95832206", + "react-fast-pdf": "git+https://github.com/rezkiy37/react-fast-pdf#d5627b27a2d73048c2d12aa2044e44332f92bcf3", "react-map-gl": "^7.1.3", "react-native": "0.73.2", "react-native-android-location-enabler": "^1.2.2", @@ -44621,8 +44621,8 @@ }, "node_modules/react-fast-pdf": { "version": "1.0.3", - "resolved": "git+ssh://git@github.com/rezkiy37/react-fast-pdf.git#b925199ddbc94eb17197bfbe285e764d95832206", - "integrity": "sha512-CRPbAXZkIYc9e3Dk93qzGD63jfqAvzg57ay2S36Do1V1UwKUFTkwwM+kbbC+kwejVq05tnIC5CRthU75PnlP1A==", + "resolved": "git+ssh://git@github.com/rezkiy37/react-fast-pdf.git#d5627b27a2d73048c2d12aa2044e44332f92bcf3", + "integrity": "sha512-YeEIjzBpxEmELZqQg6Ve3qJngr81fYA+b1or4EHR0dnx6/oGElEipAB17e+ZuCJ/NnPc3xKbUEY8UpqA/7+ssw==", "license": "MIT", "dependencies": { "react-pdf": "^7.7.0", diff --git a/package.json b/package.json index 2ce9f9bbe492..b546405d2917 100644 --- a/package.json +++ b/package.json @@ -121,7 +121,7 @@ "react-content-loader": "^6.1.0", "react-dom": "18.1.0", "react-error-boundary": "^4.0.11", - "react-fast-pdf": "git+https://github.com/rezkiy37/react-fast-pdf#b925199ddbc94eb17197bfbe285e764d95832206", + "react-fast-pdf": "git+https://github.com/rezkiy37/react-fast-pdf#d5627b27a2d73048c2d12aa2044e44332f92bcf3", "react-map-gl": "^7.1.3", "react-native": "0.73.2", "react-native-android-location-enabler": "^1.2.2", From 4feb09259b4501462807da3bb197aa705e3c1d4f Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Tue, 23 Jan 2024 13:44:07 +0000 Subject: [PATCH 008/565] [TS Migration] Migrate WorkspaceCard to Typescript --- .../workspace/card/WorkspaceCardNoVBAView.js | 49 ------------------- .../workspace/card/WorkspaceCardNoVBAView.tsx | 40 +++++++++++++++ src/pages/workspace/card/WorkspaceCardPage.js | 47 ------------------ .../workspace/card/WorkspaceCardPage.tsx | 39 +++++++++++++++ ...iew.js => WorkspaceCardVBANoECardView.tsx} | 48 +++++++----------- ...w.js => WorkspaceCardVBAWithECardView.tsx} | 39 ++++++++------- 6 files changed, 117 insertions(+), 145 deletions(-) delete mode 100644 src/pages/workspace/card/WorkspaceCardNoVBAView.js create mode 100644 src/pages/workspace/card/WorkspaceCardNoVBAView.tsx delete mode 100644 src/pages/workspace/card/WorkspaceCardPage.js create mode 100644 src/pages/workspace/card/WorkspaceCardPage.tsx rename src/pages/workspace/card/{WorkspaceCardVBANoECardView.js => WorkspaceCardVBANoECardView.tsx} (53%) rename src/pages/workspace/card/{WorkspaceCardVBAWithECardView.js => WorkspaceCardVBAWithECardView.tsx} (67%) diff --git a/src/pages/workspace/card/WorkspaceCardNoVBAView.js b/src/pages/workspace/card/WorkspaceCardNoVBAView.js deleted file mode 100644 index 3233f8ea7e23..000000000000 --- a/src/pages/workspace/card/WorkspaceCardNoVBAView.js +++ /dev/null @@ -1,49 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import {View} from 'react-native'; -import ConnectBankAccountButton from '@components/ConnectBankAccountButton'; -import * as Illustrations from '@components/Icon/Illustrations'; -import Section from '@components/Section'; -import Text from '@components/Text'; -import UnorderedList from '@components/UnorderedList'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; -import useThemeStyles from '@hooks/useThemeStyles'; - -const propTypes = { - /** The policy ID currently being configured */ - policyID: PropTypes.string.isRequired, - - ...withLocalizePropTypes, -}; - -function WorkspaceCardNoVBAView(props) { - const styles = useThemeStyles(); - return ( -
- - {props.translate('workspace.card.noVBACopy')} - - - - -
- ); -} - -WorkspaceCardNoVBAView.propTypes = propTypes; -WorkspaceCardNoVBAView.displayName = 'WorkspaceCardNoVBAView'; - -export default withLocalize(WorkspaceCardNoVBAView); diff --git a/src/pages/workspace/card/WorkspaceCardNoVBAView.tsx b/src/pages/workspace/card/WorkspaceCardNoVBAView.tsx new file mode 100644 index 000000000000..322d433a8e62 --- /dev/null +++ b/src/pages/workspace/card/WorkspaceCardNoVBAView.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import {View} from 'react-native'; +import ConnectBankAccountButton from '@components/ConnectBankAccountButton'; +import * as Illustrations from '@components/Icon/Illustrations'; +import Section from '@components/Section'; +import Text from '@components/Text'; +import UnorderedList from '@components/UnorderedList'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; + +type WorkspaceCardNoVBAViewProps = { + /** The policy ID currently being configured */ + policyID: string; +}; + +function WorkspaceCardNoVBAView({policyID}: WorkspaceCardNoVBAViewProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + return ( +
+ + {translate('workspace.card.noVBACopy')} + + + + +
+ ); +} + +WorkspaceCardNoVBAView.displayName = 'WorkspaceCardNoVBAView'; + +export default WorkspaceCardNoVBAView; diff --git a/src/pages/workspace/card/WorkspaceCardPage.js b/src/pages/workspace/card/WorkspaceCardPage.js deleted file mode 100644 index 55220b85ce63..000000000000 --- a/src/pages/workspace/card/WorkspaceCardPage.js +++ /dev/null @@ -1,47 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; -import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; -import CONST from '@src/CONST'; -import WorkspaceCardNoVBAView from './WorkspaceCardNoVBAView'; -import WorkspaceCardVBANoECardView from './WorkspaceCardVBANoECardView'; -import WorkspaceCardVBAWithECardView from './WorkspaceCardVBAWithECardView'; - -const propTypes = { - /** The route object passed to this page from the navigator */ - route: PropTypes.shape({ - /** Each parameter passed via the URL */ - params: PropTypes.shape({ - /** The policyID that is being configured */ - policyID: PropTypes.string.isRequired, - }).isRequired, - }).isRequired, - - ...withLocalizePropTypes, -}; - -function WorkspaceCardPage(props) { - return ( - - {(hasVBA, policyID, isUsingECard) => ( - <> - {!hasVBA && } - - {hasVBA && !isUsingECard && } - - {hasVBA && isUsingECard && } - - )} - - ); -} - -WorkspaceCardPage.propTypes = propTypes; -WorkspaceCardPage.displayName = 'WorkspaceCardPage'; - -export default withLocalize(WorkspaceCardPage); diff --git a/src/pages/workspace/card/WorkspaceCardPage.tsx b/src/pages/workspace/card/WorkspaceCardPage.tsx new file mode 100644 index 000000000000..f6e368db84ea --- /dev/null +++ b/src/pages/workspace/card/WorkspaceCardPage.tsx @@ -0,0 +1,39 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React from 'react'; +import useLocalize from '@hooks/useLocalize'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; +import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; +import CONST from '@src/CONST'; +import type SCREENS from '@src/SCREENS'; +import WorkspaceCardNoVBAView from './WorkspaceCardNoVBAView'; +import WorkspaceCardVBANoECardView from './WorkspaceCardVBANoECardView'; +import WorkspaceCardVBAWithECardView from './WorkspaceCardVBAWithECardView'; + +type WorkspaceCardPageProps = StackScreenProps; + +function WorkspaceCardPage({route}: WorkspaceCardPageProps) { + const {translate} = useLocalize(); + + return ( + + {(hasVBA: boolean, policyID: string, isUsingECard: boolean) => ( + <> + {false && } + + {false && } + + {true && } + + )} + + ); +} + +WorkspaceCardPage.displayName = 'WorkspaceCardPage'; + +export default WorkspaceCardPage; diff --git a/src/pages/workspace/card/WorkspaceCardVBANoECardView.js b/src/pages/workspace/card/WorkspaceCardVBANoECardView.tsx similarity index 53% rename from src/pages/workspace/card/WorkspaceCardVBANoECardView.js rename to src/pages/workspace/card/WorkspaceCardVBANoECardView.tsx index 970cd9105368..3c9b773d6994 100644 --- a/src/pages/workspace/card/WorkspaceCardVBANoECardView.js +++ b/src/pages/workspace/card/WorkspaceCardVBANoECardView.tsx @@ -1,5 +1,6 @@ import React from 'react'; import {View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -7,45 +8,37 @@ import * as Illustrations from '@components/Icon/Illustrations'; import Section from '@components/Section'; import Text from '@components/Text'; import UnorderedList from '@components/UnorderedList'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; -import userPropTypes from '@pages/settings/userPropTypes'; import * as Link from '@userActions/Link'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {User} from '@src/types/onyx'; -const propTypes = { +type WorkspaceCardVBANoECardViewOnyxProps = { /** Information about the logged in user's account */ - user: userPropTypes, - - ...withLocalizePropTypes, + user: OnyxEntry; }; -const defaultProps = { - user: {}, -}; +type WorkspaceCardVBANoECardViewProps = WorkspaceCardVBANoECardViewOnyxProps; -function WorkspaceCardVBANoECardView(props) { +function WorkspaceCardVBANoECardView({user}: WorkspaceCardVBANoECardViewProps) { const styles = useThemeStyles(); + const {translate} = useLocalize(); + return ( <>
- {Boolean(props.user.isCheckingDomain) && {props.translate('workspace.card.checkingDomain')}} + {Boolean(user?.isCheckingDomain) && {translate('workspace.card.checkingDomain')}} ); } -WorkspaceCardVBANoECardView.propTypes = propTypes; -WorkspaceCardVBANoECardView.defaultProps = defaultProps; WorkspaceCardVBANoECardView.displayName = 'WorkspaceCardVBANoECardView'; -export default compose( - withLocalize, - withOnyx({ - user: { - key: ONYXKEYS.USER, - }, - }), -)(WorkspaceCardVBANoECardView); +export default withOnyx({ + user: { + key: ONYXKEYS.USER, + }, +})(WorkspaceCardVBANoECardView); diff --git a/src/pages/workspace/card/WorkspaceCardVBAWithECardView.js b/src/pages/workspace/card/WorkspaceCardVBAWithECardView.tsx similarity index 67% rename from src/pages/workspace/card/WorkspaceCardVBAWithECardView.js rename to src/pages/workspace/card/WorkspaceCardVBAWithECardView.tsx index 40ecd80b8e6e..a53a44fa52cf 100644 --- a/src/pages/workspace/card/WorkspaceCardVBAWithECardView.js +++ b/src/pages/workspace/card/WorkspaceCardVBAWithECardView.tsx @@ -2,28 +2,35 @@ import React from 'react'; import {View} from 'react-native'; import * as Expensicons from '@components/Icon/Expensicons'; import * as Illustrations from '@components/Icon/Illustrations'; +import type {MenuItemWithLink} from '@components/MenuItemList'; import Section from '@components/Section'; import Text from '@components/Text'; import UnorderedList from '@components/UnorderedList'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as Link from '@userActions/Link'; -const propTypes = { - ...withLocalizePropTypes, +type MenuLinks = { + ISSUE_AND_MANAGE_CARDS: string; + RECONCILE_CARDS: string; + SETTLEMENT_FREQUENCY: string; }; -const MENU_LINKS = { +type MenuItems = MenuItemWithLink[]; + +const MENU_LINKS: MenuLinks = { ISSUE_AND_MANAGE_CARDS: 'domain_companycards', RECONCILE_CARDS: encodeURI('domain_companycards?param={"section":"cardReconciliation"}'), SETTLEMENT_FREQUENCY: encodeURI('domain_companycards?param={"section":"configureSettings"}'), }; -function WorkspaceCardVBAWithECardView(props) { +function WorkspaceCardVBAWithECardView() { const styles = useThemeStyles(); - const menuItems = [ + const {translate} = useLocalize(); + + const menuItems: MenuItems = [ { - title: props.translate('workspace.common.issueAndManageCards'), + title: translate('workspace.common.issueAndManageCards'), onPress: () => Link.openOldDotLink(MENU_LINKS.ISSUE_AND_MANAGE_CARDS), icon: Expensicons.ExpensifyCard, shouldShowRightIcon: true, @@ -32,7 +39,7 @@ function WorkspaceCardVBAWithECardView(props) { link: () => Link.buildOldDotURL(MENU_LINKS.ISSUE_AND_MANAGE_CARDS), }, { - title: props.translate('workspace.common.reconcileCards'), + title: translate('workspace.common.reconcileCards'), onPress: () => Link.openOldDotLink(MENU_LINKS.RECONCILE_CARDS), icon: Expensicons.ReceiptSearch, shouldShowRightIcon: true, @@ -41,7 +48,7 @@ function WorkspaceCardVBAWithECardView(props) { link: () => Link.buildOldDotURL(MENU_LINKS.RECONCILE_CARDS), }, { - title: props.translate('workspace.common.settlementFrequency'), + title: translate('workspace.common.settlementFrequency'), onPress: () => Link.openOldDotLink(MENU_LINKS.SETTLEMENT_FREQUENCY), icon: Expensicons.Gear, shouldShowRightIcon: true, @@ -53,29 +60,23 @@ function WorkspaceCardVBAWithECardView(props) { return (
- {props.translate('workspace.card.VBAWithECardCopy')} + {translate('workspace.card.VBAWithECardCopy')}
); } -WorkspaceCardVBAWithECardView.propTypes = propTypes; WorkspaceCardVBAWithECardView.displayName = 'WorkspaceCardVBAWithECardView'; -export default withLocalize(WorkspaceCardVBAWithECardView); +export default WorkspaceCardVBAWithECardView; From b9746297385965d1a1ba4150ab10e4110aef012d Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Thu, 1 Feb 2024 10:15:16 +0000 Subject: [PATCH 009/565] [TS migration] Migrate WorkspaceReimburse page --- src/ONYXKEYS.ts | 4 +- src/components/Picker/BasePicker.tsx | 4 +- src/components/Picker/types.ts | 2 +- src/libs/DistanceRequestUtils.ts | 2 +- src/libs/PolicyUtils.ts | 4 +- src/libs/actions/Policy.ts | 17 +- .../reimburse/WorkspaceRateAndUnitPage.js | 173 ------------------ .../reimburse/WorkspaceRateAndUnitPage.tsx | 157 ++++++++++++++++ .../reimburse/WorkspaceReimbursePage.js | 42 ----- .../reimburse/WorkspaceReimbursePage.tsx | 33 ++++ ...ction.js => WorkspaceReimburseSection.tsx} | 60 +++--- ...urseView.js => WorkspaceReimburseView.tsx} | 116 ++++-------- src/types/onyx/Form.ts | 9 +- src/types/onyx/Policy.ts | 6 +- src/types/onyx/index.ts | 3 +- 15 files changed, 291 insertions(+), 341 deletions(-) delete mode 100644 src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.js create mode 100644 src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.tsx delete mode 100644 src/pages/workspace/reimburse/WorkspaceReimbursePage.js create mode 100644 src/pages/workspace/reimburse/WorkspaceReimbursePage.tsx rename src/pages/workspace/reimburse/{WorkspaceReimburseSection.js => WorkspaceReimburseSection.tsx} (57%) rename src/pages/workspace/reimburse/{WorkspaceReimburseView.js => WorkspaceReimburseView.tsx} (53%) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 2867cb3905a2..0eaa79d7cdda 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -486,8 +486,8 @@ type OnyxValues = { [ONYXKEYS.FORMS.ADD_DEBIT_CARD_FORM_DRAFT]: OnyxTypes.AddDebitCardForm; [ONYXKEYS.FORMS.WORKSPACE_SETTINGS_FORM]: OnyxTypes.Form; [ONYXKEYS.FORMS.WORKSPACE_SETTINGS_FORM_DRAFT]: OnyxTypes.Form; - [ONYXKEYS.FORMS.WORKSPACE_RATE_AND_UNIT_FORM]: OnyxTypes.Form; - [ONYXKEYS.FORMS.WORKSPACE_RATE_AND_UNIT_FORM_DRAFT]: OnyxTypes.Form; + [ONYXKEYS.FORMS.WORKSPACE_RATE_AND_UNIT_FORM]: OnyxTypes.RateUnitForm; + [ONYXKEYS.FORMS.WORKSPACE_RATE_AND_UNIT_FORM_DRAFT]: OnyxTypes.RateUnitForm; [ONYXKEYS.FORMS.CLOSE_ACCOUNT_FORM]: OnyxTypes.Form; [ONYXKEYS.FORMS.CLOSE_ACCOUNT_FORM_DRAFT]: OnyxTypes.Form; [ONYXKEYS.FORMS.PROFILE_SETTINGS_FORM]: OnyxTypes.Form; diff --git a/src/components/Picker/BasePicker.tsx b/src/components/Picker/BasePicker.tsx index 1bee95532104..020a3cf72680 100644 --- a/src/components/Picker/BasePicker.tsx +++ b/src/components/Picker/BasePicker.tsx @@ -69,11 +69,11 @@ function BasePicker( */ const onValueChange = (inputValue: TPickerValue, index: number) => { if (inputID) { - onInputChange(inputValue); + onInputChange?.(inputValue); return; } - onInputChange(inputValue, index); + onInputChange?.(inputValue, index); }; const enableHighlight = () => { diff --git a/src/components/Picker/types.ts b/src/components/Picker/types.ts index edf39a59c9d8..6304b23e7a2c 100644 --- a/src/components/Picker/types.ts +++ b/src/components/Picker/types.ts @@ -73,7 +73,7 @@ type BasePickerProps = { shouldSaveDraft?: boolean; /** A callback method that is called when the value changes and it receives the selected value as an argument */ - onInputChange: (value: TPickerValue, index?: number) => void; + onInputChange?: (value: TPickerValue, index?: number) => void; /** Size of a picker component */ size?: PickerSize; diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index c92e9bfd3f67..fb4e92d7f147 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -100,7 +100,7 @@ function getDistanceMerchant( const distanceUnit = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.miles') : translate('common.kilometers'); const singularDistanceUnit = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.mile') : translate('common.kilometer'); const unitString = distanceInUnits === '1' ? singularDistanceUnit : distanceUnit; - const ratePerUnit = rate ? PolicyUtils.getUnitRateValue({rate}, toLocaleDigit) : translate('common.tbd'); + const ratePerUnit = rate ? PolicyUtils.getUnitRateValue(toLocaleDigit, {rate}) : translate('common.tbd'); // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const currencySymbol = rate ? CurrencyUtils.getCurrencySymbol(currency) || `${currency} ` : ''; diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index b8ed62f93082..a812cab24402 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -4,11 +4,11 @@ import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PersonalDetailsList, Policy, PolicyMembers, PolicyTag, PolicyTags} from '@src/types/onyx'; +import type {Rate} from '@src/types/onyx/Policy'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; type MemberEmailsToAccountIDs = Record; -type UnitRate = {rate: number}; /** * Filter out the active policies, which will exclude policies with pending deletion @@ -66,7 +66,7 @@ function getRateDisplayValue(value: number, toLocaleDigit: (arg: string) => stri return numValue.toString().replace('.', toLocaleDigit('.')).substring(0, value.toString().length); } -function getUnitRateValue(customUnitRate: UnitRate, toLocaleDigit: (arg: string) => string) { +function getUnitRateValue(toLocaleDigit: (arg: string) => string, customUnitRate?: Rate) { return getRateDisplayValue((customUnitRate?.rate ?? 0) / CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET, toLocaleDigit); } diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index fbe92aeb378d..48fd8944f196 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -67,6 +67,13 @@ type OptimisticCustomUnits = { outputCurrency: string; }; +type NewCustomUnit = { + customUnitID: string; + name: string; + attributes: Attributes; + rates: Rate; +}; + type PoliciesRecord = Record>; const allPolicies: OnyxCollection = {}; @@ -932,7 +939,7 @@ function hideWorkspaceAlertMessage(policyID: string) { Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {alertMessage: ''}); } -function updateWorkspaceCustomUnitAndRate(policyID: string, currentCustomUnit: CustomUnit, newCustomUnit: CustomUnit, lastModified: number) { +function updateWorkspaceCustomUnitAndRate(policyID: string, currentCustomUnit: CustomUnit, newCustomUnit: NewCustomUnit, lastModified: number) { if (!currentCustomUnit.customUnitID || !newCustomUnit?.customUnitID || !newCustomUnit.rates?.customUnitRateID) { return; } @@ -946,7 +953,7 @@ function updateWorkspaceCustomUnitAndRate(policyID: string, currentCustomUnit: C [newCustomUnit.customUnitID]: { ...newCustomUnit, rates: { - [newCustomUnit.rates.customUnitRateID as string]: { + [newCustomUnit.rates.customUnitRateID]: { ...newCustomUnit.rates, errors: null, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, @@ -969,7 +976,7 @@ function updateWorkspaceCustomUnitAndRate(policyID: string, currentCustomUnit: C pendingAction: null, errors: null, rates: { - [newCustomUnit.rates.customUnitRateID as string]: { + [newCustomUnit.rates.customUnitRateID]: { pendingAction: null, }, }, @@ -988,7 +995,7 @@ function updateWorkspaceCustomUnitAndRate(policyID: string, currentCustomUnit: C [currentCustomUnit.customUnitID]: { customUnitID: currentCustomUnit.customUnitID, rates: { - [newCustomUnit.rates.customUnitRateID as string]: { + [newCustomUnit.rates.customUnitRateID]: { ...currentCustomUnit.rates, errors: ErrorUtils.getMicroSecondOnyxError('workspace.reimburse.updateCustomUnitError'), }, @@ -2018,3 +2025,5 @@ export { createDraftInitialWorkspace, setWorkspaceInviteMessageDraft, }; + +export type {NewCustomUnit}; diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.js b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.js deleted file mode 100644 index 93ea7212e741..000000000000 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.js +++ /dev/null @@ -1,173 +0,0 @@ -import lodashGet from 'lodash/get'; -import React, {useEffect} from 'react'; -import {Keyboard, View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; -import FormProvider from '@components/Form/FormProvider'; -import InputWrapper from '@components/Form/InputWrapper'; -import OfflineWithFeedback from '@components/OfflineWithFeedback'; -import {withNetwork} from '@components/OnyxProvider'; -import Picker from '@components/Picker'; -import TextInput from '@components/TextInput'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; -import withThemeStyles, {withThemeStylesPropTypes} from '@components/withThemeStyles'; -import compose from '@libs/compose'; -import * as CurrencyUtils from '@libs/CurrencyUtils'; -import getPermittedDecimalSeparator from '@libs/getPermittedDecimalSeparator'; -import Navigation from '@libs/Navigation/Navigation'; -import * as NumberUtils from '@libs/NumberUtils'; -import * as PolicyUtils from '@libs/PolicyUtils'; -import withPolicy, {policyDefaultProps, policyPropTypes} from '@pages/workspace/withPolicy'; -import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; -import * as BankAccounts from '@userActions/BankAccounts'; -import * as Policy from '@userActions/Policy'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; - -const propTypes = { - ...policyPropTypes, - ...withLocalizePropTypes, - ...withThemeStylesPropTypes, -}; - -const defaultProps = { - reimbursementAccount: {}, - ...policyDefaultProps, -}; - -function WorkspaceRateAndUnitPage(props) { - useEffect(() => { - if (lodashGet(props, 'policy.customUnits', []).length !== 0) { - return; - } - - BankAccounts.setReimbursementAccountLoading(true); - Policy.openWorkspaceReimburseView(props.policy.id); - }, [props]); - - const unitItems = [ - {label: props.translate('common.kilometers'), value: CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS}, - {label: props.translate('common.miles'), value: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES}, - ]; - - const saveUnitAndRate = (unit, rate) => { - const distanceCustomUnit = _.find(lodashGet(props, 'policy.customUnits', {}), (customUnit) => customUnit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); - if (!distanceCustomUnit) { - return; - } - const currentCustomUnitRate = _.find(lodashGet(distanceCustomUnit, 'rates', {}), (r) => r.name === CONST.CUSTOM_UNITS.DEFAULT_RATE); - const unitID = lodashGet(distanceCustomUnit, 'customUnitID', ''); - const unitName = lodashGet(distanceCustomUnit, 'name', ''); - const rateNumValue = PolicyUtils.getNumericValue(rate, props.toLocaleDigit); - - const newCustomUnit = { - customUnitID: unitID, - name: unitName, - attributes: {unit}, - rates: { - ...currentCustomUnitRate, - rate: rateNumValue * CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET, - }, - }; - Policy.updateWorkspaceCustomUnitAndRate(props.policy.id, distanceCustomUnit, newCustomUnit, props.policy.lastModified); - }; - - const submit = (values) => { - saveUnitAndRate(values.unit, values.rate); - Keyboard.dismiss(); - Navigation.goBack(ROUTES.WORKSPACE_REIMBURSE.getRoute(props.policy.id)); - }; - - const validate = (values) => { - const errors = {}; - const decimalSeparator = props.toLocaleDigit('.'); - const outputCurrency = lodashGet(props, 'policy.outputCurrency', CONST.CURRENCY.USD); - // Allow one more decimal place for accuracy - const rateValueRegex = RegExp(String.raw`^-?\d{0,8}([${getPermittedDecimalSeparator(decimalSeparator)}]\d{1,${CurrencyUtils.getCurrencyDecimals(outputCurrency) + 1}})?$`, 'i'); - if (!rateValueRegex.test(values.rate) || values.rate === '') { - errors.rate = 'workspace.reimburse.invalidRateError'; - } else if (NumberUtils.parseFloatAnyLocale(values.rate) <= 0) { - errors.rate = 'workspace.reimburse.lowRateError'; - } - return errors; - }; - - const distanceCustomUnit = _.find(lodashGet(props, 'policy.customUnits', {}), (unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); - const distanceCustomRate = _.find(lodashGet(distanceCustomUnit, 'rates', {}), (rate) => rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE); - - return ( - - {() => ( - - - Policy.clearCustomUnitErrors(props.policy.id, lodashGet(distanceCustomUnit, 'customUnitID', ''), lodashGet(distanceCustomRate, 'customUnitRateID', '')) - } - > - - - - - - - - )} - - ); -} - -WorkspaceRateAndUnitPage.propTypes = propTypes; -WorkspaceRateAndUnitPage.defaultProps = defaultProps; -WorkspaceRateAndUnitPage.displayName = 'WorkspaceRateAndUnitPage'; - -export default compose( - withPolicy, - withLocalize, - withNetwork(), - withOnyx({ - reimbursementAccount: { - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - }, - }), - withThemeStyles, -)(WorkspaceRateAndUnitPage); diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.tsx new file mode 100644 index 000000000000..9c24b6bde023 --- /dev/null +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.tsx @@ -0,0 +1,157 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React, {useEffect} from 'react'; +import {Keyboard, View} from 'react-native'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import type {OnyxFormValuesFields} from '@components/Form/types'; +import OfflineWithFeedback from '@components/OfflineWithFeedback'; +import Picker from '@components/Picker'; +import TextInput from '@components/TextInput'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as CurrencyUtils from '@libs/CurrencyUtils'; +import getPermittedDecimalSeparator from '@libs/getPermittedDecimalSeparator'; +import Navigation from '@libs/Navigation/Navigation'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; +import * as NumberUtils from '@libs/NumberUtils'; +import * as PolicyUtils from '@libs/PolicyUtils'; +import withPolicy from '@pages/workspace/withPolicy'; +import type {WithPolicyProps} from '@pages/workspace/withPolicy'; +import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; +import * as BankAccounts from '@userActions/BankAccounts'; +import * as Policy from '@userActions/Policy'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; +import type {Unit} from '@src/types/onyx/Policy'; + +type WorkspaceRateAndUnitPageProps = WithPolicyProps & StackScreenProps; + +type ValidationError = {rate?: string}; + +function WorkspaceRateAndUnitPage({policy, route}: WorkspaceRateAndUnitPageProps) { + const {translate, toLocaleDigit} = useLocalize(); + const styles = useThemeStyles(); + + useEffect(() => { + if ((policy?.customUnits ?? []).length !== 0) { + return; + } + + BankAccounts.setReimbursementAccountLoading(true); + Policy.openWorkspaceReimburseView(policy?.id ?? ''); + }, [policy?.customUnits, policy?.id]); + + const unitItems = [ + {label: translate('common.kilometers'), value: CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS}, + {label: translate('common.miles'), value: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES}, + ]; + + const saveUnitAndRate = (unit: Unit, rate: number) => { + const distanceCustomUnit = Object.values(policy?.customUnits ?? {}).find((customUnit) => customUnit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); + if (!distanceCustomUnit) { + return; + } + const currentCustomUnitRate = Object.values(distanceCustomUnit?.rates ?? {}).find((r) => r.name === CONST.CUSTOM_UNITS.DEFAULT_RATE); + const unitID = distanceCustomUnit.customUnitID ?? ''; + const unitName = distanceCustomUnit.name ?? ''; + const rateNumValue = PolicyUtils.getNumericValue(rate, toLocaleDigit) as number; + + const newCustomUnit: Policy.NewCustomUnit = { + customUnitID: unitID, + name: unitName, + attributes: {unit}, + rates: { + ...currentCustomUnitRate, + rate: rateNumValue * CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET, + }, + }; + + Policy.updateWorkspaceCustomUnitAndRate(policy?.id ?? '', distanceCustomUnit, newCustomUnit, parseInt(policy?.lastModified ?? '', 2)); + }; + + const submit = (values: OnyxFormValuesFields) => { + saveUnitAndRate(values.unit, values.rate); + Keyboard.dismiss(); + Navigation.goBack(ROUTES.WORKSPACE_REIMBURSE.getRoute(policy?.id ?? '')); + }; + + const validate = (values: OnyxFormValuesFields): ValidationError => { + const errors: ValidationError = {}; + const decimalSeparator = toLocaleDigit('.'); + const outputCurrency = policy?.outputCurrency ?? CONST.CURRENCY.USD; + // Allow one more decimal place for accuracy + const rateValueRegex = RegExp(String.raw`^-?\d{0,8}([${getPermittedDecimalSeparator(decimalSeparator)}]\d{1,${CurrencyUtils.getCurrencyDecimals(outputCurrency) + 1}})?$`, 'i'); + if (!rateValueRegex.test(values.rate.toString()) || values.rate.toString() === 'Nan') { + errors.rate = 'workspace.reimburse.invalidRateError'; + } else if (NumberUtils.parseFloatAnyLocale(values.rate.toString()) <= 0) { + errors.rate = 'workspace.reimburse.lowRateError'; + } + return errors; + }; + + const distanceCustomUnit = Object.values(policy?.customUnits ?? {}).find((unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); + const distanceCustomRate = Object.values(distanceCustomUnit?.rates ?? {}).find((rate) => rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE); + + return ( + + {() => ( + + Policy.clearCustomUnitErrors(policy?.id ?? '', distanceCustomUnit?.customUnitID ?? '', distanceCustomRate?.customUnitRateID ?? '')} + > + + + + + + + + )} + + ); +} + +WorkspaceRateAndUnitPage.displayName = 'WorkspaceRateAndUnitPage'; + +export default withPolicy(WorkspaceRateAndUnitPage); diff --git a/src/pages/workspace/reimburse/WorkspaceReimbursePage.js b/src/pages/workspace/reimburse/WorkspaceReimbursePage.js deleted file mode 100644 index fa3849abc941..000000000000 --- a/src/pages/workspace/reimburse/WorkspaceReimbursePage.js +++ /dev/null @@ -1,42 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; -import compose from '@libs/compose'; -import withPolicy, {policyPropTypes} from '@pages/workspace/withPolicy'; -import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; -import CONST from '@src/CONST'; -import WorkspaceReimburseView from './WorkspaceReimburseView'; - -const propTypes = { - /** The route object passed to this page from the navigator */ - route: PropTypes.shape({ - /** Each parameter passed via the URL */ - params: PropTypes.shape({ - /** The policyID that is being configured */ - policyID: PropTypes.string.isRequired, - }).isRequired, - }).isRequired, - - ...policyPropTypes, - ...withLocalizePropTypes, -}; - -function WorkspaceReimbursePage(props) { - return ( - - {() => } - - ); -} - -WorkspaceReimbursePage.propTypes = propTypes; -WorkspaceReimbursePage.displayName = 'WorkspaceReimbursePage'; - -export default compose(withPolicy, withLocalize)(WorkspaceReimbursePage); diff --git a/src/pages/workspace/reimburse/WorkspaceReimbursePage.tsx b/src/pages/workspace/reimburse/WorkspaceReimbursePage.tsx new file mode 100644 index 000000000000..78bf58301db5 --- /dev/null +++ b/src/pages/workspace/reimburse/WorkspaceReimbursePage.tsx @@ -0,0 +1,33 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React from 'react'; +import useLocalize from '@hooks/useLocalize'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; +import type {WithPolicyProps} from '@pages/workspace/withPolicy'; +import withPolicy from '@pages/workspace/withPolicy'; +import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; +import CONST from '@src/CONST'; +import type SCREENS from '@src/SCREENS'; +import WorkspaceReimburseView from './WorkspaceReimburseView'; + +type WorkspaceReimbursePageProps = WithPolicyProps & StackScreenProps; + +function WorkspaceReimbursePage({route, policy}: WorkspaceReimbursePageProps) { + const {translate} = useLocalize(); + + return ( + + {() => } + + ); +} + +WorkspaceReimbursePage.displayName = 'WorkspaceReimbursePage'; + +export default withPolicy(WorkspaceReimbursePage); diff --git a/src/pages/workspace/reimburse/WorkspaceReimburseSection.js b/src/pages/workspace/reimburse/WorkspaceReimburseSection.tsx similarity index 57% rename from src/pages/workspace/reimburse/WorkspaceReimburseSection.js rename to src/pages/workspace/reimburse/WorkspaceReimburseSection.tsx index 00ef284c50ae..e4c99d79e324 100644 --- a/src/pages/workspace/reimburse/WorkspaceReimburseSection.js +++ b/src/pages/workspace/reimburse/WorkspaceReimburseSection.tsx @@ -1,45 +1,40 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React, {useEffect, useState} from 'react'; import {ActivityIndicator, View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; import ConnectBankAccountButton from '@components/ConnectBankAccountButton'; import * as Expensicons from '@components/Icon/Expensicons'; import * as Illustrations from '@components/Icon/Illustrations'; -import networkPropTypes from '@components/networkPropTypes'; import Section from '@components/Section'; import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; import usePrevious from '@hooks/usePrevious'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import BankAccount from '@libs/models/BankAccount'; -import * as ReimbursementAccountProps from '@pages/ReimbursementAccount/reimbursementAccountPropTypes'; import * as Link from '@userActions/Link'; +import type * as OnyxTypes from '@src/types/onyx'; -const propTypes = { +type WorkspaceReimburseSectionProps = { /** Policy values needed in the component */ - policy: PropTypes.shape({ - id: PropTypes.string, - }).isRequired, + policy: OnyxEntry; /** Bank account attached to free plan */ - reimbursementAccount: ReimbursementAccountProps.reimbursementAccountPropTypes.isRequired, - - /** Information about the network */ - network: networkPropTypes.isRequired, - - /** Returns translated string for given locale and phrase */ - translate: PropTypes.func.isRequired, + reimbursementAccount: OnyxEntry; }; -function WorkspaceReimburseSection(props) { +function WorkspaceReimburseSection({policy, reimbursementAccount}: WorkspaceReimburseSectionProps) { const theme = useTheme(); const styles = useThemeStyles(); + const {translate} = useLocalize(); const [shouldShowLoadingSpinner, setShouldShowLoadingSpinner] = useState(true); - const achState = lodashGet(props.reimbursementAccount, 'achData.state', ''); + const achState = reimbursementAccount?.achData?.state ?? ''; const hasVBA = achState === BankAccount.STATE.OPEN; - const reimburseReceiptsUrl = `reports?policyID=${props.policy.id}&from=all&type=expense&showStates=Archived&isAdvancedFilterMode=true`; - const isLoading = lodashGet(props.reimbursementAccount, 'isLoading', false); + const policyId = policy?.id ?? ''; + const reimburseReceiptsUrl = `reports?policyID=${policyId}&from=all&type=expense&showStates=Archived&isAdvancedFilterMode=true`; + const isLoading = reimbursementAccount?.isLoading ?? false; const prevIsLoading = usePrevious(isLoading); + const {isOffline} = useNetwork(); useEffect(() => { if (prevIsLoading === isLoading) { @@ -48,14 +43,14 @@ function WorkspaceReimburseSection(props) { setShouldShowLoadingSpinner(isLoading); }, [prevIsLoading, isLoading]); - if (props.network.isOffline) { + if (isOffline) { return (
- {`${props.translate('common.youAppearToBeOffline')} ${props.translate('common.thisFeatureRequiresInternet')}`} + {`${translate('common.youAppearToBeOffline')} ${translate('common.thisFeatureRequiresInternet')}`}
); @@ -76,35 +71,35 @@ function WorkspaceReimburseSection(props) { <> {hasVBA ? (
Link.openOldDotLink(reimburseReceiptsUrl), icon: Expensicons.Bank, shouldShowRightIcon: true, iconRight: Expensicons.NewWindow, - wrapperStyle: [styles.cardMenuItem], + wrapperStyle: styles.cardMenuItem, link: () => Link.buildOldDotURL(reimburseReceiptsUrl), }, ]} > - - {props.translate('workspace.reimburse.fastReimbursementsVBACopy')} + + {translate('workspace.reimburse.fastReimbursementsVBACopy')}
) : (
- - {props.translate('workspace.reimburse.unlockNoVBACopy')} + + {translate('workspace.reimburse.unlockNoVBACopy')}
)} @@ -112,7 +107,6 @@ function WorkspaceReimburseSection(props) { ); } -WorkspaceReimburseSection.propTypes = propTypes; WorkspaceReimburseSection.displayName = 'WorkspaceReimburseSection'; export default WorkspaceReimburseSection; diff --git a/src/pages/workspace/reimburse/WorkspaceReimburseView.js b/src/pages/workspace/reimburse/WorkspaceReimburseView.tsx similarity index 53% rename from src/pages/workspace/reimburse/WorkspaceReimburseView.js rename to src/pages/workspace/reimburse/WorkspaceReimburseView.tsx index 23136064fc2b..ea9ee9e8a421 100644 --- a/src/pages/workspace/reimburse/WorkspaceReimburseView.js +++ b/src/pages/workspace/reimburse/WorkspaceReimburseView.tsx @@ -1,85 +1,57 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useState} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; +import type {OnyxEntry} from 'react-native-onyx'; import CopyTextToClipboard from '@components/CopyTextToClipboard'; import * as Expensicons from '@components/Icon/Expensicons'; import * as Illustrations from '@components/Icon/Illustrations'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; -import networkPropTypes from '@components/networkPropTypes'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; -import {withNetwork} from '@components/OnyxProvider'; import Section from '@components/Section'; import Text from '@components/Text'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; -import * as ReimbursementAccountProps from '@pages/ReimbursementAccount/reimbursementAccountPropTypes'; import * as BankAccounts from '@userActions/BankAccounts'; import * as Link from '@userActions/Link'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type * as OnyxTypes from '@src/types/onyx'; +import type {Unit} from '@src/types/onyx/Policy'; import WorkspaceReimburseSection from './WorkspaceReimburseSection'; -const propTypes = { - /** Policy values needed in the component */ - policy: PropTypes.shape({ - id: PropTypes.string, - customUnits: PropTypes.objectOf( - PropTypes.shape({ - customUnitID: PropTypes.string, - name: PropTypes.string, - attributes: PropTypes.shape({ - unit: PropTypes.string, - }), - rates: PropTypes.objectOf( - PropTypes.shape({ - customUnitRateID: PropTypes.string, - name: PropTypes.string, - rate: PropTypes.number, - }), - ), - }), - ), - outputCurrency: PropTypes.string, - lastModified: PropTypes.number, - }).isRequired, - +type WorkspaceReimburseViewOnyxProps = { /** From Onyx */ /** Bank account attached to free plan */ - reimbursementAccount: ReimbursementAccountProps.reimbursementAccountPropTypes, - - /** Information about the network */ - network: networkPropTypes.isRequired, - - ...withLocalizePropTypes, + reimbursementAccount: OnyxEntry; }; -const defaultProps = { - reimbursementAccount: ReimbursementAccountProps.reimbursementAccountDefaultProps, +type WorkspaceReimburseViewProps = WorkspaceReimburseViewOnyxProps & { + /** Policy values needed in the component */ + policy: OnyxEntry; }; -function WorkspaceReimburseView(props) { +function WorkspaceReimburseView({policy, reimbursementAccount}: WorkspaceReimburseViewProps) { const styles = useThemeStyles(); - const [currentRatePerUnit, setCurrentRatePerUnit] = useState(''); - const viewAllReceiptsUrl = `expenses?policyIDList=${props.policy.id}&billableReimbursable=reimbursable&submitterEmail=%2B%2B`; - const distanceCustomUnit = _.find(lodashGet(props.policy, 'customUnits', {}), (unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); - const distanceCustomRate = _.find(lodashGet(distanceCustomUnit, 'rates', {}), (rate) => rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE); - const {translate, toLocaleDigit} = props; + const [currentRatePerUnit, setCurrentRatePerUnit] = useState(''); + const viewAllReceiptsUrl = `expenses?policyIDList=${policy?.id ?? ''}&billableReimbursable=reimbursable&submitterEmail=%2B%2B`; + const distanceCustomUnit = Object.values(policy?.customUnits ?? {}).find((unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); + const distanceCustomRate = Object.values(distanceCustomUnit?.rates ?? {}).find((rate) => rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE); + const {translate, toLocaleDigit} = useLocalize(); + const {isOffline} = useNetwork(); - const getUnitLabel = useCallback((value) => translate(`common.${value}`), [translate]); + const getUnitLabel = useCallback((value: Unit): string => translate(`common.${value}`), [translate]); const getCurrentRatePerUnitLabel = useCallback(() => { - const customUnitRate = _.find(lodashGet(distanceCustomUnit, 'rates', '{}'), (rate) => rate && rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE); - const currentUnit = getUnitLabel(lodashGet(distanceCustomUnit, 'attributes.unit', CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES)); - const currentRate = PolicyUtils.getUnitRateValue(customUnitRate, toLocaleDigit); + const customUnitRate = Object.values(distanceCustomUnit?.rates ?? {}).find((rate) => rate && rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE); + const currentUnit = getUnitLabel(distanceCustomUnit?.attributes.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES); + const currentRate = PolicyUtils.getUnitRateValue(toLocaleDigit, customUnitRate); const perWord = translate('common.per'); + return `${currentRate} ${perWord} ${currentUnit}`; }, [translate, distanceCustomUnit, toLocaleDigit, getUnitLabel]); @@ -88,19 +60,19 @@ function WorkspaceReimburseView(props) { // openWorkspaceReimburseView uses API.read which will not make the request until all WRITE requests in the sequential queue have finished responding, so there would be a delay in // updating Onyx with the optimistic data. BankAccounts.setReimbursementAccountLoading(true); - Policy.openWorkspaceReimburseView(props.policy.id); - }, [props.policy.id]); + Policy.openWorkspaceReimburseView(policy?.id ?? ''); + }, [policy?.id]); useEffect(() => { - if (props.network.isOffline) { + if (isOffline) { return; } fetchData(); - }, [props.network.isOffline, fetchData]); + }, [isOffline, fetchData]); useEffect(() => { setCurrentRatePerUnit(getCurrentRatePerUnitLabel()); - }, [props.policy.customUnits, getCurrentRatePerUnitLabel]); + }, [policy?.customUnits, getCurrentRatePerUnitLabel]); return ( <> @@ -114,7 +86,7 @@ function WorkspaceReimburseView(props) { icon: Expensicons.Receipt, shouldShowRightIcon: true, iconRight: Expensicons.NewWindow, - wrapperStyle: [styles.cardMenuItem], + wrapperStyle: styles.cardMenuItem, link: () => Link.buildOldDotURL(viewAllReceiptsUrl), }, ]} @@ -124,7 +96,7 @@ function WorkspaceReimburseView(props) { {translate('workspace.reimburse.captureNoVBACopyBeforeEmail')} {translate('workspace.reimburse.captureNoVBACopyAfterEmail')} @@ -135,44 +107,36 @@ function WorkspaceReimburseView(props) { title={translate('workspace.reimburse.trackDistance')} icon={Illustrations.TrackShoe} > - + {translate('workspace.reimburse.trackDistanceCopy')} Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(props.policy.id))} + onPress={() => Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(policy?.id ?? ''))} wrapperStyle={[styles.mhn5, styles.wAuto]} - brickRoadIndicator={(lodashGet(distanceCustomUnit, 'errors') || lodashGet(distanceCustomRate, 'errors')) && CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR} + brickRoadIndicator={(distanceCustomUnit?.errors ?? distanceCustomRate?.errors) && CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR} /> ); } -WorkspaceReimburseView.defaultProps = defaultProps; -WorkspaceReimburseView.propTypes = propTypes; WorkspaceReimburseView.displayName = 'WorkspaceReimburseView'; -export default compose( - withLocalize, - withNetwork(), - withOnyx({ - reimbursementAccount: { - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - }, - }), -)(WorkspaceReimburseView); +export default withOnyx({ + reimbursementAccount: { + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + }, +})(WorkspaceReimburseView); diff --git a/src/types/onyx/Form.ts b/src/types/onyx/Form.ts index 9c6d52a1020d..5c0bb096b42f 100644 --- a/src/types/onyx/Form.ts +++ b/src/types/onyx/Form.ts @@ -1,7 +1,8 @@ import type * as OnyxCommon from './OnyxCommon'; import type PersonalBankAccount from './PersonalBankAccount'; +import type {Unit} from './Policy'; -type FormValueType = string | boolean | Date | OnyxCommon.Errors; +type FormValueType = string | boolean | Date | number | OnyxCommon.Errors; type BaseForm = { /** Controls the loading state of the form */ @@ -59,6 +60,11 @@ type PersonalBankAccountForm = Form; type ReportFieldEditForm = Form>; +type RateUnitForm = Form<{ + unit: Unit; + rate: number; +}>; + export default Form; export type { @@ -73,4 +79,5 @@ export type { IntroSchoolPrincipalForm, PersonalBankAccountForm, ReportFieldEditForm, + RateUnitForm, }; diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index fe50bbb497d2..ecc8b1797654 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -10,7 +10,7 @@ type Rate = { currency?: string; customUnitRateID?: string; errors?: OnyxCommon.Errors; - pendingAction?: string; + pendingAction?: OnyxCommon.PendingAction; }; type Attributes = { @@ -22,7 +22,7 @@ type CustomUnit = { customUnitID: string; attributes: Attributes; rates: Record; - pendingAction?: string; + pendingAction?: OnyxCommon.PendingAction; errors?: OnyxCommon.Errors; }; @@ -156,4 +156,4 @@ type Policy = { export default Policy; -export type {Unit, CustomUnit}; +export type {Unit, CustomUnit, Rate, Attributes}; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 64eec736b5bf..a2cc5a878b47 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -9,7 +9,7 @@ import type Credentials from './Credentials'; import type Currency from './Currency'; import type CustomStatusDraft from './CustomStatusDraft'; import type Download from './Download'; -import type {AddDebitCardForm, DateOfBirthForm, DisplayNameForm, IKnowATeacherForm, IntroSchoolPrincipalForm, NewRoomForm, PrivateNotesForm, ReportFieldEditForm} from './Form'; +import type {AddDebitCardForm, DateOfBirthForm, DisplayNameForm, IKnowATeacherForm, IntroSchoolPrincipalForm, NewRoomForm, PrivateNotesForm, RateUnitForm, ReportFieldEditForm} from './Form'; import type Form from './Form'; import type FrequentlyUsedEmoji from './FrequentlyUsedEmoji'; import type {FundList} from './Fund'; @@ -151,5 +151,6 @@ export type { IKnowATeacherForm, IntroSchoolPrincipalForm, PrivateNotesForm, + RateUnitForm, ReportFieldEditForm, }; From f51dfad8cf298537861d378a9336e4598115ce5a Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Thu, 1 Feb 2024 10:15:28 +0000 Subject: [PATCH 010/565] Revert "[TS Migration] Migrate WorkspaceCard to Typescript" This reverts commit 4feb09259b4501462807da3bb197aa705e3c1d4f. --- .../workspace/card/WorkspaceCardNoVBAView.js | 49 +++++++++++++++++++ .../workspace/card/WorkspaceCardNoVBAView.tsx | 40 --------------- src/pages/workspace/card/WorkspaceCardPage.js | 47 ++++++++++++++++++ .../workspace/card/WorkspaceCardPage.tsx | 39 --------------- ...iew.tsx => WorkspaceCardVBANoECardView.js} | 48 +++++++++++------- ...w.tsx => WorkspaceCardVBAWithECardView.js} | 39 +++++++-------- 6 files changed, 145 insertions(+), 117 deletions(-) create mode 100644 src/pages/workspace/card/WorkspaceCardNoVBAView.js delete mode 100644 src/pages/workspace/card/WorkspaceCardNoVBAView.tsx create mode 100644 src/pages/workspace/card/WorkspaceCardPage.js delete mode 100644 src/pages/workspace/card/WorkspaceCardPage.tsx rename src/pages/workspace/card/{WorkspaceCardVBANoECardView.tsx => WorkspaceCardVBANoECardView.js} (53%) rename src/pages/workspace/card/{WorkspaceCardVBAWithECardView.tsx => WorkspaceCardVBAWithECardView.js} (67%) diff --git a/src/pages/workspace/card/WorkspaceCardNoVBAView.js b/src/pages/workspace/card/WorkspaceCardNoVBAView.js new file mode 100644 index 000000000000..3233f8ea7e23 --- /dev/null +++ b/src/pages/workspace/card/WorkspaceCardNoVBAView.js @@ -0,0 +1,49 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import {View} from 'react-native'; +import ConnectBankAccountButton from '@components/ConnectBankAccountButton'; +import * as Illustrations from '@components/Icon/Illustrations'; +import Section from '@components/Section'; +import Text from '@components/Text'; +import UnorderedList from '@components/UnorderedList'; +import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; + +const propTypes = { + /** The policy ID currently being configured */ + policyID: PropTypes.string.isRequired, + + ...withLocalizePropTypes, +}; + +function WorkspaceCardNoVBAView(props) { + const styles = useThemeStyles(); + return ( +
+ + {props.translate('workspace.card.noVBACopy')} + + + + +
+ ); +} + +WorkspaceCardNoVBAView.propTypes = propTypes; +WorkspaceCardNoVBAView.displayName = 'WorkspaceCardNoVBAView'; + +export default withLocalize(WorkspaceCardNoVBAView); diff --git a/src/pages/workspace/card/WorkspaceCardNoVBAView.tsx b/src/pages/workspace/card/WorkspaceCardNoVBAView.tsx deleted file mode 100644 index 322d433a8e62..000000000000 --- a/src/pages/workspace/card/WorkspaceCardNoVBAView.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react'; -import {View} from 'react-native'; -import ConnectBankAccountButton from '@components/ConnectBankAccountButton'; -import * as Illustrations from '@components/Icon/Illustrations'; -import Section from '@components/Section'; -import Text from '@components/Text'; -import UnorderedList from '@components/UnorderedList'; -import useLocalize from '@hooks/useLocalize'; -import useThemeStyles from '@hooks/useThemeStyles'; - -type WorkspaceCardNoVBAViewProps = { - /** The policy ID currently being configured */ - policyID: string; -}; - -function WorkspaceCardNoVBAView({policyID}: WorkspaceCardNoVBAViewProps) { - const styles = useThemeStyles(); - const {translate} = useLocalize(); - - return ( -
- - {translate('workspace.card.noVBACopy')} - - - - -
- ); -} - -WorkspaceCardNoVBAView.displayName = 'WorkspaceCardNoVBAView'; - -export default WorkspaceCardNoVBAView; diff --git a/src/pages/workspace/card/WorkspaceCardPage.js b/src/pages/workspace/card/WorkspaceCardPage.js new file mode 100644 index 000000000000..55220b85ce63 --- /dev/null +++ b/src/pages/workspace/card/WorkspaceCardPage.js @@ -0,0 +1,47 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; +import CONST from '@src/CONST'; +import WorkspaceCardNoVBAView from './WorkspaceCardNoVBAView'; +import WorkspaceCardVBANoECardView from './WorkspaceCardVBANoECardView'; +import WorkspaceCardVBAWithECardView from './WorkspaceCardVBAWithECardView'; + +const propTypes = { + /** The route object passed to this page from the navigator */ + route: PropTypes.shape({ + /** Each parameter passed via the URL */ + params: PropTypes.shape({ + /** The policyID that is being configured */ + policyID: PropTypes.string.isRequired, + }).isRequired, + }).isRequired, + + ...withLocalizePropTypes, +}; + +function WorkspaceCardPage(props) { + return ( + + {(hasVBA, policyID, isUsingECard) => ( + <> + {!hasVBA && } + + {hasVBA && !isUsingECard && } + + {hasVBA && isUsingECard && } + + )} + + ); +} + +WorkspaceCardPage.propTypes = propTypes; +WorkspaceCardPage.displayName = 'WorkspaceCardPage'; + +export default withLocalize(WorkspaceCardPage); diff --git a/src/pages/workspace/card/WorkspaceCardPage.tsx b/src/pages/workspace/card/WorkspaceCardPage.tsx deleted file mode 100644 index f6e368db84ea..000000000000 --- a/src/pages/workspace/card/WorkspaceCardPage.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import type {StackScreenProps} from '@react-navigation/stack'; -import React from 'react'; -import useLocalize from '@hooks/useLocalize'; -import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; -import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; -import CONST from '@src/CONST'; -import type SCREENS from '@src/SCREENS'; -import WorkspaceCardNoVBAView from './WorkspaceCardNoVBAView'; -import WorkspaceCardVBANoECardView from './WorkspaceCardVBANoECardView'; -import WorkspaceCardVBAWithECardView from './WorkspaceCardVBAWithECardView'; - -type WorkspaceCardPageProps = StackScreenProps; - -function WorkspaceCardPage({route}: WorkspaceCardPageProps) { - const {translate} = useLocalize(); - - return ( - - {(hasVBA: boolean, policyID: string, isUsingECard: boolean) => ( - <> - {false && } - - {false && } - - {true && } - - )} - - ); -} - -WorkspaceCardPage.displayName = 'WorkspaceCardPage'; - -export default WorkspaceCardPage; diff --git a/src/pages/workspace/card/WorkspaceCardVBANoECardView.tsx b/src/pages/workspace/card/WorkspaceCardVBANoECardView.js similarity index 53% rename from src/pages/workspace/card/WorkspaceCardVBANoECardView.tsx rename to src/pages/workspace/card/WorkspaceCardVBANoECardView.js index 3c9b773d6994..970cd9105368 100644 --- a/src/pages/workspace/card/WorkspaceCardVBANoECardView.tsx +++ b/src/pages/workspace/card/WorkspaceCardVBANoECardView.js @@ -1,6 +1,5 @@ import React from 'react'; import {View} from 'react-native'; -import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -8,37 +7,45 @@ import * as Illustrations from '@components/Icon/Illustrations'; import Section from '@components/Section'; import Text from '@components/Text'; import UnorderedList from '@components/UnorderedList'; -import useLocalize from '@hooks/useLocalize'; +import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import compose from '@libs/compose'; +import userPropTypes from '@pages/settings/userPropTypes'; import * as Link from '@userActions/Link'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {User} from '@src/types/onyx'; -type WorkspaceCardVBANoECardViewOnyxProps = { +const propTypes = { /** Information about the logged in user's account */ - user: OnyxEntry; + user: userPropTypes, + + ...withLocalizePropTypes, }; -type WorkspaceCardVBANoECardViewProps = WorkspaceCardVBANoECardViewOnyxProps; +const defaultProps = { + user: {}, +}; -function WorkspaceCardVBANoECardView({user}: WorkspaceCardVBANoECardViewProps) { +function WorkspaceCardVBANoECardView(props) { const styles = useThemeStyles(); - const {translate} = useLocalize(); - return ( <>
- {Boolean(user?.isCheckingDomain) && {translate('workspace.card.checkingDomain')}} + {Boolean(props.user.isCheckingDomain) && {props.translate('workspace.card.checkingDomain')}} ); } +WorkspaceCardVBANoECardView.propTypes = propTypes; +WorkspaceCardVBANoECardView.defaultProps = defaultProps; WorkspaceCardVBANoECardView.displayName = 'WorkspaceCardVBANoECardView'; -export default withOnyx({ - user: { - key: ONYXKEYS.USER, - }, -})(WorkspaceCardVBANoECardView); +export default compose( + withLocalize, + withOnyx({ + user: { + key: ONYXKEYS.USER, + }, + }), +)(WorkspaceCardVBANoECardView); diff --git a/src/pages/workspace/card/WorkspaceCardVBAWithECardView.tsx b/src/pages/workspace/card/WorkspaceCardVBAWithECardView.js similarity index 67% rename from src/pages/workspace/card/WorkspaceCardVBAWithECardView.tsx rename to src/pages/workspace/card/WorkspaceCardVBAWithECardView.js index a53a44fa52cf..40ecd80b8e6e 100644 --- a/src/pages/workspace/card/WorkspaceCardVBAWithECardView.tsx +++ b/src/pages/workspace/card/WorkspaceCardVBAWithECardView.js @@ -2,35 +2,28 @@ import React from 'react'; import {View} from 'react-native'; import * as Expensicons from '@components/Icon/Expensicons'; import * as Illustrations from '@components/Icon/Illustrations'; -import type {MenuItemWithLink} from '@components/MenuItemList'; import Section from '@components/Section'; import Text from '@components/Text'; import UnorderedList from '@components/UnorderedList'; -import useLocalize from '@hooks/useLocalize'; +import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as Link from '@userActions/Link'; -type MenuLinks = { - ISSUE_AND_MANAGE_CARDS: string; - RECONCILE_CARDS: string; - SETTLEMENT_FREQUENCY: string; +const propTypes = { + ...withLocalizePropTypes, }; -type MenuItems = MenuItemWithLink[]; - -const MENU_LINKS: MenuLinks = { +const MENU_LINKS = { ISSUE_AND_MANAGE_CARDS: 'domain_companycards', RECONCILE_CARDS: encodeURI('domain_companycards?param={"section":"cardReconciliation"}'), SETTLEMENT_FREQUENCY: encodeURI('domain_companycards?param={"section":"configureSettings"}'), }; -function WorkspaceCardVBAWithECardView() { +function WorkspaceCardVBAWithECardView(props) { const styles = useThemeStyles(); - const {translate} = useLocalize(); - - const menuItems: MenuItems = [ + const menuItems = [ { - title: translate('workspace.common.issueAndManageCards'), + title: props.translate('workspace.common.issueAndManageCards'), onPress: () => Link.openOldDotLink(MENU_LINKS.ISSUE_AND_MANAGE_CARDS), icon: Expensicons.ExpensifyCard, shouldShowRightIcon: true, @@ -39,7 +32,7 @@ function WorkspaceCardVBAWithECardView() { link: () => Link.buildOldDotURL(MENU_LINKS.ISSUE_AND_MANAGE_CARDS), }, { - title: translate('workspace.common.reconcileCards'), + title: props.translate('workspace.common.reconcileCards'), onPress: () => Link.openOldDotLink(MENU_LINKS.RECONCILE_CARDS), icon: Expensicons.ReceiptSearch, shouldShowRightIcon: true, @@ -48,7 +41,7 @@ function WorkspaceCardVBAWithECardView() { link: () => Link.buildOldDotURL(MENU_LINKS.RECONCILE_CARDS), }, { - title: translate('workspace.common.settlementFrequency'), + title: props.translate('workspace.common.settlementFrequency'), onPress: () => Link.openOldDotLink(MENU_LINKS.SETTLEMENT_FREQUENCY), icon: Expensicons.Gear, shouldShowRightIcon: true, @@ -60,23 +53,29 @@ function WorkspaceCardVBAWithECardView() { return (
- {translate('workspace.card.VBAWithECardCopy')} + {props.translate('workspace.card.VBAWithECardCopy')}
); } +WorkspaceCardVBAWithECardView.propTypes = propTypes; WorkspaceCardVBAWithECardView.displayName = 'WorkspaceCardVBAWithECardView'; -export default WorkspaceCardVBAWithECardView; +export default withLocalize(WorkspaceCardVBAWithECardView); From 3a7ad1e1314b598cd5acdd012ac15ba4890a2112 Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Thu, 1 Feb 2024 10:20:07 +0000 Subject: [PATCH 011/565] [TS migration][WorkspaceReimburse] Missing imports --- src/libs/actions/Policy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 48fd8944f196..c8f982a0c6c6 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -37,7 +37,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PersonalDetailsList, Policy, PolicyMember, PolicyTags, RecentlyUsedCategories, RecentlyUsedTags, ReimbursementAccount, Report, ReportAction, Transaction} from '@src/types/onyx'; import type {Errors} from '@src/types/onyx/OnyxCommon'; -import type {CustomUnit} from '@src/types/onyx/Policy'; +import type {Attributes, CustomUnit, Rate} from '@src/types/onyx/Policy'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; type AnnounceRoomMembersOnyxData = { From d11c98a08b95289c93bf2824f9aff85ee9741c1d Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Fri, 2 Feb 2024 09:39:11 +0000 Subject: [PATCH 012/565] [TS migration][WorkspaceReimburse] Code improvements --- .../parameters/UpdateWorkspaceCustomUnitAndRateParams.ts | 2 +- src/libs/PolicyUtils.ts | 2 +- src/libs/actions/Policy.ts | 2 +- .../workspace/reimburse/WorkspaceRateAndUnitPage.tsx | 8 ++++---- src/pages/workspace/reimburse/WorkspaceReimburseView.tsx | 1 - src/types/onyx/Form.ts | 2 +- 6 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/libs/API/parameters/UpdateWorkspaceCustomUnitAndRateParams.ts b/src/libs/API/parameters/UpdateWorkspaceCustomUnitAndRateParams.ts index 22bbd20c7308..02186cddd32a 100644 --- a/src/libs/API/parameters/UpdateWorkspaceCustomUnitAndRateParams.ts +++ b/src/libs/API/parameters/UpdateWorkspaceCustomUnitAndRateParams.ts @@ -1,6 +1,6 @@ type UpdateWorkspaceCustomUnitAndRateParams = { policyID: string; - lastModified: number; + lastModified?: number; customUnit: string; customUnitRate: string; }; diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index a812cab24402..b4270867f9b2 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -50,7 +50,7 @@ function hasCustomUnitsError(policy: OnyxEntry): boolean { return Object.keys(policy?.customUnits?.errors ?? {}).length > 0; } -function getNumericValue(value: number, toLocaleDigit: (arg: string) => string): number | string { +function getNumericValue(value: number | string, toLocaleDigit: (arg: string) => string): number | string { const numValue = parseFloat(value.toString().replace(toLocaleDigit('.'), '.')); if (Number.isNaN(numValue)) { return NaN; diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index c8f982a0c6c6..1c9295dc08bc 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -939,7 +939,7 @@ function hideWorkspaceAlertMessage(policyID: string) { Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {alertMessage: ''}); } -function updateWorkspaceCustomUnitAndRate(policyID: string, currentCustomUnit: CustomUnit, newCustomUnit: NewCustomUnit, lastModified: number) { +function updateWorkspaceCustomUnitAndRate(policyID: string, currentCustomUnit: CustomUnit, newCustomUnit: NewCustomUnit, lastModified?: string) { if (!currentCustomUnit.customUnitID || !newCustomUnit?.customUnitID || !newCustomUnit.rates?.customUnitRateID) { return; } diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.tsx index 9c24b6bde023..1f5d7d854046 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.tsx @@ -48,7 +48,7 @@ function WorkspaceRateAndUnitPage({policy, route}: WorkspaceRateAndUnitPageProps {label: translate('common.miles'), value: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES}, ]; - const saveUnitAndRate = (unit: Unit, rate: number) => { + const saveUnitAndRate = (unit: Unit, rate: string) => { const distanceCustomUnit = Object.values(policy?.customUnits ?? {}).find((customUnit) => customUnit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); if (!distanceCustomUnit) { return; @@ -68,7 +68,7 @@ function WorkspaceRateAndUnitPage({policy, route}: WorkspaceRateAndUnitPageProps }, }; - Policy.updateWorkspaceCustomUnitAndRate(policy?.id ?? '', distanceCustomUnit, newCustomUnit, parseInt(policy?.lastModified ?? '', 2)); + Policy.updateWorkspaceCustomUnitAndRate(policy?.id ?? '', distanceCustomUnit, newCustomUnit, policy?.lastModified); }; const submit = (values: OnyxFormValuesFields) => { @@ -83,9 +83,9 @@ function WorkspaceRateAndUnitPage({policy, route}: WorkspaceRateAndUnitPageProps const outputCurrency = policy?.outputCurrency ?? CONST.CURRENCY.USD; // Allow one more decimal place for accuracy const rateValueRegex = RegExp(String.raw`^-?\d{0,8}([${getPermittedDecimalSeparator(decimalSeparator)}]\d{1,${CurrencyUtils.getCurrencyDecimals(outputCurrency) + 1}})?$`, 'i'); - if (!rateValueRegex.test(values.rate.toString()) || values.rate.toString() === 'Nan') { + if (!rateValueRegex.test(values.rate) || values.rate === '') { errors.rate = 'workspace.reimburse.invalidRateError'; - } else if (NumberUtils.parseFloatAnyLocale(values.rate.toString()) <= 0) { + } else if (NumberUtils.parseFloatAnyLocale(values.rate) <= 0) { errors.rate = 'workspace.reimburse.lowRateError'; } return errors; diff --git a/src/pages/workspace/reimburse/WorkspaceReimburseView.tsx b/src/pages/workspace/reimburse/WorkspaceReimburseView.tsx index ea9ee9e8a421..375fa5b8d439 100644 --- a/src/pages/workspace/reimburse/WorkspaceReimburseView.tsx +++ b/src/pages/workspace/reimburse/WorkspaceReimburseView.tsx @@ -25,7 +25,6 @@ import type {Unit} from '@src/types/onyx/Policy'; import WorkspaceReimburseSection from './WorkspaceReimburseSection'; type WorkspaceReimburseViewOnyxProps = { - /** From Onyx */ /** Bank account attached to free plan */ reimbursementAccount: OnyxEntry; }; diff --git a/src/types/onyx/Form.ts b/src/types/onyx/Form.ts index 5c0bb096b42f..1b119460b344 100644 --- a/src/types/onyx/Form.ts +++ b/src/types/onyx/Form.ts @@ -62,7 +62,7 @@ type ReportFieldEditForm = Form>; type RateUnitForm = Form<{ unit: Unit; - rate: number; + rate: string; }>; export default Form; From 9ff4961a40c9eb4125207b5aa89852f68f9f2a81 Mon Sep 17 00:00:00 2001 From: Aswin S Date: Wed, 7 Feb 2024 07:55:32 +0530 Subject: [PATCH 013/565] fix: animated background getting cropped on android --- src/pages/home/report/AnimatedEmptyStateBackground.tsx | 2 +- src/pages/home/report/ReportActionItem.js | 4 ++-- src/styles/utils/index.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/home/report/AnimatedEmptyStateBackground.tsx b/src/pages/home/report/AnimatedEmptyStateBackground.tsx index 7e259b7473cf..5e91aada8464 100644 --- a/src/pages/home/report/AnimatedEmptyStateBackground.tsx +++ b/src/pages/home/report/AnimatedEmptyStateBackground.tsx @@ -13,7 +13,7 @@ function AnimatedEmptyStateBackground() { const StyleUtils = useStyleUtils(); const {windowWidth, isSmallScreenWidth} = useWindowDimensions(); const illustrations = useThemeIllustrations(); - const IMAGE_OFFSET_X = windowWidth / 2; + const IMAGE_OFFSET_X = 0; // If window width is greater than the max background width, repeat the background image const maxBackgroundWidth = variables.sideBarWidth + CONST.EMPTY_STATE_BACKGROUND.ASPECT_RATIO * CONST.EMPTY_STATE_BACKGROUND.WIDE_SCREEN.IMAGE_HEIGHT; diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 435c086d913f..75e9e1ca8627 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -583,7 +583,7 @@ function ReportActionItem(props) { if (ReportUtils.isTaskReport(props.report)) { if (ReportUtils.isCanceledTaskReport(props.report, parentReportAction)) { return ( - <> + - + ); } return ( diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index b3b4924ebb19..8657594f0a10 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -701,7 +701,7 @@ function getReportWelcomeBackgroundImageStyle(isSmallScreenWidth: boolean, isMon if (isSmallScreenWidth) { return { height: emptyStateBackground.SMALL_SCREEN.IMAGE_HEIGHT, - width: '200%', + width: '100%', position: 'absolute', }; } From 34135a2c023764b456df2fe86a0c95af2cb26022 Mon Sep 17 00:00:00 2001 From: ruben-rebelo Date: Thu, 8 Feb 2024 15:54:07 +0000 Subject: [PATCH 014/565] [TS migration][WorkspaceReimburse] Code improvements --- .../UpdateWorkspaceCustomUnitAndRateParams.ts | 2 +- .../reimburse/WorkspaceRateAndUnitPage.tsx | 4 ++-- .../workspace/reimburse/WorkspaceReimburseView.tsx | 13 ------------- src/types/onyx/Form.ts | 2 +- 4 files changed, 4 insertions(+), 17 deletions(-) diff --git a/src/libs/API/parameters/UpdateWorkspaceCustomUnitAndRateParams.ts b/src/libs/API/parameters/UpdateWorkspaceCustomUnitAndRateParams.ts index a94089512a36..010bcaa1e60a 100644 --- a/src/libs/API/parameters/UpdateWorkspaceCustomUnitAndRateParams.ts +++ b/src/libs/API/parameters/UpdateWorkspaceCustomUnitAndRateParams.ts @@ -1,6 +1,6 @@ type UpdateWorkspaceCustomUnitAndRateParams = { policyID: string; - lastModified?: number | string; + lastModified?: string; customUnit: string; customUnitRate: string; }; diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.tsx index 1f5d7d854046..6011c303f535 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.tsx @@ -56,7 +56,7 @@ function WorkspaceRateAndUnitPage({policy, route}: WorkspaceRateAndUnitPageProps const currentCustomUnitRate = Object.values(distanceCustomUnit?.rates ?? {}).find((r) => r.name === CONST.CUSTOM_UNITS.DEFAULT_RATE); const unitID = distanceCustomUnit.customUnitID ?? ''; const unitName = distanceCustomUnit.name ?? ''; - const rateNumValue = PolicyUtils.getNumericValue(rate, toLocaleDigit) as number; + const rateNumValue = PolicyUtils.getNumericValue(rate, toLocaleDigit); const newCustomUnit: Policy.NewCustomUnit = { customUnitID: unitID, @@ -64,7 +64,7 @@ function WorkspaceRateAndUnitPage({policy, route}: WorkspaceRateAndUnitPageProps attributes: {unit}, rates: { ...currentCustomUnitRate, - rate: rateNumValue * CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET, + rate: Number(rateNumValue) * CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET, }, }; diff --git a/src/pages/workspace/reimburse/WorkspaceReimburseView.tsx b/src/pages/workspace/reimburse/WorkspaceReimburseView.tsx index a61313b8b1e3..cb0b5bce7fc3 100644 --- a/src/pages/workspace/reimburse/WorkspaceReimburseView.tsx +++ b/src/pages/workspace/reimburse/WorkspaceReimburseView.tsx @@ -127,19 +127,6 @@ function WorkspaceReimburseView({policy, reimbursementAccount}: WorkspaceReimbur /> - - Navigation.navigate(ROUTES.WORKSPACE_RATE_AND_UNIT.getRoute(policy?.id ?? ''))} - wrapperStyle={[styles.mhn5, styles.wAuto]} - brickRoadIndicator={(distanceCustomUnit?.errors ?? distanceCustomRate?.errors) && CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR} - /> - Date: Fri, 9 Feb 2024 21:53:06 +0530 Subject: [PATCH 015/565] fix: action item taking extra space --- src/pages/home/report/ReportActionItem.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index f960b987427b..d96ae8e77872 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -627,7 +627,7 @@ function ReportActionItem(props) { if (ReportUtils.isTaskReport(props.report)) { if (ReportUtils.isCanceledTaskReport(props.report, parentReportAction)) { return ( - + + Date: Fri, 9 Feb 2024 21:57:04 +0530 Subject: [PATCH 016/565] fix: clean lint --- src/pages/home/report/ReportActionItem.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index d96ae8e77872..57f716adc4cd 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -627,7 +627,7 @@ function ReportActionItem(props) { if (ReportUtils.isTaskReport(props.report)) { if (ReportUtils.isCanceledTaskReport(props.report, parentReportAction)) { return ( - + Date: Mon, 12 Feb 2024 05:45:08 +0530 Subject: [PATCH 017/565] fix: background image horizontal shift on sensor value change --- src/pages/home/report/AnimatedEmptyStateBackground.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/AnimatedEmptyStateBackground.tsx b/src/pages/home/report/AnimatedEmptyStateBackground.tsx index bbc340f5afb0..bcc2275da037 100644 --- a/src/pages/home/report/AnimatedEmptyStateBackground.tsx +++ b/src/pages/home/report/AnimatedEmptyStateBackground.tsx @@ -12,7 +12,7 @@ function AnimatedEmptyStateBackground() { const StyleUtils = useStyleUtils(); const {windowWidth, isSmallScreenWidth} = useWindowDimensions(); const illustrations = useThemeIllustrations(); - const IMAGE_OFFSET_X = 0; + const IMAGE_OFFSET_X = windowWidth * 1.1; // If window width is greater than the max background width, repeat the background image const maxBackgroundWidth = variables.sideBarWidth + CONST.EMPTY_STATE_BACKGROUND.ASPECT_RATIO * CONST.EMPTY_STATE_BACKGROUND.WIDE_SCREEN.IMAGE_HEIGHT; @@ -37,7 +37,7 @@ function AnimatedEmptyStateBackground() { xOffset.value = clamp(xOffset.value + y * CONST.ANIMATION_GYROSCOPE_VALUE, -IMAGE_OFFSET_X, IMAGE_OFFSET_X); yOffset.value = clamp(yOffset.value - x * CONST.ANIMATION_GYROSCOPE_VALUE, -IMAGE_OFFSET_Y, IMAGE_OFFSET_Y); return { - transform: [{translateX: withSpring(-IMAGE_OFFSET_X - xOffset.value)}, {translateY: withSpring(yOffset.value)}], + transform: [{translateX: withSpring(xOffset.value)}, {translateY: withSpring(yOffset.value)}, {scale: 1.15}], }; }, [isReducedMotionEnabled]); From 977512b09fa0d41ebe06623e6394320ef942b349 Mon Sep 17 00:00:00 2001 From: Aswin S Date: Mon, 12 Feb 2024 07:30:55 +0530 Subject: [PATCH 018/565] refactor: move image offset values outside of component --- src/pages/home/report/AnimatedEmptyStateBackground.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/AnimatedEmptyStateBackground.tsx b/src/pages/home/report/AnimatedEmptyStateBackground.tsx index bcc2275da037..3a920f4f8449 100644 --- a/src/pages/home/report/AnimatedEmptyStateBackground.tsx +++ b/src/pages/home/report/AnimatedEmptyStateBackground.tsx @@ -6,13 +6,14 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; import variables from '@styles/variables'; import CONST from '@src/CONST'; -const IMAGE_OFFSET_Y = 75; +// Maximum horizontal and vertical shift in pixels on sensor value change +const IMAGE_OFFSET_X = 30; +const IMAGE_OFFSET_Y = 20; function AnimatedEmptyStateBackground() { const StyleUtils = useStyleUtils(); const {windowWidth, isSmallScreenWidth} = useWindowDimensions(); const illustrations = useThemeIllustrations(); - const IMAGE_OFFSET_X = windowWidth * 1.1; // If window width is greater than the max background width, repeat the background image const maxBackgroundWidth = variables.sideBarWidth + CONST.EMPTY_STATE_BACKGROUND.ASPECT_RATIO * CONST.EMPTY_STATE_BACKGROUND.WIDE_SCREEN.IMAGE_HEIGHT; @@ -37,6 +38,8 @@ function AnimatedEmptyStateBackground() { xOffset.value = clamp(xOffset.value + y * CONST.ANIMATION_GYROSCOPE_VALUE, -IMAGE_OFFSET_X, IMAGE_OFFSET_X); yOffset.value = clamp(yOffset.value - x * CONST.ANIMATION_GYROSCOPE_VALUE, -IMAGE_OFFSET_Y, IMAGE_OFFSET_Y); return { + // On Android, scroll view sub views gets clipped beyond container bounds. Set the top position so that image wouldn't get clipped + top: IMAGE_OFFSET_Y, transform: [{translateX: withSpring(xOffset.value)}, {translateY: withSpring(yOffset.value)}, {scale: 1.15}], }; }, [isReducedMotionEnabled]); From 55fceb1ad731bd95461db946f13c342f860ff233 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Mon, 12 Feb 2024 21:19:20 +0100 Subject: [PATCH 019/565] update config --- metro.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metro.config.js b/metro.config.js index 2422d29aaacf..68ed72d52ba0 100644 --- a/metro.config.js +++ b/metro.config.js @@ -7,7 +7,7 @@ require('dotenv').config(); const defaultConfig = getDefaultConfig(__dirname); const isE2ETesting = process.env.E2E_TESTING === 'true'; -const e2eSourceExts = ['e2e.js', 'e2e.ts']; +const e2eSourceExts = ['e2e.js', 'e2e.ts', 'e2e.tsx']; /** * Metro configuration From 82a1aaadb2f29a4a2a6a6386cd6ad466fad651d7 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Tue, 13 Feb 2024 09:23:21 +0100 Subject: [PATCH 020/565] add onViewableItemsChanged --- .../BaseInvertedFlatList/index.e2e.tsx | 50 +++++++++++++++++++ .../index.tsx} | 1 + 2 files changed, 51 insertions(+) create mode 100644 src/components/InvertedFlatList/BaseInvertedFlatList/index.e2e.tsx rename src/components/InvertedFlatList/{BaseInvertedFlatList.tsx => BaseInvertedFlatList/index.tsx} (96%) diff --git a/src/components/InvertedFlatList/BaseInvertedFlatList/index.e2e.tsx b/src/components/InvertedFlatList/BaseInvertedFlatList/index.e2e.tsx new file mode 100644 index 000000000000..0553312eae32 --- /dev/null +++ b/src/components/InvertedFlatList/BaseInvertedFlatList/index.e2e.tsx @@ -0,0 +1,50 @@ +import type {ForwardedRef} from 'react'; +import React, {forwardRef, useMemo} from 'react'; +import type {FlatListProps, ScrollViewProps} from 'react-native'; +import FlatList from '@components/FlatList'; + +type BaseInvertedFlatListProps = FlatListProps & { + shouldEnableAutoScrollToTopThreshold?: boolean; +}; + +const AUTOSCROLL_TO_TOP_THRESHOLD = 128; + +let localViewableItems: unknown; +const getViewableItems = () => localViewableItems; + +function BaseInvertedFlatListE2e(props: BaseInvertedFlatListProps, ref: ForwardedRef) { + const {shouldEnableAutoScrollToTopThreshold, ...rest} = props; + + const handleViewableItemsChanged = ({viewableItems}: { viewableItems: unknown }) => { + localViewableItems = viewableItems; + }; + + const maintainVisibleContentPosition = useMemo(() => { + const config: ScrollViewProps['maintainVisibleContentPosition'] = { + // This needs to be 1 to avoid using loading views as anchors. + minIndexForVisible: 1, + }; + + if (shouldEnableAutoScrollToTopThreshold) { + config.autoscrollToTopThreshold = AUTOSCROLL_TO_TOP_THRESHOLD; + } + + return config; + }, [shouldEnableAutoScrollToTopThreshold]); + + return ( + + ); +} + +BaseInvertedFlatListE2e.displayName = 'BaseInvertedFlatListE2e'; + +export default forwardRef(BaseInvertedFlatListE2e); +export {getViewableItems}; diff --git a/src/components/InvertedFlatList/BaseInvertedFlatList.tsx b/src/components/InvertedFlatList/BaseInvertedFlatList/index.tsx similarity index 96% rename from src/components/InvertedFlatList/BaseInvertedFlatList.tsx rename to src/components/InvertedFlatList/BaseInvertedFlatList/index.tsx index d83e54f74d66..c92203afdc74 100644 --- a/src/components/InvertedFlatList/BaseInvertedFlatList.tsx +++ b/src/components/InvertedFlatList/BaseInvertedFlatList/index.tsx @@ -25,6 +25,7 @@ function BaseInvertedFlatList(props: BaseInvertedFlatListProps, ref: Forwa return config; }, [shouldEnableAutoScrollToTopThreshold]); + console.debug(`[E2E] BaseInverted.NOT`); return ( Date: Tue, 13 Feb 2024 09:28:41 +0100 Subject: [PATCH 021/565] linking test --- src/ROUTES.ts | 4 ++ src/libs/E2E/reactNativeLaunchingTest.ts | 1 + src/libs/E2E/tests/linkingTest.e2e.ts | 69 ++++++++++++++++++++++ src/pages/home/report/ReportActionsView.js | 4 +- tests/e2e/config.js | 11 ++++ 5 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 src/libs/E2E/tests/linkingTest.e2e.ts diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 5a2ab8cfc7de..996136d12e6d 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -169,6 +169,10 @@ const ROUTES = { route: 'r/:reportID/avatar', getRoute: (reportID: string) => `r/${reportID}/avatar` as const, }, + REPORT_WITH_ID_AND_ACTION_ID: { + route: 'r/:reportID?/:reportActionID?', + getRoute: (reportID: string, reportActionID: string) => `r/${reportID}/${reportActionID}` as const, + }, EDIT_REQUEST: { route: 'r/:threadReportID/edit/:field', getRoute: (threadReportID: string, field: ValueOf) => `r/${threadReportID}/edit/${field}` as const, diff --git a/src/libs/E2E/reactNativeLaunchingTest.ts b/src/libs/E2E/reactNativeLaunchingTest.ts index 79276e7a5d75..931b41524696 100644 --- a/src/libs/E2E/reactNativeLaunchingTest.ts +++ b/src/libs/E2E/reactNativeLaunchingTest.ts @@ -38,6 +38,7 @@ const tests: Tests = { [E2EConfig.TEST_NAMES.OpenSearchPage]: require('./tests/openSearchPageTest.e2e').default, [E2EConfig.TEST_NAMES.ChatOpening]: require('./tests/chatOpeningTest.e2e').default, [E2EConfig.TEST_NAMES.ReportTyping]: require('./tests/reportTypingTest.e2e').default, + [E2EConfig.TEST_NAMES.Linking]: require('./tests/linkingTest.e2e').default, }; // Once we receive the TII measurement we know that the app is initialized and ready to be used: diff --git a/src/libs/E2E/tests/linkingTest.e2e.ts b/src/libs/E2E/tests/linkingTest.e2e.ts new file mode 100644 index 000000000000..ef370aaecf17 --- /dev/null +++ b/src/libs/E2E/tests/linkingTest.e2e.ts @@ -0,0 +1,69 @@ +import Config from 'react-native-config'; +import {getViewableItems} from '@components/InvertedFlatList/BaseInvertedFlatList/index.e2e'; +import Timing from '@libs/actions/Timing'; +import E2ELogin from '@libs/E2E/actions/e2eLogin'; +import waitForAppLoaded from '@libs/E2E/actions/waitForAppLoaded'; +import E2EClient from '@libs/E2E/client'; +import type {TestConfig} from '@libs/E2E/types'; +import getConfigValueOrThrow from '@libs/E2E/utils/getConfigValueOrThrow'; +import Navigation from '@libs/Navigation/Navigation'; +import Performance from '@libs/Performance'; +import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; + +const test = (config: TestConfig) => { + console.debug('[E2E] Logging in for comment linking'); + + const reportID = getConfigValueOrThrow('reportID', config); + const linkedReportID = getConfigValueOrThrow('linkedReportID', config); + const linkedReportActionID = getConfigValueOrThrow('linkedReportActionID', config); + + E2ELogin().then((neededLogin) => { + if (neededLogin) { + return waitForAppLoaded().then(() => E2EClient.submitTestDone()); + } + + Performance.subscribeToMeasurements((entry) => { + if (entry.name === CONST.TIMING.SIDEBAR_LOADED) { + console.debug('[E2E] Sidebar loaded, navigating to a report…'); + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportID)); + return; + } + + if (entry.name === CONST.TIMING.REPORT_INITIAL_RENDER) { + console.debug('[E2E] Navigating to linked report action…'); + Timing.start(CONST.TIMING.SWITCH_REPORT); + Performance.markStart(CONST.TIMING.SWITCH_REPORT); + + Navigation.navigate(ROUTES.REPORT_WITH_ID_AND_ACTION_ID.getRoute(linkedReportID, linkedReportActionID)); + return; + } + + if (entry.name === CONST.TIMING.SWITCH_REPORT) { + setTimeout(() => { + const res = getViewableItems(); + console.debug('[E2E] Viewable items retrieved, verifying correct message…'); + + if (res[0]?.item?.reportActionID === linkedReportActionID) { + E2EClient.submitTestResults({ + branch: Config.E2E_BRANCH, + name: 'Comment linking', + duration: entry.duration, + }) + .then(() => { + console.debug('[E2E] Test completed successfully, exiting…'); + E2EClient.submitTestDone(); + }) + .catch((err) => { + console.debug('[E2E] Error while submitting test results:', err); + }); + } else { + console.debug('[E2E] Message verification failed'); + } + }, 3000); + } + }); + }); +}; + +export default test; diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index 56f204ef6ffb..bcec51dba4e1 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -384,15 +384,15 @@ function ReportActionsView({reportActions: allReportActions, ...props}) { } didLayout.current = true; - Timing.end(CONST.TIMING.SWITCH_REPORT, hasCachedActions ? CONST.TIMING.WARM : CONST.TIMING.COLD); - // Capture the init measurement only once not per each chat switch as the value gets overwritten if (!ReportActionsView.initMeasured) { Performance.markEnd(CONST.TIMING.REPORT_INITIAL_RENDER); + Timing.end(CONST.TIMING.REPORT_INITIAL_RENDER); ReportActionsView.initMeasured = true; } else { Performance.markEnd(CONST.TIMING.SWITCH_REPORT); } + Timing.end(CONST.TIMING.SWITCH_REPORT, hasCachedActions ? CONST.TIMING.WARM : CONST.TIMING.COLD); }, [hasCachedActions], ); diff --git a/tests/e2e/config.js b/tests/e2e/config.js index a7447a29c954..79d4c7c4bdfa 100644 --- a/tests/e2e/config.js +++ b/tests/e2e/config.js @@ -6,6 +6,7 @@ const TEST_NAMES = { OpenSearchPage: 'Open search page TTI', ReportTyping: 'Report typing', ChatOpening: 'Chat opening', + Linking: 'Linking', }; /** @@ -85,5 +86,15 @@ module.exports = { // #announce Chat with many messages reportID: '5421294415618529', }, + [TEST_NAMES.Linking]: { + name: TEST_NAMES.Linking, + reportScreen: { + autoFocus: true, + }, + // Crowded Policy (Do Not Delete) Report, has a input bar available: + reportID: '8268282951170052', + linkedReportID: '5421294415618529', + linkedReportActionID: '2845024374735019929', + }, }, }; From a9315c4e0158438aa4bc8a2935c707828e7c2cb6 Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Thu, 15 Feb 2024 16:42:37 +0100 Subject: [PATCH 022/565] migrate AttachmentView --- src/CONST.ts | 4 +- .../{index.js => index.tsx} | 30 +++--- .../AttachmentViewImage/propTypes.js | 21 ---- ...ntViewPdf.js => BaseAttachmentViewPdf.tsx} | 32 +++---- .../{index.android.js => index.android.tsx} | 10 +- .../{index.ios.js => index.ios.tsx} | 7 +- .../AttachmentViewPdf/{index.js => index.tsx} | 8 +- .../AttachmentViewPdf/propTypes.js | 28 ------ .../AttachmentView/AttachmentViewPdf/types.ts | 21 ++++ .../AttachmentView/{index.js => index.tsx} | 95 ++++++++----------- .../Attachments/AttachmentView/propTypes.js | 52 ---------- .../Attachments/AttachmentView/types.ts | 38 ++++++++ src/components/Attachments/propTypes.js | 21 ---- src/components/Attachments/types.ts | 32 +++++++ src/components/ImageView/types.ts | 2 +- 15 files changed, 171 insertions(+), 230 deletions(-) rename src/components/Attachments/AttachmentView/AttachmentViewImage/{index.js => index.tsx} (67%) mode change 100755 => 100644 delete mode 100644 src/components/Attachments/AttachmentView/AttachmentViewImage/propTypes.js rename src/components/Attachments/AttachmentView/AttachmentViewPdf/{BaseAttachmentViewPdf.js => BaseAttachmentViewPdf.tsx} (83%) rename src/components/Attachments/AttachmentView/AttachmentViewPdf/{index.android.js => index.android.tsx} (94%) rename src/components/Attachments/AttachmentView/AttachmentViewPdf/{index.ios.js => index.ios.tsx} (54%) rename src/components/Attachments/AttachmentView/AttachmentViewPdf/{index.js => index.tsx} (73%) delete mode 100644 src/components/Attachments/AttachmentView/AttachmentViewPdf/propTypes.js create mode 100644 src/components/Attachments/AttachmentView/AttachmentViewPdf/types.ts rename src/components/Attachments/AttachmentView/{index.js => index.tsx} (76%) mode change 100755 => 100644 delete mode 100644 src/components/Attachments/AttachmentView/propTypes.js create mode 100644 src/components/Attachments/AttachmentView/types.ts delete mode 100644 src/components/Attachments/propTypes.js create mode 100644 src/components/Attachments/types.ts diff --git a/src/CONST.ts b/src/CONST.ts index 5c99c5877559..fbcabdd64014 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -437,7 +437,7 @@ const CONST = { }, }, ARROW_LEFT: { - descriptionKey: null, + descriptionKey: 'arrowLeft', shortcutKey: 'ArrowLeft', modifiers: [], trigger: { @@ -447,7 +447,7 @@ const CONST = { }, }, ARROW_RIGHT: { - descriptionKey: null, + descriptionKey: 'arrowRight', shortcutKey: 'ArrowRight', modifiers: [], trigger: { diff --git a/src/components/Attachments/AttachmentView/AttachmentViewImage/index.js b/src/components/Attachments/AttachmentView/AttachmentViewImage/index.tsx old mode 100755 new mode 100644 similarity index 67% rename from src/components/Attachments/AttachmentView/AttachmentViewImage/index.js rename to src/components/Attachments/AttachmentView/AttachmentViewImage/index.tsx index 14c60458b044..9a28dfd82bc4 --- a/src/components/Attachments/AttachmentView/AttachmentViewImage/index.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewImage/index.tsx @@ -1,16 +1,18 @@ import React, {memo} from 'react'; +import type AttachmentViewBaseProps from '@components/Attachments/AttachmentView/types'; import ImageView from '@components/ImageView'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; import CONST from '@src/CONST'; -import {attachmentViewImageDefaultProps, attachmentViewImagePropTypes} from './propTypes'; -const propTypes = { - ...attachmentViewImagePropTypes, - ...withLocalizePropTypes, -}; +type AttachmentViewImageProps = { + url: string | number; + + loadComplete: boolean; + + isImage: boolean; +} & AttachmentViewBaseProps; function AttachmentViewImage({ url, @@ -20,21 +22,19 @@ function AttachmentViewImage({ isSingleCarouselItem, carouselItemIndex, carouselActiveItemIndex, - isFocused, loadComplete, onPress, onError, isImage, - translate, -}) { +}: AttachmentViewImageProps) { + const {translate} = useLocalize(); const styles = useThemeStyles(); const children = ( {children} @@ -57,8 +57,6 @@ function AttachmentViewImage({ ); } -AttachmentViewImage.propTypes = propTypes; -AttachmentViewImage.defaultProps = attachmentViewImageDefaultProps; AttachmentViewImage.displayName = 'AttachmentViewImage'; -export default compose(memo, withLocalize)(AttachmentViewImage); +export default memo(AttachmentViewImage); diff --git a/src/components/Attachments/AttachmentView/AttachmentViewImage/propTypes.js b/src/components/Attachments/AttachmentView/AttachmentViewImage/propTypes.js deleted file mode 100644 index f2a275fc9a21..000000000000 --- a/src/components/Attachments/AttachmentView/AttachmentViewImage/propTypes.js +++ /dev/null @@ -1,21 +0,0 @@ -import PropTypes from 'prop-types'; -import {attachmentViewDefaultProps, attachmentViewPropTypes} from '@components/Attachments/AttachmentView/propTypes'; - -const attachmentViewImagePropTypes = { - ...attachmentViewPropTypes, - - url: PropTypes.string.isRequired, - - loadComplete: PropTypes.bool.isRequired, - - isImage: PropTypes.bool.isRequired, -}; - -const attachmentViewImageDefaultProps = { - ...attachmentViewDefaultProps, - - loadComplete: false, - isImage: false, -}; - -export {attachmentViewImagePropTypes, attachmentViewImageDefaultProps}; diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.js b/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.tsx similarity index 83% rename from src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.js rename to src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.tsx index 2f16b63aacc6..213a28d830e7 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.tsx @@ -1,21 +1,13 @@ -import PropTypes from 'prop-types'; import React, {memo, useCallback, useContext, useEffect} from 'react'; +import type {GestureResponderEvent} from 'react-native'; import AttachmentCarouselPagerContext from '@components/Attachments/AttachmentCarousel/Pager/AttachmentCarouselPagerContext'; import PDFView from '@components/PDFView'; -import {attachmentViewPdfDefaultProps, attachmentViewPdfPropTypes} from './propTypes'; - -const baseAttachmentViewPdfPropTypes = { - ...attachmentViewPdfPropTypes, +import type AttachmentViewPdfProps from './types'; +type BaseAttachmentViewPdfProps = { /** Triggered when the PDF's onScaleChanged event is triggered */ - onScaleChanged: PropTypes.func, -}; - -const baseAttachmentViewPdfDefaultProps = { - ...attachmentViewPdfDefaultProps, - - onScaleChanged: undefined, -}; + onScaleChanged: (scale: number) => void; +} & AttachmentViewPdfProps; function BaseAttachmentViewPdf({ file, @@ -28,7 +20,7 @@ function BaseAttachmentViewPdf({ onLoadComplete, errorLabelStyles, style, -}) { +}: BaseAttachmentViewPdfProps) { const attachmentCarouselPagerContext = useContext(AttachmentCarouselPagerContext); const isScrollEnabled = attachmentCarouselPagerContext === null ? undefined : attachmentCarouselPagerContext.isScrollEnabled; @@ -46,7 +38,7 @@ function BaseAttachmentViewPdf({ * as well as call the onScaleChanged prop of the AttachmentViewPdf component if defined. */ const onScaleChanged = useCallback( - (newScale) => { + (newScale: number) => { if (onScaleChangedProp !== undefined) { onScaleChangedProp(newScale); } @@ -66,13 +58,13 @@ function BaseAttachmentViewPdf({ * Otherwise it means that the PDF is currently zoomed in, therefore the onTap callback should be ignored */ const onPress = useCallback( - (e) => { + (e?: GestureResponderEvent | KeyboardEvent) => { if (onPressProp !== undefined) { onPressProp(e); } - if (attachmentCarouselPagerContext !== null && isScrollEnabled.value) { - attachmentCarouselPagerContext.onTap(e); + if (attachmentCarouselPagerContext !== null && isScrollEnabled?.value) { + attachmentCarouselPagerContext.onTap(); } }, [attachmentCarouselPagerContext, isScrollEnabled, onPressProp], @@ -80,6 +72,7 @@ function BaseAttachmentViewPdf({ return ( { isPanGestureActive.value = false; + if (!isScrollEnabled) { + return; + } isScrollEnabled.value = true; }); @@ -93,7 +96,4 @@ function AttachmentViewPdf(props) { ); } -AttachmentViewPdf.propTypes = attachmentViewPdfPropTypes; -AttachmentViewPdf.defaultProps = attachmentViewPdfDefaultProps; - export default memo(AttachmentViewPdf); diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.ios.js b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.ios.tsx similarity index 54% rename from src/components/Attachments/AttachmentView/AttachmentViewPdf/index.ios.js rename to src/components/Attachments/AttachmentView/AttachmentViewPdf/index.ios.tsx index 103ff292760f..79c9974bc8ce 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.ios.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.ios.tsx @@ -1,8 +1,8 @@ import React, {memo} from 'react'; +import type {BaseAttachmentViewPdfProps} from './BaseAttachmentViewPdf'; import BaseAttachmentViewPdf from './BaseAttachmentViewPdf'; -import {attachmentViewPdfDefaultProps, attachmentViewPdfPropTypes} from './propTypes'; -function AttachmentViewPdf(props) { +function AttachmentViewPdf(props: BaseAttachmentViewPdfProps) { return ( void; + onLoadComplete: () => void; + + /** Additional style props */ + style?: StyleProp; + + /** Styles for the error label */ + errorLabelStyles?: StyleProp; + + /** Whether this view is the active screen */ + isFocused?: boolean; +} & AttachmentViewBaseProps & + Attachment; + +export default AttachmentViewPdfProps; diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.tsx old mode 100755 new mode 100644 similarity index 76% rename from src/components/Attachments/AttachmentView/index.js rename to src/components/Attachments/AttachmentView/index.tsx index 33eab13f3851..033515621092 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.tsx @@ -1,82 +1,74 @@ import Str from 'expensify-common/lib/str'; -import PropTypes from 'prop-types'; import React, {memo, useState} from 'react'; +import type {StyleProp, ViewStyle} from 'react-native'; import {ActivityIndicator, ScrollView, View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; -import * as AttachmentsPropTypes from '@components/Attachments/propTypes'; +import type {Attachment, AttachmentSource} from '@components/Attachments/types'; import DistanceEReceipt from '@components/DistanceEReceipt'; import EReceipt from '@components/EReceipt'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import Text from '@components/Text'; import Tooltip from '@components/Tooltip'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; -import compose from '@libs/compose'; import * as TransactionUtils from '@libs/TransactionUtils'; +import type {ColorValue} from '@styles/utils/types'; import variables from '@styles/variables'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {Transaction} from '@src/types/onyx'; import AttachmentViewImage from './AttachmentViewImage'; import AttachmentViewPdf from './AttachmentViewPdf'; -import {attachmentViewDefaultProps, attachmentViewPropTypes} from './propTypes'; +import type AttachmentViewBaseProps from './types'; -const propTypes = { - ...attachmentViewPropTypes, - ...withLocalizePropTypes, +type AttachmentViewOnyxProps = { + transaction: OnyxEntry; +}; +type AttachmentViewProps = { /** URL to full-sized attachment, SVG function, or numeric static image on native platforms */ - source: AttachmentsPropTypes.attachmentSourcePropType.isRequired, + source: AttachmentSource; /** Flag to show/hide download icon */ - shouldShowDownloadIcon: PropTypes.bool, + shouldShowDownloadIcon?: boolean; /** Flag to show the loading indicator */ - shouldShowLoadingSpinnerIcon: PropTypes.bool, + shouldShowLoadingSpinnerIcon?: boolean; /** Notify parent that the UI should be modified to accommodate keyboard */ - onToggleKeyboard: PropTypes.func, + onToggleKeyboard?: () => void; /** Extra styles to pass to View wrapper */ - // eslint-disable-next-line react/forbid-prop-types - containerStyles: PropTypes.arrayOf(PropTypes.object), + containerStyles?: Array>; /** Denotes whether it is a workspace avatar or not */ - isWorkspaceAvatar: PropTypes.bool, + isWorkspaceAvatar?: boolean; /** Denotes whether it is an icon (ex: SVG) */ - maybeIcon: PropTypes.bool, + maybeIcon?: boolean; /** The id of the transaction related to the attachment */ - // eslint-disable-next-line react/no-unused-prop-types - transactionID: PropTypes.string, -}; + transactionID?: string; -const defaultProps = { - ...attachmentViewDefaultProps, - shouldShowDownloadIcon: false, - shouldShowLoadingSpinnerIcon: false, - onToggleKeyboard: () => {}, - containerStyles: [], - isWorkspaceAvatar: false, - maybeIcon: false, - transactionID: '', -}; + fallbackSource?: string | number; +} & AttachmentViewOnyxProps & + AttachmentViewBaseProps & + Attachment; function AttachmentView({ source, - file, + file = {name: ''}, isAuthTokenRequired, onPress, shouldShowLoadingSpinnerIcon, shouldShowDownloadIcon, containerStyles, onToggleKeyboard, - translate, isFocused, isUsedInCarousel, isSingleCarouselItem, @@ -87,7 +79,8 @@ function AttachmentView({ maybeIcon, fallbackSource, transaction, -}) { +}: AttachmentViewProps) { + const {translate} = useLocalize(); const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -98,10 +91,10 @@ function AttachmentView({ // Handles case where source is a component (ex: SVG) or a number // Number may represent a SVG or an image - if ((maybeIcon && typeof source === 'number') || _.isFunction(source)) { - let iconFillColor = ''; - let additionalStyles = []; - if (isWorkspaceAvatar) { + if ((maybeIcon && typeof source === 'number') ?? typeof source === 'function') { + let iconFillColor: ColorValue | undefined = ''; + let additionalStyles: ViewStyle[] = []; + if (isWorkspaceAvatar && file) { const defaultWorkspaceAvatarColor = StyleUtils.getDefaultWorkspaceAvatarColor(file.name); iconFillColor = defaultWorkspaceAvatarColor.fill; additionalStyles = [defaultWorkspaceAvatarColor]; @@ -118,7 +111,7 @@ function AttachmentView({ ); } - if (TransactionUtils.hasEReceipt(transaction)) { + if (TransactionUtils.hasEReceipt(transaction) && transaction) { return ( + - {file && file.name} + {file?.name} {!shouldShowLoadingSpinnerIcon && shouldShowDownloadIcon && ( @@ -223,16 +216,10 @@ function AttachmentView({ ); } -AttachmentView.propTypes = propTypes; -AttachmentView.defaultProps = defaultProps; AttachmentView.displayName = 'AttachmentView'; -export default compose( - memo, - withLocalize, - withOnyx({ - transaction: { - key: ({transactionID}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, - }, - }), -)(AttachmentView); +export default withOnyx({ + transaction: { + key: ({transactionID}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, + }, +})(memo(AttachmentView)); diff --git a/src/components/Attachments/AttachmentView/propTypes.js b/src/components/Attachments/AttachmentView/propTypes.js deleted file mode 100644 index d78bed8526b8..000000000000 --- a/src/components/Attachments/AttachmentView/propTypes.js +++ /dev/null @@ -1,52 +0,0 @@ -import PropTypes from 'prop-types'; -import * as AttachmentsPropTypes from '@components/Attachments/propTypes'; - -const attachmentViewPropTypes = { - /** Whether source url requires authentication */ - isAuthTokenRequired: PropTypes.bool, - - /** File object can be an instance of File or Object */ - file: AttachmentsPropTypes.attachmentFilePropType, - - /** Whether this view is the active screen */ - isFocused: PropTypes.bool, - - /** Whether this AttachmentView is shown as part of a AttachmentCarousel */ - isUsedInCarousel: PropTypes.bool, - - /** When "isUsedInCarousel" is set to true, determines whether there is only one item in the carousel */ - isSingleCarouselItem: PropTypes.bool, - - /** Whether this AttachmentView is shown as part of an AttachmentModal */ - isUsedInAttachmentModal: PropTypes.bool, - - /** The index of the carousel item */ - carouselItemIndex: PropTypes.number, - - /** The index of the currently active carousel item */ - carouselActiveItemIndex: PropTypes.number, - - /** Function for handle on press */ - onPress: PropTypes.func, - - /** Handles scale changed event */ - onScaleChanged: PropTypes.func, -}; - -const attachmentViewDefaultProps = { - isAuthTokenRequired: false, - file: { - name: '', - }, - isFocused: false, - isUsedInCarousel: false, - isSingleCarouselItem: false, - carouselItemIndex: 0, - carouselActiveItemIndex: 0, - isSingleElement: false, - isUsedInAttachmentModal: false, - onPress: undefined, - onScaleChanged: () => {}, -}; - -export {attachmentViewPropTypes, attachmentViewDefaultProps}; diff --git a/src/components/Attachments/AttachmentView/types.ts b/src/components/Attachments/AttachmentView/types.ts new file mode 100644 index 000000000000..21b3a0c01cfd --- /dev/null +++ b/src/components/Attachments/AttachmentView/types.ts @@ -0,0 +1,38 @@ +import type {GestureResponderEvent} from 'react-native'; +import type {AttachmentFile} from '@components/Attachments/types'; + +type AttachmentViewBaseProps = { + /** Whether this view is the active screen */ + isFocused?: boolean; + + /** Whether this AttachmentView is shown as part of a AttachmentCarousel */ + isUsedInCarousel?: boolean; + + /** File object can be an instance of File or Object */ + file: AttachmentFile; + + isAuthTokenRequired?: boolean; + + /** When "isUsedInCarousel" is set to true, determines whether there is only one item in the carousel */ + isSingleCarouselItem?: boolean; + + /** Whether this AttachmentView is shown as part of an AttachmentModal */ + isUsedInAttachmentModal?: boolean; + + /** The index of the carousel item */ + carouselItemIndex?: number; + + /** The index of the currently active carousel item */ + carouselActiveItemIndex?: number; + + /** Function for handle on press */ + onPress?: (e?: GestureResponderEvent | KeyboardEvent) => void; + + /** Function for handle on error */ + onError?: () => void; + + /** Handles scale changed event */ + onScaleChanged?: (scale: number) => void; +}; + +export default AttachmentViewBaseProps; diff --git a/src/components/Attachments/propTypes.js b/src/components/Attachments/propTypes.js deleted file mode 100644 index 13adc468ce64..000000000000 --- a/src/components/Attachments/propTypes.js +++ /dev/null @@ -1,21 +0,0 @@ -import PropTypes from 'prop-types'; - -const attachmentSourcePropType = PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.number]); -const attachmentFilePropType = PropTypes.shape({ - name: PropTypes.string.isRequired, -}); - -const attachmentPropType = PropTypes.shape({ - /** Whether source url requires authentication */ - isAuthTokenRequired: PropTypes.bool, - - /** URL to full-sized attachment, SVG function, or numeric static image on native platforms */ - source: attachmentSourcePropType.isRequired, - - /** File object can be an instance of File or Object */ - file: attachmentFilePropType.isRequired, -}); - -const attachmentsPropType = PropTypes.arrayOf(attachmentPropType); - -export {attachmentSourcePropType, attachmentFilePropType, attachmentPropType, attachmentsPropType}; diff --git a/src/components/Attachments/types.ts b/src/components/Attachments/types.ts new file mode 100644 index 000000000000..bb3848e7bbe6 --- /dev/null +++ b/src/components/Attachments/types.ts @@ -0,0 +1,32 @@ +// This can be either a string, function, or number +type AttachmentSource = string | number | React.FC; + +// Object shape for file where name is a required string +type AttachmentFile = { + name: string; +}; + +// The object shape for the attachment +type Attachment = { + /** Report action ID of the attachment */ + reportActionID?: string; + + /** Whether source url requires authentication */ + isAuthTokenRequired?: boolean; + + /** URL to full-sized attachment, SVG function, or numeric static image on native platforms */ + source: AttachmentSource; + + /** File object can be an instance of File or Object */ + file: AttachmentFile; + + /** Whether the attachment has been flagged */ + hasBeenFlagged?: boolean; + + /** The id of the transaction related to the attachment */ + transactionID?: string; + + isReceipt?: boolean; +}; + +export type {AttachmentSource, AttachmentFile, Attachment}; diff --git a/src/components/ImageView/types.ts b/src/components/ImageView/types.ts index b85115874a5a..9ff983c3609a 100644 --- a/src/components/ImageView/types.ts +++ b/src/components/ImageView/types.ts @@ -6,7 +6,7 @@ type ImageViewProps = { isAuthTokenRequired?: boolean; /** URL to full-sized image */ - url: string; + url: string | number; /** image file name */ fileName: string; From 0f696ce583c5757e6cab8be8f1e34a5c6aa23ab6 Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Thu, 15 Feb 2024 16:43:29 +0100 Subject: [PATCH 023/565] migrate AttachmentCarousel --- ....js => AttachmentCarouselCellRenderer.tsx} | 14 ++--- ...CarouselActions.js => CarouselActions.tsx} | 22 +++----- ...CarouselButtons.js => CarouselButtons.tsx} | 32 +++++------- .../{CarouselItem.js => CarouselItem.tsx} | 51 +++++-------------- ...ort.js => extractAttachmentsFromReport.ts} | 23 ++++----- ...CarouselArrows.js => useCarouselArrows.ts} | 11 ++-- 6 files changed, 51 insertions(+), 102 deletions(-) rename src/components/Attachments/AttachmentCarousel/{AttachmentCarouselCellRenderer.js => AttachmentCarouselCellRenderer.tsx} (71%) rename src/components/Attachments/AttachmentCarousel/{CarouselActions.js => CarouselActions.tsx} (63%) rename src/components/Attachments/AttachmentCarousel/{CarouselButtons.js => CarouselButtons.tsx} (76%) rename src/components/Attachments/AttachmentCarousel/{CarouselItem.js => CarouselItem.tsx} (70%) rename src/components/Attachments/AttachmentCarousel/{extractAttachmentsFromReport.js => extractAttachmentsFromReport.ts} (73%) rename src/components/Attachments/AttachmentCarousel/{useCarouselArrows.js => useCarouselArrows.ts} (77%) diff --git a/src/components/Attachments/AttachmentCarousel/AttachmentCarouselCellRenderer.js b/src/components/Attachments/AttachmentCarousel/AttachmentCarouselCellRenderer.tsx similarity index 71% rename from src/components/Attachments/AttachmentCarousel/AttachmentCarouselCellRenderer.js rename to src/components/Attachments/AttachmentCarousel/AttachmentCarouselCellRenderer.tsx index f4cbffc0e1e4..08d0b7f271d4 100644 --- a/src/components/Attachments/AttachmentCarousel/AttachmentCarouselCellRenderer.js +++ b/src/components/Attachments/AttachmentCarousel/AttachmentCarouselCellRenderer.tsx @@ -1,19 +1,15 @@ -import PropTypes from 'prop-types'; import React from 'react'; +import type {StyleProp, ViewStyle} from 'react-native'; import {PixelRatio, View} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; -const propTypes = { +type AttachmentCarouselCellRendererProps = { /** Cell Container styles */ - style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), + style: StyleProp; }; -const defaultProps = { - style: [], -}; - -function AttachmentCarouselCellRenderer(props) { +function AttachmentCarouselCellRenderer(props: AttachmentCarouselCellRendererProps) { const styles = useThemeStyles(); const {windowWidth, isSmallScreenWidth} = useWindowDimensions(); const modalStyles = styles.centeredModalStyles(isSmallScreenWidth, true); @@ -28,8 +24,6 @@ function AttachmentCarouselCellRenderer(props) { ); } -AttachmentCarouselCellRenderer.propTypes = propTypes; -AttachmentCarouselCellRenderer.defaultProps = defaultProps; AttachmentCarouselCellRenderer.displayName = 'AttachmentCarouselCellRenderer'; export default React.memo(AttachmentCarouselCellRenderer); diff --git a/src/components/Attachments/AttachmentCarousel/CarouselActions.js b/src/components/Attachments/AttachmentCarousel/CarouselActions.tsx similarity index 63% rename from src/components/Attachments/AttachmentCarousel/CarouselActions.js rename to src/components/Attachments/AttachmentCarousel/CarouselActions.tsx index cf5309222c4e..45fed45e1670 100644 --- a/src/components/Attachments/AttachmentCarousel/CarouselActions.js +++ b/src/components/Attachments/AttachmentCarousel/CarouselActions.tsx @@ -1,24 +1,19 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import {useEffect} from 'react'; import KeyboardShortcut from '@libs/KeyboardShortcut'; import CONST from '@src/CONST'; -const propTypes = { +type CarouselActionsProps = { /** Callback to cycle through attachments */ - onCycleThroughAttachments: PropTypes.func.isRequired, + onCycleThroughAttachments: (deltaSlide: number) => void; }; -function CarouselActions({onCycleThroughAttachments}) { +function CarouselActions({onCycleThroughAttachments}: CarouselActionsProps) { useEffect(() => { const shortcutLeftConfig = CONST.KEYBOARD_SHORTCUTS.ARROW_LEFT; const unsubscribeLeftKey = KeyboardShortcut.subscribe( shortcutLeftConfig.shortcutKey, - (e) => { - if (lodashGet(e, 'target.blur')) { - // prevents focus from highlighting around the modal - e.target.blur(); - } + (e?: KeyboardEvent) => { + (e as unknown as React.FocusEvent)?.target?.blur(); onCycleThroughAttachments(-1); }, @@ -30,10 +25,7 @@ function CarouselActions({onCycleThroughAttachments}) { const unsubscribeRightKey = KeyboardShortcut.subscribe( shortcutRightConfig.shortcutKey, (e) => { - if (lodashGet(e, 'target.blur')) { - // prevents focus from highlighting around the modal - e.target.blur(); - } + (e as unknown as React.FocusEvent)?.target?.blur(); onCycleThroughAttachments(1); }, @@ -50,6 +42,4 @@ function CarouselActions({onCycleThroughAttachments}) { return null; } -CarouselActions.propTypes = propTypes; - export default CarouselActions; diff --git a/src/components/Attachments/AttachmentCarousel/CarouselButtons.js b/src/components/Attachments/AttachmentCarousel/CarouselButtons.tsx similarity index 76% rename from src/components/Attachments/AttachmentCarousel/CarouselButtons.js rename to src/components/Attachments/AttachmentCarousel/CarouselButtons.tsx index 1847d30ede22..efa6a7979ada 100644 --- a/src/components/Attachments/AttachmentCarousel/CarouselButtons.js +++ b/src/components/Attachments/AttachmentCarousel/CarouselButtons.tsx @@ -1,8 +1,6 @@ -import PropTypes from 'prop-types'; import React from 'react'; import {View} from 'react-native'; -import _ from 'underscore'; -import * as AttachmentCarouselViewPropTypes from '@components/Attachments/propTypes'; +import type {Attachment} from '@components/Attachments/types'; import Button from '@components/Button'; import * as Expensicons from '@components/Icon/Expensicons'; import Tooltip from '@components/Tooltip'; @@ -11,36 +9,32 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; -const propTypes = { +type CarouselButtonsProps = { /** Where the arrows should be visible */ - shouldShowArrows: PropTypes.bool.isRequired, + shouldShowArrows: boolean; /** The current page index */ - page: PropTypes.number.isRequired, + page: number; /** The attachments from the carousel */ - attachments: AttachmentCarouselViewPropTypes.attachmentsPropType.isRequired, + attachments: Attachment[]; /** Callback to go one page back */ - onBack: PropTypes.func.isRequired, + onBack: () => void; + /** Callback to go one page forward */ - onForward: PropTypes.func.isRequired, + onForward: () => void; - autoHideArrow: PropTypes.func, - cancelAutoHideArrow: PropTypes.func, -}; + autoHideArrow?: () => void; -const defaultProps = { - autoHideArrow: () => {}, - cancelAutoHideArrow: () => {}, + cancelAutoHideArrow?: () => void; }; -function CarouselButtons({page, attachments, shouldShowArrows, onBack, onForward, cancelAutoHideArrow, autoHideArrow}) { +function CarouselButtons({page, attachments, shouldShowArrows, onBack, onForward, cancelAutoHideArrow, autoHideArrow}: CarouselButtonsProps) { const theme = useTheme(); const styles = useThemeStyles(); const isBackDisabled = page === 0; - const isForwardDisabled = page === _.size(attachments) - 1; - + const isForwardDisabled = page === attachments.length - 1; const {translate} = useLocalize(); const {isSmallScreenWidth} = useWindowDimensions(); @@ -82,8 +76,6 @@ function CarouselButtons({page, attachments, shouldShowArrows, onBack, onForward ) : null; } -CarouselButtons.propTypes = propTypes; -CarouselButtons.defaultProps = defaultProps; CarouselButtons.displayName = 'CarouselButtons'; export default CarouselButtons; diff --git a/src/components/Attachments/AttachmentCarousel/CarouselItem.js b/src/components/Attachments/AttachmentCarousel/CarouselItem.tsx similarity index 70% rename from src/components/Attachments/AttachmentCarousel/CarouselItem.js rename to src/components/Attachments/AttachmentCarousel/CarouselItem.tsx index 5552f15320f3..973dfa96dddf 100644 --- a/src/components/Attachments/AttachmentCarousel/CarouselItem.js +++ b/src/components/Attachments/AttachmentCarousel/CarouselItem.tsx @@ -1,8 +1,8 @@ -import PropTypes from 'prop-types'; import React, {useContext, useState} from 'react'; +import type {StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; import AttachmentView from '@components/Attachments/AttachmentView'; -import * as AttachmentsPropTypes from '@components/Attachments/propTypes'; +import type {Attachment} from '@components/Attachments/types'; import Button from '@components/Button'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import SafeAreaConsumer from '@components/SafeAreaConsumer'; @@ -12,56 +12,31 @@ import useThemeStyles from '@hooks/useThemeStyles'; import ReportAttachmentsContext from '@pages/home/report/ReportAttachmentsContext'; import CONST from '@src/CONST'; -const propTypes = { +type CarouselItemProps = { /** Attachment required information such as the source and file name */ - item: PropTypes.shape({ - /** Report action ID of the attachment */ - reportActionID: PropTypes.string, - - /** Whether source URL requires authentication */ - isAuthTokenRequired: PropTypes.bool, - - /** URL to full-sized attachment or SVG function */ - source: AttachmentsPropTypes.attachmentSourcePropType.isRequired, - - /** Additional information about the attachment file */ - file: PropTypes.shape({ - /** File name of the attachment */ - name: PropTypes.string.isRequired, - }).isRequired, - - /** Whether the attachment has been flagged */ - hasBeenFlagged: PropTypes.bool, - - /** The id of the transaction related to the attachment */ - transactionID: PropTypes.string, - }).isRequired, + item: Attachment; /** Whether there is only one element in the attachment carousel */ - isSingleItem: PropTypes.bool.isRequired, + isSingleItem: boolean; /** The index of the carousel item */ - index: PropTypes.number.isRequired, + index?: number; /** The index of the currently active carousel item */ - activeIndex: PropTypes.number.isRequired, + activeIndex?: number; /** onPress callback */ - onPress: PropTypes.func, -}; - -const defaultProps = { - onPress: undefined, + onPress?: () => void; }; -function CarouselItem({item, index, activeIndex, isSingleItem, onPress}) { +function CarouselItem({item, index, activeIndex, isSingleItem, onPress}: CarouselItemProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const {isAttachmentHidden} = useContext(ReportAttachmentsContext); // eslint-disable-next-line es/no-nullish-coalescing-operators - const [isHidden, setIsHidden] = useState(() => isAttachmentHidden(item.reportActionID) ?? item.hasBeenFlagged); + const [isHidden, setIsHidden] = useState(() => (item.reportActionID ? isAttachmentHidden(item.reportActionID) : item.hasBeenFlagged)); - const renderButton = (style) => ( + const renderButton = (style: StyleProp) => (