diff --git a/.github/actions/composite/setupNode/action.yml b/.github/actions/composite/setupNode/action.yml
index 6bdf500912c0..ffaa55c0b3be 100644
--- a/.github/actions/composite/setupNode/action.yml
+++ b/.github/actions/composite/setupNode/action.yml
@@ -24,6 +24,31 @@ runs:
path: desktop/node_modules
key: ${{ runner.os }}-desktop-node-modules-${{ hashFiles('desktop/package-lock.json') }}
+ - name: Check if patch files changed
+ id: patchCheck
+ shell: bash
+ run: |
+ set -e
+ if [[ `git diff main --name-only | grep \.patch` != null ]]; then
+ echo 'CHANGES_IN_PATCH_FILES=true' >> "$GITHUB_OUTPUT"
+ else
+ echo 'CHANGES_IN_PATCH_FILES=false' >> "$GITHUB_OUTPUT"
+ fi
+
+ - name: Patch root project node packages
+ shell: bash
+ if: |
+ steps.patchCheck.outputs.CHANGES_IN_PATCH_FILES == 'true' &&
+ steps.cache-node-modules.outputs.cache-hit == 'true'
+ run: npx patch-package
+
+ - name: Patch node packages for desktop submodule
+ shell: bash
+ if: |
+ steps.patchCheck.outputs.CHANGES_IN_PATCH_FILES == 'true' &&
+ steps.cache-desktop-node-modules.outputs.cache-hit == 'true'
+ run: cd desktop && npx patch-package
+
- name: Install root project node packages
if: steps.cache-node-modules.outputs.cache-hit != 'true'
uses: nick-fields/retry@v2
diff --git a/.github/workflows/README.md b/.github/workflows/README.md
index aa38a7778f31..e1b1696411b1 100644
--- a/.github/workflows/README.md
+++ b/.github/workflows/README.md
@@ -104,6 +104,11 @@ The GitHub workflows require a large list of secrets to deploy, notify and test
1. `APPLE_DEMO_PASSWORD` - Demo account password used for https://appstoreconnect.apple.com/
1. `BROWSERSTACK` - Used to access Browserstack's API
+### Important note about Secrets
+Secrets are available by default in most workflows. The exception to the rule is callable workflows. If a workflow is triggered by the `workflow_call` event, it will only have access to repo secrets if the workflow that called it passed in the secrets explicitly (for example, using `secrets: inherit`).
+
+Furthermore, secrets are not accessible in actions. If you need to access a secret in an action, you must declare it as an input and pass it in. GitHub _should_ still obfuscate the value of the secret in workflow run logs.
+
## Actions
All these _workflows_ are comprised of atomic _actions_. Most of the time, we can use pre-made and independently maintained actions to create powerful workflows that meet our needs. However, when we want to do something very specific or have a more complex or robust action in mind, we can create our own _actions_.
diff --git a/.github/workflows/deployExpensifyHelp.yml b/.github/workflows/deployExpensifyHelp.yml
index cb4e0f956657..ca7345ef9462 100644
--- a/.github/workflows/deployExpensifyHelp.yml
+++ b/.github/workflows/deployExpensifyHelp.yml
@@ -28,23 +28,27 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8
+
- name: Setup NodeJS
uses: Expensify/App/.github/actions/composite/setupNode@main
+
- name: Setup Pages
uses: actions/configure-pages@f156874f8191504dae5b037505266ed5dda6c382
+
- name: Create docs routes file
run: ./.github/scripts/createDocsRoutes.sh
+
- name: Build with Jekyll
uses: actions/jekyll-build-pages@0143c158f4fa0c5dcd99499a5d00859d79f70b0e
with:
source: ./docs/
destination: ./docs/_site
+
- name: Upload artifact
uses: actions/upload-pages-artifact@64bcae551a7b18bcb9a09042ddf1960979799187
with:
path: ./docs/_site
-
# Deployment job
deploy:
environment:
diff --git a/.github/workflows/e2ePerformanceTests.yml b/.github/workflows/e2ePerformanceTests.yml
index fe364b376e3b..d8f9cad138d9 100644
--- a/.github/workflows/e2ePerformanceTests.yml
+++ b/.github/workflows/e2ePerformanceTests.yml
@@ -46,6 +46,9 @@ jobs:
git fetch origin tag ${{ steps.getMostRecentRelease.outputs.VERSION }} --no-tags --depth=1
git switch --detach ${{ steps.getMostRecentRelease.outputs.VERSION }}
+ - name: Configure MapBox SDK
+ run: ./scripts/setup-mapbox-sdk.sh ${{ secrets.MAPBOX_SDK_DOWNLOAD_TOKEN }}
+
- name: Build APK
if: ${{ !fromJSON(steps.checkForExistingArtifact.outputs.exists) }}
uses: Expensify/App/.github/actions/composite/buildAndroidAPK@main
@@ -112,6 +115,9 @@ jobs:
- name: Checkout "delta ref"
run: git checkout ${{ steps.getDeltaRef.outputs.DELTA_REF }}
+ - name: Configure MapBox SDK
+ run: ./scripts/setup-mapbox-sdk.sh ${{ secrets.MAPBOX_SDK_DOWNLOAD_TOKEN }}
+
- name: Build APK
uses: Expensify/App/.github/actions/composite/buildAndroidAPK@main
with:
diff --git a/.github/workflows/platformDeploy.yml b/.github/workflows/platformDeploy.yml
index 2587d30477ae..84f8373ff247 100644
--- a/.github/workflows/platformDeploy.yml
+++ b/.github/workflows/platformDeploy.yml
@@ -36,6 +36,9 @@ jobs:
steps:
- uses: actions/checkout@v3
+ - name: Configure MapBox SDK
+ run: ./scripts/setup-mapbox-sdk.sh ${{ secrets.MAPBOX_SDK_DOWNLOAD_TOKEN }}
+
- uses: Expensify/App/.github/actions/composite/setupNode@main
- uses: ruby/setup-ruby@eae47962baca661befdfd24e4d6c34ade04858f7
@@ -144,6 +147,9 @@ jobs:
steps:
- uses: actions/checkout@v3
+ - name: Configure MapBox SDK
+ run: ./scripts/setup-mapbox-sdk.sh ${{ secrets.MAPBOX_SDK_DOWNLOAD_TOKEN }}
+
- uses: Expensify/App/.github/actions/composite/setupNode@main
- uses: ruby/setup-ruby@eae47962baca661befdfd24e4d6c34ade04858f7
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index fe234bc8373c..e79a02281ae0 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -42,7 +42,9 @@ jobs:
name: Storybook tests
steps:
- uses: actions/checkout@v3
+
- uses: Expensify/App/.github/actions/composite/setupNode@main
+
- name: Storybook run
run: npm run storybook -- --smoke-test --ci
diff --git a/.github/workflows/testBuild.yml b/.github/workflows/testBuild.yml
index adff13b2dba6..16fffcc2c65e 100644
--- a/.github/workflows/testBuild.yml
+++ b/.github/workflows/testBuild.yml
@@ -103,6 +103,9 @@ jobs:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+ - name: Configure MapBox SDK
+ run: ./scripts/setup-mapbox-sdk.sh ${{ secrets.MAPBOX_SDK_DOWNLOAD_TOKEN }}
+
- name: Run Fastlane beta test
id: runFastlaneBetaTest
run: bundle exec fastlane android build_internal
@@ -111,6 +114,8 @@ jobs:
S3_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
S3_BUCKET: ad-hoc-expensify-cash
S3_REGION: us-east-1
+ MYAPP_UPLOAD_STORE_PASSWORD: ${{ secrets.MYAPP_UPLOAD_STORE_PASSWORD }}
+ MYAPP_UPLOAD_KEY_PASSWORD: ${{ secrets.MYAPP_UPLOAD_KEY_PASSWORD }}
- uses: actions/upload-artifact@v3
with:
@@ -130,6 +135,9 @@ jobs:
with:
ref: ${{ github.event.pull_request.head.sha || needs.getBranchRef.outputs.REF }}
+ - name: Configure MapBox SDK
+ run: ./scripts/setup-mapbox-sdk.sh ${{ secrets.MAPBOX_SDK_DOWNLOAD_TOKEN }}
+
- name: Create .env.adhoc file based on staging and add PULL_REQUEST_NUMBER env to it
run: |
cp .env.staging .env.adhoc
diff --git a/.github/workflows/verifyPodfile.yml b/.github/workflows/verifyPodfile.yml
index 8b715a7047c4..64188769f0bd 100644
--- a/.github/workflows/verifyPodfile.yml
+++ b/.github/workflows/verifyPodfile.yml
@@ -15,5 +15,7 @@ jobs:
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
+
- uses: Expensify/App/.github/actions/composite/setupNode@main
+
- run: ./.github/scripts/verifyPodfile.sh
diff --git a/README.md b/README.md
index b453a278b29f..f0a94a16855c 100644
--- a/README.md
+++ b/README.md
@@ -50,13 +50,14 @@ For an M1 Mac, read this [SO](https://stackoverflow.com/c/expensify/questions/11
* Install project gems, including cocoapods, using bundler to ensure everyone uses the same versions. In the project root, run: `bundle install`
* 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.
* 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`
* Changes applied to Javascript will be applied automatically, any changes to native code will require a recompile
## Running the Android app 🤖
-* To install the Android dependencies, run: `npm install`
+* Before installing Android dependencies, you need to obtain a token from Mapbox to download their SDKs. Please run `npm run configure-mapbox` and follow the instructions. If you already did this step for iOS, there is no need to repeat this step.
* Go through the instructions on [this SO post](https://stackoverflow.com/c/expensify/questions/13283/13284#13284) to start running the app on android.
* For more information, go through the official React-Native instructions on [this page](https://reactnative.dev/docs/environment-setup#development-os) for "React Native CLI Quickstart" > Mac OS > Android
* 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)
@@ -418,4 +419,4 @@ In order to compile a production desktop build, run `npm run desktop-build`, thi
In order to compile a production iOS build, run `npm run ios-build`, this will generate a `Chat.ipa` in the root directory of this project.
#### Local production build the Android app
-To build an APK to share run (e.g. via Slack), run `npm run android-build`, this will generate a new APK in the `android/app` folder.
+To build an APK to share run (e.g. via Slack), run `npm run android-build`, this will generate a new APK in the `android/app` folder.
\ No newline at end of file
diff --git a/__mocks__/react-native.js b/__mocks__/react-native.js
index 26a943ce62bc..006d1aee38af 100644
--- a/__mocks__/react-native.js
+++ b/__mocks__/react-native.js
@@ -1,7 +1,6 @@
// eslint-disable-next-line no-restricted-imports
import * as ReactNative from 'react-native';
import _ from 'underscore';
-import CONST from '../src/CONST';
jest.doMock('react-native', () => {
let url = 'https://new.expensify.com/';
@@ -15,7 +14,12 @@ jest.doMock('react-native', () => {
// runs against index.native.js source and so anything that is testing a component reliant on withWindowDimensions()
// would be most commonly assumed to be on a mobile phone vs. a tablet or desktop style view. This behavior can be
// overridden by explicitly setting the dimensions inside a test via Dimensions.set()
- let dimensions = CONST.TESTING.SCREEN_SIZE.SMALL;
+ let dimensions = {
+ width: 300,
+ height: 700,
+ scale: 1,
+ fontScale: 1,
+ };
return Object.setPrototypeOf(
{
diff --git a/android/app/build.gradle b/android/app/build.gradle
index d8d0c11a1a0c..294d2d334ffd 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -22,7 +22,7 @@ react {
// The list of variants to that are debuggable. For those we're going to
// skip the bundling of the JS bundle and the assets. By default is just 'debug'.
// If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.
- // debuggableVariants = ["liteDebug", "prodDebug"]
+ debuggableVariants = ["developmentDebug"]
/* Bundling */
// A list containing the node command and its flags. Default is just 'node'.
@@ -90,8 +90,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1001035411
- versionName "1.3.54-11"
+ versionCode 1001035705
+ versionName "1.3.57-5"
}
flavorDimensions "default"
diff --git a/android/build.gradle b/android/build.gradle
index c04314a9aa0c..d7e9529ae6dd 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -14,6 +14,10 @@ buildscript {
multiDexEnabled = true
googlePlayServicesVersion = "17.0.0"
kotlinVersion = '1.6.20'
+
+ // This property configures the type of Mapbox SDK used by the @rnmapbox/maps library.
+ // "mapbox" indicates the usage of the Mapbox SDK.
+ RNMapboxMapsImpl = "mapbox"
}
repositories {
google()
@@ -48,5 +52,23 @@ allprojects {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url("$rootDir/../node_modules/react-native/android")
}
+ maven {
+ // Mapbox SDK requires authentication to download from Mapbox's private Maven repository.
+ url 'https://api.mapbox.com/downloads/v2/releases/maven'
+ authentication {
+ basic(BasicAuthentication)
+ }
+ credentials {
+ // 'mapbox' is the fixed username for Mapbox's Maven repository.
+ username = 'mapbox'
+
+ // The value for password is read from the 'MAPBOX_DOWNLOADS_TOKEN' gradle property.
+ // Run "npm run setup-mapbox-sdk" to set this property in «USER_HOME»/.gradle/gradle.properties
+
+ // Example gradle.properties entry:
+ // MAPBOX_DOWNLOADS_TOKEN=YOUR_SECRET_TOKEN_HERE
+ password = project.properties['MAPBOX_DOWNLOADS_TOKEN'] ?: ""
+ }
+ }
}
-}
\ No newline at end of file
+}
diff --git a/assets/emojis/index.js b/assets/emojis/index.js
index 3882ac7f0fa6..c8dab36f57d9 100644
--- a/assets/emojis/index.js
+++ b/assets/emojis/index.js
@@ -15,13 +15,18 @@ const emojiNameTable = _.reduce(
{},
);
-const emojiCodeTable = _.reduce(
+const emojiCodeTableWithSkinTones = _.reduce(
emojis,
(prev, cur) => {
const newValue = prev;
if (!cur.header) {
newValue[cur.code] = cur;
}
+ if (cur.types) {
+ cur.types.forEach((type) => {
+ newValue[type] = cur;
+ });
+ }
return newValue;
},
{},
@@ -32,5 +37,5 @@ const localeEmojis = {
es: esEmojis,
};
-export {emojiNameTable, emojiCodeTable, localeEmojis};
+export {emojiNameTable, emojiCodeTableWithSkinTones, localeEmojis};
export {skinTones, categoryFrequentlyUsed, default} from './common';
diff --git a/assets/images/emptystate__routepending.svg b/assets/images/emptystate__routepending.svg
new file mode 100644
index 000000000000..7646917046cc
--- /dev/null
+++ b/assets/images/emptystate__routepending.svg
@@ -0,0 +1,43 @@
+
+
+
diff --git a/assets/images/expensify-app-icon.svg b/assets/images/expensify-app-icon.svg
new file mode 100644
index 000000000000..a0adfe7dd952
--- /dev/null
+++ b/assets/images/expensify-app-icon.svg
@@ -0,0 +1,18 @@
+
+
+
diff --git a/assets/images/signIn/apple-logo.svg b/assets/images/signIn/apple-logo.svg
new file mode 100644
index 000000000000..4e428fc41aed
--- /dev/null
+++ b/assets/images/signIn/apple-logo.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/images/signIn/google-logo.svg b/assets/images/signIn/google-logo.svg
new file mode 100644
index 000000000000..ebdd4be8cade
--- /dev/null
+++ b/assets/images/signIn/google-logo.svg
@@ -0,0 +1,14 @@
+
diff --git a/contributingGuides/APPLE_GOOGLE_SIGNIN.md b/contributingGuides/APPLE_GOOGLE_SIGNIN.md
new file mode 100644
index 000000000000..9032a99dfbbd
--- /dev/null
+++ b/contributingGuides/APPLE_GOOGLE_SIGNIN.md
@@ -0,0 +1,272 @@
+# Overview
+
+"Sign in with Apple" and "Sign in with Google" are multi-platform sign-in methods. Both Apple and Google provide official tools, but we have to manage the fact that the behavior, APIs, and constraints for each of those tools varies quite a bit. The architecture of Apple and Google sign-in aims to provide as consistent a user experience and implementation as possible, but our options are limited by Apple and Google. This document will describe the user experience, tooling, and options available on each and why this feature is implemented the way it is.
+
+## Terms
+
+The **client app**, or **client**: this refers to the application that is attempting to access a user's resources hosted by a third party. In this case, this is the Expensify app.
+
+The **third party**: this is any other service that the client app (Expensify) wants to interact with on behalf of a user. In this case, Apple or Google. Since this flow is specifically concerned with authentication, it may also be called the **third-party authentication provider**.
+
+**Third-party sign-in**: a general phrase to refer to either "Sign in with Apple" or "Sign in with Google" (or any future similar features). Any authentication method that involves authentication with a service not provided by Expensify.
+
+## How third-party sign-in works
+
+When the user signs in to the app with a third party like Apple or Google, there is a general flow used by all of them:
+
+1. The user presses a button within the client app to start their preferred sign-in process.
+2. The user is sent to a UI owned by the third-party to sign in (e.g., the Google sign-in web page hosted by Google, or the Sign in with Apple bottom sheet provided by iOS).
+3. When the user successfully signs in with the third party, the third party generates a token and sends it back to the client app.
+4. The client app sends the token to the client backend API, where the token is verified and the user's email is extracted from the token, and the user is signed in.
+
+Both services also require registering a "client ID", along with some configuration we'll explain next. For apps that aren't built using XCode, Apple calls this a "service ID", and it can be configured under "[Services IDs](https://developer.apple.com/account/resources/identifiers/list/serviceId)" in "Certificates, Identifiers & Profiles" in the Apple Developer console. (For apps made using XCode, like the iOS app, the bundle identifier is used as the client ID.) For Google, this configuration is done under "[Credentials](https://console.cloud.google.com/apis/credentials)" in the Google Cloud console.
+
+### On web
+
+We'll cover web details first, because web is treated as the "general use" case for services like this, and then platform-specific tools are built on top of that, which we'll cover afterwards.
+
+Both services also provide official Javascript libraries for integrating their services on web platforms. Using these libraries offers improved security and decreased maintenance burden over using the APIs directly, as Google notes while they heavily discourage using their auth APIs directly; but they also add additional constraints, which will be described later in the document.
+
+How the third party sends the token in step 3 depends on the third party's implementation and the app's configuration. In both Apple and Google's case, there are two main modes: "pop-up", and "redirect".
+
+#### Redirect mode
+
+From the user's perspective, redirect mode will usually look like opening the third party's sign-in page in the same browser window, and then redirecting back to the client app in that window. But re-use of the same window isn't required. The key point is the redirection back to the client app, via the third-party sign-in form making an HTTPS request.
+
+In both the Google and Apple JS libraries, the request endpoint, found at the "redirect URI", must handle a POST request with form data in the body, which contains the token we need to send to the client back-end API. This pattern is not easily implemented with the existing single-page web app, and so we use the other mode: "pop-up mode".
+
+The redirect URI must match a URI in the Google or Apple client ID configuration.
+
+#### Pop-up mode
+
+Pop-up mode opens a pop-up window to show the third-party sign-in form. But it also changes how tokens are given to the client app. Instead of an HTTPS request, they are returned by the JS libraries in memory, either via a callback (Google) or a promise (Apple).
+
+Apple and Google both check that the client app is running on an allowed domain. The sign-in process will fail otherwise. Google allows localhost, but Apple does not, and so testing Apple in development environments requires hosting the client app on a domain that the Apple client ID (or "service ID", in Apple's case) has been configured with.
+
+In the case of Apple, sometimes it will silently fail at the very end of the sign-in process, where the only sign that something is wrong is that the pop-up fails to close. In this case, it's very likely that configuration mismatch is the issue.
+
+In addition, Apple will require a valid redirect URI be provided in the library's configuration, even though it is not used.
+
+### Considerations for non-web platforms
+
+For apps that aren't web-based, there are other options:
+
+Sign in with Google provides libraries on [Android](https://developers.google.com/identity/sign-in/android/start) and [iOS](https://developers.google.com/identity/sign-in/ios/start) to use that will authenticate the mobile app is who it says it is, via app signing. For React Native, we use the [react-native-google-signin](https://github.com/react-native-google-signin/google-signin) wrapper to use these libraries.
+
+The [iOS implementation for Sign in with Apple](https://developer.apple.com/documentation/authenticationservices/implementing_user_authentication_with_sign_in_with_apple?language=objc) can also verify the app's bundle ID and the team who signed it. We use the [react-native-apple-authentication](https://github.com/invertase/react-native-apple-authentication) wrapper library for this.
+
+There is no official library for Sign in with Apple on Android, so it has to work with the web tooling; but Android can't meet the requirements of the official JS library. It isn't hosted on a domain, which is required for pop-up flow, and can't receive an HTTPS request, which is required for redirect flow with the official JS library. To deal with this, react-native-apple-authentication's implementation uses a webview on Android, which can intercept the redirect POST and pass the data directly to the react-native app.
+
+#### Issues with third-party sign-in and Electron
+
+These tools aren't built with Electron or similar desktop apps in mind, and that presents similar challenges as Sign in with Apple for Android:
+
+1. Like mobile platforms, Electron does not have the option of validating the origin of the client app authentication request using a registered HTTPS domain
+2. Unlike many mobile platforms, there are not official tools for Electron or desktop apps in general.
+3. Attempts to get Electron to work like web are either blocked by the third-party authentication provider, broken, or inadvisable.
+
+These are the specific issues we've seen:
+
+1. [Google stopped allowing its sign-in page to render inside embedded browser frameworks](https://security.googleblog.com/2019/04/better-protection-against-man-in-middle.html) such as Electron. This means we can't open the sign-in flow inside the an Electron window. However, opening the sign-in form in the user's default web browser did work.
+2. On the other hand, opening the Sign in with Apple form in the user's default browser instead of Electron does _not_ work, and renders an Apple page with an empty body instead of the sign-in form.
+
+We decided to instead redirect the user to a dedicated page in the web app to sign in. Apple and Google each have their own routes, `/sign-in-with-apple` and `/sign-in-with-google`, where the user is shown another button to click to start the sign-in process on web (since it shows a pop-up, the user must click the button directly, otherwise the pop-up would be blocked). After signing in, the user will be shown a deep link prompt in the browser to open the desktop app, where they will be signed in using a short-lived token from the Expensify API.
+
+Due to Expensify's expectation that a user will be using the same account on web and desktop, we do not go through this process if the user was already signed in, but instead the web app prompts the user to go back to desktop again, which will also sign them in on the desktop app.
+
+## Additional design constraints
+
+### New Google web library limits button style choices
+
+The current Sign in with Google library for web [does not allow arbitrary customization of the sign-in button](https://developers.google.com/identity/gsi/web/guides/offerings#sign_in_with_google_button). (The recently deprecated version of the Sign in with Google for web did offer this capability.)
+
+This means the button is limited in design: there are no offline or hover states, and there can only be a white background for the button. We were able to get the official Apple button options to match, so we used the Google options as the starting point for the design.
+
+### Sign in with Apple does not allow `localhost`
+
+Unlike Google, Apple does not allow `localhost` as a domain to host a pop-up or redirect to. In order to test Sign in with Apple on web or desktop, this means we have to:
+
+1. Use SSH tunneling to host the app on an HTTPS domain
+2. Create a test Apple Service ID configuration in the Apple developer console, to allow testing the sign-in flow from its start until the point Apple sends its token to the Expensify app.
+3. Use token interception on Android to test the web and desktop sign-in flow from the point where the front-end Expensify app has received a token, until the point where the user is signed in to Expensify using that token.
+
+These steps are covered in more detail in the "testing" section below.
+
+# Testing Apple/Google sign-in
+
+Due to some technical constraints, Apple and Google sign-in may require additional configuration to be able to work in the development environment as expected. This document describes any additional steps for each platform.
+
+## Apple
+
+### iOS/Android
+
+The iOS and Android implementations do not require extra steps to test, aside from signing into an Apple account on the iOS device before being able to use Sign in with Apple.
+
+### Web and desktop
+
+#### Render the web Sign In with Apple button in development
+
+The Google Sign In button renders differently in development mode. To prevent confusion
+for developers about a possible regression, we decided to not render third party buttons in
+development mode.
+
+To show the Apple Sign In button in development mode, you can comment out the following code in the
+LoginForm.js file:
+
+```js
+if (CONFIG.ENVIRONMENT === CONST.ENVIRONMENT.DEV) {
+ return;
+}
+```
+
+#### Port requirements
+
+The Sign in with Apple process will break after the user signs in if the pop-up process is not started from a page at an HTTPS domain registered with Apple. To fix this, you could make a new configuration with your own HTTPS domain, but then the Apple configuration won't match that of Expensify's backend.
+
+So to be able to test this, we have two parts:
+1. Create a valid Sign in with Apple token using valid configuration for the Expensify app, by creating and intercepting one on Android
+2. Host the development web app at an HTTPS domain using SSH tunneling, and in the web app use a custom Apple config with this HTTPS domain registered
+
+Requirements:
+1. Authorization on an Apple Development account or team to create new Service IDs
+2. An SSH tunneling tool that provides static HTTPS domains. [ngrok](https://ngrok.com) is a good choice that provides one static HTTPS domain for a free account.
+
+#### Generate the token to use
+
+**Note**: complete this step before changing other configuration to test Apple on web and desktop, as updating those will cause Android to stop working while the configuration is changed.
+
+On an Android build, alter the `AppleSignIn` component to log the token generated, instead of sending it to the Expensify API:
+
+```js
+// .then((token) => Session.beginAppleSignIn(token))
+ .then((token) => console.log("TOKEN: ", token))
+```
+
+If you need to check that you received the correct data, check it on [jwt.io](https://jwt.io), which will decode it if it is a valid JWT token. It will also show when the token expires.
+
+Hardcode this token into `Session.beginAppleSignIn`, and but also verify a valid token was passed into the function, for example:
+
+```
+function beginAppleSignIn(idToken) {
++ // Show that a token was passed in, without logging the token, for privacy
++ window.alert(`ORIGINAL ID TOKEN LENGTH: ${idToken.length}`);
++ const hardcodedToken = '...';
+ const {optimisticData, successData, failureData} = signInAttemptState();
++ API.write('SignInWithApple', {idToken: hardcodedToken}, {optimisticData, successData, failureData});
+- API.write('SignInWithApple', {idToken}, {optimisticData, successData, failureData});
+}
+```
+
+#### Configure the SSH tunneling
+
+You can use any SSH tunneling service that allows you to configure custom subdomains so that we have a consistent address to use. We'll use ngrok in these examples, but ngrok requires a paid account for this. If you need a free option, try serveo.net.
+
+After you've set ngrok up to be able to run on your machine (requires configuring a key with the command line tool, instructions provided by the ngrok website after you create an account), test hosting the web app on a custom subdomain. This example assumes the development web app is running at `localhost:8082`:
+
+```
+ngrok http 8082 --host-header="localhost:8082" --subdomain=mysubdomain
+```
+
+The `--host-header` flag is there to avoid webpack errors with header validation. In addition, add `allowedHosts: 'all'` to the dev server config in `webpack.dev.js`:
+
+```js
+devServer: {
+ ...,
+ allowedHosts: 'all',
+}
+```
+
+#### Configure Apple Service ID
+
+Now that you have an HTTPS address to use, you can create an Apple Service ID configuration that will work with it.
+
+1. Create a new app ID on your Apple development team that can be used to test this, following the instructions [here](https://github.com/invertase/react-native-apple-authentication/blob/main/docs/INITIAL_SETUP.md).
+2. Create a new service ID following the instructions [here](https://github.com/invertase/react-native-apple-authentication/blob/main/docs/ANDROID_EXTRA.md). For allowed domains, enter your SSH tunnel address (e.g., `https://mysubdomain.ngrok-free.app`), and for redirect URLs, just make up an endpoint, it's never actually invoked (e.g., `mysubdomain.ngrok-free.app/appleauth`).
+
+Notes:
+- Depending on your Apple account configuration, you may need additional permissions to access some of the features described in the instructions above.
+- While the Apple Sign In configuration requires a `clientId`, the Apple Developer console calls this a `Service ID`.
+
+Finally, edit `.env` to use your client (service) ID and redirect URL config:
+
+```
+ASI_CLIENTID_OVERRIDE=com.example.test
+ASI_REDIRECTURI_OVERRIDE=https://mysubdomain.ngrok-free.app/appleauth
+```
+
+#### Run the app
+
+Remember that you will need to restart the web server if you make a change to the `.env` file.
+
+### Desktop
+
+Desktop will require the same configuration, with these additional steps:
+
+#### Configure web app URL in .env
+
+Add `NEW_EXPENSIFY_URL` to .env, and set it to the HTTPS URL where the web app can be found, for example:
+
+```
+NEW_EXPENSIFY_URL=https://subdomain.ngrok-free.app
+```
+
+This is required because the desktop app needs to know the address of the web app, and must open it at the HTTPS domain configured to work with Sign in with Apple.
+
+Note that changing this value to a domain that isn't configured for use with Expensify will cause Android to break, as it is still using the real client ID, but now has an incorrect value for `redirectURI`.
+
+#### Set Environment to something other than "Development"
+
+The DeepLinkWrapper component will not handle deep links in the development environment. To be able to test deep linking, you must set the environment to something other than "Development".
+
+Within the `.env` file, set `envName` to something other than "Development", for example:
+
+```
+envName=Staging
+```
+
+Alternatively, within the `DeepLinkWrapper/index.website.js` file you can set the `CONFIG.ENVIRONMENT` to something other than "Development".
+
+#### Handle deep links in dev on MacOS
+
+If developing on MacOS, the development desktop app can't handle deeplinks correctly. To be able to test deeplinking back to the app, follow these steps:
+
+1. Create a "real" build of the desktop app, which can handle deep links, open the build folder, and install the dmg there:
+
+```
+npm run desktop-build --publish=never
+open desktop-build
+# Then double-click "NewExpensify.dmg" in Finder window
+```
+
+2. Even with this build, the deep link may not be handled by the correct app, as the development Electron config seems to intercept it sometimes. To manage this, install [SwiftDefaultApps](https://github.com/Lord-Kamina/SwiftDefaultApps), which adds a preference pane that can be used to configure which app should handle deep links.
+
+## Google
+
+### Web
+
+#### Render the web Sign In with Google button in Development
+
+The Google Sign In button renders differently in development mode. To prevent confusion
+for developers about a possible regression, we decided to not render third party buttons in
+development mode.
+
+To show the Google Sign In button in development mode, you can comment out the following code in the
+LoginForm.js file:
+
+```js
+if (CONFIG.ENVIRONMENT === CONST.ENVIRONMENT.DEV) {
+ return;
+}
+```
+
+#### Port requirements
+
+Google allows the web app to be hosted at localhost, but according to the
+current Google console configuration for the Expensify client ID, it must be
+hosted on port 8082.
+
+### Desktop
+
+#### Set Environment to something other than "Development"
+
+The DeepLinkWrapper component will not handle deep links in the development environment. To be able to test deep linking, you must set the environment to something other than "Development".
diff --git a/contributingGuides/TS_STYLE.md b/contributingGuides/TS_STYLE.md
index 414cb9d49ef1..0d6774792c45 100644
--- a/contributingGuides/TS_STYLE.md
+++ b/contributingGuides/TS_STYLE.md
@@ -23,6 +23,7 @@
- [1.16 Reusable Types](#reusable-types)
- [1.17 `.tsx`](#tsx)
- [1.18 No inline prop types](#no-inline-prop-types)
+ - [1.19 Satisfies operator](#satisfies-operator)
- [Exception to Rules](#exception-to-rules)
- [Communication Items](#communication-items)
- [Migration Guidelines](#migration-guidelines)
@@ -101,7 +102,7 @@ type Foo = {
-- [1.2](#d-ts-extension) **`d.ts` Extension**: Do not use `d.ts` file extension even when a file contains only type declarations. Only exception is the `global.d.ts` file in which third party packages can be modified using module augmentation. Refer to the [Communication Items](#communication-items) section to learn more about module augmentation.
+- [1.2](#d-ts-extension) **`d.ts` Extension**: Do not use `d.ts` file extension even when a file contains only type declarations. Only exceptions are `src/types/global.d.ts` and `src/types/modules/*.d.ts` files in which third party packages can be modified using module augmentation. Refer to the [Communication Items](#communication-items) section to learn more about module augmentation.
> Why? Type errors in `d.ts` files are not checked by TypeScript [^1].
@@ -358,7 +359,7 @@ type Foo = {
-- [1.15](#file-organization) **File organization**: In modules with platform-specific implementations, create `types.ts` to define shared types. Import and use shared types in each platform specific files.
+- [1.15](#file-organization) **File organization**: In modules with platform-specific implementations, create `types.ts` to define shared types. Import and use shared types in each platform specific files. Do not use [`satisfies` operator](#satisfies-operator) for platform-specific implementations, always define shared types that complies with all variants.
> Why? To encourage consistent API across platform-specific implementations. If you're migrating module that doesn't have a default implement (i.e. `index.ts`, e.g. `getPlatform`), refer to [Migration Guidelines](#migration-guidelines) for further information.
@@ -458,6 +459,34 @@ type Foo = {
}
```
+
+
+- [1.19](#satisfies-operator) **Satisfies Operator**: Use the `satisfies` operator when you need to validate that the structure of an expression matches a specific type, without affecting the resulting type of the expression.
+
+ > Why? TypeScript developers often want to ensure that an expression aligns with a certain type, but they also want to retain the most specific type possible for inference purposes. The `satisfies` operator assists in doing both.
+
+ ```ts
+ // BAD
+ const sizingStyles = {
+ w50: {
+ width: '50%',
+ },
+ mw100: {
+ maxWidth: '100%',
+ },
+ } as const;
+
+ // GOOD
+ const sizingStyles = {
+ w50: {
+ width: '50%',
+ },
+ mw100: {
+ maxWidth: '100%',
+ },
+ } satisfies Record;
+ ```
+
## Exception to Rules
Most of the rules are enforced in ESLint or checked by TypeScript. If you think your particular situation warrants an exception, post the context in the `#expensify-open-source` Slack channel with your message prefixed with `TS EXCEPTION:`. The internal engineer assigned to the PR should be the one that approves each exception, however all discussion regarding granting exceptions should happen in the public channel instead of the GitHub PR page so that the TS migration team can access them easily.
@@ -472,9 +501,11 @@ This rule will apply until the migration is done. After the migration, discussio
- I think types definitions in a third party library is incomplete or incorrect
-When the library indeed contains incorrect or missing type definitions and it cannot be updated, use module augmentation to correct them. All module augmentation code should be contained in `/src/global.d.ts`.
+When the library indeed contains incorrect or missing type definitions and it cannot be updated, use module augmentation to correct them. All module augmentation code should be contained in `/src/types/modules/*.d.ts`, each library as a separate file.
```ts
+// external-library-name.d.ts
+
declare module "external-library-name" {
interface LibraryComponentProps {
// Add or modify typings
diff --git a/desktop/main.js b/desktop/main.js
index 3a153b4d13c5..b19bef060ba9 100644
--- a/desktop/main.js
+++ b/desktop/main.js
@@ -11,7 +11,7 @@ const CONFIG = require('../src/CONFIG').default;
const CONST = require('../src/CONST').default;
const Localize = require('../src/libs/Localize');
-const port = process.env.PORT || 8080;
+const port = process.env.PORT || 8082;
const {DESKTOP_SHORTCUT_ACCELERATOR} = CONST;
app.setName('New Expensify');
diff --git a/docs/Card-Rev-Share-for-Approved-Partners.md b/docs/Card-Rev-Share-for-Approved-Partners.md
new file mode 100644
index 000000000000..9b5647a004d3
--- /dev/null
+++ b/docs/Card-Rev-Share-for-Approved-Partners.md
@@ -0,0 +1,17 @@
+---
+title: Expensify Card revenue share for ExpensifyApproved! partners
+description: Earn money when your clients adopt the Expensify Card
+---
+
+
+# About
+Start making more with us! We're thrilled to announce a new incentive for our US-based ExpensifyApproved! partner accountants. You can now earn additional income for your firm every time your client uses their Expensify Card. We're offering 0.5% of the total Expensify Card spend of your clients in cashback returned to your firm. The more your clients spend, the more cashback your firm receives!
+ This program is currently only available to US-based ExpensifyApproved! partner accountants.
+
+# How-to
+To benefit from this program, all you need to do is ensure that you are listed as a domain admin on your client's Expensify account. If you're not currently a domain admin, your client can follow the instructions outlined in [our help article](https://community.expensify.com/discussion/5749/how-to-add-and-remove-domain-admins#:~:text=Domain%20Admins%20have%20total%20control,a%20member%20of%20the%20domain.) to assign you this role.
+# FAQ
+- What if my firm is not permitted to accept revenue share from our clients?
+ We understand that different firms may have different policies. If your firm is unable to accept this revenue share, you can pass the revenue share back to your client to give them an additional 0.5% of cash back using your own internal payment tools.
+- What if my firm does not wish to participate in the program?
+ Please reach out to your assigned partner manager at new.expensify.com to inform them you would not like to accept the revenue share nor do you want to pass the revenue share to your clients.
diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html
index 07f9f23bdbbf..39d62bb0ea9c 100644
--- a/docs/_layouts/default.html
+++ b/docs/_layouts/default.html
@@ -2,7 +2,7 @@
-
+
Expensify Help
@@ -13,12 +13,17 @@
+
+
+
{% seo %}
-
+
+
+