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,4 +1,7 @@
import { WelcomeForm } from '@/components/modules/welcome-form';
import { notFound } from 'next/navigation';
import { buildWelcomePreviewVars, WelcomeForm } from '@/components/modules/welcome-form';
import { requireGuildAccessOrRedirect } from '@/lib/auth';
import { getManageableGuild } from '@/lib/guilds';
import { getLocale, t } from '@/lib/i18n';
import { getWelcomeDashboard } from '@/lib/module-configs/welcome';
@@ -8,7 +11,16 @@ interface WelcomePageProps {
export default async function WelcomePage({ params }: WelcomePageProps) {
const { guildId } = await params;
const [locale, config] = await Promise.all([getLocale(), getWelcomeDashboard(guildId)]);
const session = await requireGuildAccessOrRedirect(guildId);
const [locale, config, guild] = await Promise.all([
getLocale(),
getWelcomeDashboard(guildId),
getManageableGuild(session, guildId)
]);
if (!guild) {
notFound();
}
return (
<div className="space-y-6">
@@ -16,7 +28,11 @@ export default async function WelcomePage({ params }: WelcomePageProps) {
<h1 className="text-2xl font-semibold">{t(locale, 'modules.welcome.label')}</h1>
<p className="text-sm text-muted-foreground">{t(locale, 'modules.welcome.description')}</p>
</div>
<WelcomeForm guildId={guildId} initialValue={config} />
<WelcomeForm
guildId={guildId}
initialValue={config}
previewVars={buildWelcomePreviewVars(session.user, guild)}
/>
</div>
);
}

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>