diff --git a/.eslintrc.js b/.eslintrc.js index b71338d0c1a5..85a4e86797b6 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -174,6 +174,7 @@ module.exports = { 'rulesdir/prefer-underscore-method': 'off', 'rulesdir/prefer-import-module-contents': 'off', 'react/require-default-props': 'off', + 'react/prop-types': 'off', 'no-restricted-syntax': [ 'error', { diff --git a/.github/actions/composite/setupGitForOSBotifyApp/action.yml b/.github/actions/composite/setupGitForOSBotifyApp/action.yml index 7a90cc45257d..a6c487705c56 100644 --- a/.github/actions/composite/setupGitForOSBotifyApp/action.yml +++ b/.github/actions/composite/setupGitForOSBotifyApp/action.yml @@ -60,6 +60,11 @@ runs: if: runner.debug == '1' run: echo "GIT_TRACE=true" >> "$GITHUB_ENV" + - name: Sync clock + shell: bash + run: sudo sntp -sS time.windows.com + if: runner.os == 'macOS' + - name: Generate a token id: generateToken uses: actions/create-github-app-token@9d97a4282b2c51a2f4f0465b9326399f53c890d4 diff --git a/.github/scripts/findUnusedKeys.sh b/.github/scripts/findUnusedKeys.sh index 1411fffc8389..193149e609af 100755 --- a/.github/scripts/findUnusedKeys.sh +++ b/.github/scripts/findUnusedKeys.sh @@ -6,10 +6,11 @@ LIB_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && cd ../../ && pwd)" readonly SRC_DIR="${LIB_PATH}/src" readonly STYLES_DIR="${LIB_PATH}/src/styles" -readonly STYLES_FILE="${LIB_PATH}/src/styles/styles.ts" -readonly UTILITIES_STYLES_FILE="${LIB_PATH}/src/styles/utilities" +readonly STYLES_FILE="${LIB_PATH}/src/styles/index.ts" +readonly UTILS_STYLES_FILE="${LIB_PATH}/src/styles/utils" +readonly UTILS_STYLES_GENERATORS_FILE="${LIB_PATH}/src/styles/utils/generators" readonly STYLES_KEYS_FILE="${LIB_PATH}/scripts/style_keys_list_temp.txt" -readonly UTILITY_STYLES_KEYS_FILE="${LIB_PATH}/scripts/utility_keys_list_temp.txt" +readonly UTIL_STYLES_KEYS_FILE="${LIB_PATH}/scripts/util_keys_list_temp.txt" readonly REMOVAL_KEYS_FILE="${LIB_PATH}/scripts/removal_keys_list_temp.txt" readonly AMOUNT_LINES_TO_SHOW=3 @@ -29,7 +30,7 @@ ctrl_c() { delete_temp_files exit 1 } - + count_lines() { local file=$1 if [[ -e "$file" ]]; then @@ -43,11 +44,11 @@ count_lines() { show_unused_style_keywords() { while IFS=: read -r key file line_number; do title "File: $file:$line_number" - + # Get lines before and after the error line local lines_before=$((line_number - AMOUNT_LINES_TO_SHOW)) local lines_after=$((line_number + AMOUNT_LINES_TO_SHOW)) - + # Read the lines into an array local lines=() while IFS= read -r line; do @@ -84,14 +85,14 @@ lookfor_unused_keywords() { # Search for keywords starting with "styles" while IFS= read -r keyword; do - + # Remove any [ ] characters from the keyword local clean_keyword="${keyword//[\[\]]/}" # skip styles. keyword that might be used in comments if [[ "$clean_keyword" == "styles." ]]; then continue fi - + if ! remove_keyword "$clean_keyword" ; then # In case of a leaf of the styles object is being used, it means the parent objects is being used # we need to mark it as used. @@ -99,7 +100,7 @@ lookfor_unused_keywords() { # Keyword has more than two words, remove words after the second word local keyword_prefix="${clean_keyword%.*}" remove_keyword "$keyword_prefix" - fi + fi fi done < <(grep -E -o '\bstyles\.[a-zA-Z0-9_.]*' "$file" | grep -v '\/\/' | grep -vE '\/\*.*\*\/') done < <(find "${SRC_DIR}" -type f \( "${FILE_EXTENSIONS[@]}" \)) @@ -134,10 +135,10 @@ find_styles_object_and_store_keys() { if [[ ! "$line" =~ ^[[:space:]]*(const|let|var)[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\{|^[[:space:]]*([a-zA-Z0-9_-]+\.)?[a-zA-Z0-9_-]+:[[:space:]]*\{|^[[:space:]]*\} ]]; then continue fi - + if [[ "$line" =~ ^[[:space:]]*(const|let|var)[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\{ ]]; then key="${BASH_REMATCH[2]%%:*{*)}" - echo "styles.${key}|...${key}|${base_name}.${key}:${file}:${line_number}" >> "$STYLES_KEYS_FILE" + echo "styles.${key}|...${key}|${base_name}.${key}:${file}:${line_number}" >> "$STYLES_KEYS_FILE" fi done < "$file" } @@ -225,7 +226,7 @@ find_theme_style_and_store_keys() { continue fi - + if [[ "$line" =~ ^[[:space:]]*([a-zA-Z0-9_-]+\.)?[a-zA-Z0-9_-]+:[[:space:]]*\{|^[[:space:]]*([a-zA-Z0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\(\{ ]]; then # Removing all the extra lines after the ":" local key="${line%%:*}" @@ -295,63 +296,64 @@ lookfor_unused_spread_keywords() { done < "$STYLES_FILE" } -find_utility_styles_store_prefix() { +find_util_styles_store_prefix() { # Loop through all files in the src folder while read -r file; do # Search for keywords starting with "styles" while IFS= read -r keyword; do local variable="${keyword##*/}" local variable_trimmed="${variable// /}" # Trim spaces - - echo "$variable_trimmed" >> "$UTILITY_STYLES_KEYS_FILE" - done < <(grep -E -o './utilities/[a-zA-Z0-9_-]+' "$file" | grep -v '\/\/' | grep -vE '\/\*.*\*\/') + + echo "$variable_trimmed" >> "$UTIL_STYLES_KEYS_FILE" + done < <(grep -E -o './utils/[a-zA-Z0-9_-]+' "$file" | grep -v '\/\/' | grep -vE '\/\*.*\*\/') done < <(find "${STYLES_DIR}" -type f \( "${FILE_EXTENSIONS[@]}" \)) # Sort and remove duplicates from the temporary file - sort -u -o "${UTILITY_STYLES_KEYS_FILE}" "${UTILITY_STYLES_KEYS_FILE}" + sort -u -o "${UTIL_STYLES_KEYS_FILE}" "${UTIL_STYLES_KEYS_FILE}" } -find_utility_usage_as_styles() { +find_util_usage_as_styles() { while read -r file; do local root_key local parent_dir - # Get the folder name, given this utility files are index.js + # Get the folder name, given this util files are index.js parent_dir=$(dirname "$file") root_key=$(basename "${parent_dir}") - if [[ "${root_key}" == "utilities" ]]; then + if [[ "${root_key}" == "utils" ]]; then continue fi find_theme_style_and_store_keys "${file}" 0 "${root_key}" - done < <(find "${UTILITIES_STYLES_FILE}" -type f \( "${FILE_EXTENSIONS[@]}" \)) + done < <(find "${UTILS_STYLES_FILE}" -type f \( -path "${UTILS_STYLES_GENERATORS_FILE}" -prune -o -name "${FILE_EXTENSIONS[@]}" \) -print) + } -lookfor_unused_utilities() { - # Read each utility keyword from the file +lookfor_unused_utils() { + # Read each util keyword from the file while read -r keyword; do - # Creating a copy so later the replacement can reference it + # Creating a copy so later the replacement can reference it local original_keyword="${keyword}" # Iterate through all files in "src/styles" while read -r file; do # Find all words that match "$keyword.[a-zA-Z0-9_-]+" while IFS= read -r match; do - # Replace the utility prefix with "styles" + # Replace the util prefix with "styles" local variable="${match/#$original_keyword/styles}" # Call the remove_keyword function with the variable remove_keyword "${variable}" remove_keyword "${match}" done < <(grep -E -o "$original_keyword\.[a-zA-Z0-9_-]+" "$file" | grep -v '\/\/' | grep -vE '\/\*.*\*\/') done < <(find "${STYLES_DIR}" -type f \( "${FILE_EXTENSIONS[@]}" \)) - done < "$UTILITY_STYLES_KEYS_FILE" + done < "$UTIL_STYLES_KEYS_FILE" } echo "🔍 Looking for styles." -# Find and store the name of the utility files as keys -find_utility_styles_store_prefix -find_utility_usage_as_styles +# Find and store the name of the util files as keys +find_util_styles_store_prefix +find_util_usage_as_styles # Find and store keys from styles.ts find_styles_object_and_store_keys "$STYLES_FILE" @@ -360,8 +362,8 @@ collect_theme_keys_from_styles "$STYLES_FILE" echo "🗄️ Now going through the codebase and looking for unused keys." -# Look for usages of utilities into src/styles -lookfor_unused_utilities +# Look for usages of utils into src/styles +lookfor_unused_utils lookfor_unused_spread_keywords lookfor_unused_keywords diff --git a/.github/workflows/createNewVersion.yml b/.github/workflows/createNewVersion.yml index 5f7f95e102e3..f772bfb818f0 100644 --- a/.github/workflows/createNewVersion.yml +++ b/.github/workflows/createNewVersion.yml @@ -32,12 +32,6 @@ on: OS_BOTIFY_COMMIT_TOKEN: description: OSBotify personal access token, used to workaround committing to protected branch required: true - OS_BOTIFY_APP_ID: - description: Application ID for OS Botify App - required: true - OS_BOTIFY_PRIVATE_KEY: - description: OSBotify private key - required: true jobs: validateActor: @@ -76,18 +70,16 @@ jobs: token: ${{ secrets.OS_BOTIFY_COMMIT_TOKEN }} - name: Setup git for OSBotify - uses: ./.github/actions/composite/setupGitForOSBotifyApp + uses: ./.github/actions/composite/setupGitForOSBotify id: setupGitForOSBotify with: GPG_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} - OS_BOTIFY_APP_ID: ${{ secrets.OS_BOTIFY_APP_ID }} - OS_BOTIFY_PRIVATE_KEY: ${{ secrets.OS_BOTIFY_PRIVATE_KEY }} - name: Generate version id: bumpVersion uses: ./.github/actions/javascript/bumpVersion with: - GITHUB_TOKEN: ${{ steps.setupGitForOSBotify.outputs.OS_BOTIFY_API_TOKEN }} + GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_COMMIT_TOKEN }} SEMVER_LEVEL: ${{ inputs.SEMVER_LEVEL }} - name: Commit new version diff --git a/.github/workflows/deployExpensifyHelp.yml b/.github/workflows/deployExpensifyHelp.yml index 82cd62c5e832..975f1f69e219 100644 --- a/.github/workflows/deployExpensifyHelp.yml +++ b/.github/workflows/deployExpensifyHelp.yml @@ -52,6 +52,14 @@ jobs: projectName: helpdot directory: ./docs/_site + - name: Setup Cloudflare CLI + run: pip3 install cloudflare + + - name: Purge Cloudflare cache + run: /home/runner/.local/bin/cli4 --delete hosts=["help.expensify.com"] /zones/:9ee042e6cfc7fd45e74aa7d2f78d617b/purge_cache + env: + CF_API_KEY: ${{ secrets.CLOUDFLARE_TOKEN }} + - name: Leave a comment on the PR uses: actions-cool/maintain-one-comment@de04bd2a3750d86b324829a3ff34d47e48e16f4b if: ${{ github.event_name == 'pull_request' }} diff --git a/.github/workflows/reassurePerformanceTests.yml b/.github/workflows/reassurePerformanceTests.yml index a58745b742ad..c49530c46faa 100644 --- a/.github/workflows/reassurePerformanceTests.yml +++ b/.github/workflows/reassurePerformanceTests.yml @@ -17,6 +17,11 @@ jobs: - name: Setup NodeJS uses: ./.github/actions/composite/setupNode + - name: Set dummy git credentials + run: | + git config --global user.email "test@test.com" + git config --global user.name "Test" + - name: Run performance testing script shell: bash run: | @@ -27,6 +32,7 @@ jobs: npm install --force npx reassure --baseline git switch --force --detach - + git merge --no-commit --allow-unrelated-histories "$BASELINE_BRANCH" -X ours npm install --force npx reassure --branch diff --git a/.storybook/theme.js b/.storybook/theme.js index 67898fb00943..08d8b584d580 100644 --- a/.storybook/theme.js +++ b/.storybook/theme.js @@ -1,5 +1,5 @@ import {create} from '@storybook/theming'; -import colors from '../src/styles/colors'; +import colors from '../src/styles/theme/colors'; export default create({ brandTitle: 'New Expensify UI Docs', diff --git a/Gemfile.lock b/Gemfile.lock index 93dab195ebdd..fcf4f878e2de 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -81,7 +81,8 @@ GEM declarative (0.0.20) digest-crc (0.6.5) rake (>= 12.0.0, < 14.0.0) - domain_name (0.6.20231109) + domain_name (0.5.20190701) + unf (>= 0.0.5, < 1.0.0) dotenv (2.8.1) emoji_regex (3.2.3) escape (0.0.4) @@ -261,6 +262,9 @@ GEM tzinfo (2.0.6) concurrent-ruby (~> 1.0) uber (0.1.0) + unf (0.1.4) + unf_ext + unf_ext (0.0.9.1) unicode-display_width (2.5.0) webrick (1.8.1) word_wrap (1.0.0) @@ -294,4 +298,4 @@ RUBY VERSION ruby 2.6.10p210 BUNDLED WITH - 2.1.4 + 2.4.7 diff --git a/__mocks__/@ua/react-native-airship.js b/__mocks__/@ua/react-native-airship.js index 29be662e96a1..65e7c1a8b97e 100644 --- a/__mocks__/@ua/react-native-airship.js +++ b/__mocks__/@ua/react-native-airship.js @@ -28,6 +28,7 @@ const Airship = { enableUserNotifications: () => Promise.resolve(false), clearNotifications: jest.fn(), getNotificationStatus: () => Promise.resolve({airshipOptIn: false, systemEnabled: false}), + getActiveNotifications: () => Promise.resolve([]), }, contact: { identify: jest.fn(), diff --git a/android/app/build.gradle b/android/app/build.gradle index f58e92f66ae8..6ad9e7f28407 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -91,8 +91,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001041001 - versionName "1.4.10-1" + versionCode 1001041306 + versionName "1.4.13-6" } flavorDimensions "default" diff --git a/android/app/src/main/java/com/expensify/chat/MainApplication.java b/android/app/src/main/java/com/expensify/chat/MainApplication.java index a4f2bc97416d..bc76be739949 100644 --- a/android/app/src/main/java/com/expensify/chat/MainApplication.java +++ b/android/app/src/main/java/com/expensify/chat/MainApplication.java @@ -3,7 +3,6 @@ import android.content.Context; import android.database.CursorWindow; -import androidx.appcompat.app.AppCompatDelegate; import androidx.multidex.MultiDexApplication; import com.expensify.chat.bootsplash.BootSplashPackage; @@ -67,9 +66,6 @@ public ReactNativeHost getReactNativeHost() { public void onCreate() { super.onCreate(); - // Use night (dark) mode so native UI defaults to dark theme. - AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); - SoLoader.init(this, /* native exopackage */ false); if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { // If you opted-in for the New Architecture, we load the native entry point for this app. diff --git a/assets/images/thumbs-up.svg b/assets/images/thumbs-up.svg new file mode 100644 index 000000000000..ef81c88fc854 --- /dev/null +++ b/assets/images/thumbs-up.svg @@ -0,0 +1,8 @@ + + + + + diff --git a/desktop/package-lock.json b/desktop/package-lock.json index 0ff280c4b9c6..6f62e6a5ba00 100644 --- a/desktop/package-lock.json +++ b/desktop/package-lock.json @@ -8,9 +8,9 @@ "license": "MIT", "dependencies": { "electron-context-menu": "^2.3.0", - "electron-log": "^4.4.7", - "electron-serve": "^1.0.0", - "electron-updater": "^6.1.4", + "electron-log": "^4.4.8", + "electron-serve": "^1.2.0", + "electron-updater": "^6.1.6", "node-machine-id": "^1.1.12" } }, @@ -50,9 +50,9 @@ } }, "node_modules/builder-util-runtime": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.1.tgz", - "integrity": "sha512-2rLv/uQD2x+dJ0J3xtsmI12AlRyk7p45TEbE/6o/fbb633e/S3pPgm+ct+JHsoY7r39dKHnGEFk/AASRFdnXmA==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.2.tgz", + "integrity": "sha512-Or2/ycVYRGQ876hKMfiz2Ghgzh3WllgPW75jqt1Ta2a5wprpnziFrHpQ9eUq6/ScsVXMnG4PmQqlMsE9NFg8DQ==", "dependencies": { "debug": "^4.3.4", "sax": "^1.2.4" @@ -140,21 +140,27 @@ "integrity": "sha512-R1oD5gMBPS7PVU8gJwH6CtT0e6VSoD0+SzSnYpNm+dBkcijgA+K7VAMHDfnRq/lkKPZArpzplTW6jfiMYosdzw==" }, "node_modules/electron-log": { - "version": "4.4.7", - "resolved": "https://registry.npmjs.org/electron-log/-/electron-log-4.4.7.tgz", - "integrity": "sha512-uFZQdgevOp9Fn5lDOrJMU/bmmYxDLZitbIHJM7VXN+cpB59ZnPt1FQL4bOf/Dl2gaIMPYJEfXx38GvJma5iV6A==" + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/electron-log/-/electron-log-4.4.8.tgz", + "integrity": "sha512-QQ4GvrXO+HkgqqEOYbi+DHL7hj5JM+nHi/j+qrN9zeeXVKy8ZABgbu4CnG+BBqDZ2+tbeq9tUC4DZfIWFU5AZA==" }, "node_modules/electron-serve": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/electron-serve/-/electron-serve-1.1.0.tgz", - "integrity": "sha512-tQJBCbXKoKCfkBC143QCqnEtT1s8dNE2V+b/82NF6lxnGO/2Q3a3GSLHtKl3iEDQgdzTf9pH7p418xq2rXbz1Q==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/electron-serve/-/electron-serve-1.2.0.tgz", + "integrity": "sha512-zJG3wisMrDn2G/gnjrhyB074COvly1FnS0U7Edm8bfXLB8MYX7UtwR9/y2LkFreYjzQHm9nEbAfgCmF+9M9LHQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/electron-updater": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.1.4.tgz", - "integrity": "sha512-yYAJc6RQjjV4WtInZVn+ZcLyXRhbVXoomKEfUUwDqIk5s2wxzLhWaor7lrNgxODyODhipjg4SVPMhJHi5EnsCA==", + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.1.6.tgz", + "integrity": "sha512-G2bO72i7kv+bVdBAjq6lQn8zkZ3wMRRjxBD4TGBjB77UiuMDUBeP45YAs4y08udPzttGW2qzpnbOUJY5RYWZfw==", "dependencies": { - "builder-util-runtime": "9.2.1", + "builder-util-runtime": "9.2.2", "fs-extra": "^10.1.0", "js-yaml": "^4.1.0", "lazy-val": "^1.0.5", @@ -461,9 +467,9 @@ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==" }, "builder-util-runtime": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.1.tgz", - "integrity": "sha512-2rLv/uQD2x+dJ0J3xtsmI12AlRyk7p45TEbE/6o/fbb633e/S3pPgm+ct+JHsoY7r39dKHnGEFk/AASRFdnXmA==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.2.tgz", + "integrity": "sha512-Or2/ycVYRGQ876hKMfiz2Ghgzh3WllgPW75jqt1Ta2a5wprpnziFrHpQ9eUq6/ScsVXMnG4PmQqlMsE9NFg8DQ==", "requires": { "debug": "^4.3.4", "sax": "^1.2.4" @@ -525,21 +531,21 @@ "integrity": "sha512-R1oD5gMBPS7PVU8gJwH6CtT0e6VSoD0+SzSnYpNm+dBkcijgA+K7VAMHDfnRq/lkKPZArpzplTW6jfiMYosdzw==" }, "electron-log": { - "version": "4.4.7", - "resolved": "https://registry.npmjs.org/electron-log/-/electron-log-4.4.7.tgz", - "integrity": "sha512-uFZQdgevOp9Fn5lDOrJMU/bmmYxDLZitbIHJM7VXN+cpB59ZnPt1FQL4bOf/Dl2gaIMPYJEfXx38GvJma5iV6A==" + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/electron-log/-/electron-log-4.4.8.tgz", + "integrity": "sha512-QQ4GvrXO+HkgqqEOYbi+DHL7hj5JM+nHi/j+qrN9zeeXVKy8ZABgbu4CnG+BBqDZ2+tbeq9tUC4DZfIWFU5AZA==" }, "electron-serve": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/electron-serve/-/electron-serve-1.1.0.tgz", - "integrity": "sha512-tQJBCbXKoKCfkBC143QCqnEtT1s8dNE2V+b/82NF6lxnGO/2Q3a3GSLHtKl3iEDQgdzTf9pH7p418xq2rXbz1Q==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/electron-serve/-/electron-serve-1.2.0.tgz", + "integrity": "sha512-zJG3wisMrDn2G/gnjrhyB074COvly1FnS0U7Edm8bfXLB8MYX7UtwR9/y2LkFreYjzQHm9nEbAfgCmF+9M9LHQ==" }, "electron-updater": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.1.4.tgz", - "integrity": "sha512-yYAJc6RQjjV4WtInZVn+ZcLyXRhbVXoomKEfUUwDqIk5s2wxzLhWaor7lrNgxODyODhipjg4SVPMhJHi5EnsCA==", + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.1.6.tgz", + "integrity": "sha512-G2bO72i7kv+bVdBAjq6lQn8zkZ3wMRRjxBD4TGBjB77UiuMDUBeP45YAs4y08udPzttGW2qzpnbOUJY5RYWZfw==", "requires": { - "builder-util-runtime": "9.2.1", + "builder-util-runtime": "9.2.2", "fs-extra": "^10.1.0", "js-yaml": "^4.1.0", "lazy-val": "^1.0.5", diff --git a/desktop/package.json b/desktop/package.json index bf49d93f1a7b..7545e4b57dba 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -5,9 +5,9 @@ "scripts": {}, "dependencies": { "electron-context-menu": "^2.3.0", - "electron-log": "^4.4.7", - "electron-serve": "^1.0.0", - "electron-updater": "^6.1.4", + "electron-log": "^4.4.8", + "electron-serve": "^1.2.0", + "electron-updater": "^6.1.6", "node-machine-id": "^1.1.12" }, "author": "Expensify, Inc.", diff --git a/docs/_includes/floating-concierge-button.html b/docs/_includes/floating-concierge-button.html deleted file mode 100644 index ed183058388f..000000000000 --- a/docs/_includes/floating-concierge-button.html +++ /dev/null @@ -1,5 +0,0 @@ -{% include CONST.html %} - - - Chat with concierge - diff --git a/docs/_includes/footer.html b/docs/_includes/footer.html index d18ca2199e33..798fb2cf7e96 100644 --- a/docs/_includes/footer.html +++ b/docs/_includes/footer.html @@ -111,6 +111,8 @@

Get Started

+ + diff --git a/docs/_includes/platform.html b/docs/_includes/platform.html index 6aa88f9208ae..a5653b89d7a8 100644 --- a/docs/_includes/platform.html +++ b/docs/_includes/platform.html @@ -10,9 +10,4 @@

{{ platform.hub-title }}

{% include hub-card.html hub=hub platform=selectedPlatform %} {% endfor %} - -
- - {% include floating-concierge-button.html id="floating-concierge-button-global" %} -
diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html index de3fbc203243..7d98500ecf32 100644 --- a/docs/_layouts/default.html +++ b/docs/_layouts/default.html @@ -57,9 +57,6 @@
{% endif %} - - - {% include floating-concierge-button.html id="floating-concierge-button-lhn" %}
diff --git a/docs/_sass/_colors.scss b/docs/_sass/_colors.scss index 5556c43d87f6..f0c89d31c580 100644 --- a/docs/_sass/_colors.scss +++ b/docs/_sass/_colors.scss @@ -1,14 +1,36 @@ -$color-green400: #03D47C; -$color-green-icons: #8B9C8F; -$color-green-borders: #1A3D32; -$color-button-background: #1A3D32; -$color-button-hovered: #2A604F; -$color-green-highlightBG: #072419; -$color-green-highlightBG-hover: #06231c; -$color-green-appBG: #061B09; -$color-green-hover: #00a862; -$color-light-gray-green: #AFBBB0; -$color-blue300: #5AB0FF; +// Product Color Spectrum +$color-product-dark-100: #061B09; +$color-product-dark-200: #072419; +$color-product-dark-300: #0A2E25; +$color-product-dark-400: #1A3D32; +$color-product-dark-500: #224F41; +$color-product-dark-600: #2A604F; +$color-product-dark-700: #8B9C8F; +$color-product-dark-800: #AFBBB0; +$color-product-dark-900: #E7ECE9; + +// Colors for Links and Success $color-blue200: #B0D9FF; -$color-white: #E7ECE9; -$color-gray-label: #afbbb0; +$color-blue300: #5AB0FF; +$color-green400: #03D47C; +$color-green500: #00a862; + +// Overlay BG color +$color-overlay-background: rgba(26, 61, 50, 0.72); + +// UI Colors +$color-text: $color-product-dark-900; +$color-text-supporting: $color-product-dark-800; +$color-icons: $color-product-dark-700; +$color-borders: $color-product-dark-400; +$color-highlightBG: $color-product-dark-200; +$color-row-hover: $color-product-dark-300; +$color-appBG: $color-product-dark-100; +$color-success: $color-green400; +$color-accent : $color-green400; +$color-link: $color-blue300; +$color-link-hovered: $color-blue200; +$color-button-background: $color-product-dark-400; +$color-button-background-hover: $color-product-dark-500; +$color-button-success-background: $color-green400; +$color-button-success-background-hover: $color-green500; diff --git a/docs/_sass/_main.scss b/docs/_sass/_main.scss index 7a0804b0f962..9276443c3813 100644 --- a/docs/_sass/_main.scss +++ b/docs/_sass/_main.scss @@ -3,19 +3,6 @@ @import 'fonts'; @import 'search-bar'; -$color-appBG: $color-green-appBG; -$color-highlightBG: $color-green-highlightBG; -$color-accent : $color-green400; -$color-borders: $color-green-borders; -$color-icons: $color-green-icons; -$color-text: $color-white; -$color-link: $color-blue300; -$color-link-hovered: $color-blue200; -$color-success: $color-green400; -$color-text-supporting: $color-light-gray-green; -$color-green-hover: $color-green-hover; -$color-gray-label: $color-gray-label; - * { margin: 0; padding: 0; @@ -78,6 +65,7 @@ body { height: 100%; min-height: 100%; background: $color-appBG; + color: $color-text-supporting; } hr { @@ -148,7 +136,7 @@ textarea { font-weight: 400; font-family: "ExpensifyNeue", "Helvetica Neue", "Helvetica", Arial, sans-serif; font-size: 16px; - color: $color-text; + color: $color-text-supporting; } button { @@ -159,7 +147,7 @@ button { font-weight: bold; &.success { - background-color: $color-success; + background-color: $color-button-success-background; color: $color-text; width: 100%; border-radius: 100px; @@ -167,7 +155,7 @@ button { padding-right: 20px; &:hover { - background-color: desaturate($color-success, 15%); + background-color: $color-button-success-background-hover; cursor: pointer; } @@ -262,6 +250,7 @@ button { .lhn-header { padding: 24px; + @include breakpoint($breakpoint-tablet) { padding: 44px; } @@ -269,6 +258,7 @@ button { #header-button { display: block; padding-right: 24px; + @include breakpoint($breakpoint-tablet) { display: none; } @@ -282,7 +272,7 @@ button { margin-right: auto; @include breakpoint($breakpoint-desktop) { - width: 210px; + width: 180px; align-content: normal; display: flex; margin-left: 0; @@ -372,7 +362,7 @@ button { // Box shadow is used here because border-radius and border-collapse don't work together. It leads to double borders. // https://stackoverflow.com/questions/628301/the-border-radius-property-and-border-collapsecollapse-dont-mix-how-can-i-use border-style: hidden; - box-shadow: 0 0 0 1px $color-green-borders; + box-shadow: 0 0 0 1px $color-borders; } th:first-child { @@ -394,12 +384,12 @@ button { th, td { padding: 6px 13px; - border: 1px solid $color-green-borders; + border: 1px solid $color-borders; } thead tr th { font-weight: bold; - background-color: $color-green-highlightBG; + background-color: $color-highlightBG; } .img-wrap { @@ -457,11 +447,11 @@ button { .link { display: inline; - color: $color-link; + color: $color-text-supporting; cursor: pointer; &:hover { - color: $color-link-hovered; + color: $color-link; } } @@ -530,7 +520,7 @@ button { background-color: $color-highlightBG; &:hover { - background-color: darken($color-highlightBG, 1%); + background-color: $color-row-hover; } .row { @@ -629,13 +619,14 @@ button { } p.description { + color: $color-text-supporting; padding: 20px 0 20px 0; } p.url { padding: 0; font-size: 0.8em; - color: $color-gray-label; + color: $color-text-supporting; } } @@ -721,39 +712,9 @@ button { } } -#floating-concierge-button-global { - position: fixed; - display: block; - @include breakpoint($breakpoint-tablet) { - display: none; - } -} - -#floating-concierge-button-lhn { - position: absolute; - display: none; - @include breakpoint($breakpoint-tablet) { - display: block; - } -} - .get-help { flex-wrap: wrap; - margin-top: auto; -} - -.floating-concierge-button { - bottom: 2rem; - right: 2rem; - - img { - width: 4rem; - height: 4rem; - - &:hover { - filter: saturate(2); - } - } + margin-top: 40px; } .disable-scrollbar { @@ -773,9 +734,12 @@ button { h3 { color: $color-success; + font-family: "ExpensifyNewKansas", "Helvetica Neue", "Helvetica", Arial, sans-serif; font-size: 17px; - font-weight: 700; + font-weight: 500; + padding: 0; margin-bottom: 16px; + margin-top: 0; } ul { @@ -787,13 +751,13 @@ button { margin: 0 0 8px; a { - color: $color-text; + color: $color-text-supporting; display: block; padding: 4px 0; word-break: break-word; &:hover { - color: $color-success; + color: $color-link; } } } @@ -803,11 +767,11 @@ button { padding-bottom: 20px; a { - color: $color-text; + color: $color-text-supporting; display: inline-block; &:hover { - color: $color-success; + color: $color-link; } } } diff --git a/docs/_sass/_search-bar.scss b/docs/_sass/_search-bar.scss index c2185ef8f36a..f414d25fc266 100644 --- a/docs/_sass/_search-bar.scss +++ b/docs/_sass/_search-bar.scss @@ -2,20 +2,6 @@ @import 'colors'; @import 'fonts'; -$color-appBG: $color-green-appBG; -$color-highlightBG: $color-green-highlightBG; -$color-highlightBG-hover: $color-green-highlightBG-hover; -$color-accent : $color-green400; -$color-borders: $color-green-borders; -$color-icons: $color-green-icons; -$color-text: $color-white; -$color-link: $color-blue300; -$color-link-hovered: $color-blue200; -$color-success: $color-green400; -$color-text-supporting: $color-light-gray-green; -$color-green-hover: $color-green-hover; -$color-gray-label: $color-gray-label; - .search-icon { margin: auto 0px; } @@ -81,7 +67,7 @@ $color-gray-label: $color-gray-label; left: 0; right: 0; bottom: 0; - background-color: rgba(0, 0, 0, 0.4); + background-color: $color-overlay-background; z-index: 1; } @@ -158,7 +144,7 @@ label.search-label { transform: translateY(-50%); left: 20px; pointer-events: none; - color: $color-gray-label; + color: $color-text-supporting; transform-origin: left top; user-select: none; transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1), color 150ms cubic-bezier(0.4, 0, 0.2, 1), top 500ms; @@ -194,14 +180,14 @@ label.search-label { margin-left: 15px; margin-right: 20px; border-radius: 25px; - background-color: $color-green400; + background-color: $color-button-success-background; cursor: pointer; width: 40px; height: 40px; } .gsc-search-button.gsc-search-button-v2:hover { - background-color: $color-green-hover; + background-color: $color-button-success-background-hover; } .gsc-search-button.gsc-search-button-v2 svg { @@ -227,7 +213,7 @@ label.search-label { /* Change Font result Paragraph color */ .gsc-results .gs-webResult:not(.gs-no-results-result):not(.gs-error-result) .gs-snippet, .gs-fileFormatType { - color: $color-text; + color: $color-text-supporting; } @@ -278,7 +264,7 @@ label.search-label { color: $color-text; &:hover { - background-color: $color-button-hovered; + background-color: $color-button-background-hover; text-decoration: none; } } diff --git a/docs/articles/expensify-classic/account-settings/Profile-Settings.md b/docs/articles/expensify-classic/account-settings/Profile-Settings.md deleted file mode 100644 index 3b2a0b830926..000000000000 --- a/docs/articles/expensify-classic/account-settings/Profile-Settings.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Profile Settings -description: Profile Settings ---- -## Resource Coming Soon! diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Personal-Cards.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Personal-Cards.md deleted file mode 100644 index 71edcdeba00d..000000000000 --- a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Personal-Cards.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Personal Cards -description: Connect your credit card directly to Expensify to easily track your personal finances. ---- -## Resource Coming Soon! diff --git a/docs/articles/expensify-classic/getting-started/Referral-Program.md b/docs/articles/expensify-classic/get-paid-back/Referral-Program.md similarity index 100% rename from docs/articles/expensify-classic/getting-started/Referral-Program.md rename to docs/articles/expensify-classic/get-paid-back/Referral-Program.md diff --git a/docs/articles/expensify-classic/getting-started/Mobile-App.md b/docs/articles/expensify-classic/getting-started/Mobile-App.md deleted file mode 100644 index 7fa57abbdf61..000000000000 --- a/docs/articles/expensify-classic/getting-started/Mobile-App.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Using the App -description: Using the App ---- -## Resource Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/accounting-integrations/Certinia.md b/docs/articles/expensify-classic/integrations/accounting-integrations/Certinia.md new file mode 100644 index 000000000000..65361ba1af9a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/accounting-integrations/Certinia.md @@ -0,0 +1,150 @@ +--- +title: Certinia +description: Guide to connecting Expensify and Certinia FFA and PSA/SRP (formerly known as FinancialForce) +--- +# Overview +[Cetinia](https://use.expensify.com/financialforce) (Formerly known as FinancialForce)is a cloud-based software solution that provides a range of financial management and accounting applications built on the Salesforce platform. There are two versions: PSA/SRP and FFA and we support both. + +# Before connecting to Certinia +Install the Expensify bundle in Certinia using the relevant installer: +* [PSA/SRP](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t2M000002J0BHD%252Fpackaging%252FinstallPackage.apexp%253Fp0%253D04t2M000002J0BH) +* [FFA](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t4p000001UQVj) + +## Check contact details in Certinia +First, make sure you have a user and contact in Certinia that match your main email in Expensify. Then, create contacts for all employees who will be sending expense reports. Ensure that each contact's email matches the one they use in their Expensify account. + +## If you use PSA/SRP +Each report approver needs both a User and a Contact. The user does not need to have a SalesForce license. These can be free chatter users. +Set permission controls in Certinia for your user for each contact/resource. +* Go to Permission Controls + - Create a new permission control + - Set yourself (exporter) as the user + - Select the resource (report submitter) + - Grant all available permissions +* Set permissions on any project you are exporting to + - Go to **Projects** > _select a project_ > **Project Attributes** > **Allow Expenses Without Assignment** + - Select the project > **Edit** + - Under the Project Attributes section, check **Allow Expenses Without Assignment** +* Set up Expense Types (categories in Expensify - _SRP only_) + - Go to **Main Menu** > _+ symbol_ > **Expense Type GLA Mappings** + - Click **New** to add new mappings + +# How to connect to Certinia +1. Go to **Settings** > **Workspaces** > **Groups** > _[Workspace Name]_ > **Connections** in Expensify +2. Click **Create a New Certinia (FinancialForce) Connection** +3. Log into your Certinia account +4. Expensify and Certinia will begin to sync (in Expensify) + +# How to configure export settings for Certinia +## Preferred Exporter +The preferred exporter is the user who will be the main exporter of reports. This person will receive the notifications for errors. + +## Payable Invoice Status and Date +Reports can be exported as Complete or In Progress, using date of last expense, submitted date or exported date. + +## Reimbursable and non-reimbursable exports +Both reimbursable and non-reimbursable reports are exported as payable invoices (FFA) or expense reports (PSA/SRP). If you have both Reimbursable and Non-Reimbursable expenses on a single report, we will create a separate payable invoice/expense report for each type. + +## Default Vendor (FFA) +Choose from the full list of vendors from your Certinia FFA account, this will be applied to the non-reimbursable payable invoices. + +# How to Configure coding for Certinia +## Company +Select which FinancialForce company to import from/export to. + +## Chart of Accounts (FFA) +Prepaid Expense Type and Profit & Loss accounts are imported to be used as categories on each expense. + +## Expense Type GLA Mappings (PSA/SRP) +Your Expense Type GLA Mappings are enabled in Expensify to use as categories on each expense when using both PSA and SRP; however, PSA will not import or export categories, while SRP will. + +## Dimensions (FFA) +We import four dimension levels and each has three options to select from: + +* Do not map: FinancialForce defaults will apply to the payable invoice, without importing into Expensify +* Tags: These are shown in the Tag section of your workspace, and employees can select them on each expense created +* Report fields: These will show in the Reports section of your workspace. Employees can select one to be applied at the header level i.e. the entire report. + +## Projects, Assignments, or Projects & Assignments (PSA/SRP) +These can be imported as tags with **Milestones** being optional. When selecting to import only projects, we will derive the account from the project. If an assignment is selected, we will derive both the account and project from the assignment. + +Note: If you are using a project that does not have an assignment, the box **Allow Expenses Without Assignment** must be checked on the project in FinancialForce. + +## Tax +Import tax rates from Certinia to apply to expenses. + +# How to configure advanced settings for Certinia +## Auto Sync +Auto Sync in Certinia performs daily updates to your coding. Additionally, it automatically exports reports after they receive final approval. For Non-Reimbursable expenses, syncing happens immediately upon final approval of the report. In the case of Reimbursable expenses, syncing occurs as soon as the report is reimbursed or marked as reimbursed. + +## Export tax as non-billable +When exporting Billable expenses, this dictates whether you will also bill the tax component to your clients/customers. + +# Deep Dive +## Multi-Currency in Certinia PSA/SRP +When exporting to Certinia PSA/SRP you may see up to three different currencies on the expense report in Certinia, if employees are submitting expenses in more than one original currency. +* Summary Total Reimbursement Amount: this currency is derived from the currency of the project selected on the expense. +* Amount field on the Expense line: this currency is derived from the Expensify workspace default report currency. +* Reimbursable Amount on the Expense line: this currency is derived from the currency of the resource with an email matching the report submitter. + +# FAQ +## What happens if the report can’t be exported to Certinia? +* The preferred exporter will receive an email outlining the issue and any specific error messages +* Any error messages preventing the export from taking place will be recorded in the report’s history +* The report will be listed in the exporter’s Expensify Inbox as awaiting export. + +## If I enable Auto Sync, what happens to existing approved and reimbursed reports? +You can activate Auto Sync without worry because it relies on Final Approval to trigger auto-export. Existing Approved reports won't be affected. However, for Approved reports that haven't been exported to Certinia, you'll need to either manually export them or mark them as manually entered. + +## How do I export tax? +Tax rates are created in Expensify through the tax tracking feature under **Settings** > **Workspaces** > **Groups** > _[Workspace Name]_ > **Tax**. We export the tax amount calculated on the expenses. + +## How do reports map to Payable Invoices in Certinia FFA? +* Account Name - Account associated with Expensify submitter’s email address +* Reference 1 - Report URL +* Invoice Description - Report title + +## How do reports map to Expense Reports in Certinia PSA/SRP? +* Expense report name - Report title +* Resource - User associated with Expensify submitter’s email address +* Description - Report URL +* Approver - Expensify report approver + +# Sync and Export Errors + +## ExpensiError FF0047: You must have an Ops Edit permission to edit approved records. +This error indicates that the permission control setup between the connected user and the report submitter or region is missing Ops Edit permission. + +In Certinia go to Permission Controls and click the one you need to edit. Make sure that Expense Ops Edit is selected under Permissions. + +## ExpensiError FF0076: Could not find employee in Certinia +Go to Contacts in Certinia and add the report creator/submitter's Expensify email address to their employee record, or create a record with that email listed. + +If a record already exists then search for their email address to confirm it is not associated with multiple records. + +## ExpensiError FF0089: Expense Reports for this Project require an Assignment +This error indicates that the project needs to have the permissions adjusted in Certinia + +Go to Projects > [project name] > Project Attributes and check Allow Expense Without Assignment. + +## ExpensiError FF0091: Bad Field Name — [field] is invalid for [object] +This means the field in question is not accessible to the user profile in Certinia for the user whose credentials were used to make the connection within Expensify. + +To correct this: +* Go to Setup > Build > expand Create > Object within Certinia +* Then go to Payable Invoice > Custom Fields and Relationships +* Click View Field Accessibility +* Find the employee profile in the list and select Hidden +* Make sure both checkboxes for Visible are selected + +Once this step has been completed, sync the connection within Expensify by going to **Settings** > **Workspaces** > **Groups** > _[Workspace Name]_ > **Connections** > **Sync Now** and then attempt to export the report again. + +## ExpensiError FF0132: Insufficient access. Make sure you are connecting to Certinia with a user that has the 'Modify All Data' permission + +Log into Certinia and go to Setup > Manage Users > Users and find the user whose credentials made the connection. + +* Click on their profile on the far right side of the page +* Go to System > System Permissions +* Enable Modify All Data and save + +Sync the connection within Expensify by going to **Settings** > **Workspaces** > **Groups** > _[Workspace Name]_ > **Connections** > **Sync Now** and then attempt to export the report again diff --git a/docs/articles/expensify-classic/integrations/accounting-integrations/FinancalForce.md b/docs/articles/expensify-classic/integrations/accounting-integrations/FinancalForce.md deleted file mode 100644 index 18c78ac7fc10..000000000000 --- a/docs/articles/expensify-classic/integrations/accounting-integrations/FinancalForce.md +++ /dev/null @@ -1,111 +0,0 @@ ---- -title: Financial Force -description: Guide to connecting Expensify and FinancialForce FFA and PSA/SRP ---- -# Overview -[FinancialForce](https://use.expensify.com/financialforce) is a cloud-based software solution that provides a range of financial management and accounting applications built on the Salesforce platform. There are two versions: PSA/SRP and FFA and we support both. - -# Before connecting to FinancialForce -Install the Expensify bundle in SalesForce using the relevant installer: -* [PSA/SRP](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t2M000002J0BHD%252Fpackaging%252FinstallPackage.apexp%253Fp0%253D04t2M000002J0BH) -* [FFA](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t4p000001UQVj) - -## Check contact details in FF -First, make sure you have a user and contact in FinancialForce that match your main email in Expensify. Then, create contacts for all employees who will be sending expense reports. Ensure that each contact's email matches the one they use in their Expensify account. - -## If you use PSA/SRP -Each report approver needs both a User and a Contact. The user does not need to have a SalesForce license. These can be free chatter users. -Set permission controls in FinancialForce for your user for each contact/resource. -* Go to Permission Controls - - Create a new permission control - - Set yourself (exporter) as the user - - Select the resource (report submitter) - - Grant all available permissions -* Set permissions on any project you are exporting to - - Go to **Projects** > _select a project_ > **Project Attributes** > **Allow Expenses Without Assignment** - - Select the project > **Edit** - - Under the Project Attributes section, check **Allow Expenses Without Assignment** -* Set up Expense Types (categories in Expensify - _SRP only_) - - Go to **Main Menu** > _+ symbol_ > **Expense Type GLA Mappings** - - Click **New** to add new mappings - -# How to connect to FinancialForce -1. Go to **Settings** > **Policies** > **Groups** > _[Policy Name]_ > **Connections in Expensify** -2. Click **Create a New FinancialForce Connection** -3. Log into your FinancialForce account -4. Expensify and FinancialForce will begin to sync (in Expensify) - -# How to configure export settings for FinancialForce -## Preferred Exporter -The preferred exporter is the user who will be the main exporter of reports. This person will receive the notifications for errors. - -## Payable Invoice Status and Date -Reports can be exported as Complete or In Progress, using date of last expense, submitted date or exported date. - -## Reimbursable and non-reimbursable exports -Both reimbursable and non-reimbursable reports are exported as payable invoices (FFA) or expense reports (PSA/SRP). If you have both Reimbursable and Non-Reimbursable expenses on a single report, we will create a separate payable invoice/expense report for each type. - -## Default Vendor (FFA) -Choose from the full list of vendors from your FinancialForce FFA account, this will be applied to the non-reimbursable payable invoices. - -# How to Configure coding for Financial Force -## Company -Select which FinancialForce company to import from/export to. - -## Chart of Accounts (FFA) -Prepaid Expense Type and Profit & Loss accounts are imported to be used as categories on each expense. - -## Expense Type GLA Mappings (PSA/SRP) -Your Expense Type GLA Mappings are enabled in Expensify to use as categories on each expense when using both PSA and SRP; however, PSA will not import or export categories, while SRP will. - -## Dimensions (FFA) -We import four dimension levels and each has three options to select from: - -* Do not map: FinancialForce defaults will apply to the payable invoice, without importing into Expensify -* Tags: These are shown in the Tag section of your policy, and employees can select them on each expense created -* Report fields: These will show in the Reports section of your policy. Employees can select one to be applied at the header level i.e. the entire report. - -## Projects, Assignments, or Projects & Assignments (PSA/SRP) -These can be imported as tags with **Milestones** being optional. When selecting to import only projects, we will derive the account from the project. If an assignment is selected, we will derive both the account and project from the assignment. - -Note: If you are using a project that does not have an assignment, the box **Allow Expenses Without Assignment** must be checked on the project in FinancialForce. - -## Tax -Import tax rates from FinancialForce to apply to expenses. - -# How to configure advanced settings for Financial Force -## Auto Sync -Auto Sync in FinancialForce performs daily updates to your coding. Additionally, it automatically exports reports after they receive final approval. For Non-Reimbursable expenses, syncing happens immediately upon final approval of the report. In the case of Reimbursable expenses, syncing occurs as soon as the report is reimbursed or marked as reimbursed. - -## Export tax as non-billable -When exporting Billable expenses, this dictates whether you will also bill the tax component to your clients/customers. - -# Deep Dive -## Multi-Currency in FinancialForce PSA/SRP -When exporting to FinancialForce PSA/SRP you may see up to three different currencies on the expense report in FinancialForce, if employees are submitting expenses in more than one original currency. -* Summary Total Reimbursement Amount: this currency is derived from the currency of the project selected on the expense. -* Amount field on the Expense line: this currency is derived from the Expensify policy default report currency. -* Reimbursable Amount on the Expense line: this currency is derived from the currency of the resource with an email matching the report submitter. - -# FAQ -## What happens if the report can’t be exported to FinancialForce? -* The preferred exporter will receive an email outlining the issue and any specific error messages -* Any error messages preventing the export from taking place will be recorded in the report’s history -* The report will be listed in the exporter’s Expensify Inbox as awaiting export. - -## If I enable Auto Sync, what happens to existing approved and reimbursed reports? -You can activate Auto Sync without worry because it relies on Final Approval to trigger auto-export. Existing Approved reports won't be affected. However, for Approved reports that haven't been exported to FinancialForce, you'll need to either manually export them or mark them as manually entered. - -## How do I export tax? -Tax rates are created in Expensify through the tax tracking feature under **Settings** > **Policies** > **Group** > _[Policy Name]_ > **Tax**. We export the tax amount calculated on the expenses. - -## How do reports map to Payable Invoices in FinancialForce FFA? -* Account Name - Account associated with Expensify submitter’s email address -* Reference 1 - Report URL -* Invoice Description - Report title - -## How do reports map to Expense Reports in FinancialForce PSA/SRP? -* Expense report name - Report title -* Resource - User associated with Expensify submitter’s email address -* Description - Report URL -* Approver - Expensify report approver diff --git a/docs/articles/expensify-classic/workspace-and-domain-settings/Budgets.md b/docs/articles/expensify-classic/workspace-and-domain-settings/Budgets.md new file mode 100644 index 000000000000..3c5bc0fe2421 --- /dev/null +++ b/docs/articles/expensify-classic/workspace-and-domain-settings/Budgets.md @@ -0,0 +1,56 @@ +--- +title: Budgets +description: Track employee spending across categories and tags by using Expensify's Budgets feature. +--- + +# About +Expensify’s Budgets feature allows you to: +- Set monthly and yearly budgets +- Track spending across categories and tags on an individual and workspace basis +- Get notified when a budget has met specific thresholds + +# How-to +## Category Budgets +1. Navigate to **Settings > Group > [Workspace Name] > Categories** +2. Click the **Edit Rules** button for the category you want to add a budget to +3. Select the **Budget** tab at the top of the modal that opens +4. Click the switch next to **Enable Budget** +5. Once enabled, you will see additional settings to configure: + - **Budget frequency**: you can select if you want this to be a monthly or a yearly budget + - **Total workspace budget**: you can enter an amount if you want to set a budget for the entire workspace + - **Per individual budget**: you can enter an amount if you want to set a budget per person + - **Notification threshold** - this is the % in which you will be notified as the budgets are hit + +## Single-level Tag Budgets +1. Navigate to **Settings > Group > [Workspace Name] > Tags** +2. Click **Edit Budget** next to the tag you want to add a budget to +3. Click the switch next to **Enable Budget** +4. Once enabled, you will see additional settings to configure: + - **Budget frequency**: you can select if you want this to be a monthly or a yearly budget + - **Total workspace budget**: you can enter an amount if you want to set a budget for the entire workspace + - **Per individual budget**: you can enter an amount if you want to set a budget per person + - **Notification threshold** - this is the % in which you will be notified as the budgets are hit + +## Multi-level Tag Budgets +1. Navigate to **Settings > Group > [Workspace Name] > Tags** +2. Click the **Edit Tags** button +3. Click the **Edit Budget** button next to the subtag you want to apply a budget to +4. Click the switch next to **Enable Budget** +5. Once enabled, you will see additional settings to configure: + - **Budget frequency**: you can select if you want this to be a monthly or a yearly budget + - **Total workspace budget**: you can enter an amount if you want to set a budget for the entire workspace + - **Per individual budget**: you can enter an amount if you want to set a budget per person + - **Notification threshold** - this is the % in which you will be notified as the budgets are hit + +# FAQ +## Can I import budgets as a CSV? +At this time, you cannot import budgets via CSV since we don’t import categories or tags from direct accounting integrations. + +## When will I be notified as a budget is hit? +Notifications are sent twice: + - When your notification threshold is hit (i.e, if you set this as 50%, you’ll be notified when 50% of the budget is met) + - When 100% of the budget is met + +## How will I be notified when a budget is hit? +A message will be sent in the #admins room of the Workspace. + diff --git a/docs/articles/expensify-classic/workspace-and-domain-settings/Domains-Overview.md b/docs/articles/expensify-classic/workspace-and-domain-settings/Domains-Overview.md index 3ee1c8656b4b..cf2f0f59a4a0 100644 --- a/docs/articles/expensify-classic/workspace-and-domain-settings/Domains-Overview.md +++ b/docs/articles/expensify-classic/workspace-and-domain-settings/Domains-Overview.md @@ -1,5 +1,140 @@ --- -title: Coming Soon -description: Coming Soon +title: Domains +description: Want to gain greater control over your company settings in Expensify? Read on to find out more about our Domains feature and how it can help you save time and effort when managing your company expenses. --- -## Resource Coming Soon! + +# Overview +Domains is a feature in Expensify that allows admins to have more nuanced control over a specific Expensify activity, as well as providing a bird’s eye view of company card expenditure. Think of it as your command center for things like managing user account access, enforcing stricter Workspace rules for certain groups, or issuing cards and reconciling statements. +There are several settings within Domains that you can configure so that you have more control and visibility into your organization’s settings. Those features are: +- Company Cards +- Domain Admins +- Domain Members + - Two-Factor Authentication +- Domain Groups + - Domain Group Settings +- Reporting Tools +- SAML + +There are two ways to use Domains – as an unverified domain or a verified domain. An unverified domain allows you to import Company Cards and manage them, whereas a verified domain allows you to do that in addition to: +1. Receive vendor bills in Expensify +2. Fine-tune user restrictions using domain Groups +3. Configure SAML SSO for easier login to Expensify +4. Set vacation delegates for your domain members +5. Use consolidated domain billing + +# How to claim a domain +To use the domains feature with an unverified domain, you’ll need to claim the domain first. +To claim a domain, you need to be a Workspace Admin with a company email address. This allows you to manage company bills, company cards, and reconciliation. Claiming requires an email matching your company's domain. +1. Create an Expensify account +2. Set up an expense Workspace +3. Go to **Settings > _Domains_**. +Whichever member runs through those steps will automatically be made a Domain Admin. + + +# How to verify a domain +To use the domains feature with a verified domain, you’ll want to go through the steps of verifying it. + +To verify domain ownership, follow these steps: +1. Log in to your DNS service provider, which could be your Domain Name Registrar like NameCheap or GoDaddy, a dedicated DNS service provider like DNSMadeEasy or Amazon Route53, or managed internally by your company's IT department. +2. Find the page for editing DNS records for expensify.com. This might be labeled as DNS Management or Zone File Editor. +3. Add a new TXT record and set the value as: **532F6180D8** +4. Save your changes +5. Click the Verify button to confirm domain ownership + +After successful verification, you can remove the TXT DNS record. Please note that an email will be sent to all Expensify users on the domain to inform them that their accounts will be under Domain Control after verification. + +**Tips:** +Not sure how to do this? Check the below guides from some of the most popular hosts on the web: +[123-reg.co.uk](https://www.123-reg.co.uk/) +[One.com](https://www.one.com/en/) +[Wix.com](https://www.wix.com/) +Google/GSuite +[Godaddy](https://www.godaddy.com/) +When creating the TXT record, input only the code and no other values or information. +You can always confirm if you added the TXT code correctly here: https://viewdns.info/dnsrecord/?domain=[enterdomainhere] + +# Domain settings + +## Domain Admins +Domain Admins have full authority over domain settings. They can modify member group names and rules, link or modify Company Cards, and add or remove domain members and other admins. + +### Adding a Domain Admin +1. Head to **Settings > Domains > [Domain Name] > Domain Admins** +2. In the "Email or Phone" field, type in the email address of the person you want to make a Domain Admin (this can be any email not specifically tied to the domain) +3. Click "Add Admin" + +### Removing a Domain Admin: +1. If you're already a Domain Admin, go to **Settings > Domains > [Domain Name] > Domain Admins** +2. Locate the list of Domain Admins and find the one you want to remove +3. Next to the Domain Admin's name, click the red trash can icon. This will remove that person from the Domain Admin role + +## Domain Members +A domain member is a user associated with a specific domain (usually a company or another group) in Expensify and typically managed by a Domain Admin. This is also where you can enable Two-Factor authentication for your domain. + +### Adding users to the domain +When a Domain Admin adds a user to the domain, that will create a new Expensify account for that user, and they'll receive invitations to set up their account. Users can also join a verified domain by creating their own account, as long as they have an email address associated with that domain (e.g. yourname@yourcompany.com). Once they have verified the account, all Domain Admins will be notified, and the employee will be added to the Default Group. +**Important Note:** If someone who isn't a Domain Admin invites a user to a Workspace before they're invited to the domain, their account will be created, but in a closed state. A closed state means that the account cannot be used until it has been validated. Once the Domain Admin has invited the user, the user will receive a magic link to verify their account, sign in, and open the account completely. + +### How to add users +1. In your web account, go to **Settings > Domains > [Domain Name] > Domain Admins** +2. In the email field, enter the user you want to invite. This will create their Expensify account and send them an invitation + +### Removing users from the Domain +Removing a user means taking them out of your domain and closing their Expensify account completely if they don't have another login. Be cautious because closing an account is permanent and deletes any unsubmitted or processing reports. + +### How to remove users +In your web account, go to **Settings > Domains > [Domain Name] > Domain Admins** +Check the box next to the employee's name you want to remove, then click “Close Accounts”. + +### Important notes about closing accounts through Domain settings: +If a user has a Secondary Login linked to their Expensify account, they can still access their account after it's closed in the domain. This is helpful for accessing financial data, like tax-related receipts. +Closing an account through the domain permanently removes any unsubmitted receipts/reports. Make sure to approve or reimburse all employee reports before closing an account. +If an employee doesn't have a Secondary Login, they'll be automatically removed from the group Workspace. If they have a Secondary Login, it will continue to be associated with the group Workspace. + +## Domain Groups +Domain Groups can be accessed if you have verified your domain. Groups are used to set rules or permissions for groups of users so you can enforce multiple different expense workspaces and rules. If you are a Domain Admin, you can create and edit Domain Groups under **Settings > Domains > _Domain Name_ > Groups**. + +### Creating Domain Groups +1. In your Expensify account on the web, navigate to **Settings > Domains > _Domain Name_ > Groups** +2. Select “Create Group” to create the group. This will allow you to name the Group, as well as configure permissions that will apply to members of the Group. + +### Adding members to a Domain Group +1. In your Expensify account on the web, navigate to **Settings > Domains > [Domain Name] > Domain Members** +2. Select the checkbox next to the domain members you wish to add to the Domain Group +3. Select “Add to Group” to select the Group you wish to add them to + +### Editing Domain Groups +1. In your Expensify account on the web, navigate to **Settings > Domains > _Domain Name_ > Groups** +2. Next to the Group you wish to edit, select “Edit” +3. This will open the Edit Permission Group pane, where you can edit the rules and permissions for that group +4. Make your edits and click “Save” + +## Domain Group settings +These are the settings that can be customized for each group you have created. Typically, companies use two groups (Employees and Managers) and enforce stricter rules for Employees. The settings are: +- Strict Workspace Enforcement: When enabled, all Workspace rules must be followed for a report to be submitted. If a rule is violated, the report can't be submitted until the issue is fixed. Employees can't bypass this by dismissing notifications. +- Login Restrictions: Enabling this prevents users from using non-company email addresses as their primary login. Secondary logins are still allowed. +- Workspace Creation and Removal Restrictions: This feature stops users from creating new group workspaces or unsubscribing from existing workspaces. Admins who need these abilities should be in a separate group with this restriction turned off. +- Preferred Workspace: When enabled, group members can only create reports under one designated Workspace. They can move a report to a different Workspace or their personal one later if needed. This helps keep personal and company expenses separate. If a company card uses a specific Workspace, this setting overrides it for more control over company card expenses. +- Setting a Preferred Workspace: If Preferred Workspace is on, you can choose a default group Workspace for all Group Members. + +## SAML +To enable SAML SSO in Expensify you will first need to claim and verify your domain. Once you have a verified domain, you can access SAML SSO by navigating to **Settings > Domains > _Domain Name_ > SAML** + +## Enable Two-Factor Authentication (2FA) +1. As a Domain Admin, head to: **Settings > Domains > _Your Domain Name_ > Domain Members** +2. Turn on Two Factor Authentication by toggling it to ENABLED +3. Any Domain members that do not have two-factor authentication enabled will be asked to set it up on their Home page when they next log in, and won't be able to use Expensify until they do. +4. To turn it off, simply toggle it off and refresh the page. + +**Tips:** +- When using SAML, two-factor authentication cannot be required. +- For disputing digital Expensify Card purchases, two-factor authentication must be enabled. +- It might take up to 2 hours for domain-level enforcement to take effect, and users will be prompted to configure their individual 2FA settings on their next login to Expensify. + +# FAQ + +## How many domains can I have? +You can manage multiple domains by adding them through **Settings > Domains > New Domain**. However, to verify additional domains, you must be a Workspace Admin on a Control Workspace. Keep in mind that the Collect plan allows verification for just one domain. + +## What’s the difference between claiming a domain and verifying a domain? +Claiming a domain is limited to users with matching email domains, and allows Workspace Admins with a company email to manage bills, company cards, and reconciliation. Verifying a domain offers extra features and security. diff --git a/docs/articles/new-expensify/billing-and-plan-types/Referral-Program.md b/docs/articles/new-expensify/billing-and-plan-types/Referral-Program.md deleted file mode 100644 index 683e93d0277a..000000000000 --- a/docs/articles/new-expensify/billing-and-plan-types/Referral-Program.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -title: Expensify Referral Program -description: Send your joining link, submit a receipt or invoice, and we'll pay you if your referral adopts Expensify. ---- - - -# About - -Expensify has grown thanks to our users who love Expensify so much that they tell their friends, colleagues, managers, and fellow business founders to use it, too. - -As a thank you, every time you bring a new user into the platform who directly or indirectly leads to the adoption of a paid annual plan on Expensify, you will earn $250. - -# How to get paid for referring people to Expensify - -1. Submit a report or invoice, or share your referral link with anyone you know who is spending too much time on expenses, or works at a company that could benefit from using Expensify. - -2. You will get $250 for any referred business that commits to an annual subscription, has 2 or more active users, and makes two monthly payments. - -That’s right! You can refer anyone working at any company you know. - -If their company goes on to become an Expensify customer with an annual subscription, and you are the earliest recorded referrer of a user on that company’s paid Expensify Policy, you'll get paid a referral reward. - -The best way to start is to submit any receipt to your manager (you'll get paid back and set yourself up for $250 if they start a subscription: win-win!) - -Referral rewards for the Spring/Summer 2023 campaign will be paid by direct deposit. - -# FAQ - -- **How will I know if I am the first person to refer a company to Expensify?** - -Successful referrers are notified after their referral pays for 2 months of an annual subscription. We will check for the earliest recorded referrer of a user on the policy, and if that is you, then we will let you know. - -- **How will you pay me if I am successful?** - -In the Spring 2023 campaign, Expensify will be paying successful referrers via direct deposit to the Deposit-Only account you have on file. Referral payouts will happen once a month for the duration of the campaign. If you do not have a Deposit-Only account at the time of your referral payout, your deposit will be processed in the next batch. - -Learn how to add a Deposit-Only account [here](https://community.expensify.com/discussion/4641/how-to-add-a-deposit-only-bank-account-both-personal-and-business). - -- **I’m outside of the US, how do I get paid?** - -While our referral payouts are in USD, you will be able to get paid via a Wise Borderless account. Learn more [here](https://community.expensify.com/discussion/5940/how-to-get-reimbursed-outside-the-us-with-wise-for-non-us-employees). - -- **My referral wasn’t counted! How can I appeal?** - -Expensify reserves the right to modify the terms of the referral program at any time, and pays out referral bonuses for eligible companies at its own discretion. - -Please send a message to concierge@expensify.com with the billing owner of the company you have referred and our team will review the referral and get back to you. - -- **Where can I find my referral link?** - -Expensify members who are opted-in for our newsletters will have received an email containing their unique referral link. - -On the mobile app, go to **Settings** > **Invite a Friend** > **Share Invite Link** to retrieve your referral link. diff --git a/docs/articles/new-expensify/get-paid-back/Referral-Program.md b/docs/articles/new-expensify/get-paid-back/Referral-Program.md new file mode 100644 index 000000000000..6ffb923aeb76 --- /dev/null +++ b/docs/articles/new-expensify/get-paid-back/Referral-Program.md @@ -0,0 +1,56 @@ +--- +title: New Expensify Referral Program +description: Share your invite link with a friend, start a chat with a coworker, request money from your boss -- we'll pay you $250 if your referral adopts New Expensify. +--- + + +# About + +[New Expensify](https://new.expensify.com/) is growing thanks to members like you who love it so much that they tell their friends, family, colleagues, managers, and fellow business founders to use it, too. + +As a thank you, every time you bring a new customer into New Expensify, you'll get $250. Here's how it works. + +# How to get paid to refer anyone to New Expensify + +The sky's the limit for this referral program! Your referral can be anyone - a friend, family member, boss, coworker, neighbor, or even social media follower. We're making it as easy as possible to get that cold hard $$$. + +1. There are a bunch of different ways to refer someone to New Expensify: + - Start a chat + - Request money + - Send money + - Split a bill + - Assign them a task + - @ mention them + - Invite them to a room + - Add them to a workspace + +2. You'll get $250 for each referral as long as: + - You're the first to refer them to Expensify + - They start an annual subscription with two or more active users + - They make two payments toward that annual subscription + +For now, referral rewards will be paid via direct deposit into bank accounts that are connected to Expensify. + +# FAQ + +- **How will I know if I'm the first person to refer a company to Expensify?** + +Successful referrers are notified after their referral pays for two months of an annual Expensify subscription. We'll check for the earliest recorded referrer of a member on the workspace, and if that's you, we'll let you know. + +- **How will you pay me if I am successful?** + +For now, Expensify will pay successful referrers via direct deposit to the Deposit-Only bank account you have on file. Referral payouts will happen once a month. If you don't have a Deposit-Only bank account connected to Expensify at the time of your referral payout, your deposit will be processed in the next batch. + +Learn how to add a Deposit-Only bank account [here](https://community.expensify.com/discussion/4641/how-to-add-a-deposit-only-bank-account-both-personal-and-business). + +- **I’m outside of the US, how do I get paid?** + +While our referral payouts are in USD, you'll be able to get paid via a Wise Borderless account. Learn more [here](https://community.expensify.com/discussion/5940/how-to-get-reimbursed-outside-the-us-with-wise-for-non-us-employees). + +- **My referral wasn’t counted! How can I appeal?** + +Expensify reserves the right to modify the terms of the referral program at any time, and pays out referral bonuses for eligible members at its own discretion. If you think there's been a mistake, please send a message to concierge@expensify.com with the email of your referral and our team will review your case. + +- **Where can I find my referral link?** + +In New Expensify, go to **Settings** > **Share code** > **Get $250** to retrieve your invite link. diff --git a/docs/articles/new-expensify/get-paid-back/Request-Money.md b/docs/articles/new-expensify/get-paid-back/Request-Money.md index a2b765915af0..43a72a075de7 100644 --- a/docs/articles/new-expensify/get-paid-back/Request-Money.md +++ b/docs/articles/new-expensify/get-paid-back/Request-Money.md @@ -1,6 +1,36 @@ --- -title: Request Money -description: Request Money +title: Request Money and Split Bills with Friends +description: Everything you need to know about Requesting Money and Splitting Bills with Friends! redirect_from: articles/request-money/Request-and-Split-Bills/ --- -## Resource Coming Soon! + + + +# How do these Payment Features work? +Our suite of money movement features enables you to request money owed by an individual or split a bill with a group. + +**Request Money** lets your friends pay you back directly in Expensify. When you send a payment request to a friend, Expensify will display the amount owed and the option to pay the corresponding request in a chat between you. + +**Split Bill** allows you to split payments between friends and ensures the person who settled the tab gets paid back. + +These two features ensure you can live in the moment and settle up afterward. + +# How to Request Money +- Select the Green **+** button and choose **Request Money** +- Enter the amount **$** they owe and click **Next** +- Search for the user or enter their email! +- Enter a reason for the request (optional) +- Click **Request!** +- If you change your mind, all you have to do is click **Cancel** +- The user will be able to **Settle up outside of Expensify** or pay you via **Venmo** or **PayPal.me** + +# How to Split a Bill +- Select the Green **+** button and choose **Split Bill** +- Enter the total amount for the bill and click **Next** +- Search for users or enter their emails and **Select** +- Enter a reason for the split +- The split is then shared equally between the attendees + +# FAQs +## Can I request money from more than one person at a time? +If you need to request money for more than one person at a time, you’ll want to use the Split Bill feature. The Request Money option is for one-to-one payments between two people. diff --git a/docs/index.html b/docs/index.html index 70bd5f31545a..ceea63cb398a 100644 --- a/docs/index.html +++ b/docs/index.html @@ -12,9 +12,4 @@

{{ site.data.routes.home.title }}

{% include platform-card.html href=platform.platform_href %} {% endfor %}
- -
- - {% include floating-concierge-button.html id="floating-concierge-button-global" %} -
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index a97ae1101937..8989d0ef3542 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.4.10 + 1.4.13 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.10.1 + 1.4.13.6 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes @@ -120,8 +120,6 @@ UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown - UIUserInterfaceStyle - Dark UIViewControllerBasedStatusBarAppearance diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 7eabdca438bc..95659bf6908d 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.4.10 + 1.4.13 CFBundleSignature ???? CFBundleVersion - 1.4.10.1 + 1.4.13.6 diff --git a/ios/Podfile.lock b/ios/Podfile.lock index de78a944e655..eebd6ad532d4 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,25 +1,25 @@ PODS: - - Airship (16.11.3): - - Airship/Automation (= 16.11.3) - - Airship/Basement (= 16.11.3) - - Airship/Core (= 16.11.3) - - Airship/ExtendedActions (= 16.11.3) - - Airship/MessageCenter (= 16.11.3) - - Airship/Automation (16.11.3): + - Airship (16.12.1): + - Airship/Automation (= 16.12.1) + - Airship/Basement (= 16.12.1) + - Airship/Core (= 16.12.1) + - Airship/ExtendedActions (= 16.12.1) + - Airship/MessageCenter (= 16.12.1) + - Airship/Automation (16.12.1): - Airship/Core - - Airship/Basement (16.11.3) - - Airship/Core (16.11.3): + - Airship/Basement (16.12.1) + - Airship/Core (16.12.1): - Airship/Basement - - Airship/ExtendedActions (16.11.3): + - Airship/ExtendedActions (16.12.1): - Airship/Core - - Airship/MessageCenter (16.11.3): + - Airship/MessageCenter (16.12.1): - Airship/Core - - Airship/PreferenceCenter (16.11.3): + - Airship/PreferenceCenter (16.12.1): - Airship/Core - - AirshipFrameworkProxy (2.0.8): - - Airship (= 16.11.3) - - Airship/MessageCenter (= 16.11.3) - - Airship/PreferenceCenter (= 16.11.3) + - AirshipFrameworkProxy (2.1.1): + - Airship (= 16.12.1) + - Airship/MessageCenter (= 16.12.1) + - Airship/PreferenceCenter (= 16.12.1) - AppAuth (1.6.2): - AppAuth/Core (= 1.6.2) - AppAuth/ExternalUserAgent (= 1.6.2) @@ -558,8 +558,8 @@ PODS: - React-jsinspector (0.72.4) - React-logger (0.72.4): - glog - - react-native-airship (15.2.6): - - AirshipFrameworkProxy (= 2.0.8) + - react-native-airship (15.3.1): + - AirshipFrameworkProxy (= 2.1.1) - React-Core - react-native-blob-util (0.17.3): - React-Core @@ -1135,8 +1135,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/yoga" SPEC CHECKSUMS: - Airship: c70eed50e429f97f5adb285423c7291fb7a032ae - AirshipFrameworkProxy: 7bc4130c668c6c98e2d4c60fe4c9eb61a999be99 + Airship: 2f4510b497a8200780752a5e0304a9072bfffb6d + AirshipFrameworkProxy: ea1b6c665c798637b93c465b5e505be3011f1d9d AppAuth: 3bb1d1cd9340bd09f5ed189fb00b1cc28e1e8570 boost: 57d2868c099736d80fcd648bf211b4431e51a558 BVLinearGradient: 421743791a59d259aec53f4c58793aad031da2ca @@ -1199,7 +1199,7 @@ SPEC CHECKSUMS: React-jsiexecutor: c7f826e40fa9cab5d37cab6130b1af237332b594 React-jsinspector: aaed4cf551c4a1c98092436518c2d267b13a673f React-logger: da1ebe05ae06eb6db4b162202faeafac4b435e77 - react-native-airship: 5d19f4ba303481cf4101ff9dee9249ef6a8a6b64 + react-native-airship: 6ded22e4ca54f2f80db80b7b911c2b9b696d9335 react-native-blob-util: 99f4d79189252f597fe0d810c57a3733b1b1dea6 react-native-cameraroll: 8ffb0af7a5e5de225fd667610e2979fc1f0c2151 react-native-config: 7cd105e71d903104e8919261480858940a6b9c0e diff --git a/package-lock.json b/package-lock.json index 4b51ea5ce729..5206b9bf8618 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.10-1", + "version": "1.4.13-6", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.10-1", + "version": "1.4.13-6", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -35,13 +35,13 @@ "@react-native-google-signin/google-signin": "^10.0.1", "@react-native-picker/picker": "^2.4.3", "@react-navigation/material-top-tabs": "^6.6.3", - "@react-navigation/native": "6.1.6", + "@react-navigation/native": "6.1.8", "@react-navigation/stack": "6.3.16", "@react-ng/bounds-observer": "^0.2.1", "@rnmapbox/maps": "^10.0.11", "@shopify/flash-list": "^1.6.1", "@types/node": "^18.14.0", - "@ua/react-native-airship": "^15.2.6", + "@ua/react-native-airship": "^15.3.1", "awesome-phonenumber": "^5.4.0", "babel-polyfill": "^6.26.0", "canvas-size": "^1.2.6", @@ -50,7 +50,7 @@ "date-fns-tz": "^2.0.0", "dom-serializer": "^0.2.2", "domhandler": "^4.3.0", - "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#ee14b3255da33d2b6924c357f43393251b6dc6d2", + "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#927c8409e4454e15a1b95ed0a312ff8fee38f0f0", "fbjs": "^3.0.2", "htmlparser2": "^7.2.0", "idb-keyval": "^6.2.1", @@ -8563,16 +8563,16 @@ } }, "node_modules/@react-navigation/core": { - "version": "6.4.8", - "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-6.4.8.tgz", - "integrity": "sha512-klZ9Mcf/P2j+5cHMoGyIeurEzyBM2Uq9+NoSFrF6sdV5iCWHLFhrCXuhbBiQ5wVLCKf4lavlkd/DDs47PXs9RQ==", + "version": "6.4.10", + "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-6.4.10.tgz", + "integrity": "sha512-oYhqxETRHNHKsipm/BtGL0LI43Hs2VSFoWMbBdHK9OqgQPjTVUitslgLcPpo4zApCcmBWoOLX2qPxhsBda644A==", "dependencies": { - "@react-navigation/routers": "^6.1.8", + "@react-navigation/routers": "^6.1.9", "escape-string-regexp": "^4.0.0", "nanoid": "^3.1.23", "query-string": "^7.1.3", "react-is": "^16.13.0", - "use-latest-callback": "^0.1.5" + "use-latest-callback": "^0.1.7" }, "peerDependencies": { "react": "*" @@ -8608,11 +8608,11 @@ } }, "node_modules/@react-navigation/native": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-6.1.6.tgz", - "integrity": "sha512-14PmSy4JR8HHEk04QkxQ0ZLuqtiQfb4BV9kkMXD2/jI4TZ+yc43OnO6fQ2o9wm+Bq8pY3DxyerC2AjNUz+oH7Q==", + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-6.1.8.tgz", + "integrity": "sha512-0alti852nV+8oCVm9H80G6kZvrHoy51+rXBvVCRUs2rNDDozC/xPZs8tyeCJkqdw3cpxZDK8ndXF22uWq28+0Q==", "dependencies": { - "@react-navigation/core": "^6.4.8", + "@react-navigation/core": "^6.4.9", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.1.23" @@ -8623,9 +8623,9 @@ } }, "node_modules/@react-navigation/routers": { - "version": "6.1.8", - "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-6.1.8.tgz", - "integrity": "sha512-CEge+ZLhb1HBrSvv4RwOol7EKLW1QoqVIQlE9TN5MpxS/+VoQvP+cLbuz0Op53/iJfYhtXRFd1ZAd3RTRqto9w==", + "version": "6.1.9", + "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-6.1.9.tgz", + "integrity": "sha512-lTM8gSFHSfkJvQkxacGM6VJtBt61ip2XO54aNfswD+KMw6eeZ4oehl7m0me3CR9hnDE4+60iAZR8sAhvCiI3NA==", "dependencies": { "nanoid": "^3.1.23" } @@ -20437,9 +20437,9 @@ } }, "node_modules/@ua/react-native-airship": { - "version": "15.2.6", - "resolved": "https://registry.npmjs.org/@ua/react-native-airship/-/react-native-airship-15.2.6.tgz", - "integrity": "sha512-dVlBPPYXD/4SEshv/X7mmt3xF8WfnNqiSNzCyqJSLAZ1aJuPpP9Z5WemCYsa2iv6goRZvtJSE4P79QKlfoTwXw==", + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/@ua/react-native-airship/-/react-native-airship-15.3.1.tgz", + "integrity": "sha512-g5YX4/fpBJ0ml//7ave8HE68uF4QFriCuei0xJwK+ClzbTDWYB6OldvE/wj5dMpgQ95ZFSbr5LU77muYScxXLQ==", "engines": { "node": ">= 16.0.0" }, @@ -29894,8 +29894,8 @@ }, "node_modules/expensify-common": { "version": "1.0.0", - "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#ee14b3255da33d2b6924c357f43393251b6dc6d2", - "integrity": "sha512-u5Is3a/jD9KJ/LtSofeevauDlxZX5++w+VENP2cNhT1lm1GSwRM5FlTT7bIVSyrHr0cppAgl7cLiW2aPDr2hKA==", + "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#927c8409e4454e15a1b95ed0a312ff8fee38f0f0", + "integrity": "sha512-s9l/Zy3UjDBrq0WTkgEue1DXLRkkYtuqnANQlVmODHJ9HkJADjrVSv2D0U3ltqd9X7vLCLCmmwl5AUE6466gGg==", "license": "MIT", "dependencies": { "classnames": "2.3.1", @@ -50723,9 +50723,9 @@ } }, "node_modules/use-latest-callback": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.1.6.tgz", - "integrity": "sha512-VO/P91A/PmKH9bcN9a7O3duSuxe6M14ZoYXgA6a8dab8doWNdhiIHzEkX/jFeTTRBsX0Ubk6nG4q2NIjNsj+bg==", + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.1.9.tgz", + "integrity": "sha512-CL/29uS74AwreI/f2oz2hLTW7ZqVeV5+gxFeGudzQrgkCytrHw33G4KbnQOrRlAEzzAFXi7dDLMC9zhWcVpzmw==", "peerDependencies": { "react": ">=16.8" } @@ -58884,16 +58884,16 @@ } }, "@react-navigation/core": { - "version": "6.4.8", - "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-6.4.8.tgz", - "integrity": "sha512-klZ9Mcf/P2j+5cHMoGyIeurEzyBM2Uq9+NoSFrF6sdV5iCWHLFhrCXuhbBiQ5wVLCKf4lavlkd/DDs47PXs9RQ==", + "version": "6.4.10", + "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-6.4.10.tgz", + "integrity": "sha512-oYhqxETRHNHKsipm/BtGL0LI43Hs2VSFoWMbBdHK9OqgQPjTVUitslgLcPpo4zApCcmBWoOLX2qPxhsBda644A==", "requires": { - "@react-navigation/routers": "^6.1.8", + "@react-navigation/routers": "^6.1.9", "escape-string-regexp": "^4.0.0", "nanoid": "^3.1.23", "query-string": "^7.1.3", "react-is": "^16.13.0", - "use-latest-callback": "^0.1.5" + "use-latest-callback": "^0.1.7" } }, "@react-navigation/devtools": { @@ -58915,20 +58915,20 @@ } }, "@react-navigation/native": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-6.1.6.tgz", - "integrity": "sha512-14PmSy4JR8HHEk04QkxQ0ZLuqtiQfb4BV9kkMXD2/jI4TZ+yc43OnO6fQ2o9wm+Bq8pY3DxyerC2AjNUz+oH7Q==", + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-6.1.8.tgz", + "integrity": "sha512-0alti852nV+8oCVm9H80G6kZvrHoy51+rXBvVCRUs2rNDDozC/xPZs8tyeCJkqdw3cpxZDK8ndXF22uWq28+0Q==", "requires": { - "@react-navigation/core": "^6.4.8", + "@react-navigation/core": "^6.4.9", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.1.23" } }, "@react-navigation/routers": { - "version": "6.1.8", - "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-6.1.8.tgz", - "integrity": "sha512-CEge+ZLhb1HBrSvv4RwOol7EKLW1QoqVIQlE9TN5MpxS/+VoQvP+cLbuz0Op53/iJfYhtXRFd1ZAd3RTRqto9w==", + "version": "6.1.9", + "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-6.1.9.tgz", + "integrity": "sha512-lTM8gSFHSfkJvQkxacGM6VJtBt61ip2XO54aNfswD+KMw6eeZ4oehl7m0me3CR9hnDE4+60iAZR8sAhvCiI3NA==", "requires": { "nanoid": "^3.1.23" } @@ -67439,9 +67439,9 @@ } }, "@ua/react-native-airship": { - "version": "15.2.6", - "resolved": "https://registry.npmjs.org/@ua/react-native-airship/-/react-native-airship-15.2.6.tgz", - "integrity": "sha512-dVlBPPYXD/4SEshv/X7mmt3xF8WfnNqiSNzCyqJSLAZ1aJuPpP9Z5WemCYsa2iv6goRZvtJSE4P79QKlfoTwXw==", + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/@ua/react-native-airship/-/react-native-airship-15.3.1.tgz", + "integrity": "sha512-g5YX4/fpBJ0ml//7ave8HE68uF4QFriCuei0xJwK+ClzbTDWYB6OldvE/wj5dMpgQ95ZFSbr5LU77muYScxXLQ==", "requires": {} }, "@vercel/ncc": { @@ -74403,9 +74403,9 @@ } }, "expensify-common": { - "version": "git+ssh://git@github.com/Expensify/expensify-common.git#ee14b3255da33d2b6924c357f43393251b6dc6d2", - "integrity": "sha512-u5Is3a/jD9KJ/LtSofeevauDlxZX5++w+VENP2cNhT1lm1GSwRM5FlTT7bIVSyrHr0cppAgl7cLiW2aPDr2hKA==", - "from": "expensify-common@git+ssh://git@github.com/Expensify/expensify-common.git#ee14b3255da33d2b6924c357f43393251b6dc6d2", + "version": "git+ssh://git@github.com/Expensify/expensify-common.git#927c8409e4454e15a1b95ed0a312ff8fee38f0f0", + "integrity": "sha512-s9l/Zy3UjDBrq0WTkgEue1DXLRkkYtuqnANQlVmODHJ9HkJADjrVSv2D0U3ltqd9X7vLCLCmmwl5AUE6466gGg==", + "from": "expensify-common@git+ssh://git@github.com/Expensify/expensify-common.git#927c8409e4454e15a1b95ed0a312ff8fee38f0f0", "requires": { "classnames": "2.3.1", "clipboard": "2.0.4", @@ -89240,9 +89240,9 @@ } }, "use-latest-callback": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.1.6.tgz", - "integrity": "sha512-VO/P91A/PmKH9bcN9a7O3duSuxe6M14ZoYXgA6a8dab8doWNdhiIHzEkX/jFeTTRBsX0Ubk6nG4q2NIjNsj+bg==", + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.1.9.tgz", + "integrity": "sha512-CL/29uS74AwreI/f2oz2hLTW7ZqVeV5+gxFeGudzQrgkCytrHw33G4KbnQOrRlAEzzAFXi7dDLMC9zhWcVpzmw==", "requires": {} }, "use-memo-one": { diff --git a/package.json b/package.json index 18277c7a84c5..8432a773fdf7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.10-1", + "version": "1.4.13-6", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", @@ -83,13 +83,13 @@ "@react-native-google-signin/google-signin": "^10.0.1", "@react-native-picker/picker": "^2.4.3", "@react-navigation/material-top-tabs": "^6.6.3", - "@react-navigation/native": "6.1.6", + "@react-navigation/native": "6.1.8", "@react-navigation/stack": "6.3.16", "@react-ng/bounds-observer": "^0.2.1", "@rnmapbox/maps": "^10.0.11", "@shopify/flash-list": "^1.6.1", "@types/node": "^18.14.0", - "@ua/react-native-airship": "^15.2.6", + "@ua/react-native-airship": "^15.3.1", "awesome-phonenumber": "^5.4.0", "babel-polyfill": "^6.26.0", "canvas-size": "^1.2.6", @@ -98,7 +98,7 @@ "date-fns-tz": "^2.0.0", "dom-serializer": "^0.2.2", "domhandler": "^4.3.0", - "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#ee14b3255da33d2b6924c357f43393251b6dc6d2", + "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#927c8409e4454e15a1b95ed0a312ff8fee38f0f0", "fbjs": "^3.0.2", "htmlparser2": "^7.2.0", "idb-keyval": "^6.2.1", diff --git a/patches/@react-navigation+native+6.1.6.patch b/patches/@react-navigation+native+6.1.8.patch similarity index 90% rename from patches/@react-navigation+native+6.1.6.patch rename to patches/@react-navigation+native+6.1.8.patch index eb933683c850..c461d7e510fe 100644 --- a/patches/@react-navigation+native+6.1.6.patch +++ b/patches/@react-navigation+native+6.1.8.patch @@ -133,7 +133,7 @@ index 0000000..16da117 +//# sourceMappingURL=findFocusedRouteKey.js.map \ No newline at end of file diff --git a/node_modules/@react-navigation/native/lib/module/useLinking.js b/node_modules/@react-navigation/native/lib/module/useLinking.js -index 5bf2a88..a4318ef 100644 +index 6f0ac51..a77b608 100644 --- a/node_modules/@react-navigation/native/lib/module/useLinking.js +++ b/node_modules/@react-navigation/native/lib/module/useLinking.js @@ -2,6 +2,7 @@ import { findFocusedRoute, getActionFromState as getActionFromStateDefault, getP @@ -144,37 +144,7 @@ index 5bf2a88..a4318ef 100644 import ServerContext from './ServerContext'; /** * Find the matching navigation state that changed between 2 navigation states -@@ -34,32 +35,52 @@ const findMatchingState = (a, b) => { - /** - * Run async function in series as it's called. - */ --const series = cb => { -- // Whether we're currently handling a callback -- let handling = false; -- let queue = []; -- const callback = async () => { -- try { -- if (handling) { -- // If we're currently handling a previous event, wait before handling this one -- // Add the callback to the beginning of the queue -- queue.unshift(callback); -- return; -- } -- handling = true; -- await cb(); -- } finally { -- handling = false; -- if (queue.length) { -- // If we have queued items, handle the last one -- const last = queue.pop(); -- last === null || last === void 0 ? void 0 : last(); -- } -- } -+const series = (cb) => { -+ let queue = Promise.resolve(); -+ const callback = () => { -+ queue = queue.then(cb); - }; +@@ -42,6 +43,44 @@ export const series = cb => { return callback; }; let linkingHandlers = []; @@ -219,7 +189,7 @@ index 5bf2a88..a4318ef 100644 export default function useLinking(ref, _ref) { let { independent, -@@ -251,6 +272,9 @@ export default function useLinking(ref, _ref) { +@@ -231,6 +270,9 @@ export default function useLinking(ref, _ref) { // Otherwise it's likely a change triggered by `popstate` path !== pendingPath) { const historyDelta = (focusedState.history ? focusedState.history.length : focusedState.routes.length) - (previousFocusedState.history ? previousFocusedState.history.length : previousFocusedState.routes.length); @@ -229,7 +199,7 @@ index 5bf2a88..a4318ef 100644 if (historyDelta > 0) { // If history length is increased, we should pushState // Note that path might not actually change here, for example, drawer open should pushState -@@ -262,34 +286,55 @@ export default function useLinking(ref, _ref) { +@@ -242,34 +284,55 @@ export default function useLinking(ref, _ref) { // If history length is decreased, i.e. entries were removed, we want to go back const nextIndex = history.backIndex({ diff --git a/patches/react-native-fast-image+8.6.3.patch b/patches/react-native-fast-image+8.6.3+001+initial.patch similarity index 100% rename from patches/react-native-fast-image+8.6.3.patch rename to patches/react-native-fast-image+8.6.3+001+initial.patch diff --git a/patches/react-native-fast-image+8.6.3+002+bitmap-downsampling.patch b/patches/react-native-fast-image+8.6.3+002+bitmap-downsampling.patch new file mode 100644 index 000000000000..a626d5b16b2f --- /dev/null +++ b/patches/react-native-fast-image+8.6.3+002+bitmap-downsampling.patch @@ -0,0 +1,62 @@ +diff --git a/node_modules/react-native-fast-image/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java b/node_modules/react-native-fast-image/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java +index 1339f5c..9dfec0c 100644 +--- a/node_modules/react-native-fast-image/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java ++++ b/node_modules/react-native-fast-image/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java +@@ -176,7 +176,8 @@ class FastImageViewWithUrl extends AppCompatImageView { + .apply(FastImageViewConverter + .getOptions(context, imageSource, mSource) + .placeholder(mDefaultSource) // show until loaded +- .fallback(mDefaultSource)); // null will not be treated as error ++ .fallback(mDefaultSource)) ++ .transform(new ResizeTransformation()); + + if (key != null) + builder.listener(new FastImageRequestListener(key)); +diff --git a/node_modules/react-native-fast-image/android/src/main/java/com/dylanvann/fastimage/ResizeTransformation.java b/node_modules/react-native-fast-image/android/src/main/java/com/dylanvann/fastimage/ResizeTransformation.java +new file mode 100644 +index 0000000..1daa227 +--- /dev/null ++++ b/node_modules/react-native-fast-image/android/src/main/java/com/dylanvann/fastimage/ResizeTransformation.java +@@ -0,0 +1,41 @@ ++package com.dylanvann.fastimage; ++ ++ import android.content.Context; ++ import android.graphics.Bitmap; ++ ++ import androidx.annotation.NonNull; ++ ++ import com.bumptech.glide.load.Transformation; ++ import com.bumptech.glide.load.engine.Resource; ++ import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; ++ import com.bumptech.glide.load.resource.bitmap.BitmapResource; ++ ++ import java.security.MessageDigest; ++ ++ public class ResizeTransformation implements Transformation { ++ ++ private final double MAX_BYTES = 25000000.0; ++ ++ @NonNull ++ @Override ++ public Resource transform(@NonNull Context context, @NonNull Resource resource, int outWidth, int outHeight) { ++ Bitmap toTransform = resource.get(); ++ ++ if (toTransform.getByteCount() > MAX_BYTES) { ++ double scaleFactor = Math.sqrt(MAX_BYTES / (double) toTransform.getByteCount()); ++ int newHeight = (int) (outHeight * scaleFactor); ++ int newWidth = (int) (outWidth * scaleFactor); ++ ++ BitmapPool pool = GlideApp.get(context).getBitmapPool(); ++ Bitmap scaledBitmap = Bitmap.createScaledBitmap(toTransform, newWidth, newHeight, true); ++ return BitmapResource.obtain(scaledBitmap, pool); ++ } ++ ++ return resource; ++ } ++ ++ @Override ++ public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) { ++ messageDigest.update(("ResizeTransformation").getBytes()); ++ } ++ } +\ No newline at end of file diff --git a/src/App.js b/src/App.js index e273dcce1e47..3553900bbc7f 100644 --- a/src/App.js +++ b/src/App.js @@ -8,14 +8,17 @@ import {SafeAreaProvider} from 'react-native-safe-area-context'; import '../wdyr'; import ColorSchemeWrapper from './components/ColorSchemeWrapper'; import ComposeProviders from './components/ComposeProviders'; -import CustomStatusBar from './components/CustomStatusBar'; -import CustomStatusBarContextProvider from './components/CustomStatusBar/CustomStatusBarContextProvider'; +import CustomStatusBarAndBackground from './components/CustomStatusBarAndBackground'; +import CustomStatusBarAndBackgroundContextProvider from './components/CustomStatusBarAndBackground/CustomStatusBarAndBackgroundContextProvider'; import ErrorBoundary from './components/ErrorBoundary'; import HTMLEngineProvider from './components/HTMLEngineProvider'; import {LocaleContextProvider} from './components/LocaleContextProvider'; import OnyxProvider from './components/OnyxProvider'; import PopoverContextProvider from './components/PopoverProvider'; import SafeArea from './components/SafeArea'; +import ThemeIllustrationsProvider from './components/ThemeIllustrationsProvider'; +import ThemeProvider from './components/ThemeProvider'; +import ThemeStylesProvider from './components/ThemeStylesProvider'; import {CurrentReportIDContextProvider} from './components/withCurrentReportID'; import {EnvironmentProvider} from './components/withEnvironment'; import {KeyboardStateProvider} from './components/withKeyboardState'; @@ -26,9 +29,6 @@ import OnyxUpdateManager from './libs/actions/OnyxUpdateManager'; import * as Session from './libs/actions/Session'; import * as Environment from './libs/Environment/Environment'; import {ReportAttachmentsProvider} from './pages/home/report/ReportAttachmentsContext'; -import ThemeIllustrationsProvider from './styles/illustrations/ThemeIllustrationsProvider'; -import ThemeProvider from './styles/themes/ThemeProvider'; -import ThemeStylesProvider from './styles/ThemeStylesProvider'; // For easier debugging and development, when we are in web we expose Onyx to the window, so you can more easily set data into Onyx if (window && Environment.isDevelopment()) { @@ -68,10 +68,10 @@ function App() { ReportAttachmentsProvider, PickerStateProvider, EnvironmentProvider, - CustomStatusBarContextProvider, + CustomStatusBarAndBackgroundContextProvider, ]} > - + diff --git a/src/CONST.ts b/src/CONST.ts index 6f1fe37f661d..219807587a25 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -55,6 +55,9 @@ const CONST = { ALLOWED_RECEIPT_EXTENSIONS: ['jpg', 'jpeg', 'gif', 'png', 'pdf', 'htm', 'html', 'text', 'rtf', 'doc', 'tif', 'tiff', 'msword', 'zip', 'xml', 'message'], }, + // This is limit set on servers, do not update without wider internal discussion + API_TRANSACTION_CATEGORY_MAX_LENGTH: 255, + AUTO_AUTH_STATE: { NOT_STARTED: 'not-started', SIGNING_IN: 'signing-in', @@ -252,6 +255,7 @@ const CONST = { BETA_COMMENT_LINKING: 'commentLinking', POLICY_ROOMS: 'policyRooms', VIOLATIONS: 'violations', + REPORT_FIELDS: 'reportFields', }, BUTTON_STATES: { DEFAULT: 'default', @@ -460,6 +464,7 @@ const CONST = { ONFIDO_TERMS_OF_SERVICE_URL: 'https://onfido.com/terms-of-service/', // Use Environment.getEnvironmentURL to get the complete URL with port number DEV_NEW_EXPENSIFY_URL: 'https://dev.new.expensify.com:', + EXPENSIFY_INBOX_URL: 'https://www.expensify.com/inbox', SIGN_IN_FORM_WIDTH: 300, @@ -508,6 +513,7 @@ const CONST = { TASKREOPENED: 'TASKREOPENED', POLICYCHANGELOG: { ADD_APPROVER_RULE: 'POLICYCHANGELOG_ADD_APPROVER_RULE', + ADD_BUDGET: 'POLICYCHANGELOG_ADD_BUDGET', ADD_CATEGORY: 'POLICYCHANGELOG_ADD_CATEGORY', ADD_CUSTOM_UNIT: 'POLICYCHANGELOG_ADD_CUSTOM_UNIT', ADD_CUSTOM_UNIT_RATE: 'POLICYCHANGELOG_ADD_CUSTOM_UNIT_RATE', @@ -517,6 +523,7 @@ const CONST = { ADD_TAG: 'POLICYCHANGELOG_ADD_TAG', DELETE_ALL_TAGS: 'POLICYCHANGELOG_DELETE_ALL_TAGS', DELETE_APPROVER_RULE: 'POLICYCHANGELOG_DELETE_APPROVER_RULE', + DELETE_BUDGET: 'POLICYCHANGELOG_DELETE_BUDGET', DELETE_CATEGORY: 'POLICYCHANGELOG_DELETE_CATEGORY', DELETE_CUSTOM_UNIT: 'POLICYCHANGELOG_DELETE_CUSTOM_UNIT', DELETE_CUSTOM_UNIT_RATE: 'POLICYCHANGELOG_DELETE_CUSTOM_UNIT_RATE', @@ -527,17 +534,20 @@ const CONST = { DELETE_TAG: 'POLICYCHANGELOG_DELETE_TAG', IMPORT_CUSTOM_UNIT_RATES: 'POLICYCHANGELOG_IMPORT_CUSTOM_UNIT_RATES', IMPORT_TAGS: 'POLICYCHANGELOG_IMPORT_TAGS', + INDIVIDUAL_BUDGET_NOTIFICATION: 'POLICYCHANGELOG_INDIVIDUAL_BUDGET_NOTIFICATION', INVITE_TO_ROOM: 'POLICYCHANGELOG_INVITETOROOM', REMOVE_FROM_ROOM: 'POLICYCHANGELOG_REMOVEFROMROOM', SET_AUTOREIMBURSEMENT: 'POLICYCHANGELOG_SET_AUTOREIMBURSEMENT', SET_AUTO_JOIN: 'POLICYCHANGELOG_SET_AUTO_JOIN', SET_CATEGORY_NAME: 'POLICYCHANGELOG_SET_CATEGORY_NAME', + SHARED_BUDGET_NOTIFICATION: 'POLICYCHANGELOG_SHARED_BUDGET_NOTIFICATION', UPDATE_ACH_ACCOUNT: 'POLICYCHANGELOG_UPDATE_ACH_ACCOUNT', UPDATE_APPROVER_RULE: 'POLICYCHANGELOG_UPDATE_APPROVER_RULE', UPDATE_AUDIT_RATE: 'POLICYCHANGELOG_UPDATE_AUDIT_RATE', UPDATE_AUTOHARVESTING: 'POLICYCHANGELOG_UPDATE_AUTOHARVESTING', UPDATE_AUTOREIMBURSEMENT: 'POLICYCHANGELOG_UPDATE_AUTOREIMBURSEMENT', UPDATE_AUTOREPORTING_FREQUENCY: 'POLICYCHANGELOG_UPDATE_AUTOREPORTING_FREQUENCY', + UPDATE_BUDGET: 'POLICYCHANGELOG_UPDATE_BUDGET', UPDATE_CATEGORY: 'POLICYCHANGELOG_UPDATE_CATEGORY', UPDATE_CURRENCY: 'POLICYCHANGELOG_UPDATE_CURRENCY', UPDATE_CUSTOM_UNIT: 'POLICYCHANGELOG_UPDATE_CUSTOM_UNIT', @@ -645,6 +655,9 @@ const CONST = { OWNER_ACCOUNT_ID_FAKE: 0, DEFAULT_REPORT_NAME: 'Chat Report', }, + NEXT_STEP: { + FINISHED: 'Finished!', + }, COMPOSER: { MAX_LINES: 16, MAX_LINES_SMALL_SCREEN: 6, @@ -678,6 +691,7 @@ const CONST = { TIMING: { CALCULATE_MOST_RECENT_LAST_MODIFIED_ACTION: 'calc_most_recent_last_modified_action', SEARCH_RENDER: 'search_render', + CHAT_RENDER: 'chat_render', HOMEPAGE_INITIAL_RENDER: 'homepage_initial_render', REPORT_INITIAL_RENDER: 'report_initial_render', SWITCH_REPORT: 'switch_report', @@ -697,9 +711,10 @@ const CONST = { DEFAULT: 'default', }, THEME: { - DEFAULT: 'dark', - LIGHT: 'light', + DEFAULT: 'system', + FALLBACK: 'dark', DARK: 'dark', + LIGHT: 'light', SYSTEM: 'system', }, COLOR_SCHEME: { @@ -1090,11 +1105,6 @@ const CONST = { USER_CANCELLED: 'User canceled flow.', USER_TAPPED_BACK: 'User exited by clicking the back button.', USER_EXITED: 'User exited by manual action.', - USER_CAMERA_DENINED: 'Onfido.OnfidoFlowError', - USER_CAMERA_PERMISSION: 'Encountered an error: cameraPermission', - // eslint-disable-next-line max-len - USER_CAMERA_CONSENT_DENIED: - 'Unexpected result Intent. It might be a result of incorrect integration, make sure you only pass Onfido intent to handleActivityResult. It might be due to unpredictable crash or error. Please report the problem to android-sdk@onfido.com. Intent: null \n resultCode: 0', }, }, @@ -1161,6 +1171,7 @@ const CONST = { DECLINE: 'decline', CANCEL: 'cancel', DELETE: 'delete', + APPROVE: 'approve', }, AMOUNT_MAX_LENGTH: 10, RECEIPT_STATE: { @@ -2721,17 +2732,125 @@ const CONST = { EXPENSIFY_LOGO_SIZE_RATIO: 0.22, EXPENSIFY_LOGO_MARGIN_RATIO: 0.03, }, + /** + * Acceptable values for the `accessibilityRole` prop on react native components. + * + * **IMPORTANT:** Do not use with the `role` prop as it can cause errors. + * + * @deprecated ACCESSIBILITY_ROLE is deprecated. Please use CONST.ROLE instead. + */ ACCESSIBILITY_ROLE: { + /** + * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. + */ BUTTON: 'button', + + /** + * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. + */ LINK: 'link', + + /** + * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. + */ MENUITEM: 'menuitem', - TEXT: 'presentation', + + /** + * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. + */ + TEXT: 'text', + + /** + * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. + */ RADIO: 'radio', - IMAGEBUTTON: 'img button', + + /** + * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. + */ + IMAGEBUTTON: 'imagebutton', + + /** + * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. + */ CHECKBOX: 'checkbox', + + /** + * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. + */ SWITCH: 'switch', - ADJUSTABLE: 'slider', - IMAGE: 'img', + + /** + * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. + */ + ADJUSTABLE: 'adjustable', + + /** + * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. + */ + IMAGE: 'image', + }, + /** + * Acceptable values for the `role` attribute on react native components. + * + * **IMPORTANT:** Not for use with the `accessibilityRole` prop, as it accepts different values, and new components + * should use the `role` prop instead. + */ + ROLE: { + /** Use for elements with important, time-sensitive information. */ + ALERT: 'alert', + /** Use for elements that act as buttons. */ + BUTTON: 'button', + /** Use for elements representing checkboxes. */ + CHECKBOX: 'checkbox', + /** Use for elements that allow a choice from multiple options. */ + COMBOBOX: 'combobox', + /** Use with scrollable lists to represent a grid layout. */ + GRID: 'grid', + /** Use for section headers or titles. */ + HEADING: 'heading', + /** Use for image elements. */ + IMG: 'img', + /** Use for elements that navigate to other pages or content. */ + LINK: 'link', + /** Use to identify a list of items. */ + LIST: 'list', + /** Use for a list of choices or options. */ + MENU: 'menu', + /** Use for a container of multiple menus. */ + MENUBAR: 'menubar', + /** Use for items within a menu. */ + MENUITEM: 'menuitem', + /** Use when no specific role is needed. */ + NONE: 'none', + /** Use for elements that don't require a specific role. */ + PRESENTATION: 'presentation', + /** Use for elements showing progress of a task. */ + PROGRESSBAR: 'progressbar', + /** Use for radio buttons. */ + RADIO: 'radio', + /** Use for groups of radio buttons. */ + RADIOGROUP: 'radiogroup', + /** Use for scrollbar elements. */ + SCROLLBAR: 'scrollbar', + /** Use for text fields that are used for searching. */ + SEARCHBOX: 'searchbox', + /** Use for adjustable elements like sliders. */ + SLIDER: 'slider', + /** Use for a button that opens a list of choices. */ + SPINBUTTON: 'spinbutton', + /** Use for elements providing a summary of app conditions. */ + SUMMARY: 'summary', + /** Use for on/off switch elements. */ + SWITCH: 'switch', + /** Use for tab elements in a tab list. */ + TAB: 'tab', + /** Use for a list of tabs. */ + TABLIST: 'tablist', + /** Use for timer elements. */ + TIMER: 'timer', + /** Use for toolbars containing action buttons or components. */ + TOOLBAR: 'toolbar', }, TRANSLATION_KEYS: { ATTACHMENT: 'common.attachment', @@ -2789,6 +2908,10 @@ const CONST = { NAVIGATE: 'NAVIGATE', }, }, + TIME_PERIOD: { + AM: 'AM', + PM: 'PM', + }, INDENTS: ' ', PARENT_CHILD_SEPARATOR: ': ', CATEGORY_LIST_THRESHOLD: 8, @@ -2798,7 +2921,7 @@ const CONST = { SBE: 'SbeDemoSetup', MONEY2020: 'Money2020DemoSetup', }, - + COLON: ':', MAPBOX: { PADDING: 50, DEFAULT_ZOOM: 10, @@ -2844,7 +2967,7 @@ const CONST = { SHARE_CODE: 'shareCode', }, REVENUE: 250, - LEARN_MORE_LINK: 'https://help.expensify.com/articles/new-expensify/billing-and-plan-types/Referral-Program', + LEARN_MORE_LINK: 'https://help.expensify.com/articles/new-expensify/get-paid-back/Referral-Program', LINK: 'https://join.my.expensify.com', }, diff --git a/src/Expensify.js b/src/Expensify.js index aece93c0ff4d..756df5b79b88 100644 --- a/src/Expensify.js +++ b/src/Expensify.js @@ -112,6 +112,7 @@ function Expensify(props) { }, [props.isCheckingPublicRoom]); const isAuthenticated = useMemo(() => Boolean(lodashGet(props.session, 'authToken', null)), [props.session]); + const autoAuthState = useMemo(() => lodashGet(props.session, 'autoAuthState', ''), [props.session]); const contextValue = useMemo( () => ({ @@ -207,7 +208,10 @@ function Expensify(props) { } return ( - + {shouldInit && ( <> diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 9cd55b41455b..0cc7934ad007 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -334,10 +334,10 @@ const ONYXKEYS = { WAYPOINT_FORM_DRAFT: 'waypointFormDraft', SETTINGS_STATUS_SET_FORM: 'settingsStatusSetForm', SETTINGS_STATUS_SET_FORM_DRAFT: 'settingsStatusSetFormDraft', - SETTINGS_STATUS_CLEAR_AFTER_FORM: 'settingsStatusClearAfterForm', - SETTINGS_STATUS_CLEAR_AFTER_FORM_DRAFT: 'settingsStatusClearAfterFormDraft', SETTINGS_STATUS_SET_CLEAR_AFTER_FORM: 'settingsStatusSetClearAfterForm', SETTINGS_STATUS_SET_CLEAR_AFTER_FORM_DRAFT: 'settingsStatusSetClearAfterFormDraft', + SETTINGS_STATUS_CLEAR_DATE_FORM: 'settingsStatusClearDateForm', + SETTINGS_STATUS_CLEAR_DATE_FORM_DRAFT: 'settingsStatusClearDateFormDraft', PRIVATE_NOTES_FORM: 'privateNotesForm', PRIVATE_NOTES_FORM_DRAFT: 'privateNotesFormDraft', I_KNOW_A_TEACHER_FORM: 'iKnowTeacherForm', @@ -383,7 +383,7 @@ type OnyxValues = { [ONYXKEYS.COUNTRY]: string; [ONYXKEYS.USER]: OnyxTypes.User; [ONYXKEYS.USER_LOCATION]: OnyxTypes.UserLocation; - [ONYXKEYS.LOGIN_LIST]: Record; + [ONYXKEYS.LOGIN_LIST]: OnyxTypes.LoginList; [ONYXKEYS.SESSION]: OnyxTypes.Session; [ONYXKEYS.BETAS]: OnyxTypes.Beta[]; [ONYXKEYS.NVP_PRIORITY_MODE]: ValueOf; @@ -403,8 +403,8 @@ type OnyxValues = { [ONYXKEYS.WALLET_ONFIDO]: OnyxTypes.WalletOnfido; [ONYXKEYS.WALLET_ADDITIONAL_DETAILS]: OnyxTypes.WalletAdditionalDetails; [ONYXKEYS.WALLET_TERMS]: OnyxTypes.WalletTerms; - [ONYXKEYS.BANK_ACCOUNT_LIST]: Record; - [ONYXKEYS.FUND_LIST]: Record; + [ONYXKEYS.BANK_ACCOUNT_LIST]: OnyxTypes.BankAccountList; + [ONYXKEYS.FUND_LIST]: OnyxTypes.FundList; [ONYXKEYS.CARD_LIST]: Record; [ONYXKEYS.WALLET_STATEMENT]: OnyxTypes.WalletStatement; [ONYXKEYS.PERSONAL_BANK_ACCOUNT]: OnyxTypes.PersonalBankAccount; @@ -440,7 +440,7 @@ type OnyxValues = { [ONYXKEYS.COLLECTION.POLICY_DRAFTS]: OnyxTypes.Policy; [ONYXKEYS.COLLECTION.POLICY_CATEGORIES]: OnyxTypes.PolicyCategory; [ONYXKEYS.COLLECTION.POLICY_TAGS]: OnyxTypes.PolicyTags; - [ONYXKEYS.COLLECTION.POLICY_MEMBERS]: OnyxTypes.PolicyMember; + [ONYXKEYS.COLLECTION.POLICY_MEMBERS]: OnyxTypes.PolicyMembers; [ONYXKEYS.COLLECTION.POLICY_MEMBERS_DRAFTS]: OnyxTypes.PolicyMember; [ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES]: OnyxTypes.RecentlyUsedCategories; [ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST]: OnyxTypes.PolicyMembers; @@ -453,12 +453,13 @@ type OnyxValues = { [ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT]: string; [ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT_NUMBER_OF_LINES]: number; [ONYXKEYS.COLLECTION.REPORT_IS_COMPOSER_FULL_SIZE]: boolean; - [ONYXKEYS.COLLECTION.REPORT_USER_IS_TYPING]: boolean; + [ONYXKEYS.COLLECTION.REPORT_USER_IS_TYPING]: OnyxTypes.ReportUserIsTyping; [ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM]: boolean; [ONYXKEYS.COLLECTION.SECURITY_GROUP]: OnyxTypes.SecurityGroup; [ONYXKEYS.COLLECTION.TRANSACTION]: OnyxTypes.Transaction; [ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS]: OnyxTypes.RecentlyUsedTags; [ONYXKEYS.COLLECTION.SELECTED_TAB]: string; + [ONYXKEYS.COLLECTION.PRIVATE_NOTES_DRAFT]: string; // Forms [ONYXKEYS.FORMS.ADD_DEBIT_CARD_FORM]: OnyxTypes.AddDebitCardForm; @@ -507,8 +508,8 @@ type OnyxValues = { [ONYXKEYS.FORMS.WAYPOINT_FORM_DRAFT]: OnyxTypes.Form; [ONYXKEYS.FORMS.SETTINGS_STATUS_SET_FORM]: OnyxTypes.Form; [ONYXKEYS.FORMS.SETTINGS_STATUS_SET_FORM_DRAFT]: OnyxTypes.Form; - [ONYXKEYS.FORMS.SETTINGS_STATUS_CLEAR_AFTER_FORM]: OnyxTypes.Form; - [ONYXKEYS.FORMS.SETTINGS_STATUS_CLEAR_AFTER_FORM_DRAFT]: OnyxTypes.Form; + [ONYXKEYS.FORMS.SETTINGS_STATUS_CLEAR_DATE_FORM]: OnyxTypes.Form; + [ONYXKEYS.FORMS.SETTINGS_STATUS_CLEAR_DATE_FORM_DRAFT]: OnyxTypes.Form; [ONYXKEYS.FORMS.SETTINGS_STATUS_SET_CLEAR_AFTER_FORM]: OnyxTypes.Form; [ONYXKEYS.FORMS.SETTINGS_STATUS_SET_CLEAR_AFTER_FORM_DRAFT]: OnyxTypes.Form; [ONYXKEYS.FORMS.PRIVATE_NOTES_FORM]: OnyxTypes.Form; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 53763d6d7cd1..ca1fe9f0e81a 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -34,7 +34,7 @@ const ROUTES = { VALIDATE_LOGIN: 'v/:accountID/:validateCode', GET_ASSISTANCE: { route: 'get-assistance/:taskID', - getRoute: (taskID: string) => `get-assistance/${taskID}` as const, + getRoute: (taskID: string, backTo: string) => getUrlWithBackToParam(`get-assistance/${taskID}`, backTo), }, UNLINK_LOGIN: 'u/:accountID/:validateCode', APPLE_SIGN_IN: 'sign-in-with-apple', @@ -140,7 +140,9 @@ const ROUTES = { getRoute: (backTo?: string) => getUrlWithBackToParam('settings/security/two-factor-auth', backTo), }, SETTINGS_STATUS: 'settings/profile/status', - SETTINGS_STATUS_SET: 'settings/profile/status/set', + SETTINGS_STATUS_CLEAR_AFTER: 'settings/profile/status/clear-after', + SETTINGS_STATUS_CLEAR_AFTER_DATE: 'settings/profile/status/clear-after/date', + SETTINGS_STATUS_CLEAR_AFTER_TIME: 'settings/profile/status/clear-after/time', KEYBOARD_SHORTCUTS: 'keyboard-shortcuts', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 921f57953482..2cd263237866 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -37,8 +37,10 @@ const SCREENS = { CONTACT_METHODS: 'Settings_ContactMethods', CONTACT_METHOD_DETAILS: 'Settings_ContactMethodDetails', NEW_CONTACT_METHOD: 'Settings_NewContactMethod', + STATUS_CLEAR_AFTER: 'Settings_Status_Clear_After', + STATUS_CLEAR_AFTER_DATE: 'Settings_Status_Clear_After_Date', + STATUS_CLEAR_AFTER_TIME: 'Settings_Status_Clear_After_Time', STATUS: 'Settings_Status', - STATUS_SET: 'Settings_Status_Set', PRONOUNS: 'Settings_Pronouns', TIMEZONE: 'Settings_Timezone', TIMEZONE_SELECT: 'Settings_Timezone_Select', diff --git a/src/components/AddPaymentMethodMenu.js b/src/components/AddPaymentMethodMenu.js index 4d01fa108e2a..4abe5655e307 100644 --- a/src/components/AddPaymentMethodMenu.js +++ b/src/components/AddPaymentMethodMenu.js @@ -48,6 +48,9 @@ const propTypes = { /** Currently logged in user accountID */ accountID: PropTypes.number, }), + + /** Whether the personal bank account option should be shown */ + shouldShowPersonalBankAccountOption: PropTypes.bool, }; const defaultProps = { @@ -59,9 +62,10 @@ const defaultProps = { }, anchorRef: () => {}, session: {}, + shouldShowPersonalBankAccountOption: false, }; -function AddPaymentMethodMenu({isVisible, onClose, anchorPosition, anchorAlignment, anchorRef, iouReport, onItemSelected, session}) { +function AddPaymentMethodMenu({isVisible, onClose, anchorPosition, anchorAlignment, anchorRef, iouReport, onItemSelected, session, shouldShowPersonalBankAccountOption}) { const {translate} = useLocalize(); // Users can choose to pay with business bank account in case of Expense reports or in case of P2P IOU report @@ -70,6 +74,8 @@ function AddPaymentMethodMenu({isVisible, onClose, anchorPosition, anchorAlignme ReportUtils.isExpenseReport(iouReport) || (ReportUtils.isIOUReport(iouReport) && !ReportActionsUtils.hasRequestFromCurrentAccount(lodashGet(iouReport, 'reportID', 0), lodashGet(session, 'accountID', 0))); + const canUsePersonalBankAccount = shouldShowPersonalBankAccountOption || ReportUtils.isIOUReport(iouReport); + return ( - { @@ -146,7 +148,8 @@ function AddressForm({city, country, formID, onAddressChanged, onSubmit, shouldS /> - - {isUSAForm ? ( - ) : ( - )} - - - + ); } diff --git a/src/components/AddressSearch/CurrentLocationButton.js b/src/components/AddressSearch/CurrentLocationButton.js index 90d2c15733f1..06541565f567 100644 --- a/src/components/AddressSearch/CurrentLocationButton.js +++ b/src/components/AddressSearch/CurrentLocationButton.js @@ -5,10 +5,10 @@ import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import useLocalize from '@hooks/useLocalize'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; import getButtonState from '@libs/getButtonState'; -import colors from '@styles/colors'; -import useStyleUtils from '@styles/useStyleUtils'; -import useThemeStyles from '@styles/useThemeStyles'; +import colors from '@styles/theme/colors'; const propTypes = { /** Callback that runs when location button is clicked */ diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js index 2fed1d153947..3c764b36f3eb 100644 --- a/src/components/AddressSearch/index.js +++ b/src/components/AddressSearch/index.js @@ -10,13 +10,13 @@ import networkPropTypes from '@components/networkPropTypes'; import {withNetwork} from '@components/OnyxProvider'; import TextInput from '@components/TextInput'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; import * as ApiUtils from '@libs/ApiUtils'; import compose from '@libs/compose'; import getCurrentPosition from '@libs/getCurrentPosition'; import * as GooglePlacesUtils from '@libs/GooglePlacesUtils'; -import useTheme from '@styles/themes/useTheme'; -import useStyleUtils from '@styles/useStyleUtils'; -import useThemeStyles from '@styles/useThemeStyles'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import CurrentLocationButton from './CurrentLocationButton'; diff --git a/src/components/AmountTextInput.js b/src/components/AmountTextInput.js index bd88712432a8..25e1ce6f05ec 100644 --- a/src/components/AmountTextInput.js +++ b/src/components/AmountTextInput.js @@ -1,6 +1,7 @@ import PropTypes from 'prop-types'; import React from 'react'; -import useThemeStyles from '@styles/useThemeStyles'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; import refPropTypes from './refPropTypes'; import TextInput from './TextInput'; @@ -27,6 +28,12 @@ const propTypes = { /** Function to call when selection in text input is changed */ onSelectionChange: PropTypes.func, + /** Style for the input */ + style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), + + /** Style for the container */ + containerStyles: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), + /** Function to call to handle key presses in the text input */ onKeyPress: PropTypes.func, }; @@ -36,16 +43,19 @@ const defaultProps = { selection: undefined, onSelectionChange: () => {}, onKeyPress: () => {}, + style: {}, + containerStyles: {}, }; function AmountTextInput(props) { const styles = useThemeStyles(); + const StyleUtils = useStyleUtils(); return ( ); } diff --git a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.js b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.js index 1e2d18bc4691..6161ba140726 100644 --- a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.js +++ b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.js @@ -58,7 +58,7 @@ function BaseAnchorForAttachmentsOnly(props) { onPressOut={props.onPressOut} onLongPress={(event) => showContextMenuForReport(event, anchor, report.reportID, action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))} accessibilityLabel={fileName} - role={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ROLE.BUTTON} > (linkRef = el)} style={StyleSheet.flatten([style, defaultTextStyle])} - role={CONST.ACCESSIBILITY_ROLE.LINK} + role={CONST.ROLE.LINK} hrefAttrs={{ rel, target: isEmail || !linkProps.href ? '_self' : target, diff --git a/src/components/AnimatedStep/index.tsx b/src/components/AnimatedStep/index.tsx index 1a87592cba9b..e2b9952c0617 100644 --- a/src/components/AnimatedStep/index.tsx +++ b/src/components/AnimatedStep/index.tsx @@ -1,8 +1,8 @@ import React, {useMemo} from 'react'; import {StyleProp, ViewStyle} from 'react-native'; import * as Animatable from 'react-native-animatable'; +import useThemeStyles from '@hooks/useThemeStyles'; import useNativeDriver from '@libs/useNativeDriver'; -import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import ChildrenProps from '@src/types/utils/ChildrenProps'; import {AnimationDirection} from './AnimatedStepContext'; diff --git a/src/components/AnonymousReportFooter.tsx b/src/components/AnonymousReportFooter.tsx index 65dc813a829d..877ca9444661 100644 --- a/src/components/AnonymousReportFooter.tsx +++ b/src/components/AnonymousReportFooter.tsx @@ -3,7 +3,7 @@ import {Text, View} from 'react-native'; import {OnyxCollection} from 'react-native-onyx'; import {OnyxEntry} from 'react-native-onyx/lib/types'; import useLocalize from '@hooks/useLocalize'; -import useThemeStyles from '@styles/useThemeStyles'; +import useThemeStyles from '@hooks/useThemeStyles'; import * as Session from '@userActions/Session'; import {PersonalDetails, Report} from '@src/types/onyx'; import AvatarWithDisplayName from './AvatarWithDisplayName'; diff --git a/src/components/ArchivedReportFooter.tsx b/src/components/ArchivedReportFooter.tsx index 3187bf3604e8..7dadd86debfe 100644 --- a/src/components/ArchivedReportFooter.tsx +++ b/src/components/ArchivedReportFooter.tsx @@ -2,13 +2,13 @@ import lodashEscape from 'lodash/escape'; import React from 'react'; import {OnyxEntry, withOnyx} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; -import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {PersonalDetails, Report, ReportAction} from '@src/types/onyx'; +import type {PersonalDetailsList, Report, ReportAction} from '@src/types/onyx'; import Banner from './Banner'; type ArchivedReportFooterOnyxProps = { @@ -16,7 +16,7 @@ type ArchivedReportFooterOnyxProps = { reportClosedAction: OnyxEntry; /** Personal details of all users */ - personalDetails: OnyxEntry>; + personalDetails: OnyxEntry; }; type ArchivedReportFooterProps = ArchivedReportFooterOnyxProps & { diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index 4dd0a96c31b9..b1af96561ef5 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -2,12 +2,16 @@ import Str from 'expensify-common/lib/str'; import lodashExtend from 'lodash/extend'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {Animated, Keyboard, View} from 'react-native'; +import {GestureHandlerRootView} from 'react-native-gesture-handler'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; 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 useWindowDimensions from '@hooks/useWindowDimensions'; import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; import compose from '@libs/compose'; @@ -19,9 +23,6 @@ import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import useNativeDriver from '@libs/useNativeDriver'; import reportPropTypes from '@pages/reportPropTypes'; -import useTheme from '@styles/themes/useTheme'; -import useStyleUtils from '@styles/useStyleUtils'; -import useThemeStyles from '@styles/useThemeStyles'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -90,6 +91,9 @@ const propTypes = { /** Denotes whether it is a workspace avatar or not */ isWorkspaceAvatar: PropTypes.bool, + + /** Whether it is a receipt attachment or not */ + isReceiptAttachment: PropTypes.bool, }; const defaultProps = { @@ -107,19 +111,18 @@ const defaultProps = { onModalHide: () => {}, onCarouselAttachmentChange: () => {}, isWorkspaceAvatar: false, + isReceiptAttachment: false, }; function AttachmentModal(props) { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); - const onModalHideCallbackRef = useRef(null); const [isModalOpen, setIsModalOpen] = useState(props.defaultOpen); const [shouldLoadAttachment, setShouldLoadAttachment] = useState(false); const [isAttachmentInvalid, setIsAttachmentInvalid] = useState(false); const [isDeleteReceiptConfirmModalVisible, setIsDeleteReceiptConfirmModalVisible] = useState(false); const [isAuthTokenRequired, setIsAuthTokenRequired] = useState(props.isAuthTokenRequired); - const [isAttachmentReceipt, setIsAttachmentReceipt] = useState(null); const [attachmentInvalidReasonTitle, setAttachmentInvalidReasonTitle] = useState(''); const [attachmentInvalidReason, setAttachmentInvalidReason] = useState(null); const [source, setSource] = useState(props.source); @@ -155,7 +158,6 @@ function AttachmentModal(props) { (attachment) => { setSource(attachment.source); setFile(attachment.file); - setIsAttachmentReceipt(attachment.isReceipt); setIsAuthTokenRequired(attachment.isAuthTokenRequired); onCarouselAttachmentChange(attachment); }, @@ -358,7 +360,7 @@ function AttachmentModal(props) { const sourceForAttachmentView = props.source || source; const threeDotsMenuItems = useMemo(() => { - if (!isAttachmentReceipt || !props.parentReport || !props.parentReportActions) { + if (!props.isReceiptAttachment || !props.parentReport || !props.parentReportActions) { return []; } const menuItems = []; @@ -372,8 +374,8 @@ function AttachmentModal(props) { icon: Expensicons.Camera, text: props.translate('common.replace'), onSelected: () => { - onModalHideCallbackRef.current = () => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(props.report.reportID, CONST.EDIT_REQUEST_FIELD.RECEIPT)); closeModal(); + Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(props.report.reportID, CONST.EDIT_REQUEST_FIELD.RECEIPT)); }, }); } @@ -393,17 +395,17 @@ function AttachmentModal(props) { } return menuItems; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isAttachmentReceipt, props.parentReport, props.parentReportActions, props.policy, props.transaction, file]); + }, [props.isReceiptAttachment, props.parentReport, props.parentReportActions, props.policy, props.transaction, file]); // There are a few things that shouldn't be set until we absolutely know if the file is a receipt or an attachment. - // isAttachmentReceipt will be null until its certain what the file is, in which case it will then be true|false. + // props.isReceiptAttachment will be null until its certain what the file is, in which case it will then be true|false. let headerTitle = props.headerTitle; let shouldShowDownloadButton = false; let shouldShowThreeDotsButton = false; - if (!_.isNull(isAttachmentReceipt)) { - headerTitle = translate(isAttachmentReceipt ? 'common.receipt' : 'common.attachment'); - shouldShowDownloadButton = props.allowDownload && isDownloadButtonReadyToBeShown && !isAttachmentReceipt && !isOffline; - shouldShowThreeDotsButton = isAttachmentReceipt && isModalOpen; + if (!_.isEmpty(props.report)) { + headerTitle = translate(props.isReceiptAttachment ? 'common.receipt' : 'common.attachment'); + shouldShowDownloadButton = props.allowDownload && isDownloadButtonReadyToBeShown && !props.isReceiptAttachment && !isOffline; + shouldShowThreeDotsButton = props.isReceiptAttachment && isModalOpen; } return ( @@ -420,87 +422,86 @@ function AttachmentModal(props) { }} onModalHide={(e) => { props.onModalHide(e); - if (onModalHideCallbackRef.current) { - onModalHideCallbackRef.current(); - } - setShouldLoadAttachment(false); }} propagateSwipe > - {props.isSmallScreenWidth && } - downloadAttachment(source)} - shouldShowCloseButton={!props.isSmallScreenWidth} - shouldShowBackButton={props.isSmallScreenWidth} - onBackButtonPress={closeModal} - onCloseButtonPress={closeModal} - shouldShowThreeDotsButton={shouldShowThreeDotsButton} - threeDotsAnchorPosition={styles.threeDotsPopoverOffsetAttachmentModal(windowWidth)} - threeDotsMenuItems={threeDotsMenuItems} - shouldOverlay - /> - - {!_.isEmpty(props.report) ? ( - - ) : ( - Boolean(sourceForAttachmentView) && - shouldLoadAttachment && ( - + {props.isSmallScreenWidth && } + downloadAttachment(source)} + shouldShowCloseButton={!props.isSmallScreenWidth} + shouldShowBackButton={props.isSmallScreenWidth} + onBackButtonPress={closeModal} + onCloseButtonPress={closeModal} + shouldShowThreeDotsButton={shouldShowThreeDotsButton} + threeDotsAnchorPosition={styles.threeDotsPopoverOffsetAttachmentModal(windowWidth)} + threeDotsMenuItems={threeDotsMenuItems} + shouldOverlay + /> + + {!_.isEmpty(props.report) && !props.isReceiptAttachment ? ( + - ) - )} - - {/* If we have an onConfirm method show a confirmation button */} - {Boolean(props.onConfirm) && ( - - {({safeAreaPaddingBottomStyle}) => ( - -