Username onboarding
This commit is contained in:
parent
5626cea9c3
commit
f9aaf30a32
17 changed files with 309 additions and 3 deletions
|
@ -6383,6 +6383,30 @@
|
|||
"message": "These digits help keep your username private so you can avoid unwanted messages. Share your username with only the people and groups you’d like to chat with. If you change usernames you’ll get a new set of digits.",
|
||||
"description": "Body of the popup with information about discriminator in username"
|
||||
},
|
||||
"icu:UsernameOnboardingModalBody__title": {
|
||||
"messageformat": "Set up your Signal username",
|
||||
"description": "Title of username onboarding modal"
|
||||
},
|
||||
"icu:UsernameOnboardingModalBody__row__number": {
|
||||
"messageformat": "Usernames are paired with a set of digits and aren’t shared on your profile",
|
||||
"description": "Content of the first row of username onboarding modal"
|
||||
},
|
||||
"icu:UsernameOnboardingModalBody__row__link": {
|
||||
"messageformat": "Each username has a unique link you can share with your friends to start a chat with you",
|
||||
"description": "Content of the second row of username onboarding modal"
|
||||
},
|
||||
"icu:UsernameOnboardingModalBody__row__lock": {
|
||||
"messageformat": "Turn off phone number discovery under Settings > Phone Number > Who can find my number, to use your username as the primary way others can contact you.",
|
||||
"description": "Content of the third row of username onboarding modal"
|
||||
},
|
||||
"icu:UsernameOnboardingModalBody__learn-more": {
|
||||
"messageformat": "Learn More",
|
||||
"description": "Text that open a popup with information about username onboarding"
|
||||
},
|
||||
"icu:UsernameOnboardingModalBody__continue": {
|
||||
"messageformat": "Continue",
|
||||
"description": "Text of the primary button on username onboarding modal"
|
||||
},
|
||||
"icu:UnsupportedOSWarningDialog__body": {
|
||||
"messageformat": "Signal desktop will no longer support your computer’s version of {OS} soon. To keep using Signal, update your computer’s operating system by {expirationDate}. <learnMoreLink>Learn more</learnMoreLink>",
|
||||
"description": "Body of a dialog displayed on unsupported operating systems"
|
||||
|
|
1
images/icons/v2/link_color_32.svg
Normal file
1
images/icons/v2/link_color_32.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none"><path fill="#fff" fill-rule="evenodd" d="M20.648 18.834a.945.945 0 0 1 0-1.31l2.976-3.06c1.761-1.81 1.761-4.744 0-6.554a4.424 4.424 0 0 0-6.377 0l-2.976 3.059a.885.885 0 0 1-1.276 0 .945.945 0 0 1 0-1.311l2.976-3.06a6.194 6.194 0 0 1 8.929 0c2.465 2.535 2.465 6.643 0 9.178l-2.976 3.058a.885.885 0 0 1-1.276 0ZM10.02 14.028a.945.945 0 0 1 0 1.311l-2.976 3.059c-1.761 1.81-1.761 4.744 0 6.555a4.424 4.424 0 0 0 6.378 0l2.976-3.06a.885.885 0 0 1 1.275 0 .945.945 0 0 1 0 1.312l-2.976 3.059a6.193 6.193 0 0 1-8.928 0c-2.466-2.534-2.466-6.643 0-9.177l2.976-3.059a.885.885 0 0 1 1.275 0Zm8.504.437a.945.945 0 0 0 0-1.31.885.885 0 0 0-1.276 0l-5.102 5.243a.945.945 0 0 0 0 1.311.885.885 0 0 0 1.276 0l5.102-5.244Z" clip-rule="evenodd"/><path fill="#647392" fill-rule="evenodd" d="M19.573 19.88a2.445 2.445 0 0 1 0-3.403l2.976-3.059c1.195-1.227 1.195-3.234 0-4.462a2.924 2.924 0 0 0-4.227 0l-2.976 3.059a2.385 2.385 0 0 1-3.426 0 2.445 2.445 0 0 1 0-3.403l2.976-3.06L15.971 6.6l-2.976 3.059a.945.945 0 0 0 0 1.31.885.885 0 0 0 1.276 0l2.976-3.058a4.424 4.424 0 0 1 6.377 0c1.761 1.81 1.761 4.745 0 6.554l-2.976 3.06a.945.945 0 0 0 0 1.31.885.885 0 0 0 1.276 0l2.976-3.059c2.465-2.534 2.465-6.642 0-9.176a6.194 6.194 0 0 0-8.929 0l-1.075-1.046a7.693 7.693 0 0 1 11.079 0c3.032 3.116 3.032 8.152 0 11.269l-2.976 3.058a2.385 2.385 0 0 1-3.426 0Zm-1.765 1.882.94-.914a2.445 2.445 0 0 1 0 3.403l-2.977 3.058a7.693 7.693 0 0 1-11.078 0c-3.033-3.116-3.033-8.152 0-11.268l2.976-3.059a2.385 2.385 0 0 1 3.425 0 2.445 2.445 0 0 1 0 3.403l-2.976 3.059c-1.194 1.228-1.194 3.235 0 4.463a2.924 2.924 0 0 0 4.228 0l2.976-3.06a2.385 2.385 0 0 1 3.425 0l-.939.915Zm1.79-9.654a2.445 2.445 0 0 1 0 3.403l-5.102 5.244a2.385 2.385 0 0 1-3.426 0 2.445 2.445 0 0 1 0-3.403l5.102-5.244a2.385 2.385 0 0 1 3.426 0Zm-9.579 3.231a.945.945 0 0 0 0-1.31.885.885 0 0 0-1.275 0l-2.976 3.058c-2.466 2.534-2.466 6.643 0 9.177a6.193 6.193 0 0 0 8.928 0l2.976-3.06a.945.945 0 0 0 0-1.31.885.885 0 0 0-1.275 0l-2.976 3.059a4.424 4.424 0 0 1-6.378 0c-1.761-1.81-1.761-4.745 0-6.555l2.976-3.059Zm8.504-2.185a.945.945 0 0 1 0 1.311L13.42 19.71a.885.885 0 0 1-1.276 0 .945.945 0 0 1 0-1.31l5.102-5.245a.885.885 0 0 1 1.276 0Z" clip-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 2.2 KiB |
1
images/icons/v2/lock_color_32.svg
Normal file
1
images/icons/v2/lock_color_32.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none"><path fill="#FFE3A5" d="M8 8.667a8 8 0 1 1 16 0v4.252c-.44-.22-.93-.32-1.525-.37a22.132 22.132 0 0 0-1.675-.048V8.8a4.8 4.8 0 1 0-9.6 0v3.7a22.12 22.12 0 0 0-1.675.048c-.595.05-1.084.15-1.525.37V8.666ZM8.658 14.266c.2-.102.476-.18.99-.222.526-.043 1.204-.044 2.192-.044h8.32c.988 0 1.666 0 2.192.044.514.042.79.12.99.222.456.235.828.609 1.06 1.069.102.2.179.479.22.996.044.53.045 1.213.045 2.209v5.587c0 .995-.001 1.678-.044 2.208-.042.518-.12.796-.22.997-.233.46-.605.834-1.061 1.068-.2.103-.476.18-.99.223-.526.043-1.204.044-2.192.044h-8.32c-.988 0-1.666-.001-2.192-.044-.514-.043-.79-.12-.99-.223a2.436 2.436 0 0 1-1.06-1.068c-.102-.201-.179-.48-.22-.997-.044-.53-.045-1.213-.045-2.208V18.54c0-.995.001-1.679.044-2.209.042-.517.119-.795.22-.996.233-.46.605-.834 1.061-1.069Z"/><path fill="#B3841C" fill-rule="evenodd" d="M16 .667a8 8 0 0 0-8 8v4.251c.435-.217.916-.317 1.5-.367V8.667a6.5 6.5 0 1 1 13 0v3.884c.584.05 1.065.15 1.5.367V8.668a8 8 0 0 0-8-8ZM20.8 12.5V8.8a4.8 4.8 0 1 0-9.6 0v3.7l.618-.001h.882V8.8a3.3 3.3 0 0 1 6.6 0v3.7h1.5Z" clip-rule="evenodd"/><path fill="#B2841C" fill-rule="evenodd" d="M11.818 12.5c-.952 0-1.696 0-2.293.05-.607.05-1.104.152-1.552.382-.74.38-1.339.984-1.714 1.726-.226.447-.328.943-.377 1.552-.049.6-.049 1.349-.049 2.31v5.628c0 .96 0 1.708.049 2.308.05.61.151 1.106.377 1.553a3.935 3.935 0 0 0 1.714 1.726c.448.23.945.333 1.552.383.597.049 1.341.049 2.293.049h8.364c.952 0 1.696 0 2.293-.05.607-.05 1.104-.152 1.552-.382.74-.38 1.339-.985 1.714-1.726.226-.447.328-.944.377-1.553.049-.6.049-1.348.049-2.308v-5.629c0-.96 0-1.708-.049-2.309-.05-.609-.151-1.105-.377-1.552a3.936 3.936 0 0 0-1.714-1.726c-.448-.23-.945-.333-1.552-.383-.597-.049-1.341-.049-2.293-.049h-8.364Zm-2.17 1.544c-.514.042-.79.12-.99.222a2.436 2.436 0 0 0-1.06 1.069c-.102.2-.179.479-.22.996-.044.53-.045 1.213-.045 2.209v5.587c0 .995.001 1.678.044 2.208.042.518.119.796.22.997.233.46.605.834 1.061 1.068.2.103.476.18.99.223.526.043 1.204.044 2.192.044h8.32c.988 0 1.666-.001 2.192-.044.514-.043.79-.12.99-.223a2.436 2.436 0 0 0 1.06-1.068c.102-.201.179-.48.22-.997.044-.53.045-1.213.045-2.208V18.54c0-.995-.001-1.679-.044-2.209-.042-.517-.12-.795-.22-.996a2.436 2.436 0 0 0-1.061-1.069c-.2-.102-.476-.18-.99-.222-.526-.043-1.204-.044-2.192-.044h-8.32c-.988 0-1.666 0-2.192.044Z" clip-rule="evenodd"/><path fill="#B2841C" d="M15 21.066a2 2 0 1 1 2 0v2.1a1 1 0 1 1-2 0v-2.1Z"/></svg>
|
After Width: | Height: | Size: 2.4 KiB |
1
images/icons/v2/number_color_32.svg
Normal file
1
images/icons/v2/number_color_32.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none"><path fill="#CCD1FF" fill-rule="evenodd" d="M23.806 5.583a1.167 1.167 0 0 0-2.28-.499l-1.11 5.083h-6.279l1.003-4.584a1.167 1.167 0 1 0-2.28-.499l-1.111 5.083H7.332a1.167 1.167 0 1 0 0 2.333h3.905l-1.531 7H5.333a1.167 1.167 0 1 0 0 2.333h3.863l-1.002 4.584a1.167 1.167 0 0 0 2.28.499l1.11-5.083h6.279l-1.003 4.584a1.167 1.167 0 1 0 2.28.499l1.111-5.083h4.416a1.167 1.167 0 1 0 0-2.333h-3.905l1.531-7h4.374a1.167 1.167 0 0 0 0-2.333h-3.863l1.002-4.584ZM19.905 12.5h-6.278l-1.532 7h6.278l1.532-7Z" clip-rule="evenodd"/><path fill="#6771CC" fill-rule="evenodd" d="M8.763 29.272a2.667 2.667 0 0 1-2.035-3.175l.605-2.764h-2a2.667 2.667 0 1 1 0-5.333H8.5l.875-4h-2.04a2.667 2.667 0 0 1 0-5.333h3.207l.854-3.904a2.667 2.667 0 1 1 5.21 1.14l-.604 2.764h3.207l.854-3.904a2.667 2.667 0 1 1 5.21 1.14l-.605 2.764h2a2.667 2.667 0 0 1 0 5.333H23.5l-.875 4h2.042a2.667 2.667 0 0 1 0 5.333h-3.208l-.854 3.904a2.667 2.667 0 1 1-5.21-1.14l.604-2.764h-3.207l-.854 3.904a2.667 2.667 0 0 1-3.175 2.035Zm-.57-2.855a1.167 1.167 0 0 0 2.28.499l1.112-5.083h6.278l-1.003 4.584a1.167 1.167 0 1 0 2.28.499l1.111-5.083h4.416a1.167 1.167 0 1 0 0-2.333h-3.905l1.531-7h4.374a1.167 1.167 0 0 0 0-2.333h-3.863l1.002-4.584a1.167 1.167 0 0 0-2.28-.499l-1.11 5.083h-6.279l1.003-4.584a1.167 1.167 0 1 0-2.28-.499l-1.111 5.083H7.332a1.167 1.167 0 1 0 0 2.333h3.905l-1.531 7H5.333a1.167 1.167 0 1 0 0 2.333h3.863l-1.002 4.584ZM14.834 14l-.874 4h3.207l.875-4h-3.207Zm-1.206-1.5-1.532 7h6.278l1.532-7h-6.278Z" clip-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -176,6 +176,7 @@ message AccountRecord {
|
|||
reserved 31; // hasReadOnboardingStory
|
||||
reserved 32; // hasSeenGroupStoryEducationSheet
|
||||
optional string username = 33;
|
||||
optional bool hasCompletedUsernameOnboarding = 34;
|
||||
}
|
||||
|
||||
message StoryDistributionListRecord {
|
||||
|
|
107
stylesheets/components/UsernameOnboardingModalBody.scss
Normal file
107
stylesheets/components/UsernameOnboardingModalBody.scss
Normal file
|
@ -0,0 +1,107 @@
|
|||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
.UsernameOnboardingModalBody {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
user-select: none;
|
||||
|
||||
&__large-at {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 24px;
|
||||
|
||||
margin-bottom: 12px;
|
||||
|
||||
@include light-theme {
|
||||
background-color: $color-gray-04;
|
||||
}
|
||||
|
||||
@include dark-theme {
|
||||
background-color: $color-gray-65;
|
||||
}
|
||||
|
||||
&::after {
|
||||
display: block;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
-webkit-mask-size: 100%;
|
||||
content: '';
|
||||
|
||||
@include light-theme {
|
||||
background-color: $color-gray-75;
|
||||
}
|
||||
|
||||
@include dark-theme {
|
||||
background-color: $color-gray-15;
|
||||
}
|
||||
|
||||
-webkit-mask: url(../images/icons/v2/at-24.svg) no-repeat center;
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
@include font-title-2;
|
||||
margin-bottom: 20px;
|
||||
max-width: 240px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__row {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
|
||||
&__icon {
|
||||
flex-shrink: 0;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
||||
&--number {
|
||||
background: url(../images/icons/v2/number_color_32.svg);
|
||||
}
|
||||
|
||||
&--link {
|
||||
background: url(../images/icons/v2/link_color_32.svg);
|
||||
}
|
||||
|
||||
&--lock {
|
||||
background: url(../images/icons/v2/lock_color_32.svg);
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
@include font-body-2;
|
||||
|
||||
@include light-theme {
|
||||
color: $color-gray-60;
|
||||
}
|
||||
|
||||
@include dark-theme {
|
||||
color: $color-gray-25;
|
||||
}
|
||||
|
||||
max-width: 248px;
|
||||
}
|
||||
|
||||
&--center {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
&__learn-more {
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&__submit {
|
||||
width: 100%;
|
||||
max-width: 296px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
|
@ -134,4 +134,5 @@
|
|||
@import './components/TimelineWarnings.scss';
|
||||
@import './components/TitleBarContainer.scss';
|
||||
@import './components/Toast.scss';
|
||||
@import './components/UsernameOnboardingModalBody.scss';
|
||||
@import './components/WhatsNew.scss';
|
||||
|
|
|
@ -71,6 +71,7 @@ export default {
|
|||
},
|
||||
replaceAvatar: { action: true },
|
||||
saveAvatarToDisk: { action: true },
|
||||
markCompletedUsernameOnboarding: { action: true },
|
||||
openUsernameReservationModal: { action: true },
|
||||
setUsernameEditState: { action: true },
|
||||
deleteUsername: { action: true },
|
||||
|
|
|
@ -34,6 +34,7 @@ import { assertDev } from '../util/assert';
|
|||
import { missingCaseError } from '../util/missingCaseError';
|
||||
import { ConfirmationDialog } from './ConfirmationDialog';
|
||||
import { ContextMenu } from './ContextMenu';
|
||||
import { UsernameOnboardingModalBody } from './UsernameOnboardingModalBody';
|
||||
import {
|
||||
ConversationDetailsIcon,
|
||||
IconType,
|
||||
|
@ -48,6 +49,7 @@ export enum EditState {
|
|||
ProfileName = 'ProfileName',
|
||||
Bio = 'Bio',
|
||||
Username = 'Username',
|
||||
UsernameOnboarding = 'UsernameOnboarding',
|
||||
}
|
||||
|
||||
type PropsExternalType = {
|
||||
|
@ -67,11 +69,13 @@ export type PropsDataType = {
|
|||
conversationId: string;
|
||||
familyName?: string;
|
||||
firstName: string;
|
||||
hasCompletedUsernameOnboarding: boolean;
|
||||
i18n: LocalizerType;
|
||||
isUsernameFlagEnabled: boolean;
|
||||
userAvatarData: ReadonlyArray<AvatarDataType>;
|
||||
username?: string;
|
||||
usernameEditState: UsernameEditState;
|
||||
markCompletedUsernameOnboarding: () => void;
|
||||
} & Pick<EmojiButtonProps, 'recentEmojis' | 'skinTone'>;
|
||||
|
||||
type PropsActionType = {
|
||||
|
@ -124,8 +128,10 @@ export function ProfileEditor({
|
|||
deleteUsername,
|
||||
familyName,
|
||||
firstName,
|
||||
hasCompletedUsernameOnboarding,
|
||||
i18n,
|
||||
isUsernameFlagEnabled,
|
||||
markCompletedUsernameOnboarding,
|
||||
onEditStateChanged,
|
||||
onProfileChanged,
|
||||
onSetSkinTone,
|
||||
|
@ -481,6 +487,16 @@ export function ProfileEditor({
|
|||
content = renderEditUsernameModalBody({
|
||||
onClose: () => setEditState(EditState.None),
|
||||
});
|
||||
} else if (editState === EditState.UsernameOnboarding) {
|
||||
content = (
|
||||
<UsernameOnboardingModalBody
|
||||
i18n={i18n}
|
||||
onNext={() => {
|
||||
markCompletedUsernameOnboarding();
|
||||
setEditState(EditState.Username);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else if (editState === EditState.None) {
|
||||
let maybeUsernameRow: JSX.Element | undefined;
|
||||
if (isUsernameFlagEnabled) {
|
||||
|
@ -560,7 +576,11 @@ export function ProfileEditor({
|
|||
info={username && generateUsernameLink(username, { short: true })}
|
||||
onClick={() => {
|
||||
openUsernameReservationModal();
|
||||
if (username || hasCompletedUsernameOnboarding) {
|
||||
setEditState(EditState.Username);
|
||||
} else {
|
||||
setEditState(EditState.UsernameOnboarding);
|
||||
}
|
||||
}}
|
||||
actions={actions}
|
||||
/>
|
||||
|
|
|
@ -32,11 +32,12 @@ export function ProfileEditorModal({
|
|||
toggleProfileEditorHasError,
|
||||
...restProps
|
||||
}: PropsType): JSX.Element {
|
||||
const MODAL_TITLES_BY_EDIT_STATE: Record<EditState, string> = {
|
||||
const MODAL_TITLES_BY_EDIT_STATE: Record<EditState, string | undefined> = {
|
||||
[EditState.BetterAvatar]: i18n('ProfileEditorModal--avatar'),
|
||||
[EditState.Bio]: i18n('ProfileEditorModal--about'),
|
||||
[EditState.None]: i18n('ProfileEditorModal--profile'),
|
||||
[EditState.ProfileName]: i18n('ProfileEditorModal--name'),
|
||||
[EditState.UsernameOnboarding]: undefined,
|
||||
[EditState.Username]: i18n('ProfileEditorModal--username'),
|
||||
};
|
||||
|
||||
|
|
37
ts/components/UsernameOnboardingModalBody.stories.tsx
Normal file
37
ts/components/UsernameOnboardingModalBody.stories.tsx
Normal file
|
@ -0,0 +1,37 @@
|
|||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
import type { Meta, Story } from '@storybook/react';
|
||||
|
||||
import enMessages from '../../_locales/en/messages.json';
|
||||
import { setupI18n } from '../util/setupI18n';
|
||||
|
||||
import type { PropsType } from './UsernameOnboardingModalBody';
|
||||
import { UsernameOnboardingModalBody } from './UsernameOnboardingModalBody';
|
||||
|
||||
const i18n = setupI18n('en', enMessages);
|
||||
|
||||
export default {
|
||||
component: UsernameOnboardingModalBody,
|
||||
title: 'Components/UsernameOnboardingModalBody',
|
||||
argTypes: {
|
||||
i18n: {
|
||||
defaultValue: i18n,
|
||||
},
|
||||
onNext: { action: true },
|
||||
},
|
||||
} as Meta;
|
||||
|
||||
type ArgsType = PropsType;
|
||||
|
||||
// eslint-disable-next-line react/function-component-definition
|
||||
const Template: Story<ArgsType> = args => {
|
||||
return <UsernameOnboardingModalBody {...args} />;
|
||||
};
|
||||
|
||||
export const Normal = Template.bind({});
|
||||
Normal.args = {};
|
||||
Normal.story = {
|
||||
name: 'normal',
|
||||
};
|
68
ts/components/UsernameOnboardingModalBody.tsx
Normal file
68
ts/components/UsernameOnboardingModalBody.tsx
Normal file
|
@ -0,0 +1,68 @@
|
|||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import type { LocalizerType } from '../types/Util';
|
||||
import { Button } from './Button';
|
||||
|
||||
export type PropsType = Readonly<{
|
||||
i18n: LocalizerType;
|
||||
onNext: () => void;
|
||||
}>;
|
||||
|
||||
const CLASS = 'UsernameOnboardingModalBody';
|
||||
|
||||
const SUPPORT_URL = 'https://support.signal.org/hc/articles/5389476324250';
|
||||
|
||||
export function UsernameOnboardingModalBody({
|
||||
i18n,
|
||||
onNext,
|
||||
}: PropsType): JSX.Element {
|
||||
return (
|
||||
<div className={CLASS}>
|
||||
<div className={`${CLASS}__large-at`} />
|
||||
|
||||
<div className={`${CLASS}__title`}>{i18n(`icu:${CLASS}__title`)}</div>
|
||||
|
||||
<div className={`${CLASS}__row`}>
|
||||
<div className={`${CLASS}__row__icon ${CLASS}__row__icon--number`} />
|
||||
|
||||
<div className={`${CLASS}__row__body`}>
|
||||
{i18n(`icu:${CLASS}__row__number`)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={`${CLASS}__row`}>
|
||||
<div className={`${CLASS}__row__icon ${CLASS}__row__icon--link`} />
|
||||
|
||||
<div className={`${CLASS}__row__body`}>
|
||||
{i18n(`icu:${CLASS}__row__link`)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={`${CLASS}__row`}>
|
||||
<div className={`${CLASS}__row__icon ${CLASS}__row__icon--lock`} />
|
||||
|
||||
<div className={`${CLASS}__row__body`}>
|
||||
{i18n(`icu:${CLASS}__row__lock`)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={`${CLASS}__row ${CLASS}__row--center`}>
|
||||
<a
|
||||
className={`${CLASS}__learn-more`}
|
||||
href={SUPPORT_URL}
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
{i18n(`icu:${CLASS}__learn-more`)}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<Button className={`${CLASS}__submit`} onClick={onNext}>
|
||||
{i18n(`icu:${CLASS}__continue`)}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -395,6 +395,14 @@ export function toAccountRecord(
|
|||
accountRecord.hasViewedOnboardingStory = hasViewedOnboardingStory;
|
||||
}
|
||||
|
||||
const hasCompletedUsernameOnboarding = window.storage.get(
|
||||
'hasCompletedUsernameOnboarding'
|
||||
);
|
||||
if (hasCompletedUsernameOnboarding !== undefined) {
|
||||
accountRecord.hasCompletedUsernameOnboarding =
|
||||
hasCompletedUsernameOnboarding;
|
||||
}
|
||||
|
||||
const hasStoriesDisabled = window.storage.get('hasStoriesDisabled');
|
||||
accountRecord.storiesDisabled = hasStoriesDisabled === true;
|
||||
|
||||
|
@ -1137,6 +1145,7 @@ export async function mergeAccountRecord(
|
|||
subscriberCurrencyCode,
|
||||
displayBadgesOnProfile,
|
||||
keepMutedChatsArchived,
|
||||
hasCompletedUsernameOnboarding,
|
||||
hasSetMyStoriesPrivacy,
|
||||
hasViewedOnboardingStory,
|
||||
storiesDisabled,
|
||||
|
@ -1368,6 +1377,15 @@ export async function mergeAccountRecord(
|
|||
void findAndDeleteOnboardingStoryIfExists();
|
||||
}
|
||||
}
|
||||
{
|
||||
const hasCompletedUsernameOnboardingBool = Boolean(
|
||||
hasCompletedUsernameOnboarding
|
||||
);
|
||||
await window.storage.put(
|
||||
'hasCompletedUsernameOnboarding',
|
||||
hasCompletedUsernameOnboardingBool
|
||||
);
|
||||
}
|
||||
{
|
||||
const hasStoriesDisabled = Boolean(storiesDisabled);
|
||||
await window.storage.put('hasStoriesDisabled', hasStoriesDisabled);
|
||||
|
|
|
@ -74,6 +74,12 @@ export const getUsernamesEnabled = createSelector(
|
|||
isRemoteConfigFlagEnabled(remoteConfig, 'desktop.usernames')
|
||||
);
|
||||
|
||||
export const getHasCompletedUsernameOnboarding = createSelector(
|
||||
getItems,
|
||||
(state: ItemsStateType): boolean =>
|
||||
Boolean(state.hasCompletedUsernameOnboarding)
|
||||
);
|
||||
|
||||
export const isInternalUser = createSelector(
|
||||
getRemoteConfig,
|
||||
(remoteConfig: ConfigMapType): boolean => {
|
||||
|
|
|
@ -8,10 +8,15 @@ import { mapDispatchToProps } from '../actions';
|
|||
import type { PropsDataType as ProfileEditorModalPropsType } from '../../components/ProfileEditorModal';
|
||||
import { ProfileEditorModal } from '../../components/ProfileEditorModal';
|
||||
import type { PropsDataType } from '../../components/ProfileEditor';
|
||||
import { storageServiceUploadJob } from '../../services/storage';
|
||||
import { SmartEditUsernameModalBody } from './EditUsernameModalBody';
|
||||
import type { StateType } from '../reducer';
|
||||
import { getIntl } from '../selectors/user';
|
||||
import { getEmojiSkinTone, getUsernamesEnabled } from '../selectors/items';
|
||||
import {
|
||||
getEmojiSkinTone,
|
||||
getUsernamesEnabled,
|
||||
getHasCompletedUsernameOnboarding,
|
||||
} from '../selectors/items';
|
||||
import { getMe } from '../selectors/conversations';
|
||||
import { selectRecentEmojis } from '../selectors/emojis';
|
||||
import { getUsernameEditState } from '../selectors/username';
|
||||
|
@ -22,6 +27,12 @@ function renderEditUsernameModalBody(props: {
|
|||
return <SmartEditUsernameModalBody {...props} />;
|
||||
}
|
||||
|
||||
async function markCompletedUsernameOnboarding(): Promise<void> {
|
||||
await window.storage.put('hasCompletedUsernameOnboarding', true);
|
||||
|
||||
storageServiceUploadJob();
|
||||
}
|
||||
|
||||
function mapStateToProps(
|
||||
state: StateType
|
||||
): Omit<PropsDataType, 'onEditStateChange' | 'onProfileChanged'> &
|
||||
|
@ -40,6 +51,8 @@ function mapStateToProps(
|
|||
const recentEmojis = selectRecentEmojis(state);
|
||||
const skinTone = getEmojiSkinTone(state);
|
||||
const isUsernameFlagEnabled = getUsernamesEnabled(state);
|
||||
const hasCompletedUsernameOnboarding =
|
||||
getHasCompletedUsernameOnboarding(state);
|
||||
const usernameEditState = getUsernameEditState(state);
|
||||
|
||||
return {
|
||||
|
@ -50,9 +63,11 @@ function mapStateToProps(
|
|||
conversationId,
|
||||
familyName,
|
||||
firstName: String(firstName),
|
||||
hasCompletedUsernameOnboarding,
|
||||
hasError: state.globalModals.profileEditorHasError,
|
||||
i18n: getIntl(state),
|
||||
isUsernameFlagEnabled,
|
||||
markCompletedUsernameOnboarding,
|
||||
recentEmojis,
|
||||
skinTone,
|
||||
userAvatarData,
|
||||
|
|
|
@ -154,6 +154,9 @@ describe('pnp/username', function needsName() {
|
|||
const profileEditor = window.locator('.ProfileEditor');
|
||||
await profileEditor.locator('.ProfileEditor__row >> "Username"').click();
|
||||
|
||||
debug('skipping onboarding');
|
||||
await profileEditor.locator('.module-Button >> "Continue"').click();
|
||||
|
||||
debug('entering new username');
|
||||
const usernameField = profileEditor.locator('.Input__input');
|
||||
await usernameField.type(NICKNAME);
|
||||
|
|
1
ts/types/Storage.d.ts
vendored
1
ts/types/Storage.d.ts
vendored
|
@ -71,6 +71,7 @@ export type StorageAccessType = {
|
|||
existingOnboardingStoryMessageIds: ReadonlyArray<string> | undefined;
|
||||
hasRegisterSupportForUnauthenticatedDelivery: boolean;
|
||||
hasSetMyStoriesPrivacy: boolean;
|
||||
hasCompletedUsernameOnboarding: boolean;
|
||||
hasViewedOnboardingStory: boolean;
|
||||
hasStoriesDisabled: boolean;
|
||||
storyViewReceiptsEnabled: boolean;
|
||||
|
|
Loading…
Reference in a new issue