From aa35fa835c73839acf68c6b578bb86cfc504340c Mon Sep 17 00:00:00 2001 From: Katsuki <1313124+K4tsuki@users.noreply.github.com> Date: Mon, 14 Feb 2022 14:30:37 +0700 Subject: [PATCH 1/2] Fix sync indicator --- src/pages/home/sidebar/SidebarLinks.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js index d31e1f9d0fd4..40c36ad8f850 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.js @@ -235,7 +235,7 @@ class SidebarLinks extends React.Component { @@ -295,6 +295,7 @@ export default compose( }, isSyncingData: { key: ONYXKEYS.IS_LOADING_AFTER_RECONNECT, + initWithStoredValues: false, }, betas: { key: ONYXKEYS.BETAS, From 19fc3fcce532310fe27e78292606e1a631eb0ae5 Mon Sep 17 00:00:00 2001 From: Katsuki <1313124+K4tsuki@users.noreply.github.com> Date: Thu, 3 Mar 2022 09:32:13 +0700 Subject: [PATCH 2/2] fix sync icon & sign commits --- .env.production | 2 +- .env.staging | 2 +- .github/ISSUE_TEMPLATE/Standard.md | 13 +- .github/PULL_REQUEST_TEMPLATE.md | 36 ++ .../actions/awaitStagingDeploys/action.yml | 3 + .../awaitStagingDeploys.js | 18 +- .github/actions/awaitStagingDeploys/index.js | 64 +++- .../actions/getDeployPullRequestList/index.js | 17 + .../actions/getPullRequestDetails/index.js | 17 + .github/actions/getReleaseBody/index.js | 17 + .../markPullRequestsAsDeployed/index.js | 17 + .../actions/triggerWorkflowAndWait/index.js | 17 + .github/libs/ActionUtils.js | 17 + .../scripts/validateActionsAndWorkflows.sh | 31 +- .github/workflows/cherryPick.yml | 8 +- .github/workflows/createNewVersion.yml | 4 +- .github/workflows/deploy.yml | 8 +- .github/workflows/deployBlocker.yml | 8 +- .github/workflows/finishReleaseCycle.yml | 2 +- .github/workflows/lockDeploys.yml | 2 +- .github/workflows/platformDeploy.yml | 14 +- .github/workflows/preDeploy.yml | 55 ++- .github/workflows/updateProtectedBranch.yml | 14 +- .gitignore | 3 + PR_REVIEW_GUIDELINES.md | 72 ++++ android/app/build.gradle | 4 +- android/app/src/main/AndroidManifest.xml | 1 + .../com/expensify/chat/MainApplication.java | 3 + android/settings.gradle | 6 + assets/images/key.svg | 8 + config/electron.config.js | 34 -- .../electronBuilder/electronBuilder.config.js | 44 +++ config/webpack/productionConfig.js | 29 -- config/webpack/webpack.common.js | 43 ++- config/webpack/webpack.desktop.js | 56 +++ config/webpack/webpack.dev.js | 27 +- config/webpack/webpack.prod.js | 9 - config/webpack/webpack.staging.js | 9 - desktop/ELECTRON_ENVIRONMENT.js | 30 -- desktop/README.md | 21 +- desktop/main.js | 63 ++-- desktop/notarize.js | 2 +- desktop/package-lock.json | 320 ++++++++++++++++++ desktop/package.json | 15 + desktop/start.js | 14 +- .../xcshareddata/IDEWorkspaceChecks.plist | 8 + ios/NewExpensify/Info.plist | 4 +- ios/NewExpensifyTests/Info.plist | 4 +- ios/Podfile | 2 + ios/Podfile.lock | 16 +- package-lock.json | 181 ++-------- package.json | 21 +- scripts/build-desktop.sh | 27 ++ {build => scripts}/react-native-web.sh | 0 {tests/utils => scripts}/shellUtils.sh | 0 src/{CONST/index.js => CONST.js} | 28 +- src/CONST/ENVIRONMENT.js | 5 - src/ROUTES.js | 2 + src/components/AddressSearch.js | 52 ++- src/components/AttachmentModal.js | 2 +- src/components/Avatar.js | 5 + src/components/BigNumberPad.js | 1 + src/components/Button.js | 19 +- .../CustomStatusBar/index.android.js | 1 + .../EmojiPicker/EmojiPickerButton.js | 51 +++ .../EmojiPicker/EmojiPickerMenu/index.js | 42 ++- .../EmojiPickerMenu/index.native.js | 46 ++- src/components/EmojiPicker/index.js | 187 ++++------ src/components/EnvironmentBadge.js | 8 +- .../ErrorBoundary/BaseErrorBoundary.js | 2 +- src/components/ErrorBoundary/index.native.js | 2 +- src/components/FullNameInputRow.js | 2 + src/components/Hoverable/index.native.js | 2 +- src/components/Icon/Expensicons.js | 2 + src/components/ImageWithSizeCalculation.js | 40 ++- src/components/MultipleAvatars.js | 61 ++-- src/components/PopoverWithMeasuredContent.js | 23 +- .../index.android.js | 60 ---- .../{index.ios.js => index.native.js} | 6 +- src/components/RNTextInput.js | 41 +++ src/components/ReportActionItem/IOUPreview.js | 4 +- src/components/ReportWelcomeText.js | 2 +- src/components/RoomNameInput.js | 97 ++---- src/components/Text.js | 5 +- src/components/TextInput/BaseTextInput.js | 8 +- .../TextInput/baseTextInputPropTypes.js | 2 +- src/components/TextInput/index.js | 8 +- .../TextInputFocusable/index.android.js | 5 +- .../TextInputFocusable/index.ios.js | 5 +- src/components/TextInputFocusable/index.js | 6 +- src/components/TextInputWithFocusStyles.js | 90 ----- src/components/TextInputWithName/index.js | 41 --- .../TextInputWithName/index.native.js | 21 -- .../textInputWithNamepropTypes.js | 19 -- .../TextInputWithPrefix/index.android.js | 6 +- src/components/TextInputWithPrefix/index.js | 6 +- src/components/Tooltip/index.js | 4 + src/components/Tooltip/tooltipPropTypes.js | 3 +- .../WalletStatementModalPropTypes.js | 25 ++ src/components/WalletStatementModal/index.js | 54 +++ .../WalletStatementModal/index.native.js | 39 +++ src/languages/en.js | 9 +- src/languages/es.js | 23 +- src/libs/API.js | 8 +- src/libs/FormUtils.js | 8 +- src/libs/HapticFeedback/index.android.js | 18 + src/libs/HapticFeedback/index.ios.js | 12 + src/libs/HapticFeedback/index.js | 6 + src/libs/HttpUtils.js | 23 +- src/libs/Log.js | 3 +- .../Navigation/AppNavigator/AuthScreens.js | 6 + .../AppNavigator/ModalStackNavigators.js | 7 + src/libs/Navigation/CustomActions.js | 52 +-- src/libs/Navigation/linkingConfig.js | 5 + src/libs/Network.js | 21 +- src/libs/ValidationUtils.js | 29 ++ src/libs/actions/App.js | 2 +- src/libs/actions/EmojiPickerAction.js | 31 ++ src/libs/actions/PaymentMethods.js | 2 +- src/libs/actions/PersonalDetails.js | 8 +- src/libs/actions/Report.js | 19 +- src/libs/actions/Session/index.js | 5 +- src/libs/actions/SignInRedirect.js | 7 +- src/libs/actions/WelcomeActions.js | 84 +++++ src/libs/checkForUpdates.js | 4 +- src/libs/reportUtils.js | 11 + .../EnablePayments/AdditionalDetailsStep.js | 25 +- src/pages/ReportDetailsPage.js | 2 +- src/pages/ReportSettingsPage.js | 84 ++++- src/pages/RequestCallPage.js | 87 +++-- src/pages/home/HeaderView.js | 5 +- src/pages/home/report/ReportActionCompose.js | 8 +- .../home/report/ReportActionItemSingle.js | 11 +- src/pages/home/report/ReportActionsView.js | 3 + src/pages/home/sidebar/OptionRow.js | 3 + src/pages/home/sidebar/SidebarLinks.js | 2 +- src/pages/home/sidebar/SidebarScreen.js | 40 +-- src/pages/iou/IOUCurrencySelection.js | 8 +- .../settings/Payments/PaymentMethodList.js | 38 ++- .../Payments/PaymentsPage/BasePaymentsPage.js | 9 +- src/pages/settings/Profile/ProfilePage.js | 68 ++-- .../settings/Security/CloseAccountPage.js | 9 +- .../settings/Security/SecuritySettingsPage.js | 2 +- src/pages/signin/SignInPageLayout/index.js | 23 +- src/pages/wallet/WalletStatementPage.js | 62 ++++ src/pages/workspace/WorkspaceNewRoomPage.js | 78 ++++- src/setup/index.js | 6 + src/stories/AddressSearch.stories.js | 42 +++ src/stories/Form.stories.js | 9 +- src/styles/StyleUtils.js | 15 +- src/styles/colors.js | 1 + src/styles/styles.js | 7 + tests/unit/awaitStagingDeploysTest.js | 114 ++++++- .../unit/getPullRequestsMergedBetweenTest.sh | 3 +- 154 files changed, 2529 insertions(+), 1240 deletions(-) create mode 100644 PR_REVIEW_GUIDELINES.md create mode 100644 assets/images/key.svg delete mode 100644 config/electron.config.js create mode 100644 config/electronBuilder/electronBuilder.config.js delete mode 100644 config/webpack/productionConfig.js create mode 100644 config/webpack/webpack.desktop.js delete mode 100644 config/webpack/webpack.prod.js delete mode 100644 config/webpack/webpack.staging.js delete mode 100644 desktop/ELECTRON_ENVIRONMENT.js create mode 100644 desktop/package-lock.json create mode 100644 desktop/package.json create mode 100644 ios/ExpensifyCash.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100755 scripts/build-desktop.sh rename {build => scripts}/react-native-web.sh (100%) rename {tests/utils => scripts}/shellUtils.sh (100%) rename src/{CONST/index.js => CONST.js} (97%) delete mode 100644 src/CONST/ENVIRONMENT.js create mode 100644 src/components/EmojiPicker/EmojiPickerButton.js delete mode 100644 src/components/PressableWithSecondaryInteraction/index.android.js rename src/components/PressableWithSecondaryInteraction/{index.ios.js => index.native.js} (88%) create mode 100644 src/components/RNTextInput.js delete mode 100644 src/components/TextInputWithFocusStyles.js delete mode 100755 src/components/TextInputWithName/index.js delete mode 100644 src/components/TextInputWithName/index.native.js delete mode 100644 src/components/TextInputWithName/textInputWithNamepropTypes.js create mode 100644 src/components/WalletStatementModal/WalletStatementModalPropTypes.js create mode 100644 src/components/WalletStatementModal/index.js create mode 100644 src/components/WalletStatementModal/index.native.js create mode 100644 src/libs/HapticFeedback/index.android.js create mode 100644 src/libs/HapticFeedback/index.ios.js create mode 100644 src/libs/HapticFeedback/index.js create mode 100644 src/libs/actions/EmojiPickerAction.js create mode 100644 src/libs/actions/WelcomeActions.js create mode 100644 src/pages/wallet/WalletStatementPage.js create mode 100644 src/stories/AddressSearch.stories.js diff --git a/.env.production b/.env.production index 85c73146dd13..568ec690acfd 100644 --- a/.env.production +++ b/.env.production @@ -5,4 +5,4 @@ EXPENSIFY_PARTNER_NAME=chat-expensify-com EXPENSIFY_PARTNER_PASSWORD=e21965746fd75f82bb66 PUSHER_APP_KEY=268df511a204fbb60884 USE_WEB_PROXY=false -ENVIRONMENT=PROD +ENVIRONMENT=production diff --git a/.env.staging b/.env.staging index 36e04bc26778..bd6b03805d56 100644 --- a/.env.staging +++ b/.env.staging @@ -5,4 +5,4 @@ EXPENSIFY_PARTNER_NAME=chat-expensify-com EXPENSIFY_PARTNER_PASSWORD=e21965746fd75f82bb66 PUSHER_APP_KEY=268df511a204fbb60884 USE_WEB_PROXY=false -ENVIRONMENT=STG +ENVIRONMENT=staging diff --git a/.github/ISSUE_TEMPLATE/Standard.md b/.github/ISSUE_TEMPLATE/Standard.md index e753c1079a5e..932e77753a9a 100644 --- a/.github/ISSUE_TEMPLATE/Standard.md +++ b/.github/ISSUE_TEMPLATE/Standard.md @@ -20,7 +20,7 @@ Describe what actually happened Can the user still use Expensify without this being fixed? Have you informed them of the workaround? ## Platform: - Where is this issue occurring? @@ -31,13 +31,14 @@ Where is this issue occurring? - Desktop App - Mobile Web -**Version Number:** -**Reproducible in staging?:** -**Reproducible in production?:** +**Version Number:** +**Reproducible in staging?:** +**Reproducible in production?:** +**Email or phone of affected tester (no customers):** **Logs:** https://stackoverflow.com/c/expensify/questions/4856 **Notes/Photos/Videos:** Any additional supporting documentation -**Expensify/Expensify Issue URL:** -**Issue reported by:** +**Expensify/Expensify Issue URL:** +**Issue reported by:** **Slack conversation:** [View all open jobs on GitHub](https://github.com/Expensify/App/issues?q=is%3Aopen+is%3Aissue+label%3A%22Help+Wanted%22) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 7857da1475eb..8b930c692841 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -31,6 +31,42 @@ For example: - [ ] Verify that no errors appear in the JS console +### PR Review Checklist + +#### Contributor (PR Author) Checklist +- [ ] I made sure to pull `main` before submitting my PR for review +- [ ] I linked the correct issue in the `### Fixed Issues` section above +- [ ] I wrote clear testing steps that cover the changes made in this PR + - [ ] I clearly indicated the environment tests should be run in (Staging vs Production) +- [ ] I wrote testing steps that cover success & fail scenarios (if applicable) +- [ ] I ran the tests & they passed on **all platforms** +- [ ] I included screenshots or videos for tests on [all platforms](https://github.com/Expensify/App/blob/main/CONTRIBUTING.md#make-sure-you-can-test-on-all-platforms) +- [ ] I verified there are no console errors related to changes in this PR +- [ ] I followed proper code patterns (see [Reviewing the code](https://github.com/Expensify/App/blob/main/PR_REVIEW_GUIDELINES.md#reviewing-the-code)) + - [ ] I added comments when the code was not self explanatory + - [ ] I put all copy / text shown in the product in all `src/languages/*` files (if applicable) + - [ ] I followed proper naming convention for platform-specific files (if applicable) + - [ ] I followed style guidelines (in [`Styling.md`](https://github.com/Expensify/App/blob/main/STYLING.md)) for all style edits I made +- [ ] I followed the guidelines as stated in the [Review Guidelines](https://github.com/Expensify/App/blob/main/PR_REVIEW_GUIDELINES.md) + +#### PR Reviewer Checklist +- [ ] I verified the Author pulled `main` before submitting the PR +- [ ] I verified the correct issue is linked in the `### Fixed Issues` section above +- [ ] I verified testing steps are clear and they cover the changes made in this PR + - [ ] I verified the testing environment is mentioned in the test steps +- [ ] I verified testing steps cover success & fail scenarios (if applicable) +- [ ] I verified tests pass on **all platforms** & I tested again on all platforms +- [ ] I checked that screenshots or videos are included for tests on [all platforms](https://github.com/Expensify/App/blob/main/CONTRIBUTING.md#make-sure-you-can-test-on-all-platforms) +- [ ] I verified there are no console errors related to changes in this PR +- [ ] I verified proper code patterns were followed (see [Reviewing the code](https://github.com/Expensify/App/blob/main/PR_REVIEW_GUIDELINES.md#reviewing-the-code)) + - [ ] I verified comments were added when the code was not self explanatory + - [ ] I verified any copy / text shown in the product was added in all `src/languages/*` files (if applicable) + - [ ] I verified proper naming convention for platform-specific files was followed (if applicable) + - [ ] I verified [style guidelines](https://github.com/Expensify/App/blob/main/STYLING.md) were followed +- [ ] I verified that this PR follows the guidelines as stated in the [Review Guidelines](https://github.com/Expensify/App/blob/main/PR_REVIEW_GUIDELINES.md) + ### QA Steps + + + diff --git a/config/electron.config.js b/config/electron.config.js deleted file mode 100644 index 14079e6e9a36..000000000000 --- a/config/electron.config.js +++ /dev/null @@ -1,34 +0,0 @@ -const ENVIRONMENT = require('../src/CONST/ENVIRONMENT'); - -module.exports = { - appId: 'com.expensifyreactnative.chat', - productName: 'New Expensify', - extraMetadata: { - main: './desktop/main.js', - electronEnvironment: process.env.SHOULD_DEPLOY_PRODUCTION ? ENVIRONMENT.PRODUCTION : ENVIRONMENT.STAGING, - }, - mac: { - category: 'public.app-category.finance', - icon: process.env.SHOULD_DEPLOY_PRODUCTION === 'true' ? './desktop/icon.png' : './desktop/icon-stg.png', - hardenedRuntime: true, - entitlements: 'desktop/entitlements.mac.plist', - entitlementsInherit: 'desktop/entitlements.mac.plist', - type: 'distribution', - }, - dmg: { - title: 'New Expensify', - artifactName: 'NewExpensify.dmg', - internetEnabled: true, - }, - publish: [{ - provider: 's3', - bucket: process.env.SHOULD_DEPLOY_PRODUCTION === 'true' ? 'expensify-cash' : 'staging-expensify-cash', - channel: 'latest', - }], - files: [ - './dist/**/*', - './desktop/*.js', - './src/libs/checkForUpdates.js', - './src/CONST/ENVIRONMENT.js', - ], -}; diff --git a/config/electronBuilder/electronBuilder.config.js b/config/electronBuilder/electronBuilder.config.js new file mode 100644 index 000000000000..f2455ad0428d --- /dev/null +++ b/config/electronBuilder/electronBuilder.config.js @@ -0,0 +1,44 @@ +const {version} = require('../../package.json'); + +const isStaging = process.env.ELECTRON_ENV === 'staging'; +const isPublishing = process.argv.includes('--publish'); + +/** + * The configuration for the production and staging Electron builds. + * It can be used to create local builds of the same, by omitting the `--publish` flag + */ +module.exports = { + appId: 'com.expensifyreactnative.chat', + productName: 'New Expensify', + extraMetadata: { + version, + }, + mac: { + category: 'public.app-category.finance', + target: [ + {target: 'dmg', arch: ['x64', 'arm64', 'universal']}, + ], + icon: isStaging ? './desktop/icon-stg.png' : './desktop/icon.png', + hardenedRuntime: true, + entitlements: 'desktop/entitlements.mac.plist', + entitlementsInherit: 'desktop/entitlements.mac.plist', + type: 'distribution', + }, + dmg: { + internetEnabled: true, + }, + publish: [{ + provider: 's3', + bucket: isStaging ? 'staging-expensify-cash' : 'expensify-cash', + channel: 'latest', + }], + afterSign: isPublishing ? './desktop/notarize.js' : undefined, + files: [ + 'dist', + '!dist/www/{.well-known,favicon*}', + ], + directories: { + app: 'desktop', + output: 'desktop-build', + }, +}; diff --git a/config/webpack/productionConfig.js b/config/webpack/productionConfig.js deleted file mode 100644 index 594d616ac423..000000000000 --- a/config/webpack/productionConfig.js +++ /dev/null @@ -1,29 +0,0 @@ -const webpack = require('webpack'); -const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer'); - -/** - * Get the production webpack configuration, given an environment object. - * - * @param {Object} env - * @returns {Object} - */ -function getProductionConfig(env) { - return ({ - mode: 'production', - devtool: 'source-map', - plugins: [ - // This allows us to interactively inspect JS bundle contents - ...(process.env.ANALYZE_BUNDLE === 'true' ? [new BundleAnalyzerPlugin()] : []), - new webpack.DefinePlugin({ - __REACT_WEB_CONFIG__: JSON.stringify(env), - - // React Native JavaScript environment requires the global __DEV__ variable to be accessible. - // react-native-render-html uses variable to log exclusively during development. - // See https://reactnative.dev/docs/javascript-environment - __DEV__: false, - }), - ], - }); -} - -module.exports = getProductionConfig; diff --git a/config/webpack/webpack.common.js b/config/webpack/webpack.common.js index e2d3ca0974af..1b856a3039c7 100644 --- a/config/webpack/webpack.common.js +++ b/config/webpack/webpack.common.js @@ -1,22 +1,18 @@ -const _ = require('underscore'); const path = require('path'); -const {IgnorePlugin} = require('webpack'); +const {IgnorePlugin, DefinePlugin} = require('webpack'); const {CleanWebpackPlugin} = require('clean-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CopyPlugin = require('copy-webpack-plugin'); +const dotenv = require('dotenv'); +const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer'); const CustomVersionFilePlugin = require('./CustomVersionFilePlugin'); -// Check for a --platform command line argument (default to 'web') -// If it is 'web', we want to ignore .desktop.js files, and if it is 'desktop', we want to ignore .website.js files. -const platformIndex = _.findIndex(process.argv, arg => arg === '--platform'); -const platform = (platformIndex > 0) ? process.argv[platformIndex + 1] : 'web'; -const platformExclude = platform === 'web' ? new RegExp(/\.desktop\.js$/) : new RegExp(/\.website\.js$/); - const includeModules = [ 'react-native-animatable', 'react-native-reanimated', 'react-native-picker-select', 'react-native-web', + 'react-native-webview', '@react-native-picker', 'react-native-modal', 'react-native-onyx', @@ -25,7 +21,16 @@ const includeModules = [ 'react-native-google-places-autocomplete', ].join('|'); -const webpackConfig = { +/** + * Get a production grade config for web or desktop + * @param {Object} env + * @param {String} env.envFile path to the env file to be used + * @param {'web'|'desktop'} env.platform + * @returns {Configuration} + */ +const webpackConfig = ({envFile = '.env', platform = 'web'}) => ({ + mode: 'production', + devtool: 'source-map', entry: { app: './index.js', }, @@ -59,7 +64,20 @@ const webpackConfig = { ], }), new IgnorePlugin(/^\.\/locale$/, /moment$/), - new CustomVersionFilePlugin(), + ...(platform === 'web' ? [new CustomVersionFilePlugin()] : []), + new DefinePlugin({ + __REACT_WEB_CONFIG__: JSON.stringify( + dotenv.config({path: envFile}).parsed, + ), + + // React Native JavaScript environment requires the global __DEV__ variable to be accessible. + // react-native-render-html uses variable to log exclusively during development. + // See https://reactnative.dev/docs/javascript-environment + __DEV__: /staging|prod/.test(envFile) === false, + }), + + // This allows us to interactively inspect JS bundle contents + ...(process.env.ANALYZE_BUNDLE === 'true' ? [new BundleAnalyzerPlugin()] : []), ], module: { rules: [ @@ -78,7 +96,6 @@ const webpackConfig = { */ exclude: [ new RegExp(`node_modules/(?!(${includeModules})/).*|.native.js$`), - platformExclude, ], }, { @@ -86,7 +103,6 @@ const webpackConfig = { loader: 'eslint-loader', exclude: [ /node_modules|\.native\.js$/, - platformExclude, ], options: { cache: false, @@ -142,8 +158,9 @@ const webpackConfig = { // without this, web will try to use native implementations and break in not very obvious ways. // This is also why we have to use .website.js for our own web-specific files... // Because desktop also relies on "web-specific" module implementations + // This also skips packing web only dependencies to desktop and vice versa extensions: ['.web.js', (platform === 'web') ? '.website.js' : '.desktop.js', '.js', '.jsx'], }, -}; +}); module.exports = webpackConfig; diff --git a/config/webpack/webpack.desktop.js b/config/webpack/webpack.desktop.js new file mode 100644 index 000000000000..298741eed498 --- /dev/null +++ b/config/webpack/webpack.desktop.js @@ -0,0 +1,56 @@ +const path = require('path'); +const webpack = require('webpack'); +const _ = require('underscore'); + +const desktopDependencies = require('../../desktop/package.json').dependencies; +const getCommonConfig = require('./webpack.common'); + +/** + * Desktop creates 2 configurations in parallel + * 1. electron-main - the core that serves the app content + * 2. web - the app content that would be rendered in electron + * Everything is placed in desktop/dist and ready for packaging + * @param {Object} env + * @returns {webpack.Configuration[]} + */ +module.exports = (env) => { + const rendererConfig = getCommonConfig({...env, platform: 'desktop'}); + const outputPath = path.resolve(__dirname, '../../desktop/dist'); + + rendererConfig.name = 'renderer'; + rendererConfig.output.path = path.join(outputPath, 'www'); + + // Expose react-native-config to desktop-main + const definePlugin = _.find(rendererConfig.plugins, plugin => plugin.constructor === webpack.DefinePlugin); + + const mainProcessConfig = { + mode: 'production', + name: 'desktop-main', + target: 'electron-main', + entry: { + main: './desktop/main.js', + contextBridge: './desktop/contextBridge.js', + }, + output: { + filename: '[name].js', + path: outputPath, + libraryTarget: 'commonjs2', + }, + resolve: rendererConfig.resolve, + plugins: [definePlugin], + externals: [ + ..._.keys(desktopDependencies), + 'fsevents', + ], + node: { + /** + * Disables webpack processing of __dirname and __filename, so it works like in node + * https://github.com/webpack/webpack/issues/2010 + */ + __dirname: false, + __filename: false, + }, + }; + + return [mainProcessConfig, rendererConfig]; +}; diff --git a/config/webpack/webpack.dev.js b/config/webpack/webpack.dev.js index e32304af8aac..0c46975435cc 100644 --- a/config/webpack/webpack.dev.js +++ b/config/webpack/webpack.dev.js @@ -1,12 +1,13 @@ const path = require('path'); -const webpack = require('webpack'); const {merge} = require('webpack-merge'); -const dotenv = require('dotenv'); -const common = require('./webpack.common'); +const getCommonConfig = require('./webpack.common'); -const env = dotenv.config({path: path.resolve(__dirname, '../../.env')}).parsed; - -module.exports = () => { +/** + * Configuration for the local dev server + * @param {Object} env + * @returns {Configuration} + */ +module.exports = (env = {}) => { // Check if the USE_WEB_PROXY variable has been provided // and rewrite any requests to the local proxy server const proxySettings = process.env.USE_WEB_PROXY === 'false' @@ -18,7 +19,9 @@ module.exports = () => { }, }; - return merge(common, { + const baseConfig = getCommonConfig(env); + + return merge(baseConfig, { mode: 'development', devtool: 'inline-source-map', devServer: { @@ -27,15 +30,5 @@ module.exports = () => { ...proxySettings, historyApiFallback: true, }, - plugins: [ - new webpack.DefinePlugin({ - __REACT_WEB_CONFIG__: JSON.stringify(env), - - // React Native JavaScript environment requires the global __DEV__ variable to be accessible. - // react-native-render-html uses variable to log exclusively during development. - // See https://reactnative.dev/docs/javascript-environment - __DEV__: true, - }), - ], }); }; diff --git a/config/webpack/webpack.prod.js b/config/webpack/webpack.prod.js deleted file mode 100644 index dfd7c852ab99..000000000000 --- a/config/webpack/webpack.prod.js +++ /dev/null @@ -1,9 +0,0 @@ -const path = require('path'); -const {merge} = require('webpack-merge'); -const dotenv = require('dotenv'); -const common = require('./webpack.common'); -const getProductionConfig = require('./productionConfig'); - -const env = dotenv.config({path: path.resolve(__dirname, '../../.env.production')}).parsed; - -module.exports = merge(common, getProductionConfig(env)); diff --git a/config/webpack/webpack.staging.js b/config/webpack/webpack.staging.js deleted file mode 100644 index 24f706d90731..000000000000 --- a/config/webpack/webpack.staging.js +++ /dev/null @@ -1,9 +0,0 @@ -const path = require('path'); -const {merge} = require('webpack-merge'); -const dotenv = require('dotenv'); -const common = require('./webpack.common'); -const getProductionConfig = require('./productionConfig'); - -const env = dotenv.config({path: path.resolve(__dirname, '../../.env.staging')}).parsed; - -module.exports = merge(common, getProductionConfig(env)); diff --git a/desktop/ELECTRON_ENVIRONMENT.js b/desktop/ELECTRON_ENVIRONMENT.js deleted file mode 100644 index 698e14b43078..000000000000 --- a/desktop/ELECTRON_ENVIRONMENT.js +++ /dev/null @@ -1,30 +0,0 @@ -// This variable is injected into package.json by electron-builder via the extraMetadata field (specified in electron.config.js) -// It will be `PROD` on production, `STG` on staging, and `undefined` on dev (because dev doesn't use electron-builder) -const {electronEnvironment} = require('../package.json'); -const ENVIRONMENT = require('../src/CONST/ENVIRONMENT'); - -/** - * @returns {String} – One of ['PROD', 'STG', 'DEV'] - */ -function getEnvironment() { - // If we are on dev, then the NODE_ENV environment variable will be present (set by the executing shell in start.js) - if (process.env.NODE_ENV === 'development') { - return ENVIRONMENT.DEV; - } - - // Otherwise, use the environment injected into package.json by electron-builder - return electronEnvironment; -} - -function isDev() { - return getEnvironment() === ENVIRONMENT.DEV; -} - -function isProd() { - return getEnvironment() === ENVIRONMENT.PRODUCTION; -} - -module.exports = { - isDev, - isProd, -}; diff --git a/desktop/README.md b/desktop/README.md index 85d2d7273028..87ca4dc36b24 100644 --- a/desktop/README.md +++ b/desktop/README.md @@ -23,6 +23,7 @@ * [Architecture](#architecture) * [Testing Electron Auto-Update](#testing-electron-auto-update) +* [Packaging](#packaging) # Architecture The New Expensify desktop app is built using [Electron.js](https://www.electronjs.org/). We try our best to maintain Electron best practices, particularly when it comes to [security](https://www.electronjs.org/docs/latest/tutorial/security). @@ -87,8 +88,7 @@ mc policy set public electron-builder/electron-builder Once you have Min.IO setup and running, the next step is to temporarily revert some changes from https://github.com/Expensify/App/commit/b640b3010fd7a40783d1c04faf4489836e98038d, specifically 1. Update the `desktop-build` command in package.json to add `--publish always` at the end -2. Update electron.config.js to re-add `afterSign: 'desktop/notarize.js',` -3. Update electron.config.js to replace the `publish` value with the following: +2. Update electronBuilder.config.js to replace the `publish` value with the following: ``` publish: [{ provider: 's3', @@ -122,3 +122,20 @@ AWS_ACCESS_KEY_ID=RootUserKey AWS_SECRET_ACCESS_KEY=RootPassKey APPLE_ID=YOUR_AP This command will create a build, notarize it, and push your build to the server. Note that it can take around 10 minutes for the command to complete. Once the command finishes, revert the version update in `package.json`, remove `--publish always` from the `desktop-build` command, and again run the `npm run desktop-build` command above **including the args**. After the build is done, you'll find `NewExpensify.dmg` in the `dist/` folder in the root of the project. Open the `.dmg` and install the app. Your app will attempt to auto-update in the background. + +# Packaging +To avoid bundling unnecessary `node_modules` we use a [2 package structure](https://www.electron.build/tutorials/two-package-structure) +The root [package.json](../package.json) serves for `devDependencies` and shared (renderer) `dependencies` +The [desktop/package.json](./package.json) serves for desktop (electron-main) specific dependencies +We use Webpack with a [desktop specific config](../config/webpack/webpack.desktop.js) to bundle our js code +Half of the config takes care of packaging root package dependencies - everything related to rendering App in the Electron window. Packaged under `dist/www` +The other half is about bundling the `main.js` script which initializes Electron and renders `www` + +## See what is getting packaged in the app +If you suspect unnecessary items might be getting packaged you can inspect the package content in `desktop-build/` +The app content (`dist/www`) is archived under `/New\ Expensify.app/Contents/Resources/app.asar` +To see the actual `app.asar` content run the following script +```shell +npx asar extract desktop-build/mac/New\ Expensify.app/Contents/Resources/app.asar ./unpacked-asar +``` +The expected size of `app.asar` = `desktop/dist/www/` + `desktop/node_modules/`; diff --git a/desktop/main.js b/desktop/main.js index 43962026e227..fc1bd8ff10ea 100644 --- a/desktop/main.js +++ b/desktop/main.js @@ -11,9 +11,9 @@ const serve = require('electron-serve'); const contextMenu = require('electron-context-menu'); const {autoUpdater} = require('electron-updater'); const log = require('electron-log'); -const ELECTRON_ENVIRONMENT = require('./ELECTRON_ENVIRONMENT'); const ELECTRON_EVENTS = require('./ELECTRON_EVENTS'); const checkForUpdates = require('../src/libs/checkForUpdates'); +const CONFIG = require('../src/CONFIG').default; const port = process.env.PORT || 8080; @@ -40,17 +40,6 @@ autoUpdater.logger.transports.file.level = 'info'; // See https://www.npmjs.com/package/electron-log _.assign(console, log.functions); -// setup Hot reload -if (ELECTRON_ENVIRONMENT.isDev()) { - try { - require('electron-reloader')(module, { - watchRenderer: false, - ignore: [/^(desktop)/], - }); - // eslint-disable-next-line no-empty - } catch {} -} - // This sets up the command line arguments used to manage the update. When // the --expected-update-version flag is set, the app will open pre-hidden // until it detects that it has been upgraded to the correct version. @@ -124,13 +113,14 @@ const electronUpdater = browserWindow => ({ }); const mainWindow = (() => { - const loadURL = ELECTRON_ENVIRONMENT.isDev() + const loadURL = __DEV__ ? win => win.loadURL(`http://localhost:${port}`) - : serve({directory: `${__dirname}/../dist`}); + : serve({directory: `${__dirname}/www`}); // Prod and staging set the icon in the electron-builder config, so only update it here for dev - if (ELECTRON_ENVIRONMENT.isDev()) { - app.dock.setIcon(`${__dirname}/icon-dev.png`); + if (__DEV__) { + console.debug('CONFIG: ', CONFIG); + app.dock.setIcon(`${__dirname}/../icon-dev.png`); app.setName('New Expensify'); } @@ -157,43 +147,32 @@ const mainWindow = (() => { * 1. Modify headers on any outgoing requests to match the origin of our corresponding web environment (not necessary in case of web proxy, because it already does that) * 2. Modify the Access-Control-Allow-Origin header of the response to match the "real" origin of our Electron app. */ + const webRequest = browserWindow.webContents.session.webRequest; const validDestinationFilters = {urls: ['https://*.expensify.com/*']}; - if (!ELECTRON_ENVIRONMENT.isDev()) { - const newDotURL = ELECTRON_ENVIRONMENT.isProd() ? 'https://new.expensify.com' : 'https://staging.new.expensify.com'; - + /* eslint-disable no-param-reassign */ + if (!__DEV__) { // Modify the origin and referer for requests sent to our API - browserWindow.webContents.session.webRequest.onBeforeSendHeaders(validDestinationFilters, (details, callback) => { - // eslint-disable-next-line no-param-reassign - details.requestHeaders.origin = newDotURL; - // eslint-disable-next-line no-param-reassign - details.requestHeaders.referer = newDotURL; + webRequest.onBeforeSendHeaders(validDestinationFilters, (details, callback) => { + details.requestHeaders.origin = CONFIG.EXPENSIFY.URL_EXPENSIFY_CASH; + details.requestHeaders.referer = CONFIG.EXPENSIFY.URL_EXPENSIFY_CASH; callback({requestHeaders: details.requestHeaders}); }); // Modify access-control-allow-origin header for the response - browserWindow.webContents.session.webRequest.onHeadersReceived(validDestinationFilters, (details, callback) => { - // eslint-disable-next-line no-param-reassign + webRequest.onHeadersReceived(validDestinationFilters, (details, callback) => { details.responseHeaders['access-control-allow-origin'] = ['app://-']; callback({responseHeaders: details.responseHeaders}); }); + } else { + webRequest.onHeadersReceived(validDestinationFilters, (details, callback) => { + details.responseHeaders['access-control-allow-origin'] = [`http://localhost:${process.env.PORT}`]; + callback({responseHeaders: details.responseHeaders}); + }); } - - if (ELECTRON_ENVIRONMENT.isDev()) { - const dotenv = require('dotenv'); - const path = require('path'); - const devEnvConfig = dotenv.config({path: path.resolve(__dirname, '../.env')}).parsed; - - if (devEnvConfig.USE_WEB_PROXY === 'true') { - browserWindow.webContents.session.webRequest.onHeadersReceived(validDestinationFilters, (details, callback) => { - // eslint-disable-next-line no-param-reassign - details.responseHeaders['access-control-allow-origin'] = ['http://localhost:8080']; - callback({responseHeaders: details.responseHeaders}); - }); - } - } + /* eslint-enable */ // Prod and staging overwrite the app name in the electron-builder config, so only update it here for dev - if (ELECTRON_ENVIRONMENT.isDev()) { + if (__DEV__) { browserWindow.setTitle('New Expensify'); } @@ -331,7 +310,7 @@ const mainWindow = (() => { // Start checking for JS updates .then((browserWindow) => { - if (ELECTRON_ENVIRONMENT.isDev()) { + if (__DEV__) { return; } diff --git a/desktop/notarize.js b/desktop/notarize.js index 20897a4fc7ac..70ee4a893356 100644 --- a/desktop/notarize.js +++ b/desktop/notarize.js @@ -1,5 +1,5 @@ const {notarize} = require('electron-notarize'); -const electron = require('../config/electron.config'); +const electron = require('../config/electronBuilder/electronBuilder.config'); exports.default = function notarizing(context) { const {electronPlatformName, appOutDir} = context; diff --git a/desktop/package-lock.json b/desktop/package-lock.json new file mode 100644 index 000000000000..99cc9074ef19 --- /dev/null +++ b/desktop/package-lock.json @@ -0,0 +1,320 @@ +{ + "name": "new.expensify.desktop", + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "@types/semver": { + "version": "7.3.9", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz", + "integrity": "sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==" + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==" + }, + "builder-util-runtime": { + "version": "8.9.2", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-8.9.2.tgz", + "integrity": "sha512-rhuKm5vh7E0aAmT6i8aoSfEjxzdYEFX7zDApK+eNgOhjofnWb74d9SRJv0H/8nsgOkos0TZ4zxW0P8J4N7xQ2A==", + "requires": { + "debug": "^4.3.2", + "sax": "^1.2.4" + } + }, + "cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "requires": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "requires": { + "ms": "2.1.2" + } + }, + "electron-context-menu": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/electron-context-menu/-/electron-context-menu-2.5.2.tgz", + "integrity": "sha512-1cEQR6fA9ktFsRBc+eXPwvrOgAPytUD7rUV4iBAA5zTrLAPKokJ23xeMjcK2fjrDPrlFRBxcLz0KP+GUhMrSCQ==", + "requires": { + "cli-truncate": "^2.1.0", + "electron-dl": "^3.1.0", + "electron-is-dev": "^1.2.0" + } + }, + "electron-dl": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/electron-dl/-/electron-dl-3.3.0.tgz", + "integrity": "sha512-Zwaz/OMGPIfBLV2SQH4sTsdDOs/U4y5AOHfremMBXEpjIxX+SiTx845DZAvJJwgb5hfowyWOBLiJhd/emBNLLQ==", + "requires": { + "ext-name": "^5.0.0", + "pupa": "^2.0.1", + "unused-filename": "^2.1.0" + } + }, + "electron-is-dev": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/electron-is-dev/-/electron-is-dev-1.2.0.tgz", + "integrity": "sha512-R1oD5gMBPS7PVU8gJwH6CtT0e6VSoD0+SzSnYpNm+dBkcijgA+K7VAMHDfnRq/lkKPZArpzplTW6jfiMYosdzw==" + }, + "electron-log": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/electron-log/-/electron-log-4.4.6.tgz", + "integrity": "sha512-nirYgRdY+F+vclr8ijdwy2vW03IzFpDHTaKNWu76dEN21Y76+smcES5knS7cgHUUB0qNLOi8vZO36taakjbSXA==" + }, + "electron-serve": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/electron-serve/-/electron-serve-1.1.0.tgz", + "integrity": "sha512-tQJBCbXKoKCfkBC143QCqnEtT1s8dNE2V+b/82NF6lxnGO/2Q3a3GSLHtKl3iEDQgdzTf9pH7p418xq2rXbz1Q==" + }, + "electron-updater": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-4.6.5.tgz", + "integrity": "sha512-kdTly8O9mSZfm9fslc1mnCY+mYOeaYRy7ERa2Fed240u01BKll3aiupzkd07qKw69KvhBSzuHroIW3mF0D8DWA==", + "requires": { + "@types/semver": "^7.3.6", + "builder-util-runtime": "8.9.2", + "fs-extra": "^10.0.0", + "js-yaml": "^4.1.0", + "lazy-val": "^1.0.5", + "lodash.escaperegexp": "^4.1.2", + "lodash.isequal": "^4.5.0", + "semver": "^7.3.5" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "escape-goat": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", + "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==" + }, + "ext-list": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", + "integrity": "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==", + "requires": { + "mime-db": "^1.28.0" + } + }, + "ext-name": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz", + "integrity": "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==", + "requires": { + "ext-list": "^2.0.0", + "sort-keys-length": "^1.0.0" + } + }, + "fs-extra": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", + "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "graceful-fs": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", + "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "requires": { + "argparse": "^2.0.1" + } + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "lazy-val": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", + "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==" + }, + "lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=" + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "mime-db": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", + "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==" + }, + "modify-filename": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/modify-filename/-/modify-filename-1.1.0.tgz", + "integrity": "sha1-mi3sg4Bvuy2XXyK+7IWcoms5OqE=" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "pupa": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", + "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", + "requires": { + "escape-goat": "^2.0.0" + } + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + }, + "sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", + "requires": { + "is-plain-obj": "^1.0.0" + } + }, + "sort-keys-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz", + "integrity": "sha1-nLb09OnkgVWmqgZx7dM2/xR5oYg=", + "requires": { + "sort-keys": "^1.0.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" + }, + "unused-filename": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unused-filename/-/unused-filename-2.1.0.tgz", + "integrity": "sha512-BMiNwJbuWmqCpAM1FqxCTD7lXF97AvfQC8Kr/DIeA6VtvhJaMDupZ82+inbjl5yVP44PcxOuCSxye1QMS0wZyg==", + "requires": { + "modify-filename": "^1.1.0", + "path-exists": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } +} diff --git a/desktop/package.json b/desktop/package.json new file mode 100644 index 000000000000..926e39b5ce95 --- /dev/null +++ b/desktop/package.json @@ -0,0 +1,15 @@ +{ + "name": "new.expensify.desktop", + "description": "Desktop package native dependencies", + "main": "dist/main.js", + "scripts": {}, + "dependencies": { + "electron-context-menu": "^2.3.0", + "electron-log": "^4.3.5", + "electron-serve": "^1.0.0", + "electron-updater": "^4.3.4" + }, + "author": "Expensify, Inc.", + "license": "MIT", + "private": true +} diff --git a/desktop/start.js b/desktop/start.js index cf28e08ed310..d3d26d4de08a 100644 --- a/desktop/start.js +++ b/desktop/start.js @@ -1,24 +1,30 @@ #!/usr/bin/env node const portfinder = require('portfinder'); const concurrently = require('concurrently'); +require('dotenv').config(); const basePort = 8080; portfinder.getPortPromise({ port: basePort, }).then((port) => { + const devServer = `webpack-dev-server --config config/webpack/webpack.dev.js --port ${port} --env.platform desktop`; + const buildMain = 'webpack --config config/webpack/webpack.desktop.js --config-name desktop-main --mode=development'; + const processes = [ { - command: `webpack-dev-server --config config/webpack/webpack.dev.js --port ${port} --platform desktop`, + command: devServer, name: 'Renderer', prefixColor: 'red.dim', - }, { - command: `wait-port localhost:${port} && export NODE_ENV=development PORT=${port} \ - && electron desktop/main.js`, + command: `${buildMain} && wait-port localhost:${port} && electron desktop/dist/main.js`, name: 'Main', prefixColor: 'cyan.dim', + env: { + PORT: port, + NODE_ENV: 'development', + }, }, ]; concurrently(processes, { diff --git a/ios/ExpensifyCash.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/ExpensifyCash.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000000..18d981003d68 --- /dev/null +++ b/ios/ExpensifyCash.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 8912f6cecde7..62faebbc0b09 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.1.38 + 1.1.40 CFBundleSignature ???? CFBundleURLTypes @@ -31,7 +31,7 @@ CFBundleVersion - 1.1.38.3 + 1.1.40.0 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index d4d030ed5edf..0f0f5e6358dc 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.1.38 + 1.1.40 CFBundleSignature ???? CFBundleVersion - 1.1.38.3 + 1.1.40.0 diff --git a/ios/Podfile b/ios/Podfile index e2c5f53e6b95..6ddfb790f587 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -17,6 +17,8 @@ target 'NewExpensify' do :hermes_enabled => true ) + pod 'react-native-webview', :path => '../node_modules/react-native-webview' + target 'NewExpensifyTests' do inherit! :complete # Pods for testing diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 6939287b873f..fe5225951dcc 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -460,6 +460,8 @@ PODS: - React - react-native-safe-area-context (3.1.8): - React-Core + - react-native-webview (11.17.2): + - React-Core - React-perflogger (0.66.4) - React-RCTActionSheet (0.66.4): - React-Core/RCTActionSheetHeaders (= 0.66.4) @@ -668,6 +670,7 @@ DEPENDENCIES: - "react-native-progress-bar-android (from `../node_modules/@react-native-community/progress-bar-android`)" - "react-native-progress-view (from `../node_modules/@react-native-community/progress-view`)" - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) + - react-native-webview (from `../node_modules/react-native-webview`) - React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`) - React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`) - React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`) @@ -806,6 +809,8 @@ EXTERNAL SOURCES: :path: "../node_modules/@react-native-community/progress-view" react-native-safe-area-context: :path: "../node_modules/react-native-safe-area-context" + react-native-webview: + :path: "../node_modules/react-native-webview" React-perflogger: :path: "../node_modules/react-native/ReactCommon/reactperflogger" React-RCTActionSheet: @@ -926,9 +931,9 @@ SPEC CHECKSUMS: React-jsinspector: d0374f7509d407d2264168b6d0fad0b54e300b85 React-logger: 933f80c97c633ee8965d609876848148e3fef438 react-native-config: 6502b1879f97ed5ac570a029961fc35ea606cd14 - react-native-document-picker: 429972f7ece4463aa5bcdd789622b3a674a3c5d1 - react-native-flipper: 169e8ba429b73ad637ce007337ce4b415e783799 - react-native-image-picker: 4089335b89b625d4e34d53fb249c48a7a791b3ea + react-native-document-picker: 772d04a4bc5c35da9abe27b08ac271420ae3f9ef + react-native-flipper: cd9eabd8917104c1bbdca2621717cdca3b2addef + react-native-image-picker: f45729c43d4f854508ab25c0d0f0f711a2a8a267 react-native-netinfo: 3a61a486f2329f5884753fd5bab21450a535d97c react-native-pdf: 4b5a9e4465a6a3b399e91dc4838eb44ddf716d1f react-native-performance: 8edfa2bbc9a2af4a02f01d342118e413a95145e0 @@ -936,6 +941,7 @@ SPEC CHECKSUMS: react-native-progress-bar-android: ce95a69f11ac580799021633071368d08aaf9ad8 react-native-progress-view: 5816e8a6be812c2b122c6225a2a3db82d9008640 react-native-safe-area-context: 01158a92c300895d79dee447e980672dc3fb85a6 + react-native-webview: 77ee909f73e1fcab76380f7dcc3344771fe61bd8 React-perflogger: 93075d8931c32cd1fce8a98c15d2d5ccc4d891bd React-RCTActionSheet: 7d3041e6761b4f3044a37079ddcb156575fb6d89 React-RCTAnimation: 743e88b55ac62511ae5c2e22803d4f503f2a3a13 @@ -971,6 +977,6 @@ SPEC CHECKSUMS: Yoga: e7dc4e71caba6472ff48ad7d234389b91dadc280 YogaKit: f782866e155069a2cca2517aafea43200b01fd5a -PODFILE CHECKSUM: 46dab5ec14a7bc81989e2f3e8cd4bba211deddbe +PODFILE CHECKSUM: 1199d07d3ccd1ea23300ad9e27bc9287716223bf -COCOAPODS: 1.11.2 +COCOAPODS: 1.10.1 diff --git a/package-lock.json b/package-lock.json index 713cd80a22e1..fda48d0e4030 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.1.38-3", + "version": "1.1.40-0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -15283,11 +15283,6 @@ "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", "dev": true }, - "@types/semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-+nVsLKlcUCeMzD2ufHEYuJ9a2ovstb6Dp52A5VsoKxDXgvE051XgHI/33I1EymwkRGQkwnA0LkhnUzituGs4EQ==" - }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -16947,11 +16942,6 @@ "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", "dev": true }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==" - }, "async": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", @@ -16985,7 +16975,8 @@ "at-least-node": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==" + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true }, "atob": { "version": "2.1.2", @@ -18687,15 +18678,6 @@ } } }, - "builder-util-runtime": { - "version": "8.7.2", - "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-8.7.2.tgz", - "integrity": "sha512-xBqv+8bg6cfnzAQK1k3OGpfaHg+QkPgIgpEkXNhouZ0WiUkyZCftuRc2LYzQrLucFywpa14Xbc6+hTbpq83yRA==", - "requires": { - "debug": "^4.1.1", - "sax": "^1.2.4" - } - }, "builtin-status-codes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", @@ -19346,15 +19328,6 @@ "string-width": "^4.2.0" } }, - "cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", - "requires": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" - } - }, "cli-width": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", @@ -21928,35 +21901,11 @@ } } }, - "electron-context-menu": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/electron-context-menu/-/electron-context-menu-2.3.0.tgz", - "integrity": "sha512-XYsYkNY+jvX4C5o09qMuZoKL6e9frnQzBFehZSIiKp6zK0u3XYowJYDyK3vDKKZxYsOIGiE/Gbx40jERC03Ctw==", - "requires": { - "cli-truncate": "^2.0.0", - "electron-dl": "^3.0.0", - "electron-is-dev": "^1.0.1" - } - }, - "electron-dl": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/electron-dl/-/electron-dl-3.0.2.tgz", - "integrity": "sha512-pRgE9Jbhoo5z6Vk3qi+vIrfpMDlCp2oB1UeR96SMnsfz073jj0AZGQwp69EdIcEvlUlwBSGyJK8Jt6OB6JLn+g==", - "requires": { - "ext-name": "^5.0.0", - "pupa": "^2.0.1", - "unused-filename": "^2.1.0" - } - }, "electron-is-dev": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/electron-is-dev/-/electron-is-dev-1.2.0.tgz", - "integrity": "sha512-R1oD5gMBPS7PVU8gJwH6CtT0e6VSoD0+SzSnYpNm+dBkcijgA+K7VAMHDfnRq/lkKPZArpzplTW6jfiMYosdzw==" - }, - "electron-log": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/electron-log/-/electron-log-4.3.5.tgz", - "integrity": "sha512-J5Ew3axdk7W4jzzxKLSAi1sqbcAoo9CzHuBVsG0tT47j256xKulNrWFf3lZmHJ1KDXOQUcuwOngQF0jjmpEdpw==" + "integrity": "sha512-R1oD5gMBPS7PVU8gJwH6CtT0e6VSoD0+SzSnYpNm+dBkcijgA+K7VAMHDfnRq/lkKPZArpzplTW6jfiMYosdzw==", + "dev": true }, "electron-notarize": { "version": "1.0.0", @@ -22216,31 +22165,12 @@ } } }, - "electron-serve": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/electron-serve/-/electron-serve-1.0.0.tgz", - "integrity": "sha512-Rsm4tjj1eK7NUWKgGw6NjHkjfB+bIXZh0ztybUYzqmwCm1wzb7zv95LERbwricDZfCsKHB0V57NgVvHdi2OOAQ==" - }, "electron-to-chromium": { "version": "1.3.820", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.820.tgz", "integrity": "sha512-5cFwDmo2yzEA9hn55KZ9+cX/b6DSFvpKz8Hb2fiDmriXWB+DBoXKXmncQwNRFBBTlUdsvPHCoy594OoMLAO0Tg==", "dev": true }, - "electron-updater": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-4.3.5.tgz", - "integrity": "sha512-5jjN7ebvfj1cLI0VZMdCnJk6aC4bP+dy7ryBf21vArR0JzpRVk0OZHA2QBD+H5rm6ZSeDYHOY6+8PrMEqJ4wlQ==", - "requires": { - "@types/semver": "^7.3.1", - "builder-util-runtime": "8.7.2", - "fs-extra": "^9.0.1", - "js-yaml": "^3.14.0", - "lazy-val": "^1.0.4", - "lodash.isequal": "^4.5.0", - "semver": "^7.3.2" - } - }, "element-resize-detector": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/element-resize-detector/-/element-resize-detector-1.2.4.tgz", @@ -22538,7 +22468,8 @@ "escape-goat": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", - "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==" + "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", + "dev": true }, "escape-html": { "version": "1.0.3", @@ -24123,8 +24054,8 @@ } }, "expensify-common": { - "version": "git+https://github.com/Expensify/expensify-common.git#2d0f8f2f3424fea79854ea10d050824b5bd0c27b", - "from": "git+https://github.com/Expensify/expensify-common.git#2d0f8f2f3424fea79854ea10d050824b5bd0c27b", + "version": "git+https://github.com/Expensify/expensify-common.git#f77bb4710c13d01153716df7fb087b637ba3b8bd", + "from": "git+https://github.com/Expensify/expensify-common.git#f77bb4710c13d01153716df7fb087b637ba3b8bd", "requires": { "classnames": "2.3.1", "clipboard": "2.0.4", @@ -24255,23 +24186,6 @@ } } }, - "ext-list": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", - "integrity": "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==", - "requires": { - "mime-db": "^1.28.0" - } - }, - "ext-name": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz", - "integrity": "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==", - "requires": { - "ext-list": "^2.0.0", - "sort-keys-length": "^1.0.0" - } - }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -25076,6 +24990,7 @@ "version": "9.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", + "dev": true, "requires": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", @@ -26891,11 +26806,6 @@ "path-is-inside": "^1.0.2" } }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" - }, "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -30757,6 +30667,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz", "integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==", + "dev": true, "requires": { "graceful-fs": "^4.1.6", "universalify": "^1.0.0" @@ -30872,7 +30783,8 @@ "lazy-val": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.4.tgz", - "integrity": "sha512-u93kb2fPbIrfzBuLjZE+w+fJbUUMhNDXxNmMfaqNgpfQf1CO5ZSe2LfsnBqVAk7i/2NF48OSoRj+Xe2VT+lE8Q==" + "integrity": "sha512-u93kb2fPbIrfzBuLjZE+w+fJbUUMhNDXxNmMfaqNgpfQf1CO5ZSe2LfsnBqVAk7i/2NF48OSoRj+Xe2VT+lE8Q==", + "dev": true }, "lcid": { "version": "1.0.0", @@ -33793,11 +33705,6 @@ "resolved": "https://registry.npmjs.org/mockdate/-/mockdate-3.0.5.tgz", "integrity": "sha512-iniQP4rj1FhBdBYS/+eQv7j1tadJ9lJtdzgOpvsOHng/GbcDh2Fhdeq+ZRldrPYdXvCyfFUmFeEwEGXZB5I/AQ==" }, - "modify-filename": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/modify-filename/-/modify-filename-1.1.0.tgz", - "integrity": "sha1-mi3sg4Bvuy2XXyK+7IWcoms5OqE=" - }, "moment": { "version": "2.29.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", @@ -36039,14 +35946,6 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, - "pupa": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.0.1.tgz", - "integrity": "sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA==", - "requires": { - "escape-goat": "^2.0.0" - } - }, "pusher-js": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pusher-js/-/pusher-js-7.0.0.tgz", @@ -37347,6 +37246,22 @@ "version": "git+https://github.com/Expensify/react-native-web.git#3cd81b5af5916dca1dbdfef6b4e9ad261772afe6", "from": "git+https://github.com/Expensify/react-native-web.git#3cd81b5af5916dca1dbdfef6b4e9ad261772afe6" }, + "react-native-webview": { + "version": "11.17.2", + "resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-11.17.2.tgz", + "integrity": "sha512-7Sac02xq11qFACJmSUuCnH0aUFtSWUvSRC09EZ2qwNXq4IvT05xlX6978nlKUXf2ljw/0qZIzqbKzuXnu6Wq8Q==", + "requires": { + "escape-string-regexp": "2.0.0", + "invariant": "2.2.4" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==" + } + } + }, "react-pdf": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-5.2.0.tgz", @@ -39288,16 +39203,6 @@ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" }, - "slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - } - }, "smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -39507,22 +39412,6 @@ } } }, - "sort-keys": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", - "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", - "requires": { - "is-plain-obj": "^1.0.0" - } - }, - "sort-keys-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz", - "integrity": "sha1-nLb09OnkgVWmqgZx7dM2/xR5oYg=", - "requires": { - "sort-keys": "^1.0.0" - } - }, "source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", @@ -41748,7 +41637,8 @@ "universalify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", - "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==" + "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", + "dev": true }, "unpipe": { "version": "1.0.0", @@ -41797,15 +41687,6 @@ } } }, - "unused-filename": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unused-filename/-/unused-filename-2.1.0.tgz", - "integrity": "sha512-BMiNwJbuWmqCpAM1FqxCTD7lXF97AvfQC8Kr/DIeA6VtvhJaMDupZ82+inbjl5yVP44PcxOuCSxye1QMS0wZyg==", - "requires": { - "modify-filename": "^1.1.0", - "path-exists": "^4.0.0" - } - }, "upath": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", diff --git a/package.json b/package.json index af2ff72d806d..ce25848df9c3 100644 --- a/package.json +++ b/package.json @@ -1,25 +1,25 @@ { "name": "new.expensify", - "version": "1.1.38-3", + "version": "1.1.40-0", "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.", "license": "MIT", "private": true, "scripts": { - "postinstall": "./build/react-native-web.sh", + "postinstall": "scripts/react-native-web.sh && cd desktop && npm install", "clean": "react-native clean-project-auto", "android": "npm run check-metro-bundler-port && react-native run-android", "ios": "npm run check-metro-bundler-port && react-native run-ios", "ipad": "npm run check-metro-bundler-port && react-native run-ios --simulator=\"iPad Pro (12.9-inch) (4th generation)\"", "ipad-sm": "npm run check-metro-bundler-port && react-native run-ios --simulator=\"iPad Pro (9.7-inch)\"", - "desktop": "node desktop/start.js", "start": "react-native start", "web": "node web/proxy.js & webpack-dev-server --open --config config/webpack/webpack.dev.js", - "build": "webpack --config config/webpack/webpack.prod.js", - "build-staging": "webpack --config config/webpack/webpack.staging.js", - "desktop-build": "webpack --config config/webpack/webpack.prod.js --platform desktop && electron-builder --config config/electron.config.js", - "desktop-build-staging": "webpack --config config/webpack/webpack.staging.js --platform desktop && electron-builder --config config/electron.config.js", + "build": "webpack --config config/webpack/webpack.common.js --env.envFile=.env.production", + "build-staging": "webpack --config config/webpack/webpack.common.js --env.envFile=.env.staging", + "desktop": "node desktop/start.js", + "desktop-build": "scripts/build-desktop.sh production", + "desktop-build-staging": "scripts/build-desktop.sh staging", "ios-build": "fastlane ios build", "android-build": "fastlane android build", "test": "jest", @@ -61,11 +61,7 @@ "dom-serializer": "^0.2.2", "domhandler": "^4.3.0", "dotenv": "^8.2.0", - "electron-context-menu": "^2.3.0", - "electron-log": "^4.3.5", - "electron-serve": "^1.0.0", - "electron-updater": "^4.3.4", - "expensify-common": "git+https://github.com/Expensify/expensify-common.git#2d0f8f2f3424fea79854ea10d050824b5bd0c27b", + "expensify-common": "git+https://github.com/Expensify/expensify-common.git#f77bb4710c13d01153716df7fb087b637ba3b8bd", "fbjs": "^3.0.2", "file-loader": "^6.0.0", "html-entities": "^1.3.1", @@ -106,6 +102,7 @@ "react-native-screens": "^3.10.1", "react-native-svg": "^12.1.0", "react-native-web": "git+https://github.com/Expensify/react-native-web.git#3cd81b5af5916dca1dbdfef6b4e9ad261772afe6", + "react-native-webview": "^11.17.2", "react-pdf": "^5.2.0", "react-plaid-link": "^3.2.0", "react-web-config": "^1.0.0", diff --git a/scripts/build-desktop.sh b/scripts/build-desktop.sh new file mode 100755 index 000000000000..caacef993025 --- /dev/null +++ b/scripts/build-desktop.sh @@ -0,0 +1,27 @@ +#!/bin/bash +set -e + +ELECTRON_ENV=${1:-development} + +if [[ "$ELECTRON_ENV" == "staging" ]]; then + ENV_FILE=".env.staging" +elif [[ "$ELECTRON_ENV" == "production" ]]; then + ENV_FILE=".env.production" +else + ENV_FILE=".env" +fi + +SCRIPTS_DIR=$(dirname "${BASH_SOURCE[0]}") +LOCAL_PACKAGES=$(npm bin) +source "$SCRIPTS_DIR/shellUtils.sh"; + +title "Bundling Desktop js Bundle Using Webpack" +info " • ELECTRON_ENV: $ELECTRON_ENV" +info " • ENV file: $ENV_FILE" +info "" +"$LOCAL_PACKAGES/webpack" --config config/webpack/webpack.desktop.js --env.envFile=$ENV_FILE + +title "Building Desktop App Archive Using Electron" +info "" +shift 1 +"$LOCAL_PACKAGES/electron-builder" --config config/electronBuilder/electronBuilder.config.js "$@" diff --git a/build/react-native-web.sh b/scripts/react-native-web.sh similarity index 100% rename from build/react-native-web.sh rename to scripts/react-native-web.sh diff --git a/tests/utils/shellUtils.sh b/scripts/shellUtils.sh similarity index 100% rename from tests/utils/shellUtils.sh rename to scripts/shellUtils.sh diff --git a/src/CONST/index.js b/src/CONST.js similarity index 97% rename from src/CONST/index.js rename to src/CONST.js index 8d401264cb8d..d169485906e4 100755 --- a/src/CONST/index.js +++ b/src/CONST.js @@ -1,9 +1,9 @@ import lodashGet from 'lodash/get'; import Config from 'react-native-config'; -import ENVIRONMENT from './ENVIRONMENT'; -import * as Url from '../libs/Url'; +import * as Url from './libs/Url'; const CLOUDFRONT_URL = 'https://d2k5nsl2zxldvw.cloudfront.net'; +const USE_EXPENSIFY_URL = 'https://use.expensify.com'; const ACTIVE_ENVIRONMENT_NEW_EXPENSIFY_URL = Url.addTrailingForwardSlash(lodashGet(Config, 'EXPENSIFY_URL_CASH', 'https://new.expensify.com')); const PLATFORM_OS_MACOS = 'Mac OS'; const ANDROID_PACKAGE_NAME = 'com.expensify.chat'; @@ -185,6 +185,7 @@ const CONST = { }, CONCIERGE_CHAT_NAME: 'Concierge', CLOUDFRONT_URL, + USE_EXPENSIFY_URL, NEW_ZOOM_MEETING_URL: 'https://zoom.us/start/videomeeting', NEW_GOOGLE_MEET_MEETING_URL: 'https://meet.google.com/new', DEEPLINK_BASE_URL: 'new-expensify://', @@ -192,13 +193,13 @@ const CONST = { EXPENSIFY_ICON_URL: `${CLOUDFRONT_URL}/images/favicon-2019.png`, UPWORK_URL: 'https://github.com/Expensify/App/issues?q=is%3Aopen+is%3Aissue+label%3A%22Help+Wanted%22', GITHUB_URL: 'https://github.com/Expensify/App', - TERMS_URL: 'https://use.expensify.com/terms', - PRIVACY_URL: 'https://use.expensify.com/privacy', - LICENSES_URL: 'https://use.expensify.com/licenses', + TERMS_URL: `${USE_EXPENSIFY_URL}/terms`, + PRIVACY_URL: `${USE_EXPENSIFY_URL}/privacy`, + LICENSES_URL: `${USE_EXPENSIFY_URL}/licenses`, PLAY_STORE_URL: `https://play.google.com/store/apps/details?id=${ANDROID_PACKAGE_NAME}&hl=en`, ADD_SECONDARY_LOGIN_URL: encodeURI('settings?param={"section":"account","openModal":"secondaryLogin"}'), MANAGE_CARDS_URL: 'domain_companycards', - FEES_URL: 'https://use.expensify.com/fees', + FEES_URL: `${USE_EXPENSIFY_URL}/fees`, CFPB_PREPAID_URL: 'https://cfpb.gov/prepaid', STAGING_SECURE_URL: 'https://staging-secure.expensify.com/', NEWDOT: 'new.expensify.com', @@ -297,6 +298,7 @@ const CONST = { ERROR: { API_OFFLINE: 'session.offlineMessageRetry', UNKNOWN_ERROR: 'Unknown error', + REQUEST_CANCELLED: 'AbortError', }, NETWORK: { METHOD: { @@ -366,7 +368,10 @@ const CONST = { ADD_PAYMENT_MENU_POSITION_Y: 226, ADD_PAYMENT_MENU_POSITION_X: 356, - EMOJI_PICKER_SIZE: 320, + EMOJI_PICKER_SIZE: { + WIDTH: 320, + HEIGHT: 400, + }, NON_NATIVE_EMOJI_PICKER_LIST_HEIGHT: 300, EMOJI_PICKER_ITEM_HEIGHT: 40, EMOJI_PICKER_HEADER_HEIGHT: 38, @@ -391,6 +396,12 @@ const CONST = { ADMIN: 'admin@expensify.com', }, + ENVIRONMENT: { + DEV: 'development', + STAGING: 'staging', + PRODUCTION: 'production', + }, + // Used to delay the initial fetching of reportActions when the app first inits or reconnects (e.g. returning // from backgound). The times are based on how long it generally seems to take for the app to become interactive // in each scenario. @@ -526,6 +537,7 @@ const CONST = { ROLE: { ADMIN: 'admin', }, + ROOM_PREFIX: '#', }, TERMS: { @@ -609,6 +621,4 @@ const CONST = { }, }; -CONST.ENVIRONMENT = ENVIRONMENT; - export default CONST; diff --git a/src/CONST/ENVIRONMENT.js b/src/CONST/ENVIRONMENT.js deleted file mode 100644 index dd742b0f8245..000000000000 --- a/src/CONST/ENVIRONMENT.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - DEV: 'DEV', - STAGING: 'STG', - PRODUCTION: 'PROD', -}; diff --git a/src/ROUTES.js b/src/ROUTES.js index c71b311767cd..54b27b273347 100644 --- a/src/ROUTES.js +++ b/src/ROUTES.js @@ -89,6 +89,8 @@ export default { // This is a special validation URL that will take the user to /workspace/new after validation. This is used // when linking users from e.com in order to share a session in this app. ENABLE_PAYMENTS: 'enable-payments', + WALLET_STATEMENT_WITH_DATE: 'statements/:yearMonth', + getWalletStatementWithDateRoute: yearMonth => `statements/${yearMonth}`, WORKSPACE_NEW: 'workspace/new', WORKSPACE_INITIAL: 'workspace/:policyID', WORKSPACE_INVITE: 'workspace/:policyID/invite', diff --git a/src/components/AddressSearch.js b/src/components/AddressSearch.js index 2cb3df16232f..dac2f8cd7c8d 100644 --- a/src/components/AddressSearch.js +++ b/src/components/AddressSearch.js @@ -9,6 +9,7 @@ import styles from '../styles/styles'; import TextInput from './TextInput'; import Log from '../libs/Log'; import * as GooglePlacesUtils from '../libs/GooglePlacesUtils'; +import * as FormUtils from '../libs/FormUtils'; // The error that's being thrown below will be ignored until we fork the // react-native-google-places-autocomplete repo and replace the @@ -16,6 +17,26 @@ import * as GooglePlacesUtils from '../libs/GooglePlacesUtils'; LogBox.ignoreLogs(['VirtualizedLists should never be nested']); const propTypes = { + /** Indicates that the input is being used with the Form component */ + isFormInput: PropTypes.bool, + + /** + * The ID used to uniquely identify the input + * + * @param {Object} props - props passed to the input + * @returns {Object} - returns an Error object if isFormInput is supplied but inputID is falsey or not a string + */ + inputID: props => FormUtils.validateInputIDProps(props), + + /** Saves a draft of the input value when used in a form */ + shouldSaveDraft: PropTypes.bool, + + /** Callback that is called when the text input is blurred */ + onBlur: PropTypes.func, + + /** Error text to display */ + errorText: PropTypes.string, + /** The label to display for the field */ label: PropTypes.string.isRequired, @@ -32,7 +53,12 @@ const propTypes = { }; const defaultProps = { - value: '', + isFormInput: false, + inputID: undefined, + shouldSaveDraft: false, + onBlur: () => {}, + errorText: '', + value: undefined, containerStyles: [], }; @@ -66,7 +92,7 @@ const AddressSearch = (props) => { const state = GooglePlacesUtils.getAddressComponent(addressComponents, 'administrative_area_level_1', 'short_name'); const values = {}; - if (street && street.length > props.value.trim().length) { + if (street && props.value && street.length > props.value.trim().length) { // We are only passing the street number and name if the combined length is longer than the value // that was initially passed to the autocomplete component. Google Places can truncate details // like Apt # and this is the best way we have to tell that the new value it's giving us is less @@ -111,10 +137,27 @@ const AddressSearch = (props) => { }} textInputProps={{ InputComp: TextInput, + ref: (node) => { + if (!props.innerRef) { + return; + } + + if (_.isFunction(props.innerRef)) { + props.innerRef(node); + return; + } + + // eslint-disable-next-line no-param-reassign + props.innerRef.current = node; + }, label: props.label, containerStyles: props.containerStyles, errorText: props.errorText, value: props.value, + isFormInput: props.isFormInput, + inputID: props.inputID, + shouldSaveDraft: props.shouldSaveDraft, + onBlur: props.onBlur, onChangeText: (text) => { if (skippedFirstOnChangeTextRef.current) { props.onChange({street: text}); @@ -160,4 +203,7 @@ const AddressSearch = (props) => { AddressSearch.propTypes = propTypes; AddressSearch.defaultProps = defaultProps; -export default withLocalize(AddressSearch); +export default withLocalize(React.forwardRef((props, ref) => ( + // eslint-disable-next-line react/jsx-props-no-spreading + +))); diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index 1fc8d10c5022..0d0b80417a93 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -139,7 +139,7 @@ class AttachmentModal extends PureComponent { ? addEncryptedAuthTokenToURL(this.state.sourceURL) : this.state.sourceURL; - const attachmentViewStyles = this.props.isSmallScreenWidth + const attachmentViewStyles = this.props.isSmallScreenWidth || this.props.isMediumScreenWidth ? [styles.imageModalImageCenterContainer] : [styles.imageModalImageCenterContainer, styles.p5]; diff --git a/src/components/Avatar.js b/src/components/Avatar.js index 228ed6fb23a4..0e6aa6a381e6 100644 --- a/src/components/Avatar.js +++ b/src/components/Avatar.js @@ -2,6 +2,7 @@ import React, {PureComponent} from 'react'; import {Image, View} from 'react-native'; import PropTypes from 'prop-types'; import styles from '../styles/styles'; +import themeColors from '../styles/themes/default'; import RoomAvatar from './RoomAvatar'; import stylePropTypes from '../styles/stylePropTypes'; @@ -42,8 +43,12 @@ class Avatar extends PureComponent { const imageStyle = [ this.props.size === 'small' ? styles.avatarSmall : styles.avatarNormal, + + // Background color isn't added for room avatar because it changes it's shape to a square + this.props.isChatRoom ? {} : {backgroundColor: themeColors.icon}, ...this.props.imageStyles, ]; + return ( {this.props.isChatRoom diff --git a/src/components/BigNumberPad.js b/src/components/BigNumberPad.js index 604eab5b0662..0ebb6d86ed24 100644 --- a/src/components/BigNumberPad.js +++ b/src/components/BigNumberPad.js @@ -59,6 +59,7 @@ class BigNumberPad extends React.Component { return (