Username recovery improvements

This commit is contained in:
Fedor Indutny 2024-02-06 10:35:59 -08:00 committed by GitHub
parent a70ae1060d
commit 533a1b32d4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 423 additions and 99 deletions

View file

@ -8,6 +8,7 @@ import type { UsernameReservationType } from '../../types/Username';
import {
ReserveUsernameError,
ConfirmUsernameResult,
ResetUsernameLinkResult,
} from '../../types/Username';
import * as usernameServices from '../../services/username';
import { storageServiceUploadJob } from '../../services/storage';
@ -33,6 +34,7 @@ import { useBoundActions } from '../../hooks/useBoundActions';
export type UsernameReservationStateType = ReadonlyDeep<{
state: UsernameReservationState;
recoveredUsername?: string;
reservation?: UsernameReservationType;
error?: UsernameReservationError;
abortController?: AbortController;
@ -44,6 +46,7 @@ export type UsernameStateType = ReadonlyDeep<{
// UsernameLinkModalBody
linkState: UsernameLinkState;
linkRecovered: boolean;
// EditUsernameModalBody
usernameReservation: UsernameReservationStateType;
@ -60,6 +63,7 @@ const RESERVE_USERNAME = 'username/RESERVE_USERNAME';
const CONFIRM_USERNAME = 'username/CONFIRM_USERNAME';
const DELETE_USERNAME = 'username/DELETE_USERNAME';
const RESET_USERNAME_LINK = 'username/RESET_USERNAME_LINK';
const CLEAR_USERNAME_LINK_RECOVERED = 'username/CLEAR_USERNAME_LINK_RECOVERED';
type SetUsernameEditStateActionType = ReadonlyDeep<{
type: typeof SET_USERNAME_EDIT_STATE;
@ -83,7 +87,7 @@ type SetUsernameReservationErrorActionType = ReadonlyDeep<{
};
}>;
type ClearUsernameReservation = ReadonlyDeep<{
type ClearUsernameReservationActionType = ReadonlyDeep<{
type: typeof CLEAR_USERNAME_RESERVATION;
}>;
@ -101,19 +105,23 @@ type DeleteUsernameActionType = ReadonlyDeep<
PromiseAction<typeof DELETE_USERNAME, void>
>;
type ResetUsernameLinkActionType = ReadonlyDeep<
PromiseAction<typeof RESET_USERNAME_LINK, void>
PromiseAction<typeof RESET_USERNAME_LINK, ResetUsernameLinkResult>
>;
type ClearUsernameLinkRecoveredActionType = ReadonlyDeep<{
type: typeof CLEAR_USERNAME_LINK_RECOVERED;
}>;
export type UsernameActionType = ReadonlyDeep<
| SetUsernameEditStateActionType
| OpenUsernameReservationModalActionType
| CloseUsernameReservationModalActionType
| SetUsernameReservationErrorActionType
| ClearUsernameReservation
| ClearUsernameReservationActionType
| ReserveUsernameActionType
| ConfirmUsernameActionType
| DeleteUsernameActionType
| ResetUsernameLinkActionType
| ClearUsernameLinkRecoveredActionType
>;
export const actions = {
@ -127,6 +135,7 @@ export const actions = {
deleteUsername,
markCompletedUsernameOnboarding,
resetUsernameLink,
clearUsernameLinkRecovered,
setUsernameLinkColor,
markCompletedUsernameLinkOnboarding,
};
@ -165,7 +174,7 @@ export function setUsernameReservationError(
};
}
export function clearUsernameReservation(): ClearUsernameReservation {
export function clearUsernameReservation(): ClearUsernameReservationActionType {
return {
type: CLEAR_USERNAME_RESERVATION,
};
@ -352,12 +361,19 @@ function setUsernameLinkColor(
};
}
export function clearUsernameLinkRecovered(): ClearUsernameLinkRecoveredActionType {
return {
type: CLEAR_USERNAME_LINK_RECOVERED,
};
}
// Reducers
export function getEmptyState(): UsernameStateType {
return {
editState: UsernameEditState.Editing,
linkState: UsernameLinkState.Ready,
linkRecovered: false,
usernameReservation: {
state: UsernameReservationState.Closed,
},
@ -370,6 +386,17 @@ export function reducer(
): UsernameStateType {
const { usernameReservation } = state;
if (action.type === OPEN_USERNAME_RESERVATION_MODAL) {
return {
...state,
editState: UsernameEditState.Editing,
linkState: UsernameLinkState.Ready,
usernameReservation: {
state: UsernameReservationState.Open,
},
};
}
if (action.type === SET_USERNAME_EDIT_STATE) {
const { editState } = action.payload;
return {
@ -378,15 +405,6 @@ export function reducer(
};
}
if (action.type === OPEN_USERNAME_RESERVATION_MODAL) {
return {
...state,
usernameReservation: {
state: UsernameReservationState.Open,
},
};
}
if (action.type === CLOSE_USERNAME_RESERVATION_MODAL) {
return {
...state,
@ -546,6 +564,20 @@ export function reducer(
},
};
}
if (payload === ConfirmUsernameResult.OkRecovered) {
const { reservation } = state.usernameReservation;
assertDev(
reservation !== undefined,
'Must be reserving before resolving confirmation'
);
return {
...state,
usernameReservation: {
state: UsernameReservationState.Closed,
recoveredUsername: reservation.username,
},
};
}
if (payload === ConfirmUsernameResult.ConflictOrGone) {
return {
...state,
@ -597,13 +629,16 @@ export function reducer(
return {
...state,
linkState: UsernameLinkState.Updating,
linkRecovered: false,
};
}
if (action.type === 'username/RESET_USERNAME_LINK_FULFILLED') {
const { payload } = action;
return {
...state,
linkState: UsernameLinkState.Ready,
linkRecovered: payload === ResetUsernameLinkResult.OkRecovered,
};
}
@ -611,6 +646,14 @@ export function reducer(
return {
...state,
linkState: UsernameLinkState.Error,
linkRecovered: false,
};
}
if (action.type === 'username/CLEAR_USERNAME_LINK_RECOVERED') {
return {
...state,
linkRecovered: false,
};
}

View file

@ -29,6 +29,11 @@ export const getUsernameLinkState = createSelector(
(state: UsernameStateType): UsernameLinkState => state.linkState
);
export const getUsernameLinkRecovered = createSelector(
getUsernameState,
(state: UsernameStateType): boolean => state.linkRecovered
);
export const getUsernameReservation = createSelector(
getUsernameState,
(state: UsernameStateType): UsernameReservationStateType =>
@ -54,3 +59,9 @@ export const getUsernameReservationError = createSelector(
reservation: UsernameReservationStateType
): UsernameReservationError | undefined => reservation.error
);
export const getRecoveredUsername = createSelector(
getUsernameReservation,
(reservation: UsernameReservationStateType): string | undefined =>
reservation.recoveredUsername
);

View file

@ -14,6 +14,7 @@ import {
getUsernameReservationState,
getUsernameReservationObject,
getUsernameReservationError,
getRecoveredUsername,
} from '../selectors/username';
import { getUsernameCorrupted } from '../selectors/items';
import { getMe } from '../selectors/conversations';
@ -25,10 +26,12 @@ function mapStateToProps(state: StateType): PropsDataType {
return {
i18n,
usernameCorrupted,
currentUsername: usernameCorrupted ? undefined : username,
minNickname: getMinNickname(),
maxNickname: getMaxNickname(),
state: getUsernameReservationState(state),
recoveredUsername: getRecoveredUsername(state),
reservation: getUsernameReservationObject(state),
error: getUsernameReservationError(state),
};

View file

@ -25,9 +25,11 @@ import { selectRecentEmojis } from '../selectors/emojis';
import {
getUsernameEditState,
getUsernameLinkState,
getUsernameLinkRecovered,
} from '../selectors/username';
function renderEditUsernameModalBody(props: {
isRootModal: boolean;
onClose: () => void;
}): JSX.Element {
return <SmartEditUsernameModalBody {...props} />;
@ -46,7 +48,6 @@ function mapStateToProps(
firstName,
familyName,
id: conversationId,
phoneNumber,
username,
} = getMe(state);
const recentEmojis = selectRecentEmojis(state);
@ -56,6 +57,7 @@ function mapStateToProps(
getHasCompletedUsernameLinkOnboarding(state);
const usernameEditState = getUsernameEditState(state);
const usernameLinkState = getUsernameLinkState(state);
const usernameLinkRecovered = getUsernameLinkRecovered(state);
const usernameLinkColor = getUsernameLinkColor(state);
const usernameLink = getUsernameLink(state);
const usernameCorrupted = getUsernameCorrupted(state);
@ -76,7 +78,6 @@ function mapStateToProps(
isUsernameFlagEnabled,
recentEmojis,
skinTone,
phoneNumber,
userAvatarData,
username,
usernameCorrupted,
@ -84,6 +85,7 @@ function mapStateToProps(
usernameLinkState,
usernameLinkColor,
usernameLinkCorrupted,
usernameLinkRecovered,
usernameLink,
renderEditUsernameModalBody,