Enhance welcome module with embed text rendering and autorole management

- Updated the welcome embed functionality to allow for user mentions in titles, authors, and footers, while maintaining clickable mentions in descriptions.
- Implemented autorole assignment logic with checks for role manageability and hierarchy, including detailed logging for skipped roles.
- Improved the WebUI to display real user and guild data in the welcome embed preview, enhancing user experience.
- Refactored the welcome text rendering to support additional options for mention handling based on the context of the embed fields.
This commit is contained in:
smueller
2026-07-24 10:54:33 +02:00
parent ccdf9aafe8
commit 0ebe540c3f
9 changed files with 234 additions and 52 deletions

View File

@@ -1,6 +1,12 @@
'use client';
import type { MessageComponentsV2, WelcomeConfigDashboard, WelcomeEmbed } from '@nexumi/shared';
import type {
DashboardGuild,
MessageComponentsV2,
SessionUser,
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';
@@ -25,16 +31,44 @@ 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',
'user.avatar': 'https://cdn.discordapp.com/embed/avatars/0.png',
server: 'Nexumi',
'server.icon': 'https://cdn.discordapp.com/embed/avatars/1.png',
memberCount: '128'
};
function discordDefaultAvatarUrl(userId: string): string {
try {
const index = Number((BigInt(userId) >> 22n) % 6n);
return `https://cdn.discordapp.com/embed/avatars/${index}.png`;
} catch {
return 'https://cdn.discordapp.com/embed/avatars/0.png';
}
}
function userAvatarUrl(user: SessionUser): string {
return user.avatar
? `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png?size=256`
: discordDefaultAvatarUrl(user.id);
}
function guildIconUrl(guild: DashboardGuild): string {
return guild.icon
? `https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}.png?size=128`
: discordDefaultAvatarUrl(guild.id);
}
/** Preview placeholders from the logged-in dashboard user and current guild. */
export function buildWelcomePreviewVars(
user: SessionUser,
guild: DashboardGuild
): Record<string, string> {
const displayName = user.globalName?.trim() || user.username;
return {
user: `@${displayName}`,
'user.name': displayName,
'user.tag': user.username,
'user.id': user.id,
'user.avatar': userAvatarUrl(user),
server: guild.name,
'server.icon': guildIconUrl(guild),
memberCount: '—'
};
}
interface LocalWelcomeValue extends Omit<
WelcomeConfigDashboard,
@@ -85,9 +119,10 @@ async function saveWelcome(guildId: string, value: LocalWelcomeValue): Promise<S
interface WelcomeFormProps {
guildId: string;
initialValue: WelcomeConfigDashboard;
previewVars: Record<string, string>;
}
export function WelcomeForm({ guildId, initialValue }: WelcomeFormProps) {
export function WelcomeForm({ guildId, initialValue, previewVars }: WelcomeFormProps) {
const t = useTranslations();
return (
@@ -162,7 +197,7 @@ export function WelcomeForm({ guildId, initialValue }: WelcomeFormProps) {
<DiscordEmbedBuilder
value={value.welcomeEmbed}
onChange={(welcomeEmbed) => setValue((prev) => ({ ...prev, welcomeEmbed }))}
previewVars={WELCOME_PREVIEW_VARS}
previewVars={previewVars}
placeholderPreset="welcome"
/>
</div>
@@ -176,7 +211,7 @@ export function WelcomeForm({ guildId, initialValue }: WelcomeFormProps) {
guildId={guildId}
value={value.welcomeComponents}
onChange={(welcomeComponents) => setValue((prev) => ({ ...prev, welcomeComponents }))}
previewVars={WELCOME_PREVIEW_VARS}
previewVars={previewVars}
placeholderPreset="welcome"
/>
</div>
@@ -312,7 +347,7 @@ export function WelcomeForm({ guildId, initialValue }: WelcomeFormProps) {
<DiscordEmbedBuilder
value={value.leaveEmbed}
onChange={(leaveEmbed) => setValue((prev) => ({ ...prev, leaveEmbed }))}
previewVars={WELCOME_PREVIEW_VARS}
previewVars={previewVars}
placeholderPreset="welcome"
titlePlaceholder={t('modulePages.welcome.leaveEmbedTitlePlaceholder')}
descriptionPlaceholder={t('modulePages.welcome.leaveEmbedDescriptionPlaceholder')}
@@ -328,7 +363,7 @@ export function WelcomeForm({ guildId, initialValue }: WelcomeFormProps) {
guildId={guildId}
value={value.leaveComponents}
onChange={(leaveComponents) => setValue((prev) => ({ ...prev, leaveComponents }))}
previewVars={WELCOME_PREVIEW_VARS}
previewVars={previewVars}
placeholderPreset="welcome"
/>
</div>