- Changed placeholder text in WelcomeForm to use more descriptive keys for leave content. - Enhanced DiscordEmbedBuilder to accept custom title and description placeholders, improving flexibility for embed creation. - Updated localization files in English and German to include new placeholder texts, ensuring consistency and clarity for users.
266 lines
12 KiB
TypeScript
266 lines
12 KiB
TypeScript
'use client';
|
|
|
|
import type { WelcomeConfigDashboard, WelcomeEmbed } from '@nexumi/shared';
|
|
import { FieldAnchor } from '@/components/layout/field-anchor';
|
|
import { useTranslations } from '@/components/locale-provider';
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { DiscordChannelSelect } from '@/components/ui/discord-channel-select';
|
|
import {
|
|
DiscordEmbedBuilder,
|
|
embedFromUnknown,
|
|
normalizeEmbed
|
|
} from '@/components/ui/discord-embed-builder';
|
|
import { DiscordRoleMultiSelect } from '@/components/ui/discord-role-select';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import { PlaceholderHelp } from '@/components/ui/placeholder-help';
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
|
import { Switch } from '@/components/ui/switch';
|
|
import { Textarea } from '@/components/ui/textarea';
|
|
import type { SettingsSaveResult } from '@/components/settings/settings-form';
|
|
import { SettingsForm } from '@/components/settings/settings-form';
|
|
|
|
const WELCOME_PREVIEW_VARS = {
|
|
user: '@Alex',
|
|
'user.name': 'Alex',
|
|
'user.tag': 'Alex',
|
|
'user.id': '123456789012345678',
|
|
server: 'Nexumi',
|
|
memberCount: '128'
|
|
};
|
|
|
|
interface LocalWelcomeValue extends Omit<WelcomeConfigDashboard, 'welcomeEmbed' | 'leaveEmbed'> {
|
|
welcomeEmbed: WelcomeEmbed | null;
|
|
leaveEmbed: WelcomeEmbed | null;
|
|
}
|
|
|
|
function toLocal(config: WelcomeConfigDashboard): LocalWelcomeValue {
|
|
return {
|
|
...config,
|
|
welcomeEmbed: embedFromUnknown(config.welcomeEmbed),
|
|
leaveEmbed: embedFromUnknown(config.leaveEmbed)
|
|
};
|
|
}
|
|
|
|
async function saveWelcome(guildId: string, value: LocalWelcomeValue): Promise<SettingsSaveResult> {
|
|
const payload: WelcomeConfigDashboard = {
|
|
...value,
|
|
welcomeEmbed: normalizeEmbed(value.welcomeEmbed),
|
|
leaveEmbed: normalizeEmbed(value.leaveEmbed)
|
|
};
|
|
|
|
try {
|
|
const response = await fetch(`/api/guilds/${guildId}/welcome`, {
|
|
method: 'PATCH',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload)
|
|
});
|
|
if (!response.ok) {
|
|
const body = (await response.json().catch(() => null)) as { error?: string } | null;
|
|
return { ok: false, error: body?.error ?? `Request failed with status ${response.status}` };
|
|
}
|
|
return { ok: true };
|
|
} catch {
|
|
return { ok: false, error: 'Network error' };
|
|
}
|
|
}
|
|
|
|
interface WelcomeFormProps {
|
|
guildId: string;
|
|
initialValue: WelcomeConfigDashboard;
|
|
}
|
|
|
|
export function WelcomeForm({ guildId, initialValue }: WelcomeFormProps) {
|
|
const t = useTranslations();
|
|
|
|
return (
|
|
<SettingsForm<LocalWelcomeValue> initialValue={toLocal(initialValue)} onSave={(value) => saveWelcome(guildId, value)}>
|
|
{({ value, setValue }) => (
|
|
<div className="space-y-6">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>{t('modulePages.welcome.welcomeTitle')}</CardTitle>
|
|
<CardDescription>{t('modulePages.welcome.welcomeDescription')}</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<FieldAnchor id="field-welcome-enabled">
|
|
<div className="flex items-center justify-between rounded-md border border-border p-4">
|
|
<Label>{t('modulePages.welcome.welcomeEnabledLabel')}</Label>
|
|
<Switch
|
|
checked={value.welcomeEnabled}
|
|
onCheckedChange={(checked) => setValue((prev) => ({ ...prev, welcomeEnabled: checked }))}
|
|
/>
|
|
</div>
|
|
</FieldAnchor>
|
|
<div className="grid gap-4 sm:grid-cols-2">
|
|
<FieldAnchor id="field-welcome-channel">
|
|
<div className="space-y-2">
|
|
<Label>{t('modulePages.welcome.welcomeChannelId')}</Label>
|
|
<DiscordChannelSelect
|
|
guildId={guildId}
|
|
value={value.welcomeChannelId}
|
|
onChange={(welcomeChannelId) => setValue((prev) => ({ ...prev, welcomeChannelId }))}
|
|
/>
|
|
</div>
|
|
</FieldAnchor>
|
|
<FieldAnchor id="field-welcome-type">
|
|
<div className="space-y-2">
|
|
<Label>{t('modulePages.welcome.welcomeType')}</Label>
|
|
<Select
|
|
value={value.welcomeType}
|
|
onValueChange={(welcomeType) =>
|
|
setValue((prev) => ({ ...prev, welcomeType: welcomeType as WelcomeConfigDashboard['welcomeType'] }))
|
|
}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="TEXT">{t('modulePages.welcome.type.TEXT')}</SelectItem>
|
|
<SelectItem value="EMBED">{t('modulePages.welcome.type.EMBED')}</SelectItem>
|
|
<SelectItem value="IMAGE">{t('modulePages.welcome.type.IMAGE')}</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
</FieldAnchor>
|
|
</div>
|
|
<FieldAnchor id="field-welcome-content">
|
|
<div className="space-y-2">
|
|
<Label>{t('modulePages.welcome.welcomeContent')}</Label>
|
|
<Textarea
|
|
value={value.welcomeContent ?? ''}
|
|
onChange={(event) => setValue((prev) => ({ ...prev, welcomeContent: event.target.value || null }))}
|
|
placeholder={t('modulePages.welcome.contentPlaceholder')}
|
|
/>
|
|
<PlaceholderHelp preset="welcome" />
|
|
</div>
|
|
</FieldAnchor>
|
|
<FieldAnchor id="field-welcome-embed">
|
|
<div className="space-y-2">
|
|
<Label>{t('modulePages.welcome.welcomeEmbed')}</Label>
|
|
<DiscordEmbedBuilder
|
|
value={value.welcomeEmbed}
|
|
onChange={(welcomeEmbed) => setValue((prev) => ({ ...prev, welcomeEmbed }))}
|
|
previewVars={WELCOME_PREVIEW_VARS}
|
|
placeholderPreset="welcome"
|
|
/>
|
|
</div>
|
|
</FieldAnchor>
|
|
<FieldAnchor id="field-welcome-dm">
|
|
<div className="space-y-2">
|
|
<div className="flex items-center justify-between rounded-md border border-border p-4">
|
|
<Label>{t('modulePages.welcome.welcomeDmEnabled')}</Label>
|
|
<Switch
|
|
checked={value.welcomeDmEnabled}
|
|
onCheckedChange={(checked) => setValue((prev) => ({ ...prev, welcomeDmEnabled: checked }))}
|
|
/>
|
|
</div>
|
|
<Textarea
|
|
value={value.welcomeDmContent ?? ''}
|
|
onChange={(event) => setValue((prev) => ({ ...prev, welcomeDmContent: event.target.value || null }))}
|
|
placeholder={t('modulePages.welcome.dmContentPlaceholder')}
|
|
/>
|
|
<PlaceholderHelp preset="welcomeDm" />
|
|
</div>
|
|
</FieldAnchor>
|
|
<FieldAnchor id="field-welcome-user-autoroles">
|
|
<div className="space-y-2">
|
|
<Label>{t('modulePages.welcome.userAutoroles')}</Label>
|
|
<DiscordRoleMultiSelect
|
|
guildId={guildId}
|
|
value={value.userAutoroleIds}
|
|
onChange={(userAutoroleIds) => setValue((prev) => ({ ...prev, userAutoroleIds }))}
|
|
/>
|
|
</div>
|
|
</FieldAnchor>
|
|
<FieldAnchor id="field-welcome-bot-autoroles">
|
|
<div className="space-y-2">
|
|
<Label>{t('modulePages.welcome.botAutoroles')}</Label>
|
|
<DiscordRoleMultiSelect
|
|
guildId={guildId}
|
|
value={value.botAutoroleIds}
|
|
onChange={(botAutoroleIds) => setValue((prev) => ({ ...prev, botAutoroleIds }))}
|
|
/>
|
|
</div>
|
|
</FieldAnchor>
|
|
<div className="grid gap-4 sm:grid-cols-2">
|
|
<FieldAnchor id="field-welcome-image-title">
|
|
<div className="space-y-2">
|
|
<Label>{t('modulePages.welcome.imageTitle')}</Label>
|
|
<Input
|
|
value={value.imageTitle ?? ''}
|
|
onChange={(event) => setValue((prev) => ({ ...prev, imageTitle: event.target.value || null }))}
|
|
/>
|
|
</div>
|
|
</FieldAnchor>
|
|
<FieldAnchor id="field-welcome-image-subtitle">
|
|
<div className="space-y-2">
|
|
<Label>{t('modulePages.welcome.imageSubtitle')}</Label>
|
|
<Input
|
|
value={value.imageSubtitle ?? ''}
|
|
onChange={(event) => setValue((prev) => ({ ...prev, imageSubtitle: event.target.value || null }))}
|
|
/>
|
|
</div>
|
|
</FieldAnchor>
|
|
</div>
|
|
<PlaceholderHelp preset="welcome" />
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>{t('modulePages.welcome.leaveTitle')}</CardTitle>
|
|
<CardDescription>{t('modulePages.welcome.leaveDescription')}</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<FieldAnchor id="field-leave-enabled">
|
|
<div className="flex items-center justify-between rounded-md border border-border p-4">
|
|
<Label>{t('modulePages.welcome.leaveEnabledLabel')}</Label>
|
|
<Switch
|
|
checked={value.leaveEnabled}
|
|
onCheckedChange={(checked) => setValue((prev) => ({ ...prev, leaveEnabled: checked }))}
|
|
/>
|
|
</div>
|
|
</FieldAnchor>
|
|
<FieldAnchor id="field-leave-channel">
|
|
<div className="space-y-2">
|
|
<Label>{t('modulePages.welcome.leaveChannelId')}</Label>
|
|
<DiscordChannelSelect
|
|
guildId={guildId}
|
|
value={value.leaveChannelId}
|
|
onChange={(leaveChannelId) => setValue((prev) => ({ ...prev, leaveChannelId }))}
|
|
/>
|
|
</div>
|
|
</FieldAnchor>
|
|
<FieldAnchor id="field-leave-content">
|
|
<div className="space-y-2">
|
|
<Label>{t('modulePages.welcome.leaveContent')}</Label>
|
|
<Textarea
|
|
value={value.leaveContent ?? ''}
|
|
onChange={(event) => setValue((prev) => ({ ...prev, leaveContent: event.target.value || null }))}
|
|
placeholder={t('modulePages.welcome.leaveContentPlaceholder')}
|
|
/>
|
|
<PlaceholderHelp preset="welcome" />
|
|
</div>
|
|
</FieldAnchor>
|
|
<FieldAnchor id="field-leave-embed">
|
|
<div className="space-y-2">
|
|
<Label>{t('modulePages.welcome.leaveEmbed')}</Label>
|
|
<DiscordEmbedBuilder
|
|
value={value.leaveEmbed}
|
|
onChange={(leaveEmbed) => setValue((prev) => ({ ...prev, leaveEmbed }))}
|
|
previewVars={WELCOME_PREVIEW_VARS}
|
|
placeholderPreset="welcome"
|
|
titlePlaceholder={t('modulePages.welcome.leaveEmbedTitlePlaceholder')}
|
|
descriptionPlaceholder={t('modulePages.welcome.leaveEmbedDescriptionPlaceholder')}
|
|
/>
|
|
</div>
|
|
</FieldAnchor>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
)}
|
|
</SettingsForm>
|
|
);
|
|
}
|