Skip to content

Commit

Permalink
Username onboarding
Browse files Browse the repository at this point in the history
  • Loading branch information
indutny-signal committed Feb 13, 2023
1 parent 5626cea commit f9aaf30
Show file tree
Hide file tree
Showing 17 changed files with 309 additions and 3 deletions.
24 changes: 24 additions & 0 deletions _locales/en/messages.json
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions images/icons/v2/link_color_32.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions images/icons/v2/lock_color_32.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions images/icons/v2/number_color_32.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions protos/SignalStorage.proto
Expand Up @@ -176,6 +176,7 @@ message AccountRecord {
reserved 31; // hasReadOnboardingStory
reserved 32; // hasSeenGroupStoryEducationSheet
optional string username = 33;
optional bool hasCompletedUsernameOnboarding = 34;
}

message StoryDistributionListRecord {
Expand Down
107 changes: 107 additions & 0 deletions stylesheets/components/UsernameOnboardingModalBody.scss
@@ -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;
}
}
1 change: 1 addition & 0 deletions stylesheets/manifest.scss
Expand Up @@ -134,4 +134,5 @@
@import './components/TimelineWarnings.scss';
@import './components/TitleBarContainer.scss';
@import './components/Toast.scss';
@import './components/UsernameOnboardingModalBody.scss';
@import './components/WhatsNew.scss';
1 change: 1 addition & 0 deletions ts/components/ProfileEditor.stories.tsx
Expand Up @@ -71,6 +71,7 @@ export default {
},
replaceAvatar: { action: true },
saveAvatarToDisk: { action: true },
markCompletedUsernameOnboarding: { action: true },
openUsernameReservationModal: { action: true },
setUsernameEditState: { action: true },
deleteUsername: { action: true },
Expand Down
22 changes: 21 additions & 1 deletion ts/components/ProfileEditor.tsx
Expand Up @@ -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,
Expand All @@ -48,6 +49,7 @@ export enum EditState {
ProfileName = 'ProfileName',
Bio = 'Bio',
Username = 'Username',
UsernameOnboarding = 'UsernameOnboarding',
}

type PropsExternalType = {
Expand All @@ -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 = {
Expand Down Expand Up @@ -124,8 +128,10 @@ export function ProfileEditor({
deleteUsername,
familyName,
firstName,
hasCompletedUsernameOnboarding,
i18n,
isUsernameFlagEnabled,
markCompletedUsernameOnboarding,
onEditStateChanged,
onProfileChanged,
onSetSkinTone,
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -560,7 +576,11 @@ export function ProfileEditor({
info={username && generateUsernameLink(username, { short: true })}
onClick={() => {
openUsernameReservationModal();
setEditState(EditState.Username);
if (username || hasCompletedUsernameOnboarding) {
setEditState(EditState.Username);
} else {
setEditState(EditState.UsernameOnboarding);
}
}}
actions={actions}
/>
Expand Down
3 changes: 2 additions & 1 deletion ts/components/ProfileEditorModal.tsx
Expand Up @@ -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'),
};

Expand Down
37 changes: 37 additions & 0 deletions ts/components/UsernameOnboardingModalBody.stories.tsx
@@ -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 changes: 68 additions & 0 deletions ts/components/UsernameOnboardingModalBody.tsx
@@ -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>
);
}

0 comments on commit f9aaf30

Please sign in to comment.