Add "become a sustainer" button and view to badge dialog

This commit is contained in:
Evan Hahn 2021-11-16 10:45:16 -06:00 committed by GitHub
parent a466b939bc
commit 515943c46c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 197 additions and 32 deletions

View File

@ -5296,6 +5296,30 @@
"message": "next",
"description": "Generic next label"
},
"BadgeDialog__become-a-sustainer-button": {
"message": "Become a Sustainer",
"description": "In the badge dialog. This button is shown under sustainer badges, taking users to some instructions"
},
"BadgeSustainerInstructions__header": {
"message": "Become a Sustainer",
"description": "In the instructions for becoming a sustainer. The heading."
},
"BadgeSustainerInstructions__subheader": {
"message": "Signal is powered by people like you. Contribute and receive a badge.",
"description": "In the instructions for becoming a sustainer. The subheading."
},
"BadgeSustainerInstructions__instructions__1": {
"message": "Open Signal on your phone",
"description": "In the instructions for becoming a sustainer. First instruction."
},
"BadgeSustainerInstructions__instructions__2": {
"message": "Tap on your profile photo in the top left to open Settings",
"description": "In the instructions for becoming a sustainer. Second instruction."
},
"BadgeSustainerInstructions__instructions__3": {
"message": "Tap on \"Become a Sustainer\" and subscribe",
"description": "In the instructions for becoming a sustainer. Third instruction."
},
"CompositionArea--expand": {
"message": "Expand",
"description": "Aria label for expanding composition area"

View File

@ -0,0 +1 @@
<svg height="320" viewBox="0 0 292 320" width="292" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="a" gradientTransform="matrix(1 0 0 -1 0 -4326)" gradientUnits="userSpaceOnUse" x1="146" x2="146" y1="-4571.26" y2="-4645.78"><stop offset="0" stop-opacity="0"/><stop offset="1" stop-color="#2e2e2e"/></linearGradient><path d="m292 319.78v-267.29a52 52 0 0 0 -51.87-52.13l-187.44-.58a52 52 0 0 0 -52.19 51.94l-.5 268.06z" fill="#5e5e5e" fill-rule="evenodd"/><path d="m279.37 319.78.68-273.17a34.7 34.7 0 0 0 -34.61-34.78l-26.36-.06-143.51-.35-27.17-.07a34.67 34.67 0 0 0 -34.77 34.65l-.63 273.78z" fill="#121212" fill-rule="evenodd"/><path d="m34.51 115.67a22.33 22.33 0 1 1 22.33 22.33 22.32 22.32 0 0 1 -22.33-22.33zm21.74-40.39a13 13 0 1 0 -13-13 13 13 0 0 0 13 13zm151.3 33.65a2.93 2.93 0 0 0 -3-3h-105.07a3 3 0 1 0 0 5.92h105.17a2.88 2.88 0 0 0 2.9-2.92zm-43.88 13.78a2.93 2.93 0 0 0 -3-3h-61.19a3 3 0 1 0 0 5.92h61.29a2.88 2.88 0 0 0 2.9-2.92zm-106.83 77.54a22.3 22.3 0 1 0 -22.33-22.25 22.3 22.3 0 0 0 22.33 22.25zm150.71-29a2.93 2.93 0 0 0 -3-3h-105.07a3 3 0 1 0 0 5.92h105.17a2.88 2.88 0 0 0 2.9-2.96zm-43.88 13.84a2.93 2.93 0 0 0 -3-3h-61.19a3 3 0 1 0 0 5.91h61.29a2.88 2.88 0 0 0 2.9-2.95zm-106.83 77.51a22.3 22.3 0 1 0 -22.33-22.3 22.31 22.31 0 0 0 22.33 22.3zm150.71-29.05a2.93 2.93 0 0 0 -3-3h-105.07a3 3 0 1 0 0 5.91h105.17a2.88 2.88 0 0 0 2.9-2.91zm-43.88 13.79a2.93 2.93 0 0 0 -3-3h-61.19a3 3 0 1 0 0 5.91h61.29a2.88 2.88 0 0 0 2.9-2.91zm43.88 48.5a2.93 2.93 0 0 0 -3-3h-105.07a3 3 0 1 0 0 5.92h105.17a2.88 2.88 0 0 0 2.9-2.92zm-43.88 13.78a2.93 2.93 0 0 0 -3-3h-61.19a3 3 0 1 0 0 5.92h61.29a2.88 2.88 0 0 0 2.9-2.92zm-121 10.16h28.38a22.33 22.33 0 1 0 -28.42 0z" fill="#5e5e5e"/><path d="m0 245.26h292v74.52h-292z" fill="url(#a)"/><path d="m56.25 94.87a33.37 33.37 0 1 1 33.36-33.37 33.41 33.41 0 0 1 -33.36 33.37zm0-60.73a27.37 27.37 0 1 0 27.36 27.36 27.4 27.4 0 0 0 -27.36-27.36z" fill="#6191f3"/></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1 @@
<svg height="320" viewBox="0 0 292 320" width="292" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><clipPath id="a"><path d="m.34.31h291.66v318.95h-291.66z"/></clipPath><linearGradient id="b" gradientTransform="matrix(1 0 0 -1 0 -4326)" gradientUnits="userSpaceOnUse" x1="145.79" x2="145.79" y1="-4571.48" y2="-4645.26"><stop offset="0" stop-color="#fff" stop-opacity="0"/><stop offset="1" stop-color="#fff"/></linearGradient><linearGradient id="c" x1="146" x2="146" xlink:href="#b" y1="-4571.48" y2="-4646"/><g clip-path="url(#a)"><path d="m.5 51.93a52 52 0 0 1 52.19-51.93l187.44.58a52 52 0 0 1 51.87 52.12l-.93 497.55a52 52 0 0 1 -52.19 51.93l-187.44-.57a52 52 0 0 1 -51.87-52.13z" fill="#dedede" fill-rule="evenodd"/><path d="m48.4 11.57a34.67 34.67 0 0 0 -34.77 34.6l-1.25 507.29a34.69 34.69 0 0 0 34.62 34.77l197 .48a34.69 34.69 0 0 0 34.78-34.6l1.25-507.28a34.7 34.7 0 0 0 -34.61-34.78z" fill="#fff" fill-rule="evenodd"/><g fill="#dbdbdb"><path d="m204.65 111.36h-105.17a3 3 0 1 1 0-5.91h105.11a2.93 2.93 0 0 1 3 3 2.88 2.88 0 0 1 -2.94 2.91z" fill-rule="evenodd"/><path d="m160.77 125.14h-61.29a3 3 0 1 1 0-5.91h61.23a2.93 2.93 0 0 1 3 3 2.88 2.88 0 0 1 -2.94 2.91z" fill-rule="evenodd"/><path d="m204.65 173.65h-105.17a3 3 0 1 1 0-5.92h105.11a2.93 2.93 0 0 1 3 3 2.88 2.88 0 0 1 -2.94 2.92z" fill-rule="evenodd"/><path d="m160.77 187.49h-61.29a3 3 0 1 1 0-5.92h61.23a2.93 2.93 0 0 1 3 3 2.88 2.88 0 0 1 -2.94 2.92z" fill-rule="evenodd"/><path d="m204.65 236h-105.17a3 3 0 1 1 0-5.92h105.11a2.93 2.93 0 0 1 3 3 2.88 2.88 0 0 1 -2.94 2.92z" fill-rule="evenodd"/><path d="m160.77 249.77h-61.29a3 3 0 1 1 0-5.91h61.23a2.93 2.93 0 0 1 3 2.95 2.88 2.88 0 0 1 -2.94 2.96z" fill-rule="evenodd"/><path d="m204.65 298.27h-105.17a3 3 0 1 1 0-5.91h105.11a2.93 2.93 0 0 1 3 2.95 2.88 2.88 0 0 1 -2.94 2.96z" fill-rule="evenodd"/><path d="m34.51 115.15a22.33 22.33 0 1 1 22.33 22.3 22.31 22.31 0 0 1 -22.33-22.3z"/><path d="m56.84 199.73a22.3 22.3 0 1 0 -22.33-22.3 22.31 22.31 0 0 0 22.33 22.3z"/><path d="m56.84 262.07a22.3 22.3 0 1 0 -22.33-22.3 22.31 22.31 0 0 0 22.33 22.3z"/><path d="m56.84 324.42a22.3 22.3 0 1 0 -22.33-22.3 22.31 22.31 0 0 0 22.33 22.3z"/><path d="m163.67 309.1a2.93 2.93 0 0 0 -3-3h-61.19a3 3 0 1 0 0 5.91h61.29a2.88 2.88 0 0 0 2.9-2.91z"/></g><path d="m.24 245.48h291.09v73.78h-291.09z" fill="url(#b)"/><path d="m56.25 74.76a13 13 0 1 0 -13-13 13 13 0 0 0 13 13z" fill="#dbdbdb"/></g><path d="m292 320v-267.3a52 52 0 0 0 -51.87-52.12l-187.44-.58a52 52 0 0 0 -52.19 51.93l-.5 268.07z" fill="#dedede" fill-rule="evenodd"/><path d="m279.37 320 .68-273.17a34.7 34.7 0 0 0 -34.61-34.78l-26.36-.05-143.51-.37-27.17-.06a34.67 34.67 0 0 0 -34.77 34.6l-.63 273.83z" fill="#fff" fill-rule="evenodd"/><path d="m34.51 115.89a22.33 22.33 0 1 1 22.33 22.3 22.32 22.32 0 0 1 -22.33-22.3zm21.74-40.39a13 13 0 1 0 -13-13 13 13 0 0 0 13 13zm151.3 33.65a2.93 2.93 0 0 0 -3-3h-105.07a3 3 0 1 0 0 5.92h105.17a2.88 2.88 0 0 0 2.9-2.92zm-43.88 13.78a2.93 2.93 0 0 0 -3-3h-61.19a3 3 0 1 0 0 5.92h61.29a2.88 2.88 0 0 0 2.9-2.92zm-106.83 77.54a22.3 22.3 0 1 0 -22.33-22.29 22.3 22.3 0 0 0 22.33 22.29zm150.71-29a2.93 2.93 0 0 0 -3-3h-105.07a3 3 0 1 0 0 5.92h105.17a2.88 2.88 0 0 0 2.9-2.96zm-43.88 13.84a2.93 2.93 0 0 0 -3-3h-61.19a3 3 0 1 0 0 5.92h61.29a2.88 2.88 0 0 0 2.9-2.96zm-106.83 77.51a22.3 22.3 0 1 0 -22.33-22.3 22.31 22.31 0 0 0 22.33 22.3zm150.71-29a2.93 2.93 0 0 0 -3-3h-105.07a3 3 0 1 0 0 5.91h105.17a2.88 2.88 0 0 0 2.9-2.96zm-43.88 13.79a2.93 2.93 0 0 0 -3-3h-61.19a3 3 0 1 0 0 5.91h61.29a2.88 2.88 0 0 0 2.9-2.96zm43.88 48.5a2.93 2.93 0 0 0 -3-3h-105.07a3 3 0 1 0 0 5.92h105.17a2.88 2.88 0 0 0 2.9-2.97zm-43.88 13.78a2.93 2.93 0 0 0 -3-3h-61.19a3 3 0 1 0 0 5.92h61.29a2.88 2.88 0 0 0 2.9-2.97zm-121.04 10.11h28.42a22.33 22.33 0 1 0 -28.42 0z" fill="#dbdbdb"/><path d="m0 245.48h292v74.52h-292z" fill="url(#c)"/><path d="m56.25 95.08a33.36 33.36 0 1 1 33.36-33.36 33.4 33.4 0 0 1 -33.36 33.36zm0-60.72a27.36 27.36 0 1 0 27.36 27.36 27.4 27.4 0 0 0 -27.36-27.36z" fill="#2c6bed"/></svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -14,7 +14,7 @@
max-width: 420px;
}
&__body {
&__contents {
display: flex;
align-items: center;
}
@ -110,5 +110,17 @@
&__description {
@include font-body-1;
@include fixed-height(5.5em);
margin-bottom: 12px;
}
&__instructions-button {
width: 100%;
&--hidden {
visibility: hidden;
}
}
.BadgeCarouselIndex {
margin-top: 24px;
}
}

View File

@ -0,0 +1,52 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
.BadgeSustainerInstructionsDialog {
user-select: none;
// We use this selector for specificity.
&.module-Modal {
max-width: 420px;
}
&__header {
@include font-title-2;
text-align: center;
}
&__subheader {
@include font-body-1;
font-weight: normal;
text-align: center;
}
&__instructions {
@include font-body-2;
padding: 0;
list-style-position: inside;
&::before {
background-size: contain;
content: '';
display: block;
height: 160px;
margin: 24px auto;
width: 146px;
@include light-theme {
background-image: url('../images/mobile-settings-light.svg');
}
@include dark-theme {
background-image: url('../images/mobile-settings-dark.svg');
}
}
> li {
margin-top: 1em;
&:first-child {
margin-top: 0;
}
}
}
}

View File

@ -35,6 +35,11 @@
cursor: not-allowed;
}
&--large {
@include font-title-2;
font-weight: bold;
}
&--medium {
@include font-body-1-bold;
}

View File

@ -34,6 +34,7 @@
@import './components/AvatarTextEditor.scss';
@import './components/BadgeCarouselIndex.scss';
@import './components/BadgeDialog.scss';
@import './components/BadgeSustainerInstructionsDialog.scss';
@import './components/BetterAvatarBubble.scss';
@import './components/Button.scss';
@import './components/CallingLobby.scss';

View File

@ -2,14 +2,18 @@
// SPDX-License-Identifier: AGPL-3.0-only
import React, { useEffect, useState } from 'react';
import classNames from 'classnames';
import { strictAssert } from '../util/assert';
import type { LocalizerType } from '../types/Util';
import type { BadgeType } from '../badges/types';
import { BadgeCategory } from '../badges/BadgeCategory';
import { Modal } from './Modal';
import { Button, ButtonSize } from './Button';
import { BadgeDescription } from './BadgeDescription';
import { BadgeImage } from './BadgeImage';
import { BadgeCarouselIndex } from './BadgeCarouselIndex';
import { BadgeSustainerInstructionsDialog } from './BadgeSustainerInstructionsDialog';
type PropsType = Readonly<{
badges: ReadonlyArray<BadgeType>;
@ -20,16 +24,32 @@ type PropsType = Readonly<{
}>;
export function BadgeDialog(props: PropsType): null | JSX.Element {
const { badges, onClose } = props;
const { badges, i18n, onClose } = props;
const [isShowingInstructions, setIsShowingInstructions] = useState(false);
const hasBadges = badges.length > 0;
useEffect(() => {
if (!hasBadges) {
if (!hasBadges && !isShowingInstructions) {
onClose();
}
}, [hasBadges, onClose]);
}, [hasBadges, isShowingInstructions, onClose]);
return hasBadges ? <BadgeDialogWithBadges {...props} /> : null;
if (isShowingInstructions) {
return (
<BadgeSustainerInstructionsDialog
i18n={i18n}
onClose={() => setIsShowingInstructions(false)}
/>
);
}
return hasBadges ? (
<BadgeDialogWithBadges
{...props}
onShowInstructions={() => setIsShowingInstructions(true)}
/>
) : null;
}
function BadgeDialogWithBadges({
@ -37,8 +57,9 @@ function BadgeDialogWithBadges({
firstName,
i18n,
onClose,
onShowInstructions,
title,
}: PropsType): JSX.Element {
}: PropsType & { onShowInstructions: () => unknown }): JSX.Element {
const firstBadge = badges[0];
strictAssert(
firstBadge,
@ -75,35 +96,48 @@ function BadgeDialogWithBadges({
i18n={i18n}
onClose={onClose}
>
<button
aria-label={i18n('previous')}
className="BadgeDialog__nav BadgeDialog__nav--previous"
disabled={currentBadgeIndex === 0}
onClick={() => navigate(-1)}
type="button"
/>
<div className="BadgeDialog__main">
<BadgeImage badge={currentBadge} size={160} />
<div className="BadgeDialog__name">{currentBadge.name}</div>
<div className="BadgeDialog__description">
<BadgeDescription
firstName={firstName}
template={currentBadge.descriptionTemplate}
title={title}
<div className="BadgeDialog__contents">
<button
aria-label={i18n('previous')}
className="BadgeDialog__nav BadgeDialog__nav--previous"
disabled={currentBadgeIndex === 0}
onClick={() => navigate(-1)}
type="button"
/>
<div className="BadgeDialog__main">
<BadgeImage badge={currentBadge} size={160} />
<div className="BadgeDialog__name">{currentBadge.name}</div>
<div className="BadgeDialog__description">
<BadgeDescription
firstName={firstName}
template={currentBadge.descriptionTemplate}
title={title}
/>
</div>
<Button
className={classNames(
'BadgeDialog__instructions-button',
currentBadge.category !== BadgeCategory.Donor &&
'BadgeDialog__instructions-button--hidden'
)}
onClick={onShowInstructions}
size={ButtonSize.Large}
>
{i18n('BadgeDialog__become-a-sustainer-button')}
</Button>
<BadgeCarouselIndex
currentIndex={currentBadgeIndex}
totalCount={badges.length}
/>
</div>
<BadgeCarouselIndex
currentIndex={currentBadgeIndex}
totalCount={badges.length}
<button
aria-label={i18n('next')}
className="BadgeDialog__nav BadgeDialog__nav--next"
disabled={currentBadgeIndex === badges.length - 1}
onClick={() => navigate(1)}
type="button"
/>
</div>
<button
aria-label={i18n('next')}
className="BadgeDialog__nav BadgeDialog__nav--next"
disabled={currentBadgeIndex === badges.length - 1}
onClick={() => navigate(1)}
type="button"
/>
</Modal>
);
}

View File

@ -0,0 +1,33 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { ReactElement } from 'react';
import React from 'react';
import type { LocalizerType } from '../types/Util';
import { Modal } from './Modal';
export function BadgeSustainerInstructionsDialog({
i18n,
onClose,
}: Readonly<{ i18n: LocalizerType; onClose: () => unknown }>): ReactElement {
return (
<Modal
hasXButton
moduleClassName="BadgeSustainerInstructionsDialog"
i18n={i18n}
onClose={onClose}
>
<h1 className="BadgeSustainerInstructionsDialog__header">
{i18n('BadgeSustainerInstructions__header')}
</h1>
<h2 className="BadgeSustainerInstructionsDialog__subheader">
{i18n('BadgeSustainerInstructions__subheader')}
</h2>
<ol className="BadgeSustainerInstructionsDialog__instructions">
<li>{i18n('BadgeSustainerInstructions__instructions__1')}</li>
<li>{i18n('BadgeSustainerInstructions__instructions__2')}</li>
<li>{i18n('BadgeSustainerInstructions__instructions__3')}</li>
</ol>
</Modal>
);
}

View File

@ -13,7 +13,7 @@ story.add('Kitchen sink', () => (
<>
{Object.values(ButtonVariant).map(variant => (
<React.Fragment key={variant}>
{[ButtonSize.Medium, ButtonSize.Small].map(size => (
{[ButtonSize.Large, ButtonSize.Medium, ButtonSize.Small].map(size => (
<React.Fragment key={size}>
<p>
<Button onClick={action('onClick')} size={size} variant={variant}>

View File

@ -8,6 +8,7 @@ import classNames from 'classnames';
import { assert } from '../util/assert';
export enum ButtonSize {
Large,
Medium,
Small,
}
@ -65,6 +66,7 @@ type PropsType = {
);
const SIZE_CLASS_NAMES = new Map<ButtonSize, string>([
[ButtonSize.Large, 'module-Button--large'],
[ButtonSize.Medium, 'module-Button--medium'],
[ButtonSize.Small, 'module-Button--small'],
]);