signal-desktop/ts/components/BadgeDialog.tsx

148 lines
4.2 KiB
TypeScript
Raw Normal View History

2021-11-02 23:01:13 +00:00
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { useEffect, useState } from 'react';
import classNames from 'classnames';
2021-11-02 23:01:13 +00:00
import { strictAssert } from '../util/assert';
import type { LocalizerType } from '../types/Util';
import type { BadgeType } from '../badges/types';
import { BadgeCategory } from '../badges/BadgeCategory';
2021-11-02 23:01:13 +00:00
import { Modal } from './Modal';
import { Button, ButtonSize } from './Button';
2021-11-02 23:01:13 +00:00
import { BadgeDescription } from './BadgeDescription';
import { BadgeImage } from './BadgeImage';
import { BadgeCarouselIndex } from './BadgeCarouselIndex';
import { BadgeSustainerInstructionsDialog } from './BadgeSustainerInstructionsDialog';
2021-11-02 23:01:13 +00:00
type PropsType = Readonly<{
areWeASubscriber: boolean;
2021-11-02 23:01:13 +00:00
badges: ReadonlyArray<BadgeType>;
firstName?: string;
i18n: LocalizerType;
onClose: () => unknown;
title: string;
}>;
export function BadgeDialog(props: PropsType): null | JSX.Element {
const { badges, i18n, onClose } = props;
const [isShowingInstructions, setIsShowingInstructions] = useState(false);
2021-11-02 23:01:13 +00:00
const hasBadges = badges.length > 0;
useEffect(() => {
if (!hasBadges && !isShowingInstructions) {
2021-11-02 23:01:13 +00:00
onClose();
}
}, [hasBadges, isShowingInstructions, onClose]);
if (isShowingInstructions) {
return (
<BadgeSustainerInstructionsDialog
i18n={i18n}
onClose={() => setIsShowingInstructions(false)}
/>
);
}
2021-11-02 23:01:13 +00:00
return hasBadges ? (
<BadgeDialogWithBadges
{...props}
onShowInstructions={() => setIsShowingInstructions(true)}
/>
) : null;
2021-11-02 23:01:13 +00:00
}
function BadgeDialogWithBadges({
areWeASubscriber,
2021-11-02 23:01:13 +00:00
badges,
firstName,
i18n,
onClose,
onShowInstructions,
2021-11-02 23:01:13 +00:00
title,
}: PropsType & { onShowInstructions: () => unknown }): JSX.Element {
2021-11-02 23:01:13 +00:00
const firstBadge = badges[0];
strictAssert(
firstBadge,
'<BadgeDialogWithBadges> got an empty array of badges'
);
const [currentBadgeId, setCurrentBadgeId] = useState(firstBadge.id);
let currentBadge: BadgeType;
let currentBadgeIndex: number = badges.findIndex(
b => b.id === currentBadgeId
);
if (currentBadgeIndex === -1) {
currentBadgeIndex = 0;
currentBadge = firstBadge;
} else {
currentBadge = badges[currentBadgeIndex];
}
const setCurrentBadgeIndex = (index: number): void => {
const newBadge = badges[index];
strictAssert(newBadge, '<BadgeDialog> tried to select a nonexistent badge');
setCurrentBadgeId(newBadge.id);
};
const navigate = (change: number): void => {
setCurrentBadgeIndex(currentBadgeIndex + change);
};
return (
<Modal
hasXButton
moduleClassName="BadgeDialog"
i18n={i18n}
onClose={onClose}
>
<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>
{!areWeASubscriber && (
<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}
2021-11-02 23:01:13 +00:00
/>
</div>
<button
aria-label={i18n('next')}
className="BadgeDialog__nav BadgeDialog__nav--next"
disabled={currentBadgeIndex === badges.length - 1}
onClick={() => navigate(1)}
type="button"
2021-11-02 23:01:13 +00:00
/>
</div>
</Modal>
);
}