Merge branch 'profile-fields-mobile' into 'develop'
Display custom profile fields on mobile Closes #1160 See merge request soapbox-pub/soapbox!1955
This commit is contained in:
commit
d61e115804
3 changed files with 86 additions and 70 deletions
73
app/soapbox/features/ui/components/profile-field.tsx
Normal file
73
app/soapbox/features/ui/components/profile-field.tsx
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
import classNames from 'clsx';
|
||||||
|
import React from 'react';
|
||||||
|
import { defineMessages, useIntl, FormatDateOptions } from 'react-intl';
|
||||||
|
|
||||||
|
import Markup from 'soapbox/components/markup';
|
||||||
|
import { HStack, Icon } from 'soapbox/components/ui';
|
||||||
|
import BundleContainer from 'soapbox/features/ui/containers/bundle-container';
|
||||||
|
import { CryptoAddress } from 'soapbox/features/ui/util/async-components';
|
||||||
|
|
||||||
|
import type { Field } from 'soapbox/types/entities';
|
||||||
|
|
||||||
|
const getTicker = (value: string): string => (value.match(/\$([a-zA-Z]*)/i) || [])[1];
|
||||||
|
const isTicker = (value: string): boolean => Boolean(getTicker(value));
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
linkVerifiedOn: { id: 'account.link_verified_on', defaultMessage: 'Ownership of this link was checked on {date}' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const dateFormatOptions: FormatDateOptions = {
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
year: 'numeric',
|
||||||
|
hour12: true,
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: '2-digit',
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IProfileField {
|
||||||
|
field: Field,
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Renders a single profile field. */
|
||||||
|
const ProfileField: React.FC<IProfileField> = ({ field }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
if (isTicker(field.name)) {
|
||||||
|
return (
|
||||||
|
<BundleContainer fetchComponent={CryptoAddress}>
|
||||||
|
{Component => (
|
||||||
|
<Component
|
||||||
|
ticker={getTicker(field.name).toLowerCase()}
|
||||||
|
address={field.value_plain}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</BundleContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<dl>
|
||||||
|
<dt title={field.name}>
|
||||||
|
<Markup weight='bold' tag='span' dangerouslySetInnerHTML={{ __html: field.name_emojified }} />
|
||||||
|
</dt>
|
||||||
|
|
||||||
|
<dd
|
||||||
|
className={classNames({ 'text-success-500': field.verified_at })}
|
||||||
|
title={field.value_plain}
|
||||||
|
>
|
||||||
|
<HStack space={2} alignItems='center'>
|
||||||
|
{field.verified_at && (
|
||||||
|
<span className='flex-none' title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(field.verified_at, dateFormatOptions) })}>
|
||||||
|
<Icon src={require('@tabler/icons/check.svg')} />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Markup className='break-words overflow-hidden' tag='span' dangerouslySetInnerHTML={{ __html: field.value_emojified }} />
|
||||||
|
</HStack>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProfileField;
|
|
@ -1,77 +1,11 @@
|
||||||
import classNames from 'clsx';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { defineMessages, useIntl, FormattedMessage, FormatDateOptions } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import Markup from 'soapbox/components/markup';
|
import { Widget, Stack } from 'soapbox/components/ui';
|
||||||
import { Widget, Stack, HStack, Icon } from 'soapbox/components/ui';
|
|
||||||
import BundleContainer from 'soapbox/features/ui/containers/bundle-container';
|
|
||||||
import { CryptoAddress } from 'soapbox/features/ui/util/async-components';
|
|
||||||
|
|
||||||
import type { Account, Field } from 'soapbox/types/entities';
|
import ProfileField from './profile-field';
|
||||||
|
|
||||||
const getTicker = (value: string): string => (value.match(/\$([a-zA-Z]*)/i) || [])[1];
|
import type { Account } from 'soapbox/types/entities';
|
||||||
const isTicker = (value: string): boolean => Boolean(getTicker(value));
|
|
||||||
|
|
||||||
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' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const dateFormatOptions: FormatDateOptions = {
|
|
||||||
month: 'short',
|
|
||||||
day: 'numeric',
|
|
||||||
year: 'numeric',
|
|
||||||
hour12: true,
|
|
||||||
hour: 'numeric',
|
|
||||||
minute: '2-digit',
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IProfileField {
|
|
||||||
field: Field,
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Renders a single profile field. */
|
|
||||||
const ProfileField: React.FC<IProfileField> = ({ field }) => {
|
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
if (isTicker(field.name)) {
|
|
||||||
return (
|
|
||||||
<BundleContainer fetchComponent={CryptoAddress}>
|
|
||||||
{Component => (
|
|
||||||
<Component
|
|
||||||
ticker={getTicker(field.name).toLowerCase()}
|
|
||||||
address={field.value_plain}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</BundleContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<dl>
|
|
||||||
<dt title={field.name}>
|
|
||||||
<Markup weight='bold' tag='span' dangerouslySetInnerHTML={{ __html: field.name_emojified }} />
|
|
||||||
</dt>
|
|
||||||
|
|
||||||
<dd
|
|
||||||
className={classNames({ 'text-success-500': field.verified_at })}
|
|
||||||
title={field.value_plain}
|
|
||||||
>
|
|
||||||
<HStack space={2} alignItems='center'>
|
|
||||||
{field.verified_at && (
|
|
||||||
<span className='flex-none' title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(field.verified_at, dateFormatOptions) })}>
|
|
||||||
<Icon src={require('@tabler/icons/check.svg')} />
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Markup className='break-words overflow-hidden' tag='span' dangerouslySetInnerHTML={{ __html: field.value_emojified }} />
|
|
||||||
</HStack>
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IProfileFieldsPanel {
|
interface IProfileFieldsPanel {
|
||||||
account: Account,
|
account: Account,
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { badgeToTag, getBadges as getAccountBadges } from 'soapbox/utils/badges'
|
||||||
import { capitalize } from 'soapbox/utils/strings';
|
import { capitalize } from 'soapbox/utils/strings';
|
||||||
|
|
||||||
import ProfileFamiliarFollowers from './profile-familiar-followers';
|
import ProfileFamiliarFollowers from './profile-familiar-followers';
|
||||||
|
import ProfileField from './profile-field';
|
||||||
import ProfileStats from './profile-stats';
|
import ProfileStats from './profile-stats';
|
||||||
|
|
||||||
import type { Account } from 'soapbox/types/entities';
|
import type { Account } from 'soapbox/types/entities';
|
||||||
|
@ -231,6 +232,14 @@ const ProfileInfoPanel: React.FC<IProfileInfoPanel> = ({ account, username }) =>
|
||||||
|
|
||||||
<ProfileFamiliarFollowers account={account} />
|
<ProfileFamiliarFollowers account={account} />
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
|
{account.fields.size > 0 && (
|
||||||
|
<Stack space={2} className='mt-4 xl:hidden'>
|
||||||
|
{account.fields.map((field, i) => (
|
||||||
|
<ProfileField field={field} key={i} />
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue