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/android/app/build.gradle b/android/app/build.gradle index ed2ac15d1e47..1e34491b04ad 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 1001037208 - versionName "1.3.72-8" + versionCode 1001037209 + versionName "1.3.72-9" } flavorDimensions "default" diff --git a/contributingGuides/STYLE.md b/contributingGuides/STYLE.md index b615104f6aab..0a88ecd7bda8 100644 --- a/contributingGuides/STYLE.md +++ b/contributingGuides/STYLE.md @@ -491,6 +491,19 @@ When writing a function component you must ALWAYS add a `displayName` property a export default Avatar; ``` +## Forwarding refs + +When forwarding a ref define named component and pass it directly to the `forwardRef`. By doing this we remove potential extra layer in React tree in form of anonymous component. + +```javascript + function FancyInput(props, ref) { + ... + return + } + + export default React.forwardRef(FancyInput) +``` + ## Stateless components vs Pure Components vs Class based components vs Render Props - When to use what? Class components are DEPRECATED. Use function components and React hooks. diff --git a/contributingGuides/TS_CHEATSHEET.md b/contributingGuides/TS_CHEATSHEET.md index df6d70b5ae90..1e330dafb7cf 100644 --- a/contributingGuides/TS_CHEATSHEET.md +++ b/contributingGuides/TS_CHEATSHEET.md @@ -43,7 +43,9 @@ - [1.2](#forwardRef) **`forwardRef`** ```ts - import { forwardRef, useRef, ReactNode } from "react"; + // CustomTextInput.tsx + + import { forwardRef, useRef, ReactNode, ForwardedRef } from "react"; import { TextInput, View } from "react-native"; export type CustomTextInputProps = { @@ -51,16 +53,18 @@ children?: ReactNode; }; - const CustomTextInput = forwardRef( - (props, ref) => { - return ( - - - {props.children} - - ); - } - ); + function CustomTextInput(props: CustomTextInputProps, ref: ForwardedRef) { + return ( + + + {props.children} + + ); + }; + + export default forwardRef(CustomTextInput); + + // ParentComponent.tsx function ParentComponent() { const ref = useRef(); 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 c548535fec61..441bd2feab92 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.3.72.8 + 1.3.72.9 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 6e9bdea1a53a..27273c7f3866 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.3.72.8 + 1.3.72.9 diff --git a/package-lock.json b/package-lock.json index 3ed8661f6711..c2e322bf8ba2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.3.72-8", + "version": "1.3.72-9", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.3.72-8", + "version": "1.3.72-9", "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 ea638423b1b2..c568ff7b0ab4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.3.72-8", + "version": "1.3.72-9", "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 8d5c9a25edb1..eed1b98ae551 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1312,9 +1312,9 @@ const CONST = { }, // Auth limit is 60k for the column but we store edits and other metadata along the html so let's use a lower limit to accommodate for it. - MAX_COMMENT_LENGTH: 15000, + MAX_COMMENT_LENGTH: 10000, - // Furthermore, applying markup is very resource-consuming, so let's set a slightly lower limit for that + // Use the same value as MAX_COMMENT_LENGTH to ensure the entire comment is parsed. Note that applying markup is very resource-consuming. MAX_MARKUP_LENGTH: 10000, MAX_THREAD_REPLIES_PREVIEW: 99, @@ -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/components/Attachments/AttachmentCarousel/AttachmentCarouselCellRenderer.js b/src/components/Attachments/AttachmentCarousel/AttachmentCarouselCellRenderer.js new file mode 100644 index 000000000000..2c698d5c8a61 --- /dev/null +++ b/src/components/Attachments/AttachmentCarousel/AttachmentCarouselCellRenderer.js @@ -0,0 +1,34 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {View, PixelRatio} from 'react-native'; +import useWindowDimensions from '../../../hooks/useWindowDimensions'; +import styles from '../../../styles/styles'; + +const propTypes = { + /** Cell Container styles */ + style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), +}; + +const defaultProps = { + style: [], +}; + +function AttachmentCarouselCellRenderer(props) { + const {windowWidth, isSmallScreenWidth} = useWindowDimensions(); + const modalStyles = styles.centeredModalStyles(isSmallScreenWidth, true); + const style = [props.style, styles.h100, {width: PixelRatio.roundToNearestPixel(windowWidth - (modalStyles.marginHorizontal + modalStyles.borderWidth) * 2)}]; + + return ( + + ); +} + +AttachmentCarouselCellRenderer.propTypes = propTypes; +AttachmentCarouselCellRenderer.defaultProps = defaultProps; +AttachmentCarouselCellRenderer.displayName = 'AttachmentCarouselCellRenderer'; + +export default React.memo(AttachmentCarouselCellRenderer); diff --git a/src/components/Attachments/AttachmentCarousel/index.js b/src/components/Attachments/AttachmentCarousel/index.js index dde75c7caad6..00b603cdd7d9 100644 --- a/src/components/Attachments/AttachmentCarousel/index.js +++ b/src/components/Attachments/AttachmentCarousel/index.js @@ -3,6 +3,7 @@ import {View, FlatList, PixelRatio, Keyboard} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import styles from '../../../styles/styles'; +import AttachmentCarouselCellRenderer from './AttachmentCarouselCellRenderer'; import CarouselActions from './CarouselActions'; import withWindowDimensions from '../../withWindowDimensions'; import CarouselButtons from './CarouselButtons'; @@ -12,7 +13,6 @@ import ONYXKEYS from '../../../ONYXKEYS'; import withLocalize from '../../withLocalize'; import compose from '../../../libs/compose'; import useCarouselArrows from './useCarouselArrows'; -import useWindowDimensions from '../../../hooks/useWindowDimensions'; import CarouselItem from './CarouselItem'; import Navigation from '../../../libs/Navigation/Navigation'; import BlockingView from '../../BlockingViews/BlockingView'; @@ -30,7 +30,6 @@ const viewabilityConfig = { function AttachmentCarousel({report, reportActions, source, onNavigate, setDownloadButtonVisibility, translate}) { const scrollRef = useRef(null); - const {windowWidth, isSmallScreenWidth} = useWindowDimensions(); const canUseTouchScreen = DeviceCapabilities.canUseTouchScreen(); const [containerWidth, setContainerWidth] = useState(0); @@ -132,29 +131,6 @@ function AttachmentCarousel({report, reportActions, source, onNavigate, setDownl [containerWidth], ); - /** - * Defines how a container for a single attachment should be rendered - * @param {Object} cellRendererProps - * @returns {JSX.Element} - */ - const renderCell = useCallback( - (cellProps) => { - // Use window width instead of layout width to address the issue in https://github.com/Expensify/App/issues/17760 - // considering horizontal margin and border width in centered modal - const modalStyles = styles.centeredModalStyles(isSmallScreenWidth, true); - const style = [cellProps.style, styles.h100, {width: PixelRatio.roundToNearestPixel(windowWidth - (modalStyles.marginHorizontal + modalStyles.borderWidth) * 2)}]; - - return ( - - ); - }, - [isSmallScreenWidth, windowWidth], - ); - /** * Defines how a single attachment should be rendered * @param {Object} item @@ -226,7 +202,7 @@ function AttachmentCarousel({report, reportActions, source, onNavigate, setDownl windowSize={5} maxToRenderPerBatch={3} data={attachments} - CellRendererComponent={renderCell} + CellRendererComponent={AttachmentCarouselCellRenderer} renderItem={renderItem} getItemLayout={getItemLayout} keyExtractor={(item) => item.source} diff --git a/src/components/AvatarCropModal/AvatarCropModal.js b/src/components/AvatarCropModal/AvatarCropModal.js index baa958106f84..d16ba1364b71 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" > {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/CountryPicker/CountrySelectorModal.js b/src/components/CountryPicker/CountrySelectorModal.js index ac543d9921d2..ba8c369d7b96 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" > Transaction.addStop(iou.transactionID)} + onPress={() => { + const newIndex = _.size(lodashGet(transaction, 'comment.waypoints', {})); + Navigation.navigate(ROUTES.getMoneyRequestWaypointRoute('request', newIndex)); + }} text={translate('distance.addStop')} isDisabled={numberOfWaypoints === MAX_WAYPOINTS} innerStyles={[styles.ph10]} @@ -308,6 +311,7 @@ function DistanceRequest({iou, iouType, report, transaction, mapboxAccessToken, {({safeAreaPaddingBottomStyle}) => ( diff --git a/src/components/ExceededCommentLength.js b/src/components/ExceededCommentLength.js index 2aa50779e10f..7c9ec4d2db25 100644 --- a/src/components/ExceededCommentLength.js +++ b/src/components/ExceededCommentLength.js @@ -4,6 +4,7 @@ import {debounce} from 'lodash'; import {withOnyx} from 'react-native-onyx'; import CONST from '../CONST'; import * as ReportUtils from '../libs/ReportUtils'; +import useLocalize from '../hooks/useLocalize'; import Text from './Text'; import styles from '../styles/styles'; import ONYXKEYS from '../ONYXKEYS'; @@ -25,6 +26,7 @@ const defaultProps = { }; function ExceededCommentLength(props) { + const {numberFormat, translate} = useLocalize(); const [commentLength, setCommentLength] = useState(0); const updateCommentLength = useMemo( () => @@ -44,7 +46,14 @@ function ExceededCommentLength(props) { return null; } - return {`${commentLength}/${CONST.MAX_COMMENT_LENGTH}`}; + return ( + + {translate('composer.commentExceededMaxLength', {formattedMaxLength: numberFormat(CONST.MAX_COMMENT_LENGTH)})} + + ); } ExceededCommentLength.propTypes = propTypes; diff --git a/src/components/HeaderPageLayout.js b/src/components/HeaderPageLayout.js index bec1e52b1cad..b57a5bc85e82 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" > {({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/ImageView/index.js b/src/components/ImageView/index.js index 3de7450c72a2..e0dce180043b 100644 --- a/src/components/ImageView/index.js +++ b/src/components/ImageView/index.js @@ -232,14 +232,14 @@ function ImageView({isAuthTokenRequired, url, fileName}) { source={{uri: url}} isAuthTokenRequired={isAuthTokenRequired} // Hide image until finished loading to prevent showing preview with wrong dimensions. - style={isLoading ? undefined : [styles.w100, styles.h100]} + style={isLoading || zoomScale === 0 ? undefined : [styles.w100, styles.h100]} // When Image dimensions are lower than the container boundary(zoomscale <= 1), use `contain` to render the image with natural dimensions. // Both `center` and `contain` keeps the image centered on both x and y axis. resizeMode={zoomScale > 1 ? Image.resizeMode.center : Image.resizeMode.contain} onLoadStart={imageLoadingStart} onLoad={imageLoad} /> - {isLoading && } + {(isLoading || zoomScale === 0) && } ); } 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..0aefa0649ce8 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 3a0741cf9bd2..2afc6240f85d 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -170,7 +170,7 @@ function MoneyRequestPreview(props) { !_.isEmpty(requestMerchant) && !props.isBillSplit && requestMerchant !== CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT && requestMerchant !== CONST.TRANSACTION.DEFAULT_MERCHANT; const shouldShowDescription = !_.isEmpty(description) && !shouldShowMerchant; - const receiptImages = hasReceipt ? [ReceiptUtils.getThumbnailAndImageURIs(props.transaction.receipt.source, props.transaction.filename || props.transaction.receiptFilename || '')] : []; + const receiptImages = hasReceipt ? [ReceiptUtils.getThumbnailAndImageURIs(props.transaction.receipt.source, props.transaction.filename || '')] : []; const getSettledMessage = () => { switch (lodashGet(props.action, 'originalMessage.paymentType', '')) { 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/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index c14e40010b54..1350c62bda88 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -120,9 +120,7 @@ function ReportPreview(props) { const isScanning = hasReceipts && ReportUtils.areAllRequestsBeingSmartScanned(props.iouReportID, props.action); const hasErrors = hasReceipts && ReportUtils.hasMissingSmartscanFields(props.iouReportID); const lastThreeTransactionsWithReceipts = ReportUtils.getReportPreviewDisplayTransactions(props.action); - const lastThreeReceipts = _.map(lastThreeTransactionsWithReceipts, ({receipt, filename, receiptFilename}) => - ReceiptUtils.getThumbnailAndImageURIs(receipt.source, filename || receiptFilename || ''), - ); + const lastThreeReceipts = _.map(lastThreeTransactionsWithReceipts, ({receipt, filename}) => ReceiptUtils.getThumbnailAndImageURIs(receipt.source, filename || '')); const hasOnlyOneReceiptRequest = numberOfRequests === 1 && hasReceipts; const previewSubtitle = hasOnlyOneReceiptRequest 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} > {}, 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 { const onDimensionChange = (newDimensions) => { const {window} = newDimensions; - setWindowDimension({ windowHeight: window.height, windowWidth: window.width, }); }; - const dimensionsEventListener = Dimensions.addEventListener('change', onDimensionChange); + const onDimensionChangeDebounce = lodashDebounce(onDimensionChange, 300); + + const dimensionsEventListener = Dimensions.addEventListener('change', onDimensionChangeDebounce); return () => { if (!dimensionsEventListener) { diff --git a/src/languages/en.ts b/src/languages/en.ts index 1882b7e97afb..f7c028d2a106 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -69,6 +69,7 @@ import type { SetTheRequestParams, UpdatedTheRequestParams, RemovedTheRequestParams, + FormattedMaxLengthParams, RequestedAmountMessageParams, TagSelectionParams, TranslationBase, @@ -282,6 +283,7 @@ export default { composer: { noExtensionFoundForMimeType: 'No extension found for mime type', problemGettingImageYouPasted: 'There was a problem getting the image you pasted', + commentExceededMaxLength: ({formattedMaxLength}: FormattedMaxLengthParams) => `The maximum comment length is ${formattedMaxLength} characters.`, }, baseUpdateAppModal: { updateApp: 'Update app', diff --git a/src/languages/es.ts b/src/languages/es.ts index 87c7a19fed8a..a68f33a33730 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -69,6 +69,7 @@ import type { SetTheRequestParams, UpdatedTheRequestParams, RemovedTheRequestParams, + FormattedMaxLengthParams, RequestedAmountMessageParams, TagSelectionParams, EnglishTranslation, @@ -272,6 +273,7 @@ export default { composer: { noExtensionFoundForMimeType: 'No se encontró una extension para este tipo de contenido', problemGettingImageYouPasted: 'Ha ocurrido un problema al obtener la imagen que has pegado', + commentExceededMaxLength: ({formattedMaxLength}: FormattedMaxLengthParams) => `El comentario debe tener máximo ${formattedMaxLength} caracteres.`, }, baseUpdateAppModal: { updateApp: 'Actualizar app', diff --git a/src/languages/types.ts b/src/languages/types.ts index 9af00ceef8de..70bf2e4cae3d 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -190,6 +190,8 @@ type RemovedTheRequestParams = {valueName: string; oldValueToDisplay: string}; type UpdatedTheRequestParams = {valueName: string; newValueToDisplay: string; oldValueToDisplay: string}; +type FormattedMaxLengthParams = {formattedMaxLength: string}; + type TagSelectionParams = {tagName: string}; /* Translation Object types */ @@ -303,5 +305,6 @@ export type { SetTheRequestParams, UpdatedTheRequestParams, RemovedTheRequestParams, + FormattedMaxLengthParams, TagSelectionParams, }; diff --git a/src/libs/BootSplash/index.native.js b/src/libs/BootSplash/index.native.ts similarity index 91% rename from src/libs/BootSplash/index.native.js rename to src/libs/BootSplash/index.native.ts index 942b3cadb74a..0790b4de89bc 100644 --- a/src/libs/BootSplash/index.native.js +++ b/src/libs/BootSplash/index.native.ts @@ -3,7 +3,7 @@ import Log from '../Log'; const BootSplash = NativeModules.BootSplash; -function hide() { +function hide(): Promise { 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/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/OnyxTabNavigator.js b/src/libs/Navigation/OnyxTabNavigator.js index dc68021bf515..2782054497b0 100644 --- a/src/libs/Navigation/OnyxTabNavigator.js +++ b/src/libs/Navigation/OnyxTabNavigator.js @@ -33,6 +33,7 @@ function OnyxTabNavigator({id, selectedTab, children, ...rest}) { id={id} initialRouteName={selectedTab} backBehavior="initialRoute" + keyboardDismissMode="none" screenListeners={{ state: (event) => { const state = event.data.state; 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..5847452537ef 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); } /** 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 2f9bd39d03c7..a5af66f08460 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1641,6 +1641,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. * @@ -1817,7 +1840,7 @@ function hasReportNameError(report) { } /** - * For comments shorter than 10k chars, convert the comment from MD into HTML because that's how it is stored in the database + * For comments shorter than or equal to 10k chars, convert the comment from MD into HTML because that's how it is stored in the database * For longer comments, skip parsing, but still escape the text, and display plaintext for performance reasons. It takes over 40s to parse a 100k long string!! * * @param {String} text @@ -1825,7 +1848,7 @@ function hasReportNameError(report) { */ function getParsedComment(text) { const parser = new ExpensiMark(); - return text.length < CONST.MAX_MARKUP_LENGTH ? parser.replace(text) : _.escape(text); + return text.length <= CONST.MAX_MARKUP_LENGTH ? parser.replace(text) : _.escape(text); } /** @@ -3676,6 +3699,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/actions/IOU.js b/src/libs/actions/IOU.js index 9816de9ec65e..65975222718d 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -1166,6 +1166,7 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC } // STEP 4: Compose the optimistic data + const currentTime = DateUtils.getDBTime(); const optimisticData = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -1189,6 +1190,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 = [ @@ -1209,6 +1218,7 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC created: null, currency: null, merchant: null, + category: null, }, }, }, @@ -1244,6 +1254,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 55b03110e925..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 @@ -1066,7 +1127,7 @@ const removeLinksFromHtml = (html, links) => { */ const handleUserDeletedLinksInHtml = (newCommentText, originalHtml) => { const parser = new ExpensiMark(); - if (newCommentText.length >= CONST.MAX_MARKUP_LENGTH) { + if (newCommentText.length > CONST.MAX_MARKUP_LENGTH) { return newCommentText; } const markdownOriginalComment = parser.htmlToMarkdown(originalHtml).trim(); @@ -1093,10 +1154,10 @@ function editReportComment(reportID, originalReportAction, textForNewComment) { const htmlForNewComment = handleUserDeletedLinksInHtml(textForNewComment, originalCommentHTML); const reportComment = parser.htmlToText(htmlForNewComment); - // For comments shorter than 10k chars, convert the comment from MD into HTML because that's how it is stored in the database + // For comments shorter than or equal to 10k chars, convert the comment from MD into HTML because that's how it is stored in the database // For longer comments, skip parsing and display plaintext for performance reasons. It takes over 40s to parse a 100k long string!! let parsedOriginalCommentHTML = originalCommentHTML; - if (textForNewComment.length < CONST.MAX_MARKUP_LENGTH) { + if (textForNewComment.length <= CONST.MAX_MARKUP_LENGTH) { const autolinkFilter = {filterRules: _.filter(_.pluck(parser.rules, 'name'), (name) => name !== 'autolink')}; parsedOriginalCommentHTML = parser.replace(parser.htmlToMarkdown(originalCommentHTML).trim(), autolinkFilter); } @@ -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/actions/Transaction.js b/src/libs/actions/Transaction.js index 663dfd1c8564..81764a9c62be 100644 --- a/src/libs/actions/Transaction.js +++ b/src/libs/actions/Transaction.js @@ -138,6 +138,10 @@ function removeWaypoint(transactionID, currentIndex) { if (!isRemovedWaypointEmpty) { newTransaction = { ...newTransaction, + // Clear any errors that may be present, which apply to the old route + errorFields: { + route: null, + }, // Clear the existing route so that we don't show an old route routes: { route0: { 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 e691ea22ba79..588e1a5642aa 100644 --- a/src/libs/migrateOnyx.js +++ b/src/libs/migrateOnyx.js @@ -1,12 +1,11 @@ import _ from 'underscore'; import Log from './Log'; import AddEncryptedAuthToken from './migrations/AddEncryptedAuthToken'; -import RenameActiveClientsKey from './migrations/RenameActiveClientsKey'; 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'; +import RenameReceiptFilename from './migrations/RenameReceiptFilename'; export default function () { const startTime = Date.now(); @@ -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, - RenameActiveClientsKey, - RenamePriorityModeKey, - AddEncryptedAuthToken, - RenameExpensifyNewsStatus, - AddLastVisibleActionCreated, - PersonalDetailsByAccountID, - ]; + 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/migrations/RenameActiveClientsKey.js b/src/libs/migrations/RenameActiveClientsKey.js deleted file mode 100644 index 54b36e13cff5..000000000000 --- a/src/libs/migrations/RenameActiveClientsKey.js +++ /dev/null @@ -1,33 +0,0 @@ -import Onyx from 'react-native-onyx'; -import _ from 'underscore'; -import Log from '../Log'; -import ONYXKEYS from '../../ONYXKEYS'; - -// This migration changes the name of the Onyx key ACTIVE_CLIENTS from activeClients2 to activeClients -export default function () { - return new Promise((resolve) => { - // Connect to the old key in Onyx to get the old value of activeClients2 - // then set the new key activeClients to hold the old data - // finally remove the old key by setting the value to null - const connectionID = Onyx.connect({ - key: 'activeClients2', - callback: (oldActiveClients) => { - Onyx.disconnect(connectionID); - - // Fail early here because there is nothing to migrate - if (_.isEmpty(oldActiveClients)) { - Log.info('[Migrate Onyx] Skipped migration RenameActiveClientsKey'); - return resolve(); - } - - Onyx.multiSet({ - activeClients2: null, - [ONYXKEYS.ACTIVE_CLIENTS]: oldActiveClients, - }).then(() => { - Log.info('[Migrate Onyx] Ran migration RenameActiveClientsKey'); - resolve(); - }); - }, - }); - }); -} diff --git a/src/libs/migrations/RenameReceiptFilename.js b/src/libs/migrations/RenameReceiptFilename.js new file mode 100644 index 000000000000..b8df705fd7d1 --- /dev/null +++ b/src/libs/migrations/RenameReceiptFilename.js @@ -0,0 +1,57 @@ +import Onyx from 'react-native-onyx'; +import _ from 'underscore'; +import lodashHas from 'lodash/has'; +import ONYXKEYS from '../../ONYXKEYS'; +import Log from '../Log'; + +// This migration changes the property name on a transaction from receiptFilename to filename so that it matches what is stored in the database +export default function () { + return new Promise((resolve) => { + // Connect to the TRANSACTION collection key in Onyx to get all of the stored transactions. + // Go through each transaction and change the property name + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (transactions) => { + Onyx.disconnect(connectionID); + + if (!transactions || transactions.length === 0) { + Log.info('[Migrate Onyx] Skipped migration RenameReceiptFilename because there are no transactions'); + return resolve(); + } + + if (!_.compact(_.pluck(transactions, 'receiptFilename')).length) { + Log.info('[Migrate Onyx] Skipped migration RenameReceiptFilename because there were no transactions with the receiptFilename property'); + return resolve(); + } + + Log.info('[Migrate Onyx] Running RenameReceiptFilename migration'); + + const dataToSave = _.reduce( + transactions, + (result, transaction) => { + // Do nothing if there is no receiptFilename property + if (!lodashHas(transaction, 'receiptFilename')) { + return result; + } + Log.info(`[Migrate Onyx] Renaming receiptFilename ${transaction.receiptFilename} to filename`); + return { + ...result, + [`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`]: { + filename: transaction.receiptFilename, + receiptFilename: null, + }, + }; + }, + {}, + ); + + // eslint-disable-next-line rulesdir/prefer-actions-set-data + Onyx.mergeCollection(ONYXKEYS.COLLECTION.TRANSACTION, dataToSave).then(() => { + Log.info(`[Migrate Onyx] Ran migration RenameReceiptFilename and renamed ${_.size(dataToSave)} properties`); + resolve(); + }); + }, + }); + }); +} 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..cc50f927d428 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" > + { + 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..344e767a69ca 100644 --- a/src/pages/EditRequestCreatedPage.js +++ b/src/pages/EditRequestCreatedPage.js @@ -23,6 +23,7 @@ function EditRequestCreatedPage({defaultCreated, onSubmit}) {
    descriptionInputRef.current && descriptionInputRef.current.focus()} + testID="EditRequestDescriptionPage" > merchantInputRef.current && merchantInputRef.current.focus()} + testID="EditRequestMerchantPage" > { + 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..ea6e85a9b9d3 100644 --- a/src/pages/EditRequestReceiptPage.js +++ b/src/pages/EditRequestReceiptPage.js @@ -31,6 +31,7 @@ function EditRequestReceiptPage({route, transactionID}) { + {() => { if (this.props.userWallet.errorCode === CONST.WALLET.ERROR.KYC) { return ( diff --git a/src/pages/ErrorPage/NotFoundPage.js b/src/pages/ErrorPage/NotFoundPage.js index f0121cd8f3ef..bcf0da3ae9a2 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..6af38c84b582 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..d5b790a3b5ca 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..a90ae3030ba2 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" > {({safeAreaPaddingBottomStyle, insets}) => ( focusAndUpdateMultilineInputRange(privateNotesInput.current)} + testID="PrivateNotesEditPage" > + 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..92967795a756 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..38604f0bf2b9 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..fe68a5d7ed2b 100755 --- a/src/pages/ReportParticipantsPage.js +++ b/src/pages/ReportParticipantsPage.js @@ -99,7 +99,10 @@ function ReportParticipantsPage(props) { })); return ( - + {({safeAreaPaddingBottomStyle}) => ( {({didScreenTransitionEnd}) => ( diff --git a/src/pages/SearchPage.js b/src/pages/SearchPage.js index 2ee29380ff80..ce43b757428a 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..6f35bf1b3600 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..d4e69dd5dd3b 100644 --- a/src/pages/TeachersUnite/ImTeacherPage.js +++ b/src/pages/TeachersUnite/ImTeacherPage.js @@ -19,7 +19,7 @@ function ImTeacherPage() { const {translate} = useLocalize(); return ( - + Navigation.goBack(ROUTES.TEACHERS_UNITE)} diff --git a/src/pages/TeachersUnite/IntroSchoolPrincipalPage.js b/src/pages/TeachersUnite/IntroSchoolPrincipalPage.js index fe25c54cb8cc..10bc47c00556 100644 --- a/src/pages/TeachersUnite/IntroSchoolPrincipalPage.js +++ b/src/pages/TeachersUnite/IntroSchoolPrincipalPage.js @@ -80,7 +80,10 @@ function IntroSchoolPrincipalPage(props) { ); return ( - + Navigation.goBack(ROUTES.TEACHERS_UNITE)} diff --git a/src/pages/TeachersUnite/KnowATeacherPage.js b/src/pages/TeachersUnite/KnowATeacherPage.js index 795bc170a83c..774217899ce4 100644 --- a/src/pages/TeachersUnite/KnowATeacherPage.js +++ b/src/pages/TeachersUnite/KnowATeacherPage.js @@ -91,7 +91,10 @@ function KnowATeacherPage(props) { ); return ( - + Navigation.goBack(ROUTES.TEACHERS_UNITE)} diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 63e60a545de9..f2b0875dbc53 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -16,15 +16,14 @@ import * as ReportUtils from '../../libs/ReportUtils'; import ReportActionsView from './report/ReportActionsView'; import ReportActionsSkeletonView from '../../components/ReportActionsSkeletonView'; import reportActionPropTypes from './report/reportActionPropTypes'; -import {withNetwork} from '../../components/OnyxProvider'; +import useNetwork from '../../hooks/useNetwork'; +import useWindowDimensions from '../../hooks/useWindowDimensions'; +import useLocalize from '../../hooks/useLocalize'; import compose from '../../libs/compose'; import Visibility from '../../libs/Visibility'; -import networkPropTypes from '../../components/networkPropTypes'; -import withWindowDimensions, {windowDimensionsPropTypes} from '../../components/withWindowDimensions'; import OfflineWithFeedback from '../../components/OfflineWithFeedback'; import ReportFooter from './report/ReportFooter'; import Banner from '../../components/Banner'; -import withLocalize from '../../components/withLocalize'; import reportPropTypes from '../reportPropTypes'; import FullPageNotFoundView from '../../components/BlockingViews/FullPageNotFoundView'; import withViewportOffsetTop, {viewportOffsetTopPropTypes} from '../../components/withViewportOffsetTop'; @@ -80,16 +79,15 @@ const propTypes = { }), ), - /** Information about the network */ - network: networkPropTypes.isRequired, - /** The account manager report ID */ accountManagerReportID: PropTypes.string, /** All of the personal details for everyone */ personalDetails: PropTypes.objectOf(personalDetailsPropType), - ...windowDimensionsPropTypes, + /** Whether user is leaving the current report */ + userLeavingStatus: PropTypes.bool, + ...viewportOffsetTopPropTypes, ...withCurrentReportIDPropTypes, }; @@ -105,6 +103,7 @@ const defaultProps = { betas: [], policies: {}, accountManagerReportID: null, + userLeavingStatus: false, personalDetails: {}, ...withCurrentReportIDDefaultProps, }; @@ -138,19 +137,22 @@ function ReportScreen({ accountManagerReportID, personalDetails, policies, - translate, - network, - isSmallScreenWidth, isSidebarLoaded, viewportOffsetTop, isComposerFullSize, errors, + userLeavingStatus, currentReportID, }) { + const {translate} = useLocalize(); + const {isOffline} = useNetwork(); + const {isSmallScreenWidth} = useWindowDimensions(); + const firstRenderRef = useRef(true); const flatListRef = useRef(); const reactionListRef = useRef(); const prevReport = usePrevious(report); + const prevUserLeavingStatus = usePrevious(userLeavingStatus); const [skeletonViewContainerHeight, setSkeletonViewContainerHeight] = useState(0); const [isBannerVisible, setIsBannerVisible] = useState(true); @@ -175,6 +177,7 @@ function ReportScreen({ const policy = policies[`${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`]; const isTopMostReportId = currentReportID === getReportID(route); + const didSubscribeToReportLeavingEvents = useRef(false); const isDefaultReport = checkDefaultReport(report); @@ -234,7 +237,7 @@ function ReportScreen({ } // It possible that we may not have the report object yet in Onyx yet e.g. we navigated to a URL for an accessible report that - // is not stored locally yet. If props.report.reportID exists, then the report has been stored locally and nothing more needs to be done. + // is not stored locally yet. If report.reportID exists, then the report has been stored locally and nothing more needs to be done. // If it doesn't exist, then we fetch the report from the API. if (report.reportID && report.reportID === getReportID(route)) { return; @@ -282,6 +285,14 @@ function ReportScreen({ useEffect(() => { fetchReportIfNeeded(); ComposerActions.setShouldShowComposeInput(true); + return () => { + if (!didSubscribeToReportLeavingEvents) { + return; + } + + Report.unsubscribeFromLeavingRoomReportChannel(report.reportID); + }; + // I'm disabling the warning, as it expects to use exhaustive deps, even though we want this useEffect to run only on the first render. // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -292,24 +303,51 @@ function ReportScreen({ firstRenderRef.current = false; return; } + + const onyxReportID = report.reportID; + const prevOnyxReportID = prevReport.reportID; + const routeReportID = getReportID(route); + + // Navigate to the Concierge chat if the room was removed from another device (e.g. user leaving a room) + if ( + // non-optimistic case + (!prevUserLeavingStatus && userLeavingStatus) || + // optimistic case + (prevOnyxReportID && prevOnyxReportID === routeReportID && !onyxReportID && prevReport.statusNum === CONST.REPORT.STATUS.OPEN && report.statusNum === CONST.REPORT.STATUS.CLOSED) + ) { + Navigation.goBack(); + Report.navigateToConciergeChat(); + return; + } + // If you already have a report open and are deeplinking to a new report on native, // the ReportScreen never actually unmounts and the reportID in the route also doesn't change. // Therefore, we need to compare if the existing reportID is the same as the one in the route // before deciding that we shouldn't call OpenReport. - const onyxReportID = report.reportID; - const routeReportID = getReportID(route); if (onyxReportID === prevReport.reportID && (!onyxReportID || onyxReportID === routeReportID)) { return; } fetchReportIfNeeded(); ComposerActions.setShouldShowComposeInput(true); - }, [route, report, errors, fetchReportIfNeeded, prevReport.reportID]); + }, [route, report, errors, fetchReportIfNeeded, prevReport.reportID, prevUserLeavingStatus, userLeavingStatus, prevReport.statusNum]); + + useEffect(() => { + // Ensures subscription event succeeds when the report/workspace room is created optimistically. + // Check if the optimistic `OpenReport` or `AddWorkspaceRoom` has succeeded by confirming + // any `pendingFields.createChat` or `pendingFields.addWorkspaceRoom` fields are set to null. + // Existing reports created will have empty fields for `pendingFields`. + const didCreateReportSuccessfully = !report.pendingFields || (!report.pendingFields.addWorkspaceRoom && !report.pendingFields.createChat); + if (!didSubscribeToReportLeavingEvents.current && didCreateReportSuccessfully) { + Report.subscribeToReportLeavingEvents(reportID); + didSubscribeToReportLeavingEvents.current = true; + } + }, [report, didSubscribeToReportLeavingEvents, reportID]); // eslint-disable-next-line rulesdir/no-negated-variables const shouldShowNotFoundPage = useMemo( - () => (!_.isEmpty(report) && !isDefaultReport && !report.reportID && !isOptimisticDelete && !report.isLoadingReportActions && !isLoading) || shouldHideReport, - [report, isLoading, shouldHideReport, isDefaultReport, isOptimisticDelete], + () => (!_.isEmpty(report) && !isDefaultReport && !report.reportID && !isOptimisticDelete && !report.isLoadingReportActions && !isLoading && !userLeavingStatus) || shouldHideReport, + [report, isLoading, shouldHideReport, isDefaultReport, isOptimisticDelete, userLeavingStatus], ); return ( @@ -322,6 +360,7 @@ function ReportScreen({ )} @@ -420,9 +459,6 @@ ReportScreen.displayName = 'ReportScreen'; export default compose( withViewportOffsetTop, - withLocalize, - withWindowDimensions, - withNetwork(), withCurrentReportID, withOnyx({ isSidebarLoaded: { @@ -451,5 +487,8 @@ export default compose( personalDetails: { key: ONYXKEYS.PERSONAL_DETAILS_LIST, }, + userLeavingStatus: { + key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${getReportID(route)}`, + }, }), )(ReportScreen); diff --git a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js index 3d54306b6248..6697aa793f7e 100644 --- a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js +++ b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js @@ -34,6 +34,7 @@ function BaseSidebarScreen(props) { includeSafeAreaPaddingBottom={false} shouldEnableKeyboardAvoidingView={false} style={[styles.sidebar, Browser.isMobile() ? styles.userSelectNone : {}]} + testID="SidebarScreen" > {({insets}) => ( <> diff --git a/src/pages/iou/IOUCurrencySelection.js b/src/pages/iou/IOUCurrencySelection.js index 1238d8934f75..fa0cb3e9da36 100644 --- a/src/pages/iou/IOUCurrencySelection.js +++ b/src/pages/iou/IOUCurrencySelection.js @@ -96,6 +96,7 @@ function IOUCurrencySelection(props) { const currencyOptions = _.map(currencyList, (currencyInfo, currencyCode) => { const isSelectedCurrency = currencyCode === selectedCurrencyCode; return { + currencyName: currencyInfo.name, text: `${currencyCode} - ${CurrencyUtils.getLocalizedCurrencySymbol(currencyCode)}`, currencyCode, keyForList: currencyCode, @@ -105,7 +106,7 @@ function IOUCurrencySelection(props) { }); const searchRegex = new RegExp(Str.escapeForRegExp(searchValue.trim()), 'i'); - const filteredCurrencies = _.filter(currencyOptions, (currencyOption) => searchRegex.test(currencyOption.text)); + const filteredCurrencies = _.filter(currencyOptions, (currencyOption) => searchRegex.test(currencyOption.text) || searchRegex.test(currencyOption.currencyName)); const isEmpty = searchValue.trim() && !filteredCurrencies.length; return { @@ -131,6 +132,7 @@ function IOUCurrencySelection(props) { optionsSelectorRef.current && optionsSelectorRef.current.focus()} + testID="IOUCurrencySelection" > {({safeAreaPaddingBottomStyle}) => ( <> diff --git a/src/pages/iou/MoneyRequestCategoryPage.js b/src/pages/iou/MoneyRequestCategoryPage.js index 80b88a762609..63eb35d3d4a1 100644 --- a/src/pages/iou/MoneyRequestCategoryPage.js +++ b/src/pages/iou/MoneyRequestCategoryPage.js @@ -10,6 +10,8 @@ import HeaderWithBackButton from '../../components/HeaderWithBackButton'; import CategoryPicker from '../../components/CategoryPicker'; import ONYXKEYS from '../../ONYXKEYS'; import reportPropTypes from '../reportPropTypes'; +import * as IOU from '../../libs/actions/IOU'; +import {iouPropTypes, iouDefaultProps} from './propTypes'; const propTypes = { /** Navigation route context info provided by react navigation */ @@ -24,15 +26,20 @@ const propTypes = { }), }).isRequired, + /* Onyx Props */ /** The report currently being used */ report: reportPropTypes, + + /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ + iou: iouPropTypes, }; const defaultProps = { report: {}, + iou: iouDefaultProps, }; -function MoneyRequestCategoryPage({route, report}) { +function MoneyRequestCategoryPage({route, report, iou}) { const {translate} = useLocalize(); const reportID = lodashGet(route, 'params.reportID', ''); @@ -42,10 +49,21 @@ function MoneyRequestCategoryPage({route, report}) { Navigation.goBack(ROUTES.getMoneyRequestConfirmationRoute(iouType, reportID)); }; + const updateCategory = (category) => { + if (category.searchText === iou.category) { + IOU.resetMoneyRequestCategory(); + } else { + IOU.setMoneyRequestCategory(category.searchText); + } + + Navigation.goBack(ROUTES.getMoneyRequestConfirmationRoute(iouType, reportID)); + }; + return ( ); @@ -69,4 +87,7 @@ export default withOnyx({ report: { key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${lodashGet(route, 'params.reportID', '')}`, }, + iou: { + key: ONYXKEYS.IOU, + }, })(MoneyRequestCategoryPage); diff --git a/src/pages/iou/MoneyRequestDatePage.js b/src/pages/iou/MoneyRequestDatePage.js index 68af1a6244b6..1f14eac17603 100644 --- a/src/pages/iou/MoneyRequestDatePage.js +++ b/src/pages/iou/MoneyRequestDatePage.js @@ -85,6 +85,7 @@ function MoneyRequestDatePage({iou, route, selectedTab}) { focusAndUpdateMultilineInputRange(inputRef.current)} + testID="MoneyRequestDescriptionPage" > inputRef.current && inputRef.current.focus()} + testID="MoneyRequestMerchantPage" > {({safeAreaPaddingBottomStyle}) => ( diff --git a/src/pages/iou/MoneyRequestTagPage.js b/src/pages/iou/MoneyRequestTagPage.js index c9b5eb4f8f6f..b00643f65f4d 100644 --- a/src/pages/iou/MoneyRequestTagPage.js +++ b/src/pages/iou/MoneyRequestTagPage.js @@ -80,6 +80,7 @@ function MoneyRequestTagPage({route, report, policyTags, iou}) { `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report ? report.policyID : '0'}`, diff --git a/src/pages/iou/SplitBillDetailsPage.js b/src/pages/iou/SplitBillDetailsPage.js index 0107b041abf4..67fb489a43f1 100644 --- a/src/pages/iou/SplitBillDetailsPage.js +++ b/src/pages/iou/SplitBillDetailsPage.js @@ -72,7 +72,7 @@ function SplitBillDetailsPage(props) { const {amount: splitAmount, currency: splitCurrency, comment: splitComment} = ReportUtils.getTransactionDetails(transaction); return ( - + { + if (parsedWaypointIndex < _.size(allWaypoints)) { + Transaction.saveWaypoint(transactionID, waypointIndex, waypoint); + } else { + const finishWaypoint = lodashGet(allWaypoints, `waypoint${_.size(allWaypoints) - 1}`, {}); + Transaction.saveWaypoint(transactionID, waypointIndex, finishWaypoint); + Transaction.saveWaypoint(transactionID, waypointIndex - 1, waypoint); + } + }; + const onSubmit = (values) => { const waypointValue = values[`waypoint${waypointIndex}`] || ''; @@ -133,8 +143,7 @@ function WaypointEditor({transactionID, route: {params: {iouType = '', waypointI lng: null, address: waypointValue, }; - - Transaction.saveWaypoint(transactionID, waypointIndex, waypoint); + saveWaypoint(waypoint); } // Other flows will be handled by selecting a waypoint with selectWaypoint as this is mainly for the offline flow @@ -153,18 +162,27 @@ function WaypointEditor({transactionID, route: {params: {iouType = '', waypointI lng: values.lng, address: values.address, }; - - Transaction.saveWaypoint(transactionID, waypointIndex, waypoint); + saveWaypoint(waypoint); Navigation.goBack(ROUTES.getMoneyRequestDistanceTabRoute(iouType)); }; + const focusAddressInput = () => { + InteractionManager.runAfterInteractions(() => { + if (!textInput.current) { + return; + } + textInput.current.focus(); + }); + }; + return ( textInput.current && textInput.current.focus()} shouldEnableMaxHeight + testID="WaypointEditor" > - waypointCount - 1) && isFocused}> + waypointCount) && isFocused}> setIsDeleteStopModalOpen(true), }, ]} + onModalHide={focusAddressInput} /> setIsDeleteStopModalOpen(false)} + onModalHide={focusAddressInput} prompt={translate('distance.deleteWaypointConfirmation')} confirmText={translate('common.delete')} cancelText={translate('common.cancel')} diff --git a/src/pages/iou/propTypes/index.js b/src/pages/iou/propTypes/index.js index 5ecd00d11876..586f8424a2c2 100644 --- a/src/pages/iou/propTypes/index.js +++ b/src/pages/iou/propTypes/index.js @@ -18,6 +18,9 @@ const iouPropTypes = PropTypes.shape({ /** The merchant name */ merchant: PropTypes.string, + /** The category name */ + category: PropTypes.string, + /** The tag */ tag: PropTypes.string, @@ -37,6 +40,7 @@ const iouDefaultProps = { currency: CONST.CURRENCY.USD, comment: '', merchant: '', + category: '', tag: '', created: '', participants: [], diff --git a/src/pages/iou/steps/MoneyRequestConfirmPage.js b/src/pages/iou/steps/MoneyRequestConfirmPage.js index 93ee2c7f8aac..83716374720f 100644 --- a/src/pages/iou/steps/MoneyRequestConfirmPage.js +++ b/src/pages/iou/steps/MoneyRequestConfirmPage.js @@ -277,7 +277,10 @@ function MoneyRequestConfirmPage(props) { }; return ( - + {({safeAreaPaddingBottomStyle}) => ( { @@ -372,6 +376,7 @@ export default compose( key: `${ONYXKEYS.COLLECTION.SELECTED_TAB}${CONST.TAB.RECEIPT_TAB_ID}`, }, }), + // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file withOnyx({ policy: { key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`, diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js index cb9f4cdd9b7f..0f08e591324b 100644 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js @@ -63,7 +63,19 @@ function MoneyRequestParticipantsPage({iou, selectedTab, route}) { setHeaderTitle(_.isEmpty(iou.participants) ? translate('tabSelector.manual') : translate('iou.split')); }, [iou.participants, isDistanceRequest, translate]); - const navigateToNextStep = (moneyRequestType) => { + const navigateToRequestStep = (moneyRequestType, option) => { + if (option.reportID) { + isNewReportIDSelectedLocally.current = true; + IOU.setMoneyRequestId(`${moneyRequestType}${option.reportID}`); + Navigation.navigate(ROUTES.getMoneyRequestConfirmationRoute(moneyRequestType, option.reportID)); + return; + } + + IOU.setMoneyRequestId(moneyRequestType); + Navigation.navigate(ROUTES.getMoneyRequestConfirmationRoute(moneyRequestType, reportID.current)); + }; + + const navigateToSplitStep = (moneyRequestType) => { IOU.setMoneyRequestId(moneyRequestType); Navigation.navigate(ROUTES.getMoneyRequestConfirmationRoute(moneyRequestType, reportID.current)); }; @@ -102,6 +114,7 @@ function MoneyRequestParticipantsPage({iou, selectedTab, route}) { includeSafeAreaPaddingBottom={false} shouldEnableMaxHeight={DeviceCapabilities.canUseTouchScreen()} onEntryTransitionEnd={() => optionsSelectorRef.current && optionsSelectorRef.current.focus()} + testID="MoneyRequestParticipantsPage" > {({safeAreaPaddingBottomStyle}) => ( @@ -113,8 +126,8 @@ function MoneyRequestParticipantsPage({iou, selectedTab, route}) { ref={(el) => (optionsSelectorRef.current = el)} participants={iou.participants} onAddParticipants={IOU.setMoneyRequestParticipants} - navigateToRequest={() => navigateToNextStep(CONST.IOU.MONEY_REQUEST_TYPE.REQUEST)} - navigateToSplit={() => navigateToNextStep(CONST.IOU.MONEY_REQUEST_TYPE.SPLIT)} + navigateToRequest={(option) => navigateToRequestStep(CONST.IOU.MONEY_REQUEST_TYPE.REQUEST, option)} + navigateToSplit={() => navigateToSplitStep(CONST.IOU.MONEY_REQUEST_TYPE.SPLIT)} safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} iouType={iouType.current} isDistanceRequest={isDistanceRequest} diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js index a8da1b4ec9ed..170ee042bffa 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js @@ -161,7 +161,7 @@ function MoneyRequestParticipantsSelector({ onAddParticipants([ {accountID: option.accountID, login: option.login, isPolicyExpenseChat: option.isPolicyExpenseChat, reportID: option.reportID, selected: true, searchText: option.searchText}, ]); - navigateToRequest(); + navigateToRequest(option); }; /** diff --git a/src/pages/iou/steps/NewRequestAmountPage.js b/src/pages/iou/steps/NewRequestAmountPage.js index b3d777aec2d5..488bd658cb33 100644 --- a/src/pages/iou/steps/NewRequestAmountPage.js +++ b/src/pages/iou/steps/NewRequestAmountPage.js @@ -181,6 +181,7 @@ function NewRequestAmountPage({route, iou, report, selectedTab}) { includeSafeAreaPaddingBottom={false} shouldEnableKeyboardAvoidingView={false} onEntryTransitionEnd={focusTextInput} + testID="NewRequestAmountPage" > {({safeAreaPaddingBottomStyle}) => ( diff --git a/src/pages/settings/AboutPage/AboutPage.js b/src/pages/settings/AboutPage/AboutPage.js index 4af48b896fe0..105ea79637bb 100644 --- a/src/pages/settings/AboutPage/AboutPage.js +++ b/src/pages/settings/AboutPage/AboutPage.js @@ -81,7 +81,10 @@ function AboutPage(props) { ]; return ( - + {({safeAreaPaddingBottomStyle}) => ( <> + Navigation.goBack(ROUTES.SETTINGS_ABOUT)} diff --git a/src/pages/settings/Preferences/LanguagePage.js b/src/pages/settings/Preferences/LanguagePage.js index 706fa3b62db6..5004b85312f3 100644 --- a/src/pages/settings/Preferences/LanguagePage.js +++ b/src/pages/settings/Preferences/LanguagePage.js @@ -26,7 +26,10 @@ function LanguagePage(props) { })); return ( - + Navigation.goBack(ROUTES.SETTINGS_PREFERENCES)} diff --git a/src/pages/settings/Preferences/PriorityModePage.js b/src/pages/settings/Preferences/PriorityModePage.js index 0c3d28fe9d81..76791640f9bf 100644 --- a/src/pages/settings/Preferences/PriorityModePage.js +++ b/src/pages/settings/Preferences/PriorityModePage.js @@ -46,7 +46,10 @@ function PriorityModePage(props) { ); return ( - + Navigation.goBack(ROUTES.SETTINGS_PREFERENCES)} diff --git a/src/pages/settings/Preferences/ThemePage.js b/src/pages/settings/Preferences/ThemePage.js index 4e41d5fc7129..a901ee8d350a 100644 --- a/src/pages/settings/Preferences/ThemePage.js +++ b/src/pages/settings/Preferences/ThemePage.js @@ -35,7 +35,10 @@ function ThemePage(props) { })); return ( - + + this.validateCodeFormRef.current && this.validateCodeFormRef.current.focus()}> + this.validateCodeFormRef.current && this.validateCodeFormRef.current.focus()} + testID="ContactMethodDetailsPage" + > Navigation.goBack(ROUTES.SETTINGS_CONTACT_METHODS)} diff --git a/src/pages/settings/Profile/Contacts/ContactMethodsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodsPage.js index 62e62183a156..41b44e5b6339 100644 --- a/src/pages/settings/Profile/Contacts/ContactMethodsPage.js +++ b/src/pages/settings/Profile/Contacts/ContactMethodsPage.js @@ -111,7 +111,10 @@ function ContactMethodsPage(props) { }); return ( - + Navigation.goBack(ROUTES.SETTINGS_PROFILE)} diff --git a/src/pages/settings/Profile/Contacts/NewContactMethodPage.js b/src/pages/settings/Profile/Contacts/NewContactMethodPage.js index 236b7c1c73aa..0de9c34e14b6 100644 --- a/src/pages/settings/Profile/Contacts/NewContactMethodPage.js +++ b/src/pages/settings/Profile/Contacts/NewContactMethodPage.js @@ -98,6 +98,7 @@ function NewContactMethodPage(props) { }} includeSafeAreaPaddingBottom={false} shouldEnableMaxHeight + testID="NewContactMethodPage" > + Navigation.goBack(ROUTES.SETTINGS_STATUS)} diff --git a/src/pages/settings/Profile/DisplayNamePage.js b/src/pages/settings/Profile/DisplayNamePage.js index d93eeed2689c..d4924636acbc 100644 --- a/src/pages/settings/Profile/DisplayNamePage.js +++ b/src/pages/settings/Profile/DisplayNamePage.js @@ -69,6 +69,7 @@ function DisplayNamePage(props) { + + Navigation.goBack(ROUTES.SETTINGS_PERSONAL_DETAILS)} diff --git a/src/pages/settings/Profile/PersonalDetails/LegalNamePage.js b/src/pages/settings/Profile/PersonalDetails/LegalNamePage.js index 0caf20a3e128..4fb538ac59b6 100644 --- a/src/pages/settings/Profile/PersonalDetails/LegalNamePage.js +++ b/src/pages/settings/Profile/PersonalDetails/LegalNamePage.js @@ -71,6 +71,7 @@ function LegalNamePage(props) { + Navigation.goBack(ROUTES.SETTINGS_PROFILE)} diff --git a/src/pages/settings/Profile/ProfilePage.js b/src/pages/settings/Profile/ProfilePage.js index 1864ba60524c..edc6e29b58a6 100755 --- a/src/pages/settings/Profile/ProfilePage.js +++ b/src/pages/settings/Profile/ProfilePage.js @@ -107,7 +107,10 @@ function ProfilePage(props) { }, [props.currentUserPersonalDetails]); return ( - + Navigation.goBack(ROUTES.SETTINGS)} diff --git a/src/pages/settings/Profile/PronounsPage.js b/src/pages/settings/Profile/PronounsPage.js index 5f8824046d69..4f8fb7cdb0df 100644 --- a/src/pages/settings/Profile/PronounsPage.js +++ b/src/pages/settings/Profile/PronounsPage.js @@ -65,7 +65,10 @@ function PronounsPage({currentUserPersonalDetails}) { }; return ( - + Navigation.goBack(ROUTES.SETTINGS_PROFILE)} diff --git a/src/pages/settings/Profile/TimezoneInitialPage.js b/src/pages/settings/Profile/TimezoneInitialPage.js index 94cb40a3f6f3..101d5b2cf77b 100644 --- a/src/pages/settings/Profile/TimezoneInitialPage.js +++ b/src/pages/settings/Profile/TimezoneInitialPage.js @@ -41,7 +41,7 @@ function TimezoneInitialPage(props) { }; return ( - + Navigation.goBack(ROUTES.SETTINGS_PROFILE)} diff --git a/src/pages/settings/Profile/TimezoneSelectPage.js b/src/pages/settings/Profile/TimezoneSelectPage.js index 228bae5cbb6b..05de3657e0ac 100644 --- a/src/pages/settings/Profile/TimezoneSelectPage.js +++ b/src/pages/settings/Profile/TimezoneSelectPage.js @@ -78,7 +78,10 @@ function TimezoneSelectPage(props) { }; return ( - + Navigation.goBack(ROUTES.SETTINGS_TIMEZONE)} diff --git a/src/pages/settings/Report/NotificationPreferencePage.js b/src/pages/settings/Report/NotificationPreferencePage.js index 17bd77a235ce..5fdb07ceddfa 100644 --- a/src/pages/settings/Report/NotificationPreferencePage.js +++ b/src/pages/settings/Report/NotificationPreferencePage.js @@ -43,7 +43,10 @@ function NotificationPreferencePage(props) { ); return ( - + + roomNameInputRef.current && roomNameInputRef.current.focus()} includeSafeAreaPaddingBottom={false} + testID="RoomNamePage" > + + Navigation.goBack(ROUTES.SETTINGS_SECURITY)} diff --git a/src/pages/settings/Security/TwoFactorAuth/StepWrapper/StepWrapper.js b/src/pages/settings/Security/TwoFactorAuth/StepWrapper/StepWrapper.js index 16f9489d87b0..817bd9e5fb5b 100644 --- a/src/pages/settings/Security/TwoFactorAuth/StepWrapper/StepWrapper.js +++ b/src/pages/settings/Security/TwoFactorAuth/StepWrapper/StepWrapper.js @@ -24,6 +24,7 @@ function StepWrapper({ nameOnCardRef.current && nameOnCardRef.current.focus()} includeSafeAreaPaddingBottom={false} + testID="AddDebitCardPage" > + Navigation.goBack(ROUTES.SETTINGS_WALLET_TRANSFER_BALANCE)} diff --git a/src/pages/settings/Wallet/TransferBalancePage.js b/src/pages/settings/Wallet/TransferBalancePage.js index b52695c2e922..10e591e5945b 100644 --- a/src/pages/settings/Wallet/TransferBalancePage.js +++ b/src/pages/settings/Wallet/TransferBalancePage.js @@ -137,7 +137,7 @@ function TransferBalancePage(props) { if (props.walletTransfer.shouldShowSuccess && !props.walletTransfer.loading) { return ( - + + + Navigation.goBack(ROUTES.SETTINGS)} diff --git a/src/pages/signin/SignInModal.js b/src/pages/signin/SignInModal.js index 50b0af0bfa05..03788298debf 100644 --- a/src/pages/signin/SignInModal.js +++ b/src/pages/signin/SignInModal.js @@ -22,6 +22,7 @@ function SignInModal() { style={[styles.highlightBG]} includeSafeAreaPaddingBottom={false} shouldEnableMaxHeight + testID="SignInModal" > diff --git a/src/pages/tasks/NewTaskDescriptionPage.js b/src/pages/tasks/NewTaskDescriptionPage.js index dacf368a574e..008e31a96c1d 100644 --- a/src/pages/tasks/NewTaskDescriptionPage.js +++ b/src/pages/tasks/NewTaskDescriptionPage.js @@ -57,6 +57,7 @@ function NewTaskDescriptionPage(props) { includeSafeAreaPaddingBottom={false} onEntryTransitionEnd={() => focusAndUpdateMultilineInputRange(inputRef.current)} shouldEnableMaxHeight + testID="NewTaskDescriptionPage" > inputRef.current && inputRef.current.focus()} includeSafeAreaPaddingBottom={false} shouldEnableMaxHeight + testID="NewTaskDetailsPage" > + Task.dismissModalAndClearOutTaskInfo()} @@ -157,50 +160,54 @@ function NewTaskPage(props) { Navigation.goBack(ROUTES.NEW_TASK_DETAILS); }} /> - - - Navigation.navigate(ROUTES.NEW_TASK_TITLE)} - shouldShowRightIcon - /> - Navigation.navigate(ROUTES.NEW_TASK_DESCRIPTION)} - shouldShowRightIcon - shouldParseTitle - numberOfLinesTitle={2} - titleStyle={styles.flex1} - /> - Navigation.navigate(ROUTES.NEW_TASK_ASSIGNEE)} - shouldShowRightIcon - /> - Navigation.navigate(ROUTES.NEW_TASK_SHARE_DESTINATION)} - interactive={!props.task.parentReportID} - shouldShowRightIcon={!props.task.parentReportID} + + + + Navigation.navigate(ROUTES.NEW_TASK_TITLE)} + shouldShowRightIcon + /> + Navigation.navigate(ROUTES.NEW_TASK_DESCRIPTION)} + shouldShowRightIcon + shouldParseTitle + numberOfLinesTitle={2} + titleStyle={styles.flex1} + /> + Navigation.navigate(ROUTES.NEW_TASK_ASSIGNEE)} + shouldShowRightIcon + /> + Navigation.navigate(ROUTES.NEW_TASK_SHARE_DESTINATION)} + interactive={!props.task.parentReportID} + shouldShowRightIcon={!props.task.parentReportID} + /> + + + + onSubmit()} + enabledWhenOffline + buttonText={props.translate('newTaskPage.confirmTask')} + containerStyles={[styles.mh0, styles.mt5, styles.flex1, styles.ph5]} /> - onSubmit()} - enabledWhenOffline - buttonText={props.translate('newTaskPage.confirmTask')} - containerStyles={[styles.mh0, styles.mt5, styles.flex1, styles.ph5]} - /> - + ); diff --git a/src/pages/tasks/NewTaskTitlePage.js b/src/pages/tasks/NewTaskTitlePage.js index 14d7e7d23b9e..8b7401cc3023 100644 --- a/src/pages/tasks/NewTaskTitlePage.js +++ b/src/pages/tasks/NewTaskTitlePage.js @@ -77,6 +77,7 @@ function NewTaskTitlePage(props) { }} includeSafeAreaPaddingBottom={false} shouldEnableMaxHeight + testID="NewTaskTitlePage" > optionRef.current && optionRef.current.textInput.focus()} + testID="TaskAssigneeeSelectorModal" > {({didScreenTransitionEnd, safeAreaPaddingBottomStyle}) => ( diff --git a/src/pages/tasks/TaskDescriptionPage.js b/src/pages/tasks/TaskDescriptionPage.js index 382e8d80d4ad..3a336486dbfd 100644 --- a/src/pages/tasks/TaskDescriptionPage.js +++ b/src/pages/tasks/TaskDescriptionPage.js @@ -67,6 +67,7 @@ function TaskDescriptionPage(props) { includeSafeAreaPaddingBottom={false} onEntryTransitionEnd={() => focusAndUpdateMultilineInputRange(inputRef.current)} shouldEnableMaxHeight + testID="TaskDescriptionPage" > {({didScreenTransitionEnd}) => ( diff --git a/src/pages/tasks/TaskShareDestinationSelectorModal.js b/src/pages/tasks/TaskShareDestinationSelectorModal.js index 44d7b1f6a633..45694492aaca 100644 --- a/src/pages/tasks/TaskShareDestinationSelectorModal.js +++ b/src/pages/tasks/TaskShareDestinationSelectorModal.js @@ -116,6 +116,7 @@ function TaskShareDestinationSelectorModal(props) { optionRef.current && optionRef.current.textInput.focus()} + testID="TaskShareDestinationSelectorModal" > {({didScreenTransitionEnd, safeAreaPaddingBottomStyle}) => ( <> diff --git a/src/pages/tasks/TaskTitlePage.js b/src/pages/tasks/TaskTitlePage.js index be0884e971ae..71da6462b118 100644 --- a/src/pages/tasks/TaskTitlePage.js +++ b/src/pages/tasks/TaskTitlePage.js @@ -79,6 +79,7 @@ function TaskTitlePage(props) { includeSafeAreaPaddingBottom={false} onEntryTransitionEnd={() => inputRef.current && inputRef.current.focus()} shouldEnableMaxHeight + testID="TaskTitlePage" > {({didScreenTransitionEnd}) => ( diff --git a/src/pages/wallet/WalletStatementPage.js b/src/pages/wallet/WalletStatementPage.js index cbd9b72e3299..336f2cdda0aa 100644 --- a/src/pages/wallet/WalletStatementPage.js +++ b/src/pages/wallet/WalletStatementPage.js @@ -94,6 +94,7 @@ function WalletStatementPage(props) { + {({safeAreaPaddingBottomStyle}) => ( Navigation.goBack(ROUTES.SETTINGS_WORKSPACES)} diff --git a/src/pages/workspace/WorkspaceInviteMessagePage.js b/src/pages/workspace/WorkspaceInviteMessagePage.js index 3baf84f54ccf..b72e71f18cc5 100644 --- a/src/pages/workspace/WorkspaceInviteMessagePage.js +++ b/src/pages/workspace/WorkspaceInviteMessagePage.js @@ -158,7 +158,10 @@ class WorkspaceInviteMessagePage extends React.Component { const policyName = lodashGet(this.props.policy, 'name'); return ( - + + {({didScreenTransitionEnd}) => { const sections = didScreenTransitionEnd ? getSections() : []; diff --git a/src/pages/workspace/WorkspaceMembersPage.js b/src/pages/workspace/WorkspaceMembersPage.js index 7bb9a91c130e..669b0e41b08d 100644 --- a/src/pages/workspace/WorkspaceMembersPage.js +++ b/src/pages/workspace/WorkspaceMembersPage.js @@ -344,6 +344,7 @@ function WorkspaceMembersPage(props) { {({insets}) => ( Navigation.goBack(ROUTES.SETTINGS_WORKSPACES)} diff --git a/src/styles/styles.js b/src/styles/styles.js index 4a2472913fd2..a2dfde018e94 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -176,12 +176,6 @@ const styles = (theme) => ({ ...textUnderline, ...theme, // TODO: Should we do this? - rateCol: { - margin: 0, - padding: 0, - flexBasis: '48%', - }, - autoCompleteSuggestionsContainer: { backgroundColor: theme.appBG, borderRadius: 8, @@ -233,18 +227,6 @@ const styles = (theme) => ({ color: theme.textSupporting, }, - appIconBorderRadius: { - overflow: 'hidden', - borderRadius: 12, - }, - - unitCol: { - margin: 0, - padding: 0, - marginLeft: '4%', - flexBasis: '48%', - }, - webViewStyles: webViewStyles(theme), link: link(theme), @@ -267,19 +249,6 @@ const styles = (theme) => ({ backgroundColor: theme.appBG, }, - h1: { - color: theme.heading, - fontFamily: fontFamily.EXP_NEUE_BOLD, - fontSize: variables.fontSizeh1, - fontWeight: fontWeightBold, - }, - - h3: { - fontFamily: fontFamily.EXP_NEUE_BOLD, - fontSize: variables.fontSizeNormal, - fontWeight: fontWeightBold, - }, - h4: { fontFamily: fontFamily.EXP_NEUE_BOLD, fontSize: variables.fontSizeLabel, @@ -430,10 +399,6 @@ const styles = (theme) => ({ color: theme.textSupporting, }, - colorHeading: { - color: theme.heading, - }, - bgTransparent: { backgroundColor: 'transparent', }, @@ -634,10 +599,6 @@ const styles = (theme) => ({ backgroundColor: theme.activeComponentBG, }, - fontWeightBold: { - fontWeight: fontWeightBold, - }, - touchableButtonImage: { alignItems: 'center', height: variables.componentSizeNormal, @@ -847,10 +808,6 @@ const styles = (theme) => ({ height: CONST.DESKTOP_HEADER_PADDING, }, - pushTextRight: { - left: 100000, - }, - reportOptions: { marginLeft: 8, }, @@ -1123,10 +1080,6 @@ const styles = (theme) => ({ noOutline: addOutlineWidth({}, 0), - errorOutline: { - borderColor: theme.danger, - }, - textLabelSupporting: { fontFamily: fontFamily.EXP_NEUE, fontSize: variables.fontSizeLabel, @@ -1192,13 +1145,6 @@ const styles = (theme) => ({ marginBottom: 4, }, - desktopRedirectPage: { - backgroundColor: theme.appBG, - minHeight: '100%', - flex: 1, - alignItems: 'center', - }, - signInPage: { backgroundColor: theme.highlightBG, minHeight: '100%', @@ -1592,11 +1538,6 @@ const styles = (theme) => ({ width: 18, }, - chatContent: { - flex: 4, - justifyContent: 'flex-end', - }, - chatContentScrollView: { flexGrow: 1, justifyContent: 'flex-start', @@ -1662,6 +1603,16 @@ const styles = (theme) => ({ ...wordBreak.breakWord, }, + renderHTMLTitle: { + color: theme.text, + fontSize: variables.fontSizeNormal, + fontFamily: fontFamily.EXP_NEUE, + lineHeight: variables.lineHeightXLarge, + maxWidth: '100%', + ...whiteSpace.preWrap, + ...wordBreak.breakWord, + }, + chatItemComposeWithFirstRow: { minHeight: 90, }, @@ -1751,11 +1702,6 @@ const styles = (theme) => ({ textAlignVertical: 'top', }, - editInputComposeSpacing: { - backgroundColor: theme.transparent, - marginVertical: 8, - }, - // composer padding should not be modified unless thoroughly tested against the cases in this PR: #12669 textInputComposeSpacing: { paddingVertical: 5, @@ -1878,23 +1824,6 @@ const styles = (theme) => ({ width: 200, }, - chatSwticherPillWrapper: { - marginTop: 5, - marginRight: 4, - }, - - navigationModalOverlay: { - ...userSelect.userSelectNone, - position: 'absolute', - width: '100%', - height: '100%', - transform: [ - { - translateX: -variables.sideBarWidth, - }, - ], - }, - sidebarVisible: { borderRightWidth: 1, }, @@ -1919,14 +1848,6 @@ const styles = (theme) => ({ borderRadius: 24, }, - singleSubscript: { - height: variables.iconSizeNormal, - width: variables.iconSizeNormal, - backgroundColor: theme.icon, - borderRadius: 20, - zIndex: 1, - }, - singleAvatarSmall: { height: 18, width: 18, @@ -1970,17 +1891,6 @@ const styles = (theme) => ({ right: 0, }, - leftSideLargeAvatar: { - left: 15, - }, - - rightSideLargeAvatar: { - right: 15, - zIndex: 2, - borderWidth: 4, - borderRadius: 100, - }, - secondAvatarInline: { bottom: -3, right: -25, @@ -2000,18 +1910,6 @@ const styles = (theme) => ({ height: variables.avatarSizeXLarge, }, - avatarNormal: { - height: variables.componentSizeNormal, - width: variables.componentSizeNormal, - borderRadius: variables.componentSizeNormal, - }, - - avatarSmall: { - height: variables.avatarSizeSmall, - width: variables.avatarSizeSmall, - borderRadius: variables.avatarSizeSmall, - }, - avatarInnerText: { color: theme.textLight, fontSize: variables.fontSizeSmall, @@ -2028,21 +1926,6 @@ const styles = (theme) => ({ textAlign: 'center', }, - avatarSpace: { - top: 3, - left: 3, - }, - - avatar: { - backgroundColor: theme.sidebar, - borderColor: theme.sidebar, - }, - - focusedAvatar: { - backgroundColor: theme.border, - borderColor: theme.border, - }, - emptyAvatar: { height: variables.avatarSizeNormal, width: variables.avatarSizeNormal, @@ -2089,11 +1972,6 @@ const styles = (theme) => ({ marginRight: variables.avatarChatSpacing - 4, }, - modalViewContainer: { - alignItems: 'center', - flex: 1, - }, - borderTop: { borderTopWidth: variables.borderTopWidth, borderColor: theme.border, @@ -2183,14 +2061,6 @@ const styles = (theme) => ({ ...(isSmallScreenWidth && flex.flex1), }), - modalCenterContentContainer: { - flex: 1, - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', - backgroundColor: theme.modalBackdrop, - }, - centeredModalStyles: (isSmallScreenWidth, isFullScreenWhenSmall) => ({ borderWidth: isSmallScreenWidth && !isFullScreenWhenSmall ? 1 : 0, marginHorizontal: isSmallScreenWidth ? 0 : 20, @@ -2213,28 +2083,6 @@ const styles = (theme) => ({ alignItems: 'center', }, - notFoundSafeArea: { - flex: 1, - backgroundColor: theme.heading, - }, - - notFoundView: { - flex: 1, - alignItems: 'center', - paddingTop: 40, - paddingBottom: 40, - justifyContent: 'space-between', - }, - - notFoundLogo: { - width: 202, - height: 63, - }, - - notFoundContent: { - alignItems: 'center', - }, - notFoundTextHeader: { ...headlineFont, color: theme.heading, @@ -2245,20 +2093,6 @@ const styles = (theme) => ({ textAlign: 'center', }, - notFoundTextBody: { - color: theme.componentBG, - fontFamily: fontFamily.EXP_NEUE_BOLD, - fontWeight: fontWeightBold, - fontSize: 15, - }, - - notFoundButtonText: { - color: theme.link, - fontFamily: fontFamily.EXP_NEUE_BOLD, - fontWeight: fontWeightBold, - fontSize: 15, - }, - blockingViewContainer: { paddingBottom: variables.contentHeaderHeight, }, @@ -2309,18 +2143,6 @@ const styles = (theme) => ({ justifyContent: 'space-around', }, - settingsPageColumn: { - width: '100%', - alignItems: 'center', - justifyContent: 'space-around', - }, - - settingsPageContainer: { - justifyContent: 'space-between', - alignItems: 'center', - width: '100%', - }, - twoFactorAuthSection: { backgroundColor: theme.appBG, padding: 0, @@ -2458,18 +2280,6 @@ const styles = (theme) => ({ left: -16, }, - svgAvatarBorder: { - borderRadius: 100, - overflow: 'hidden', - }, - - displayName: { - fontSize: variables.fontSizeLarge, - fontFamily: fontFamily.EXP_NEUE_BOLD, - fontWeight: fontWeightBold, - color: theme.heading, - }, - pageWrapper: { width: '100%', alignItems: 'center', @@ -2541,21 +2351,11 @@ const styles = (theme) => ({ transform: [{rotate: '180deg'}], }, - navigationSceneContainer: { - backgroundColor: theme.appBG, - }, - navigationScreenCardStyle: { backgroundColor: theme.appBG, height: '100%', }, - navigationSceneFullScreenWrapper: { - borderRadius: variables.componentBorderRadiusCard, - overflow: 'hidden', - height: '100%', - }, - invisible: { position: 'absolute', opacity: 0, @@ -2606,14 +2406,6 @@ const styles = (theme) => ({ paddingBottom: 0, }, - detailsPageSectionVersion: { - alignSelf: 'center', - color: theme.textSupporting, - fontSize: variables.fontSizeSmall, - height: 24, - lineHeight: 20, - }, - switchTrack: { width: 50, height: 28, @@ -2767,12 +2559,6 @@ const styles = (theme) => ({ alignSelf: 'center', }, - iouDetailsContainer: { - flexGrow: 1, - paddingStart: 20, - paddingEnd: 20, - }, - codeWordWrapper: { ...codeStyles.codeWordWrapper, }, @@ -2812,11 +2598,6 @@ const styles = (theme) => ({ zIndex: 10, }, - navigatorFullScreenLoading: { - backgroundColor: theme.highlightBG, - opacity: 1, - }, - reimbursementAccountFullScreenLoading: { backgroundColor: theme.componentBG, opacity: 0.8, @@ -2901,40 +2682,6 @@ const styles = (theme) => ({ height: '100%', }, - fullscreenCard: { - position: 'absolute', - left: 0, - top: 0, - width: '100%', - height: '100%', - }, - - fullscreenCardWeb: { - left: 'auto', - right: '-24%', - top: '-18%', - height: '120%', - }, - - fullscreenCardWebCentered: { - left: '0', - right: '0', - top: '0', - height: '60%', - }, - - fullscreenCardMobile: { - left: '-20%', - top: '-30%', - width: '150%', - }, - - fullscreenCardMediumScreen: { - left: '-15%', - top: '-30%', - width: '145%', - }, - smallEditIcon: { alignItems: 'center', backgroundColor: theme.buttonHoveredBG, @@ -2953,41 +2700,6 @@ const styles = (theme) => ({ bottom: -4, }, - workspaceCard: { - width: '100%', - height: 400, - borderRadius: variables.componentBorderRadiusCard, - overflow: 'hidden', - backgroundColor: theme.heroCard, - }, - - workspaceCardMobile: { - height: 475, - }, - - workspaceCardMediumScreen: { - height: 540, - }, - - workspaceCardMainText: { - fontSize: variables.fontSizeXXXLarge, - fontWeight: 'bold', - lineHeight: variables.fontSizeXXXLarge, - }, - - workspaceCardContent: { - zIndex: 1, - padding: 50, - }, - - workspaceCardContentMediumScreen: { - padding: 25, - }, - - workspaceCardCTA: { - width: 250, - }, - autoGrowHeightMultilineInput: { maxHeight: 115, }, @@ -3067,12 +2779,6 @@ const styles = (theme) => ({ opacity: variables.overlayOpacity, }, - communicationsLinkIcon: { - right: -36, - top: 0, - bottom: 0, - }, - shortTermsBorder: { borderWidth: 1, borderColor: theme.border, @@ -3263,10 +2969,6 @@ const styles = (theme) => ({ fontSize: 48, }, - closeAccountMessageInput: { - height: 153, - }, - imageCropContainer: { overflow: 'hidden', alignItems: 'center', @@ -3363,24 +3065,11 @@ const styles = (theme) => ({ alignItems: 'center', }, - callRequestSection: { - backgroundColor: theme.appBG, - paddingHorizontal: 0, - paddingBottom: 0, - marginHorizontal: 0, - marginBottom: 0, - }, - archivedReportFooter: { borderRadius: variables.componentBorderRadius, ...wordBreak.breakWord, }, - saveButtonPadding: { - paddingLeft: 18, - paddingRight: 18, - }, - deeplinkWrapperContainer: { padding: 20, flex: 1, @@ -3426,11 +3115,7 @@ const styles = (theme) => ({ alignSelf: 'flex-start', marginRight: 4, }, - reactionListItem: { - flexDirection: 'row', - paddingVertical: 12, - paddingHorizontal: 20, - }, + reactionListHeaderText: { color: theme.textSupporting, marginLeft: 8, @@ -3522,11 +3207,6 @@ const styles = (theme) => ({ width: '100%', }, - listPickerSeparator: { - height: 1, - backgroundColor: theme.buttonDefaultBG, - }, - datePickerRoot: { position: 'relative', zIndex: 99, @@ -3579,15 +3259,6 @@ const styles = (theme) => ({ width: 16, }, - validateCodeMessage: { - width: variables.modalContentMaxWidth, - textAlign: 'center', - }, - - whisper: { - backgroundColor: theme.cardBG, - }, - contextMenuItemPopoverMaxWidth: { maxWidth: 375, }, diff --git a/src/types/modules/react-native.d.ts b/src/types/modules/react-native.d.ts index 1b0b39e5f67d..ebe0974db690 100644 --- a/src/types/modules/react-native.d.ts +++ b/src/types/modules/react-native.d.ts @@ -1,9 +1,14 @@ /* eslint-disable @typescript-eslint/consistent-type-definitions */ import 'react-native'; +import {BootSplashModule} from '../../libs/BootSplash/types'; declare module 'react-native' { interface TextInput { // Typescript type declaration is missing in React Native for setting text selection. setSelection: (start: number, end: number) => void; } + + interface NativeModulesStatic { + BootSplash: BootSplashModule; + } } diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index cacbb5d15199..df4a1364a894 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -4,22 +4,22 @@ import * as OnyxCommon from './OnyxCommon'; type Policy = { /** The ID of the policy */ - id?: string; + id: string; /** The name of the policy */ - name?: string; + name: string; /** The current user's role in the policy */ - role?: ValueOf; + role: ValueOf; /** The policy type */ - type?: ValueOf; + type: ValueOf; /** The email of the policy owner */ - owner?: string; + owner: string; /** The output currency for the policy */ - outputCurrency?: string; + outputCurrency: string; /** The URL for the policy avatar */ avatar?: string; @@ -27,11 +27,26 @@ type Policy = { /** Error objects keyed by field name containing errors keyed by microtime */ errorFields?: OnyxCommon.ErrorFields; + /** Indicates the type of change made to the policy that hasn't been synced with the server yet */ pendingAction?: OnyxCommon.PendingAction; + + /** A list of errors keyed by microtime */ errors: OnyxCommon.Errors; + + /** Whether this policy was loaded from a policy summary, or loaded completely with all of its values */ isFromFullPolicy?: boolean; + + /** When this policy was last modified */ lastModified?: string; + + /** The custom units data for this policy */ customUnits?: Record; + + /** Whether chat rooms can be created and used on this policy. Enabled manually by CQ/JS snippet. Always true for free policies. */ + areChatRoomsEnabled: boolean; + + /** Whether policy expense chats can be created and used on this policy. Enabled manually by CQ/JS snippet. Always true for free policies. */ + isPolicyExpenseChatEnabled: boolean; }; export default Policy; diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index de128d85e6b1..ea0b178444b5 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -19,25 +19,27 @@ type Route = { type Routes = Record; type Transaction = { - transactionID: string; amount: number; category: string; - currency: string; - reportID: string; comment: Comment; - merchant: string; created: string; - pendingAction: OnyxCommon.PendingAction; + currency: string; errors: OnyxCommon.Errors; + // The name of the file used for a receipt (formerly receiptFilename) + filename?: string; + merchant: string; modifiedAmount?: number; modifiedCreated?: string; modifiedCurrency?: string; + pendingAction: OnyxCommon.PendingAction; receipt: { receiptID?: number; source?: string; state?: ValueOf; }; + reportID: string; routes?: Routes; + transactionID: string; tag: string; }; diff --git a/tests/unit/MigrationTest.js b/tests/unit/MigrationTest.js index f209c6e4ed38..60b4e5ebe813 100644 --- a/tests/unit/MigrationTest.js +++ b/tests/unit/MigrationTest.js @@ -1,11 +1,8 @@ import Onyx from 'react-native-onyx'; import _ from 'underscore'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; -import CONST from '../../src/CONST'; import Log from '../../src/libs/Log'; -import getPlatform from '../../src/libs/getPlatform'; import AddLastVisibleActionCreated from '../../src/libs/migrations/AddLastVisibleActionCreated'; -import MoveToIndexedDB from '../../src/libs/migrations/MoveToIndexedDB'; import PersonalDetailsByAccountID from '../../src/libs/migrations/PersonalDetailsByAccountID'; import CheckForPreviousReportActionID from '../../src/libs/migrations/CheckForPreviousReportActionID'; import ONYXKEYS from '../../src/ONYXKEYS'; @@ -28,71 +25,6 @@ describe('Migrations', () => { return waitForBatchedUpdates(); }); - describe('MoveToIndexedDb', () => { - let mockMultiSet; - beforeEach(() => { - getPlatform.mockImplementation(() => CONST.PLATFORM.WEB); - mockMultiSet = jest.spyOn(Onyx, 'multiSet').mockImplementation(() => Promise.resolve()); - localStorage.clear(); - }); - - afterAll(() => { - mockMultiSet.mockRestore(Onyx, 'multiSet'); - localStorage.clear(); - }); - - it('Should do nothing for non web/desktop platforms', () => { - // Given the migration is not running on web or desktop - getPlatform.mockImplementation(() => CONST.PLATFORM.ANDROID); - - // When the migration runs - return MoveToIndexedDB().then(() => { - // Then we don't expect any storage calls - expect(Onyx.multiSet).not.toHaveBeenCalled(); - }); - }); - - it('Should do nothing when there is no old session data', () => { - // Given no session in old storage medium (localStorage) - localStorage.removeItem(ONYXKEYS.SESSION); - - // When the migration runs - return MoveToIndexedDB().then(() => { - // Then we don't expect any storage calls - expect(Onyx.multiSet).not.toHaveBeenCalled(); - }); - }); - - it('Should migrate Onyx keys in localStorage to (new) Onyx', () => { - // Given some old data exists in storage - const data = { - [ONYXKEYS.SESSION]: {authToken: 'mock-token', loading: false}, - [ONYXKEYS.ACCOUNT]: {email: 'test@mock.com'}, - [ONYXKEYS.NETWORK]: {isOffline: true}, - }; - - _.forEach(data, (value, key) => localStorage.setItem(key, JSON.stringify(value))); - - // When the migration runs - return MoveToIndexedDB().then(() => { - // Then multiset should be called with all the data available in localStorage - expect(Onyx.multiSet).toHaveBeenCalledWith(data); - }); - }); - - it('Should not clear non Onyx keys from localStorage', () => { - // Given some Onyx and non-Onyx data exists in localStorage - localStorage.setItem(ONYXKEYS.SESSION, JSON.stringify({authToken: 'mock-token'})); - localStorage.setItem('non-onyx-item', 'MOCK'); - - // When the migration runs - return MoveToIndexedDB().then(() => { - // Then non-Onyx data should remain in localStorage - expect(localStorage.getItem('non-onyx-item')).toEqual('MOCK'); - }); - }); - }); - describe('AddLastVisibleActionCreated', () => { it('Should add lastVisibleActionCreated wherever lastActionCreated currently is', () => Onyx.multiSet({ diff --git a/tests/unit/OptionsListUtilsTest.js b/tests/unit/OptionsListUtilsTest.js index 637dc1e18376..6f20e48835fd 100644 --- a/tests/unit/OptionsListUtilsTest.js +++ b/tests/unit/OptionsListUtilsTest.js @@ -660,6 +660,7 @@ describe('OptionsListUtils', () => { const selectedOptions = [ { name: 'Medical', + enabled: true, }, ]; const smallCategoriesList = { @@ -686,13 +687,6 @@ describe('OptionsListUtils', () => { shouldShow: false, indexOffset: 0, data: [ - { - text: 'Taxi', - keyForList: 'Taxi', - searchText: 'Taxi', - tooltipText: 'Taxi', - isDisabled: true, - }, { text: 'Restaurant', keyForList: 'Restaurant', @@ -817,7 +811,7 @@ describe('OptionsListUtils', () => { keyForList: 'Medical', searchText: 'Medical', tooltipText: 'Medical', - isDisabled: true, + isDisabled: false, }, ], }, @@ -826,13 +820,6 @@ describe('OptionsListUtils', () => { shouldShow: true, indexOffset: 1, data: [ - { - text: 'Taxi', - keyForList: 'Taxi', - searchText: 'Taxi', - tooltipText: 'Taxi', - isDisabled: true, - }, { text: 'Restaurant', keyForList: 'Restaurant', @@ -847,13 +834,6 @@ describe('OptionsListUtils', () => { shouldShow: true, indexOffset: 3, data: [ - { - text: 'Taxi', - keyForList: 'Taxi', - searchText: 'Taxi', - tooltipText: 'Taxi', - isDisabled: true, - }, { text: 'Restaurant', keyForList: 'Restaurant', @@ -882,13 +862,6 @@ describe('OptionsListUtils', () => { tooltipText: 'Milk', isDisabled: false, }, - { - text: ' Vegetables', - keyForList: 'Vegetables', - searchText: 'Food: Vegetables', - tooltipText: 'Vegetables', - isDisabled: true, - }, { text: 'Cars', keyForList: 'Cars', @@ -903,13 +876,6 @@ describe('OptionsListUtils', () => { tooltipText: 'Audi', isDisabled: false, }, - { - text: ' BMW', - keyForList: 'BMW', - searchText: 'Cars: BMW', - tooltipText: 'BMW', - isDisabled: true, - }, { text: ' Mercedes-Benz', keyForList: 'Mercedes-Benz', @@ -938,13 +904,6 @@ describe('OptionsListUtils', () => { tooltipText: 'Breakfast', isDisabled: false, }, - { - text: ' Dinner', - keyForList: 'Dinner', - searchText: 'Travel: Meals: Dinner', - tooltipText: 'Dinner', - isDisabled: true, - }, { text: ' Lunch', keyForList: 'Lunch', @@ -982,13 +941,6 @@ describe('OptionsListUtils', () => { tooltipText: 'Food: Milk', isDisabled: false, }, - { - text: 'Food: Vegetables', - keyForList: 'Food: Vegetables', - searchText: 'Food: Vegetables', - tooltipText: 'Food: Vegetables', - isDisabled: true, - }, ], }, ]; @@ -1000,6 +952,23 @@ describe('OptionsListUtils', () => { data: [], }, ]; + const emptyCategoriesList = {}; + const emptySelectedResultList = [ + { + title: '', + shouldShow: false, + indexOffset: 0, + data: [ + { + text: 'Medical', + keyForList: 'Medical', + searchText: 'Medical', + tooltipText: 'Medical', + isDisabled: false, + }, + ], + }, + ]; const smallResult = OptionsListUtils.getFilteredOptions(REPORTS, PERSONAL_DETAILS, [], emptySearch, [], [], false, false, true, smallCategoriesList); expect(smallResult.categoryOptions).toStrictEqual(smallResultList); @@ -1054,6 +1023,9 @@ describe('OptionsListUtils', () => { recentlyUsedCategories, ); expect(largeWrongSearchResult.categoryOptions).toStrictEqual(largeWrongSearchResultList); + + const emptyResult = OptionsListUtils.getFilteredOptions(REPORTS, PERSONAL_DETAILS, [], search, selectedOptions, [], false, false, true, emptyCategoriesList); + expect(emptyResult.categoryOptions).toStrictEqual(emptySelectedResultList); }); it('getFilteredOptions() for tags', () => { @@ -1443,13 +1415,6 @@ describe('OptionsListUtils', () => { }, }; const result = [ - { - text: 'Taxi', - keyForList: 'Taxi', - searchText: 'Taxi', - tooltipText: 'Taxi', - isDisabled: true, - }, { text: 'Restaurant', keyForList: 'Restaurant', @@ -1478,13 +1443,6 @@ describe('OptionsListUtils', () => { tooltipText: 'Milk', isDisabled: false, }, - { - text: ' Vegetables', - keyForList: 'Vegetables', - searchText: 'Food: Vegetables', - tooltipText: 'Vegetables', - isDisabled: true, - }, { text: 'Cars', keyForList: 'Cars', @@ -1499,13 +1457,6 @@ describe('OptionsListUtils', () => { tooltipText: 'Audi', isDisabled: false, }, - { - text: ' BMW', - keyForList: 'BMW', - searchText: 'Cars: BMW', - tooltipText: 'BMW', - isDisabled: true, - }, { text: ' Mercedes-Benz', keyForList: 'Mercedes-Benz', @@ -1513,13 +1464,6 @@ describe('OptionsListUtils', () => { tooltipText: 'Mercedes-Benz', isDisabled: false, }, - { - text: 'Medical', - keyForList: 'Medical', - searchText: 'Medical', - tooltipText: 'Medical', - isDisabled: true, - }, { text: 'Travel', keyForList: 'Travel', @@ -1541,13 +1485,6 @@ describe('OptionsListUtils', () => { tooltipText: 'Breakfast', isDisabled: false, }, - { - text: ' Dinner', - keyForList: 'Dinner', - searchText: 'Travel: Meals: Dinner', - tooltipText: 'Dinner', - isDisabled: true, - }, { text: ' Lunch', keyForList: 'Lunch',