Skip to content

Commit

Permalink
Merge from main
Browse files Browse the repository at this point in the history
  • Loading branch information
iwiznia committed Aug 1, 2022
2 parents c3ccb28 + 631994f commit 1803d86
Show file tree
Hide file tree
Showing 27 changed files with 492 additions and 280 deletions.
1 change: 1 addition & 0 deletions config/webpack/webpack.common.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ const webpackConfig = ({envFile = '.env', platform = 'web'}) => ({
alias: {
'react-native-config': 'react-web-config',
'react-native$': 'react-native-web',
'react-content-loader/native': 'react-content-loader',
},

// React Native libraries may have web-specific module implementations that appear with the extension `.web.js`
Expand Down
8 changes: 5 additions & 3 deletions contributingGuides/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,16 @@ The data will automatically be sent to the user via Pusher.
#### WRITE Response Errors
When there is an error on a WRITE response (`jsonCode!==200`), the error must come back to the client on the HTTPS response. The error is only relevant to the client that made the request and it wouldn't make sense to send it out to all connected clients.

Error messages should be returned and stored as a String under the `error` property. If absolutely needed, additional error properties can be stored under other, more specific fields that sit at the same level as `error`:
Error messages should be returned and stored as an object under the `errors` property, keyed by an integer [microtime](https://github.com/Expensify/Web-Expensify/blob/25d056c9c531ea7f12c9bf3283ec554dd5d1d316/lib/Onyx.php#L148-L154). If absolutely needed, additional error properties can be stored under other, more specific fields that sit at the same level as `errors`:
```php
[
'onyxMethod' => Onyx::METHOD_MERGE,
'key' => OnyxKeys::WALLET_ADDITIONAL_DETAILS,
'value' => [
'error' => 'We\'re having trouble verifying your SSN. Please enter the full 9 digits of your SSN.',
'errorCode' => 'ssnError'
'errors' => [
Onyx::getErrorMicroTime() => 'We\'re having trouble verifying your SSN. Please enter the full 9 digits of your SSN.',
],
'errorCode' => 'ssnError',
],
]
```
Expand Down
145 changes: 75 additions & 70 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"pusher-js": "^7.0.6",
"react": "^17.0.2",
"react-collapse": "^5.1.0",
"react-content-loader": "^6.1.0",
"react-dom": "^17.0.2",
"react-native": "0.66.4",
"react-native-collapsible": "^1.6.0",
Expand Down
10 changes: 8 additions & 2 deletions src/CONST.js
Original file line number Diff line number Diff line change
Expand Up @@ -476,9 +476,15 @@ const CONST = {
NON_NATIVE_EMOJI_PICKER_LIST_HEIGHT: 300,
EMOJI_PICKER_ITEM_HEIGHT: 40,
EMOJI_PICKER_HEADER_HEIGHT: 38,

COMPOSER_MAX_HEIGHT: 125,

CHAT_SKELETON_VIEW: {
AVERAGE_ROW_HEIGHT: 80,
HEIGHT_FOR_ROW_COUNT: {
1: 60,
2: 80,
3: 100,
},
},
EMAIL: {
CONCIERGE: 'concierge@expensify.com',
HELP: 'help@expensify.com',
Expand Down
7 changes: 4 additions & 3 deletions src/ONYXKEYS.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ export default {
POLICY: 'policy_',
REPORTS_WITH_DRAFT: 'reportWithDraft_',
REPORT_IS_COMPOSER_FULL_SIZE: 'reportIsComposerFullSize_',
IS_LOADING_REPORT_ACTIONS: 'isLoadingReportActions_',
IS_LOADING_INITIAL_REPORT_ACTIONS: 'isLoadingInitialReportActions_',
IS_LOADING_MORE_REPORT_ACTIONS: 'isLoadingMoreReportActions_',
POLICY_MEMBER_LIST: 'policyMemberList_',
},

Expand Down Expand Up @@ -179,8 +180,8 @@ export default {
// Is Keyboard shortcuts modal open?
IS_SHORTCUTS_MODAL_OPEN: 'isShortcutsModalOpen',

// Is close acount modal open?
IS_CLOSE_ACCOUNT_MODAL_OPEN: 'isCloseAccountModalOpen',
// Data related to user closing their account (loading status and error message)
CLOSE_ACCOUNT: 'closeAccount',

// Stores information about active wallet transfer amount, selectedAccountID, status, etc
WALLET_TRANSFER: 'walletTransfer',
Expand Down
26 changes: 26 additions & 0 deletions src/components/ReportActionsSkeletonView/SkeletonViewLines.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react';
import PropTypes from 'prop-types';
import {Rect, Circle} from 'react-native-svg';
import SkeletonViewContentLoader from 'react-content-loader/native';
import CONST from '../../CONST';

const propTypes = {
/** Number of rows to show in Skeleton UI block */
numberOfRows: PropTypes.number.isRequired,
};

const SkeletonViewLines = props => (
<SkeletonViewContentLoader
height={CONST.CHAT_SKELETON_VIEW.HEIGHT_FOR_ROW_COUNT[props.numberOfRows]}
>
<Circle cx="40" cy="26" r="20" />
<Rect x="67" y="11" width="20%" height="8" />
<Rect x="67" y="31" width="90%" height="8" />
{props.numberOfRows > 1 && <Rect x="67" y="51" width="50%" height="8" />}
{props.numberOfRows > 2 && <Rect x="67" y="71" width="50%" height="8" />}
</SkeletonViewContentLoader>
);

SkeletonViewLines.displayName = 'SkeletonViewLines';
SkeletonViewLines.propTypes = propTypes;
export default SkeletonViewLines;
33 changes: 33 additions & 0 deletions src/components/ReportActionsSkeletonView/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';
import PropTypes from 'prop-types';
import SkeletonViewLines from './SkeletonViewLines';
import CONST from '../../CONST';

const propTypes = {
/** Height of the container component */
containerHeight: PropTypes.number.isRequired,
};

const ReportActionsSkeletonView = (props) => {
// Determines the number of content items based on container height
const possibleVisibleContentItems = Math.floor(props.containerHeight / CONST.CHAT_SKELETON_VIEW.AVERAGE_ROW_HEIGHT);
const skeletonViewLines = [];
for (let index = 0; index < possibleVisibleContentItems; index++) {
const iconIndex = (index + 1) % 4;
switch (iconIndex) {
case 2:
skeletonViewLines.push(<SkeletonViewLines numberOfRows={2} key={`skeletonViewLines${index}`} />);
break;
case 0:
skeletonViewLines.push(<SkeletonViewLines numberOfRows={3} key={`skeletonViewLines${index}`} />);
break;
default:
skeletonViewLines.push(<SkeletonViewLines numberOfRows={1} key={`skeletonViewLines${index}`} />);
}
}
return <>{skeletonViewLines}</>;
};

ReportActionsSkeletonView.displayName = 'ReportActionsSkeletonView';
ReportActionsSkeletonView.propTypes = propTypes;
export default ReportActionsSkeletonView;
1 change: 0 additions & 1 deletion src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,6 @@ export default {
enterMessageHere: 'Enter message here',
closeAccountWarning: 'Closing your account cannot be undone.',
closeAccountPermanentlyDeleteData: 'This will permanently delete all of your unsubmitted expense data. Type your phone number or email address to confirm.',
closeAccountSuccess: 'Account closed successfully',
closeAccountActionRequired: 'Looks like you need to complete some actions before closing your account. Check out the guide',
closeAccountTryAgainAfter: 'and try again after.',
enterDefaultContact: 'Enter your default contact method',
Expand Down
1 change: 0 additions & 1 deletion src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,6 @@ export default {
enterMessageHere: 'Ingresa el mensaje aquí',
closeAccountWarning: 'Una vez cerrada tu cuenta no se puede revertir.',
closeAccountPermanentlyDeleteData: 'Esta acción eliminará permanentemente toda la información de tus gastos no enviados. Escribe tu número de teléfono o correo electrónico para confirmar',
closeAccountSuccess: 'Cuenta cerrada exitosamente',
closeAccountActionRequired: 'Parece que necesitas completar algunas acciones antes de cerrar tu cuenta. Mira la guía',
closeAccountTryAgainAfter: 'e intenta nuevamente',
enterDefaultContact: 'Tu método de contacto predeterminado',
Expand Down
5 changes: 3 additions & 2 deletions src/libs/Firebase/index.native.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable no-unused-vars */
import perf from '@react-native-firebase/perf';
import lodashGet from 'lodash/get';
import * as Environment from '../Environment/Environment';
import Log from '../Log';

Expand Down Expand Up @@ -32,19 +33,19 @@ function startTrace(customEventName) {
*/
function stopTrace(customEventName) {
const stop = global.performance.now();

if (Environment.isDevelopment()) {
return;
}

const {trace, start} = traceMap[customEventName];
const trace = lodashGet(traceMap, [customEventName, 'trace']);
if (!trace) {
return;
}

trace.stop();

// Uncomment to inspect logs on release builds
// const start = lodashGet(traceMap, [customEventName, 'start']);
// Log.info(`sidebar_loaded: ${stop - start} ms`, true);

delete traceMap[customEventName];
Expand Down
43 changes: 43 additions & 0 deletions src/libs/actions/App.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import moment from 'moment-timezone';
import {AppState, Linking} from 'react-native';
import Onyx from 'react-native-onyx';
import lodashGet from 'lodash/get';
Expand All @@ -19,10 +20,12 @@ import ROUTES from '../../ROUTES';
import * as SessionUtils from '../SessionUtils';

let currentUserAccountID;
let currentUserEmail = '';
Onyx.connect({
key: ONYXKEYS.SESSION,
callback: (val) => {
currentUserAccountID = lodashGet(val, 'accountID', '');
currentUserEmail = lodashGet(val, 'email', '');
},
});

Expand All @@ -33,6 +36,12 @@ Onyx.connect({
initWithStoredValues: false,
});

let myPersonalDetails;
Onyx.connect({
key: ONYXKEYS.PERSONAL_DETAILS,
callback: val => myPersonalDetails = val[currentUserEmail],
});

const allPolicies = {};
Onyx.connect({
key: ONYXKEYS.COLLECTION.POLICY,
Expand Down Expand Up @@ -206,6 +215,39 @@ function setUpPoliciesAndNavigate(session) {
});
}

function openProfile() {
const oldTimezoneData = myPersonalDetails.timezone || {};
const newTimezoneData = {
automatic: lodashGet(oldTimezoneData, 'automatic', true),
selected: moment.tz.guess(true),
};

API.write('OpenProfile', {
timezone: JSON.stringify(newTimezoneData),
}, {
optimisticData: [{
onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.PERSONAL_DETAILS,
value: {
[currentUserEmail]: {
timezone: newTimezoneData,
},
},
}],
failureData: [{
onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.PERSONAL_DETAILS,
value: {
[currentUserEmail]: {
timezone: oldTimezoneData,
},
},
}],
});

Navigation.navigate(ROUTES.SETTINGS_PROFILE);
}

// When the app reconnects from being offline, fetch all initialization data
NetworkConnection.onReconnect(() => {
getAppData();
Expand All @@ -219,5 +261,6 @@ export {
getAppData,
fixAccountAndReloadData,
setUpPoliciesAndNavigate,
openProfile,
openApp,
};
29 changes: 5 additions & 24 deletions src/libs/actions/CloseAccount.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,14 @@
import Onyx from 'react-native-onyx';
import ONYXKEYS from '../../ONYXKEYS';

let isCloseAccountModalOpen;
Onyx.connect({
key: ONYXKEYS.IS_CLOSE_ACCOUNT_MODAL_OPEN,
callback: flag => isCloseAccountModalOpen = flag,
});

/**
* Set CloseAccount flag to show modal
*/
function showCloseAccountModal() {
if (isCloseAccountModalOpen) {
return;
}
Onyx.set(ONYXKEYS.IS_CLOSE_ACCOUNT_MODAL_OPEN, true);
}

/**
* Unset CloseAccount flag to hide modal
* Clear CloseAccount error message to hide modal
*/
function hideCloseAccountModal() {
if (!isCloseAccountModalOpen) {
return;
}
Onyx.set(ONYXKEYS.IS_CLOSE_ACCOUNT_MODAL_OPEN, false);
function clearError() {
Onyx.merge(ONYXKEYS.CLOSE_ACCOUNT, {error: ''});
}

export {
showCloseAccountModal,
hideCloseAccountModal,
// eslint-disable-next-line import/prefer-default-export
clearError,
};
73 changes: 66 additions & 7 deletions src/libs/actions/PersonalDetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,48 @@ function setPersonalDetails(details, shouldGrowl) {
});
}

function updateProfile(firstName, lastName, pronouns, timezone) {
const myPersonalDetails = personalDetails[currentUserEmail];
API.write('UpdateProfile', {
// 'details' is an old param that will be removed in https://github.com/Expensify/Expensify/issues/220321
details: JSON.stringify({firstName, lastName, pronouns}),
firstName,
lastName,
pronouns,
timezone: JSON.stringify(timezone),
}, {
optimisticData: [{
onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.PERSONAL_DETAILS,
value: {
[currentUserEmail]: {
firstName,
lastName,
pronouns,
timezone,
displayName: getDisplayName(currentUserEmail, {
firstName,
lastName,
}),
},
},
}],
failureData: [{
onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.PERSONAL_DETAILS,
value: {
[currentUserEmail]: {
firstName: myPersonalDetails.firstName,
lastName: myPersonalDetails.lastName,
pronouns: myPersonalDetails.pronouns,
timezone: myPersonalDetails.timeZone,
displayName: myPersonalDetails.displayName,
},
},
}],
});
}

/**
* Fetches the local currency based on location and sets currency code/symbol to Onyx
*/
Expand Down Expand Up @@ -295,14 +337,30 @@ function setAvatar(file) {

/**
* Replaces the user's avatar image with a default avatar
*
* @param {String} defaultAvatarURL
*/
function deleteAvatar(defaultAvatarURL) {
// We don't want to save the default avatar URL in the backend since we don't want to allow
// users the option of removing the default avatar, instead we'll save an empty string
DeprecatedAPI.PersonalDetails_Update({details: JSON.stringify({avatar: ''})});
mergeLocalPersonalDetails({avatar: defaultAvatarURL});
function deleteAvatar() {
const defaultAvatar = ReportUtils.getDefaultAvatar(currentUserEmail);

API.write('DeleteUserAvatar', {}, {
optimisticData: [{
onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.PERSONAL_DETAILS,
value: {
[currentUserEmail]: {
avatar: defaultAvatar,
},
},
}],
failureData: [{
onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.PERSONAL_DETAILS,
value: {
[currentUserEmail]: {
avatar: personalDetails[currentUserEmail].avatar,
},
},
}],
});
}

export {
Expand All @@ -315,4 +373,5 @@ export {
openIOUModalPage,
getMaxCharacterError,
extractFirstAndLastNameFromAvailableDetails,
updateProfile,
};
2 changes: 1 addition & 1 deletion src/libs/actions/Policy.js
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ function subscribeToPolicyEvents() {
if (!_.isEmpty(policyExpenseChatIDs)) {
Report.fetchChatReportsByIDs(policyExpenseChatIDs);
_.each(policyExpenseChatIDs, (reportID) => {
Report.fetchActions(reportID);
Report.fetchInitialActions(reportID);
});
}

Expand Down
Loading

0 comments on commit 1803d86

Please sign in to comment.