diff --git a/.eslintrc.js b/.eslintrc.js index f509613d8819..afcea2cb3401 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,5 +1,6 @@ module.exports = { - extends: ['expensify', 'plugin:storybook/recommended'], + extends: ['expensify', 'plugin:storybook/recommended', 'plugin:react-hooks/recommended'], + plugins: ['react-hooks'], parser: 'babel-eslint', ignorePatterns: ['!.*', 'src/vendor', '.github/actions/**/index.js', 'desktop/dist/*.js', 'dist/*.js', 'node_modules/.bin/**', '.git/**'], env: { diff --git a/.github/actions/composite/setupNode/action.yml b/.github/actions/composite/setupNode/action.yml index d475acf5380f..643c707da230 100644 --- a/.github/actions/composite/setupNode/action.yml +++ b/.github/actions/composite/setupNode/action.yml @@ -12,6 +12,6 @@ runs: - name: Install node packages uses: nick-invision/retry@0711ba3d7808574133d713a0d92d2941be03a350 with: - timeout_minutes: 10 - max_attempts: 5 + timeout_minutes: 30 + max_attempts: 3 command: npm ci diff --git a/.github/workflows/platformDeploy.yml b/.github/workflows/platformDeploy.yml index 4a8a3fd732c0..e1b38b713d7b 100644 --- a/.github/workflows/platformDeploy.yml +++ b/.github/workflows/platformDeploy.yml @@ -10,7 +10,7 @@ on: env: SHOULD_DEPLOY_PRODUCTION: ${{ github.event_name == 'release' }} - DEVELOPER_DIR: /Applications/Xcode_14.0.1.app/Contents/Developer + DEVELOPER_DIR: /Applications/Xcode_14.1.app/Contents/Developer jobs: validateActor: diff --git a/.github/workflows/testBuild.yml b/.github/workflows/testBuild.yml index 6b068c9f6f8e..f52453dcdf40 100644 --- a/.github/workflows/testBuild.yml +++ b/.github/workflows/testBuild.yml @@ -11,7 +11,7 @@ on: branches: ['*ci-test/**'] env: - DEVELOPER_DIR: /Applications/Xcode_14.0.1.app/Contents/Developer + DEVELOPER_DIR: /Applications/Xcode_14.1.app/Contents/Developer jobs: validateActor: diff --git a/__mocks__/@react-native-camera-roll/camera-roll.js b/__mocks__/@react-native-camera-roll/camera-roll.js new file mode 100644 index 000000000000..4274cd531a85 --- /dev/null +++ b/__mocks__/@react-native-camera-roll/camera-roll.js @@ -0,0 +1,5 @@ +export default { + CameraRoll: { + save: jest.fn(), + }, +}; diff --git a/__mocks__/@ua/react-native-airship.js b/__mocks__/@ua/react-native-airship.js new file mode 100644 index 000000000000..ec394d982ab8 --- /dev/null +++ b/__mocks__/@ua/react-native-airship.js @@ -0,0 +1,40 @@ +const EventType = { + NotificationResponse: 'notificationResponse', + PushReceived: 'pushReceived', +}; + +const iOS = { + ForegroundPresentationOption: { + Alert: 'alert', + Sound: 'sound', + Badge: 'badge', + }, +}; + +const Airship = { + setUserNotificationsEnabled: jest.fn(), + addListener: jest.fn(), + removeAllListeners: jest.fn(), + setBadgeNumber: jest.fn(), + push: { + iOS: { + setBadgeNumber: jest.fn(), + setForegroundPresentationOptions: jest.fn(), + }, + enableUserNotifications: () => Promise.resolve(false), + clearNotifications: jest.fn(), + getNotificationStatus: () => Promise.resolve({airshipOptIn: false, systemEnabled: false}), + }, + contact: { + identify: jest.fn(), + getNamedUserId: jest.fn(), + reset: jest.fn(), + }, +}; + +export default Airship; + +export { + EventType, + iOS, +}; diff --git a/__mocks__/urbanairship-react-native.js b/__mocks__/urbanairship-react-native.js deleted file mode 100644 index 5bc90f267bf2..000000000000 --- a/__mocks__/urbanairship-react-native.js +++ /dev/null @@ -1,33 +0,0 @@ -const EventType = { - NotificationResponse: 'notificationResponse', - PushReceived: 'pushReceived', -}; - -const iOS = { - ForegroundPresentationOption: { - Alert: 'alert', - Sound: 'sound', - Badge: 'badge', - }, -}; - -const UrbanAirship = { - setUserNotificationsEnabled: jest.fn(), - clearNotifications: jest.fn(), - addListener: jest.fn(), - getNamedUser: jest.fn(), - enableUserPushNotifications: () => Promise.resolve(false), - setNamedUser: jest.fn(), - removeAllListeners: jest.fn(), - setBadgeNumber: jest.fn(), - setForegroundPresentationOptions: jest.fn(), - getNotificationStatus: () => Promise.resolve({airshipOptIn: false, systemEnabled: false}), -}; - -export default UrbanAirship; - -export { - EventType, - iOS, - UrbanAirship, -}; diff --git a/android/app/build.gradle b/android/app/build.gradle index 206473700086..9e57edc17154 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -156,8 +156,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001028900 - versionName "1.2.89-0" + versionCode 1001029202 + versionName "1.2.92-2" buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() if (isNewArchitectureEnabled()) { @@ -332,6 +332,10 @@ dependencies { // Plaid SDK implementation project(':react-native-plaid-link-sdk') + + // Fixes a version conflict between airship and react-native-plaid-link-sdk + implementation "androidx.work:work-runtime-ktx:2.8.0" + // This okhttp3 dependency prevents the app from crashing - See https://github.com/plaid/react-native-plaid-link-sdk/issues/74#issuecomment-648435002 implementation "com.squareup.okhttp3:okhttp-urlconnection:4.+" } diff --git a/android/build.gradle b/android/build.gradle index 2f24d17f2073..938b8c48f054 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -3,8 +3,8 @@ buildscript { ext { minSdkVersion = 21 - compileSdkVersion = 32 - targetSdkVersion = 32 + compileSdkVersion = 33 + targetSdkVersion = 33 if (System.properties['os.arch'] == "aarch64") { // For M1 Users we need to use the NDK 24 which added support for aarch64 diff --git a/config/proxyConfig.js b/config/proxyConfig.js new file mode 100644 index 000000000000..fa09c436461f --- /dev/null +++ b/config/proxyConfig.js @@ -0,0 +1,9 @@ +/** + * These are the base API roots used to send requests to the proxy. + * We only specify for staging URLs as API requests are sent to the production + * servers by default. + */ +module.exports = { + STAGING: '/staging/', + STAGING_SECURE: '/staging-secure/', +}; diff --git a/config/webpack/webpack.dev.js b/config/webpack/webpack.dev.js index 0b48c4e5f35a..2867d857ee04 100644 --- a/config/webpack/webpack.dev.js +++ b/config/webpack/webpack.dev.js @@ -21,6 +21,7 @@ module.exports = (env = {}) => portfinder.getPortPromise({port: BASE_PORT}) : { proxy: { '/api': 'http://[::1]:9000', + '/staging': 'http://[::1]:9000', '/chat-attachments': 'http://[::1]:9000', }, }; diff --git a/contributingGuides/CONTRIBUTING.md b/contributingGuides/CONTRIBUTING.md index 035f186bc5dc..43cf91e08473 100644 --- a/contributingGuides/CONTRIBUTING.md +++ b/contributingGuides/CONTRIBUTING.md @@ -170,3 +170,10 @@ Additionally if you want to discuss an idea with the open source community witho #### Important note about JavaScript Style - Read our official [JavaScript and React style guide](https://github.com/Expensify/App/blob/main/contributingGuides/STYLE.md). Please refer to our Style Guide before asking for a review. - We have nothing against Prettier or any other automatic style fixers, but we generally don't use them here at Expensify. Do not use Prettier. The style changes these tools enforce don't always align with the ones we recommend and require in our eslint configs and can result in unnecessary changes for our reviewers. Ignoring this advice will ultimately make your changes take longer to review as we will ask you to undo any style changes that are not related to the important changes you are making. + +#### For external agencies that Expensify partners with +Follow all the above above steps and processes. When you find a job you'd like to work on: +- Post “I’m from [agency], I’d like to work on this job” + - If no proposals have been submitted by other contributors, BugZero (BZ) team member or an internal engineer will assign the issue to you. + - If there are existing proposals, BZ will put the issue on hold. Contributor+ (C+) will review the existing proposals. If a contributor’s proposal is accepted then contributor will be assigned to the issue. If not the issue will be assigned to the agency-employee. +- Once assigned follow the steps [here](https://github.com/Expensify/App/blob/main/contributingGuides/CONTRIBUTING.md#propose-a-solution-for-the-job) to submit your proposal diff --git a/contributingGuides/STYLE.md b/contributingGuides/STYLE.md index 62e64bede72c..9d94623f0e33 100644 --- a/contributingGuides/STYLE.md +++ b/contributingGuides/STYLE.md @@ -123,10 +123,10 @@ There are a few things that we have customized for our tastes which will take pr // Bad -const valid = this.props.something && this.props.somethingElse; +const valid = props.something && props.somethingElse; // Good -const isValid = this.props.something && this.props.somethingElse; +const isValid = props.something && props.somethingElse; ``` ## Functions @@ -297,11 +297,9 @@ Don't destructure props or state. It makes the source of a given variable unclea ```javascript // Bad -render() { - const {userData} = this.props; - const {firstName, lastName} = this.state; - ... -} +const {userData} = props; +const {firstName, lastName} = state; +... // Bad const UserInfo = ({name, email}) => ( @@ -413,63 +411,7 @@ const propTypes = { }; ``` -* Avoid public methods on components and calling them via refs - -```javascript -// Bad -class MyComponent extends React.Component { - - /** - * Refresh the data in our component by calling `MyComponent.refreshData()` - * - * @public - * @params {Object[]} newData - */ - refreshData(newData) { - this.setState({data: newData}); - } -} - -class SomeOtherComponent extends Component { - setDataInMyComponent(newData) { - this.myComponent.refreshData(newData); - } - - render() { - return ( - this.myComponent = el} /> - ); - } -} -``` - -```javascript -// Good -class MyComponent extends Component { - ... -} - -class SomeOtherComponent extends Component { - constructor(props) { - this.state = { - data: {}, - }; - } - - ... - - render() { - return ( - - ); - } -} -``` - -**Note:** One exception to this rule would be lower level React Native UI components like inputs that maybe need a `focus()` method to be called via a `ref`. - * Do not use underscores when naming private methods. -* Do not add method documentation for the built-in [lifecycle methods](https://facebook.github.io/react/docs/component-specs.html) of a component. * Add descriptions to all `propTypes` using a block comment above the definition. No need to document the types (that's what `propTypes` is doing already), but add some context for each property so that other developers understand the intended use. ```javascript @@ -540,96 +482,48 @@ const propTypes = { } ``` -## Binding methods - -For class components, methods should be bound in the `constructor()` if passed directly as a prop or needing to be accessed via the component instance from outside of the component's execution context. Binding all methods in the constructor is unnecessary. Learn and understand how `this` works [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this). - -```javascript -// Bad -class SomeComponent { - constructor(props) { - super(props); - - this.myMethod = this.myMethod.bind(this); - } - - myMethod() {...} - - render() { - return ( - // No need to bind this since arrow function is used -