Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: validate new contact method action #48320

Merged
merged 12 commits into from
Sep 2, 2024
3 changes: 3 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ const ONYXKEYS = {
/** Contains metadata (partner, login, validation date) for all of the user's logins */
LOGIN_LIST: 'loginList',

PENDING_CONTACT_ACTION: 'pendingContactAction',
getusha marked this conversation as resolved.
Show resolved Hide resolved

/** Information about the current session (authToken, accountID, email, loading, error) */
SESSION: 'session',
STASHED_SESSION: 'stashedSession',
Expand Down Expand Up @@ -811,6 +813,7 @@ type OnyxValuesMapping = {
[ONYXKEYS.USER]: OnyxTypes.User;
[ONYXKEYS.USER_LOCATION]: OnyxTypes.UserLocation;
[ONYXKEYS.LOGIN_LIST]: OnyxTypes.LoginList;
[ONYXKEYS.PENDING_CONTACT_ACTION]: OnyxTypes.PendingContactAction;
[ONYXKEYS.SESSION]: OnyxTypes.Session;
[ONYXKEYS.USER_METADATA]: OnyxTypes.UserMetadata;
[ONYXKEYS.STASHED_SESSION]: OnyxTypes.Session;
Expand Down
1 change: 1 addition & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ const ROUTES = {
route: 'settings/profile/contact-methods/:contactMethod/details',
getRoute: (contactMethod: string, backTo?: string) => getUrlWithBackToParam(`settings/profile/contact-methods/${encodeURIComponent(contactMethod)}/details`, backTo),
},
SETINGS_CONTACT_METHOD_VALIDATE_ACTION: 'settings/profile/contact-methods/validate-action',
SETTINGS_NEW_CONTACT_METHOD: {
route: 'settings/profile/contact-methods/new',
getRoute: (backTo?: string) => getUrlWithBackToParam('settings/profile/contact-methods/new', backTo),
Expand Down
1 change: 1 addition & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ const SCREENS = {
DISPLAY_NAME: 'Settings_Display_Name',
CONTACT_METHODS: 'Settings_ContactMethods',
CONTACT_METHOD_DETAILS: 'Settings_ContactMethodDetails',
CONTACT_METHOD_ACTION: 'Settings_VerifyContactMethodAction',
NEW_CONTACT_METHOD: 'Settings_NewContactMethod',
STATUS_CLEAR_AFTER: 'Settings_Status_Clear_After',
STATUS_CLEAR_AFTER_DATE: 'Settings_Status_Clear_After_Date',
Expand Down
2 changes: 1 addition & 1 deletion src/libs/API/parameters/AddNewContactMethodParams.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
type AddNewContactMethodParams = {partnerUserID: string};
type AddNewContactMethodParams = {partnerUserID: string; validateCode: string};

export default AddNewContactMethodParams;
1 change: 1 addition & 0 deletions src/libs/API/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ const WRITE_COMMANDS = {
CONNECT_BANK_ACCOUNT_WITH_PLAID: 'ConnectBankAccountWithPlaid',
ADD_PERSONAL_BANK_ACCOUNT: 'AddPersonalBankAccount',
RESTART_BANK_ACCOUNT_SETUP: 'RestartBankAccountSetup',
RESEND_VALIDATE_CODE: 'ResendValidateCode',
OPT_IN_TO_PUSH_NOTIFICATIONS: 'OptInToPushNotifications',
OPT_OUT_OF_PUSH_NOTIFICATIONS: 'OptOutOfPushNotifications',
READ_NEWEST_ACTION: 'ReadNewestAction',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ const SettingsModalStackNavigator = createModalStackNavigator<SettingsNavigatorP
[SCREENS.SETTINGS.PROFILE.ADDRESS_STATE]: () => require<ReactComponentModule>('../../../../pages/settings/Profile/PersonalDetails/StateSelectionPage').default,
[SCREENS.SETTINGS.PROFILE.CONTACT_METHODS]: () => require<ReactComponentModule>('../../../../pages/settings/Profile/Contacts/ContactMethodsPage').default,
[SCREENS.SETTINGS.PROFILE.CONTACT_METHOD_DETAILS]: () => require<ReactComponentModule>('../../../../pages/settings/Profile/Contacts/ContactMethodDetailsPage').default,
[SCREENS.SETTINGS.PROFILE.CONTACT_METHOD_ACTION]: () => require<ReactComponentModule>('../../../../pages/settings/Profile/Contacts/ValidateContactActionPage').default,
getusha marked this conversation as resolved.
Show resolved Hide resolved
[SCREENS.SETTINGS.PROFILE.NEW_CONTACT_METHOD]: () => require<ReactComponentModule>('../../../../pages/settings/Profile/Contacts/NewContactMethodPage').default,
[SCREENS.SETTINGS.PREFERENCES.PRIORITY_MODE]: () => require<ReactComponentModule>('../../../../pages/settings/Preferences/PriorityModePage').default,
[SCREENS.WORKSPACE.ACCOUNTING.ROOT]: () => require<ReactComponentModule>('../../../../pages/workspace/accounting/PolicyAccountingPage').default,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const CENTRAL_PANE_TO_RHP_MAPPING: Partial<Record<CentralPaneName, string[]>> =
SCREENS.SETTINGS.PROFILE.DISPLAY_NAME,
SCREENS.SETTINGS.PROFILE.CONTACT_METHODS,
SCREENS.SETTINGS.PROFILE.CONTACT_METHOD_DETAILS,
SCREENS.SETTINGS.PROFILE.CONTACT_METHOD_ACTION,
SCREENS.SETTINGS.PROFILE.NEW_CONTACT_METHOD,
SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER,
SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER_DATE,
Expand Down
3 changes: 3 additions & 0 deletions src/libs/Navigation/linkingConfig/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,9 @@ const config: LinkingOptions<RootStackParamList>['config'] = {
[SCREENS.SETTINGS.PROFILE.CONTACT_METHOD_DETAILS]: {
path: ROUTES.SETTINGS_CONTACT_METHOD_DETAILS.route,
},
[SCREENS.SETTINGS.PROFILE.CONTACT_METHOD_ACTION]: {
path: ROUTES.SETINGS_CONTACT_METHOD_VALIDATE_ACTION,
},
[SCREENS.SETTINGS.PROFILE.NEW_CONTACT_METHOD]: {
path: ROUTES.SETTINGS_NEW_CONTACT_METHOD.route,
exact: true,
Expand Down
86 changes: 83 additions & 3 deletions src/libs/actions/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,11 +288,64 @@
},
});
}
/**
* Validates the action to add secondary contact method
*/
function saveNewContactMethodAndValidateAction(contactMethod: string) {
const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.PENDING_CONTACT_ACTION,
value: {
contactMethod: contactMethod,

Check failure on line 300 in src/libs/actions/User.ts

View workflow job for this annotation

GitHub Actions / Run ESLint

Expected property shorthand
errorFields: {
actionVerified: null,
},
pendingFields: {
actionVerified: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
},
},
}

Check failure on line 308 in src/libs/actions/User.ts

View workflow job for this annotation

GitHub Actions / Run ESLint

Insert `,`
];

const successData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.PENDING_CONTACT_ACTION,
value: {
validateCodeSent: true,
errorFields: {
actionVerified: null,
},
pendingFields: {
actionVerified: null,
},
},
}

Check failure on line 324 in src/libs/actions/User.ts

View workflow job for this annotation

GitHub Actions / Run ESLint

Insert `,`
];

const failureData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.PENDING_CONTACT_ACTION,
value: {
errorFields: {
actionVerified: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('contacts.genericFailureMessages.requestContactMethodValidateCode'),
},
pendingFields: {
actionVerified: null,
},
},
}

Check failure on line 339 in src/libs/actions/User.ts

View workflow job for this annotation

GitHub Actions / Run ESLint

Insert `,`
];

API.write(WRITE_COMMANDS.RESEND_VALIDATE_CODE, {}, {optimisticData, successData, failureData});
}

/**
* Adds a secondary login to a user's account
*/
function addNewContactMethodAndNavigate(contactMethod: string, backTo?: string) {
function addNewContactMethodAndNavigate(contactMethod: string, validateCode = '') {
const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
Expand All @@ -310,6 +363,11 @@
},
},
},
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.ACCOUNT,
value: {isLoading: true},
},
];
const successData: OnyxUpdate[] = [
{
Expand All @@ -323,6 +381,23 @@
},
},
},
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.PENDING_CONTACT_ACTION,
value: {
errorFields: {
actionVerified: null,
},
pendingFields: {
actionVerified: null,
},
},
},
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.ACCOUNT,
value: {isLoading: false},
},
];
const failureData: OnyxUpdate[] = [
{
Expand All @@ -339,12 +414,16 @@
},
},
},
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.ACCOUNT,
value: {isLoading: false},
},
];

const parameters: AddNewContactMethodParams = {partnerUserID: contactMethod};
const parameters: AddNewContactMethodParams = {partnerUserID: contactMethod, validateCode};

API.write(WRITE_COMMANDS.ADD_NEW_CONTACT_METHOD, parameters, {optimisticData, successData, failureData});
Navigation.goBack(ROUTES.SETTINGS_CONTACT_METHODS.getRoute(backTo));
getusha marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand Down Expand Up @@ -1144,4 +1223,5 @@
updateDraftCustomStatus,
clearDraftCustomStatus,
requestRefund,
saveNewContactMethodAndValidateAction,
};
10 changes: 6 additions & 4 deletions src/pages/settings/Profile/Contacts/NewContactMethodPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,18 @@
import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
import INPUT_IDS from '@src/types/form/NewContactMethodForm';
import type {LoginList} from '@src/types/onyx';
import type {Account, LoginList} from '@src/types/onyx';
import type {Errors} from '@src/types/onyx/OnyxCommon';

type NewContactMethodPageOnyxProps = {
/** Login list for the user that is signed in */
loginList: OnyxEntry<LoginList>;
account: OnyxEntry<Account>;
getusha marked this conversation as resolved.
Show resolved Hide resolved
};

type NewContactMethodPageProps = NewContactMethodPageOnyxProps & StackScreenProps<SettingsNavigatorParamList, typeof SCREENS.SETTINGS.PROFILE.NEW_CONTACT_METHOD>;

function NewContactMethodPage({loginList, route}: NewContactMethodPageProps) {
function NewContactMethodPage({loginList, route, account}: NewContactMethodPageProps) {

Check failure on line 38 in src/pages/settings/Profile/Contacts/NewContactMethodPage.tsx

View workflow job for this annotation

GitHub Actions / Run ESLint

'account' is defined but never used
const styles = useThemeStyles();
const {translate} = useLocalize();
const loginInputRef = useRef<AnimatedTextInputRef>(null);
Expand All @@ -47,9 +48,10 @@
const validateIfnumber = LoginUtils.validateNumber(phoneLogin);
const submitDetail = (validateIfnumber || values.phoneOrEmail).trim().toLowerCase();

User.addNewContactMethodAndNavigate(submitDetail, route.params?.backTo);
User.saveNewContactMethodAndValidateAction(submitDetail);
getusha marked this conversation as resolved.
Show resolved Hide resolved
Navigation.navigate(ROUTES.SETINGS_CONTACT_METHOD_VALIDATE_ACTION);
},
[route.params?.backTo],

Check warning on line 54 in src/pages/settings/Profile/Contacts/NewContactMethodPage.tsx

View workflow job for this annotation

GitHub Actions / Run ESLint

React Hook useCallback has an unnecessary dependency: 'route.params.backTo'. Either exclude it or remove the dependency array
);

const validate = React.useCallback(
Expand Down Expand Up @@ -105,7 +107,6 @@
onSubmit={addNewContactMethod}
submitButtonText={translate('common.add')}
style={[styles.flexGrow1, styles.mh5]}
enabledWhenOffline
>
<Text style={styles.mb5}>{translate('common.pleaseEnterEmailOrPhoneNumber')}</Text>
<View style={styles.mb6}>
Expand All @@ -131,4 +132,5 @@

export default withOnyx<NewContactMethodPageProps, NewContactMethodPageOnyxProps>({
loginList: {key: ONYXKEYS.LOGIN_LIST},
account: {key: ONYXKEYS.ACCOUNT},
})(NewContactMethodPage);
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Account, LoginList} from '@src/types/onyx';
import type {Account, LoginList, PendingContactAction} from '@src/types/onyx';
import {isEmptyObject} from '@src/types/utils/EmptyObject';

type ValidateCodeFormHandle = {
Expand All @@ -45,29 +45,44 @@
contactMethod: string;

/** If the magic code has been resent previously */
hasMagicCodeBeenSent: boolean;
hasMagicCodeBeenSent?: boolean;

/** Login list for the user that is signed in */
loginList: LoginList;
loginList?: LoginList;

/** Specifies autocomplete hints for the system, so it can provide autofill */
autoComplete?: AutoCompleteVariant;

/** Forwarded inner ref */
innerRef?: ForwardedRef<ValidateCodeFormHandle>;

/**Whether we are validating the action taken to add the magic code */

Check failure on line 59 in src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.tsx

View workflow job for this annotation

GitHub Actions / Run ESLint

Expected exception block, space or tab after '/**' in comment
getusha marked this conversation as resolved.
Show resolved Hide resolved
isValidatingAction?: boolean;

/**The contact that's going to be added after the validation */

Check failure on line 62 in src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.tsx

View workflow job for this annotation

GitHub Actions / Run ESLint

Expected exception block, space or tab after '/**' in comment
getusha marked this conversation as resolved.
Show resolved Hide resolved
pendingContact?: PendingContactAction;
};

type BaseValidateCodeFormProps = BaseValidateCodeFormOnyxProps & ValidateCodeFormProps;

function BaseValidateCodeForm({account = {}, contactMethod, hasMagicCodeBeenSent, loginList, autoComplete = 'one-time-code', innerRef = () => {}}: BaseValidateCodeFormProps) {
function BaseValidateCodeForm({
account = {},
contactMethod,
hasMagicCodeBeenSent,
loginList,
autoComplete = 'one-time-code',
innerRef = () => {},
isValidatingAction = false,
pendingContact,
}: BaseValidateCodeFormProps) {
const {translate} = useLocalize();
const {isOffline} = useNetwork();
const theme = useTheme();
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const [formError, setFormError] = useState<ValidateCodeFormError>({});
const [validateCode, setValidateCode] = useState('');
const loginData = loginList[contactMethod];
const loginData = loginList?.[pendingContact?.contactMethod ?? contactMethod];
const inputValidateCodeRef = useRef<MagicCodeInputHandle>(null);
const validateLoginError = ErrorUtils.getEarliestErrorField(loginData, 'validateLogin');
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- nullish coalescing doesn't achieve the same result in this case
Expand Down Expand Up @@ -166,8 +181,14 @@
}

setFormError({});

if (!!pendingContact?.contactMethod && isValidatingAction) {
User.addNewContactMethodAndNavigate(pendingContact?.contactMethod, validateCode);
return;
}

User.validateSecondaryLogin(loginList, contactMethod, validateCode);
}, [loginList, validateCode, contactMethod]);

Check warning on line 191 in src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.tsx

View workflow job for this annotation

GitHub Actions / Run ESLint

React Hook useCallback has missing dependencies: 'isValidatingAction' and 'pendingContact?.contactMethod'. Either include them or remove the dependency array

return (
<>
Expand All @@ -183,8 +204,8 @@
autoFocus={false}
/>
<OfflineWithFeedback
pendingAction={loginData.pendingFields?.validateCodeSent}
errors={ErrorUtils.getLatestErrorField(loginData, 'validateCodeSent')}
pendingAction={pendingContact?.pendingFields?.validateCodeSent ?? loginData?.pendingFields?.validateCodeSent}
errors={ErrorUtils.getLatestErrorField(pendingContact ?? loginData, pendingContact ? 'actionVerified' : 'validateCodeSent')}
errorRowStyles={[styles.mt2]}
onClose={() => User.clearContactMethodErrors(contactMethod, 'validateCodeSent')}
>
Expand All @@ -201,18 +222,18 @@
>
<Text style={[StyleUtils.getDisabledLinkStyles(shouldDisableResendValidateCode)]}>{translate('validateCodeForm.magicCodeNotReceived')}</Text>
</PressableWithFeedback>
{hasMagicCodeBeenSent && (
{(hasMagicCodeBeenSent || !!pendingContact?.validateCodeSent) && (

Check failure on line 225 in src/pages/settings/Profile/Contacts/ValidateCodeForm/BaseValidateCodeForm.tsx

View workflow job for this annotation

GitHub Actions / Run ESLint

Prefer using nullish coalescing operator (`??`) instead of a logical or (`||`), as it is a safer operator
<DotIndicatorMessage
type="success"
style={[styles.mt6, styles.flex0]}
// eslint-disable-next-line @typescript-eslint/naming-convention
messages={{0: translate('resendValidationForm.linkHasBeenResent')}}
messages={{0: pendingContact?.validateCodeSent ? translate('validateCodeModal.successfulNewCodeRequest') : translate('resendValidationForm.linkHasBeenResent')}}
/>
)}
</View>
</OfflineWithFeedback>
<OfflineWithFeedback
pendingAction={loginData.pendingFields?.validateLogin}
pendingAction={loginData?.pendingFields?.validateLogin}
errors={validateLoginError}
errorRowStyles={[styles.mt2]}
onClose={() => User.clearContactMethodErrors(contactMethod, 'validateLogin')}
Expand Down
Loading
Loading