diff --git a/.eslintrc.js b/.eslintrc.js index b76782af60f4..85a4e86797b6 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -14,6 +14,11 @@ const restrictedImportPaths = [ importNames: ['TouchableOpacity', 'TouchableWithoutFeedback', 'TouchableNativeFeedback', 'TouchableHighlight'], message: "Please use 'PressableWithFeedback' and/or 'PressableWithoutFeedback' from 'src/components/Pressable' instead.", }, + { + name: 'react-native-safe-area-context', + importNames: ['useSafeAreaInsets', 'SafeAreaConsumer', 'SafeAreaInsetsContext'], + message: "Please use 'useSafeAreaInsets' from 'src/hooks/useSafeAreaInset' and/or 'SafeAreaConsumer' from 'src/components/SafeAreaConsumer' instead.", + }, ]; const restrictedImportPatterns = [ @@ -169,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/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 351a11506d68..4f7d1c71553a 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -18,8 +18,8 @@ $ https://github.com/Expensify/App/issues/ Do NOT only link the issue number like this: $ # ---> -$ -PROPOSAL: +$ +PROPOSAL: ### Tests @@ -98,7 +98,7 @@ This is a checklist for PR authors. Please make sure to complete all tasks and c - [ ] The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory - [ ] If a new CSS style is added I verified that: - [ ] A similar style doesn't already exist - - [ ] The style can't be created with an existing [StyleUtils](https://github.com/Expensify/App/blob/main/src/styles/StyleUtils.js) function (i.e. `StyleUtils.getBackgroundAndBorderStyle(themeColors.componentBG)`) + - [ ] The style can't be created with an existing [StyleUtils](https://github.com/Expensify/App/blob/main/src/styles/utils/index.ts) function (i.e. `StyleUtils.getBackgroundAndBorderStyle(theme.componentBG)`) - [ ] If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic. - [ ] If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like `Avatar` is modified, I verified that `Avatar` is working as expected in all cases) - [ ] If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected. 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/deployBlocker.yml b/.github/workflows/deployBlocker.yml index d118b3fee252..cb5dc6d28b32 100644 --- a/.github/workflows/deployBlocker.yml +++ b/.github/workflows/deployBlocker.yml @@ -22,6 +22,13 @@ jobs: env: GITHUB_TOKEN: ${{ github.token }} + - name: Escape html characters in GH issue title + env: + GH_ISSUE_TITLE: ${{ github.event.issue.title }} + run: | + escaped_title=$(echo "$GH_ISSUE_TITLE" | sed -e 's/&/\&/g; s//\>/g; s/"/\"/g; s/'"'"'/\'/g; s/|/\|/g') + echo "GH_ISSUE_TITLE=$escaped_title" >> "$GITHUB_ENV" + - name: 'Post the issue in the #expensify-open-source slack room' if: ${{ success() }} uses: 8398a7/action-slack@v3 @@ -32,7 +39,7 @@ jobs: channel: '#expensify-open-source', attachments: [{ color: "#DB4545", - text: '💥 We have found a New Expensify Deploy Blocker, if you have any idea which PR could be causing this, please comment in the issue: <${{ github.event.issue.html_url }}|${{ github.event.issue.title }}>'.replace(/[&<>"'|]/g, function(m) { return {'&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '|': '|'}[m]; }), + text: '💥 We have found a New Expensify Deploy Blocker, if you have any idea which PR could be causing this, please comment in the issue: <${{ github.event.issue.html_url }}|${{ env.GH_ISSUE_TITLE }}>' }] } env: diff --git a/.github/workflows/e2ePerformanceTests.yml b/.github/workflows/e2ePerformanceTests.yml index 016fe89ccfce..92b4e52c159c 100644 --- a/.github/workflows/e2ePerformanceTests.yml +++ b/.github/workflows/e2ePerformanceTests.yml @@ -211,7 +211,9 @@ jobs: test_spec_file: tests/e2e/TestSpec.yml test_spec_type: APPIUM_NODE_TEST_SPEC remote_src: false - file_artifacts: Customer Artifacts.zip + file_artifacts: | + Customer Artifacts.zip + Test spec output.txt log_artifacts: debug.log cleanup: true timeout: 5400 @@ -220,6 +222,7 @@ jobs: if: failure() run: | echo ${{ steps.schedule-awsdf-main.outputs.data }} + cat "./mainResults/Host_Machine_Files/\$WORKING_DIRECTORY/Test spec output.txt" unzip "Customer Artifacts.zip" -d mainResults cat ./mainResults/Host_Machine_Files/\$WORKING_DIRECTORY/debug.log diff --git a/.github/workflows/platformDeploy.yml b/.github/workflows/platformDeploy.yml index 291bd80816b9..8ccc0a7f6056 100644 --- a/.github/workflows/platformDeploy.yml +++ b/.github/workflows/platformDeploy.yml @@ -56,6 +56,12 @@ jobs: - name: Setup Node uses: ./.github/actions/composite/setupNode + - name: Setup Java + uses: actions/setup-java@v3 + with: + distribution: 'oracle' + java-version: '17' + - name: Setup Ruby uses: ruby/setup-ruby@a05e47355e80e57b9a67566a813648fa67d92011 with: @@ -341,6 +347,9 @@ jobs: if: ${{ failure() }} needs: [android, desktop, iOS, web] steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Post Slack message on failure uses: ./.github/actions/composite/announceFailedWorkflowInSlack with: 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/.github/workflows/testBuild.yml b/.github/workflows/testBuild.yml index 6f222398d04b..94a51a2d11bd 100644 --- a/.github/workflows/testBuild.yml +++ b/.github/workflows/testBuild.yml @@ -84,6 +84,12 @@ jobs: - name: Setup Node uses: ./.github/actions/composite/setupNode + - name: Setup Java + uses: actions/setup-java@v3 + with: + distribution: 'oracle' + java-version: '17' + - name: Setup Ruby uses: ruby/setup-ruby@a05e47355e80e57b9a67566a813648fa67d92011 with: diff --git a/.imgbotconfig b/.imgbotconfig index ff5c3345cc4d..45cdf03ec36e 100644 --- a/.imgbotconfig +++ b/.imgbotconfig @@ -1,6 +1,7 @@ { "ignoredFiles": [ - "assets/images/empty-state_background-fade.png" // Caused an issue with colour gradients, https://github.com/Expensify/App/issues/30499 + "assets/images/themeDependent/empty-state_background-fade-dark.png", // Caused an issue with colour gradients, https://github.com/Expensify/App/issues/30499 + "assets/images/themeDependent/empty-state_background-fade-light.png" ], "aggressiveCompression": "false" } diff --git a/.storybook/public/index.css b/.storybook/public/index.css index 8ace4b240684..2d2411c083c1 100644 --- a/.storybook/public/index.css +++ b/.storybook/public/index.css @@ -24,5 +24,5 @@ a.sidebar-item[data-selected="true"], a.sidebar-item[data-selected="true"]:focus } .sidebar-container { - background: #07271f; + background: #072419; } diff --git a/.storybook/theme.js b/.storybook/theme.js index 96631764726f..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', @@ -7,17 +7,17 @@ export default create({ fontBase: 'ExpensifyNeue-Regular', fontCode: 'monospace', base: 'dark', - appBg: colors.darkHighlightBackground, - colorPrimary: colors.darkDefaultButton, + appBg: colors.productDark200, + colorPrimary: colors.productDark400, colorSecondary: colors.green, - appContentBg: colors.darkAppBackground, - textColor: colors.darkPrimaryText, - barTextColor: colors.darkPrimaryText, + appContentBg: colors.productDark100, + textColor: colors.productDark900, + barTextColor: colors.productDark900, barSelectedColor: colors.green, - barBg: colors.darkAppBackground, - appBorderColor: colors.darkBorders, - inputBg: colors.darkHighlightBackground, - inputBorder: colors.darkBorders, + barBg: colors.productDark100, + appBorderColor: colors.productDark400, + inputBg: colors.productDark200, + inputBorder: colors.productDark400, appBorderRadius: 8, inputBorderRadius: 8, }); diff --git a/__mocks__/@ua/react-native-airship.js b/__mocks__/@ua/react-native-airship.js index 1672c064f9be..29be662e96a1 100644 --- a/__mocks__/@ua/react-native-airship.js +++ b/__mocks__/@ua/react-native-airship.js @@ -31,7 +31,7 @@ const Airship = { }, contact: { identify: jest.fn(), - getNamedUserId: jest.fn(), + getNamedUserId: () => Promise.resolve(undefined), reset: jest.fn(), }, }; diff --git a/android/app/build.gradle b/android/app/build.gradle index 1d6ed247e1b9..dbc0935e9b76 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 1001040401 - versionName "1.4.4-1" + versionCode 1001041300 + versionName "1.4.13-0" } flavorDimensions "default" diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index aa0e8136957f..a040598e982d 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -11,7 +11,6 @@ @drawable/rn_edit_text_material @style/AppTheme.Popup @style/TextViewSpinnerDropDownItem - @style/DatePickerDialogTheme @style/AlertDialogTheme @@ -28,18 +27,6 @@ @font/expneuebold - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/assets/images/home-background--mobile.svg b/assets/images/home-background--mobile.svg index 7c4d4d8289b7..d2fa08475c9d 100644 --- a/assets/images/home-background--mobile.svg +++ b/assets/images/home-background--mobile.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/images/home-fade-gradient--mobile.svg b/assets/images/home-fade-gradient--mobile.svg index 0b24b678a2e6..ad150f3c870c 100644 --- a/assets/images/home-fade-gradient--mobile.svg +++ b/assets/images/home-fade-gradient--mobile.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/images/home-fade-gradient.svg b/assets/images/home-fade-gradient.svg index bfe04d545364..c446d7b46a42 100644 --- a/assets/images/home-fade-gradient.svg +++ b/assets/images/home-fade-gradient.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/images/themeDependent/empty-state_background-fade-dark.png b/assets/images/themeDependent/empty-state_background-fade-dark.png new file mode 100644 index 000000000000..59951ef707fb Binary files /dev/null and b/assets/images/themeDependent/empty-state_background-fade-dark.png differ diff --git a/assets/images/themeDependent/empty-state_background-fade-light.png b/assets/images/themeDependent/empty-state_background-fade-light.png new file mode 100644 index 000000000000..200996057b47 Binary files /dev/null and b/assets/images/themeDependent/empty-state_background-fade-light.png differ diff --git a/assets/images/themeDependent/example-check-image-dark-en.png b/assets/images/themeDependent/example-check-image-dark-en.png new file mode 100644 index 000000000000..6b8e84a0faa2 Binary files /dev/null and b/assets/images/themeDependent/example-check-image-dark-en.png differ diff --git a/assets/images/themeDependent/example-check-image-dark-es.png b/assets/images/themeDependent/example-check-image-dark-es.png new file mode 100644 index 000000000000..446678e401b6 Binary files /dev/null and b/assets/images/themeDependent/example-check-image-dark-es.png differ diff --git a/assets/images/themeDependent/example-check-image-light-en.png b/assets/images/themeDependent/example-check-image-light-en.png new file mode 100644 index 000000000000..d7b5f035c625 Binary files /dev/null and b/assets/images/themeDependent/example-check-image-light-en.png differ diff --git a/assets/images/themeDependent/example-check-image-light-es.png b/assets/images/themeDependent/example-check-image-light-es.png new file mode 100644 index 000000000000..2183b522a35b Binary files /dev/null and b/assets/images/themeDependent/example-check-image-light-es.png differ 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/config/webpack/webpack.common.js b/config/webpack/webpack.common.js index 22d035368c42..008b4f45911f 100644 --- a/config/webpack/webpack.common.js +++ b/config/webpack/webpack.common.js @@ -66,7 +66,8 @@ const webpackConfig = ({envFile = '.env', platform = 'web'}) => ({ template: 'web/index.html', filename: 'index.html', splashLogo: fs.readFileSync(path.resolve(__dirname, `../../assets/images/new-expensify${mapEnvToLogoSuffix(envFile)}.svg`), 'utf-8'), - usePolyfillIO: platform === 'web', + isWeb: platform === 'web', + isProduction: envFile === '.env.production', isStaging: envFile === '.env.staging', }), new FontPreloadPlugin({ @@ -210,7 +211,21 @@ const webpackConfig = ({envFile = '.env', platform = 'web'}) => ({ // This is also why we have to use .website.js for our own web-specific files... // Because desktop also relies on "web-specific" module implementations // This also skips packing web only dependencies to desktop and vice versa - extensions: ['.web.js', platform === 'web' ? '.website.js' : '.desktop.js', '.js', '.jsx', '.web.ts', platform === 'web' ? '.website.ts' : '.desktop.ts', '.ts', '.web.tsx', '.tsx'], + extensions: [ + '.web.js', + ...(platform === 'desktop' ? ['.desktop.js'] : []), + '.website.js', + '.js', + '.jsx', + '.web.ts', + ...(platform === 'desktop' ? ['.desktop.ts'] : []), + '.website.ts', + ...(platform === 'desktop' ? ['.desktop.tsx'] : []), + '.website.tsx', + '.ts', + '.web.tsx', + '.tsx', + ], fallback: { 'process/browser': require.resolve('process/browser'), }, diff --git a/contributingGuides/NAVIGATION.md b/contributingGuides/NAVIGATION.md index d7a94c2a4337..543b133fe62b 100644 --- a/contributingGuides/NAVIGATION.md +++ b/contributingGuides/NAVIGATION.md @@ -1,6 +1,6 @@ # Overview -The navigation in the App consists of a top-level Stack Navigator (called `RootStack`) with each of its `Screen` components handling different high-level flow. All those flows can be seen in `AuthScreens.js` file. +The navigation in the App consists of a top-level Stack Navigator (called `RootStack`) with each of its `Screen` components handling different high-level flow. All those flows can be seen in `AuthScreens.tsx` file. ## Terminology @@ -20,11 +20,11 @@ Navigation Actions - User actions correspond to resulting navigation actions tha ## Adding RHP flows -Most of the time, if you want to add some of the flows concerning one of your reports, e.g. `Money Request` from a user, you will most probably use `RightModalNavigator.js` and `ModalStackNavigators.js` file: +Most of the time, if you want to add some of the flows concerning one of your reports, e.g. `Money Request` from a user, you will most probably use `RightModalNavigator.tsx` and `ModalStackNavigators.tsx` file: -- Since each of those flows is kind of a modal stack, if you want to add a page to the existing flow, you should just add a page to the correct stack in `ModalStackNavigators.js`. +- Since each of those flows is kind of a modal stack, if you want to add a page to the existing flow, you should just add a page to the correct stack in `ModalStackNavigators.tsx`. -- If you want to create new flow, add a `Screen` in `RightModalNavigator.js` and make new modal in `ModalStackNavigators.js` with chosen pages. +- If you want to create new flow, add a `Screen` in `RightModalNavigator.tsx` and make new modal in `ModalStackNavigators.tsx` with chosen pages. When creating RHP flows, you have to remember a couple things: @@ -40,13 +40,36 @@ When creating RHP flows, you have to remember a couple things: An example of adding `Settings_Workspaces` page: -1. Add path to `ROUTES.js`: https://github.com/Expensify/App/blob/3531af22dcadaa94ed11eccf370517dca0b8c305/src/ROUTES.js#L36 +1. Add the page name to `SCREENS.ts` which will be reused throughout the app (linkingConfig, navigators, etc.): + +```ts +const SCREENS = { + SETTINGS: { + WORKSPACES: 'Settings_Workspaces', + }, +} as const; +``` + +2. Add path to `ROUTES.ts`: https://github.com/Expensify/App/blob/main/src/ROUTES.ts + +```ts +export const ROUTES = { + // static route + SETTINGS_WORKSPACES: 'settings/workspaces', + // dynamic route + SETTINGS_WORKSPACES: { + route: 'settings/:accountID', + getRoute: (accountID: number) => `settings/${accountID}` as const, + }, +}; + +``` -2. Add `Settings_Workspaces` page to proper RHP flow in `linkingConfig.js`: https://github.com/Expensify/App/blob/3531af22dcadaa94ed11eccf370517dca0b8c305/src/libs/Navigation/linkingConfig.js#L40-L42 +3. Add `Settings_Workspaces` page to proper RHP flow in `linkingConfig.ts`: https://github.com/Expensify/App/blob/fbc11ca729ffa4676fb3bc8cd110ac3890debff6/src/libs/Navigation/linkingConfig.ts#L47-L50 -3. Add your page to proper navigator (it should be aligned with where you've put it in the previous step) https://github.com/Expensify/App/blob/3531af22dcadaa94ed11eccf370517dca0b8c305/src/libs/Navigation/AppNavigator/ModalStackNavigators.js#L334-L338 +4. Add your page to proper navigator (it should be aligned with where you've put it in the previous step) https://github.com/Expensify/App/blob/fbc11ca729ffa4676fb3bc8cd110ac3890debff6/src/libs/Navigation/AppNavigator/ModalStackNavigators.js#L141 -4. Make sure `HeaderWithBackButton` leads to the previous page in navigation flow of your page: https://github.com/Expensify/App/blob/3531af22dcadaa94ed11eccf370517dca0b8c305/src/pages/workspace/WorkspacesListPage.js#L186 +5. Make sure `HeaderWithBackButton` leads to the previous page in navigation flow of your page: https://github.com/Expensify/App/blob/3531af22dcadaa94ed11eccf370517dca0b8c305/src/pages/workspace/WorkspacesListPage.js#L186 ## Performance solutions @@ -183,4 +206,4 @@ The action for the first step created with `getMinimalAction` looks like this: ``` ### Deeplinking -There is no minimal action for deeplinking directly to the `Profile` screen. But because the `Settings_root` is not on the stack, pressing UP will reset the params for navigators to the correct ones. \ No newline at end of file +There is no minimal action for deeplinking directly to the `Profile` screen. But because the `Settings_root` is not on the stack, pressing UP will reset the params for navigators to the correct ones. diff --git a/contributingGuides/REVIEWER_CHECKLIST.md b/contributingGuides/REVIEWER_CHECKLIST.md index 16c8f88927b1..b7cbd87dc4b9 100644 --- a/contributingGuides/REVIEWER_CHECKLIST.md +++ b/contributingGuides/REVIEWER_CHECKLIST.md @@ -46,7 +46,7 @@ - [ ] The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory - [ ] If a new CSS style is added I verified that: - [ ] A similar style doesn't already exist - - [ ] The style can't be created with an existing [StyleUtils](https://github.com/Expensify/App/blob/main/src/styles/StyleUtils.js) function (i.e. `StyleUtils.getBackgroundAndBorderStyle(themeColors.componentBG`) + - [ ] The style can't be created with an existing [StyleUtils](https://github.com/Expensify/App/blob/main/src/utils/index.ts) function (i.e. `StyleUtils.getBackgroundAndBorderStyle(theme.componentBG`) - [ ] If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic. - [ ] If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like `Avatar` is modified, I verified that `Avatar` is working as expected in all cases) - [ ] If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected. 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/_data/_routes.yml b/docs/_data/_routes.yml index efdcf41b63d5..e320b690c226 100644 --- a/docs/_data/_routes.yml +++ b/docs/_data/_routes.yml @@ -112,21 +112,11 @@ platforms: title: Expensify Card icon: /assets/images/hand-card.svg description: Explore how the Expensify Card combines convenience and security to enhance everyday business transactions. Discover how to apply for, oversee, and maximize your card perks here. - - - href: expensify-partner-program - title: Expensify Partner Program - icon: /assets/images/handshake.svg - description: Discover how to get the most out of Expensify as an ExpensifyApproved! accountant partner. Learn how to set up your clients, receive CPE credits, and take advantage of your partner discount. - href: get-paid-back title: Get Paid Back icon: /assets/images/money-into-wallet.svg description: Whether you submit an expense report or an invoice, find out here how to ensure a smooth and timely payback process every time. - - - href: send-payments - title: Send Payments - icon: /assets/images/money-wings.svg - description: Uncover step-by-step guidance on sending direct reimbursements to employees, paying an invoice to a vendor, and utilizing third-party payment options. - href: workspace-and-domain-settings title: Workspace & Domain Settings 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 b34a7d13b7f0..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: #2C6755; -$color-green-highlightBG: #07271F; -$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/Account-Details.md b/docs/articles/expensify-classic/account-settings/Account-Details.md index 8f80a35f3525..bc4b94bf8a51 100644 --- a/docs/articles/expensify-classic/account-settings/Account-Details.md +++ b/docs/articles/expensify-classic/account-settings/Account-Details.md @@ -59,3 +59,11 @@ Is your Secondary Login (personal email) invalidated in your company account? If 3. You will be presented with a confirmation message saying Expensify sent you an email with a validation link 4. Head to your personal email account and follow the prompts 5. You'll receive a link in the email to click that will unlink the two accounts + +# FAQ +## The profile picture on my account updated automatically. Why did this happen? +Our focus is always on making your experience user-friendly and saving you valuable time. One of the ways we achieve this is by utilizing a public API to retrieve public data linked to your email address. + +This tool searches for public accounts or profiles associated with your email address, such as on LinkedIn. When it identifies one, it pulls in the uploaded profile picture and name to Expensify. + +While this automated process is generally accurate, there may be instances where it's not entirely correct. If this happens, we apologize for any inconvenience caused. The good news is that rectifying such situations is a straightforward process. You can quickly update your information manually by following the directions provided above, ensuring your data is accurate and up to date in no time. diff --git a/docs/articles/expensify-classic/account-settings/Merge-Accounts.md b/docs/articles/expensify-classic/account-settings/Merge-Accounts.md index 7fc355b30bd9..abb218c74118 100644 --- a/docs/articles/expensify-classic/account-settings/Merge-Accounts.md +++ b/docs/articles/expensify-classic/account-settings/Merge-Accounts.md @@ -5,29 +5,32 @@ description: How to merge two Expensify accounts and why this is useful. # Overview -Merging accounts allows you to combine two accounts. When you combine two accounts, all receipts, expenses, expense reports, invoices, bills, imported cards, secondary logins, co-pilots, and group policy settings will be combined into one account. +Merging accounts allows you to combine two accounts. When you combine two accounts, all receipts, expenses, expense reports, invoices, bills, imported cards, secondary logins, co-pilots, and group workspace settings will be combined into one account. This can be useful if you start off with an account of your own but your organization creates a separate account for you. You can then track both personal and business expenses via one account. # How to merge accounts Merging two accounts together is fairly straightforward. Let’s go over how to do that below: 1. Navigate to [expensify.com](https://www.expensify.com) 2. Log into the account you want to set as the Primary account -3. Navigate to Settings > Account > Account Details -4. Scroll down to the Merge Accounts section and fill in the fields. Once you click Merge, a magic code link will be sent to you via email and you'll be prompted to enter the magic code -5. Copy the magic code, switch back to the expensify.com page, and paste the code into the required field +3. Navigate to **Settings > Account > Account Details** +4. Scroll down to Merge Accounts and fill in the fields 6. Click Merge Accounts +7. Once you click Merge, a magic code is sent to you via email +8. Paste the code into the required field If you have any questions about this process, feel free to reach out to Concierge for some assistance! # FAQ ## Can you merge accounts from the mobile app? No, accounts can only be merged from the full website at expensify.com. ## Can I administratively merge two accounts together? -No, only the account holder (user) can perform account merging. +No, only the account holder (member) can perform account merging. ## Is merging accounts reversible? No, merging accounts is not reversible. It is a permanent action that cannot be undone. +## I have open expenses in the account I'm merging from. Will those expenses merge into the new account? +All expenses must be reported and submitted for them to merge into the new account. Any open expenses will not merge. ## Are there any restrictions on account merging? Yes! Please see below: -* If your email address belongs to a verified domain (verified in Expensify), you must start the process from the email account under the verified domain. You cannot merge a verified company email account into a personal account. -* If you have two accounts with two different verified domains, you cannot merge them together. -## What happens to my “personal” Individual policy when merging accounts? -The old “personal” Individual policy will be deleted. If you plan to submit reports under a different policy in the future, ensure that any reports on the Individual policy in the old account are marked as Open before merging the accounts. You can typically do this by selecting “Undo Submit” on any submitted reports. +- If your email address belongs to a verified domain (verified in Expensify), you must start the process from the email account under the verified domain. You cannot merge a verified company email account into a personal account. +- If you have two accounts with two different verified domains, you cannot merge them together. +## What happens to my “personal” Individual workspace when merging accounts? +The old “personal” Individual workspace is deleted. If you plan to submit reports under a different workspace in the future, ensure that any reports on the Individual workspace in the old account are marked as Open before merging the accounts. You can typically do this by selecting “Undo Submit” on any submitted reports. diff --git a/docs/articles/expensify-classic/account-settings/Notification-Troubleshooting.md b/docs/articles/expensify-classic/account-settings/Notification-Troubleshooting.md new file mode 100644 index 000000000000..22df0dc7f6ca --- /dev/null +++ b/docs/articles/expensify-classic/account-settings/Notification-Troubleshooting.md @@ -0,0 +1,75 @@ +--- +title: Notification Troubleshooting +description: This article is about how to troubleshoot notifications from Expensify. +--- + +# Overview +Sometimes, members may have trouble receiving important email notifications from Expensify, such as Expensify Magic Code emails, account validation emails, secondary login validations, integration emails, or report action notifications (rejections, approvals, etc.). + +# Here's how to troubleshoot missing Expensify notifications: + +1. **No error message, but the email is never received** +The email might be delayed; give it 30-60 minutes to arrive in your inbox. +Check **Email Preferences** on the web via **Settings > Your Account > Preferences**In the **Contact Preferences** section. Ensure that the relevant boxes are checked for the email type you're missing. Check your email spam and trash folders, as Expensify messages might end up there inadvertently. +Check to make sure you haven't unintentionally blocked Expensify emails and whitelist [expensify.com](https://community.expensify.com/home/leaving?allowTrusted=1&target=http%3A%2F%2Fexpensify.com%2F), mg.expensify.com, and [amazonSES.com](https://community.expensify.com/home/leaving?allowTrusted=1&target=http%3A%2F%2Famazonses.com%2F) with your email provider. + +2. **A "We're having trouble emailing you" banner at the top of your screen** +Verify that your email address in your account settings is correct and is a real deliverable email address. +Re-send Verification Email: Look for an option to re-send a verification email, usually provided when this banner appears. + +![ExpensifyHelp_EmailError]({{site.url}}/assets/images/ExpensifyHelp_EmailError.png){:width="100%"} + +# Deep Dive + +**For Private Domains**: + +If your organization uses a private domain, consult your IT department or IT person to ensure that the following domains are whitelisted to receive our emails: expensify.com, mg.expensify.com, and amazonSES.com. These domains are the sources of various notification emails, so make sure they aren't being blocked. + +**For Public Domains (e.g., Gmail, Yahoo, Hotmail)**: + +To whitelist our emails on public email services: + +1. Check your Spam Folder: Search for messages from expensify.com in your Spam folder, open them, and click "Not Spam" at the top of the message. +2. Create a Filter: Set up a filter that identifies the entire expensify.com domain and directs all incoming messages to your inbox, preventing them from going to Spam. +3. Add Specific Contacts: While optional, adding specific email addresses from Expensify as contacts can further prevent emails from going to Spam. + +Please note that even if you receive emails from our Concierge support communication, ensure that both expensify.com and mg.expensify.com are whitelisted as they use different servers. + +**Email Server Blocking**: +Your email server may be blocking our emails due to spam filters or other services. Check with your IT department to investigate and resolve any server-level email blocking issues. + +**Mimecast**: +If your company uses Mimecast, a service that can affect email deliverability, check with your IT department. If Mimecast is in use, reach out to us at concierge@expensify.com through a new email, as this should ensure delivery to your inbox. Mimecast should eventually recognize the Expensify domain, preventing future filtering. + +**For Outlook Users**: +For Outlook users specifically: + +1. Click the gear icon in Outlook and select "View all Outlook settings." +2. Choose "Mail" from the settings menu. +3. Under the "Junk email" submenu, click "Add" under "Safe senders and domains." +4. Enter the email address you want to whitelist. +5. Click "Save." + +When you click the "Settings" link in the banner in Expensify, you'll be directed to your account settings page, where you may encounter a few different scenarios: + +- "Temporarily Suspended Emails": If the message mentions "temporarily suspended emails to," follow the steps provided in the yellow box. This situation typically occurs when we can't find a valid inbox to send our emails to. Possible reasons include: + - A misspelled email address during account creation. + - Use of a distribution list email (acting as an "alias" email) without a linked inbox. + - An auto-responder that has been responding to our emails for an extended period. +- To resolve this issue, confirm that the email address is indeed associated with an active inbox. Then, click the link that says "here," and your email should be unblocked shortly. +- SMTP Error (Gray Box): In some cases, you might encounter a gray box with an SMTP error message. This error can vary, but it typically looks something like this: + +![ExpensifyHelp_SMTPError]({{site.url}}/assets/images/ExpensifyHelp_SMTPError.png){:width="100%"} + +**These look a bit cryptic, yes, but hang in there!** + +The error messages you see are the raw message text received from your email provider's server to Amazon. These messages can vary in text, but the best course of action is to follow the link provided (by copying and pasting) in the text for the next steps. + +**Scenario 1**: If the message in the gray box includes "mimecast.com": It means that our emails are being blocked by the server. In this case, you should contact your IT person or team to address the issue. + +**Scenario 2**: If the message in the gray box mentions "blacklist at org/.com/.net," or resembles the screenshot provided, it indicates that your IT team has configured your email to use a third-party email reputation or blacklisting service. Here's what you need to know: +- All our emails are SPF and DKIM-signed, meaning they are cryptographically signed as coming from us and are not spam. +- The problem arises because we send mail from a cloud-based service. This means that the sender's IP serves multiple vendors, including Expensify. If one of those vendors is marked as spam, it can block all messages from that IP, even if they're from different vendors (including us). +- The better approach is for the server to flag spam via DKIM and SPF (rather than solely relying on the sender's IP address), as our messages are correctly signed and encrypted to prevent spoofing. + +To resolve these issues, consider discussing them with your IT team, as they can help implement the necessary changes to ensure you receive our emails without interruption. diff --git a/docs/articles/expensify-classic/account-settings/Preferences.md b/docs/articles/expensify-classic/account-settings/Preferences.md index 532da4d8a986..8131cd0e80c5 100644 --- a/docs/articles/expensify-classic/account-settings/Preferences.md +++ b/docs/articles/expensify-classic/account-settings/Preferences.md @@ -1,5 +1,28 @@ --- -title: Preferences -description: Preferences +title: Account Preferences +description: Expensify Account Preferences --- -## Resource Coming Soon! + +# Overview +Customize your Expensify experience by updating your account details and preferences. Here you can update your profile picture, adjust contact preferences, and perform other actions to personalize your account. + +# How to manage contact preferences +To edit your notification preferences or unsubscribe from Expensify updates: +- On the web, navigate to **Settings > Account > Preferences** +Scroll down to find the ‘Contact Preferences’ section. To stop receiving a specific type of email, uncheck the corresponding box. + +# How to set your time zone +Wherever you are, we'll time-stamp your report actions according to your local time. This helps you keep track of when submissions or approvals occurred. Setting your time zone in Expensify is simple and ensures accurate time-stamping for your report actions, especially in the comments section of the expense report you're reviewing. + +To set your time zone: +Navigate to **Settings > Account > Preferences > Scroll down to Time Zone** + +![ExpensifyHelp_Timezone]({{site.url}}/assets/images/ExpensifyHelp_Timezone.png){:width="100%"} + + **Note:** To set your time zone automatically based on your location, tick the box that says **Set my time zone automatically**. + +If you prefer to set your time zone manually, leave the box unticked and select your time zone from the searchable list of locations. + +When you add a comment to a report, all the report actions will be time-stamped in your local time. Adjusting your time zone to the appropriate location makes tracking and understanding submission and approval times much easier. + +![ExpensifyHelp_Time]({{site.url}}/assets/images/ExpensifyHelp_Time.png){:width="100%"} 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/bank-accounts-and-credit-cards/company-cards/Connect-Company-Cards.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Company-Card-Settings.md similarity index 98% rename from docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Connect-Company-Cards.md rename to docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Company-Card-Settings.md index f2ff837d7638..fa5879d85ea8 100644 --- a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Connect-Company-Cards.md +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Company-Card-Settings.md @@ -1,6 +1,6 @@ --- title: Company-Card-Settings.md -description: Company card settings +description: Once you connect your cards, customize the configuration using company card settings. --- # Overview Once you’ve imported your company cards via commercial card feed, direct bank feed, or CSV import, the next step is to configure the cards’ settings. diff --git a/docs/articles/expensify-classic/billing-and-subscriptions/Payment-Card.md b/docs/articles/expensify-classic/billing-and-subscriptions/Payment-Card.md deleted file mode 100644 index 41a1fb96f56f..000000000000 --- a/docs/articles/expensify-classic/billing-and-subscriptions/Payment-Card.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Payment Card -description: Payment Card ---- -## Resource Coming Soon! diff --git a/docs/articles/expensify-classic/expensify-card/Cardholder-Settings-and-Features.md b/docs/articles/expensify-classic/expensify-card/Cardholder-Settings-and-Features.md new file mode 100644 index 000000000000..bc0cb28f42b1 --- /dev/null +++ b/docs/articles/expensify-classic/expensify-card/Cardholder-Settings-and-Features.md @@ -0,0 +1,84 @@ +--- +title: Cardholder Settings and Features +description: Expensify Card Settings for Employees +--- + +# How to use your Expensify Card +Once you receive your card, you can start using it right away. + +First, you'll want to take note of the Smart Limit tied to your card – this is listed in your card settings via **Settings > Account > Credit Card Import**. This limit represents the total amount of unapproved expenses you can have on the card. + +It's crucial to continuously submit your expenses promptly, as that'll ensure they can be approved and restore your full limit. You can always chat with your admin if you need your limit adjusted. + +You can swipe your Expensify Card like you would with any other card. As you make purchases, you'll get instant alerts on your phone letting you know if you need to SmartScan receipts. Any SmartScanned receipts should merge with the card expense automatically. + +If your organization doesn't require itemized receipts, you can rely on eReceipts instead. As long as the expense isn't lodging-related, Expensify will automatically generate an IRS-compliant eReceipt for every transaction. + +You can report and submit Expensify Card expenses just like any other expenses. As they're approved, your Smart Limit will be refreshed accordingly, allowing you to keep making purchases. + +## Enable Notifications +Download the Expensify mobile app and enable push notifications to stay current on your spending activity. Your card is connected to your Expensify account, so each transaction on your card will trigger a push notification. We'll also send you a push notification if we detect potentially fraudulent activity and allow you to confirm your purchase. + +Follow the steps below to enable real-time alerts on your mobile device. + +**If you have an iPhone**: +1. Open the Expensify app and tap the three-bar icon in the upper-left corner +2. Tap **Settings** and enable **Receive realtime alerts** +3. Accept the confirmation dialogue to go to your iPhone's notification settings for Expensify. Turn on Allow Notifications, and choose the notification types you’d like! + +**If you have an Android**: +1. Go to Settings and open 'Apps and Notifications'. +2. Find and open Expensify and enable notifications. +3. Customize your alerts. Depending on your phone model, you may have extra options to customize the types of notifications you receive. + +## Your virtual card +Once you're assigned a limit, you'll be able to use your virtual card immediately. You can view your virtual card details via **Settings > Account > Credit Card Import > Show Details**. Keep in mind that your virtual card and physical card share a limit. + +The virtual Expensify Card includes a card number, expiration date, and security code (CVC). You can use the virtual card for online purchases, in-app transactions, and in-person payments once it's linked to a mobile wallet (Apple Pay or Google Pay). + +## How to access your virtual card details +Here's how to access your virtual card details via the Expensify mobile app: +1. Tap the three-bar icon in the upper-left corner +2. Tap **Settings > Connected Cards** +3. Under **Virtual Card**, tap **Show Details** + +From there, you can view your virtual card's number, CVV, expiration date, and billing address. + +Here's how to access your virtual card details via the Expensify web app: +1. Head to **Settings > Account > Credit Card Import** +2. Under **Virtual Card**, click **Show Details** + +From there, you can view your virtual card's card number, CVV, expiration date, and billing address. + +## How to add your virtual card to a digital wallet (Apple Pay or Google Pay) + +To use the Expensify Card for contactless payment, add it to your digital wallet from the mobile app: +1. Tap the three-bar icon in the upper-left corner +2. Tap **Settings > Connected Cards** +3. Depending on your device, tap **Add to Apple Wallet** or **Add to Gpay** +4. Complete the remaining steps + +## Expensify Card declines +As long as you've enabled 'Receive real-time alerts', you'll get a notification explaining the reason for each decline. You can enable alerts in the mobile app by clicking on the three-bar icon in the upper-left corner > **Settings** > toggle **Receive real-time alerts**. + +Here are some reasons an Expensify Card transaction might be declined: + +- You have an insufficient card limit + - If a transaction exceeds your Expensify Card's available limit, the transaction will be declined. You can see the remaining limit in the mobile app under **Settings > Connected Cards** or in the web app under **Settings > Account > Credit Card Import**. + - Submitting expenses and getting them approved will free up your limit for more spending. + +- Your card isn't active yet or it was disabled by your Domain Admin +- Your card information was entered incorrectly with the merchant. Entering incorrect card information, such as the CVC, ZIP, or expiration date, will also lead to declines. +There was suspicious activity +- If Expensify detects unusual or suspicious activity, we may block transactions as a security measure + - This could happen due to irregular spending patterns, attempted purchases from risky vendors, or multiple rapid transactions. + - Check your Expensify Home page to approve unusual merchants and try again. + - If the spending looks suspicious, we may complete a manual due diligence check, and our team will do this as quickly as possible - your cards will all be locked while this happens. +- The merchant is located in a restricted country + +# FAQ +## Can I use Smart Limits with a free Expensify account? +If you're on the Free plan, you won't have the option to use Smart Limits. Your card limit will simply reset at the end of each calendar month. + +## I still haven't received my Expensify Card. What should I do? +For more information on why your card hasn't arrived, you can check out this resource on [Requesting a Card](https://help.expensify.com/articles/expensify-classic/expensify-card/Request-the-Card#what-if-i-havent-received-my-card-after-multiple-weeks). diff --git a/docs/articles/expensify-classic/expensify-card/Expensify-Card-Perks.md b/docs/articles/expensify-classic/expensify-card/Expensify-Card-Perks.md index 4c216faffc18..8bcc11fbf167 100644 --- a/docs/articles/expensify-classic/expensify-card/Expensify-Card-Perks.md +++ b/docs/articles/expensify-classic/expensify-card/Expensify-Card-Perks.md @@ -5,7 +5,30 @@ description: Get the most out of your Expensify Card with exclusive perks! # Overview -The Expensify Card is packed with perks, both native to our Card program and through exclusive discounts with partnering solutions. Below, we’ll cover all of our exclusive offers in more detail and how to claim discounts with our partners. +The Expensify Card is packed with perks, both native to our Card program and through exclusive discounts with partnering solutions. The Expensify Card’s primary perks include: +- Swipe to Win, where every swipe has a chance to win fun personalized gifts for you and your closest friends and family members +- Unbeatable cash back incentive with each swipe +Below, we’ll cover all of our exclusive offers in more detail and how to claim discounts with our partners. + +# Expensify Card Perks + +## Swipe to Win +Swipe to Win is a new [Expensify Card](https://use.expensify.com/company-credit-card) perk that gives cardholders the chance to send a gift to a friend, family member, or essential worker on the frontlines! + +Winners can choose to _Send a Smile_ or _Send a Laugh_. To start, we’re offering one gift per option: + +- **Send A Smile:** Champagne by Expensify +- **Send a Laugh:** Jenga Set + +**How to Participate** +It’s easy! Once you have an Expensify Card, you just need to start using it. With each swipe, you're automatically entered to win and have a 1 in 250 chance of getting a prize! + +**How will I know if I’ve won?** +Winners will be notified immediately via the Expensify app, and receive additional instructions on how to choose and send their desired gift. + +If you don't have Expensify notifications turned on yet, here are some helpful guides: +- [Apple Notification Preferences](https://support.apple.com/en-us/HT201925) +- [Android Notification Preferences](https://community.expensify.com/home/leaving?allowTrusted=1&target=https%3A%2F%2Fsupport.google.com%2Fandroid%2Fanswer%2F9079661%3Fhl%3Den) # Partner Specific Perks @@ -188,4 +211,3 @@ Stripe Atlas helps removes obstacles typically associated with starting a busine **Receive $100 off Stripe Atlas and get access to a startup toolkit and special offers on additional Strip Atlas services.** **How to redeem:** Sign up with your Expensify Card. - diff --git a/docs/articles/expensify-classic/expensify-partner-program/Coming-Soon.md b/docs/articles/expensify-classic/expensify-partner-program/Coming-Soon.md deleted file mode 100644 index 6b85bb0364b5..000000000000 --- a/docs/articles/expensify-classic/expensify-partner-program/Coming-Soon.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Coming Soon -description: Coming Soon ---- diff --git a/docs/articles/expensify-classic/expensify-partner-program/Partner-Billing-Guide.md b/docs/articles/expensify-classic/expensify-partner-program/Partner-Billing-Guide.md new file mode 100644 index 000000000000..750a1fc10e77 --- /dev/null +++ b/docs/articles/expensify-classic/expensify-partner-program/Partner-Billing-Guide.md @@ -0,0 +1,87 @@ +--- +title: Partner Billing Guide +description: Understand how Expensify bills partners and their clients +--- + +# Overview + +The ExpensifyApproved! Partner Program offers exclusive billing rates and features tailored for accountants to ensure seamless client management. If you are an accountant or consultant who recommends spend management solutions to your clients, becoming an ExpensifyApproved! Accountant may be a great certification for you. This guide will walk you through the unique billing perks available to ExpensifyApproved! partners, emphasizing the importance of understanding and managing client billing effectively. To learn what perks partners receive, check out the ExpensifyApproved! program details here. + +# Get exclusive partner pricing + +All ExpensifyApproved! Partners are automatically eligible for a special rate of $9/seat monthly, without an annual commitment when they adopt the Expensify Card. This provides flexibility as active users can vary. Here are the specifics on pricing for our Approved! Partners’ clients: +- **Bundled pricing:** US Clients using the Expensify Card get up to a 50% discount, bringing their monthly bill to $9/seat. Reach out to your Partner Manager to discuss exclusive international pricing discount. +- **Unbundled pricing (or pay-per-use pricing):** Clients not using the Expensify Card are billed at $18/seat monthly. +- **No annual commitment:** Partners pay only for what they use, with no annual subscriptions required. +- **Expensify Card revenue share:** All partners receive a 0.5% revenue share on Expensify Card transactions made by clients. This revenue share can be passed back to the client for an additional discount to offset their Expensify bill. + +# Understanding the billing process + +Expensify bills the owner of the expense workspace for the activity on that workspace. If accountants retain ownership of client workspaces, they receive the bill and can then re-bill their clients based on individual agreements. + +Each month, Expensify will send a consolidated bill detailing: +- **Pay-per-use seats:** This is the number of active clients and their users for the month. +- **Expensify Card discount**: This amount reflects how much spend is put on your card, which then determines the discount for that month. +- **Total monthly price:** This amount is the overall price of Expensify when using the Expensify Card discount to offset the cost of the pay-per-use seats. +- **Workspace list:** This is an overview of all client workspaces with their respective active seats. + +## Consolidated Domain Billing + +If your firm wishes to consolidate all Expensify billing to a single account, the Consolidated Domain Billing feature is your go-to tool. It centralizes payment for all group workspaces owned by any domain member of your firm. + +### Activating Consolidated Domain Billing: + 1. Claim and verify your firm’s domain. + 2. Navigate to **Settings > Domains > [Domain Name] > Domain Admins** and set a **"Primary Domain Admin"** by using the drop down toggle to select an email address. + 3. Enable **Consolidated Domain Billing** in the same section. + +The Consolidated Domain Billing tool ensures that billing takes place under a single Expensify account associated with your firm. This eliminates the need to add multiple payment cards across various accounts to cover payments for multiple clients. + +## Maintaining a Console of all clients: + +If your firm wants to have a console view of all client workspaces and domains, you will want to create a single, centralized login to manage all client workspaces and domains, such as accounting@myfirm.com. + + 1. Create a dedicated email address that will act as the universal workspace owner, for example, accounting@myfirm.com. + 2. Register this email with Expensify or your chosen platform and ensure it is verified and secured. + 3. Within each client workspace settings, add your centralized email (e.g. accounting@myfirm.com) as a workspace admin. + 4. Do the same with each client domain. + +## Applying Client IDs to a bill + +Using client IDs for Optimized Billing in Expensify: A unique identifier feature for ExpensifyApproved! accountants. Streamline client Workspace recognition and make your billing process more efficient. + +# How to assign a client ID to a workspace + 1. Log in to your account: Ensure you’re using an Approved! accountant account. + 2. Navigate to the desired Workspace: Go to **Settings > Workspaces > [Workspace Name] > Overview**. + 3. Input the identifier: Here, you can input an alphanumeric unique identifier for each client workspace. +**Note:** If a client has multiple workspaces, ensure each workspace has a consistent client ID. + +# How to access and download billing receipts +- Accessing Billing: **Settings: Go to Settings > Your Account > Payments > Billing History.** +- Download the Receipt: Click on **"Download Receipt CSV".** + +# Deep Dive +- Using client IDs for all Workspaces: It's beneficial to use client IDs for all Workspaces to ensure each one is easily recognizable. +- Benefits of itemized billing receipts: Employing client IDs offers itemized billing by client, with each itemization detailing unique active users. + +# FAQ + +**Do I automatically get the special billing rate as an ExpensifyApproved! Partner?** +- Yes, when you join the ExpensifyApproved! program, you will automatically get the special billing rate. To join the ExpensifyApproved! Program, you need to enroll in ExpensifyApproved! University. + +**How can I check my billing details?** +- To check your billing details, be on the lookout for your detailed billing statement sent out at the end of each month. + +**Can I pass the bill to my clients?** +- Yes, you can pass the bill on to your clients. If you retain ownership of client workspaces in Expensify, you can re-bill your clients. If you’d like the client to own the billing of the Expensify, they can take over billing. + +**What if I don't want to use the Expensify Card?** +- If you prefer not to use the Expensify Card, your clients will be billed at $18/seat monthly. + +**Why use client IDs?** +- Client IDs provide a streamlined method to identify and manage workspaces, especially beneficial when a client has multiple workspaces. + +**Do I need a client ID for each Workspace?** +- Yes, if you want to ensure seamless identification and billing processes. + +**Where can I see the Billing Receipts?** +- All billing owners receive an emailed PDF of their monthly billing receipt, but a CSV version can also be downloaded from the platform. diff --git a/docs/articles/expensify-classic/expensify-partner-program/Your-Expensify-Partner-Manager.md b/docs/articles/expensify-classic/expensify-partner-program/Your-Expensify-Partner-Manager.md index 2db69d0a8791..8243833dcc23 100644 --- a/docs/articles/expensify-classic/expensify-partner-program/Your-Expensify-Partner-Manager.md +++ b/docs/articles/expensify-classic/expensify-partner-program/Your-Expensify-Partner-Manager.md @@ -1,5 +1,5 @@ --- -title: Your Expensify Partner Manager +title: Expensify Partner Support description: Understanding support for our partners --- @@ -10,6 +10,7 @@ Our well-rounded support methodology is designed to provide comprehensive assist ## 1. ExpensifyApproved! University **Purpose:** Equip your team with a comprehensive understanding of Expensify. + **Benefits:** - Foundation-level knowledge about the platform. - 3 CPE credits upon successful completion (US-only). @@ -17,16 +18,39 @@ Our well-rounded support methodology is designed to provide comprehensive assist - Visit university.Expensify.com to access our comprehensive training program. ## 2. Partner Manager -**Role:** A designated liaison for your firm. +**Role:** +A Partner Manager is a dedicated point of contact for your firm Partner Managers support our accounting partners by providing recommendations for client’s accounts, assisting with firm-wide training, and ensuring partners receive the full benefits of our partnership program. They will actively monitor open technical issues and be proactive with recommendations to increase efficiency. + + **Key Responsibilities:** - Handle any escalations promptly. - Organize firm-wide training sessions. - Assist with strategic planning and the introduction of new features. - Once you've completed the ExpensifyApproved! University, log in to your Expensify account. Click on the "Support" option to connect with your dedicated Partner Manager. +**How do I know if I have a Partner Manager?** + +For your firm to be assigned a Partner Manager, you must complete the ExpensifyApproved! University training course. Every external accountant or bookkeeper who completes the training is automatically enrolled in our program and receives all the benefits, including access to the Partner Manager. So everyone at your firm must complete the training to receive the maximum benefit. + +You can check to see if you’ve completed the course and enrolled in the ExpensifyApproved! Accountants program simply by logging into your Expensify account. In the bottom left-hand corner of the website, you will see the ExpensifyApproved! logo. + +**How do I contact my Partner Manager?** +1. Signing in to new.expensify.com and searching for your Partner Manager +2. Replying to or clicking the chat link on any email you get from your Partner Manager + +**How do I know if my Partner Manager is online?** + +You will be able to see if they are online via their status in new.expensify.com, which will either say “online” or have their working hours. + +**Can I get on a call with my Partner Manager?** + +Of course! You can ask your Partner Manager to schedule a call whenever you think one might be helpful. Partner Managers can discuss client onboarding strategies, firm-wide training, and client setups. + +We recommend continuing to work with Concierge for general support questions, as this team is always online and available to help immediately. ## 3. Client Setup Specialist **Purpose:** Ensure smooth onboarding for every client you refer. + **Duties:** - Comprehensive assistance with setting up Expensify. - Help with configuring accounting integrations. @@ -35,6 +59,7 @@ Our well-rounded support methodology is designed to provide comprehensive assist ## 4. Client Account Manager **Role:** Dedicated support for ongoing client needs. + **Responsibilities:** - Address day-to-day product inquiries. - Assist clients in navigating and optimizing their use of Expensify. @@ -42,6 +67,7 @@ Our well-rounded support methodology is designed to provide comprehensive assist ## 5. Concierge chat support **Availability:** Real-time support for any urgent inquiries. + **Features:** - Immediate assistance with an average response time of under two minutes. - Available to both accountants and clients for all product-related questions. diff --git a/docs/articles/expensify-classic/workspace-and-domain-settings/Creating-Per-Diem-Expenses.md b/docs/articles/expensify-classic/get-paid-back/Per-Diem-Expenses.md similarity index 89% rename from docs/articles/expensify-classic/workspace-and-domain-settings/Creating-Per-Diem-Expenses.md rename to docs/articles/expensify-classic/get-paid-back/Per-Diem-Expenses.md index 214188e35137..1b537839af77 100644 --- a/docs/articles/expensify-classic/workspace-and-domain-settings/Creating-Per-Diem-Expenses.md +++ b/docs/articles/expensify-classic/get-paid-back/Per-Diem-Expenses.md @@ -1,17 +1,17 @@ --- -title: Creating Per Diem Expenses +title: Per-Diem-Expenses description: How to create Per Diem expenses on mobile and web. --- # Overview What are Per Diems? Per diems, short for "per diem allowance" or "daily allowance," are fixed daily payments provided by your employer to cover expenses incurred during business or work-related travel. These allowances simplify expense tracking and reimbursement for meals, lodging, and incidental expenses during a trip. Per Diems can be masterfully tracked in Expensify! -## How To create per diem expenses +## How to create per diem expenses To add per diem expenses, you need three pieces of information: 1. Where did you go? - Specify your travel destination. 2. How long were you away? - Define the period you're claiming for. -3. Which rate did you use? - Select the appropriate per diem rate. +3. Which rate did you use? - Select the appropriate per diem rate (this is set by your employer). ### Step 1: On either the web or mobile app, click New Expense and choose Per Diem @@ -31,15 +31,15 @@ Finally, submit your Per Diem expense for approval, and you'll be on your way to # FAQ -## Can I Edit My Per Diems? +## Can I edit my per diem expenses? Per Diems cannot be amended. To make changes, delete the expense and recreate it as needed. -## What If My Admin Requires Daily Per Diems? +## What if my admin requires daily per diems? No problem! Create a separate Per Diem expense for each day of your trip. -## I Have Questions About the Amount I'm Due +## I have questions about the amount I'm due Reach out to your internal Admin team, as they've configured the rates in your policy to meet specific requirements. -## Can I Add Start and End Times to a Per Diem? +## Can I add start and end times to per diems? Unfortunately, you cannot add start and end times to Per Diems in Expensify. By following these steps, you can efficiently create and manage your Per Diem expenses in Expensify, making the process of tracking and getting reimbursed hassle-free. diff --git a/docs/articles/expensify-classic/get-paid-back/Per-Diem.md b/docs/articles/expensify-classic/get-paid-back/Per-Diem.md deleted file mode 100644 index 780e5969c441..000000000000 --- a/docs/articles/expensify-classic/get-paid-back/Per-Diem.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Per Diem -description: Per Diem ---- -## 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/get-paid-back/Trips.md b/docs/articles/expensify-classic/get-paid-back/Trips.md index 7efba1875a90..a65a8bfb8eec 100644 --- a/docs/articles/expensify-classic/get-paid-back/Trips.md +++ b/docs/articles/expensify-classic/get-paid-back/Trips.md @@ -1,5 +1,37 @@ --- title: Trips -description: Trips +description: Automate getting paid back for your travel through Expensify's Trips feature. --- -## Resource Coming Soon! +# Overview + +Discover how Expensify streamlines your travel expense management when it comes to trips. With the automatic classification of trip receipts and real-time notifications for travel changes, you can effortlessly stay organized and informed on all your trips. + +When a travel receipt/itinerary is uploaded into Expensify and SmartScanned, the Trips section of the mobile app will automatically populate your trip information.. If your flight has any cancellations, unexpected changes, or delays, we will make sure you know about it. We will notify you of the change as soon as it happens via the mobile app. + +For the receipt to be processed as a Trip, it must include the total amount of the expense, date, and merchant name. + +If your company is using a travel integration from the list shown below, you can automate this process entirely: +- [TravelPerk](https://help.expensify.com/articles/expensify-classic/integrations/travel-integrations/TravelPerk) +- [Egencia](https://help.expensify.com/articles/expensify-classic/integrations/travel-integrations/Egencia) +- [Navan](https://help.expensify.com/articles/expensify-classic/integrations/travel-integrations/Trip-Actions) + +# How to add a Trip to your account + +Trip receipts are typically sent via email, and will include multiple pages. With that in mind, we recommend emailing receipts directly to Expensify for ease. + +To email a flight or hotel receipt, you’ll forward the receipt from your Expensify-associated email address to receipts@expensify.com. + +# How to access your Trip information + +To view details about your past or upcoming trips, follow these steps within the Expensify mobile app: +1. Open the Expensify mobile app +2. Navigate to the "Menu" option (top left ≡ icon) +3. Select **Trips** + +# FAQ + +## How do I capture Trip receipts sent to my personal email address? +If you received your receipt in an email that is not associated with your Expensify account, you can add this email as a [secondary login](https://help.expensify.com/articles/expensify-classic/account-settings/Account-Details#how-to-add-a-secondary-login) to directly forward the receipt into your account. + +## How do I upload Trip receipts that were not sent to me by email? +If your trip receipt was not sent to you by email, you can manually upload the receipt to your account. Check out this resource for more information on [manually uploading receipts](https://help.expensify.com/articles/expensify-classic/get-paid-back/expenses/Upload-Receipts#manually-upload). 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/getting-started/support/Expensify-Support.md b/docs/articles/expensify-classic/getting-started/support/Expensify-Support.md new file mode 100644 index 000000000000..f4a6acdd8571 --- /dev/null +++ b/docs/articles/expensify-classic/getting-started/support/Expensify-Support.md @@ -0,0 +1,117 @@ +--- +title: Your Expensify Account Manager +description: Everything you need to know about Having an Expensify account manager +redirect_from: articles/other/Your-Expensify-Account-Manager/ +--- + + + +# Introduction to Expensify Support + +Expensify offers comprehensive support for customers of all sizes. From small business owners to multinational corporations, Expensify's support teams are available to assist! + +Expensify has four different support teams. Let's go over how each of them operates below. + +**Concierge Support:** +- Concierge is available 24/7 and is the first line of support +- Users can access Concierge through the Expensify app or via email at concierge@expensify.com +- Provides quick responses, usually within minutes +- Don't be your team's tech support - refer your users to Concierge for all their Expensify product questions + +**Setup Specialists:** +- Dedicated onboarding experts to assist users in getting started with Expensify +- Assist in connecting cards and bank accounts, configuring integrations, and more + +**Account Managers:** +- A dedicated point of contact for companies with more than ten users +- Serve as product specialists +- Available to answer questions and promote best practices +- Provide ongoing support and guidance + +**ExpensifyApproved! Accounting Partner Managers:** +- Offer an additional support layer for accounting partners +- Partner Managers are dedicated Expensify team members +- Provide firmwide training, assist with strategic goal planning, and collaborative marketing + +This comprehensive support structure ensures that Expensify users receive the assistance they need, whether new to the platform or looking to optimize their experience. + +# How to get support in Expensify + +## Concierge + +Reach out to Concierge when you have general questions about Expensify. Concierge can also be used to manage expenses, submit reports, and automate approvals. You can contact Concierge while logged into your Expensify account by clicking **Support** on the left-hand menu and then selecting **Concierge**. + +Try questions such as: +- What is SmartScan? +- How do I add a mileage expense from the mobile app? +- Where can I add a Secondary Login? + +## Setup Specialist + +Expensify provides onboarding guidance for anyone through a Setup Specialist! You can contact your Setup Specialist while logged into your Expensify account by clicking **Support** on the left-hand menu and then selecting **Account Setup Specialist**. + +### What to expect from your Setup Specialist: + +- Personalized Support: Your Setup Specialist is here to assist you with tailored support. They can answer your questions and arrange screen share calls to help you set up your Expensify account or familiarize you with the product. + +- Product Demos: Are you curious about how the Expensify Card works or how it integrates with your accounting software? Just let your Setup Specialist know, and they'll guide you and your team through any feature you'd like to explore. + +- Setup Assistant: You don't have to do it alone when setting up your Expensify account. When you're ready to begin, chat with your Setup Specialist in the #admins room. They'll ensure everything is configured correctly right from the start. + + +## Account Manager + +If you have more than ten active users, or you're an ExpensifyApproved! Accountant's client, you're eligible for a designated Account Manager. + +Once you've completed onboarding to Expensify with your Setup Specialist, your Account Manager will take over as the main point of contact. Your account manager is there to support you and other Workspace admins in reviewing your account, advising on best practices, and making changes to your Workspace on your behalf whenever you need a hand. + +They will actively monitor open technical issues and be proactive with recommendations to increase efficiency and minimize time spent on expense management. + +### How to contact your Account Manager + +If you are a Workspace admin or domain admin, you will hear from your Account Manager as soon as one is assigned to your company. + +Unlike Concierge, an Account Manager's support will not be real-time, 24 hours a day. Your Account Manager will be as responsive as possible when online, but anything sent when they're offline will not be responded to until they're working again. + +You can contact your Account Manager while logged into your Expensify account by clicking **Support** on the left-hand menu and then selecting **Account Manager**. + +## Partner Manager + +A Partner Manager is a dedicated point of contact to support our ExpensifyApproved! Accountants with questions about their Expensify account. Partner Managers support our accounting partners by providing recommendations for client's accounts, assisting with firm-wide training, and ensuring partners receive the full benefits of our partnership program. They will actively monitor open technical issues and be proactive with recommendations to increase efficiency and minimize time spent on expense management. + +For your firm to be assigned a Partner Manager, you must complete the [ExpensifyApproved! University](https://use.expensify.com/accountants-program) training course. Every external accountant or bookkeeper who completes the training is automatically enrolled in our program and receives benefits, including access to a Partner Manager. Everyone at your firm must complete the training to receive the maximum benefit. + +### How to contact your Partner Manager + +Your Partner Manager should reach out to you once you've completed ExpensifyApproved! University. Otherwise, you can contact your Partner Manager anytime by clicking **Support** on the left-hand menu and then selecting **Partner Manager**. + +## Tips on Contacting Expensify Support + +- **Use the Right Email**: Contact us from the email address linked to your Expensify account. This helps us locate your information accurately. +- **Be Clear and Specific**: When asking questions or reporting issues, provide specific examples like affected users' email addresses or report IDs. This makes it easier for us to assist you effectively. +- **Practice Kindness**: Remember that we're here to help. Please be polite, considerate, and patient as we work together to resolve any concerns you have. + +# FAQ +## Who gets an Account Manager? +Members who have 10 or more active users, or clients of ExpensifyApproved! Accounts are automatically assigned a dedicated Account Manager. + +To be assigned an Account Manager immediately, log into your Expensify account and go to **Settings > Workspaces > Group**, then click **Subscription** and increase your subscription size to 10 or more. + +## What if I'm unable to reach my Account Manager? +If you're unable to reach your account manager, then you can always reach out to Concierge for assistance. Your account manager will always reply once they're online again. + +## Can I schedule a call with my Account Manager? +Of course! You can ask your Account Manager for a call whenever you think one might be helpful. + +**What makes a successful call with an Account Manager**: +- Scheduling the call using their calendar link (this should be shared with you once you're assigned an Account Manager) +- Listing out exactly what you'd like to go over with the Account Manager, and then sharing that list with them before the call +- Having anyone else in your organization who regularly manages your Expensify setup join the call + +## When should I chat with Concierge instead of my Account Manager? +If you have a general question about the status of a report or updating an account setting, Concierge is more than capable of getting you the answers you need. + +We recommend working with Concierge on general support questions, as this team is always online and available to help immediately. + +## Who gets assigned a Setup Specialist? +This feature is specifically for new members! Whenever you start a free trial, a product Setup Specialist will be assigned to guide you through configuring your Expensify account. diff --git a/docs/articles/expensify-classic/getting-started/support/Your-Expensify-Account-Manager.md b/docs/articles/expensify-classic/getting-started/support/Your-Expensify-Account-Manager.md deleted file mode 100644 index a6fa0220c0dc..000000000000 --- a/docs/articles/expensify-classic/getting-started/support/Your-Expensify-Account-Manager.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -title: Your Expensify Account Manager -description: Everything you need to know about Having an Expensify account manager -redirect_from: articles/other/Your-Expensify-Account-Manager/ ---- - - - -# What is an account manager? -An account manager is a dedicated point of contact to support policy admins with questions about their Expensify account. They are available to help you and other policy admins review your account, advise on best practices, and make changes to your policy on your behalf whenever you need a hand. They will actively monitor open technical issues and be proactive with recommendations to increase efficiency and minimize time spent on expense management. - -Unlike Concierge, an account manager’s support will not be real-time, 24 hours a day. A benefit of Concierge is that you get real-time support every day. Your account manager will be super responsive when online, but anything sent when they’re offline will not be responded to until they’re online again. - -For real-time responses and simple troubleshooting issues, you can always message our general support by writing to Concierge via the in-product chat or by emailing concierge@expensify.com. - -# How do I know if I have an account manager? -If you are a policy admin or domain admin, you will also hear from your account manager as soon as one gets assigned to your company. If you'd like a reminder who your account manager is, just click the Support link on the left side of Expensify - you'll see your account manager's name and photo, with an option to contact them for help. - -## How do I contact my account manager? -We make it easy to contact your account manager: - -1. Log in to your Expensify account, click "Support" along the left side of the page, and click the “Account Manager” option -2. Reply to (or click the chat link on) any email you get from your account manager -3. Sign in to new.expensify.com and go to the #admins room for any of your policies. Your account manager is in your #admin rooms ready to help you, so you can ask for help here and your account manager will respond in the chat. - -# FAQs -## Who gets an account manager? -Every customer with 10 or more paid subscribers is automatically assigned a dedicated account manager. If you have fewer than 10 active users each month, you can still get an account manager by increasing your subscription to 10 or more users, To get assigned an account manager immediately, log into your Expensify account and go to Settings > Policies > Group, then click Subscription and increase your subscription size to 10 or more. - -## How do I know if my account manager is online? -You will be able to see if they are online via their status, which will either say something like “online” or have their working hours. - -## What if I’m unable to reach my account manager? -If for some reason, you’re unable to reach your account manager, perhaps because they’re offline, then you can always reach out to Concierge for assistance. Your account manager will always get back to you when they’re online again. - -## Can I get on a call with my account manager? -Of course! You can ask your account manager to schedule a call whenever you think one might be helpful. We usually find that the most effective calls are those that deal with general setup questions. For technical troubleshooting, we typically recommend chat as that allows your account manager time to look into the issue, test things on their end, and, if needed, consult the wider Expensify technical team. It also allows you to easily refer back to instructions and links. \ No newline at end of file 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/integrations/accounting-integrations/QuickBooks-Desktop.md b/docs/articles/expensify-classic/integrations/accounting-integrations/QuickBooks-Desktop.md index d8c7c145a670..958e423273ce 100644 --- a/docs/articles/expensify-classic/integrations/accounting-integrations/QuickBooks-Desktop.md +++ b/docs/articles/expensify-classic/integrations/accounting-integrations/QuickBooks-Desktop.md @@ -76,6 +76,7 @@ Expensify's integration with QuickBooks brings in your Chart of Accounts as Cate 3. Expensify offers Auto-Categorization to automatically assign expenses to the appropriate expense categories. 4. If needed, you can edit the names of the imported Categories to simplify expense coding for your employees. Keep in mind that if you make changes to these accounts in QuickBooks Desktop, the category names in Expensify will update to match them during the next sync. 5. _**Important:**_ Each expense must have a category selected to export to QuickBooks Desktop. The selected category must be one imported from QuickBooks Desktop; you cannot manually create categories within Expensify policy settings. + ## Classes Classes can be imported from QuickBooks as either tags (line-item level) or report fields (header level). diff --git a/docs/articles/expensify-classic/integrations/accounting-integrations/QuickBooks-Online.md b/docs/articles/expensify-classic/integrations/accounting-integrations/QuickBooks-Online.md index 9d17160d3a36..4075aaf18016 100644 --- a/docs/articles/expensify-classic/integrations/accounting-integrations/QuickBooks-Online.md +++ b/docs/articles/expensify-classic/integrations/accounting-integrations/QuickBooks-Online.md @@ -14,6 +14,8 @@ It's crucial to understand the requirements based on your specific QuickBooks su - An error will occur if you try to export to QuickBooks with a feature enabled that isn't part of your subscription. - Please be aware that Expensify does not support the Self-Employed subscription in QuickBooks Online. +![QuickBooks Online - Subscription types]({{site.url}}/assets/images/QBO1.png){:width="100%"} + # How to connect to QuickBooks Online ## Step 1: Setup employees in QuickBooks Online @@ -79,14 +81,20 @@ This is a single itemized vendor bill for each Expensify report. If the accounti The submitter will be listed as the vendor in the vendor bill. +![Vendor Bill]({{site.url}}/assets/images/QBO2-Bill.png){:width="100%"} + ## Check This is a single itemized check for each Expensify report. You can mark a check to be printed later in QuickBooks Online. +![Check to print]({{site.url}}/assets/images/QBO3-Checktoprint.png){:width="100%"} + ## Journal entry This is a single itemized journal entry for each Expensify report. +![Journal Entry]({{site.url}}/assets/images/QBO4-JournalEntry.png){:width="100%"} + # Non-reimbursable expenses Non-reimbursable expenses export to QuickBooks Online as: @@ -102,7 +110,9 @@ Using Credit/Debit Card Transactions: - Each expense will be exported as a bank transaction with its transaction date. - If you split an expense in Expensify, we'll consolidate it into a single credit card transaction in QuickBooks with multiple line items posted to the corresponding General Ledger accounts. -Pro-Tip: To ensure the payee field in QuickBooks Online reflects the merchant name for Credit Card expenses, ensure there's a matching Vendor in QuickBooks Online. Expensify checks for an exact match during export. If none are found, the payee will be mapped to a vendor we create and labeled as Credit Card Misc. or Debit Card Misc. +Pro-Tip: To ensure the payee field in QuickBooks Online reflects the merchant name for Credit Card expenses, ensure there's a matching Vendor in QuickBooks Online. Expensify checks for an exact match during export. If none are found, the payee will be mapped to a vendor we create and labeled as Credit Card Misc. or Debit Card Misc. + +![Expense]({{site.url}}/assets/images/QBO5-Expense.png){:width="100%"} If you centrally manage your company cards through Domains, you can export expenses from each card to a specific account in QuickBooks. @@ -224,6 +234,8 @@ Step 3: Importing Your Credit Card Transactions into QuickBooks Online - After completing Steps 1 and 2, you can import your credit card transactions into QuickBooks Online. These imported banking transactions will align with the ones brought in from Expensify. QuickBooks Online will guide you through the process of matching these transactions, similar to the example below: +![Transactions]({{site.url}}/assets/images/QBO7-Transactions.png){:width="100%"} + ## Tax in QuickBooks Online If your country applies taxes on sales (like GST, HST, or VAT), you can utilize Expensify's Tax Tracking along with your QuickBooks Online tax rates. Please note: Tax Tracking is not available for Workspaces linked to the US version of QuickBooks Online. If you need assistance applying taxes after reports are exported, contact QuickBooks. @@ -247,6 +259,8 @@ When working with QuickBooks Online Multi-Currency, there are some things to rem In QuickBooks Online, the currency conversion rates are not applied when exporting. All transactions will be exported with a 1:1 conversion rate, so for example, if a vendor's currency is CAD (Canadian Dollar) and the home currency is USD (US Dollar), the export will show these currencies without applying conversion rates. +![Check]({{site.url}}/assets/images/QBO6-Check.png){:width="100%"} + To correct this, you must manually update the conversion rate after the report has been exported to QuickBooks Online. Specifically for Vendor Bills: diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/Trip-Actions.md b/docs/articles/expensify-classic/integrations/travel-integrations/Navan.md similarity index 100% rename from docs/articles/expensify-classic/integrations/travel-integrations/Trip-Actions.md rename to docs/articles/expensify-classic/integrations/travel-integrations/Navan.md diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/Uber.md b/docs/articles/expensify-classic/integrations/travel-integrations/Uber.md index 3ee1c8656b4b..16da0c0caa5b 100644 --- a/docs/articles/expensify-classic/integrations/travel-integrations/Uber.md +++ b/docs/articles/expensify-classic/integrations/travel-integrations/Uber.md @@ -1,5 +1,24 @@ --- -title: Coming Soon -description: Coming Soon +title: Uber integration +description: Connecting your Uber account to Expensify --- -## Resource Coming Soon! +## Overview + +Link Expensify directly to your Uber account so your Uber for Business receipts populate automatically in Expensify. + +# How to connect Uber to Expensify + +You can do this right in the Uber app: + +1. Head to Account > Business hub > Get started +2. Tap Create an individual account > Get started +3. Enter your business email and tap Next +4. Select the payment card you'd like to use for your business profile +5. Choose how frequently you’d like to receive travel summaries +6. Select Expensify as your expense provider +Expensify and Uber are now connected! + +Now, every time you use Uber for Business – be it for rides or meals – the receipt will be imported and scanned into Expensify automatically. + +![Uber integration set up steps: Connecting your account](https://help.expensify.com/assets/images/Uber1.png){:width="100%"} +![Uber integration set up steps: Selecting Expensify](https://help.expensify.com/assets/images/Uber2.png){:width="100%"} 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/billing-and-plan-types/The-Free-Plan.md b/docs/articles/new-expensify/billing-and-plan-types/The-Free-Plan.md index e157ede1969d..3b79072aa393 100644 --- a/docs/articles/new-expensify/billing-and-plan-types/The-Free-Plan.md +++ b/docs/articles/new-expensify/billing-and-plan-types/The-Free-Plan.md @@ -30,16 +30,38 @@ Once you’ve completed your company setup, you should have completed the follow - Invited members to the workspace - Assigned Expensify Cards -# Inviting Members to the Free Plan: +# Inviting Members to the Free Plan - Navigate to the Settings Menu and click **_Members_** to invite your team. You can invite employees one at a time, or you can invite multiple users by listing out their email addresses separated by a comma - To use the Expensify Card, you must invite them to your workspace via your company email address (i.e., admin@companyemail.com and NOT admin@gmail.com). # Managing the Free Plan -To access your workspace settings, click your profile icon and then on your workspace name. +## Workspace Settings -This settings menu allows you to manage your workspace members, issue additional Expensify Cards, and utilize this plan’s various bill pay and payment options. +To access your workspace settings, click your profile icon and then on your workspace name. This settings menu allows you to manage your workspace members, issue additional Expensify Cards, and utilize this plan’s various bill pay and payment options. + +## Paying an Expense Report +- Once a user creates an expense it will automatically be shared with you in a Processing report. +- Pay expenses directly through Expensify by choosing ‘Reimburse > via Direct Deposit (ACH)` in a report on www.expensify.com or by choosing ‘Pay with Expensify’ in a payment request on new.expensify.com. +- Notify your user that you’ll pay them manually outside of Expensify by choosing ‘Reimburse > I’ll do it manually’ in a report on www.expensify.com or choosing ‘Pay Elsewhere’ in a payment request on new.expensify.com. +- Reports with only non-reimbursable expenses on them have the option to ‘Mark as Closed’ in the report on www.expensify.com or ‘Mark as Done’ in the payment request on new.expensify.com. + +## Changing Submitted Expenses + +Request an edit an expense or remove an expense before you pay, you can let your user know by making a comment in the Report History section of their Processing report or chatting with them on new.expensify.com. + +# Managing Expenses + +## Creating an Expense +- You can create an expense either by swiping the Expensify card or just smartscan a receipt! +- Once you create an expense it will be automatically added to a report and shared with your admin. +- You can edit or delete any expense that hasn’t been paid or closed by your admin. + +## Getting paid for Expenses +- Automatic submission is already set up, so your admin can pay you back immediately once you create an expense. +- Your admin will get a notification when you send them a new expense, but you can also remind them to pay you by making a comment in the Report History section of your Processing report or chatting with them on new.expensify.com. # FAQs + ## Do I need a business bank account to use the Free Plan? You will need a US business checking account if you want to enable the Expensify Card and set up direct ACH reimbursement. @@ -50,9 +72,27 @@ If you're not in the US, you can still use the Free Plan, but the Expensify Card The Expensify Workplace only allows for one admin (the workspace creator). -## Scheduled Submit is set to weekly on the Free Plan. Can I change this? +## I am on a paid plan, can I switch to a Free Plan? + +You can set up a Free Plan, but you must honor any active subscription you have also. If you're on a paid plan, it is likely you want more functionality than what the Free Plan offers such as a direct connection with an accounting integration and approval workflows. + +## Can I get cashback on Expensify Card purchases if I have a free plan? + +You can get 1% credited back to your settlement account once you spend over $25,000 per month across all cards and 2% when you spend over $250,000! -No, expense reports on the Free Plan submit weekly, and there is no way to customize approval settings. +## Can I switch the workspace currency on the Free Plan? It looks set to USD + +Yes, you can change the currency of the Free Plan by going to Avatar > [Workspace Name] > General settings > Default currency. + +We do require a US business bank account for reimbursements and Expensify Card settlements though, so if you have a business bank account linked to your account, then the currency of the Free Plan will be USD. + +## Is a free plan available to people outside the USA? + +Yes! You can use the free plan anywhere in the world to track expenses, send invoices etc. Just remember, anything that requires a verified business bank account (such as ACH reimbursement and the Expensify Card) is only available to those with a US checking account! + +## Can I integrate the free plan with my accounting package? + +No. In order to use our accounting sync, you will need to update to a paid plan ## With the Free Plan, can I add my own categories? @@ -60,4 +100,48 @@ Categories are standardized on the Free Plan and can’t be edited. Custom categ ## With the Free Plan, can I export reports using a custom format? -The Free Plan offers standard report export formats. You'll need to upgrade to a paid plan to create a custom export format. \ No newline at end of file +The Free Plan offers standard report export formats. You'll need to upgrade to a paid plan to create a custom export format. + +## Can I change the mileage rate or unit used for distance tracking? + +Yes. The workspace admin can access the rate and unit settings by going to Avatar > [Workspace name] > Reimburse expenses > Track distance. + +## Can I add non-Expensify cardholders to my Workspace? + +Yes. You can invite any user to your Workspace. Just click invite and enter their email and they will be invited to download the app and join! + +## Expenses are automatically shared with the admin on a Processing report on the Free Plan, can I change this? + +No. Expense reports submitted on the Free Plan are set to submit automatically and do not allow for approvals. Different Scheduled Submit settings are available on our paid plans. + +## How do I give a user I invited to my Workspace an Expensify Card? + +You can give a user an Expensify Card by going to the Company Cards Page on expensify.com and setting a card limit >$0. Please note that in order to give a user a card, they must be using a private company email address (i.e. name@companyname.com NOT name@gmail.com) + +## Can I use a different bank account to the one I have added for some of the features in the workspace? + +No. The bank account you have added will be used to issue corporate cards, reimburse expenses, collect invoices, and pay bills! + +## Can I upgrade my Free Workspace to a Paid Workspace or do I need to create a new one? + +There is no way to upgrade a workspace, so you would need to create a new Workspace. Paid workspaces have more functionalities than what the Free Plan offers, such as a direct connection with an accounting integration and approval workflows. + +## Can I create more than one Processing report at a time? + +No. To keep things simple, we only allow one Processing report per user at a time. If you need to have more than one report at a time, our paid plans support unlimited reports. + +## A user has added an expense I need to change before I pay it, how do I let them know? + +Users can edit and delete expenses on Processing reports. If you need something changed, let them know by commenting in the Report History section of the report on expensify.com or by chatting with them in new.expensify.com. + +## Can I ‘Reopen’ a report once it’s Reimbursed or Closed? + +No. Once an admin reimburses or closes a report, it cannot be ‘reopened’, but you can always comment on a report to add context. + +## I accidentally reimbursed a report too soon. Can I cancel the reimbursement? + +Depending on how quickly you report it to us, we may be able to help cancel a reimbursement. Chat Concierge to see if we can help cancel a reimbursement. + +## As an admin, can I edit users’ expenses and delete them from reports? + +No. Only users can edit and delete expenses on the Free plan. Admin control of submitted expenses on a workspace is a feature of our paid plans. If you need something changed, let the user know by commenting in the Report History section of the report on www.expensify.com or by chatting with them in new.expensify.com. diff --git a/docs/articles/new-expensify/expensify-partner-program/Coming-Soon.md b/docs/articles/new-expensify/expensify-partner-program/Coming-Soon.md deleted file mode 100644 index 6b85bb0364b5..000000000000 --- a/docs/articles/new-expensify/expensify-partner-program/Coming-Soon.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Coming Soon -description: Coming Soon ---- diff --git a/docs/articles/new-expensify/get-paid-back/Distance-Requests.md b/docs/articles/new-expensify/get-paid-back/Distance-Requests.md new file mode 100644 index 000000000000..91b88409be8b --- /dev/null +++ b/docs/articles/new-expensify/get-paid-back/Distance-Requests.md @@ -0,0 +1,27 @@ +--- +title: Distance Requests +description: How to create a distance request and request reimbursement for mileage +--- + + +# Overview + +Expensify allows you to request reimbursement for mileage by creating a distance request from a map. You can send a distance request in Expensify's mobile, desktop, or web app. + + +# How to create and send a distance request + +1. Click the green + button and select Request Money. +2. Select Distance along the top row of the Request Money window. +3. Enter the Start and Finish addresses then click Next. If there are multiple stops, you can add them before clicking Next. +4. Choose who to send the request to by selecting your organization's workspace from the list of recent workspaces. +5. On the confirmation page, confirm the amount, date, distance, and optionally add a description or category. Click the Request button. +6. A workspace admin will receive your request and can reimburse you through Expensify or elsewhere! + + + +# FAQs + +## Is there an easy way to reuse recent locations? + +Yes! We save your recently used locations and list them out on the page where you select the Start and Finish. 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..34a35f5dc7c8 --- /dev/null +++ b/docs/articles/new-expensify/get-paid-back/Referral-Program.md @@ -0,0 +1,53 @@ +--- +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 referral $$$. + +1. There are a bunch of different ways to kick off a referral in New Expensify: + - Start a chat + - Request money + - Send money + - @ mention someone + - 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/articles/new-expensify/send-payments/Coming-Soon.md b/docs/articles/new-expensify/send-payments/Coming-Soon.md deleted file mode 100644 index 6b85bb0364b5..000000000000 --- a/docs/articles/new-expensify/send-payments/Coming-Soon.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Coming Soon -description: Coming Soon ---- diff --git a/docs/articles/new-expensify/workspace-and-domain-settings/Coming-Soon.md b/docs/articles/new-expensify/workspace-and-domain-settings/Coming-Soon.md deleted file mode 100644 index 6b85bb0364b5..000000000000 --- a/docs/articles/new-expensify/workspace-and-domain-settings/Coming-Soon.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Coming Soon -description: Coming Soon ---- diff --git a/docs/assets/images/ExpensifyHelp_EmailError.png b/docs/assets/images/ExpensifyHelp_EmailError.png new file mode 100644 index 000000000000..203f0cfe2b76 Binary files /dev/null and b/docs/assets/images/ExpensifyHelp_EmailError.png differ diff --git a/docs/assets/images/ExpensifyHelp_SMTPError.png b/docs/assets/images/ExpensifyHelp_SMTPError.png new file mode 100644 index 000000000000..b95ea70a9dfa Binary files /dev/null and b/docs/assets/images/ExpensifyHelp_SMTPError.png differ diff --git a/docs/assets/images/ExpensifyHelp_Time.png b/docs/assets/images/ExpensifyHelp_Time.png new file mode 100644 index 000000000000..f4d91b1e8aeb Binary files /dev/null and b/docs/assets/images/ExpensifyHelp_Time.png differ diff --git a/docs/assets/images/ExpensifyHelp_Timezone.png b/docs/assets/images/ExpensifyHelp_Timezone.png new file mode 100644 index 000000000000..d2afc9e693e7 Binary files /dev/null and b/docs/assets/images/ExpensifyHelp_Timezone.png differ diff --git a/docs/assets/images/QBO1.png b/docs/assets/images/QBO1.png new file mode 100644 index 000000000000..4037db44d3bb Binary files /dev/null and b/docs/assets/images/QBO1.png differ diff --git a/docs/assets/images/QBO2-Bill.png b/docs/assets/images/QBO2-Bill.png new file mode 100644 index 000000000000..f75716a85079 Binary files /dev/null and b/docs/assets/images/QBO2-Bill.png differ diff --git a/docs/assets/images/QBO3-Checktoprint.png b/docs/assets/images/QBO3-Checktoprint.png new file mode 100644 index 000000000000..732141a74353 Binary files /dev/null and b/docs/assets/images/QBO3-Checktoprint.png differ diff --git a/docs/assets/images/QBO4-JournalEntry.png b/docs/assets/images/QBO4-JournalEntry.png new file mode 100644 index 000000000000..342d20e49608 Binary files /dev/null and b/docs/assets/images/QBO4-JournalEntry.png differ diff --git a/docs/assets/images/QBO5-Expense.png b/docs/assets/images/QBO5-Expense.png new file mode 100644 index 000000000000..5d71f3955557 Binary files /dev/null and b/docs/assets/images/QBO5-Expense.png differ diff --git a/docs/assets/images/QBO6-Check.png b/docs/assets/images/QBO6-Check.png new file mode 100644 index 000000000000..087720a76875 Binary files /dev/null and b/docs/assets/images/QBO6-Check.png differ diff --git a/docs/assets/images/QBO7-Transactions.png b/docs/assets/images/QBO7-Transactions.png new file mode 100644 index 000000000000..c7752c42870b Binary files /dev/null and b/docs/assets/images/QBO7-Transactions.png differ diff --git a/docs/assets/images/Uber1.png b/docs/assets/images/Uber1.png new file mode 100644 index 000000000000..d5a7d651c6b9 Binary files /dev/null and b/docs/assets/images/Uber1.png differ diff --git a/docs/assets/images/Uber2.png b/docs/assets/images/Uber2.png new file mode 100644 index 000000000000..27ac9925a900 Binary files /dev/null and b/docs/assets/images/Uber2.png differ diff --git a/docs/assets/images/settings-old-dot.svg b/docs/assets/images/settings-old-dot.svg index ca5bc04bd0ff..85561a886459 100644 --- a/docs/assets/images/settings-old-dot.svg +++ b/docs/assets/images/settings-old-dot.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file 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 836abbcb500d..a917b6a4499c 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.4.4 + 1.4.13 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.4.1 + 1.4.13.0 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 1fee9bb4d417..b1e7c2a22e26 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.4.4 + 1.4.13 CFBundleSignature ???? CFBundleVersion - 1.4.4.1 + 1.4.13.0 diff --git a/ios/Podfile.lock b/ios/Podfile.lock index d94e36b0b3c9..390511397b0e 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -256,7 +256,7 @@ PODS: - Onfido (~> 28.3.0) - React - OpenSSL-Universal (1.1.1100) - - Plaid (4.1.0) + - Plaid (4.7.0) - PromisesObjC (2.2.0) - RCT-Folly (2021.07.22.00): - boost @@ -569,7 +569,7 @@ PODS: - react-native-config/App (= 1.4.6) - react-native-config/App (1.4.6): - React-Core - - react-native-document-picker (8.1.1): + - react-native-document-picker (8.2.1): - React-Core - react-native-flipper (0.159.0): - React-Core @@ -585,12 +585,12 @@ PODS: - React-Core - react-native-pager-view (6.2.0): - React-Core - - react-native-pdf (6.7.1): + - react-native-pdf (6.7.3): - React-Core - react-native-performance (5.1.0): - React-Core - - react-native-plaid-link-sdk (10.0.0): - - Plaid (~> 4.1.0) + - react-native-plaid-link-sdk (10.8.0): + - Plaid (~> 4.7.0) - React-Core - react-native-quick-sqlite (8.0.0-beta.2): - React @@ -726,8 +726,6 @@ PODS: - React-Core - RNCPicker (2.4.4): - React-Core - - RNDateTimePicker (3.5.2): - - React-Core - RNDeviceInfo (10.3.0): - React-Core - RNDevMenu (4.1.1): @@ -919,7 +917,6 @@ DEPENDENCIES: - "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)" - "RNCClipboard (from `../node_modules/@react-native-clipboard/clipboard`)" - "RNCPicker (from `../node_modules/@react-native-picker/picker`)" - - "RNDateTimePicker (from `../node_modules/@react-native-community/datetimepicker`)" - RNDeviceInfo (from `../node_modules/react-native-device-info`) - RNDevMenu (from `../node_modules/react-native-dev-menu`) - RNFastImage (from `../node_modules/react-native-fast-image`) @@ -1121,8 +1118,6 @@ EXTERNAL SOURCES: :path: "../node_modules/@react-native-clipboard/clipboard" RNCPicker: :path: "../node_modules/@react-native-picker/picker" - RNDateTimePicker: - :path: "../node_modules/@react-native-community/datetimepicker" RNDeviceInfo: :path: "../node_modules/react-native-device-info" RNDevMenu: @@ -1212,7 +1207,7 @@ SPEC CHECKSUMS: Onfido: c7d010d9793790d44a07799d9be25aa8e3814ee7 onfido-react-native-sdk: b346a620af5669f9fecb6dc3052314a35a94ad9f OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c - Plaid: 7d340abeadb46c7aa1a91f896c5b22395a31fcf2 + Plaid: 431ef9be5314a1345efb451bc5e6b067bfb3b4c6 PromisesObjC: 09985d6d70fbe7878040aa746d78236e6946d2ef RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1 RCTRequired: c0569ecc035894e4a68baecb30fe6a7ea6e399f9 @@ -1233,7 +1228,7 @@ SPEC CHECKSUMS: react-native-blob-util: 99f4d79189252f597fe0d810c57a3733b1b1dea6 react-native-cameraroll: 8ffb0af7a5e5de225fd667610e2979fc1f0c2151 react-native-config: 7cd105e71d903104e8919261480858940a6b9c0e - react-native-document-picker: f68191637788994baed5f57d12994aa32cf8bf88 + react-native-document-picker: 69ca2094d8780cfc1e7e613894d15290fdc54bba react-native-flipper: dc5290261fbeeb2faec1bdc57ae6dd8d562e1de4 react-native-geolocation: 0f7fe8a4c2de477e278b0365cce27d089a8c5903 react-native-image-manipulator: c48f64221cfcd46e9eec53619c4c0374f3328a56 @@ -1241,9 +1236,9 @@ SPEC CHECKSUMS: react-native-key-command: 5af6ee30ff4932f78da6a2109017549042932aa5 react-native-netinfo: ccbe1085dffd16592791d550189772e13bf479e2 react-native-pager-view: 0ccb8bf60e2ebd38b1f3669fa3650ecce81db2df - react-native-pdf: 7c0e91ada997bac8bac3bb5bea5b6b81f5a3caae + react-native-pdf: b4ca3d37a9a86d9165287741c8b2ef4d8940c00e react-native-performance: cef2b618d47b277fb5c3280b81a3aad1e72f2886 - react-native-plaid-link-sdk: 9eb0f71dad94b3bdde649c7a384cba93024af46c + react-native-plaid-link-sdk: df1618a85a615d62ff34e34b76abb7a56497fbc1 react-native-quick-sqlite: bcc7a7a250a40222f18913a97cd356bf82d0a6c4 react-native-render-html: 96c979fe7452a0a41559685d2f83b12b93edac8c react-native-safe-area-context: 99b24a0c5acd0d5dcac2b1a7f18c49ea317be99a @@ -1270,7 +1265,6 @@ SPEC CHECKSUMS: RNCAsyncStorage: 8616bd5a58af409453ea4e1b246521bb76578d60 RNCClipboard: d77213bfa269013bf4b857b7a9ca37ee062d8ef1 RNCPicker: 0b65be85fe7954fbb2062ef079e3d1cde252d888 - RNDateTimePicker: 7658208086d86d09e1627b5c34ba0cf237c60140 RNDeviceInfo: 4701f0bf2a06b34654745053db0ce4cb0c53ada7 RNDevMenu: 72807568fe4188bd4c40ce32675d82434b43c45d RNFastImage: 5c9c9fed9c076e521b3f509fe79e790418a544e8 diff --git a/ios/expensify_chat_adhoc.mobileprovision.gpg b/ios/expensify_chat_adhoc.mobileprovision.gpg index 1dae451f168c..f4691df10d67 100644 Binary files a/ios/expensify_chat_adhoc.mobileprovision.gpg and b/ios/expensify_chat_adhoc.mobileprovision.gpg differ diff --git a/package-lock.json b/package-lock.json index 9e7820f866db..86c0950a0b12 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.4-1", + "version": "1.4.13-0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.4-1", + "version": "1.4.13-0", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -26,7 +26,6 @@ "@react-native-async-storage/async-storage": "^1.17.10", "@react-native-camera-roll/camera-roll": "5.4.0", "@react-native-clipboard/clipboard": "^1.12.1", - "@react-native-community/datetimepicker": "^3.5.2", "@react-native-community/geolocation": "^3.0.6", "@react-native-community/netinfo": "^9.3.10", "@react-native-firebase/analytics": "^12.3.0", @@ -36,7 +35,7 @@ "@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", @@ -51,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", @@ -78,7 +77,7 @@ "react-native-config": "^1.4.5", "react-native-dev-menu": "^4.1.1", "react-native-device-info": "^10.3.0", - "react-native-document-picker": "^8.0.0", + "react-native-document-picker": "^8.2.1", "react-native-draggable-flatlist": "^4.0.1", "react-native-fast-image": "^8.6.3", "react-native-fs": "^2.20.0", @@ -94,11 +93,11 @@ "react-native-modal": "^13.0.0", "react-native-onyx": "1.0.118", "react-native-pager-view": "^6.2.0", - "react-native-pdf": "^6.7.1", + "react-native-pdf": "^6.7.3", "react-native-performance": "^5.1.0", "react-native-permissions": "^3.9.3", - "react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#eae05855286dc699954415cc1d629bfd8e8e47e2", - "react-native-plaid-link-sdk": "^10.0.0", + "react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#0d15d4618f58e99c1261921111e68ee85bb3c2a8", + "react-native-plaid-link-sdk": "10.8.0", "react-native-qrcode-svg": "^6.2.0", "react-native-quick-sqlite": "^8.0.0-beta.2", "react-native-reanimated": "3.5.4", @@ -8292,15 +8291,6 @@ "node": ">= 4.0.0" } }, - "node_modules/@react-native-community/datetimepicker": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@react-native-community/datetimepicker/-/datetimepicker-3.5.2.tgz", - "integrity": "sha512-TWRuAtr/DnrEcRewqvXMLea2oB+YF+SbtuYLHguALLxNJQLl/RFB7aTNZeF+OoH75zKFqtXECXV1/uxQUpA+sg==", - "license": "MIT", - "dependencies": { - "invariant": "^2.2.4" - } - }, "node_modules/@react-native-community/eslint-config": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@react-native-community/eslint-config/-/eslint-config-3.0.0.tgz", @@ -8573,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": "*" @@ -8618,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" @@ -8633,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" } @@ -25886,10 +25876,9 @@ } }, "node_modules/crypto-js": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.3.0.tgz", - "integrity": "sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q==", - "license": "MIT" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" }, "node_modules/css-box-model": { "version": "1.2.1", @@ -29905,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", @@ -44225,8 +44214,9 @@ } }, "node_modules/react-native-document-picker": { - "version": "8.1.1", - "license": "MIT", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/react-native-document-picker/-/react-native-document-picker-8.2.1.tgz", + "integrity": "sha512-luH2hKdq4cUwE651OscyGderLMsCusOsBzw4MBca91CgprlAGVMm1/pDwJDV5t9LIewVK8DIgXGXzgrsusKVhA==", "dependencies": { "invariant": "^2.2.4" }, @@ -44476,11 +44466,11 @@ } }, "node_modules/react-native-pdf": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/react-native-pdf/-/react-native-pdf-6.7.1.tgz", - "integrity": "sha512-zszQygtNBYoUfEtP/fV7zhzGeohDlUksh2p3OzshLrxdY9mw7Tm5VXAxYq4d8HsomRJUbFlJ7rHaTU9AQL800g==", + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/react-native-pdf/-/react-native-pdf-6.7.3.tgz", + "integrity": "sha512-bK1fVkj18kBA5YlRFNJ3/vJ1bEX3FDHyAPY6ArtIdVs+vv0HzcK5WH9LSd2bxUsEMIyY9CSjP4j8BcxNXTiQkQ==", "dependencies": { - "crypto-js": "^3.2.0", + "crypto-js": "4.2.0", "deprecated-react-native-prop-types": "^2.3.0" }, "peerDependencies": { @@ -44524,9 +44514,9 @@ } }, "node_modules/react-native-picker-select": { - "version": "8.0.4", - "resolved": "git+ssh://git@github.com/Expensify/react-native-picker-select.git#eae05855286dc699954415cc1d629bfd8e8e47e2", - "integrity": "sha512-3U/mtHN/pKC5yXtJnqj5rre8+4YPSqoXCn/3qKjb5u8BMIiuc5H3KJ0ZbKlZEg/8Uh4j0cvrtcNasdPgMqRgCQ==", + "version": "8.1.0", + "resolved": "git+ssh://git@github.com/Expensify/react-native-picker-select.git#0d15d4618f58e99c1261921111e68ee85bb3c2a8", + "integrity": "sha512-ly0ZCt3K4RX7t9lfSb2OSGAw0cv8UqdMoxNfh5j+KujYYq+N8VsI9O/lmqquNeX/AMp5hM3fjetEWue4nZw/hA==", "license": "MIT", "dependencies": { "lodash.isequal": "^4.5.0" @@ -44536,8 +44526,12 @@ } }, "node_modules/react-native-plaid-link-sdk": { - "version": "10.0.0", - "license": "MIT", + "version": "10.8.0", + "resolved": "https://registry.npmjs.org/react-native-plaid-link-sdk/-/react-native-plaid-link-sdk-10.8.0.tgz", + "integrity": "sha512-rhyI19SZdwKCsHtkJ0ZOCD/r0vNLS1vqUAS3HPPa97IIN6nS2ln9krLA7lFfMKtWxY5Z5d73SqTmqhd1qqdNuA==", + "dependencies": { + "react-native-plaid-link-sdk": "^10.4.0" + }, "peerDependencies": { "react": "*", "react-native": ">=0.66.0" @@ -50729,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" } @@ -58708,14 +58702,6 @@ "joi": "^17.2.1" } }, - "@react-native-community/datetimepicker": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@react-native-community/datetimepicker/-/datetimepicker-3.5.2.tgz", - "integrity": "sha512-TWRuAtr/DnrEcRewqvXMLea2oB+YF+SbtuYLHguALLxNJQLl/RFB7aTNZeF+OoH75zKFqtXECXV1/uxQUpA+sg==", - "requires": { - "invariant": "^2.2.4" - } - }, "@react-native-community/eslint-config": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@react-native-community/eslint-config/-/eslint-config-3.0.0.tgz", @@ -58898,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": { @@ -58929,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" } @@ -71516,9 +71502,9 @@ } }, "crypto-js": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.3.0.tgz", - "integrity": "sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" }, "css-box-model": { "version": "1.2.1", @@ -74417,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", @@ -84700,7 +84686,9 @@ "requires": {} }, "react-native-document-picker": { - "version": "8.1.1", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/react-native-document-picker/-/react-native-document-picker-8.2.1.tgz", + "integrity": "sha512-luH2hKdq4cUwE651OscyGderLMsCusOsBzw4MBca91CgprlAGVMm1/pDwJDV5t9LIewVK8DIgXGXzgrsusKVhA==", "requires": { "invariant": "^2.2.4" } @@ -84838,11 +84826,11 @@ "requires": {} }, "react-native-pdf": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/react-native-pdf/-/react-native-pdf-6.7.1.tgz", - "integrity": "sha512-zszQygtNBYoUfEtP/fV7zhzGeohDlUksh2p3OzshLrxdY9mw7Tm5VXAxYq4d8HsomRJUbFlJ7rHaTU9AQL800g==", + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/react-native-pdf/-/react-native-pdf-6.7.3.tgz", + "integrity": "sha512-bK1fVkj18kBA5YlRFNJ3/vJ1bEX3FDHyAPY6ArtIdVs+vv0HzcK5WH9LSd2bxUsEMIyY9CSjP4j8BcxNXTiQkQ==", "requires": { - "crypto-js": "^3.2.0", + "crypto-js": "4.2.0", "deprecated-react-native-prop-types": "^2.3.0" } }, @@ -84866,16 +84854,20 @@ "requires": {} }, "react-native-picker-select": { - "version": "git+ssh://git@github.com/Expensify/react-native-picker-select.git#eae05855286dc699954415cc1d629bfd8e8e47e2", - "integrity": "sha512-3U/mtHN/pKC5yXtJnqj5rre8+4YPSqoXCn/3qKjb5u8BMIiuc5H3KJ0ZbKlZEg/8Uh4j0cvrtcNasdPgMqRgCQ==", - "from": "react-native-picker-select@git+https://github.com/Expensify/react-native-picker-select.git#eae05855286dc699954415cc1d629bfd8e8e47e2", + "version": "git+ssh://git@github.com/Expensify/react-native-picker-select.git#0d15d4618f58e99c1261921111e68ee85bb3c2a8", + "integrity": "sha512-ly0ZCt3K4RX7t9lfSb2OSGAw0cv8UqdMoxNfh5j+KujYYq+N8VsI9O/lmqquNeX/AMp5hM3fjetEWue4nZw/hA==", + "from": "react-native-picker-select@git+https://github.com/Expensify/react-native-picker-select.git#0d15d4618f58e99c1261921111e68ee85bb3c2a8", "requires": { "lodash.isequal": "^4.5.0" } }, "react-native-plaid-link-sdk": { - "version": "10.0.0", - "requires": {} + "version": "10.8.0", + "resolved": "https://registry.npmjs.org/react-native-plaid-link-sdk/-/react-native-plaid-link-sdk-10.8.0.tgz", + "integrity": "sha512-rhyI19SZdwKCsHtkJ0ZOCD/r0vNLS1vqUAS3HPPa97IIN6nS2ln9krLA7lFfMKtWxY5Z5d73SqTmqhd1qqdNuA==", + "requires": { + "react-native-plaid-link-sdk": "^10.4.0" + } }, "react-native-qrcode-svg": { "version": "6.2.0", @@ -89248,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 c1fbee4e8243..a3dd3776d125 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.4-1", + "version": "1.4.13-0", "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.", @@ -51,6 +51,7 @@ "symbolicate:android": "npx metro-symbolicate android/app/build/generated/sourcemaps/react/release/index.android.bundle.map", "symbolicate:ios": "npx metro-symbolicate main.jsbundle.map", "test:e2e": "node tests/e2e/testRunner.js --development --skipCheckout --skipInstallDeps --buildMode none", + "test:e2e:dev": "node tests/e2e/testRunner.js --development --skipCheckout --config ./config.dev.js --buildMode skip --skipInstallDeps", "gh-actions-unused-styles": "./.github/scripts/findUnusedKeys.sh", "workflow-test": "./workflow_tests/scripts/runWorkflowTests.sh", "workflow-test:generate": "node workflow_tests/utils/preGenerateTest.js", @@ -73,7 +74,6 @@ "@react-native-async-storage/async-storage": "^1.17.10", "@react-native-camera-roll/camera-roll": "5.4.0", "@react-native-clipboard/clipboard": "^1.12.1", - "@react-native-community/datetimepicker": "^3.5.2", "@react-native-community/geolocation": "^3.0.6", "@react-native-community/netinfo": "^9.3.10", "@react-native-firebase/analytics": "^12.3.0", @@ -83,7 +83,7 @@ "@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", @@ -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", @@ -125,7 +125,7 @@ "react-native-config": "^1.4.5", "react-native-dev-menu": "^4.1.1", "react-native-device-info": "^10.3.0", - "react-native-document-picker": "^8.0.0", + "react-native-document-picker": "^8.2.1", "react-native-draggable-flatlist": "^4.0.1", "react-native-fast-image": "^8.6.3", "react-native-fs": "^2.20.0", @@ -141,11 +141,11 @@ "react-native-modal": "^13.0.0", "react-native-onyx": "1.0.118", "react-native-pager-view": "^6.2.0", - "react-native-pdf": "^6.7.1", + "react-native-pdf": "^6.7.3", "react-native-performance": "^5.1.0", "react-native-permissions": "^3.9.3", - "react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#eae05855286dc699954415cc1d629bfd8e8e47e2", - "react-native-plaid-link-sdk": "^10.0.0", + "react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#0d15d4618f58e99c1261921111e68ee85bb3c2a8", + "react-native-plaid-link-sdk": "10.8.0", "react-native-qrcode-svg": "^6.2.0", "react-native-quick-sqlite": "^8.0.0-beta.2", "react-native-reanimated": "3.5.4", 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/scripts/build-desktop.sh b/scripts/build-desktop.sh index 88ab17e7a2bd..025559dc4671 100755 --- a/scripts/build-desktop.sh +++ b/scripts/build-desktop.sh @@ -25,4 +25,4 @@ npx webpack --config config/webpack/webpack.desktop.js --env envFile=$ENV_FILE title "Building Desktop App Archive Using Electron" info "" shift 1 -npx electron-builder --config config/electronBuilder.config.js "$@" +npx electron-builder --config config/electronBuilder.config.js --publish always "$@" diff --git a/scripts/start-android.sh b/scripts/start-android.sh old mode 100644 new mode 100755 diff --git a/src/App.js b/src/App.js index ac34ece5c6c7..3553900bbc7f 100644 --- a/src/App.js +++ b/src/App.js @@ -6,14 +6,19 @@ import Onyx from 'react-native-onyx'; import {PickerStateProvider} from 'react-native-picker-select'; 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 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'; @@ -24,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()) { @@ -51,6 +53,9 @@ function App() { - + - + + + diff --git a/src/CONST.ts b/src/CONST.ts index 9b284752d074..c0e3d64b5eee 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', @@ -483,6 +487,7 @@ const CONST = { MAX_REPORT_PREVIEW_RECEIPTS: 3, }, REPORT: { + MAX_COUNT_BEFORE_FOCUS_UPDATE: 30, MAXIMUM_PARTICIPANTS: 8, SPLIT_REPORTID: '-2', ACTIONS: { @@ -497,6 +502,7 @@ const CONST = { MODIFIEDEXPENSE: 'MODIFIEDEXPENSE', MOVED: 'MOVED', REIMBURSEMENTQUEUED: 'REIMBURSEMENTQUEUED', + REIMBURSEMENTDEQUEUED: 'REIMBURSEMENTDEQUEUED', RENAMED: 'RENAMED', REPORTPREVIEW: 'REPORTPREVIEW', SUBMITTED: 'SUBMITTED', @@ -506,6 +512,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', @@ -515,6 +522,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', @@ -536,6 +544,7 @@ const CONST = { 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', @@ -556,6 +565,8 @@ const CONST = { UPDATE_REIMBURSEMENT_CHOICE: 'POLICYCHANGELOG_UPDATE_REIMBURSEMENT_CHOICE', UPDATE_REPORT_FIELD: 'POLICYCHANGELOG_UPDATE_REPORT_FIELD', UPDATE_TAG: 'POLICYCHANGELOG_UPDATE_TAG', + UPDATE_TAG_ENABLED: 'POLICYCHANGELOG_UPDATE_TAG_ENABLED', + UPDATE_TAG_LIST: 'POLICYCHANGELOG_UPDATE_TAG_LIST', UPDATE_TAG_LIST_NAME: 'POLICYCHANGELOG_UPDATE_TAG_LIST_NAME', UPDATE_TAG_NAME: 'POLICYCHANGELOG_UPDATE_TAG_NAME', UPDATE_TIME_ENABLED: 'POLICYCHANGELOG_UPDATE_TIME_ENABLED', @@ -599,6 +610,7 @@ const CONST = { ADMINS: '#admins', }, STATE: { + OPEN: 'OPEN', SUBMITTED: 'SUBMITTED', PROCESSING: 'PROCESSING', }, @@ -640,6 +652,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, @@ -692,11 +707,20 @@ const CONST = { DEFAULT: 'default', }, THEME: { - DEFAULT: 'dark', - LIGHT: 'light', + DEFAULT: 'system', + FALLBACK: 'dark', DARK: 'dark', + LIGHT: 'light', SYSTEM: 'system', }, + COLOR_SCHEME: { + LIGHT: 'light', + DARK: 'dark', + }, + STATUS_BAR_STYLE: { + LIGHT_CONTENT: 'light-content', + DARK_CONTENT: 'dark-content', + }, TRANSACTION: { DEFAULT_MERCHANT: 'Request', UNKNOWN_MERCHANT: 'Unknown Merchant', @@ -952,6 +976,7 @@ const CONST = { GUIDES_DOMAIN: 'team.expensify.com', HELP: 'help@expensify.com', INTEGRATION_TESTING_CREDS: 'integrationtestingcreds@expensify.com', + NOTIFICATIONS: 'notifications@expensify.com', PAYROLL: 'payroll@expensify.com', QA: 'qa@expensify.com', QA_TRAVIS: 'qa+travisreceipts@expensify.com', @@ -971,6 +996,7 @@ const CONST = { FIRST_RESPONDER: Number(Config?.EXPENSIFY_ACCOUNT_ID_FIRST_RESPONDER ?? 9375152), HELP: Number(Config?.EXPENSIFY_ACCOUNT_ID_HELP ?? -1), INTEGRATION_TESTING_CREDS: Number(Config?.EXPENSIFY_ACCOUNT_ID_INTEGRATION_TESTING_CREDS ?? -1), + NOTIFICATIONS: Number(Config?.EXPENSIFY_ACCOUNT_ID_NOTIFICATIONS ?? 11665625), PAYROLL: Number(Config?.EXPENSIFY_ACCOUNT_ID_PAYROLL ?? 9679724), QA: Number(Config?.EXPENSIFY_ACCOUNT_ID_QA ?? 3126513), QA_TRAVIS: Number(Config?.EXPENSIFY_ACCOUNT_ID_QA_TRAVIS ?? 8595733), @@ -1075,11 +1101,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', }, }, @@ -1120,6 +1141,8 @@ const CONST = { }, IOU: { + // This is the transactionID used when going through the create money request flow so that it mimics a real transaction (like the edit flow) + OPTIMISTIC_TRANSACTION_ID: '1', // Note: These payment types are used when building IOU reportAction message values in the server and should // not be changed. PAYMENT_TYPE: { @@ -1132,6 +1155,11 @@ const CONST = { SPLIT: 'split', REQUEST: 'request', }, + REQUEST_TYPE: { + DISTANCE: 'distance', + MANUAL: 'manual', + SCAN: 'scan', + }, REPORT_ACTION_TYPE: { PAY: 'pay', CREATE: 'create', @@ -1139,6 +1167,7 @@ const CONST = { DECLINE: 'decline', CANCEL: 'cancel', DELETE: 'delete', + APPROVE: 'approve', }, AMOUNT_MAX_LENGTH: 10, RECEIPT_STATE: { @@ -1155,6 +1184,9 @@ const CONST = { SVG: 'svg', }, RECEIPT_ERROR: 'receiptError', + CANCEL_REASON: { + PAYMENT_EXPIRED: 'CANCEL_REASON_PAYMENT_EXPIRED', + }, }, GROWL: { @@ -1358,7 +1390,6 @@ const CONST = { TIME_STARTS_01: /^01:\d{2} [AP]M$/, TIME_FORMAT: /^\d{2}:\d{2} [AP]M$/, DATE_TIME_FORMAT: /^\d{2}-\d{2} \d{2}:\d{2} [AP]M$/, - ATTACHMENT_ROUTE: /\/r\/(\d*)\/attachment/, ILLEGAL_FILENAME_CHARACTERS: /\/|<|>|\*|"|:|\?|\\|\|/g, ENCODE_PERCENT_CHARACTER: /%(25)+/g, @@ -1395,6 +1426,7 @@ const CONST = { this.EMAIL.FIRST_RESPONDER, this.EMAIL.HELP, this.EMAIL.INTEGRATION_TESTING_CREDS, + this.EMAIL.NOTIFICATIONS, this.EMAIL.PAYROLL, this.EMAIL.QA, this.EMAIL.QA_TRAVIS, @@ -2696,17 +2728,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', @@ -2737,6 +2877,9 @@ const CONST = { NEW_CHAT: 'chat', NEW_ROOM: 'room', RECEIPT_TAB_ID: 'ReceiptTab', + IOU_REQUEST_TYPE: 'iouRequestType', + }, + TAB_REQUEST: { MANUAL: 'manual', SCAN: 'scan', DISTANCE: 'distance', @@ -2761,6 +2904,10 @@ const CONST = { NAVIGATE: 'NAVIGATE', }, }, + TIME_PERIOD: { + AM: 'AM', + PM: 'PM', + }, INDENTS: ' ', PARENT_CHILD_SEPARATOR: ': ', CATEGORY_LIST_THRESHOLD: 8, @@ -2770,7 +2917,7 @@ const CONST = { SBE: 'SbeDemoSetup', MONEY2020: 'Money2020DemoSetup', }, - + COLON: ':', MAPBOX: { PADDING: 50, DEFAULT_ZOOM: 10, @@ -2790,7 +2937,7 @@ const CONST = { HORIZONTAL_SPACER: { DEFAULT_BORDER_BOTTOM_WIDTH: 1, DEFAULT_MARGIN_VERTICAL: 8, - HIDDEN_MARGIN_VERTICAL: 0, + HIDDEN_MARGIN_VERTICAL: 4, HIDDEN_BORDER_BOTTOM_WIDTH: 0, }, @@ -2816,7 +2963,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', }, @@ -2843,6 +2990,33 @@ const CONST = { RUNS: 20, }, + /** + * Bank account names + */ + BANK_NAMES: { + EXPENSIFY: 'expensify', + AMERICAN_EXPRESS: 'americanexpress', + BANK_OF_AMERICA: 'bank of america', + BB_T: 'bbt', + CAPITAL_ONE: 'capital one', + CHASE: 'chase', + CHARLES_SCHWAB: 'charles schwab', + CITIBANK: 'citibank', + CITIZENS_BANK: 'citizens bank', + DISCOVER: 'discover', + FIDELITY: 'fidelity', + GENERIC_BANK: 'generic bank', + HUNTINGTON_BANK: 'huntington bank', + HUNTINGTON_NATIONAL: 'huntington national', + NAVY_FEDERAL_CREDIT_UNION: 'navy federal credit union', + PNC: 'pnc', + REGIONS_BANK: 'regions bank', + SUNTRUST: 'suntrust', + TD_BANK: 'td bank', + US_BANK: 'us bank', + USAA: 'usaa', + }, + /** * Constants for maxToRenderPerBatch parameter that is used for FlatList or SectionList. This controls the amount of items rendered per batch, which is the next chunk of items rendered on every scroll. */ diff --git a/src/Expensify.js b/src/Expensify.js index 1b692f86a197..756df5b79b88 100644 --- a/src/Expensify.js +++ b/src/Expensify.js @@ -7,6 +7,7 @@ import _ from 'underscore'; import ConfirmModal from './components/ConfirmModal'; import DeeplinkWrapper from './components/DeeplinkWrapper'; import EmojiPicker from './components/EmojiPicker/EmojiPicker'; +import FocusModeNotification from './components/FocusModeNotification'; import GrowlNotification from './components/GrowlNotification'; import AppleAuthWrapper from './components/SignInButtons/AppleAuthWrapper'; import SplashScreenHider from './components/SplashScreenHider'; @@ -76,6 +77,9 @@ const propTypes = { /** Whether the app is waiting for the server's response to determine if a room is public */ isCheckingPublicRoom: PropTypes.bool, + /** Whether we should display the notification alerting the user that focus mode has been auto-enabled */ + focusModeNotification: PropTypes.bool, + ...withLocalizePropTypes, }; @@ -88,6 +92,7 @@ const defaultProps = { isSidebarLoaded: false, screenShareRequest: null, isCheckingPublicRoom: true, + focusModeNotification: false, }; const SplashScreenHiddenContext = React.createContext({}); @@ -107,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( () => ({ @@ -202,7 +208,10 @@ function Expensify(props) { } return ( - + {shouldInit && ( <> @@ -221,6 +230,7 @@ function Expensify(props) { isVisible /> ) : null} + {props.focusModeNotification ? : null} )} @@ -261,6 +271,10 @@ export default compose( screenShareRequest: { key: ONYXKEYS.SCREEN_SHARE_REQUEST, }, + focusModeNotification: { + key: ONYXKEYS.FOCUS_MODE_NOTIFICATION, + initWithStoredValues: false, + }, }), )(Expensify); diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 5576eb64736d..0cc7934ad007 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -152,6 +152,12 @@ const ONYXKEYS = { /** The user's cash card and imported cards (including the Expensify Card) */ CARD_LIST: 'cardList', + /** Whether the user has tried focus mode yet */ + NVP_TRY_FOCUS_MODE: 'tryFocusMode', + + /** Boolean flag used to display the focus mode notification */ + FOCUS_MODE_NOTIFICATION: 'focusModeNotification', + /** Stores information about the user's saved statements */ WALLET_STATEMENT: 'walletStatement', @@ -265,6 +271,7 @@ const ONYXKEYS = { REPORT_USER_IS_LEAVING_ROOM: 'reportUserIsLeavingRoom_', SECURITY_GROUP: 'securityGroup_', TRANSACTION: 'transactions_', + TRANSACTION_VIOLATIONS: 'transactionViolations_', // Holds temporary transactions used during the creation and edit flow TRANSACTION_DRAFT: 'transactionsDraft_', @@ -327,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', @@ -376,12 +383,14 @@ 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; [ONYXKEYS.NVP_BLOCKED_FROM_CONCIERGE]: OnyxTypes.BlockedFromConcierge; [ONYXKEYS.NVP_PRIVATE_PUSH_NOTIFICATION_ID]: string; + [ONYXKEYS.NVP_TRY_FOCUS_MODE]: boolean; + [ONYXKEYS.FOCUS_MODE_NOTIFICATION]: boolean; [ONYXKEYS.NVP_LAST_PAYMENT_METHOD]: Record; [ONYXKEYS.NVP_RECENT_WAYPOINTS]: OnyxTypes.RecentWaypoint[]; [ONYXKEYS.PUSH_NOTIFICATIONS_ENABLED]: boolean; @@ -389,13 +398,13 @@ type OnyxValues = { [ONYXKEYS.IS_PLAID_DISABLED]: boolean; [ONYXKEYS.PLAID_LINK_TOKEN]: string; [ONYXKEYS.ONFIDO_TOKEN]: string; - [ONYXKEYS.NVP_PREFERRED_LOCALE]: ValueOf; + [ONYXKEYS.NVP_PREFERRED_LOCALE]: OnyxTypes.Locale; [ONYXKEYS.USER_WALLET]: OnyxTypes.UserWallet; [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; @@ -407,6 +416,7 @@ type OnyxValues = { [ONYXKEYS.IS_LOADING_PAYMENT_METHODS]: boolean; [ONYXKEYS.IS_LOADING_REPORT_DATA]: boolean; [ONYXKEYS.IS_TEST_TOOLS_MODAL_OPEN]: boolean; + [ONYXKEYS.IS_LOADING_APP]: boolean; [ONYXKEYS.WALLET_TRANSFER]: OnyxTypes.WalletTransfer; [ONYXKEYS.LAST_ACCESSED_WORKSPACE_POLICY_ID]: string; [ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT]: boolean; @@ -419,6 +429,7 @@ type OnyxValues = { [ONYXKEYS.MAPBOX_ACCESS_TOKEN]: OnyxTypes.MapboxAccessToken; [ONYXKEYS.ONYX_UPDATES_FROM_SERVER]: OnyxTypes.OnyxUpdatesFromServer; [ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT]: number; + [ONYXKEYS.DEMO_INFO]: OnyxTypes.DemoInfo; [ONYXKEYS.MAX_CANVAS_AREA]: number; [ONYXKEYS.MAX_CANVAS_HEIGHT]: number; [ONYXKEYS.MAX_CANVAS_WIDTH]: number; @@ -429,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; @@ -442,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; @@ -496,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 26589a3db0e0..ca1fe9f0e81a 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1,30 +1,29 @@ -import {ValueOf} from 'type-fest'; +import {IsEqual, ValueOf} from 'type-fest'; import CONST from './CONST'; -/** - * This is a file containing constants for all the routes we want to be able to go to - */ +// This is a file containing constants for all the routes we want to be able to go to /** * Builds a URL with an encoded URI component for the `backTo` param which can be added to the end of URLs */ -function getUrlWithBackToParam(url: string, backTo?: string): string { - const backToParam = backTo ? `${url.includes('?') ? '&' : '?'}backTo=${encodeURIComponent(backTo)}` : ''; - return url + backToParam; +function getUrlWithBackToParam(url: TUrl, backTo?: string): `${TUrl}` | `${TUrl}?backTo=${string}` | `${TUrl}&backTo=${string}` { + const backToParam = backTo ? (`${url.includes('?') ? '&' : '?'}backTo=${encodeURIComponent(backTo)}` as const) : ''; + return `${url}${backToParam}` as const; } -export default { +const ROUTES = { HOME: '', - /** This is a utility route used to go to the user's concierge chat, or the sign-in page if the user's not authenticated */ + + // This is a utility route used to go to the user's concierge chat, or the sign-in page if the user's not authenticated CONCIERGE: 'concierge', FLAG_COMMENT: { route: 'flag/:reportID/:reportActionID', - getRoute: (reportID: string, reportActionID: string) => `flag/${reportID}/${reportActionID}`, + getRoute: (reportID: string, reportActionID: string) => `flag/${reportID}/${reportActionID}` as const, }, SEARCH: 'search', DETAILS: { route: 'details', - getRoute: (login: string) => `details?login=${encodeURIComponent(login)}`, + getRoute: (login: string) => `details?login=${encodeURIComponent(login)}` as const, }, PROFILE: { route: 'a/:accountID', @@ -35,7 +34,7 @@ export default { VALIDATE_LOGIN: 'v/:accountID/:validateCode', GET_ASSISTANCE: { route: 'get-assistance/:taskID', - getRoute: (taskID: string) => `get-assistance/${taskID}`, + getRoute: (taskID: string, backTo: string) => getUrlWithBackToParam(`get-assistance/${taskID}`, backTo), }, UNLINK_LOGIN: 'u/:accountID/:validateCode', APPLE_SIGN_IN: 'sign-in-with-apple', @@ -54,7 +53,7 @@ export default { BANK_ACCOUNT_PERSONAL: 'bank-account/personal', BANK_ACCOUNT_WITH_STEP_TO_OPEN: { route: 'bank-account/:stepToOpen?', - getRoute: (stepToOpen = '', policyID = '', backTo?: string): string => getUrlWithBackToParam(`bank-account/${stepToOpen}?policyID=${policyID}`, backTo), + getRoute: (stepToOpen = '', policyID = '', backTo?: string) => getUrlWithBackToParam(`bank-account/${stepToOpen}?policyID=${policyID}`, backTo), }, SETTINGS: 'settings', @@ -77,44 +76,44 @@ export default { SETTINGS_WALLET: 'settings/wallet', SETTINGS_WALLET_DOMAINCARD: { route: '/settings/wallet/card/:domain', - getRoute: (domain: string) => `/settings/wallet/card/${domain}`, + getRoute: (domain: string) => `/settings/wallet/card/${domain}` as const, }, SETTINGS_REPORT_FRAUD: { route: '/settings/wallet/card/:domain/report-virtual-fraud', - getRoute: (domain: string) => `/settings/wallet/card/${domain}/report-virtual-fraud`, + getRoute: (domain: string) => `/settings/wallet/card/${domain}/report-virtual-fraud` as const, }, SETTINGS_WALLET_CARD_GET_PHYSICAL_NAME: { route: '/settings/wallet/card/:domain/get-physical/name', - getRoute: (domain: string) => `/settings/wallet/card/${domain}/get-physical/name`, + getRoute: (domain: string) => `/settings/wallet/card/${domain}/get-physical/name` as const, }, SETTINGS_WALLET_CARD_GET_PHYSICAL_PHONE: { route: '/settings/wallet/card/:domain/get-physical/phone', - getRoute: (domain: string) => `/settings/wallet/card/${domain}/get-physical/phone`, + getRoute: (domain: string) => `/settings/wallet/card/${domain}/get-physical/phone` as const, }, SETTINGS_WALLET_CARD_GET_PHYSICAL_ADDRESS: { route: '/settings/wallet/card/:domain/get-physical/address', - getRoute: (domain: string) => `/settings/wallet/card/${domain}/get-physical/address`, + getRoute: (domain: string) => `/settings/wallet/card/${domain}/get-physical/address` as const, }, SETTINGS_WALLET_CARD_GET_PHYSICAL_CONFIRM: { route: '/settings/wallet/card/:domain/get-physical/confirm', - getRoute: (domain: string) => `/settings/wallet/card/${domain}/get-physical/confirm`, + getRoute: (domain: string) => `/settings/wallet/card/${domain}/get-physical/confirm` as const, }, SETTINGS_ADD_DEBIT_CARD: 'settings/wallet/add-debit-card', SETTINGS_ADD_BANK_ACCOUNT: 'settings/wallet/add-bank-account', SETTINGS_ENABLE_PAYMENTS: 'settings/wallet/enable-payments', SETTINGS_WALLET_CARD_DIGITAL_DETAILS_UPDATE_ADDRESS: { route: 'settings/wallet/card/:domain/digital-details/update-address', - getRoute: (domain: string) => `settings/wallet/card/${domain}/digital-details/update-address`, + getRoute: (domain: string) => `settings/wallet/card/${domain}/digital-details/update-address` as const, }, SETTINGS_WALLET_TRANSFER_BALANCE: 'settings/wallet/transfer-balance', SETTINGS_WALLET_CHOOSE_TRANSFER_ACCOUNT: 'settings/wallet/choose-transfer-account', SETTINGS_WALLET_REPORT_CARD_LOST_OR_DAMAGED: { route: '/settings/wallet/card/:domain/report-card-lost-or-damaged', - getRoute: (domain: string) => `/settings/wallet/card/${domain}/report-card-lost-or-damaged`, + getRoute: (domain: string) => `/settings/wallet/card/${domain}/report-card-lost-or-damaged` as const, }, SETTINGS_WALLET_CARD_ACTIVATE: { route: 'settings/wallet/card/:domain/activate', - getRoute: (domain: string) => `settings/wallet/card/${domain}/activate`, + getRoute: (domain: string) => `settings/wallet/card/${domain}/activate` as const, }, SETTINGS_PERSONAL_DETAILS: 'settings/profile/personal-details', SETTINGS_PERSONAL_DETAILS_LEGAL_NAME: 'settings/profile/personal-details/legal-name', @@ -130,12 +129,20 @@ export default { }, SETTINGS_CONTACT_METHOD_DETAILS: { route: 'settings/profile/contact-methods/:contactMethod/details', - getRoute: (contactMethod: string) => `settings/profile/contact-methods/${encodeURIComponent(contactMethod)}/details`, + getRoute: (contactMethod: string) => `settings/profile/contact-methods/${encodeURIComponent(contactMethod)}/details` as const, + }, + SETTINGS_NEW_CONTACT_METHOD: { + route: 'settings/profile/contact-methods/new', + getRoute: (backTo?: string) => getUrlWithBackToParam('settings/profile/contact-methods/new', backTo), + }, + SETTINGS_2FA: { + route: 'settings/security/two-factor-auth', + getRoute: (backTo?: string) => getUrlWithBackToParam('settings/security/two-factor-auth', backTo), }, - SETTINGS_NEW_CONTACT_METHOD: 'settings/profile/contact-methods/new', - SETTINGS_2FA: 'settings/security/two-factor-auth', 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', @@ -146,161 +153,238 @@ export default { REPORT: 'r', REPORT_WITH_ID: { route: 'r/:reportID?/:reportActionID?', - getRoute: (reportID: string) => `r/${reportID}`, + getRoute: (reportID: string) => `r/${reportID}` as const, }, EDIT_REQUEST: { route: 'r/:threadReportID/edit/:field', - getRoute: (threadReportID: string, field: ValueOf) => `r/${threadReportID}/edit/${field}`, + getRoute: (threadReportID: string, field: ValueOf) => `r/${threadReportID}/edit/${field}` as const, }, EDIT_CURRENCY_REQUEST: { route: 'r/:threadReportID/edit/currency', - getRoute: (threadReportID: string, currency: string, backTo: string) => `r/${threadReportID}/edit/currency?currency=${currency}&backTo=${backTo}`, + getRoute: (threadReportID: string, currency: string, backTo: string) => `r/${threadReportID}/edit/currency?currency=${currency}&backTo=${backTo}` as const, }, REPORT_WITH_ID_DETAILS_SHARE_CODE: { route: 'r/:reportID/details/shareCode', - getRoute: (reportID: string) => `r/${reportID}/details/shareCode`, + getRoute: (reportID: string) => `r/${reportID}/details/shareCode` as const, }, REPORT_ATTACHMENTS: { route: 'r/:reportID/attachment', - getRoute: (reportID: string, source: string) => `r/${reportID}/attachment?source=${encodeURI(source)}`, + getRoute: (reportID: string, source: string) => `r/${reportID}/attachment?source=${encodeURI(source)}` as const, }, REPORT_PARTICIPANTS: { route: 'r/:reportID/participants', - getRoute: (reportID: string) => `r/${reportID}/participants`, + getRoute: (reportID: string) => `r/${reportID}/participants` as const, }, REPORT_WITH_ID_DETAILS: { route: 'r/:reportID/details', - getRoute: (reportID: string) => `r/${reportID}/details`, + getRoute: (reportID: string) => `r/${reportID}/details` as const, }, REPORT_SETTINGS: { route: 'r/:reportID/settings', - getRoute: (reportID: string) => `r/${reportID}/settings`, + getRoute: (reportID: string) => `r/${reportID}/settings` as const, }, REPORT_SETTINGS_ROOM_NAME: { route: 'r/:reportID/settings/room-name', - getRoute: (reportID: string) => `r/${reportID}/settings/room-name`, + getRoute: (reportID: string) => `r/${reportID}/settings/room-name` as const, }, REPORT_SETTINGS_NOTIFICATION_PREFERENCES: { route: 'r/:reportID/settings/notification-preferences', - getRoute: (reportID: string) => `r/${reportID}/settings/notification-preferences`, + getRoute: (reportID: string) => `r/${reportID}/settings/notification-preferences` as const, }, REPORT_SETTINGS_WRITE_CAPABILITY: { route: 'r/:reportID/settings/who-can-post', - getRoute: (reportID: string) => `r/${reportID}/settings/who-can-post`, + getRoute: (reportID: string) => `r/${reportID}/settings/who-can-post` as const, }, REPORT_WELCOME_MESSAGE: { route: 'r/:reportID/welcomeMessage', - getRoute: (reportID: string) => `r/${reportID}/welcomeMessage`, + getRoute: (reportID: string) => `r/${reportID}/welcomeMessage` as const, }, SPLIT_BILL_DETAILS: { route: 'r/:reportID/split/:reportActionID', - getRoute: (reportID: string, reportActionID: string) => `r/${reportID}/split/${reportActionID}`, + getRoute: (reportID: string, reportActionID: string) => `r/${reportID}/split/${reportActionID}` as const, }, EDIT_SPLIT_BILL: { route: `r/:reportID/split/:reportActionID/edit/:field`, - getRoute: (reportID: string, reportActionID: string, field: ValueOf) => `r/${reportID}/split/${reportActionID}/edit/${field}`, + getRoute: (reportID: string, reportActionID: string, field: ValueOf) => `r/${reportID}/split/${reportActionID}/edit/${field}` as const, }, EDIT_SPLIT_BILL_CURRENCY: { route: 'r/:reportID/split/:reportActionID/edit/currency', - getRoute: (reportID: string, reportActionID: string, currency: string, backTo: string) => `r/${reportID}/split/${reportActionID}/edit/currency?currency=${currency}&backTo=${backTo}`, + getRoute: (reportID: string, reportActionID: string, currency: string, backTo: string) => + `r/${reportID}/split/${reportActionID}/edit/currency?currency=${currency}&backTo=${backTo}` as const, }, TASK_TITLE: { route: 'r/:reportID/title', - getRoute: (reportID: string) => `r/${reportID}/title`, + getRoute: (reportID: string) => `r/${reportID}/title` as const, }, TASK_DESCRIPTION: { route: 'r/:reportID/description', - getRoute: (reportID: string) => `r/${reportID}/description`, + getRoute: (reportID: string) => `r/${reportID}/description` as const, }, TASK_ASSIGNEE: { route: 'r/:reportID/assignee', - getRoute: (reportID: string) => `r/${reportID}/assignee`, + getRoute: (reportID: string) => `r/${reportID}/assignee` as const, }, PRIVATE_NOTES_VIEW: { route: 'r/:reportID/notes/:accountID', - getRoute: (reportID: string, accountID: string | number) => `r/${reportID}/notes/${accountID}`, + getRoute: (reportID: string, accountID: string | number) => `r/${reportID}/notes/${accountID}` as const, }, PRIVATE_NOTES_LIST: { route: 'r/:reportID/notes', - getRoute: (reportID: string) => `r/${reportID}/notes`, + getRoute: (reportID: string) => `r/${reportID}/notes` as const, }, PRIVATE_NOTES_EDIT: { route: 'r/:reportID/notes/:accountID/edit', - getRoute: (reportID: string, accountID: string | number) => `r/${reportID}/notes/${accountID}/edit`, + getRoute: (reportID: string, accountID: string | number) => `r/${reportID}/notes/${accountID}/edit` as const, }, ROOM_MEMBERS: { route: 'r/:reportID/members', - getRoute: (reportID: string) => `r/${reportID}/members`, + getRoute: (reportID: string) => `r/${reportID}/members` as const, }, ROOM_INVITE: { route: 'r/:reportID/invite', - getRoute: (reportID: string) => `r/${reportID}/invite`, + getRoute: (reportID: string) => `r/${reportID}/invite` as const, }, // To see the available iouType, please refer to CONST.IOU.TYPE MONEY_REQUEST: { route: ':iouType/new/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/${reportID}`, + getRoute: (iouType: string, reportID = '') => `${iouType}/new/${reportID}` as const, }, MONEY_REQUEST_AMOUNT: { route: ':iouType/new/amount/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/amount/${reportID}`, + getRoute: (iouType: string, reportID = '') => `${iouType}/new/amount/${reportID}` as const, }, MONEY_REQUEST_PARTICIPANTS: { route: ':iouType/new/participants/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/participants/${reportID}`, + getRoute: (iouType: string, reportID = '') => `${iouType}/new/participants/${reportID}` as const, }, MONEY_REQUEST_CONFIRMATION: { route: ':iouType/new/confirmation/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/confirmation/${reportID}`, + getRoute: (iouType: string, reportID = '') => `${iouType}/new/confirmation/${reportID}` as const, }, MONEY_REQUEST_DATE: { route: ':iouType/new/date/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/date/${reportID}`, + getRoute: (iouType: string, reportID = '') => `${iouType}/new/date/${reportID}` as const, }, MONEY_REQUEST_CURRENCY: { route: ':iouType/new/currency/:reportID?', - getRoute: (iouType: string, reportID: string, currency: string, backTo: string) => `${iouType}/new/currency/${reportID}?currency=${currency}&backTo=${backTo}`, + getRoute: (iouType: string, reportID: string, currency: string, backTo: string) => `${iouType}/new/currency/${reportID}?currency=${currency}&backTo=${backTo}` as const, }, MONEY_REQUEST_DESCRIPTION: { route: ':iouType/new/description/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/description/${reportID}`, + getRoute: (iouType: string, reportID = '') => `${iouType}/new/description/${reportID}` as const, }, MONEY_REQUEST_CATEGORY: { route: ':iouType/new/category/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/category/${reportID}`, + getRoute: (iouType: string, reportID = '') => `${iouType}/new/category/${reportID}` as const, }, MONEY_REQUEST_TAG: { route: ':iouType/new/tag/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/tag/${reportID}`, + getRoute: (iouType: string, reportID = '') => `${iouType}/new/tag/${reportID}` as const, }, MONEY_REQUEST_MERCHANT: { route: ':iouType/new/merchant/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/merchant/${reportID}`, + getRoute: (iouType: string, reportID = '') => `${iouType}/new/merchant/${reportID}` as const, }, MONEY_REQUEST_WAYPOINT: { route: ':iouType/new/waypoint/:waypointIndex', - getRoute: (iouType: string, waypointIndex: number) => `${iouType}/new/waypoint/${waypointIndex}`, + getRoute: (iouType: string, waypointIndex: number) => `${iouType}/new/waypoint/${waypointIndex}` as const, }, MONEY_REQUEST_RECEIPT: { route: ':iouType/new/receipt/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/receipt/${reportID}`, + getRoute: (iouType: string, reportID = '') => `${iouType}/new/receipt/${reportID}` as const, }, MONEY_REQUEST_DISTANCE: { route: ':iouType/new/address/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/address/${reportID}`, + getRoute: (iouType: string, reportID = '') => `${iouType}/new/address/${reportID}` as const, }, MONEY_REQUEST_EDIT_WAYPOINT: { route: 'r/:threadReportID/edit/distance/:transactionID/waypoint/:waypointIndex', - getRoute: (threadReportID: number, transactionID: string, waypointIndex: number) => `r/${threadReportID}/edit/distance/${transactionID}/waypoint/${waypointIndex}`, + getRoute: (threadReportID: number, transactionID: string, waypointIndex: number) => `r/${threadReportID}/edit/distance/${transactionID}/waypoint/${waypointIndex}` as const, }, MONEY_REQUEST_DISTANCE_TAB: { route: ':iouType/new/:reportID?/distance', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/${reportID}/distance`, + getRoute: (iouType: string, reportID = '') => `${iouType}/new/${reportID}/distance` as const, }, MONEY_REQUEST_MANUAL_TAB: ':iouType/new/:reportID?/manual', MONEY_REQUEST_SCAN_TAB: ':iouType/new/:reportID?/scan', + MONEY_REQUEST_CREATE: { + route: 'create/:iouType/start/:transactionID/:reportID', + getRoute: (iouType: ValueOf, transactionID: string, reportID: string) => `create/${iouType}/start/${transactionID}/${reportID}` as const, + }, + MONEY_REQUEST_STEP_CONFIRMATION: { + route: 'create/:iouType/confirmation/:transactionID/:reportID/', + getRoute: (iouType: ValueOf, transactionID: string, reportID: string) => `create/${iouType}/confirmation/${transactionID}/${reportID}/` as const, + }, + MONEY_REQUEST_STEP_AMOUNT: { + route: 'create/:iouType/amount/:transactionID/:reportID/', + getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`create/${iouType}/amount/${transactionID}/${reportID}/`, backTo), + }, + MONEY_REQUEST_STEP_CATEGORY: { + route: 'create/:iouType/category/:transactionID/:reportID/', + getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`create/${iouType}/category/${transactionID}/${reportID}/`, backTo), + }, + MONEY_REQUEST_STEP_CURRENCY: { + route: 'create/:iouType/currency/:transactionID/:reportID/:pageIndex?/', + getRoute: (iouType: ValueOf, transactionID: string, reportID: string, pageIndex = '', backTo = '') => + getUrlWithBackToParam(`create/${iouType}/currency/${transactionID}/${reportID}/${pageIndex}`, backTo), + }, + MONEY_REQUEST_STEP_DATE: { + route: 'create/:iouType/date/:transactionID/:reportID/', + getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`create/${iouType}/date/${transactionID}/${reportID}/`, backTo), + }, + MONEY_REQUEST_STEP_DESCRIPTION: { + route: 'create/:iouType/description/:transactionID/:reportID/', + getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`create/${iouType}/description/${transactionID}/${reportID}/`, backTo), + }, + MONEY_REQUEST_STEP_DISTANCE: { + route: 'create/:iouType/distance/:transactionID/:reportID/', + getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`create/${iouType}/distance/${transactionID}/${reportID}/`, backTo), + }, + MONEY_REQUEST_STEP_MERCHANT: { + route: 'create/:iouType/merchante/:transactionID/:reportID/', + getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`create/${iouType}/merchante/${transactionID}/${reportID}/`, backTo), + }, + MONEY_REQUEST_STEP_PARTICIPANTS: { + route: 'create/:iouType/participants/:transactionID/:reportID/', + getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`create/${iouType}/participants/${transactionID}/${reportID}/`, backTo), + }, + MONEY_REQUEST_STEP_SCAN: { + route: 'create/:iouType/scan/:transactionID/:reportID/', + getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`create/${iouType}/scan/${transactionID}/${reportID}/`, backTo), + }, + MONEY_REQUEST_STEP_TAG: { + route: 'create/:iouType/tag/:transactionID/:reportID/', + getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`create/${iouType}/tag/${transactionID}/${reportID}/`, backTo), + }, + MONEY_REQUEST_STEP_WAYPOINT: { + route: 'create/:iouType/waypoint/:transactionID/:reportID/:pageIndex/', + getRoute: (iouType: ValueOf, transactionID: string, reportID: string, pageIndex = '', backTo = '') => + getUrlWithBackToParam(`create/${iouType}/waypoint/${transactionID}/${reportID}/${pageIndex}`, backTo), + }, + MONEY_REQUEST_CREATE_TAB_DISTANCE: { + route: 'create/:iouType/start/:transactionID/:reportID/distance', + getRoute: (iouType: ValueOf, transactionID: string, reportID: string) => `create/${iouType}/start/${transactionID}/${reportID}/distance` as const, + }, + MONEY_REQUEST_CREATE_TAB_MANUAL: { + route: 'create/:iouType/start/:transactionID/:reportID/manual', + getRoute: (iouType: ValueOf, transactionID: string, reportID: string) => `create/${iouType}/start/${transactionID}/${reportID}/manual` as const, + }, + MONEY_REQUEST_CREATE_TAB_SCAN: { + route: 'create/:iouType/start/:transactionID/:reportID/scan', + getRoute: (iouType: ValueOf, transactionID: string, reportID: string) => `create/${iouType}/start/${transactionID}/${reportID}/scan` as const, + }, + IOU_REQUEST: 'request/new', IOU_SEND: 'send/new', IOU_SEND_ADD_BANK_ACCOUNT: 'send/new/add-bank-account', @@ -321,63 +405,63 @@ export default { ERECEIPT: { route: 'eReceipt/:transactionID', - getRoute: (transactionID: string) => `eReceipt/${transactionID}`, + getRoute: (transactionID: string) => `eReceipt/${transactionID}` as const, }, WORKSPACE_NEW: 'workspace/new', WORKSPACE_NEW_ROOM: 'workspace/new-room', WORKSPACE_INITIAL: { route: 'workspace/:policyID', - getRoute: (policyID: string) => `workspace/${policyID}`, + getRoute: (policyID: string) => `workspace/${policyID}` as const, }, WORKSPACE_INVITE: { route: 'workspace/:policyID/invite', - getRoute: (policyID: string) => `workspace/${policyID}/invite`, + getRoute: (policyID: string) => `workspace/${policyID}/invite` as const, }, WORKSPACE_INVITE_MESSAGE: { route: 'workspace/:policyID/invite-message', - getRoute: (policyID: string) => `workspace/${policyID}/invite-message`, + getRoute: (policyID: string) => `workspace/${policyID}/invite-message` as const, }, WORKSPACE_SETTINGS: { route: 'workspace/:policyID/settings', - getRoute: (policyID: string) => `workspace/${policyID}/settings`, + getRoute: (policyID: string) => `workspace/${policyID}/settings` as const, }, WORKSPACE_SETTINGS_CURRENCY: { route: 'workspace/:policyID/settings/currency', - getRoute: (policyID: string) => `workspace/${policyID}/settings/currency`, + getRoute: (policyID: string) => `workspace/${policyID}/settings/currency` as const, }, WORKSPACE_CARD: { route: 'workspace/:policyID/card', - getRoute: (policyID: string) => `workspace/${policyID}/card`, + getRoute: (policyID: string) => `workspace/${policyID}/card` as const, }, WORKSPACE_REIMBURSE: { route: 'workspace/:policyID/reimburse', - getRoute: (policyID: string) => `workspace/${policyID}/reimburse`, + getRoute: (policyID: string) => `workspace/${policyID}/reimburse` as const, }, WORKSPACE_RATE_AND_UNIT: { route: 'workspace/:policyID/rateandunit', - getRoute: (policyID: string) => `workspace/${policyID}/rateandunit`, + getRoute: (policyID: string) => `workspace/${policyID}/rateandunit` as const, }, WORKSPACE_BILLS: { route: 'workspace/:policyID/bills', - getRoute: (policyID: string) => `workspace/${policyID}/bills`, + getRoute: (policyID: string) => `workspace/${policyID}/bills` as const, }, WORKSPACE_INVOICES: { route: 'workspace/:policyID/invoices', - getRoute: (policyID: string) => `workspace/${policyID}/invoices`, + getRoute: (policyID: string) => `workspace/${policyID}/invoices` as const, }, WORKSPACE_TRAVEL: { route: 'workspace/:policyID/travel', - getRoute: (policyID: string) => `workspace/${policyID}/travel`, + getRoute: (policyID: string) => `workspace/${policyID}/travel` as const, }, WORKSPACE_MEMBERS: { route: 'workspace/:policyID/members', - getRoute: (policyID: string) => `workspace/${policyID}/members`, + getRoute: (policyID: string) => `workspace/${policyID}/members` as const, }, // Referral program promotion REFERRAL_DETAILS_MODAL: { route: 'referral/:contentType', - getRoute: (contentType: string) => `referral/${contentType}`, + getRoute: (contentType: string) => `referral/${contentType}` as const, }, // These are some one-off routes that will be removed once they're no longer needed (see GH issues for details) @@ -385,3 +469,25 @@ export default { SBE: 'sbe', MONEY2020: 'money2020', } as const; + +export {getUrlWithBackToParam}; +export default ROUTES; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type ExtractRouteName = TRoute extends {getRoute: (...args: any[]) => infer TRouteName} ? TRouteName : TRoute; + +type AllRoutes = { + [K in keyof typeof ROUTES]: ExtractRouteName<(typeof ROUTES)[K]>; +}[keyof typeof ROUTES]; + +type RouteIsPlainString = IsEqual; + +/** + * Represents all routes in the app as a union of literal strings. + * + * If this type resolves to `never`, it implies that one or more routes defined within `ROUTES` have not correctly used + * `as const` in their `getRoute` function return value. + */ +type Route = RouteIsPlainString extends true ? never : AllRoutes; + +export type {Route}; diff --git a/src/SCREENS.ts b/src/SCREENS.ts index f957a1dbb25e..2cd263237866 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -2,6 +2,7 @@ * This is a file containing constants for all of the screen names. In most cases, we should use the routes for * navigation. But there are situations where we may need to access screen names directly. */ +import DeepValueOf from './types/utils/DeepValueOf'; const PROTECTED_SCREENS = { HOME: 'Home', @@ -9,35 +10,236 @@ const PROTECTED_SCREENS = { REPORT_ATTACHMENTS: 'ReportAttachments', } as const; -export default { +const SCREENS = { ...PROTECTED_SCREENS, - LOADING: 'Loading', REPORT: 'Report', NOT_FOUND: 'not-found', TRANSITION_BETWEEN_APPS: 'TransitionBetweenApps', VALIDATE_LOGIN: 'ValidateLogin', + UNLINK_LOGIN: 'UnlinkLogin', SETTINGS: { ROOT: 'Settings_Root', - PREFERENCES: 'Settings_Preferences', + SHARE_CODE: 'Settings_Share_Code', WORKSPACES: 'Settings_Workspaces', SECURITY: 'Settings_Security', - STATUS: 'Settings_Status', - WALLET: 'Settings_Wallet', - WALLET_DOMAIN_CARD: 'Settings_Wallet_DomainCard', - WALLET_CARD_GET_PHYSICAL: { - NAME: 'Settings_Card_Get_Physical_Name', - PHONE: 'Settings_Card_Get_Physical_Phone', - ADDRESS: 'Settings_Card_Get_Physical_Address', - CONFIRM: 'Settings_Card_Get_Physical_Confirm', + ABOUT: 'Settings_About', + APP_DOWNLOAD_LINKS: 'Settings_App_Download_Links', + LOUNGE_ACCESS: 'Settings_Lounge_Access', + ADD_DEBIT_CARD: 'Settings_Add_Debit_Card', + ADD_BANK_ACCOUNT: 'Settings_Add_Bank_Account', + CLOSE: 'Settings_Close', + TWO_FACTOR_AUTH: 'Settings_TwoFactorAuth', + REPORT_CARD_LOST_OR_DAMAGED: 'Settings_ReportCardLostOrDamaged', + + PROFILE: { + ROOT: 'Settings_Profile', + DISPLAY_NAME: 'Settings_Display_Name', + 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', + PRONOUNS: 'Settings_Pronouns', + TIMEZONE: 'Settings_Timezone', + TIMEZONE_SELECT: 'Settings_Timezone_Select', + + PERSONAL_DETAILS: { + INITIAL: 'Settings_PersonalDetails_Initial', + LEGAL_NAME: 'Settings_PersonalDetails_LegalName', + DATE_OF_BIRTH: 'Settings_PersonalDetails_DateOfBirth', + ADDRESS: 'Settings_PersonalDetails_Address', + ADDRESS_COUNTRY: 'Settings_PersonalDetails_Address_Country', + }, + }, + + PREFERENCES: { + ROOT: 'Settings_Preferences', + PRIORITY_MODE: 'Settings_Preferences_PriorityMode', + LANGUAGE: 'Settings_Preferences_Language', + THEME: 'Settings_Preferences_Theme', + }, + + WALLET: { + ROOT: 'Settings_Wallet', + DOMAIN_CARD: 'Settings_Wallet_DomainCard', + CARD_GET_PHYSICAL: { + NAME: 'Settings_Card_Get_Physical_Name', + PHONE: 'Settings_Card_Get_Physical_Phone', + ADDRESS: 'Settings_Card_Get_Physical_Address', + CONFIRM: 'Settings_Card_Get_Physical_Confirm', + }, + TRANSFER_BALANCE: 'Settings_Wallet_Transfer_Balance', + CHOOSE_TRANSFER_ACCOUNT: 'Settings_Wallet_Choose_Transfer_Account', + ENABLE_PAYMENTS: 'Settings_Wallet_EnablePayments', + CARD_ACTIVATE: 'Settings_Wallet_Card_Activate', + REPORT_VIRTUAL_CARD_FRAUD: 'Settings_Wallet_ReportVirtualCardFraud', + CARDS_DIGITAL_DETAILS_UPDATE_ADDRESS: 'Settings_Wallet_Cards_Digital_Details_Update_Address', }, }, SAVE_THE_WORLD: { ROOT: 'SaveTheWorld_Root', }, + RIGHT_MODAL: { + SETTINGS: 'Settings', + NEW_CHAT: 'NewChat', + SEARCH: 'Search', + DETAILS: 'Details', + PROFILE: 'Profile', + REPORT_DETAILS: 'Report_Details', + REPORT_SETTINGS: 'Report_Settings', + REPORT_WELCOME_MESSAGE: 'Report_WelcomeMessage', + PARTICIPANTS: 'Participants', + MONEY_REQUEST: 'MoneyRequest', + NEW_TASK: 'NewTask', + TEACHERS_UNITE: 'TeachersUnite', + TASK_DETAILS: 'Task_Details', + ENABLE_PAYMENTS: 'EnablePayments', + SPLIT_DETAILS: 'SplitDetails', + ADD_PERSONAL_BANK_ACCOUNT: 'AddPersonalBankAccount', + WALLET_STATEMENT: 'Wallet_Statement', + FLAG_COMMENT: 'Flag_Comment', + EDIT_REQUEST: 'EditRequest', + SIGN_IN: 'SignIn', + PRIVATE_NOTES: 'Private_Notes', + ROOM_MEMBERS: 'RoomMembers', + ROOM_INVITE: 'RoomInvite', + REFERRAL: 'Referral', + }, SIGN_IN_WITH_APPLE_DESKTOP: 'AppleSignInDesktop', SIGN_IN_WITH_GOOGLE_DESKTOP: 'GoogleSignInDesktop', DESKTOP_SIGN_IN_REDIRECT: 'DesktopSignInRedirect', SAML_SIGN_IN: 'SAMLSignIn', + + MONEY_REQUEST: { + MANUAL_TAB: 'manual', + SCAN_TAB: 'scan', + DISTANCE_TAB: 'distance', + CREATE: 'Money_Request_Create', + STEP_CONFIRMATION: 'Money_Request_Step_Confirmation', + STEP_AMOUNT: 'Money_Request_Step_Amount', + STEP_CATEGORY: 'Money_Request_Step_Category', + STEP_CURRENCY: 'Money_Request_Step_Currency', + STEP_DATE: 'Money_Request_Step_Date', + STEP_DESCRIPTION: 'Money_Request_Step_Description', + STEP_DISTANCE: 'Money_Request_Step_Distance', + STEP_MERCHANT: 'Money_Request_Step_Merchant', + STEP_PARTICIPANTS: 'Money_Request_Step_Participants', + STEP_SCAN: 'Money_Request_Step_Scan', + STEP_TAG: 'Money_Request_Step_Tag', + STEP_WAYPOINT: 'Money_Request_Step_Waypoint', + ROOT: 'Money_Request', + AMOUNT: 'Money_Request_Amount', + PARTICIPANTS: 'Money_Request_Participants', + CONFIRMATION: 'Money_Request_Confirmation', + CURRENCY: 'Money_Request_Currency', + DATE: 'Money_Request_Date', + DESCRIPTION: 'Money_Request_Description', + CATEGORY: 'Money_Request_Category', + TAG: 'Money_Request_Tag', + MERCHANT: 'Money_Request_Merchant', + WAYPOINT: 'Money_Request_Waypoint', + EDIT_WAYPOINT: 'Money_Request_Edit_Waypoint', + DISTANCE: 'Money_Request_Distance', + RECEIPT: 'Money_Request_Receipt', + }, + + IOU_SEND: { + ADD_BANK_ACCOUNT: 'IOU_Send_Add_Bank_Account', + ADD_DEBIT_CARD: 'IOU_Send_Add_Debit_Card', + ENABLE_PAYMENTS: 'IOU_Send_Enable_Payments', + }, + + REPORT_SETTINGS: { + ROOT: 'Report_Settings_Root', + ROOM_NAME: 'Report_Settings_Room_Name', + NOTIFICATION_PREFERENCES: 'Report_Settings_Notification_Preferences', + WRITE_CAPABILITY: 'Report_Settings_Write_Capability', + }, + + NEW_TASK: { + ROOT: 'NewTask_Root', + TASK_ASSIGNEE_SELECTOR: 'NewTask_TaskAssigneeSelector', + TASK_SHARE_DESTINATION_SELECTOR: 'NewTask_TaskShareDestinationSelector', + DETAILS: 'NewTask_Details', + TITLE: 'NewTask_Title', + DESCRIPTION: 'NewTask_Description', + }, + + TASK: { + TITLE: 'Task_Title', + DESCRIPTION: 'Task_Description', + ASSIGNEE: 'Task_Assignee', + }, + + PRIVATE_NOTES: { + VIEW: 'PrivateNotes_View', + LIST: 'PrivateNotes_List', + EDIT: 'PrivateNotes_Edit', + }, + + REPORT_DETAILS: { + ROOT: 'Report_Details_Root', + SHARE_CODE: 'Report_Details_Share_Code', + }, + + WORKSPACE: { + INITIAL: 'Workspace_Initial', + SETTINGS: 'Workspace_Settings', + CARD: 'Workspace_Card', + REIMBURSE: 'Workspace_Reimburse', + RATE_AND_UNIT: 'Workspace_RateAndUnit', + BILLS: 'Workspace_Bills', + INVOICES: 'Workspace_Invoices', + TRAVEL: 'Workspace_Travel', + MEMBERS: 'Workspace_Members', + INVITE: 'Workspace_Invite', + INVITE_MESSAGE: 'Workspace_Invite_Message', + CURRENCY: 'Workspace_Settings_Currency', + }, + + EDIT_REQUEST: { + ROOT: 'EditRequest_Root', + CURRENCY: 'EditRequest_Currency', + }, + + NEW_CHAT: { + ROOT: 'NewChat_Root', + NEW_CHAT: 'chat', + NEW_ROOM: 'room', + }, + + SPLIT_DETAILS: { + ROOT: 'SplitDetails_Root', + EDIT_REQUEST: 'SplitDetails_Edit_Request', + EDIT_CURRENCY: 'SplitDetails_Edit_Currency', + }, + + I_KNOW_A_TEACHER: 'I_Know_A_Teacher', + INTRO_SCHOOL_PRINCIPAL: 'Intro_School_Principal', + I_AM_A_TEACHER: 'I_Am_A_Teacher', + ENABLE_PAYMENTS_ROOT: 'EnablePayments_Root', + ADD_PERSONAL_BANK_ACCOUNT_ROOT: 'AddPersonalBankAccount_Root', + REIMBURSEMENT_ACCOUNT_ROOT: 'Reimbursement_Account_Root', + WALLET_STATEMENT_ROOT: 'WalletStatement_Root', + SIGN_IN_ROOT: 'SignIn_Root', + DETAILS_ROOT: 'Details_Root', + PROFILE_ROOT: 'Profile_Root', + REPORT_WELCOME_MESSAGE_ROOT: 'Report_WelcomeMessage_Root', + REPORT_PARTICIPANTS_ROOT: 'ReportParticipants_Root', + ROOM_MEMBERS_ROOT: 'RoomMembers_Root', + ROOM_INVITE_ROOT: 'RoomInvite_Root', + SEARCH_ROOT: 'Search_Root', + FLAG_COMMENT_ROOT: 'FlagComment_Root', + REIMBURSEMENT_ACCOUNT: 'ReimbursementAccount', + GET_ASSISTANCE: 'GetAssistance', + REFERRAL_DETAILS: 'Referral_Details', + KEYBOARD_SHORTCUTS: 'KeyboardShortcuts', } as const; +type Screen = DeepValueOf; + +export default SCREENS; export {PROTECTED_SCREENS}; +export type {Screen}; 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 3c7feb8fb70c..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 * as StyleUtils from '@styles/StyleUtils'; -import useThemeStyles from '@styles/useThemeStyles'; +import colors from '@styles/theme/colors'; const propTypes = { /** Callback that runs when location button is clicked */ @@ -25,6 +25,7 @@ const defaultProps = { function CurrentLocationButton({onPress, isDisabled}) { const styles = useThemeStyles(); + const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); return ( diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js index a401300e920d..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 * as StyleUtils from '@styles/StyleUtils'; -import useTheme from '@styles/themes/useTheme'; -import useThemeStyles from '@styles/useThemeStyles'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import CurrentLocationButton from './CurrentLocationButton'; @@ -165,6 +165,7 @@ function AddressSearch({ }) { const theme = useTheme(); const styles = useThemeStyles(); + const StyleUtils = useStyleUtils(); const [displayListViewBorder, setDisplayListViewBorder] = useState(false); const [isTyping, setIsTyping] = useState(false); const [isFocused, setIsFocused] = useState(false); 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} > () => { ReportActionContextMenu.hideContextMenu(); @@ -51,7 +52,7 @@ function BaseAnchorForCommentsOnly({onPressIn, onPressOut, href = '', rel = '', linkProps.href = href; } const defaultTextStyle = DeviceCapabilities.canUseTouchScreen() || isSmallScreenWidth ? {} : {...styles.userSelectText, ...styles.cursorPointer}; - const isEmail = Str.isValidEmailMarkdown(href.replace(/mailto:/i, '')); + const isEmail = Str.isValidEmail(href.replace(/mailto:/i, '')); return ( (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 607f4f0a4b11..e2b9952c0617 100644 --- a/src/components/AnimatedStep/index.tsx +++ b/src/components/AnimatedStep/index.tsx @@ -1,8 +1,8 @@ -import React from 'react'; +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 styles from '@styles/styles'; import CONST from '@src/CONST'; import ChildrenProps from '@src/types/utils/ChildrenProps'; import {AnimationDirection} from './AnimatedStepContext'; @@ -18,18 +18,15 @@ type AnimatedStepProps = ChildrenProps & { onAnimationEnd: () => void; }; -function getAnimationStyle(direction: AnimationDirection) { - let transitionValue; +function AnimatedStep({onAnimationEnd, direction = CONST.ANIMATION_DIRECTION.IN, style, children}: AnimatedStepProps) { + const styles = useThemeStyles(); - if (direction === 'in') { - transitionValue = CONST.ANIMATED_TRANSITION_FROM_VALUE; - } else { - transitionValue = -CONST.ANIMATED_TRANSITION_FROM_VALUE; - } - return styles.makeSlideInTranslation('translateX', transitionValue); -} + const animationStyle = useMemo(() => { + const transitionValue = direction === 'in' ? CONST.ANIMATED_TRANSITION_FROM_VALUE : -CONST.ANIMATED_TRANSITION_FROM_VALUE; + + return styles.makeSlideInTranslation('translateX', transitionValue); + }, [direction, styles]); -function AnimatedStep({onAnimationEnd, direction = CONST.ANIMATION_DIRECTION.IN, style = [], children}: AnimatedStepProps) { return ( { @@ -39,7 +36,7 @@ function AnimatedStep({onAnimationEnd, direction = CONST.ANIMATION_DIRECTION.IN, onAnimationEnd(); }} duration={CONST.ANIMATED_TRANSITION} - animation={getAnimationStyle(direction)} + animation={animationStyle} useNativeDriver={useNativeDriver} style={style} > diff --git a/src/components/AnonymousReportFooter.js b/src/components/AnonymousReportFooter.js deleted file mode 100644 index 387e2ab01930..000000000000 --- a/src/components/AnonymousReportFooter.js +++ /dev/null @@ -1,67 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import {Text, View} from 'react-native'; -import reportPropTypes from '@pages/reportPropTypes'; -import useThemeStyles from '@styles/useThemeStyles'; -import * as Session from '@userActions/Session'; -import AvatarWithDisplayName from './AvatarWithDisplayName'; -import Button from './Button'; -import ExpensifyWordmark from './ExpensifyWordmark'; -import participantPropTypes from './participantPropTypes'; -import withLocalize, {withLocalizePropTypes} from './withLocalize'; - -const propTypes = { - /** The report currently being looked at */ - report: reportPropTypes, - - isSmallSizeLayout: PropTypes.bool, - - /** Personal details of all the users */ - personalDetails: PropTypes.objectOf(participantPropTypes), - - ...withLocalizePropTypes, -}; - -const defaultProps = { - report: {}, - isSmallSizeLayout: false, - personalDetails: {}, -}; - -function AnonymousReportFooter(props) { - const styles = useThemeStyles(); - return ( - - - - - - - - - - {props.translate('anonymousReportFooter.logoTagline')} - - -