Update configuration and README for improved setup and security; disable default API and emoji sync, and enhance Discord permission checks in API routes.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
TheOnlyMace
2026-07-21 16:00:52 +02:00
commit fdfc8de44d
925 changed files with 75254 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
/**
* ╔══════════════════════════════════════════════════════════════════╗
* ║ ║
* ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
* ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
* ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
* ║ ║
* ║ © 2026 CodeX Devs — All Rights Reserved ║
* ║ ║
* ║ discord ── https://discord.gg/codexdev ║
* ║ youtube ── https://youtube.com/@CodeXDevs ║
* ║ github ── https://github.com/RayExo ║
* ║ ║
* ╚══════════════════════════════════════════════════════════════════╝
*/
import NextAuth from "next-auth";
import { authOptions } from "@/lib/auth";
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };

View File

@@ -0,0 +1,146 @@
/**
* Authenticated server-side proxy to the bot FastAPI backend.
* - Requires Discord OAuth session
* - Guild routes: user must have Manage Server / Admin on that guild
* - Admin routes: user must be in ADMIN_IDS
*/
import { NextRequest, NextResponse } from "next/server";
import { getServerSession } from "next-auth/next";
import { authOptions } from "@/lib/auth";
import { isAdmin } from "@/lib/utils";
import { userCanManageGuild } from "@/lib/guild-auth";
const BOT_API_URL =
process.env.DASHBOARD_API_URL ||
process.env.NEXT_PUBLIC_API_URL ||
"http://127.0.0.1:8000/api/v1";
const API_KEY = process.env.DASHBOARD_API_KEY;
type RouteContext = { params: { path: string[] } };
async function authorizeProxyRequest(
path: string,
session: { user?: { id?: string }; accessToken?: string }
): Promise<NextResponse | null> {
if (!session.user) {
return NextResponse.json({ detail: "Unauthorized — sign in required." }, { status: 401 });
}
const accessToken = session.accessToken;
if (!accessToken) {
return NextResponse.json({ detail: "Missing Discord access token." }, { status: 401 });
}
const normalized = path.replace(/\/$/, "");
if (normalized.startsWith("admin")) {
if (!isAdmin(session.user.id)) {
return NextResponse.json({ detail: "Forbidden — admin access required." }, { status: 403 });
}
return null;
}
const guildMatch = normalized.match(/^guilds\/(\d+)/);
if (guildMatch) {
const guildId = guildMatch[1];
const allowed = await userCanManageGuild(accessToken, guildId);
if (!allowed) {
return NextResponse.json(
{ detail: "Forbidden — you cannot manage this server." },
{ status: 403 }
);
}
}
return null;
}
async function proxyRequest(request: NextRequest, { params }: RouteContext) {
if (!API_KEY) {
return NextResponse.json(
{ detail: "DASHBOARD_API_KEY is not configured on the dashboard server." },
{ status: 500 }
);
}
const session = await getServerSession(authOptions);
const path = params.path.join("/");
const authError = await authorizeProxyRequest(path, session ?? {});
if (authError) return authError;
const targetUrl = `${BOT_API_URL.replace(/\/$/, "")}/${path}${request.nextUrl.search}`;
const headers = new Headers();
headers.set("Authorization", `Bearer ${API_KEY}`);
if (session?.accessToken) {
headers.set("X-Discord-Access-Token", session.accessToken);
}
if (session?.user?.id) {
headers.set("X-Discord-User-Id", session.user.id);
}
const contentType = request.headers.get("content-type");
if (contentType) {
headers.set("Content-Type", contentType);
}
const init: RequestInit = {
method: request.method,
headers,
cache: "no-store",
};
if (request.method !== "GET" && request.method !== "HEAD") {
init.body = await request.text();
}
try {
const response = await fetch(targetUrl, init);
let body = await response.text();
// Filter guild list to servers the user can manage (bot guild list ∩ user admin guilds)
const normalized = path.replace(/\/$/, "");
if (
request.method === "GET" &&
normalized === "guilds" &&
response.ok &&
session?.accessToken
) {
try {
const botGuilds = JSON.parse(body) as Array<{ id: string }>;
const { fetchUserGuilds, canManageGuild } = await import("@/lib/guild-auth");
const userGuilds = await fetchUserGuilds(session.accessToken);
const manageableIds = new Set(
userGuilds.filter(canManageGuild).map((g) => String(g.id))
);
const filtered = botGuilds.filter((g) => manageableIds.has(String(g.id)));
body = JSON.stringify(filtered);
} catch {
// If filtering fails, return original response rather than breaking the dashboard
}
}
return new NextResponse(body, {
status: response.status,
headers: {
"Content-Type": response.headers.get("Content-Type") || "application/json",
},
});
} catch (error) {
console.error(`[API Proxy] Failed to reach bot API at ${targetUrl}:`, error);
return NextResponse.json(
{ detail: "Could not connect to the bot API backend." },
{ status: 502 }
);
}
}
export const GET = proxyRequest;
export const POST = proxyRequest;
export const PATCH = proxyRequest;
export const DELETE = proxyRequest;
export const PUT = proxyRequest;

View File

@@ -0,0 +1,37 @@
/**
* ╔══════════════════════════════════════════════════════════════════╗
* ║ ║
* ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
* ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
* ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
* ║ ║
* ║ © 2026 CodeX Devs — All Rights Reserved ║
* ║ ║
* ║ discord ── https://discord.gg/codexdev ║
* ║ youtube ── https://youtube.com/@CodeXDevs ║
* ║ github ── https://github.com/RayExo ║
* ║ ║
* ╚══════════════════════════════════════════════════════════════════╝
*/
import React from "react";
import { getServerSession } from "next-auth/next";
import { authOptions } from "@/lib/auth";
import { redirect } from "next/navigation";
import { isAdmin, cn } from "@/lib/utils";
import { Shield, Users, Server, Activity, Database, Cpu, Globe, Lock, Settings } from "lucide-react";
import { AdminContent } from "@/components/dashboard/admin-content";
export default async function AdminPage() {
const session = await getServerSession(authOptions);
// Server-side protection
if (!session || !isAdmin(session.user?.id)) {
redirect("/dashboard");
}
return <AdminContent />;
}

View File

@@ -0,0 +1,72 @@
/**
* ╔══════════════════════════════════════════════════════════════════╗
* ║ ║
* ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
* ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
* ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
* ║ ║
* ║ © 2026 CodeX Devs — All Rights Reserved ║
* ║ ║
* ║ discord ── https://discord.gg/codexdev ║
* ║ youtube ── https://youtube.com/@CodeXDevs ║
* ║ github ── https://github.com/RayExo ║
* ║ ║
* ╚══════════════════════════════════════════════════════════════════╝
*/
"use client";
import React, { useEffect } from "react";
import { AlertTriangle, RefreshCw, Home } from "lucide-react";
import { Button } from "@/components/ui/button";
import Link from "next/link";
export default function DashboardError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
console.error("Dashboard Error:", error);
}, [error]);
return (
<div className="flex flex-col items-center justify-center min-h-[60vh] p-6 text-center animate-in fade-in zoom-in-95 duration-500">
<div className="h-20 w-20 bg-red-500/10 rounded-3xl flex items-center justify-center mb-6">
<AlertTriangle className="h-10 w-10 text-red-500" />
</div>
<h2 className="text-2xl font-black text-white mb-2 tracking-tight">System Fault Detected</h2>
<p className="text-slate-400 max-w-md mb-8">
The neural link experienced an unexpected interruption. This could be due to a connection timeout or an internal API failure.
</p>
<div className="flex flex-col sm:flex-row gap-4 w-full max-w-xs">
<Button
onClick={() => reset()}
className="flex-1 gap-2 h-12 font-bold"
>
<RefreshCw className="h-4 w-4" />
Retry Connection
</Button>
<Link href="/dashboard" className="flex-1">
<Button
variant="outline"
className="w-full gap-2 h-12 font-bold border-slate-800"
>
<Home className="h-4 w-4" />
Go Home
</Button>
</Link>
</div>
{process.env.NODE_ENV === 'development' && (
<pre className="mt-8 p-4 bg-black/40 border border-slate-800 rounded-xl text-left text-xs text-red-400 overflow-auto max-w-full font-mono">
{error.message}
</pre>
)}
</div>
);
}

View File

@@ -0,0 +1,46 @@
/**
* ╔══════════════════════════════════════════════════════════════════╗
* ║ ║
* ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
* ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
* ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
* ║ ║
* ║ © 2026 CodeX Devs — All Rights Reserved ║
* ║ ║
* ║ discord ── https://discord.gg/codexdev ║
* ║ youtube ── https://youtube.com/@CodeXDevs ║
* ║ github ── https://github.com/RayExo ║
* ║ ║
* ╚══════════════════════════════════════════════════════════════════╝
*/
import React from "react";
import { ShieldAlert } from "lucide-react";
import dynamic from "next/dynamic";
import { api } from "@/lib/api";
const AntiNukeForm = dynamic(() => import("@/components/dashboard/antinuke-form").then(mod => mod.AntiNukeForm), {
loading: () => <div className="h-96 w-full animate-pulse bg-slate-800/20 rounded-3xl" />
});
export default async function AntiNukePage({ params }: { params: { guildId: string } }) {
const config = await api.getAntiNuke(params.guildId);
if (!config) return null;
return (
<div className="max-w-6xl mx-auto space-y-8 animate-in fade-in slide-in-from-bottom-2 duration-500">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
<div>
<h2 className="text-2xl font-bold text-white flex items-center gap-2">
<ShieldAlert className="h-6 w-6 text-red-500" />
Anti-Nuke Protection
</h2>
<p className="text-slate-400 mt-1">Protect your server from malicious mass-deletion, mass-banning, and other destructive actions.</p>
</div>
</div>
<AntiNukeForm initialConfig={config} guildId={params.guildId} />
</div>
);
}

View File

@@ -0,0 +1,46 @@
/**
* ╔══════════════════════════════════════════════════════════════════╗
* ║ ║
* ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
* ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
* ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
* ║ ║
* ║ © 2026 CodeX Devs — All Rights Reserved ║
* ║ ║
* ║ discord ── https://discord.gg/codexdev ║
* ║ youtube ── https://youtube.com/@CodeXDevs ║
* ║ github ── https://github.com/RayExo ║
* ║ ║
* ╚══════════════════════════════════════════════════════════════════╝
*/
import React from "react";
import { ShieldCheck } from "lucide-react";
import dynamic from "next/dynamic";
import { api } from "@/lib/api";
const AutomodForm = dynamic(() => import("@/components/dashboard/automod-form").then(mod => mod.AutomodForm), {
loading: () => <div className="h-96 w-full animate-pulse bg-slate-800/20 rounded-3xl" />
});
export default async function AutomodPage({ params }: { params: { guildId: string } }) {
const config = await api.getAutomod(params.guildId);
if (!config) return null;
return (
<div className="max-w-6xl mx-auto space-y-8 animate-in fade-in slide-in-from-bottom-2 duration-500">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
<div>
<h2 className="text-2xl font-bold text-white flex items-center gap-2">
<ShieldCheck className="h-6 w-6 text-primary" />
Auto Moderation
</h2>
<p className="text-slate-400 mt-1">Protect your community with automated filter systems.</p>
</div>
</div>
<AutomodForm initialConfig={config} guildId={params.guildId} />
</div>
);
}

View File

@@ -0,0 +1,182 @@
/**
* ╔══════════════════════════════════════════════════════════════════╗
* ║ ║
* ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
* ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
* ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
* ║ ║
* ║ © 2026 CodeX Devs — All Rights Reserved ║
* ║ ║
* ║ discord ── https://discord.gg/codexdev ║
* ║ youtube ── https://youtube.com/@CodeXDevs ║
* ║ github ── https://github.com/RayExo ║
* ║ ║
* ╚══════════════════════════════════════════════════════════════════╝
*/
"use client";
import React, { useState, useEffect } from "react";
import { Zap, Save, RefreshCcw, Plus, Trash2, Smile, Info } from "lucide-react";
import { toast } from "sonner";
import { api } from "@/lib/api";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { cn } from "@/lib/utils";
export default function AutoReactPage({ params }: { params: { guildId: string } }) {
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [config, setConfig] = useState<any>({ triggers: [] });
const fetchData = async () => {
try {
setLoading(true);
const configData = await api.getAutoReact(params.guildId);
setConfig(configData);
} catch (error) {
console.error("Failed to fetch auto react data:", error);
toast.error("Failed to load auto react configuration");
} finally {
setLoading(false);
}
};
useEffect(() => { fetchData(); }, [params.guildId]);
const handleSave = async () => {
setSaving(true);
const promise = api.updateAutoReact(params.guildId, { triggers: config.triggers });
toast.promise(promise, {
loading: 'Saving auto react configuration...',
success: 'Auto react settings saved!',
error: 'Failed to save auto react config',
});
try { await promise; } catch {} finally { setSaving(false); }
};
const addTrigger = () => {
if (config.triggers.length >= 10) {
toast.error("Maximum 10 triggers allowed");
return;
}
setConfig({ ...config, triggers: [...config.triggers, { trigger: "", emojis: "" }] });
};
const removeTrigger = (index: number) => {
const newTriggers = [...config.triggers];
newTriggers.splice(index, 1);
setConfig({ ...config, triggers: newTriggers });
};
const updateTrigger = (index: number, field: string, value: string) => {
const newTriggers = [...config.triggers];
newTriggers[index] = { ...newTriggers[index], [field]: value };
setConfig({ ...config, triggers: newTriggers });
};
if (loading) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<RefreshCcw className="w-8 h-8 animate-spin text-primary" />
</div>
);
}
return (
<div className="max-w-6xl mx-auto space-y-8 animate-in fade-in slide-in-from-bottom-2 duration-500">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
<div>
<h2 className="text-2xl font-bold text-white flex items-center gap-2">
<Zap className="h-6 w-6 text-primary" />
Auto React
</h2>
<p className="text-slate-400 mt-1">Automatically react with emojis when specific trigger words are sent.</p>
</div>
<Button onClick={addTrigger} disabled={config.triggers.length >= 10} variant="secondary" className="gap-2">
<Plus className="w-4 h-4" /> Add Trigger
</Button>
</div>
<div className="grid grid-cols-1 lg:grid-cols-4 gap-8">
<div className="lg:col-span-3 space-y-6">
<div className="bg-[#141B2D] border border-slate-800 rounded-3xl overflow-hidden shadow-xl p-8 space-y-6">
{config.triggers.length === 0 ? (
<div className="text-center p-12 bg-slate-900/20 rounded-2xl border border-dashed border-slate-700">
<Smile className="w-14 h-14 text-slate-600 mx-auto mb-4" />
<p className="text-lg font-medium text-slate-400">No triggers configured</p>
<p className="text-sm text-slate-500 mb-6">Start by adding your first auto-reaction trigger.</p>
<Button variant="outline" onClick={addTrigger} className="gap-2">
<Plus className="w-4 h-4" /> Add Your First Trigger
</Button>
</div>
) : (
<>
{config.triggers.map((item: any, index: number) => (
<div key={index} className="p-6 bg-slate-900/40 border border-slate-800 rounded-2xl space-y-4 animate-in zoom-in-95 duration-200">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="p-2.5 rounded-xl bg-yellow-500/10 text-yellow-500">
<Zap className="h-4 w-4" />
</div>
<h4 className="font-bold text-white">Trigger #{index + 1}</h4>
</div>
<Button variant="ghost" size="sm" onClick={() => removeTrigger(index)} className="text-red-400 hover:text-red-300 hover:bg-red-400/10 h-8 w-8 p-0">
<Trash2 className="h-4 w-4" />
</Button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<label className="text-xs font-bold text-slate-400">Trigger Word (single word)</label>
<Input
placeholder="e.g. hello"
value={item.trigger}
onChange={(e) => updateTrigger(index, "trigger", e.target.value)}
className="bg-slate-900/50 border-slate-800 h-12"
/>
</div>
<div className="space-y-2">
<label className="text-xs font-bold text-slate-400">Emojis (space separated)</label>
<Input
placeholder="e.g. 👋 ✨ ❤️"
value={item.emojis}
onChange={(e) => updateTrigger(index, "emojis", e.target.value)}
className="bg-slate-900/50 border-slate-800 h-12"
/>
</div>
</div>
</div>
))}
<Button onClick={handleSave} disabled={saving} className="w-full h-14 text-base font-bold gap-2">
{saving ? <RefreshCcw className="h-5 w-5 animate-spin" /> : <Save className="h-5 w-5" />}
Save All Triggers
</Button>
</>
)}
</div>
</div>
{/* Sidebar */}
<div className="space-y-6">
<div className="bg-gradient-to-br from-yellow-500/10 to-transparent border border-yellow-500/20 rounded-3xl p-6 relative overflow-hidden group">
<div className="absolute -right-4 -top-4 opacity-[0.03] group-hover:scale-110 transition-transform">
<Zap className="h-32 w-32 text-yellow-500" />
</div>
<div className="flex items-center gap-2 mb-4">
<Info className="h-4 w-4 text-yellow-500" />
<h3 className="text-sm font-bold text-white">How It Works</h3>
</div>
<ul className="text-xs text-slate-500 space-y-2">
<li> Triggers are single words that the bot watches for.</li>
<li> When a message contains a trigger, the bot reacts with the configured emojis.</li>
<li> Up to 10 emojis per trigger, and 10 triggers max per guild.</li>
<li> Custom emojis must be from this server.</li>
</ul>
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,54 @@
/**
* ╔══════════════════════════════════════════════════════════════════╗
* ║ ║
* ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
* ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
* ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
* ║ ║
* ║ © 2026 CodeX Devs — All Rights Reserved ║
* ║ ║
* ║ discord ── https://discord.gg/codexdev ║
* ║ youtube ── https://youtube.com/@CodeXDevs ║
* ║ github ── https://github.com/RayExo ║
* ║ ║
* ╚══════════════════════════════════════════════════════════════════╝
*/
import React from "react";
import { UserPlus } from "lucide-react";
import dynamic from "next/dynamic";
import { api } from "@/lib/api";
export const revalidate = 0; // Never cache this page
const AutoRoleForm = dynamic(() => import("@/components/dashboard/autorole-form").then(mod => mod.AutoRoleForm), {
loading: () => <div className="h-96 w-full animate-pulse bg-slate-800/20 rounded-3xl" />
});
export default async function AutoRolePage({ params }: { params: { guildId: string } }) {
const [config, roles] = await Promise.all([
api.getAutoRole(params.guildId),
api.getRoles(params.guildId)
]);
return (
<div className="max-w-6xl mx-auto space-y-8 animate-in fade-in slide-in-from-bottom-2 duration-500">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
<div>
<h2 className="text-2xl font-bold text-white flex items-center gap-2">
<UserPlus className="h-6 w-6 text-primary" />
Auto Role
</h2>
<p className="text-slate-400 mt-1">Automatically assign roles to new members and bots.</p>
</div>
</div>
<AutoRoleForm
initialConfig={config}
roles={roles}
guildId={params.guildId}
/>
</div>
);
}

View File

@@ -0,0 +1,49 @@
/**
* ╔══════════════════════════════════════════════════════════════════╗
* ║ ║
* ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
* ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
* ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
* ║ ║
* ║ © 2026 CodeX Devs — All Rights Reserved ║
* ║ ║
* ║ discord ── https://discord.gg/codexdev ║
* ║ youtube ── https://youtube.com/@CodeXDevs ║
* ║ github ── https://github.com/RayExo ║
* ║ ║
* ╚══════════════════════════════════════════════════════════════════╝
*/
import React from "react";
import { Ghost } from "lucide-react";
import dynamic from "next/dynamic";
import { api } from "@/lib/api";
const CustomRolesForm = dynamic(() => import("@/components/dashboard/customroles-form").then(mod => mod.CustomRolesForm), {
loading: () => <div className="h-96 w-full animate-pulse bg-slate-800/20 rounded-3xl" />
});
export default async function CustomRolesPage({ params }: { params: { guildId: string } }) {
const [config, roles] = await Promise.all([
api.getCustomRoles(params.guildId),
api.getRoles(params.guildId),
]);
if (!config) return null;
return (
<div className="max-w-6xl mx-auto space-y-8 animate-in fade-in slide-in-from-bottom-2 duration-500">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
<div>
<h2 className="text-2xl font-bold text-white flex items-center gap-2">
<Ghost className="h-6 w-6 text-primary" />
Custom Roles
</h2>
<p className="text-slate-400 mt-1">Configure predefined roles that can be easily assigned using commands.</p>
</div>
</div>
<CustomRolesForm initialConfig={config} roles={roles} guildId={params.guildId} />
</div>
);
}

View File

@@ -0,0 +1,194 @@
/**
* ╔══════════════════════════════════════════════════════════════════╗
* ║ ║
* ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
* ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
* ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
* ║ ║
* ║ © 2026 CodeX Devs — All Rights Reserved ║
* ║ ║
* ║ discord ── https://discord.gg/codexdev ║
* ║ youtube ── https://youtube.com/@CodeXDevs ║
* ║ github ── https://github.com/RayExo ║
* ║ ║
* ╚══════════════════════════════════════════════════════════════════╝
*/
"use client";
import React, { useState, useEffect } from "react";
import { Volume2, Save, RefreshCcw, ShieldCheck, Info, Power } from "lucide-react";
import { toast } from "sonner";
import { api } from "@/lib/api";
import { Button } from "@/components/ui/button";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { cn } from "@/lib/utils";
export default function InvcRolePage({ params }: { params: { guildId: string } }) {
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [roles, setRoles] = useState<any[]>([]);
const [config, setConfig] = useState<any>({ role_id: null, enabled: false });
const fetchData = async () => {
try {
setLoading(true);
const [configData, rolesData] = await Promise.all([
api.getInvcRole(params.guildId),
api.getRoles(params.guildId),
]);
setConfig(configData);
setRoles(rolesData);
} catch (error) {
console.error("Failed to fetch InvcRole data:", error);
toast.error("Failed to load Voice Role configuration");
} finally {
setLoading(false);
}
};
useEffect(() => { fetchData(); }, [params.guildId]);
const handleSave = async () => {
setSaving(true);
const promise = api.updateInvcRole(params.guildId, {
role_id: config.role_id,
enabled: config.enabled
});
toast.promise(promise, {
loading: 'Saving Voice Role configuration...',
success: 'Voice Role settings saved!',
error: 'Failed to save Voice Role config',
});
try { await promise; } catch {} finally { setSaving(false); }
};
const filteredRoles = roles.filter(r => r.name !== "@everyone");
if (loading) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<RefreshCcw className="w-8 h-8 animate-spin text-primary" />
</div>
);
}
const formatColor = (decimal: number) => {
if (!decimal || decimal === 0) return "#94a3b8";
return `#${decimal.toString(16).padStart(6, '0')}`;
};
return (
<div className="max-w-6xl mx-auto space-y-8 animate-in fade-in slide-in-from-bottom-2 duration-500">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
<div>
<h2 className="text-2xl font-bold text-white flex items-center gap-2">
<Volume2 className="h-6 w-6 text-primary" />
Voice Role
</h2>
<p className="text-slate-400 mt-1">Automatically assign a role when members join a voice channel.</p>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-4 gap-8">
<div className="lg:col-span-3 space-y-6">
<div className="bg-[#141B2D] border border-slate-800 rounded-3xl overflow-hidden shadow-xl p-8 space-y-8">
{/* Status & Toggle */}
<div className="flex items-center justify-between p-6 bg-slate-900/40 rounded-2xl border border-slate-800">
<div className="flex items-center gap-4">
<div className={cn("p-3 rounded-xl transition-colors", config.enabled ? "bg-emerald-500/20 text-emerald-500" : "bg-red-500/20 text-red-500")}>
<Power className="w-5 h-5" />
</div>
<div>
<h3 className="text-lg font-black text-white">Voice Role System</h3>
<p className="text-sm text-slate-400 mt-1">{config.enabled ? "The system is active and monitoring channels." : "The system is currently disabled."}</p>
</div>
</div>
<div className="flex items-center gap-4">
<div className="px-3 py-1 rounded-full bg-slate-900 border border-slate-800 flex items-center gap-2">
<div className={cn("w-2 h-2 rounded-full", config.enabled ? 'bg-emerald-500 animate-pulse' : 'bg-red-500')} />
<span className="text-[10px] font-bold uppercase text-slate-400">
{config.enabled ? 'Live' : 'Off'}
</span>
</div>
<Switch
checked={config.enabled}
onCheckedChange={(checked) => setConfig({ ...config, enabled: checked })}
className="data-[state=checked]:bg-emerald-500"
/>
</div>
</div>
{/* Role Selector */}
<div className={cn("p-6 border rounded-2xl space-y-4 transition-all duration-300",
config.enabled ? "bg-slate-900/40 border-slate-800 opacity-100" : "bg-slate-900/10 border-slate-900 opacity-50 pointer-events-none grayscale")}>
<div className="flex items-center gap-3">
<div className="p-3 bg-primary/20 text-primary rounded-xl">
<ShieldCheck className="w-5 h-5" />
</div>
<div>
<h4 className="font-bold text-white">Voice State Role</h4>
<p className="text-xs text-slate-400 mt-1">Choose the role to be assigned automatically to voice participants.</p>
</div>
</div>
<Select
value={config.role_id || "none"}
onValueChange={(val) => setConfig({ ...config, role_id: val === "none" ? null : val })}
disabled={!config.enabled}
>
<SelectTrigger className="w-full h-12 bg-slate-900 border-slate-800 font-medium">
<SelectValue placeholder="Select a role..." />
</SelectTrigger>
<SelectContent className="bg-slate-900 border-slate-800 max-h-[300px]">
<SelectItem value="none" className="text-slate-400 focus:bg-slate-800">No Role Selected</SelectItem>
{filteredRoles.map((r) => (
<SelectItem key={r.id} value={r.id} className="focus:bg-slate-800">
<div className="flex items-center gap-2">
<div className="w-2 h-2 rounded-full" style={{ backgroundColor: formatColor(r.color) }} />
{r.name}
</div>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<Button onClick={handleSave} disabled={saving} className="w-full h-14 text-base font-bold gap-2 shadow-lg shadow-primary/20 transition-all hover:scale-[1.01] active:scale-[0.99]">
{saving ? <RefreshCcw className="h-5 w-5 animate-spin" /> : <Save className="h-5 w-5" />}
Save All Changes
</Button>
</div>
</div>
{/* Sidebar */}
<div className="space-y-6">
<div className="bg-gradient-to-br from-primary/10 to-transparent border border-primary/20 rounded-3xl p-6 relative overflow-hidden group">
<div className="absolute -right-4 -top-4 opacity-[0.03] group-hover:scale-110 transition-transform">
<Volume2 className="h-32 w-32 text-primary" />
</div>
<div className="flex items-center gap-2 mb-4">
<Info className="h-4 w-4 text-primary" />
<h3 className="text-sm font-bold text-white">How It Works</h3>
</div>
<ul className="text-xs text-slate-500 space-y-3 leading-relaxed">
<li className="flex gap-2">
<span className="text-primary font-bold">01</span>
<span>Role is added when a user joins any voice channel.</span>
</li>
<li className="flex gap-2">
<span className="text-primary font-bold">02</span>
<span>Role is removed when they disconnect from all channels.</span>
</li>
<li className="flex gap-2">
<span className="text-primary font-bold">03</span>
<span>Make sure the bot role is higher than the selected role.</span>
</li>
</ul>
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,167 @@
/**
* ╔══════════════════════════════════════════════════════════════════╗
* ║ ║
* ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
* ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
* ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
* ║ ║
* ║ © 2026 CodeX Devs — All Rights Reserved ║
* ║ ║
* ║ discord ── https://discord.gg/codexdev ║
* ║ youtube ── https://youtube.com/@CodeXDevs ║
* ║ github ── https://github.com/RayExo ║
* ║ ║
* ╚══════════════════════════════════════════════════════════════════╝
*/
"use client";
import React, { useState, useEffect } from "react";
import { TrendingUp, RefreshCcw, Medal, User, LogOut, UserMinus, UserPlus, Info } from "lucide-react";
import { toast } from "sonner";
import { api } from "@/lib/api";
import { cn } from "@/lib/utils";
export default function InvitesPage({ params }: { params: { guildId: string } }) {
const [loading, setLoading] = useState(true);
const [data, setData] = useState<any[]>([]);
const fetchLeaderboard = async () => {
try {
setLoading(true);
const res = await api.getInvites(params.guildId);
setData(res.data || []);
} catch (error) {
console.error("Failed to fetch invites leaderboard:", error);
toast.error("Failed to load invites leaderboard");
setData([]);
} finally {
setLoading(false);
}
};
useEffect(() => { fetchLeaderboard(); }, [params.guildId]);
if (loading) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<RefreshCcw className="w-8 h-8 animate-spin text-primary" />
</div>
);
}
return (
<div className="max-w-6xl mx-auto space-y-8 animate-in fade-in slide-in-from-bottom-2 duration-500">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
<div>
<h2 className="text-2xl font-bold text-white flex items-center gap-2">
<TrendingUp className="h-6 w-6 text-primary" />
Invite Leaderboard
</h2>
<p className="text-slate-400 mt-1">Top inviters in the server based on tracked join events.</p>
</div>
<button onClick={fetchLeaderboard} className="text-slate-400 hover:text-white transition-colors">
<RefreshCcw className="w-5 h-5" />
</button>
</div>
<div className="grid grid-cols-1 lg:grid-cols-4 gap-8">
<div className="lg:col-span-3 space-y-6">
<div className="bg-[#141B2D] border border-slate-800 rounded-3xl overflow-hidden shadow-xl p-8 space-y-8">
{/* Stats Cards */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div className="p-5 bg-primary/5 border border-primary/20 rounded-2xl flex flex-col gap-1">
<div className="text-xs font-bold uppercase text-primary flex items-center gap-2"><UserPlus className="w-3.5 h-3.5" /> Total</div>
<span className="text-2xl font-black text-white">{data.reduce((acc, curr) => acc + (curr.total || 0), 0)}</span>
</div>
<div className="p-5 bg-red-500/5 border border-red-500/20 rounded-2xl flex flex-col gap-1">
<div className="text-xs font-bold uppercase text-red-500 flex items-center gap-2"><LogOut className="w-3.5 h-3.5" /> Left</div>
<span className="text-2xl font-black text-white">{data.reduce((acc, curr) => acc + (curr.left || 0), 0)}</span>
</div>
<div className="p-5 bg-yellow-500/5 border border-yellow-500/20 rounded-2xl flex flex-col gap-1">
<div className="text-xs font-bold uppercase text-yellow-500 flex items-center gap-2"><UserMinus className="w-3.5 h-3.5" /> Fake</div>
<span className="text-2xl font-black text-white">{data.reduce((acc, curr) => acc + (curr.fake || 0), 0)}</span>
</div>
<div className="p-5 bg-emerald-500/5 border border-emerald-500/20 rounded-2xl flex flex-col gap-1">
<div className="text-xs font-bold uppercase text-emerald-500 flex items-center gap-2"><TrendingUp className="w-3.5 h-3.5" /> Top</div>
<span className="text-2xl font-black text-white">{data.length > 0 ? data[0].total : 0}</span>
</div>
</div>
{/* Leaderboard */}
<div className="pt-6 border-t border-slate-800 space-y-3">
<h4 className="text-sm font-bold text-white flex items-center gap-2 mb-4">
<Medal className="h-5 w-5 text-yellow-500" /> Rankings
</h4>
{data.length === 0 ? (
<div className="text-center p-12 bg-slate-900/20 rounded-2xl border border-dashed border-slate-700">
<TrendingUp className="w-10 h-10 text-slate-600 mx-auto mb-3" />
<p className="text-sm text-slate-500">No invite data found yet.</p>
</div>
) : (
data.map((row, index) => (
<div key={index} className="flex items-center justify-between p-4 bg-slate-900/40 rounded-xl border border-slate-800 hover:bg-slate-800/50 transition-colors">
<div className="flex items-center gap-4">
<div className="w-10 h-10 rounded-full bg-slate-800 flex items-center justify-center text-sm font-black border border-slate-700">
{index === 0 ? "🥇" : index === 1 ? "🥈" : index === 2 ? "🥉" : <span className="text-slate-500">#{index + 1}</span>}
</div>
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-full bg-slate-800 flex items-center justify-center text-slate-400 border border-slate-700">
<User className="w-4 h-4" />
</div>
<span className="text-sm text-slate-300 font-mono">{row.user_id}</span>
</div>
</div>
<div className="flex items-center gap-6 text-sm">
<div className="text-right">
<div className="text-[10px] uppercase font-bold text-slate-500">Total</div>
<div className="font-black text-primary">{row.total}</div>
</div>
<div className="text-right">
<div className="text-[10px] uppercase font-bold text-slate-500">Real</div>
<div className="font-medium text-emerald-400">{row.total - row.left - row.fake - row.rejoin}</div>
</div>
<div className="text-right">
<div className="text-[10px] uppercase font-bold text-slate-500">Left</div>
<div className="text-red-400">{row.left}</div>
</div>
<div className="text-right">
<div className="text-[10px] uppercase font-bold text-slate-500">Fake</div>
<div className="text-yellow-400">{row.fake}</div>
</div>
<div className="text-right">
<div className="text-[10px] uppercase font-bold text-slate-500">Rejoin</div>
<div className="text-blue-400">{row.rejoin}</div>
</div>
</div>
</div>
))
)}
</div>
</div>
</div>
{/* Sidebar */}
<div className="space-y-6">
<div className="bg-gradient-to-br from-primary/10 to-transparent border border-primary/20 rounded-3xl p-6 relative overflow-hidden group">
<div className="absolute -right-4 -top-4 opacity-[0.03] group-hover:scale-110 transition-transform">
<TrendingUp className="h-32 w-32 text-primary" />
</div>
<div className="flex items-center gap-2 mb-4">
<Info className="h-4 w-4 text-primary" />
<h3 className="text-sm font-bold text-white">About Tracking</h3>
</div>
<ul className="text-xs text-slate-500 space-y-2">
<li> Invite tracking is automatic for all members.</li>
<li> &quot;Real&quot; = Total minus Left, Fake, and Rejoins.</li>
<li> Use <span className="text-primary">,invitelogging #channel</span> to enable live logs.</li>
<li> Admins can manually adjust invite counts.</li>
</ul>
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,52 @@
/**
* ╔══════════════════════════════════════════════════════════════════╗
* ║ ║
* ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
* ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
* ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
* ║ ║
* ║ © 2026 CodeX Devs — All Rights Reserved ║
* ║ ║
* ║ discord ── https://discord.gg/codexdev ║
* ║ youtube ── https://youtube.com/@CodeXDevs ║
* ║ github ── https://github.com/RayExo ║
* ║ ║
* ╚══════════════════════════════════════════════════════════════════╝
*/
import React from "react";
import { Mic } from "lucide-react";
import dynamic from "next/dynamic";
import { api } from "@/lib/api";
export const revalidate = 0; // Never cache this page
const J2CForm = dynamic(() => import("@/components/dashboard/j2c-form").then(mod => mod.J2CForm), {
loading: () => <div className="h-96 w-full animate-pulse bg-slate-800/20 rounded-3xl" />
});
export default async function J2CPage({ params }: { params: { guildId: string } }) {
const [config, channels] = await Promise.all([
api.getJ2C(params.guildId),
api.getChannels(params.guildId),
]);
if (!config) return null;
return (
<div className="max-w-6xl mx-auto space-y-8 animate-in fade-in slide-in-from-bottom-2 duration-500">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
<div>
<h2 className="text-2xl font-bold text-white flex items-center gap-2">
<Mic className="h-6 w-6 text-primary" />
Join to Create
</h2>
<p className="text-slate-400 mt-1">Set up temporary voice channels that are created automatically when a member joins a specific channel.</p>
</div>
</div>
<J2CForm initialConfig={config} channels={channels} guildId={params.guildId} />
</div>
);
}

View File

@@ -0,0 +1,128 @@
/**
* ╔══════════════════════════════════════════════════════════════════╗
* ║ ║
* ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
* ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
* ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
* ║ ║
* ║ © 2026 CodeX Devs — All Rights Reserved ║
* ║ ║
* ║ discord ── https://discord.gg/codexdev ║
* ║ youtube ── https://youtube.com/@CodeXDevs ║
* ║ github ── https://github.com/RayExo ║
* ║ ║
* ╚══════════════════════════════════════════════════════════════════╝
*/
"use client";
import React, { useState, useEffect } from "react";
import { MessageSquare, Save, RefreshCcw, Send } from "lucide-react";
import { toast } from "sonner";
import { api } from "@/lib/api";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Textarea } from "@/components/ui/textarea";
import { Label } from "@/components/ui/label";
export default function JoinDMPage({ params }: { params: { guildId: string } }) {
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [config, setConfig] = useState<any>({
message: "",
});
const fetchData = async () => {
try {
setLoading(true);
const configData = await api.getJoinDM(params.guildId);
setConfig(configData);
} catch (error) {
console.error("Failed to fetch JoinDM data:", error);
toast.error("Failed to load Join DM configuration");
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, [params.guildId]);
const handleSave = async () => {
try {
setSaving(true);
await api.updateJoinDM(params.guildId, config);
toast.success("Join DM configuration saved successfully");
} catch (error) {
console.error("Failed to save JoinDM config:", error);
toast.error("Failed to save Join DM configuration");
} finally {
setSaving(false);
}
};
if (loading) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<RefreshCcw className="w-8 h-8 animate-spin text-primary" />
</div>
);
}
return (
<div className="space-y-6">
<div>
<h2 className="text-3xl font-bold tracking-tight">Join DM</h2>
<p className="text-muted-foreground">
Send a private message to new members when they join your server.
</p>
</div>
<Card className="border-primary/20 bg-background/50 backdrop-blur-xl">
<CardHeader>
<div className="flex items-center gap-2">
<MessageSquare className="w-5 h-5 text-primary" />
<CardTitle>Welcome Message</CardTitle>
</div>
<CardDescription>
This message will be sent to the user&apos;s DMs. You can use text to welcome them.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label>Message Content</Label>
<Textarea
placeholder="Welcome to the server! Make sure to read the rules..."
className="min-h-[200px]"
value={config.message || ""}
onChange={(e) => setConfig({ ...config, message: e.target.value })}
/>
</div>
<div className="flex justify-end pt-4">
<Button onClick={handleSave} disabled={saving} className="gap-2">
{saving ? <RefreshCcw className="w-4 h-4 animate-spin" /> : <Save className="w-4 h-4" />}
Save Message
</Button>
</div>
</CardContent>
</Card>
<Card className="border-blue-500/20 bg-blue-500/5">
<CardHeader>
<div className="flex items-center gap-2">
<Send className="w-5 h-5 text-blue-500" />
<CardTitle className="text-blue-500 text-base">Usage Note</CardTitle>
</div>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">
The bot will automatically attach &quot;Sent from [Server Name]&quot; to the end of your message.
Ensure your bot has permissions to DM members (usually by being in the same server and not being blocked).
</p>
</CardContent>
</Card>
</div>
);
}

View File

@@ -0,0 +1,183 @@
/**
* ╔══════════════════════════════════════════════════════════════════╗
* ║ ║
* ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
* ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
* ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
* ║ ║
* ║ © 2026 CodeX Devs — All Rights Reserved ║
* ║ ║
* ║ discord ── https://discord.gg/codexdev ║
* ║ youtube ── https://youtube.com/@CodeXDevs ║
* ║ github ── https://github.com/RayExo ║
* ║ ║
* ╚══════════════════════════════════════════════════════════════════╝
*/
import React from "react";
import Image from "next/image";
import Link from "next/link";
import {
Users,
ShieldCheck,
Ticket,
BarChart4,
FileText,
Settings,
Hash,
Shield,
Layers,
ArrowLeft,
ShieldAlert
} from "lucide-react";
import { api } from "@/lib/api";
import { cn } from "@/lib/utils";
import { getServerSession } from "next-auth/next";
import { authOptions } from "@/lib/auth";
import { userCanManageGuild } from "@/lib/guild-auth";
import { redirect } from "next/navigation";
export const revalidate = 0; // Never cache any guild dashboard page
import { Button } from "@/components/ui/button";
import { GuildTabs } from "@/components/guild-tabs";
interface GuildLayoutProps {
children: React.ReactNode;
params: { guildId: string };
}
export default async function GuildLayout({
children,
params,
}: GuildLayoutProps) {
const guildId = params.guildId;
let guild;
let error = null;
const session = await getServerSession(authOptions);
if (!session?.accessToken) {
redirect("/");
}
const hasGuildAccess = await userCanManageGuild(session.accessToken, guildId);
if (!hasGuildAccess) {
return (
<div className="flex flex-col items-center justify-center min-h-[400px] border-2 border-dashed border-red-500/20 rounded-3xl bg-red-500/5 p-12 text-center">
<ShieldAlert className="h-16 w-16 text-red-500 mb-6 opacity-50" />
<h2 className="text-2xl font-bold text-white">Access Denied</h2>
<p className="text-slate-400 mt-2 max-w-md">
You do not have permission to manage this server.
</p>
<Link href="/dashboard/guilds" className="mt-8">
<Button variant="outline">Back to Servers</Button>
</Link>
</div>
);
}
try {
guild = await api.getGuildDetails(guildId);
} catch (err: any) {
console.error("Failed to fetch guild details:", err);
error = err.message || "Failed to load guild data.";
}
if (error || !guild) {
return (
<div className="flex flex-col items-center justify-center min-h-[400px] border-2 border-dashed border-red-500/20 rounded-3xl bg-red-500/5 p-12 text-center">
<ShieldAlert className="h-16 w-16 text-red-500 mb-6 opacity-50" />
<h2 className="text-2xl font-bold text-white">Access Denied</h2>
<p className="text-slate-400 mt-2 max-w-md">{error || "This guild does not exist or you do not have permission to manage it."}</p>
<Link href="/dashboard/guilds" className="mt-8">
<Button variant="outline">Back to Servers</Button>
</Link>
</div>
);
}
return (
<div className="space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-700">
{/* Breadcrumb / Back button */}
<Link href="/dashboard/guilds" className="inline-flex items-center gap-2 text-slate-500 hover:text-white transition-colors text-sm font-medium group">
<ArrowLeft className="h-4 w-4 group-hover:-translate-x-1 transition-transform" />
Back to all servers
</Link>
{/* Guild Header */}
<div className="bg-[#141B2D] border border-slate-800 rounded-3xl p-8 shadow-xl shadow-black/20">
<div className="flex flex-col lg:flex-row lg:items-center gap-8">
<div className="relative">
{guild.icon ? (
<Image
src={guild.icon}
alt={guild.name}
width={120}
height={120}
className="rounded-3xl border-4 border-slate-800 shadow-2xl"
/>
) : (
<div className="h-[120px] w-[120px] bg-primary rounded-3xl flex items-center justify-center text-4xl font-bold text-white shadow-2xl border-4 border-slate-800">
{guild.name.charAt(0)}
</div>
)}
<div className="absolute -bottom-2 -right-2 bg-emerald-500 text-white p-2 rounded-xl shadow-lg border-2 border-[#141B2D]" title="Active">
<div className="h-3 w-3 rounded-full bg-white animate-pulse" />
</div>
</div>
<div className="flex-1 space-y-4">
<div>
<div className="flex items-center gap-3">
<h1 className="text-4xl font-black text-white tracking-tight">{guild.name}</h1>
<span className="px-3 py-1 bg-slate-800 rounded-lg text-[10px] uppercase font-black text-slate-500 tracking-tighter border border-white/5">
ID: {guildId}
</span>
</div>
<p className="text-slate-400 mt-1 italic opacity-80">Server Owner Dashboard</p>
</div>
<div className="flex flex-wrap gap-4">
{[
{ label: "Members", value: guild.member_count, icon: Users, color: "text-blue-400" },
{ label: "Roles", value: guild.role_count, icon: Shield, color: "text-emerald-400" },
{ label: "Channels", value: guild.channel_count, icon: Hash, color: "text-purple-400" },
].map((item) => (
<div key={item.label} className="flex items-center gap-3 bg-slate-800/50 px-5 py-3 rounded-2xl border border-white/5 shadow-inner">
<div className={cn("p-2 rounded-lg bg-slate-900/50", item.color)}>
<item.icon className="h-5 w-5" />
</div>
<div>
<p className="text-[10px] uppercase font-bold text-slate-500 tracking-wider leading-none mb-1">{item.label}</p>
<p className="text-xl font-bold text-white leading-none">{item.value.toLocaleString()}</p>
</div>
</div>
))}
</div>
</div>
<div className="flex flex-col sm:flex-row lg:flex-col gap-3">
<Link href={`/dashboard/guild/${guildId}`} className="w-full">
<Button className="w-full">
Refresh
</Button>
</Link>
<Link href={`/dashboard/guild/${guildId}/settings`} className="w-full">
<Button variant="secondary" className="w-full">
Server Settings
</Button>
</Link>
</div>
</div>
</div>
{/* Modern Tab Navigation */}
<GuildTabs guildId={guildId} />
{/* Tab Content */}
<div className="min-h-[400px]">
{children}
</div>
</div>
);
}

View File

@@ -0,0 +1,206 @@
/**
* ╔══════════════════════════════════════════════════════════════════╗
* ║ ║
* ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
* ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
* ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
* ║ ║
* ║ © 2026 CodeX Devs — All Rights Reserved ║
* ║ ║
* ║ discord ── https://discord.gg/codexdev ║
* ║ youtube ── https://youtube.com/@CodeXDevs ║
* ║ github ── https://github.com/RayExo ║
* ║ ║
* ╚══════════════════════════════════════════════════════════════════╝
*/
"use client";
import React, { useState, useEffect } from "react";
import {
Trophy,
User,
Zap,
RefreshCcw,
ChevronLeft,
ChevronRight,
TrendingUp,
Medal,
Crown,
Search
} from "lucide-react";
import { api } from "@/lib/api";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { LeaderboardEntry } from "@/types/api";
export default function LeaderboardPage({ params }: { params: { guildId: string } }) {
const [leaderboard, setLeaderboard] = useState<LeaderboardEntry[]>([]);
const [loading, setLoading] = useState(true);
const [search, setSearch] = useState("");
useEffect(() => {
async function fetchLeaderboard() {
try {
const data = await api.getLeaderboard(params.guildId);
setLeaderboard(data);
} catch (err) {
console.error("Failed to fetch leaderboard:", err);
} finally {
setLoading(false);
}
}
fetchLeaderboard();
}, [params.guildId]);
const filteredData = leaderboard.filter(entry =>
entry.name.toLowerCase().includes(search.toLowerCase()) ||
entry.user_id.toString().includes(search)
);
if (loading) {
return (
<div className="flex flex-col items-center justify-center h-96 space-y-4">
<RefreshCcw className="h-10 w-10 text-primary animate-spin" />
<p className="text-slate-400 animate-pulse font-medium tracking-tight">Syncing global rankings...</p>
</div>
);
}
return (
<div className="max-w-6xl mx-auto space-y-8 animate-in fade-in slide-in-from-bottom-2 duration-500 pb-20">
<div className="flex flex-col md:flex-row md:items-end justify-between gap-6">
<div>
<h2 className="text-3xl font-black text-white flex items-center gap-3 tracking-tight">
<Trophy className="h-8 w-8 text-amber-500 drop-shadow-[0_0_10px_rgba(245,158,11,0.5)]" />
Social Leaderboard
</h2>
<p className="text-slate-400 mt-1 font-medium italic">Top contributors by experience and activity.</p>
</div>
<div className="relative w-full md:w-80 group">
<Search className="absolute left-4 top-1/2 -translate-y-1/2 h-4 w-4 text-slate-500 group-focus-within:text-primary transition-colors" />
<Input
placeholder="Search members..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="pl-12 bg-slate-900/50 border-slate-800 rounded-2xl h-12 focus:ring-primary/20 transition-all"
/>
</div>
</div>
{/* Top 3 Podium */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 pt-4">
{leaderboard.slice(0, 3).map((user, i) => (
<div key={user.user_id} className={cn(
"relative p-8 rounded-[40px] border flex flex-col items-center text-center overflow-hidden transition-all hover:scale-[1.02]",
i === 0 ? "bg-gradient-to-b from-amber-500/20 to-transparent border-amber-500/30 md:-translate-y-4 order-1 md:order-2" :
i === 1 ? "bg-gradient-to-b from-slate-300/10 to-transparent border-slate-500/20 order-2 md:order-1" :
"bg-gradient-to-b from-amber-800/20 to-transparent border-amber-900/20 order-3"
)}>
<div className="absolute top-0 right-0 p-4">
{i === 0 ? <Crown className="h-8 w-8 text-amber-500 fill-amber-500/20" /> :
i === 1 ? <Medal className="h-8 w-8 text-slate-300 fill-slate-300/20" /> :
<Medal className="h-8 w-8 text-amber-800 fill-amber-800/10" />}
</div>
<div className="h-20 w-20 rounded-full bg-slate-800 border-4 border-slate-700 mb-4 flex items-center justify-center relative shadow-2xl">
<User className="h-10 w-10 text-slate-500" />
<div className={cn(
"absolute -bottom-1 -right-1 h-8 w-8 rounded-full border-4 border-[#141B2D] flex items-center justify-center text-[10px] font-black",
i === 0 ? "bg-amber-500 text-black" : i === 1 ? "bg-slate-300 text-black" : "bg-amber-800 text-white"
)}>
#{i + 1}
</div>
</div>
<h3 className="text-xl font-black text-white truncate max-w-full">{user.name}</h3>
<p className="text-sm font-bold text-slate-500 flex items-center gap-1 mt-1">
LEVEL {user.level}
</p>
<div className="mt-6 flex items-center gap-2 px-4 py-2 bg-black/20 rounded-2xl border border-white/5">
<Zap className="h-3 w-3 text-primary" />
<span className="text-sm font-black text-slate-200">{user.xp.toLocaleString()} XP</span>
</div>
</div>
))}
</div>
{/* Main Leaderboard Table */}
<div className="bg-[#141B2D] border border-slate-800 rounded-[40px] overflow-hidden shadow-2xl">
<div className="overflow-x-auto">
<table className="w-full text-left">
<thead>
<tr className="border-b border-slate-800 bg-slate-900/30">
<th className="px-8 py-6 text-[10px] font-black uppercase text-slate-500 tracking-[0.2em]">Rank</th>
<th className="px-8 py-6 text-[10px] font-black uppercase text-slate-500 tracking-[0.2em]">User</th>
<th className="px-8 py-6 text-[10px] font-black uppercase text-slate-500 tracking-[0.2em]">Level</th>
<th className="px-8 py-6 text-[10px] font-black uppercase text-slate-500 tracking-[0.2em] text-right">Total XP</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-800/50">
{filteredData.slice(3).map((entry, i) => (
<tr key={entry.user_id} className="group hover:bg-white/[0.02] transition-colors">
<td className="px-8 py-6">
<span className="text-sm font-black text-slate-500 group-hover:text-white transition-colors">
#{i + 4}
</span>
</td>
<td className="px-8 py-6">
<div className="flex items-center gap-4">
<div className="h-10 w-10 rounded-xl bg-slate-800/50 border border-slate-700/50 flex items-center justify-center">
<User className="h-5 w-5 text-slate-600" />
</div>
<div className="flex flex-col">
<span className="text-sm font-bold text-slate-200 group-hover:text-white transition-colors">{entry.name}</span>
<span className="text-[10px] text-slate-600 font-mono tracking-tighter">{entry.user_id}</span>
</div>
</div>
</td>
<td className="px-8 py-6">
<div className="flex items-center gap-2">
<div className="px-3 py-1 bg-primary/10 rounded-lg border border-primary/20">
<span className="text-xs font-black text-primary italic">LVL {entry.level}</span>
</div>
</div>
</td>
<td className="px-8 py-6 text-right">
<div className="flex items-center justify-end gap-2">
<TrendingUp className="h-3 w-3 text-emerald-500 opacity-50" />
<span className="text-sm font-black text-slate-200 tabular-nums">{entry.xp.toLocaleString()}</span>
</div>
</td>
</tr>
))}
{filteredData.length === 3 && (
<tr>
<td colSpan={4} className="px-8 py-20 text-center">
<p className="text-slate-500 italic font-medium">No additional members ranked yet...</p>
</td>
</tr>
)}
</tbody>
</table>
</div>
{/* Pagination Overlay (Simulation) */}
<div className="p-6 border-t border-slate-800 bg-slate-900/20 flex items-center justify-between">
<p className="text-xs text-slate-500 font-medium">
Showing <span className="text-white">{filteredData.length}</span> active competitors
</p>
<div className="flex items-center gap-2">
<Button size="icon" variant="outline" className="h-8 w-8 rounded-lg border-slate-800" disabled>
<ChevronLeft className="h-4 w-4" />
</Button>
<Button size="icon" variant="outline" className="h-8 w-8 rounded-lg border-slate-800" disabled>
<ChevronRight className="h-4 w-4" />
</Button>
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,42 @@
/**
* ╔══════════════════════════════════════════════════════════════════╗
* ║ ║
* ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
* ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
* ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
* ║ ║
* ║ © 2026 CodeX Devs — All Rights Reserved ║
* ║ ║
* ║ discord ── https://discord.gg/codexdev ║
* ║ youtube ── https://youtube.com/@CodeXDevs ║
* ║ github ── https://github.com/RayExo ║
* ║ ║
* ╚══════════════════════════════════════════════════════════════════╝
*/
import React from "react";
import { BarChart4 } from "lucide-react";
import { api } from "@/lib/api";
import { LevelingForm } from "@/components/dashboard/leveling-form";
export default async function LevelingPage({ params }: { params: { guildId: string } }) {
const config = await api.getLeveling(params.guildId);
if (!config) return null;
return (
<div className="max-w-5xl mx-auto space-y-8 animate-in fade-in slide-in-from-bottom-2 duration-500">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
<div>
<h2 className="text-2xl font-bold text-white flex items-center gap-2">
<BarChart4 className="h-6 w-6 text-primary" />
Leveling System
</h2>
<p className="text-slate-400 mt-1">Reward active members with XP and rank progressions.</p>
</div>
</div>
<LevelingForm initialConfig={config} guildId={params.guildId} />
</div>
);
}

View File

@@ -0,0 +1,42 @@
/**
* ╔══════════════════════════════════════════════════════════════════╗
* ║ ║
* ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
* ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
* ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
* ║ ║
* ║ © 2026 CodeX Devs — All Rights Reserved ║
* ║ ║
* ║ discord ── https://discord.gg/codexdev ║
* ║ youtube ── https://youtube.com/@CodeXDevs ║
* ║ github ── https://github.com/RayExo ║
* ║ ║
* ╚══════════════════════════════════════════════════════════════════╝
*/
import React from "react";
export default function GuildLoading() {
return (
<div className="space-y-8 animate-in fade-in duration-500">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
<div className="space-y-2">
<div className="h-8 w-64 bg-slate-800 rounded-lg animate-pulse" />
<div className="h-4 w-96 bg-slate-800/50 rounded-lg animate-pulse" />
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-4 gap-8">
<div className="lg:col-span-3 space-y-4">
{[1, 2, 3, 4].map((i) => (
<div key={i} className="h-32 w-full bg-[#141B2D] border border-slate-800 rounded-[40px] animate-pulse" />
))}
</div>
<div className="space-y-6">
<div className="h-64 w-full bg-[#141B2D] border border-slate-800 rounded-[40px] animate-pulse" />
<div className="h-48 w-full bg-[#141B2D] border border-slate-800 rounded-[40px] animate-pulse" />
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,63 @@
/**
* ╔══════════════════════════════════════════════════════════════════╗
* ║ ║
* ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
* ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
* ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
* ║ ║
* ║ © 2026 CodeX Devs — All Rights Reserved ║
* ║ ║
* ║ discord ── https://discord.gg/codexdev ║
* ║ youtube ── https://youtube.com/@CodeXDevs ║
* ║ github ── https://github.com/RayExo ║
* ║ ║
* ╚══════════════════════════════════════════════════════════════════╝
*/
import React from "react";
import {
BellRing,
ChevronRight
} from "lucide-react";
import dynamic from "next/dynamic";
import { api } from "@/lib/api";
import { Button } from "@/components/ui/button";
const LoggingForm = dynamic(() => import("@/components/dashboard/logging-form").then(mod => mod.LoggingForm), {
loading: () => <div className="h-96 w-full animate-pulse bg-slate-800/20 rounded-[40px]" />
});
export default async function LoggingPage({ params }: { params: { guildId: string } }) {
const [loggingData, channelsData] = await Promise.all([
api.getLogging(params.guildId),
api.getChannels(params.guildId)
]);
if (!loggingData) return null;
return (
<div className="max-w-6xl mx-auto space-y-8 animate-in fade-in slide-in-from-bottom-2 duration-500 pb-20">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
<div>
<h2 className="text-2xl font-black text-white flex items-center gap-2 tracking-tight">
<BellRing className="h-6 w-6 text-primary" />
Audit Logging
</h2>
<p className="text-slate-400 mt-1 font-medium italic">Configure events and dispatch routes for your server.</p>
</div>
<div className="flex items-center gap-4">
<Button variant="outline" className="gap-2 border-slate-800 bg-slate-900/50 rounded-2xl">
Audit History
<ChevronRight className="h-4 w-4" />
</Button>
</div>
</div>
<LoggingForm
initialConfig={loggingData}
channels={channelsData}
guildId={params.guildId}
/>
</div>
);
}

View File

@@ -0,0 +1,152 @@
/**
* ╔══════════════════════════════════════════════════════════════════╗
* ║ ║
* ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
* ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
* ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
* ║ ║
* ║ © 2026 CodeX Devs — All Rights Reserved ║
* ║ ║
* ║ discord ── https://discord.gg/codexdev ║
* ║ youtube ── https://youtube.com/@CodeXDevs ║
* ║ github ── https://github.com/RayExo ║
* ║ ║
* ╚══════════════════════════════════════════════════════════════════╝
*/
import React from "react";
import {
Plus,
Settings2,
Terminal,
Database,
Search,
Zap,
ShieldCheck,
Ticket,
BarChart4,
FileText,
Activity
} from "lucide-react";
export default function GuildOverviewPage({ params }: { params: { guildId: string } }) {
const modules = [
{ title: "Auto Moderation", desc: "Anti-spam, bad words, and links protection.", icon: ShieldCheck, status: "Active" },
{ title: "Ticket System", desc: "Helpdesk for user support and inquiries.", icon: Ticket, status: "Configured" },
{ title: "Leveling", desc: "Gamify your community with XP and ranks.", icon: BarChart4, status: "Active" },
{ title: "Event Logging", desc: "Detailed audit logs for every server event.", icon: FileText, status: "Active" },
];
return (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Quick Config Column */}
<div className="space-y-8">
<section>
<div className="flex items-center gap-2 mb-6">
<h2 className="text-xl font-bold text-white tracking-tight">Active Modules</h2>
<div className="h-[2px] flex-1 bg-slate-800" />
<Plus className="h-4 w-4 text-slate-600 cursor-pointer hover:text-white transition-colors" />
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{modules.map((mod) => (
<div key={mod.title} className="bg-[#141B2D] border border-slate-800 p-5 rounded-2xl group hover:border-slate-600 transition-all shadow-sm">
<div className="flex items-start justify-between mb-4">
<div className="h-10 w-10 bg-slate-800 rounded-xl flex items-center justify-center text-primary group-hover:scale-110 transition-transform">
<mod.icon className="h-5 w-5" />
</div>
<span className="text-[10px] font-black uppercase text-emerald-500 bg-emerald-500/10 px-2 py-0.5 rounded-full border border-emerald-500/20">
{mod.status}
</span>
</div>
<h3 className="font-bold text-white mb-1">{mod.title}</h3>
<p className="text-xs text-slate-500 leading-relaxed">{mod.desc}</p>
</div>
))}
</div>
</section>
<section>
<div className="flex items-center gap-2 mb-6">
<h2 className="text-xl font-bold text-white tracking-tight">System Console</h2>
<div className="h-[2px] flex-1 bg-slate-800" />
</div>
<div className="bg-slate-900 border border-slate-800 rounded-2xl p-4 font-mono text-xs overflow-hidden shadow-2xl">
<div className="flex items-center gap-2 mb-3 border-b border-slate-800 pb-2">
<Terminal className="h-4 w-4 text-primary" />
<span className="text-slate-400">guild_event_stream_{params.guildId}</span>
</div>
<div className="space-y-1.5 opacity-80">
<p className="text-slate-500">[{new Date().toLocaleTimeString()}] <span className="text-emerald-500">INIT</span> Dashboard connected to WebSocket pool...</p>
<p className="text-slate-500">[{new Date().toLocaleTimeString()}] <span className="text-primary">INFO</span> Fetching guild_config from primary database...</p>
<p className="text-slate-500">[{new Date().toLocaleTimeString()}] <span className="text-emerald-500">DONE</span> Cache synchronized successfully.</p>
<p className="text-slate-400 animate-pulse">_</p>
</div>
</div>
</section>
</div>
{/* Integration Status Column */}
<div className="space-y-8">
<section className="bg-gradient-to-br from-primary/10 to-transparent border border-primary/20 rounded-3xl p-8 relative overflow-hidden group">
<div className="absolute top-0 right-0 p-8 opacity-10 group-hover:scale-125 transition-transform">
<Database className="h-48 w-48 text-white" />
</div>
<h2 className="text-2xl font-black text-white mb-2 italic tracking-tighter">Database Status</h2>
<p className="text-slate-400 text-sm mb-8 max-w-[280px]">All guild data is encrypted and replicated across our high-performance edge network.</p>
<div className="space-y-4 relative z-10">
{[
{ label: 'Uptime', value: '99.98%', icon: Zap },
{ label: 'Sync Delay', value: '12ms', icon: Activity },
{ label: 'Region', value: 'Global Edges', icon: GlobalizationIcon }
].map((stat) => (stat.icon &&
<div key={stat.label} className="flex items-center justify-between p-4 bg-black/20 backdrop-blur-md rounded-2xl border border-white/5">
<div className="flex items-center gap-3">
<div className="h-8 w-8 bg-black/40 rounded-lg flex items-center justify-center text-primary">
<stat.icon className="h-4 w-4" />
</div>
<span className="text-sm font-medium text-slate-300">{stat.label}</span>
</div>
<span className="text-sm font-bold text-white tracking-widest">{stat.value}</span>
</div>
))}
</div>
</section>
<section className="bg-[#141B2D] border border-slate-800 rounded-3xl p-8">
<h2 className="text-xl font-bold text-white mb-6">Security Context</h2>
<div className="flex items-center gap-6">
<div className="h-20 w-20 rounded-full border-4 border-emerald-500/20 flex items-center justify-center relative shadow-[0_0_20px_rgba(16,185,129,0.1)]">
<div className="h-16 w-16 rounded-full border-4 border-emerald-500 flex items-center justify-center text-emerald-500 font-black text-xl italic">
100%
</div>
</div>
<div>
<h3 className="text-lg font-bold text-white">Trust Factor</h3>
<p className="text-slate-400 text-sm">Bot is fully authenticated with administrator privileges.</p>
</div>
</div>
</section>
</div>
</div>
);
}
function GlobalizationIcon({ className }: { className?: string }) {
return (
<svg
className={className}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="12" cy="12" r="10" />
<line x1="2" y1="12" x2="22" y2="12" />
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
</svg>
);
}

View File

@@ -0,0 +1,276 @@
/**
* ╔══════════════════════════════════════════════════════════════════╗
* ║ ║
* ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
* ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
* ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
* ║ ║
* ║ © 2026 CodeX Devs — All Rights Reserved ║
* ║ ║
* ║ discord ── https://discord.gg/codexdev ║
* ║ youtube ── https://youtube.com/@CodeXDevs ║
* ║ github ── https://github.com/RayExo ║
* ║ ║
* ╚══════════════════════════════════════════════════════════════════╝
*/
"use client";
import React, { useState, useEffect } from "react";
import { MousePointer2, RefreshCcw, Plus, Trash2, BellRing, Info, Save } from "lucide-react";
import { toast } from "sonner";
import { api } from "@/lib/api";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { cn } from "@/lib/utils";
export default function ReactionRolesPage({ params }: { params: { guildId: string } }) {
const [loading, setLoading] = useState(true);
const [loadingAction, setLoadingAction] = useState(false);
const [config, setConfig] = useState<any>({ dm_enabled: true, roles: [] });
const [roles, setRoles] = useState<any[]>([]);
const [newRR, setNewRR] = useState({ message_id: "", emoji: "", role_id: "" });
const fetchData = async () => {
try {
setLoading(true);
const [configData, rolesData] = await Promise.all([
api.getRR(params.guildId),
api.getRoles(params.guildId),
]);
setConfig(configData);
setRoles(rolesData);
} catch (error) {
console.error("Failed to load RR:", error);
toast.error("Failed to load reaction roles configuration");
} finally {
setLoading(false);
}
};
useEffect(() => { fetchData(); }, [params.guildId]);
const filteredRoles = roles.filter(r => r.name !== "@everyone");
const toggleDM = async (val: boolean) => {
try {
await api.updateRR(params.guildId, { dm_enabled: val });
setConfig({ ...config, dm_enabled: val });
toast.success(`DM notifications ${val ? "enabled" : "disabled"}`);
} catch {
toast.error("Failed to update DM setting");
}
};
const handleAdd = async () => {
if (!newRR.message_id || !newRR.emoji || !newRR.role_id) {
toast.error("Please fill in all fields");
return;
}
setLoadingAction(true);
try {
await api.updateRR(params.guildId, {
add_role: { message_id: newRR.message_id, emoji: newRR.emoji, role_id: newRR.role_id },
});
setConfig({
...config,
roles: [...config.roles, { message_id: newRR.message_id, emoji: newRR.emoji, role_id: newRR.role_id }]
});
toast.success("Reaction role added");
setNewRR({ message_id: "", emoji: "", role_id: "" });
} catch {
toast.error("Failed to add reaction role");
} finally {
setLoadingAction(false);
}
};
const handleDelete = async (messageId: string, emoji: string) => {
setLoadingAction(true);
try {
await api.updateRR(params.guildId, {
remove_role_message_id: messageId,
remove_role_emoji: emoji,
});
setConfig({
...config,
roles: config.roles.filter((r: any) => !(String(r.message_id) === String(messageId) && r.emoji === emoji))
});
toast.success("Reaction role removed");
} catch {
toast.error("Failed to remove reaction role");
} finally {
setLoadingAction(false);
}
};
const formatColor = (decimal: number) => {
if (!decimal || decimal === 0) return "#94a3b8";
return `#${decimal.toString(16).padStart(6, '0')}`;
};
if (loading) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<RefreshCcw className="w-8 h-8 animate-spin text-primary" />
</div>
);
}
return (
<div className="max-w-6xl mx-auto space-y-8 animate-in fade-in slide-in-from-bottom-2 duration-500">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
<div>
<h2 className="text-2xl font-bold text-white flex items-center gap-2">
<MousePointer2 className="h-6 w-6 text-primary" />
Reaction Roles
</h2>
<p className="text-slate-400 mt-1">Allow members to self-assign roles by reacting to a message.</p>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-4 gap-8">
<div className="lg:col-span-3 space-y-6">
<div className="bg-[#141B2D] border border-slate-800 rounded-3xl overflow-hidden shadow-xl p-8 space-y-8">
{/* DM Toggle */}
<div className="flex items-center justify-between p-6 bg-slate-900/40 rounded-2xl border border-slate-800">
<div className="flex items-center gap-4">
<div className="p-3 bg-primary/20 text-primary rounded-xl"><BellRing className="w-5 h-5" /></div>
<div>
<h3 className="text-lg font-black text-white">DM Notifications</h3>
<p className="text-sm text-slate-400 mt-1">Send a DM when a user gets/loses a role.</p>
</div>
</div>
<Switch
checked={config.dm_enabled}
onCheckedChange={toggleDM}
className="scale-125"
/>
</div>
{/* Add New */}
<div className="pt-6 border-t border-slate-800 space-y-4">
<div className="flex items-center gap-3">
<div className="p-2.5 rounded-xl bg-primary/10 text-primary"><Plus className="h-5 w-5" /></div>
<h4 className="font-bold text-white text-base">Create New Reaction Role</h4>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="space-y-2">
<label className="text-xs font-bold text-slate-400">Message ID</label>
<Input
placeholder="e.g. 1234567890"
value={newRR.message_id}
onChange={(e) => setNewRR({ ...newRR, message_id: e.target.value })}
className="bg-slate-900/50 border-slate-800 h-12"
/>
</div>
<div className="space-y-2">
<label className="text-xs font-bold text-slate-400">Emoji</label>
<Input
placeholder="e.g. ✅ or custom emoji"
value={newRR.emoji}
onChange={(e) => setNewRR({ ...newRR, emoji: e.target.value })}
className="bg-slate-900/50 border-slate-800 h-12"
/>
</div>
<div className="space-y-2">
<label className="text-xs font-bold text-slate-400">Role to Assign</label>
<Select
value={newRR.role_id || ""}
onValueChange={(val) => setNewRR({ ...newRR, role_id: val })}
>
<SelectTrigger className="w-full h-12 bg-slate-900/50 border-slate-800">
<SelectValue placeholder="Select a role..." />
</SelectTrigger>
<SelectContent className="bg-slate-900 border-slate-800 max-h-[250px]">
{filteredRoles.map((role) => (
<SelectItem key={role.id} value={role.id} className="focus:bg-slate-800">
<div className="flex items-center gap-2">
<div className="w-2 h-2 rounded-full" style={{ backgroundColor: formatColor(role.color) }} />
{role.name}
</div>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
<Button onClick={handleAdd} disabled={loadingAction} className="w-full gap-2" variant="secondary">
{loadingAction ? <RefreshCcw className="h-4 w-4 animate-spin" /> : <Plus className="h-4 w-4" />}
Add to Active Listeners
</Button>
</div>
{/* Active roles */}
<div className="pt-6 border-t border-slate-800 space-y-3">
<h4 className="text-sm font-bold text-white flex items-center gap-2">
<MousePointer2 className="h-5 w-5 text-primary" /> Active Reaction Roles
</h4>
{config.roles.length === 0 ? (
<div className="text-center p-8 bg-slate-900/20 rounded-2xl border border-dashed border-slate-700">
<p className="text-sm text-slate-500 italic">No reaction roles configured.</p>
</div>
) : (
config.roles.map((rr: any, idx: number) => {
const roleName = filteredRoles.find(r => String(r.id) === String(rr.role_id))?.name || "Unknown Role";
return (
<div key={idx} className="flex items-center justify-between p-4 bg-slate-900/40 rounded-xl border border-slate-800">
<div className="flex items-center gap-6">
<div className="flex flex-col">
<span className="text-[10px] uppercase font-bold text-slate-500">Message ID</span>
<span className="text-sm font-mono text-slate-300">{rr.message_id}</span>
</div>
<div className="flex flex-col items-center">
<span className="text-[10px] uppercase font-bold text-slate-500">Emoji</span>
<span className="text-lg">{rr.emoji}</span>
</div>
<div className="flex flex-col">
<span className="text-[10px] uppercase font-bold text-slate-500">Role</span>
<span className="text-sm font-medium text-primary">{roleName}</span>
</div>
</div>
<Button
variant="ghost"
size="sm"
onClick={() => handleDelete(String(rr.message_id), rr.emoji)}
disabled={loadingAction}
className="text-red-400 hover:text-red-300 hover:bg-red-400/10 h-8 w-8 p-0"
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
);
})
)}
</div>
</div>
</div>
{/* Sidebar */}
<div className="space-y-6">
<div className="bg-gradient-to-br from-primary/10 to-transparent border border-primary/20 rounded-3xl p-6 relative overflow-hidden group">
<div className="absolute -right-4 -top-4 opacity-[0.03] group-hover:scale-110 transition-transform">
<MousePointer2 className="h-32 w-32 text-primary" />
</div>
<div className="flex items-center gap-2 mb-4">
<Info className="h-4 w-4 text-primary" />
<h3 className="text-sm font-bold text-white">Usage Guide</h3>
</div>
<ul className="text-xs text-slate-500 space-y-2">
<li> The bot must have access to the message you specify.</li>
<li> The bot role must be above the role being assigned.</li>
<li> The bot auto-reacts to the message once added.</li>
<li> Users react to get the role, un-react to remove it.</li>
</ul>
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,38 @@
/**
* ╔══════════════════════════════════════════════════════════════════╗
* ║ ║
* ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
* ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
* ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
* ║ ║
* ║ © 2026 CodeX Devs — All Rights Reserved ║
* ║ ║
* ║ discord ── https://discord.gg/codexdev ║
* ║ youtube ── https://youtube.com/@CodeXDevs ║
* ║ github ── https://github.com/RayExo ║
* ║ ║
* ╚══════════════════════════════════════════════════════════════════╝
*/
import React from "react";
import { Settings2 } from "lucide-react";
import { api } from "@/lib/api";
import { SettingsForm } from "@/components/dashboard/settings-form";
export default async function GuildSettingsPage({ params }: { params: { guildId: string } }) {
const config = await api.getPrefix(params.guildId);
return (
<div className="max-w-4xl mx-auto space-y-8 animate-in fade-in slide-in-from-bottom-2 duration-500">
<div>
<h2 className="text-2xl font-bold text-white flex items-center gap-2">
<Settings2 className="h-6 w-6 text-primary" />
General Settings
</h2>
<p className="text-slate-400 mt-1">Manage core bot configuration for this server.</p>
</div>
<SettingsForm initialPrefix={config.prefix} guildId={params.guildId} />
</div>
);
}

View File

@@ -0,0 +1,57 @@
/**
* ╔══════════════════════════════════════════════════════════════════╗
* ║ ║
* ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
* ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
* ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
* ║ ║
* ║ © 2026 CodeX Devs — All Rights Reserved ║
* ║ ║
* ║ discord ── https://discord.gg/codexdev ║
* ║ youtube ── https://youtube.com/@CodeXDevs ║
* ║ github ── https://github.com/RayExo ║
* ║ ║
* ╚══════════════════════════════════════════════════════════════════╝
*/
import React from "react";
import { Ticket, ExternalLink } from "lucide-react";
import dynamic from "next/dynamic";
import { api } from "@/lib/api";
import { Button } from "@/components/ui/button";
const TicketsForm = dynamic(() => import("@/components/dashboard/tickets-form").then(mod => mod.TicketsForm), {
loading: () => <div className="h-96 w-full animate-pulse bg-slate-800/20 rounded-3xl" />
});
export default async function TicketsPage({ params }: { params: { guildId: string } }) {
const config = await api.getTickets(params.guildId);
if (!config) return null;
return (
<div className="max-w-6xl mx-auto space-y-8 animate-in fade-in slide-in-from-bottom-2 duration-500 pb-20">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
<div>
<h2 className="text-2xl font-bold text-white flex items-center gap-2">
<Ticket className="h-6 w-6 text-primary" />
Ticket System
</h2>
<p className="text-slate-400 mt-1">Manage private support channels and inquiry categories.</p>
</div>
<div className="flex items-center gap-4">
<div className="bg-emerald-500/10 border border-emerald-500/20 px-4 py-2 rounded-2xl flex items-center gap-2">
<div className="h-2 w-2 rounded-full bg-emerald-500 animate-pulse" />
<span className="text-xs font-bold text-emerald-500 uppercase">Live System</span>
</div>
{/* <Button variant="outline" className="gap-2">
<ExternalLink className="h-4 w-4" />
Preview Panel
</Button> */}
</div>
</div>
<TicketsForm initialConfig={config} guildId={params.guildId} />
</div>
);
}

View File

@@ -0,0 +1,136 @@
/**
* ╔══════════════════════════════════════════════════════════════════╗
* ║ ║
* ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
* ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
* ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
* ║ ║
* ║ © 2026 CodeX Devs — All Rights Reserved ║
* ║ ║
* ║ discord ── https://discord.gg/codexdev ║
* ║ youtube ── https://youtube.com/@CodeXDevs ║
* ║ github ── https://github.com/RayExo ║
* ║ ║
* ╚══════════════════════════════════════════════════════════════════╝
*/
"use client";
import React, { useState, useEffect } from "react";
import { Search, Save, RefreshCcw, Bell } from "lucide-react";
import { toast } from "sonner";
import { api } from "@/lib/api";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label";
export default function TrackingPage({ params }: { params: { guildId: string } }) {
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [channels, setChannels] = useState<any[]>([]);
const [config, setConfig] = useState<any>({
channel_id: null,
});
const fetchData = async () => {
try {
setLoading(true);
const [configData, channelsData] = await Promise.all([
api.getTracking(params.guildId),
api.getChannels(params.guildId),
]);
setConfig(configData);
setChannels(channelsData);
} catch (error) {
console.error("Failed to fetch tracking data:", error);
toast.error("Failed to load tracking configuration");
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, [params.guildId]);
const handleSave = async () => {
try {
setSaving(true);
await api.updateTracking(params.guildId, config);
toast.success("Tracking configuration saved successfully");
} catch (error) {
console.error("Failed to save tracking config:", error);
toast.error("Failed to save tracking configuration");
} finally {
setSaving(false);
}
};
if (loading) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<RefreshCcw className="w-8 h-8 animate-spin text-primary" />
</div>
);
}
return (
<div className="space-y-6">
<div>
<h2 className="text-3xl font-bold tracking-tight">Invite Tracking</h2>
<p className="text-muted-foreground">
Configure where the bot logs invite information when a member joins.
</p>
</div>
<Card className="border-primary/20 bg-background/50 backdrop-blur-xl">
<CardHeader>
<div className="flex items-center gap-2">
<Search className="w-5 h-5 text-primary" />
<CardTitle>Logging Configuration</CardTitle>
</div>
<CardDescription>
Choose a channel to receive notifications about member invites and join sources.
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="space-y-2">
<Label>Log Channel</Label>
<Select
value={config.channel_id?.toString() || "none"}
onValueChange={(val) => setConfig({ ...config, channel_id: val === "none" ? null : parseInt(val) })}
options={[
{ value: "none", label: "Disabled" },
...channels.map((chan) => ({ value: chan.id.toString(), label: `#${chan.name}` }))
]}
/>
</div>
<div className="flex justify-end pt-4">
<Button onClick={handleSave} disabled={saving} className="gap-2">
{saving ? <RefreshCcw className="w-4 h-4 animate-spin" /> : <Save className="w-4 h-4" />}
Save Changes
</Button>
</div>
</CardContent>
</Card>
<Card className="border-yellow-500/20 bg-yellow-500/5">
<CardHeader>
<div className="flex items-center gap-2">
<Bell className="w-5 h-5 text-yellow-500" />
<CardTitle className="text-yellow-500">Information</CardTitle>
</div>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">
The invite tracking module monitors all join events and attempts to identify which invite link was used.
This information is then logged to your chosen channel.
</p>
</CardContent>
</Card>
</div>
);
}

View File

@@ -0,0 +1,239 @@
/**
* ╔══════════════════════════════════════════════════════════════════╗
* ║ ║
* ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
* ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
* ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
* ║ ║
* ║ © 2026 CodeX Devs — All Rights Reserved ║
* ║ ║
* ║ discord ── https://discord.gg/codexdev ║
* ║ youtube ── https://youtube.com/@CodeXDevs ║
* ║ github ── https://github.com/RayExo ║
* ║ ║
* ╚══════════════════════════════════════════════════════════════════╝
*/
"use client";
import React, { useState, useEffect } from "react";
import { Link2, RefreshCcw, Plus, Trash2, Info, Save } from "lucide-react";
import { toast } from "sonner";
import { api } from "@/lib/api";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { cn } from "@/lib/utils";
export default function VanityRolesPage({ params }: { params: { guildId: string } }) {
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [roles, setRoles] = useState<any[]>([]);
const [channels, setChannels] = useState<any[]>([]);
const [setups, setSetups] = useState<any[]>([]);
const [newSetup, setNewSetup] = useState({ vanity: "", role_id: "", log_channel_id: "" });
const fetchData = async () => {
try {
setLoading(true);
const [setupsData, rolesData, channelsData] = await Promise.all([
api.getVanityRoles(params.guildId),
api.getRoles(params.guildId),
api.getChannels(params.guildId),
]);
setSetups(setupsData);
setRoles(rolesData);
setChannels(channelsData);
} catch (error) {
console.error("Failed to fetch vanity roles data:", error);
toast.error("Failed to load vanity roles configuration");
} finally {
setLoading(false);
}
};
useEffect(() => { fetchData(); }, [params.guildId]);
const textChannels = channels.filter(c => c.type === "0" || c.type === 0);
const filteredRoles = roles.filter(r => r.name !== "@everyone");
const handleAdd = async () => {
if (!newSetup.vanity || !newSetup.role_id || !newSetup.log_channel_id) {
toast.error("Please fill in all fields");
return;
}
setSaving(true);
try {
await api.addVanityRole(params.guildId, {
vanity: newSetup.vanity,
role_id: newSetup.role_id,
log_channel_id: newSetup.log_channel_id,
});
toast.success("Vanity role setup added!");
setNewSetup({ vanity: "", role_id: "", log_channel_id: "" });
fetchData();
} catch (error) {
toast.error("Failed to add vanity role setup");
} finally {
setSaving(false);
}
};
const handleDelete = async (vanity: string) => {
try {
await api.deleteVanityRole(params.guildId, vanity);
toast.success("Vanity role setup deleted");
fetchData();
} catch (error) {
toast.error("Failed to delete vanity role setup");
}
};
const formatColor = (decimal: number) => {
if (!decimal || decimal === 0) return "#94a3b8";
return `#${decimal.toString(16).padStart(6, '0')}`;
};
if (loading) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<RefreshCcw className="w-8 h-8 animate-spin text-primary" />
</div>
);
}
return (
<div className="max-w-6xl mx-auto space-y-8 animate-in fade-in slide-in-from-bottom-2 duration-500">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
<div>
<h2 className="text-2xl font-bold text-white flex items-center gap-2">
<Link2 className="h-6 w-6 text-primary" />
Vanity Roles
</h2>
<p className="text-slate-400 mt-1">Give special roles to members with your vanity/invite link in their status.</p>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-4 gap-8">
<div className="lg:col-span-3 space-y-6">
<div className="bg-[#141B2D] border border-slate-800 rounded-3xl overflow-hidden shadow-xl p-8 space-y-8">
{/* Add New Setup */}
<div className="space-y-4">
<div className="flex items-center gap-3">
<div className="p-2.5 rounded-xl bg-primary/10 text-primary"><Plus className="h-5 w-5" /></div>
<h4 className="font-bold text-white text-base">Add New Vanity Setup</h4>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="space-y-2">
<label className="text-xs font-bold text-slate-400">Vanity Text / URL</label>
<Input
placeholder="e.g. .gg/my-server"
value={newSetup.vanity}
onChange={(e) => setNewSetup({ ...newSetup, vanity: e.target.value })}
className="bg-slate-900/50 border-slate-800 h-12"
/>
</div>
<div className="space-y-2">
<label className="text-xs font-bold text-slate-400">Role to Give</label>
<Select value={newSetup.role_id} onValueChange={(val) => setNewSetup({ ...newSetup, role_id: val })}>
<SelectTrigger className="w-full h-12 bg-slate-900/50 border-slate-800">
<SelectValue placeholder="Select a role..." />
</SelectTrigger>
<SelectContent className="bg-slate-900 border-slate-800 max-h-[300px]">
{filteredRoles.map((r) => (
<SelectItem key={r.id} value={r.id} className="focus:bg-slate-800">
<div className="flex items-center gap-2">
<div className="w-2 h-2 rounded-full" style={{ backgroundColor: formatColor(r.color) }} />
{r.name}
</div>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<label className="text-xs font-bold text-slate-400">Log Channel</label>
<Select value={newSetup.log_channel_id} onValueChange={(val) => setNewSetup({ ...newSetup, log_channel_id: val })}>
<SelectTrigger className="w-full h-12 bg-slate-900/50 border-slate-800">
<SelectValue placeholder="Select a channel..." />
</SelectTrigger>
<SelectContent className="bg-slate-900 border-slate-800 max-h-[300px]">
{textChannels.map((c) => (
<SelectItem key={c.id} value={c.id} className="focus:bg-slate-800">#{c.name}</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
<Button onClick={handleAdd} disabled={saving} className="w-full gap-2" variant="secondary">
{saving ? <RefreshCcw className="h-4 w-4 animate-spin" /> : <Plus className="h-4 w-4" />}
Add Vanity Setup
</Button>
</div>
{/* Active Setups */}
<div className="pt-6 border-t border-slate-800 space-y-4">
<h4 className="text-sm font-bold text-white flex items-center gap-2">
<Link2 className="h-5 w-5 text-primary" /> Active Setups
</h4>
{setups.length === 0 ? (
<div className="text-center p-8 bg-slate-900/20 rounded-2xl border border-dashed border-slate-700">
<Link2 className="w-10 h-10 text-slate-600 mx-auto mb-3" />
<p className="text-sm text-slate-500">No vanity role setups configured yet.</p>
</div>
) : (
setups.map((setup, index) => {
const role = filteredRoles.find(r => r.id === String(setup.role_id));
const channel = channels.find(c => c.id === String(setup.log_channel_id));
return (
<div key={index} className="flex items-center justify-between p-4 bg-slate-900/40 rounded-xl border border-slate-800 animate-in zoom-in-95 duration-200">
<div className="flex items-center gap-8">
<div className="flex flex-col">
<span className="text-[10px] uppercase font-bold text-slate-500">Vanity Text</span>
<span className="font-medium text-primary">{setup.vanity}</span>
</div>
<div className="flex flex-col">
<span className="text-[10px] uppercase font-bold text-slate-500">Role</span>
<span className="font-medium text-slate-200">{role?.name || "Unknown"}</span>
</div>
<div className="flex flex-col">
<span className="text-[10px] uppercase font-bold text-slate-500">Log Channel</span>
<span className="font-medium text-slate-200">#{channel?.name || "Unknown"}</span>
</div>
</div>
<Button variant="ghost" size="sm" onClick={() => handleDelete(setup.vanity)} className="text-red-400 hover:text-red-300 hover:bg-red-400/10 h-8 w-8 p-0">
<Trash2 className="w-4 h-4" />
</Button>
</div>
);
})
)}
</div>
</div>
</div>
{/* Sidebar */}
<div className="space-y-6">
<div className="bg-gradient-to-br from-primary/10 to-transparent border border-primary/20 rounded-3xl p-6 relative overflow-hidden group">
<div className="absolute -right-4 -top-4 opacity-[0.03] group-hover:scale-110 transition-transform">
<Link2 className="h-32 w-32 text-primary" />
</div>
<div className="flex items-center gap-2 mb-4">
<Info className="h-4 w-4 text-primary" />
<h3 className="text-sm font-bold text-white">How It Works</h3>
</div>
<ul className="text-xs text-slate-500 space-y-2">
<li> The bot monitors member custom statuses.</li>
<li> If a member adds the vanity text, the role is auto-assigned.</li>
<li> Removing the text will remove the role.</li>
<li> Logs are sent to the configured channel.</li>
</ul>
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,264 @@
/**
* ╔══════════════════════════════════════════════════════════════════╗
* ║ ║
* ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
* ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
* ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
* ║ ║
* ║ © 2026 CodeX Devs — All Rights Reserved ║
* ║ ║
* ║ discord ── https://discord.gg/codexdev ║
* ║ youtube ── https://youtube.com/@CodeXDevs ║
* ║ github ── https://github.com/RayExo ║
* ║ ║
* ╚══════════════════════════════════════════════════════════════════╝
*/
"use client";
import React, { useState, useEffect } from "react";
import { Shield, Save, RefreshCcw, Power, Fingerprint, Bell, Hash, UserCheck, Info } from "lucide-react";
import { toast } from "sonner";
import { api } from "@/lib/api";
import { Button } from "@/components/ui/button";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { cn } from "@/lib/utils";
export default function VerificationPage({ params }: { params: { guildId: string } }) {
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [channels, setChannels] = useState<any[]>([]);
const [roles, setRoles] = useState<any[]>([]);
const [config, setConfig] = useState<any>({
verification_channel_id: null,
verified_role_id: null,
log_channel_id: null,
verification_method: "both",
enabled: true,
});
const fetchData = async () => {
try {
setLoading(true);
const [configData, channelsData, rolesData] = await Promise.all([
api.getVerification(params.guildId),
api.getChannels(params.guildId),
api.getRoles(params.guildId),
]);
setConfig(configData);
setChannels(channelsData);
setRoles(rolesData);
} catch (error) {
console.error("Failed to fetch verification data:", error);
toast.error("Failed to load verification configuration");
} finally {
setLoading(false);
}
};
useEffect(() => { fetchData(); }, [params.guildId]);
const handleSave = async () => {
setSaving(true);
const promise = api.updateVerification(params.guildId, {
verification_channel_id: config.verification_channel_id,
verified_role_id: config.verified_role_id,
log_channel_id: config.log_channel_id,
verification_method: config.verification_method,
enabled: config.enabled,
});
toast.promise(promise, {
loading: 'Saving verification configuration...',
success: 'Verification settings saved!',
error: 'Failed to save verification config',
});
try { await promise; } catch {} finally { setSaving(false); }
};
const textChannels = channels.filter(c => c.type === "0" || c.type === 0);
const filteredRoles = roles.filter(r => r.name !== "@everyone");
if (loading) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<RefreshCcw className="w-8 h-8 animate-spin text-primary" />
</div>
);
}
return (
<div className="max-w-6xl mx-auto space-y-8 animate-in fade-in slide-in-from-bottom-2 duration-500">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
<div>
<h2 className="text-2xl font-bold text-white flex items-center gap-2">
<Shield className="h-6 w-6 text-primary" />
Verification
</h2>
<p className="text-slate-400 mt-1">Set up a gatekeeper system to verify new members.</p>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-4 gap-8">
<div className="lg:col-span-3 space-y-6">
<div className="bg-[#141B2D] border border-slate-800 rounded-3xl overflow-hidden shadow-xl p-8 space-y-8">
{/* Enable/Disable Toggle */}
<div className="flex items-center justify-between p-6 bg-slate-900/40 rounded-2xl border border-slate-800">
<div className="flex items-center gap-4">
<div className={cn("p-3 rounded-xl transition-colors", config.enabled ? "bg-emerald-500/20 text-emerald-500" : "bg-red-500/20 text-red-500")}>
<Power className="w-5 h-5" />
</div>
<div>
<h3 className="text-lg font-black text-white">System Status</h3>
<p className="text-sm text-slate-400 mt-1">Enable or disable the verification module.</p>
</div>
</div>
<div className="flex items-center gap-4">
<div className="px-4 py-2 rounded-full bg-slate-900 border border-slate-800 flex items-center gap-2">
<div className={cn("w-2 h-2 rounded-full", config.enabled ? 'bg-emerald-500 animate-pulse' : 'bg-red-500')} />
<span className="text-xs font-bold uppercase text-slate-300">
{config.enabled ? 'Active' : 'Inactive'}
</span>
</div>
<Switch
checked={config.enabled}
onCheckedChange={(val) => setConfig({ ...config, enabled: val })}
className="scale-125 data-[state=checked]:bg-emerald-500"
/>
</div>
</div>
{/* Selectors Grid */}
<div className={cn("grid grid-cols-1 md:grid-cols-2 gap-6 transition-all duration-300", !config.enabled && "opacity-50 pointer-events-none")}>
{/* Verification Channel */}
<div className="p-6 bg-slate-900/40 border border-slate-800 rounded-2xl space-y-4">
<div className="flex items-center gap-3">
<div className="p-3 bg-primary/20 text-primary rounded-xl"><Hash className="w-5 h-5" /></div>
<div>
<h4 className="font-bold text-white">Verification Channel</h4>
<p className="text-xs text-slate-400 mt-1">Channel where verification happens</p>
</div>
</div>
<Select
value={config.verification_channel_id || "none"}
onValueChange={(val) => setConfig({ ...config, verification_channel_id: val === "none" ? null : val })}
>
<SelectTrigger className="w-full h-12 bg-slate-900 border-slate-800 font-medium">
<SelectValue placeholder="Select a channel..." />
</SelectTrigger>
<SelectContent className="bg-slate-900 border-slate-800 max-h-[300px]">
<SelectItem value="none" className="text-slate-400 focus:bg-slate-800">Not Set</SelectItem>
{textChannels.map((c) => (
<SelectItem key={c.id} value={c.id} className="focus:bg-slate-800">#{c.name}</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* Verified Role */}
<div className="p-6 bg-slate-900/40 border border-slate-800 rounded-2xl space-y-4">
<div className="flex items-center gap-3">
<div className="p-3 bg-emerald-500/20 text-emerald-500 rounded-xl"><UserCheck className="w-5 h-5" /></div>
<div>
<h4 className="font-bold text-white">Verified Role</h4>
<p className="text-xs text-slate-400 mt-1">Role given after verification</p>
</div>
</div>
<Select
value={config.verified_role_id || "none"}
onValueChange={(val) => setConfig({ ...config, verified_role_id: val === "none" ? null : val })}
>
<SelectTrigger className="w-full h-12 bg-slate-900 border-slate-800 font-medium">
<SelectValue placeholder="Select a role..." />
</SelectTrigger>
<SelectContent className="bg-slate-900 border-slate-800 max-h-[300px]">
<SelectItem value="none" className="text-slate-400 focus:bg-slate-800">Not Set</SelectItem>
{filteredRoles.map((r) => (
<SelectItem key={r.id} value={r.id} className="focus:bg-slate-800">{r.name}</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* Verification Method */}
<div className="p-6 bg-slate-900/40 border border-slate-800 rounded-2xl space-y-4">
<div className="flex items-center gap-3">
<div className="p-3 bg-purple-500/20 text-purple-500 rounded-xl"><Fingerprint className="w-5 h-5" /></div>
<div>
<h4 className="font-bold text-white">Verification Method</h4>
<p className="text-xs text-slate-400 mt-1">How users verify themselves</p>
</div>
</div>
<Select
value={config.verification_method || "both"}
onValueChange={(val) => setConfig({ ...config, verification_method: val })}
>
<SelectTrigger className="w-full h-12 bg-slate-900 border-slate-800 font-medium">
<SelectValue placeholder="Select method..." />
</SelectTrigger>
<SelectContent className="bg-slate-900 border-slate-800">
<SelectItem value="button" className="focus:bg-slate-800">Button Click</SelectItem>
<SelectItem value="captcha" className="focus:bg-slate-800">CAPTCHA Image</SelectItem>
<SelectItem value="both" className="focus:bg-slate-800">Both (Combined)</SelectItem>
</SelectContent>
</Select>
</div>
{/* Log Channel */}
<div className="p-6 bg-slate-900/40 border border-slate-800 rounded-2xl space-y-4">
<div className="flex items-center gap-3">
<div className="p-3 bg-blue-500/20 text-blue-500 rounded-xl"><Bell className="w-5 h-5" /></div>
<div>
<h4 className="font-bold text-white">Log Channel</h4>
<p className="text-xs text-slate-400 mt-1">Where verification logs are sent</p>
</div>
</div>
<Select
value={config.log_channel_id || "none"}
onValueChange={(val) => setConfig({ ...config, log_channel_id: val === "none" ? null : val })}
>
<SelectTrigger className="w-full h-12 bg-slate-900 border-slate-800 font-medium">
<SelectValue placeholder="Select a channel..." />
</SelectTrigger>
<SelectContent className="bg-slate-900 border-slate-800 max-h-[300px]">
<SelectItem value="none" className="text-slate-400 focus:bg-slate-800">Disabled</SelectItem>
{textChannels.map((c) => (
<SelectItem key={c.id} value={c.id} className="focus:bg-slate-800">#{c.name}</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
<Button onClick={handleSave} disabled={saving} className="w-full h-14 text-base font-bold gap-2">
{saving ? <RefreshCcw className="h-5 w-5 animate-spin" /> : <Save className="h-5 w-5" />}
Save Configuration
</Button>
</div>
</div>
{/* Sidebar */}
<div className="space-y-6">
<div className="bg-gradient-to-br from-primary/10 to-transparent border border-primary/20 rounded-3xl p-6 relative overflow-hidden group">
<div className="absolute -right-4 -top-4 opacity-[0.03] group-hover:scale-110 transition-transform">
<Shield className="h-32 w-32 text-primary" />
</div>
<div className="flex items-center gap-2 mb-4">
<Info className="h-4 w-4 text-primary" />
<h3 className="text-sm font-bold text-white">Important</h3>
</div>
<p className="text-xs text-slate-400 leading-relaxed mb-4">
Ensure @everyone does NOT have &quot;Send Messages&quot; in non-verification channels.
</p>
<ul className="text-xs text-slate-500 space-y-2">
<li> Assign the Verified Role only to members who pass.</li>
<li> The bot role must be above the Verified Role.</li>
<li> CAPTCHA provides stronger anti-bot protection.</li>
</ul>
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,49 @@
/**
* ╔══════════════════════════════════════════════════════════════════╗
* ║ ║
* ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
* ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
* ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
* ║ ║
* ║ © 2026 CodeX Devs — All Rights Reserved ║
* ║ ║
* ║ discord ── https://discord.gg/codexdev ║
* ║ youtube ── https://youtube.com/@CodeXDevs ║
* ║ github ── https://github.com/RayExo ║
* ║ ║
* ╚══════════════════════════════════════════════════════════════════╝
*/
import React from "react";
import { SmilePlus } from "lucide-react";
import dynamic from "next/dynamic";
import { api } from "@/lib/api";
const WelcomeForm = dynamic(() => import("@/components/dashboard/welcome-form").then(mod => mod.WelcomeForm), {
loading: () => <div className="h-96 w-full animate-pulse bg-slate-800/20 rounded-3xl" />
});
export default async function WelcomePage({ params }: { params: { guildId: string } }) {
const [welcomeData, channelsData] = await Promise.all([
api.getWelcome(params.guildId),
api.getChannels(params.guildId)
]);
if (!welcomeData) return null;
return (
<div className="max-w-6xl mx-auto space-y-8 animate-in fade-in slide-in-from-bottom-2 duration-500">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
<div>
<h2 className="text-2xl font-bold text-white flex items-center gap-2">
<SmilePlus className="h-6 w-6 text-primary" />
Welcomer
</h2>
<p className="text-slate-400 mt-1">Greet new members to your server.</p>
</div>
</div>
<WelcomeForm initialConfig={welcomeData} channels={channelsData} guildId={params.guildId} />
</div>
);
}

View File

@@ -0,0 +1,205 @@
/**
* ╔══════════════════════════════════════════════════════════════════╗
* ║ ║
* ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
* ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
* ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
* ║ ║
* ║ © 2026 CodeX Devs — All Rights Reserved ║
* ║ ║
* ║ discord ── https://discord.gg/codexdev ║
* ║ youtube ── https://youtube.com/@CodeXDevs ║
* ║ github ── https://github.com/RayExo ║
* ║ ║
* ╚══════════════════════════════════════════════════════════════════╝
*/
import React from "react";
import Image from "next/image";
import Link from "next/link";
import { Users, ShieldCheck, ChevronRight, Hash } from "lucide-react";
import { api } from "@/lib/api";
import { Button } from "@/components/ui/button";
import { GuildSummary } from "@/types/api";
import { getServerSession } from "next-auth/next";
import { authOptions } from "@/lib/auth";
import { redirect } from "next/navigation";
export const dynamic = "force-dynamic";
export const revalidate = 0;
export default async function GuildsPage() {
const session = await getServerSession(authOptions);
if (!session || !session.accessToken) {
redirect("/");
}
let botGuilds: GuildSummary[] = [];
let userGuilds: any[] = [];
let userDiscordError: string | null = null;
let botError: string | null = null;
try {
botGuilds = await api.listGuilds();
} catch (err: any) {
console.error("Failed to fetch bot guilds:", err);
botError = err.message || "Failed to load bot servers.";
}
try {
const res = await fetch("https://discord.com/api/users/@me/guilds", {
headers: {
Authorization: `Bearer ${session.accessToken}`,
},
next: { revalidate: 300 } // Cache for 5 mins
});
if (res.ok) {
userGuilds = await res.json();
} else {
userDiscordError = "Failed to fetch your Discord servers.";
}
} catch (err) {
console.error("Discord API Error:", err);
userDiscordError = "Error connecting to Discord.";
}
// Filter out guilds that the user is an admin of (permission flag 0x8 or MANAGE_GUILD 0x20)
// Manage Server is 0x20, Admin is 0x8
const MANAGE_GUILD = BigInt(0x20);
const ADMINISTRATOR = BigInt(0x8);
const adminUserGuilds = userGuilds.filter(g => {
try {
const perms = BigInt(g.permissions);
return (perms & ADMINISTRATOR) === ADMINISTRATOR ||
(perms & MANAGE_GUILD) === MANAGE_GUILD ||
g.owner === true;
} catch {
return g.owner === true;
}
});
const adminGuildIds = new Set(adminUserGuilds.map(g => String(g.id)));
// The intersecting guilds we can manage
const guilds = botGuilds.filter(g => adminGuildIds.has(String(g.id)));
const error = botError || userDiscordError;
return (
<div className="space-y-8">
<div className="flex justify-between items-end">
<div>
<h1 className="text-3xl font-bold text-white">Your Servers</h1>
<p className="text-slate-400 mt-2">
Select a server to manage its unique configuration and modules.
</p>
</div>
<div className="text-sm font-medium px-4 py-2 bg-slate-800 rounded-xl border border-slate-700 text-slate-300">
Showing <span className="text-white">{guilds.length}</span> active guilds
</div>
</div>
{error ? (
<div className="bg-red-500/10 border border-red-500/20 p-8 rounded-2xl text-center">
<ShieldCheck className="h-12 w-12 text-red-500 mx-auto mb-4 opacity-50" />
<h3 className="text-white font-bold text-lg">Connection Error</h3>
<p className="text-slate-400 mt-2">{error}</p>
<Button variant="outline" className="mt-6">Retry Connection</Button>
</div>
) : guilds.length === 0 ? (
<div className="bg-slate-800/30 border border-slate-800 border-dashed p-16 rounded-3xl text-center">
<div className="h-16 w-16 bg-slate-800 rounded-full flex items-center justify-center mx-auto mb-6">
<Users className="h-8 w-8 text-slate-600" />
</div>
<h3 className="text-white font-bold text-xl">No Servers Found</h3>
<p className="text-slate-400 mt-2 max-w-sm mx-auto">
The bot hasn&apos;t joined any servers yet, or you don&apos;t have permission.
</p>
<div className="mt-8 bg-slate-900/50 p-4 rounded-xl text-left font-mono text-sm text-slate-300 max-w-2xl mx-auto overflow-auto max-h-48 whitespace-pre">
<p className="font-bold text-red-400 mb-2">Diagnostic Data:</p>
<p>1. Bot&apos;s Cache Total Servers: {botGuilds.length}</p>
<p>2. Your Discord Profile Total Servers: {userGuilds.length}</p>
<p>3. Your Discord Profile Admin/Manage Servers: {adminUserGuilds.length}</p>
{botGuilds.length > 0 && adminUserGuilds.length > 0 && (
<div className="mt-4 pt-4 border-t border-white/10">
<p className="text-emerald-400 mb-1">Bot First Server ID: {botGuilds[0].id} (Type: {typeof botGuilds[0].id})</p>
<p className="text-blue-400">Your First Admin Server ID: {adminUserGuilds[0].id} (Type: {typeof adminUserGuilds[0].id})</p>
</div>
)}
<hr className="my-2 border-white/10" />
<p>Intersection Mappings found: {guilds.length}</p>
{userDiscordError && <p className="text-red-400">User Error: {userDiscordError}</p>}
{botError && <p className="text-red-400">Bot Error: {botError}</p>}
</div>
<Button className="mt-8">Invite to Discord</Button>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
{guilds.map((guild) => (
<div
key={guild.id}
className="bg-[#141B2D] border border-slate-800 rounded-3xl group hover:border-primary/50 hover:bg-[#202c3f] transition-all duration-300 overflow-hidden shadow-sm hover:shadow-primary/5 shadow-black/20"
>
<div className="p-6">
<div className="flex items-start justify-between mb-6">
<div className="relative">
{guild.icon_url ? (
<Image
src={guild.icon_url}
alt={guild.name}
width={64}
height={64}
className="rounded-2xl border-2 border-slate-800 shadow-xl group-hover:scale-105 transition-transform"
/>
) : (
<div className="h-16 w-16 bg-primary/20 rounded-2xl flex items-center justify-center border-2 border-slate-800 text-primary font-bold text-2xl shadow-xl group-hover:scale-105 transition-transform">
{guild.name.charAt(0)}
</div>
)}
<div className="absolute -bottom-1 -right-1 h-4 w-4 rounded-full bg-emerald-500 border-2 border-[#141B2D]" title="Bot Online" />
</div>
<div className="flex flex-col items-end text-right">
<span className="text-[10px] uppercase font-bold text-slate-500 tracking-widest mb-1">Guild ID</span>
<span className="text-xs font-mono text-slate-400 bg-black/20 px-2 py-1 rounded-lg border border-white/5 truncate max-w-[120px]">
{guild.id}
</span>
</div>
</div>
<div>
<h3 className="text-xl font-bold text-white truncate group-hover:text-primary transition-colors">
{guild.name}
</h3>
<div className="flex items-center gap-4 mt-4 text-slate-400">
<div className="flex items-center gap-1.5 bg-slate-800/50 px-3 py-1.5 rounded-xl border border-white/5">
<Users className="h-4 w-4 text-slate-500" />
<span className="text-sm font-semibold text-slate-300">{guild.member_count.toLocaleString()}</span>
</div>
<div className="flex items-center gap-1.5 bg-slate-800/50 px-3 py-1.5 rounded-xl border border-white/5">
<Hash className="h-4 w-4 text-slate-500" />
<span className="text-sm font-semibold text-slate-300">Active</span>
</div>
</div>
</div>
</div>
<div className="px-6 py-4 bg-slate-800/20 border-t border-slate-800/80 group-hover:bg-primary/5 transition-colors">
<Button className="w-full justify-between group/btn py-6" variant="secondary" asChild>
<Link href={`/dashboard/guild/${guild.id}`}>
<span>Manage Server</span>
<ChevronRight className="h-4 w-4 group-hover/btn:translate-x-1 transition-transform" />
</Link>
</Button>
</div>
</div>
))}
</div>
)}
</div>
);
}

View File

@@ -0,0 +1,462 @@
/**
* ╔══════════════════════════════════════════════════════════════════╗
* ║ ║
* ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
* ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
* ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
* ║ ║
* ║ © 2026 CodeX Devs — All Rights Reserved ║
* ║ ║
* ║ discord ── https://discord.gg/codexdev ║
* ║ youtube ── https://youtube.com/@CodeXDevs ║
* ║ github ── https://github.com/RayExo ║
* ║ ║
* ╚══════════════════════════════════════════════════════════════════╝
*/
"use client";
import React, { useState, useEffect, useRef } from "react";
import Link from "next/link";
import { usePathname } from "next/navigation";
import {
LayoutDashboard, Server, ShieldCheck, Ticket, BarChart4, FileText, Settings,
Menu, X, Bell, User, Search, ChevronRight, Star, Sparkles, LogOut,
LifeBuoy, ChevronDown, Bot, Shield
} from "lucide-react";
import { useSession, signIn, signOut } from "next-auth/react";
import { cn, isAdmin } from "@/lib/utils";
import { api } from "@/lib/api";
import { AdminConfig } from "@/types/api";
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const [isProfileOpen, setIsProfileOpen] = useState(false);
const pathname = usePathname();
const { data: session, status } = useSession();
const [isNotificationsOpen, setIsNotificationsOpen] = useState(false);
const [globalNotification, setGlobalNotification] = useState<string | null>(null);
const bellRef = useRef<HTMLDivElement>(null);
const profileRef = useRef<HTMLDivElement>(null);
// Close dropdowns on click outside
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
const target = event.target as Node;
if (bellRef.current && !bellRef.current.contains(target)) {
setIsNotificationsOpen(false);
}
if (profileRef.current && !profileRef.current.contains(target)) {
setIsProfileOpen(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);
// Auto-close sidebar on mobile when navigating
React.useEffect(() => {
setIsSidebarOpen(false);
setIsProfileOpen(false);
}, [pathname]);
React.useEffect(() => {
if (status === "unauthenticated") {
signIn("discord");
}
// Fetch global notification
const fetchNotification = async () => {
try {
const config = await api.getAdminConfig();
setGlobalNotification(config.global_notification);
} catch (err) {
console.error("Failed to fetch notifications:", err);
}
};
fetchNotification();
}, [status]);
if (status === "loading" || status === "unauthenticated") {
return (
<div className="min-h-screen bg-[#0f172a] flex items-center justify-center">
<div className="animate-pulse flex flex-col items-center gap-4">
<div className="h-12 w-12 rounded-xl bg-primary flex items-center justify-center shadow-lg shadow-primary/20">
<span className="font-black text-white italic text-xl">{process.env.NEXT_PUBLIC_BRAND_NAME_WORD || "ZX"}</span>
</div>
<p className="text-slate-400 font-bold tracking-widest uppercase text-xs">
Authenticating...
</p>
</div>
</div>
);
}
const match = pathname.match(/\/dashboard\/guild\/([^\/]+)/);
const currentGuildId = match ? match[1] : null;
// Base sidebar items will be filtered if we are inside a guild
const allSidebarItems = currentGuildId
? [
{ name: "Overview", href: `/dashboard/guild/${currentGuildId}`, icon: LayoutDashboard },
{
name: "Security",
items: [
{ name: "Anti-Nuke", href: `/dashboard/guild/${currentGuildId}/antinuke`, icon: ShieldCheck },
{ name: "Automod", href: `/dashboard/guild/${currentGuildId}/automod`, icon: ShieldCheck },
{ name: "Verification", href: `/dashboard/guild/${currentGuildId}/verification`, icon: User },
],
},
{
name: "Engagement",
items: [
{ name: "Welcome", href: `/dashboard/guild/${currentGuildId}/welcome`, icon: Bell },
{ name: "Leveling", href: `/dashboard/guild/${currentGuildId}/leveling`, icon: BarChart4 },
{ name: "Vanity Roles", href: `/dashboard/guild/${currentGuildId}/vanityroles`, icon: Star },
{ name: "Auto Role", href: `/dashboard/guild/${currentGuildId}/autorole`, icon: Search },
{ name: "Auto React", href: `/dashboard/guild/${currentGuildId}/autoreact`, icon: Settings },
{ name: "Reaction Roles", href: `/dashboard/guild/${currentGuildId}/reactionroles`, icon: Search },
{ name: "Join DM", href: `/dashboard/guild/${currentGuildId}/joindm`, icon: User },
{ name: "Invites", href: `/dashboard/guild/${currentGuildId}/invites`, icon: Search },
{ name: "Tracking", href: `/dashboard/guild/${currentGuildId}/tracking`, icon: BarChart4 },
],
},
{
name: "Utility",
items: [
{ name: "Tickets", href: `/dashboard/guild/${currentGuildId}/tickets`, icon: Ticket },
{ name: "Join to Create", href: `/dashboard/guild/${currentGuildId}/j2c`, icon: Menu },
{ name: "Custom Roles", href: `/dashboard/guild/${currentGuildId}/customroles`, icon: ShieldCheck },
{ name: "Voice Role", href: `/dashboard/guild/${currentGuildId}/invcrole`, icon: Settings },
],
},
{ name: "Settings", href: `/dashboard/guild/${currentGuildId}/settings`, icon: Settings },
{ name: "Back to Servers", href: "/dashboard/guilds", icon: Server },
]
: [
{ name: "Dashboard", href: "/dashboard", icon: LayoutDashboard },
{ name: "Servers", href: "/dashboard/guilds", icon: Server },
...(isAdmin(session?.user?.id)
? [{ name: "Admin Panel", href: "/dashboard/admin", icon: Shield }]
: []),
];
// Separate the "Back to Servers" item when inside a guild
let mainSidebarItems = allSidebarItems;
let backLinkItem: any = null;
if (currentGuildId) {
mainSidebarItems = allSidebarItems.filter(
(item) => !(item.name === "Back to Servers")
);
backLinkItem = allSidebarItems.find((item) => item.name === "Back to Servers");
}
const BackLinkIcon = backLinkItem?.icon || Server;
return (
<div className="min-h-screen bg-[#020617] text-slate-200">
{/* Liquid Background Elements */}
<div className="fixed inset-0 overflow-hidden pointer-events-none z-0">
<div className="absolute top-[-10%] right-[-10%] w-[40%] h-[40%] bg-red-500/5 blur-[120px] rounded-full animate-pulse" />
<div className="absolute bottom-[10%] left-[-5%] w-[30%] h-[30%] bg-indigo-500/5 blur-[100px] rounded-full animate-pulse [animation-delay:2s]" />
</div>
{/* Mobile Sidebar Overlay */}
{isSidebarOpen && (
<div
className="fixed inset-0 z-40 bg-black/60 backdrop-blur-sm lg:hidden"
onClick={() => setIsSidebarOpen(false)}
/>
)}
{/* Sidebar - now using flex column */}
<aside
className={cn(
"fixed left-4 top-4 bottom-4 z-50 w-64 transform transition-all duration-500 ease-in-out lg:translate-x-0 glass border border-white/10 rounded-[2.5rem] shadow-2xl shadow-black/40 overflow-hidden flex flex-col",
isSidebarOpen ? "translate-x-0" : "-translate-x-[110%]"
)}
>
{/* Header */}
<div className="flex h-16 items-center px-6 mt-4 flex-shrink-0">
<div className="flex items-center gap-3 group">
<div className="h-9 w-9 rounded-xl bg-gradient-to-br from-red-500 to-red-800 flex items-center justify-center shadow-lg shadow-red-500/20 group-hover:scale-110 transition-transform border border-white/10">
<Bot className="h-5 w-5 text-white" />
</div>
<div className="flex flex-col">
<h1 className="text-lg font-bold tracking-tight text-white font-outfit leading-none">
{process.env.NEXT_PUBLIC_BRAND_NAME || "ZyroX"}
</h1>
<span className="text-[9px] font-black uppercase tracking-[0.2em] text-red-500/80 mt-1">
Dashboard
</span>
</div>
</div>
<button
className="ml-auto p-2 lg:hidden text-slate-400 hover:text-white"
onClick={() => setIsSidebarOpen(false)}
>
<X className="h-5 w-5" />
</button>
</div>
{/* Scrollable Navigation */}
<nav className="mt-8 px-4 space-y-6 overflow-y-auto flex-1 no-scrollbar relative z-10 pb-3">
{mainSidebarItems.map((item: any) => {
if (item.items) {
return (
<div key={item.name} className="space-y-2">
<p className="px-4 text-[10px] font-black uppercase tracking-[0.3em] text-slate-600 mb-3">
{item.name}
</p>
<div className="space-y-1">
{item.items.map((subItem: any) => {
const isActive = pathname === subItem.href;
return (
<Link
key={subItem.name}
href={subItem.href}
className={cn(
"flex items-center gap-3 px-4 py-2.5 rounded-xl transition-all duration-300 group text-[13px] font-bold",
isActive
? "bg-red-500/10 text-red-500 border border-red-500/20 shadow-[0_0_20px_rgba(239,68,68,0.1)]"
: "text-slate-400 hover:bg-white/[0.03] hover:text-slate-200"
)}
>
<subItem.icon
className={cn(
"h-4 w-4 transition-all duration-300",
isActive
? "text-red-500 scale-110"
: "text-slate-600 group-hover:text-slate-400"
)}
/>
{subItem.name}
{isActive && (
<div className="ml-auto w-1.5 h-1.5 rounded-full bg-red-500 animate-pulse shadow-[0_0_10px_rgba(239,68,68,0.5)]" />
)}
</Link>
);
})}
</div>
</div>
);
}
const isActive = pathname === item.href;
return (
<Link
key={item.name}
href={item.href}
className={cn(
"flex items-center gap-4 px-4 py-3 rounded-2xl transition-all duration-300 group text-[14px] font-bold",
isActive
? "bg-red-500/10 text-red-500 border border-red-500/20 shadow-[0_0_20px_rgba(239,68,68,0.1)]"
: "text-slate-400 hover:bg-white/[0.03] hover:text-slate-200"
)}
>
<item.icon
className={cn(
"h-5 w-5 transition-all duration-300",
isActive ? "text-red-500 scale-110" : "text-slate-600 group-hover:text-slate-400"
)}
/>
{item.name}
{isActive ? (
<ChevronRight className="ml-auto h-4 w-4 text-red-500" />
) : (
<ChevronRight className="ml-auto h-4 w-4 opacity-0 group-hover:opacity-30 transition-opacity" />
)}
</Link>
);
})}
</nav>
{/* Fixed "Back to Servers" link (only shown inside a guild) */}
{backLinkItem && (
<div className="px-4 py-2 flex-shrink-0">
<div className="h-px bg-white/5 w-3/4 mx-auto rounded-full mb-2" />
<Link
href={backLinkItem.href || "/dashboard/guilds"}
className={cn(
"flex items-center gap-4 px-4 py-3 rounded-2xl transition-all duration-300 group text-[14px] font-bold",
pathname === backLinkItem.href
? "bg-red-500/10 text-red-500 border border-red-500/20 shadow-[0_0_20px_rgba(239,68,68,0.1)]"
: "text-slate-400 hover:bg-white/[0.03] hover:text-slate-200"
)}
>
<BackLinkIcon
className={cn(
"h-5 w-5 transition-all duration-300",
pathname === backLinkItem.href
? "text-red-500 scale-110"
: "text-slate-600 group-hover:text-slate-400"
)}
/>
{backLinkItem.name}
{pathname === backLinkItem.href ? (
<ChevronRight className="ml-auto h-4 w-4 text-red-500" />
) : (
<ChevronRight className="ml-auto h-4 w-4 opacity-0 group-hover:opacity-30 transition-opacity" />
)}
</Link>
</div>
)}
{/* User Profile - now a normal flex child, no absolute positioning */}
<div className="flex-shrink-0 p-4 border-t border-white/5 glass-red bg-red-500/[0.02]">
<div className="flex items-center gap-3 p-2 bg-white/[0.02] rounded-2xl border border-white/[0.05]">
<div className="h-10 w-10 rounded-full bg-red-500/10 flex items-center justify-center ring-1 ring-white/10 overflow-hidden border border-red-500/20">
{session?.user?.image ? (
<img
src={session.user.image}
alt="User Avatar"
className="h-full w-full object-cover opacity-80"
/>
) : (
<User className="h-6 w-6 text-red-500/50" />
)}
</div>
<div className="overflow-hidden">
<p className="text-sm font-bold text-white truncate font-outfit">
{session?.user?.name || "Administrator"}
</p>
<p className="text-[10px] font-black uppercase text-red-500/60 truncate tracking-widest">
User
</p>
</div>
</div>
</div>
</aside>
{/* Main Content Area (unchanged) */}
<div className="lg:pl-72 flex flex-col min-h-screen relative z-10">
{/* Top Navbar (unchanged) */}
<header className="h-20 sticky top-4 z-30 mx-4 lg:mx-10 flex items-center justify-between border border-white/10 glass bg-white/[0.01] backdrop-blur-3xl px-8 rounded-[2rem] shadow-xl shadow-black/20 mb-6 mt-4">
<button
className="p-2 lg:hidden text-slate-400 hover:bg-white/5 rounded-xl transition-colors"
onClick={() => setIsSidebarOpen(true)}
>
<Menu className="h-6 w-6" />
</button>
<div className="hidden md:flex items-center w-96 max-w-full relative group">
<Search className="absolute left-4 h-4 w-4 text-slate-500 group-focus-within:text-red-500 transition-colors" />
<input
type="text"
placeholder="Query neural network..."
className="w-full bg-white/[0.03] border border-white/5 rounded-2xl py-2.5 pl-12 pr-4 text-xs font-bold text-slate-300 focus:outline-none focus:ring-1 focus:ring-red-500/30 focus:bg-white/[0.05] transition-all placeholder:text-slate-600"
/>
</div>
<div className="flex items-center gap-6">
<div className="relative" ref={bellRef}>
<button
onClick={() => setIsNotificationsOpen(!isNotificationsOpen)}
className="relative p-2.5 text-slate-400 hover:bg-white/5 hover:text-white rounded-xl transition-all group"
>
<Bell className="h-5 w-5" />
{globalNotification && (
<span className="absolute top-2 right-2 h-2 w-2 rounded-full bg-red-500 border-2 border-[#020617] shadow-[0_0_10px_rgba(239,68,68,0.5)]"></span>
)}
</button>
{isNotificationsOpen && (
<div className="absolute right-0 mt-3 w-80 bg-[#0a0f1e]/90 backdrop-blur-3xl border border-white/5 rounded-[24px] shadow-[0_20px_50px_rgba(0,0,0,0.5)] p-4 z-20 animate-in fade-in zoom-in-95 duration-300 origin-top-right">
<div className="flex items-center justify-between mb-4 border-b border-white/5 pb-2">
<p className="text-[10px] font-black text-slate-500 uppercase tracking-[0.2em]">Broadcast Metrics</p>
<button
onClick={() => setGlobalNotification(null)}
className="text-[10px] font-bold text-red-500/60 hover:text-red-500 transition-colors uppercase"
>
Clear
</button>
</div>
{globalNotification ? (
<div className="bg-red-500/5 border border-red-500/10 rounded-2xl p-4">
<div className="flex items-center gap-2 mb-2">
<Sparkles className="h-3 w-3 text-red-500" />
<span className="text-[10px] font-black uppercase text-red-500 tracking-widest">System Broadcast</span>
</div>
<p className="text-xs font-medium text-slate-300 leading-relaxed">
{globalNotification}
</p>
</div>
) : (
<div className="py-8 flex flex-col items-center justify-center text-center">
<div className="h-10 w-10 rounded-full bg-slate-800 flex items-center justify-center mb-3">
<Bell className="h-5 w-5 text-slate-600" />
</div>
<p className="text-xs font-bold text-slate-500">No active broadcasts</p>
<p className="text-[10px] font-medium text-slate-600 mt-1 uppercase tracking-widest">Everything is operating normally</p>
</div>
)}
</div>
)}
</div>
<div className="h-8 w-[1px] bg-white/5 hidden sm:block"></div>
{/* Profile Dropdown (unchanged) */}
<div className="relative" ref={profileRef}>
<button
onClick={() => setIsProfileOpen(!isProfileOpen)}
className="flex items-center gap-3.5 p-1.5 rounded-2xl hover:bg-white/5 transition-all group border border-transparent hover:border-white/10"
>
<div className="h-9 w-9 rounded-full bg-red-500/10 flex items-center justify-center overflow-hidden border border-red-500/20 ring-2 ring-transparent group-hover:ring-red-500/30 transition-all">
{session?.user?.image ? (
<img src={session.user.image} alt="User Avatar" className="h-full w-full object-cover opacity-80" />
) : (
<User className="h-5 w-5 text-red-500/50" />
)}
</div>
<div className="hidden sm:flex flex-col items-start leading-none gap-1">
<span className="text-xs font-bold text-slate-200 group-hover:text-white transition-colors">
{session?.user?.name?.split(' ')[0] || "Admin"}
</span>
<span className="text-[9px] font-black uppercase text-red-500/60 tracking-widest">Active</span>
</div>
<ChevronDown
className={cn("h-4 w-4 text-slate-600 transition-transform hidden sm:block", isProfileOpen && "rotate-180")}
/>
</button>
{isProfileOpen && (
<div className="absolute right-0 mt-3 w-56 bg-[#0a0f1e]/90 backdrop-blur-3xl border border-white/5 rounded-[24px] shadow-[0_20px_50px_rgba(0,0,0,0.5)] p-2 z-20 animate-in fade-in zoom-in-95 duration-300 origin-top-right">
<div className="px-4 py-3 border-b border-white/5 mb-2">
<p className="text-[9px] font-black text-slate-500 uppercase tracking-[0.2em] mb-1">Authenticated As</p>
<p className="text-sm font-bold text-white truncate">{session?.user?.name || "Administrator"}</p>
</div>
<button className="w-full flex items-center gap-3 px-4 py-3 rounded-xl text-xs font-bold text-slate-400 hover:bg-white/5 hover:text-white transition-all group/item">
<LifeBuoy className="h-4 w-4 text-slate-600 group-hover/item:text-red-500 transition-colors" />
Support Matrix
</button>
<button
onClick={() => signOut({ callbackUrl: '/' })}
className="w-full flex items-center gap-3 px-4 py-3 rounded-xl text-xs font-black uppercase tracking-widest text-red-500/80 hover:bg-red-500/10 hover:text-red-500 transition-all group/item"
>
<LogOut className="h-4 w-4" />
Deauthorize
</button>
</div>
)}
</div>
</div>
</header>
{/* Content Area */}
<main className="flex-1 p-6 lg:p-10 animate-in fade-in duration-700 relative z-10">
<div className="max-w-[1600px] mx-auto">{children}</div>
</main>
</div>
</div>
);
}

View File

@@ -0,0 +1,33 @@
/**
* ╔══════════════════════════════════════════════════════════════════╗
* ║ ║
* ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
* ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
* ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
* ║ ║
* ║ © 2026 CodeX Devs — All Rights Reserved ║
* ║ ║
* ║ discord ── https://discord.gg/codexdev ║
* ║ youtube ── https://youtube.com/@CodeXDevs ║
* ║ github ── https://github.com/RayExo ║
* ║ ║
* ╚══════════════════════════════════════════════════════════════════╝
*/
import React from "react";
import { RefreshCcw } from "lucide-react";
export default function DashboardLoading() {
return (
<div className="flex flex-col items-center justify-center min-h-[60vh] space-y-4 animate-in fade-in duration-500">
<div className="relative">
<div className="h-16 w-16 border-4 border-slate-800 rounded-full" />
<RefreshCcw className="h-16 w-16 text-primary animate-spin absolute top-0 left-0" />
</div>
<div className="space-y-2 text-center">
<h3 className="text-white font-bold text-lg">Initializing System</h3>
<p className="text-slate-500 text-sm animate-pulse">Fetching parameters from edge cortex...</p>
</div>
</div>
);
}

View File

@@ -0,0 +1,163 @@
/**
* ╔══════════════════════════════════════════════════════════════════╗
* ║ ║
* ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
* ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
* ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
* ║ ║
* ║ © 2026 CodeX Devs — All Rights Reserved ║
* ║ ║
* ║ discord ── https://discord.gg/codexdev ║
* ║ youtube ── https://youtube.com/@CodeXDevs ║
* ║ github ── https://github.com/RayExo ║
* ║ ║
* ╚══════════════════════════════════════════════════════════════════╝
*/
import React from "react";
import {
Users,
MessageSquare,
Zap,
Activity,
Server as ServerIcon,
ShieldAlert,
Settings,
LifeBuoy,
FileText
} from "lucide-react";
import { api } from "@/lib/api";
export default async function DashboardPage() {
let botInfo;
let error = null;
try {
botInfo = await api.getBotInfo();
} catch (err: any) {
console.error("Failed to fetch bot info:", err);
error = err.message || "Failed to connect to the bot API.";
// Fallback data for UI structure if API fails
botInfo = {
name: "ZyroX Bot",
guilds: 0,
users: 0,
commands: 0,
latency: "0ms"
};
}
const stats = [
{ name: "Total Guilds", value: botInfo.guilds.toLocaleString(), icon: ServerIcon },
{ name: "Total Users", value: botInfo.users.toLocaleString(), icon: Users },
{ name: "System Uptime", value: "99.9%", icon: Activity },
{ name: "API Latency", value: botInfo.latency, icon: Activity },
];
return (
<div className="space-y-10 animate-in fade-in slide-in-from-bottom-4 duration-700">
<div className="flex flex-col md:flex-row md:items-end justify-between gap-6 px-2">
<div>
<h1 className="text-4xl md:text-5xl font-bold text-white font-outfit tracking-tight">System <span className="text-red-500 italic">Core.</span></h1>
<p className="text-slate-400 mt-3 font-medium flex items-center gap-2">
Status and live metrics for <span className="text-red-500 font-bold px-2 py-0.5 rounded-lg bg-red-500/10 border border-red-500/20">{botInfo.name}</span>
</p>
</div>
{error && (
<div className="flex items-center gap-2 px-5 py-2.5 glass-red rounded-2xl text-red-500 text-xs font-bold uppercase tracking-widest animate-pulse">
<ShieldAlert className="h-4 w-4" />
<span>{error}</span>
</div>
)}
</div>
{/* Stats Grid */}
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4">
{stats.map((stat) => (
<div key={stat.name} className="group glass border-white/5 p-7 rounded-[32px] relative overflow-hidden hover:border-red-500/30 transition-all duration-500 shadow-2xl">
{/* Animated Glow Overlay */}
<div className="absolute inset-0 bg-gradient-to-br from-red-500/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500" />
<div className="flex items-center justify-between relative z-10">
<div>
<p className="text-[10px] font-black text-slate-500 uppercase tracking-[0.2em] mb-2">{stat.name}</p>
<p className="text-3xl font-bold text-white font-outfit tracking-tight">{stat.value}</p>
</div>
<div className="p-4 bg-red-500/10 rounded-2xl border border-red-500/20 group-hover:scale-110 group-hover:bg-red-500/20 transition-all duration-500">
<stat.icon className="h-6 w-6 text-red-500" />
</div>
</div>
<div className="absolute bottom-0 left-12 right-12 h-[1px] bg-red-500/50 blur-[1px] opacity-0 group-hover:opacity-100 transition-opacity duration-500" />
</div>
))}
</div>
{/* Featured Modules & Support */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
<div className="lg:col-span-2 glass border-white/5 rounded-[40px] p-10 relative group overflow-hidden shadow-2xl">
<div className="absolute inset-0 bg-gradient-to-br from-red-500/[0.03] to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-700" />
<div className="flex items-center justify-between mb-8 relative z-10">
<h2 className="text-2xl font-bold text-white font-outfit tracking-tight">Quick Actions</h2>
<div className="flex items-center gap-2 px-3 py-1 rounded-full bg-red-500/10 border border-red-500/20">
<Zap className="h-3 w-3 text-red-500" />
<span className="text-[10px] font-black text-red-500 uppercase tracking-widest">Efficiency</span>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 relative z-10">
{[
{ title: "Manage Servers", desc: "View and configure your Discord guilds.", icon: ServerIcon, href: "/dashboard/guilds" },
{ title: "Global Settings", desc: "Adjust your personal dashboard preferences.", icon: Settings, href: "/dashboard" },
{ title: "Support Matrix", desc: "Get help from our neural support team.", icon: LifeBuoy, href: "#" },
{ title: "Documentation", desc: "Learn how to master the ZyroX engine.", icon: FileText, href: "#" },
].map((item) => (
<a key={item.title} href={item.href} className="flex items-center gap-5 p-4 rounded-2xl bg-white/[0.02] border border-white/[0.03] group/item hover:bg-white/[0.05] hover:border-red-500/20 transition-all">
<div className="h-12 w-12 rounded-2xl bg-red-500/5 border border-red-500/10 flex items-center justify-center group-hover/item:bg-red-500/10 transition-colors">
<item.icon className="h-5 w-5 text-red-500/60 group-hover/item:text-red-500 transition-colors" />
</div>
<div>
<h4 className="text-sm font-bold text-white group-hover/item:text-red-500 transition-colors">{item.title}</h4>
<p className="text-[10px] text-slate-500 font-medium uppercase tracking-wider">{item.desc}</p>
</div>
</a>
))}
</div>
</div>
<div className="glass border-white/5 rounded-[40px] p-10 flex flex-col justify-between relative group shadow-2xl overflow-hidden">
<div className="absolute inset-0 bg-gradient-to-bl from-red-500/[0.05] to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-700" />
<div className="relative z-10">
<h2 className="text-2xl font-bold text-white mb-3 font-outfit">Module Status</h2>
<p className="text-slate-500 text-sm mb-10 font-medium">Global operational health of ZyroX core.</p>
<div className="space-y-4">
{[
{ name: 'Neural Gateway', status: 'Optimal' },
{ name: 'Database Cluster', status: 'Synchronized' },
{ name: 'Edge Shards', status: 'Operational' }
].map((service) => (
<div key={service.name} className="flex items-center justify-between p-4 bg-white/[0.02] rounded-2xl border border-white/[0.05] hover:border-red-500/20 transition-colors">
<span className="text-xs font-bold text-slate-300">{service.name}</span>
<div className="flex items-center gap-3">
<div className="h-1.5 w-1.5 rounded-full bg-emerald-500 shadow-[0_0_10px_rgba(16,185,129,0.5)]" />
<span className="text-[9px] uppercase font-black text-emerald-500 tracking-widest">{service.status}</span>
</div>
</div>
))}
</div>
</div>
<button className="mt-12 w-full py-4 glass-red hover:bg-red-500/10 text-red-500 rounded-[20px] text-[11px] font-black uppercase tracking-[0.2em] transition-all border border-red-500/20 relative z-10">
System Diagnostics
</button>
{/* Abstract Design Element */}
<div className="absolute -bottom-10 -right-10 h-32 w-32 bg-red-500/10 blur-3xl rounded-full" />
</div>
</div>
</div>
);
}

204
dashboard/app/docs/page.tsx Normal file
View File

@@ -0,0 +1,204 @@
/**
* ╔══════════════════════════════════════════════════════════════════╗
* ║ ║
* ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
* ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
* ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
* ║ ║
* ║ © 2026 CodeX Devs — All Rights Reserved ║
* ║ ║
* ║ discord ── https://discord.gg/codexdev ║
* ║ youtube ── https://youtube.com/@CodeXDevs ║
* ║ github ── https://github.com/RayExo ║
* ║ ║
* ╚══════════════════════════════════════════════════════════════════╝
*/
"use client";
import React, { useState } from "react";
import Link from "next/link";
import {
Bot,
ChevronLeft,
Search,
ShieldCheck,
Zap,
Activity,
Layers,
Sparkles,
Search as SearchIcon,
BookOpen,
Menu,
X
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
const DOCS_NAV = [
{
title: "Getting Started",
items: [
{ name: "Introduction", description: "Learn about the Neural Core." },
{ name: "Quick Start", description: "Deploy in 30 seconds." },
{ name: "Architecture", description: "Deep dive into our engine." }
]
},
{
title: "Security Modules",
items: [
{ name: "Anti-Nuke", description: "Absolute lockdown protocols." },
{ name: "Verification", description: "Captcha & Neural checks." },
{ name: "Automod", description: "Context-aware AI filtering." }
]
},
{
title: "Management",
items: [
{ name: "Join to Create", description: "Dynamic voice channels." },
{ name: "Leveling", description: "Cinematic rank generation." },
{ name: "Tickets", description: "Enterprise helpdesk." }
]
}
];
export default function DocsPage() {
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const [activeTab, setActiveTab] = useState("Introduction");
return (
<div className="min-h-screen bg-[#020617] text-slate-200 font-sans">
{/* Background Decor */}
<div className="fixed inset-0 overflow-hidden pointer-events-none z-0">
<div className="absolute top-[-10%] left-[-10%] w-[50%] h-[50%] bg-red-500/[0.02] blur-[150px] rounded-full" />
</div>
{/* Nav */}
<nav className="fixed top-0 w-full z-50 border-b border-white/[0.03] bg-[#020617]/80 backdrop-blur-3xl px-6 h-20 flex items-center justify-between">
<div className="flex items-center gap-8">
<Link href="/" className="flex items-center gap-4 group">
<div className="h-8 w-8 rounded-lg bg-red-600 flex items-center justify-center mr-3">
<Bot className="h-5 w-5 text-white" />
</div>
<span className="text-xl font-bold text-white font-outfit uppercase tracking-tighter hidden md:block">ZyroX Docs</span>
</Link>
<div className="hidden lg:flex items-center w-80 relative group">
<SearchIcon className="absolute left-4 h-4 w-4 text-slate-500 group-focus-within:text-red-500 transition-colors" />
<input
type="text"
placeholder="Search documentation..."
className="w-full bg-white/[0.03] border border-white/5 rounded-2xl py-2.5 pl-12 pr-4 text-xs font-bold text-slate-300 focus:outline-none focus:ring-1 focus:ring-red-500/30 focus:bg-white/[0.05] transition-all"
/>
</div>
</div>
<div className="flex items-center gap-4">
<button
className="lg:hidden p-2 text-slate-400"
onClick={() => setIsSidebarOpen(!isSidebarOpen)}
>
{isSidebarOpen ? <X /> : <Menu />}
</button>
<Link href="/">
<Button variant="ghost" className="text-slate-400 hover:text-white gap-2 text-xs font-black uppercase tracking-widest">
Exit Docs
</Button>
</Link>
</div>
</nav>
<div className="max-w-7xl mx-auto flex pt-20">
{/* Sidebar */}
<aside className={cn(
"fixed inset-y-0 left-0 z-40 w-80 bg-[#020617] border-r border-white/5 pt-20 transition-transform lg:translate-x-0 lg:static lg:bg-transparent",
isSidebarOpen ? "translate-x-0" : "-translate-x-full"
)}>
<div className="h-full p-8 overflow-y-auto no-scrollbar">
{DOCS_NAV.map((section) => (
<div key={section.title} className="mb-10">
<h4 className="text-[10px] font-black uppercase tracking-[0.4em] text-slate-600 mb-6">{section.title}</h4>
<div className="space-y-1">
{section.items.map((item) => (
<button
key={item.name}
onClick={() => {
setActiveTab(item.name);
setIsSidebarOpen(false);
}}
className={cn(
"w-full flex flex-col items-start gap-1 p-4 rounded-2xl transition-all text-left",
activeTab === item.name
? "bg-red-500/10 border border-red-500/20 shadow-[0_0_20px_rgba(239,68,68,0.05)]"
: "hover:bg-white/[0.02] border border-transparent"
)}
>
<span className={cn("text-sm font-bold", activeTab === item.name ? "text-red-500" : "text-slate-300")}>{item.name}</span>
<span className="text-[10px] text-slate-600 font-bold uppercase tracking-tight">{item.description}</span>
</button>
))}
</div>
</div>
))}
</div>
</aside>
{/* Content */}
<main className="flex-1 p-8 lg:p-16 relative z-10 max-w-4xl">
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-red-500/10 border border-red-500/20 text-red-500 text-[10px] font-black uppercase tracking-widest mb-8">
<BookOpen className="h-3 w-3" />
V2.4 Runtime Environment
</div>
<h1 className="text-6xl font-bold text-white font-outfit tracking-tighter uppercase mb-8 italic">
{activeTab}<span className="text-red-500 not-italic">.</span>
</h1>
<div className="prose prose-invert max-w-none">
<p className="text-lg text-slate-400 mb-12 leading-relaxed">
Welcome to the {activeTab} section of the ZyroX Engine documentation. Our engine is designed for communities that demand absolute performance and cinematic management tools.
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="p-8 rounded-[32px] glass border-white/5 space-y-4">
<Zap className="h-6 w-6 text-red-500" />
<h3 className="text-xl font-bold text-white font-outfit uppercase">Fast Dispatch</h3>
<p className="text-sm text-slate-500 font-bold uppercase tracking-tight">Commands are dispatched via our global edge network in under 12ms.</p>
</div>
<div className="p-8 rounded-[32px] glass border-white/5 space-y-4">
<ShieldCheck className="h-6 w-6 text-emerald-500" />
<h3 className="text-xl font-bold text-white font-outfit uppercase">Secure Node</h3>
<p className="text-sm text-slate-500 font-bold uppercase tracking-tight">Every module runs in a dedicated neural sandbox with AES-256 encryption.</p>
</div>
</div>
<div className="p-8 rounded-[40px] bg-red-500/[0.02] border border-red-500/10 relative overflow-hidden">
<div className="absolute top-0 right-0 p-8 opacity-10">
<Layers className="h-32 w-32 text-red-500" />
</div>
<h2 className="text-2xl font-bold text-white font-outfit uppercase tracking-tight mb-4">Neural Architecture</h2>
<h3 className="text-white font-bold">Protocol Overview</h3>
<p className="text-slate-500 font-bold leading-relaxed mb-8">
The ZyroX Engine utilizes a decentralized event stream processing model. When a Discord event is received, it is instantly routed to the nearest edge cluster.
</p>
<div className="bg-black/40 p-6 rounded-2xl border border-white/5 font-mono text-sm text-red-500 mb-8">
$ zyrox initialize --cluster-shard [neural_07] --mode enterprise
</div>
</div>
</div>
<div className="mt-20 pt-12 border-t border-white/5 flex items-center justify-between">
<div>
<p className="text-[10px] font-black uppercase text-slate-600 tracking-[0.4em] mb-2">Internal Ref</p>
<p className="text-sm font-bold text-slate-400">DOC-ID: CX_7749_B</p>
</div>
<div className="flex items-center gap-2">
<div className="h-2 w-2 rounded-full bg-red-500 animate-pulse" />
<span className="text-[10px] font-black uppercase text-red-500 tracking-[0.2em]">Live Stream Active</span>
</div>
</div>
</main>
</div>
</div>
);
}

77
dashboard/app/globals.css Normal file
View File

@@ -0,0 +1,77 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--background: #020617;
--foreground: #f8fafc;
--primary: #ef4444;
--primary-glow: rgba(239, 68, 68, 0.15);
--glass-bg: rgba(15, 23, 42, 0.6);
--glass-border: rgba(255, 255, 255, 0.05);
--glass-red-border: rgba(239, 68, 68, 0.2);
}
@media (prefers-color-scheme: dark) {
:root {
--background: #020617;
--foreground: #f8fafc;
}
}
body {
color: var(--foreground);
background: var(--background);
font-family: var(--font-inter), Arial, Helvetica, sans-serif;
overflow-x: hidden;
}
/* Custom Scrollbar Styles */
::-webkit-scrollbar {
width: 10px;
}
::-webkit-scrollbar-track {
background: #020617;
}
::-webkit-scrollbar-thumb {
background: #1e293b;
border-radius: 10px;
border: 2px solid #020617;
}
::-webkit-scrollbar-thumb:hover {
background: #ef4444; /* Primary Red */
}
/* Firefox */
* {
scrollbar-width: thin;
scrollbar-color: #1e293b #020617;
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
.glass {
@apply backdrop-blur-xl bg-white/[0.02] border border-white/[0.05];
}
.glass-red {
@apply backdrop-blur-xl bg-red-500/[0.02] border border-red-500/[0.1];
}
.liquid-glass {
background: linear-gradient(135deg, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0) 100%);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
}
}

49
dashboard/app/layout.tsx Normal file
View File

@@ -0,0 +1,49 @@
/**
* ╔══════════════════════════════════════════════════════════════════╗
* ║ ║
* ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
* ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
* ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
* ║ ║
* ║ © 2026 CodeX Devs — All Rights Reserved ║
* ║ ║
* ║ discord ── https://discord.gg/codexdev ║
* ║ youtube ── https://youtube.com/@CodeXDevs ║
* ║ github ── https://github.com/RayExo ║
* ║ ║
* ╚══════════════════════════════════════════════════════════════════╝
*/
import type { Metadata } from "next";
import { Inter, Outfit } from "next/font/google";
import "./globals.css";
import { Toaster } from "@/components/ui/sonner";
import { AuthProvider } from "@/components/auth-provider";
const inter = Inter({ subsets: ["latin"], variable: "--font-inter" });
const outfit = Outfit({ subsets: ["latin"], variable: "--font-outfit" });
const brandName = process.env.NEXT_PUBLIC_BRAND_NAME || "Zyrox";
export const metadata: Metadata = {
title: `${brandName} - Ultimate Discord Bot`,
description: "Advanced Discord community management and security.",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" className={`${inter.variable} ${outfit.variable}`}>
<body className="font-sans antialiased text-slate-200">
<AuthProvider>
{children}
<Toaster />
</AuthProvider>
</body>
</html>
);
}

454
dashboard/app/page.tsx Normal file
View File

@@ -0,0 +1,454 @@
/**
* ╔══════════════════════════════════════════════════════════════════╗
* ║ ║
* ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
* ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
* ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
* ║ ║
* ║ © 2026 CodeX Devs — All Rights Reserved ║
* ║ ║
* ║ discord ── https://discord.gg/codexdev ║
* ║ youtube ── https://youtube.com/@CodeXDevs ║
* ║ github ── https://github.com/RayExo ║
* ║ ║
* ╚══════════════════════════════════════════════════════════════════╝
*/
"use client";
import React from "react";
import Link from "next/link";
import { signIn } from "next-auth/react";
import {
ShieldCheck,
Zap,
BarChart4,
MessageSquare,
ChevronRight,
LayoutDashboard,
LogIn,
Layers,
Sparkles,
Bot,
Activity,
History,
CheckCircle2,
ShieldAlert,
Globe,
Terminal,
Cpu,
Users2,
Lock,
Radio,
Gamepad2,
Music4,
User
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
export default function LandingPage() {
return (
<div className="min-h-screen bg-[#020617] text-slate-200 selection:bg-red-500/30 font-sans overflow-x-hidden">
{/* Dynamic Background */}
<div className="fixed inset-0 overflow-hidden pointer-events-none z-0">
<div className="absolute top-[-20%] left-[-10%] w-[70%] h-[70%] bg-red-500/[0.03] blur-[150px] rounded-full animate-pulse" />
<div className="absolute bottom-[-10%] right-[-10%] w-[60%] h-[60%] bg-red-600/[0.03] blur-[150px] rounded-full animate-pulse [animation-delay:2s]" />
</div>
{/* Navigation */}
<nav className="fixed top-0 w-full z-50 border-b border-white/[0.03] bg-[#020617]/80 backdrop-blur-3xl transition-all duration-500">
<div className="max-w-7xl mx-auto px-6 h-20 flex items-center justify-between">
<div className="flex items-center gap-4 group cursor-pointer">
<div className="h-11 w-11 rounded-2xl bg-gradient-to-br from-red-500 to-red-800 flex items-center justify-center shadow-lg shadow-red-500/25 border border-white/20 group-hover:scale-110 group-hover:rotate-6 transition-all duration-500">
<Bot className="h-6 w-6 text-white" />
</div>
<div className="flex flex-col">
<h1 className="text-lg font-bold tracking-tight text-white font-outfit leading-none">{process.env.NEXT_PUBLIC_BRAND_NAME || "ZyroX"}</h1>
<span className="text-[9px] font-black uppercase tracking-[0.2em] text-red-500/80 mt-1">Dashboard</span>
</div>
</div>
<div className="hidden lg:flex items-center gap-10 text-[11px] font-black uppercase tracking-widest text-slate-500">
<Link href="#features" className="hover:text-red-500 transition-colors">Features</Link>
<Link href="#architecture" className="hover:text-red-500 transition-colors">Architecture</Link>
<Link href="#modules" className="hover:text-red-500 transition-colors">Modules</Link>
<Link href="#network" className="hover:text-red-500 transition-colors">Network</Link>
</div>
<div className="flex items-center gap-4">
<Button
onClick={() => signIn('discord', { callbackUrl: '/dashboard' })}
className="rounded-xl px-7 h-11 font-black uppercase tracking-widest text-[10px] gap-2.5 shadow-2xl shadow-red-500/20 hover:scale-[1.05] active:scale-95 transition-all bg-gradient-to-r from-red-500 to-red-700 border-none"
>
<LogIn className="h-3.5 w-3.5" />
Initialize Console
</Button>
</div>
</div>
</nav>
{/* Hero Section */}
<header className="relative z-10 pt-56 pb-32 px-6">
<div className="max-w-7xl mx-auto text-center">
<div className="inline-flex items-center gap-3 px-6 py-2.5 rounded-2xl bg-red-500/[0.03] border border-red-500/10 text-red-500 text-[10px] font-black uppercase tracking-[0.3em] mb-16 animate-in fade-in slide-in-from-bottom-8 duration-1000 backdrop-blur-md">
<span className="relative flex h-2 w-2">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-500 opacity-75"></span>
<span className="relative inline-flex rounded-full h-2 w-2 bg-red-600"></span>
</span>
Neural Core v2 Active Global Shard 07
</div>
<h1 className="text-6xl sm:text-8xl md:text-[10rem] font-bold text-white tracking-tighter leading-[0.8] mb-12 font-outfit animate-in fade-in slide-in-from-bottom-12 duration-1000 delay-100 uppercase">
Evolution <br />
<span className="bg-gradient-to-r from-red-500 via-red-400 to-orange-500 bg-clip-text text-transparent italic font-black">Moderated.</span>
</h1>
<p className="text-lg md:text-2xl text-slate-500 max-w-3xl mx-auto leading-relaxed mb-20 animate-in fade-in slide-in-from-bottom-12 duration-1000 delay-200 font-medium">
The hyper-performance Discord engine.
Automated security, cinematic leveling, and precision tools for the world&apos;s most elite communities.
</p>
<div className="flex flex-col sm:flex-row items-center justify-center gap-6 animate-in fade-in slide-in-from-bottom-12 duration-1000 delay-300">
<Button
onClick={() => signIn('discord', { callbackUrl: '/dashboard' })}
className="w-full sm:w-auto rounded-2xl px-14 py-9 text-lg font-black uppercase gap-4 group shadow-[0_0_50px_rgba(239,68,68,0.2)] bg-red-600 text-white hover:bg-red-500 border-none transition-all hover:scale-105"
>
<LayoutDashboard className="h-6 w-6 group-hover:rotate-12 transition-transform" />
Open Dashboard
</Button>
<Button variant="outline" className="w-full sm:w-auto rounded-2xl px-14 py-9 text-lg font-bold border-white/5 bg-white/[0.02] backdrop-blur-3xl hover:bg-white/[0.05] gap-3 text-white transition-all">
Add to Server
<ChevronRight className="h-5 w-5 opacity-40 group-hover:translate-x-1 transition-transform" />
</Button>
</div>
</div>
{/* Cinematic Dashboard Mockup */}
<div className="max-w-6xl mx-auto mt-40 relative group animate-in fade-in zoom-in-95 duration-1000 delay-500">
<div className="absolute -inset-4 bg-gradient-to-r from-red-500/20 to-transparent rounded-[60px] blur-3xl opacity-0 group-hover:opacity-100 transition-opacity duration-1000" />
<div className="relative bg-[#020617] border border-white/[0.05] rounded-[50px] overflow-hidden shadow-[0_30px_100px_rgba(0,0,0,0.8)]">
{/* Fake Window Controls */}
<div className="h-16 border-b border-white/[0.03] flex items-center justify-between px-10 bg-white/[0.01]">
<div className="flex items-center gap-3">
<div className="h-3 w-3 rounded-full bg-red-500/40" />
<div className="h-3 w-3 rounded-full bg-white/10" />
<div className="h-3 w-3 rounded-full bg-white/5" />
</div>
<div className="px-6 py-2 rounded-2xl bg-white/[0.03] border border-white/[0.05] text-[10px] font-black text-slate-500 tracking-[0.2em] uppercase">
sec.neural.core // active_node_01
</div>
<div className="w-12" />
</div>
{/* Content Placeholder with High Contrast */}
<div className="aspect-[16/10] p-16 flex flex-col gap-16 relative overflow-hidden bg-gradient-to-br from-[#020617] to-[#0a0f1e]">
<div className="flex items-center justify-between z-10 relative">
<div className="space-y-6">
<div className="h-12 w-64 bg-red-500/10 rounded-[20px] border border-red-500/20" />
<div className="h-6 w-[500px] bg-white/[0.02] rounded-xl" />
</div>
<div className="h-20 w-20 rounded-[30px] bg-red-500/10 border border-red-500/20 flex items-center justify-center shadow-[0_0_30px_rgba(239,68,68,0.2)]">
<Activity className="h-8 w-8 text-red-500 animate-pulse" />
</div>
</div>
<div className="grid grid-cols-3 gap-10 z-10">
{[1, 2, 3].map(i => (
<div key={i} className="h-40 bg-white/[0.02] border border-white/[0.04] rounded-[40px] p-8 space-y-6 hover:border-red-500/20 transition-colors">
<div className="h-10 w-10 rounded-2xl bg-red-500/10 border border-red-500/20" />
<div className="h-4 w-2/3 bg-white/5 rounded-lg" />
<div className="h-3 w-1/2 bg-white/[0.02] rounded-lg" />
</div>
))}
</div>
{/* Visual Glow Layer */}
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[90%] h-[90%] bg-red-500/[0.02] blur-[150px] pointer-events-none" />
</div>
</div>
</div>
</header>
{/* Grid Section */}
<section id="features" className="py-48 px-6 relative">
<div className="max-w-7xl mx-auto">
<div className="flex flex-col md:flex-row md:items-end justify-between mb-32 gap-12 px-4">
<div className="max-w-3xl">
<h2 className="text-6xl md:text-8xl font-bold text-white tracking-tighter font-outfit mb-8 uppercase italic leading-none">High-Scale <br /><span className="text-red-500 not-italic">Infrastructure.</span></h2>
<p className="text-2xl text-slate-500 font-medium leading-relaxed">Global redundancy delivers sub-millisecond dispatch times across 20+ edge regions. Zero lag, zero downtime.</p>
</div>
<div className="flex items-center gap-10 pb-4">
<div className="text-right">
<p className="text-[10px] font-black uppercase text-slate-600 tracking-[0.3em] mb-3">Ping Latency</p>
<p className="text-5xl font-black text-red-500 font-outfit">12ms</p>
</div>
<div className="h-16 w-[1px] bg-white/5" />
<div className="text-right">
<p className="text-[10px] font-black uppercase text-slate-600 tracking-[0.3em] mb-3">Global Uptime</p>
<p className="text-5xl font-black text-white font-outfit">99.9<span className="text-slate-700">9</span>%</p>
</div>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{[
{
title: "Neuro-Security",
desc: "Contextual AI analysis detects raids and token-logging attempts in real-time.",
icon: ShieldCheck,
color: "bg-red-500/10 border-red-500/20 text-red-500"
},
{
title: "Edge Dispatch",
desc: "Distributed command execution ensuring your commands work everywhere, instantly.",
icon: Zap,
color: "bg-orange-500/10 border-orange-500/20 text-orange-500"
},
{
title: "Leveling Engine",
desc: "Premium rendered rewards with 4K rank card generation and multi-role hierarchies.",
icon: BarChart4,
color: "bg-red-600/10 border-red-600/20 text-red-600"
},
{
title: "Threaded Support",
desc: "High-volume ticket systems with enterprise encryption and lifetime transcripts.",
icon: MessageSquare,
color: "bg-slate-500/10 border-slate-500/20 text-slate-400"
},
{
title: "Real-time Flux",
desc: "Watch server events live with zero-latency WebSocket data streaming.",
icon: History,
color: "bg-red-800/10 border-red-800/20 text-red-700"
},
{
title: "Cloud Integrity",
desc: "Encrypted backups of all server configurations stored in off-site neural vaults.",
icon: Layers,
color: "bg-white/10 border-white/20 text-white"
}
].map((feature, i) => (
<div key={i} className="group glass border-white/5 p-12 rounded-[50px] hover:border-red-500/30 transition-all duration-700 relative overflow-hidden">
<div className="absolute top-0 right-0 p-12 opacity-0 group-hover:opacity-5 scale-50 group-hover:scale-110 transition-all duration-1000">
<feature.icon className="h-64 w-64 text-white" />
</div>
<div className={cn("h-20 w-20 rounded-3xl flex items-center justify-center mb-10 border transition-all duration-700 group-hover:scale-110 group-hover:rotate-6 shadow-2xl shadow-black/40", feature.color)}>
<feature.icon className="h-10 w-10 shadow-lg" />
</div>
<h3 className="text-3xl font-bold text-white mb-6 tracking-tight font-outfit relative z-10">{feature.title}</h3>
<p className="text-slate-500 leading-relaxed font-bold relative z-10 group-hover:text-slate-400 transition-colors uppercase text-[10px] tracking-[0.2em]">{feature.desc}</p>
<div className="mt-8 h-[2px] w-0 bg-red-500 group-hover:w-full transition-all duration-700" />
</div>
))}
</div>
</div>
</section>
{/* Architecture Section */}
<section id="architecture" className="py-48 px-6 bg-white/[0.01]">
<div className="max-w-7xl mx-auto grid grid-cols-1 lg:grid-cols-2 gap-24 items-center">
<div className="space-y-12">
<div className="inline-flex px-4 py-1.5 rounded-full bg-red-500/10 border border-red-500/20 text-red-500 text-[10px] font-black uppercase tracking-[0.3em]">
The Stack
</div>
<h2 className="text-6xl md:text-7xl font-bold text-white tracking-tighter font-outfit uppercase">Neural Core <br /><span className="text-slate-600 italic">Technology.</span></h2>
<p className="text-xl text-slate-500 leading-relaxed font-medium">
Our proprietary engine is built on a custom Rust-based microkernel that handles millions of events with a footprint smaller than a typical Discord bot.
</p>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-8 pt-8">
{[
{ icon: Terminal, title: "Custom DSL", desc: "Write advanced logic with our intuitive ZyroX scripting language." },
{ icon: Cpu, title: "FPGA Ready", desc: "Hardware-accelerated pattern matching for instant response." },
{ icon: Lock, title: "Zero Trust", desc: "Every command execution is sandboxed and cryptographically verified." },
{ icon: Radio, title: "Low Entropy", desc: "Optimized for minimal CPU jitter and maximum reliability." }
].map((item, i) => (
<div key={i} className="space-y-4 p-6 rounded-[30px] border border-white/[0.03] hover:bg-white/[0.02] transition-colors">
<item.icon className="h-6 w-6 text-red-500" />
<h4 className="text-lg font-bold text-white font-outfit uppercase tracking-tight">{item.title}</h4>
<p className="text-sm text-slate-600 font-bold leading-relaxed">{item.desc}</p>
</div>
))}
</div>
</div>
<div className="relative aspect-square flex items-center justify-center group">
<div className="absolute inset-0 bg-red-500/5 blur-[120px] rounded-full animate-pulse" />
<div className="h-[80%] w-[80%] border-2 border-white/[0.05] rounded-full animate-[spin_60s_linear_infinite] flex items-center justify-center relative">
<div className="absolute top-0 left-1/2 -translate-x-1/2 h-4 w-4 rounded-full bg-red-500 shadow-[0_0_20px_rgba(239,68,68,0.5)]" />
<div className="h-[70%] w-[70%] border border-white/[0.05] rounded-full animate-[spin_40s_linear_infinite_reverse] flex items-center justify-center">
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 h-3 w-3 rounded-full bg-white/20 shadow-[0_0_20px_rgba(255,255,255,0.2)]" />
<div className="h-[60%] w-[60%] border border-white/[0.05] rounded-full animate-[spin_20s_linear_infinite] flex items-center justify-center">
<Bot className="h-20 w-20 text-red-500/40 group-hover:text-red-500 transition-all duration-700 group-hover:scale-125" />
</div>
</div>
</div>
</div>
</div>
</section>
{/* Modules Grid */}
<section id="modules" className="py-48 px-6">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-32 space-y-6">
<h2 className="text-6xl md:text-8xl font-bold text-white tracking-tighter font-outfit uppercase">The Matrix <br /><span className="bg-gradient-to-r from-red-600 to-red-400 bg-clip-text text-transparent italic">Complete.</span></h2>
<p className="text-2xl text-slate-500 max-w-3xl mx-auto font-medium lowercase">Every module you need. Redefined for the modern era.</p>
</div>
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
{[
{ name: "Anti-Nuke", desc: "Absolute server lockdown.", icon: ShieldAlert },
{ name: "Verification", desc: "Bot-free onboarding.", icon: CheckCircle2 },
{ name: "Welcome", desc: "Cinematic entries.", icon: Sparkles },
{ name: "Vanity Roles", desc: "Custom server identity.", icon: Gamepad2 },
{ name: "Auto Role", desc: "Instant rank assignment.", icon: User },
{ name: "Join to Create", desc: "Self-service voice channels.", icon: Music4 },
{ name: "Tracking", desc: "Predictive user metrics.", icon: Activity },
{ name: "Invites", desc: "Advanced growth tracking.", icon: Globe },
{ name: "Custom Roles", desc: "User-defined permissions.", icon: Lock },
{ name: "Reaction Roles", desc: "Interactive role menus.", icon: Layers },
{ name: "Tickets", desc: "Support at lightspeed.", icon: MessageSquare },
{ name: "Join DM", desc: "Personalized welcomes.", icon: MessageSquare }
].map((mod, i) => (
<div key={i} className="group p-8 rounded-[40px] bg-white/[0.01] border border-white/[0.03] hover:bg-red-500/[0.02] hover:border-red-500/20 transition-all duration-500">
<div className="h-14 w-14 rounded-2xl bg-white/[0.03] flex items-center justify-center mb-6 group-hover:bg-red-500/10 transition-colors">
<mod.icon className="h-6 w-6 text-slate-600 group-hover:text-red-500 transition-colors" />
</div>
<h4 className="text-xl font-bold text-white font-outfit mb-2 tracking-tight">{mod.name}</h4>
<p className="text-xs text-slate-600 font-bold uppercase tracking-widest">{mod.desc}</p>
</div>
))}
</div>
</div>
</section>
{/* Network / Global Section */}
<section id="network" className="py-48 px-6 bg-red-600/[0.01] relative overflow-hidden">
<div className="max-w-7xl mx-auto relative z-10">
<div className="flex flex-col lg:flex-row items-center gap-24">
<div className="flex-1 space-y-12">
<h2 className="text-6xl md:text-8xl font-bold text-white tracking-tighter font-outfit uppercase">Global <br /><span className="text-red-500">Reach.</span></h2>
<p className="text-2xl text-slate-500 leading-relaxed font-medium">
Powering servers with over 12 million combined users. Our network spans every continent, bringing your community closer together.
</p>
<div className="space-y-8">
{[
{ stat: "12M+", label: "Users Protected" },
{ stat: "24", label: "Edge Clusters" },
{ stat: "5.2K", label: "Verified Communities" }
].map((item, i) => (
<div key={i} className="flex items-center gap-8">
<div className="text-5xl font-black text-white font-outfit">{item.stat}</div>
<div className="h-[1px] flex-1 bg-white/5" />
<div className="text-[11px] font-black uppercase text-red-500 tracking-[0.3em]">{item.label}</div>
</div>
))}
</div>
</div>
<div className="flex-1 relative group">
<div className="absolute inset-0 bg-red-500/10 blur-[150px] opacity-0 group-hover:opacity-100 transition-opacity duration-1000" />
<div className="aspect-square bg-[#020617] border border-white/[0.05] rounded-[60px] p-12 relative overflow-hidden flex items-center justify-center">
<Globe className="h-64 w-64 text-red-500/10 animate-pulse" />
<div className="absolute inset-0 flex items-center justify-center">
<div className="h-32 w-32 bg-red-500/20 blur-[60px] rounded-full" />
</div>
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 space-y-12">
<Bot className="h-20 w-20 text-red-500 shadow-[0_0_50px_rgba(239,68,68,0.5)] bg-[#020617] rounded-3xl p-4 border border-red-500/50" />
</div>
</div>
</div>
</div>
</div>
</section>
{/* FAQ Section */}
<section className="py-48 px-6">
<div className="max-w-4xl mx-auto">
<div className="text-center mb-24">
<h2 className="text-5xl md:text-6xl font-black text-white font-outfit tracking-tighter uppercase mb-6">Knowledge Base</h2>
<p className="text-slate-600 font-bold uppercase tracking-widest text-xs">Frequently Asked Questions</p>
</div>
<div className="space-y-6">
{[
{ q: "Is the ZyroX Engine free to use?", a: "The core engine is 100% free for all communities. We offer premium clusters for ultra-high-scale enterprise servers." },
{ q: "How secure is my server data?", a: "Every byte of configuration data is AES-256 encrypted at rest. We never store personal user data beyond Discord's standard requirements." },
{ q: "Can I migrate from other bots?", a: "Yes, our Migration Matrix tool allows you to import leveling and configuration data from most popular bots in minutes." },
{ q: "What is the 'Neural Core'?", a: "It's our advanced event-processing architecture that uses predictive analysis to moderate raids before they escalate." }
].map((item, i) => (
<div key={i} className="p-10 rounded-[40px] border border-white/[0.03] hover:border-white/10 transition-all bg-white/[0.01] group">
<h4 className="text-xl font-bold text-white mb-6 font-outfit uppercase tracking-tight flex items-center gap-4">
<div className="h-2 w-2 rounded-full bg-red-500 opacity-20 group-hover:opacity-100 transition-all" />
{item.q}
</h4>
<p className="text-slate-500 font-bold leading-relaxed">{item.a}</p>
</div>
))}
</div>
</div>
</section>
{/* CTA Section */}
<section className="py-48 px-6">
<div className="max-w-6xl mx-auto relative rounded-[80px] p-24 md:p-32 overflow-hidden bg-gradient-to-br from-red-600 to-red-900 text-center shadow-[0_40px_100px_rgba(0,0,0,0.6)]">
<div className="absolute inset-0 bg-[url('https://grainy-gradients.vercel.app/noise.svg')] opacity-20 mix-blend-overlay" />
<div className="relative z-10">
<h2 className="text-7xl md:text-[9rem] font-bold text-white tracking-tighter font-outfit mb-12 uppercase leading-[0.8] italic">Ready to <br />Evolve?</h2>
<p className="text-2xl text-white/70 max-w-3xl mx-auto mb-20 font-medium">Join 5,000+ communities scaling their automation with the ZyroX Engine. Setup takes less than 30 seconds.</p>
<div className="flex flex-col sm:flex-row items-center justify-center gap-8 tracking-widest uppercase text-xs font-black">
<Button
onClick={() => signIn('discord', { callbackUrl: '/dashboard' })}
className="w-full sm:w-auto rounded-3xl px-16 py-10 bg-white text-black hover:bg-slate-100 border-none shadow-[0_20px_50px_rgba(0,0,0,0.4)] font-black text-lg transition-transform hover:scale-105 active:scale-95"
>
Get Started Free
</Button>
<div className="flex items-center gap-3 text-white">
<div className="h-3 w-3 rounded-full bg-white animate-pulse" />
Neural Uplink: Stable
</div>
</div>
</div>
</div>
</section>
{/* Footer */}
<footer className="py-32 border-t border-white/[0.03] bg-[#020617] relative z-20">
<div className="max-w-7xl mx-auto px-6">
<div className="grid grid-cols-1 md:grid-cols-4 gap-20 mb-32">
<div className="col-span-1 md:col-span-2 space-y-12">
<div className="flex items-center gap-4 group">
<span className="text-3xl font-bold text-white font-outfit uppercase tracking-tighter">{process.env.NEXT_PUBLIC_BRAND_NAME || "ZyroX"} Engine</span>
</div>
<p className="text-slate-600 max-w-sm font-bold leading-relaxed uppercase text-xs tracking-widest">
The high-performance Discord engine for communities that demand excellence. Open-source, secure, and infinitely scalable.
</p>
</div>
<div className="space-y-8">
<h4 className="text-[10px] font-black uppercase tracking-[0.4em] text-white opacity-40">System</h4>
<ul className="space-y-5 text-[11px] font-black uppercase tracking-widest text-slate-500">
<li><Link href="#" className="hover:text-red-500 transition-colors">GitHub Repository</Link></li>
<li><Link href="/docs" className="hover:text-red-500 transition-colors">Documentation</Link></li>
<li><Link href="#" className="hover:text-red-500 transition-colors">API References</Link></li>
</ul>
</div>
<div className="space-y-8">
<h4 className="text-[10px] font-black uppercase tracking-[0.4em] text-white opacity-40">Identity</h4>
<ul className="space-y-5 text-[11px] font-black uppercase tracking-widest text-slate-500">
<li><Link href="/privacy" className="hover:text-red-500 transition-colors">Privacy Shield</Link></li>
<li><Link href="/terms" className="hover:text-red-500 transition-colors">Terms of Service</Link></li>
<li><Link href="#" className="hover:text-red-500 transition-colors">Discord Server</Link></li>
</ul>
</div>
</div>
<div className="pt-12 border-t border-white/5 flex flex-col md:flex-row items-center justify-between gap-6 opacity-40">
<p className="text-slate-700 text-[10px] font-black uppercase tracking-[0.4em]">
© 2026 {process.env.NEXT_PUBLIC_BRAND_NAME || "ZyroX"} Development // Advanced Neural Infrastructure.
</p>
<div className="flex items-center gap-8">
<div className="flex items-center gap-3 text-[10px] font-black text-red-500 uppercase tracking-[0.3em]">
<div className="h-2 w-2 rounded-full bg-red-500 animate-pulse" />
All Nodes Operational
</div>
</div>
</div>
</div>
</footer>
</div>
);
}

View File

@@ -0,0 +1,105 @@
/**
* ╔══════════════════════════════════════════════════════════════════╗
* ║ ║
* ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
* ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
* ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
* ║ ║
* ║ © 2026 CodeX Devs — All Rights Reserved ║
* ║ ║
* ║ discord ── https://discord.gg/codexdev ║
* ║ youtube ── https://youtube.com/@CodeXDevs ║
* ║ github ── https://github.com/RayExo ║
* ║ ║
* ╚══════════════════════════════════════════════════════════════════╝
*/
"use client";
import React from "react";
import Link from "next/link";
import { Bot, ChevronLeft, ShieldCheck, Lock, Eye, FileText } from "lucide-react";
import { Button } from "@/components/ui/button";
export default function PrivacyPage() {
return (
<div className="min-h-screen bg-[#020617] text-slate-200 font-sans">
{/* Background Decor */}
<div className="fixed inset-0 overflow-hidden pointer-events-none z-0">
<div className="absolute top-[-10%] right-[-5%] w-[40%] h-[40%] bg-red-500/[0.03] blur-[120px] rounded-full" />
</div>
<nav className="fixed top-0 w-full z-50 border-b border-white/[0.03] bg-[#020617]/80 backdrop-blur-3xl px-6 h-20 flex items-center justify-between">
<Link href="/" className="flex items-center gap-4 group">
<div className="h-9 w-9 rounded-xl bg-red-600 flex items-center justify-center mr-4">
<Bot className="h-5 w-5 text-white" />
</div>
<span className="text-xl font-bold text-white font-outfit uppercase tracking-tighter">ZyroX Engine</span>
</Link>
<Link href="/">
<Button variant="ghost" className="text-slate-400 hover:text-white gap-2">
<ChevronLeft className="h-4 w-4" />
Back to Home
</Button>
</Link>
</nav>
<main className="relative z-10 pt-40 pb-32 px-6">
<div className="max-w-4xl mx-auto">
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-red-500/10 border border-red-500/20 text-red-500 text-[10px] font-black uppercase tracking-widest mb-8">
<ShieldCheck className="h-3 w-3" />
Privacy Shield v2.0
</div>
<h1 className="text-5xl md:text-7xl font-bold text-white font-outfit tracking-tighter uppercase mb-12 italic">
Privacy <span className="text-red-500 not-italic">Policy.</span>
</h1>
<div className="glass border-white/5 rounded-[40px] p-10 md:p-16 space-y-12">
<section className="space-y-6">
<div className="flex items-center gap-4 text-white">
<div className="h-10 w-10 rounded-2xl bg-white/5 border border-white/10 flex items-center justify-center text-red-500">
<Eye className="h-5 w-5" />
</div>
<h2 className="text-2xl font-bold font-outfit uppercase tracking-tight">Data Collection</h2>
</div>
<p className="text-slate-400 leading-relaxed font-medium">
The ZyroX Engine collects only the minimum necessary data to function within Discord. This includes your Discord User ID, Server (Guild) ID, and configuration settings provided during setup. We do not store message content unless explicitly configured for logging purposes by server administrators.
</p>
</section>
<section className="space-y-6">
<div className="flex items-center gap-4 text-white">
<div className="h-10 w-10 rounded-2xl bg-white/5 border border-white/10 flex items-center justify-center text-red-500">
<Lock className="h-5 w-5" />
</div>
<h2 className="text-2xl font-bold font-outfit uppercase tracking-tight">Data Integrity</h2>
</div>
<p className="text-slate-400 leading-relaxed font-medium">
All configuration data is AES-256 encrypted at rest. Our neural vaults are distributed across global edge nodes, ensuring that your server settings are both secure and instantly available. We never sell or distribute your data to third parties.
</p>
</section>
<section className="space-y-6">
<div className="flex items-center gap-4 text-white">
<div className="h-10 w-10 rounded-2xl bg-white/5 border border-white/10 flex items-center justify-center text-red-500">
<FileText className="h-5 w-5" />
</div>
<h2 className="text-2xl font-bold font-outfit uppercase tracking-tight">User Rights</h2>
</div>
<p className="text-slate-400 leading-relaxed font-medium">
You have the right to request a full dump of your data or immediate deletion of all configurations associated with your Discord account or guild. These requests can be initialized through our support matrix or directly within the dashboard settings.
</p>
</section>
<div className="pt-12 border-t border-white/5">
<p className="text-[10px] font-black uppercase text-slate-600 tracking-[0.4em]">
Last Modified: March 2026 // Neural Jurisdiction: Global Edge Network
</p>
</div>
</div>
</div>
</main>
</div>
);
}

View File

@@ -0,0 +1,105 @@
/**
* ╔══════════════════════════════════════════════════════════════════╗
* ║ ║
* ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
* ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
* ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
* ║ ║
* ║ © 2026 CodeX Devs — All Rights Reserved ║
* ║ ║
* ║ discord ── https://discord.gg/codexdev ║
* ║ youtube ── https://youtube.com/@CodeXDevs ║
* ║ github ── https://github.com/RayExo ║
* ║ ║
* ╚══════════════════════════════════════════════════════════════════╝
*/
"use client";
import React from "react";
import Link from "next/link";
import { Bot, ChevronLeft, Scale, Terminal, ShieldAlert, Cpu } from "lucide-react";
import { Button } from "@/components/ui/button";
export default function TermsPage() {
return (
<div className="min-h-screen bg-[#020617] text-slate-200 font-sans">
{/* Background Decor */}
<div className="fixed inset-0 overflow-hidden pointer-events-none z-0">
<div className="absolute bottom-[-10%] left-[-5%] w-[40%] h-[40%] bg-red-600/[0.03] blur-[120px] rounded-full" />
</div>
<nav className="fixed top-0 w-full z-50 border-b border-white/[0.03] bg-[#020617]/80 backdrop-blur-3xl px-6 h-20 flex items-center justify-between">
<Link href="/" className="flex items-center gap-4 group">
<div className="h-10 w-10 rounded-xl bg-red-600 flex items-center justify-center group-hover:rotate-12 transition-transform">
<Bot className="h-5 w-5 text-white" />
</div>
<span className="text-xl font-bold text-white font-outfit uppercase tracking-tighter">{process.env.NEXT_PUBLIC_BRAND_NAME || "ZyroX"} Engine</span>
</Link>
<Link href="/">
<Button variant="ghost" className="text-slate-400 hover:text-white gap-2">
<ChevronLeft className="h-4 w-4" />
Back to Home
</Button>
</Link>
</nav>
<main className="relative z-10 pt-40 pb-32 px-6">
<div className="max-w-4xl mx-auto">
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-red-500/10 border border-red-500/20 text-red-500 text-[10px] font-black uppercase tracking-widest mb-8">
<Scale className="h-3 w-3" />
Neural Protocol v2.4
</div>
<h1 className="text-5xl md:text-7xl font-bold text-white font-outfit tracking-tighter uppercase mb-12 italic text-right">
Terms of <span className="text-red-500 not-italic">Service.</span>
</h1>
<div className="glass border-white/5 rounded-[40px] p-10 md:p-16 space-y-12 bg-gradient-to-br from-white/[0.01] to-transparent">
<section className="space-y-6">
<div className="flex items-center gap-4 text-white">
<div className="h-10 w-10 rounded-2xl bg-white/5 border border-white/10 flex items-center justify-center text-red-500">
<Terminal className="h-5 w-5" />
</div>
<h2 className="text-2xl font-bold font-outfit uppercase tracking-tight">Acceptance of Protocol</h2>
</div>
<p className="text-slate-400 leading-relaxed font-medium">
By integrating the {process.env.NEXT_PUBLIC_BRAND_NAME || "ZyroX"} Engine into your Discord server, you agree to abide by these terms. The engine is provided &quot;as is,&quot; and while we strive for 100% uptime through our neural edge clusters, we are not liable for any data loss resulting from third-party API disruptions.
</p>
</section>
<section className="space-y-6">
<div className="flex items-center gap-4 text-white">
<div className="h-10 w-10 rounded-2xl bg-white/5 border border-white/10 flex items-center justify-center text-red-500">
<ShieldAlert className="h-5 w-5" />
</div>
<h2 className="text-2xl font-bold font-outfit uppercase tracking-tight">Usage Constraints</h2>
</div>
<p className="text-slate-400 leading-relaxed font-medium">
You may not use the {process.env.NEXT_PUBLIC_BRAND_NAME || "ZyroX"} Engine for any illicit activities, including but not limited to: automated harassment, token logging, or raid coordination. Violation of these constraints will result in immediate neural deauthorization and blacklisting from the global cluster network.
</p>
</section>
<section className="space-y-6">
<div className="flex items-center gap-4 text-white">
<div className="h-10 w-10 rounded-2xl bg-white/5 border border-white/10 flex items-center justify-center text-red-500">
<Cpu className="h-5 w-5" />
</div>
<h2 className="text-2xl font-bold font-outfit uppercase tracking-tight">API & Scaling</h2>
</div>
<p className="text-slate-400 leading-relaxed font-medium">
We reserve the right to throttle or limit API access for guilds that exceed disproportionate resource allocations. High-scale enterprise clusters are available for communities requiring dedicated neural shards.
</p>
</section>
<div className="pt-12 border-t border-white/5">
<p className="text-[10px] font-black uppercase text-slate-600 tracking-[0.4em]">
March 2026 // Distributed via {process.env.NEXT_PUBLIC_BRAND_NAME || "ZyroX"} Neural Cloud
</p>
</div>
</div>
</div>
</main>
</div>
);
}