Files
glchat/web/src/pages/settings/GuildSettingsPage.tsx
T

178 lines
6.1 KiB
TypeScript
Raw Normal View History

import { useEffect, useMemo } from 'react';
import { useQuery } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
import { fetchGuild, fetchMembers, guildMembersQueryKey, guildQueryKey } from '@/api/guilds';
import { fetchMyGuilds, myGuildsQueryKey } from '@/api/users';
import { ErrorNotice } from '@/components/ui/ErrorNotice';
import { Card } from '@/components/ui/primitives';
import { SkeletonLines } from '@/components/ui/Skeleton';
import { canAdministerGuild, canManageEmojis } from '@/lib/identity';
import { useCurrentUser } from '@/lib/hooks';
import { useSessionStore } from '@/stores/session';
import { GuildDangerSection } from '@/pages/settings/GuildDangerSection';
import { GuildEmojisSection } from '@/pages/settings/GuildEmojisSection';
import { GuildGeneralSection } from '@/pages/settings/GuildGeneralSection';
import { GuildMembersSection } from '@/pages/settings/GuildMembersSection';
import { GuildPicker } from '@/pages/settings/GuildPicker';
import { GuildRolesSection } from '@/pages/settings/GuildRolesSection';
/**
* Пункт «Сервер» окна настроек: администрирование одного сервера —
* общее (имя и описание), участники, роли, эмодзи и «Опасная зона».
*
* В отличие от вкладки «Профиль → Сервер» (там только свой никнейм), сюда
* попадают через шестерёнку в шапке сервера или через пункт меню настроек.
* Сервер можно переключить тем же выпадающим списком.
*/
export function GuildSettingsPage() {
const sessionGuilds = useSessionStore((state) => state.guilds);
const upsertGuild = useSessionStore((state) => state.upsertGuild);
const selectSettingsGuild = useSessionStore((state) => state.selectSettingsGuild);
// Подстраховка снапшота: без неё выбранный сервер не найти в сторе.
const myGuilds = useQuery({
queryKey: myGuildsQueryKey,
queryFn: ({ signal }) => fetchMyGuilds(signal),
retry: 0,
enabled: sessionGuilds.length === 0,
});
useEffect(() => {
if (sessionGuilds.length > 0 || myGuilds.data === undefined) {
return;
}
for (const guild of myGuilds.data) {
upsertGuild(guild);
}
}, [sessionGuilds.length, myGuilds.data, upsertGuild]);
const guilds = useMemo(
() => (sessionGuilds.length > 0 ? sessionGuilds : (myGuilds.data ?? [])),
[sessionGuilds, myGuilds.data],
);
return (
<div className="flex flex-col gap-5">
<GuildPicker
guilds={guilds}
isPending={myGuilds.isPending && guilds.length === 0}
onSelect={selectSettingsGuild}
/>
<GuildSettingsBody guilds={guilds} />
{myGuilds.isError ? (
<Card>
<ErrorNotice error={myGuilds.error} onRetry={() => void myGuilds.refetch()} />
</Card>
) : null}
</div>
);
}
/** Секции выбранного сервера: данные тянутся по REST только для него. */
function GuildSettingsBody({ guilds }: { guilds: { id: string }[] }) {
const { t } = useTranslation();
const currentUser = useCurrentUser();
const selectedGuildId = useSessionStore((state) => state.settingsGuildId);
const selectSettingsGuild = useSessionStore((state) => state.selectSettingsGuild);
const active = useMemo(
() => guilds.find((guild) => guild.id === selectedGuildId) ?? null,
[guilds, selectedGuildId],
);
// Первый доступный сервер выбираем сами: список только что загрузился.
useEffect(() => {
if (active !== null || guilds.length === 0) {
return;
}
const first = guilds[0];
if (first !== undefined) {
selectSettingsGuild(first.id);
}
}, [active, guilds, selectSettingsGuild]);
const activeId = active?.id ?? null;
const detail = useQuery({
queryKey: guildQueryKey(activeId ?? ''),
queryFn: ({ signal }) => fetchGuild(activeId ?? '', signal),
enabled: activeId !== null,
retry: 0,
});
const members = useQuery({
queryKey: guildMembersQueryKey(activeId ?? ''),
queryFn: ({ signal }) => fetchMembers(activeId ?? '', signal),
enabled: activeId !== null,
retry: 0,
});
if (activeId === null) {
return (
<Card>
<p className="text-sm text-fg-muted">{t('settings.servers.notFound')}</p>
</Card>
);
}
if (detail.isPending) {
return (
<Card>
<SkeletonLines rows={5} />
</Card>
);
}
const guild = detail.data;
if (guild === undefined) {
return (
<Card>
<ErrorNotice error={detail.error} onRetry={() => void detail.refetch()} />
</Card>
);
}
const canManage = canAdministerGuild(
guild.my_permissions,
guild.owner_id,
currentUser.data?.id,
currentUser.data?.is_instance_admin === true,
);
// «Эмодзи» сервер разрешает владельцу, админу инстанса, ADMINISTRATOR
// и MANAGE_EMOJIS: остальным секция не показывается.
const canManageEmojiSet = canManageEmojis(
guild.my_permissions,
guild.owner_id,
currentUser.data?.id,
currentUser.data?.is_instance_admin === true,
);
return (
<>
<GuildGeneralSection key={guild.id} guild={guild} canEdit={canManage} />
<GuildMembersSection
members={members.data}
isPending={members.isPending}
error={members.error}
onRetry={() => void members.refetch()}
/>
<GuildRolesSection key={`${guild.id}-roles`} guildId={guild.id} />
{canManageEmojiSet ? (
<GuildEmojisSection key={`${guild.id}-emojis`} guildId={guild.id} />
) : null}
<GuildDangerSection
key={`${guild.id}-danger`}
guildId={guild.id}
guildName={guild.name}
isOwner={currentUser.data?.id === guild.owner_id}
isInstanceAdmin={currentUser.data?.is_instance_admin === true}
/>
</>
);
}
export default GuildSettingsPage;