feat(cosmetics): оформление профиля — галерея, роли и личные стили

- миграция 00013: guild_cosmetics (рамки и иконки) и user_styles («для друзей»
  и пер-серверные стили) (AGENT.md 7.2, 7.4)
- store: CRUD галереи, личные стили и вычисление оформления поэлементно
  (роль → пер-серверный стиль → «для друзей» → дефолт) с откатом, когда роль
  больше не выдаёт элемент
- API: галерея сервера под MANAGE_ROLES, оформление роли в create/update,
  GET/PUT/DELETE /users/@me/styles с доступными элементами по областям
- клиент: редактор личного стиля с предпросмотром, галерея оформления в
  настройках сервера, оформление роли в разделе «Роли»
- рендер: рамка под аватаром, иконка рядом с ником, цвет и встроенные эффекты
  ника (CSS, при reduced-motion статично) в ленте и списке участников
This commit is contained in:
2026-09-21 01:28:48 +03:00
parent 2e038a1d3f
commit 48e30a1897
25 changed files with 3542 additions and 41 deletions
+20 -1
View File
@@ -12,12 +12,14 @@ import {
canAdministerGuild,
canBanMembers,
canManageEmojis,
canManageRoles,
canManageSounds,
canViewAuditLog,
} from '@/lib/identity';
import { useCurrentUser } from '@/lib/hooks';
import { useSessionStore } from '@/stores/session';
import { GuildAppearanceSection } from '@/pages/settings/GuildAppearanceSection';
import { GuildCosmeticsSection } from '@/pages/settings/GuildCosmeticsSection';
import { GuildDangerSection } from '@/pages/settings/GuildDangerSection';
import { GuildEmojisSection } from '@/pages/settings/GuildEmojisSection';
import { GuildGeneralSection } from '@/pages/settings/GuildGeneralSection';
@@ -180,6 +182,14 @@ function GuildSettingsBody({ guilds }: { guilds: { id: string }[] }) {
currentUser.data?.is_instance_admin === true,
);
// Галерея оформления и рамки/иконки ролей (AGENT.md 7.4) — MANAGE_ROLES.
const canManageRoleSet = canManageRoles(
guild.my_permissions,
guild.owner_id,
currentUser.data?.id,
currentUser.data?.is_instance_admin === true,
);
// «Звуки» (AGENT.md 7.13) — та же логика для права MANAGE_SOUNDS.
const canManageSoundSet = canManageSounds(
guild.my_permissions,
@@ -203,7 +213,16 @@ function GuildSettingsBody({ guilds }: { guilds: { id: string }[] }) {
canTimeout={canModerateMembers('TIMEOUT_MEMBERS')}
currentUserId={currentUser.data?.id}
/>
<GuildRolesSection key={`${guild.id}-roles`} guildId={guild.id} />
<GuildCosmeticsSection
key={`${guild.id}-cosmetics`}
guildId={guild.id}
canManage={canManageRoleSet}
/>
<GuildRolesSection
key={`${guild.id}-roles`}
guildId={guild.id}
canManage={canManageRoleSet}
/>
{canManageEmojiSet ? (
<GuildEmojisSection key={`${guild.id}-emojis`} guildId={guild.id} />
) : null}