[Glitch] Profile redesign: Additional badges

Port 1100035af4bb14676870e4a0d1fabcd88c4e9baf to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
This commit is contained in:
Echo 2026-02-02 11:23:08 +01:00 committed by Claire
parent 5b926157e8
commit 7dad362e29
5 changed files with 240 additions and 33 deletions

View File

@ -0,0 +1,64 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import CelebrationIcon from '@/material-icons/400-24px/celebration-fill.svg?react';
import * as badges from './badge';
const meta = {
component: badges.Badge,
title: 'Components/Badge',
args: {
label: 'Example',
},
} satisfies Meta<typeof badges.Badge>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};
export const Domain: Story = {
args: {
domain: 'example.com',
},
};
export const CustomIcon: Story = {
args: {
icon: <CelebrationIcon />,
},
};
export const Admin: Story = {
args: {
roleId: '1',
},
render(args) {
return <badges.AdminBadge {...args} />;
},
};
export const Group: Story = {
render(args) {
return <badges.GroupBadge {...args} />;
},
};
export const Automated: Story = {
render(args) {
return <badges.AutomatedBadge {...args} />;
},
};
export const Muted: Story = {
render(args) {
return <badges.MutedBadge {...args} />;
},
};
export const Blocked: Story = {
render(args) {
return <badges.BlockedBadge {...args} />;
},
};

View File

@ -4,17 +4,28 @@ import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import AdminIcon from '@/images/icons/icon_admin.svg?react';
import BlockIcon from '@/material-icons/400-24px/block.svg?react';
import GroupsIcon from '@/material-icons/400-24px/group.svg?react';
import PersonIcon from '@/material-icons/400-24px/person.svg?react';
import SmartToyIcon from '@/material-icons/400-24px/smart_toy.svg?react';
import VolumeOffIcon from '@/material-icons/400-24px/volume_off.svg?react';
export const Badge: FC<{
interface BadgeProps {
label: ReactNode;
icon?: ReactNode;
className?: string;
domain?: ReactNode;
roleId?: string;
}> = ({ icon = <PersonIcon />, label, className, domain, roleId }) => (
}
export const Badge: FC<BadgeProps> = ({
icon = <PersonIcon />,
label,
className,
domain,
roleId,
}) => (
<div
className={classNames('account-role', className)}
data-account-role-id={roleId}
@ -25,13 +36,23 @@ export const Badge: FC<{
</div>
);
export const GroupBadge: FC<{ className?: string }> = ({ className }) => (
export const AdminBadge: FC<Partial<BadgeProps>> = (props) => (
<Badge
icon={<AdminIcon />}
label={
<FormattedMessage id='account.badges.admin' defaultMessage='Admin' />
}
{...props}
/>
);
export const GroupBadge: FC<Partial<BadgeProps>> = (props) => (
<Badge
icon={<GroupsIcon />}
label={
<FormattedMessage id='account.badges.group' defaultMessage='Group' />
}
className={className}
{...props}
/>
);
@ -44,3 +65,23 @@ export const AutomatedBadge: FC<{ className?: string }> = ({ className }) => (
className={className}
/>
);
export const MutedBadge: FC<Partial<BadgeProps>> = (props) => (
<Badge
icon={<VolumeOffIcon />}
label={
<FormattedMessage id='account.badges.muted' defaultMessage='Muted' />
}
{...props}
/>
);
export const BlockedBadge: FC<Partial<BadgeProps>> = (props) => (
<Badge
icon={<BlockIcon />}
label={
<FormattedMessage id='account.badges.blocked' defaultMessage='Blocked' />
}
{...props}
/>
);

View File

@ -105,7 +105,7 @@ export const AccountHeader: React.FC<{
)}
<div className='account__header__image'>
{me !== account.id && relationship && (
{me !== account.id && relationship && !isRedesignEnabled() && (
<AccountInfo relationship={relationship} />
)}

View File

@ -1,15 +1,22 @@
import type { FC, ReactNode } from 'react';
import { useEffect } from 'react';
import type { FC } from 'react';
import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import { fetchRelationships } from '@/flavours/glitch/actions/accounts';
import {
AdminBadge,
AutomatedBadge,
Badge,
BlockedBadge,
GroupBadge,
MutedBadge,
} from '@/flavours/glitch/components/badge';
import { Icon } from '@/flavours/glitch/components/icon';
import { useAccount } from '@/flavours/glitch/hooks/useAccount';
import type { AccountRole } from '@/flavours/glitch/models/account';
import { useAppSelector } from '@/flavours/glitch/store';
import IconAdmin from '@/images/icons/icon_admin.svg?react';
import { useAppDispatch, useAppSelector } from '@/flavours/glitch/store';
import { isRedesignEnabled } from '../common';
@ -20,6 +27,17 @@ export const AccountBadges: FC<{ accountId: string }> = ({ accountId }) => {
const localDomain = useAppSelector(
(state) => state.meta.get('domain') as string,
);
const relationship = useAppSelector((state) =>
state.relationships.get(accountId),
);
const dispatch = useAppDispatch();
useEffect(() => {
if (!relationship) {
dispatch(fetchRelationships([accountId]));
}
}, [accountId, dispatch, relationship]);
const badges = [];
if (!account) {
@ -28,39 +46,113 @@ export const AccountBadges: FC<{ accountId: string }> = ({ accountId }) => {
const className = isRedesignEnabled() ? classes.badge : '';
if (account.bot) {
badges.push(<AutomatedBadge key='bot-badge' className={className} />);
} else if (account.group) {
badges.push(<GroupBadge key='group-badge' className={className} />);
}
const domain = account.acct.includes('@')
? account.acct.split('@')[1]
: localDomain;
account.roles.forEach((role) => {
let icon: ReactNode = undefined;
if (isAdminBadge(role)) {
icon = (
<Icon
icon={IconAdmin}
id='badge-admin'
className={classes.badgeIcon}
noFill
/>
badges.push(
<AdminBadge
key={role.id}
label={role.name}
className={className}
domain={`(${domain})`}
roleId={role.id}
/>,
);
} else {
badges.push(
<Badge
key={role.id}
label={role.name}
className={className}
domain={isRedesignEnabled() ? `(${domain})` : domain}
roleId={role.id}
/>,
);
}
badges.push(
<Badge
key={role.id}
label={role.name}
className={className}
domain={isRedesignEnabled() ? `(${domain})` : domain}
roleId={role.id}
icon={icon}
/>,
);
});
if (account.bot) {
badges.push(<AutomatedBadge key='bot-badge' className={className} />);
}
if (account.group) {
badges.push(<GroupBadge key='group-badge' className={className} />);
}
if (isRedesignEnabled() && relationship) {
if (relationship.blocking) {
badges.push(
<BlockedBadge
key='blocking'
className={classNames(className, classes.badgeBlocked)}
/>,
);
} else if (relationship.domain_blocking) {
badges.push(
<BlockedBadge
key='domain-blocking'
className={classNames(className, classes.badgeBlocked)}
domain={domain}
label={
<FormattedMessage
id='account.badges.domain_blocked'
defaultMessage='Blocked domain'
/>
}
/>,
);
} else if (relationship.muting) {
badges.push(
<MutedBadge
key='muted-badge'
className={classNames(className, classes.badgeMuted)}
/>,
);
} else if (
relationship.followed_by &&
(relationship.following || relationship.requested)
) {
badges.push(
<Badge
key='mutuals-badge'
label={
<FormattedMessage
id='account.badges.mutuals'
defaultMessage='You follow each other'
/>
}
className={className}
/>,
);
} else if (relationship.followed_by) {
badges.push(
<Badge
key='follows-you-badge'
label={
<FormattedMessage
id='account.badges.follows_you'
defaultMessage='Follows you'
/>
}
className={className}
/>,
);
} else if (relationship.requested_by) {
badges.push(
<Badge
key='requested-to-follow-badge'
label={
<FormattedMessage
id='account.badges.requested_to_follow'
defaultMessage='Requested to follow you'
/>
}
className={className}
/>,
);
}
}
if (!badges.length) {
return null;
}

View File

@ -57,6 +57,16 @@
}
}
.badgeMuted {
background-color: var(--color-bg-inverted);
color: var(--color-text-on-inverted);
}
.badgeBlocked {
background-color: var(--color-bg-error-base);
color: var(--color-text-on-error-base);
}
svg.badgeIcon {
opacity: 1;
}