diff --git a/.eslintrc.js b/.eslintrc.js index 3c144064eb62..b5b4add538f6 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -24,7 +24,7 @@ const restrictedImportPatterns = [ ]; module.exports = { - extends: ['expensify', 'plugin:storybook/recommended', 'plugin:react-hooks/recommended', 'prettier', 'plugin:react-native-a11y/basic'], + extends: ['expensify', 'plugin:storybook/recommended', 'plugin:react-hooks/recommended', 'plugin:react-native-a11y/basic', 'prettier'], plugins: ['react-hooks', 'react-native-a11y'], parser: 'babel-eslint', ignorePatterns: ['!.*', 'src/vendor', '.github/actions/**/index.js', 'desktop/dist/*.js', 'dist/*.js', 'node_modules/.bin/**', 'node_modules/.cache/**', '.git/**'], @@ -46,7 +46,6 @@ module.exports = { touchables: ['PressableWithoutFeedback', 'PressableWithFeedback'], }, ], - curly: 'error', }, }, { @@ -76,6 +75,7 @@ module.exports = { patterns: restrictedImportPatterns, }, ], + curly: 'error', }, }, { @@ -162,6 +162,7 @@ module.exports = { patterns: restrictedImportPatterns, }, ], + curly: 'error', }, }, { diff --git a/.github/scripts/createDocsRoutes.js b/.github/scripts/createDocsRoutes.js index 0fc9aa33ff27..6604a9d207fa 100644 --- a/.github/scripts/createDocsRoutes.js +++ b/.github/scripts/createDocsRoutes.js @@ -2,17 +2,21 @@ const yaml = require('js-yaml'); const fs = require('fs'); const _ = require('underscore'); -const warn = 'Number of hubs in _routes.yml does not match number of hubs in docs/articles. Please update _routes.yml with hub info.'; +const warnMessage = (platform) => `Number of hubs in _routes.yml does not match number of hubs in docs/${platform}/articles. Please update _routes.yml with hub info.`; const disclaimer = '# This file is auto-generated. Do not edit it directly. Use npm run createDocsRoutes instead.\n'; const docsDir = `${process.cwd()}/docs`; const routes = yaml.load(fs.readFileSync(`${docsDir}/_data/_routes.yml`, 'utf8')); +const platformNames = { + expensifyClassic: 'expensify-classic', + newExpensify: 'new-expensify', +}; /** * @param {String} str - The string to convert to title case * @returns {String} */ function toTitleCase(str) { - return str.replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()); + return str.replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1)); } /** @@ -28,7 +32,7 @@ function getArticleObj(filename) { } /** - * If the articlea / sections exist in the hub, then push the entry to the array. + * If the article / sections exist in the hub, then push the entry to the array. * Otherwise, create the array and push the entry to it. * @param {*} hubs - The hubs array * @param {*} hub - The hub we are iterating @@ -44,20 +48,20 @@ function pushOrCreateEntry(hubs, hub, key, entry) { } } -function run() { - const hubs = fs.readdirSync(`${docsDir}/articles`); - if (hubs.length !== routes.hubs.length) { - // If new hubs have been added without metadata addition to _routes.yml - console.error(warn); - process.exit(1); - } +/** + * Add articles and sections to hubs + * @param {Array} hubs - The hubs inside docs/articles/ for a platform + * @param {String} platformName - Expensify Classic or New Expensify + * @param {Array} routeHubs - The hubs insude docs/data/_routes.yml for a platform + */ +function createHubsWithArticles(hubs, platformName, routeHubs) { _.each(hubs, (hub) => { // Iterate through each directory in articles - fs.readdirSync(`${docsDir}/articles/${hub}`).forEach((fileOrFolder) => { + fs.readdirSync(`${docsDir}/articles/${platformName}/${hub}`).forEach((fileOrFolder) => { // If the directory content is a markdown file, then it is an article if (fileOrFolder.endsWith('.md')) { const articleObj = getArticleObj(fileOrFolder); - pushOrCreateEntry(routes.hubs, hub, 'articles', articleObj); + pushOrCreateEntry(routeHubs, hub, 'articles', articleObj); return; } @@ -66,17 +70,38 @@ function run() { const articles = []; // Each subfolder will be a section containing articles - fs.readdirSync(`${docsDir}/articles/${hub}/${section}`).forEach((subArticle) => { + fs.readdirSync(`${docsDir}/articles/${platformName}/${hub}/${section}`).forEach((subArticle) => { articles.push(getArticleObj(subArticle)); }); - pushOrCreateEntry(routes.hubs, hub, 'sections', { + pushOrCreateEntry(routeHubs, hub, 'sections', { href: section, title: toTitleCase(section.replaceAll('-', ' ')), articles, }); }); }); +} + +function run() { + const expensifyClassicArticleHubs = fs.readdirSync(`${docsDir}/articles/${platformNames.expensifyClassic}`); + const newExpensifyArticleHubs = fs.readdirSync(`${docsDir}/articles/${platformNames.newExpensify}`); + + const expensifyClassicRoute = _.find(routes.platforms, (platform) => platform.href === platformNames.expensifyClassic); + const newExpensifyRoute = _.find(routes.platforms, (platform) => platform.href === platformNames.newExpensify); + + if (expensifyClassicArticleHubs.length !== expensifyClassicRoute.hubs.length) { + console.error(warnMessage(platformNames.expensifyClassic)); + process.exit(1); + } + + if (newExpensifyArticleHubs.length !== newExpensifyRoute.hubs.length) { + console.error(warnMessage(platformNames.newExpensify)); + process.exit(1); + } + + createHubsWithArticles(expensifyClassicArticleHubs, platformNames.expensifyClassic, expensifyClassicRoute.hubs); + createHubsWithArticles(newExpensifyArticleHubs, platformNames.newExpensify, newExpensifyRoute.hubs); // Convert the object to YAML and write it to the file let yamlString = yaml.dump(routes); diff --git a/.github/scripts/findUnusedKeys.sh b/.github/scripts/findUnusedKeys.sh new file mode 100755 index 000000000000..77c3ea25326b --- /dev/null +++ b/.github/scripts/findUnusedKeys.sh @@ -0,0 +1,375 @@ +#!/bin/bash + +# Configurations +declare LIB_PATH +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.js" +readonly UTILITIES_STYLES_FILE="${LIB_PATH}/src/styles/utilities" +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 REMOVAL_KEYS_FILE="${LIB_PATH}/scripts/removal_keys_list_temp.txt" +readonly AMOUNT_LINES_TO_SHOW=3 + +readonly FILE_EXTENSIONS=('-name' '*.js' '-o' '-name' '*.jsx' '-o' '-name' '*.ts' '-o' '-name' '*.tsx') + +source scripts/shellUtils.sh + +# trap ctrl-c and call ctrl_c() +trap ctrl_c INT + +delete_temp_files() { + find "${LIB_PATH}/scripts" -name "*keys_list_temp*" -type f -exec rm -f {} \; +} + +# shellcheck disable=SC2317 # Don't warn about unreachable commands in this function +ctrl_c() { + delete_temp_files + exit 1 +} + +count_lines() { + local file=$1 + if [[ -e "$file" ]]; then + wc -l < "$file" + else + echo "File not found: $file" + fi +} + +# Read the style file with unused keys +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 + lines+=("$line") + done < "$file" + + # Loop through the lines + for ((i = lines_before; i <= lines_after; i++)); do + local line="${lines[i]}" + # Print context of the error line + echo "$line" + done + error "Unused key: $key" + echo "--------------------------------" + done < "$STYLES_KEYS_FILE" +} + +# Function to remove a keyword from the temp file +remove_keyword() { + local keyword="$1" + if grep -q "$keyword" "$STYLES_KEYS_FILE"; then + grep -v "$keyword" "$STYLES_KEYS_FILE" > "$REMOVAL_KEYS_FILE" + mv "$REMOVAL_KEYS_FILE" "$STYLES_KEYS_FILE" + + return 0 # Keyword was removed + else + return 1 # Keyword was not found + fi +} + +lookfor_unused_keywords() { + # 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 + + # 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. + if [[ "$clean_keyword" =~ ^styles\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+$ ]]; then + # Keyword has more than two words, remove words after the second word + local keyword_prefix="${clean_keyword%.*}" + remove_keyword "$keyword_prefix" + fi + fi + done < <(grep -E -o '\bstyles\.[a-zA-Z0-9_.]*' "$file" | grep -v '\/\/' | grep -vE '\/\*.*\*\/') + done < <(find "${SRC_DIR}" -type f \( "${FILE_EXTENSIONS[@]}" \)) +} + + +# Function to find and store keys from a file +find_styles_object_and_store_keys() { + local file="$1" + local base_name="${2:-styles}" # Set styles as default + local line_number=0 + local inside_arrow_function=false + + while IFS= read -r line; do + ((line_number++)) + + # Check if we are inside an arrow function and we find a closing curly brace + if [[ "$inside_arrow_function" == true ]]; then + if [[ "$line" =~ ^[[:space:]]*\}\) ]]; then + inside_arrow_function=false + fi + continue + fi + + # Check if we are inside an arrow function + if [[ "$line" =~ ^[[:space:]]*([a-zA-Z0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\(\{ || "$line" =~ ^[[:space:]]*(const|let|var)[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\(.*\)[[:space:]]*'=>' ]]; then + inside_arrow_function=true + continue + fi + + # Skip lines that are not key-related + 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" + fi + done < "$file" +} + +find_styles_functions_and_store_keys() { + local file="$1" + local line_number=0 + local inside_object=false + local inside_arrow_function=false + local key="" + + while IFS= read -r line; do + ((line_number++)) + + # Skip lines that are not key-related + if [[ "${line}" == *styles* ]]; then + continue + fi + + # Check if we are inside an arrow function and we find a closing curly brace + if [[ "$inside_arrow_function" == true ]]; then + if [[ "$line" =~ ^[[:space:]]*\}\) ]]; then + inside_arrow_function=false + fi + continue + fi + + # Check if we are inside an arrow function + if [[ "${line}" =~ ^[[:space:]]*([a-zA-Z0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\( ]]; then + inside_arrow_function=true + key="${line%%:*}" + key="${key// /}" # Trim spaces + echo "styles.${key}|...${key}:${file}:${line_number}" >> "$STYLES_KEYS_FILE" + continue + fi + + if [[ "$line" =~ ^[[:space:]]*(const|let|var)[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\(.*\)[[:space:]]*'=>' ]]; then + inside_arrow_function=true + key="${BASH_REMATCH[2]}" + key="${key// /}" # Trim spaces + echo "styles.${key}|...${key}:${file}:${line_number}" >> "$STYLES_KEYS_FILE" + continue + fi + + done < "$file" +} + +find_theme_style_and_store_keys() { + local file="$1" + local start_line_number="$2" + local base_name="${3:-styles}" # Set styles as default + local parent_keys=() + local root_key="" + local line_number=0 + local inside_arrow_function=false + + while IFS= read -r line; do + ((line_number++)) + + if [ ! "$line_number" -ge "$start_line_number" ]; then + continue + fi + + # Check if we are inside an arrow function and we find a closing curly brace + if [[ "$inside_arrow_function" == true ]]; then + if [[ "$line" =~ ^[[:space:]]*\}\) ]]; then + inside_arrow_function=false + fi + continue + fi + + # Check if we are inside an arrow function + if [[ "$line" =~ ^[[:space:]]*([a-zA-Z0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\(\{ || "$line" =~ ^[[:space:]]*(const|let|var)[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\(.*\)[[:space:]]*'=>' ]]; then + inside_arrow_function=true + continue + fi + + # Skip lines that are not key-related + 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:]]*([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%%:*}" + key="${key// /}" # Trim spaces + + if [[ ${#parent_keys[@]} -gt 0 ]]; then + local parent_key_trimmed="${parent_keys[${#parent_keys[@]}-1]// /}" # Trim spaces + key="$parent_key_trimmed.$key" + elif [[ -n "$root_key" ]]; then + local parent_key_trimmed="${root_key// /}" # Trim spaces + key="$parent_key_trimmed.$key" + fi + + echo "styles.${key}|...${key}|${base_name}.${key}:${file}:${line_number}" >> "$STYLES_KEYS_FILE" + parent_keys+=("$key") + elif [[ "$line" =~ ^[[:space:]]*\} ]]; then + parent_keys=("${parent_keys[@]:0:${#parent_keys[@]}-1}") + fi + done < "$file" +} + +# Given that all the styles are inside of a function, we need to find the function and then look for the styles +collect_theme_keys_from_styles() { + local file="$1" + local line_number=0 + local inside_styles=false + + while IFS= read -r line; do + ((line_number++)) + + if [[ "$inside_styles" == false ]]; then + if [[ "$line" =~ ^[[:space:]]*(const|let|var)[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\(.*\)[[:space:]]*'=>' ]]; then + key="${BASH_REMATCH[2]}" + key="${key// /}" # Trim spaces + if [[ "$key" == "styles"* ]]; then + inside_styles=true + # Need to start within the style function + ((line_number++)) + find_theme_style_and_store_keys "$STYLES_FILE" "$line_number" + fi + continue + fi + fi + done < "$file" +} + +lookfor_unused_spread_keywords() { + local inside_object=false + local key="" + + while IFS= read -r line; do + # Detect the start of an object + if [[ "$line" =~ ^[[:space:]]*([a-zA-Z0-9_-]+\.)?[a-zA-Z0-9_-]+:[[:space:]]*\{ ]]; then + inside_object=true + fi + + # Detect the end of an object + if [[ "$line" =~ ^[[:space:]]*\},?$ ]]; then + inside_object=false + fi + + # If we're inside an object and the line starts with '...', capture the key + if [[ "$inside_object" == true && "$line" =~ ^[[:space:]]*\.\.\.([a-zA-Z0-9_]+)(\(.+\))?,?$ ]]; then + key="${BASH_REMATCH[1]}" + remove_keyword "...${key}" + fi + done < "$STYLES_FILE" +} + +find_utility_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 '\/\*.*\*\/') + 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}" +} + +find_utility_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 + parent_dir=$(dirname "$file") + root_key=$(basename "${parent_dir}") + + if [[ "${root_key}" == "utilities" ]]; then + continue + fi + + find_theme_style_and_store_keys "${file}" 0 "${root_key}" + done < <(find "${UTILITIES_STYLES_FILE}" -type f \( "${FILE_EXTENSIONS[@]}" \)) +} + +lookfor_unused_utilities() { + # Read each utility keyword from the file + while read -r keyword; do + # 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" + 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" +} + +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 keys from styles.js +find_styles_object_and_store_keys "$STYLES_FILE" +find_styles_functions_and_store_keys "$STYLES_FILE" +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 +lookfor_unused_spread_keywords +lookfor_unused_keywords + +final_styles_line_count=$(count_lines "$STYLES_KEYS_FILE") + +if [[ $final_styles_line_count -eq 0 ]]; then + # Exit successfully (status code 0) + delete_temp_files + success "Styles are in a good shape" + exit 0 +else + show_unused_style_keywords + delete_temp_files + error "Unused keys: $final_styles_line_count" + exit 1 +fi diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 5953a4aa89e2..b403a1eb737c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -33,3 +33,7 @@ jobs: echo 'Error: Prettier diff detected! Please run `npm run prettier` and commit the changes.' exit 1 fi + + - name: Run unused style searcher + shell: bash + run: ./.github/scripts/findUnusedKeys.sh diff --git a/.gitignore b/.gitignore index aae9baad529f..d3b4daac04d7 100644 --- a/.gitignore +++ b/.gitignore @@ -107,3 +107,7 @@ tsconfig.tsbuildinfo # Workflow test logs /workflow_tests/logs/ + +# Yalc +.yalc +yalc.lock diff --git a/android/app/build.gradle b/android/app/build.gradle index 1e34491b04ad..629f10f56267 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -90,8 +90,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001037209 - versionName "1.3.72-9" + versionCode 1001037300 + versionName "1.3.73-0" } flavorDimensions "default" diff --git a/docs/_data/_routes.yml b/docs/_data/_routes.yml index 32c8a5211ee2..20582b6b8c7e 100644 --- a/docs/_data/_routes.yml +++ b/docs/_data/_routes.yml @@ -1,26 +1,142 @@ home: href: home title: Welcome to ExpensifyHelp! - description: Find the answers to all of your questions about receipts, expenses, corporate cards, or anything else in the spend management universe. - -# Hubs are comprised of sections and articles. Sections contain multiple related articles, but there can be standalone articles as well -hubs: - - href: split-bills - title: Split bills - description: With only a couple of clicks, split bills with your friends or coworkers. - icon: /assets/images/paper-airplane.svg - - - href: request-money - title: Request money - icon: /assets/images/money-case.svg - description: Request money for work expenses, bills, or a night out with friends. - - - href: playbooks - title: Playbooks - icon: /assets/images/playbook.svg - description: Best practices for how to best deploy Expensify for your business - - - href: other - title: Other - description: Everything else you're looking for is right here. - icon: /assets/images/lightbulb.svg + description: Questions? Find the answers by clicking a Category or using the search bar located in the left-hand menu. + +platforms: + - href: expensify-classic + title: Expensify Classic + hub-title: Expensify Classic - Help & Resources + url: expensify.com + description: Your account settings will look something like this + image: /assets/images/paper-airplane.svg + + # Hubs are comprised of sections and articles. Sections contain multiple related articles, but there can be standalone articles as well + hubs: + - href: account-settings + title: Account Settings + icon: /assets/images/gears.svg + description: With only a couple of clicks, split bills with your friends or coworkers. + + - href: bank-accounts-and-credit-cards + title: Bank Accounts & Credit Cards + icon: /assets/images/bank-card.svg + description: Request money for work expenses, bills, or a night out with friends. + + - href: billing-and-subscriptions + title: Billing & Subscriptions + icon: /assets/images/money-wings.svg + description: Best practices for how to best deploy Expensify for your business + + - href: expense-and-report-features + title: Expense & Report Features + icon: /assets/images/money-receipt.svg + description: Everything else you're looking for is right here. + + - href: expensify-card + title: Expensify Card + icon: /assets/images/hand-card.svg + description: Request money for work expenses, bills, or a night out with friends. + + - href: exports + title: Exports + icon: /assets/images/monitor.svg + description: Best practices for how to best deploy Expensify for your business + + - href: get-paid-back + title: Get Paid Back + description: Everything else you're looking for is right here. + icon: /assets/images/money-into-wallet.svg + + - href: getting-started + title: Getting Started + description: Everything else you're looking for is right here. + icon: /assets/images/accounting.svg + + - href: integrations + title: Integrations + description: Everything else you're looking for is right here. + icon: /assets/images/workflow.svg + + - href: manage-employees-and-report-approvals + title: Manage Employees & Report Approvals + icon: /assets/images/envelope-receipt.svg + description: Everything else you're looking for is right here. + + - href: policy-and-domain-settings + title: Policy & Domain Setting + icon: /assets/images/shield.svg + description: Everything else you're looking for is right here. + + - href: send-payments + title: Send Payments + icon: /assets/images/money-wings.svg + description: Everything else you're looking for is right here. + + - href: new-expensify + title: New Expensify + hub-title: New Expensify - Help & Resources + url: new.expensify.com + description: Your account settings will look something like this + image: /assets/images/paper-airplane.svg + + hubs: + - href: account-settings + title: Account Settings + icon: /assets/images/gears.svg + description: With only a couple of clicks, split bills with your friends or coworkers. + + - href: bank-accounts-and-credit-cards + title: Bank Accounts & Credit Cards + icon: /assets/images/bank-card.svg + description: description + + - href: billing-and-plan-types + title: Billing & Plan Types + icon: /assets/images/money-wings.svg + description: description + + - href: expense-and-report-features + title: Expense & Report Features + icon: /assets/images/money-receipt.svg + description: description + + - href: expensify-card + title: Expensify Card + icon: /assets/images/hand-card.svg + description: description + + - href: exports + title: Exports + icon: /assets/images/monitor.svg + description: description + + - href: get-paid-back + title: Get Paid Back + icon: /assets/images/money-into-wallet.svg + description: description + + - href: getting-started + title: Getting Started + icon: /assets/images/accounting.svg + description: description + + - href: integrations + title: Integrations + icon: /assets/images/workflow.svg + description: description + + - href: manage-employees-and-report-approvals + title: Manage Employees & Report Approvals + icon: /assets/images/envelope-receipt.svg + description: description + + - href: send-payments + title: Send Payments + icon: /assets/images/money-wings.svg + description: description. + + - href: workspace-and-domain-settings + title: Workspace & Domain Settings + icon: /assets/images/shield.svg + description: description. diff --git a/docs/_includes/article-card.html b/docs/_includes/article-card.html index a088e5e406db..b6d8998c13ef 100644 --- a/docs/_includes/article-card.html +++ b/docs/_includes/article-card.html @@ -1,4 +1,4 @@ - +

{{ include.title }}

diff --git a/docs/_includes/hub-card.html b/docs/_includes/hub-card.html index 36bf3bc36e6a..b5188bda7670 100644 --- a/docs/_includes/hub-card.html +++ b/docs/_includes/hub-card.html @@ -1,6 +1,6 @@ -{% assign hub = site.data.routes.hubs | where: "href", include.href | first %} - -
+{% assign hub = include.hub %} +{% assign platform = include.platform %} +
{{ hub.href }} diff --git a/docs/_includes/hub.html b/docs/_includes/hub.html index acdc901a38f6..6b0b0e590b19 100644 --- a/docs/_includes/hub.html +++ b/docs/_includes/hub.html @@ -1,5 +1,8 @@ -{% assign activeHub = page.url | remove: "/hubs/" | remove: ".html" %} -{% assign hub = site.data.routes.hubs | where: "href", activeHub | first %} +{% assign activePlatform = page.url | replace: '/', ' ' | truncatewords: 1 | remove:'...' %} +{% assign platform = site.data.routes.platforms | where: "href", activePlatform | first %} + +{% assign activeHub = page.url | remove: activePlatform | remove: "/hubs/" | remove: "/" | remove: ".html" %} +{% assign hub = platform.hubs | where: "href", activeHub | first %}

{{ hub.title }} @@ -9,6 +12,16 @@

{{ hub.description }}

+{% if hub.articles %} +
+
+ {% for article in hub.articles %} + {% include article-card.html hub=hub.href href=article.href title=article.title platform=activePlatform %} + {% endfor %} +
+
+{% endif %} + {% for section in hub.sections %}

@@ -18,18 +31,8 @@

{% for article in section.articles %} {% assign article_href = section.href | append: '/' | append: article.href %} - {% include article-card.html hub=hub.href href=article_href title=article.title %} + {% include article-card.html hub=hub.href href=article_href title=article.title platform=activePlatform %} {% endfor %}

{% endfor %} - -{% if hub.articles %} -
-
- {% for article in hub.articles %} - {% include article-card.html hub=hub.href href=article.href title=article.title %} - {% endfor %} -
-
-{% endif %} diff --git a/docs/_includes/lhn-article-link.html b/docs/_includes/lhn-article-link.html index f9c4f31f0dbe..91c0de4aacce 100644 --- a/docs/_includes/lhn-article-link.html +++ b/docs/_includes/lhn-article-link.html @@ -1,5 +1,5 @@
  • - + {{ include.title }}
  • diff --git a/docs/_includes/lhn-template.html b/docs/_includes/lhn-template.html index 0473e5da9e9c..015c8211e5b2 100644 --- a/docs/_includes/lhn-template.html +++ b/docs/_includes/lhn-template.html @@ -1,4 +1,5 @@ -{% assign activeHub = page.url | remove: "/hubs/" | remove: ".html" %} +{% assign activePlatform = page.url | replace:'/',' ' | truncatewords: 1 | remove:'...' %} +{% assign activeHub = page.url | remove: activePlatform | remove: "/hubs/" | remove: "/" | remove: ".html" %}
      - {% for hub in site.data.routes.hubs %} - {% if hub.href == activeHub %} + {% for platform in site.data.routes.platforms %} + {% if platform.href == activePlatform %}
    • -
        - - {% for section in hub.sections %} + {% for hub in platform.hubs %} +
          + {% if hub.href == activeHub %} + +
            + {% for article in hub.articles %} + {% include lhn-article-link.html platform=activePlatform hub=hub.href href=article.href title=article.title %} + {% endfor %} + + {% for section in hub.sections %} +
          • + {{ section.title }} +
              + {% for article in section.articles %} + {% assign article_href = section.href | append: '/' | append: article.href %} + {% include lhn-article-link.html platform=activePlatform hub=hub.href href=article_href title=article.title %} + {% endfor %} +
            +
          • + {% endfor %} +
          + {% else %}
        • - {{ section.title }} -
            - {% for article in section.articles %} - {% assign article_href = section.href | append: '/' | append: article.href %} - {% include lhn-article-link.html hub=hub.href href=article_href title=article.title %} - {% endfor %} -
          + + + {{ hub.title }} +
        • - {% endfor %} - - - {% for article in hub.articles %} - {% include lhn-article-link.html hub=hub.href href=article.href title=article.title %} - {% endfor %} + {% endif %}
        + {% endfor %} + {% else %}
      • - + - {{ hub.title }} + {{ platform.title }}
      • {% endif %} diff --git a/docs/_includes/platform-card.html b/docs/_includes/platform-card.html new file mode 100644 index 000000000000..d56a234a5c14 --- /dev/null +++ b/docs/_includes/platform-card.html @@ -0,0 +1,13 @@ +{% assign platform = site.data.routes.platforms | where: "href", include.href | first %} + + +
        +
        + {{ platform.href }} +
        +
        +

        {{ platform.title }}

        +

        {{ platform.description }}

        +
        +
        +
        diff --git a/docs/_includes/platform.html b/docs/_includes/platform.html new file mode 100644 index 000000000000..f3867ee4f5b7 --- /dev/null +++ b/docs/_includes/platform.html @@ -0,0 +1,18 @@ +{% assign selectedPlatform = page.url | remove: "/hubs/" | remove: "/" | remove: ".html" %} +{% assign platform = site.data.routes.platforms | where: "href", selectedPlatform | first %} +
        +

        {{ platform.hub-title }}

        + +

        {{ site.data.routes.home.description }}

        + +
        + {% for hub in platform.hubs %} + {% 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 209d14de0f48..de3fbc203243 100644 --- a/docs/_layouts/default.html +++ b/docs/_layouts/default.html @@ -96,12 +96,5 @@

        Didn't find what you were looking for?

        {% include footer.html %}

    - - - diff --git a/docs/articles/expensify-classic/account-settings/Account-Access.md b/docs/articles/expensify-classic/account-settings/Account-Access.md new file mode 100644 index 000000000000..f04b45c42639 --- /dev/null +++ b/docs/articles/expensify-classic/account-settings/Account-Access.md @@ -0,0 +1,5 @@ +--- +title: Account Access +description: Account Access +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/account-settings/Close-Account.md b/docs/articles/expensify-classic/account-settings/Close-Account.md new file mode 100644 index 000000000000..cf5052fa56f1 --- /dev/null +++ b/docs/articles/expensify-classic/account-settings/Close-Account.md @@ -0,0 +1,5 @@ +--- +title: Close Account +description: Close Account +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/account-settings/Merge-Accounts.md b/docs/articles/expensify-classic/account-settings/Merge-Accounts.md new file mode 100644 index 000000000000..1c5f22478e17 --- /dev/null +++ b/docs/articles/expensify-classic/account-settings/Merge-Accounts.md @@ -0,0 +1,5 @@ +--- +title: Merge Accounts +description: Merge Accounts +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/account-settings/Preferences.md b/docs/articles/expensify-classic/account-settings/Preferences.md new file mode 100644 index 000000000000..a3e53e1177a1 --- /dev/null +++ b/docs/articles/expensify-classic/account-settings/Preferences.md @@ -0,0 +1,5 @@ +--- +title: Preferences +description: Preferences +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/account-settings/Profile-Settings.md b/docs/articles/expensify-classic/account-settings/Profile-Settings.md new file mode 100644 index 000000000000..bdc18036a46e --- /dev/null +++ b/docs/articles/expensify-classic/account-settings/Profile-Settings.md @@ -0,0 +1,5 @@ +--- +title: Profile Settings +description: Profile Settings +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Business-Bank-Accounts-AUS.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Business-Bank-Accounts-AUS.md new file mode 100644 index 000000000000..44488defcd67 --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Business-Bank-Accounts-AUS.md @@ -0,0 +1,5 @@ +--- +title: Business Bank Accounts - AUS +description: Business Bank Accounts - AUS +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Business-Bank-Accounts-USD.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Business-Bank-Accounts-USD.md new file mode 100644 index 000000000000..218d6dcd1efa --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Business-Bank-Accounts-USD.md @@ -0,0 +1,5 @@ +--- +title: Business Bank Accounts - USD +description: Business Bank Accounts - USD +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Deposit-Accounts-AUS.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Deposit-Accounts-AUS.md new file mode 100644 index 000000000000..dba02f6fc52c --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Deposit-Accounts-AUS.md @@ -0,0 +1,5 @@ +--- +title: Deposit Accounts - AUS +description: Deposit Accounts - AUS +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Deposit-Accounts-USD.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Deposit-Accounts-USD.md new file mode 100644 index 000000000000..8d3fe6e51484 --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Deposit-Accounts-USD.md @@ -0,0 +1,5 @@ +--- +title: Deposit Accounts - USD +description: Deposit Accounts - USD +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Global-Reimbursement.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Global-Reimbursement.md new file mode 100644 index 000000000000..40bdfb7741ab --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Global-Reimbursement.md @@ -0,0 +1,5 @@ +--- +title: Global Reimbursement +description: Global Reimbursement +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Personal-Credit-Cards.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Personal-Credit-Cards.md new file mode 100644 index 000000000000..016ca90ee7f7 --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/Personal-Credit-Cards.md @@ -0,0 +1,5 @@ +--- +title: Personal Credit Cards +description: Personal Credit Cards +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/ANZ.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/ANZ.md new file mode 100644 index 000000000000..6bfc7b14c09a --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/ANZ.md @@ -0,0 +1,6 @@ +--- +title: ANZ +description: A guide to integrate with your ANZ card +--- +## Resources Coming Soon! +Coming Soon!! diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Brex.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Brex.md new file mode 100644 index 000000000000..7d5ad7bf0315 --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Brex.md @@ -0,0 +1,5 @@ +--- +title: Brex +description: Brex +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/CSV-Import.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/CSV-Import.md new file mode 100644 index 000000000000..db68d4431a3a --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/CSV-Import.md @@ -0,0 +1,5 @@ +--- +title: CSV Import +description: CSV Import +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Commercial-Card-Feeds.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Commercial-Card-Feeds.md new file mode 100644 index 000000000000..e49d0d61855c --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Commercial-Card-Feeds.md @@ -0,0 +1,5 @@ +--- +title: Commercial Card Feeds +description: Commercial Card Feeds +--- +## Resources 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/Connect-Company-Cards.md new file mode 100644 index 000000000000..ecd4fc0a6538 --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Connect-Company-Cards.md @@ -0,0 +1,5 @@ +--- +title: Connect Company Cards +description: Connect Company Cards +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Direct-Bank-Connections.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Direct-Bank-Connections.md new file mode 100644 index 000000000000..6775b2684b61 --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Direct-Bank-Connections.md @@ -0,0 +1,5 @@ +--- +title: Direct Bank Connections +description: Direct Bank Connections +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Export-To-GL-Accounts.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Export-To-GL-Accounts.md new file mode 100644 index 000000000000..58485888b921 --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Export-To-GL-Accounts.md @@ -0,0 +1,5 @@ +--- +title: Export to GL Accounts +description: Export to GL Accounts +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Reconciliation.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Reconciliation.md new file mode 100644 index 000000000000..be400ee2c13c --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Reconciliation.md @@ -0,0 +1,5 @@ +--- +title: Reconciliation +description: Reconciliation +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Troubleshooting.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Troubleshooting.md new file mode 100644 index 000000000000..d9e0d1bb994b --- /dev/null +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/company-cards/Troubleshooting.md @@ -0,0 +1,5 @@ +--- +title: Troubleshooting +description: Troubleshooting +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/billing-and-subscriptions/Annual-Subscription.md b/docs/articles/expensify-classic/billing-and-subscriptions/Annual-Subscription.md new file mode 100644 index 000000000000..c80a0d57400d --- /dev/null +++ b/docs/articles/expensify-classic/billing-and-subscriptions/Annual-Subscription.md @@ -0,0 +1,5 @@ +--- +title: Annual Subscription +description: Annual Subscription +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/billing-and-subscriptions/Billing-Owner.md b/docs/articles/expensify-classic/billing-and-subscriptions/Billing-Owner.md new file mode 100644 index 000000000000..590fbc78007e --- /dev/null +++ b/docs/articles/expensify-classic/billing-and-subscriptions/Billing-Owner.md @@ -0,0 +1,5 @@ +--- +title: Billing-Owner +description: Billing-Owner +--- +## Resources Coming Soon! \ No newline at end of file diff --git a/docs/articles/expensify-classic/billing-and-subscriptions/Change-Plan-Or-Subscription.md b/docs/articles/expensify-classic/billing-and-subscriptions/Change-Plan-Or-Subscription.md new file mode 100644 index 000000000000..2f593625a7d5 --- /dev/null +++ b/docs/articles/expensify-classic/billing-and-subscriptions/Change-Plan-Or-Subscription.md @@ -0,0 +1,5 @@ +--- +title: Change Plan or Subscription +description: Change Plan or Subscription +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/billing-and-subscriptions/Consolidated-Domain-Billing.md b/docs/articles/expensify-classic/billing-and-subscriptions/Consolidated-Domain-Billing.md new file mode 100644 index 000000000000..de6ec4a4a466 --- /dev/null +++ b/docs/articles/expensify-classic/billing-and-subscriptions/Consolidated-Domain-Billing.md @@ -0,0 +1,5 @@ +--- +title: Consolidated Domain Billing +description: Consolidated Domain Billing +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/billing-and-subscriptions/Free-Trial.md b/docs/articles/expensify-classic/billing-and-subscriptions/Free-Trial.md new file mode 100644 index 000000000000..8a7b7edd19d9 --- /dev/null +++ b/docs/articles/expensify-classic/billing-and-subscriptions/Free-Trial.md @@ -0,0 +1,5 @@ +--- +title: Free Trial +description: Free Trial +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/billing-and-subscriptions/Individual-Subscription.md b/docs/articles/expensify-classic/billing-and-subscriptions/Individual-Subscription.md new file mode 100644 index 000000000000..d6be489a1146 --- /dev/null +++ b/docs/articles/expensify-classic/billing-and-subscriptions/Individual-Subscription.md @@ -0,0 +1,5 @@ +--- +title: Individual Subscription +description: Individual Subscription +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/billing-and-subscriptions/Overview.md b/docs/articles/expensify-classic/billing-and-subscriptions/Overview.md new file mode 100644 index 000000000000..3352c72167cd --- /dev/null +++ b/docs/articles/expensify-classic/billing-and-subscriptions/Overview.md @@ -0,0 +1,5 @@ +--- +title: Overview +description: Overview +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/billing-and-subscriptions/Pay-Per-Use-Subscription.md b/docs/articles/expensify-classic/billing-and-subscriptions/Pay-Per-Use-Subscription.md new file mode 100644 index 000000000000..be431a287557 --- /dev/null +++ b/docs/articles/expensify-classic/billing-and-subscriptions/Pay-Per-Use-Subscription.md @@ -0,0 +1,5 @@ +--- +title: Pay-per-use Subscription +description: Pay-per-use Subscription +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/billing-and-subscriptions/Payment-Card.md b/docs/articles/expensify-classic/billing-and-subscriptions/Payment-Card.md new file mode 100644 index 000000000000..91c5d4e91eda --- /dev/null +++ b/docs/articles/expensify-classic/billing-and-subscriptions/Payment-Card.md @@ -0,0 +1,5 @@ +--- +title: Payment Card +description: Payment Card +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/billing-and-subscriptions/Tax-Exempt.md b/docs/articles/expensify-classic/billing-and-subscriptions/Tax-Exempt.md new file mode 100644 index 000000000000..c8f781cbd59b --- /dev/null +++ b/docs/articles/expensify-classic/billing-and-subscriptions/Tax-Exempt.md @@ -0,0 +1,5 @@ +--- +title: Tax Exempt +description: Tax Exempt +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/expense-and-report-features/Attendee-Tracking.md b/docs/articles/expensify-classic/expense-and-report-features/Attendee-Tracking.md new file mode 100644 index 000000000000..bc7fbdfe84aa --- /dev/null +++ b/docs/articles/expensify-classic/expense-and-report-features/Attendee-Tracking.md @@ -0,0 +1,5 @@ +--- +title: Attendee Tracking +description: Attendee Tracking +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/expense-and-report-features/Currency.md b/docs/articles/expensify-classic/expense-and-report-features/Currency.md new file mode 100644 index 000000000000..611365aa5013 --- /dev/null +++ b/docs/articles/expensify-classic/expense-and-report-features/Currency.md @@ -0,0 +1,5 @@ +--- +title: Currency +description: Currency +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/expense-and-report-features/Expense-Rules.md b/docs/articles/expensify-classic/expense-and-report-features/Expense-Rules.md new file mode 100644 index 000000000000..81c664497e14 --- /dev/null +++ b/docs/articles/expensify-classic/expense-and-report-features/Expense-Rules.md @@ -0,0 +1,5 @@ +--- +title: Expense Rules +description: Expense Rules +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/expense-and-report-features/Expense-Types.md b/docs/articles/expensify-classic/expense-and-report-features/Expense-Types.md new file mode 100644 index 000000000000..a75209e4dfb1 --- /dev/null +++ b/docs/articles/expensify-classic/expense-and-report-features/Expense-Types.md @@ -0,0 +1,5 @@ +--- +title: Expense Types +description: Expense Types +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/expense-and-report-features/Report-Comments.md b/docs/articles/expensify-classic/expense-and-report-features/Report-Comments.md new file mode 100644 index 000000000000..3938c02bd333 --- /dev/null +++ b/docs/articles/expensify-classic/expense-and-report-features/Report-Comments.md @@ -0,0 +1,5 @@ +--- +title: Report Comments +description: Report Comments +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/expense-and-report-features/The-Expenses-Page.md b/docs/articles/expensify-classic/expense-and-report-features/The-Expenses-Page.md new file mode 100644 index 000000000000..f202587568e5 --- /dev/null +++ b/docs/articles/expensify-classic/expense-and-report-features/The-Expenses-Page.md @@ -0,0 +1,5 @@ +--- +title: The Expenses Page +description: The Expenses Page +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/expense-and-report-features/The-Reports-Page.md b/docs/articles/expensify-classic/expense-and-report-features/The-Reports-Page.md new file mode 100644 index 000000000000..37da613e750a --- /dev/null +++ b/docs/articles/expensify-classic/expense-and-report-features/The-Reports-Page.md @@ -0,0 +1,5 @@ +--- +title: The Reports Page +description: The Reports Page +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/expensify-card/Auto-Reconciliation.md b/docs/articles/expensify-classic/expensify-card/Auto-Reconciliation.md new file mode 100644 index 000000000000..e1d1a990b166 --- /dev/null +++ b/docs/articles/expensify-classic/expensify-card/Auto-Reconciliation.md @@ -0,0 +1,5 @@ +--- +title: Auto-reconciliation +description: Auto-reconciliation +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/expensify-card/CPA-Card.md b/docs/articles/expensify-classic/expensify-card/CPA-Card.md new file mode 100644 index 000000000000..9f4c47a6a402 --- /dev/null +++ b/docs/articles/expensify-classic/expensify-card/CPA-Card.md @@ -0,0 +1,5 @@ +--- +title: CPA Card +description: CPA Card +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/expensify-card/Card-Settings.md b/docs/articles/expensify-classic/expensify-card/Card-Settings.md new file mode 100644 index 000000000000..ff9a959d38aa --- /dev/null +++ b/docs/articles/expensify-classic/expensify-card/Card-Settings.md @@ -0,0 +1,5 @@ +--- +title: Card Settings +description: Card Settings +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/expensify-card/Connect-To-Indirect-Integration.md b/docs/articles/expensify-classic/expensify-card/Connect-To-Indirect-Integration.md new file mode 100644 index 000000000000..0e05269f6501 --- /dev/null +++ b/docs/articles/expensify-classic/expensify-card/Connect-To-Indirect-Integration.md @@ -0,0 +1,5 @@ +--- +title: Connect to Indirect Integration +description: Connect to Indirect Integration +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/expensify-card/File-A-Dispute.md b/docs/articles/expensify-classic/expensify-card/File-A-Dispute.md new file mode 100644 index 000000000000..296999410687 --- /dev/null +++ b/docs/articles/expensify-classic/expensify-card/File-A-Dispute.md @@ -0,0 +1,5 @@ +--- +title: File a Dispute +description: File a Dispute +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/expensify-card/Get-The-Card.md b/docs/articles/expensify-classic/expensify-card/Get-The-Card.md new file mode 100644 index 000000000000..9c8e804f6363 --- /dev/null +++ b/docs/articles/expensify-classic/expensify-card/Get-The-Card.md @@ -0,0 +1,5 @@ +--- +title: Get the Card +description: Get the Card +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/expensify-card/Statements.md b/docs/articles/expensify-classic/expensify-card/Statements.md new file mode 100644 index 000000000000..602fa610dd0b --- /dev/null +++ b/docs/articles/expensify-classic/expensify-card/Statements.md @@ -0,0 +1,5 @@ +--- +title: Statements +description: Statements +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/expensify-card/The-Reports-Page.md b/docs/articles/expensify-classic/expensify-card/The-Reports-Page.md new file mode 100644 index 000000000000..37da613e750a --- /dev/null +++ b/docs/articles/expensify-classic/expensify-card/The-Reports-Page.md @@ -0,0 +1,5 @@ +--- +title: The Reports Page +description: The Reports Page +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/exports/Custom-Templates.md b/docs/articles/expensify-classic/exports/Custom-Templates.md new file mode 100644 index 000000000000..5dcfe58b09f5 --- /dev/null +++ b/docs/articles/expensify-classic/exports/Custom-Templates.md @@ -0,0 +1,5 @@ +--- +title: Custom Templates +description: Custom Templates +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/exports/Default-Export-Templates.md b/docs/articles/expensify-classic/exports/Default-Export-Templates.md new file mode 100644 index 000000000000..4dcb624698af --- /dev/null +++ b/docs/articles/expensify-classic/exports/Default-Export-Templates.md @@ -0,0 +1,5 @@ +--- +title: Default Export Templates +description: Default Export Templates +--- +## Resources Coming Soon! diff --git a/docs/articles/other/Insights.md b/docs/articles/expensify-classic/exports/Insights.md similarity index 100% rename from docs/articles/other/Insights.md rename to docs/articles/expensify-classic/exports/Insights.md diff --git a/docs/articles/expensify-classic/exports/The-Reports-Page.md b/docs/articles/expensify-classic/exports/The-Reports-Page.md new file mode 100644 index 000000000000..37da613e750a --- /dev/null +++ b/docs/articles/expensify-classic/exports/The-Reports-Page.md @@ -0,0 +1,5 @@ +--- +title: The Reports Page +description: The Reports Page +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/get-paid-back/Mileage.md b/docs/articles/expensify-classic/get-paid-back/Mileage.md new file mode 100644 index 000000000000..381bc28626f9 --- /dev/null +++ b/docs/articles/expensify-classic/get-paid-back/Mileage.md @@ -0,0 +1,5 @@ +--- +title: Mileage +description: Mileage +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/get-paid-back/Per-Diem.md b/docs/articles/expensify-classic/get-paid-back/Per-Diem.md new file mode 100644 index 000000000000..e5a57fc62bdf --- /dev/null +++ b/docs/articles/expensify-classic/get-paid-back/Per-Diem.md @@ -0,0 +1,5 @@ +--- +title: Per Diem +description: Per Diem +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/get-paid-back/Third-Party-Payments.md b/docs/articles/expensify-classic/get-paid-back/Third-Party-Payments.md new file mode 100644 index 000000000000..d472e54778e1 --- /dev/null +++ b/docs/articles/expensify-classic/get-paid-back/Third-Party-Payments.md @@ -0,0 +1,5 @@ +--- +title: Third Party Payments +description: Third Party Payments +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/get-paid-back/Trips.md b/docs/articles/expensify-classic/get-paid-back/Trips.md new file mode 100644 index 000000000000..3499865c4ee9 --- /dev/null +++ b/docs/articles/expensify-classic/get-paid-back/Trips.md @@ -0,0 +1,5 @@ +--- +title: Trips +description: Trips +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/get-paid-back/expenses/Apply-Tax.md b/docs/articles/expensify-classic/get-paid-back/expenses/Apply-Tax.md new file mode 100644 index 000000000000..224b622cec3f --- /dev/null +++ b/docs/articles/expensify-classic/get-paid-back/expenses/Apply-Tax.md @@ -0,0 +1,5 @@ +--- +title: Apply Tax +description: Apply Tax +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/get-paid-back/expenses/Create-Expenses.md b/docs/articles/expensify-classic/get-paid-back/expenses/Create-Expenses.md new file mode 100644 index 000000000000..8f4d035e1fe7 --- /dev/null +++ b/docs/articles/expensify-classic/get-paid-back/expenses/Create-Expenses.md @@ -0,0 +1,5 @@ +--- +title: Create Expenses +description: Create Expenses +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/get-paid-back/expenses/Merge-Expenses.md b/docs/articles/expensify-classic/get-paid-back/expenses/Merge-Expenses.md new file mode 100644 index 000000000000..c628244c9b2e --- /dev/null +++ b/docs/articles/expensify-classic/get-paid-back/expenses/Merge-Expenses.md @@ -0,0 +1,5 @@ +--- +title: Merge Expenses +description: Merge Expenses +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/get-paid-back/expenses/Upload-Receipts.md b/docs/articles/expensify-classic/get-paid-back/expenses/Upload-Receipts.md new file mode 100644 index 000000000000..2091b5f3e7f0 --- /dev/null +++ b/docs/articles/expensify-classic/get-paid-back/expenses/Upload-Receipts.md @@ -0,0 +1,5 @@ +--- +title: Upload Receipts +description: Upload Receipts +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/get-paid-back/reports/Create-A-Report.md b/docs/articles/expensify-classic/get-paid-back/reports/Create-A-Report.md new file mode 100644 index 000000000000..e6cc65290e73 --- /dev/null +++ b/docs/articles/expensify-classic/get-paid-back/reports/Create-A-Report.md @@ -0,0 +1,5 @@ +--- +title: Create a Report +description: Create a Report +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/get-paid-back/reports/Reimbursements.md b/docs/articles/expensify-classic/get-paid-back/reports/Reimbursements.md new file mode 100644 index 000000000000..91c4459d2ebd --- /dev/null +++ b/docs/articles/expensify-classic/get-paid-back/reports/Reimbursements.md @@ -0,0 +1,5 @@ +--- +title: Reimbursements +description: Reimbursements +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/getting-started/Best-Practices.md b/docs/articles/expensify-classic/getting-started/Best-Practices.md new file mode 100644 index 000000000000..16b284ae60df --- /dev/null +++ b/docs/articles/expensify-classic/getting-started/Best-Practices.md @@ -0,0 +1,5 @@ +--- +title: Best Practices +description: Best Practices +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/getting-started/Employees.md b/docs/articles/expensify-classic/getting-started/Employees.md new file mode 100644 index 000000000000..f139c40be926 --- /dev/null +++ b/docs/articles/expensify-classic/getting-started/Employees.md @@ -0,0 +1,5 @@ +--- +title: Employees +description: Employees +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/getting-started/Individual-Users.md b/docs/articles/expensify-classic/getting-started/Individual-Users.md new file mode 100644 index 000000000000..2e152ea515d7 --- /dev/null +++ b/docs/articles/expensify-classic/getting-started/Individual-Users.md @@ -0,0 +1,5 @@ +--- +title: Individual Users +description: Individual Users +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/getting-started/Invite-Employees.md b/docs/articles/expensify-classic/getting-started/Invite-Employees.md new file mode 100644 index 000000000000..5cdb8eb086b0 --- /dev/null +++ b/docs/articles/expensify-classic/getting-started/Invite-Employees.md @@ -0,0 +1,5 @@ +--- +title: Invite Employees +description: Invite Employees +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/getting-started/Plan-Types.md b/docs/articles/expensify-classic/getting-started/Plan-Types.md new file mode 100644 index 000000000000..7bb725a1aa35 --- /dev/null +++ b/docs/articles/expensify-classic/getting-started/Plan-Types.md @@ -0,0 +1,5 @@ +--- +title: Plan-Types +description: Plan-Types +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/getting-started/Policy-Admins.md b/docs/articles/expensify-classic/getting-started/Policy-Admins.md new file mode 100644 index 000000000000..91d56b0c4f71 --- /dev/null +++ b/docs/articles/expensify-classic/getting-started/Policy-Admins.md @@ -0,0 +1,5 @@ +--- +title: Policy Admins +description: Policy Admins +--- +## Resources Coming Soon! diff --git a/docs/articles/other/Referral-Program.md b/docs/articles/expensify-classic/getting-started/Referral-Program.md similarity index 98% rename from docs/articles/other/Referral-Program.md rename to docs/articles/expensify-classic/getting-started/Referral-Program.md index 1faff1c9ec4f..683e93d0277a 100644 --- a/docs/articles/other/Referral-Program.md +++ b/docs/articles/expensify-classic/getting-started/Referral-Program.md @@ -50,4 +50,4 @@ Please send a message to concierge@expensify.com with the billing owner of the c 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. +On the mobile app, go to **Settings** > **Invite a Friend** > **Share Invite Link** to retrieve your referral link. diff --git a/docs/articles/expensify-classic/getting-started/Security.md b/docs/articles/expensify-classic/getting-started/Security.md new file mode 100644 index 000000000000..41451e2ba958 --- /dev/null +++ b/docs/articles/expensify-classic/getting-started/Security.md @@ -0,0 +1,5 @@ +--- +title: Security +description: Security +--- +## Resources Coming Soon! diff --git a/docs/articles/other/Your-Expensify-Account-Manager.md b/docs/articles/expensify-classic/getting-started/Support/Your-Expensify-Account-Manager.md similarity index 99% rename from docs/articles/other/Your-Expensify-Account-Manager.md rename to docs/articles/expensify-classic/getting-started/Support/Your-Expensify-Account-Manager.md index 70e0435e00e1..3ef47337a74c 100644 --- a/docs/articles/other/Your-Expensify-Account-Manager.md +++ b/docs/articles/expensify-classic/getting-started/Support/Your-Expensify-Account-Manager.md @@ -33,4 +33,4 @@ You will be able to see if they are online via their status, which will either s 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. +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/getting-started/Using-The-App.md b/docs/articles/expensify-classic/getting-started/Using-The-App.md new file mode 100644 index 000000000000..37767ea9d78d --- /dev/null +++ b/docs/articles/expensify-classic/getting-started/Using-The-App.md @@ -0,0 +1,5 @@ +--- +title: Using the App +description: Using the App +--- +## Resources Coming Soon! diff --git a/docs/articles/other/Card-Revenue-Share-for-ExpensifyApproved!-Partners.md b/docs/articles/expensify-classic/getting-started/approved-accountants/Card-Revenue-Share-For-Expensify-Approved-Partners.md similarity index 99% rename from docs/articles/other/Card-Revenue-Share-for-ExpensifyApproved!-Partners.md rename to docs/articles/expensify-classic/getting-started/approved-accountants/Card-Revenue-Share-For-Expensify-Approved-Partners.md index 44614d506d49..b18531d43200 100644 --- a/docs/articles/other/Card-Revenue-Share-for-ExpensifyApproved!-Partners.md +++ b/docs/articles/expensify-classic/getting-started/approved-accountants/Card-Revenue-Share-For-Expensify-Approved-Partners.md @@ -13,4 +13,4 @@ To benefit from this program, all you need to do is ensure that you are listed a - What if my firm is not permitted to accept revenue share from our clients?

    We understand that different firms may have different policies. If your firm is unable to accept this revenue share, you can pass the revenue share back to your client to give them an additional 0.5% of cash back using your own internal payment tools.

    - What if my firm does not wish to participate in the program?
    -
    Please reach out to your assigned partner manager at new.expensify.com to inform them you would not like to accept the revenue share nor do you want to pass the revenue share to your clients. +
    Please reach out to your assigned partner manager at new.expensify.com to inform them you would not like to accept the revenue share nor do you want to pass the revenue share to your clients. \ No newline at end of file diff --git a/docs/articles/other/Your-Expensify-Partner-Manager.md b/docs/articles/expensify-classic/getting-started/approved-accountants/Your-Expensify-Partner-Manager.md similarity index 99% rename from docs/articles/other/Your-Expensify-Partner-Manager.md rename to docs/articles/expensify-classic/getting-started/approved-accountants/Your-Expensify-Partner-Manager.md index 9a68fbfd8b39..c7a5dc5a04ab 100644 --- a/docs/articles/other/Your-Expensify-Partner-Manager.md +++ b/docs/articles/expensify-classic/getting-started/approved-accountants/Your-Expensify-Partner-Manager.md @@ -31,4 +31,4 @@ If you’re unable to contact your Partner Manager (i.e., they're out of office ## 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. +We recommend continuing to work with Concierge for **general support questions**, as this team is always online and available to help immediately. \ No newline at end of file diff --git a/docs/articles/playbooks/Expensify-Playbook-for-Small-to-Medium-Sized-Businesses.md b/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md similarity index 99% rename from docs/articles/playbooks/Expensify-Playbook-for-Small-to-Medium-Sized-Businesses.md rename to docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md index a4004dbe1b88..2b95a1d13fde 100644 --- a/docs/articles/playbooks/Expensify-Playbook-for-Small-to-Medium-Sized-Businesses.md +++ b/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md @@ -280,4 +280,4 @@ Now that we’ve gone through all of the steps for setting up your account, let 4. Click *Accept Terms* ## You’re all set! -Congrats, you are all set up! If you need any assistance with anything mentioned above or would like to understand other features available in Expensify, reach out to your Setup Specialist directly in *[new.expensify.com](https://new.expensify.com)*. Don’t have one yet? Create a Control Policy, and we’ll automatically assign a dedicated Setup Specialist to you. +Congrats, you are all set up! If you need any assistance with anything mentioned above or would like to understand other features available in Expensify, reach out to your Setup Specialist directly in *[new.expensify.com](https://new.expensify.com)*. Don’t have one yet? Create a Control Policy, and we’ll automatically assign a dedicated Setup Specialist to you. diff --git a/docs/articles/playbooks/Expensify-Playbook-for-US-Based-Bootstrapped-Startups.md b/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-US-Based-Bootstrapped-Startups.md similarity index 99% rename from docs/articles/playbooks/Expensify-Playbook-for-US-Based-Bootstrapped-Startups.md rename to docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-US-Based-Bootstrapped-Startups.md index 089ad16834ac..86c6a583c758 100644 --- a/docs/articles/playbooks/Expensify-Playbook-for-US-Based-Bootstrapped-Startups.md +++ b/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-US-Based-Bootstrapped-Startups.md @@ -88,4 +88,3 @@ When you have bills to pay you can click *View all bills* under the *Manage your # You’re all set! Congrats, you are all set up! If you need any assistance with anything mentioned above, reach out to either your Concierge directly in *[new.expensify.com](https://new.expensify.com/concierge)*, or email concierge@expensify.com. Create a Collect or Control Policy, and we’ll automatically assign a dedicated Setup Specialist to you. - diff --git a/docs/articles/playbooks/Expensify-Playbook-for-US-based-VC-Backed-Startups.md b/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-US-Based-VC-Backed-Startups.md similarity index 100% rename from docs/articles/playbooks/Expensify-Playbook-for-US-based-VC-Backed-Startups.md rename to docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-US-Based-VC-Backed-Startups.md diff --git a/docs/articles/expensify-classic/getting-started/tips-and-tricks.md b/docs/articles/expensify-classic/getting-started/tips-and-tricks.md new file mode 100644 index 000000000000..d85c7f3a0cb9 --- /dev/null +++ b/docs/articles/expensify-classic/getting-started/tips-and-tricks.md @@ -0,0 +1,5 @@ +--- +title: Tips and Tricks +description: Tips and Tricks +--- +## Resources Coming Soon! diff --git a/docs/articles/other/Enable-Location-Access-on-Web.md b/docs/articles/expensify-classic/getting-started/tips-and-tricks/Enable-Location-Access-On-Web.md similarity index 99% rename from docs/articles/other/Enable-Location-Access-on-Web.md rename to docs/articles/expensify-classic/getting-started/tips-and-tricks/Enable-Location-Access-On-Web.md index 6cc0d19e4cde..649212b00f7b 100644 --- a/docs/articles/other/Enable-Location-Access-on-Web.md +++ b/docs/articles/expensify-classic/getting-started/tips-and-tricks/Enable-Location-Access-On-Web.md @@ -52,4 +52,4 @@ Ask: The site must ask if it can use your location. Deny: The site can’t use your location. Allow: The site can always use your location. -[Safari help page](https://support.apple.com/guide/safari/websites-ibrwe2159f50/mac) +[Safari help page](https://support.apple.com/guide/safari/websites-ibrwe2159f50/mac) \ No newline at end of file diff --git a/docs/articles/expensify-classic/integrations/accounting-integrations/Bill-dot-com.md b/docs/articles/expensify-classic/integrations/accounting-integrations/Bill-dot-com.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/accounting-integrations/Bill-dot-com.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/accounting-integrations/FinancalForce.md b/docs/articles/expensify-classic/integrations/accounting-integrations/FinancalForce.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/accounting-integrations/FinancalForce.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/accounting-integrations/NetSuite.md b/docs/articles/expensify-classic/integrations/accounting-integrations/NetSuite.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/accounting-integrations/NetSuite.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/accounting-integrations/QuickBooks-Desktop.md b/docs/articles/expensify-classic/integrations/accounting-integrations/QuickBooks-Desktop.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/accounting-integrations/QuickBooks-Desktop.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/accounting-integrations/QuickBooks-Online.md b/docs/articles/expensify-classic/integrations/accounting-integrations/QuickBooks-Online.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/accounting-integrations/QuickBooks-Online.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/accounting-integrations/Sage-Intacct.md b/docs/articles/expensify-classic/integrations/accounting-integrations/Sage-Intacct.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/accounting-integrations/Sage-Intacct.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/accounting-integrations/Xero.md b/docs/articles/expensify-classic/integrations/accounting-integrations/Xero.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/accounting-integrations/Xero.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/hr-integrations/ADP.md b/docs/articles/expensify-classic/integrations/hr-integrations/ADP.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/hr-integrations/ADP.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/hr-integrations/Greenhouse.md b/docs/articles/expensify-classic/integrations/hr-integrations/Greenhouse.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/hr-integrations/Greenhouse.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/hr-integrations/Gusto.md b/docs/articles/expensify-classic/integrations/hr-integrations/Gusto.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/hr-integrations/Gusto.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/hr-integrations/QuickBooks-Time.md b/docs/articles/expensify-classic/integrations/hr-integrations/QuickBooks-Time.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/hr-integrations/QuickBooks-Time.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/hr-integrations/Rippling.md b/docs/articles/expensify-classic/integrations/hr-integrations/Rippling.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/hr-integrations/Rippling.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/hr-integrations/Workday.md b/docs/articles/expensify-classic/integrations/hr-integrations/Workday.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/hr-integrations/Workday.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/hr-integrations/Zenefits.md b/docs/articles/expensify-classic/integrations/hr-integrations/Zenefits.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/hr-integrations/Zenefits.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/other-integrations/Google-Apps-SSO.md b/docs/articles/expensify-classic/integrations/other-integrations/Google-Apps-SSO.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/other-integrations/Google-Apps-SSO.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/Bolt.md b/docs/articles/expensify-classic/integrations/travel-integrations/Bolt.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/travel-integrations/Bolt.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/Egencia.md b/docs/articles/expensify-classic/integrations/travel-integrations/Egencia.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/travel-integrations/Egencia.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/Global-VaTax.md b/docs/articles/expensify-classic/integrations/travel-integrations/Global-VaTax.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/travel-integrations/Global-VaTax.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/Grab.md b/docs/articles/expensify-classic/integrations/travel-integrations/Grab.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/travel-integrations/Grab.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/Hotel-Tonight.md b/docs/articles/expensify-classic/integrations/travel-integrations/Hotel-Tonight.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/travel-integrations/Hotel-Tonight.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/Kayak.md b/docs/articles/expensify-classic/integrations/travel-integrations/Kayak.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/travel-integrations/Kayak.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/Lyft.md b/docs/articles/expensify-classic/integrations/travel-integrations/Lyft.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/travel-integrations/Lyft.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/TrainLine.md b/docs/articles/expensify-classic/integrations/travel-integrations/TrainLine.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/travel-integrations/TrainLine.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/TravelPerk.md b/docs/articles/expensify-classic/integrations/travel-integrations/TravelPerk.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/travel-integrations/TravelPerk.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/Trip-Actions.md b/docs/articles/expensify-classic/integrations/travel-integrations/Trip-Actions.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/travel-integrations/Trip-Actions.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/TripCatcher.md b/docs/articles/expensify-classic/integrations/travel-integrations/TripCatcher.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/travel-integrations/TripCatcher.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/Uber.md b/docs/articles/expensify-classic/integrations/travel-integrations/Uber.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/travel-integrations/Uber.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/manage-employees-and-report-approvals/Adding-Users.md b/docs/articles/expensify-classic/manage-employees-and-report-approvals/Adding-Users.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/manage-employees-and-report-approvals/Adding-Users.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/manage-employees-and-report-approvals/Approval-Workflows.md b/docs/articles/expensify-classic/manage-employees-and-report-approvals/Approval-Workflows.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/manage-employees-and-report-approvals/Approval-Workflows.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/manage-employees-and-report-approvals/Approving-Reports.md b/docs/articles/expensify-classic/manage-employees-and-report-approvals/Approving-Reports.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/manage-employees-and-report-approvals/Approving-Reports.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/manage-employees-and-report-approvals/User-Roles.md b/docs/articles/expensify-classic/manage-employees-and-report-approvals/User-Roles.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/manage-employees-and-report-approvals/User-Roles.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/manage-employees-and-report-approvals/Vacation-Delegate.md b/docs/articles/expensify-classic/manage-employees-and-report-approvals/Vacation-Delegate.md new file mode 100644 index 000000000000..e10e0fafb77d --- /dev/null +++ b/docs/articles/expensify-classic/manage-employees-and-report-approvals/Vacation-Delegate.md @@ -0,0 +1,8 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! + + +Kayak.md Lyft.md TrainLine.md TravelPerk.md Trip Actions.md TripCatcher.md Uber.md \ No newline at end of file diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/Admins.md b/docs/articles/expensify-classic/policy-and-domain-settings/Admins.md new file mode 100644 index 000000000000..8c1267068d6b --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/Admins.md @@ -0,0 +1,5 @@ +--- +title: Admins +description: Admins +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/Categories.md b/docs/articles/expensify-classic/policy-and-domain-settings/Categories.md new file mode 100644 index 000000000000..00ade2b9d04f --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/Categories.md @@ -0,0 +1,5 @@ +--- +title: Categories +description: Categories +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/Domain-Admins.md b/docs/articles/expensify-classic/policy-and-domain-settings/Domain-Admins.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/Domain-Admins.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/Domain-Members.md b/docs/articles/expensify-classic/policy-and-domain-settings/Domain-Members.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/Domain-Members.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/Domains-Overview.md b/docs/articles/expensify-classic/policy-and-domain-settings/Domains-Overview.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/Domains-Overview.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/Expenses.md b/docs/articles/expensify-classic/policy-and-domain-settings/Expenses.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/Expenses.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/Invoicing.md b/docs/articles/expensify-classic/policy-and-domain-settings/Invoicing.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/Invoicing.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/Overview.md b/docs/articles/expensify-classic/policy-and-domain-settings/Overview.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/Overview.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/Per-Diem.md b/docs/articles/expensify-classic/policy-and-domain-settings/Per-Diem.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/Per-Diem.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/Reimbursement.md b/docs/articles/expensify-classic/policy-and-domain-settings/Reimbursement.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/Reimbursement.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/Reports.md b/docs/articles/expensify-classic/policy-and-domain-settings/Reports.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/Reports.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/SAML.md b/docs/articles/expensify-classic/policy-and-domain-settings/SAML.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/SAML.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/Tags.md b/docs/articles/expensify-classic/policy-and-domain-settings/Tags.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/Tags.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/Tax.md b/docs/articles/expensify-classic/policy-and-domain-settings/Tax.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/Tax.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/Trips.md b/docs/articles/expensify-classic/policy-and-domain-settings/Trips.md new file mode 100644 index 000000000000..4c91b7095a4a --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/Trips.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/send-payments/Pay-Bills.md b/docs/articles/expensify-classic/send-payments/Pay-Bills.md new file mode 100644 index 000000000000..e319196eb4bd --- /dev/null +++ b/docs/articles/expensify-classic/send-payments/Pay-Bills.md @@ -0,0 +1,5 @@ +--- +title: Pay Bills +description: Pay Bills +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/send-payments/Pay-Invoices.md b/docs/articles/expensify-classic/send-payments/Pay-Invoices.md new file mode 100644 index 000000000000..0ea4d28a731a --- /dev/null +++ b/docs/articles/expensify-classic/send-payments/Pay-Invoices.md @@ -0,0 +1,5 @@ +--- +title: Pay Invoices +description: Pay Invoices +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/send-payments/Reimbursing-Reports.md b/docs/articles/expensify-classic/send-payments/Reimbursing-Reports.md new file mode 100644 index 000000000000..6c3309310ba8 --- /dev/null +++ b/docs/articles/expensify-classic/send-payments/Reimbursing-Reports.md @@ -0,0 +1,5 @@ +--- +title: Reimbursing Reports +description: Reimbursing Reports +--- +## Resources Coming Soon! diff --git a/docs/articles/expensify-classic/send-payments/Third-Party-Payments.md b/docs/articles/expensify-classic/send-payments/Third-Party-Payments.md new file mode 100644 index 000000000000..4b1166cc9c00 --- /dev/null +++ b/docs/articles/expensify-classic/send-payments/Third-Party-Payments.md @@ -0,0 +1,8 @@ +--- +title: Third Party Payments +description: Third Party Payments +--- +## Resources Coming Soon! + + + \ No newline at end of file diff --git a/docs/articles/new-expensify/account-settings/Coming-Soon.md b/docs/articles/new-expensify/account-settings/Coming-Soon.md new file mode 100644 index 000000000000..6b85bb0364b5 --- /dev/null +++ b/docs/articles/new-expensify/account-settings/Coming-Soon.md @@ -0,0 +1,4 @@ +--- +title: Coming Soon +description: Coming Soon +--- diff --git a/docs/articles/new-expensify/bank-accounts-and-credit-cards/Coming-Soon.md b/docs/articles/new-expensify/bank-accounts-and-credit-cards/Coming-Soon.md new file mode 100644 index 000000000000..6b85bb0364b5 --- /dev/null +++ b/docs/articles/new-expensify/bank-accounts-and-credit-cards/Coming-Soon.md @@ -0,0 +1,4 @@ +--- +title: Coming Soon +description: Coming Soon +--- diff --git a/docs/articles/split-bills/workspaces/The-Free-Plan.md b/docs/articles/new-expensify/billing-and-plan-types/The-Free-Plan.md similarity index 98% rename from docs/articles/split-bills/workspaces/The-Free-Plan.md rename to docs/articles/new-expensify/billing-and-plan-types/The-Free-Plan.md index 45c9d09d4777..0a8d6b3493e0 100644 --- a/docs/articles/split-bills/workspaces/The-Free-Plan.md +++ b/docs/articles/new-expensify/billing-and-plan-types/The-Free-Plan.md @@ -59,4 +59,4 @@ 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. +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 diff --git a/docs/articles/new-expensify/expense-and-report-features/Coming-Soon.md b/docs/articles/new-expensify/expense-and-report-features/Coming-Soon.md new file mode 100644 index 000000000000..6b85bb0364b5 --- /dev/null +++ b/docs/articles/new-expensify/expense-and-report-features/Coming-Soon.md @@ -0,0 +1,4 @@ +--- +title: Coming Soon +description: Coming Soon +--- diff --git a/docs/articles/new-expensify/expensify-card/Coming-Soon.md b/docs/articles/new-expensify/expensify-card/Coming-Soon.md new file mode 100644 index 000000000000..6b85bb0364b5 --- /dev/null +++ b/docs/articles/new-expensify/expensify-card/Coming-Soon.md @@ -0,0 +1,4 @@ +--- +title: Coming Soon +description: Coming Soon +--- diff --git a/docs/articles/new-expensify/exports/Coming-Soon.md b/docs/articles/new-expensify/exports/Coming-Soon.md new file mode 100644 index 000000000000..6b85bb0364b5 --- /dev/null +++ b/docs/articles/new-expensify/exports/Coming-Soon.md @@ -0,0 +1,4 @@ +--- +title: Coming Soon +description: Coming Soon +--- diff --git a/docs/articles/new-expensify/get-paid-back/Request-Money.md b/docs/articles/new-expensify/get-paid-back/Request-Money.md new file mode 100644 index 000000000000..55a3f3c8172e --- /dev/null +++ b/docs/articles/new-expensify/get-paid-back/Request-Money.md @@ -0,0 +1,5 @@ +--- +title: Request Money +description: Request Money +--- +## Resources Coming Soon! diff --git a/docs/articles/other/Expensify-Lounge.md b/docs/articles/new-expensify/getting-started/Expensify-Lounge.md similarity index 100% rename from docs/articles/other/Expensify-Lounge.md rename to docs/articles/new-expensify/getting-started/Expensify-Lounge.md diff --git a/docs/articles/new-expensify/getting-started/Referral-Program.md b/docs/articles/new-expensify/getting-started/Referral-Program.md new file mode 100644 index 000000000000..683e93d0277a --- /dev/null +++ b/docs/articles/new-expensify/getting-started/Referral-Program.md @@ -0,0 +1,53 @@ +--- +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/other/Everything-About-Chat.md b/docs/articles/new-expensify/getting-started/chat/Everything-About-Chat.md similarity index 99% rename from docs/articles/other/Everything-About-Chat.md rename to docs/articles/new-expensify/getting-started/chat/Everything-About-Chat.md index d52932daa5ff..9f73d1c759c2 100644 --- a/docs/articles/other/Everything-About-Chat.md +++ b/docs/articles/new-expensify/getting-started/chat/Everything-About-Chat.md @@ -83,5 +83,3 @@ You will receive a whisper from Concierge any time your content has been flagged ![Moderation Reporter Whisper](https://help.expensify.com/assets/images/moderation-reporter-whisper.png){:width="100%"} *Note: Any message sent in public chat rooms are automatically reviewed by an automated system looking for offensive content and sent to our moderators for final decisions if it is found.* - - diff --git a/docs/articles/other/Expensify-Chat-For-Admins.md b/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Admins.md similarity index 99% rename from docs/articles/other/Expensify-Chat-For-Admins.md rename to docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Admins.md index 247b2b0e03d0..31de150d5b5e 100644 --- a/docs/articles/other/Expensify-Chat-For-Admins.md +++ b/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Admins.md @@ -24,3 +24,4 @@ In order to get the most out of Expensify Chat, we created a list of best practi - The rooms will all stay open after the conference ends, so encourage speakers to keep engaging as long as the conversation is going in their session room. - Continue sharing photos and videos from the event or anything fun in #social as part of a wrap up for everyone. - Use the #announce room to give attendees a sneak preview of your next event. +- \ No newline at end of file diff --git a/docs/articles/other/Expensify-Chat-For-Conference-Attendees.md b/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Conference-Attendees.md similarity index 100% rename from docs/articles/other/Expensify-Chat-For-Conference-Attendees.md rename to docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Conference-Attendees.md diff --git a/docs/articles/other/Expensify-Chat-For-Conference-Speakers.md b/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Conference-Speakers.md similarity index 100% rename from docs/articles/other/Expensify-Chat-For-Conference-Speakers.md rename to docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Conference-Speakers.md diff --git a/docs/articles/playbooks/Expensify-Chat-Playbook-for-Conferences.md b/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-Playbook-For-Conferences.md similarity index 100% rename from docs/articles/playbooks/Expensify-Chat-Playbook-for-Conferences.md rename to docs/articles/new-expensify/getting-started/chat/Expensify-Chat-Playbook-For-Conferences.md diff --git a/docs/articles/new-expensify/integrations/accounting-integrations/QuickBooks-Online.md b/docs/articles/new-expensify/integrations/accounting-integrations/QuickBooks-Online.md new file mode 100644 index 000000000000..ed4d127d5c26 --- /dev/null +++ b/docs/articles/new-expensify/integrations/accounting-integrations/QuickBooks-Online.md @@ -0,0 +1,5 @@ +--- +title: QuickBooks Online +description: QuickBooks Online +--- +## Resources Coming Soon! diff --git a/docs/articles/new-expensify/manage-employees-and-report-approvals/Coming-Soon.md b/docs/articles/new-expensify/manage-employees-and-report-approvals/Coming-Soon.md new file mode 100644 index 000000000000..6b85bb0364b5 --- /dev/null +++ b/docs/articles/new-expensify/manage-employees-and-report-approvals/Coming-Soon.md @@ -0,0 +1,4 @@ +--- +title: Coming Soon +description: Coming Soon +--- diff --git a/docs/articles/new-expensify/send-payments/Coming-Soon.md b/docs/articles/new-expensify/send-payments/Coming-Soon.md new file mode 100644 index 000000000000..6b85bb0364b5 --- /dev/null +++ b/docs/articles/new-expensify/send-payments/Coming-Soon.md @@ -0,0 +1,4 @@ +--- +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 new file mode 100644 index 000000000000..6b85bb0364b5 --- /dev/null +++ b/docs/articles/new-expensify/workspace-and-domain-settings/Coming-Soon.md @@ -0,0 +1,4 @@ +--- +title: Coming Soon +description: Coming Soon +--- diff --git a/docs/articles/request-money/Request-and-Split-Bills.md b/docs/articles/request-money/Request-and-Split-Bills.md deleted file mode 100644 index bb27cd75c742..000000000000 --- a/docs/articles/request-money/Request-and-Split-Bills.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: Request Money and Split Bills with Friends -description: Everything you need to know about Requesting Money and Splitting Bills with Friends! ---- - - - -# 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** -- Select the relevant option: - - **Manual:** Enter the merchant and amount manually. - - **Scan:** Take a photo of the receipt to have the merchant and amount auto-filled. - - **Distance:** Enter the details of your trip, plus any stops along the way, and the mileage and amount will be automatically calculated. -- 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/split-bills/paying-friends/Request-and-Split-Bills.md b/docs/articles/split-bills/paying-friends/Request-and-Split-Bills.md deleted file mode 100644 index a2c63cf6f8f7..000000000000 --- a/docs/articles/split-bills/paying-friends/Request-and-Split-Bills.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -title: Request Money and Split Bills with Friends -description: Everything you need to know about Requesting Money and Splitting Bills with Friends! ---- - - - -# 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/assets/images/accounting.svg b/docs/assets/images/accounting.svg new file mode 100644 index 000000000000..4398e9573747 --- /dev/null +++ b/docs/assets/images/accounting.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/images/bank-card.svg b/docs/assets/images/bank-card.svg new file mode 100644 index 000000000000..48da9af0d986 --- /dev/null +++ b/docs/assets/images/bank-card.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/images/envelope-receipt.svg b/docs/assets/images/envelope-receipt.svg new file mode 100644 index 000000000000..40f57cc4ebda --- /dev/null +++ b/docs/assets/images/envelope-receipt.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/images/gears.svg b/docs/assets/images/gears.svg new file mode 100644 index 000000000000..23621afc8008 --- /dev/null +++ b/docs/assets/images/gears.svg @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/images/hand-card.svg b/docs/assets/images/hand-card.svg new file mode 100644 index 000000000000..779e6ff4184c --- /dev/null +++ b/docs/assets/images/hand-card.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/images/money-into-wallet.svg b/docs/assets/images/money-into-wallet.svg new file mode 100644 index 000000000000..d6d5b0e7d6e7 --- /dev/null +++ b/docs/assets/images/money-into-wallet.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/images/money-receipt.svg b/docs/assets/images/money-receipt.svg new file mode 100644 index 000000000000..379d56727e42 --- /dev/null +++ b/docs/assets/images/money-receipt.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/images/money-wings.svg b/docs/assets/images/money-wings.svg new file mode 100644 index 000000000000..c2155080f721 --- /dev/null +++ b/docs/assets/images/money-wings.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/images/monitor.svg b/docs/assets/images/monitor.svg new file mode 100644 index 000000000000..6e2580b4c9e8 --- /dev/null +++ b/docs/assets/images/monitor.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/docs/assets/images/workflow.svg b/docs/assets/images/workflow.svg new file mode 100644 index 000000000000..e5eac423cd1d --- /dev/null +++ b/docs/assets/images/workflow.svg @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/js/main.js b/docs/assets/js/main.js index f0f335536c20..aebd0f5d4864 100644 --- a/docs/assets/js/main.js +++ b/docs/assets/js/main.js @@ -58,10 +58,10 @@ function navigateBack() { return; } - const hubs = JSON.parse(document.getElementById('hubs-data').value); - const hubToNavigate = hubs.find((hub) => window.location.pathname.includes(hub)); // eslint-disable-line rulesdir/prefer-underscore-method - if (hubToNavigate) { - window.location.href = `/hubs/${hubToNavigate}`; + // Path name is of the form /articles/[platform]/[hub]/[resource] + const path = window.location.pathname.split('/'); + if (path[2] && path[3]) { + window.location.href = `/${path[2]}/hubs/${path[3]}`; } else { window.location.href = '/'; } diff --git a/docs/expensify-classic/hubs/account-settings.html b/docs/expensify-classic/hubs/account-settings.html new file mode 100644 index 000000000000..434761a6c4fa --- /dev/null +++ b/docs/expensify-classic/hubs/account-settings.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Account Settings +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/expensify-classic/hubs/bank-accounts-and-credit-cards.html b/docs/expensify-classic/hubs/bank-accounts-and-credit-cards.html new file mode 100644 index 000000000000..2f91f0913d6e --- /dev/null +++ b/docs/expensify-classic/hubs/bank-accounts-and-credit-cards.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Bank Accounts & Credit Cards +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/expensify-classic/hubs/billing-and-subscriptions.html b/docs/expensify-classic/hubs/billing-and-subscriptions.html new file mode 100644 index 000000000000..05dc38835b51 --- /dev/null +++ b/docs/expensify-classic/hubs/billing-and-subscriptions.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Billing & Subscriptions +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/expensify-classic/hubs/expense-and-report-features.html b/docs/expensify-classic/hubs/expense-and-report-features.html new file mode 100644 index 000000000000..44afa4b18b51 --- /dev/null +++ b/docs/expensify-classic/hubs/expense-and-report-features.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Expense & Report Settings +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/expensify-classic/hubs/expensify-card.html b/docs/expensify-classic/hubs/expensify-card.html new file mode 100644 index 000000000000..3afd8ac662e7 --- /dev/null +++ b/docs/expensify-classic/hubs/expensify-card.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Expensify Card +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/expensify-classic/hubs/exports.html b/docs/expensify-classic/hubs/exports.html new file mode 100644 index 000000000000..16c96cb51d01 --- /dev/null +++ b/docs/expensify-classic/hubs/exports.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Exports +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/expensify-classic/hubs/get-paid-back.html b/docs/expensify-classic/hubs/get-paid-back.html new file mode 100644 index 000000000000..1f84c1510b92 --- /dev/null +++ b/docs/expensify-classic/hubs/get-paid-back.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Get Paid Back +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/expensify-classic/hubs/getting-started.html b/docs/expensify-classic/hubs/getting-started.html new file mode 100644 index 000000000000..14ca13d0c2e8 --- /dev/null +++ b/docs/expensify-classic/hubs/getting-started.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Getting Started +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/expensify-classic/hubs/index.html b/docs/expensify-classic/hubs/index.html new file mode 100644 index 000000000000..05c7b52bfa2d --- /dev/null +++ b/docs/expensify-classic/hubs/index.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Expensify Classic +--- + +{% include platform.html %} \ No newline at end of file diff --git a/docs/expensify-classic/hubs/integrations.html b/docs/expensify-classic/hubs/integrations.html new file mode 100644 index 000000000000..d1f173534c8a --- /dev/null +++ b/docs/expensify-classic/hubs/integrations.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Integrations +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/expensify-classic/hubs/manage-employees-and-report-approvals.html b/docs/expensify-classic/hubs/manage-employees-and-report-approvals.html new file mode 100644 index 000000000000..788e445ebc91 --- /dev/null +++ b/docs/expensify-classic/hubs/manage-employees-and-report-approvals.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Manage Employees And Report Approvals +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/expensify-classic/hubs/policy-and-domain-settings.html b/docs/expensify-classic/hubs/policy-and-domain-settings.html new file mode 100644 index 000000000000..ffd514fcb6fa --- /dev/null +++ b/docs/expensify-classic/hubs/policy-and-domain-settings.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Policy And Domain Settings +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/expensify-classic/hubs/send-payments.html b/docs/expensify-classic/hubs/send-payments.html new file mode 100644 index 000000000000..c8275af5c353 --- /dev/null +++ b/docs/expensify-classic/hubs/send-payments.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Send Payments +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/hubs/other.html b/docs/hubs/other.html deleted file mode 100644 index 7d6d7f204062..000000000000 --- a/docs/hubs/other.html +++ /dev/null @@ -1,6 +0,0 @@ ---- -layout: default -title: Other ---- - -{% include hub.html %} diff --git a/docs/hubs/playbooks.html b/docs/hubs/playbooks.html deleted file mode 100644 index 0f15922fd061..000000000000 --- a/docs/hubs/playbooks.html +++ /dev/null @@ -1,6 +0,0 @@ ---- -layout: default -title: Playbooks ---- - -{% include hub.html %} diff --git a/docs/hubs/request-money.html b/docs/hubs/request-money.html deleted file mode 100644 index e3ef68f5c050..000000000000 --- a/docs/hubs/request-money.html +++ /dev/null @@ -1,6 +0,0 @@ ---- -layout: default -title: Request money ---- - -{% include hub.html %} diff --git a/docs/hubs/split-bills.html b/docs/hubs/split-bills.html deleted file mode 100644 index 6464ab62ef07..000000000000 --- a/docs/hubs/split-bills.html +++ /dev/null @@ -1,6 +0,0 @@ ---- -layout: default -title: Split bills ---- - -{% include hub.html %} diff --git a/docs/index.html b/docs/index.html index 74296c200971..70bd5f31545a 100644 --- a/docs/index.html +++ b/docs/index.html @@ -6,15 +6,11 @@

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

    {{ site.data.routes.home.description }}

    -

    - Which best describes how you use Expensify? -

    -
    - {% include hub-card.html href="split-bills" %} - {% include hub-card.html href="request-money" %} - {% include hub-card.html href="playbooks" %} - {% include hub-card.html href="other" %} + {% for platform in site.data.routes.platforms %} + {% assign platform_href = platform.href %} + {% include platform-card.html href=platform.platform_href %} + {% endfor %}
    diff --git a/docs/new-expensify/hubs/account-settings.html b/docs/new-expensify/hubs/account-settings.html new file mode 100644 index 000000000000..434761a6c4fa --- /dev/null +++ b/docs/new-expensify/hubs/account-settings.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Account Settings +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/new-expensify/hubs/bank-accounts-and-credit-cards.html b/docs/new-expensify/hubs/bank-accounts-and-credit-cards.html new file mode 100644 index 000000000000..2f91f0913d6e --- /dev/null +++ b/docs/new-expensify/hubs/bank-accounts-and-credit-cards.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Bank Accounts & Credit Cards +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/new-expensify/hubs/billing-and-plan-types.html b/docs/new-expensify/hubs/billing-and-plan-types.html new file mode 100644 index 000000000000..b49b2b62b1d6 --- /dev/null +++ b/docs/new-expensify/hubs/billing-and-plan-types.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Billing & Plan Types +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/new-expensify/hubs/expense-and-report-features.html b/docs/new-expensify/hubs/expense-and-report-features.html new file mode 100644 index 000000000000..0057ae0fa46c --- /dev/null +++ b/docs/new-expensify/hubs/expense-and-report-features.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Expense and Report Features +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/new-expensify/hubs/expensify-card.html b/docs/new-expensify/hubs/expensify-card.html new file mode 100644 index 000000000000..3afd8ac662e7 --- /dev/null +++ b/docs/new-expensify/hubs/expensify-card.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Expensify Card +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/new-expensify/hubs/exports.html b/docs/new-expensify/hubs/exports.html new file mode 100644 index 000000000000..16c96cb51d01 --- /dev/null +++ b/docs/new-expensify/hubs/exports.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Exports +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/new-expensify/hubs/get-paid-back.html b/docs/new-expensify/hubs/get-paid-back.html new file mode 100644 index 000000000000..1f84c1510b92 --- /dev/null +++ b/docs/new-expensify/hubs/get-paid-back.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Get Paid Back +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/new-expensify/hubs/getting-started.html b/docs/new-expensify/hubs/getting-started.html new file mode 100644 index 000000000000..14ca13d0c2e8 --- /dev/null +++ b/docs/new-expensify/hubs/getting-started.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Getting Started +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/new-expensify/hubs/index.html b/docs/new-expensify/hubs/index.html new file mode 100644 index 000000000000..de046b16e755 --- /dev/null +++ b/docs/new-expensify/hubs/index.html @@ -0,0 +1,6 @@ +--- +layout: default +title: New Expensify +--- + +{% include platform.html %} \ No newline at end of file diff --git a/docs/new-expensify/hubs/integrations.html b/docs/new-expensify/hubs/integrations.html new file mode 100644 index 000000000000..d1f173534c8a --- /dev/null +++ b/docs/new-expensify/hubs/integrations.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Integrations +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/new-expensify/hubs/manage-employees-and-report-approvals.html b/docs/new-expensify/hubs/manage-employees-and-report-approvals.html new file mode 100644 index 000000000000..31e992f32d5d --- /dev/null +++ b/docs/new-expensify/hubs/manage-employees-and-report-approvals.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Manage Employees & Report Approvals +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/new-expensify/hubs/send-payments.html b/docs/new-expensify/hubs/send-payments.html new file mode 100644 index 000000000000..c8275af5c353 --- /dev/null +++ b/docs/new-expensify/hubs/send-payments.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Send Payments +--- + +{% include hub.html %} \ No newline at end of file diff --git a/docs/new-expensify/hubs/workspace-and-domain-settings.html b/docs/new-expensify/hubs/workspace-and-domain-settings.html new file mode 100644 index 000000000000..c4e5d06d6b8a --- /dev/null +++ b/docs/new-expensify/hubs/workspace-and-domain-settings.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Workspace & Domain Settings +--- + +{% include hub.html %} \ No newline at end of file diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 441bd2feab92..0014eb50f5e8 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.3.72 + 1.3.73 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 1.3.72.9 + 1.3.73.0 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 27273c7f3866..256676c4de81 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.3.72 + 1.3.73 CFBundleSignature ???? CFBundleVersion - 1.3.72.9 + 1.3.73.0 diff --git a/package-lock.json b/package-lock.json index ff0500eb385b..68ac5f47123d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.3.72-9", + "version": "1.3.73-0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.3.72-9", + "version": "1.3.73-0", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -190,7 +190,7 @@ "electron-builder": "24.6.4", "eslint": "^7.6.0", "eslint-config-airbnb-typescript": "^17.1.0", - "eslint-config-expensify": "^2.0.38", + "eslint-config-expensify": "^2.0.39", "eslint-config-prettier": "^8.8.0", "eslint-plugin-jest": "^24.1.0", "eslint-plugin-jsdoc": "^46.2.6", @@ -26492,9 +26492,9 @@ } }, "node_modules/eslint-config-expensify": { - "version": "2.0.38", - "resolved": "https://registry.npmjs.org/eslint-config-expensify/-/eslint-config-expensify-2.0.38.tgz", - "integrity": "sha512-jAlrYSjkDV8YESUUPcaTjUM8Fgru+37FS+Hq6zzcRR0FbA5bLiOPguhJHo77XpYh5N+PEf4wrpgsS04sjdgDPg==", + "version": "2.0.39", + "resolved": "https://registry.npmjs.org/eslint-config-expensify/-/eslint-config-expensify-2.0.39.tgz", + "integrity": "sha512-DIxR3k99ZIDPE2NK+WLLRWpoDq06gTXdY8XZg9Etd1UqZ7fXm/Yz3/QkTxu7CH7UaXbCH3P4PTo023ULQGKOSw==", "dev": true, "dependencies": { "@lwc/eslint-plugin-lwc": "^0.11.0", @@ -67265,9 +67265,9 @@ } }, "eslint-config-expensify": { - "version": "2.0.38", - "resolved": "https://registry.npmjs.org/eslint-config-expensify/-/eslint-config-expensify-2.0.38.tgz", - "integrity": "sha512-jAlrYSjkDV8YESUUPcaTjUM8Fgru+37FS+Hq6zzcRR0FbA5bLiOPguhJHo77XpYh5N+PEf4wrpgsS04sjdgDPg==", + "version": "2.0.39", + "resolved": "https://registry.npmjs.org/eslint-config-expensify/-/eslint-config-expensify-2.0.39.tgz", + "integrity": "sha512-DIxR3k99ZIDPE2NK+WLLRWpoDq06gTXdY8XZg9Etd1UqZ7fXm/Yz3/QkTxu7CH7UaXbCH3P4PTo023ULQGKOSw==", "dev": true, "requires": { "@lwc/eslint-plugin-lwc": "^0.11.0", diff --git a/package.json b/package.json index 44b936c8c588..0f3420b6bb73 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.3.72-9", + "version": "1.3.73-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.", @@ -48,6 +48,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", + "gh-actions-unused-styles": "./.github/scripts/findUnusedKeys.sh", "workflow-test": "./workflow_tests/scripts/runWorkflowTests.sh", "workflow-test:generate": "node workflow_tests/utils/preGenerateTest.js" }, @@ -232,7 +233,7 @@ "electron-builder": "24.6.4", "eslint": "^7.6.0", "eslint-config-airbnb-typescript": "^17.1.0", - "eslint-config-expensify": "^2.0.38", + "eslint-config-expensify": "^2.0.39", "eslint-config-prettier": "^8.8.0", "eslint-plugin-jest": "^24.1.0", "eslint-plugin-jsdoc": "^46.2.6", diff --git a/src/CONST.ts b/src/CONST.ts index dcd5ac1a8db7..eed1b98ae551 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1358,6 +1358,7 @@ const CONST = { DATE: 'date', DESCRIPTION: 'description', MERCHANT: 'merchant', + CATEGORY: 'category', RECEIPT: 'receipt', }, FOOTER: { diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 05256f2b806c..8f95dff079fc 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -249,6 +249,7 @@ const ONYXKEYS = { REPORT_DRAFT_COMMENT_NUMBER_OF_LINES: 'reportDraftCommentNumberOfLines_', REPORT_IS_COMPOSER_FULL_SIZE: 'reportIsComposerFullSize_', REPORT_USER_IS_TYPING: 'reportUserIsTyping_', + REPORT_USER_IS_LEAVING_ROOM: 'reportUserIsLeavingRoom_', SECURITY_GROUP: 'securityGroup_', TRANSACTION: 'transactions_', @@ -386,6 +387,7 @@ type OnyxValues = { [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_LEAVING_ROOM]: boolean; [ONYXKEYS.COLLECTION.SECURITY_GROUP]: OnyxTypes.SecurityGroup; [ONYXKEYS.COLLECTION.TRANSACTION]: OnyxTypes.Transaction; [ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS]: OnyxTypes.RecentlyUsedTags; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 2c37116db395..feead4890114 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -139,8 +139,8 @@ export default { SEARCH: 'search', TEACHERS_UNITE: 'teachersunite', I_KNOW_A_TEACHER: 'teachersunite/i-know-a-teacher', - INTRO_SCHOOL_PRINCIPAL: 'teachersunite/intro-school-principal', I_AM_A_TEACHER: 'teachersunite/i-am-a-teacher', + INTRO_SCHOOL_PRINCIPAL: 'teachersunite/intro-school-principal', DETAILS: 'details', getDetailsRoute: (login: string) => `details?login=${encodeURIComponent(login)}`, PROFILE: 'a/:accountID', diff --git a/src/components/AvatarCropModal/AvatarCropModal.js b/src/components/AvatarCropModal/AvatarCropModal.js index baa958106f84..10e8a76f756d 100644 --- a/src/components/AvatarCropModal/AvatarCropModal.js +++ b/src/components/AvatarCropModal/AvatarCropModal.js @@ -366,6 +366,7 @@ function AvatarCropModal(props) { style={[styles.pb0]} includePaddingTop={false} includeSafeAreaPaddingBottom={false} + testID={AvatarCropModal.displayName} > {props.isSmallScreenWidth && } { - if (!iou.category) { + if (!selectedCategory) { return []; } return [ { - name: iou.category, + name: selectedCategory, enabled: true, accountID: null, }, ]; - }, [iou.category]); + }, [selectedCategory]); const initialFocusedIndex = useMemo(() => { if (isCategoriesCountBelowThreshold && selectedOptions.length > 0) { @@ -53,20 +50,6 @@ function CategoryPicker({policyCategories, reportID, iouType, iou, policyRecentl const headerMessage = OptionsListUtils.getHeaderMessage(lodashGet(sections, '[0].data.length', 0) > 0, false, searchValue); const shouldShowTextInput = !isCategoriesCountBelowThreshold; - const navigateBack = () => { - Navigation.goBack(ROUTES.getMoneyRequestConfirmationRoute(iouType, reportID)); - }; - - const updateCategory = (category) => { - if (category.searchText === iou.category) { - IOU.resetMoneyRequestCategory(); - } else { - IOU.setMoneyRequestCategory(category.searchText); - } - - navigateBack(); - }; - return ( ); } @@ -97,7 +80,4 @@ export default withOnyx({ policyRecentlyUsedCategories: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES}${policyID}`, }, - iou: { - key: ONYXKEYS.IOU, - }, })(CategoryPicker); diff --git a/src/components/ContextMenuItem.js b/src/components/ContextMenuItem.js index c7ca3f948da4..2314f2fcf64e 100644 --- a/src/components/ContextMenuItem.js +++ b/src/components/ContextMenuItem.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {forwardRef, useImperativeHandle} from 'react'; import PropTypes from 'prop-types'; import MenuItem from './MenuItem'; import Icon from './Icon'; @@ -34,6 +34,12 @@ const propTypes = { /** The action accept for anonymous user or not */ isAnonymousAction: PropTypes.bool, + + /** Whether the menu item is focused or not */ + isFocused: PropTypes.bool, + + /** Forwarded ref to ContextMenuItem */ + innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), }; const defaultProps = { @@ -42,9 +48,11 @@ const defaultProps = { successText: '', description: '', isAnonymousAction: false, + isFocused: false, + innerRef: null, }; -function ContextMenuItem({onPress, successIcon, successText, icon, text, isMini, description, isAnonymousAction}) { +function ContextMenuItem({onPress, successIcon, successText, icon, text, isMini, description, isAnonymousAction, isFocused, innerRef}) { const {windowWidth} = useWindowDimensions(); const [isThrottledButtonActive, setThrottledButtonInactive] = useThrottledButtonState(); @@ -61,6 +69,8 @@ function ContextMenuItem({onPress, successIcon, successText, icon, text, isMini, } }; + useImperativeHandle(innerRef, () => ({triggerPressAndUpdateSuccess})); + const itemIcon = !isThrottledButtonActive && successIcon ? successIcon : icon; const itemText = !isThrottledButtonActive && successText ? successText : text; @@ -89,6 +99,7 @@ function ContextMenuItem({onPress, successIcon, successText, icon, text, isMini, descriptionTextStyle={styles.breakAll} style={getContextMenuItemStyles(windowWidth)} isAnonymousAction={isAnonymousAction} + focused={isFocused} /> ); } @@ -97,4 +108,10 @@ ContextMenuItem.propTypes = propTypes; ContextMenuItem.defaultProps = defaultProps; ContextMenuItem.displayName = 'ContextMenuItem'; -export default ContextMenuItem; +export default forwardRef((props, ref) => ( + +)); diff --git a/src/components/CountryPicker/CountrySelectorModal.js b/src/components/CountryPicker/CountrySelectorModal.js index ac543d9921d2..6c6cd19af0c7 100644 --- a/src/components/CountryPicker/CountrySelectorModal.js +++ b/src/components/CountryPicker/CountrySelectorModal.js @@ -78,6 +78,7 @@ function CountrySelectorModal({currentCountry, isVisible, onClose, onCountrySele style={[styles.pb0]} includePaddingTop={false} includeSafeAreaPaddingBottom={false} + testID={CountrySelectorModal.displayName} > {({safeAreaPaddingBottomStyle}) => ( diff --git a/src/components/HeaderPageLayout.js b/src/components/HeaderPageLayout.js index bec1e52b1cad..17c2255593e9 100644 --- a/src/components/HeaderPageLayout.js +++ b/src/components/HeaderPageLayout.js @@ -58,6 +58,7 @@ function HeaderPageLayout({backgroundColor, children, footer, headerContainerSty shouldEnablePickerAvoiding={false} includeSafeAreaPaddingBottom={false} offlineIndicatorStyle={[appBGColor]} + testID={HeaderPageLayout.displayName} > {({safeAreaPaddingBottomStyle}) => ( <> diff --git a/src/components/HeaderWithBackButton/index.js b/src/components/HeaderWithBackButton/index.js index aab54612e206..7bcd57385d5f 100755 --- a/src/components/HeaderWithBackButton/index.js +++ b/src/components/HeaderWithBackButton/index.js @@ -47,6 +47,7 @@ function HeaderWithBackButton({ }, threeDotsMenuItems = [], children = null, + onModalHide = () => {}, shouldOverlay = false, }) { const [isDownloadButtonActive, temporarilyDisableDownloadButton] = useThrottledButtonState(); @@ -138,6 +139,7 @@ function HeaderWithBackButton({ menuItems={threeDotsMenuItems} onIconPress={onThreeDotsButtonPress} anchorPosition={threeDotsAnchorPosition} + onModalHide={onModalHide} shouldOverlay={shouldOverlay} /> )} diff --git a/src/components/Hoverable/index.js b/src/components/Hoverable/index.js index 7dd918f15cf4..38ea64952a2c 100644 --- a/src/components/Hoverable/index.js +++ b/src/components/Hoverable/index.js @@ -46,7 +46,6 @@ class Hoverable extends Component { * If the user has started scrolling and the isHoveredRef is true, then we should set the hover state to false. * This is to hide the existing hover and reaction bar. */ - this.isHoveredRef = false; this.setState({isHovered: false}, this.props.onHoverOut); } this.isScrollingRef = scrolling; diff --git a/src/components/InvertedFlatList/BaseInvertedFlatList.js b/src/components/InvertedFlatList/BaseInvertedFlatList.js index d3fcda0ea5fd..0bfffb733052 100644 --- a/src/components/InvertedFlatList/BaseInvertedFlatList.js +++ b/src/components/InvertedFlatList/BaseInvertedFlatList.js @@ -136,8 +136,6 @@ class BaseInvertedFlatList extends Component { // Native platforms do not need to measure items and work fine without this. // Web requires that items be measured or else crazy things happen when scrolling. getItemLayout={this.props.shouldMeasureItems ? this.getItemLayout : undefined} - // We keep this property very low so that chat switching remains fast - maxToRenderPerBatch={1} windowSize={15} // Commenting the line below as it breaks the unread indicator test diff --git a/src/components/InvertedFlatList/index.js b/src/components/InvertedFlatList/index.js index 74409e9a0fe0..d46cd5801605 100644 --- a/src/components/InvertedFlatList/index.js +++ b/src/components/InvertedFlatList/index.js @@ -119,6 +119,9 @@ function InvertedFlatList(props) { shouldMeasureItems contentContainerStyle={StyleSheet.compose(contentContainerStyle, styles.justifyContentEnd)} onScroll={handleScroll} + // We need to keep batch size to one to workaround a bug in react-native-web. + // This can be removed once https://github.com/Expensify/App/pull/24482 is merged. + maxToRenderPerBatch={1} /> ); } diff --git a/src/components/LHNOptionsList/OptionRowLHNData.js b/src/components/LHNOptionsList/OptionRowLHNData.js index 2c51d6332946..4eebdd387fab 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.js +++ b/src/components/LHNOptionsList/OptionRowLHNData.js @@ -195,6 +195,7 @@ export default React.memo( key: ONYXKEYS.NVP_PREFERRED_LOCALE, }, }), + // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file withOnyx({ parentReportActions: { key: ({fullReport}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${fullReport.parentReportID}`, @@ -209,6 +210,7 @@ export default React.memo( // However, performance overhead of this is minimized by using memos inside the component. receiptTransactions: {key: ONYXKEYS.COLLECTION.TRANSACTION}, }), + // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file withOnyx({ transaction: { key: ({fullReport, parentReportActions}) => diff --git a/src/components/MapView/MapView.tsx b/src/components/MapView/MapView.tsx index 7a2248ffafb9..d9f51e111a43 100644 --- a/src/components/MapView/MapView.tsx +++ b/src/components/MapView/MapView.tsx @@ -54,7 +54,9 @@ const MapView = forwardRef(({accessToken, style, ma }, [accessToken]); const setMapIdle = (e: MapState) => { - if (e.gestures.isGestureActive) return; + if (e.gestures.isGestureActive) { + return; + } setIsIdle(true); if (onMapReady) { onMapReady(); diff --git a/src/components/MenuItem.js b/src/components/MenuItem.js index bb4eeb7a18ac..268351699567 100644 --- a/src/components/MenuItem.js +++ b/src/components/MenuItem.js @@ -24,11 +24,7 @@ import variables from '../styles/variables'; import * as Session from '../libs/actions/Session'; import Hoverable from './Hoverable'; import useWindowDimensions from '../hooks/useWindowDimensions'; -import RenderHTML from './RenderHTML'; -import getPlatform from '../libs/getPlatform'; - -const platform = getPlatform(); -const isNative = platform === CONST.PLATFORM.IOS || platform === CONST.PLATFORM.ANDROID; +import MenuItemRenderHTMLTitle from './MenuItemRenderHTMLTitle'; const propTypes = menuItemPropTypes; @@ -251,16 +247,10 @@ const MenuItem = React.forwardRef((props, ref) => { )} - {Boolean(props.title) && - (Boolean(props.shouldRenderAsHTML) || (Boolean(props.shouldParseTitle) && Boolean(html.length))) && - (isNative ? ( - - ) : ( - - - - ))} - {!props.shouldRenderAsHTML && !html.length && Boolean(props.title) && ( + {Boolean(props.title) && (Boolean(props.shouldRenderAsHTML) || (Boolean(props.shouldParseTitle) && Boolean(html.length))) && ( + + )} + {!props.shouldRenderAsHTML && !props.shouldParseTitle && Boolean(props.title) && ( + + + ); +} + +MenuItemRenderHTMLTitle.propTypes = propTypes; +MenuItemRenderHTMLTitle.defaultProps = defaultProps; +MenuItemRenderHTMLTitle.displayName = 'MenuItemRenderHTMLTitle'; + +export default MenuItemRenderHTMLTitle; diff --git a/src/components/MenuItemRenderHTMLTitle/index.native.js b/src/components/MenuItemRenderHTMLTitle/index.native.js new file mode 100644 index 000000000000..b3dff8d77eff --- /dev/null +++ b/src/components/MenuItemRenderHTMLTitle/index.native.js @@ -0,0 +1,17 @@ +import React from 'react'; +import RenderHTML from '../RenderHTML'; +import menuItemRenderHTMLTitlePropTypes from './propTypes'; + +const propTypes = menuItemRenderHTMLTitlePropTypes; + +const defaultProps = {}; + +function MenuItemRenderHTMLTitle(props) { + return ; +} + +MenuItemRenderHTMLTitle.propTypes = propTypes; +MenuItemRenderHTMLTitle.defaultProps = defaultProps; +MenuItemRenderHTMLTitle.displayName = 'MenuItemRenderHTMLTitle'; + +export default MenuItemRenderHTMLTitle; diff --git a/src/components/MenuItemRenderHTMLTitle/propTypes.js b/src/components/MenuItemRenderHTMLTitle/propTypes.js new file mode 100644 index 000000000000..68e279eb28c3 --- /dev/null +++ b/src/components/MenuItemRenderHTMLTitle/propTypes.js @@ -0,0 +1,8 @@ +import PropTypes from 'prop-types'; + +const propTypes = { + /** Processed title to display for the MenuItem */ + title: PropTypes.string.isRequired, +}; + +export default propTypes; diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index 4703ca099c7c..13471407914f 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -194,14 +194,23 @@ function MoneyRequestConfirmationList(props) { const {unit, rate, currency} = props.mileageRate; const distance = lodashGet(transaction, 'routes.route0.distance', 0); const shouldCalculateDistanceAmount = props.isDistanceRequest && props.iouAmount === 0; - const shouldCategoryBeEditable = !_.isEmpty(props.policyCategories) && Permissions.canUseCategories(props.betas); + + // A flag for verifying that the current report is a sub-report of a workspace chat + const isPolicyExpenseChat = useMemo(() => ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(ReportUtils.getReport(props.reportID))), [props.reportID]); + + // A flag for showing the categories field + const shouldShowCategories = isPolicyExpenseChat && Permissions.canUseCategories(props.betas) && OptionsListUtils.hasEnabledOptions(_.values(props.policyCategories)); // Fetches the first tag list of the policy const tagListKey = _.first(_.keys(props.policyTags)); const tagList = lodashGet(props.policyTags, [tagListKey, 'tags'], []); const tagListName = lodashGet(props.policyTags, [tagListKey, 'name'], ''); const canUseTags = Permissions.canUseTags(props.betas); - const shouldShowTags = canUseTags && _.any(tagList, (tag) => tag.enabled); + // A flag for showing the tags field + const shouldShowTags = isPolicyExpenseChat && canUseTags && _.any(tagList, (tag) => tag.enabled); + + // A flag for showing the billable field + const shouldShowBillable = canUseTags && !lodashGet(props.policy, 'disabledFields.defaultBillable', true); const hasRoute = TransactionUtils.hasRoute(transaction); const isDistanceRequestWithoutRoute = props.isDistanceRequest && !hasRoute; @@ -518,7 +527,7 @@ function MoneyRequestConfirmationList(props) { disabled={didConfirm || props.isReadOnly || !isTypeRequest} /> )} - {shouldCategoryBeEditable && ( + {shouldShowCategories && ( )} - {canUseTags && !lodashGet(props.policy, 'disabledFields.defaultBillable', true) && ( + {shouldShowBillable && ( {translate('common.billable')} {}, withoutOverlay: false, + onModalHide: () => {}, }; function PopoverMenu(props) { @@ -78,6 +82,7 @@ function PopoverMenu(props) { isVisible={props.isVisible} onModalHide={() => { setFocusedIndex(-1); + props.onModalHide(); if (selectedItemIndex.current !== null) { props.menuItems[selectedItemIndex.current].onSelected(); selectedItemIndex.current = null; diff --git a/src/components/ReimbursementAccountLoadingIndicator.js b/src/components/ReimbursementAccountLoadingIndicator.js index 61a602be0bda..21aac35f4005 100644 --- a/src/components/ReimbursementAccountLoadingIndicator.js +++ b/src/components/ReimbursementAccountLoadingIndicator.js @@ -4,14 +4,12 @@ import PropTypes from 'prop-types'; import Lottie from 'lottie-react-native'; import * as LottieAnimations from './LottieAnimations'; import styles from '../styles/styles'; -import withLocalize, {withLocalizePropTypes} from './withLocalize'; +import useLocalize from '../hooks/useLocalize'; import Text from './Text'; import HeaderWithBackButton from './HeaderWithBackButton'; import ScreenWrapper from './ScreenWrapper'; import FullScreenLoadingIndicator from './FullscreenLoadingIndicator'; import FullPageOfflineBlockingView from './BlockingViews/FullPageOfflineBlockingView'; -import compose from '../libs/compose'; -import {withNetwork} from './OnyxProvider'; const propTypes = { /** Whether the user is submitting verifications data */ @@ -19,17 +17,18 @@ const propTypes = { /** Method to trigger when pressing back button of the header */ onBackButtonPress: PropTypes.func.isRequired, - ...withLocalizePropTypes, }; function ReimbursementAccountLoadingIndicator(props) { + const {translate} = useLocalize(); return ( @@ -42,7 +41,7 @@ function ReimbursementAccountLoadingIndicator(props) { style={styles.loadingVBAAnimation} /> - {props.translate('reimbursementAccountLoadingAnimation.explanationLine')} + {translate('reimbursementAccountLoadingAnimation.explanationLine')} ) : ( @@ -56,4 +55,4 @@ function ReimbursementAccountLoadingIndicator(props) { ReimbursementAccountLoadingIndicator.propTypes = propTypes; ReimbursementAccountLoadingIndicator.displayName = 'ReimbursementAccountLoadingIndicator'; -export default compose(withLocalize, withNetwork())(ReimbursementAccountLoadingIndicator); +export default ReimbursementAccountLoadingIndicator; diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index 2afc6240f85d..c0dde899ad8e 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -23,6 +23,7 @@ import * as DeviceCapabilities from '../../libs/DeviceCapabilities'; import reportActionPropTypes from '../../pages/home/report/reportActionPropTypes'; import {showContextMenuForReport} from '../ShowContextMenuContext'; import * as OptionsListUtils from '../../libs/OptionsListUtils'; +import * as ReportActionsUtils from '../../libs/ReportActionsUtils'; import * as CurrencyUtils from '../../libs/CurrencyUtils'; import * as IOUUtils from '../../libs/IOUUtils'; import * as ReportUtils from '../../libs/ReportUtils'; @@ -239,7 +240,9 @@ function MoneyRequestPreview(props) { isHovered={isScanning} /> )} - {_.isEmpty(props.transaction) ? ( + {_.isEmpty(props.transaction) && + !ReportActionsUtils.isMessageDeleted(props.action) && + lodashGet(props.action, 'pendingAction') !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE ? ( ) : ( diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index d8b15653d8af..178cab75a0c2 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -1,7 +1,8 @@ -import React from 'react'; +import React, {useMemo} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import lodashGet from 'lodash/get'; +import lodashValues from 'lodash/values'; import PropTypes from 'prop-types'; import reportPropTypes from '../../pages/reportPropTypes'; import ONYXKEYS from '../../ONYXKEYS'; @@ -9,9 +10,11 @@ import ROUTES from '../../ROUTES'; import Navigation from '../../libs/Navigation/Navigation'; import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsPropTypes} from '../withCurrentUserPersonalDetails'; import compose from '../../libs/compose'; +import Permissions from '../../libs/Permissions'; import MenuItemWithTopDescription from '../MenuItemWithTopDescription'; import styles from '../../styles/styles'; import * as ReportUtils from '../../libs/ReportUtils'; +import * as OptionsListUtils from '../../libs/OptionsListUtils'; import * as ReportActionsUtils from '../../libs/ReportActionsUtils'; import * as StyleUtils from '../../styles/StyleUtils'; import CONST from '../../CONST'; @@ -27,26 +30,36 @@ import Image from '../Image'; import ReportActionItemImage from './ReportActionItemImage'; import * as TransactionUtils from '../../libs/TransactionUtils'; import OfflineWithFeedback from '../OfflineWithFeedback'; +import categoryPropTypes from '../categoryPropTypes'; import SpacerView from '../SpacerView'; const propTypes = { /** The report currently being looked at */ report: reportPropTypes.isRequired, + /** Whether we should display the horizontal rule below the component */ + shouldShowHorizontalRule: PropTypes.bool.isRequired, + + /* Onyx Props */ + /** List of betas available to current user */ + betas: PropTypes.arrayOf(PropTypes.string), + /** The expense report or iou report (only will have a value if this is a transaction thread) */ parentReport: iouReportPropTypes, + /** Collection of categories attached to a policy */ + policyCategories: PropTypes.objectOf(categoryPropTypes), + /** The transaction associated with the transactionThread */ transaction: transactionPropTypes, - /** Whether we should display the horizontal rule below the component */ - shouldShowHorizontalRule: PropTypes.bool.isRequired, - ...withCurrentUserPersonalDetailsPropTypes, }; const defaultProps = { + betas: [], parentReport: {}, + policyCategories: {}, transaction: { amount: 0, currency: CONST.CURRENCY.USD, @@ -54,7 +67,7 @@ const defaultProps = { }, }; -function MoneyRequestView({report, parentReport, shouldShowHorizontalRule, transaction}) { +function MoneyRequestView({betas, report, parentReport, policyCategories, shouldShowHorizontalRule, transaction}) { const {isSmallScreenWidth} = useWindowDimensions(); const {translate} = useLocalize(); @@ -66,6 +79,7 @@ function MoneyRequestView({report, parentReport, shouldShowHorizontalRule, trans currency: transactionCurrency, comment: transactionDescription, merchant: transactionMerchant, + category: transactionCategory, } = ReportUtils.getTransactionDetails(transaction); const isEmptyMerchant = transactionMerchant === '' || transactionMerchant === CONST.TRANSACTION.UNKNOWN_MERCHANT || transactionMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; @@ -73,6 +87,10 @@ function MoneyRequestView({report, parentReport, shouldShowHorizontalRule, trans const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID); const canEdit = ReportUtils.canEditMoneyRequest(parentReportAction); + // A flag for verifying that the current report is a sub-report of a workspace chat + const isPolicyExpenseChat = useMemo(() => ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(report)), [report]); + // A flag for showing categories + const shouldShowCategory = isPolicyExpenseChat && Permissions.canUseCategories(betas) && (transactionCategory || OptionsListUtils.hasEnabledOptions(lodashValues(policyCategories))); let description = `${translate('iou.amount')} • ${translate('iou.cash')}`; if (isSettled) { @@ -170,6 +188,18 @@ function MoneyRequestView({report, parentReport, shouldShowHorizontalRule, trans subtitleTextStyle={styles.textLabelError} /> + {shouldShowCategory && ( + + Navigation.navigate(ROUTES.getEditRequestRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.CATEGORY))} + /> + + )} `${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`, }, policy: { key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`, }, + policyCategories: { + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${report.policyID}`, + }, session: { key: ONYXKEYS.SESSION, }, diff --git a/src/components/ScreenWrapper/index.js b/src/components/ScreenWrapper/index.js index f760e5d5aeb4..1cad1e96b26d 100644 --- a/src/components/ScreenWrapper/index.js +++ b/src/components/ScreenWrapper/index.js @@ -109,6 +109,7 @@ class ScreenWrapper extends React.Component { style={styles.flex1} // eslint-disable-next-line react/jsx-props-no-spreading {...(this.props.environment === CONST.ENVIRONMENT.DEV ? this.panResponder.panHandlers : {})} + testID={this.props.testID} > { - if (!props.disableKeyboard) { - return; - } - - const appStateSubscription = AppState.addEventListener('change', (nextAppState) => { - if (!nextAppState.match(/inactive|background/)) { - return; - } - - Keyboard.dismiss(); - }); - - return () => { - appStateSubscription.remove(); - }; - }, [props.disableKeyboard]); - // AutoFocus which only works on mount: useEffect(() => { // We are manually managing focus to prevent this issue: https://github.com/Expensify/App/issues/4514 diff --git a/src/components/TextInput/index.native.js b/src/components/TextInput/index.native.js index eb9970f2261f..059550855c0a 100644 --- a/src/components/TextInput/index.native.js +++ b/src/components/TextInput/index.native.js @@ -1,19 +1,40 @@ -import React, {forwardRef} from 'react'; +import React, {forwardRef, useEffect} from 'react'; +import {AppState, Keyboard} from 'react-native'; import styles from '../../styles/styles'; import BaseTextInput from './BaseTextInput'; import * as baseTextInputPropTypes from './baseTextInputPropTypes'; -const TextInput = forwardRef((props, ref) => ( - -)); +const TextInput = forwardRef((props, ref) => { + useEffect(() => { + if (!props.disableKeyboard) { + return; + } + + const appStateSubscription = AppState.addEventListener('change', (nextAppState) => { + if (!nextAppState.match(/inactive|background/)) { + return; + } + + Keyboard.dismiss(); + }); + + return () => { + appStateSubscription.remove(); + }; + }, [props.disableKeyboard]); + + return ( + + ); +}); TextInput.propTypes = baseTextInputPropTypes.propTypes; TextInput.defaultProps = baseTextInputPropTypes.defaultProps; diff --git a/src/components/ThreeDotsMenu/index.js b/src/components/ThreeDotsMenu/index.js index f0cee6fdea2f..5e22a74fa37e 100644 --- a/src/components/ThreeDotsMenu/index.js +++ b/src/components/ThreeDotsMenu/index.js @@ -1,5 +1,5 @@ import React, {useState, useRef} from 'react'; -import {View} from 'react-native'; +import {InteractionManager, View} from 'react-native'; import PropTypes from 'prop-types'; import _ from 'underscore'; import Icon from '../Icon'; @@ -46,6 +46,9 @@ const propTypes = { vertical: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_VERTICAL)), }), + /** Function to call on modal hide */ + onModalHide: PropTypes.func, + /** Whether the popover menu should overlay the current view */ shouldOverlay: PropTypes.bool, }; @@ -60,10 +63,11 @@ const defaultProps = { horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP, // we assume that popover menu opens below the button, anchor is at TOP }, + onModalHide: () => {}, shouldOverlay: false, }; -function ThreeDotsMenu({iconTooltip, icon, iconFill, iconStyles, onIconPress, menuItems, anchorPosition, anchorAlignment, shouldOverlay}) { +function ThreeDotsMenu({iconTooltip, icon, iconFill, iconStyles, onIconPress, menuItems, anchorPosition, anchorAlignment, onModalHide, shouldOverlay}) { const [isPopupMenuVisible, setPopupMenuVisible] = useState(false); const buttonRef = useRef(null); const {translate} = useLocalize(); @@ -73,6 +77,9 @@ function ThreeDotsMenu({iconTooltip, icon, iconFill, iconStyles, onIconPress, me }; const hidePopoverMenu = () => { + InteractionManager.runAfterInteractions(() => { + onModalHide(); + }); setPopupMenuVisible(false); }; @@ -105,6 +112,7 @@ function ThreeDotsMenu({iconTooltip, icon, iconFill, iconStyles, onIconPress, me { Log.info('[BootSplash] hiding splash screen', false); return BootSplash.hide(); } diff --git a/src/libs/BootSplash/index.js b/src/libs/BootSplash/index.ts similarity index 62% rename from src/libs/BootSplash/index.js rename to src/libs/BootSplash/index.ts index c169f380a8eb..24842fe631f4 100644 --- a/src/libs/BootSplash/index.js +++ b/src/libs/BootSplash/index.ts @@ -1,20 +1,21 @@ import Log from '../Log'; +import {VisibilityStatus} from './types'; -function resolveAfter(delay) { - return new Promise((resolve) => setTimeout(resolve, delay)); +function resolveAfter(delay: number): Promise { + return new Promise((resolve) => setTimeout(resolve, delay)); } -function hide() { +function hide(): Promise { Log.info('[BootSplash] hiding splash screen', false); return document.fonts.ready.then(() => { const splash = document.getElementById('splash'); if (splash) { - splash.style.opacity = 0; + splash.style.opacity = '0'; } return resolveAfter(250).then(() => { - if (!splash || !splash.parentNode) { + if (!splash?.parentNode) { return; } splash.parentNode.removeChild(splash); @@ -22,7 +23,7 @@ function hide() { }); } -function getVisibilityStatus() { +function getVisibilityStatus(): Promise { return Promise.resolve(document.getElementById('splash') ? 'visible' : 'hidden'); } diff --git a/src/libs/BootSplash/types.ts b/src/libs/BootSplash/types.ts new file mode 100644 index 000000000000..2329d5315817 --- /dev/null +++ b/src/libs/BootSplash/types.ts @@ -0,0 +1,9 @@ +type VisibilityStatus = 'visible' | 'hidden'; + +type BootSplashModule = { + navigationBarHeight: number; + hide: () => Promise; + getVisibilityStatus: () => Promise; +}; + +export type {BootSplashModule, VisibilityStatus}; diff --git a/src/libs/DeviceCapabilities/canUseTouchScreen/index.native.js b/src/libs/DeviceCapabilities/canUseTouchScreen/index.native.js deleted file mode 100644 index 4306b0cff3f6..000000000000 --- a/src/libs/DeviceCapabilities/canUseTouchScreen/index.native.js +++ /dev/null @@ -1,5 +0,0 @@ -function canUseTouchScreen() { - return true; -} - -export default canUseTouchScreen; diff --git a/src/libs/DeviceCapabilities/canUseTouchScreen/index.native.ts b/src/libs/DeviceCapabilities/canUseTouchScreen/index.native.ts new file mode 100644 index 000000000000..60980801e73c --- /dev/null +++ b/src/libs/DeviceCapabilities/canUseTouchScreen/index.native.ts @@ -0,0 +1,5 @@ +import CanUseTouchScreen from './types'; + +const canUseTouchScreen: CanUseTouchScreen = () => true; + +export default canUseTouchScreen; diff --git a/src/libs/DeviceCapabilities/canUseTouchScreen/index.js b/src/libs/DeviceCapabilities/canUseTouchScreen/index.ts similarity index 51% rename from src/libs/DeviceCapabilities/canUseTouchScreen/index.js rename to src/libs/DeviceCapabilities/canUseTouchScreen/index.ts index 17dcc9dffd73..9e21f5a42b5d 100644 --- a/src/libs/DeviceCapabilities/canUseTouchScreen/index.js +++ b/src/libs/DeviceCapabilities/canUseTouchScreen/index.ts @@ -1,29 +1,38 @@ +import {Merge} from 'type-fest'; +import CanUseTouchScreen from './types'; + +type ExtendedNavigator = Merge; + /** * Allows us to identify whether the platform has a touchscreen. * * https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent - * - * @returns {Boolean} */ -function canUseTouchScreen() { +const canUseTouchScreen: CanUseTouchScreen = () => { let hasTouchScreen = false; + + // TypeScript removed support for msMaxTouchPoints, this doesn't mean however that + // this property doesn't exist - hence the use of ExtendedNavigator to ensure + // that the functionality doesn't change + // https://github.com/microsoft/TypeScript-DOM-lib-generator/issues/1029 if ('maxTouchPoints' in navigator) { hasTouchScreen = navigator.maxTouchPoints > 0; } else if ('msMaxTouchPoints' in navigator) { - hasTouchScreen = navigator.msMaxTouchPoints > 0; + hasTouchScreen = (navigator as ExtendedNavigator).msMaxTouchPoints > 0; } else { - const mQ = window.matchMedia && matchMedia('(pointer:coarse)'); + // Same case as for Navigator - TypeScript thinks that matchMedia is obligatory property of window although it may not be + const mQ = window.matchMedia?.('(pointer:coarse)'); if (mQ && mQ.media === '(pointer:coarse)') { hasTouchScreen = !!mQ.matches; } else if ('orientation' in window) { hasTouchScreen = true; // deprecated, but good fallback } else { // Only as a last resort, fall back to user agent sniffing - const UA = navigator.userAgent; + const UA = (navigator as ExtendedNavigator).userAgent; hasTouchScreen = /\b(BlackBerry|webOS|iPhone|IEMobile)\b/i.test(UA) || /\b(Android|Windows Phone|iPad|iPod)\b/i.test(UA); } } return hasTouchScreen; -} +}; export default canUseTouchScreen; diff --git a/src/libs/DeviceCapabilities/canUseTouchScreen/types.ts b/src/libs/DeviceCapabilities/canUseTouchScreen/types.ts new file mode 100644 index 000000000000..6b71ecffeb05 --- /dev/null +++ b/src/libs/DeviceCapabilities/canUseTouchScreen/types.ts @@ -0,0 +1,3 @@ +type CanUseTouchScreen = () => boolean; + +export default CanUseTouchScreen; diff --git a/src/libs/DeviceCapabilities/hasHoverSupport/index.js b/src/libs/DeviceCapabilities/hasHoverSupport/index.js deleted file mode 100644 index 84a3fbbc5ed1..000000000000 --- a/src/libs/DeviceCapabilities/hasHoverSupport/index.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Allows us to identify whether the platform is hoverable. - * - * @returns {Boolean} - */ -function hasHoverSupport() { - return window.matchMedia('(hover: hover) and (pointer: fine)').matches; -} - -export default hasHoverSupport; diff --git a/src/libs/DeviceCapabilities/hasHoverSupport/index.native.js b/src/libs/DeviceCapabilities/hasHoverSupport/index.native.ts similarity index 52% rename from src/libs/DeviceCapabilities/hasHoverSupport/index.native.js rename to src/libs/DeviceCapabilities/hasHoverSupport/index.native.ts index d77fcc17448a..097b3b0cbba1 100644 --- a/src/libs/DeviceCapabilities/hasHoverSupport/index.native.js +++ b/src/libs/DeviceCapabilities/hasHoverSupport/index.native.ts @@ -1,9 +1,8 @@ +import HasHoverSupport from './types'; + /** * Allows us to identify whether the platform is hoverable. - * - * @returns {Boolean} */ - -const hasHoverSupport = () => false; +const hasHoverSupport: HasHoverSupport = () => false; export default hasHoverSupport; diff --git a/src/libs/DeviceCapabilities/hasHoverSupport/index.ts b/src/libs/DeviceCapabilities/hasHoverSupport/index.ts new file mode 100644 index 000000000000..1ff0f461db69 --- /dev/null +++ b/src/libs/DeviceCapabilities/hasHoverSupport/index.ts @@ -0,0 +1,8 @@ +import HasHoverSupport from './types'; + +/** + * Allows us to identify whether the platform is hoverable. + */ +const hasHoverSupport: HasHoverSupport = () => window.matchMedia?.('(hover: hover) and (pointer: fine)').matches; + +export default hasHoverSupport; diff --git a/src/libs/DeviceCapabilities/hasHoverSupport/types.ts b/src/libs/DeviceCapabilities/hasHoverSupport/types.ts new file mode 100644 index 000000000000..b8fe944cf88e --- /dev/null +++ b/src/libs/DeviceCapabilities/hasHoverSupport/types.ts @@ -0,0 +1,3 @@ +type HasHoverSupport = () => boolean; + +export default HasHoverSupport; diff --git a/src/libs/DeviceCapabilities/index.js b/src/libs/DeviceCapabilities/index.ts similarity index 100% rename from src/libs/DeviceCapabilities/index.js rename to src/libs/DeviceCapabilities/index.ts diff --git a/src/libs/Firebase/index.js b/src/libs/Firebase/index.js deleted file mode 100644 index a8c7a34f869f..000000000000 --- a/src/libs/Firebase/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/** Web does not use Firebase for performance tracing */ - -export default { - startTrace() {}, - stopTrace() {}, -}; diff --git a/src/libs/Firebase/index.native.js b/src/libs/Firebase/index.native.ts similarity index 71% rename from src/libs/Firebase/index.native.js rename to src/libs/Firebase/index.native.ts index 0fc452a523ec..2014e2b8bd3e 100644 --- a/src/libs/Firebase/index.native.js +++ b/src/libs/Firebase/index.native.ts @@ -1,15 +1,11 @@ /* eslint-disable no-unused-vars */ import perf from '@react-native-firebase/perf'; -import lodashGet from 'lodash/get'; import * as Environment from '../Environment/Environment'; -import Log from '../Log'; +import {StartTrace, StopTrace, TraceMap} from './types'; -const traceMap = {}; +const traceMap: TraceMap = {}; -/** - * @param {String} customEventName - */ -function startTrace(customEventName) { +const startTrace: StartTrace = (customEventName) => { const start = global.performance.now(); if (Environment.isDevelopment()) { return; @@ -27,18 +23,16 @@ function startTrace(customEventName) { start, }; }); -} +}; -/** - * @param {String} customEventName - */ -function stopTrace(customEventName) { - const stop = global.performance.now(); +const stopTrace: StopTrace = (customEventName) => { + // Uncomment to inspect logs on release builds + // const stop = global.performance.now(); if (Environment.isDevelopment()) { return; } - const trace = lodashGet(traceMap, [customEventName, 'trace']); + const trace = traceMap[customEventName].trace; if (!trace) { return; } @@ -50,7 +44,7 @@ function stopTrace(customEventName) { // Log.info(`sidebar_loaded: ${stop - start} ms`, true); delete traceMap[customEventName]; -} +}; export default { startTrace, diff --git a/src/libs/Firebase/index.ts b/src/libs/Firebase/index.ts new file mode 100644 index 000000000000..3048cf1d9bd8 --- /dev/null +++ b/src/libs/Firebase/index.ts @@ -0,0 +1,10 @@ +import {StartTrace, StopTrace} from './types'; + +/** Web does not use Firebase for performance tracing */ +const startTrace: StartTrace = () => {}; +const stopTrace: StopTrace = () => {}; + +export default { + startTrace, + stopTrace, +}; diff --git a/src/libs/Firebase/types.ts b/src/libs/Firebase/types.ts new file mode 100644 index 000000000000..3822f7072a7f --- /dev/null +++ b/src/libs/Firebase/types.ts @@ -0,0 +1,11 @@ +import {FirebasePerformanceTypes} from '@react-native-firebase/perf'; + +type Trace = { + trace: FirebasePerformanceTypes.Trace; + start: number; +}; +type TraceMap = Record; +type StartTrace = (customEventName: string) => void; +type StopTrace = (customEventName: string) => void; + +export type {StartTrace, StopTrace, TraceMap}; diff --git a/src/libs/Growl.ts b/src/libs/Growl.ts index 99c728f0a210..33d7311973cb 100644 --- a/src/libs/Growl.ts +++ b/src/libs/Growl.ts @@ -12,7 +12,9 @@ const isReadyPromise = new Promise((resolve) => { }); function setIsReady() { - if (!resolveIsReadyPromise) return; + if (!resolveIsReadyPromise) { + return; + } resolveIsReadyPromise(); } @@ -21,7 +23,9 @@ function setIsReady() { */ function show(bodyText: string, type: string, duration: number = CONST.GROWL.DURATION) { isReadyPromise.then(() => { - if (!growlRef?.current?.show) return; + if (!growlRef?.current?.show) { + return; + } growlRef.current.show(bodyText, type, duration); }); } diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js index 5c110264e034..f3939eabe6f7 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js @@ -345,7 +345,7 @@ const NewTeachersUniteNavigator = createModalStackNavigator([ }, { getComponent: () => { - const IntroSchoolPrincipalPage = require('../../../pages/TeachersUnite/IntroSchoolPrincipalPage').default; + const IntroSchoolPrincipalPage = require('../../../pages/TeachersUnite/ImTeacherPage').default; return IntroSchoolPrincipalPage; }, name: 'Intro_School_Principal', diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 3c6e879bd423..3bdf77745432 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -593,6 +593,30 @@ function isCurrentUser(userDetails) { return _.some(_.keys(loginList), (login) => login.toLowerCase() === userDetailsLogin.toLowerCase()); } +/** + * Calculates count of all enabled options + * + * @param {Object[]} options - an initial strings array + * @param {Boolean} options[].enabled - a flag to enable/disable option in a list + * @param {String} options[].name - a name of an option + * @returns {Number} + */ +function getEnabledCategoriesCount(options) { + return _.filter(options, (option) => option.enabled).length; +} + +/** + * Verifies that there is at least one enabled option + * + * @param {Object[]} options - an initial strings array + * @param {Boolean} options[].enabled - a flag to enable/disable option in a list + * @param {String} options[].name - a name of an option + * @returns {Boolean} + */ +function hasEnabledOptions(options) { + return _.some(options, (option) => option.enabled); +} + /** * Build the options for the category tree hierarchy via indents * @@ -606,6 +630,10 @@ function getCategoryOptionTree(options, isOneLine = false) { const optionCollection = {}; _.each(options, (option) => { + if (!option.enabled) { + return; + } + if (isOneLine) { if (_.has(optionCollection, option.name)) { return; @@ -656,10 +684,26 @@ function getCategoryOptionTree(options, isOneLine = false) { */ function getCategoryListSections(categories, recentlyUsedCategories, selectedOptions, searchInputValue, maxRecentReportsToShow) { const categorySections = []; - const categoriesValues = _.values(categories); + const categoriesValues = _.chain(categories) + .values() + .filter((category) => category.enabled) + .value(); + const numberOfCategories = _.size(categoriesValues); let indexOffset = 0; + if (numberOfCategories === 0 && selectedOptions.length > 0) { + categorySections.push({ + // "Selected" section + title: '', + shouldShow: false, + indexOffset, + data: getCategoryOptionTree(selectedOptions, true), + }); + + return categorySections; + } + if (!_.isEmpty(searchInputValue)) { const searchCategories = _.filter(categoriesValues, (category) => category.name.toLowerCase().includes(searchInputValue.toLowerCase())); @@ -1474,6 +1518,8 @@ export { isSearchStringMatch, shouldOptionShowTooltip, getLastMessageTextForReport, + getEnabledCategoriesCount, + hasEnabledOptions, getCategoryOptionTree, formatMemberForList, }; diff --git a/src/libs/PolicyUtils.js b/src/libs/PolicyUtils.js index 164f284a4ef5..d2de5b1c0d7e 100644 --- a/src/libs/PolicyUtils.js +++ b/src/libs/PolicyUtils.js @@ -6,11 +6,12 @@ import ONYXKEYS from '../ONYXKEYS'; /** * Filter out the active policies, which will exclude policies with pending deletion + * These are policies that we can use to create reports with in NewDot. * @param {Object} policies * @returns {Array} */ function getActivePolicies(policies) { - return _.filter(policies, (policy) => policy && policy.isPolicyExpenseChatEnabled && policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); + return _.filter(policies, (policy) => policy && (policy.isPolicyExpenseChatEnabled || policy.areChatRoomsEnabled) && policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); } /** @@ -166,6 +167,14 @@ function getIneligibleInvitees(policyMembers, personalDetails) { return memberEmailsToExclude; } +/** + * @param {Object} policy + * @returns {Boolean} + */ +function isPendingDeletePolicy(policy) { + return policy.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; +} + export { getActivePolicies, hasPolicyMemberError, @@ -179,4 +188,5 @@ export { isPolicyAdmin, getMemberAccountIDsForWorkspace, getIneligibleInvitees, + isPendingDeletePolicy, }; diff --git a/src/libs/Pusher/EventType.js b/src/libs/Pusher/EventType.js index 639e10020fc7..85ccc5e17242 100644 --- a/src/libs/Pusher/EventType.js +++ b/src/libs/Pusher/EventType.js @@ -5,6 +5,7 @@ export default { REPORT_COMMENT: 'reportComment', ONYX_API_UPDATE: 'onyxApiUpdate', + USER_IS_LEAVING_ROOM: 'client-userIsLeavingRoom', USER_IS_TYPING: 'client-userIsTyping', MULTIPLE_EVENTS: 'multipleEvents', MULTIPLE_EVENT_TYPE: { diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index edf646d0266b..eee9d6549f6c 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -947,23 +947,31 @@ function getIconsForParticipants(participants, personalDetails) { for (let i = 0; i < participantsList.length; i++) { const accountID = participantsList[i]; const avatarSource = UserUtils.getAvatar(lodashGet(personalDetails, [accountID, 'avatar'], ''), accountID); - participantDetails.push([ - accountID, - lodashGet(personalDetails, [accountID, 'displayName']) || lodashGet(personalDetails, [accountID, 'login'], ''), - lodashGet(personalDetails, [accountID, 'firstName'], ''), - avatarSource, - ]); + const displayNameLogin = lodashGet(personalDetails, [accountID, 'displayName']) || lodashGet(personalDetails, [accountID, 'login'], ''); + participantDetails.push([accountID, displayNameLogin, avatarSource]); } - // Sort all logins by first name (which is the second element in the array) - const sortedParticipantDetails = participantDetails.sort((a, b) => a[2] - b[2]); + const sortedParticipantDetails = _.chain(participantDetails) + .sort((first, second) => { + // First sort by displayName/login + const displayNameLoginOrder = first[1].localeCompare(second[1]); + if (displayNameLoginOrder !== 0) { + return displayNameLoginOrder; + } - // Now that things are sorted, gather only the avatars (third element in the array) and return those + // Then fallback on accountID as the final sorting criteria. + // This will ensure that the order of avatars with same login/displayName + // stay consistent across all users and devices + return first[0] > second[0]; + }) + .value(); + + // Now that things are sorted, gather only the avatars (second element in the array) and return those const avatars = []; for (let i = 0; i < sortedParticipantDetails.length; i++) { const userIcon = { id: sortedParticipantDetails[i][0], - source: sortedParticipantDetails[i][3], + source: sortedParticipantDetails[i][2], type: CONST.ICON_TYPE_AVATAR, name: sortedParticipantDetails[i][1], }; @@ -1641,6 +1649,29 @@ function getParentReport(report) { return lodashGet(allReports, `${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`, {}); } +/** + * Returns the root parentReport if the given report is nested. + * Uses recursion to iterate any depth of nested reports. + * + * @param {Object} report + * @returns {Object} + */ +function getRootParentReport(report) { + if (!report) { + return {}; + } + + // Returns the current report as the root report, because it does not have a parentReportID + if (!report.parentReportID) { + return report; + } + + const parentReport = getReport(report.parentReportID); + + // Runs recursion to iterate a parent report + return getRootParentReport(parentReport); +} + /** * Get the title for a report. * @@ -3676,6 +3707,7 @@ export { isAllowedToComment, getBankAccountRoute, getParentReport, + getRootParentReport, getTaskParentReportActionIDInAssigneeReport, getReportPreviewMessage, getModifiedExpenseMessage, diff --git a/src/libs/TransactionUtils.js b/src/libs/TransactionUtils.js index cea530f6f47a..5dcfbc467c20 100644 --- a/src/libs/TransactionUtils.js +++ b/src/libs/TransactionUtils.js @@ -147,6 +147,10 @@ function getUpdatedTransaction(transaction, transactionChanges, isFromExpenseRep shouldStopSmartscan = true; } + if (_.has(transactionChanges, 'category')) { + updatedTransaction.category = transactionChanges.category; + } + if (shouldStopSmartscan && _.has(transaction, 'receipt') && !_.isEmpty(transaction.receipt) && lodashGet(transaction, 'receipt.state') !== CONST.IOU.RECEIPT_STATE.OPEN) { updatedTransaction.receipt.state = CONST.IOU.RECEIPT_STATE.OPEN; } @@ -157,6 +161,7 @@ function getUpdatedTransaction(transaction, transactionChanges, isFromExpenseRep ...(_.has(transactionChanges, 'amount') && {amount: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), ...(_.has(transactionChanges, 'currency') && {currency: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), ...(_.has(transactionChanges, 'merchant') && {merchant: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), + ...(_.has(transactionChanges, 'category') && {category: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), }; return updatedTransaction; diff --git a/src/libs/UpdateMultilineInputRange/index.ios.js b/src/libs/UpdateMultilineInputRange/index.ios.js new file mode 100644 index 000000000000..85ed529a33bc --- /dev/null +++ b/src/libs/UpdateMultilineInputRange/index.ios.js @@ -0,0 +1,23 @@ +/** + * Place the cursor at the end of the value (if there is a value in the input). + * + * When a multiline input contains a text value that goes beyond the scroll height, the cursor will be placed + * at the end of the text value, and automatically scroll the input field to this position after the field gains + * focus. This provides a better user experience in cases where the text in the field has to be edited. The auto- + * scroll behaviour works on all platforms except iOS native. + * See https://github.com/Expensify/App/issues/20836 for more details. + * + * @param {Object} input the input element + */ +export default function updateMultilineInputRange(input) { + if (!input) { + return; + } + + /* + * Adding this iOS specific patch because of the scroll issue in native iOS + * Issue: does not scroll multiline input when text exceeds the maximum number of lines + * For more details: https://github.com/Expensify/App/pull/27702#issuecomment-1728651132 + */ + input.focus(); +} diff --git a/src/libs/focusAndUpdateMultilineInputRange.js b/src/libs/UpdateMultilineInputRange/index.js similarity index 80% rename from src/libs/focusAndUpdateMultilineInputRange.js rename to src/libs/UpdateMultilineInputRange/index.js index b5e438899d3d..179d30dc611d 100644 --- a/src/libs/focusAndUpdateMultilineInputRange.js +++ b/src/libs/UpdateMultilineInputRange/index.js @@ -1,5 +1,5 @@ /** - * Focus a multiline text input and place the cursor at the end of the value (if there is a value in the input). + * Place the cursor at the end of the value (if there is a value in the input). * * When a multiline input contains a text value that goes beyond the scroll height, the cursor will be placed * at the end of the text value, and automatically scroll the input field to this position after the field gains @@ -9,12 +9,11 @@ * * @param {Object} input the input element */ -export default function focusAndUpdateMultilineInputRange(input) { +export default function updateMultilineInputRange(input) { if (!input) { return; } - input.focus(); if (input.value && input.setSelectionRange) { const length = input.value.length; input.setSelectionRange(length, length); diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 08b032e383b8..86cd791f7fce 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -1148,6 +1148,7 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC } // STEP 4: Compose the optimistic data + const currentTime = DateUtils.getDBTime(); const optimisticData = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -1171,6 +1172,14 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.chatReportID}`, value: updatedChatReport, }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`, + value: { + lastReadTime: currentTime, + lastVisibleActionCreated: currentTime, + }, + }, ]; const successData = [ @@ -1191,6 +1200,7 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC created: null, currency: null, merchant: null, + category: null, }, }, }, @@ -1226,6 +1236,14 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.chatReportID}`, value: chatReport, }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`, + value: { + lastReadTime: transactionThread.lastReadTime, + lastVisibleActionCreated: transactionThread.lastVisibleActionCreated, + }, + }, ]; // STEP 6: Call the API endpoint diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index aa0d4b432da4..9fa1e5fe0567 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -103,24 +103,24 @@ function getReportChannelName(reportID) { } /** - * There are 2 possibilities that we can receive via pusher for a user's typing status: + * There are 2 possibilities that we can receive via pusher for a user's typing/leaving status: * 1. The "new" way from New Expensify is passed as {[login]: Boolean} (e.g. {yuwen@expensify.com: true}), where the value - * is whether the user with that login is typing on the report or not. + * is whether the user with that login is typing/leaving on the report or not. * 2. The "old" way from e.com which is passed as {userLogin: login} (e.g. {userLogin: bstites@expensify.com}) * * This method makes sure that no matter which we get, we return the "new" format * - * @param {Object} typingStatus + * @param {Object} status * @returns {Object} */ -function getNormalizedTypingStatus(typingStatus) { - let normalizedTypingStatus = typingStatus; +function getNormalizedStatus(status) { + let normalizedStatus = status; - if (_.first(_.keys(typingStatus)) === 'userLogin') { - normalizedTypingStatus = {[typingStatus.userLogin]: true}; + if (_.first(_.keys(status)) === 'userLogin') { + normalizedStatus = {[status.userLogin]: true}; } - return normalizedTypingStatus; + return normalizedStatus; } /** @@ -141,7 +141,7 @@ function subscribeToReportTypingEvents(reportID) { // If the pusher message comes from OldDot, we expect the typing status to be keyed by user // login OR by 'Concierge'. If the pusher message comes from NewDot, it is keyed by accountID // since personal details are keyed by accountID. - const normalizedTypingStatus = getNormalizedTypingStatus(typingStatus); + const normalizedTypingStatus = getNormalizedStatus(typingStatus); const accountIDOrLogin = _.first(_.keys(normalizedTypingStatus)); if (!accountIDOrLogin) { @@ -170,6 +170,41 @@ function subscribeToReportTypingEvents(reportID) { }); } +/** + * Initialize our pusher subscriptions to listen for someone leaving a room. + * + * @param {String} reportID + */ +function subscribeToReportLeavingEvents(reportID) { + if (!reportID) { + return; + } + + // Make sure we have a clean Leaving indicator before subscribing to leaving events + Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${reportID}`, false); + + const pusherChannelName = getReportChannelName(reportID); + Pusher.subscribe(pusherChannelName, Pusher.TYPE.USER_IS_LEAVING_ROOM, (leavingStatus) => { + // If the pusher message comes from OldDot, we expect the leaving status to be keyed by user + // login OR by 'Concierge'. If the pusher message comes from NewDot, it is keyed by accountID + // since personal details are keyed by accountID. + const normalizedLeavingStatus = getNormalizedStatus(leavingStatus); + const accountIDOrLogin = _.first(_.keys(normalizedLeavingStatus)); + + if (!accountIDOrLogin) { + return; + } + + if (Number(accountIDOrLogin) !== currentUserAccountID) { + return; + } + + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${reportID}`, true); + }).catch((error) => { + Log.hmmm('[Report] Failed to initially subscribe to Pusher channel', false, {errorType: error.type, pusherChannelName}); + }); +} + /** * Remove our pusher subscriptions to listen for someone typing in a report. * @@ -185,6 +220,21 @@ function unsubscribeFromReportChannel(reportID) { Pusher.unsubscribe(pusherChannelName, Pusher.TYPE.USER_IS_TYPING); } +/** + * Remove our pusher subscriptions to listen for someone leaving a report. + * + * @param {String} reportID + */ +function unsubscribeFromLeavingRoomReportChannel(reportID) { + if (!reportID) { + return; + } + + const pusherChannelName = getReportChannelName(reportID); + Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${reportID}`, false); + Pusher.unsubscribe(pusherChannelName, Pusher.TYPE.USER_IS_LEAVING_ROOM); +} + // New action subscriber array for report pages let newActionSubscribers = []; @@ -865,6 +915,17 @@ function broadcastUserIsTyping(reportID) { typingStatus[currentUserAccountID] = true; Pusher.sendEvent(privateReportChannelName, Pusher.TYPE.USER_IS_TYPING, typingStatus); } +/** + * Broadcasts to the report's private pusher channel whether a user is leaving a report + * + * @param {String} reportID + */ +function broadcastUserIsLeavingRoom(reportID) { + const privateReportChannelName = getReportChannelName(reportID); + const leavingStatus = {}; + leavingStatus[currentUserAccountID] = true; + Pusher.sendEvent(privateReportChannelName, Pusher.TYPE.USER_IS_LEAVING_ROOM, leavingStatus); +} /** * When a report changes in Onyx, this fetches the report from the API if the report doesn't have a name @@ -1781,6 +1842,12 @@ function getCurrentUserAccountID() { function leaveRoom(reportID) { const report = lodashGet(allReports, [reportID], {}); const reportKeys = _.keys(report); + + // Pusher's leavingStatus should be sent earlier. + // Place the broadcast before calling the LeaveRoom API to prevent a race condition + // between Onyx report being null and Pusher's leavingStatus becoming true. + broadcastUserIsLeavingRoom(reportID); + API.write( 'LeaveRoom', { @@ -2071,10 +2138,13 @@ export { updateWriteCapabilityAndNavigate, updateNotificationPreferenceAndNavigate, subscribeToReportTypingEvents, + subscribeToReportLeavingEvents, unsubscribeFromReportChannel, + unsubscribeFromLeavingRoomReportChannel, saveReportComment, saveReportCommentNumberOfLines, broadcastUserIsTyping, + broadcastUserIsLeavingRoom, togglePinnedState, editReportComment, handleUserDeletedLinksInHtml, diff --git a/src/libs/isInputAutoFilled.ts b/src/libs/isInputAutoFilled.ts index 0abe634001e4..e1b9942b0e78 100644 --- a/src/libs/isInputAutoFilled.ts +++ b/src/libs/isInputAutoFilled.ts @@ -4,7 +4,9 @@ import isSelectorSupported from './isSelectorSupported'; * Check the input is auto filled or not */ export default function isInputAutoFilled(input: Element): boolean { - if (!input?.matches) return false; + if (!input?.matches) { + return false; + } if (isSelectorSupported(':autofill')) { return input.matches(':-webkit-autofill') || input.matches(':autofill'); } diff --git a/src/libs/migrateOnyx.js b/src/libs/migrateOnyx.js index e401cbd9db69..588e1a5642aa 100644 --- a/src/libs/migrateOnyx.js +++ b/src/libs/migrateOnyx.js @@ -2,7 +2,6 @@ import _ from 'underscore'; import Log from './Log'; import AddEncryptedAuthToken from './migrations/AddEncryptedAuthToken'; import RenamePriorityModeKey from './migrations/RenamePriorityModeKey'; -import MoveToIndexedDB from './migrations/MoveToIndexedDB'; import RenameExpensifyNewsStatus from './migrations/RenameExpensifyNewsStatus'; import AddLastVisibleActionCreated from './migrations/AddLastVisibleActionCreated'; import PersonalDetailsByAccountID from './migrations/PersonalDetailsByAccountID'; @@ -14,15 +13,7 @@ export default function () { return new Promise((resolve) => { // Add all migrations to an array so they are executed in order - const migrationPromises = [ - MoveToIndexedDB, - RenamePriorityModeKey, - AddEncryptedAuthToken, - RenameExpensifyNewsStatus, - AddLastVisibleActionCreated, - PersonalDetailsByAccountID, - RenameReceiptFilename, - ]; + const migrationPromises = [RenamePriorityModeKey, AddEncryptedAuthToken, RenameExpensifyNewsStatus, AddLastVisibleActionCreated, PersonalDetailsByAccountID, RenameReceiptFilename]; // Reduce all promises down to a single promise. All promises run in a linear fashion, waiting for the // previous promise to finish before moving onto the next one. diff --git a/src/libs/migrations/MoveToIndexedDB.js b/src/libs/migrations/MoveToIndexedDB.js deleted file mode 100644 index 1f62985ed7bf..000000000000 --- a/src/libs/migrations/MoveToIndexedDB.js +++ /dev/null @@ -1,75 +0,0 @@ -import _ from 'underscore'; -import Onyx from 'react-native-onyx'; - -import Log from '../Log'; -import ONYXKEYS from '../../ONYXKEYS'; -import getPlatform from '../getPlatform'; -import CONST from '../../CONST'; - -/** - * Test whether the current platform is eligible for migration - * This migration is only applicable for desktop/web - * We're also skipping logged-out users as there would be nothing to migrate - * - * @returns {Boolean} - */ -function shouldMigrate() { - const isTargetPlatform = _.contains([CONST.PLATFORM.WEB, CONST.PLATFORM.DESKTOP], getPlatform()); - if (!isTargetPlatform) { - Log.info('[Migrate Onyx] Skipped migration MoveToIndexedDB (Not applicable to current platform)'); - return false; - } - - const session = window.localStorage.getItem(ONYXKEYS.SESSION); - if (!session || !session.includes('authToken')) { - Log.info('[Migrate Onyx] Skipped migration MoveToIndexedDB (Not applicable to logged out users)'); - return false; - } - - return true; -} - -/** - * Find Onyx data and move it from local storage to IndexedDB - * - * @returns {Promise} - */ -function applyMigration() { - const onyxKeys = new Set(_.values(ONYXKEYS)); - const onyxCollections = _.values(ONYXKEYS.COLLECTION); - - // Prepare a key-value dictionary of keys eligible for migration - // Targeting existing Onyx keys in local storage or any key prefixed by a collection name - const dataToMigrate = _.chain(window.localStorage) - .keys() - .filter((key) => onyxKeys.has(key) || _.some(onyxCollections, (collectionKey) => key.startsWith(collectionKey))) - .map((key) => [key, JSON.parse(window.localStorage.getItem(key))]) - .object() - .value(); - - // Move the data in Onyx and only then delete it from local storage - return Onyx.multiSet(dataToMigrate).then(() => _.each(dataToMigrate, (value, key) => window.localStorage.removeItem(key))); -} - -/** - * Migrate Web/Desktop storage to IndexedDB - * - * @returns {Promise} - */ -export default function () { - if (!shouldMigrate()) { - return Promise.resolve(); - } - - return new Promise((resolve, reject) => { - applyMigration() - .then(() => { - Log.info('[Migrate Onyx] Ran migration MoveToIndexedDB'); - resolve(); - }) - .catch((e) => { - Log.alert('[Migrate Onyx] MoveToIndexedDB failed', {error: e.message, stack: e.stack}, false); - reject(e); - }); - }); -} diff --git a/src/libs/requireParameters.ts b/src/libs/requireParameters.ts index 098a6d114430..ebeb55e254e0 100644 --- a/src/libs/requireParameters.ts +++ b/src/libs/requireParameters.ts @@ -14,7 +14,9 @@ export default function requireParameters(parameterNames: string[], parameters: const propertiesToRedact = ['authToken', 'password', 'partnerUserSecret', 'twoFactorAuthCode']; const parametersCopy = {...parameters}; Object.keys(parametersCopy).forEach((key) => { - if (!propertiesToRedact.includes(key.toString())) return; + if (!propertiesToRedact.includes(key.toString())) { + return; + } parametersCopy[key] = ''; }); diff --git a/src/pages/AddPersonalBankAccountPage.js b/src/pages/AddPersonalBankAccountPage.js index 98bc09a7a217..7c04970c3980 100644 --- a/src/pages/AddPersonalBankAccountPage.js +++ b/src/pages/AddPersonalBankAccountPage.js @@ -103,6 +103,7 @@ class AddPersonalBankAccountPage extends React.Component { includeSafeAreaPaddingBottom={shouldShowSuccess} shouldEnablePickerAvoiding={false} shouldShowOfflineIndicator={false} + testID={AddPersonalBankAccountPage.displayName} > + { + onSubmit({ + category: category.searchText, + }); + }; + + return ( + + + + + + ); +} + +EditRequestCategoryPage.propTypes = propTypes; +EditRequestCategoryPage.displayName = 'EditRequestCategoryPage'; + +export default EditRequestCategoryPage; diff --git a/src/pages/EditRequestCreatedPage.js b/src/pages/EditRequestCreatedPage.js index a29936ba9ba4..9edce7350400 100644 --- a/src/pages/EditRequestCreatedPage.js +++ b/src/pages/EditRequestCreatedPage.js @@ -23,6 +23,7 @@ function EditRequestCreatedPage({defaultCreated, onSubmit}) {
    { + focusTimeoutRef.current = setTimeout(() => { + if (descriptionInputRef.current) { + descriptionInputRef.current.focus(); + } + return () => { + if (!focusTimeoutRef.current) { + return; + } + clearTimeout(focusTimeoutRef.current); + }; + }, CONST.ANIMATED_TRANSITION); + }, []), + ); + return ( descriptionInputRef.current && descriptionInputRef.current.focus()} + testID={EditRequestDescriptionPage.displayName} > (descriptionInputRef.current = e)} + ref={(el) => { + if (!el) { + return; + } + descriptionInputRef.current = el; + updateMultilineInputRange(descriptionInputRef.current); + }} autoGrowHeight containerStyles={[styles.autoGrowHeightMultilineInput]} textAlignVertical="top" diff --git a/src/pages/EditRequestMerchantPage.js b/src/pages/EditRequestMerchantPage.js index 7e7791fa3212..7e15a88a3ab4 100644 --- a/src/pages/EditRequestMerchantPage.js +++ b/src/pages/EditRequestMerchantPage.js @@ -38,6 +38,7 @@ function EditRequestMerchantPage({defaultMerchant, onSubmit}) { includeSafeAreaPaddingBottom={false} shouldEnableMaxHeight onEntryTransitionEnd={() => merchantInputRef.current && merchantInputRef.current.focus()} + testID={EditRequestMerchantPage.displayName} > { + let updatedCategory = transactionChanges.category; + // In case the same category has been selected, do reset of the category. + if (transactionCategory === updatedCategory) { + updatedCategory = ''; + } + editMoneyRequest({category: updatedCategory}); + }} + /> + ); + } + if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.RECEIPT) { return ( `${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}`, }, }), + // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file withOnyx({ parentReport: { key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT}${report ? report.parentReportID : '0'}`, diff --git a/src/pages/EditRequestReceiptPage.js b/src/pages/EditRequestReceiptPage.js index 47aa23a93432..f3c12fe2f9ee 100644 --- a/src/pages/EditRequestReceiptPage.js +++ b/src/pages/EditRequestReceiptPage.js @@ -31,6 +31,7 @@ function EditRequestReceiptPage({route, transactionID}) { diff --git a/src/pages/EnablePayments/AdditionalDetailsStep.js b/src/pages/EnablePayments/AdditionalDetailsStep.js index 9804edd63fcd..cf18fc6d7303 100644 --- a/src/pages/EnablePayments/AdditionalDetailsStep.js +++ b/src/pages/EnablePayments/AdditionalDetailsStep.js @@ -149,6 +149,7 @@ function AdditionalDetailsStep({walletAdditionalDetails, translate, currentUserP + {() => { if (this.props.userWallet.errorCode === CONST.WALLET.ERROR.KYC) { return ( diff --git a/src/pages/EnablePayments/OnfidoStep.js b/src/pages/EnablePayments/OnfidoStep.js index b7181295ab77..fc856d813683 100644 --- a/src/pages/EnablePayments/OnfidoStep.js +++ b/src/pages/EnablePayments/OnfidoStep.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useCallback} from 'react'; import {withOnyx} from 'react-native-onyx'; import Onfido from '../../components/Onfido'; import ONYXKEYS from '../../ONYXKEYS'; @@ -7,8 +7,7 @@ import Navigation from '../../libs/Navigation/Navigation'; import CONST from '../../CONST'; import HeaderWithBackButton from '../../components/HeaderWithBackButton'; import * as Wallet from '../../libs/actions/Wallet'; -import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; -import compose from '../../libs/compose'; +import useLocalize from '../../hooks/useLocalize'; import Growl from '../../libs/Growl'; import OnfidoPrivacy from './OnfidoPrivacy'; import walletOnfidoDataPropTypes from './walletOnfidoDataPropTypes'; @@ -18,8 +17,6 @@ import ROUTES from '../../ROUTES'; const propTypes = { /** Stores various information used to build the UI and call any APIs */ walletOnfidoData: walletOnfidoDataPropTypes, - - ...withLocalizePropTypes, }; const defaultProps = { @@ -29,60 +26,66 @@ const defaultProps = { }, }; -class OnfidoStep extends React.Component { - /** - * @returns {boolean|*} - */ - canShowOnfido() { - return this.props.walletOnfidoData.hasAcceptedPrivacyPolicy && !this.props.walletOnfidoData.isLoading && !this.props.walletOnfidoData.error && this.props.walletOnfidoData.sdkToken; - } +function OnfidoStep({walletOnfidoData}) { + const {translate} = useLocalize(); + + const shouldShowOnfido = walletOnfidoData.hasAcceptedPrivacyPolicy && !walletOnfidoData.isLoading && !walletOnfidoData.error && walletOnfidoData.sdkToken; + + const goBack = useCallback(() => { + Navigation.goBack(ROUTES.HOME); + }, []); + + const goToPreviousStep = useCallback(() => { + Wallet.updateCurrentStep(CONST.WALLET.STEP.ADDITIONAL_DETAILS); + }, []); + + const reportError = useCallback(() => { + Growl.error(translate('onfidoStep.genericError'), 10000); + }, [translate]); - render() { - return ( - <> - Wallet.updateCurrentStep(CONST.WALLET.STEP.ADDITIONAL_DETAILS)} - /> - - {this.canShowOnfido() ? ( - { - Growl.error(this.props.translate('onfidoStep.genericError'), 10000); - }} - onUserExit={() => { - Navigation.goBack(ROUTES.HOME); - }} - onSuccess={(data) => { - BankAccounts.verifyIdentity({ - onfidoData: JSON.stringify({ - ...data, - applicantID: this.props.walletOnfidoData.applicantID, - }), - }); - }} - /> - ) : ( - - )} - - - ); - } + const verifyIdentity = useCallback( + (data) => { + BankAccounts.verifyIdentity({ + onfidoData: JSON.stringify({ + ...data, + applicantID: walletOnfidoData.applicantID, + }), + }); + }, + [walletOnfidoData.applicantID], + ); + + return ( + <> + + + {shouldShowOnfido ? ( + + ) : ( + + )} + + + ); } OnfidoStep.propTypes = propTypes; OnfidoStep.defaultProps = defaultProps; +OnfidoStep.displayName = 'OnfidoStep'; -export default compose( - withLocalize, - withOnyx({ - walletOnfidoData: { - key: ONYXKEYS.WALLET_ONFIDO, +export default withOnyx({ + walletOnfidoData: { + key: ONYXKEYS.WALLET_ONFIDO, - // Let's get a new onfido token each time the user hits this flow (as it should only be once) - initWithStoredValues: false, - }, - }), -)(OnfidoStep); + // Let's get a new onfido token each time the user hits this flow (as it should only be once) + initWithStoredValues: false, + }, +})(OnfidoStep); diff --git a/src/pages/ErrorPage/NotFoundPage.js b/src/pages/ErrorPage/NotFoundPage.js index f0121cd8f3ef..445e81296566 100644 --- a/src/pages/ErrorPage/NotFoundPage.js +++ b/src/pages/ErrorPage/NotFoundPage.js @@ -5,7 +5,7 @@ import FullPageNotFoundView from '../../components/BlockingViews/FullPageNotFoun // eslint-disable-next-line rulesdir/no-negated-variables function NotFoundPage() { return ( - + ); diff --git a/src/pages/FlagCommentPage.js b/src/pages/FlagCommentPage.js index 1a9a2d8c8767..53da810007ea 100644 --- a/src/pages/FlagCommentPage.js +++ b/src/pages/FlagCommentPage.js @@ -154,7 +154,10 @@ function FlagCommentPage(props) { )); return ( - + {({safeAreaPaddingBottomStyle}) => ( diff --git a/src/pages/GetAssistancePage.js b/src/pages/GetAssistancePage.js index 34f996936654..97b498d758b7 100644 --- a/src/pages/GetAssistancePage.js +++ b/src/pages/GetAssistancePage.js @@ -78,7 +78,7 @@ function GetAssistancePage(props) { } return ( - + Navigation.goBack(ROUTES.SETTINGS_WORKSPACES)} diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index cb54aa8e5a7b..c3b66910face 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -173,6 +173,7 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate}) includeSafeAreaPaddingBottom={false} includePaddingTop={false} shouldEnableMaxHeight + testID={NewChatPage.displayName} > {({safeAreaPaddingBottomStyle, insets}) => ( { + focusTimeoutRef.current = setTimeout(() => { + if (privateNotesInput.current) { + privateNotesInput.current.focus(); + } + return () => { + if (!focusTimeoutRef.current) { + return; + } + clearTimeout(focusTimeoutRef.current); + }; + }, CONST.ANIMATED_TRANSITION); + }, []), + ); const savePrivateNote = () => { const editedNote = parser.replace(privateNote); @@ -79,7 +97,7 @@ function PrivateNotesEditPage({route, personalDetailsList, session, report}) { return ( focusAndUpdateMultilineInputRange(privateNotesInput.current)} + testID={PrivateNotesEditPage.displayName} > setPrivateNote(text)} - ref={(el) => (privateNotesInput.current = el)} + ref={(el) => { + if (!el) { + return; + } + privateNotesInput.current = el; + updateMultilineInputRange(privateNotesInput.current); + }} /> diff --git a/src/pages/PrivateNotes/PrivateNotesListPage.js b/src/pages/PrivateNotes/PrivateNotesListPage.js index 098bfd2a245b..fdfaa4c60e33 100644 --- a/src/pages/PrivateNotes/PrivateNotesListPage.js +++ b/src/pages/PrivateNotes/PrivateNotesListPage.js @@ -117,7 +117,10 @@ function PrivateNotesListPage({report, personalDetailsList, network, session}) { }, [report, personalDetailsList, session, translate]); return ( - + Navigation.dismissModal()} /> {report.isLoadingPrivateNotes && _.isEmpty(lodashGet(report, 'privateNotes', {})) ? ( - + ) : ( _.map(privateNotes, (item, index) => getMenuItem(item, index)) )} diff --git a/src/pages/PrivateNotes/PrivateNotesViewPage.js b/src/pages/PrivateNotes/PrivateNotesViewPage.js index 48f053f10f90..4c6d960d5d9a 100644 --- a/src/pages/PrivateNotes/PrivateNotesViewPage.js +++ b/src/pages/PrivateNotes/PrivateNotesViewPage.js @@ -56,7 +56,10 @@ function PrivateNotesViewPage({route, personalDetailsList, session, report}) { const privateNote = lodashGet(report, ['privateNotes', route.params.accountID, 'note'], ''); return ( - + { function ProfilePage(props) { const accountID = Number(lodashGet(props.route.params, 'accountID', 0)); - // eslint-disable-next-line rulesdir/prefer-early-return - useEffect(() => { - if (ValidationUtils.isValidAccountRoute(accountID)) { - PersonalDetails.openPublicProfilePage(accountID); - } - }, [accountID]); - const details = lodashGet(props.personalDetails, accountID, ValidationUtils.isValidAccountRoute(accountID) ? {} : {isloading: false}); const displayName = details.displayName ? details.displayName : props.translate('common.hidden'); @@ -143,8 +136,15 @@ function ProfilePage(props) { const chatReportWithCurrentUser = !isCurrentUser && !Session.isAnonymousUser() ? ReportUtils.getChatByParticipants([accountID]) : 0; + // eslint-disable-next-line rulesdir/prefer-early-return + useEffect(() => { + if (ValidationUtils.isValidAccountRoute(accountID) && !hasMinimumDetails) { + PersonalDetails.openPublicProfilePage(accountID); + } + }, [accountID, hasMinimumDetails]); + return ( - + Navigation.goBack(navigateBackTo)} diff --git a/src/pages/ReimbursementAccount/ACHContractStep.js b/src/pages/ReimbursementAccount/ACHContractStep.js index 1ef43843571c..761be71d864a 100644 --- a/src/pages/ReimbursementAccount/ACHContractStep.js +++ b/src/pages/ReimbursementAccount/ACHContractStep.js @@ -145,7 +145,10 @@ function ACHContractStep(props) { }; return ( - + + + + + + Navigation.goBack(ROUTES.SETTINGS_WORKSPACES)} @@ -385,7 +385,7 @@ class ReimbursementAccountPage extends React.Component { if (errorText) { return ( - + + + diff --git a/src/pages/ReportParticipantsPage.js b/src/pages/ReportParticipantsPage.js index 6ccb7a0c2e87..c10401fb90db 100755 --- a/src/pages/ReportParticipantsPage.js +++ b/src/pages/ReportParticipantsPage.js @@ -99,7 +99,10 @@ function ReportParticipantsPage(props) { })); return ( - + {({safeAreaPaddingBottomStyle}) => ( { setWelcomeMessage(value); @@ -54,55 +56,58 @@ function ReportWelcomeMessagePage(props) { Report.updateWelcomeMessage(props.report.reportID, props.report.welcomeMessage, welcomeMessage.trim()); }, [props.report.reportID, props.report.welcomeMessage, welcomeMessage]); - return ( - { - if (!welcomeMessageInputRef.current) { - return; + useFocusEffect( + useCallback(() => { + focusTimeoutRef.current = setTimeout(() => { + if (welcomeMessageInputRef.current) { + welcomeMessageInputRef.current.focus(); } - focusAndUpdateMultilineInputRange(welcomeMessageInputRef.current); - }} - > - {({didScreenTransitionEnd}) => ( - - -
    - {props.translate('welcomeMessagePage.explainerText')} - - { - // Before updating the DOM, React sets the affected ref.current values to null. After updating the DOM, React immediately sets them to the corresponding DOM nodes - // to avoid focus multiple time, we should early return if el is null. - if (!el) { - return; - } - if (!welcomeMessageInputRef.current && didScreenTransitionEnd) { - focusAndUpdateMultilineInputRange(el); - } - welcomeMessageInputRef.current = el; - }} - value={welcomeMessage} - onChangeText={handleWelcomeMessageChange} - autoCapitalize="none" - textAlignVertical="top" - containerStyles={[styles.autoGrowHeightMultilineInput]} - /> - -
    -
    - )} + return () => { + if (!focusTimeoutRef.current) { + return; + } + clearTimeout(focusTimeoutRef.current); + }; + }, CONST.ANIMATED_TRANSITION); + }, []), + ); + + return ( + + + +
    + {props.translate('welcomeMessagePage.explainerText')} + + { + if (!el) { + return; + } + welcomeMessageInputRef.current = el; + updateMultilineInputRange(welcomeMessageInputRef.current); + }} + value={welcomeMessage} + onChangeText={handleWelcomeMessageChange} + autoCapitalize="none" + textAlignVertical="top" + containerStyles={[styles.autoGrowHeightMultilineInput]} + /> + +
    +
    ); } diff --git a/src/pages/SearchPage.js b/src/pages/SearchPage.js index 2ee29380ff80..141f4e841853 100755 --- a/src/pages/SearchPage.js +++ b/src/pages/SearchPage.js @@ -169,7 +169,10 @@ class SearchPage extends Component { ); return ( - + {({didScreenTransitionEnd, safeAreaPaddingBottomStyle}) => ( <> diff --git a/src/pages/ShareCodePage.js b/src/pages/ShareCodePage.js index a36149a5f4fa..f19c79db5459 100644 --- a/src/pages/ShareCodePage.js +++ b/src/pages/ShareCodePage.js @@ -54,7 +54,7 @@ class ShareCodePage extends React.Component { const isNative = platform === CONST.PLATFORM.IOS || platform === CONST.PLATFORM.ANDROID; return ( - + Navigation.goBack(isReport ? ROUTES.getReportDetailsRoute(this.props.report.reportID) : ROUTES.SETTINGS)} diff --git a/src/pages/TeachersUnite/ImTeacherPage.js b/src/pages/TeachersUnite/ImTeacherPage.js index dbeba700d208..a938abf81b79 100644 --- a/src/pages/TeachersUnite/ImTeacherPage.js +++ b/src/pages/TeachersUnite/ImTeacherPage.js @@ -1,54 +1,36 @@ import React from 'react'; -import ScreenWrapper from '../../components/ScreenWrapper'; -import HeaderWithBackButton from '../../components/HeaderWithBackButton'; -import ROUTES from '../../ROUTES'; -import Navigation from '../../libs/Navigation/Navigation'; -import FixedFooter from '../../components/FixedFooter'; -import styles from '../../styles/styles'; -import Button from '../../components/Button'; -import * as Illustrations from '../../components/Icon/Illustrations'; -import variables from '../../styles/variables'; -import useLocalize from '../../hooks/useLocalize'; -import BlockingView from '../../components/BlockingViews/BlockingView'; +import PropTypes from 'prop-types'; +import {withOnyx} from 'react-native-onyx'; +import ONYXKEYS from '../../ONYXKEYS'; +import * as LoginUtils from '../../libs/LoginUtils'; +import ImTeacherUpdateEmailPage from './ImTeacherUpdateEmailPage'; +import IntroSchoolPrincipalPage from './IntroSchoolPrincipalPage'; -const propTypes = {}; +const propTypes = { + /** Current user session */ + session: PropTypes.shape({ + /** Current user primary login */ + email: PropTypes.string.isRequired, + }), +}; -const defaultProps = {}; +const defaultProps = { + session: { + email: null, + }, +}; -function ImTeacherPage() { - const {translate} = useLocalize(); - - return ( - - Navigation.goBack(ROUTES.TEACHERS_UNITE)} - /> - Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHODS)} - iconWidth={variables.signInLogoWidthLargeScreen} - iconHeight={variables.lhnLogoWidth} - /> - -