Skip to content

Commit 4dfad75

Browse files
authored
Merge branch 'Expensify:main' into hotfix-fab-1
2 parents 6451a9e + 51fc156 commit 4dfad75

175 files changed

Lines changed: 4722 additions & 13429 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Mobile-Expensify

android/app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ android {
111111
minSdkVersion rootProject.ext.minSdkVersion
112112
targetSdkVersion rootProject.ext.targetSdkVersion
113113
multiDexEnabled rootProject.ext.multiDexEnabled
114-
versionCode 1009033903
115-
versionName "9.3.39-3"
114+
versionCode 1009034001
115+
versionName "9.3.40-1"
116116
// Supported language variants must be declared here to avoid from being removed during the compilation.
117117
// This also helps us to not include unnecessary language variants in the APK.
118118
resConfigs "en", "es"
Lines changed: 1 addition & 0 deletions
Loading

assets/images/product-illustrations/upgrade-rocket.svg

Lines changed: 1 addition & 0 deletions
Loading

contributingGuides/NAVIGATION.md

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ The navigation in the app is built on top of the `react-navigation` library. To
2727
- [Entry screens (access control)](#entry-screens-access-control)
2828
- [Current limitations (work in progress)](#current-limitations-work-in-progress)
2929
- [Multi-segment dynamic routes](#multi-segment-dynamic-routes)
30+
- [Suffix layering (stacking dynamic routes)](#suffix-layering-stacking-dynamic-routes)
3031
- [Dynamic routes with query parameters](#dynamic-routes-with-query-parameters)
3132
- [How to add a new dynamic route](#how-to-add-a-new-dynamic-route)
3233
- [Migrating from backTo to dynamic routes](#migrating-from-backto-to-dynamic-routes)
@@ -701,7 +702,7 @@ A dynamic route is a URL suffix (e.g. `verify-account`) that can be appended to
701702

702703
Do not use dynamic routes when:
703704
- Your use case falls under the [current limitations](#current-limitations-work-in-progress):
704-
- You need to stack multiple dynamic route suffixes (e.g. `/a/verify-account/another-flow`).
705+
- You need path parameters in dynamic suffixes (e.g. `a/:reportID`).
705706
- The screen has a single, fixed entry and a fixed back destination. In this case, use a normal static route instead.
706707

707708
### Dynamic routes configuration
@@ -732,10 +733,9 @@ When adding or extending a dynamic route, list every screen that should be able
732733

733734
### Current limitations (work in progress)
734735

735-
- **Stacking:** Multiple dynamic route suffixes on top of each other (e.g. `/a/verify-account/another-flow`) are not supported. Only one dynamic suffix per path is allowed.
736736
- **Path parameters:** Suffixes must not include path params (e.g. `a/:reportID`). Query parameters are supported - see [Dynamic routes with query parameters](#dynamic-routes-with-query-parameters).
737737

738-
If you try to use dynamic routes for these cases now, you will either fail to navigate to the page at all or end up on a non-existent page, and the navigation will be broken.
738+
If you try to use dynamic routes for this case now, you will either fail to navigate to the page at all or end up on a non-existent page, and the navigation will be broken.
739739

740740
### Multi-segment dynamic routes
741741

@@ -751,6 +751,61 @@ For instance, if both `verify-account` and `add-bank-account/verify-account`
751751
are registered, a path ending with `/add-bank-account/verify-account`
752752
will always match the longer, more specific suffix.
753753

754+
### Suffix layering (stacking dynamic routes)
755+
756+
Dynamic route suffixes can be stacked on top of each other,
757+
producing URLs like `/base-path/suffix-a/suffix-b`.
758+
Each suffix in the chain is resolved recursively: the parser strips the outermost suffix first,
759+
resolves the remaining path (which may itself contain another dynamic suffix),
760+
and repeats until it reaches a static base path.
761+
762+
For example, given the path `/settings/wallet/verify-account/country`:
763+
764+
1. The outermost suffix `country` is identified and stripped, leaving `/settings/wallet/verify-account`.
765+
2. `/settings/wallet/verify-account` still contains a dynamic suffix `verify-account`, which is stripped to get `/settings/wallet`.
766+
3. `/settings/wallet` is a static path - standard React Navigation parsing returns the base state.
767+
4. The parser walks back up: it checks that the focused screen of `/settings/wallet` is listed in `VERIFY_ACCOUNT.entryScreens`.
768+
5. Then it checks that the focused screen of the resolved `/settings/wallet/verify-account` state is listed in `COUNTRY.entryScreens`.
769+
6. If all authorization checks pass, the final navigation state is built for the full path.
770+
771+
#### Authorization per layer
772+
773+
Each suffix independently validates access via its own `entryScreens` array.
774+
The focused screen resolved from the layer directly beneath must be listed
775+
in the current suffix's `entryScreens`. If any layer fails authorization,
776+
the path falls back to standard React Navigation parsing and a warning is logged.
777+
778+
#### Configuration example
779+
780+
```ts
781+
DYNAMIC_ROUTES: {
782+
VERIFY_ACCOUNT: {
783+
path: 'verify-account',
784+
entryScreens: [SCREENS.SETTINGS.WALLET.ROOT, SCREENS.TRAVEL.MY_TRIPS],
785+
},
786+
ADDRESS_COUNTRY: {
787+
path: 'country',
788+
entryScreens: [SCREENS.SETTINGS.DYNAMIC_VERIFY_ACCOUNT],
789+
getRoute: (country = '') => `country${country ? `?country=${country}` : ''}`,
790+
queryParams: ['country'],
791+
},
792+
},
793+
```
794+
795+
With this configuration, `country` can be opened on top of `verify-account`
796+
because `DYNAMIC_VERIFY_ACCOUNT` is listed in `ADDRESS_COUNTRY.entryScreens`.
797+
Back navigation removes one suffix at a time:
798+
`/settings/wallet/verify-account/country``/settings/wallet/verify-account``/settings/wallet`.
799+
800+
#### Multi-segment suffixes in layered paths
801+
802+
Suffix layering works with multi-segment suffixes as well.
803+
For example, if `deep/verify-account` and `country` are both registered,
804+
the path `/settings/wallet/deep/verify-account/country` will first strip `country`,
805+
then strip `deep/verify-account`, and resolve `/settings/wallet` as the base.
806+
The matching algorithm always tests the longest candidate suffix first,
807+
so overlapping registrations are resolved deterministically.
808+
754809
### Dynamic routes with query parameters
755810

756811
Dynamic route suffixes can carry query parameters

docs/articles/new-expensify/wallet-and-payments/Share-a-Business-Bank-Account.md

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,6 @@ To share an existing account with other admins:
4040

4141
![Close the confirmation screen once the share is successful]({{site.url}}/assets/images/Help-ShareBankAccount-4.png){:width="100%"}
4242

43-
---
44-
4543
## What happens after sharing a business bank account
4644

4745
- The shared-to admins will receive a Concierge message letting them know the account was shared.
@@ -55,6 +53,25 @@ To share an existing account with other admins:
5553
**For non-US accounts:**
5654
- The shared account is ready to use immediately — no test transactions required.
5755

56+
## How business bank account sharing works when changing the payer
57+
58+
When changing the **Authorized Payer** in a Workspace:
59+
60+
- If you have access but the new payer doesn't:
61+
- You'll be prompted to share the bank account from the Authorized Payer screen.
62+
- The account shares immediately when you confirm.
63+
- A confirmation screen appears after successful sharing.
64+
65+
- If your own copy of the bank account is pending validation, and you try to assign a new payer who doesn’t have access:
66+
- You’ll see an error prompting you to **validate your account first** by entering the test transactions before changing payers.
67+
68+
- If **neither** you nor the new selected payer has access:
69+
- You’ll be shown an error telling you to ask the current payer to share the account.
70+
71+
This process ensures you can only set an Authorized Payer who already has bank account access or receives access during the payer change.
72+
73+
---
74+
5875
# FAQ
5976

6077
## Why share a business bank account?
@@ -80,4 +97,12 @@ If an admin already has access to the bank account, they won’t appear in the s
8097

8198
Yes — once an admin has validated the account (if required), they can share the bank account with other admins.
8299

100+
## Can I share a bank account during the payer change process?
101+
102+
Yes. If the new payer doesn’t already have access, you’ll be prompted to share the bank account before confirming the payer change. This ensures the new payer can reimburse expenses without delay.
103+
104+
## What happens if I try to set a payer but neither of us has bank account access?
105+
106+
You’ll be shown an error and prompted to ask the current payer to share the account with one of you before proceeding.
107+
83108
</div>

ios/NewExpensify/Info.plist

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
<key>CFBundlePackageType</key>
2424
<string>APPL</string>
2525
<key>CFBundleShortVersionString</key>
26-
<string>9.3.39</string>
26+
<string>9.3.40</string>
2727
<key>CFBundleSignature</key>
2828
<string>????</string>
2929
<key>CFBundleURLTypes</key>
@@ -44,7 +44,7 @@
4444
</dict>
4545
</array>
4646
<key>CFBundleVersion</key>
47-
<string>9.3.39.3</string>
47+
<string>9.3.40.1</string>
4848
<key>FullStory</key>
4949
<dict>
5050
<key>OrgId</key>

ios/NotificationServiceExtension/Info.plist

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
<key>CFBundleName</key>
1212
<string>$(PRODUCT_NAME)</string>
1313
<key>CFBundleShortVersionString</key>
14-
<string>9.3.39</string>
14+
<string>9.3.40</string>
1515
<key>CFBundleVersion</key>
16-
<string>9.3.39.3</string>
16+
<string>9.3.40.1</string>
1717
<key>NSExtension</key>
1818
<dict>
1919
<key>NSExtensionPointIdentifier</key>

ios/ShareViewController/Info.plist

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
<key>CFBundleName</key>
1212
<string>$(PRODUCT_NAME)</string>
1313
<key>CFBundleShortVersionString</key>
14-
<string>9.3.39</string>
14+
<string>9.3.40</string>
1515
<key>CFBundleVersion</key>
16-
<string>9.3.39.3</string>
16+
<string>9.3.40.1</string>
1717
<key>NSExtension</key>
1818
<dict>
1919
<key>NSExtensionAttributes</key>

package-lock.json

Lines changed: 10 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)