diff --git a/.eslintrc.js b/.eslintrc.js index 75a74ed371c4..b5b4add538f6 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -38,7 +38,6 @@ module.exports = { { files: ['*.js', '*.jsx', '*.ts', '*.tsx'], rules: { - 'rulesdir/no-multiple-onyx-in-file': 'off', 'rulesdir/onyx-props-must-have-default': 'off', 'react-native-a11y/has-accessibility-hint': ['off'], 'react-native-a11y/has-valid-accessibility-descriptors': [ @@ -164,7 +163,6 @@ module.exports = { }, ], curly: 'error', - 'you-dont-need-lodash-underscore/throttle': 'off', }, }, { diff --git a/.github/actions/composite/buildAndroidAPK/action.yml b/.github/actions/composite/buildAndroidAPK/action.yml index fc280ab2a223..819234df0bc3 100644 --- a/.github/actions/composite/buildAndroidAPK/action.yml +++ b/.github/actions/composite/buildAndroidAPK/action.yml @@ -13,7 +13,7 @@ runs: - uses: ruby/setup-ruby@eae47962baca661befdfd24e4d6c34ade04858f7 with: - ruby-version: "2.7" + ruby-version: '2.7' bundler-cache: true - uses: gradle/gradle-build-action@3fbe033aaae657f011f88f29be9e65ed26bd29ef @@ -26,4 +26,4 @@ runs: uses: actions/upload-artifact@65d862660abb392b8c4a3d1195a2108db131dd05 with: name: ${{ inputs.ARTIFACT_NAME }} - path: android/app/build/outputs/apk/e2e/release/app-e2e-release.apk + path: android/app/build/outputs/apk/e2eRelease/app-e2eRelease.apk diff --git a/.github/actions/javascript/getPullRequestDetails/action.yml b/.github/actions/javascript/getPullRequestDetails/action.yml index ed2c60f018a1..a59cf55bdf9f 100644 --- a/.github/actions/javascript/getPullRequestDetails/action.yml +++ b/.github/actions/javascript/getPullRequestDetails/action.yml @@ -13,14 +13,8 @@ inputs: outputs: MERGE_COMMIT_SHA: description: 'The merge_commit_sha of the given pull request' - HEAD_COMMIT_SHA: - description: 'The head_commit_sha of the given pull request' MERGE_ACTOR: description: 'The actor who merged the pull request' - IS_MERGED: - description: 'True if the pull request is merged' - FORKED_REPO_URL: - description: 'Output forked repo URL if PR includes changes from a fork' runs: using: 'node16' main: './index.js' diff --git a/.github/workflows/e2ePerformanceTests.yml b/.github/workflows/e2ePerformanceTests.yml index 3666e8c7d343..d8f9cad138d9 100644 --- a/.github/workflows/e2ePerformanceTests.yml +++ b/.github/workflows/e2ePerformanceTests.yml @@ -84,7 +84,12 @@ jobs: - name: Unmerged PR - Fetch head ref of unmerged PR if: ${{ !fromJSON(steps.getPullRequestDetails.outputs.IS_MERGED) }} run: | - git fetch origin ${{ steps.getPullRequestDetails.outputs.HEAD_COMMIT_SHA }} --no-tags --depth=1 + if [[ ${{ steps.getPullRequestDetails.outputs.FORKED_REPO_URL }} != '' ]]; then + git remote add pr_remote ${{ steps.getPullRequestDetails.outputs.FORKED_REPO_URL }} + git fetch pr_remote ${{ steps.getPullRequestDetails.outputs.HEAD_COMMIT_SHA }} --no-tags --depth=1 + else + git fetch origin ${{ steps.getPullRequestDetails.outputs.HEAD_COMMIT_SHA }} --no-tags --depth=1 + fi - name: Unmerged PR - Set dummy git credentials before merging if: ${{ !fromJSON(steps.getPullRequestDetails.outputs.IS_MERGED) }} @@ -96,7 +101,7 @@ jobs: if: ${{ !fromJSON(steps.getPullRequestDetails.outputs.IS_MERGED) }} id: getMergeCommitShaIfUnmergedPR run: | - git merge --allow-unrelated-histories --no-commit ${{ steps.getPullRequestDetails.outputs.HEAD_COMMIT_SHA }} + git merge --no-commit ${{ steps.getPullRequestDetails.outputs.HEAD_COMMIT_SHA }} git checkout ${{ steps.getPullRequestDetails.outputs.HEAD_COMMIT_SHA }} env: GITHUB_TOKEN: ${{ github.token }} @@ -135,19 +140,18 @@ jobs: name: baseline-apk-${{ needs.buildBaseline.outputs.VERSION }} path: zip - # The downloaded artifact will be a file named "app-e2e-release.apk" so we have to rename it + # The downloaded artifact will be a file named "app-e2eRelease.apk" so we have to rename it - name: Rename baseline APK - run: mv "${{steps.downloadBaselineAPK.outputs.download-path}}/app-e2e-release.apk" "${{steps.downloadBaselineAPK.outputs.download-path}}/app-e2eRelease-baseline.apk" + run: mv "${{steps.downloadBaselineAPK.outputs.download-path}}/app-e2eRelease.apk" "${{steps.downloadBaselineAPK.outputs.download-path}}/app-e2eRelease-baseline.apk" - name: Download delta APK uses: actions/download-artifact@e9ef242655d12993efdcda9058dee2db83a2cb9b - id: downloadDeltaAPK with: name: delta-apk-${{ needs.buildDelta.outputs.DELTA_REF }} path: zip - name: Rename delta APK - run: mv "${{steps.downloadDeltaAPK.outputs.download-path}}/app-e2e-release.apk" "${{steps.downloadDeltaAPK.outputs.download-path}}/app-e2eRelease-compare.apk" + run: mv "${{steps.downloadBaselineAPK.outputs.download-path}}/app-e2eRelease.apk" "${{steps.downloadBaselineAPK.outputs.download-path}}/app-e2eRelease-compare.apk" - name: Copy e2e code into zip folder run: cp -r tests/e2e zip @@ -192,6 +196,7 @@ jobs: run: cat "./Host_Machine_Files/\$WORKING_DIRECTORY/debug.log" - name: Check if test failed, if so post the results and add the DeployBlocker label + if: ${{ github.event_name == 'workflow_call' }} run: | if grep -q '🔴' ./Host_Machine_Files/\$WORKING_DIRECTORY/output.md; then gh pr edit ${{ inputs.PR_NUMBER }} --add-label DeployBlockerCash diff --git a/.storybook/preview.js b/.storybook/preview.js index a989960794f2..b198c0d2d626 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -6,7 +6,7 @@ import './fonts.css'; import ComposeProviders from '../src/components/ComposeProviders'; import HTMLEngineProvider from '../src/components/HTMLEngineProvider'; import OnyxProvider from '../src/components/OnyxProvider'; -import {LocaleContextProvider} from '../src/components/LocaleContextProvider'; +import {LocaleContextProvider} from '../src/components/withLocalize'; import {KeyboardStateProvider} from '../src/components/withKeyboardState'; import {EnvironmentProvider} from '../src/components/withEnvironment'; import {WindowDimensionsProvider} from '../src/components/withWindowDimensions'; diff --git a/.well-known/apple-app-site-association b/.well-known/apple-app-site-association index d6da0232f2fc..c871764117ed 100644 --- a/.well-known/apple-app-site-association +++ b/.well-known/apple-app-site-association @@ -68,17 +68,9 @@ "/": "/workspace/*", "comment": "Workspace Details" }, - { - "/": "/get-assistance/*", - "comment": "Get Assistance Pages" - }, { "/": "/teachersunite/*", "comment": "Teachers Unite!" - }, - { - "/": "/search/*", - "comment": "Search" } ] } diff --git a/README.md b/README.md index 9aad797ebb51..fce7cee8dcdd 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,6 @@ For an M1 Mac, read this [SO](https://stackoverflow.com/questions/64901180/how-t * If you get the error `Could not find 'bundler'`, install the bundler gem first: `gem install bundler` and try again. * If you are using MacOS and get the error `Gem::FilePermissionError` when trying to install the bundler gem, you're likely using system Ruby, which requires administrator permission to modify. To get around this, install another version of Ruby with a version manager like [rbenv](https://github.com/rbenv/rbenv#installation). * Before installing iOS dependencies, you need to obtain a token from Mapbox to download their SDKs. Please run `npm run configure-mapbox` and follow the instructions. - * For help with MapBox token, you can see [this Slack thread](https://expensify.slack.com/archives/C01GTK53T8Q/p1692740856745279?thread_ts=1692322511.804599&cid=C01GTK53T8Q) * To install the iOS dependencies, run: `npm install && npm run pod-install` * If you are an Expensify employee and want to point the emulator to your local VM, follow [this](https://stackoverflow.com/c/expensify/questions/7699) * To run a on a **Development Simulator**: `npm run ios` diff --git a/android/app/build.gradle b/android/app/build.gradle index f5c28fd53492..bcac489f6828 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -58,7 +58,7 @@ project.ext.envConfigFiles = [ adhocRelease: ".env.adhoc", developmentRelease: ".env", developmentDebug: ".env", - e2eRelease: "tests/e2e/.env.e2e" + e2eRelease: ".env.production" ] /** @@ -90,8 +90,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001037705 - versionName "1.3.77-5" + versionCode 1001037402 + versionName "1.3.74-2" } flavorDimensions "default" @@ -136,20 +136,10 @@ android { signingConfig signingConfigs.debug } release { + signingConfig signingConfigs.release productFlavors.production.signingConfig signingConfigs.release minifyEnabled enableProguardInReleaseBuilds proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" - - signingConfig null - // buildTypes take precedence over productFlavors when it comes to the signing configuration, - // thus we need to manually set the signing config, so that the e2e uses the debug config again. - // In other words, the signingConfig setting above will be ignored when we build the flavor in release mode. - productFlavors.all { flavor -> - // All release builds should be signed with the release config ... - flavor.signingConfig signingConfigs.release - } - // ... except for the e2e flavor, which we maybe want to build locally: - productFlavors.e2e.signingConfig signingConfigs.debug } } diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml index 2eab8de1eb7b..7e1870e8b30b 100644 --- a/android/app/src/debug/AndroidManifest.xml +++ b/android/app/src/debug/AndroidManifest.xml @@ -4,7 +4,6 @@ - - - @@ -85,7 +83,6 @@ - diff --git a/assets/images/chatbubbles.svg b/assets/images/chatbubbles.svg deleted file mode 100644 index 6194c43e631e..000000000000 --- a/assets/images/chatbubbles.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - diff --git a/assets/images/google-meet.svg b/assets/images/google-meet.svg index 980cd102f67a..138a11859321 100644 --- a/assets/images/google-meet.svg +++ b/assets/images/google-meet.svg @@ -1,7 +1,8 @@ - - - - - + \ No newline at end of file diff --git a/assets/images/zoom-icon.svg b/assets/images/zoom-icon.svg index 24d019654795..6c6ed03cb2f3 100644 --- a/assets/images/zoom-icon.svg +++ b/assets/images/zoom-icon.svg @@ -1,7 +1 @@ - - - - - + \ No newline at end of file diff --git a/contributingGuides/FORMS.md b/contributingGuides/FORMS.md index 1fb67483daca..661c700130c7 100644 --- a/contributingGuides/FORMS.md +++ b/contributingGuides/FORMS.md @@ -4,20 +4,6 @@ This document lists specific guidelines for using our Form component and general ## General Form UI/UX -### Inputs -Any form input needs to be wrapped in [InputWrapper](https://github.com/Expensify/App/blob/029d009731dcd3c44cd1321672b9672ef0d3d7d9/src/components/Form/InputWrapper.js) and passed as `InputComponent` property additionally it's necessary po pass an unique `inputID`. All other props of the input can be passed as `InputWrapper` props. -```jsx - -``` - ### Labels, Placeholders, & Hints Labels are required for each input and should clearly mark the field. Optional text may appear below a field when a hint, suggestion, or context feels necessary. If validation fails on such a field, its error should clearly explain why without relying on the hint. Inline errors should always replace the microcopy hints. Placeholders should not be used as it’s customary for labels to appear inside form fields and animate them above the field when focused. @@ -27,8 +13,7 @@ Labels are required for each input and should clearly mark the field. Optional t Labels and hints are enabled by passing the appropriate props to each input: ```jsx - @@ -39,8 +24,7 @@ Labels and hints are enabled by passing the appropriate props to each input: If a field has a character limit we should give that field a max limit. This is done by passing the maxLength prop to TextInput. ```jsx - ``` @@ -58,8 +42,7 @@ We should always set people up for success on native platforms by enabling the b We have a couple of keyboard types [defined](https://github.com/Expensify/App/blob/572caa9e7cf32a2d64fe0e93d171bb05a1dfb217/src/CONST.js#L357-L360) and should be used like so: ```jsx - ``` @@ -73,8 +56,7 @@ As a best practice we should avoid asking for information we can get via other m Browsers use the name prop to autofill information into the input. Here's a [reference](https://developers.google.com/web/fundamentals/design-and-ux/input/forms#recommended_input_name_and_autocomplete_attribute_values) for available values for the name prop. ```jsx - ``` @@ -109,7 +91,7 @@ To give a slightly more detailed example of how this would work with phone numbe Form inputs will NOT store draft values by default. This is to avoid accidentally storing any sensitive information like passwords, SSN or bank account information. We need to explicitly tell each form input to save draft values by passing the shouldSaveDraft prop to the input. Saving draft values is highly desirable and we should always try to save draft values. This way when a user continues a given flow they can easily pick up right where they left off if they accidentally exited a flow. Inputs with saved draft values [will be cleared when a user logs out](https://github.com/Expensify/App/blob/aa1f0f34eeba5d761657168255a1ae9aebdbd95e/src/libs/actions/SignInRedirect.js#L52) (like most data). Additionally, we should clear draft data once the form is successfully submitted by calling `Onyx.set(ONYXKEY.FORM_ID, null)` in the onSubmit callback passed to Form. ```jsx - ``` @@ -196,9 +178,9 @@ Submit buttons shall not be disabled or blocked from being pressed in most cases The only time we won’t allow a user to press the submit button is when we have submitted the form and are waiting for a response (e.g. from the API). In this case we will show a loading indicator and additional taps on the submit button will have no effect. This is handled by the Form component and will also ensure that a form cannot be submitted multiple times. -## Using Form +## Using Form.js -The example below shows how to use [FormProvider](https://github.com/Expensify/App/blob/029d009731dcd3c44cd1321672b9672ef0d3d7d9/src/components/Form/FormProvider.js) and [InputWrapper](https://github.com/Expensify/App/blob/029d009731dcd3c44cd1321672b9672ef0d3d7d9/src/components/Form/InputWrapper.js) in our app. You can also refer to [Form.stories.js](https://github.com/Expensify/App/blob/c5a84e5b4c0b8536eed2214298a565e5237a27ca/src/stories/Form.stories.js) for more examples. +The example below shows how to use [Form.js](https://github.com/Expensify/App/blob/c5a84e5b4c0b8536eed2214298a565e5237a27ca/src/components/Form.js) in our app. You can also refer to [Form.stories.js](https://github.com/Expensify/App/blob/c5a84e5b4c0b8536eed2214298a565e5237a27ca/src/stories/Form.stories.js) for more examples. ```jsx function validate(values) { @@ -219,47 +201,43 @@ function onSubmit(values) { }, 1000); } - - // Wrapping InputWrapper in a View to show that Form inputs can be nested in other components + // Wrapping TextInput in a View to show that Form inputs can be nested in other components - - - + ``` -`FormProvider` also works with inputs nested in a custom component, e.g. [AddressForm](https://github.com/Expensify/App/blob/86579225ff30b21dea507347735259637a2df461/src/pages/ReimbursementAccount/AddressForm.js). The only exception is that the nested component shouldn't be wrapped around any HoC and all inputs in the component needs to be wrapped with `InputWrapper`. +`Form.js` also works with inputs nested in a custom component, e.g. [AddressForm](https://github.com/Expensify/App/blob/86579225ff30b21dea507347735259637a2df461/src/pages/ReimbursementAccount/AddressForm.js). The only exception is that the nested component shouldn't be wrapped around any HoC. ```jsx const BankAccountForm = () => ( <> - - ( ); // ... - - + ``` ### Props provided to Form inputs @@ -288,7 +266,7 @@ The following prop is available to form inputs: - value: The value to show for the input. - onValueChange: A callback that is called when the input's value changes. -InputWrapper component will automatically provide the following props to any input with the inputID prop. +Form.js will automatically provide the following props to any input with the inputID prop. - ref: A React ref that must be attached to the input. - value: The input value. @@ -309,13 +287,13 @@ An example of this can be seen in the [ACHContractStep](https://github.com/Expen ### Safe Area Padding -Any `FormProvider.js` that has a button will also add safe area padding by default. If the `` is inside a `` we will want to disable the default safe area padding applied there e.g. +Any `Form.js` that has a button will also add safe area padding by default. If the `
` is inside a `` we will want to disable the default safe area padding applied there e.g. ```js - + {...} - + ``` diff --git a/docs/404.html b/docs/404.html index 4338293218cc..1773388c6923 100644 --- a/docs/404.html +++ b/docs/404.html @@ -1,8 +1,8 @@ --- permalink: /404.html --- -
- +
+ Hmm it's not here...
That page is nowhere to be found.
diff --git a/docs/Gemfile b/docs/Gemfile index 701ae50ca381..7cad729ee45b 100644 --- a/docs/Gemfile +++ b/docs/Gemfile @@ -32,6 +32,3 @@ gem "http_parser.rb", "~> 0.6.0", :platforms => [:jruby] gem "webrick", "~> 1.7" gem 'jekyll-seo-tag' - -gem 'jekyll-redirect-from' - diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index 0963d3c73e6c..1a5b26e2dc23 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -263,7 +263,6 @@ DEPENDENCIES github-pages http_parser.rb (~> 0.6.0) jekyll-feed (~> 0.12) - jekyll-redirect-from jekyll-seo-tag tzinfo (~> 1.2) tzinfo-data @@ -271,4 +270,4 @@ DEPENDENCIES webrick (~> 1.7) BUNDLED WITH - 2.4.19 + 2.4.3 diff --git a/docs/_config.yml b/docs/_config.yml index dc134d0d2c19..114e562cae04 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,7 +1,7 @@ title: Expensify Help tagline: Expensify Help - all your Expensify questions answered in one place. description: Got a question about receipts, expenses, corporate cards, or anything else in the spend management universe? Get answers at help.expensify.com. -url: https://help.expensify.com +url: help.expensify.com author: Expensify logo: /assets/images/expensify-help.svg open_url: true @@ -17,7 +17,3 @@ exclude: [README.md, TEMPLATE.md, vendor] plugins: - jekyll-seo-tag - - jekyll-redirect-from - -whitelist: - - jekyll-redirect-from diff --git a/docs/_sass/_main.scss b/docs/_sass/_main.scss index 3ad2276713da..6e4095569a6d 100644 --- a/docs/_sass/_main.scss +++ b/docs/_sass/_main.scss @@ -641,13 +641,30 @@ button { } .centered-content { - width: 100%; - height: calc(100vh - 56px); - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; + height: 240px; text-align: center; + font-size: larger; + position: absolute; + top: calc((100vh - 240px) / 2); + + width: 380px; + right: calc((100vw - 380px) / 2); + @include breakpoint($breakpoint-tablet) { + width: 500px; + right: calc((100vw - 500px) / 2); + } + + &.with-lhn { + right: calc((100vw - 380px) / 2); + + @include breakpoint($breakpoint-tablet) { + right: calc((100vw - 320px - 500px ) / 2); + } + + @include breakpoint($breakpoint-desktop) { + right: calc((100vw - 420px - 500px) / 2); + } + } div { margin-top: 8px; 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..b3126201715f --- /dev/null +++ b/docs/articles/expensify-classic/account-settings/Account-Access.md @@ -0,0 +1,5 @@ +--- +title: Account Access +description: Account Access +--- +## Resource Coming Soon! diff --git a/docs/articles/expensify-classic/account-settings/Account-Details.md b/docs/articles/expensify-classic/account-settings/Account-Details.md deleted file mode 100644 index 46a6c6ba0c25..000000000000 --- a/docs/articles/expensify-classic/account-settings/Account-Details.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Account Details -description: Account Details ---- -## Resource 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..073d3a9bd700 --- /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 +--- +## Resource Coming Soon! diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/International-Reimbursements.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/International-Reimbursements.md deleted file mode 100644 index 7313c73ac6e6..000000000000 --- a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/International-Reimbursements.md +++ /dev/null @@ -1,105 +0,0 @@ ---- -title: International Reimbursements -description: International Reimbursements ---- -# Overview - -If your company’s business bank account is in the US, Canada, the UK, Europe, or Australia, you now have the option to send direct reimbursements to nearly any country across the globe! -The process to enable global reimbursements is dependent on the currency of your reimbursement bank account, so be sure to review the corresponding instructions below. - -# How to request international reimbursements - -## The reimbursement account is in USD - -If your reimbursement bank account is in USD, the first step is connecting the bank account to Expensify. -The individual who plans on sending reimbursements internationally should head to **Settings > Account > Payments > Add Verified Bank Account**. From there, you will provide company details, input personal information, and upload a copy of your ID. - -Once the USD bank account is verified (or if you already had a USD business bank account connected), click the support icon in your Expensify account to inform your Setup Specialist, Account Manager, or Concierge that you’d like to enable international reimbursements. From there, Expensify will ask you to confirm the currencies of the reimbursement and employee bank accounts. - -Our team will assess your account, and if you meet the criteria, international reimbursements will be enabled. - -## The reimbursement account is in AUD, CAD, GBP, EUR - -To request international reimbursements, contact Expensify Support to make that request. - -You can do this by clicking on the support icon and informing your Setup Specialist, Account Manager, or Concierge that you’d like to set up global reimbursements on your account. -From there, Expensify will ask you to confirm both the currencies of the reimbursement and employee bank accounts. - -Our team will assess your account, and if you meet the criteria, international reimbursements will be enabled. - -# How to verify the bank account for sending international payments - -Once international payments are enabled on your Expensify account, the next step is verifying the bank account to send the reimbursements. - -## The reimbursement account is in USD - -First, confirm the workspace settings are set up correctly by doing the following: -1. Head to **Settings > Workspaces > Group > _[Workspace Name]_ > Reports** and check that the workspace currency is USD -2. Under **Settings > Workspaces > Group > _[Workspace Name]_ > Reimbursements**, set the reimbursement method to direct -3. Under **Settings > Workspaces > Group > _[Workspace Name]_ > Reimbursements**, set the USD bank account to the default account - -Once that’s all set, head to **Settings > Account > Payments**, and click **Enable Global Reimbursement** on the bank account (this button may not show for up to 60 minutes after the Expensify team confirms international reimbursements are available on your account). - -From there, you’ll fill out a form via DocuSign. Once the form is complete, it is automatically sent to our Compliance Team for review. Our Support Team will contact you with more details if additional information is required. - -## The reimbursement account is in AUD, CAD, GBP, EUR - -First, confirm the workspace currency corresponds with the currency of the reimbursement bank account. You can do this under **Settings > Workspaces > Group > _[Workspace Name]_ > Reports**. It should be AUD, CAD, GBP, or EUR. - -Next, add the bank account to Expensify: -1. Head to **Settings > Workspaces > Group > _[Workspace Name]_ > Reimbursements** and set the reimbursement method to direct (this button may not show for up to 60 minutes after the Expensify team confirms international reimbursements are available on your account) -2. Click **Add Business Bank Account** -3. If the incorrect country shows as the default, click **Switch Country** to select the correct country -4. Enter the bank account details -5. Click **Save & Continue** - -From there, you’ll fill out a form via DocuSign. Once the form is complete, it is automatically sent to our Compliance Team for review. Our Support Team will contact you with more details if additional information is required. - -# How to start reimbursing internationally - -After the bank account is verified for international payments, set the correct bank account as the reimbursement account. - -You can do this under **Settings > Workspaces > Group > _[Workspace Name]_ > Reimbursements** by selecting the reimbursement account as the default account. - -Finally, have your employees add their deposit-only bank accounts. They can do this by logging into their Expensify accounts, heading to **Settings > Account > Payments**, and clicking **Add Deposit-Only Bank Account**. - -# Deep Dive - -## Documents requested - -Our Compliance Team may ask for additional information depending on who initiates the verification or what information you provide on the DocuSign form. - -Examples of additional requested information: -- The reimburser’s proof of address and ID -- Company directors’ proofs of address and IDs -- An authorization letter -- An independently certified documentation such as shareholder agreement from a lawyer, notary, or public accountant if an individual owns more than 25% of the company - -# FAQ - -## How many people can send reimbursements internationally? - -Once your company is authorized to send global payments, only the individual who went through the verification can reimburse international employees. - -## How long does it take to verify an account for international payments? - -It varies! The verification process can take a few business days to several weeks. It depends on whether or not the information in the DocuSign form is correct if our Compliance Team requires any additional information, and how responsive the employee verifying the company’s details is to our requests. - -## If I already have a USD bank account connected to Expensify, do I need to go through the verification process again to enable international payments? - -If you’ve already connected a US business bank account, you can request to enable global reimbursements by contacting Expensify Support immediately. However, additional steps are required to verify the bank account for international payments. - -## My employee says they don’t have the option to add their non-USD bank account as a deposit account – what should they do? - -Have the employee double-check that their default workspace is set as the workspace that's connected to the bank you're using to send international payments. - -An employee can confirm their default workspace is under **Settings > Workspaces > Group**. The default workspace has a green checkmark next to it. They can change their default workspace by clicking **Default Workspace** on the correct workspace. - -## Who is the “Authorized User” on the International Reimbursement DocuSign form? - -This is the person who will process international reimbursements. The authorized user should be the same person who manages the bank account connection in Expensify. - -## Who should I enter as the “User” on the International Reimbursement form? - -You can leave this form section blank since the “User” is Expensify. - diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Add-a-Business-Bank-Account-(AUD).md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Add-a-Business-Bank-Account-(AUD).md deleted file mode 100644 index 6f0c738693ca..000000000000 --- a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Add-a-Business-Bank-Account-(AUD).md +++ /dev/null @@ -1,50 +0,0 @@ ---- -title: Add-a-Business-Bank-Account-(AUD).md -description: This article provides insight on setting up and using an Australian Business Bank account in Expensify. ---- - -# How to add an Australian business bank account (for admins) -A withdrawal account is the business bank account that you want to use to pay your employee reimbursements. - -_Your policy currency must be set to AUD and reimbursement setting set to Indirect to continue. If your main policy is used for something other than AUD, then you will need to create a new one and set that policy to AUD._ -To set this up, you’ll run through the following steps: - -1. Go to *Settings > Your Account > Payments* and click *Add Verified Bank Account* -![Click the Verified Bank Account button in the bottom right-hand corner of the screen](https://help.expensify.com/assets/images/add-vba-australian-account.png){:width="100%"} - -2. Enter the required information to connect to your business bank account. If you don't know your Bank User ID/Direct Entry ID/APCA Number, please contact your bank and they will be able to provide this. -![Enter your information in each of the required fields](https://help.expensify.com/assets/images/add-vba-australian-account-modal.png){:width="100%"} - -3. Link the withdrawal account to your policy by heading to *Settings > Policies > Group > [Policy name] > Reimbursement* -4. Click *Direct* reimbursement -5. Set the default withdrawal account for processing reimbursements -6. Tell your employees to add their deposit accounts and start reimbursing. - -# How to delete a bank account -If you’re no longer using a bank account you previously connected to Expensify, you can delete it by doing the following: - -1. Navigate to Settings > Accounts > Payments -2. Click *Delete* -![Click the Delete button](https://help.expensify.com/assets/images/delete-australian-bank-account.png){:width="100%"} - -You can complete this process either via the web app (on a computer), or via the mobile app. - -# Deep Dive -## Bank-specific batch payment support - -If you are new to using Batch Payments in Australia, to reimburse your staff or process payroll, you may want to check out these bank-specific instructions for how to upload your .aba file: - -ANZ Bank - [Import a file for payroll payments](https://www.anz.com.au/support/internet-banking/pay-transfer-business/payroll/import-file/) -CommBank - [Importing and using
 Direct Entry (EFT) files](https://www.commbank.com.au/business/pds/003-279-importing-a-de-file.pdf) -Westpac - [Importing Payment Files](https://www.westpac.com.au/business-banking/online-banking/support-faqs/import-files/) -NAB - [Quick Reference Guide - Upload a payment file](https://www.nab.com.au/business/online-banking/nab-connect/help) -Bendigo Bank - [Bulk payments user guide](https://www.bendigobank.com.au/globalassets/documents/business/bulk-payments-user-guide.pdf) -Bank of Queensland - [Payments file upload facility FAQ](https://www.boq.com.au/help-and-support/online-banking/ob-faqs-and-support/faq-pfuf) - -*Note:* Some financial institutions require an ABA file to include a *self-balancing transaction*. If you are unsure, please check with your bank to ensure whether to tick this option or not, as selecting an incorrect option will result in the ABA file not working with your bank's internet banking platform. - -## Enable Global Reimbursement - -If you have employees in other countries outside of Australia, you can now reimburse them directly using Global Reimbursement. - -To do this, you’ll first need to delete any existing Australian business bank accounts. Then, you’ll want to follow the instructions to enable Global Reimbursements diff --git a/docs/articles/expensify-classic/expensify-card/Auto-Reconciliation.md b/docs/articles/expensify-classic/expensify-card/Auto-Reconciliation.md index 9de47d6e5beb..85202835a0e4 100644 --- a/docs/articles/expensify-classic/expensify-card/Auto-Reconciliation.md +++ b/docs/articles/expensify-classic/expensify-card/Auto-Reconciliation.md @@ -1,200 +1,5 @@ --- -title: Expensify Card Auto-Reconciliation -description: Everything you need to know about Expensify Card Auto-Reconciliation +title: Auto-reconciliation +description: Auto-reconciliation --- - - -# Overview -If your company uses the Expensify Card, and connects to a direct accounting integration, you can auto-reconcile card spending each month. - -The integrations that auto-reconciliation are available on are: - -- QuickBooks Online -- Xero -- NetSuite -- Sage Intacct - -# How-to Set Up Expensify Card Auto-Reconciliation - -## Auto-Reconciliation Prerequisites - -- Connection: -1. A Preferred Workspace is set. -2. A Reconciliation Account is set and matches the Expensify Card settlement account. -- Automation: -1. Auto-Sync is enabled on the Preferred Workspace above. -2. Scheduled Submit is enabled on the Preferred Workspace above. -- User: -1. A Domain Admin is set as the Preferred Workspace’s Preferred Exporter. - -To set up your auto-reconciliation account with the Expensify Card, follow these steps: -1. Navigate to your Settings. -2. Choose "Domains," then select your specific domain name. -3. Click on "Company Cards." -4. From the dropdown menu, pick the Expensify Card. -5. Head to the "Settings" tab. -6. Select the account in your accounting solution that you want to use for reconciliation. Make sure this account matches the settlement business bank account. - -That's it! You've successfully set up your auto-reconciliation account. - -## How does Auto-Reconciliation work -Once Auto-Reconciliation is enabled, there are a few things that happen. Let’s go over those! - -### Handling Purchases and Card Balance Payments -**What happens**: When an Expensify Card is used to make purchases, the amount spent is automatically deducted from your company’s 'Settlement Account' (your business checking account). This deduction happens on a daily or monthly basis, depending on your chosen settlement frequency. Don't worry; this settlement account is pre-defined when you apply for the Expensify Card, and you can't accidentally change it. -**Accounting treatment**: After your card balance is settled each day, we update your accounting system with a journal entry. This entry credits your bank account (referred to as the GL account) and debits the Expensify Card Clearing Account. To ensure accuracy, please make sure that the 'bank account' in your Expensify Card settings matches your real-life settlement account. You can easily verify this by navigating to **Settings > Account > Payments**, where you'll see 'Settlement Account' next to your business bank account. To keep track of settlement figures by date, use the Company Card Reconciliation Dashboard's Settlements tab: - -### Submitting, Approving, and Exporting Expenses -**What happens**: Users submit their expenses on a report, which might occur after some time has passed since the initial purchase. Once the report is approved, it's then exported to your accounting software. -**Accounting treatment**: When the report is exported, we create a journal entry in your accounting system. This entry credits the Clearing Account and debits the Liability Account for the purchase amount. The Liability Account functions as a bank account in your ledger, specifically for Expensify Card expenses: - -# Deep Dive -## QuickBooks Online - -### Initial Setup -1. Start by accessing your group workspace linked to QuickBooks Online. On the Export tab, make sure that the user chosen as the Preferred Exporter holds the role of a Workspace Admin and has an email address associated with your Expensify Cards' domain. For instance, if your domain is company.com, the Preferred Exporter's email should be email@company.com. -2. Head over to the Advanced tab and ensure that Auto-Sync is enabled. -3. Now, navigate to **Settings > Domains > *Domain Name* > Company Cards > Settings**. Use the dropdown menu next to "Preferred Workspace" to select the group workspace connected to QuickBooks Online and with Scheduled Submit enabled. -4. In the dropdown menu next to "Expensify Card reconciliation account," choose your existing QuickBooks Online bank account for reconciliation. This should be the same account you use for Expensify Card settlements. -5. In the dropdown menu next to "Expensify Card settlement account," select your business bank account used for settlements (found in Expensify under **Settings > Account > Payments**). - -### How This Works -1. On the day of your first card settlement, we'll create the Expensify Card Liability account in your QuickBooks Online general ledger. If you've opted for Daily Settlement, we'll also create an Expensify Clearing Account. -2. During your QuickBooks Online auto-sync on that same day, if there are unsettled transactions, we'll generate a journal entry totaling all posted transactions since the last settlement. This entry will credit the selected bank account and debit the new Expensify Clearing Account (for Daily Settlement) or the Expensify Liability Account (for Monthly Settlement). -3. Once the transactions are posted and the expense report is approved in Expensify, the report will be exported to QuickBooks Online with each line as individual credit card expenses. For Daily Settlement, an additional journal entry will credit the Expensify Clearing Account and debit the Expensify Card Liability Account. For Monthly Settlement, the journal entry will credit the Liability account directly and debit the appropriate expense categories. - -### Example -- We have card transactions for the day totaling $100, so we create the following journal entry upon sync: -- The current balance of the Expensify Clearing Account is now $100: -- After transactions are posted in Expensify and the report is approved and exported, a second journal entry is generated: -- We reconcile the matching amounts automatically, clearing the balance of the Expensify Clearing Account: -- Now, you'll have a debit on your credit card account (increasing the total spent) and a credit on the bank account (reducing the available amount). The Clearing Account balance is $0. -- Each expense will also create a credit card expense, similar to how we do it today, exported upon final approval. This action debits the expense account (category) and includes any other line item data. -- This process occurs daily during the QuickBooks Online Auto-Sync to ensure your card remains reconciled. - -**Note:** If Auto-Reconciliation is disabled for your company's Expensify Cards, a Domain Admin can set an export account for individual cards via **Settings > Domains > *Domain Name* > Company Cards > Edit Exports**. The Expensify Card transactions will always export as Credit Card charges in your accounting software, even if the non-reimbursable setting is configured differently, such as a Vendor Bill. - -## Xero - -### Initial Setup -1. Begin by accessing your group workspace linked to Xero. On the Export tab, ensure that the Preferred Exporter is a Workspace Admin with an email address from your Expensify Cards domain (e.g. company.com). -2. Head to the Advanced tab and confirm that Auto-Sync is enabled. -3. Now, go to **Settings > Domains > *Domain Name* > Company Cards > Settings**. From the dropdown menu next to "Preferred Workspace," select the group workspace connected to Xero with Scheduled Submit enabled. -4. In the dropdown menu for "Expensify Card settlement account," pick your settlement business bank account (found in Expensify under **Settings > Account > Payments**). -5. In the dropdown menu for "Expensify Card reconciliation account," select the corresponding GL account from Xero for your settlement business bank account from step 4. - -### How This Works -1. During the first overnight Auto Sync after enabling Continuous Reconciliation, Expensify will create a Liability Account (Bank Account) on your Xero Dashboard. If you've opted for Daily Settlement, an additional Clearing Account will be created in your General Ledger. Two Contacts —Expensify and Expensify Card— will also be generated: -2. The bank account for Expensify Card transactions is tied to the Liability Account Expensify created. Note that this doesn't apply to other cards or non-reimbursable expenses, which follow your workspace settings. - -### Daily Settlement Reconciliation -- If you've selected Daily Settlement, Expensify uses entries in the Clearing Account to reconcile the daily settlement. This is because Expensify bills on posted transactions, which you can review via **Settings > Domains > *Domain Name* > Company Cards > Reconciliation > Settlements**. -- At the end of each day (or month on your settlement date), the settlement charge posts to your Business Bank Account. Expensify assigns the Clearing Account (or Liability Account for monthly settlement) as a Category to the transaction, posting it in your GL. The charge is successfully reconciled. - -### Bank Transaction Reconciliation -- Expensify will pay off the Liability Account with the Clearing Account balance and reconcile bank transaction entries to the Liability Account with your Expense Accounts. -- When transactions are approved and exported from Expensify, bank transactions (Receive Money) are added to the Liability Account, and coded to the Clearing Account. Simultaneously, Spend Money transactions are created and coded to the Category field. If you see many Credit Card Misc. entries, add commonly used merchants as Contacts in Xero to export with the original merchant name. -- The Clearing Account balance is reduced, paying off the entries to the Liability Account created in Step 1. Each payment to and from the Liability Account should have a corresponding bank transaction referencing an expense account. Liability Account Receive Money payments appear with "EXPCARD-APPROVAL" and the corresponding Report ID from Expensify. -- You can run a Bank Reconciliation Summary displaying entries in the Liability Account referencing individual payments, as well as entries that reduce the Clearing Account balance to unapproved expenses. -- **Important**: To bring your Liability Account balance to 0, enable marking transactions as reconciled in Xero. When a Spend Money bank transaction in the Liability Account has a matching Receive Transaction, you can mark both as Reconciled using the provided hyperlink. - -**Note**: If Auto-Reconciliation is disabled for your company's Expensify Cards, a Domain Admin can set an export account for individual cards via **Settings > Domains > *Domain Name* > Company Cards > Edit Exports**. The Expensify Card transactions will always export as a Credit Card charge in your accounting software, regardless of the non-reimbursable setting in their accounting configuration. - -## NetSuite - -### Initial Setup -1. Start by accessing your group workspace connected to NetSuite and click on "Configure" under **Connections > NetSuite**. -2. On the Export tab, ensure that the Preferred Exporter is a Workspace Admin with an email address from your Expensify Cards' domain. For example, if your domain is company.com, the Preferred Exporter's email should be email@company.com. -3. Head over to the Advanced tab and make sure Auto-Sync is enabled. -4. Now, go to **Settings > Domains > *Domain Name* > Company Cards > Settings**. From the dropdown menu next to "Preferred Workspace," select the group workspace connected to NetSuite with Scheduled Submit enabled. -5. In the dropdown menu next to "Expensify Card reconciliation account," choose your existing NetSuite bank account used for reconciliation. This account must match the one set in Step 3. -6. In the dropdown menu next to "Expensify Card settlement account," select your daily settlement business bank account (found in Expensify under **Settings > Account > Payments**). - -### How This Works with Daily Settlement -1. After setting up the card and running the first auto-sync, we'll create the Expensify Card Liability account and the Expensify Clearing Account within your NetSuite subsidiary general ledger. -2. During the same sync, if there are newly posted transactions, we'll create a journal entry totaling all posted transactions for the day. This entry will credit the selected bank account and debit the new Expensify Clearing account. -3. Once transactions are approved in Expensify, the report will be exported to NetSuite, with each line recorded as individual credit card expenses. Additionally, another journal entry will be generated, crediting the Expensify Clearing Account and debiting the Expensify Card Liability account. - -### How This Works with Monthly Settlement -1. After the first monthly settlement, during Auto-Sync, Expensify creates a Liability Account in NetSuite (without a clearing account). -2. Each time the monthly settlement occurs, Expensify calculates the total purchase amount since the last settlement and creates a Journal Entry that credits the settlement bank account (GL Account) and debits the Expensify Liability Account in NetSuite. -3. As expenses are approved and exported to NetSuite, Expensify credits the Liability Account and debits the correct expense categories. - -**Note**: By default, the Journal Entries created by Expensify are set to the approval level "Approved for posting," so they will automatically credit and debit the appropriate accounts. If you have "Require approval on Journal Entries" enabled in your accounting preferences in NetSuite (**Setup > Accounting > Accounting Preferences**), this will override that default. Additionally, if you have set up Custom Workflows (**Customization > Workflow**), these can also override the default. In these cases, the Journal Entries created by Expensify will post as "Pending approval." You will need to approve these Journal Entries manually to complete the reconciliation process. - -### Example -- Let's say you have card transactions totaling $100 for the day. -- We create a journal entry: -- After transactions are posted in Expensify, we create the second Journal Entry(ies): -- We then reconcile the matching amounts automatically, clearing the balance of the Expensify Clearing Account. -- Now, you'll have a debit on your Credit Card account (increasing the total spent) and a credit on the bank account (reducing the amount available). The clearing account has a $0 balance. -- Each expense will also create a Journal Entry, just as we do today, exported upon final approval. This entry will debit the expense account (category) and contain any other line item data. -- This process happens daily during the NetSuite Auto-Sync to keep your card reconciled. - -**Note**: Currently, only Journal Entry export is supported for auto-reconciliation. You can set other export options for all other non-reimbursable spend in the **Configure > Export** tab. Be on the lookout for Expense Report export in the future! - -If Auto-Reconciliation is disabled for your company's Expensify Cards, a Domain Admin can set an export account for individual Expensify Cards via **Settings > Domains > Company Cards > Edit Exports**. The Expensify Card transactions will always export as a Credit Card charge in your accounting software, regardless of the non-reimbursable setting in their accounting configuration. - -## Sage Intacct - -### Initial Setup -1. Start by accessing your group workspace connected to Sage Intacct and click on "Configure" under **Connections > Sage Intacct**. -2. On the Export tab, ensure that you've selected a specific entity. To enable Expensify to create the liability account, syncing at the entity level is crucial, especially for multi-entity environments. -3. Still on the Export tab, confirm that the user chosen as the Preferred Exporter is a Workspace Admin, and their email address belongs to the domain used for Expensify Cards. For instance, if your domain is company.com, the Preferred Exporter's email should be email@company.com. -4. Head over to the Advanced tab and make sure Auto-Sync is enabled. -5. Now, go to **Settings > Domains > *Domain Name* > Company Cards > Settings**. From the dropdown menu next to "Preferred Workspace," select the group workspace connected to Sage Intacct with Scheduled Submit enabled. -6. In the dropdown menu next to "Expensify Card reconciliation account" pick your existing Sage Intacct bank account used for daily settlement. This account must match the one set in the next step. -7. In the dropdown menu next to "Expensify Card settlement account" select your daily settlement business bank account (found in Expensify under **Settings > Account > Payments**). -8. Use the dropdown menus to select your cash-only and accrual-only journals. If your organization operates on a cash-only or accrual-only basis, choose "No Selection" for the journals as needed. If your organization uses both cash and accrual methods, please select both a cash-only and an accrual-only journal. Don't forget to save your settings! - -### How This Works with Daily Settlement -1. After setting up the card and running the first auto-sync, we'll create the Expensify Card Expensify Clearing Account within your Sage Intacct general ledger. Once the first card transaction is exported, we'll create a Liability Account. -2. In the same sync, if there are newly posted transactions from your Expensify Cards, we'll then create a journal entry totaling all posted transactions for the day. This entry will credit the business bank account (set in Step 4 above) and debit the new Expensify Clearing account. -3. Once Expensify Card transactions are approved in Expensify, the report will be exported to Sage Intacct, with each line recorded as individual credit card expenses. Additionally, another journal entry will be generated, crediting the Expensify Clearing Account and debiting the Expensify Card Liability Account. - -### How This Works with Monthly Settlement -1. After the initial export of a card transaction, Expensify establishes a Liability Account in Intacct (without a clearing account). -2. Each time a monthly settlement occurs, Expensify calculates the total purchase amount since the last settlement and creates a Journal Entry. This entry credits the settlement bank account (GL Account) and debits the Expensify Liability Account in Intacct. -3. As expenses are approved and exported to Intacct, Expensify credits the Liability Account and debits the appropriate expense categories. - -# FAQ - -## What are the timeframes for auto-reconciliation in Expensify? -We offer either daily or monthly auto-reconciliation: -- Daily Settlement: each day, as purchases are made on your Expensify Cards, the posted balance is withdrawn from your Expensify Card Settlement Account (your business bank account). -- Monthly Settlement: each month, on the day of the month that you enabled Expensify Cards (or switched from Daily to Monthly Settlement), the posted balance of all purchases since the last settlement payment is withdrawn from your Expensify Card Settlement Account (your business bank account). - -## Why is my Expensify Card auto-reconciliation not working with Xero? -When initially creating the Liability and Bank accounts to complete the auto-reconciliation process, we rely on the system to match and recognize those accounts created. You can't make any changes or we will not “find” those accounts. - -If you have changed the accounts. It's an easy fix, just rename them! -- Internal Account Code: must be **ExpCardLbl** -- Account Type: must be **Bank** - -## My accounting integration is not syncing. How will this affect the Expensify Card auto-reconciliation? -When you receive a message that your accounting solution’s connection failed to sync, you will also receive an email or error message with the steps to correct the sync issue. If you do not, please contact Support for help. When your accounting solution’s sync reconnects and is successful, your auto-reconciliation will resume. - -If your company doesn't have auto-reconciliation enabled for its Expensify Cards, you can still set up individual export accounts. Here's how: - -1. Make sure you have Domain Admin privileges. -2. Navigate to **Settings > Domains** -3. Select 'Company Cards' -4. Find the Expensify Card you want to configure and choose 'Edit Exports.' -5. Pick the export account where you want the Expensify Card transactions to be recorded. -6. Please note that these transactions will always be exported as Credit Card charges in your accounting software. This remains the case even if you've configured non-reimbursable settings as something else, such as a Vendor Bill. - -These simple steps will ensure your Expensify Card transactions are correctly exported to the designated account in your accounting software. - -## Why does my Expensify Card Liability Account have a balance? -If you’re using the Expensify Card with auto-reconciliation, your Expensify Card Liability Account balance should always be $0 in your accounting system. - -If you see that your Expensify Card Liability Account balance isn’t $0, then you’ll need to take action to return that balance to $0. - -If you were using Expensify Cards before auto-reconciliation was enabled for your accounting system, then any expenses that occurred prior will not be cleared from the Liability Account. -You will need to prepare a manual journal entry for the approved amount to bring the balance to $0. - -To address this, please follow these steps: -1. Identify the earliest date of a transaction entry in the Liability Account that doesn't have a corresponding entry. Remember that each expense will typically have both a positive and a negative entry in the Liability Account, balancing out to $0. -2. Go to the General Ledger (GL) account where your daily Expensify Card settlement withdrawals are recorded, and locate entries for the dates identified in Step 1. -3. Adjust each settlement entry so that it now posts to the Clearing Account. -4. Create a Journal Entry or Receive Money Transaction to clear the balance in the Liability Account using the funds currently held in the Clearing Account, which was set up in Step 2. +## Resource Coming Soon! diff --git a/docs/articles/expensify-classic/exports/Default-Export-Templates.md b/docs/articles/expensify-classic/exports/Default-Export-Templates.md index f6043aaea2eb..7650cff38946 100644 --- a/docs/articles/expensify-classic/exports/Default-Export-Templates.md +++ b/docs/articles/expensify-classic/exports/Default-Export-Templates.md @@ -2,29 +2,4 @@ title: Default Export Templates description: Default Export Templates --- -# Overview -Use default export templates for exporting report data to a CSV format, for data analysis, or uploading to an accounting software. -Below is a breakdown of the available default templates. -# How to use default export templates -- **All Data - Expense Level Export** - This export prints a line for each expense with all of the data associated with the expenses. This is useful if you want to see all of the data stored in Expensify for each expense. -- **All Data - Report Level Export** - This export prints a line per report, giving a summary of the report data. -- **Basic Export** - A simpler expense level export without as much detail. This exports the data visible on the PDF of the report. Basics such as date, amount, merchant, category, tag, reimbursable state, description, receipt URL, and original expense currency and amount. -- **Canadian Multiple Tax Export** - Exports a line per expense with all available info on the taxes applied to the expenses on your report(s). This is useful if you need to see the tax spend. -- **Category Export** - Exports category names with the total amount attributed to each category on the report. While you can also access this information on the Insights page, it can be convenient to export to a CSV to run further analysis in your favorite spreadsheet program. -- **Per Diem Export** - This exports basic expense details only for the per diem expenses on the report. Useful for reviewing employee Per Diem spend. -- **Tag Export** - Exports tag names into columns with the total amount attributed to each tag on the report. - -# How to export using a default template -1. Navigate to your Reports page -2. Select the reports you want to export (you can use the filters to help you find the reports you’re after) -3. Click the **Export to** in the top right corner -4. Select the export template you’d like to use - -# FAQ -## Why are my numbers exporting in a weird format? -Do your numbers look something like this: 1.7976931348623157e+308? This means that your spreadsheet program is formatting long numbers in an exponential or scientific format. If that happens, you can correct it by changing the data to Plain Text or a Number in your spreadsheet program. -## Why are my leading zeros missing? -Is the export showing “1” instead of “01”? This means that your spreadsheet program is cutting off the leading zero. This is a common issue with viewing exported data in Excel. Unfortunately, we don’t have a good solution for this. We recommend checking your spreadsheet program’s help documents for suggestions for formatting. -## I want a report that is not in the default list, how can I build that? -For a guide on building your own custom template check out Exports > Custom Exports in the Help pages! - +## Resource Coming Soon! diff --git a/docs/articles/expensify-classic/exports/Insights.md b/docs/articles/expensify-classic/exports/Insights.md index 6c71630015c5..682c2a251228 100644 --- a/docs/articles/expensify-classic/exports/Insights.md +++ b/docs/articles/expensify-classic/exports/Insights.md @@ -1,7 +1,6 @@ --- title: Custom Reporting and Insights description: How to get the most out of the Custom Reporing and Insights -redirect_from: articles/other/Insights/ --- {% raw %} diff --git a/docs/articles/expensify-classic/exports/Other-Export-Options.md b/docs/articles/expensify-classic/exports/Other-Export-Options.md deleted file mode 100644 index 31f5aaf93032..000000000000 --- a/docs/articles/expensify-classic/exports/Other-Export-Options.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: Other Export Options -description: Other Export Options ---- - -# Overview -Here’s a quick look at how to export your expense and report data into a spreadsheet, accounting package, or PDF. We’ll also show you how to print out your reports in a few easy steps. - -# How to export expenses and reports to a CSV or accounting package -From the **Expenses** page, you can export individual expenses into a CSV. From the Reports page, you can export entire reports into a CSV or connected accounting package. Here’s how to do both: - -1. Go to either the Expenses or Reports page -2. On the left hand side, select the expenses/reports you’d like to export -3. Click **Export to** at the top right of the page -4. Choose the desired export option - -You can use one of the default templates or create your own template. The default templates and the option to export to a connected accounting package are only available on the **Reports** page. Visit the specific help page for your accounting package to learn more about how to get this set up. - -# How to export a report as a PDF -1. Go to the **Reports** page -2. Click into a report -3. Click on **Details** in the top right of the report -4. Click the **download icon** to generate a PDF - -The PDF will include all expenses, any attached receipts, and all report notes. - -# How to print a report -1. Go to the Reports page -2. Click into a report -3. Click on **Details** in the top right of the report -4. Click the **print icon** - -# FAQ -## Why isn’t my report exporting? -Big reports with lots of expenses may cause the PDF download to fail due to images with large resolutions. In that case, try breaking the report into multiple smaller reports. Also, please note that a report must have at least one expense to be exported or saved as a PDF. -## Can I download multiple PDFs at once? -No, you can’t download multiple reports as PDFs at the same time. If you’d like to export multiple reports, an alternative to consider is the CSV export option. -## The data exported to Excel is showing incorrectly. How can I fix this? -When opening a CSV file export from Expensify in Excel, it’ll automatically register report IDs and transaction IDs as numbers and assign the number format to the report ID column. If a number is greater than a certain length, Excel will contract the number and display it in exponential form. To prevent this, the number needs to be imported as text, which can be done by opening Excel and clicking File > Import > select your CSV file > follow the prompts and on step 3 set the report ID/transactionID column to import as Text. 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 index b5f5ec8be048..36e0a2194d24 100644 --- a/docs/articles/expensify-classic/get-paid-back/expenses/Apply-Tax.md +++ b/docs/articles/expensify-classic/get-paid-back/expenses/Apply-Tax.md @@ -1,39 +1,5 @@ --- title: Apply Tax -description: This is article shows you how to apply taxes to your expenses! +description: Apply Tax --- - - - -# About - -There are two types of tax in Expensify: Simple Tax (i.e. one tax rate) and Complex Tax (i.e. more than one tax rate). This article shows you how to apply both to your expenses! - - -# How-to Apply Tax - -When Tax Tracking is enabled on a Workspace, the default tax rate is selected under **Settings > Workspace > _Workspace Name_ > Tax**, with the default tax rate applied to all expenses automatically. - -There may be multiple tax rates set up within your Workspace, so if the tax on your receipt is different to the default tax that has been applied, you can select the appropriate rate from the tax drop-down on the web expense editor or the mobile app. - -If the tax amount on your receipt is different to the calculated amount or the tax rate doesn’t show up, you can always manually type in the correct tax amount. - - -# FAQ - -## How do I set up multiple taxes (GST/PST/QST) on indirect connections? -Expenses sometimes have more than one tax applied to them - for example in Canada, expenses can have both a Federal GST and a provincial PST or QST. - -To handle these, you can create a single tax that combines both taxes into a single effective tax rate. For example, if you have a GST of 5% and PST of 7%, adding the two tax rates together gives you an effective tax rate of 12%. - -From the Reports page, you can select Reports and then click **Export To > Tax Report** to generate a CSV containing all the expense information, including the split-out taxes. - - -# Deep Dive - -If you have a receipt that has more than one tax rate (i.e. Complex Tax) on it, then there are two options for handling this in Expensify! - -Many tax authorities do not require the reporting of tax amounts by rate and the easiest approach is to apply the highest rate on the receipt and then modify the tax amount to reflect the amount shown on the receipt if this is less. Please check with your local tax advisor if this approach will be allowed. - -Alternatively, you can apply each specific tax rate by splitting the expense into the components that each rate will be applied to. To do this, click on **Split Expense** and apply the correct tax rate to each part. - +## Resource 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 index 7fa714189542..8323be7b8e3f 100644 --- a/docs/articles/expensify-classic/get-paid-back/expenses/Create-Expenses.md +++ b/docs/articles/expensify-classic/get-paid-back/expenses/Create-Expenses.md @@ -1,128 +1,5 @@ --- -title: Create-Expenses.md -description: This is an article that shows you all the ways that you can create Expenses in Expensify! +title: Create Expenses +description: Create Expenses --- - - -# About -Whether you're using SmartScan for automatic expense creation, or manually creating, splitting, or duplicating expenses, you can rest assured your expenses will be correctly tracked in Expensify. - -# How-to Create Expenses -## Using SmartScan -Use the big green camera button within the Expensify mobile app to snap a photo of your physical receipt to have it SmartScanned. -For digital or emailed receipts, simply forward them to receipts@expensify.com and it will be SmartScanned and added to your Expensify account. - -There’s no need to keep the app open and most SmartScans are finished within the hour. If more details are needed, Concierge will reach out to you with a friendly message. -## Using the Mobile App -Simply tap the **+** icon in the top-right corner -Choose **Expense** and then select **Manually Create**. -If you don't have a receipt handy or want to add it later, fill in your expense details and click the **Save** button. -## Using the Expensify Website -Log into the Expensify website -Click on the **Expenses** page and find the **New Expense** dropdown. -Select your expense type, hit the **Save** button and you're all set. -You can then add details like the Merchant and Category, attach a receipt image, and even add a description. -# How to Split an Expense -Splitting an expense in Expensify allows you to break down a single expense into multiple expenses. Each split expense is treated as an individual expense which can be categorized and tagged separately. The same receipt image will be attached to all of the split expenses, allowing you to divide a single expense into smaller, more manageable expenses. -To split an expense on the mobile app: - -1. Open an expense. -2. At the bottom of the screen, tap **More Options**. -3. Then, use the **Split** button to divide the expense. - -To split an expense on the Expensify website: - -1. Click on the expense you want to split. -2. Click on the **Split** button. - - On the Expenses page, this button is at the top. - - Within an individual expense, you'll find it at the bottom. -3. This will automatically be split in two, but you can decide how many expenses you want to split it into by clicking on the **Add Split** button. - - Remember, the total of all pieces must add up to the original expense amount, and no piece can have a $0.00 amount (or you won't be able to save the changes). - -# How to Create Bulk Expenses - -If you have multiple saved receipt images or PDFs to upload, you can drag and drop them onto your Expenses page in batches of ten - this will start the SmartScan process for all of them. - -You can also create a number of future 'placeholder' expenses for your recurring expenses (such as recurring bills or subscriptions) which you don't have receipts for by clicking *New Expense > Create Multiple* to quickly add multiple expenses in batches of up to ten. - -# How to Edit Bulk Expenses -Editing expenses in bulk will allow you to apply the same coding across multiple expenses and is a web-only feature. To bulk edit expenses: -Go to the Expenses page. -To narrow down your selection, use the filters (e.g. "Merchant" and "Draft") to find the specific expenses you want to edit. -Select all the expenses you want to edit. -Click on the **Edit Multiple** button at the top of the page. -# How to Edit Expenses on a Report -If you’d like to edit expenses within a Draft report: - -1. Click on the Report containing all the expenses. -2. Click on **Details**. -3. Click on the Pencil icon. -3. Select the **Edit Multiple** button. - -If you've already submitted your report, you'll need to Retract it or have it Unapproved first before you can edit the expenses. - -# FAQ - -## Does Expensify account for duplicates? - -Yes, Expensify will account for duplicates. Expensify works behind the scenes to identify duplicate expenses before they are submitted, warning employees when they exist. If a duplicate expense is submitted, the same warning will be shown to the approver responsible for reviewing the report. - -If two expenses are SmartScanned on the same day for the same amount, they will be flagged as duplicates unless: -The expenses were split from a single expense, -The expenses were imported from a credit card, or -Matching email receipts sent to receipts@expensify.com were received with different timestamps. - -## How do I resolve a duplicate expense? - -If Concierge has let you know it's flagged a receipt as a duplicate, scanning the receipt again will trigger the same duplicate flagging.Users have the ability to resolve duplicates by either deleting the duplicated transactions, merging them, or ignoring them (if they are legitimately separate expenses of the same date and amount). - -## How do I recover a duplicate or undelete an expense? - -To recover a duplicate or undelete an expense: -Log into your Expensify account on the website and navigate to the Expenses page -Use the filters to search for deleted expenses by selecting the "Deleted" filter -Select the checkbox next to the expenses you want to restore -Click the **Undelete** button and you're all set. You’ll find the expense on your Expenses page again. - -# Deep Dive - -## What are the different Expense statuses? - -There are a number of different expense statuses in Expensify: -1. **Personal**: Personal expenses are not yet part of a report (and therefore unsubmitted) and are not viewable by anyone but the expense creator/owner. -2. **Draft**: Draft expenses are seen as still in progress, and are unsubmitted. Your Policy Admin will be able to view them, making this a collaborative step toward reimbursement. -3. **Processing**: Processing expenses are submitted, but waiting for approval. -4. **Approved**: If it's a non-reimbursable expense, the workflow is complete at this point. If it's a reimbursable expense, you're one step closer to getting paid. -5. **Reimbursed**: Reimbursed expenses are fully settled. You can check the Report Comments to see when you'll get paid. -6. **Closed**: Sometimes an expense accidentally ends up on your Individual Policy, falling into the Closed status. You’ll need to reopen the report and change the Policy by clicking on the **Details** tab in order to resubmit your report. - -## What are Violations? - -Violations represent errors or discrepancies that Expensify has picked up and need to be corrected before a report can be successfully submitted. The one exception is when an expense comment is added, it will override the violation - as the user is providing a valid reason for submission. - -To enable or configure violations according to your policy, go to **Settings > Policies > _Policy Name_ > Expenses > Expense Violations**. Keep in mind that Expensify includes certain system mandatory violations that can't be disabled, even if your policy has violations turned off. - -You can spot violations by the exclamation marks (!) attached to expenses. Hovering over the symbol will provide a brief description and you can find more detailed information below the list of expenses. The two types of violations are: -1. **Red**: These indicate violations directly tied to your report's Policy settings. They are clear rule violations that must be addressed before submission. -2. **Yellow**: Concierge will highlight items that require attention but may not necessarily need corrective action. For example, if a receipt was SmartScanned and then the amount was modified, we’ll bring it to your attention so that it can be manually reviewed. - -## How to Track Attendees - -Attendee tracking makes it easy to track shared expenses and maintain transparency in your group spending. - -Internal attendees are considered users within your policies or domain. To add internal attendees on mobile or web: -1. Click or tap the **Attendee** field within your expense. -2. Select the internal attendees you'd like to add from the list of searchable users. -3. You can continue adding more attendees or save the Expense. - -External attendees are considered users outside your group policy or domain. To add external attendees: -1. Click or tap the **Attendee** field within your expense. -2. Type in the individual's name or email address. -3. Tap **Add** to include the attendee. -4. You can continue adding more attendees or save the Expense. - -To remove an attendee from an expense: - -1. Open the expense. -2. Click or tap the **Attendees** field to display the list of attendees. -3. From the list, de-select the attendees you'd like to remove from the expense. +## Resource 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 index a8444b98c951..e7705a32f215 100644 --- a/docs/articles/expensify-classic/get-paid-back/expenses/Merge-Expenses.md +++ b/docs/articles/expensify-classic/get-paid-back/expenses/Merge-Expenses.md @@ -1,65 +1,5 @@ --- title: Merge Expenses -description: This article shows you all the ways that you can merge your expenses in Expensify! +description: Merge Expenses --- - - -# About -The merge expense function helps combine two separate expenses into one. This is useful when the same expense has been accidentally entered more than once, or if you have a connected credit card and an imported expense didn’t automatically merge with a manual entry. - -# How-to merge expenses -It’s important to note that merging expenses doesn't add the two values together. Instead, merging them combines both expenses to create a single, consolidated expense. - -Keep in mind: -1. Merging expenses cannot be undone. -2. You can only merge two expenses at a time. -3. You can merge a cash expense with a credit card expense, or two cash expenses - but not two credit card expenses. -4. In order to merge, both expenses will need to be in a Personal or Draft status. - -# How to merge expenses on the web app -To merge two expenses from the Expenses page: -1. Sign into your Expensify account. -2. Navigate to the Expenses page on the left-hand navigation. -3. Click the checkboxes next to the two expenses you wish to merge. -4. Click **Merge**. -5. You'll be able to choose which aspect of each of the two expenses you would like to be used on the resulting expense, such as the receipt image, card, merchant, category, and more. - -To merge two expenses from the Reports page: -1. Sign into your Expensify account. -2. Navigate to the Reports page on the left-hand navigation. -3. Click the Report that contains the expenses that you wish to merge. -4. Click on the **Details** tab, then the Pencil icon. -5. Select the two expenses that you wish to merge. -6. You'll be able to choose which aspect of each of the two expenses you would like to be used on the resulting expense, such as the receipt image, card, merchant, category, and more. - -# How to merge expenses on the Expensify mobile app -On the mobile app, merging is prompted when you see the message _"Potential duplicate expense detected"_. Simply tap **Resolve Now** to take a closer look, then hit **Merge Expense**, and you're done! - -If the expenses exist on two different reports, you will be asked which report you'd like the newly created single expense to be reported onto. - -# FAQ - -## Can you merge expenses across different reports? - -You cannot merge expenses across different reports. Expenses will only merge if they are on the same report. If you have expenses across different reports that you wish to merge, you’ll need to move both expenses onto the same report (and ensure they are in the Draft status) in order to merge them. - -## Can you merge expenses across different accounts? - -You cannot merge expenses across two separate accounts. You will need to choose one submitter and transfer the expense information to that user's account in order to merge the expense. - -## Can you merge expenses with different currencies? - -Yes, you can merge expenses with different currencies. The conversion amount will be based on the daily exchange rate for the date of the transaction, as long as the converted rates are within +/- 5%. If the currencies are the same, then the amounts must be an exact match to merge. - -## Can Expensify automatically merge a cash expense with a credit card expense? - -Yes, Expensify can merge a cash expense with a credit card expense. A receipt will need to be SmartScanned via the app or forwarded to [receipts@expensify.com](mailto:receipts@expensify.com) in order to merge with a card expense. Note that the SmartScan must be fully completed and not stopped or edited, otherwise the two won’t merge. - -## It doesn’t look like my cash and card expenses merged properly. What are some troubleshooting tips? -First, check the expense types - you can only merge a SmartScanned receipt (which will initially show with a cash icon) with a card transaction imported from a bank or via CSV. - -If the card expense in your Expensify account is older than the receipt you're trying to merge it with, they won't merge, and if the receipt is dated more than 7 days prior to the card expense, then they also will not merge. - -If you have any expenses that are more than 90 days old from the date they were incurred (not the date they were imported to Expensify), Expensify will not automatically merge them. This safeguard helps prevent the merging of very old expenses that might not align with recent transactions or receipts. - -Lastly, transactions imported with the Expensify API (via the Expense Importer) will not automatically merge with SmartScanned transactions. +## Resource 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 index 29380dab5a5b..b71fd1a3c8bf 100644 --- a/docs/articles/expensify-classic/get-paid-back/expenses/Upload-Receipts.md +++ b/docs/articles/expensify-classic/get-paid-back/expenses/Upload-Receipts.md @@ -1,36 +1,5 @@ --- -title: Upload-Receipts.md -description: This article shows you all the ways that you can upload your receipts to Expensify! +title: Upload Receipts +description: Upload Receipts --- - - -# About -Need to get paid? Check out this guide to see all the ways that you can upload your receipts to Expensify - whether it’s by SmartScanning them by forwarding via email or manually by taking a picture of a receipt, we’ll cover it here! - -# How-to Upload Receipts -## SmartScan -The easiest way to upload your receipts to Expensify is to SmartScan them with Expensify’s mobile app or forward a receipt from your email inbox! - -When you SmartScan a receipt, we’ll read the Merchant, Date and Amount of the transaction, create an expense, and add it to your Expensify account automatically. The best practice is to take a picture of the receipt at the time of purchase or forward it to your Expensify account from the point of sale system. If you have a credit card connected and you upload a receipt that matches a card expense, the SmartScanned receipt will automatically merge with the imported card expense instead. - -## Email Receipts -To SmartScan a receipt on your mobile app, tap the green camera button, point and shoot! You can also forward your digital receipts (or photos of receipts) to receipts@expensify.com from the email address associated with your Expensify account, and they’ll be SmartScanned. This may take a few minutes because Expensify aims to have the most accurate OCR. - -## Manually Upload -To upload receipts on the web, simply navigate to the Expenses page and click on **New Expense**. Select **Scan Receipt** and choose the file you would like to upload, or drag-and-drop your image directly into the Expenses page, and that will start the SmartScanning process! - -# FAQ -## How do you SmartScan multiple receipts? -You can utilize the Rapid Fire Mode to quickly SmartScan multiple receipts at once! - -To activate it, tap on the green camera button in the mobile app and then tap on the camera icon on the bottom right. When you see the little fire icon on the camera, Rapid Fire Mode has been activated - tap the camera icon again to disable Rapid Fire Mode. - -## How do you create an expense from an email address that is different from your Expensify login? -You can email a receipt from a different email address by adding it as a Secondary Login to your Expensify account - this ensures that any receipts sent from this email to receipts@expensify.com will be associated with your current Expensify account. - -Once that email address has been added as a Secondary Login, simply forward your receipt image or emails to receipts@expensify.com. - -## How do you crop or rotate a receipt image? -You can crop and rotate a receipt image on the web app, and you can only edit one expense at a time. - -Navigate to your Expenses page and locate the expense whose receipt image you'd like to edit, then click the expense to open the Edit screen. If there is an image file associated with the receipt, you will see the Rotate and Crop buttons. Alternatively, you can also navigate to your Reports page, click on a report, and locate the individual expense. +## Resource 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 index ea808695e7cd..fb4f756b2820 100644 --- 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 @@ -1,166 +1,5 @@ --- title: Create a Report -description: Learn how to create and edit reports in Expensify +description: Create a Report --- - - -# Overview - -This article covers all the basics of creating, editing, deleting and managing your reports. - -# How to create a report - -_Using the web app:_ - -To create a report on the Expensify website, click the New Report button on the **Reports** page. - -_Using the mobile app:_ - -Tap the ☰ icon. -Tap **Reports**. -Tap the **+** icon. -Choose your desired report type. - -# How to edit a report - -## Adding expenses to a report - -You can add expenses to the report by clicking **Add Expenses** at the top of the report. - -## Removing expenses from a report on the Expensify web app - -To remove expenses from the report on the web app, click the red ❌ next to the expense. - -## Removing expenses from a report on the Expensify mobile app - -To remove an expense on an Android device, hold the expense and tap **Delete**. - -To remove an expense on an iOS device, swipe the expense to the left and tap **Delete**. - -## Editing the report title - -To edit the report title, click the pencil icon next to the name. To save your changes, tap the enter key on your keyboard. - -**Note:** You may be unable to edit your reports' titles based on the settings. - -## Bulk-editing expenses on a report - -Click Details in the top-right of the report on the web app, then click the pencil icon to bring up the editing modal. You can click the pencil icon to the left of an expense to edit it, or you can edit multiple expenses at once by ticking the checkbox of the expenses you’d like to bulk-edit and then clicking **Edit Multiple** at the top of the modal. - -## Commenting on the report - -You can comment on the report by adding your comment to the **Report Comments** section at the bottom. Expensify will also log report actions here. - -## Attachments - -If you’d like to attach a photo or document to the report, follow the instructions below to add the attachment to your report comment section. - -_Using the web app:_ - -1. Click the **Paperclip** icon in the comment box of the **Report Comments** section. -2. Select the file to attach. -3. Check the preview of the attachment and click Upload. - -_Using the mobile app:_ - -1. Tap into the report. -2. Scroll to the bottom of the report and tap the paper clip icon to attach a file. - -**Note:** Report comments support jpeg, jpg, png, gif, csv, and pdf files. - -## Changing the report's workspace - -To change the report's workspace, click **Details** in the top-right of the report on the web app, then select the correct workspace from the **Workspace** drop-down. - -## Changing the report type (Expense Report/Invoice) - -To change the report type, click **Details** in the top-right of the report on the web app, then select the correct report type from the **Type** drop-down. - -## Changing the layout of the report - -There are three ways you can change the report layout under the Details section of the report. To do this, select the desired layout from the relevant drop-down menu: - - - **View** - Choose between a Basic or Detailed report view. - - **Group By** - Choose to group expenses on the report based on their Category or Tag. - - **Split By** - Split out the expenses based on their Reimbursable or Billable status. - -# How to submit a report - -1. Click **Submit** in the top-left of the report (or **Submit Report** at the top in the mobile app). -2. Verify the approver and click **Submit** again. - -# How to retract your report (Undo Submit) - -As long as the report is still in a Processing state, you can retract this submission to put the report back to Draft status to make corrections and re-submit. - -To retract a **Processing** report on the web app, click the Undo Submit button at the upper left-hand corner of the report. - -To complete this from the mobile app, simply open the report from within your app and click the **Retract** button at the top of the report. - -# How to share a report - -Click Details in the top-right of the report on the web app to bring up the sharing settings. The following options are available: - - - Click the **Printer** icon to print the report. - - Click the **Download** icon to download a PDF of the report - - Click the **Share** icon to share the report via email or SMS. - -# How to close a report - -You can close your report if you don't need it approved by your employer. - -_To close a report on the Expensify website:_ - -1. Navigate to the report in question. -2. Click **Mark as Closed** at the top of the report. -3. You can re-open a report once it’s closed by clicking **Undo Close** at the top of the report. - -# How to delete a report - -_Deleting a report on the web app:_ - -Click Details in the top-right of the report on the web app, then click the Trash icon to delete the report. Any expenses on the report will move to an Unreported state. - -_Deleting a report on the mobile app:_ - -To delete a Draft report on an Android, press and hold the report name and tap **Delete**. - -To delete a Draft report on an iOS device, go to the **Reports** screen, swipe the report to the left, and tap **Delete**. - -_Deleting a report in the Processing, Approved, Reimbursed or Closed state:_ - -If you want to delete a Processing or Closed report, please follow the How to undo your report submission instructions in this article to move the report back into an Draft status, then follow the steps above. - -If you want to delete an Approved or Reimbursed report, please speak to your Company Admin as this may not be possible. - -# How to move expenses between reports - -Navigate to your Expenses page. -Tick the checkbox next to each expense you'd like to move. -Click the Add To Report button in the top right corner. -Select your desired report from the drop-down. - -# How to use Guided Review to clean up your report - -Open your report on the web app and click Review at the top. The system will walk you through each violation on the report. -As you go through each violation, click View to look at the expense in more detail or resolve any violations. -Click Next to move on to the next item. -Click Finish to complete the review process when you’re done. - -# FAQ - -## Is there a difference between Expense Reports, Bills, and Invoices? - -**Expense Reports** are submitted by an employee to their employer. They contain either personally incurred expenses that the employee should be reimbursed for, or non-reimbursable expenses (such as company card expenses) incurred by the employee that require tracking for accounting purposes. - -**Invoices** are reports that a business or contractor will send to another business to charge them for goods or services the business received. Each invoice will have a matching **Bill** owned by the recipient so they may use it to pay the invoice sender. - -## Which report type should I use? - -If you bought something on a company card or need to be reimbursed by your employer, you’ll need an **Expense Report**. - -If someone external to the business sends you an invoice for their services, you’ll want a **Bill** (or even better - use our Bill Pay process) - -## When should I submit my report? - -Your Company Admin can answer this one, and they may have configured the workspace’s [Scheduled Submit] setting to enforce a regular cadence for you. If not, you can still set this up under your [Individual workspace]. +## Resource 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 index a31c0a582fd7..c2cc25b32373 100644 --- a/docs/articles/expensify-classic/get-paid-back/reports/Reimbursements.md +++ b/docs/articles/expensify-classic/get-paid-back/reports/Reimbursements.md @@ -1,42 +1,5 @@ -# Overview - -If you want to know more about how and when you’ll be reimbursed through Expensify, we’ve answered your questions below. - -# How to Get Reimbursed - -To get paid back after submitting a report for reimbursement, you’ll want to be sure to connect your bank account. You can do that under **Settings** > **Account** > **Payments** > **Add a Deposit Account**. Once your employer has approved your report, the reimbursement will be paid into the account you added. - -# Deep Dive - -## Reimbursement Timing - -### US Bank Accounts - -If your company uses Expensify's ACH reimbursement we'll first check to see if the report is eligible for Rapid Reimbursement (next business day). For a report to be eligible for Rapid Reimbursement it must fall under two limits: - - - $100 per deposit bank account per day or less for the individuals being reimbursed or businesses receiving payments for bills. - - Less than $10,000 being disbursed in a 24-hour time period from the verified bank account being used to pay the reimbursement. - -If the request passes both checks, then you can expect to see funds deposited into your bank account on the next business day. - -If either limit has been reached, then you can expect to see funds deposited within your bank account within the typical ACH timeframe of 3-5 business days. - -### International Bank Accounts - -If receiving reimbursement to an international deposit account via Global Reimbursement, you should expect to see funds deposited in your bank account within 4 business days. - -## Bank Processing Timeframes - -Banks only process transactions and ACH activity on weekdays that are not bank holidays. These are considered business days. Additionally, the business day on which a transaction will be processed depends upon whether or not a request is created before or after the cutoff time, which is typically 3 pm PST. -For example, if your reimbursement is initiated at 4 pm on Wednesday, this is past the bank's cutoff time, and it will not begin processing until the next business day. -If that same reimbursement starts processing on Thursday, and it's estimated to take 3-5 business days, this will cover a weekend, and both days are not considered business days. So, assuming there are no bank holidays added into this mix, here is how that reimbursement timeline would play out: - -**Wednesday**: Reimbursement initiated after 3 pm PST; will be processed the next business day by your company’s bank. -**Thursday**: Your company's bank will begin processing the withdrawal request -**Friday**: Business day 1 -**Saturday**: Weekend -**Sunday**: Weekend -**Monday**: Business day 2 -**Tuesday**: Business day 3 -**Wednesday**: Business day 4 -**Thursday**: Business day 5 +--- +title: Reimbursements +description: Reimbursements +--- +## Resource Coming Soon! diff --git a/docs/articles/expensify-classic/getting-started/Individual-Users.md b/docs/articles/expensify-classic/getting-started/Individual-Users.md index 12029f80388b..de7a527df010 100644 --- a/docs/articles/expensify-classic/getting-started/Individual-Users.md +++ b/docs/articles/expensify-classic/getting-started/Individual-Users.md @@ -1,43 +1,5 @@ --- title: Individual Users -description: Learn how Expensify can help you track and submit your personal or self-employed business expenses. +description: Individual Users --- -# Overview -If you're an individual using Expensify, the Track and Submit plans are designed to assist self-employed users in effectively managing both their personal and business finances. - -# How to use the Track plan - -The Track plan is tailored for solo Expensify users who don't require expense submission to others. Individuals or sole proprietors can choose the Track plan to customize their Individual Workspace to align with their personal expense tracking requirements. - -You can select the Track plan from the Workspace settings. Navigate to **Settings > Workspace > Individual > *[Workspace Name]* > Plan** to select Track. -You can also do this from the Pricing page at https://www.expensify.com/pricing. - -The Track plan includes a predefined set of categories designed to align with IRS Schedule C expense categories. However, you have the flexibility to add extra categories as needed. For a more detailed breakdown, you can also set up tags to create another layer of coding. - -The Track plan offers 25 free SmartScans per month. If you require more than 25 SmartScans, you can upgrade to a Monthly Individual subscription at a cost of $4.99 USD per month. - -# How to use the Submit plan -The Submit plan is designed for individuals who need to keep track of their expenses and share them with someone else, such as their boss, accountant, or even a housemate. It's specifically tailored for single users who want to both track and submit their expenses efficiently. - -You can select the Track plan from the Workspace settings. Navigate to **Settings > Workspaces > Individual > *[Workspace Name]* > Plan** to select "Submit" or from the Pricing page at https://www.expensify.com/pricing. - -You will select who your expenses get sent to under **Settings > Workspace > Individual > *[Workspace Name]* > Reports**. If the recipient already has an Expensify account, they'll be able to see the report directly in the Expensify app. Otherwise, non-Expensify users will receive a PDF copy of the report attached to the email so it can be processed. - -The Submit plan includes a predefined set of categories designed to align with IRS Schedule C expense categories. However, you have the flexibility to add extra categories as needed. For a more detailed breakdown, you can also set up tags to create another layer of coding. - -The Submit plan offers 25 free SmartScans per month.If you require more than 25 SmartScans, you can upgrade to a Monthly Individual subscription at a cost of $4.99 USD per month. - -# FAQ - -## Who should use the Track plan? -An individual who wants to store receipts, look to track spending by category to help with budgeting and a self-employed user who needs to track receipts and mileage for tax purposes. - -## Who should use the Submit plan? -An individual who seeks to utilize the features of the track plan to monitor their expenses while also requiring the ability to submit those expenses to someone else. - -## How can I keep track of personal and business expenses in the same account? -You have the capability to create distinct "business" and "personal" tags and assign them to your expenses for proper categorization. By doing so, you can effectively code your expenses based on their nature. Additionally, you can utilize filters to ensure that you only view the expenses that are relevant to your specific needs, whether they are business-related or personal. - -## How can I export expenses for tax purposes? -From the expense page, you have the option to select all of your expenses and export them to a CSV (Comma-Separated Values) file. This CSV file can be conveniently imported directly into your tax software for easier tax preparation. - +## Resource Coming Soon! diff --git a/docs/articles/expensify-classic/getting-started/Referral-Program.md b/docs/articles/expensify-classic/getting-started/Referral-Program.md index b4a2b4a7de74..683e93d0277a 100644 --- a/docs/articles/expensify-classic/getting-started/Referral-Program.md +++ b/docs/articles/expensify-classic/getting-started/Referral-Program.md @@ -1,7 +1,6 @@ --- title: Expensify Referral Program description: Send your joining link, submit a receipt or invoice, and we'll pay you if your referral adopts Expensify. -redirect_from: articles/other/Referral-Program/ --- diff --git a/docs/articles/expensify-classic/getting-started/approved-accountants/Card-Revenue-Share-For-Expensify-Approved-Partners.md b/docs/articles/expensify-classic/getting-started/approved-accountants/Card-Revenue-Share-For-Expensify-Approved-Partners.md index a8e1b0690b72..b18531d43200 100644 --- a/docs/articles/expensify-classic/getting-started/approved-accountants/Card-Revenue-Share-For-Expensify-Approved-Partners.md +++ b/docs/articles/expensify-classic/getting-started/approved-accountants/Card-Revenue-Share-For-Expensify-Approved-Partners.md @@ -1,7 +1,6 @@ --- title: Expensify Card revenue share for ExpensifyApproved! partners description: Earn money when your clients adopt the Expensify Card -redirect_from: articles/other/Card-Revenue-Share-for-ExpensifyApproved!-Partners/ --- diff --git a/docs/articles/expensify-classic/getting-started/approved-accountants/Your-Expensify-Partner-Manager.md b/docs/articles/expensify-classic/getting-started/approved-accountants/Your-Expensify-Partner-Manager.md index 104cd49daf96..c7a5dc5a04ab 100644 --- a/docs/articles/expensify-classic/getting-started/approved-accountants/Your-Expensify-Partner-Manager.md +++ b/docs/articles/expensify-classic/getting-started/approved-accountants/Your-Expensify-Partner-Manager.md @@ -1,7 +1,6 @@ --- title: Your Expensify Partner Manager description: Everything you need to know about your Expensify Partner Manager -redirect_from: articles/other/Your-Expensify-Partner-Manager/ --- diff --git a/docs/articles/expensify-classic/getting-started/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 index a7553e6ae179..2b95a1d13fde 100644 --- a/docs/articles/expensify-classic/getting-started/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 @@ -1,7 +1,6 @@ --- title: Expensify Playbook for Small to Medium-Sized Businesses description: Best practices for how to deploy Expensify for your business -redirect_from: articles/playbooks/Expensify-Playbook-for-Small-to-Medium-Sized-Businesses/ --- ## Overview This guide provides practical tips and recommendations for small businesses with 100 to 250 employees to effectively use Expensify to improve spend visibility, facilitate employee reimbursements, and reduce the risk of fraudulent expenses. diff --git a/docs/articles/expensify-classic/getting-started/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 index bef59546a13d..86c6a583c758 100644 --- a/docs/articles/expensify-classic/getting-started/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 @@ -1,7 +1,6 @@ --- title: Expensify Playbook for US-Based Bootstrapped Startups description: Best practices for how to deploy Expensify for your business -redirect_from: articles/playbooks/Expensify-Playbook-for-US-Based-Bootstrapped-Startups/ --- This playbook details best practices on how Bootstrapped Startups with less than 5 employees can use Expensify to prioritize product development while capturing business-related receipts for future reimbursement. diff --git a/docs/articles/expensify-classic/getting-started/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 index bdce2cd7bf81..501d2f1538ef 100644 --- a/docs/articles/expensify-classic/getting-started/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 @@ -1,7 +1,6 @@ --- title: Expensify Playbook for US-Based VC-Backed Startups description: Best practices for how to deploy Expensify for your business -redirect_from: articles/playbooks/Expensify-Playbook-for-US-based-VC-Backed-Startups/ --- This playbook details best practices on how Seed to Series A startups with under 100 employees can use Expensify to prioritize top-line revenue growth while managing spend responsibly. diff --git a/docs/articles/expensify-classic/getting-started/support/Your-Expensify-Account-Manager.md b/docs/articles/expensify-classic/getting-started/support/Your-Expensify-Account-Manager.md index a6fa0220c0dc..3ef47337a74c 100644 --- a/docs/articles/expensify-classic/getting-started/support/Your-Expensify-Account-Manager.md +++ b/docs/articles/expensify-classic/getting-started/support/Your-Expensify-Account-Manager.md @@ -1,7 +1,6 @@ --- title: Your Expensify Account Manager description: Everything you need to know about Having an Expensify account manager -redirect_from: articles/other/Your-Expensify-Account-Manager/ --- diff --git a/docs/articles/expensify-classic/getting-started/tips-and-tricks/Enable-Location-Access-On-Web.md b/docs/articles/expensify-classic/getting-started/tips-and-tricks/Enable-Location-Access-On-Web.md index 507d24503af8..649212b00f7b 100644 --- a/docs/articles/expensify-classic/getting-started/tips-and-tricks/Enable-Location-Access-On-Web.md +++ b/docs/articles/expensify-classic/getting-started/tips-and-tricks/Enable-Location-Access-On-Web.md @@ -1,7 +1,6 @@ --- title: Enable Location Access on Web description: How to enable location access for Expensify websites on your browser -redirect_from: articles/other/Enable-Location-Access-on-Web/ --- diff --git a/docs/articles/expensify-classic/integrations/HR-integrations/Greenhouse.md b/docs/articles/expensify-classic/integrations/HR-integrations/Greenhouse.md index b44e5a090d17..3ee1c8656b4b 100644 --- a/docs/articles/expensify-classic/integrations/HR-integrations/Greenhouse.md +++ b/docs/articles/expensify-classic/integrations/HR-integrations/Greenhouse.md @@ -1,43 +1,5 @@ --- -title: Greenhouse Integration -description: Automatically send candidates from Greenhouse to Expensify for easy reimbursement +title: Coming Soon +description: Coming Soon --- - -# Overview -Expensify's direct integration with Greenhouse allows you to automatically send candidates from Greenhouse to Expensify for easy reimbursement. The integration can set the candidate's recruiter or recruiting coordinator as approver in Expensify. - -## Prerequisites of the integration -- You must be a Workspace Admin in Expensify and an Admin in Greenhouse with Developer Permissions to complete this connection. This can be the same person or two different people. -- Each Greenhouse candidate record must have an email address in order to send to Expensify since we use this as the unique identifier in Expensify. -- We highly recommend that you create a specific Expensify workspace for candidates so that you can set up a separate workflow and a different set of Categories and Tags from what your employees would see. - -# How to connect Greenhouse to Expensify -## Establish the connection from Expensify - -1. Log into Expensify as a Workspace admin and navigate to **Settings > Workspaces > _[Workspace Name]_ > Connections** -2. Under Greenhouse, click **Connect to Greenhouse** then click **Sync with Greenhouse**, which will open the "Greenhouse Integration" instructions page in a new browser window - -## Create the Web hook - -1. Click the link under Step 1 on the Greenhouse Integration instructions page, or log into your Greenhouse account and navigate to **Configure > Dev Center > Web Hooks > Web Hooks**. -2. After landing on the "Create a New Web Hook" page, follow the steps on the Greenhouse Integration instructions page to create the web hook. - -## Create the custom candidate field - -1. Click the link under Step 2 on the Greenhouse Integration instructions page, or log into your Greenhouse account and navigate to **Configure > Custom Options > Custom Company Fields > Candidates** -2. Follow the steps on the Greenhouse Integration instructions page to create the custom Candidate field. -3. Click **Finish** (Step 3 on the Greenhouse Integration instructions page) to finish connecting Greenhouse with Expensify. - -# How to send candidates from Greenhouse to Expensify -## In Greenhouse: - -1. Log into Greenhouse and go to any candidate’s Details tab -2. Confirm that the Email field is filled in -3. Optionally select the Recruiter field to set the recruiter as the candidate's expense approver in Expensify (Note: if you'd prefer to have the Recruiting Coordinator used as the default approver, please reach out to concierge@expensify.com or your account manager to request that we change the default approver on your behalf) -4. Send this candidate to Expensify by toggling the **Invite to Expensify** field to **Yes** and clicking **Save** - -## In Expensify: - -1. Navigate to **Settings > Policies > Group > _[Workspace Name]_ > Members** -2. The candidate you just sent to Expensify should be listed in the workspace members list -3. If the Recruiter (or Recruiting Coordinator) field was filled in in Greenhouse, the candidate will already be configured to submit reports to that recruiter for approval. If no Recruiter was selected, then the candidate will submit based on the Expensify workspace approval settings. +## Resource Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/HR-integrations/Gusto.md b/docs/articles/expensify-classic/integrations/HR-integrations/Gusto.md index f7a5127c9c0e..3ee1c8656b4b 100644 --- a/docs/articles/expensify-classic/integrations/HR-integrations/Gusto.md +++ b/docs/articles/expensify-classic/integrations/HR-integrations/Gusto.md @@ -1,55 +1,5 @@ --- -title: Gusto Integration -description: Automatically sync employees between Gusto and Expensify +title: Coming Soon +description: Coming Soon --- - -# Overview - -Expensify's direct integration with Gusto will automatically: - -- **Create new Expensify accounts** for full-time, active employees when they're hired -- **Update the approval workflow in Expensify** based on any changes in Gusto -- **Deprovision an employee's Expensify account** upon Gusto termination date - -# How to connect the Gusto integration -## Before connecting Expensify with Gusto, please review the prerequisites: - -- You must be an admin in both Gusto and in Expensify to establish the integration -- You must have a paid group workspace in Expensify (i.e. a Control or Collect workspace) -- Every employee record in Gusto must have an email address, since that’s how each employee will sign into Expensify. We recommend that each employee's Gusto record use their work email address. -- Gusto will add all employees to one Expensify workspace, so if you have more than one workspace, you'll need to choose one to connect to Gusto - -## To connect your Expensify workspace to Gusto: - -1. Navigate to **Settings > Workspaces > _[Workspace Name]_ > Connections** -2. Scroll down to HR Integrations, click the **Connect with Gusto** radio button, then click the **Connect with Gusto** button -3. Login to your Gusto account using your Gusto admin credentials and authorize Expensify to access your Gusto account - -## To configure the connection: - -1. Select the Approval Workflow that works best for your team - a. **Basic Approval** - Each employee will submit expense reports to one final approver. By default, the final approver is the workspace's Billing Owner in Expensify. - b. **Manager Approval** - Expense reports will first be submitted to each employee's direct manager as listed in Gusto, and then forwarded to one final approver (the Expensify workspace's Billing Owner by default). This option is only available on the Control workspace plan. - c. **Configure Manually** - Use the Members table to manually configure how employees submit reports. In this case, you're choosing to not import employee managers, and you will need to manually set and update the approval workflow for each employee. This option is only available on the Control workspace plan. -2. Click **Save** in the bottom right corner to sync employees into Expensify -3. If the connection is successful, you'll see a summary of how many employees were synced. If any employees were skipped, we'll tell you why. - -# FAQ -## Can I import different sets of employees into different Expensify workspaces? - -No - Gusto will add all employees to one Expensify workspace, so if you have more than one workspace, you'll need to choose when connecting. - -## Can I change the Approval Workflow mode after connecting? - -Yes! You can change the Approval Workflow mode in two ways: - -1. Go to **Settings > Workspaces > _[Workspace Name]_ > Members**, then scroll down to Approval Mode below the list of workspace members -2. Go to **Settings > Workspaces > _[Workspace Name]_ > Connections**, click Configure under Gusto, then select the desired Approval Mode and **Save** - - -## Why do my employees have duplicate Expensify accounts after I set up the Gusto integration? - -If your employees are set up in Expensify with their company emails, but with their personal emails in Gusto, then they will end up with duplicate Expensify accounts after you connect the two systems. The Gusto integration imports users from Gusto using the emails entered in Gusto - if it's a different email from an existing account in Expensify, then a new, separate account will be created. - -To resolve this, you can ask each affected employee to merge their existing Expensify account with the new Expensify account by navigating to **Settings > Account > Account Details** and scrolling down to **Merge Accounts**. - +## Resource Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/HR-integrations/Rippling.md b/docs/articles/expensify-classic/integrations/HR-integrations/Rippling.md index fa4aaec3376f..3ee1c8656b4b 100644 --- a/docs/articles/expensify-classic/integrations/HR-integrations/Rippling.md +++ b/docs/articles/expensify-classic/integrations/HR-integrations/Rippling.md @@ -1,13 +1,5 @@ --- -title: Rippling Integration -description: Sync employee and expense data between Expensify and Rippling +title: Coming Soon +description: Coming Soon --- -# Overview -The Rippling integration allows mutual customers to sync employee and expense data between Expensify and Rippling. The Rippling integration allows you to: -1. **Automate Employee Management:** Automatically create and remove employee accounts in Expensify directly from Rippling. -2. **Simplify Employee Access to Expensify:** Employees can sign into Expensify from their Rippling SSO bar using SAML single sign-on. -3. **Import Reimbursable Expense Reports:** Admins can export reimbursable expense reports from Expensify directly into Rippling Payroll. - -# How to use the Rippling integration -The Rippling team manages this integration. To connect your Expensify workspace with Rippling, please visit the Rippling App Shop and sign into your Rippling account. -For instructions on how to connect, and for troubleshooting the integration, please contact the Rippling support team by emailing support@rippling.com. +## Resource Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/HR-integrations/Workday.md b/docs/articles/expensify-classic/integrations/HR-integrations/Workday.md index e9077fc40a50..3ee1c8656b4b 100644 --- a/docs/articles/expensify-classic/integrations/HR-integrations/Workday.md +++ b/docs/articles/expensify-classic/integrations/HR-integrations/Workday.md @@ -1,104 +1,5 @@ --- -title: Workday Integration -description: Automatically sync employees between Workday and Expensify +title: Coming Soon +description: Coming Soon --- - -# Overview -By leveraging Expensify's [Employee Updater API](https://integrations.expensify.com/Integration-Server/doc/employeeUpdater/), you can set up a fully customizable integration between Workday and Expensify. This integration can: - -- **Provision new employees in Expensify:** Employees are automatically invited to the correct Expensify workspace on their start date based on data in Workday. -- **Update employees and approval workflows:** Any changes to employee email and manager are automatically updated in Expensify. -- **Deprovision employees:** Employees can optionally be removed from their primary Expensify workspace on their termination date. -Please note that while your Account Manager can help advise on setting up the Workday integration, the Expensify API is a self-serve tool. - -# How to set up an Advanced Custom Report in Workday -The first step to integrating Workday with Expensify is to create an advanced custom report in Workday. This report can: -- Map Workday column data to an Expensify Workspace for import. -- Import employee names, email addresses and manager emails into the Expensify Workspace. -- Set the employee’s **Submits To** column in the Expensify Members table. -- Set the employee's Expensify **Custom Field 1 & 2**, typically used for Employee ID, Cost Center and/or Legal Entity. -- Add employees to different Expensify Domain Groups. -- Auto-assign Expensify Cards. - -In order to complete the steps below, you'll need a Workday System Administrator to create an **Integration System User** and **Integration System Security Group**. - -## Create an Integration System User -1. Search "create user" and click **Create Integration System User**. -2. Add a password, leave **Require New Password at Next Sign In** unchecked, set **Session Timeout Minutes** to 0, and check **Do Not Allow UI Sessions**. -3. Click **OK**. - -## Create a Security Group -1. Search "create security group", then click **Create Security Group**. -2. Create a **Constrained** security group and specify the **Organizations** you'd like to sync data from. -3. Add the **Integration System User** you created to your **Security Group**. -4. Search and select "security group membership and access". -5. Search for the security group you just created. -6. Click the ellipsis, then **Security Group > Maintain Domain Permissions for Security Group**. -7. Under **Integration Permissions**, add "External Account Provisioning" to **Domain Security Workspaces permitting Put access** and "Worker Data: Workers" to **Domain Security Workspaces permitting Get access**. -8. Click **OK** and **Done**. -9. Search **Activate Pending Security Workspace Changes** and complete the task for activating the security workspace change, adding a comment if required and checking the **Confirmed** check-box. - -## Create the Advanced Custom Report -Before completing the steps below, you will need Workday Report Writer access to create an Advanced Custom Report in Workday and enable it as a RAAS (Report as a Service). - -1. Search “Create Custom Report” and click **Create Custom Report**. -2. Enter the report details: - - Give the report a **Name**. - - Set the **Report Type** to **Advanced**. - - Check **Enable As Web Service**. - - Uncheck **Optimized for Performance**. - - For **Data Source**, search and select **All Active and Terminated Employees**. - - Click **OK**. -3. Select the **Column Data** you’d like to sync with Expensify. Typical fields synced with Expensify from Workday are as follows (Required fields are marked with \*): - - First Name - - Last Name - - Primary Work Email\* - - Employee ID\* - - Expensify Workspace ID\* - - Worker’s Manager [Primary Work Email]\* - - Domain Group ID (If you want to specify a Domain Group in Expensify, please work with your Account Manager to get your Domain Group IDs) - - Cost Center - - Entity ID (sometimes called Legal Entity) - - Active/Inactive - - Termination Date - - Note: _if there is field data you want to import that is not listed above, or you have any special requests, let your Expensify Account Manager know and we will work with you to accommodate the request._ -4. Rename the columns so they match Expensify's API key names (The full list of names are found here): - - employeeID - - firstName - - lastName - - employeeEmail - - managerEmail - - workspaceID - - domainGroupID - - approvesTo -Switch to the **Share** tab, and share the report with your **Integration System User** and **Security Group**. - -## Enable your report as a Report as a Service (RAAS) - -1. In your Workday tenant, search “view custom report” and select it. On the **View Custom Report** screen, click **My Reports**. -2. Select the report you have created and click **OK**. -3. Click **Actions > Web Service > View URLs** and click **OK**. -4. Scroll to the **JSON** section, right-click **JSON**, then select **Copy URL**. - -## Activate the Workday Integration - -If you would like to enable and run the API job that performs a recurring sync, you can do so by following Expensify’s API reference documentation [here](https://integrations.expensify.com/Integration-Server/doc/employeeUpdater/#api-principles). - -If you would like Expensify to perform the sync on your behalf, please follow the steps below. - -1. To generate your **Expensify API Credentials**, log into Expensify with an account that has both Workspace Admin and Domain Admin access, then head to https://www.expensify.com/tools/integrations/ where you will find your partnerUserID and partnerUserSecret. -2. Go to **Settings > Workspaces > Group > _[Workspace Name]_ > Connections > HR Integrations** and click **Connect to Workday**. -3. In the form, supply the following details: - - partnerUserID - - partnerUserSecret - - Workday ISU Username (e.g. ISU_Expensify) - - Workday password - - Workday REST Web Services URL - - Preferred go live date (e.g. YYYY/MM/DD, or leave blank) - - Expensify Card Auto-Assignment? (Y/N) - - Note: If using Expensify Cards, card auto-assignment occurs when a Smart Limit for a Group is enabled. - - Deprovision Users? (Y/N) - -After you submit the form, the request is sent to your Expensify Account Manager. Your Account Manager will create a recurring sync that will retrieve the data columns from your Workday Web Services URL and apply the rule mappings you have specified above. - -If we have any questions, we will reach out to you via direct message. +## Resource Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/HR-integrations/Zenefits.md b/docs/articles/expensify-classic/integrations/HR-integrations/Zenefits.md index e94d915e4dfa..3ee1c8656b4b 100644 --- a/docs/articles/expensify-classic/integrations/HR-integrations/Zenefits.md +++ b/docs/articles/expensify-classic/integrations/HR-integrations/Zenefits.md @@ -1,39 +1,5 @@ --- -title: Zenefits Integration -description: Automatically sync employees between Zenefits and Expensify +title: Coming Soon +description: Coming Soon --- -# How the Zenefits integration works with Expensify - -Expensify's direct integration with Zenefits will automatically: -- **Create new Expensify accounts** for full-time, active employees when they're hired -- **Update the approval workflow in Expensify** based on any changes in Zenefits -- **Deprovision an employee's Expensify account** upon Zenefits termination date - -# How to connect the Zenefits integration -## Before connecting Expensify with Zenefits, please review the prerequisites: - -- You must be an admin in Zenefits and in Expensify to establish the integration -- You must have a Control or Collect workspace in Expensify to integrate with Zenefits. If you do not, you'll be given the opportunity to upgrade to Control or Collect during the integration setup. -- Every employee record in Zenefits must have a work email address since we use this as the unique identifier in Expensify. -- Zenefits will add all your employees to one Expensify workspace. If your company uses multiple Expensify workspaces, you'll be given the option to choose which workspace to connect to when you're setting up the integration. - -## To connect your Expensify workspace to Gusto: - -1. Navigate to **Settings > Workspaces > Group > _[Workspace Name]_ > Connections** -2. Scroll down to HR Integrations, click the **Connect to Zenefits** radio button, then click **Sync with Zenefits** -3. Login to your Zenefits account using your Zenefits admin credentials and authorize Expensify to access your Zenefits account. -4. If you want to exclude an individual user from syncing with Expensify, make your selections before clicking **Authorize** - -## To configure the connection: - -1. Select the Approval Workflow that works best for your team: - - **Basic Approval:** Each employee will submit expense reports to one final approver. By default, the final approver is the workspace's Billing Owner. - - **Manager Approval:** Expense reports will first be submitted to each employee's direct manager, and then forwarded to one final approver. By default, the final approver is the workspace's Billing Owner. - - **Configure Manually:** Use the members table to manually configure how employees submit reports. In this case, you're choosing to not import an employee's manager. You will need to set and update the approval workflow for each employee manually. If your team has a highly complex approval workflow, this option will allow for multi-tiered approval chains. - -# Zenefit integration FAQs -## Will this notify my employees? -Each employee will receive a welcome email at their work email address along with a request to validate their account and choose a password. They can also download our mobile app for iOS and Android devices. Please note that there is no way to disable the welcome email. - -## Should I connect the integration from Expensify or from Zenefits? -It's totally up to you! You'll have the same options available to you when syncing from either product. +## Resource Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/accounting-integrations/Indirect-Accounting-Integrations.md b/docs/articles/expensify-classic/integrations/accounting-integrations/Indirect-Accounting-Integrations.md deleted file mode 100644 index 852db0b7f7c0..000000000000 --- a/docs/articles/expensify-classic/integrations/accounting-integrations/Indirect-Accounting-Integrations.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -title: Indirect Accounting Integrations -description: Learn how to export your expenses and reports to a built-for-purpose flat file that works with your accounting platform. ---- - - -# Overview - -Along with the direct integrations Expensify supports, there's also an option to integrate with other accounting solutions via a flat-file import. - -When you set up one of these accounting packages in Expensify, we will automatically create and add a relevant export template. The template will allow you to quickly and easily transfer expense and report data to your accounting package. - -# How to Set Up an Indirect Accounting Integration - -## Home Page - -After selecting your Group Plan type for your first workspace, you'll be taken through a few workspace setup tasks on the home page. When you reach the **Accounting Software** task, select your accounting solution from the available options. - -You'll receive a confirmation message, and the respective export template will be added to the account. From then on, it will show in the **Export to** option on the **Reports** page and at the top of each report. - -## Workspace Settings - -Head to **Settings** > **Workspaces** > **Group** > _Your desired workspace_ > **Connections** and select an accounting package from the options listed here. You'll receive a confirmation message, and the respective export template will be added to the account. From then on, it will show in the **Export to** option on the **Reports** page and at the top of each report. - -# How to Export a Report for My Accounting Package - -You can export reports to these templates in two ways: - -To export a report, click **Export To** in the top-left of a report and select your accounting package from the dropdown menu. - -To export multiple reports, tick the checkbox next to the reports on the **Reports** page, then click **Export To** and select your accounting package from the dropdown menu. - -# FAQ - -## Which accounting packages offer this indirect integration with Expensify? - -We support a pre-configured flat-file integration for the following accounting packages: - - - Sage - - Microsoft Dynamics - - MYOB - - Oracle - - SAP - -## What if my accounting package isn’t listed here? - -If your accounting package isn’t listed, but it still accepts a flat-file import, select **Other** when completing the Accounting Software task on your Home page or head to **Settings** > **Workspaces** > **Group** > _Your desired workspace_ > **Export Formats**. This option allows you to create your own templates to export your expense and report data into a format compatible with your accounting system. - diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/Global-VaTax.md b/docs/articles/expensify-classic/integrations/travel-integrations/Global-VaTax.md index 4a18b30458a6..3ee1c8656b4b 100644 --- a/docs/articles/expensify-classic/integrations/travel-integrations/Global-VaTax.md +++ b/docs/articles/expensify-classic/integrations/travel-integrations/Global-VaTax.md @@ -1,31 +1,5 @@ --- -title: Global VaTax Integration -description: The Expensify-Global VaTax integration turns your company’s international expenses into an easy VAT refund. +title: Coming Soon +description: Coming Soon --- -# About -The Expensify-Global VaTax integration turns your company’s international expenses into an easy VAT refund. -If your company is based in the U.S. but doing business abroad, track your [expenses](https://use.expensify.com/expense-management) in Expensify and then sync to [Global VaTax](https://globalvatax.com/) to receive a VAT refund. -## VAT -[Value Added Tax (VAT)](https://www.investopedia.com/terms/v/valueaddedtax.asp#:~:text=Value%2Dadded%20tax%20(VAT)%20is%20a%20flat%20tax%20levied,different%20parties%20to%20a%20transaction.) is a consumption tax applied to goods and services in many countries. It is typically applied automatically upon purchase, however, international business travelers can reclaim some VAT. - -VAT-recoverable expenses include hotels, entertainment, conferences, legal and marketing fees, DDP shipping and storage fees, aviation recovery, and other international expenses. -## VAT Refund -The VAT refund process is complex and requires a detailed understanding of the regulations and requirements in each country. The VAT rules and rates vary from country to country and by expense type. - -You can seamlessly sync your Expensify expenses to Global VaTax for easy VAT analysis, calculation, and reporting. - - -# How to Connect to Global VaTax -1. Fill out [this form](https://www.vataxcloud.com/expensify/signup) to receive a confirmation email from Global VaTax. -2. Click the link in the confirmation email to sign into your Global VaTax account. -3. Go to the **Method of Extraction** section. -4. Click **Expensify** -5. Follow the steps to obtain your partner credentials and activate the integration. -6. Enter your credentials and extraction dates -7. Click **Submit** -8. VaTax Cloud will begin automatically calculating your VAT rates for each eligible expense line item, per expense type, in each country. -9. Within a few hours, you'll receive an email that your VAT potential analysis report is ready for review. - -Your VAT reclaim will be prepared by Global VaTax in the necessary languages and submitted to the appropriate tax agents in each country. - -After submitting your VAT reclaim, you can track it via the submission analysis report in Global VaTax. +## Resource Coming Soon! diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/Lyft.md b/docs/articles/expensify-classic/integrations/travel-integrations/Lyft.md index b863b8e5a61c..3ee1c8656b4b 100644 --- a/docs/articles/expensify-classic/integrations/travel-integrations/Lyft.md +++ b/docs/articles/expensify-classic/integrations/travel-integrations/Lyft.md @@ -1,24 +1,5 @@ --- -title: Lyft & Expensify -description: Setting up and using Expensify's Lyft integration +title: Coming Soon +description: Coming Soon --- - - -# Overview -You can link Expensify directly to your Lyft account. This means that your receipts for work-related Lyft rides will populate automatically in Expensify. - -# How to connect a Lyft Business profile for individual use -If you use Lyft for work, you can connect your business profile to Expensify to have your Business-related trips populate directly in your Expensify account. -1. Open the Lyft mobile app and tap your profile photo in the upper left. -2. Go to Settings. -3. Select Business profile, and create one if you don’t already have one. -4. Tap Expense management and choose Expensify. You'll be asked to enter your email, make sure it matches the email you use to access Expensify. -You're all set! Switch into business profile mode each time you take a ride for work, and your receipts will automatically populate in Expensify. - -# How to set up Lyft for Business -If you have a Lyft for Business account for your company, setting up the Expensify and Lyft integration for your whole team is very straightforward: -1. Open your Lyft for Business portal from a web browser. -2. Navigate to People > Business Profile (+) and input each team member's work email. -3. Once invited, each employee will receive an email prompting Lyft Business Profile setup. -4. After they set up their profiles, employees can then switch into business profile mode each time they take a ride for work, and their receipts will automatically populate in Expensify. -Now, when any employee completes a ride on a Lyft Business Profile, it will show up in the "Rides" section of your Lyft for Business portal. +## Resource 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 index 237047fa270e..3ee1c8656b4b 100644 --- a/docs/articles/expensify-classic/integrations/travel-integrations/Trip-Actions.md +++ b/docs/articles/expensify-classic/integrations/travel-integrations/Trip-Actions.md @@ -1,47 +1,5 @@ --- -title: Navan Integration -description: Connecting Navan and Expensify +title: Coming Soon +description: Coming Soon --- -# Overview -Booking travel through Navan? Link your Expensify and Navan accounts to automatically send flight receipts to Expensify for easy reporting. - -# How to connect Navan to Expensify -To set up the integration, run through the following steps: -1. Sign in at navan.com -2. Click on your profile in the upper right corner -3. Select “Integrations” from the drop-down menu -4. In the Expensify box, click the “Enable” link -5. Uncheck the box that says “Disallow company card expensing” -6. Check the box that says “Automatically push expenses when booking a flight” -7. If you’re using a central booking account, and you’d like all expenses to be sent to one Expensify account, click “Edit” next to “Expensify” in Navan and enter the email address you’d like to send receipts to - -Once you complete these steps, any flights you book through Navan will automatically be added to an expense report in Expensify. - -If you booked your Navan flight using your Expensify Card, the Navan expense will automatically merge with the card expense. Learn more about the Expensify Card [here](https://use.expensify.com/company-credit-card). - - -# FAQ - -## How do I expense a prepaid hotel booking in Expensify using the Navan integration? -Bookings that weren’t made in Navan directly (such as a prepaid hotel booking) won’t auto-import into Expensify. To import these trips into Expensify, follow these steps: - -1. Click the “Trips” tab in Navan. -2. Select the trip or hotel that you'd like to expense. -3. Click the “Expense to Expensify” link at the bottom of the page. - -This also works for importing flights that were booked prior to setting up the integration. - -## What kind of data is shared between Expensify and Navan? -Travel booking details from Navan such as flight, hotel, and rental car costs will sync to Expensify to help auto-generate expense reports. - -## What should I do if an expense doesn't sync correctly? -If an expense isn’t syncing correctly, check the integration settings in Navan and confirm that there are no sync errors. If there are no errors but the problems persist, contact the support teams of either platform for assistance. - -## Are manual adjustments to expenses in Expensify reflected in Navan? -Manual adjustments to expenses made in Expensify won't retroactively adjust data in the Navan. Always verify any changes on both platforms to ensure consistency. - -## Is there a cost associated with this integration? -Costs depend on your subscription plans with Expensify and Navan. Expensify doesn’t charge extra for this integration. - -## How do I disconnect the integration? -To disconnect the integration, navigate to the integrations section in Navan, find Expensify, and select the option to disable the integration. +## Resource 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 index 65238457f1a9..3ee1c8656b4b 100644 --- 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 @@ -1,63 +1,5 @@ --- -title: User Roles -description: Each member has a role that defines what they can see and do in the workspace. +title: Coming Soon +description: Coming Soon --- - -# Overview - -This guide is for those who are part of a **Group Workspace**. - -Each member has a role that defines what they can see and do in the workspace. Most members will have the role of "Employee." - -# How to manage user roles - -To find and edit the roles of group workspace members, go to **Settings > Workspaces > Group > _[Workspace Name]_ > Members > Workspace Members** - -Here you'll see the list of members in your group workspace. To change their roles, click **Settings** next to the member’s name and choose the role that the member needs. - -Next, let’s go over the various user roles that are available on a group workspace. - -### The Employee Role - -- **What can they do:** Employees can only see their own expense reports or reports that have been submitted to or shared with them. They can't change settings or invite new users. -- **Who is it for:** Regular employees who only need to manage their own expenses, or managers who are reviewing expense reports for a few users but don’t need global visibility. -- **Approvers:** Members who approve expenses can either be Employees, Admins, or Workspace Auditors, depending on how much control they need. -- **Billable:** Employees are billable actors if they take actions on a report on your Group Workspace (including **SmartScanning** a receipt). - -### Workspace Admin Role - -- **What can they do:** Admins have full control. They can change settings, invite members, and view all reports. They can also process reimbursements if they have access to the company’s account. -- **Billing Owners:** Billing owners are Admins by default. **Workspace Admins** are assigned by the owner or another admin. -- **Billable:** Yes, if they perform actions like changing settings or inviting users. Just viewing reports is not billable. - -### Workspace Auditor Role - -- **What can they do:** Workspace Auditors can see all reports, make comments, and export them. They can also mark reports as reimbursed if they're the final approver. -- **Who is it for:** Accountants, bookkeepers, and internal or external audit agents who need to view but not edit workspace settings. -- **Billable:** Yes, if they perform any actions like commenting or exporting a report. Viewing alone doesn't incur a charge. - -### Technical Contact - -- **What can they do:** In case of connection issues, alerts go to the billing owner by default. You can set a technical contact if you want alerts to go to an IT administrator instead. -- **How to set one:** Go to **Settings > Workspaces > Group > [Workspace Name] > Connections > Technical Contact**. -- **Billable:** The technical contact doesn’t need to be a group workspace member and so is not counted towards your billable activity. - -Note: running expense analytics from **Insights** follows the same rules. All the reports and data graphs you generate will be created based on the expense data you have access to. - -# Deep Dive - -## Expense Data Visibility - -The amount of expense data you can see depends on your role within any group workspaces you're part of: - -- **Employees:** Whether you're on a free or paid plan, if you're not approving expenses, you'll only see your own expenses. -- **Approvers:** If you approve expenses for your team and also submit your own, you can view both individual and team-wide expenses and analytics. -- **Admins:** Users with an admin role can see analytics and data for every expense report made by anyone on the workspace. - -If you need to see more data, here are some options: - -- **Become an Admin:** Check within your organization if you can be upgraded to an admin role in your group workspaces. -- **Become a Copilot:** Ask to be added as a **Copilot** to an existing admin account, which will allow you some additional viewing privileges. -- **Become an Approver:** You could also be added as an **Approver** in an existing workflow to view more data. - - +## Resource 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 index 7c21b12a83e1..e107734216f5 100644 --- 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 @@ -1,54 +1,8 @@ --- -title: Vacation Delegate -description: In Expensify, a vacation delegate is someone you choose to act on your behalf when you're on vacation or taking personal time off. +title: Coming Soon +description: Coming Soon --- - -# Overview - -A delegate is someone who can handle approving expense reports for you, which is especially useful when you're out of the office! - -In Expensify, a **Vacation Delegate** is someone you choose to act on your behalf when you're on vacation or taking personal time off. They will approve expense reports just like you would, and everything moves forward as usual afterward. - -The system keeps a detailed audit trail, showing exactly when your delegate stepped in to approve a report for you. And if your delegate also goes on vacation, they can have their own delegate, so reports keep getting approved. - -By using this feature, you ensure that all reports get the approvals they need, even when you're not around. - -# How to use Vacation Delegate - -If you're planning to take some time off, you can use the **Vacation Delegate** feature to assign someone to approve expense reports for you. The reports will continue on their usual path as if you had approved them yourself. - -## Set a Vacation Delegate for yourself - -1. Go to the Expensify website (note: you can't do this from the mobile app). -2. Navigate to **Settings > Your Account > Account Details** and scroll down to find **Vacation Delegate**. -3. Enter the email address of the person you're designating as your delegate and click **Set Delegate**. - -Voila! You've set a vacation delegate. Any reports that usually come to you will now go to your delegate instead. When you return, you can easily remove the delegate by clicking a link at the top of the Expensify homepage. - -## Setting a Vacation Delegate as a Domain Admin - -1. Head to **Settings > Domains > [Your Domain Name] > Domain Members > Edit Settings** -2. Enter the delegate's email address and click **Save.** - -Your delegate's actions will be noted in the history and comments of each report they approve, so you can keep track of what happened while you were away. - -# Deep Dive - -## An audit trail of delegate actions - -The system records every action your vacation delegate takes on your behalf in the **Report History and Comments**. So, you can see when they approved an expense report for you. - -# FAQs - -## Why can't my Vacation Delegate reimburse reports that they approve? - -If your **Vacation Delegate** also needs to reimburse reports on your behalf whilst you're away, they'll also need access to the reimbursement account. - -If they do not have access to the reimbursement account used on your workspace, they won’t have the option to reimburse reports, even as your **Vacation Delegate**. - -## What if my Vacation Delegate is also on vacation? - -Don't worry, your delegate can also pick their own **Vacation Delegate**. This way, expense reports continue to get approved even if multiple people are away. - +## Resource 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..cea96cfe2057 --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/Admins.md @@ -0,0 +1,5 @@ +--- +title: Admins +description: Admins +--- +## Resource 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..3ee1c8656b4b --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/Domain-Admins.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resource 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..3ee1c8656b4b --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/Domain-Members.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resource 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 index 424338120010..3ee1c8656b4b 100644 --- a/docs/articles/expensify-classic/policy-and-domain-settings/Expenses.md +++ b/docs/articles/expensify-classic/policy-and-domain-settings/Expenses.md @@ -1,118 +1,5 @@ --- -title: Expensify Workspace Expense Settings -description: Expense Settings +title: Coming Soon +description: Coming Soon --- -# Overview - -Expensify offers multiple ways to customize how expenses are created in your workspace. In this doc, you’ll learn how to set up and expense basics, distance expenses, and time expenses. - -Whether you’re flying solo with your Individual workspace or submitting with a team on your Group workspace, we have settings to support how you use Expensify. - -# How to manage expense settings in your workspace - -Let’s cover the expense basics first! In the following sections, we’ll go through each part of managing expense settings in your workspace. - -## Controlling cash expenses - -A cash expense is any expense created manually or by uploading a receipt for SmartScan; a cash expense does not mean the expense was paid for with cash. The other type of expense you’ll most commonly see is credit card expenses, which means the expenses imported from a credit card or bank connection. - -There are four options for cash expenses: - -- **Reimbursable by default** - All cash expenses are reimbursable but can be marked as non-reimbursable as needed. -- **Non-reimbursable by default** - All cash expenses are non-reimbursable but can be marked as reimbursable as needed. -- **Forced always reimbursable** - All cash expenses are forced to be reimbursable; they cannot be marked as non-reimbursable. -- **Forced always non-reimbursable** - All cash expenses are forced to be non-reimbursable; they cannot be marked as reimbursable. - -## Setting up billable expenses - -Billable expenses refer to expenses you or your employees incur that need to be re-billed to a specific client or vendor. - -If you need to track expenses for the purpose of billing them to customers, clients, or other departments, billable expenses are supported in both Individual and Group workspaces. Either way, head to **Settings** > **Workspaces** > **Individual** or **Group** > [_Workspace Name_] > **Expenses**. - -Under Expense Basics, you can choose the setting that is best for you. - -- **Disabled** means expenses are not allowed to be billable at all. -- **Default to billable** means expenses will always be billable but can be marked as non-billable as needed. -- **Default to non-billable** means expenses will always be non-billable but can be marked as billable as needed. - -If your Group workspace is connected to Xero, QuickBooks Online, NetSuite, or Sage Intacct, you can export billable expenses to be invoiced to customers. To set this up, go to the Coding tab in the connection configuration settings. - -## Using eReceipts - -eReceipts are full digital replacements of their paper equivalents for purchases of $75 or less. - -Click the toggle to your preferred configuration. - -- **Enabled** - All imported credit card expenses in US dollars of $75 or less will have eReceipts in the receipt image. -- **Disabled** - No expenses will generate an eReceipt. - -Note: _We will not generate an eReceipt for lodging expenses._ - -## Securing receipt images - -Whether you’re sharing your receipts with your accountant, having an auditor review exported expenses, or simply wanting to export to keep a hard copy for yourself, receipt visibility will be an essential consideration. - -Under _Public Receipt Visibility_, you can determine who can view receipts on your workspace. - -- **Enabled** means receipts are viewable by anyone with the URL. They don't need to be an Expensify user or a workspace member to view receipts. -- **Disabled** means receipts are viewable by users of Expensify, who would have access to view the receipt in the application. You must be an Expensify user with access to the report a receipt is on and logged into your account to view a receipt image via URL. - - -## Track mileage expenses - -Whether using the Individual or Group workspace, you can create distance rates to capture expenses in miles or kilometers. - -Preliminary setup steps include: - -1. Selecting whether you want to capture _miles_ or _kilometers_, -2. Setting the default category to be used on distance expenses, -3. Click **Add A Mileage Rate** to add as many rates as you need, -4. Set the reimbursable amount per mile or kilometer. - -Note: _If a rate is toggled off it is immediately disabled. This means that users are no longer able to select it when creating a new distance expense. If only one rate is available then this rate will be toggled on by default._ - -## Set an hourly rate - -Using Expensify you can track time-based expenses to bill your clients at an hourly rate or allow employees to claim an hourly stipend. - -Click the toggle under the _Time_ section to enable the feature and set a default hourly rate. After that, you and your users will be able to create time-based expenses from the [**Expenses**](https://expensify.com/expenses) page of the account. - -# Deep dives - -## What is Concierge Receipt Audit for the Control Plan? - -Concierge Receipt Audit is a real-time audit and compliance of receipts submitted by employees and workspace users. Concierge checks every receipt for accuracy and compliance, flagging any expenses that seem fishy before expense reports are even submitted for approval. All risky expenses are highlighted for manual review, leaving you with more control over and visibility into expenses. When a report is submitted and there are risky expenses on it, you will be immediately prompted to review the risky expenses and determine the next steps. - -**Why you should use Concierge Receipt Audit** - -- To make sure you don't miss any risky expenses that need human oversight. -- To avoid needing to manually review all your company receipts. -- It's included for free with the [Control Plan](https://www.expensify.com/pricing). -- Instead of paying someone to audit your company expenses or being concerned that your expenses might be audited by a government agency. -- It's easy to use! Concierge will alert you to the risky expense and present it to you in an easy-to-follow review tutorial. -- In addition to the risky expense alerts, Expensify will include a Note with audit details on every report. - -Note: _If a report has audit alerts on it, you'll need to Review the report and Accept the alerts before it can be approved._ - -## Tracking tax on mileage expenses - -If you’re tracking tax in Expensify you can also track tax on distance expenses. The first step is to enable tax the workspace. You can do this by going to **Settings** > **Workspaces** > **Individual** or **Group** > [_Workspace Name_] > **Tax**. - -Once tax is enabled on a workspace level you will see a toggle to _Track Tax_ in the Distance section of the workspace settings. If tax is disabled on the workspace the Track Tax toggle will not display. - -When Track Tax is enabled you will need to enter additional information to the rates you have set, this includes the _Tax Reclaimable on_ and _Tax Rate_ fields. With that information, Expensify will work out the correct tax reclaim for each expense. - -If you enable tax but don’t select a tax rate or enter a tax reclaimable amount, we will not calculate any tax amount for that rate. If, at any point, you switch the tax rate or enter a different reclaimable portion for an existing distance rate, the mileage rate will need to be re-selected on expenses for the tax amount to update according to the new values. - -Note: _Expensify won’t automatically track cumulative mileage. If you need to track cumulative mileage per employee, we recommend building a mileage report using our custom export formulas._ - -# FAQs - -## Why do I see eReceipts for expenses greater than $75? - -An eReceipt is generated for Expensify card purchases of any amount in the following categories: Airlines, Commuter expenses, Gas, Groceries, Mail, Meals, Car rental, Taxis, and Utilities. - -## Why didn’t my rate get updated with the newest rate guidance by the IRS? - -Expensify does not update mileage rates to match the rate provided by the IRS. An admin of the workspace will need to update the rate or create a new rate in the workspace. This is because Expensify has customers worldwide, not just in the United States, and most companies want to communicate the change with employees and control the timing. - +## Resource 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..3ee1c8656b4b --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/Overview.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resource 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..3ee1c8656b4b --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/Reports.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resource 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..3ee1c8656b4b --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/Tax.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resource 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..3ee1c8656b4b --- /dev/null +++ b/docs/articles/expensify-classic/policy-and-domain-settings/Trips.md @@ -0,0 +1,5 @@ +--- +title: Coming Soon +description: Coming Soon +--- +## Resource Coming Soon! diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/reports/Currency.md b/docs/articles/expensify-classic/policy-and-domain-settings/reports/Currency.md deleted file mode 100644 index 19fd4e8f3723..000000000000 --- a/docs/articles/expensify-classic/policy-and-domain-settings/reports/Currency.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -title: Currency -description: How currency works in Expensify and how to change the default currency in your Expensify workspace ---- -# Overview - -In this article, we’ll outline how to change the default currency in your account and how currency works in Expensify. -Expensify supports expenses in almost every currency in the world. Group workspace admins and individual workspace users can specify the desired output currency for employee reports. Expensify handles the currency conversion process. - -# How to change your default currency - -The default currency for all expenses added to your account is set by the primary company workspace. Just head to **Settings > Workspaces > Group > *[Workspace Name]* > Reports > Report Basics** and select your desired Report Currency. - -If you are not using a group workspace, you can change your default currency under **Settings > Workspaces > Individual > *[Workspace Name]* > Reports** and then choose your desired Report Currency. Please note that the currency selected here will be overridden should you begin reporting on a group workspace. - -# How currency works in Expensify - -When totaling expenses across multiple currencies, we convert them to a single currency, which is the "report currency" of the report's expense workspace, or your personal output currency if no workspace is in use. - -**Important notes:** - -- Currency settings on a workspace are all-or-nothing. To reflect a different output currency in reports, create a new workspace for those employees and adjust the currency settings accordingly. -- Currency settings in the workspace take precedence over a user's individual account settings. - -# How the conversion rate is determined -When converting expenses between currencies, we rely on [Open Exchange Rates](https://openexchangerates.org/) to determine the average bid and ask rate on the expense date. This rate becomes available after the market closes for that day, resulting in varying conversion rates depending on when the expense occurred and how the currencies were trading. - -If the markets were closed on the expense date (e.g., weekends), we use the daily average rate from the last open market day prior to the purchase. For future-dated expenses, we use the most recent available data. Consequently, the report's value may fluctuate until the expense date, potentially leading to unexpected results. You cannot submit reports until the markets have closed for all the expense dates on the report. - -To bypass exchange rate calculations, manually enter expenses in your default currency. These entries will only be converted when included in a report with a different default currency. - diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/reports/Report-Fields-And-Titles.md b/docs/articles/expensify-classic/policy-and-domain-settings/reports/Report-Fields-And-Titles.md deleted file mode 100644 index e79e30ce42c9..000000000000 --- a/docs/articles/expensify-classic/policy-and-domain-settings/reports/Report-Fields-And-Titles.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -title: Report Fields & Titles -description: This article is about managing Report Fields and Report Titles in Expensify ---- -# Overview - -In this article, we'll go over how to use Report Titles and Report Fields. - -## How to use Report Titles - -Default report titles enable group workspace admins or individual workspace users to establish a standardized title format for reports associated with a specific workspace. Additionally, admins have the choice to enforce these report titles, preventing employees from altering them. This ensures uniformity in report naming conventions during the review process, eliminating the need for employees to manually input titles for each report they generate. - -- Group workspace admins can set the Default Report Title from **Settings > Workspaces > Group > *[Workspace Name]* > Reports**. -- Individual users can set the Default Report Title from **Settings > Workspaces > Individual > *[Workspace Name]* > Reports**. - -You can configure the title by using the formulas that we provide to populate the Report Title. Take a look at the help article on Custom Formulas to find all eligible formulas for your Report Titles. - -## Deep Dive on Report Titles - -Some formulas will automatically update the report title as changes are made to the report. For example, any formula related to dates, total amounts, workspace name, would adjust the title before the report is submitted for approval. Changes will not retroactively update report titles for reports which have been Approved or Reimbursed. - -To prevent report title editing by employees, simply enable "Enforce Default Report Title." - -## How to use Report Fields - -Report fields let you specify header-level details, distinct from tags which pertain to expenses on individual line items. These details can encompass specific project names, business trip information, locations, and more. Customize them according to your workspace's requirements. - -To set up Report Fields, follow these steps: -- Workspace Admins can create report fields for group workspaces from **Settings > Workspaces > Group > *[Workspace Name]* > Reports > Report and Invoice Fields**. For individual workspaces, follow **Settings > Workspaces > Individual > *[Workspace Name]* > Reports > Report and Invoice Fields**. -- Under "Add New Field," enter the desired field name in the "Field Title" to describe the type of information to be selected. -- Choose the appropriate input method under "Type": - - Text: Provides users with a free-text box to enter the requested information. - - Dropdown: Creates a selection of options for users to choose from. - - Date: Displays a clickable box that opens a calendar for users to select a date. - -## Deep Dive on Report Fields - -You cannot create these report fields directly in Expensify if you are connected to an accounting integration (QuickBooks Online, QuickBooks Desktop, Intacct, Xero, or NetSuite). Please refer to the relevant article for instructions on creating fields within that system. - -When report fields are configured on a workspace, they become mandatory information for associated reports. Leaving a report field empty or unselected will trigger a report violation, potentially blocking report submission or export. - -Report fields are "sticky," which means that any changes made by an employee will persist and be reflected in subsequent reports they create. - diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/reports/Scheduled-Submit.md b/docs/articles/expensify-classic/policy-and-domain-settings/reports/Scheduled-Submit.md deleted file mode 100644 index c05df92bbbff..000000000000 --- a/docs/articles/expensify-classic/policy-and-domain-settings/reports/Scheduled-Submit.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: Scheduled Submit -description: How to use the Scheduled Submit feature ---- -# Overview - -Scheduled Submit reduces the delay between the time an employee creates an expense to when it is submitted to the admin. This gives admins significantly faster visibility into employee spend. Without Scheduled Submit enabled, expenses can be left Unreported giving workspace admins no visibility into employee spend. - -The biggest delay in expense management is the time it takes for an employee to actually submit the expense after it is incurred. Scheduled Submit allows you to automatically collect employee expenses on a schedule of your choosing without delaying the process while you wait for employees to submit them. - -It works like this: Employee expenses are automatically gathered onto a report. If there is not an existing report, a new one will be created. This report is submitted automatically at the cadence you choose (daily, weekly, monthly, twice month, by trip). - -# How to enable Scheduled Submit - -**For workspace admins**: To enable Scheduled Submit on your group workspace, follow **Settings > Workspaces > Group > *[Workspace Name]* > Reports > Scheduled Submit**. From there, toggle Scheduled Submit to Enabled. Then, choose your desired frequency from the dropdown menu. -For individuals or employees: To enable Scheduled Submit on your individual workspace, follow **Settings > Workspaces > Individual > *[Workspace Name]* > Reports > Scheduled Submit**. From there, toggle Scheduled Submit to Enabled. Then, choose your desired frequency from the dropdown menu. - -## Scheduled Submit frequency options - -**Daily**: Each night, expenses without violations will be submitted. Expenses with violations will remain on an open report until the violations are corrected, after which they will be submitted in the evening (PDT). - -**Weekly**: Expenses that are free of violations will be submitted on a weekly basis. However, expenses with violations will be held in a new open report and combined with any new expenses. They will then be submitted at the end of the following weekly cycle, specifically on Sunday evening (PDT). - -**Twice a month**: Expenses that are violation-free will be submitted on both the 15th and the last day of each month, in the evening (PDT). Expenses with violations will not be submitted, but moved on to a new open report so the employee can resolve the violations and then will be submitted at the conclusion of the next cycle. - -**Monthly**: Expenses that are free from violations will be submitted on a monthly basis. Expenses with violations will be held back and moved to a new Open report so the violations can be resolved, and they will be submitted on the evening (PDT) of the specified date. - -**By trip**: Expenses are grouped by trip. This is calculated by grouping all expenses together that occur in a similar time frame. If two full days pass without any new expenses being created, the trip report will be submitted on the evening of the second day. Any expenses generated after this submission will initiate a new trip report. Please note that the "2-day" period refers to a date-based interval, not a 48-hour time frame. - -**Manually**: An open report will be created, and expenses will be added to it automatically. However, it's important to note that the report will not be submitted automatically; manual submission of reports will be required.This is a great option for automatically gathering all an employee’s expenses on a report for the employee’s convenience, but they will still need to review and submit the report. - -# Deep Dive - -## Schedule Submit Override -If Scheduled Submit is disabled at the group workspace level or configured the frequency as "Manually," the individual workspace settings of a user will take precedence and be applied. This means an employee can still set up Scheduled Submit for themselves even if the admin has not enabled it. We highly recommend Scheduled Submit as it helps put your expenses on auto-pilot! - -## Personal Card Transactions -Personal card transactions are handled differently compared to other expenses. If a user has added a card through Settings > Account > Credit Card Import, they need to make sure it is set as non-reimbursable and transactions must be automatically merged with a SmartScanned receipt. If transactions are set to come in as reimbursable or they aren’t merged with a SmartScanned receipt, Scheduled Submit settings will not apply. diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/tax-tracking.md b/docs/articles/expensify-classic/policy-and-domain-settings/tax-tracking.md deleted file mode 100644 index 7b859c5101b1..000000000000 --- a/docs/articles/expensify-classic/policy-and-domain-settings/tax-tracking.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: Tax -description: How to track expense taxes ---- -# Overview -Expensify’s tax tracking feature allows you to: -- Add tax names, rates, and codes whether you’re connected to an accounting system or not. -- Enable/disable taxes you’d like to make available to users. -- Set a default tax for Workspace currency expenses and, optionally, another default tax (including exempt) for foreign currency expenses which - will automatically apply to all new expenses. - -# How to Enable Tax Tracking -Tax tracking can be enabled in the Tax section of the Workspace settings of any Workspace, whether group or individual. -## If Connected to an Accounting Integration -If your group Workspace is connected to Xero, QuickBooks Online, Sage Intacct, or NetSuite, make sure to first enable tax via the connection configuration page (Settings > Policies > Group > [Workspace Name] > Connections > Configure) and then sync the connection. Your tax rates will be imported from the accounting system and indicated by its logo. -## Not Connected to an Accounting Integration -If your Workspace is not connected to an accounting system, go to Settings > Policies > Group > [Workspace Name] > Tax to enable tax. - -# Tracking Tax by Expense Category -To set a different tax rate for a specific expense type in the Workspace currency, go to Settings > Workspaces > Group > [Workspace Name] > Categories page. Click "Edit Rules" next to the desired category and set the "Category default tax". This will be applied to new expenses, overriding the default Workspace currency tax rate. diff --git a/docs/articles/new-expensify/billing-and-plan-types/The-Free-Plan.md b/docs/articles/new-expensify/billing-and-plan-types/The-Free-Plan.md index e157ede1969d..0a8d6b3493e0 100644 --- a/docs/articles/new-expensify/billing-and-plan-types/The-Free-Plan.md +++ b/docs/articles/new-expensify/billing-and-plan-types/The-Free-Plan.md @@ -1,7 +1,6 @@ --- title: The Free Plan description: Everything you need to know about Expensify's Free Plan! -redirect_from: articles/split-bills/workspaces/The-Free-Plan/ --- diff --git a/docs/articles/new-expensify/get-paid-back/Request-Money.md b/docs/articles/new-expensify/get-paid-back/Request-Money.md index a2b765915af0..dc6de6656cc9 100644 --- a/docs/articles/new-expensify/get-paid-back/Request-Money.md +++ b/docs/articles/new-expensify/get-paid-back/Request-Money.md @@ -1,6 +1,5 @@ --- title: Request Money description: Request Money -redirect_from: articles/request-money/Request-and-Split-Bills/ --- ## Resource Coming Soon! diff --git a/docs/articles/new-expensify/getting-started/Expensify-Lounge.md b/docs/articles/new-expensify/getting-started/Expensify-Lounge.md index bdccbe927769..01a2d7a9e250 100644 --- a/docs/articles/new-expensify/getting-started/Expensify-Lounge.md +++ b/docs/articles/new-expensify/getting-started/Expensify-Lounge.md @@ -1,7 +1,6 @@ --- title: Welcome to the Expensify Lounge! description: How to get the most out of the Expensify Lounge. -redirect_from: articles/other/Expensify-Lounge/ --- diff --git a/docs/articles/new-expensify/getting-started/chat/Everything-About-Chat.md b/docs/articles/new-expensify/getting-started/chat/Everything-About-Chat.md index 77bbe54e8e2c..9f73d1c759c2 100644 --- a/docs/articles/new-expensify/getting-started/chat/Everything-About-Chat.md +++ b/docs/articles/new-expensify/getting-started/chat/Everything-About-Chat.md @@ -1,7 +1,6 @@ --- title: Everything About Chat description: Everything you need to know about Expensify's Chat Features! -redirect_from: articles/other/Everything-About-Chat/ --- diff --git a/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Admins.md b/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Admins.md index 996d7896502f..31de150d5b5e 100644 --- a/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Admins.md +++ b/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Admins.md @@ -1,7 +1,6 @@ --- title: Expensify Chat for Admins description: Best Practices for Admins settings up Expensify Chat -redirect_from: articles/other/Expensify-Chat-For-Admins/ --- ## Overview diff --git a/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Conference-Attendees.md b/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Conference-Attendees.md index 20e15aaa6c72..3d30237dca5a 100644 --- a/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Conference-Attendees.md +++ b/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Conference-Attendees.md @@ -1,7 +1,6 @@ --- title: Expensify Chat for Conference Attendees description: Best Practices for Conference Attendees -redirect_from: articles/other/Expensify-Chat-For-Conference-Attendees/ --- ## Overview diff --git a/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Conference-Speakers.md b/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Conference-Speakers.md index 3e19cf6fe26a..5bd52425d92b 100644 --- a/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Conference-Speakers.md +++ b/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Conference-Speakers.md @@ -1,7 +1,6 @@ --- title: Expensify Chat for Conference Speakers description: Best Practices for Conference Speakers -redirect_from: articles/other/Expensify-Chat-For-Conference-Speakers/ --- ## Overview diff --git a/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-Playbook-For-Conferences.md b/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-Playbook-For-Conferences.md index a81aef2044a2..8f806bb03146 100644 --- a/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-Playbook-For-Conferences.md +++ b/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-Playbook-For-Conferences.md @@ -1,7 +1,6 @@ --- title: Expensify Chat Playbook for Conferences description: Best practices for how to deploy Expensify Chat for your conference -redirect_from: articles/playbooks/Expensify-Chat-Playbook-for-Conferences/ --- ## Overview To help make setting up Expensify Chat for your event and your attendees super simple, we’ve created a guide for all of the technical setup details. diff --git a/docs/assets/images/add-australian-deposit-only-account-modal.png b/docs/assets/images/add-australian-deposit-only-account-modal.png deleted file mode 100644 index 1196a57c8f8f..000000000000 Binary files a/docs/assets/images/add-australian-deposit-only-account-modal.png and /dev/null differ diff --git a/docs/assets/images/add-australian-deposit-only-account.png b/docs/assets/images/add-australian-deposit-only-account.png deleted file mode 100644 index 4cea4fb11757..000000000000 Binary files a/docs/assets/images/add-australian-deposit-only-account.png and /dev/null differ diff --git a/docs/assets/images/add-vba-australian-account-modal.png b/docs/assets/images/add-vba-australian-account-modal.png deleted file mode 100644 index ee624eca3814..000000000000 Binary files a/docs/assets/images/add-vba-australian-account-modal.png and /dev/null differ diff --git a/docs/assets/images/add-vba-australian-account.png b/docs/assets/images/add-vba-australian-account.png deleted file mode 100644 index f064225e176a..000000000000 Binary files a/docs/assets/images/add-vba-australian-account.png and /dev/null differ diff --git a/docs/assets/images/delete-australian-bank-account.png b/docs/assets/images/delete-australian-bank-account.png deleted file mode 100644 index 2148973e5a6c..000000000000 Binary files a/docs/assets/images/delete-australian-bank-account.png and /dev/null differ diff --git a/docs/expensify-classic/hubs/policy-and-domain-settings/reports.html b/docs/expensify-classic/hubs/policy-and-domain-settings/reports.html deleted file mode 100644 index 86641ee60b7d..000000000000 --- a/docs/expensify-classic/hubs/policy-and-domain-settings/reports.html +++ /dev/null @@ -1,5 +0,0 @@ ---- -layout: default ---- - -{% include section.html %} diff --git a/fastlane/Fastfile b/fastlane/Fastfile index dac53193fdc6..c7d0f2f4f0f5 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -17,7 +17,7 @@ platform :android do desc "Generate a new local APK for e2e testing" lane :build_e2e do ENV["ENVFILE"]="tests/e2e/.env.e2e" - ENV["ENTRY_FILE"]="src/libs/E2E/reactNativeLaunchingTest.js" + ENV["ENTRY_FILE"]="#{Dir.pwd}/../src/libs/E2E/reactNativeLaunchingTest.js" ENV["E2E_TESTING"]="true" gradle( diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 08ed29025adf..f41740a8bcb2 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.3.77 + 1.3.74 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 1.3.77.5 + 1.3.74.2 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index d5f54b72d910..95714ea2cc9f 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.3.77 + 1.3.74 CFBundleSignature ???? CFBundleVersion - 1.3.77.5 + 1.3.74.2 diff --git a/package-lock.json b/package-lock.json index 795dc0e8fc26..51c5f4f24b32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.3.77-5", + "version": "1.3.74-2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.3.77-5", + "version": "1.3.74-2", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -50,7 +50,7 @@ "date-fns-tz": "^2.0.0", "dom-serializer": "^0.2.2", "domhandler": "^4.3.0", - "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#ab4895807dd9a26f64bfaee80db15ee2c48a5124", + "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#1609f1848cc0c2528064519c3ea48b4953a708ee", "fbjs": "^3.0.2", "htmlparser2": "^7.2.0", "idb-keyval": "^6.2.1", @@ -66,7 +66,6 @@ "prop-types": "^15.7.2", "pusher-js": "8.3.0", "react": "18.2.0", - "react-beautiful-dnd": "^13.1.1", "react-collapse": "^5.1.0", "react-content-loader": "^6.1.0", "react-dom": "18.1.0", @@ -79,20 +78,19 @@ "react-native-dev-menu": "^4.1.1", "react-native-device-info": "^10.3.0", "react-native-document-picker": "^8.0.0", - "react-native-draggable-flatlist": "^4.0.1", "react-native-fast-image": "^8.6.3", "react-native-fs": "^2.20.0", "react-native-gesture-handler": "2.12.0", - "react-native-google-places-autocomplete": "git+https://github.com/Expensify/react-native-google-places-autocomplete.git#c8c2a873335df19081056a5667f5c109583882e1", + "react-native-google-places-autocomplete": "git+https://github.com/Expensify/react-native-google-places-autocomplete.git#cef3ac29d9501091453136e1219e24c4ec9f9d76", "react-native-haptic-feedback": "^1.13.0", "react-native-image-pan-zoom": "^2.1.12", "react-native-image-picker": "^5.1.0", "react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#8393b7e58df6ff65fd41f60aee8ece8822c91e2b", - "react-native-key-command": "^1.0.5", + "react-native-key-command": "^1.0.1", "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "1.0.98", + "react-native-onyx": "1.0.87", "react-native-pager-view": "^6.2.0", "react-native-pdf": "^6.7.1", "react-native-performance": "^5.1.0", @@ -163,7 +161,6 @@ "@types/mock-fs": "^4.13.1", "@types/pusher-js": "^5.1.0", "@types/react": "^18.2.12", - "@types/react-beautiful-dnd": "^13.1.4", "@types/react-collapse": "^5.0.1", "@types/react-dom": "^18.2.4", "@types/react-pdf": "^5.7.2", @@ -18520,15 +18517,6 @@ "@types/unist": "*" } }, - "node_modules/@types/hoist-non-react-statics": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", - "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", - "dependencies": { - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0" - } - }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", "dev": true, @@ -18800,15 +18788,6 @@ "csstype": "^3.0.2" } }, - "node_modules/@types/react-beautiful-dnd": { - "version": "13.1.4", - "resolved": "https://registry.npmjs.org/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.1.4.tgz", - "integrity": "sha512-4bIBdzOr0aavN+88q3C7Pgz+xkb7tz3whORYrmSj77wfVEMfiWiooIwVWFR7KM2e+uGTe5BVrXqSfb0aHeflJA==", - "dev": true, - "dependencies": { - "@types/react": "*" - } - }, "node_modules/@types/react-collapse": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@types/react-collapse/-/react-collapse-5.0.1.tgz", @@ -18843,17 +18822,6 @@ "pdfjs-dist": "^2.10.377" } }, - "node_modules/@types/react-redux": { - "version": "7.1.26", - "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.26.tgz", - "integrity": "sha512-UKPo7Cm7rswYU6PH6CmTNCRv5NYF3HrgKuHEYTK8g/3czYLrUux50gQ2pkxc9c7ZpQZi+PNhgmI8oNIRoiVIxg==", - "dependencies": { - "@types/hoist-non-react-statics": "^3.3.0", - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0", - "redux": "^4.0.0" - } - }, "node_modules/@types/react-test-renderer": { "version": "18.0.0", "resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-18.0.0.tgz", @@ -24538,14 +24506,6 @@ "version": "3.3.0", "license": "MIT" }, - "node_modules/css-box-model": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", - "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", - "dependencies": { - "tiny-invariant": "^1.0.6" - } - }, "node_modules/css-color-keywords": { "version": "1.0.0", "license": "ISC", @@ -28147,8 +28107,8 @@ }, "node_modules/expensify-common": { "version": "1.0.0", - "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#ab4895807dd9a26f64bfaee80db15ee2c48a5124", - "integrity": "sha512-F5WjTSz/UwnYE99NcdAFiDLmNFCq32S8sZXF9Ekf4hXzGMnPFsAsxA2UBYwE8SiFqgF86hceYt9qrjv1yCxLPw==", + "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#1609f1848cc0c2528064519c3ea48b4953a708ee", + "integrity": "sha512-sUd/ky6xCB/mShVaD2nVkedGL2xy+h6Jf5MfX9GOiYX8wB2D8uZSpqswz515uwcp8RDWrA5wxM2cR6pBXNfgxw==", "license": "MIT", "dependencies": { "classnames": "2.3.1", @@ -40648,11 +40608,6 @@ "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==" }, - "node_modules/raf-schd": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", - "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==" - }, "node_modules/ramda": { "version": "0.29.0", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.29.0.tgz", @@ -40755,24 +40710,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-beautiful-dnd": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz", - "integrity": "sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==", - "dependencies": { - "@babel/runtime": "^7.9.2", - "css-box-model": "^1.2.0", - "memoize-one": "^5.1.1", - "raf-schd": "^4.0.2", - "react-redux": "^7.2.0", - "redux": "^4.0.4", - "use-memo-one": "^1.1.1" - }, - "peerDependencies": { - "react": "^16.8.5 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.5 || ^17.0.0 || ^18.0.0" - } - }, "node_modules/react-collapse": { "version": "5.1.1", "license": "MIT", @@ -41084,19 +41021,6 @@ } } }, - "node_modules/react-native-draggable-flatlist": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/react-native-draggable-flatlist/-/react-native-draggable-flatlist-4.0.1.tgz", - "integrity": "sha512-ZO1QUTNx64KZfXGXeXcBfql67l38X7kBcJ3rxUVZzPHt5r035GnGzIC0F8rqSXp6zgnwgUYMfB6zQc5PKmPL9Q==", - "dependencies": { - "@babel/preset-typescript": "^7.17.12" - }, - "peerDependencies": { - "react-native": ">=0.64.0", - "react-native-gesture-handler": ">=2.0.0", - "react-native-reanimated": ">=2.8.0" - } - }, "node_modules/react-native-fast-image": { "version": "8.6.3", "license": "(MIT AND Apache-2.0)", @@ -41152,8 +41076,8 @@ }, "node_modules/react-native-google-places-autocomplete": { "version": "2.5.1", - "resolved": "git+ssh://git@github.com/Expensify/react-native-google-places-autocomplete.git#c8c2a873335df19081056a5667f5c109583882e1", - "integrity": "sha512-jYQJlI5Pp/UI4k4Xy9fqnE0x4BC+O6c5Fh7I+7SjtaywA5KpZqQcYApx2e9YcH/igJ4Rdp/n4awKPX+vE5vFcg==", + "resolved": "git+ssh://git@github.com/Expensify/react-native-google-places-autocomplete.git#cef3ac29d9501091453136e1219e24c4ec9f9d76", + "integrity": "sha512-2z3ED8jOXasPTzBqvPwpG10LQsBArTRsYszmoz+TfqbgZrSBmP3c8rhaC//lx6Pvfs2r+KYWqJUrLf4mbCrjZw==", "license": "MIT", "dependencies": { "lodash.debounce": "^4.0.8", @@ -41204,25 +41128,19 @@ "license": "MIT" }, "node_modules/react-native-key-command": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/react-native-key-command/-/react-native-key-command-1.0.5.tgz", - "integrity": "sha512-SJWf1e8f3yGFrFDNCmJ+aiGmnwokGgtMicfvuyukhQtXkncCQb9pBI4uhBen0Bd30uMmUDgGAA9O56OyIdf5jw==", + "version": "1.0.1", + "license": "MIT", "dependencies": { - "eventemitter3": "^5.0.1", + "events": "^3.3.0", "underscore": "^1.13.4" }, "peerDependencies": { "react": "^18.1.0", "react-dom": "18.1.0", "react-native": "^0.70.4", - "react-native-web": "^0.19.7" + "react-native-web": "^0.18.1" } }, - "node_modules/react-native-key-command/node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" - }, "node_modules/react-native-linear-gradient": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/react-native-linear-gradient/-/react-native-linear-gradient-2.8.1.tgz", @@ -41263,9 +41181,9 @@ } }, "node_modules/react-native-onyx": { - "version": "1.0.98", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.98.tgz", - "integrity": "sha512-2wJNmZVBJs2Y0p1G/es4tQZnplJR8rOyVbHv9KZaq/SXluLUnIovttf1MMhVXidDLT+gcE+u20Mck/Gpb8bY0w==", + "version": "1.0.87", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.87.tgz", + "integrity": "sha512-6mIhobSwpClDDGnJm9XEdjnpEdWfFesJ18J8Ifsb4tL6AVi+uxos5bnlZcOoMbtlUk3UozrgSyTjMfFrkD/aZA==", "dependencies": { "ascii-table": "0.0.9", "fast-equals": "^4.0.3", @@ -41550,23 +41468,21 @@ } }, "node_modules/react-native-web": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.19.9.tgz", - "integrity": "sha512-m69arZbS6FV+BNSKE6R/NQwUX+CzxCkYM7AJlSLlS8dz3BDzlaxG8Bzqtzv/r3r1YFowhnZLBXVKIwovKDw49g==", + "version": "0.18.12", + "license": "MIT", "peer": true, "dependencies": { "@babel/runtime": "^7.18.6", - "@react-native/normalize-color": "^2.1.0", + "create-react-class": "^15.7.0", "fbjs": "^3.0.4", "inline-style-prefixer": "^6.0.1", - "memoize-one": "^6.0.0", - "nullthrows": "^1.1.1", + "normalize-css-color": "^1.0.2", "postcss-value-parser": "^4.2.0", - "styleq": "^0.1.3" + "styleq": "^0.1.2" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^17.0.2 || ^18.0.0", + "react-dom": "^17.0.2 || ^18.0.0" } }, "node_modules/react-native-web-linear-gradient": { @@ -41587,12 +41503,6 @@ "react-native-web": "*" } }, - "node_modules/react-native-web/node_modules/memoize-one": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", - "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", - "peer": true - }, "node_modules/react-native-webview": { "version": "11.23.0", "license": "MIT", @@ -41809,35 +41719,6 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, - "node_modules/react-redux": { - "version": "7.2.9", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", - "integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==", - "dependencies": { - "@babel/runtime": "^7.15.4", - "@types/react-redux": "^7.1.20", - "hoist-non-react-statics": "^3.3.2", - "loose-envify": "^1.4.0", - "prop-types": "^15.7.2", - "react-is": "^17.0.2" - }, - "peerDependencies": { - "react": "^16.8.3 || ^17 || ^18" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - } - } - }, - "node_modules/react-redux/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" - }, "node_modules/react-refresh": { "version": "0.11.0", "dev": true, @@ -45279,9 +45160,8 @@ } }, "node_modules/styleq": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/styleq/-/styleq-0.1.3.tgz", - "integrity": "sha512-3ZUifmCDCQanjeej1f6kyl/BeP/Vae5EYkQ9iJfUm/QwZvlgnZzyflqAsAWYURdtea8Vkvswu2GrC57h3qffcA==" + "version": "0.1.2", + "license": "MIT" }, "node_modules/sudo-prompt": { "version": "9.2.1", @@ -46906,14 +46786,6 @@ "react": ">=16.8" } }, - "node_modules/use-memo-one": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz", - "integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, "node_modules/use-resize-observer": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/use-resize-observer/-/use-resize-observer-9.1.0.tgz", @@ -61606,15 +61478,6 @@ "@types/unist": "*" } }, - "@types/hoist-non-react-statics": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", - "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", - "requires": { - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0" - } - }, "@types/html-minifier-terser": { "version": "6.1.0", "dev": true @@ -61853,15 +61716,6 @@ "csstype": "^3.0.2" } }, - "@types/react-beautiful-dnd": { - "version": "13.1.4", - "resolved": "https://registry.npmjs.org/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.1.4.tgz", - "integrity": "sha512-4bIBdzOr0aavN+88q3C7Pgz+xkb7tz3whORYrmSj77wfVEMfiWiooIwVWFR7KM2e+uGTe5BVrXqSfb0aHeflJA==", - "dev": true, - "requires": { - "@types/react": "*" - } - }, "@types/react-collapse": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@types/react-collapse/-/react-collapse-5.0.1.tgz", @@ -61894,17 +61748,6 @@ "pdfjs-dist": "^2.10.377" } }, - "@types/react-redux": { - "version": "7.1.26", - "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.26.tgz", - "integrity": "sha512-UKPo7Cm7rswYU6PH6CmTNCRv5NYF3HrgKuHEYTK8g/3czYLrUux50gQ2pkxc9c7ZpQZi+PNhgmI8oNIRoiVIxg==", - "requires": { - "@types/hoist-non-react-statics": "^3.3.0", - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0", - "redux": "^4.0.0" - } - }, "@types/react-test-renderer": { "version": "18.0.0", "resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-18.0.0.tgz", @@ -65905,14 +65748,6 @@ "crypto-js": { "version": "3.3.0" }, - "css-box-model": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", - "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", - "requires": { - "tiny-invariant": "^1.0.6" - } - }, "css-color-keywords": { "version": "1.0.0" }, @@ -68400,9 +68235,9 @@ } }, "expensify-common": { - "version": "git+ssh://git@github.com/Expensify/expensify-common.git#ab4895807dd9a26f64bfaee80db15ee2c48a5124", - "integrity": "sha512-F5WjTSz/UwnYE99NcdAFiDLmNFCq32S8sZXF9Ekf4hXzGMnPFsAsxA2UBYwE8SiFqgF86hceYt9qrjv1yCxLPw==", - "from": "expensify-common@git+ssh://git@github.com/Expensify/expensify-common.git#ab4895807dd9a26f64bfaee80db15ee2c48a5124", + "version": "git+ssh://git@github.com/Expensify/expensify-common.git#1609f1848cc0c2528064519c3ea48b4953a708ee", + "integrity": "sha512-sUd/ky6xCB/mShVaD2nVkedGL2xy+h6Jf5MfX9GOiYX8wB2D8uZSpqswz515uwcp8RDWrA5wxM2cR6pBXNfgxw==", + "from": "expensify-common@git+ssh://git@github.com/Expensify/expensify-common.git#1609f1848cc0c2528064519c3ea48b4953a708ee", "requires": { "classnames": "2.3.1", "clipboard": "2.0.4", @@ -76923,11 +76758,6 @@ "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==" }, - "raf-schd": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", - "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==" - }, "ramda": { "version": "0.29.0", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.29.0.tgz", @@ -76995,20 +76825,6 @@ "loose-envify": "^1.1.0" } }, - "react-beautiful-dnd": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz", - "integrity": "sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==", - "requires": { - "@babel/runtime": "^7.9.2", - "css-box-model": "^1.2.0", - "memoize-one": "^5.1.1", - "raf-schd": "^4.0.2", - "react-redux": "^7.2.0", - "redux": "^4.0.4", - "use-memo-one": "^1.1.1" - } - }, "react-collapse": { "version": "5.1.1", "requires": {} @@ -77326,14 +77142,6 @@ "invariant": "^2.2.4" } }, - "react-native-draggable-flatlist": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/react-native-draggable-flatlist/-/react-native-draggable-flatlist-4.0.1.tgz", - "integrity": "sha512-ZO1QUTNx64KZfXGXeXcBfql67l38X7kBcJ3rxUVZzPHt5r035GnGzIC0F8rqSXp6zgnwgUYMfB6zQc5PKmPL9Q==", - "requires": { - "@babel/preset-typescript": "^7.17.12" - } - }, "react-native-fast-image": { "version": "8.6.3", "requires": {} @@ -77366,9 +77174,9 @@ } }, "react-native-google-places-autocomplete": { - "version": "git+ssh://git@github.com/Expensify/react-native-google-places-autocomplete.git#c8c2a873335df19081056a5667f5c109583882e1", - "integrity": "sha512-jYQJlI5Pp/UI4k4Xy9fqnE0x4BC+O6c5Fh7I+7SjtaywA5KpZqQcYApx2e9YcH/igJ4Rdp/n4awKPX+vE5vFcg==", - "from": "react-native-google-places-autocomplete@git+https://github.com/Expensify/react-native-google-places-autocomplete.git#c8c2a873335df19081056a5667f5c109583882e1", + "version": "git+ssh://git@github.com/Expensify/react-native-google-places-autocomplete.git#cef3ac29d9501091453136e1219e24c4ec9f9d76", + "integrity": "sha512-2z3ED8jOXasPTzBqvPwpG10LQsBArTRsYszmoz+TfqbgZrSBmP3c8rhaC//lx6Pvfs2r+KYWqJUrLf4mbCrjZw==", + "from": "react-native-google-places-autocomplete@git+https://github.com/Expensify/react-native-google-places-autocomplete.git#cef3ac29d9501091453136e1219e24c4ec9f9d76", "requires": { "lodash.debounce": "^4.0.8", "prop-types": "^15.7.2", @@ -77398,19 +77206,10 @@ "from": "react-native-image-size@git+https://github.com/Expensify/react-native-image-size#8393b7e58df6ff65fd41f60aee8ece8822c91e2b" }, "react-native-key-command": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/react-native-key-command/-/react-native-key-command-1.0.5.tgz", - "integrity": "sha512-SJWf1e8f3yGFrFDNCmJ+aiGmnwokGgtMicfvuyukhQtXkncCQb9pBI4uhBen0Bd30uMmUDgGAA9O56OyIdf5jw==", + "version": "1.0.1", "requires": { - "eventemitter3": "^5.0.1", + "events": "^3.3.0", "underscore": "^1.13.4" - }, - "dependencies": { - "eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" - } } }, "react-native-linear-gradient": { @@ -77431,9 +77230,9 @@ } }, "react-native-onyx": { - "version": "1.0.98", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.98.tgz", - "integrity": "sha512-2wJNmZVBJs2Y0p1G/es4tQZnplJR8rOyVbHv9KZaq/SXluLUnIovttf1MMhVXidDLT+gcE+u20Mck/Gpb8bY0w==", + "version": "1.0.87", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-1.0.87.tgz", + "integrity": "sha512-6mIhobSwpClDDGnJm9XEdjnpEdWfFesJ18J8Ifsb4tL6AVi+uxos5bnlZcOoMbtlUk3UozrgSyTjMfFrkD/aZA==", "requires": { "ascii-table": "0.0.9", "fast-equals": "^4.0.3", @@ -77601,27 +77400,16 @@ "requires": {} }, "react-native-web": { - "version": "0.19.9", - "resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.19.9.tgz", - "integrity": "sha512-m69arZbS6FV+BNSKE6R/NQwUX+CzxCkYM7AJlSLlS8dz3BDzlaxG8Bzqtzv/r3r1YFowhnZLBXVKIwovKDw49g==", + "version": "0.18.12", "peer": true, "requires": { "@babel/runtime": "^7.18.6", - "@react-native/normalize-color": "^2.1.0", + "create-react-class": "^15.7.0", "fbjs": "^3.0.4", "inline-style-prefixer": "^6.0.1", - "memoize-one": "^6.0.0", - "nullthrows": "^1.1.1", + "normalize-css-color": "^1.0.2", "postcss-value-parser": "^4.2.0", - "styleq": "^0.1.3" - }, - "dependencies": { - "memoize-one": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", - "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", - "peer": true - } + "styleq": "^0.1.2" } }, "react-native-web-linear-gradient": { @@ -77671,26 +77459,6 @@ "react-script-hook": "^1.6.0" } }, - "react-redux": { - "version": "7.2.9", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", - "integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==", - "requires": { - "@babel/runtime": "^7.15.4", - "@types/react-redux": "^7.1.20", - "hoist-non-react-statics": "^3.3.2", - "loose-envify": "^1.4.0", - "prop-types": "^15.7.2", - "react-is": "^17.0.2" - }, - "dependencies": { - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" - } - } - }, "react-refresh": { "version": "0.11.0", "dev": true @@ -80098,9 +79866,7 @@ } }, "styleq": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/styleq/-/styleq-0.1.3.tgz", - "integrity": "sha512-3ZUifmCDCQanjeej1f6kyl/BeP/Vae5EYkQ9iJfUm/QwZvlgnZzyflqAsAWYURdtea8Vkvswu2GrC57h3qffcA==" + "version": "0.1.2" }, "sudo-prompt": { "version": "9.2.1", @@ -81179,12 +80945,6 @@ "integrity": "sha512-VO/P91A/PmKH9bcN9a7O3duSuxe6M14ZoYXgA6a8dab8doWNdhiIHzEkX/jFeTTRBsX0Ubk6nG4q2NIjNsj+bg==", "requires": {} }, - "use-memo-one": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz", - "integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==", - "requires": {} - }, "use-resize-observer": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/use-resize-observer/-/use-resize-observer-9.1.0.tgz", diff --git a/package.json b/package.json index 47679bfcf359..e55e9c785379 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.3.77-5", + "version": "1.3.74-2", "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.", @@ -93,7 +93,7 @@ "date-fns-tz": "^2.0.0", "dom-serializer": "^0.2.2", "domhandler": "^4.3.0", - "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#ab4895807dd9a26f64bfaee80db15ee2c48a5124", + "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#1609f1848cc0c2528064519c3ea48b4953a708ee", "fbjs": "^3.0.2", "htmlparser2": "^7.2.0", "idb-keyval": "^6.2.1", @@ -109,7 +109,6 @@ "prop-types": "^15.7.2", "pusher-js": "8.3.0", "react": "18.2.0", - "react-beautiful-dnd": "^13.1.1", "react-collapse": "^5.1.0", "react-content-loader": "^6.1.0", "react-dom": "18.1.0", @@ -122,20 +121,19 @@ "react-native-dev-menu": "^4.1.1", "react-native-device-info": "^10.3.0", "react-native-document-picker": "^8.0.0", - "react-native-draggable-flatlist": "^4.0.1", "react-native-fast-image": "^8.6.3", "react-native-fs": "^2.20.0", "react-native-gesture-handler": "2.12.0", - "react-native-google-places-autocomplete": "git+https://github.com/Expensify/react-native-google-places-autocomplete.git#c8c2a873335df19081056a5667f5c109583882e1", + "react-native-google-places-autocomplete": "git+https://github.com/Expensify/react-native-google-places-autocomplete.git#cef3ac29d9501091453136e1219e24c4ec9f9d76", "react-native-haptic-feedback": "^1.13.0", "react-native-image-pan-zoom": "^2.1.12", "react-native-image-picker": "^5.1.0", "react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#8393b7e58df6ff65fd41f60aee8ece8822c91e2b", - "react-native-key-command": "^1.0.5", + "react-native-key-command": "^1.0.1", "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "1.0.98", + "react-native-onyx": "1.0.87", "react-native-pager-view": "^6.2.0", "react-native-pdf": "^6.7.1", "react-native-performance": "^5.1.0", @@ -206,7 +204,6 @@ "@types/mock-fs": "^4.13.1", "@types/pusher-js": "^5.1.0", "@types/react": "^18.2.12", - "@types/react-beautiful-dnd": "^13.1.4", "@types/react-collapse": "^5.0.1", "@types/react-dom": "^18.2.4", "@types/react-pdf": "^5.7.2", diff --git a/patches/react-beautiful-dnd+13.1.1.patch b/patches/react-beautiful-dnd+13.1.1.patch deleted file mode 100644 index 577d54d42004..000000000000 --- a/patches/react-beautiful-dnd+13.1.1.patch +++ /dev/null @@ -1,259 +0,0 @@ -diff --git a/node_modules/react-beautiful-dnd/dist/react-beautiful-dnd.cjs.js b/node_modules/react-beautiful-dnd/dist/react-beautiful-dnd.cjs.js -index 12884e9..3a16e5e 100644 ---- a/node_modules/react-beautiful-dnd/dist/react-beautiful-dnd.cjs.js -+++ b/node_modules/react-beautiful-dnd/dist/react-beautiful-dnd.cjs.js -@@ -6227,8 +6227,10 @@ function useTouchSensor(api) { - x: clientX, - y: clientY - }; -+ var handle = api.findClosestDragHandle(event); -+ !handle ? process.env.NODE_ENV !== "production" ? invariant(false, 'Touch sensor unable to find drag handle') : invariant(false) : void 0; - unbindEventsRef.current(); -- startPendingDrag(actions, point); -+ startPendingDrag(actions, point, handle); - } - }; - }, [api]); -@@ -6268,7 +6270,7 @@ function useTouchSensor(api) { - phase.actions.abort(); - } - }, [stop]); -- var bindCapturingEvents = useMemoOne.useCallback(function bindCapturingEvents() { -+ var bindCapturingEvents = useMemoOne.useCallback(function bindCapturingEvents(target) { - var options = { - capture: true, - passive: false -@@ -6278,7 +6280,7 @@ function useTouchSensor(api) { - completed: stop, - getPhase: getPhase - }; -- var unbindTarget = bindEvents(window, getHandleBindings(args), options); -+ var unbindTarget = bindEvents(target, getHandleBindings(args), options); - var unbindWindow = bindEvents(window, getWindowBindings(args), options); - - unbindEventsRef.current = function unbindAll() { -@@ -6296,7 +6298,7 @@ function useTouchSensor(api) { - hasMoved: false - }); - }, [getPhase, setPhase]); -- var startPendingDrag = useMemoOne.useCallback(function startPendingDrag(actions, point) { -+ var startPendingDrag = useMemoOne.useCallback(function startPendingDrag(actions, point, target) { - !(getPhase().type === 'IDLE') ? process.env.NODE_ENV !== "production" ? invariant(false, 'Expected to move from IDLE to PENDING drag') : invariant(false) : void 0; - var longPressTimerId = setTimeout(startDragging, timeForLongPress); - setPhase({ -@@ -6305,7 +6307,7 @@ function useTouchSensor(api) { - actions: actions, - longPressTimerId: longPressTimerId - }); -- bindCapturingEvents(); -+ bindCapturingEvents(target); - }, [bindCapturingEvents, getPhase, setPhase, startDragging]); - useIsomorphicLayoutEffect(function mount() { - listenForCapture(); -@@ -6454,7 +6456,6 @@ function findClosestDragHandleFromEvent(contextId, event) { - - return handle; - } -- - function tryGetClosestDraggableIdFromEvent(contextId, event) { - var handle = findClosestDragHandleFromEvent(contextId, event); - -@@ -6786,6 +6787,9 @@ function useSensorMarshal(_ref4) { - sourceEvent: options && options.sourceEvent ? options.sourceEvent : null - }); - }, [contextId, lockAPI, registry, store]); -+ var findClosestDragHandle = useMemoOne.useCallback(function (event) { -+ return findClosestDragHandleFromEvent(contextId, event); -+ }, [contextId]); - var findClosestDraggableId = useMemoOne.useCallback(function (event) { - return tryGetClosestDraggableIdFromEvent(contextId, event); - }, [contextId]); -@@ -6810,11 +6814,12 @@ function useSensorMarshal(_ref4) { - canGetLock: canGetLock, - tryGetLock: tryGetLock, - findClosestDraggableId: findClosestDraggableId, -+ findClosestDragHandle: findClosestDragHandle, - findOptionsForDraggable: findOptionsForDraggable, - tryReleaseLock: tryReleaseLock, - isLockClaimed: isLockClaimed - }; -- }, [canGetLock, tryGetLock, findClosestDraggableId, findOptionsForDraggable, tryReleaseLock, isLockClaimed]); -+ }, [canGetLock, tryGetLock, findClosestDraggableId, findClosestDragHandle, findOptionsForDraggable, tryReleaseLock, isLockClaimed]); - useValidateSensorHooks(useSensors); - - for (var i = 0; i < useSensors.length; i++) { -diff --git a/node_modules/react-beautiful-dnd/dist/react-beautiful-dnd.esm.js b/node_modules/react-beautiful-dnd/dist/react-beautiful-dnd.esm.js -index ecced69..3233bf8 100644 ---- a/node_modules/react-beautiful-dnd/dist/react-beautiful-dnd.esm.js -+++ b/node_modules/react-beautiful-dnd/dist/react-beautiful-dnd.esm.js -@@ -6220,8 +6220,10 @@ function useTouchSensor(api) { - x: clientX, - y: clientY - }; -+ var handle = api.findClosestDragHandle(event); -+ !handle ? process.env.NODE_ENV !== "production" ? invariant(false, 'Touch sensor unable to find drag handle') : invariant(false) : void 0; - unbindEventsRef.current(); -- startPendingDrag(actions, point); -+ startPendingDrag(actions, point, handle); - } - }; - }, [api]); -@@ -6261,7 +6263,7 @@ function useTouchSensor(api) { - phase.actions.abort(); - } - }, [stop]); -- var bindCapturingEvents = useCallback(function bindCapturingEvents() { -+ var bindCapturingEvents = useCallback(function bindCapturingEvents(target) { - var options = { - capture: true, - passive: false -@@ -6271,7 +6273,7 @@ function useTouchSensor(api) { - completed: stop, - getPhase: getPhase - }; -- var unbindTarget = bindEvents(window, getHandleBindings(args), options); -+ var unbindTarget = bindEvents(target, getHandleBindings(args), options); - var unbindWindow = bindEvents(window, getWindowBindings(args), options); - - unbindEventsRef.current = function unbindAll() { -@@ -6289,7 +6291,7 @@ function useTouchSensor(api) { - hasMoved: false - }); - }, [getPhase, setPhase]); -- var startPendingDrag = useCallback(function startPendingDrag(actions, point) { -+ var startPendingDrag = useCallback(function startPendingDrag(actions, point, target) { - !(getPhase().type === 'IDLE') ? process.env.NODE_ENV !== "production" ? invariant(false, 'Expected to move from IDLE to PENDING drag') : invariant(false) : void 0; - var longPressTimerId = setTimeout(startDragging, timeForLongPress); - setPhase({ -@@ -6298,7 +6300,7 @@ function useTouchSensor(api) { - actions: actions, - longPressTimerId: longPressTimerId - }); -- bindCapturingEvents(); -+ bindCapturingEvents(target); - }, [bindCapturingEvents, getPhase, setPhase, startDragging]); - useIsomorphicLayoutEffect(function mount() { - listenForCapture(); -@@ -6447,7 +6449,6 @@ function findClosestDragHandleFromEvent(contextId, event) { - - return handle; - } -- - function tryGetClosestDraggableIdFromEvent(contextId, event) { - var handle = findClosestDragHandleFromEvent(contextId, event); - -@@ -6779,6 +6780,9 @@ function useSensorMarshal(_ref4) { - sourceEvent: options && options.sourceEvent ? options.sourceEvent : null - }); - }, [contextId, lockAPI, registry, store]); -+ var findClosestDragHandle = useCallback(function (event) { -+ return findClosestDragHandleFromEvent(contextId, event); -+ }, [contextId]); - var findClosestDraggableId = useCallback(function (event) { - return tryGetClosestDraggableIdFromEvent(contextId, event); - }, [contextId]); -@@ -6803,11 +6807,12 @@ function useSensorMarshal(_ref4) { - canGetLock: canGetLock, - tryGetLock: tryGetLock, - findClosestDraggableId: findClosestDraggableId, -+ findClosestDragHandle: findClosestDragHandle, - findOptionsForDraggable: findOptionsForDraggable, - tryReleaseLock: tryReleaseLock, - isLockClaimed: isLockClaimed - }; -- }, [canGetLock, tryGetLock, findClosestDraggableId, findOptionsForDraggable, tryReleaseLock, isLockClaimed]); -+ }, [canGetLock, tryGetLock, findClosestDraggableId, findClosestDragHandle, findOptionsForDraggable, tryReleaseLock, isLockClaimed]); - useValidateSensorHooks(useSensors); - - for (var i = 0; i < useSensors.length; i++) { -diff --git a/node_modules/react-beautiful-dnd/dist/react-beautiful-dnd.js b/node_modules/react-beautiful-dnd/dist/react-beautiful-dnd.js -index 84b63e9..c62471d 100644 ---- a/node_modules/react-beautiful-dnd/dist/react-beautiful-dnd.js -+++ b/node_modules/react-beautiful-dnd/dist/react-beautiful-dnd.js -@@ -9224,8 +9224,10 @@ - x: clientX, - y: clientY - }; -+ var handle = api.findClosestDragHandle(event); -+ !handle ? invariant(false, 'Touch sensor unable to find drag handle') : void 0; - unbindEventsRef.current(); -- startPendingDrag(actions, point); -+ startPendingDrag(actions, point, handle); - } - }; - }, [api]); -@@ -9265,7 +9267,7 @@ - phase.actions.abort(); - } - }, [stop]); -- var bindCapturingEvents = useCallback(function bindCapturingEvents() { -+ var bindCapturingEvents = useCallback(function bindCapturingEvents(target) { - var options = { - capture: true, - passive: false -@@ -9275,7 +9277,7 @@ - completed: stop, - getPhase: getPhase - }; -- var unbindTarget = bindEvents(window, getHandleBindings(args), options); -+ var unbindTarget = bindEvents(target, getHandleBindings(args), options); - var unbindWindow = bindEvents(window, getWindowBindings(args), options); - - unbindEventsRef.current = function unbindAll() { -@@ -9293,7 +9295,7 @@ - hasMoved: false - }); - }, [getPhase, setPhase]); -- var startPendingDrag = useCallback(function startPendingDrag(actions, point) { -+ var startPendingDrag = useCallback(function startPendingDrag(actions, point, target) { - !(getPhase().type === 'IDLE') ? invariant(false, 'Expected to move from IDLE to PENDING drag') : void 0; - var longPressTimerId = setTimeout(startDragging, timeForLongPress); - setPhase({ -@@ -9302,7 +9304,7 @@ - actions: actions, - longPressTimerId: longPressTimerId - }); -- bindCapturingEvents(); -+ bindCapturingEvents(target); - }, [bindCapturingEvents, getPhase, setPhase, startDragging]); - useIsomorphicLayoutEffect$1(function mount() { - listenForCapture(); -@@ -9451,7 +9453,6 @@ - - return handle; - } -- - function tryGetClosestDraggableIdFromEvent(contextId, event) { - var handle = findClosestDragHandleFromEvent(contextId, event); - -@@ -9783,6 +9784,9 @@ - sourceEvent: options && options.sourceEvent ? options.sourceEvent : null - }); - }, [contextId, lockAPI, registry, store]); -+ var findClosestDragHandle = useCallback(function (event) { -+ return findClosestDragHandleFromEvent(contextId, event); -+ }, [contextId]); - var findClosestDraggableId = useCallback(function (event) { - return tryGetClosestDraggableIdFromEvent(contextId, event); - }, [contextId]); -@@ -9807,11 +9811,12 @@ - canGetLock: canGetLock, - tryGetLock: tryGetLock, - findClosestDraggableId: findClosestDraggableId, -+ findClosestDragHandle: findClosestDragHandle, - findOptionsForDraggable: findOptionsForDraggable, - tryReleaseLock: tryReleaseLock, - isLockClaimed: isLockClaimed - }; -- }, [canGetLock, tryGetLock, findClosestDraggableId, findOptionsForDraggable, tryReleaseLock, isLockClaimed]); -+ }, [canGetLock, tryGetLock, findClosestDraggableId, findClosestDragHandle, findOptionsForDraggable, tryReleaseLock, isLockClaimed]); - useValidateSensorHooks(useSensors); - - for (var i = 0; i < useSensors.length; i++) { -diff --git a/node_modules/react-beautiful-dnd/dist/react-beautiful-dnd.min.js b/node_modules/react-beautiful-dnd/dist/react-beautiful-dnd.min.js -index a18e84d..9ec9c6d 100644 ---- a/node_modules/react-beautiful-dnd/dist/react-beautiful-dnd.min.js -+++ b/node_modules/react-beautiful-dnd/dist/react-beautiful-dnd.min.js -@@ -1 +1 @@ --!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("react"),require("react-dom")):"function"==typeof define&&define.amd?define(["exports","react","react-dom"],t):t((e=e||self).ReactBeautifulDnd={},e.React,e.ReactDOM)}(this,(function(e,t,r){"use strict";var n="default"in t?t.default:t,i="default"in r?r.default:r;function o(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,e.__proto__=t}function a(){}function l(){return(l=Object.assign||function(e){for(var t=1;t");return t.callbacks},t.setCallbacks=function(e){t.callbacks=e},t}o(t,e);var r=t.prototype;return r.componentDidMount=function(){this.unbind=u(window,[{eventName:"error",fn:this.onWindowError}])},r.componentDidCatch=function(e){if(!(e instanceof c))throw e;this.setState({})},r.componentWillUnmount=function(){this.unbind()},r.render=function(){return this.props.children(this.setCallbacks)},t}(n.Component),p=function(e){return e+1},f=function(e,t){var r=e.droppableId===t.droppableId,n=p(e.index),i=p(t.index);return r?"\n You have moved the item from position "+n+"\n to position "+i+"\n ":"\n You have moved the item from position "+n+"\n in list "+e.droppableId+"\n to list "+t.droppableId+"\n in position "+i+"\n "},g=function(e,t,r){return t.droppableId===r.droppableId?"\n The item "+e+"\n has been combined with "+r.draggableId:"\n The item "+e+"\n in list "+t.droppableId+"\n has been combined with "+r.draggableId+"\n in list "+r.droppableId+"\n "},v=function(e){return"\n The item has returned to its starting position\n of "+p(e.index)+"\n"},m="\n Press space bar to start a drag.\n When dragging you can use the arrow keys to move the item around and escape to cancel.\n Some screen readers may require you to be in focus mode or to use your pass through key\n",b=function(e){return"\n You have lifted an item in position "+p(e.source.index)+"\n"},h=function(e){var t=e.destination;if(t)return f(e.source,t);var r=e.combine;return r?g(e.draggableId,e.source,r):"You are over an area that cannot be dropped on"},y=function(e){if("CANCEL"===e.reason)return"\n Movement cancelled.\n "+v(e.source)+"\n ";var t=e.destination,r=e.combine;return t?"\n You have dropped the item.\n "+f(e.source,t)+"\n ":r?"\n You have dropped the item.\n "+g(e.draggableId,e.source,r)+"\n ":"\n The item has been dropped while not over a drop area.\n "+v(e.source)+"\n "};var x=function(e){var t,r=e.Symbol;return"function"==typeof r?r.observable?t=r.observable:(t=r("observable"),r.observable=t):t="@@observable",t}("undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof module?module:Function("return this")()),I=function(){return Math.random().toString(36).substring(7).split("").join(".")},D={INIT:"@@redux/INIT"+I(),REPLACE:"@@redux/REPLACE"+I(),PROBE_UNKNOWN_ACTION:function(){return"@@redux/PROBE_UNKNOWN_ACTION"+I()}};function w(e){if("object"!=typeof e||null===e)return!1;for(var t=e;null!==Object.getPrototypeOf(t);)t=Object.getPrototypeOf(t);return Object.getPrototypeOf(e)===t}function E(e,t,r){var n;if("function"==typeof t&&"function"==typeof r||"function"==typeof r&&"function"==typeof arguments[3])throw new Error("It looks like you are passing several store enhancers to createStore(). This is not supported. Instead, compose them together to a single function.");if("function"==typeof t&&void 0===r&&(r=t,t=void 0),void 0!==r){if("function"!=typeof r)throw new Error("Expected the enhancer to be a function.");return r(E)(e,t)}if("function"!=typeof e)throw new Error("Expected the reducer to be a function.");var i=e,o=t,a=[],l=a,u=!1;function c(){l===a&&(l=a.slice())}function s(){if(u)throw new Error("You may not call store.getState() while the reducer is executing. The reducer has already received the state as an argument. Pass it down from the top reducer instead of reading it from the store.");return o}function d(e){if("function"!=typeof e)throw new Error("Expected the listener to be a function.");if(u)throw new Error("You may not call store.subscribe() while the reducer is executing. If you would like to be notified after the store has been updated, subscribe from a component and invoke store.getState() in the callback to access the latest state. See https://redux.js.org/api-reference/store#subscribelistener for more details.");var t=!0;return c(),l.push(e),function(){if(t){if(u)throw new Error("You may not unsubscribe from a store listener while the reducer is executing. See https://redux.js.org/api-reference/store#subscribelistener for more details.");t=!1,c();var r=l.indexOf(e);l.splice(r,1),a=null}}}function p(e){if(!w(e))throw new Error("Actions must be plain objects. Use custom middleware for async actions.");if(void 0===e.type)throw new Error('Actions may not have an undefined "type" property. Have you misspelled a constant?');if(u)throw new Error("Reducers may not dispatch actions.");try{u=!0,o=i(o,e)}finally{u=!1}for(var t=a=l,r=0;r=0||(i[r]=e[r]);return i}var K={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},Q={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},Z={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},ee={};function te(e){return L.isMemo(e)?Z:ee[e.$$typeof]||K}ee[L.ForwardRef]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},ee[L.Memo]=Z;var re=Object.defineProperty,ne=Object.getOwnPropertyNames,ie=Object.getOwnPropertySymbols,oe=Object.getOwnPropertyDescriptor,ae=Object.getPrototypeOf,le=Object.prototype;var ue=function e(t,r,n){if("string"!=typeof r){if(le){var i=ae(r);i&&i!==le&&e(t,i,n)}var o=ne(r);ie&&(o=o.concat(ie(r)));for(var a=te(t),l=te(r),u=0;u=0;n--){var i=t[n](e);if(i)return i}return function(t,n){throw new Error("Invalid value of type "+typeof e+" for "+r+" argument when connecting component "+n.wrappedComponentName+".")}}function Ne(e,t){return e===t}function Be(e){var t=void 0===e?{}:e,r=t.connectHOC,n=void 0===r?be:r,i=t.mapStateToPropsFactories,o=void 0===i?Ee:i,a=t.mapDispatchToPropsFactories,l=void 0===a?we:a,u=t.mergePropsFactories,c=void 0===u?Se:u,s=t.selectorFactory,d=void 0===s?Ae:s;return function(e,t,r,i){void 0===i&&(i={});var a=i,u=a.pure,s=void 0===u||u,p=a.areStatesEqual,f=void 0===p?Ne:p,g=a.areOwnPropsEqual,v=void 0===g?ye:g,m=a.areStatePropsEqual,b=void 0===m?ye:m,h=a.areMergedPropsEqual,y=void 0===h?ye:h,x=X(a,["pure","areStatesEqual","areOwnPropsEqual","areStatePropsEqual","areMergedPropsEqual"]),I=Re(e,o,"mapStateToProps"),D=Re(t,l,"mapDispatchToProps"),w=Re(r,c,"mergeProps");return n(d,J({methodName:"connect",getDisplayName:function(e){return"Connect("+e+")"},shouldHandleStateChanges:Boolean(e),initMapStateToProps:I,initMapDispatchToProps:D,initMergeProps:w,pure:s,areStatesEqual:f,areOwnPropsEqual:v,areStatePropsEqual:b,areMergedPropsEqual:y},x))}}var Te,Me=Be();function Le(e,r){var n=t.useState((function(){return{inputs:r,result:e()}}))[0],i=t.useRef(n),o=Boolean(r&&i.current.inputs&&function(e,t){if(e.length!==t.length)return!1;for(var r=0;re.bottom,l=n.lefte.right;return!(!a||!l)||(a&&o||l&&i)}},Ot=function(e){var t=St(e.top,e.bottom),r=St(e.left,e.right);return function(e){return t(e.top)&&t(e.bottom)&&r(e.left)&&r(e.right)}},At={direction:"vertical",line:"y",crossAxisLine:"x",start:"top",end:"bottom",size:"height",crossAxisStart:"left",crossAxisEnd:"right",crossAxisSize:"width"},Rt={direction:"horizontal",line:"x",crossAxisLine:"y",start:"left",end:"right",size:"width",crossAxisStart:"top",crossAxisEnd:"bottom",crossAxisSize:"height"},Nt=function(e){var t=e.target,r=e.destination,n=e.viewport,i=e.withDroppableDisplacement,o=e.isVisibleThroughFrameFn,a=i?function(e,t){var r=t.frame?t.frame.scroll.diff.displacement:Fe;return nt(e,r)}(t,r):t;return function(e,t,r){return!!t.subject.active&&r(t.subject.active)(e)}(a,r,o)&&function(e,t,r){return r(t)(e)}(a,n,o)},Bt=function(e){return Nt(l({},e,{isVisibleThroughFrameFn:Pt}))},Tt=function(e){return Nt(l({},e,{isVisibleThroughFrameFn:Ot}))};function Mt(e){var t=e.afterDragging,r=e.destination,n=e.displacedBy,i=e.viewport,o=e.forceShouldAnimate,a=e.last;return t.reduce((function(e,t){var l=function(e,t){var r=e.page.marginBox,n={top:t.point.y,right:0,bottom:0,left:t.point.x};return $e(Ye(r,n))}(t,n),u=t.descriptor.id;if(e.all.push(u),!Bt({target:l,destination:r,viewport:i,withDroppableDisplacement:!0}))return e.invisible[t.descriptor.id]=!0,e;var c={draggableId:u,shouldAnimate:function(e,t,r){if("boolean"==typeof r)return r;if(!t)return!0;var n=t.invisible,i=t.visible;if(n[e])return!1;var o=i[e];return!o||o.shouldAnimate}(u,a,o)};return e.visible[u]=c,e}),{all:[],visible:{},invisible:{}})}function Lt(e){var t=e.insideDestination,r=e.inHomeList,n=e.displacedBy,i=e.destination,o=function(e,t){if(!e.length)return 0;var r=e[e.length-1].descriptor.index;return t.inHomeList?r:r+1}(t,{inHomeList:r});return{displaced:Et,displacedBy:n,at:{type:"REORDER",destination:{droppableId:i.descriptor.id,index:o}}}}function Gt(e){var t=e.draggable,r=e.insideDestination,n=e.destination,i=e.viewport,o=e.displacedBy,a=e.last,l=e.index,u=e.forceShouldAnimate,c=Dt(t,n);if(null==l)return Lt({insideDestination:r,inHomeList:c,displacedBy:o,destination:n});var s=pt(r,(function(e){return e.descriptor.index===l}));if(!s)return Lt({insideDestination:r,inHomeList:c,displacedBy:o,destination:n});var d=It(t,r),p=r.indexOf(s);return{displaced:Mt({afterDragging:d.slice(p),destination:n,displacedBy:o,last:a,viewport:i.frame,forceShouldAnimate:u}),displacedBy:o,at:{type:"REORDER",destination:{droppableId:n.descriptor.id,index:l}}}}function _t(e,t){return Boolean(t.effected[e])}var Ft=function(e){var t=e.isMovingForward,r=e.isInHomeList,n=e.draggable,i=e.draggables,o=e.destination,a=e.insideDestination,l=e.previousImpact,u=e.viewport,c=e.afterCritical,d=l.at;if(d||s(!1),"REORDER"===d.type){var p=function(e){var t=e.isMovingForward,r=e.isInHomeList,n=e.insideDestination,i=e.location;if(!n.length)return null;var o=i.index,a=t?o+1:o-1,l=n[0].descriptor.index,u=n[n.length-1].descriptor.index;return a(r?u:u+1)?null:a}({isMovingForward:t,isInHomeList:r,location:d.destination,insideDestination:a});return null==p?null:Gt({draggable:n,insideDestination:a,destination:o,viewport:u,last:l.displaced,displacedBy:l.displacedBy,index:p})}var f=function(e){var t=e.isMovingForward,r=e.destination,n=e.draggables,i=e.combine,o=e.afterCritical;if(!r.isCombineEnabled)return null;var a=i.draggableId,l=n[a].descriptor.index;return _t(a,o)?t?l:l-1:t?l+1:l}({isMovingForward:t,destination:o,displaced:l.displaced,draggables:i,combine:d.combine,afterCritical:c});return null==f?null:Gt({draggable:n,insideDestination:a,destination:o,viewport:u,last:l.displaced,displacedBy:l.displacedBy,index:f})},jt=function(e){var t=e.afterCritical,r=e.impact,n=e.draggables,i=xt(r);i||s(!1);var o=i.draggableId,a=n[o].page.borderBox.center,l=function(e){var t=e.displaced,r=e.afterCritical,n=e.combineWith,i=e.displacedBy,o=Boolean(t.visible[n]||t.invisible[n]);return _t(n,r)?o?Fe:Ue(i.point):o?i.point:Fe}({displaced:r.displaced,afterCritical:t,combineWith:o,displacedBy:r.displacedBy});return je(a,l)},kt=function(e,t){return t.margin[e.start]+t.borderBox[e.size]/2},Wt=function(e,t,r){return t[e.crossAxisStart]+r.margin[e.crossAxisStart]+r.borderBox[e.crossAxisSize]/2},Ut=function(e){var t=e.axis,r=e.moveRelativeTo,n=e.isMoving;return He(t.line,r.marginBox[t.end]+kt(t,n),Wt(t,r.marginBox,n))},Ht=function(e){var t=e.axis,r=e.moveRelativeTo,n=e.isMoving;return He(t.line,r.marginBox[t.start]-function(e,t){return t.margin[e.end]+t.borderBox[e.size]/2}(t,n),Wt(t,r.marginBox,n))},qt=function(e){var t=e.impact,r=e.draggable,n=e.draggables,i=e.droppable,o=e.afterCritical,a=ht(i.descriptor.id,n),l=r.page,u=i.axis;if(!a.length)return function(e){var t=e.axis,r=e.moveInto,n=e.isMoving;return He(t.line,r.contentBox[t.start]+kt(t,n),Wt(t,r.contentBox,n))}({axis:u,moveInto:i.page,isMoving:l});var c=t.displaced,s=t.displacedBy,d=c.all[0];if(d){var p=n[d];if(_t(d,o))return Ht({axis:u,moveRelativeTo:p.page,isMoving:l});var f=Ze(p.page,s.point);return Ht({axis:u,moveRelativeTo:f,isMoving:l})}var g=a[a.length-1];if(g.descriptor.id===r.descriptor.id)return l.borderBox.center;if(_t(g.descriptor.id,o)){var v=Ze(g.page,Ue(o.displacedBy.point));return Ut({axis:u,moveRelativeTo:v,isMoving:l})}return Ut({axis:u,moveRelativeTo:g.page,isMoving:l})},Vt=function(e,t){var r=e.frame;return r?je(t,r.scroll.diff.displacement):t},zt=function(e){var t=function(e){var t=e.impact,r=e.draggable,n=e.droppable,i=e.draggables,o=e.afterCritical,a=r.page.borderBox.center,l=t.at;return n&&l?"REORDER"===l.type?qt({impact:t,draggable:r,draggables:i,droppable:n,afterCritical:o}):jt({impact:t,draggables:i,afterCritical:o}):a}(e),r=e.droppable;return r?Vt(r,t):t},$t=function(e,t){var r=ke(t,e.scroll.initial),n=Ue(r);return{frame:$e({top:t.y,bottom:t.y+e.frame.height,left:t.x,right:t.x+e.frame.width}),scroll:{initial:e.scroll.initial,max:e.scroll.max,current:t,diff:{value:r,displacement:n}}}};function Yt(e,t){return e.map((function(e){return t[e]}))}var Jt=function(e){var t=e.pageBorderBoxCenter,r=e.draggable,n=function(e,t){return je(e.scroll.diff.displacement,t)}(e.viewport,t),i=ke(n,r.page.borderBox.center);return je(r.client.borderBox.center,i)},Xt=function(e){var t=e.draggable,r=e.destination,n=e.newPageBorderBoxCenter,i=e.viewport,o=e.withDroppableDisplacement,a=e.onlyOnMainAxis,u=void 0!==a&&a,c=ke(n,t.page.borderBox.center),s={target:nt(t.page.borderBox,c),destination:r,withDroppableDisplacement:o,viewport:i};return u?function(e){return Nt(l({},e,{isVisibleThroughFrameFn:(t=e.destination.axis,function(e){var r=St(e.top,e.bottom),n=St(e.left,e.right);return function(e){return t===At?r(e.top)&&r(e.bottom):n(e.left)&&n(e.right)}})}));var t}(s):Tt(s)},Kt=function(e){var t=e.isMovingForward,r=e.draggable,n=e.destination,i=e.draggables,o=e.previousImpact,a=e.viewport,u=e.previousPageBorderBoxCenter,c=e.previousClientSelection,d=e.afterCritical;if(!n.isEnabled)return null;var p=ht(n.descriptor.id,i),f=Dt(r,n),g=function(e){var t=e.isMovingForward,r=e.draggable,n=e.destination,i=e.insideDestination,o=e.previousImpact;if(!n.isCombineEnabled)return null;if(!yt(o))return null;function a(e){var t={type:"COMBINE",combine:{draggableId:e,droppableId:n.descriptor.id}};return l({},o,{at:t})}var u=o.displaced.all,c=u.length?u[0]:null;if(t)return c?a(c):null;var d=It(r,i);if(!c)return d.length?a(d[d.length-1].descriptor.id):null;var p=dt(d,(function(e){return e.descriptor.id===c}));-1===p&&s(!1);var f=p-1;return f<0?null:a(d[f].descriptor.id)}({isMovingForward:t,draggable:r,destination:n,insideDestination:p,previousImpact:o})||Ft({isMovingForward:t,isInHomeList:f,draggable:r,draggables:i,destination:n,insideDestination:p,previousImpact:o,viewport:a,afterCritical:d});if(!g)return null;var v=zt({impact:g,draggable:r,droppable:n,draggables:i,afterCritical:d});if(Xt({draggable:r,destination:n,newPageBorderBoxCenter:v,viewport:a.frame,withDroppableDisplacement:!1,onlyOnMainAxis:!0}))return{clientSelection:Jt({pageBorderBoxCenter:v,draggable:r,viewport:a}),impact:g,scrollJumpRequest:null};var m=ke(v,u);return{clientSelection:c,impact:function(e){var t=e.impact,r=e.viewport,n=e.destination,i=e.draggables,o=e.maxScrollChange,a=$t(r,je(r.scroll.current,o)),u=n.frame?lt(n,je(n.frame.scroll.current,o)):n,c=t.displaced,s=Mt({afterDragging:Yt(c.all,i),destination:n,displacedBy:t.displacedBy,viewport:a.frame,last:c,forceShouldAnimate:!1}),d=Mt({afterDragging:Yt(c.all,i),destination:u,displacedBy:t.displacedBy,viewport:r.frame,last:c,forceShouldAnimate:!1}),p={},f={},g=[c,s,d];return c.all.forEach((function(e){var t=function(e,t){for(var r=0;r1?s.sort((function(e,t){return Qt(e)[l.start]-Qt(t)[l.start]}))[0]:c.sort((function(e,t){var n=Ve(r,it(Qt(e))),i=Ve(r,it(Qt(t)));return n!==i?n-i:Qt(e)[l.start]-Qt(t)[l.start]}))[0]}({isMovingForward:t,pageBorderBoxCenter:r,source:i,droppables:a,viewport:l});if(!c)return null;var s=ht(c.descriptor.id,o),d=function(e){var t=e.previousPageBorderBoxCenter,r=e.moveRelativeTo,n=e.insideDestination,i=e.draggable,o=e.draggables,a=e.destination,l=e.viewport,u=e.afterCritical;if(!r){if(n.length)return null;var c={displaced:Et,displacedBy:wt,at:{type:"REORDER",destination:{droppableId:a.descriptor.id,index:0}}},s=zt({impact:c,draggable:i,droppable:a,draggables:o,afterCritical:u}),d=Dt(i,a)?a:nr(a,i,o);return Xt({draggable:i,destination:d,newPageBorderBoxCenter:s,viewport:l.frame,withDroppableDisplacement:!1,onlyOnMainAxis:!0})?c:null}var p,f=Boolean(t[a.axis.line]<=r.page.borderBox.center[a.axis.line]),g=(p=r.descriptor.index,r.descriptor.id===i.descriptor.id?p:f?p:p+1),v=tr(a.axis,i.displaceBy);return Gt({draggable:i,insideDestination:n,destination:a,viewport:l,displacedBy:v,last:Et,index:g})}({previousPageBorderBoxCenter:r,destination:c,draggable:n,draggables:o,moveRelativeTo:function(e){var t=e.pageBorderBoxCenter,r=e.viewport,n=e.destination,i=e.insideDestination,o=e.afterCritical;return i.filter((function(e){return Tt({target:er(e,o),destination:n,viewport:r.frame,withDroppableDisplacement:!0})})).sort((function(e,r){var i=qe(t,Vt(n,Zt(e,o))),a=qe(t,Vt(n,Zt(r,o)));return in.left&&r.topn.top))return!1;if(ur(i)(t.center))return!0;var o=e.axis,a=i.center[o.crossAxisLine],l=t[o.crossAxisStart],u=t[o.crossAxisEnd],c=St(i[o.crossAxisStart],i[o.crossAxisEnd]),s=c(l),d=c(u);return!s&&!d||(s?la)}));return i.length?1===i.length?i[0].descriptor.id:function(e){var t=e.pageBorderBox,r=e.draggable,n=e.candidates,i=r.page.borderBox.center,o=n.map((function(e){var r=e.axis,n=He(e.axis.line,t.center[r.line],e.page.borderBox.center[r.crossAxisLine]);return{id:e.descriptor.id,distance:qe(i,n)}})).sort((function(e,t){return t.distance-e.distance}));return o[0]?o[0].id:null}({pageBorderBox:t,draggable:r,candidates:i}):null}var sr=function(e,t){return $e(nt(e,t))};function dr(e){var t=e.displaced,r=e.id;return Boolean(t.visible[r]||t.invisible[r])}var pr=function(e){var t=e.pageOffset,r=e.draggable,n=e.draggables,i=e.droppables,o=e.previousImpact,a=e.viewport,l=e.afterCritical,u=sr(r.page.borderBox,t),c=cr({pageBorderBox:u,draggable:r,droppables:i});if(!c)return Ct;var s=i[c],d=ht(s.descriptor.id,n),p=function(e,t){var r=e.frame;return r?sr(t,r.scroll.diff.value):t}(s,u);return function(e){var t=e.draggable,r=e.pageBorderBoxWithDroppableScroll,n=e.previousImpact,i=e.destination,o=e.insideDestination,a=e.afterCritical;if(!i.isCombineEnabled)return null;var l=i.axis,u=tr(i.axis,t.displaceBy),c=u.value,s=r[l.start],d=r[l.end],p=pt(It(t,o),(function(e){var t=e.descriptor.id,r=e.page.borderBox,i=r[l.size]/4,o=_t(t,a),u=dr({displaced:n.displaced,id:t});return o?u?d>r[l.start]+i&&dr[l.start]-c+i&&sr[l.start]+c+i&&dr[l.start]+i&&st.descriptor.index?r.descriptor.index-1:r.descriptor.index:null}({draggable:r,closest:pt(It(r,i),(function(e){var t=e.descriptor.id,r=e.page.borderBox.center[u.line],n=_t(t,l),i=dr({displaced:o,id:t});return n?i?p<=r:d=1500)return Yr;var o=$r+Jr*(i/1500);return Number(("CANCEL"===n?.6*o:o).toFixed(2))}({current:i.current.client.offset,destination:h,reason:o});r(function(e){return{type:"DROP_ANIMATE",payload:e}}({newHomeClientOffset:h,dropDuration:x,completed:y}))}else r(Gr({completed:y}))}}else r(function(e){return{type:"DROP_PENDING",payload:e}}({reason:o}))}else e(n)}}},Kr=function(e){var t=[],r=null,n=function(){for(var n=arguments.length,i=new Array(n),o=0;ot.startScrollingFrom)return 0;if(e<=t.maxScrollValueAt)return Sn;if(e===t.startScrollingFrom)return 1;var r=An({startOfRange:t.maxScrollValueAt,endOfRange:t.startScrollingFrom,current:e}),n=Sn*Pn(1-r);return Math.ceil(n)}(t,r);return 0===o?0:i?Math.max(function(e,t){var r=t,n=Nn,i=Date.now()-r;if(i>=Nn)return e;if(it.height,o=r.width>t.width;return o||i?o&&i?null:{x:o?0:n.x,y:i?0:n.y}:n}({container:r,subject:n,proposedScroll:c});return s?We(s,Fe)?null:s:null},Gn=ze((function(e){return 0===e?0:e>0?1:-1})),_n=(bn=function(e,t){return e<0?e:e>t?e-t:0},function(e){var t=e.current,r=e.max,n=e.change,i=je(t,n),o={x:bn(i.x,r.x),y:bn(i.y,r.y)};return We(o,Fe)?null:o}),Fn=function(e){var t=e.max,r=e.current,n=e.change,i={x:Math.max(r.x,t.x),y:Math.max(r.y,t.y)},o=Gn(n),a=_n({max:i,current:r,change:o});return!a||(0!==o.x&&0===a.x||0!==o.y&&0===a.y)},jn=function(e,t){return Fn({current:e.scroll.current,max:e.scroll.max,change:t})},kn=function(e,t){var r=e.frame;return!!r&&Fn({current:r.scroll.current,max:r.scroll.max,change:t})},Wn=function(e){var t=e.state,r=e.dragStartTime,n=e.shouldUseTimeDampening,i=e.scrollWindow,o=e.scrollDroppable,a=t.current.page.borderBoxCenter,l=t.dimensions.draggables[t.critical.draggable.id].page.marginBox;if(t.isWindowScrollAllowed){var u=function(e){var t=e.viewport,r=e.subject,n=e.center,i=e.dragStartTime,o=e.shouldUseTimeDampening,a=Ln({dragStartTime:i,container:t.frame,subject:r,center:n,shouldUseTimeDampening:o});return a&&jn(t,a)?a:null}({dragStartTime:r,viewport:t.viewport,subject:l,center:a,shouldUseTimeDampening:n});if(u)return void i(u)}var c=wn({center:a,destination:or(t.impact),droppables:t.dimensions.droppables});if(c){var s=function(e){var t=e.droppable,r=e.subject,n=e.center,i=e.dragStartTime,o=e.shouldUseTimeDampening,a=t.frame;if(!a)return null;var l=Ln({dragStartTime:i,container:a.pageMarginBox,subject:r,center:n,shouldUseTimeDampening:o});return l&&kn(t,l)?l:null}({dragStartTime:r,droppable:c,subject:l,center:a,shouldUseTimeDampening:n});s&&o(c.descriptor.id,s)}},Un=function(e){var t=e.move,r=e.scrollDroppable,n=e.scrollWindow,i=function(e,t){if(!kn(e,t))return t;var n=function(e,t){var r=e.frame;return r&&kn(e,t)?_n({current:r.scroll.current,max:r.scroll.max,change:t}):null}(e,t);if(!n)return r(e.descriptor.id,t),null;var i=ke(t,n);return r(e.descriptor.id,i),ke(t,i)},o=function(e,t,r){if(!e)return r;if(!jn(t,r))return r;var i=function(e,t){if(!jn(e,t))return null;var r=e.scroll.max,n=e.scroll.current;return _n({current:n,max:r,change:t})}(t,r);if(!i)return n(r),null;var o=ke(r,i);return n(o),ke(r,o)};return function(e){var r=e.scrollJumpRequest;if(r){var n=or(e.impact);n||s(!1);var a=i(e.dimensions.droppables[n],r);if(a){var l=e.viewport,u=o(e.isWindowScrollAllowed,l,a);u&&function(e,r){var n=je(e.current.client.selection,r);t({client:n})}(e,u)}}}},Hn=function(e){var t=e.scrollDroppable,r=e.scrollWindow,n=e.move,i=function(e){var t=e.scrollWindow,r=e.scrollDroppable,n=Kr(t),i=Kr(r),o=null,a=function(e){o||s(!1);var t=o,r=t.shouldUseTimeDampening,a=t.dragStartTime;Wn({state:e,scrollWindow:n,scrollDroppable:i,dragStartTime:a,shouldUseTimeDampening:r})};return{start:function(e){o&&s(!1);var t=Date.now(),r=!1,n=function(){r=!0};Wn({state:e,dragStartTime:0,shouldUseTimeDampening:!1,scrollWindow:n,scrollDroppable:n}),o={dragStartTime:t,shouldUseTimeDampening:r},r&&a(e)},stop:function(){o&&(n.cancel(),i.cancel(),o=null)},scroll:a}}({scrollWindow:r,scrollDroppable:t}),o=Un({move:n,scrollWindow:r,scrollDroppable:t});return{scroll:function(e){"DRAGGING"===e.phase&&("FLUID"!==e.movementMode?e.scrollJumpRequest&&o(e):i.scroll(e))},start:i.start,stop:i.stop}},qn={base:hn="data-rbd-drag-handle",draggableId:hn+"-draggable-id",contextId:hn+"-context-id"},Vn=function(){var e="data-rbd-draggable";return{base:e,contextId:e+"-context-id",id:e+"-id"}}(),zn=function(){var e="data-rbd-droppable";return{base:e,contextId:e+"-context-id",id:e+"-id"}}(),$n={contextId:"data-rbd-scroll-container-context-id"},Yn=function(e,t){return e.map((function(e){var r=e.styles[t];return r?e.selector+" { "+r+" }":""})).join(" ")},Jn="undefined"!=typeof window&&void 0!==window.document&&void 0!==window.document.createElement?t.useLayoutEffect:t.useEffect,Xn=function(){var e=document.querySelector("head");return e||s(!1),e},Kn=function(e){var t=document.createElement("style");return e&&t.setAttribute("nonce",e),t.type="text/css",t};function Qn(e,r){var n=Ge((function(){return function(e){var t,r,n,i=(t=e,function(e){return"["+e+'="'+t+'"]'}),o=(r="\n cursor: -webkit-grab;\n cursor: grab;\n ",{selector:i(qn.contextId),styles:{always:"\n -webkit-touch-callout: none;\n -webkit-tap-highlight-color: rgba(0,0,0,0);\n touch-action: manipulation;\n ",resting:r,dragging:"pointer-events: none;",dropAnimating:r}}),a=[(n="\n transition: "+Hr.outOfTheWay+";\n ",{selector:i(Vn.contextId),styles:{dragging:n,dropAnimating:n,userCancel:n}}),o,{selector:i(zn.contextId),styles:{always:"overflow-anchor: none;"}},{selector:"body",styles:{dragging:"\n cursor: grabbing;\n cursor: -webkit-grabbing;\n user-select: none;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n overflow-anchor: none;\n "}}];return{always:Yn(a,"always"),resting:Yn(a,"resting"),dragging:Yn(a,"dragging"),dropAnimating:Yn(a,"dropAnimating"),userCancel:Yn(a,"userCancel")}}(e)}),[e]),i=t.useRef(null),o=t.useRef(null),a=_e(ct((function(e){var t=o.current;t||s(!1),t.textContent=e})),[]),l=_e((function(e){var t=i.current;t||s(!1),t.textContent=e}),[]);Jn((function(){(i.current||o.current)&&s(!1);var t=Kn(r),u=Kn(r);return i.current=t,o.current=u,t.setAttribute("data-rbd-always",e),u.setAttribute("data-rbd-dynamic",e),Xn().appendChild(t),Xn().appendChild(u),l(n.always),a(n.resting),function(){var e=function(e){var t=e.current;t||s(!1),Xn().removeChild(t),e.current=null};e(i),e(o)}}),[r,l,a,n.always,n.resting,e]);var u=_e((function(){return a(n.dragging)}),[a,n.dragging]),c=_e((function(e){a("DROP"!==e?n.userCancel:n.dropAnimating)}),[a,n.dropAnimating,n.userCancel]),d=_e((function(){o.current&&a(n.resting)}),[a,n.resting]);return Ge((function(){return{dragging:u,dropping:c,resting:d}}),[u,c,d])}var Zn=function(e){return e&&e.ownerDocument?e.ownerDocument.defaultView:window};function ei(e){return e instanceof Zn(e).HTMLElement}function ti(e){var r=t.useRef({}),n=t.useRef(null),i=t.useRef(null),o=t.useRef(!1),a=_e((function(e,t){var n={id:e,focus:t};return r.current[e]=n,function(){var t=r.current;t[e]!==n&&delete t[e]}}),[]),l=_e((function(t){var r=function(e,t){var r="["+qn.contextId+'="'+e+'"]',n=ft(document.querySelectorAll(r));if(!n.length)return null;var i=pt(n,(function(e){return e.getAttribute(qn.draggableId)===t}));return i&&ei(i)?i:null}(e,t);r&&r!==document.activeElement&&r.focus()}),[e]),u=_e((function(e,t){n.current===e&&(n.current=t)}),[]),c=_e((function(){i.current||o.current&&(i.current=requestAnimationFrame((function(){i.current=null;var e=n.current;e&&l(e)})))}),[l]),s=_e((function(e){n.current=null;var t=document.activeElement;t&&t.getAttribute(qn.draggableId)===e&&(n.current=e)}),[]);return Jn((function(){return o.current=!0,function(){o.current=!1;var e=i.current;e&&cancelAnimationFrame(e)}}),[]),Ge((function(){return{register:a,tryRecordFocus:s,tryRestoreFocusRecorded:c,tryShiftRecord:u}}),[a,s,c,u])}function ri(){var e={draggables:{},droppables:{}},t=[];function r(e){t.length&&t.forEach((function(t){return t(e)}))}function n(t){return e.draggables[t]||null}function i(t){return e.droppables[t]||null}return{draggable:{register:function(t){e.draggables[t.descriptor.id]=t,r({type:"ADDITION",value:t})},update:function(t,r){var n=e.draggables[r.descriptor.id];n&&n.uniqueId===t.uniqueId&&(delete e.draggables[r.descriptor.id],e.draggables[t.descriptor.id]=t)},unregister:function(t){var i=t.descriptor.id,o=n(i);o&&t.uniqueId===o.uniqueId&&(delete e.draggables[i],r({type:"REMOVAL",value:t}))},getById:function(e){var t=n(e);return t||s(!1),t},findById:n,exists:function(e){return Boolean(n(e))},getAllByType:function(t){return st(e.draggables).filter((function(e){return e.descriptor.type===t}))}},droppable:{register:function(t){e.droppables[t.descriptor.id]=t},unregister:function(t){var r=i(t.descriptor.id);r&&t.uniqueId===r.uniqueId&&delete e.droppables[t.descriptor.id]},getById:function(e){var t=i(e);return t||s(!1),t},findById:i,exists:function(e){return Boolean(i(e))},getAllByType:function(t){return st(e.droppables).filter((function(e){return e.descriptor.type===t}))}},subscribe:function(e){return t.push(e),function(){var r=t.indexOf(e);-1!==r&&t.splice(r,1)}},clean:function(){e.draggables={},e.droppables={},t.length=0}}}var ni=n.createContext(null),ii=function(){var e=document.body;return e||s(!1),e},oi={position:"absolute",width:"1px",height:"1px",margin:"-1px",border:"0",padding:"0",overflow:"hidden",clip:"rect(0 0 0 0)","clip-path":"inset(100%)"};var ai=0,li={separator:"::"};function ui(e,t){return void 0===t&&(t=li),Ge((function(){return""+e+t.separator+ai++}),[t.separator,e])}var ci=n.createContext(null);function si(e){var r=t.useRef(e);return t.useEffect((function(){r.current=e})),r}var di,pi=((di={})[13]=!0,di[9]=!0,di),fi=function(e){pi[e.keyCode]&&e.preventDefault()},gi=function(){var e="visibilitychange";return"undefined"==typeof document?e:pt([e,"ms"+e,"webkit"+e,"moz"+e,"o"+e],(function(e){return"on"+e in document}))||e}();var vi,mi={type:"IDLE"};function bi(e){var t=e.cancel,r=e.completed,n=e.getPhase,i=e.setPhase;return[{eventName:"mousemove",fn:function(e){var t=e.button,r=e.clientX,o=e.clientY;if(0===t){var a={x:r,y:o},l=n();if("DRAGGING"===l.type)return e.preventDefault(),void l.actions.move(a);"PENDING"!==l.type&&s(!1);var u=l.point;if(c=u,d=a,Math.abs(d.x-c.x)>=5||Math.abs(d.y-c.y)>=5){var c,d;e.preventDefault();var p=l.actions.fluidLift(a);i({type:"DRAGGING",actions:p})}}}},{eventName:"mouseup",fn:function(e){var i=n();"DRAGGING"===i.type?(e.preventDefault(),i.actions.drop({shouldBlockNextClick:!0}),r()):t()}},{eventName:"mousedown",fn:function(e){"DRAGGING"===n().type&&e.preventDefault(),t()}},{eventName:"keydown",fn:function(e){if("PENDING"!==n().type)return 27===e.keyCode?(e.preventDefault(),void t()):void fi(e);t()}},{eventName:"resize",fn:t},{eventName:"scroll",options:{passive:!0,capture:!1},fn:function(){"PENDING"===n().type&&t()}},{eventName:"webkitmouseforcedown",fn:function(e){var r=n();"IDLE"===r.type&&s(!1),r.actions.shouldRespectForcePress()?t():e.preventDefault()}},{eventName:gi,fn:t}]}function hi(e){var r=t.useRef(mi),n=t.useRef(a),i=Ge((function(){return{eventName:"mousedown",fn:function(t){if(!t.defaultPrevented&&0===t.button&&!(t.ctrlKey||t.metaKey||t.shiftKey||t.altKey)){var r=e.findClosestDraggableId(t);if(r){var i=e.tryGetLock(r,c,{sourceEvent:t});if(i){t.preventDefault();var o={x:t.clientX,y:t.clientY};n.current(),f(i,o)}}}}}}),[e]),o=Ge((function(){return{eventName:"webkitmouseforcewillbegin",fn:function(t){if(!t.defaultPrevented){var r=e.findClosestDraggableId(t);if(r){var n=e.findOptionsForDraggable(r);n&&(n.shouldRespectForcePress||e.canGetLock(r)&&t.preventDefault())}}}}}),[e]),l=_e((function(){n.current=u(window,[o,i],{passive:!1,capture:!0})}),[o,i]),c=_e((function(){"IDLE"!==r.current.type&&(r.current=mi,n.current(),l())}),[l]),d=_e((function(){var e=r.current;c(),"DRAGGING"===e.type&&e.actions.cancel({shouldBlockNextClick:!0}),"PENDING"===e.type&&e.actions.abort()}),[c]),p=_e((function(){var e=bi({cancel:d,completed:c,getPhase:function(){return r.current},setPhase:function(e){r.current=e}});n.current=u(window,e,{capture:!0,passive:!1})}),[d,c]),f=_e((function(e,t){"IDLE"!==r.current.type&&s(!1),r.current={type:"PENDING",point:t,actions:e},p()}),[p]);Jn((function(){return l(),function(){n.current()}}),[l])}function yi(){}var xi=((vi={})[34]=!0,vi[33]=!0,vi[36]=!0,vi[35]=!0,vi);function Ii(e,t){function r(){t(),e.cancel()}return[{eventName:"keydown",fn:function(n){return 27===n.keyCode?(n.preventDefault(),void r()):32===n.keyCode?(n.preventDefault(),t(),void e.drop()):40===n.keyCode?(n.preventDefault(),void e.moveDown()):38===n.keyCode?(n.preventDefault(),void e.moveUp()):39===n.keyCode?(n.preventDefault(),void e.moveRight()):37===n.keyCode?(n.preventDefault(),void e.moveLeft()):void(xi[n.keyCode]?n.preventDefault():fi(n))}},{eventName:"mousedown",fn:r},{eventName:"mouseup",fn:r},{eventName:"click",fn:r},{eventName:"touchstart",fn:r},{eventName:"resize",fn:r},{eventName:"wheel",fn:r,options:{passive:!0}},{eventName:gi,fn:r}]}function Di(e){var r=t.useRef(yi),n=Ge((function(){return{eventName:"keydown",fn:function(t){if(!t.defaultPrevented&&32===t.keyCode){var n=e.findClosestDraggableId(t);if(n){var o=e.tryGetLock(n,c,{sourceEvent:t});if(o){t.preventDefault();var a=!0,l=o.snapLift();r.current(),r.current=u(window,Ii(l,c),{capture:!0,passive:!1})}}}function c(){a||s(!1),a=!1,r.current(),i()}}}}),[e]),i=_e((function(){r.current=u(window,[n],{passive:!1,capture:!0})}),[n]);Jn((function(){return i(),function(){r.current()}}),[i])}var wi={type:"IDLE"};function Ei(e){var r=t.useRef(wi),n=t.useRef(a),i=_e((function(){return r.current}),[]),o=_e((function(e){r.current=e}),[]),l=Ge((function(){return{eventName:"touchstart",fn:function(t){if(!t.defaultPrevented){var r=e.findClosestDraggableId(t);if(r){var i=e.tryGetLock(r,d,{sourceEvent:t});if(i){var o=t.touches[0],a={x:o.clientX,y:o.clientY};n.current(),v(i,a)}}}}}}),[e]),c=_e((function(){n.current=u(window,[l],{capture:!0,passive:!1})}),[l]),d=_e((function(){var e=r.current;"IDLE"!==e.type&&("PENDING"===e.type&&clearTimeout(e.longPressTimerId),o(wi),n.current(),c())}),[c,o]),p=_e((function(){var e=r.current;d(),"DRAGGING"===e.type&&e.actions.cancel({shouldBlockNextClick:!0}),"PENDING"===e.type&&e.actions.abort()}),[d]),f=_e((function(){var e={capture:!0,passive:!1},t={cancel:p,completed:d,getPhase:i},r=u(window,function(e){var t=e.cancel,r=e.completed,n=e.getPhase;return[{eventName:"touchmove",options:{capture:!1},fn:function(e){var r=n();if("DRAGGING"===r.type){r.hasMoved=!0;var i=e.touches[0],o={x:i.clientX,y:i.clientY};e.preventDefault(),r.actions.move(o)}else t()}},{eventName:"touchend",fn:function(e){var i=n();"DRAGGING"===i.type?(e.preventDefault(),i.actions.drop({shouldBlockNextClick:!0}),r()):t()}},{eventName:"touchcancel",fn:function(e){"DRAGGING"===n().type?(e.preventDefault(),t()):t()}},{eventName:"touchforcechange",fn:function(e){var r=n();"IDLE"===r.type&&s(!1);var i=e.touches[0];if(i&&i.force>=.15){var o=r.actions.shouldRespectForcePress();if("PENDING"!==r.type)return o?r.hasMoved?void e.preventDefault():void t():void e.preventDefault();o&&t()}}},{eventName:gi,fn:t}]}(t),e),o=u(window,function(e){var t=e.cancel,r=e.getPhase;return[{eventName:"orientationchange",fn:t},{eventName:"resize",fn:t},{eventName:"contextmenu",fn:function(e){e.preventDefault()}},{eventName:"keydown",fn:function(e){"DRAGGING"===r().type?(27===e.keyCode&&e.preventDefault(),t()):t()}},{eventName:gi,fn:t}]}(t),e);n.current=function(){r(),o()}}),[p,i,d]),g=_e((function(){var e=i();"PENDING"!==e.type&&s(!1);var t=e.actions.fluidLift(e.point);o({type:"DRAGGING",actions:t,hasMoved:!1})}),[i,o]),v=_e((function(e,t){"IDLE"!==i().type&&s(!1);var r=setTimeout(g,120);o({type:"PENDING",point:t,actions:e,longPressTimerId:r}),f()}),[f,i,o,g]);Jn((function(){return c(),function(){n.current();var e=i();"PENDING"===e.type&&(clearTimeout(e.longPressTimerId),o(wi))}}),[i,c,o]),Jn((function(){return u(window,[{eventName:"touchmove",fn:function(){},options:{capture:!1,passive:!1}}])}),[])}var Ci={input:!0,button:!0,textarea:!0,select:!0,option:!0,optgroup:!0,video:!0,audio:!0};function Si(e,t){var r=t.target;return!!ei(r)&&function e(t,r){if(null==r)return!1;if(Boolean(Ci[r.tagName.toLowerCase()]))return!0;var n=r.getAttribute("contenteditable");return"true"===n||""===n||r!==t&&e(t,r.parentElement)}(e,r)}var Pi=function(e){return $e(e.getBoundingClientRect()).center};var Oi="undefined"==typeof document?"matches":pt(["matches","msMatchesSelector","webkitMatchesSelector"],(function(e){return e in Element.prototype}))||"matches";function Ai(e,t){return e.closest?e.closest(t):function e(t,r){return null==t?null:t[Oi](r)?t:e(t.parentElement,r)}(e,t)}function Ri(e,t){var r,n=t.target;if(!((r=n)instanceof Zn(r).Element))return null;var i=Ai(n,function(e){return"["+qn.contextId+'="'+e+'"]'}(e));return i&&ei(i)?i:null}function Ni(e){e.preventDefault()}function Bi(e){var t=e.expected,r=e.phase,n=e.isLockActive;e.shouldWarn;return!!n()&&t===r}function Ti(e){var t=e.lockAPI,r=e.store,n=e.registry,i=e.draggableId;if(t.isClaimed())return!1;var o=n.draggable.findById(i);return!!o&&(!!o.options.isEnabled&&!!xn(r.getState(),i))}function Mi(e){var t=e.lockAPI,r=e.contextId,n=e.store,i=e.registry,o=e.draggableId,c=e.forceSensorStop,d=e.sourceEvent;if(!Ti({lockAPI:t,store:n,registry:i,draggableId:o}))return null;var p=i.draggable.getById(o),f=function(e,t){var r="["+Vn.contextId+'="'+e+'"]',n=pt(ft(document.querySelectorAll(r)),(function(e){return e.getAttribute(Vn.id)===t}));return n&&ei(n)?n:null}(r,p.descriptor.id);if(!f)return null;if(d&&!p.options.canDragInteractiveElements&&Si(f,d))return null;var g=t.claim(c||a),v="PRE_DRAG";function m(){return p.options.shouldRespectForcePress}function b(){return t.isActive(g)}var h=function(e,t){Bi({expected:e,phase:v,isLockActive:b,shouldWarn:!0})&&n.dispatch(t())}.bind(null,"DRAGGING");function y(e){function r(){t.release(),v="COMPLETED"}function i(t,i){if(void 0===i&&(i={shouldBlockNextClick:!1}),e.cleanup(),i.shouldBlockNextClick){var o=u(window,[{eventName:"click",fn:Ni,options:{once:!0,passive:!1,capture:!0}}]);setTimeout(o)}r(),n.dispatch(_r({reason:t}))}return"PRE_DRAG"!==v&&(r(),"PRE_DRAG"!==v&&s(!1)),n.dispatch(function(e){return{type:"LIFT",payload:e}}(e.liftActionArgs)),v="DRAGGING",l({isActive:function(){return Bi({expected:"DRAGGING",phase:v,isLockActive:b,shouldWarn:!1})},shouldRespectForcePress:m,drop:function(e){return i("DROP",e)},cancel:function(e){return i("CANCEL",e)}},e.actions)}return{isActive:function(){return Bi({expected:"PRE_DRAG",phase:v,isLockActive:b,shouldWarn:!1})},shouldRespectForcePress:m,fluidLift:function(e){var t=Kr((function(e){h((function(){return Nr({client:e})}))}));return l({},y({liftActionArgs:{id:o,clientSelection:e,movementMode:"FLUID"},cleanup:function(){return t.cancel()},actions:{move:t}}),{move:t})},snapLift:function(){var e={moveUp:function(){return h(Br)},moveRight:function(){return h(Mr)},moveDown:function(){return h(Tr)},moveLeft:function(){return h(Lr)}};return y({liftActionArgs:{id:o,clientSelection:Pi(f),movementMode:"SNAP"},cleanup:a,actions:e})},abort:function(){Bi({expected:"PRE_DRAG",phase:v,isLockActive:b,shouldWarn:!0})&&t.release()}}}var Li=[hi,Di,Ei];function Gi(e){var r=e.contextId,n=e.store,i=e.registry,o=e.customSensors,a=e.enableDefaultSensors,l=[].concat(a?Li:[],o||[]),u=t.useState((function(){return function(){var e=null;function t(){e||s(!1),e=null}return{isClaimed:function(){return Boolean(e)},isActive:function(t){return t===e},claim:function(t){e&&s(!1);var r={abandon:t};return e=r,r},release:t,tryAbandon:function(){e&&(e.abandon(),t())}}}()}))[0],c=_e((function(e,t){e.isDragging&&!t.isDragging&&u.tryAbandon()}),[u]);Jn((function(){var e=n.getState();return n.subscribe((function(){var t=n.getState();c(e,t),e=t}))}),[u,n,c]),Jn((function(){return u.tryAbandon}),[u.tryAbandon]);for(var d=_e((function(e){return Ti({lockAPI:u,registry:i,store:n,draggableId:e})}),[u,i,n]),p=_e((function(e,t,o){return Mi({lockAPI:u,registry:i,contextId:r,store:n,draggableId:e,forceSensorStop:t,sourceEvent:o&&o.sourceEvent?o.sourceEvent:null})}),[r,u,i,n]),f=_e((function(e){return function(e,t){var r=Ri(e,t);return r?r.getAttribute(qn.draggableId):null}(r,e)}),[r]),g=_e((function(e){var t=i.draggable.findById(e);return t?t.options:null}),[i.draggable]),v=_e((function(){u.isClaimed()&&(u.tryAbandon(),"IDLE"!==n.getState().phase&&n.dispatch({type:"FLUSH",payload:null}))}),[u,n]),m=_e(u.isClaimed,[u]),b=Ge((function(){return{canGetLock:d,tryGetLock:p,findClosestDraggableId:f,findOptionsForDraggable:g,tryReleaseLock:v,isLockClaimed:m}}),[d,p,f,g,v,m]),h=0;h");return t.callbacks},t.setCallbacks=function(e){t.callbacks=e},t}o(t,e);var r=t.prototype;return r.componentDidMount=function(){this.unbind=u(window,[{eventName:"error",fn:this.onWindowError}])},r.componentDidCatch=function(e){if(!(e instanceof c))throw e;this.setState({})},r.componentWillUnmount=function(){this.unbind()},r.render=function(){return this.props.children(this.setCallbacks)},t}(n.Component),p=function(e){return e+1},f=function(e,t){var r=e.droppableId===t.droppableId,n=p(e.index),i=p(t.index);return r?"\n You have moved the item from position "+n+"\n to position "+i+"\n ":"\n You have moved the item from position "+n+"\n in list "+e.droppableId+"\n to list "+t.droppableId+"\n in position "+i+"\n "},g=function(e,t,r){return t.droppableId===r.droppableId?"\n The item "+e+"\n has been combined with "+r.draggableId:"\n The item "+e+"\n in list "+t.droppableId+"\n has been combined with "+r.draggableId+"\n in list "+r.droppableId+"\n "},v=function(e){return"\n The item has returned to its starting position\n of "+p(e.index)+"\n"},m="\n Press space bar to start a drag.\n When dragging you can use the arrow keys to move the item around and escape to cancel.\n Some screen readers may require you to be in focus mode or to use your pass through key\n",b=function(e){return"\n You have lifted an item in position "+p(e.source.index)+"\n"},h=function(e){var t=e.destination;if(t)return f(e.source,t);var r=e.combine;return r?g(e.draggableId,e.source,r):"You are over an area that cannot be dropped on"},y=function(e){if("CANCEL"===e.reason)return"\n Movement cancelled.\n "+v(e.source)+"\n ";var t=e.destination,r=e.combine;return t?"\n You have dropped the item.\n "+f(e.source,t)+"\n ":r?"\n You have dropped the item.\n "+g(e.draggableId,e.source,r)+"\n ":"\n The item has been dropped while not over a drop area.\n "+v(e.source)+"\n "};var x=function(e){var t,r=e.Symbol;return"function"==typeof r?r.observable?t=r.observable:(t=r("observable"),r.observable=t):t="@@observable",t}("undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof module?module:Function("return this")()),I=function(){return Math.random().toString(36).substring(7).split("").join(".")},D={INIT:"@@redux/INIT"+I(),REPLACE:"@@redux/REPLACE"+I(),PROBE_UNKNOWN_ACTION:function(){return"@@redux/PROBE_UNKNOWN_ACTION"+I()}};function w(e){if("object"!=typeof e||null===e)return!1;for(var t=e;null!==Object.getPrototypeOf(t);)t=Object.getPrototypeOf(t);return Object.getPrototypeOf(e)===t}function E(e,t,r){var n;if("function"==typeof t&&"function"==typeof r||"function"==typeof r&&"function"==typeof arguments[3])throw new Error("It looks like you are passing several store enhancers to createStore(). This is not supported. Instead, compose them together to a single function.");if("function"==typeof t&&void 0===r&&(r=t,t=void 0),void 0!==r){if("function"!=typeof r)throw new Error("Expected the enhancer to be a function.");return r(E)(e,t)}if("function"!=typeof e)throw new Error("Expected the reducer to be a function.");var i=e,o=t,a=[],l=a,u=!1;function c(){l===a&&(l=a.slice())}function s(){if(u)throw new Error("You may not call store.getState() while the reducer is executing. The reducer has already received the state as an argument. Pass it down from the top reducer instead of reading it from the store.");return o}function d(e){if("function"!=typeof e)throw new Error("Expected the listener to be a function.");if(u)throw new Error("You may not call store.subscribe() while the reducer is executing. If you would like to be notified after the store has been updated, subscribe from a component and invoke store.getState() in the callback to access the latest state. See https://redux.js.org/api-reference/store#subscribelistener for more details.");var t=!0;return c(),l.push(e),function(){if(t){if(u)throw new Error("You may not unsubscribe from a store listener while the reducer is executing. See https://redux.js.org/api-reference/store#subscribelistener for more details.");t=!1,c();var r=l.indexOf(e);l.splice(r,1),a=null}}}function p(e){if(!w(e))throw new Error("Actions must be plain objects. Use custom middleware for async actions.");if(void 0===e.type)throw new Error('Actions may not have an undefined "type" property. Have you misspelled a constant?');if(u)throw new Error("Reducers may not dispatch actions.");try{u=!0,o=i(o,e)}finally{u=!1}for(var t=a=l,r=0;r=0||(i[r]=e[r]);return i}var K={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},Q={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},Z={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},ee={};function te(e){return L.isMemo(e)?Z:ee[e.$$typeof]||K}ee[L.ForwardRef]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},ee[L.Memo]=Z;var re=Object.defineProperty,ne=Object.getOwnPropertyNames,ie=Object.getOwnPropertySymbols,oe=Object.getOwnPropertyDescriptor,ae=Object.getPrototypeOf,le=Object.prototype;var ue=function e(t,r,n){if("string"!=typeof r){if(le){var i=ae(r);i&&i!==le&&e(t,i,n)}var o=ne(r);ie&&(o=o.concat(ie(r)));for(var a=te(t),l=te(r),u=0;u=0;n--){var i=t[n](e);if(i)return i}return function(t,n){throw new Error("Invalid value of type "+typeof e+" for "+r+" argument when connecting component "+n.wrappedComponentName+".")}}function Ne(e,t){return e===t}function Be(e){var t=void 0===e?{}:e,r=t.connectHOC,n=void 0===r?be:r,i=t.mapStateToPropsFactories,o=void 0===i?Ee:i,a=t.mapDispatchToPropsFactories,l=void 0===a?we:a,u=t.mergePropsFactories,c=void 0===u?Se:u,s=t.selectorFactory,d=void 0===s?Ae:s;return function(e,t,r,i){void 0===i&&(i={});var a=i,u=a.pure,s=void 0===u||u,p=a.areStatesEqual,f=void 0===p?Ne:p,g=a.areOwnPropsEqual,v=void 0===g?ye:g,m=a.areStatePropsEqual,b=void 0===m?ye:m,h=a.areMergedPropsEqual,y=void 0===h?ye:h,x=X(a,["pure","areStatesEqual","areOwnPropsEqual","areStatePropsEqual","areMergedPropsEqual"]),I=Re(e,o,"mapStateToProps"),D=Re(t,l,"mapDispatchToProps"),w=Re(r,c,"mergeProps");return n(d,J({methodName:"connect",getDisplayName:function(e){return"Connect("+e+")"},shouldHandleStateChanges:Boolean(e),initMapStateToProps:I,initMapDispatchToProps:D,initMergeProps:w,pure:s,areStatesEqual:f,areOwnPropsEqual:v,areStatePropsEqual:b,areMergedPropsEqual:y},x))}}var Te,Me=Be();function Le(e,r){var n=t.useState((function(){return{inputs:r,result:e()}}))[0],i=t.useRef(n),o=Boolean(r&&i.current.inputs&&function(e,t){if(e.length!==t.length)return!1;for(var r=0;re.bottom,l=n.lefte.right;return!(!a||!l)||(a&&o||l&&i)}},Ot=function(e){var t=St(e.top,e.bottom),r=St(e.left,e.right);return function(e){return t(e.top)&&t(e.bottom)&&r(e.left)&&r(e.right)}},At={direction:"vertical",line:"y",crossAxisLine:"x",start:"top",end:"bottom",size:"height",crossAxisStart:"left",crossAxisEnd:"right",crossAxisSize:"width"},Rt={direction:"horizontal",line:"x",crossAxisLine:"y",start:"left",end:"right",size:"width",crossAxisStart:"top",crossAxisEnd:"bottom",crossAxisSize:"height"},Nt=function(e){var t=e.target,r=e.destination,n=e.viewport,i=e.withDroppableDisplacement,o=e.isVisibleThroughFrameFn,a=i?function(e,t){var r=t.frame?t.frame.scroll.diff.displacement:Fe;return nt(e,r)}(t,r):t;return function(e,t,r){return!!t.subject.active&&r(t.subject.active)(e)}(a,r,o)&&function(e,t,r){return r(t)(e)}(a,n,o)},Bt=function(e){return Nt(l({},e,{isVisibleThroughFrameFn:Pt}))},Tt=function(e){return Nt(l({},e,{isVisibleThroughFrameFn:Ot}))};function Mt(e){var t=e.afterDragging,r=e.destination,n=e.displacedBy,i=e.viewport,o=e.forceShouldAnimate,a=e.last;return t.reduce((function(e,t){var l=function(e,t){var r=e.page.marginBox,n={top:t.point.y,right:0,bottom:0,left:t.point.x};return $e(Ye(r,n))}(t,n),u=t.descriptor.id;if(e.all.push(u),!Bt({target:l,destination:r,viewport:i,withDroppableDisplacement:!0}))return e.invisible[t.descriptor.id]=!0,e;var c={draggableId:u,shouldAnimate:function(e,t,r){if("boolean"==typeof r)return r;if(!t)return!0;var n=t.invisible,i=t.visible;if(n[e])return!1;var o=i[e];return!o||o.shouldAnimate}(u,a,o)};return e.visible[u]=c,e}),{all:[],visible:{},invisible:{}})}function Lt(e){var t=e.insideDestination,r=e.inHomeList,n=e.displacedBy,i=e.destination,o=function(e,t){if(!e.length)return 0;var r=e[e.length-1].descriptor.index;return t.inHomeList?r:r+1}(t,{inHomeList:r});return{displaced:Et,displacedBy:n,at:{type:"REORDER",destination:{droppableId:i.descriptor.id,index:o}}}}function Gt(e){var t=e.draggable,r=e.insideDestination,n=e.destination,i=e.viewport,o=e.displacedBy,a=e.last,l=e.index,u=e.forceShouldAnimate,c=Dt(t,n);if(null==l)return Lt({insideDestination:r,inHomeList:c,displacedBy:o,destination:n});var s=pt(r,(function(e){return e.descriptor.index===l}));if(!s)return Lt({insideDestination:r,inHomeList:c,displacedBy:o,destination:n});var d=It(t,r),p=r.indexOf(s);return{displaced:Mt({afterDragging:d.slice(p),destination:n,displacedBy:o,last:a,viewport:i.frame,forceShouldAnimate:u}),displacedBy:o,at:{type:"REORDER",destination:{droppableId:n.descriptor.id,index:l}}}}function _t(e,t){return Boolean(t.effected[e])}var Ft=function(e){var t=e.isMovingForward,r=e.isInHomeList,n=e.draggable,i=e.draggables,o=e.destination,a=e.insideDestination,l=e.previousImpact,u=e.viewport,c=e.afterCritical,d=l.at;if(d||s(!1),"REORDER"===d.type){var p=function(e){var t=e.isMovingForward,r=e.isInHomeList,n=e.insideDestination,i=e.location;if(!n.length)return null;var o=i.index,a=t?o+1:o-1,l=n[0].descriptor.index,u=n[n.length-1].descriptor.index;return a(r?u:u+1)?null:a}({isMovingForward:t,isInHomeList:r,location:d.destination,insideDestination:a});return null==p?null:Gt({draggable:n,insideDestination:a,destination:o,viewport:u,last:l.displaced,displacedBy:l.displacedBy,index:p})}var f=function(e){var t=e.isMovingForward,r=e.destination,n=e.draggables,i=e.combine,o=e.afterCritical;if(!r.isCombineEnabled)return null;var a=i.draggableId,l=n[a].descriptor.index;return _t(a,o)?t?l:l-1:t?l+1:l}({isMovingForward:t,destination:o,displaced:l.displaced,draggables:i,combine:d.combine,afterCritical:c});return null==f?null:Gt({draggable:n,insideDestination:a,destination:o,viewport:u,last:l.displaced,displacedBy:l.displacedBy,index:f})},jt=function(e){var t=e.afterCritical,r=e.impact,n=e.draggables,i=xt(r);i||s(!1);var o=i.draggableId,a=n[o].page.borderBox.center,l=function(e){var t=e.displaced,r=e.afterCritical,n=e.combineWith,i=e.displacedBy,o=Boolean(t.visible[n]||t.invisible[n]);return _t(n,r)?o?Fe:Ue(i.point):o?i.point:Fe}({displaced:r.displaced,afterCritical:t,combineWith:o,displacedBy:r.displacedBy});return je(a,l)},kt=function(e,t){return t.margin[e.start]+t.borderBox[e.size]/2},Wt=function(e,t,r){return t[e.crossAxisStart]+r.margin[e.crossAxisStart]+r.borderBox[e.crossAxisSize]/2},Ut=function(e){var t=e.axis,r=e.moveRelativeTo,n=e.isMoving;return He(t.line,r.marginBox[t.end]+kt(t,n),Wt(t,r.marginBox,n))},Ht=function(e){var t=e.axis,r=e.moveRelativeTo,n=e.isMoving;return He(t.line,r.marginBox[t.start]-function(e,t){return t.margin[e.end]+t.borderBox[e.size]/2}(t,n),Wt(t,r.marginBox,n))},qt=function(e){var t=e.impact,r=e.draggable,n=e.draggables,i=e.droppable,o=e.afterCritical,a=ht(i.descriptor.id,n),l=r.page,u=i.axis;if(!a.length)return function(e){var t=e.axis,r=e.moveInto,n=e.isMoving;return He(t.line,r.contentBox[t.start]+kt(t,n),Wt(t,r.contentBox,n))}({axis:u,moveInto:i.page,isMoving:l});var c=t.displaced,s=t.displacedBy,d=c.all[0];if(d){var p=n[d];if(_t(d,o))return Ht({axis:u,moveRelativeTo:p.page,isMoving:l});var f=Ze(p.page,s.point);return Ht({axis:u,moveRelativeTo:f,isMoving:l})}var g=a[a.length-1];if(g.descriptor.id===r.descriptor.id)return l.borderBox.center;if(_t(g.descriptor.id,o)){var v=Ze(g.page,Ue(o.displacedBy.point));return Ut({axis:u,moveRelativeTo:v,isMoving:l})}return Ut({axis:u,moveRelativeTo:g.page,isMoving:l})},Vt=function(e,t){var r=e.frame;return r?je(t,r.scroll.diff.displacement):t},zt=function(e){var t=function(e){var t=e.impact,r=e.draggable,n=e.droppable,i=e.draggables,o=e.afterCritical,a=r.page.borderBox.center,l=t.at;return n&&l?"REORDER"===l.type?qt({impact:t,draggable:r,draggables:i,droppable:n,afterCritical:o}):jt({impact:t,draggables:i,afterCritical:o}):a}(e),r=e.droppable;return r?Vt(r,t):t},$t=function(e,t){var r=ke(t,e.scroll.initial),n=Ue(r);return{frame:$e({top:t.y,bottom:t.y+e.frame.height,left:t.x,right:t.x+e.frame.width}),scroll:{initial:e.scroll.initial,max:e.scroll.max,current:t,diff:{value:r,displacement:n}}}};function Yt(e,t){return e.map((function(e){return t[e]}))}var Jt=function(e){var t=e.pageBorderBoxCenter,r=e.draggable,n=function(e,t){return je(e.scroll.diff.displacement,t)}(e.viewport,t),i=ke(n,r.page.borderBox.center);return je(r.client.borderBox.center,i)},Xt=function(e){var t=e.draggable,r=e.destination,n=e.newPageBorderBoxCenter,i=e.viewport,o=e.withDroppableDisplacement,a=e.onlyOnMainAxis,u=void 0!==a&&a,c=ke(n,t.page.borderBox.center),s={target:nt(t.page.borderBox,c),destination:r,withDroppableDisplacement:o,viewport:i};return u?function(e){return Nt(l({},e,{isVisibleThroughFrameFn:(t=e.destination.axis,function(e){var r=St(e.top,e.bottom),n=St(e.left,e.right);return function(e){return t===At?r(e.top)&&r(e.bottom):n(e.left)&&n(e.right)}})}));var t}(s):Tt(s)},Kt=function(e){var t=e.isMovingForward,r=e.draggable,n=e.destination,i=e.draggables,o=e.previousImpact,a=e.viewport,u=e.previousPageBorderBoxCenter,c=e.previousClientSelection,d=e.afterCritical;if(!n.isEnabled)return null;var p=ht(n.descriptor.id,i),f=Dt(r,n),g=function(e){var t=e.isMovingForward,r=e.draggable,n=e.destination,i=e.insideDestination,o=e.previousImpact;if(!n.isCombineEnabled)return null;if(!yt(o))return null;function a(e){var t={type:"COMBINE",combine:{draggableId:e,droppableId:n.descriptor.id}};return l({},o,{at:t})}var u=o.displaced.all,c=u.length?u[0]:null;if(t)return c?a(c):null;var d=It(r,i);if(!c)return d.length?a(d[d.length-1].descriptor.id):null;var p=dt(d,(function(e){return e.descriptor.id===c}));-1===p&&s(!1);var f=p-1;return f<0?null:a(d[f].descriptor.id)}({isMovingForward:t,draggable:r,destination:n,insideDestination:p,previousImpact:o})||Ft({isMovingForward:t,isInHomeList:f,draggable:r,draggables:i,destination:n,insideDestination:p,previousImpact:o,viewport:a,afterCritical:d});if(!g)return null;var v=zt({impact:g,draggable:r,droppable:n,draggables:i,afterCritical:d});if(Xt({draggable:r,destination:n,newPageBorderBoxCenter:v,viewport:a.frame,withDroppableDisplacement:!1,onlyOnMainAxis:!0}))return{clientSelection:Jt({pageBorderBoxCenter:v,draggable:r,viewport:a}),impact:g,scrollJumpRequest:null};var m=ke(v,u);return{clientSelection:c,impact:function(e){var t=e.impact,r=e.viewport,n=e.destination,i=e.draggables,o=e.maxScrollChange,a=$t(r,je(r.scroll.current,o)),u=n.frame?lt(n,je(n.frame.scroll.current,o)):n,c=t.displaced,s=Mt({afterDragging:Yt(c.all,i),destination:n,displacedBy:t.displacedBy,viewport:a.frame,last:c,forceShouldAnimate:!1}),d=Mt({afterDragging:Yt(c.all,i),destination:u,displacedBy:t.displacedBy,viewport:r.frame,last:c,forceShouldAnimate:!1}),p={},f={},g=[c,s,d];return c.all.forEach((function(e){var t=function(e,t){for(var r=0;r1?s.sort((function(e,t){return Qt(e)[l.start]-Qt(t)[l.start]}))[0]:c.sort((function(e,t){var n=Ve(r,it(Qt(e))),i=Ve(r,it(Qt(t)));return n!==i?n-i:Qt(e)[l.start]-Qt(t)[l.start]}))[0]}({isMovingForward:t,pageBorderBoxCenter:r,source:i,droppables:a,viewport:l});if(!c)return null;var s=ht(c.descriptor.id,o),d=function(e){var t=e.previousPageBorderBoxCenter,r=e.moveRelativeTo,n=e.insideDestination,i=e.draggable,o=e.draggables,a=e.destination,l=e.viewport,u=e.afterCritical;if(!r){if(n.length)return null;var c={displaced:Et,displacedBy:wt,at:{type:"REORDER",destination:{droppableId:a.descriptor.id,index:0}}},s=zt({impact:c,draggable:i,droppable:a,draggables:o,afterCritical:u}),d=Dt(i,a)?a:nr(a,i,o);return Xt({draggable:i,destination:d,newPageBorderBoxCenter:s,viewport:l.frame,withDroppableDisplacement:!1,onlyOnMainAxis:!0})?c:null}var p,f=Boolean(t[a.axis.line]<=r.page.borderBox.center[a.axis.line]),g=(p=r.descriptor.index,r.descriptor.id===i.descriptor.id?p:f?p:p+1),v=tr(a.axis,i.displaceBy);return Gt({draggable:i,insideDestination:n,destination:a,viewport:l,displacedBy:v,last:Et,index:g})}({previousPageBorderBoxCenter:r,destination:c,draggable:n,draggables:o,moveRelativeTo:function(e){var t=e.pageBorderBoxCenter,r=e.viewport,n=e.destination,i=e.insideDestination,o=e.afterCritical;return i.filter((function(e){return Tt({target:er(e,o),destination:n,viewport:r.frame,withDroppableDisplacement:!0})})).sort((function(e,r){var i=qe(t,Vt(n,Zt(e,o))),a=qe(t,Vt(n,Zt(r,o)));return in.left&&r.topn.top))return!1;if(ur(i)(t.center))return!0;var o=e.axis,a=i.center[o.crossAxisLine],l=t[o.crossAxisStart],u=t[o.crossAxisEnd],c=St(i[o.crossAxisStart],i[o.crossAxisEnd]),s=c(l),d=c(u);return!s&&!d||(s?la)}));return i.length?1===i.length?i[0].descriptor.id:function(e){var t=e.pageBorderBox,r=e.draggable,n=e.candidates,i=r.page.borderBox.center,o=n.map((function(e){var r=e.axis,n=He(e.axis.line,t.center[r.line],e.page.borderBox.center[r.crossAxisLine]);return{id:e.descriptor.id,distance:qe(i,n)}})).sort((function(e,t){return t.distance-e.distance}));return o[0]?o[0].id:null}({pageBorderBox:t,draggable:r,candidates:i}):null}var sr=function(e,t){return $e(nt(e,t))};function dr(e){var t=e.displaced,r=e.id;return Boolean(t.visible[r]||t.invisible[r])}var pr=function(e){var t=e.pageOffset,r=e.draggable,n=e.draggables,i=e.droppables,o=e.previousImpact,a=e.viewport,l=e.afterCritical,u=sr(r.page.borderBox,t),c=cr({pageBorderBox:u,draggable:r,droppables:i});if(!c)return Ct;var s=i[c],d=ht(s.descriptor.id,n),p=function(e,t){var r=e.frame;return r?sr(t,r.scroll.diff.value):t}(s,u);return function(e){var t=e.draggable,r=e.pageBorderBoxWithDroppableScroll,n=e.previousImpact,i=e.destination,o=e.insideDestination,a=e.afterCritical;if(!i.isCombineEnabled)return null;var l=i.axis,u=tr(i.axis,t.displaceBy),c=u.value,s=r[l.start],d=r[l.end],p=pt(It(t,o),(function(e){var t=e.descriptor.id,r=e.page.borderBox,i=r[l.size]/4,o=_t(t,a),u=dr({displaced:n.displaced,id:t});return o?u?d>r[l.start]+i&&dr[l.start]-c+i&&sr[l.start]+c+i&&dr[l.start]+i&&st.descriptor.index?r.descriptor.index-1:r.descriptor.index:null}({draggable:r,closest:pt(It(r,i),(function(e){var t=e.descriptor.id,r=e.page.borderBox.center[u.line],n=_t(t,l),i=dr({displaced:o,id:t});return n?i?p<=r:d=1500)return Yr;var o=$r+Jr*(i/1500);return Number(("CANCEL"===n?.6*o:o).toFixed(2))}({current:i.current.client.offset,destination:h,reason:o});r(function(e){return{type:"DROP_ANIMATE",payload:e}}({newHomeClientOffset:h,dropDuration:x,completed:y}))}else r(Gr({completed:y}))}}else r(function(e){return{type:"DROP_PENDING",payload:e}}({reason:o}))}else e(n)}}},Kr=function(e){var t=[],r=null,n=function(){for(var n=arguments.length,i=new Array(n),o=0;ot.startScrollingFrom)return 0;if(e<=t.maxScrollValueAt)return Sn;if(e===t.startScrollingFrom)return 1;var r=An({startOfRange:t.maxScrollValueAt,endOfRange:t.startScrollingFrom,current:e}),n=Sn*Pn(1-r);return Math.ceil(n)}(t,r);return 0===o?0:i?Math.max(function(e,t){var r=t,n=Nn,i=Date.now()-r;if(i>=Nn)return e;if(it.height,o=r.width>t.width;return o||i?o&&i?null:{x:o?0:n.x,y:i?0:n.y}:n}({container:r,subject:n,proposedScroll:c});return s?We(s,Fe)?null:s:null},Gn=ze((function(e){return 0===e?0:e>0?1:-1})),_n=(bn=function(e,t){return e<0?e:e>t?e-t:0},function(e){var t=e.current,r=e.max,n=e.change,i=je(t,n),o={x:bn(i.x,r.x),y:bn(i.y,r.y)};return We(o,Fe)?null:o}),Fn=function(e){var t=e.max,r=e.current,n=e.change,i={x:Math.max(r.x,t.x),y:Math.max(r.y,t.y)},o=Gn(n),a=_n({max:i,current:r,change:o});return!a||(0!==o.x&&0===a.x||0!==o.y&&0===a.y)},jn=function(e,t){return Fn({current:e.scroll.current,max:e.scroll.max,change:t})},kn=function(e,t){var r=e.frame;return!!r&&Fn({current:r.scroll.current,max:r.scroll.max,change:t})},Wn=function(e){var t=e.state,r=e.dragStartTime,n=e.shouldUseTimeDampening,i=e.scrollWindow,o=e.scrollDroppable,a=t.current.page.borderBoxCenter,l=t.dimensions.draggables[t.critical.draggable.id].page.marginBox;if(t.isWindowScrollAllowed){var u=function(e){var t=e.viewport,r=e.subject,n=e.center,i=e.dragStartTime,o=e.shouldUseTimeDampening,a=Ln({dragStartTime:i,container:t.frame,subject:r,center:n,shouldUseTimeDampening:o});return a&&jn(t,a)?a:null}({dragStartTime:r,viewport:t.viewport,subject:l,center:a,shouldUseTimeDampening:n});if(u)return void i(u)}var c=wn({center:a,destination:or(t.impact),droppables:t.dimensions.droppables});if(c){var s=function(e){var t=e.droppable,r=e.subject,n=e.center,i=e.dragStartTime,o=e.shouldUseTimeDampening,a=t.frame;if(!a)return null;var l=Ln({dragStartTime:i,container:a.pageMarginBox,subject:r,center:n,shouldUseTimeDampening:o});return l&&kn(t,l)?l:null}({dragStartTime:r,droppable:c,subject:l,center:a,shouldUseTimeDampening:n});s&&o(c.descriptor.id,s)}},Un=function(e){var t=e.move,r=e.scrollDroppable,n=e.scrollWindow,i=function(e,t){if(!kn(e,t))return t;var n=function(e,t){var r=e.frame;return r&&kn(e,t)?_n({current:r.scroll.current,max:r.scroll.max,change:t}):null}(e,t);if(!n)return r(e.descriptor.id,t),null;var i=ke(t,n);return r(e.descriptor.id,i),ke(t,i)},o=function(e,t,r){if(!e)return r;if(!jn(t,r))return r;var i=function(e,t){if(!jn(e,t))return null;var r=e.scroll.max,n=e.scroll.current;return _n({current:n,max:r,change:t})}(t,r);if(!i)return n(r),null;var o=ke(r,i);return n(o),ke(r,o)};return function(e){var r=e.scrollJumpRequest;if(r){var n=or(e.impact);n||s(!1);var a=i(e.dimensions.droppables[n],r);if(a){var l=e.viewport,u=o(e.isWindowScrollAllowed,l,a);u&&function(e,r){var n=je(e.current.client.selection,r);t({client:n})}(e,u)}}}},Hn=function(e){var t=e.scrollDroppable,r=e.scrollWindow,n=e.move,i=function(e){var t=e.scrollWindow,r=e.scrollDroppable,n=Kr(t),i=Kr(r),o=null,a=function(e){o||s(!1);var t=o,r=t.shouldUseTimeDampening,a=t.dragStartTime;Wn({state:e,scrollWindow:n,scrollDroppable:i,dragStartTime:a,shouldUseTimeDampening:r})};return{start:function(e){o&&s(!1);var t=Date.now(),r=!1,n=function(){r=!0};Wn({state:e,dragStartTime:0,shouldUseTimeDampening:!1,scrollWindow:n,scrollDroppable:n}),o={dragStartTime:t,shouldUseTimeDampening:r},r&&a(e)},stop:function(){o&&(n.cancel(),i.cancel(),o=null)},scroll:a}}({scrollWindow:r,scrollDroppable:t}),o=Un({move:n,scrollWindow:r,scrollDroppable:t});return{scroll:function(e){"DRAGGING"===e.phase&&("FLUID"!==e.movementMode?e.scrollJumpRequest&&o(e):i.scroll(e))},start:i.start,stop:i.stop}},qn={base:hn="data-rbd-drag-handle",draggableId:hn+"-draggable-id",contextId:hn+"-context-id"},Vn=function(){var e="data-rbd-draggable";return{base:e,contextId:e+"-context-id",id:e+"-id"}}(),zn=function(){var e="data-rbd-droppable";return{base:e,contextId:e+"-context-id",id:e+"-id"}}(),$n={contextId:"data-rbd-scroll-container-context-id"},Yn=function(e,t){return e.map((function(e){var r=e.styles[t];return r?e.selector+" { "+r+" }":""})).join(" ")},Jn="undefined"!=typeof window&&void 0!==window.document&&void 0!==window.document.createElement?t.useLayoutEffect:t.useEffect,Xn=function(){var e=document.querySelector("head");return e||s(!1),e},Kn=function(e){var t=document.createElement("style");return e&&t.setAttribute("nonce",e),t.type="text/css",t};function Qn(e,r){var n=Ge((function(){return function(e){var t,r,n,i=(t=e,function(e){return"["+e+'="'+t+'"]'}),o=(r="\n cursor: -webkit-grab;\n cursor: grab;\n ",{selector:i(qn.contextId),styles:{always:"\n -webkit-touch-callout: none;\n -webkit-tap-highlight-color: rgba(0,0,0,0);\n touch-action: manipulation;\n ",resting:r,dragging:"pointer-events: none;",dropAnimating:r}}),a=[(n="\n transition: "+Hr.outOfTheWay+";\n ",{selector:i(Vn.contextId),styles:{dragging:n,dropAnimating:n,userCancel:n}}),o,{selector:i(zn.contextId),styles:{always:"overflow-anchor: none;"}},{selector:"body",styles:{dragging:"\n cursor: grabbing;\n cursor: -webkit-grabbing;\n user-select: none;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n overflow-anchor: none;\n "}}];return{always:Yn(a,"always"),resting:Yn(a,"resting"),dragging:Yn(a,"dragging"),dropAnimating:Yn(a,"dropAnimating"),userCancel:Yn(a,"userCancel")}}(e)}),[e]),i=t.useRef(null),o=t.useRef(null),a=_e(ct((function(e){var t=o.current;t||s(!1),t.textContent=e})),[]),l=_e((function(e){var t=i.current;t||s(!1),t.textContent=e}),[]);Jn((function(){(i.current||o.current)&&s(!1);var t=Kn(r),u=Kn(r);return i.current=t,o.current=u,t.setAttribute("data-rbd-always",e),u.setAttribute("data-rbd-dynamic",e),Xn().appendChild(t),Xn().appendChild(u),l(n.always),a(n.resting),function(){var e=function(e){var t=e.current;t||s(!1),Xn().removeChild(t),e.current=null};e(i),e(o)}}),[r,l,a,n.always,n.resting,e]);var u=_e((function(){return a(n.dragging)}),[a,n.dragging]),c=_e((function(e){a("DROP"!==e?n.userCancel:n.dropAnimating)}),[a,n.dropAnimating,n.userCancel]),d=_e((function(){o.current&&a(n.resting)}),[a,n.resting]);return Ge((function(){return{dragging:u,dropping:c,resting:d}}),[u,c,d])}var Zn=function(e){return e&&e.ownerDocument?e.ownerDocument.defaultView:window};function ei(e){return e instanceof Zn(e).HTMLElement}function ti(e){var r=t.useRef({}),n=t.useRef(null),i=t.useRef(null),o=t.useRef(!1),a=_e((function(e,t){var n={id:e,focus:t};return r.current[e]=n,function(){var t=r.current;t[e]!==n&&delete t[e]}}),[]),l=_e((function(t){var r=function(e,t){var r="["+qn.contextId+'="'+e+'"]',n=ft(document.querySelectorAll(r));if(!n.length)return null;var i=pt(n,(function(e){return e.getAttribute(qn.draggableId)===t}));return i&&ei(i)?i:null}(e,t);r&&r!==document.activeElement&&r.focus()}),[e]),u=_e((function(e,t){n.current===e&&(n.current=t)}),[]),c=_e((function(){i.current||o.current&&(i.current=requestAnimationFrame((function(){i.current=null;var e=n.current;e&&l(e)})))}),[l]),s=_e((function(e){n.current=null;var t=document.activeElement;t&&t.getAttribute(qn.draggableId)===e&&(n.current=e)}),[]);return Jn((function(){return o.current=!0,function(){o.current=!1;var e=i.current;e&&cancelAnimationFrame(e)}}),[]),Ge((function(){return{register:a,tryRecordFocus:s,tryRestoreFocusRecorded:c,tryShiftRecord:u}}),[a,s,c,u])}function ri(){var e={draggables:{},droppables:{}},t=[];function r(e){t.length&&t.forEach((function(t){return t(e)}))}function n(t){return e.draggables[t]||null}function i(t){return e.droppables[t]||null}return{draggable:{register:function(t){e.draggables[t.descriptor.id]=t,r({type:"ADDITION",value:t})},update:function(t,r){var n=e.draggables[r.descriptor.id];n&&n.uniqueId===t.uniqueId&&(delete e.draggables[r.descriptor.id],e.draggables[t.descriptor.id]=t)},unregister:function(t){var i=t.descriptor.id,o=n(i);o&&t.uniqueId===o.uniqueId&&(delete e.draggables[i],r({type:"REMOVAL",value:t}))},getById:function(e){var t=n(e);return t||s(!1),t},findById:n,exists:function(e){return Boolean(n(e))},getAllByType:function(t){return st(e.draggables).filter((function(e){return e.descriptor.type===t}))}},droppable:{register:function(t){e.droppables[t.descriptor.id]=t},unregister:function(t){var r=i(t.descriptor.id);r&&t.uniqueId===r.uniqueId&&delete e.droppables[t.descriptor.id]},getById:function(e){var t=i(e);return t||s(!1),t},findById:i,exists:function(e){return Boolean(i(e))},getAllByType:function(t){return st(e.droppables).filter((function(e){return e.descriptor.type===t}))}},subscribe:function(e){return t.push(e),function(){var r=t.indexOf(e);-1!==r&&t.splice(r,1)}},clean:function(){e.draggables={},e.droppables={},t.length=0}}}var ni=n.createContext(null),ii=function(){var e=document.body;return e||s(!1),e},oi={position:"absolute",width:"1px",height:"1px",margin:"-1px",border:"0",padding:"0",overflow:"hidden",clip:"rect(0 0 0 0)","clip-path":"inset(100%)"};var ai=0,li={separator:"::"};function ui(e,t){return void 0===t&&(t=li),Ge((function(){return""+e+t.separator+ai++}),[t.separator,e])}var ci=n.createContext(null);function si(e){var r=t.useRef(e);return t.useEffect((function(){r.current=e})),r}var di,pi=((di={})[13]=!0,di[9]=!0,di),fi=function(e){pi[e.keyCode]&&e.preventDefault()},gi=function(){var e="visibilitychange";return"undefined"==typeof document?e:pt([e,"ms"+e,"webkit"+e,"moz"+e,"o"+e],(function(e){return"on"+e in document}))||e}();var vi,mi={type:"IDLE"};function bi(e){var t=e.cancel,r=e.completed,n=e.getPhase,i=e.setPhase;return[{eventName:"mousemove",fn:function(e){var t=e.button,r=e.clientX,o=e.clientY;if(0===t){var a={x:r,y:o},l=n();if("DRAGGING"===l.type)return e.preventDefault(),void l.actions.move(a);"PENDING"!==l.type&&s(!1);var u=l.point;if(c=u,d=a,Math.abs(d.x-c.x)>=5||Math.abs(d.y-c.y)>=5){var c,d;e.preventDefault();var p=l.actions.fluidLift(a);i({type:"DRAGGING",actions:p})}}}},{eventName:"mouseup",fn:function(e){var i=n();"DRAGGING"===i.type?(e.preventDefault(),i.actions.drop({shouldBlockNextClick:!0}),r()):t()}},{eventName:"mousedown",fn:function(e){"DRAGGING"===n().type&&e.preventDefault(),t()}},{eventName:"keydown",fn:function(e){if("PENDING"!==n().type)return 27===e.keyCode?(e.preventDefault(),void t()):void fi(e);t()}},{eventName:"resize",fn:t},{eventName:"scroll",options:{passive:!0,capture:!1},fn:function(){"PENDING"===n().type&&t()}},{eventName:"webkitmouseforcedown",fn:function(e){var r=n();"IDLE"===r.type&&s(!1),r.actions.shouldRespectForcePress()?t():e.preventDefault()}},{eventName:gi,fn:t}]}function hi(e){var r=t.useRef(mi),n=t.useRef(a),i=Ge((function(){return{eventName:"mousedown",fn:function(t){if(!t.defaultPrevented&&0===t.button&&!(t.ctrlKey||t.metaKey||t.shiftKey||t.altKey)){var r=e.findClosestDraggableId(t);if(r){var i=e.tryGetLock(r,c,{sourceEvent:t});if(i){t.preventDefault();var o={x:t.clientX,y:t.clientY};n.current(),f(i,o)}}}}}}),[e]),o=Ge((function(){return{eventName:"webkitmouseforcewillbegin",fn:function(t){if(!t.defaultPrevented){var r=e.findClosestDraggableId(t);if(r){var n=e.findOptionsForDraggable(r);n&&(n.shouldRespectForcePress||e.canGetLock(r)&&t.preventDefault())}}}}}),[e]),l=_e((function(){n.current=u(window,[o,i],{passive:!1,capture:!0})}),[o,i]),c=_e((function(){"IDLE"!==r.current.type&&(r.current=mi,n.current(),l())}),[l]),d=_e((function(){var e=r.current;c(),"DRAGGING"===e.type&&e.actions.cancel({shouldBlockNextClick:!0}),"PENDING"===e.type&&e.actions.abort()}),[c]),p=_e((function(){var e=bi({cancel:d,completed:c,getPhase:function(){return r.current},setPhase:function(e){r.current=e}});n.current=u(window,e,{capture:!0,passive:!1})}),[d,c]),f=_e((function(e,t){"IDLE"!==r.current.type&&s(!1),r.current={type:"PENDING",point:t,actions:e},p()}),[p]);Jn((function(){return l(),function(){n.current()}}),[l])}function yi(){}var xi=((vi={})[34]=!0,vi[33]=!0,vi[36]=!0,vi[35]=!0,vi);function Ii(e,t){function r(){t(),e.cancel()}return[{eventName:"keydown",fn:function(n){return 27===n.keyCode?(n.preventDefault(),void r()):32===n.keyCode?(n.preventDefault(),t(),void e.drop()):40===n.keyCode?(n.preventDefault(),void e.moveDown()):38===n.keyCode?(n.preventDefault(),void e.moveUp()):39===n.keyCode?(n.preventDefault(),void e.moveRight()):37===n.keyCode?(n.preventDefault(),void e.moveLeft()):void(xi[n.keyCode]?n.preventDefault():fi(n))}},{eventName:"mousedown",fn:r},{eventName:"mouseup",fn:r},{eventName:"click",fn:r},{eventName:"touchstart",fn:r},{eventName:"resize",fn:r},{eventName:"wheel",fn:r,options:{passive:!0}},{eventName:gi,fn:r}]}function Di(e){var r=t.useRef(yi),n=Ge((function(){return{eventName:"keydown",fn:function(t){if(!t.defaultPrevented&&32===t.keyCode){var n=e.findClosestDraggableId(t);if(n){var o=e.tryGetLock(n,c,{sourceEvent:t});if(o){t.preventDefault();var a=!0,l=o.snapLift();r.current(),r.current=u(window,Ii(l,c),{capture:!0,passive:!1})}}}function c(){a||s(!1),a=!1,r.current(),i()}}}}),[e]),i=_e((function(){r.current=u(window,[n],{passive:!1,capture:!0})}),[n]);Jn((function(){return i(),function(){r.current()}}),[i])}var wi={type:"IDLE"};function Ei(e){var r=t.useRef(wi),n=t.useRef(a),i=_e((function(){return r.current}),[]),o=_e((function(e){r.current=e}),[]),l=Ge((function(){return{eventName:"touchstart",fn:function(t){if(!t.defaultPrevented){var r=e.findClosestDraggableId(t);if(r){var i=e.tryGetLock(r,d,{sourceEvent:t});if(i){var o=t.touches[0],a={x:o.clientX,y:o.clientY},l=e.findClosestDragHandle(t);l||s(!1),n.current(),v(i,a,l)}}}}}}),[e]),c=_e((function(){n.current=u(window,[l],{capture:!0,passive:!1})}),[l]),d=_e((function(){var e=r.current;"IDLE"!==e.type&&("PENDING"===e.type&&clearTimeout(e.longPressTimerId),o(wi),n.current(),c())}),[c,o]),p=_e((function(){var e=r.current;d(),"DRAGGING"===e.type&&e.actions.cancel({shouldBlockNextClick:!0}),"PENDING"===e.type&&e.actions.abort()}),[d]),f=_e((function(e){var t={capture:!0,passive:!1},r={cancel:p,completed:d,getPhase:i},o=u(e,function(e){var t=e.cancel,r=e.completed,n=e.getPhase;return[{eventName:"touchmove",options:{capture:!1},fn:function(e){var r=n();if("DRAGGING"===r.type){r.hasMoved=!0;var i=e.touches[0],o={x:i.clientX,y:i.clientY};e.preventDefault(),r.actions.move(o)}else t()}},{eventName:"touchend",fn:function(e){var i=n();"DRAGGING"===i.type?(e.preventDefault(),i.actions.drop({shouldBlockNextClick:!0}),r()):t()}},{eventName:"touchcancel",fn:function(e){"DRAGGING"===n().type?(e.preventDefault(),t()):t()}},{eventName:"touchforcechange",fn:function(e){var r=n();"IDLE"===r.type&&s(!1);var i=e.touches[0];if(i&&i.force>=.15){var o=r.actions.shouldRespectForcePress();if("PENDING"!==r.type)return o?r.hasMoved?void e.preventDefault():void t():void e.preventDefault();o&&t()}}},{eventName:gi,fn:t}]}(r),t),a=u(window,function(e){var t=e.cancel,r=e.getPhase;return[{eventName:"orientationchange",fn:t},{eventName:"resize",fn:t},{eventName:"contextmenu",fn:function(e){e.preventDefault()}},{eventName:"keydown",fn:function(e){"DRAGGING"===r().type?(27===e.keyCode&&e.preventDefault(),t()):t()}},{eventName:gi,fn:t}]}(r),t);n.current=function(){o(),a()}}),[p,i,d]),g=_e((function(){var e=i();"PENDING"!==e.type&&s(!1);var t=e.actions.fluidLift(e.point);o({type:"DRAGGING",actions:t,hasMoved:!1})}),[i,o]),v=_e((function(e,t,r){"IDLE"!==i().type&&s(!1);var n=setTimeout(g,120);o({type:"PENDING",point:t,actions:e,longPressTimerId:n}),f(r)}),[f,i,o,g]);Jn((function(){return c(),function(){n.current();var e=i();"PENDING"===e.type&&(clearTimeout(e.longPressTimerId),o(wi))}}),[i,c,o]),Jn((function(){return u(window,[{eventName:"touchmove",fn:function(){},options:{capture:!1,passive:!1}}])}),[])}var Ci={input:!0,button:!0,textarea:!0,select:!0,option:!0,optgroup:!0,video:!0,audio:!0};function Si(e,t){var r=t.target;return!!ei(r)&&function e(t,r){if(null==r)return!1;if(Boolean(Ci[r.tagName.toLowerCase()]))return!0;var n=r.getAttribute("contenteditable");return"true"===n||""===n||r!==t&&e(t,r.parentElement)}(e,r)}var Pi=function(e){return $e(e.getBoundingClientRect()).center};var Oi="undefined"==typeof document?"matches":pt(["matches","msMatchesSelector","webkitMatchesSelector"],(function(e){return e in Element.prototype}))||"matches";function Ai(e,t){return e.closest?e.closest(t):function e(t,r){return null==t?null:t[Oi](r)?t:e(t.parentElement,r)}(e,t)}function Ri(e,t){var r,n=t.target;if(!((r=n)instanceof Zn(r).Element))return null;var i=Ai(n,function(e){return"["+qn.contextId+'="'+e+'"]'}(e));return i&&ei(i)?i:null}function Ni(e){e.preventDefault()}function Bi(e){var t=e.expected,r=e.phase,n=e.isLockActive;e.shouldWarn;return!!n()&&t===r}function Ti(e){var t=e.lockAPI,r=e.store,n=e.registry,i=e.draggableId;if(t.isClaimed())return!1;var o=n.draggable.findById(i);return!!o&&(!!o.options.isEnabled&&!!xn(r.getState(),i))}function Mi(e){var t=e.lockAPI,r=e.contextId,n=e.store,i=e.registry,o=e.draggableId,c=e.forceSensorStop,d=e.sourceEvent;if(!Ti({lockAPI:t,store:n,registry:i,draggableId:o}))return null;var p=i.draggable.getById(o),f=function(e,t){var r="["+Vn.contextId+'="'+e+'"]',n=pt(ft(document.querySelectorAll(r)),(function(e){return e.getAttribute(Vn.id)===t}));return n&&ei(n)?n:null}(r,p.descriptor.id);if(!f)return null;if(d&&!p.options.canDragInteractiveElements&&Si(f,d))return null;var g=t.claim(c||a),v="PRE_DRAG";function m(){return p.options.shouldRespectForcePress}function b(){return t.isActive(g)}var h=function(e,t){Bi({expected:e,phase:v,isLockActive:b,shouldWarn:!0})&&n.dispatch(t())}.bind(null,"DRAGGING");function y(e){function r(){t.release(),v="COMPLETED"}function i(t,i){if(void 0===i&&(i={shouldBlockNextClick:!1}),e.cleanup(),i.shouldBlockNextClick){var o=u(window,[{eventName:"click",fn:Ni,options:{once:!0,passive:!1,capture:!0}}]);setTimeout(o)}r(),n.dispatch(_r({reason:t}))}return"PRE_DRAG"!==v&&(r(),"PRE_DRAG"!==v&&s(!1)),n.dispatch(function(e){return{type:"LIFT",payload:e}}(e.liftActionArgs)),v="DRAGGING",l({isActive:function(){return Bi({expected:"DRAGGING",phase:v,isLockActive:b,shouldWarn:!1})},shouldRespectForcePress:m,drop:function(e){return i("DROP",e)},cancel:function(e){return i("CANCEL",e)}},e.actions)}return{isActive:function(){return Bi({expected:"PRE_DRAG",phase:v,isLockActive:b,shouldWarn:!1})},shouldRespectForcePress:m,fluidLift:function(e){var t=Kr((function(e){h((function(){return Nr({client:e})}))}));return l({},y({liftActionArgs:{id:o,clientSelection:e,movementMode:"FLUID"},cleanup:function(){return t.cancel()},actions:{move:t}}),{move:t})},snapLift:function(){var e={moveUp:function(){return h(Br)},moveRight:function(){return h(Mr)},moveDown:function(){return h(Tr)},moveLeft:function(){return h(Lr)}};return y({liftActionArgs:{id:o,clientSelection:Pi(f),movementMode:"SNAP"},cleanup:a,actions:e})},abort:function(){Bi({expected:"PRE_DRAG",phase:v,isLockActive:b,shouldWarn:!0})&&t.release()}}}var Li=[hi,Di,Ei];function Gi(e){var r=e.contextId,n=e.store,i=e.registry,o=e.customSensors,a=e.enableDefaultSensors,l=[].concat(a?Li:[],o||[]),u=t.useState((function(){return function(){var e=null;function t(){e||s(!1),e=null}return{isClaimed:function(){return Boolean(e)},isActive:function(t){return t===e},claim:function(t){e&&s(!1);var r={abandon:t};return e=r,r},release:t,tryAbandon:function(){e&&(e.abandon(),t())}}}()}))[0],c=_e((function(e,t){e.isDragging&&!t.isDragging&&u.tryAbandon()}),[u]);Jn((function(){var e=n.getState();return n.subscribe((function(){var t=n.getState();c(e,t),e=t}))}),[u,n,c]),Jn((function(){return u.tryAbandon}),[u.tryAbandon]);for(var d=_e((function(e){return Ti({lockAPI:u,registry:i,store:n,draggableId:e})}),[u,i,n]),p=_e((function(e,t,o){return Mi({lockAPI:u,registry:i,contextId:r,store:n,draggableId:e,forceSensorStop:t,sourceEvent:o&&o.sourceEvent?o.sourceEvent:null})}),[r,u,i,n]),f=_e((function(e){return Ri(r,e)}),[r]),g=_e((function(e){return function(e,t){var r=Ri(e,t);return r?r.getAttribute(qn.draggableId):null}(r,e)}),[r]),v=_e((function(e){var t=i.draggable.findById(e);return t?t.options:null}),[i.draggable]),m=_e((function(){u.isClaimed()&&(u.tryAbandon(),"IDLE"!==n.getState().phase&&n.dispatch({type:"FLUSH",payload:null}))}),[u,n]),b=_e(u.isClaimed,[u]),h=Ge((function(){return{canGetLock:d,tryGetLock:p,findClosestDraggableId:g,findClosestDragHandle:f,findOptionsForDraggable:v,tryReleaseLock:m,isLockClaimed:b}}),[d,p,g,f,v,m,b]),y=0;y /dev/null then diff --git a/scripts/shellUtils.sh b/scripts/shellUtils.sh index 4c9e2febc34d..876933af9766 100644 --- a/scripts/shellUtils.sh +++ b/scripts/shellUtils.sh @@ -41,46 +41,3 @@ function join_by_string { shift printf "%s" "$first" "${@/#/$separator}" } - -# Usage: get_abs_path -# Will make a path absolute, resolving any relative paths -# example: get_abs_path "./foo/bar" -get_abs_path() { - local the_path=$1 - local -a path_elements - IFS='/' read -ra path_elements <<< "$the_path" - - # If the path is already absolute, start with an empty string. - # We'll prepend the / later when reconstructing the path. - if [[ "$the_path" = /* ]]; then - abs_path="" - else - abs_path="$(pwd)" - fi - - # Handle each path element - for element in "${path_elements[@]}"; do - if [ "$element" = "." ] || [ -z "$element" ]; then - continue - elif [ "$element" = ".." ]; then - # Remove the last element from abs_path - abs_path=$(dirname "$abs_path") - else - # Append element to the absolute path - abs_path="${abs_path}/${element}" - fi - done - - # Remove any trailing '/' - while [[ $abs_path == */ ]]; do - abs_path=${abs_path%/} - done - - # Special case for root - [ -z "$abs_path" ] && abs_path="/" - - # Special case to remove any starting '//' when the input path was absolute - abs_path=${abs_path/#\/\//\/} - - echo "$abs_path" -} \ No newline at end of file diff --git a/src/App.js b/src/App.js index 1d2e07345c24..284c6115d7b8 100644 --- a/src/App.js +++ b/src/App.js @@ -9,7 +9,7 @@ import {PickerStateProvider} from 'react-native-picker-select'; import CustomStatusBar from './components/CustomStatusBar'; import ErrorBoundary from './components/ErrorBoundary'; import Expensify from './Expensify'; -import {LocaleContextProvider} from './components/LocaleContextProvider'; +import {LocaleContextProvider} from './components/withLocalize'; import OnyxProvider from './components/OnyxProvider'; import HTMLEngineProvider from './components/HTMLEngineProvider'; import PopoverContextProvider from './components/PopoverProvider'; diff --git a/src/CONST.ts b/src/CONST.ts index e98a42c973cf..4d216285bc50 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -440,12 +440,6 @@ const CONST = { INTERNAL_DEV_EXPENSIFY_URL: 'https://www.expensify.com.dev', STAGING_EXPENSIFY_URL: 'https://staging.expensify.com', EXPENSIFY_URL: 'https://www.expensify.com', - BANK_ACCOUNT_PERSONAL_DOCUMENTATION_INFO_URL: - 'https://community.expensify.com/discussion/6983/faq-why-do-i-need-to-provide-personal-documentation-when-setting-up-updating-my-bank-account', - PERSONAL_DATA_PROTECTION_INFO_URL: 'https://community.expensify.com/discussion/5677/deep-dive-security-how-expensify-protects-your-information', - ONFIDO_FACIAL_SCAN_POLICY_URL: 'https://onfido.com/facial-scan-policy-and-release/', - ONFIDO_PRIVACY_POLICY_URL: 'https://onfido.com/privacy/', - ONFIDO_TERMS_OF_SERVICE_URL: 'https://onfido.com/terms-of-service/', // Use Environment.getEnvironmentURL to get the complete URL with port number DEV_NEW_EXPENSIFY_URL: 'http://localhost:', @@ -737,7 +731,6 @@ const CONST = { MAX_RETRY_WAIT_TIME_MS: 10 * 1000, PROCESS_REQUEST_DELAY_MS: 1000, MAX_PENDING_TIME_MS: 10 * 1000, - MAX_REQUEST_RETRIES: 10, }, DEFAULT_TIME_ZONE: {automatic: true, selected: 'America/Los_Angeles'}, DEFAULT_ACCOUNT_DATA: {errors: null, success: '', isLoading: false}, @@ -989,15 +982,6 @@ const CONST = { GOLD: 'GOLD', SILVER: 'SILVER', }, - WEB_MESSAGE_TYPE: { - STATEMENT: 'STATEMENT_NAVIGATE', - CONCIERGE: 'CONCIERGE_NAVIGATE', - }, - MTL_WALLET_PROGRAM_ID: '760', - PROGRAM_ISSUERS: { - EXPENSIFY_PAYMENTS: 'Expensify Payments LLC', - BANCORP_BANK: 'The Bancorp Bank', - }, }, PLAID: { @@ -1238,7 +1222,6 @@ const CONST = { EMOJI_NAME: /:[\w+-]+:/g, EMOJI_SUGGESTIONS: /:[a-zA-Z0-9_+-]{1,40}$/, AFTER_FIRST_LINE_BREAK: /\n.*/g, - LINE_BREAK: /\n/g, CODE_2FA: /^\d{6}$/, ATTACHMENT_ID: /chat-attachments\/(\d+)/, HAS_COLON_ONLY_AT_THE_BEGINNING: /^:[^:]+$/, @@ -1269,8 +1252,6 @@ const CONST = { DATE_TIME_FORMAT: /^\d{2}-\d{2} \d{2}:\d{2} [AP]M$/, ATTACHMENT_ROUTE: /\/r\/(\d*)\/attachment/, ILLEGAL_FILENAME_CHARACTERS: /\/|<|>|\*|"|:|\?|\\|\|/g, - - ENCODE_PERCENT_CHARACTER: /%(25)+/g, }, PRONOUNS: { @@ -1378,7 +1359,6 @@ const CONST = { MERCHANT: 'merchant', CATEGORY: 'category', RECEIPT: 'receipt', - DISTANCE: 'distance', TAG: 'tag', }, FOOTER: { diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 0a17d3a1d2f7..6649a33fe15e 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -1,4 +1,5 @@ import {ValueOf} from 'type-fest'; +import {OnyxUpdate} from 'react-native-onyx'; import DeepValueOf from './types/utils/DeepValueOf'; import * as OnyxTypes from './types/onyx'; import CONST from './CONST'; @@ -29,6 +30,9 @@ const ONYXKEYS = { /** Note: These are Persisted Requests - not all requests in the main queue as the key name might lead one to believe */ PERSISTED_REQUESTS: 'networkRequestQueue', + /** Onyx updates from a response, or success or failure data from a request. */ + QUEUED_ONYX_UPDATES: 'queuedOnyxUpdates', + /** Stores current date */ CURRENT_DATE: 'currentDate', @@ -238,10 +242,6 @@ const ONYXKEYS = { POLICY_RECENTLY_USED_TAGS: 'policyRecentlyUsedTags_', WORKSPACE_INVITE_MEMBERS_DRAFT: 'workspaceInviteMembersDraft_', REPORT: 'report_', - // REPORT_METADATA is a perf optimization used to hold loading states (isLoadingReportActions, isLoadingMoreReportActions). - // A lot of components are connected to the Report entity and do not care about the actions. Setting the loading state - // directly on the report caused a lot of unnecessary re-renders - REPORT_METADATA: 'reportMetadata_', REPORT_ACTIONS: 'reportActions_', REPORT_ACTIONS_DRAFTS: 'reportActionsDrafts_', REPORT_ACTIONS_REACTIONS: 'reportActionsReactions_', @@ -306,6 +306,7 @@ type OnyxValues = { [ONYXKEYS.DEVICE_ID]: string; [ONYXKEYS.IS_SIDEBAR_LOADED]: boolean; [ONYXKEYS.PERSISTED_REQUESTS]: OnyxTypes.Request[]; + [ONYXKEYS.QUEUED_ONYX_UPDATES]: OnyxUpdate[]; [ONYXKEYS.CURRENT_DATE]: string; [ONYXKEYS.CREDENTIALS]: OnyxTypes.Credentials; [ONYXKEYS.IOU]: OnyxTypes.IOU; @@ -379,7 +380,6 @@ type OnyxValues = { [ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST]: OnyxTypes.PolicyMember; [ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT]: Record; [ONYXKEYS.COLLECTION.REPORT]: OnyxTypes.Report; - [ONYXKEYS.COLLECTION.REPORT_METADATA]: OnyxTypes.ReportMetadata; [ONYXKEYS.COLLECTION.REPORT_ACTIONS]: OnyxTypes.ReportAction; [ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS]: string; [ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS]: OnyxTypes.ReportActionReactions; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index b2dafa643b22..78d5f4d54888 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -5,18 +5,19 @@ import CONST from './CONST'; * This is a file containing constants for all of the routes we want to be able to go to */ +// prettier-ignore export default { HOME: '', /** This is a utility route used to go to the user's concierge chat, or the sign-in page if the user's not authenticated */ CONCIERGE: 'concierge', FLAG_COMMENT: { route: 'flag/:reportID/:reportActionID', - getRoute: (reportID: string, reportActionID: string) => `flag/${reportID}/${reportActionID}`, + getRoute: (reportID: string, reportActionID: string) => `flag/${reportID}/${reportActionID}` }, SEARCH: 'search', DETAILS: { route: 'details', - getRoute: (login: string) => `details?login=${encodeURIComponent(login)}`, + getRoute: (login: string) => `details?login=${encodeURIComponent(login)}` }, PROFILE: { route: 'a/:accountID', @@ -30,7 +31,7 @@ export default { VALIDATE_LOGIN: 'v/:accountID/:validateCode', GET_ASSISTANCE: { route: 'get-assistance/:taskID', - getRoute: (taskID: string) => `get-assistance/${taskID}`, + getRoute: (taskID: string) => `get-assistance/${taskID}` }, UNLINK_LOGIN: 'u/:accountID/:validateCode', APPLE_SIGN_IN: 'sign-in-with-apple', @@ -84,16 +85,6 @@ export default { SETTINGS_PERSONAL_DETAILS_LEGAL_NAME: 'settings/profile/personal-details/legal-name', SETTINGS_PERSONAL_DETAILS_DATE_OF_BIRTH: 'settings/profile/personal-details/date-of-birth', SETTINGS_PERSONAL_DETAILS_ADDRESS: 'settings/profile/personal-details/address', - SETTINGS_PERSONAL_DETAILS_ADDRESS_COUNTRY: { - route: 'settings/profile/personal-details/address/country', - getRoute: (country: string, backTo?: string) => { - let route = `settings/profile/personal-details/address/country?country=${country}`; - if (backTo) { - route += `&backTo=${encodeURIComponent(backTo)}`; - } - return route; - }, - }, SETTINGS_CONTACT_METHODS: 'settings/profile/contact-methods', SETTINGS_CONTACT_METHOD_DETAILS: { route: 'settings/profile/contact-methods/:contactMethod/details', @@ -111,11 +102,11 @@ export default { REPORT: 'r', REPORT_WITH_ID: { route: 'r/:reportID?/:reportActionID?', - getRoute: (reportID: string) => `r/${reportID}`, + getRoute: (reportID: string) => `r/${reportID}` }, EDIT_REQUEST: { route: 'r/:threadReportID/edit/:field', - getRoute: (threadReportID: string, field: ValueOf) => `r/${threadReportID}/edit/${field}`, + getRoute: (threadReportID: string, field: ValueOf) => `r/${threadReportID}/edit/${field}` }, EDIT_CURRENCY_REQUEST: { route: 'r/:threadReportID/edit/currency', @@ -123,89 +114,89 @@ export default { }, REPORT_WITH_ID_DETAILS_SHARE_CODE: { route: 'r/:reportID/details/shareCode', - getRoute: (reportID: string) => `r/${reportID}/details/shareCode`, + getRoute: (reportID: string) => `r/${reportID}/details/shareCode` }, REPORT_ATTACHMENTS: { route: 'r/:reportID/attachment', - getRoute: (reportID: string, source: string) => `r/${reportID}/attachment?source=${encodeURI(source)}`, + getRoute: (reportID: string, source: string) => `r/${reportID}/attachment?source=${encodeURI(source)}` }, REPORT_PARTICIPANTS: { route: 'r/:reportID/participants', - getRoute: (reportID: string) => `r/${reportID}/participants`, + getRoute: (reportID: string) => `r/${reportID}/participants` }, REPORT_WITH_ID_DETAILS: { route: 'r/:reportID/details', - getRoute: (reportID: string) => `r/${reportID}/details`, + getRoute: (reportID: string) => `r/${reportID}/details` }, REPORT_SETTINGS: { route: 'r/:reportID/settings', - getRoute: (reportID: string) => `r/${reportID}/settings`, + getRoute: (reportID: string) => `r/${reportID}/settings` }, REPORT_SETTINGS_ROOM_NAME: { route: 'r/:reportID/settings/room-name', - getRoute: (reportID: string) => `r/${reportID}/settings/room-name`, + getRoute: (reportID: string) => `r/${reportID}/settings/room-name` }, REPORT_SETTINGS_NOTIFICATION_PREFERENCES: { route: 'r/:reportID/settings/notification-preferences', - getRoute: (reportID: string) => `r/${reportID}/settings/notification-preferences`, + getRoute: (reportID: string) => `r/${reportID}/settings/notification-preferences` }, REPORT_SETTINGS_WRITE_CAPABILITY: { route: 'r/:reportID/settings/who-can-post', - getRoute: (reportID: string) => `r/${reportID}/settings/who-can-post`, + getRoute: (reportID: string) => `r/${reportID}/settings/who-can-post` }, REPORT_WELCOME_MESSAGE: { route: 'r/:reportID/welcomeMessage', - getRoute: (reportID: string) => `r/${reportID}/welcomeMessage`, + getRoute: (reportID: string) => `r/${reportID}/welcomeMessage` }, SPLIT_BILL_DETAILS: { route: 'r/:reportID/split/:reportActionID', - getRoute: (reportID: string, reportActionID: string) => `r/${reportID}/split/${reportActionID}`, + getRoute: (reportID: string, reportActionID: string) => `r/${reportID}/split/${reportActionID}` }, TASK_TITLE: { route: 'r/:reportID/title', - getRoute: (reportID: string) => `r/${reportID}/title`, + getRoute: (reportID: string) => `r/${reportID}/title` }, TASK_DESCRIPTION: { route: 'r/:reportID/description', - getRoute: (reportID: string) => `r/${reportID}/description`, + getRoute: (reportID: string) => `r/${reportID}/description` }, TASK_ASSIGNEE: { route: 'r/:reportID/assignee', - getRoute: (reportID: string) => `r/${reportID}/assignee`, + getRoute: (reportID: string) => `r/${reportID}/assignee` }, PRIVATE_NOTES_VIEW: { route: 'r/:reportID/notes/:accountID', - getRoute: (reportID: string, accountID: string | number) => `r/${reportID}/notes/${accountID}`, + getRoute: (reportID: string, accountID: string | number) => `r/${reportID}/notes/${accountID}` }, PRIVATE_NOTES_LIST: { route: 'r/:reportID/notes', - getRoute: (reportID: string) => `r/${reportID}/notes`, + getRoute: (reportID: string) => `r/${reportID}/notes` }, PRIVATE_NOTES_EDIT: { route: 'r/:reportID/notes/:accountID/edit', - getRoute: (reportID: string, accountID: string | number) => `r/${reportID}/notes/${accountID}/edit`, + getRoute: (reportID: string, accountID: string | number) => `r/${reportID}/notes/${accountID}/edit` }, // To see the available iouType, please refer to CONST.IOU.MONEY_REQUEST_TYPE MONEY_REQUEST: { route: ':iouType/new/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/${reportID}`, + getRoute: (iouType: string, reportID = '') => `${iouType}/new/${reportID}` }, MONEY_REQUEST_AMOUNT: { route: ':iouType/new/amount/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/amount/${reportID}`, + getRoute: (iouType: string, reportID = '') => `${iouType}/new/amount/${reportID}` }, MONEY_REQUEST_PARTICIPANTS: { route: ':iouType/new/participants/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/participants/${reportID}`, + getRoute: (iouType: string, reportID = '') => `${iouType}/new/participants/${reportID}` }, MONEY_REQUEST_CONFIRMATION: { route: ':iouType/new/confirmation/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/confirmation/${reportID}`, + getRoute: (iouType: string, reportID = '') => `${iouType}/new/confirmation/${reportID}` }, MONEY_REQUEST_DATE: { route: ':iouType/new/date/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/date/${reportID}`, + getRoute: (iouType: string, reportID = '') => `${iouType}/new/date/${reportID}` }, MONEY_REQUEST_CURRENCY: { route: ':iouType/new/currency/:reportID?', @@ -213,39 +204,35 @@ export default { }, MONEY_REQUEST_DESCRIPTION: { route: ':iouType/new/description/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/description/${reportID}`, + getRoute: (iouType: string, reportID = '') => `${iouType}/new/description/${reportID}` }, MONEY_REQUEST_CATEGORY: { route: ':iouType/new/category/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/category/${reportID}`, + getRoute: (iouType: string, reportID = '') => `${iouType}/new/category/${reportID}` }, MONEY_REQUEST_TAG: { route: ':iouType/new/tag/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/tag/${reportID}`, + getRoute: (iouType: string, reportID = '') => `${iouType}/new/tag/${reportID}` }, MONEY_REQUEST_MERCHANT: { route: ':iouType/new/merchant/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/merchant/${reportID}`, + getRoute: (iouType: string, reportID = '') => `${iouType}/new/merchant/${reportID}` }, MONEY_REQUEST_WAYPOINT: { route: ':iouType/new/waypoint/:waypointIndex', - getRoute: (iouType: string, waypointIndex: number) => `${iouType}/new/waypoint/${waypointIndex}`, + getRoute: (iouType: string, waypointIndex: number) => `${iouType}/new/waypoint/${waypointIndex}` }, MONEY_REQUEST_RECEIPT: { route: ':iouType/new/receipt/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/receipt/${reportID}`, + getRoute: (iouType: string, reportID = '') => `${iouType}/new/receipt/${reportID}` }, - MONEY_REQUEST_DISTANCE: { + MONEY_REQUEST_ADDRESS: { route: ':iouType/new/address/:reportID?', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/address/${reportID}`, - }, - MONEY_REQUEST_EDIT_WAYPOINT: { - route: 'r/:threadReportID/edit/distance/:transactionID/waypoint/:waypointIndex', - getRoute: (threadReportID: number, transactionID: string, waypointIndex: number) => `r/${threadReportID}/edit/distance/${transactionID}/waypoint/${waypointIndex}`, + getRoute: (iouType: string, reportID = '') => `${iouType}/new/address/${reportID}` }, MONEY_REQUEST_DISTANCE_TAB: { route: ':iouType/new/:reportID?/distance', - getRoute: (iouType: string, reportID = '') => `${iouType}/new/${reportID}/distance`, + getRoute: (iouType: string, reportID = '') => `${iouType}/new/${reportID}/distance` }, MONEY_REQUEST_MANUAL_TAB: ':iouType/new/:reportID?/manual', MONEY_REQUEST_SCAN_TAB: ':iouType/new/:reportID?/scan', @@ -272,47 +259,47 @@ export default { WORKSPACE_NEW_ROOM: 'workspace/new-room', WORKSPACE_INITIAL: { route: 'workspace/:policyID', - getRoute: (policyID: string) => `workspace/${policyID}`, + getRoute: (policyID: string) => `workspace/${policyID}` }, WORKSPACE_INVITE: { route: 'workspace/:policyID/invite', - getRoute: (policyID: string) => `workspace/${policyID}/invite`, + getRoute: (policyID: string) => `workspace/${policyID}/invite` }, WORKSPACE_INVITE_MESSAGE: { route: 'workspace/:policyID/invite-message', - getRoute: (policyID: string) => `workspace/${policyID}/invite-message`, + getRoute: (policyID: string) => `workspace/${policyID}/invite-message` }, WORKSPACE_SETTINGS: { route: 'workspace/:policyID/settings', - getRoute: (policyID: string) => `workspace/${policyID}/settings`, + getRoute: (policyID: string) => `workspace/${policyID}/settings` }, WORKSPACE_CARD: { route: 'workspace/:policyID/card', - getRoute: (policyID: string) => `workspace/${policyID}/card`, + getRoute: (policyID: string) => `workspace/${policyID}/card` }, WORKSPACE_REIMBURSE: { route: 'workspace/:policyID/reimburse', - getRoute: (policyID: string) => `workspace/${policyID}/reimburse`, + getRoute: (policyID: string) => `workspace/${policyID}/reimburse` }, WORKSPACE_RATE_AND_UNIT: { route: 'workspace/:policyID/rateandunit', - getRoute: (policyID: string) => `workspace/${policyID}/rateandunit`, + getRoute: (policyID: string) => `workspace/${policyID}/rateandunit` }, WORKSPACE_BILLS: { route: 'workspace/:policyID/bills', - getRoute: (policyID: string) => `workspace/${policyID}/bills`, + getRoute: (policyID: string) => `workspace/${policyID}/bills` }, WORKSPACE_INVOICES: { route: 'workspace/:policyID/invoices', - getRoute: (policyID: string) => `workspace/${policyID}/invoices`, + getRoute: (policyID: string) => `workspace/${policyID}/invoices` }, WORKSPACE_TRAVEL: { route: 'workspace/:policyID/travel', - getRoute: (policyID: string) => `workspace/${policyID}/travel`, + getRoute: (policyID: string) => `workspace/${policyID}/travel` }, WORKSPACE_MEMBERS: { route: 'workspace/:policyID/members', - getRoute: (policyID: string) => `workspace/${policyID}/members`, + getRoute: (policyID: string) => `workspace/${policyID}/members` }, // These are some on-off routes that will be removed once they're no longer needed (see GH issues for details) diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js index 83a410b7f0b0..1b4200572664 100644 --- a/src/components/AddressSearch/index.js +++ b/src/components/AddressSearch/index.js @@ -208,15 +208,14 @@ function AddressSearch(props) { // Autocomplete returns any additional valid address fragments (e.g. Apt #) as subpremise. street2: subpremise, - // Make sure country is updated first, since city and state will be reset if the country changes - country: '', + // When locality is not returned, many countries return the city as postalTown (e.g. 5 New Street // Square, London), otherwise as sublocality (e.g. 384 Court Street Brooklyn). If postalTown is // returned, the sublocality will be a city subdivision so shouldn't take precedence (e.g. // Salagatan, Upssala, Sweden). city: locality || postalTown || sublocality || cityAutocompleteFallback, zipCode, - + country: '', state: state || stateAutoCompleteFallback, lat: lodashGet(details, 'geometry.location.lat', 0), lng: lodashGet(details, 'geometry.location.lng', 0), @@ -292,12 +291,6 @@ function AddressSearch(props) { {props.translate('common.noResultsFound')} ) } - renderHeaderComponent={() => - !props.value && - props.predefinedPlaces && ( - {props.translate('common.recentDestinations')} - ) - } onPress={(data, details) => { saveLocationDetails(data, details); diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index fc0101f4adcc..c0fe0e2d26f8 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -1,11 +1,10 @@ -import React, {useState, useCallback, useRef, useMemo} from 'react'; +import React, {useState, useCallback, useRef} from 'react'; import PropTypes from 'prop-types'; import {View, Animated, Keyboard} from 'react-native'; import Str from 'expensify-common/lib/str'; import lodashGet from 'lodash/get'; import lodashExtend from 'lodash/extend'; import _ from 'underscore'; -import {withOnyx} from 'react-native-onyx'; import CONST from '../CONST'; import Modal from './Modal'; import AttachmentView from './Attachments/AttachmentView'; @@ -31,11 +30,6 @@ import useWindowDimensions from '../hooks/useWindowDimensions'; import Navigation from '../libs/Navigation/Navigation'; import ROUTES from '../ROUTES'; import useNativeDriver from '../libs/useNativeDriver'; -import * as ReportUtils from '../libs/ReportUtils'; -import * as ReportActionsUtils from '../libs/ReportActionsUtils'; -import ONYXKEYS from '../ONYXKEYS'; -import * as Policy from '../libs/actions/Policy'; -import useNetwork from '../hooks/useNetwork'; /** * Modal render prop component that exposes modal launching triggers that can be used @@ -127,7 +121,6 @@ function AttachmentModal(props) { : undefined, ); const {translate} = useLocalize(); - const {isOffline} = useNetwork(); const onCarouselAttachmentChange = props.onCarouselAttachmentChange; @@ -331,37 +324,6 @@ function AttachmentModal(props) { const sourceForAttachmentView = props.source || source; - const threeDotsMenuItems = useMemo(() => { - if (!isAttachmentReceipt || !props.parentReport || !props.parentReportActions) { - return []; - } - const menuItems = []; - const parentReportAction = props.parentReportActions[props.report.parentReportActionID]; - const isDeleted = ReportActionsUtils.isDeletedAction(parentReportAction); - const isSettled = ReportUtils.isSettled(props.parentReport.reportID); - - const isAdmin = Policy.isAdminOfFreePolicy([props.policy]) && ReportUtils.isExpenseReport(props.parentReport); - const isRequestor = ReportUtils.isMoneyRequestReport(props.parentReport) && lodashGet(props.session, 'accountID', null) === parentReportAction.actorAccountID; - const canEdit = !isSettled && !isDeleted && (isAdmin || isRequestor); - if (canEdit) { - menuItems.push({ - icon: Expensicons.Camera, - text: props.translate('common.replace'), - onSelected: () => { - onModalHideCallbackRef.current = () => Navigation.navigate(ROUTES.getEditRequestRoute(props.report.reportID, CONST.EDIT_REQUEST_FIELD.RECEIPT)); - closeModal(); - }, - }); - } - menuItems.push({ - icon: Expensicons.Download, - text: props.translate('common.download'), - onSelected: () => downloadAttachment(source), - }); - return menuItems; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isAttachmentReceipt, props.parentReport, props.parentReportActions, props.policy]); - return ( <> downloadAttachment(source)} shouldShowCloseButton={!props.isSmallScreenWidth} shouldShowBackButton={props.isSmallScreenWidth} @@ -396,7 +358,21 @@ function AttachmentModal(props) { onCloseButtonPress={closeModal} shouldShowThreeDotsButton={isAttachmentReceipt} threeDotsAnchorPosition={styles.threeDotsPopoverOffsetAttachmentModal(windowWidth)} - threeDotsMenuItems={threeDotsMenuItems} + threeDotsMenuItems={[ + { + icon: Expensicons.Camera, + text: props.translate('common.replace'), + onSelected: () => { + onModalHideCallbackRef.current = () => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(props.report.reportID, CONST.EDIT_REQUEST_FIELD.RECEIPT)); + closeModal(); + }, + }, + { + icon: Expensicons.Download, + text: props.translate('common.download'), + onSelected: () => downloadAttachment(source), + }, + ]} shouldOverlay /> @@ -466,22 +442,4 @@ function AttachmentModal(props) { AttachmentModal.propTypes = propTypes; AttachmentModal.defaultProps = defaultProps; AttachmentModal.displayName = 'AttachmentModal'; -export default compose( - withWindowDimensions, - withLocalize, - withOnyx({ - parentReport: { - key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT}${report ? report.parentReportID : '0'}`, - }, - policy: { - key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`, - }, - parentReportActions: { - key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.parentReportID : '0'}`, - canEvict: false, - }, - session: { - key: ONYXKEYS.SESSION, - }, - }), -)(AttachmentModal); +export default compose(withWindowDimensions, withLocalize)(AttachmentModal); diff --git a/src/components/AvatarWithDisplayName.js b/src/components/AvatarWithDisplayName.js index 03ae8f51bfb6..81eace444de4 100644 --- a/src/components/AvatarWithDisplayName.js +++ b/src/components/AvatarWithDisplayName.js @@ -43,8 +43,6 @@ const propTypes = { /** Whether if it's an unauthenticated user */ isAnonymous: PropTypes.bool, - shouldEnableDetailPageNavigation: PropTypes.bool, - ...windowDimensionsPropTypes, ...withLocalizePropTypes, }; @@ -55,15 +53,9 @@ const defaultProps = { report: {}, isAnonymous: false, size: CONST.AVATAR_SIZE.DEFAULT, - shouldEnableDetailPageNavigation: false, }; -const showActorDetails = (report, shouldEnableDetailPageNavigation = false) => { - // We should navigate to the details page if the report is a IOU/expense report - if (shouldEnableDetailPageNavigation) { - return ReportUtils.navigateToDetailsPage(report); - } - +const showActorDetails = (report) => { if (ReportUtils.isExpenseReport(report)) { Navigation.navigate(ROUTES.PROFILE.getRoute(report.ownerAccountID)); return; @@ -101,12 +93,12 @@ function AvatarWithDisplayName(props) { const defaultSubscriptSize = isExpenseRequest ? CONST.AVATAR_SIZE.SMALL_NORMAL : props.size; const avatarBorderColor = props.isAnonymous ? themeColors.highlightBG : themeColors.componentBG; - const headerView = ( + return ( {Boolean(props.report && title) && ( showActorDetails(props.report, props.shouldEnableDetailPageNavigation)} + onPress={() => showActorDetails(props.report)} accessibilityLabel={title} accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} > @@ -153,21 +145,6 @@ function AvatarWithDisplayName(props) { )} ); - - if (!props.shouldEnableDetailPageNavigation) { - return headerView; - } - - return ( - ReportUtils.navigateToDetailsPage(props.report)} - style={[styles.flexRow, styles.alignItemsCenter, styles.flex1]} - accessibilityLabel={title} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} - > - {headerView} - - ); } AvatarWithDisplayName.propTypes = propTypes; AvatarWithDisplayName.displayName = 'AvatarWithDisplayName'; diff --git a/src/components/Button/index.js b/src/components/Button/index.js index dc12a4ded5c2..4ca933a45d6f 100644 --- a/src/components/Button/index.js +++ b/src/components/Button/index.js @@ -18,9 +18,6 @@ import PressableWithFeedback from '../Pressable/PressableWithFeedback'; import refPropTypes from '../refPropTypes'; const propTypes = { - /** Should the press event bubble across multiple instances when Enter key triggers it. */ - allowBubble: PropTypes.bool, - /** The text for the button label */ text: PropTypes.string, @@ -126,7 +123,6 @@ const propTypes = { }; const defaultProps = { - allowBubble: false, text: '', shouldShowRightIcon: false, icon: null, @@ -187,7 +183,7 @@ class Button extends Component { shortcutConfig.descriptionKey, shortcutConfig.modifiers, true, - this.props.allowBubble, + false, this.props.enterKeyEventListenerPriority, false, ); diff --git a/src/components/ButtonWithDropdownMenu.js b/src/components/ButtonWithDropdownMenu.js index a0a6e276bc28..54d6c0deac5a 100644 --- a/src/components/ButtonWithDropdownMenu.js +++ b/src/components/ButtonWithDropdownMenu.js @@ -19,9 +19,6 @@ const propTypes = { /** Callback to execute when the main button is pressed */ onPress: PropTypes.func.isRequired, - /** Call the onPress function on main button when Enter key is pressed */ - pressOnEnter: PropTypes.bool, - /** Whether we should show a loading state for the main button */ isLoading: PropTypes.bool, @@ -60,7 +57,6 @@ const propTypes = { const defaultProps = { isLoading: false, isDisabled: false, - pressOnEnter: false, menuHeaderText: '', style: [], buttonSize: CONST.DROPDOWN_BUTTON_SIZE.MEDIUM, @@ -105,7 +101,6 @@ function ButtonWithDropdownMenu(props) {