68 lines
2.6 KiB
TypeScript
68 lines
2.6 KiB
TypeScript
|
|
import { useQuery } from '@tanstack/react-query';
|
||
|
|
import { useTranslation } from 'react-i18next';
|
||
|
|
|
||
|
|
import { fetchRoles, guildRolesQueryKey } from '@/api/guilds';
|
||
|
|
import { ErrorNotice } from '@/components/ui/ErrorNotice';
|
||
|
|
import { SkeletonLines } from '@/components/ui/Skeleton';
|
||
|
|
import { roleColorHex } from '@/lib/format';
|
||
|
|
import { SettingsSection } from '@/pages/settings/SettingsSection';
|
||
|
|
|
||
|
|
interface GuildRolesSectionProps {
|
||
|
|
guildId: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** «Роли»: цвет и позиция. В Фазе 1 — только чтение. */
|
||
|
|
export function GuildRolesSection({ guildId }: GuildRolesSectionProps) {
|
||
|
|
const { t } = useTranslation();
|
||
|
|
|
||
|
|
const roles = useQuery({
|
||
|
|
queryKey: guildRolesQueryKey(guildId),
|
||
|
|
queryFn: ({ signal }) => fetchRoles(guildId, signal),
|
||
|
|
retry: 0,
|
||
|
|
});
|
||
|
|
|
||
|
|
const sorted = (roles.data ?? []).slice().sort((left, right) => right.position - left.position);
|
||
|
|
|
||
|
|
return (
|
||
|
|
<SettingsSection
|
||
|
|
title={t('settings.servers.roles.title')}
|
||
|
|
description={t('settings.servers.roles.description')}
|
||
|
|
>
|
||
|
|
{roles.isPending ? (
|
||
|
|
<SkeletonLines className="mt-4" rows={3} />
|
||
|
|
) : roles.isError ? (
|
||
|
|
<ErrorNotice className="mt-4" error={roles.error} onRetry={() => void roles.refetch()} />
|
||
|
|
) : sorted.length === 0 ? (
|
||
|
|
<p className="mt-4 text-sm text-fg-muted">{t('settings.servers.roles.empty')}</p>
|
||
|
|
) : (
|
||
|
|
<ul className="gl-stagger mt-4 flex flex-col gap-1" data-testid="guild-roles">
|
||
|
|
{sorted.map((role) => {
|
||
|
|
const color = roleColorHex(role.color);
|
||
|
|
return (
|
||
|
|
<li
|
||
|
|
key={role.id}
|
||
|
|
className="flex items-center gap-3 rounded-[var(--radius-md)] px-2 py-1.5 transition-colors duration-[var(--duration-fast)] hover:bg-surface-2"
|
||
|
|
>
|
||
|
|
<span
|
||
|
|
aria-hidden="true"
|
||
|
|
className="h-3.5 w-3.5 shrink-0 rounded-full border border-border/60"
|
||
|
|
style={color === null ? undefined : { backgroundColor: color }}
|
||
|
|
/>
|
||
|
|
<span className="min-w-0 flex-1 truncate text-sm">{role.name}</span>
|
||
|
|
{role.is_default ? (
|
||
|
|
<span className="text-xs text-fg-muted">
|
||
|
|
{t('settings.servers.roles.default')}
|
||
|
|
</span>
|
||
|
|
) : null}
|
||
|
|
<span className="text-xs text-fg-muted">
|
||
|
|
{t('settings.servers.roles.position', { position: role.position })}
|
||
|
|
</span>
|
||
|
|
</li>
|
||
|
|
);
|
||
|
|
})}
|
||
|
|
</ul>
|
||
|
|
)}
|
||
|
|
</SettingsSection>
|
||
|
|
);
|
||
|
|
}
|