diff --git a/app/soapbox/features/ui/components/profile_info_panel.js b/app/soapbox/features/ui/components/profile_info_panel.js
deleted file mode 100644
index 9b1f83c4e..000000000
--- a/app/soapbox/features/ui/components/profile_info_panel.js
+++ /dev/null
@@ -1,271 +0,0 @@
-'use strict';
-
-import { List as ImmutableList } from 'immutable';
-import PropTypes from 'prop-types';
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import { connect } from 'react-redux';
-
-import { initAccountNoteModal } from 'soapbox/actions/account_notes';
-import Badge from 'soapbox/components/badge';
-import { Icon, HStack, Stack, Text } from 'soapbox/components/ui';
-import VerificationBadge from 'soapbox/components/verification_badge';
-import { isLocal } from 'soapbox/utils/accounts';
-import { displayFqn } from 'soapbox/utils/state';
-
-import ProfileStats from './profile_stats';
-
-// Basically ensure the URL isn't `javascript:alert('hi')` or something like that
-const isSafeUrl = text => {
- try {
- const url = new URL(text);
- return ['http:', 'https:'].includes(url.protocol);
- } catch (e) {
- return false;
- }
-};
-
-const messages = defineMessages({
- linkVerifiedOn: { id: 'account.link_verified_on', defaultMessage: 'Ownership of this link was checked on {date}' },
- account_locked: { id: 'account.locked_info', defaultMessage: 'This account privacy status is set to locked. The owner manually reviews who can follow them.' },
- deactivated: { id: 'account.deactivated', defaultMessage: 'Deactivated' },
- bot: { id: 'account.badges.bot', defaultMessage: 'Bot' },
-});
-
-class ProfileInfoPanel extends ImmutablePureComponent {
-
- static propTypes = {
- account: ImmutablePropTypes.record,
- identity_proofs: ImmutablePropTypes.list,
- intl: PropTypes.object.isRequired,
- username: PropTypes.string,
- displayFqn: PropTypes.bool,
- onShowNote: PropTypes.func,
- };
-
- getStaffBadge = () => {
- const { account } = this.props;
-
- if (account?.admin) {
- return ;
- } else if (account?.moderator) {
- return ;
- } else {
- return null;
- }
- }
-
- getBadges = () => {
- const { account } = this.props;
- const staffBadge = this.getStaffBadge();
- const isPatron = account.getIn(['patron', 'is_patron']);
-
- const badges = [];
-
- if (staffBadge) {
- badges.push(staffBadge);
- }
-
- if (isPatron) {
- badges.push();
- }
-
- if (account.donor) {
- badges.push();
- }
-
- return badges;
- }
-
- renderBirthday = () => {
- const { account, intl } = this.props;
-
- const birthday = account.get('birthday');
- if (!birthday) return null;
-
- const formattedBirthday = intl.formatDate(birthday, { timeZone: 'UTC', day: 'numeric', month: 'long', year: 'numeric' });
-
- const date = new Date(birthday);
- const today = new Date();
-
- const hasBirthday = date.getDate() === today.getDate() && date.getMonth() === today.getMonth();
-
- return (
-
-
-
-
- {hasBirthday ? (
-
- ) : (
-
- )}
-
-
- );
- }
-
- handleShowNote = e => {
- const { account, onShowNote } = this.props;
-
- e.preventDefault();
- onShowNote(account);
- }
-
- render() {
- const { account, displayFqn, intl, username } = this.props;
-
- if (!account) {
- return (
-
-
-
-
-
- @{username}
-
-
-
-
-
- );
- }
-
- const content = { __html: account.get('note_emojified') };
- const deactivated = !account.getIn(['pleroma', 'is_active'], true);
- const displayNameHtml = deactivated ? { __html: intl.formatMessage(messages.deactivated) } : { __html: account.get('display_name_html') };
- const memberSinceDate = intl.formatDate(account.get('created_at'), { month: 'long', year: 'numeric' });
- const verified = account.get('verified');
- const badges = this.getBadges();
-
- return (
-
-
- {/* Not sure if this is actual used. */}
- {/*
-
-
*/}
-
-
-
-
-
- {verified && }
-
- {account.bot && }
-
- {badges.length > 0 && (
-
- {badges}
-
- )}
-
-
-
-
- @{displayFqn ? account.fqn : account.acct}
-
-
- {account.get('locked') && (
-
- )}
-
-
-
-
-
- {
- (account.get('note').length > 0 && account.get('note') !== '') &&
-
- }
-
-
- {isLocal(account) ? (
-
-
-
-
-
-
-
- ) : null}
-
- {account.get('location') ? (
-
-
-
-
- {account.get('location')}
-
-
- ) : null}
-
- {account.get('website') ? (
-
-
-
-
-
- ) : null}
-
- {this.renderBirthday()}
-
-
-
- );
- }
-
-}
-
-const mapStateToProps = (state, { account }) => {
- const identity_proofs = account ? state.getIn(['identity_proofs', account.get('id')], ImmutableList()) : ImmutableList();
- return {
- identity_proofs,
- domain: state.getIn(['meta', 'domain']),
- displayFqn: displayFqn(state),
- };
-};
-
-const mapDispatchToProps = (dispatch) => ({
- onShowNote(account) {
- dispatch(initAccountNoteModal(account));
- },
-});
-
-export default injectIntl(
- connect(mapStateToProps, mapDispatchToProps, null, {
- forwardRef: true,
- },
- )(ProfileInfoPanel));
diff --git a/app/soapbox/features/ui/components/profile_info_panel.tsx b/app/soapbox/features/ui/components/profile_info_panel.tsx
new file mode 100644
index 000000000..7fe628ddb
--- /dev/null
+++ b/app/soapbox/features/ui/components/profile_info_panel.tsx
@@ -0,0 +1,230 @@
+'use strict';
+
+import React from 'react';
+import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
+
+import Badge from 'soapbox/components/badge';
+import { Icon, HStack, Stack, Text } from 'soapbox/components/ui';
+import VerificationBadge from 'soapbox/components/verification_badge';
+import { useSoapboxConfig } from 'soapbox/hooks';
+import { isLocal } from 'soapbox/utils/accounts';
+
+import ProfileStats from './profile_stats';
+
+import type { Account } from 'soapbox/types/entities';
+
+/** Basically ensure the URL isn't `javascript:alert('hi')` or something like that */
+const isSafeUrl = (text: string): boolean => {
+ try {
+ const url = new URL(text);
+ return ['http:', 'https:'].includes(url.protocol);
+ } catch (e) {
+ return false;
+ }
+};
+
+const messages = defineMessages({
+ linkVerifiedOn: { id: 'account.link_verified_on', defaultMessage: 'Ownership of this link was checked on {date}' },
+ account_locked: { id: 'account.locked_info', defaultMessage: 'This account privacy status is set to locked. The owner manually reviews who can follow them.' },
+ deactivated: { id: 'account.deactivated', defaultMessage: 'Deactivated' },
+ bot: { id: 'account.badges.bot', defaultMessage: 'Bot' },
+});
+
+interface IProfileInfoPanel {
+ account: Account,
+ /** Username from URL params, in case the account isn't found. */
+ username: string,
+}
+
+/** User profile metadata, such as location, birthday, etc. */
+const ProfileInfoPanel: React.FC = ({ account, username }) => {
+ const intl = useIntl();
+ const { displayFqn } = useSoapboxConfig();
+
+ const getStaffBadge = (): React.ReactNode => {
+ if (account?.admin) {
+ return ;
+ } else if (account?.moderator) {
+ return ;
+ } else {
+ return null;
+ }
+ };
+
+ const getBadges = (): React.ReactNode[] => {
+ const staffBadge = getStaffBadge();
+ const isPatron = account.getIn(['patron', 'is_patron']) === true;
+
+ const badges = [];
+
+ if (staffBadge) {
+ badges.push(staffBadge);
+ }
+
+ if (isPatron) {
+ badges.push();
+ }
+
+ if (account.donor) {
+ badges.push();
+ }
+
+ return badges;
+ };
+
+ const renderBirthday = (): React.ReactNode => {
+ const birthday = account.birthday;
+ if (!birthday) return null;
+
+ const formattedBirthday = intl.formatDate(birthday, { timeZone: 'UTC', day: 'numeric', month: 'long', year: 'numeric' });
+
+ const date = new Date(birthday);
+ const today = new Date();
+
+ const hasBirthday = date.getDate() === today.getDate() && date.getMonth() === today.getMonth();
+
+ return (
+
+
+
+
+ {hasBirthday ? (
+
+ ) : (
+
+ )}
+
+
+ );
+ };
+
+ if (!account) {
+ return (
+
+
+
+
+
+ @{username}
+
+
+
+
+
+ );
+ }
+
+ const content = { __html: account.note_emojified };
+ const deactivated = !account.pleroma.get('is_active', true) === true;
+ const displayNameHtml = deactivated ? { __html: intl.formatMessage(messages.deactivated) } : { __html: account.display_name_html };
+ const memberSinceDate = intl.formatDate(account.created_at, { month: 'long', year: 'numeric' });
+ const verified = account.verified;
+ const badges = getBadges();
+
+ return (
+
+
+ {/* Not sure if this is actual used. */}
+ {/*
+
+
*/}
+
+
+
+
+
+ {verified && }
+
+ {account.bot && }
+
+ {badges.length > 0 && (
+
+ {badges}
+
+ )}
+
+
+
+
+ @{displayFqn ? account.fqn : account.acct}
+
+
+ {account.locked && (
+
+ )}
+
+
+
+
+
+ {account.note.length > 0 && account.note !== '' && (
+
+ )}
+
+
+ {isLocal(account as any) ? (
+
+
+
+
+
+
+
+ ) : null}
+
+ {account.location ? (
+
+
+
+
+ {account.location}
+
+
+ ) : null}
+
+ {account.website ? (
+
+
+
+
+
+ {isSafeUrl(account.website) ? (
+ {account.website}
+ ) : (
+ account.website
+ )}
+
+
+
+ ) : null}
+
+ {renderBirthday()}
+
+
+
+ );
+};
+
+export default ProfileInfoPanel;