diff --git a/.github/scripts/verifyDeploy.sh b/.github/scripts/verifyDeploy.sh
new file mode 100755
index 000000000000..0a8fd3c97bcf
--- /dev/null
+++ b/.github/scripts/verifyDeploy.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+
+ENV="$1"
+EXPECTED_VERSION="$2"
+
+BASE_URL=""
+if [[ "$ENV" == 'staging' ]]; then
+ BASE_URL='https://staging.new.expensify.com'
+else
+ BASE_URL='https://new.expensify.com'
+fi
+
+sleep 5
+ATTEMPT=0
+MAX_ATTEMPTS=10
+while [[ $ATTEMPT -lt $MAX_ATTEMPTS ]]; do
+ ((ATTEMPT++))
+
+ echo "Attempt $ATTEMPT: Checking deployed version..."
+ DOWNLOADED_VERSION="$(wget -q -O /dev/stdout "$BASE_URL"/version.json | jq -r '.version')"
+
+ if [[ "$EXPECTED_VERSION" == "$DOWNLOADED_VERSION" ]]; then
+ echo "Success: Deployed version matches local version: $DOWNLOADED_VERSION"
+ exit 0
+ fi
+
+ if [[ $ATTEMPT -lt $MAX_ATTEMPTS ]]; then
+ echo "Version mismatch, found $DOWNLOADED_VERSION. Retrying in 5 seconds..."
+ sleep 5
+ fi
+done
+
+echo "Error: Deployed version did not match local version after $MAX_ATTEMPTS attempts. Something went wrong..."
+exit 1
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 53afe03720f7..99cd0c1dabc5 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -386,23 +386,11 @@ jobs:
- name: Verify staging deploy
if: ${{ !fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }}
- run: |
- sleep 5
- DOWNLOADED_VERSION="$(wget -q -O /dev/stdout https://staging.new.expensify.com/version.json | jq -r '.version')"
- if [[ '${{ needs.prep.outputs.APP_VERSION }}' != "$DOWNLOADED_VERSION" ]]; then
- echo "Error: deployed version $DOWNLOADED_VERSION does not match local version ${{ needs.prep.outputs.APP_VERSION }}. Something went wrong..."
- exit 1
- fi
+ run: ./.github/scripts/verifyDeploy.sh staging ${{ needs.prep.outputs.APP_VERSION }}
- name: Verify production deploy
if: ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }}
- run: |
- sleep 5
- DOWNLOADED_VERSION="$(wget -q -O /dev/stdout https://new.expensify.com/version.json | jq -r '.version')"
- if [[ '${{ needs.prep.outputs.APP_VERSION }}' != "$DOWNLOADED_VERSION" ]]; then
- echo "Error: deployed version $DOWNLOADED_VERSION does not match local version ${{ needs.prep.outputs.APP_VERSION }}. Something went wrong..."
- exit 1
- fi
+ run: ./.github/scripts/verifyDeploy.sh production ${{ needs.prep.outputs.APP_VERSION }}
- name: Upload web sourcemaps artifact
uses: actions/upload-artifact@v4
@@ -507,11 +495,13 @@ jobs:
GITHUB_TOKEN: ${{ github.token }}
- name: Rename web and desktop sourcemaps artifacts before assets upload in order to have unique ReleaseAsset.name
+ continue-on-error: true
run: |
mv ./desktop-staging-sourcemaps-artifact/merged-source-map.js.map ./desktop-staging-sourcemaps-artifact/desktop-staging-merged-source-map.js.map
mv ./web-staging-sourcemaps-artifact/merged-source-map.js.map ./web-staging-sourcemaps-artifact/web-staging-merged-source-map.js.map
- name: Upload artifacts to GitHub Release
+ continue-on-error: true
run: |
gh release upload ${{ needs.prep.outputs.APP_VERSION }} --repo ${{ github.repository }} --clobber \
./android-sourcemaps-artifact/index.android.bundle.map#android-sourcemap-${{ needs.prep.outputs.APP_VERSION }} \
@@ -552,11 +542,6 @@ jobs:
- name: Download all workflow run artifacts
uses: actions/download-artifact@v4
- - name: Rename web and desktop sourcemaps artifacts before assets upload in order to have unique ReleaseAsset.name
- run: |
- mv ./desktop-sourcemaps-artifact/merged-source-map.js.map ./desktop-sourcemaps-artifact/desktop-merged-source-map.js.map
- mv ./web-sourcemaps-artifact/merged-source-map.js.map ./web-sourcemaps-artifact/web-merged-source-map.js.map
-
- name: 🚀 Edit the release to be no longer a prerelease 🚀
run: |
LATEST_RELEASE="$(gh release list --repo ${{ github.repository }} --exclude-pre-releases --json tagName,isLatest --jq '.[] | select(.isLatest) | .tagName')"
@@ -565,7 +550,14 @@ jobs:
env:
GITHUB_TOKEN: ${{ github.token }}
+ - name: Rename web and desktop sourcemaps artifacts before assets upload in order to have unique ReleaseAsset.name
+ continue-on-error: true
+ run: |
+ mv ./desktop-sourcemaps-artifact/merged-source-map.js.map ./desktop-sourcemaps-artifact/desktop-merged-source-map.js.map
+ mv ./web-sourcemaps-artifact/merged-source-map.js.map ./web-sourcemaps-artifact/web-merged-source-map.js.map
+
- name: Upload artifacts to GitHub Release
+ continue-on-error: true
run: |
gh release upload ${{ needs.prep.outputs.APP_VERSION }} --repo ${{ github.repository }} --clobber \
./desktop-sourcemaps-artifact/desktop-merged-source-map.js.map#desktop-sourcemap-${{ needs.prep.outputs.APP_VERSION }} \
diff --git a/.github/workflows/deployNewHelp.yml b/.github/workflows/deployNewHelp.yml
new file mode 100644
index 000000000000..8c4d0fb0ae3b
--- /dev/null
+++ b/.github/workflows/deployNewHelp.yml
@@ -0,0 +1,74 @@
+name: Deploy New Help Site
+
+on:
+ # Run on any push to main that has changes to the help directory
+# TEST: Verify Cloudflare picks this up even if not run when merged to main
+# push:
+# branches:
+# - main
+# paths:
+# - 'help/**'
+
+ # Run on any pull request (except PRs against staging or production) that has changes to the help directory
+ pull_request:
+ types: [opened, synchronize]
+ branches-ignore: [staging, production]
+ paths:
+ - 'help/**'
+
+ # Run on any manual trigger
+ workflow_dispatch:
+
+# Allow only one concurrent deployment
+concurrency:
+ group: "newhelp"
+ cancel-in-progress: false
+
+jobs:
+ build:
+ env:
+ IS_PR_FROM_FORK: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork }}
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ # Set up Ruby and run bundle install inside the /help directory
+ - name: Set up Ruby
+ uses: ruby/setup-ruby@v1
+ with:
+ bundler-cache: true
+ working-directory: ./help
+
+ - name: Build Jekyll site
+ run: bundle exec jekyll build --source ./ --destination ./_site
+ working-directory: ./help # Ensure Jekyll is building the site in /help
+
+ - name: Deploy to Cloudflare Pages
+ uses: cloudflare/pages-action@v1
+ id: deploy
+ if: env.IS_PR_FROM_FORK != 'true'
+ with:
+ apiToken: ${{ secrets.CLOUDFLARE_PAGES_TOKEN }}
+ accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
+ projectName: newhelp
+ directory: ./help/_site # Deploy the built site
+
+ - name: Setup Cloudflare CLI
+ if: env.IS_PR_FROM_FORK != 'true'
+ run: pip3 install cloudflare==2.19.0
+
+ - name: Purge Cloudflare cache
+ if: env.IS_PR_FROM_FORK != 'true'
+ run: /home/runner/.local/bin/cli4 --verbose --delete hosts=["newhelp.expensify.com"] /zones/:9ee042e6cfc7fd45e74aa7d2f78d617b/purge_cache
+ env:
+ CF_API_KEY: ${{ secrets.CLOUDFLARE_TOKEN }}
+
+ - name: Leave a comment on the PR
+ uses: actions-cool/maintain-one-comment@v3.2.0
+ if: ${{ github.event_name == 'pull_request' && env.IS_PR_FROM_FORK != 'true' }}
+ with:
+ token: ${{ github.token }}
+ body: ${{ format('A preview of your New Help changes have been deployed to {0} :zap:️', steps.deploy.outputs.alias) }}
+
diff --git a/.github/workflows/preDeploy.yml b/.github/workflows/preDeploy.yml
index 796468170275..bfe860e60224 100644
--- a/.github/workflows/preDeploy.yml
+++ b/.github/workflows/preDeploy.yml
@@ -4,7 +4,7 @@ name: Process new code merged to main
on:
push:
branches: [main]
- paths-ignore: [docs/**, contributingGuides/**, jest/**, tests/**]
+ paths-ignore: [docs/**, help/**, contributingGuides/**, jest/**, tests/**]
jobs:
typecheck:
diff --git a/.github/workflows/reassurePerformanceTests.yml b/.github/workflows/reassurePerformanceTests.yml
index d4a25a63952b..fb7a34d6fa01 100644
--- a/.github/workflows/reassurePerformanceTests.yml
+++ b/.github/workflows/reassurePerformanceTests.yml
@@ -4,7 +4,7 @@ on:
pull_request:
types: [opened, synchronize]
branches-ignore: [staging, production]
- paths-ignore: [docs/**, .github/**, contributingGuides/**, tests/**, '**.md', '**.sh']
+ paths-ignore: [docs/**, help/**, .github/**, contributingGuides/**, tests/**, '**.md', '**.sh']
jobs:
perf-tests:
diff --git a/.github/workflows/sendReassurePerfData.yml b/.github/workflows/sendReassurePerfData.yml
index 42d946cece95..884182bfc896 100644
--- a/.github/workflows/sendReassurePerfData.yml
+++ b/.github/workflows/sendReassurePerfData.yml
@@ -3,7 +3,7 @@ name: Send Reassure Performance Tests to Graphite
on:
push:
branches: [main]
- paths-ignore: [docs/**, contributingGuides/**, jest/**]
+ paths-ignore: [docs/**, help/**, contributingGuides/**, jest/**]
jobs:
perf-tests:
diff --git a/.prettierignore b/.prettierignore
index a9f7e1464529..98d06e8c5f71 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -15,6 +15,7 @@ package-lock.json
*.css
*.scss
*.md
+*.markdown
# We need to modify the import here specifically, hence we disable prettier to get rid of the sorted imports
src/libs/E2E/reactNativeLaunchingTest.ts
# Temporary while we keep react-compiler in our repo
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 9153966e1d5d..3a620c00fd95 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -110,8 +110,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1009004004
- versionName "9.0.40-4"
+ versionCode 1009004103
+ versionName "9.0.41-3"
// Supported language variants must be declared here to avoid from being removed during the compilation.
// This also helps us to not include unnecessary language variants in the APK.
resConfigs "en", "es"
diff --git a/docs/articles/new-expensify/connections/quickbooks-online/Configure-Quickbooks-Online.md b/docs/articles/new-expensify/connections/quickbooks-online/Configure-Quickbooks-Online.md
index 787602337bd2..73e3340d41a2 100644
--- a/docs/articles/new-expensify/connections/quickbooks-online/Configure-Quickbooks-Online.md
+++ b/docs/articles/new-expensify/connections/quickbooks-online/Configure-Quickbooks-Online.md
@@ -1,9 +1,76 @@
---
title: Configure Quickbooks Online
-description: Coming Soon
+description: Configure your QuickBooks Online connection with Expensify
---
-# FAQ
+Once you've set up your QuickBooks Online connection, you'll be able to configure your import and export settings.
+
+# Step 1: Configure import settings
+
+The following steps help you determine how data will be imported from QuickBooks Online to Expensify.
+
+
+ - Under the Accounting settings for your workspace, click Import under the QuickBooks Online connection.
+ - Review each of the following import settings:
+
+ - Chart of accounts: The chart of accounts are automatically imported from QuickBooks Online as categories. This cannot be amended.
+ - Classes: Choose whether to import classes, which will be shown in Expensify as tags for expense-level coding.
+ - Customers/projects: Choose whether to import customers/projects, which will be shown in Expensify as tags for expense-level coding.
+ - Locations: Choose whether to import locations, which will be shown in Expensify as tags for expense-level coding.
+{% include info.html %}
+As Locations are only configurable as tags, you cannot export expense reports as vendor bills or checks to QuickBooks Online. To unlock these export options, either disable locations import or upgrade to the Control Plan to export locations encoded as a report field.
+{% include end-info.html %}
+ - Taxes: Choose whether to import tax rates and defaults.
+
+
+
+# Step 2: Configure export settings
+
+The following steps help you determine how data will be exported from Expensify to QuickBooks Online.
+
+
+ - Under the Accounting settings for your workspace, click Export under the QuickBooks Online connection.
+ - Review each of the following export settings:
+
+ - Preferred Exporter: Choose whether to assign a Workspace Admin as the Preferred Exporter. Once selected, the Preferred Exporter automatically receives reports for export in their account to help automate the exporting process.
+
+{% include info.html %}
+* Other Workspace Admins will still be able to export to QuickBooks Online.
+* If you set different export accounts for individual company cards under your domain settings, then your Preferred Exporter must be a Domain Admin.
+{% include end-info.html %}
+
+ - Date: Choose whether to use the date of last expense, export date, or submitted date.
+ - Export Out-of-Pocket Expenses as: Select whether out-of-pocket expenses will be exported as a check, journal entry, or vendor bill.
+
+{% include info.html %}
+These settings may vary based on whether tax is enabled for your workspace.
+* If tax is not enabled on the workspace, you’ll also select the Accounts Payable/AP.
+* If tax is enabled on the workspace, journal entry will not be available as an option. If you select the journal entries option first and later enable tax on the workspace, you will see a red dot and an error message under the “Export Out-of-Pocket Expenses as” options. To resolve this error, you must change your export option to vendor bill or check to successfully code and export expense reports.
+{% include end-info.html %}
+
+ - Invoices: Select the QuickBooks Online invoice account that invoices will be exported to.
+ - Export as: Select whether company cards export to QuickBooks Online as a credit card (the default), debit card, or vendor bill. Then select the account they will export to.
+ - If you select vendor bill, you’ll also select the accounts payable account that vendor bills will be created from, as well as whether to set a default vendor for credit card transactions upon export. If this option is enabled, you will select the vendor that all credit card transactions will be applied to.
+
+
+
+# Step 3: Configure advanced settings
+
+The following steps help you determine the advanced settings for your connection, like auto-sync and employee invitation settings.
+
+
+ - Under the Accounting settings for your workspace, click Advanced under the QuickBooks Online connection.
+ - Select an option for each of the following settings:
+
+ - Auto-sync: Choose whether to enable QuickBooks Online to automatically communicate changes with Expensify to ensure that the data shared between the two systems is up-to-date. New report approvals/reimbursements will be synced during the next auto-sync period.
+ - Invite Employees: Choose whether to enable Expensify to import employee records from QuickBooks Online and invite them to this workspace.
+ - Automatically Create Entities: Choose whether to enable Expensify to automatically create vendors and customers in QuickBooks Online if a matching vendor or customer does not exist.
+ - Sync Reimbursed Reports: Choose whether to enable report syncing for reimbursed expenses. If enabled, all reports that are marked as Paid in QuickBooks Online will also show in Expensify as Paid. If enabled, you must also select the QuickBooks Online account that reimbursements are coming out of, and Expensify will automatically create the payment in QuickBooks Online.
+ - Invoice Collection Account: Select the invoice collection account that you want invoices to appear under once the invoice is marked as paid.
+
+
+
+{% include faq-begin.md %}
## How do I know if a report is successfully exported to QuickBooks Online?
@@ -22,3 +89,5 @@ When an admin manually exports a report, Expensify will notify them if the repor
- If a report has been exported and marked as paid in QuickBooks Online, it will be automatically marked as reimbursed in Expensify during the next sync.
Reports that have yet to be exported to QuickBooks Online won’t be automatically exported.
+
+{% include faq-end.md %}
diff --git a/docs/articles/new-expensify/expenses-&-payments/Pay-an-invoice.md b/docs/articles/new-expensify/expenses-&-payments/Pay-an-invoice.md
index 727c6b86b7a6..615fac731c41 100644
--- a/docs/articles/new-expensify/expenses-&-payments/Pay-an-invoice.md
+++ b/docs/articles/new-expensify/expenses-&-payments/Pay-an-invoice.md
@@ -32,6 +32,8 @@ To pay an invoice,
You can also view all unpaid invoices by searching for the sender’s email or phone number on the left-hand side of the app. The invoices waiting for your payment will have a green dot.
+![Click Pay Button on the Invoice]({{site.url}}/assets/images/ExpensifyHelp-Invoice-1.png){:width="100%"}
+
{% include faq-begin.md %}
**Can someone else pay an invoice besides the person who received it?**
diff --git a/docs/articles/new-expensify/expenses-&-payments/Send-an-invoice.md b/docs/articles/new-expensify/expenses-&-payments/Send-an-invoice.md
index 85bd6b655186..57b81a031a01 100644
--- a/docs/articles/new-expensify/expenses-&-payments/Send-an-invoice.md
+++ b/docs/articles/new-expensify/expenses-&-payments/Send-an-invoice.md
@@ -57,6 +57,18 @@ Only workspace admins can send invoices. Invoices can be sent directly from Expe
{% include end-selector.html %}
+![Go to Account Settings click Workspace]({{site.url}}/assets/images/invoices_01.png){:width="100%"}
+
+![Click More Features for the workspace and enable Invoices]({{site.url}}/assets/images/invoices_02.png){:width="100%"}
+
+![Click the green button Send Invoice]({{site.url}}/assets/images/invoices_03.png){:width="100%"}
+
+![Enter Invoice amount]({{site.url}}/assets/images/invoices_04.png){:width="100%"}
+
+![Choose a recipient]({{site.url}}/assets/images/invoices_05.png){:width="100%"}
+
+![Add Invoice details and Send Invoice]({{site.url}}/assets/images/invoices_06.png){:width="100%"}
+
# Receive invoice payment
If you have not [connected a business bank account](https://help.expensify.com/articles/new-expensify/expenses-&-payments/Connect-a-Business-Bank-Account) to receive invoice payments, you will see an **Invoice balance** in your [Wallet](https://help.expensify.com/articles/new-expensify/expenses-&-payments/Set-up-your-wallet). Expensify will automatically transfer these invoice payments once a business bank account is connected.
diff --git a/help/.gitignore b/help/.gitignore
new file mode 100644
index 000000000000..f40fbd8ba564
--- /dev/null
+++ b/help/.gitignore
@@ -0,0 +1,5 @@
+_site
+.sass-cache
+.jekyll-cache
+.jekyll-metadata
+vendor
diff --git a/help/.ruby-version b/help/.ruby-version
new file mode 100644
index 000000000000..a0891f563f38
--- /dev/null
+++ b/help/.ruby-version
@@ -0,0 +1 @@
+3.3.4
diff --git a/help/404.html b/help/404.html
new file mode 100644
index 000000000000..086a5c9ea988
--- /dev/null
+++ b/help/404.html
@@ -0,0 +1,25 @@
+---
+permalink: /404.html
+layout: default
+---
+
+
+
+
+
404
+
+
Page not found :(
+
The requested page could not be found.
+
diff --git a/help/Gemfile b/help/Gemfile
new file mode 100644
index 000000000000..4f2e425b8aba
--- /dev/null
+++ b/help/Gemfile
@@ -0,0 +1,19 @@
+source "https://rubygems.org"
+
+gem "jekyll", "~> 4.3.4"
+gem "minima", "~> 2.5"
+gem "nokogiri"
+
+group :jekyll_plugins do
+ gem "jekyll-feed", "~> 0.12"
+end
+
+# If using tzinfo-data for timezone support, ensure it's bundled for relevant platforms
+platforms :mingw, :x64_mingw, :mswin, :jruby do
+ gem "tzinfo", ">= 1", "< 3"
+ gem "tzinfo-data"
+end
+
+gem "wdm", "~> 0.1", platforms: [:mingw, :x64_mingw, :mswin]
+gem "http_parser.rb", "~> 0.6.0", platforms: [:jruby]
+
diff --git a/help/Gemfile.lock b/help/Gemfile.lock
new file mode 100644
index 000000000000..7434e1c4e935
--- /dev/null
+++ b/help/Gemfile.lock
@@ -0,0 +1,200 @@
+GEM
+ remote: https://rubygems.org/
+ specs:
+ addressable (2.8.7)
+ public_suffix (>= 2.0.2, < 7.0)
+ bigdecimal (3.1.8)
+ colorator (1.1.0)
+ concurrent-ruby (1.3.4)
+ em-websocket (0.5.3)
+ eventmachine (>= 0.12.9)
+ http_parser.rb (~> 0)
+ eventmachine (1.2.7)
+ ffi (1.17.0)
+ ffi (1.17.0-aarch64-linux-gnu)
+ ffi (1.17.0-aarch64-linux-musl)
+ ffi (1.17.0-arm-linux-gnu)
+ ffi (1.17.0-arm-linux-musl)
+ ffi (1.17.0-arm64-darwin)
+ ffi (1.17.0-x86-linux-gnu)
+ ffi (1.17.0-x86-linux-musl)
+ ffi (1.17.0-x86_64-darwin)
+ ffi (1.17.0-x86_64-linux-gnu)
+ ffi (1.17.0-x86_64-linux-musl)
+ forwardable-extended (2.6.0)
+ google-protobuf (4.28.2)
+ bigdecimal
+ rake (>= 13)
+ google-protobuf (4.28.2-aarch64-linux)
+ bigdecimal
+ rake (>= 13)
+ google-protobuf (4.28.2-arm64-darwin)
+ bigdecimal
+ rake (>= 13)
+ google-protobuf (4.28.2-x86-linux)
+ bigdecimal
+ rake (>= 13)
+ google-protobuf (4.28.2-x86_64-darwin)
+ bigdecimal
+ rake (>= 13)
+ google-protobuf (4.28.2-x86_64-linux)
+ bigdecimal
+ rake (>= 13)
+ http_parser.rb (0.8.0)
+ i18n (1.14.6)
+ concurrent-ruby (~> 1.0)
+ jekyll (4.3.4)
+ addressable (~> 2.4)
+ colorator (~> 1.0)
+ em-websocket (~> 0.5)
+ i18n (~> 1.0)
+ jekyll-sass-converter (>= 2.0, < 4.0)
+ jekyll-watch (~> 2.0)
+ kramdown (~> 2.3, >= 2.3.1)
+ kramdown-parser-gfm (~> 1.0)
+ liquid (~> 4.0)
+ mercenary (>= 0.3.6, < 0.5)
+ pathutil (~> 0.9)
+ rouge (>= 3.0, < 5.0)
+ safe_yaml (~> 1.0)
+ terminal-table (>= 1.8, < 4.0)
+ webrick (~> 1.7)
+ jekyll-feed (0.17.0)
+ jekyll (>= 3.7, < 5.0)
+ jekyll-sass-converter (3.0.0)
+ sass-embedded (~> 1.54)
+ jekyll-seo-tag (2.8.0)
+ jekyll (>= 3.8, < 5.0)
+ jekyll-watch (2.2.1)
+ listen (~> 3.0)
+ kramdown (2.4.0)
+ rexml
+ kramdown-parser-gfm (1.1.0)
+ kramdown (~> 2.0)
+ liquid (4.0.4)
+ listen (3.9.0)
+ rb-fsevent (~> 0.10, >= 0.10.3)
+ rb-inotify (~> 0.9, >= 0.9.10)
+ mercenary (0.4.0)
+ mini_portile2 (2.8.7)
+ minima (2.5.2)
+ jekyll (>= 3.5, < 5.0)
+ jekyll-feed (~> 0.9)
+ jekyll-seo-tag (~> 2.1)
+ nokogiri (1.16.7)
+ mini_portile2 (~> 2.8.2)
+ racc (~> 1.4)
+ nokogiri (1.16.7-aarch64-linux)
+ racc (~> 1.4)
+ nokogiri (1.16.7-arm-linux)
+ racc (~> 1.4)
+ nokogiri (1.16.7-arm64-darwin)
+ racc (~> 1.4)
+ nokogiri (1.16.7-x86-linux)
+ racc (~> 1.4)
+ nokogiri (1.16.7-x86_64-darwin)
+ racc (~> 1.4)
+ nokogiri (1.16.7-x86_64-linux)
+ racc (~> 1.4)
+ pathutil (0.16.2)
+ forwardable-extended (~> 2.6)
+ public_suffix (6.0.1)
+ racc (1.8.1)
+ rake (13.2.1)
+ rb-fsevent (0.11.2)
+ rb-inotify (0.11.1)
+ ffi (~> 1.0)
+ rexml (3.3.7)
+ rouge (4.4.0)
+ safe_yaml (1.0.5)
+ sass-embedded (1.79.3)
+ google-protobuf (~> 4.27)
+ rake (>= 13)
+ sass-embedded (1.79.3-aarch64-linux-android)
+ google-protobuf (~> 4.27)
+ sass-embedded (1.79.3-aarch64-linux-gnu)
+ google-protobuf (~> 4.27)
+ sass-embedded (1.79.3-aarch64-linux-musl)
+ google-protobuf (~> 4.27)
+ sass-embedded (1.79.3-aarch64-mingw-ucrt)
+ google-protobuf (~> 4.27)
+ sass-embedded (1.79.3-arm-linux-androideabi)
+ google-protobuf (~> 4.27)
+ sass-embedded (1.79.3-arm-linux-gnueabihf)
+ google-protobuf (~> 4.27)
+ sass-embedded (1.79.3-arm-linux-musleabihf)
+ google-protobuf (~> 4.27)
+ sass-embedded (1.79.3-arm64-darwin)
+ google-protobuf (~> 4.27)
+ sass-embedded (1.79.3-riscv64-linux-android)
+ google-protobuf (~> 4.27)
+ sass-embedded (1.79.3-riscv64-linux-gnu)
+ google-protobuf (~> 4.27)
+ sass-embedded (1.79.3-riscv64-linux-musl)
+ google-protobuf (~> 4.27)
+ sass-embedded (1.79.3-x86-cygwin)
+ google-protobuf (~> 4.27)
+ sass-embedded (1.79.3-x86-linux-android)
+ google-protobuf (~> 4.27)
+ sass-embedded (1.79.3-x86-linux-gnu)
+ google-protobuf (~> 4.27)
+ sass-embedded (1.79.3-x86-linux-musl)
+ google-protobuf (~> 4.27)
+ sass-embedded (1.79.3-x86-mingw-ucrt)
+ google-protobuf (~> 4.27)
+ sass-embedded (1.79.3-x86_64-cygwin)
+ google-protobuf (~> 4.27)
+ sass-embedded (1.79.3-x86_64-darwin)
+ google-protobuf (~> 4.27)
+ sass-embedded (1.79.3-x86_64-linux-android)
+ google-protobuf (~> 4.27)
+ sass-embedded (1.79.3-x86_64-linux-gnu)
+ google-protobuf (~> 4.27)
+ sass-embedded (1.79.3-x86_64-linux-musl)
+ google-protobuf (~> 4.27)
+ terminal-table (3.0.2)
+ unicode-display_width (>= 1.1.1, < 3)
+ unicode-display_width (2.6.0)
+ webrick (1.8.2)
+
+PLATFORMS
+ aarch64-linux
+ aarch64-linux-android
+ aarch64-linux-gnu
+ aarch64-linux-musl
+ aarch64-mingw-ucrt
+ arm-linux-androideabi
+ arm-linux-gnu
+ arm-linux-gnueabihf
+ arm-linux-musl
+ arm-linux-musleabihf
+ arm64-darwin
+ riscv64-linux-android
+ riscv64-linux-gnu
+ riscv64-linux-musl
+ ruby
+ x86-cygwin
+ x86-linux
+ x86-linux-android
+ x86-linux-gnu
+ x86-linux-musl
+ x86-mingw-ucrt
+ x86_64-cygwin
+ x86_64-darwin
+ x86_64-linux
+ x86_64-linux-android
+ x86_64-linux-gnu
+ x86_64-linux-musl
+
+DEPENDENCIES
+ http_parser.rb (~> 0.6.0)
+ jekyll (~> 4.3.4)
+ jekyll-feed (~> 0.12)
+ minima (~> 2.5)
+ nokogiri
+ tzinfo (>= 1, < 3)
+ tzinfo-data
+ wdm (~> 0.1)
+
+BUNDLED WITH
+ 2.5.19
diff --git a/help/_config.yml b/help/_config.yml
new file mode 100644
index 000000000000..9135a372964e
--- /dev/null
+++ b/help/_config.yml
@@ -0,0 +1,7 @@
+title: New Expensify Help
+email: concierge@expensify.com
+description: Comprehensive help documentation for New Expensify.
+url: https://newhelp.expensify.com
+twitter_username: expensify
+github_username: expensify
+
diff --git a/help/_layouts/default.html b/help/_layouts/default.html
new file mode 100644
index 000000000000..cf95e1f54b06
--- /dev/null
+++ b/help/_layouts/default.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+ {{ page.title }}
+
+
+
+
+
+
+ {{ content }}
+
+
+
+
+
+
diff --git a/help/_layouts/product.html b/help/_layouts/product.html
new file mode 100644
index 000000000000..cb8b5e882f24
--- /dev/null
+++ b/help/_layouts/product.html
@@ -0,0 +1,11 @@
+---
+layout: default
+---
+
+{{ page.title }}
+
+
+
+ {{ content }}
+
+
diff --git a/help/_plugins/51_HeaderIDPostRender.rb b/help/_plugins/51_HeaderIDPostRender.rb
new file mode 100644
index 000000000000..4af97cc788f6
--- /dev/null
+++ b/help/_plugins/51_HeaderIDPostRender.rb
@@ -0,0 +1,59 @@
+require 'nokogiri'
+require 'cgi' # Use CGI for URL encoding
+
+module Jekyll
+ class HeaderIDPostRender
+ # Hook into Jekyll's post_render stage to ensure we work with the final HTML
+ Jekyll::Hooks.register :pages, :post_render, priority: 51 do |page|
+ process_page(page)
+ end
+
+ Jekyll::Hooks.register :documents, :post_render, priority: 51 do |post|
+ process_page(post)
+ end
+
+ def self.process_page(page)
+ return unless page.output_ext == ".html" # Only apply to HTML pages
+ return if page.output.nil? # Skip if no output has been generated
+
+ puts " Processing page: #{page.path}"
+
+ # Parse the page's content for header elements
+ doc = Nokogiri::HTML(page.output)
+ h1_id = ""
+ h2_id = ""
+ h3_id = ""
+
+ # Process all , , and elements
+ (2..4).each do |level|
+ doc.css("h#{level}").each do |header|
+ header_text = header.text.strip.downcase
+ header_id = CGI.escape(header_text.gsub(/\s+/, '-').gsub(/[^\w\-]/, ''))
+
+ puts " Found h#{level}: '#{header_text}' -> ID: '#{header_id}'"
+
+ # Create hierarchical IDs by appending to the parent header IDs
+ if level == 2
+ h2_id = header_id
+ header['id'] = h2_id
+ elsif level == 3
+ h3_id = "#{h2_id}:#{header_id}"
+ header['id'] = h3_id
+ elsif level == 4
+ h4_id = "#{h3_id}:#{header_id}"
+ header['id'] = h4_id
+ end
+
+ puts " Assigned ID: #{header['id']}"
+ end
+ end
+
+ # Log the final output being written
+ puts " Writing updated HTML for page: #{page.path}"
+
+ # Write the updated HTML back to the page
+ page.output = doc.to_html
+ end
+ end
+end
+
diff --git a/help/index.md b/help/index.md
new file mode 100644
index 000000000000..e5d075402ecb
--- /dev/null
+++ b/help/index.md
@@ -0,0 +1,5 @@
+---
+title: New Expensify Help
+---
+Pages:
+* [Expensify Superapp](/superapp.html)
diff --git a/help/robots.txt b/help/robots.txt
new file mode 100644
index 000000000000..6ffbc308f73e
--- /dev/null
+++ b/help/robots.txt
@@ -0,0 +1,3 @@
+User-agent: *
+Disallow: /
+
diff --git a/help/superapp.md b/help/superapp.md
new file mode 100644
index 000000000000..d09860a1ce7e
--- /dev/null
+++ b/help/superapp.md
@@ -0,0 +1,115 @@
+---
+layout: product
+title: Expensify Superapp
+---
+
+## Introduction
+The Expensify Superapp packs the full power of 6 world class business, finance, and collaboration products, into a single app that works identically on desktop and mobile, efficiently with your colleagues, and seamlessly with your customers, vendors, family, and friends.
+
+### When should I use Expensify?
+Expensify can do a lot. You should check us out whenever you need to:
+
+Track and manage expenses
+: Whether you are reimbursing employee receipts, deducting personal expenses, or just splitting the bill, Expensify Expense is for you.
+
+Issue corporate cards
+: Skip the reimbursement and capture receipts electronically in realtime by issuing the Expensify Card to yourself and your employees.
+
+Book and manage travel
+: If you are booking your own business trip, arranging a trip for a colleague, or managing the travel of your whole company, Expensify Travel has got you covered.
+
+Chat with friends and coworkers
+: Whether it's collaborating with your team, supporting you client, negotiating with your vendor, or just saying Hi to a friend, Expensify Chat connects you with anyone with an email address or SMS number
+
+Collect invoice payments online
+: Expensify Invoice allows you to collect online payments from consumers and businesses alike – anyone with an email address or SMS number.
+
+Approve and pay bills online
+: Scan, process, and approve bills online using Expensify Billpay, then we'll pay them electronically or via check, whatever they prefer.
+
+If you send, receive, or spend money – or even just talk to literally anyone, about literally anything – Expensify is the tool for you.
+
+### Who uses Expensify?
+Expensify offers something for everyone. Some people who commonly use us include:
+
+Individuals
+: Millions of individuals use Expensify to track personal expenses to maximize their tax deductions, stay within personal budgets, or just see where their money is going.
+
+Friends
+: Expensify is a great way to split bills with friends, whether it's monthly rent and household expenses, a big ticket bachelorette party, or just grabbing drinks with friends.
+
+Employees
+: Road warriors and desk jockeys alike count on Expensify to reimburse expense reports they create in international airports, swanky hotels, imposing conference centers, quaint coffeeshops, and boring office supply stores around the world.
+
+Managers
+: Bosses manage corporate spend with Expensify to empower their best (and keep tabs on their… not so best), staying ahead of schedule and under budget.
+
+Accountants
+: Internal accountants, fractional CFOs, CAS practices – you name it, they use Expensify to Invoice customers, process vendor bills, capture eReceipts, manage corporate spend: the whole shebang. If you're an accountant, we're already best friends.
+
+Travel managers
+: Anyone looking to manage employee travel has come to the right place.
+
+If you are a person online who does basically anything, you can probably do it with Expensify.
+
+### Why should I use Expensify?
+Though we do a lot, you've got a lot of options for everything we do. But you should use us because we are:
+Simple enough for individuals - We've worked extremely hard to make a product that strips out all the complex jargon and enterprise baggage, and gives you a simple tool that doesn't overwhelm you with functionality and language you don't understand.
+
+Powerful enough for enterprises
+: We've worked extremely hard to make a product that "scales up" to reveal increasingly sophisticated features, but only to those who need it, and only when they need it. Expensify is used by public companies, multinational companies, companies with tens of thousands of employees, non-profits, investment firms, accounting firms, manufacturers, and basically every industry in every currency and in every country around the world. If you are a company, we can support your needs, no matter how big or small.
+
+6 products for the price of 1
+: Do you pay for an expense management system? A corporate card? A travel management platform? An enterprise chat tool? An invoicing tool? A billpay tool? Now you don't need to. Expensify's superapp design allows us to offer ALL these features on a single platform, at probably less than what you pay for any of them individually.
+
+Supports everyone everywhere
+: Expensify works on iPhones and Androids, desktops and browsers. We support every currency, and can reimburse to almost any country. You don't need to be an IT wizard – if you can type in their email address or SMS number, you can do basically everything with them.
+
+You get paid to use it
+: Do you spend money? Spend it on the Expensify Card and we pay you up to 2% cashback. It's your money after all.
+
+Revenue share for accountants
+: Do you manage the books for a bunch of clients? Become an Expensify Approved Accountant and take home 0.5% revenue share. Or share it with your clients as a discount, up to you!
+
+You are in the driver's seat; we're here to earn your business. But we're going to work harder for you than the other guys, and you won't be disappointed.
+
+## Concepts
+The Expensify Superapp has a lot of moving pieces, so let's break them down one by one.
+
+### What makes Expensify a superapp?
+A "superapp" is a single app that combines multiple products into one seamlessly interconnected experience. Expensify isn't a "suite" of separate products linked through a single account – Expensify is a single app with a single core design that can perform multiple product functions. The secret to making such a seamless experience is that we build all product functions atop the same common core:
+
+App
+: The basis of the superapp experience is the actual app itself, which runs on your mobile phone or desktop computer. (What is the Expensify app?)
+
+Chats
+: Even if you don't plan on using Expensify Chat for enterprise-grade workspace collaboration, chat is infused through the entire product. (What is a chat?)
+
+Expense
+: Even if you aren't actively managing your expenses, you've still got them. Every product that deals with money is ultimately dealing with expenses of some kind. (What is an expense?)
+
+Workspace
+: Though Expensify works great for our millions of individual members, every product really shines when used between groups of members sharing a "workspace". (What is a workspace?)
+
+Domain
+: To support more advanced security features, many products provide extra functionality to members who are on the same email "domain". (What is a domain?)
+
+These are the foundational concepts you'll see again and again that underpin the superapp as a whole.
+
+### What is the Expensify app?
+Just like your eyes are a window to your soul, the Expensify App is the doorway through which you experience the entire global world of interconnected chat-centric collaborative data that comprises the Expensify network. The main tools of this app consist of:
+
+Inbox
+: The main screen of the app is the Inbox, which highlights exactly what you should do next, consolidated across all products. (What does the Inbox do?)
+
+Search
+: The next major screen is Search, which as you'd expect, let's you search everything across all products, from one convenient and powerful place. (What does Search do?)
+
+Settings
+: Settings wraps up all your personal, workspace, and domain configuration options, all in one helpful space. (What are Expensify's settings?)
+
+Create
+: Finally, the big green plus button is the Create button, which lets you create pretty much anything, across all the products. (What does the Create button do?)
+
+It's a deceptively simple app, with a few very familiar looking screens and buttons that unlock an incredible range of sophisticated multi-product power.
+
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index af696a13c998..35d71276f8ef 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -19,7 +19,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 9.0.40
+ 9.0.41
CFBundleSignature
????
CFBundleURLTypes
@@ -40,7 +40,7 @@
CFBundleVersion
- 9.0.40.4
+ 9.0.41.3
FullStory
OrgId
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index 0795209286ed..ca0c99bb87a2 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -15,10 +15,10 @@
CFBundlePackageType
BNDL
CFBundleShortVersionString
- 9.0.40
+ 9.0.41
CFBundleSignature
????
CFBundleVersion
- 9.0.40.4
+ 9.0.41.3
diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist
index a545bd82c164..2d97209598e2 100644
--- a/ios/NotificationServiceExtension/Info.plist
+++ b/ios/NotificationServiceExtension/Info.plist
@@ -11,9 +11,9 @@
CFBundleName
$(PRODUCT_NAME)
CFBundleShortVersionString
- 9.0.40
+ 9.0.41
CFBundleVersion
- 9.0.40.4
+ 9.0.41.3
NSExtension
NSExtensionPointIdentifier
diff --git a/package-lock.json b/package-lock.json
index d43c8fee25c5..a83d0550ad6f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "9.0.40-4",
+ "version": "9.0.41-3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "9.0.40-4",
+ "version": "9.0.41-3",
"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": "2.0.88",
+ "expensify-common": "2.0.94",
"expo": "51.0.31",
"expo-av": "14.0.7",
"expo-image": "1.12.15",
@@ -24037,9 +24037,9 @@
}
},
"node_modules/expensify-common": {
- "version": "2.0.88",
- "resolved": "https://registry.npmjs.org/expensify-common/-/expensify-common-2.0.88.tgz",
- "integrity": "sha512-4k6X6BekydYSRWkWRMB/Ts0W5Zx3BskEpLQEuxpq+cW9QIvTyFliho/dMLaXYOqS6nMQuzkjJYqfGPx9agVnOg==",
+ "version": "2.0.94",
+ "resolved": "https://registry.npmjs.org/expensify-common/-/expensify-common-2.0.94.tgz",
+ "integrity": "sha512-Cco5X6u4IL5aQlFqa2IgGgR+vAffYLxpPN2d7bzfptW/pRLY2L2JRJohgvXEswlCcTKFVt4nIJ4bx9YIOvzxBA==",
"dependencies": {
"awesome-phonenumber": "^5.4.0",
"classnames": "2.5.0",
diff --git a/package.json b/package.json
index e8322960c61c..5c6a0635e819 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "9.0.40-4",
+ "version": "9.0.41-3",
"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.",
@@ -107,7 +107,7 @@
"date-fns-tz": "^2.0.0",
"dom-serializer": "^0.2.2",
"domhandler": "^4.3.0",
- "expensify-common": "2.0.88",
+ "expensify-common": "2.0.94",
"expo": "51.0.31",
"expo-av": "14.0.7",
"expo-image": "1.12.15",
diff --git a/patches/react-native-draggable-flatlist+4.0.1.patch b/patches/react-native-draggable-flatlist+4.0.1.patch
new file mode 100644
index 000000000000..348f1aa5de8a
--- /dev/null
+++ b/patches/react-native-draggable-flatlist+4.0.1.patch
@@ -0,0 +1,94 @@
+diff --git a/node_modules/react-native-draggable-flatlist/src/components/DraggableFlatList.tsx b/node_modules/react-native-draggable-flatlist/src/components/DraggableFlatList.tsx
+index d7d98c2..2f59c7a 100644
+--- a/node_modules/react-native-draggable-flatlist/src/components/DraggableFlatList.tsx
++++ b/node_modules/react-native-draggable-flatlist/src/components/DraggableFlatList.tsx
+@@ -295,7 +295,7 @@ function DraggableFlatListInner(props: DraggableFlatListProps) {
+ const springTo = placeholderOffset.value - activeCellOffset.value;
+ touchTranslate.value = withSpring(
+ springTo,
+- animationConfigRef.current,
++ animationConfigRef.value,
+ () => {
+ runOnJS(onDragEnd)({
+ from: activeIndexAnim.value,
+diff --git a/node_modules/react-native-draggable-flatlist/src/context/refContext.tsx b/node_modules/react-native-draggable-flatlist/src/context/refContext.tsx
+index ea21575..66c5eed 100644
+--- a/node_modules/react-native-draggable-flatlist/src/context/refContext.tsx
++++ b/node_modules/react-native-draggable-flatlist/src/context/refContext.tsx
+@@ -1,14 +1,14 @@
+ import React, { useContext } from "react";
+ import { useMemo, useRef } from "react";
+ import { FlatList } from "react-native-gesture-handler";
+-import Animated, { WithSpringConfig } from "react-native-reanimated";
++import Animated, { type SharedValue, useSharedValue, WithSpringConfig } from "react-native-reanimated";
+ import { DEFAULT_PROPS } from "../constants";
+ import { useProps } from "./propsContext";
+ import { CellData, DraggableFlatListProps } from "../types";
+
+ type RefContextValue = {
+ propsRef: React.MutableRefObject>;
+- animationConfigRef: React.MutableRefObject;
++ animationConfigRef: SharedValue;
+ cellDataRef: React.MutableRefObject