96 lines
3.5 KiB
TypeScript
96 lines
3.5 KiB
TypeScript
|
|
import { useState, type FormEvent } from 'react';
|
||
|
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||
|
|
import { useTranslation } from 'react-i18next';
|
||
|
|
|
||
|
|
import { guildQueryKey, updateGuild } from '@/api/guilds';
|
||
|
|
import { myGuildsQueryKey } from '@/api/users';
|
||
|
|
import type { Guild } from '@/api/types';
|
||
|
|
import { ErrorNotice } from '@/components/ui/ErrorNotice';
|
||
|
|
import { Field, TextAreaField } from '@/components/ui/Field';
|
||
|
|
import { Button } from '@/components/ui/primitives';
|
||
|
|
import { useSessionStore } from '@/stores/session';
|
||
|
|
import { SettingsSection } from '@/pages/settings/SettingsSection';
|
||
|
|
|
||
|
|
interface GuildGeneralSectionProps {
|
||
|
|
guild: Guild;
|
||
|
|
/** Владелец или участник с правами управления — иначе поля только для чтения. */
|
||
|
|
canEdit: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Общее в настройках сервера: имя и описание (PATCH /guilds/{id}). */
|
||
|
|
export function GuildGeneralSection({ guild, canEdit }: GuildGeneralSectionProps) {
|
||
|
|
const { t } = useTranslation();
|
||
|
|
const queryClient = useQueryClient();
|
||
|
|
const [name, setName] = useState(guild.name);
|
||
|
|
const [description, setDescription] = useState(guild.description);
|
||
|
|
const [saved, setSaved] = useState(false);
|
||
|
|
|
||
|
|
const save = useMutation({
|
||
|
|
mutationFn: () =>
|
||
|
|
updateGuild(guild.id, { name: name.trim(), description: description.trim() }),
|
||
|
|
onSuccess: (updated) => {
|
||
|
|
queryClient.setQueryData(guildQueryKey(guild.id), updated);
|
||
|
|
// Рейка и сайдбар берут имя из снапшота — обновляем и его.
|
||
|
|
useSessionStore.getState().renameGuild(guild.id, updated.name);
|
||
|
|
void queryClient.invalidateQueries({ queryKey: guildQueryKey(guild.id) });
|
||
|
|
void queryClient.invalidateQueries({ queryKey: myGuildsQueryKey });
|
||
|
|
setSaved(true);
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
const onSubmit = (event: FormEvent<HTMLFormElement>): void => {
|
||
|
|
event.preventDefault();
|
||
|
|
if (!canEdit || name.trim() === '') {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
setSaved(false);
|
||
|
|
save.mutate();
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<SettingsSection
|
||
|
|
title={t('settings.servers.general.title')}
|
||
|
|
description={t('settings.servers.general.description')}
|
||
|
|
>
|
||
|
|
<form className="mt-4 flex flex-col gap-4" onSubmit={onSubmit} noValidate>
|
||
|
|
<Field
|
||
|
|
label={t('settings.servers.general.name')}
|
||
|
|
name="guild_name"
|
||
|
|
value={name}
|
||
|
|
maxLength={64}
|
||
|
|
disabled={!canEdit}
|
||
|
|
onChange={(event) => setName(event.target.value)}
|
||
|
|
/>
|
||
|
|
<TextAreaField
|
||
|
|
label={t('settings.servers.general.guildDescription')}
|
||
|
|
placeholder={t('settings.servers.general.descriptionPlaceholder')}
|
||
|
|
name="guild_description"
|
||
|
|
rows={3}
|
||
|
|
maxLength={500}
|
||
|
|
value={description}
|
||
|
|
disabled={!canEdit}
|
||
|
|
onChange={(event) => setDescription(event.target.value)}
|
||
|
|
/>
|
||
|
|
{canEdit ? null : (
|
||
|
|
<p className="text-xs text-fg-muted">{t('settings.servers.readOnlyHint')}</p>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{save.isError ? <ErrorNotice error={save.error} /> : null}
|
||
|
|
{saved && !save.isError ? (
|
||
|
|
<p role="status" className="text-sm text-success">
|
||
|
|
{t('settings.servers.general.saved')}
|
||
|
|
</p>
|
||
|
|
) : null}
|
||
|
|
|
||
|
|
{canEdit ? (
|
||
|
|
<div className="flex justify-end">
|
||
|
|
<Button type="submit" disabled={save.isPending || name.trim() === ''}>
|
||
|
|
{t(save.isPending ? 'common.saving' : 'common.save')}
|
||
|
|
</Button>
|
||
|
|
</div>
|
||
|
|
) : null}
|
||
|
|
</form>
|
||
|
|
</SettingsSection>
|
||
|
|
);
|
||
|
|
}
|