Enhance environment configuration and expand Prisma schema for bot management
- Added OWNER_USER_IDS to .env.example for specifying global bot owners. - Introduced new models in the Prisma schema: CommandOverride, OwnerTeamMember, GlobalUserBlacklist, GuildBlacklist, FeatureFlag, BotPresenceConfig, ChangelogEntry, and OwnerAuditLog to support advanced bot management features. - Updated env.ts to preprocess OWNER_USER_IDS for better handling of global bot owner IDs. - Modified ModulePlaceholderPage to ensure proper routing for remaining dashboard modules.
This commit is contained in:
46
apps/webui/src/app/api/guilds/[guildId]/automod/route.ts
Normal file
46
apps/webui/src/app/api/guilds/[guildId]/automod/route.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { AutomodConfigDashboardPatchSchema } from '@nexumi/shared';
|
||||
import { type NextRequest, NextResponse } from 'next/server';
|
||||
import { requireGuildAccess, toApiErrorResponse } from '@/lib/auth';
|
||||
import { writeDashboardAudit } from '@/lib/audit';
|
||||
import { getAutomodDashboard, updateAutomodDashboard } from '@/lib/module-configs/automod';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
|
||||
interface RouteParams {
|
||||
params: Promise<{ guildId: string }>;
|
||||
}
|
||||
|
||||
export async function GET(_request: NextRequest, { params }: RouteParams) {
|
||||
const { guildId } = await params;
|
||||
try {
|
||||
await requireGuildAccess(guildId);
|
||||
const config = await getAutomodDashboard(guildId);
|
||||
return NextResponse.json(config);
|
||||
} catch (error) {
|
||||
return toApiErrorResponse(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function PATCH(request: NextRequest, { params }: RouteParams) {
|
||||
const { guildId } = await params;
|
||||
try {
|
||||
const session = await requireGuildAccess(guildId);
|
||||
const body = await request.json();
|
||||
const patch = AutomodConfigDashboardPatchSchema.parse(body);
|
||||
|
||||
const before = await getAutomodDashboard(guildId);
|
||||
const after = await updateAutomodDashboard(guildId, patch);
|
||||
|
||||
await writeDashboardAudit(prisma, {
|
||||
guildId,
|
||||
actorUserId: session.user.id,
|
||||
action: 'automod.update',
|
||||
path: `/dashboard/${guildId}/automod`,
|
||||
before,
|
||||
after
|
||||
});
|
||||
|
||||
return NextResponse.json(after);
|
||||
} catch (error) {
|
||||
return toApiErrorResponse(error);
|
||||
}
|
||||
}
|
||||
25
apps/webui/src/app/api/guilds/[guildId]/cases/route.ts
Normal file
25
apps/webui/src/app/api/guilds/[guildId]/cases/route.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { CaseListQuerySchema } from '@nexumi/shared';
|
||||
import { type NextRequest, NextResponse } from 'next/server';
|
||||
import { requireGuildAccess, toApiErrorResponse } from '@/lib/auth';
|
||||
import { listCases } from '@/lib/module-configs/cases';
|
||||
|
||||
interface RouteParams {
|
||||
params: Promise<{ guildId: string }>;
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest, { params }: RouteParams) {
|
||||
const { guildId } = await params;
|
||||
try {
|
||||
await requireGuildAccess(guildId);
|
||||
const searchParams = request.nextUrl.searchParams;
|
||||
const query = CaseListQuerySchema.parse({
|
||||
search: searchParams.get('search') ?? undefined,
|
||||
action: searchParams.get('action') ?? undefined,
|
||||
limit: searchParams.get('limit') ?? undefined
|
||||
});
|
||||
const cases = await listCases(guildId, query);
|
||||
return NextResponse.json({ cases });
|
||||
} catch (error) {
|
||||
return toApiErrorResponse(error);
|
||||
}
|
||||
}
|
||||
46
apps/webui/src/app/api/guilds/[guildId]/economy/route.ts
Normal file
46
apps/webui/src/app/api/guilds/[guildId]/economy/route.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { EconomyConfigDashboardPatchSchema } from '@nexumi/shared';
|
||||
import { type NextRequest, NextResponse } from 'next/server';
|
||||
import { requireGuildAccess, toApiErrorResponse } from '@/lib/auth';
|
||||
import { writeDashboardAudit } from '@/lib/audit';
|
||||
import { getEconomyDashboard, updateEconomyDashboard } from '@/lib/module-configs/economy';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
|
||||
interface RouteParams {
|
||||
params: Promise<{ guildId: string }>;
|
||||
}
|
||||
|
||||
export async function GET(_request: NextRequest, { params }: RouteParams) {
|
||||
const { guildId } = await params;
|
||||
try {
|
||||
await requireGuildAccess(guildId);
|
||||
const config = await getEconomyDashboard(guildId);
|
||||
return NextResponse.json(config);
|
||||
} catch (error) {
|
||||
return toApiErrorResponse(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function PATCH(request: NextRequest, { params }: RouteParams) {
|
||||
const { guildId } = await params;
|
||||
try {
|
||||
const session = await requireGuildAccess(guildId);
|
||||
const body = await request.json();
|
||||
const patch = EconomyConfigDashboardPatchSchema.parse(body);
|
||||
|
||||
const before = await getEconomyDashboard(guildId);
|
||||
const after = await updateEconomyDashboard(guildId, patch);
|
||||
|
||||
await writeDashboardAudit(prisma, {
|
||||
guildId,
|
||||
actorUserId: session.user.id,
|
||||
action: 'economy.update',
|
||||
path: `/dashboard/${guildId}/economy`,
|
||||
before,
|
||||
after
|
||||
});
|
||||
|
||||
return NextResponse.json(after);
|
||||
} catch (error) {
|
||||
return toApiErrorResponse(error);
|
||||
}
|
||||
}
|
||||
46
apps/webui/src/app/api/guilds/[guildId]/fun/route.ts
Normal file
46
apps/webui/src/app/api/guilds/[guildId]/fun/route.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { FunConfigDashboardPatchSchema } from '@nexumi/shared';
|
||||
import { type NextRequest, NextResponse } from 'next/server';
|
||||
import { requireGuildAccess, toApiErrorResponse } from '@/lib/auth';
|
||||
import { writeDashboardAudit } from '@/lib/audit';
|
||||
import { getFunDashboard, updateFunDashboard } from '@/lib/module-configs/fun';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
|
||||
interface RouteParams {
|
||||
params: Promise<{ guildId: string }>;
|
||||
}
|
||||
|
||||
export async function GET(_request: NextRequest, { params }: RouteParams) {
|
||||
const { guildId } = await params;
|
||||
try {
|
||||
await requireGuildAccess(guildId);
|
||||
const config = await getFunDashboard(guildId);
|
||||
return NextResponse.json(config);
|
||||
} catch (error) {
|
||||
return toApiErrorResponse(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function PATCH(request: NextRequest, { params }: RouteParams) {
|
||||
const { guildId } = await params;
|
||||
try {
|
||||
const session = await requireGuildAccess(guildId);
|
||||
const body = await request.json();
|
||||
const patch = FunConfigDashboardPatchSchema.parse(body);
|
||||
|
||||
const before = await getFunDashboard(guildId);
|
||||
const after = await updateFunDashboard(guildId, patch);
|
||||
|
||||
await writeDashboardAudit(prisma, {
|
||||
guildId,
|
||||
actorUserId: session.user.id,
|
||||
action: 'fun.update',
|
||||
path: `/dashboard/${guildId}/fun`,
|
||||
before,
|
||||
after
|
||||
});
|
||||
|
||||
return NextResponse.json(after);
|
||||
} catch (error) {
|
||||
return toApiErrorResponse(error);
|
||||
}
|
||||
}
|
||||
46
apps/webui/src/app/api/guilds/[guildId]/leveling/route.ts
Normal file
46
apps/webui/src/app/api/guilds/[guildId]/leveling/route.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { LevelingConfigDashboardPatchSchema } from '@nexumi/shared';
|
||||
import { type NextRequest, NextResponse } from 'next/server';
|
||||
import { requireGuildAccess, toApiErrorResponse } from '@/lib/auth';
|
||||
import { writeDashboardAudit } from '@/lib/audit';
|
||||
import { getLevelingDashboard, updateLevelingDashboard } from '@/lib/module-configs/leveling';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
|
||||
interface RouteParams {
|
||||
params: Promise<{ guildId: string }>;
|
||||
}
|
||||
|
||||
export async function GET(_request: NextRequest, { params }: RouteParams) {
|
||||
const { guildId } = await params;
|
||||
try {
|
||||
await requireGuildAccess(guildId);
|
||||
const config = await getLevelingDashboard(guildId);
|
||||
return NextResponse.json(config);
|
||||
} catch (error) {
|
||||
return toApiErrorResponse(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function PATCH(request: NextRequest, { params }: RouteParams) {
|
||||
const { guildId } = await params;
|
||||
try {
|
||||
const session = await requireGuildAccess(guildId);
|
||||
const body = await request.json();
|
||||
const patch = LevelingConfigDashboardPatchSchema.parse(body);
|
||||
|
||||
const before = await getLevelingDashboard(guildId);
|
||||
const after = await updateLevelingDashboard(guildId, patch);
|
||||
|
||||
await writeDashboardAudit(prisma, {
|
||||
guildId,
|
||||
actorUserId: session.user.id,
|
||||
action: 'leveling.update',
|
||||
path: `/dashboard/${guildId}/leveling`,
|
||||
before,
|
||||
after
|
||||
});
|
||||
|
||||
return NextResponse.json(after);
|
||||
} catch (error) {
|
||||
return toApiErrorResponse(error);
|
||||
}
|
||||
}
|
||||
46
apps/webui/src/app/api/guilds/[guildId]/logging/route.ts
Normal file
46
apps/webui/src/app/api/guilds/[guildId]/logging/route.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { LoggingConfigDashboardPatchSchema } from '@nexumi/shared';
|
||||
import { type NextRequest, NextResponse } from 'next/server';
|
||||
import { requireGuildAccess, toApiErrorResponse } from '@/lib/auth';
|
||||
import { writeDashboardAudit } from '@/lib/audit';
|
||||
import { getLoggingDashboard, updateLoggingDashboard } from '@/lib/module-configs/logging';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
|
||||
interface RouteParams {
|
||||
params: Promise<{ guildId: string }>;
|
||||
}
|
||||
|
||||
export async function GET(_request: NextRequest, { params }: RouteParams) {
|
||||
const { guildId } = await params;
|
||||
try {
|
||||
await requireGuildAccess(guildId);
|
||||
const config = await getLoggingDashboard(guildId);
|
||||
return NextResponse.json(config);
|
||||
} catch (error) {
|
||||
return toApiErrorResponse(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function PATCH(request: NextRequest, { params }: RouteParams) {
|
||||
const { guildId } = await params;
|
||||
try {
|
||||
const session = await requireGuildAccess(guildId);
|
||||
const body = await request.json();
|
||||
const patch = LoggingConfigDashboardPatchSchema.parse(body);
|
||||
|
||||
const before = await getLoggingDashboard(guildId);
|
||||
const after = await updateLoggingDashboard(guildId, patch);
|
||||
|
||||
await writeDashboardAudit(prisma, {
|
||||
guildId,
|
||||
actorUserId: session.user.id,
|
||||
action: 'logging.update',
|
||||
path: `/dashboard/${guildId}/logging`,
|
||||
before,
|
||||
after
|
||||
});
|
||||
|
||||
return NextResponse.json(after);
|
||||
} catch (error) {
|
||||
return toApiErrorResponse(error);
|
||||
}
|
||||
}
|
||||
46
apps/webui/src/app/api/guilds/[guildId]/moderation/route.ts
Normal file
46
apps/webui/src/app/api/guilds/[guildId]/moderation/route.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { ModerationDashboardPatchSchema } from '@nexumi/shared';
|
||||
import { type NextRequest, NextResponse } from 'next/server';
|
||||
import { requireGuildAccess, toApiErrorResponse } from '@/lib/auth';
|
||||
import { writeDashboardAudit } from '@/lib/audit';
|
||||
import { getModerationDashboard, updateModerationDashboard } from '@/lib/module-configs/moderation';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
|
||||
interface RouteParams {
|
||||
params: Promise<{ guildId: string }>;
|
||||
}
|
||||
|
||||
export async function GET(_request: NextRequest, { params }: RouteParams) {
|
||||
const { guildId } = await params;
|
||||
try {
|
||||
await requireGuildAccess(guildId);
|
||||
const config = await getModerationDashboard(guildId);
|
||||
return NextResponse.json(config);
|
||||
} catch (error) {
|
||||
return toApiErrorResponse(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function PATCH(request: NextRequest, { params }: RouteParams) {
|
||||
const { guildId } = await params;
|
||||
try {
|
||||
const session = await requireGuildAccess(guildId);
|
||||
const body = await request.json();
|
||||
const patch = ModerationDashboardPatchSchema.parse(body);
|
||||
|
||||
const before = await getModerationDashboard(guildId);
|
||||
const after = await updateModerationDashboard(guildId, patch);
|
||||
|
||||
await writeDashboardAudit(prisma, {
|
||||
guildId,
|
||||
actorUserId: session.user.id,
|
||||
action: 'moderation.update',
|
||||
path: `/dashboard/${guildId}/moderation`,
|
||||
before,
|
||||
after
|
||||
});
|
||||
|
||||
return NextResponse.json(after);
|
||||
} catch (error) {
|
||||
return toApiErrorResponse(error);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { VerificationConfigDashboardPatchSchema } from '@nexumi/shared';
|
||||
import { type NextRequest, NextResponse } from 'next/server';
|
||||
import { requireGuildAccess, toApiErrorResponse } from '@/lib/auth';
|
||||
import { writeDashboardAudit } from '@/lib/audit';
|
||||
import { getVerificationDashboard, updateVerificationDashboard } from '@/lib/module-configs/verification';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
|
||||
interface RouteParams {
|
||||
params: Promise<{ guildId: string }>;
|
||||
}
|
||||
|
||||
export async function GET(_request: NextRequest, { params }: RouteParams) {
|
||||
const { guildId } = await params;
|
||||
try {
|
||||
await requireGuildAccess(guildId);
|
||||
const config = await getVerificationDashboard(guildId);
|
||||
return NextResponse.json(config);
|
||||
} catch (error) {
|
||||
return toApiErrorResponse(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function PATCH(request: NextRequest, { params }: RouteParams) {
|
||||
const { guildId } = await params;
|
||||
try {
|
||||
const session = await requireGuildAccess(guildId);
|
||||
const body = await request.json();
|
||||
const patch = VerificationConfigDashboardPatchSchema.parse(body);
|
||||
|
||||
const before = await getVerificationDashboard(guildId);
|
||||
const after = await updateVerificationDashboard(guildId, patch);
|
||||
|
||||
await writeDashboardAudit(prisma, {
|
||||
guildId,
|
||||
actorUserId: session.user.id,
|
||||
action: 'verification.update',
|
||||
path: `/dashboard/${guildId}/verification`,
|
||||
before,
|
||||
after
|
||||
});
|
||||
|
||||
return NextResponse.json(after);
|
||||
} catch (error) {
|
||||
return toApiErrorResponse(error);
|
||||
}
|
||||
}
|
||||
23
apps/webui/src/app/api/guilds/[guildId]/warnings/route.ts
Normal file
23
apps/webui/src/app/api/guilds/[guildId]/warnings/route.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { WarningListQuerySchema } from '@nexumi/shared';
|
||||
import { type NextRequest, NextResponse } from 'next/server';
|
||||
import { requireGuildAccess, toApiErrorResponse } from '@/lib/auth';
|
||||
import { listWarnings } from '@/lib/module-configs/warnings';
|
||||
|
||||
interface RouteParams {
|
||||
params: Promise<{ guildId: string }>;
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest, { params }: RouteParams) {
|
||||
const { guildId } = await params;
|
||||
try {
|
||||
await requireGuildAccess(guildId);
|
||||
const searchParams = request.nextUrl.searchParams;
|
||||
const query = WarningListQuerySchema.parse({
|
||||
userId: searchParams.get('userId') ?? undefined
|
||||
});
|
||||
const warnings = await listWarnings(guildId, query);
|
||||
return NextResponse.json({ warnings });
|
||||
} catch (error) {
|
||||
return toApiErrorResponse(error);
|
||||
}
|
||||
}
|
||||
46
apps/webui/src/app/api/guilds/[guildId]/welcome/route.ts
Normal file
46
apps/webui/src/app/api/guilds/[guildId]/welcome/route.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { WelcomeConfigDashboardPatchSchema } from '@nexumi/shared';
|
||||
import { type NextRequest, NextResponse } from 'next/server';
|
||||
import { requireGuildAccess, toApiErrorResponse } from '@/lib/auth';
|
||||
import { writeDashboardAudit } from '@/lib/audit';
|
||||
import { getWelcomeDashboard, updateWelcomeDashboard } from '@/lib/module-configs/welcome';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
|
||||
interface RouteParams {
|
||||
params: Promise<{ guildId: string }>;
|
||||
}
|
||||
|
||||
export async function GET(_request: NextRequest, { params }: RouteParams) {
|
||||
const { guildId } = await params;
|
||||
try {
|
||||
await requireGuildAccess(guildId);
|
||||
const config = await getWelcomeDashboard(guildId);
|
||||
return NextResponse.json(config);
|
||||
} catch (error) {
|
||||
return toApiErrorResponse(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function PATCH(request: NextRequest, { params }: RouteParams) {
|
||||
const { guildId } = await params;
|
||||
try {
|
||||
const session = await requireGuildAccess(guildId);
|
||||
const body = await request.json();
|
||||
const patch = WelcomeConfigDashboardPatchSchema.parse(body);
|
||||
|
||||
const before = await getWelcomeDashboard(guildId);
|
||||
const after = await updateWelcomeDashboard(guildId, patch);
|
||||
|
||||
await writeDashboardAudit(prisma, {
|
||||
guildId,
|
||||
actorUserId: session.user.id,
|
||||
action: 'welcome.update',
|
||||
path: `/dashboard/${guildId}/welcome`,
|
||||
before,
|
||||
after
|
||||
});
|
||||
|
||||
return NextResponse.json(after);
|
||||
} catch (error) {
|
||||
return toApiErrorResponse(error);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,31 @@
|
||||
import { DASHBOARD_MODULES } from '@nexumi/shared';
|
||||
import { DASHBOARD_MODULES, type DashboardModuleId } from '@nexumi/shared';
|
||||
import { Construction } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { getLocale, t } from '@/lib/i18n';
|
||||
|
||||
/**
|
||||
* Batch A modules (Phase 7) got dedicated route folders (e.g.
|
||||
* `dashboard/[guildId]/moderation/page.tsx`), which Next.js prefers over this
|
||||
* catch-all - so they never reach this component. This placeholder now only
|
||||
* serves the remaining Batch B/C modules until they get dedicated pages too.
|
||||
*/
|
||||
const REMAINING_MODULE_IDS: ReadonlySet<DashboardModuleId> = new Set([
|
||||
'giveaways',
|
||||
'tickets',
|
||||
'selfroles',
|
||||
'tags',
|
||||
'starboard',
|
||||
'suggestions',
|
||||
'birthdays',
|
||||
'tempvoice',
|
||||
'stats',
|
||||
'feeds',
|
||||
'scheduler',
|
||||
'guildbackup'
|
||||
]);
|
||||
|
||||
interface ModulePlaceholderPageProps {
|
||||
params: Promise<{ guildId: string; module: string }>;
|
||||
}
|
||||
@@ -13,7 +34,7 @@ export default async function ModulePlaceholderPage({ params }: ModulePlaceholde
|
||||
const { guildId, module: moduleHref } = await params;
|
||||
const moduleEntry = DASHBOARD_MODULES.find((entry) => entry.href === moduleHref);
|
||||
|
||||
if (!moduleEntry) {
|
||||
if (!moduleEntry || !REMAINING_MODULE_IDS.has(moduleEntry.id)) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
|
||||
15
apps/webui/src/app/dashboard/[guildId]/automod/loading.tsx
Normal file
15
apps/webui/src/app/dashboard/[guildId]/automod/loading.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
|
||||
export default function AutomodLoading() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-8 w-48" />
|
||||
<Skeleton className="h-4 w-80" />
|
||||
</div>
|
||||
{Array.from({ length: 3 }).map((_, index) => (
|
||||
<Skeleton key={index} className="h-40 rounded-lg" />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
22
apps/webui/src/app/dashboard/[guildId]/automod/page.tsx
Normal file
22
apps/webui/src/app/dashboard/[guildId]/automod/page.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { AutomodForm } from '@/components/modules/automod-form';
|
||||
import { getLocale, t } from '@/lib/i18n';
|
||||
import { getAutomodDashboard } from '@/lib/module-configs/automod';
|
||||
|
||||
interface AutomodPageProps {
|
||||
params: Promise<{ guildId: string }>;
|
||||
}
|
||||
|
||||
export default async function AutomodPage({ params }: AutomodPageProps) {
|
||||
const { guildId } = await params;
|
||||
const [locale, config] = await Promise.all([getLocale(), getAutomodDashboard(guildId)]);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold">{t(locale, 'modules.automod.label')}</h1>
|
||||
<p className="text-sm text-muted-foreground">{t(locale, 'modules.automod.description')}</p>
|
||||
</div>
|
||||
<AutomodForm guildId={guildId} initialValue={config} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
15
apps/webui/src/app/dashboard/[guildId]/economy/loading.tsx
Normal file
15
apps/webui/src/app/dashboard/[guildId]/economy/loading.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
|
||||
export default function EconomyLoading() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-8 w-48" />
|
||||
<Skeleton className="h-4 w-80" />
|
||||
</div>
|
||||
{Array.from({ length: 2 }).map((_, index) => (
|
||||
<Skeleton key={index} className="h-48 rounded-lg" />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
22
apps/webui/src/app/dashboard/[guildId]/economy/page.tsx
Normal file
22
apps/webui/src/app/dashboard/[guildId]/economy/page.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { EconomyForm } from '@/components/modules/economy-form';
|
||||
import { getLocale, t } from '@/lib/i18n';
|
||||
import { getEconomyDashboard } from '@/lib/module-configs/economy';
|
||||
|
||||
interface EconomyPageProps {
|
||||
params: Promise<{ guildId: string }>;
|
||||
}
|
||||
|
||||
export default async function EconomyPage({ params }: EconomyPageProps) {
|
||||
const { guildId } = await params;
|
||||
const [locale, config] = await Promise.all([getLocale(), getEconomyDashboard(guildId)]);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold">{t(locale, 'modules.economy.label')}</h1>
|
||||
<p className="text-sm text-muted-foreground">{t(locale, 'modules.economy.description')}</p>
|
||||
</div>
|
||||
<EconomyForm guildId={guildId} initialValue={config} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
13
apps/webui/src/app/dashboard/[guildId]/fun/loading.tsx
Normal file
13
apps/webui/src/app/dashboard/[guildId]/fun/loading.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
|
||||
export default function FunLoading() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-8 w-48" />
|
||||
<Skeleton className="h-4 w-80" />
|
||||
</div>
|
||||
<Skeleton className="h-40 rounded-lg" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
22
apps/webui/src/app/dashboard/[guildId]/fun/page.tsx
Normal file
22
apps/webui/src/app/dashboard/[guildId]/fun/page.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { FunForm } from '@/components/modules/fun-form';
|
||||
import { getLocale, t } from '@/lib/i18n';
|
||||
import { getFunDashboard } from '@/lib/module-configs/fun';
|
||||
|
||||
interface FunPageProps {
|
||||
params: Promise<{ guildId: string }>;
|
||||
}
|
||||
|
||||
export default async function FunPage({ params }: FunPageProps) {
|
||||
const { guildId } = await params;
|
||||
const [locale, config] = await Promise.all([getLocale(), getFunDashboard(guildId)]);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold">{t(locale, 'modules.fun.label')}</h1>
|
||||
<p className="text-sm text-muted-foreground">{t(locale, 'modules.fun.description')}</p>
|
||||
</div>
|
||||
<FunForm guildId={guildId} initialValue={config} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
15
apps/webui/src/app/dashboard/[guildId]/leveling/loading.tsx
Normal file
15
apps/webui/src/app/dashboard/[guildId]/leveling/loading.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
|
||||
export default function LevelingLoading() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-8 w-48" />
|
||||
<Skeleton className="h-4 w-80" />
|
||||
</div>
|
||||
{Array.from({ length: 3 }).map((_, index) => (
|
||||
<Skeleton key={index} className="h-48 rounded-lg" />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
22
apps/webui/src/app/dashboard/[guildId]/leveling/page.tsx
Normal file
22
apps/webui/src/app/dashboard/[guildId]/leveling/page.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { LevelingForm } from '@/components/modules/leveling-form';
|
||||
import { getLocale, t } from '@/lib/i18n';
|
||||
import { getLevelingDashboard } from '@/lib/module-configs/leveling';
|
||||
|
||||
interface LevelingPageProps {
|
||||
params: Promise<{ guildId: string }>;
|
||||
}
|
||||
|
||||
export default async function LevelingPage({ params }: LevelingPageProps) {
|
||||
const { guildId } = await params;
|
||||
const [locale, config] = await Promise.all([getLocale(), getLevelingDashboard(guildId)]);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold">{t(locale, 'modules.leveling.label')}</h1>
|
||||
<p className="text-sm text-muted-foreground">{t(locale, 'modules.leveling.description')}</p>
|
||||
</div>
|
||||
<LevelingForm guildId={guildId} initialValue={config} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
15
apps/webui/src/app/dashboard/[guildId]/logging/loading.tsx
Normal file
15
apps/webui/src/app/dashboard/[guildId]/logging/loading.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
|
||||
export default function LoggingLoading() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-8 w-48" />
|
||||
<Skeleton className="h-4 w-80" />
|
||||
</div>
|
||||
{Array.from({ length: 2 }).map((_, index) => (
|
||||
<Skeleton key={index} className="h-48 rounded-lg" />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
22
apps/webui/src/app/dashboard/[guildId]/logging/page.tsx
Normal file
22
apps/webui/src/app/dashboard/[guildId]/logging/page.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { LoggingForm } from '@/components/modules/logging-form';
|
||||
import { getLocale, t } from '@/lib/i18n';
|
||||
import { getLoggingDashboard } from '@/lib/module-configs/logging';
|
||||
|
||||
interface LoggingPageProps {
|
||||
params: Promise<{ guildId: string }>;
|
||||
}
|
||||
|
||||
export default async function LoggingPage({ params }: LoggingPageProps) {
|
||||
const { guildId } = await params;
|
||||
const [locale, config] = await Promise.all([getLocale(), getLoggingDashboard(guildId)]);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold">{t(locale, 'modules.logging.label')}</h1>
|
||||
<p className="text-sm text-muted-foreground">{t(locale, 'modules.logging.description')}</p>
|
||||
</div>
|
||||
<LoggingForm guildId={guildId} initialValue={config} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
|
||||
export default function ModerationLoading() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-8 w-48" />
|
||||
<Skeleton className="h-4 w-80" />
|
||||
</div>
|
||||
{Array.from({ length: 3 }).map((_, index) => (
|
||||
<Skeleton key={index} className="h-40 rounded-lg" />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
30
apps/webui/src/app/dashboard/[guildId]/moderation/page.tsx
Normal file
30
apps/webui/src/app/dashboard/[guildId]/moderation/page.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { CaseListQuerySchema } from '@nexumi/shared';
|
||||
import { CasesTable } from '@/components/modules/cases-table';
|
||||
import { ModerationForm } from '@/components/modules/moderation-form';
|
||||
import { getLocale, t } from '@/lib/i18n';
|
||||
import { listCases } from '@/lib/module-configs/cases';
|
||||
import { getModerationDashboard } from '@/lib/module-configs/moderation';
|
||||
|
||||
interface ModerationPageProps {
|
||||
params: Promise<{ guildId: string }>;
|
||||
}
|
||||
|
||||
export default async function ModerationPage({ params }: ModerationPageProps) {
|
||||
const { guildId } = await params;
|
||||
const [locale, moderation, cases] = await Promise.all([
|
||||
getLocale(),
|
||||
getModerationDashboard(guildId),
|
||||
listCases(guildId, CaseListQuerySchema.parse({}))
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold">{t(locale, 'modules.moderation.label')}</h1>
|
||||
<p className="text-sm text-muted-foreground">{t(locale, 'modules.moderation.description')}</p>
|
||||
</div>
|
||||
<ModerationForm guildId={guildId} initialValue={moderation} />
|
||||
<CasesTable guildId={guildId} initialCases={cases} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
|
||||
export default function VerificationLoading() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-8 w-48" />
|
||||
<Skeleton className="h-4 w-80" />
|
||||
</div>
|
||||
<Skeleton className="h-64 rounded-lg" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
22
apps/webui/src/app/dashboard/[guildId]/verification/page.tsx
Normal file
22
apps/webui/src/app/dashboard/[guildId]/verification/page.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { VerificationForm } from '@/components/modules/verification-form';
|
||||
import { getLocale, t } from '@/lib/i18n';
|
||||
import { getVerificationDashboard } from '@/lib/module-configs/verification';
|
||||
|
||||
interface VerificationPageProps {
|
||||
params: Promise<{ guildId: string }>;
|
||||
}
|
||||
|
||||
export default async function VerificationPage({ params }: VerificationPageProps) {
|
||||
const { guildId } = await params;
|
||||
const [locale, config] = await Promise.all([getLocale(), getVerificationDashboard(guildId)]);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold">{t(locale, 'modules.verification.label')}</h1>
|
||||
<p className="text-sm text-muted-foreground">{t(locale, 'modules.verification.description')}</p>
|
||||
</div>
|
||||
<VerificationForm guildId={guildId} initialValue={config} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
15
apps/webui/src/app/dashboard/[guildId]/welcome/loading.tsx
Normal file
15
apps/webui/src/app/dashboard/[guildId]/welcome/loading.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
|
||||
export default function WelcomeLoading() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-8 w-48" />
|
||||
<Skeleton className="h-4 w-80" />
|
||||
</div>
|
||||
{Array.from({ length: 2 }).map((_, index) => (
|
||||
<Skeleton key={index} className="h-64 rounded-lg" />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
22
apps/webui/src/app/dashboard/[guildId]/welcome/page.tsx
Normal file
22
apps/webui/src/app/dashboard/[guildId]/welcome/page.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { WelcomeForm } from '@/components/modules/welcome-form';
|
||||
import { getLocale, t } from '@/lib/i18n';
|
||||
import { getWelcomeDashboard } from '@/lib/module-configs/welcome';
|
||||
|
||||
interface WelcomePageProps {
|
||||
params: Promise<{ guildId: string }>;
|
||||
}
|
||||
|
||||
export default async function WelcomePage({ params }: WelcomePageProps) {
|
||||
const { guildId } = await params;
|
||||
const [locale, config] = await Promise.all([getLocale(), getWelcomeDashboard(guildId)]);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<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} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user