From 88b7ef89bcaa07ff52a1f1dfd9505ebf2b338a0a Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 21 Dec 2023 18:11:06 +0100 Subject: [PATCH 001/557] 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/557] 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/557] 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 44c41df41b81ab483fe0c3a13505b01fab0084fa Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 31 Jan 2024 15:34:26 +0100 Subject: [PATCH 004/557] 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 005/557] 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 006/557] 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 007/557] [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 008/557] [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 009/557] 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 010/557] [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 011/557] [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 012/557] 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 013/557] [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 014/557] 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 015/557] 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 016/557] 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 017/557] 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 018/557] 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 019/557] 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 020/557] 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 021/557] 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 022/557] 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) => ( + ) : ( + + )} + From a8b03dfc8579f1b6d801c7297e65bcc6240fcc77 Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Mon, 11 Mar 2024 14:12:38 +0100 Subject: [PATCH 098/557] fix lint --- .../Attachments/AttachmentCarousel/index.native.tsx | 5 ++++- src/components/ImageView/index.native.tsx | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/Attachments/AttachmentCarousel/index.native.tsx b/src/components/Attachments/AttachmentCarousel/index.native.tsx index 08a6323691de..9e5cd90c3d57 100644 --- a/src/components/Attachments/AttachmentCarousel/index.native.tsx +++ b/src/components/Attachments/AttachmentCarousel/index.native.tsx @@ -1,7 +1,7 @@ import React, {useCallback, useEffect, useRef, useState} from 'react'; import {Keyboard, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import type {Attachment} from '@components/Attachments/types'; +import type {Attachment, AttachmentSource} from '@components/Attachments/types'; import BlockingView from '@components/BlockingViews/BlockingView'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import * as Illustrations from '@components/Icon/Illustrations'; @@ -32,6 +32,7 @@ function AttachmentCarousel({ const [page, setPage] = useState(); const [attachments, setAttachments] = useState([]); const {shouldShowArrows, setShouldShowArrows, autoHideArrows, cancelAutoHideArrows} = useCarouselArrows(); + const [activeSource, setActiveSource] = useState(source); const compareImage = useCallback((attachment: Attachment) => attachment.source === source, [source]); @@ -72,6 +73,7 @@ function AttachmentCarousel({ const item = attachments[newPageIndex]; setPage(newPageIndex); + setActiveSource(item.source); onNavigate(item); }, @@ -146,6 +148,7 @@ function AttachmentCarousel({ updatePage(newPage)} onClose={onClose} diff --git a/src/components/ImageView/index.native.tsx b/src/components/ImageView/index.native.tsx index b1084a885688..c27869f0e281 100644 --- a/src/components/ImageView/index.native.tsx +++ b/src/components/ImageView/index.native.tsx @@ -6,7 +6,7 @@ import type ImageViewProps from './types'; function ImageView({isAuthTokenRequired = false, url, style, zoomRange = DEFAULT_ZOOM_RANGE, onError}: ImageViewProps) { return ( Date: Mon, 11 Mar 2024 15:41:47 +0000 Subject: [PATCH 099/557] [TS migration] Migrate AddressSearch, Banner, Button and ButtonWithDropdownMenu stories to typescript --- src/components/AddressSearch/index.tsx | 1 + src/components/AddressSearch/types.ts | 2 +- src/components/Banner.tsx | 1 + src/components/Button/index.tsx | 1 + ...h.stories.js => AddressSearch.stories.tsx} | 20 ++++++++++++------- .../{Banner.stories.js => Banner.stories.tsx} | 12 +++++++---- .../{Button.stories.js => Button.stories.tsx} | 18 ++++++++++------- ....js => ButtonWithDropdownMenu.stories.tsx} | 16 +++++++++------ 8 files changed, 46 insertions(+), 25 deletions(-) rename src/stories/{AddressSearch.stories.js => AddressSearch.stories.tsx} (53%) rename src/stories/{Banner.stories.js => Banner.stories.tsx} (75%) rename src/stories/{Button.stories.js => Button.stories.tsx} (80%) rename src/stories/{ButtonWithDropdownMenu.stories.js => ButtonWithDropdownMenu.stories.tsx} (57%) diff --git a/src/components/AddressSearch/index.tsx b/src/components/AddressSearch/index.tsx index a2e3f5d9948e..1eebff7b5bfa 100644 --- a/src/components/AddressSearch/index.tsx +++ b/src/components/AddressSearch/index.tsx @@ -455,3 +455,4 @@ function AddressSearch( AddressSearch.displayName = 'AddressSearchWithRef'; export default forwardRef(AddressSearch); +export type {AddressSearchProps}; diff --git a/src/components/AddressSearch/types.ts b/src/components/AddressSearch/types.ts index 27e068cd1777..efbcc6374341 100644 --- a/src/components/AddressSearch/types.ts +++ b/src/components/AddressSearch/types.ts @@ -96,4 +96,4 @@ type AddressSearchProps = { type IsCurrentTargetInsideContainerType = (event: FocusEvent | NativeSyntheticEvent, containerRef: RefObject) => boolean; -export type {CurrentLocationButtonProps, AddressSearchProps, RenamedInputKeysProps, IsCurrentTargetInsideContainerType}; +export type {CurrentLocationButtonProps, AddressSearchProps, RenamedInputKeysProps, IsCurrentTargetInsideContainerType, StreetValue}; diff --git a/src/components/Banner.tsx b/src/components/Banner.tsx index 56fe7c4d0b42..b4bdfa8405fa 100644 --- a/src/components/Banner.tsx +++ b/src/components/Banner.tsx @@ -109,3 +109,4 @@ function Banner({text, onClose, onPress, containerStyles, textStyles, shouldRend Banner.displayName = 'Banner'; export default memo(Banner); +export type {BannerProps} diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx index ca1bc391e800..b11e16e47f6e 100644 --- a/src/components/Button/index.tsx +++ b/src/components/Button/index.tsx @@ -348,3 +348,4 @@ function Button( Button.displayName = 'Button'; export default withNavigationFallback(React.forwardRef(Button)); +export type {ButtonProps}; diff --git a/src/stories/AddressSearch.stories.js b/src/stories/AddressSearch.stories.tsx similarity index 53% rename from src/stories/AddressSearch.stories.js rename to src/stories/AddressSearch.stories.tsx index 8b9223bc5ea2..a648a20c0bfa 100644 --- a/src/stories/AddressSearch.stories.js +++ b/src/stories/AddressSearch.stories.tsx @@ -1,12 +1,17 @@ +import type {ComponentMeta, ComponentStory} from '@storybook/react'; import React, {useState} from 'react'; +import type {AddressSearchProps} from '@components/AddressSearch'; import AddressSearch from '@components/AddressSearch'; +import type {RenamedInputKeysProps, StreetValue} from '@components/AddressSearch/types'; + +type AddressSearchStory = ComponentStory; /** * We use the Component Story Format for writing stories. Follow the docs here: * * https://storybook.js.org/docs/react/writing-stories/introduction#component-story-format */ -export default { +const story: ComponentMeta = { title: 'Components/AddressSearch', component: AddressSearch, args: { @@ -15,12 +20,12 @@ export default { }, }; -function Template(args) { - const [value, setValue] = useState(''); +function Template(args: AddressSearchProps) { + const [value, setValue] = useState(''); return ( setValue(street)} + value={value as string} + onInputChange={(inputValue) => setValue(inputValue)} // eslint-disable-next-line react/jsx-props-no-spreading {...args} /> @@ -29,11 +34,12 @@ function Template(args) { // Arguments can be passed to the component by binding // See: https://storybook.js.org/docs/react/writing-stories/introduction#using-args -const Default = Template.bind({}); +const Default: AddressSearchStory = Template.bind({}); -const ErrorStory = Template.bind({}); +const ErrorStory: AddressSearchStory = Template.bind({}); ErrorStory.args = { errorText: 'The street you are looking for does not exist', }; +export default story; export {Default, ErrorStory}; diff --git a/src/stories/Banner.stories.js b/src/stories/Banner.stories.tsx similarity index 75% rename from src/stories/Banner.stories.js rename to src/stories/Banner.stories.tsx index 3a6f454843d1..6e71979a88a8 100644 --- a/src/stories/Banner.stories.js +++ b/src/stories/Banner.stories.tsx @@ -1,6 +1,10 @@ +import type {ComponentStory} from '@storybook/react'; import React from 'react'; +import type {BannerProps} from '@components/Banner'; import Banner from '@components/Banner'; +type BannerStory = ComponentStory; + /** * We use the Component Story Format for writing stories. Follow the docs here: * @@ -11,25 +15,25 @@ const story = { component: Banner, }; -function Template(args) { +function Template(args: BannerProps) { // eslint-disable-next-line react/jsx-props-no-spreading return ; } // Arguments can be passed to the component by binding // See: https://storybook.js.org/docs/react/writing-stories/introduction#using-args -const InfoBanner = Template.bind({}); +const InfoBanner: BannerStory = Template.bind({}); InfoBanner.args = { text: 'This is an informational banner', }; -const HTMLBanner = Template.bind({}); +const HTMLBanner: BannerStory = Template.bind({}); HTMLBanner.args = { text: 'This is a informational banner containing HTML', shouldRenderHTML: true, }; -const BannerWithLink = Template.bind({}); +const BannerWithLink: BannerStory = Template.bind({}); BannerWithLink.args = { text: 'This is a informational banner containing internal Link and public link', shouldRenderHTML: true, diff --git a/src/stories/Button.stories.js b/src/stories/Button.stories.tsx similarity index 80% rename from src/stories/Button.stories.js rename to src/stories/Button.stories.tsx index 2bf254b9f382..0ad1fc57b8df 100644 --- a/src/stories/Button.stories.js +++ b/src/stories/Button.stories.tsx @@ -1,29 +1,33 @@ /* eslint-disable react/jsx-props-no-spreading */ +import type {ComponentMeta, ComponentStory} from '@storybook/react'; import React, {useCallback, useState} from 'react'; import {View} from 'react-native'; +import type {ButtonProps} from '@components/Button'; import Button from '@components/Button'; import Text from '@components/Text'; +type ButtonStory = ComponentStory; + /** * We use the Component Story Format for writing stories. Follow the docs here: * * https://storybook.js.org/docs/react/writing-stories/introduction#component-story-format */ -const story = { +const story: ComponentMeta = { title: 'Components/Button', component: Button, }; -function Template(args) { +function Template(args: ButtonProps) { // eslint-disable-next-line react/jsx-props-no-spreading return