Skip to content

Commit 7b71bd1

Browse files
authored
Restructure addon detail page header (#13638)
* organize promoted related utils * Integrate PromotedBadge to the Badge component. * Implement rating badge in AddonBadges component and remove AddonMeta component. Update styles and adjust related components accordingly. * Use render prop instead of child mapping * Use built-in for zeeeee rounding. * TMP: fix hardcoded user count * Respond to fixes from copilot
1 parent 2fb56d6 commit 7b71bd1

File tree

36 files changed

+706
-1119
lines changed

36 files changed

+706
-1119
lines changed

src/amo/components/AddonBadges/index.js

Lines changed: 128 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
/* @flow */
22
import * as React from 'react';
33
import { connect } from 'react-redux';
4+
import { withRouter } from 'react-router-dom';
45
import { compose } from 'redux';
56

7+
import { reviewListURL } from 'amo/reducers/reviews';
68
import { CLIENT_APP_FIREFOX } from 'amo/constants';
79
import translate from 'amo/i18n/translate';
810
import { getPromotedCategory } from 'amo/utils/addons';
9-
import Badge from 'amo/components/Badge';
10-
import PromotedBadge from 'amo/components/PromotedBadge';
11+
import Badge, { BadgeIcon, BadgePill } from 'amo/components/Badge';
1112
import type { AppState } from 'amo/store';
1213
import type { AddonType } from 'amo/types/addons';
1314
import type { I18nType } from 'amo/types/i18n';
15+
import type { ReactRouterLocationType } from 'amo/types/router';
16+
import { getPromotedProps } from 'amo/utils/promoted';
1417

1518
import './styles.scss';
1619

@@ -31,46 +34,146 @@ type InternalProps = {|
3134
...DefaultProps,
3235
...PropsFromState,
3336
i18n: I18nType,
37+
location: ReactRouterLocationType,
3438
|};
3539

40+
export const roundToOneDigit = (value: number | null): number => {
41+
return value ? Number(value.toFixed(1)) : 0;
42+
};
43+
3644
export class AddonBadgesBase extends React.Component<InternalProps> {
3745
static defaultProps: DefaultProps = {
3846
_getPromotedCategory: getPromotedCategory,
3947
};
4048

41-
render(): null | React.Node {
42-
const { _getPromotedCategory, addon, clientApp, i18n } = this.props;
49+
renderAndroidCompatibleBadge(): React.Node {
50+
const { addon, clientApp, i18n } = this.props;
4351

44-
if (!addon) {
52+
if (clientApp !== CLIENT_APP_FIREFOX || !addon.isAndroidCompatible)
4553
return null;
46-
}
54+
55+
return (
56+
<Badge
57+
type="android"
58+
label={i18n.gettext('Available on Firefox for Android™')}
59+
size="large"
60+
/>
61+
);
62+
}
63+
64+
renderExperimentalBadge(): React.Node {
65+
const { addon, i18n } = this.props;
66+
67+
if (!addon.is_experimental) return null;
68+
69+
return (
70+
<Badge
71+
type="experimental-badge"
72+
label={i18n.gettext('Experimental')}
73+
size="large"
74+
>
75+
{(props) => (
76+
<BadgePill {...props}>
77+
<BadgeIcon {...props} />
78+
</BadgePill>
79+
)}
80+
</Badge>
81+
);
82+
}
83+
84+
renderRequiresPaymentBadge(): React.Node {
85+
const { addon, i18n } = this.props;
86+
87+
if (!addon.requires_payment) return null;
88+
89+
return (
90+
<Badge
91+
type="requires-payment"
92+
label={i18n.gettext('Some features may require payment')}
93+
size="large"
94+
/>
95+
);
96+
}
97+
98+
renderPromotedBadge(): React.Node {
99+
const { _getPromotedCategory, addon, clientApp, i18n } = this.props;
47100

48101
const promotedCategory = _getPromotedCategory({
49102
addon,
50103
clientApp,
51104
forBadging: true,
52105
});
53106

107+
if (!promotedCategory) return null;
108+
109+
const props = getPromotedProps(i18n, promotedCategory);
110+
111+
return (
112+
<Badge
113+
link={props.linkUrl}
114+
title={props.linkTitle}
115+
type={props.category}
116+
label={props.label}
117+
size="large"
118+
/>
119+
);
120+
}
121+
122+
renderRatingMeta(): React.Node {
123+
const { addon, i18n, location } = this.props;
124+
125+
if (!addon?.ratings) return null;
126+
127+
const addonRatingCount: number = addon.ratings.count;
128+
const averageRating: number = addon.ratings.average;
129+
const roundedAverage = roundToOneDigit(averageRating || null);
130+
131+
const reviewCount = i18n.formatNumber(addonRatingCount);
132+
const reviewsLink = reviewListURL({ addonSlug: addon.slug, location });
133+
const reviewsLabel = `${roundedAverage} (${reviewCount || 0} reviews)`;
134+
135+
return (
136+
<Badge
137+
link={reviewsLink}
138+
type="star-full"
139+
label={reviewsLabel}
140+
size="large"
141+
/>
142+
);
143+
}
144+
145+
renderUserCount(): React.Node {
146+
const { addon, i18n } = this.props;
147+
148+
if (!addon) return null;
149+
150+
const averageDailyUsers = addon.average_daily_users;
151+
152+
const userCount = i18n.formatNumber(averageDailyUsers);
153+
const userTitle =
154+
averageDailyUsers > 0
155+
? i18n.ngettext('User', 'Users', averageDailyUsers)
156+
: i18n.gettext('No Users');
157+
158+
const userLabel = `${userCount} ${userTitle}`;
159+
return <Badge type="user-fill" label={userLabel} size="large" />;
160+
}
161+
162+
render(): null | React.Node {
163+
const { addon } = this.props;
164+
165+
if (!addon) {
166+
return null;
167+
}
168+
54169
return (
55170
<div className="AddonBadges">
56-
{promotedCategory ? (
57-
<PromotedBadge category={promotedCategory} size="large" />
58-
) : null}
59-
{addon.is_experimental ? (
60-
<Badge type="experimental" label={i18n.gettext('Experimental')} />
61-
) : null}
62-
{addon.requires_payment ? (
63-
<Badge
64-
type="requires-payment"
65-
label={i18n.gettext('Some features may require payment')}
66-
/>
67-
) : null}
68-
{clientApp === CLIENT_APP_FIREFOX && addon.isAndroidCompatible && (
69-
<Badge
70-
type="android-compatible"
71-
label={i18n.gettext('Available on Firefox for Android™')}
72-
/>
73-
)}
171+
{this.renderPromotedBadge()}
172+
{this.renderExperimentalBadge()}
173+
{this.renderRequiresPaymentBadge()}
174+
{this.renderAndroidCompatibleBadge()}
175+
{this.renderRatingMeta()}
176+
{this.renderUserCount()}
74177
</div>
75178
);
76179
}
@@ -83,6 +186,7 @@ const mapStateToProps = (state: AppState): PropsFromState => {
83186
};
84187

85188
const AddonBadges: React.ComponentType<Props> = compose(
189+
withRouter,
86190
connect(mapStateToProps),
87191
translate(),
88192
)(AddonBadgesBase);

src/amo/components/AddonBadges/styles.scss

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
@import '~amo/css/styles';
22

33
.AddonBadges {
4-
width: 100%;
4+
display: flex;
5+
flex-wrap: wrap;
6+
gap: 6px;
7+
justify-items: start;
8+
align-items: center;
59

610
.Addon-theme & {
711
display: flex;
@@ -27,7 +31,6 @@
2731
}
2832

2933
@include respond-to(large) {
30-
@include margin-start(12px);
3134
@include text-align-end;
3235

3336
.Addon-theme & {

src/amo/components/AddonMeta/index.js

Lines changed: 0 additions & 145 deletions
This file was deleted.

0 commit comments

Comments
 (0)