mirror of
https://github.com/aydenjahola/discord-multipurpose-bot.git
synced 2024-11-22 08:45:55 +00:00
add inital auth implementation
This commit is contained in:
parent
3a2fe0ebc1
commit
43b8e55022
10 changed files with 550 additions and 14 deletions
255
dashboard/app/admin/manage/[guildId]/page.tsx
Normal file
255
dashboard/app/admin/manage/[guildId]/page.tsx
Normal file
|
@ -0,0 +1,255 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
interface ServerSetting {
|
||||||
|
_id: string;
|
||||||
|
guildId: string;
|
||||||
|
emailDomains: string[];
|
||||||
|
countingChannelId: string;
|
||||||
|
generalChannelId: string;
|
||||||
|
logChannelId: string;
|
||||||
|
verificationChannelId: string;
|
||||||
|
verifiedRoleName: string;
|
||||||
|
actionItemsChannelId: string;
|
||||||
|
actionItemsTargetChannelId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Channel {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GuildData {
|
||||||
|
guildId: string;
|
||||||
|
guildName: string;
|
||||||
|
guildIcon: string | null;
|
||||||
|
settings: ServerSetting | null;
|
||||||
|
channels: Channel[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const getChannelNames = async (
|
||||||
|
guildId: string,
|
||||||
|
channelIds: string[]
|
||||||
|
): Promise<Channel[]> => {
|
||||||
|
const channels: Channel[] = [];
|
||||||
|
try {
|
||||||
|
const response = await axios.get(
|
||||||
|
`/api/discord/channels?guildId=${guildId}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const allChannels: Channel[] = response.data;
|
||||||
|
channelIds.forEach((channelId) => {
|
||||||
|
const channel = allChannels.find((c) => c.id === channelId);
|
||||||
|
if (channel) {
|
||||||
|
channels.push({ id: channel.id, name: channel.name });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching channels:", error);
|
||||||
|
}
|
||||||
|
return channels;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ManageServerPage({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: Promise<{ guildId: string }>;
|
||||||
|
}) {
|
||||||
|
const router = useRouter();
|
||||||
|
const [guildData, setGuildData] = useState<GuildData | null>(null);
|
||||||
|
const [activeTab, setActiveTab] = useState<string>("settings");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchParams = async () => {
|
||||||
|
if (params) {
|
||||||
|
const resolvedParams = await params;
|
||||||
|
const guildId = resolvedParams.guildId;
|
||||||
|
|
||||||
|
const response = await fetch(`/api/discord/guilds`);
|
||||||
|
const serverSettings = await response.json();
|
||||||
|
|
||||||
|
const guildInfo = serverSettings.find(
|
||||||
|
(setting: ServerSetting) => setting.guildId === guildId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (guildInfo) {
|
||||||
|
const channelIds = [
|
||||||
|
guildInfo.countingChannelId,
|
||||||
|
guildInfo.generalChannelId,
|
||||||
|
guildInfo.logChannelId,
|
||||||
|
guildInfo.verificationChannelId,
|
||||||
|
guildInfo.actionItemsChannelId,
|
||||||
|
guildInfo.actionItemsTargetChannelId,
|
||||||
|
];
|
||||||
|
const channels = await getChannelNames(guildId, channelIds);
|
||||||
|
|
||||||
|
setGuildData({
|
||||||
|
guildId,
|
||||||
|
guildName: guildInfo.guildName,
|
||||||
|
guildIcon: guildInfo.guildIcon,
|
||||||
|
settings: guildInfo,
|
||||||
|
channels,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchParams();
|
||||||
|
}, [params]);
|
||||||
|
|
||||||
|
const renderSettingsContent = () => {
|
||||||
|
if (!guildData?.settings) return <div>No settings found.</div>;
|
||||||
|
|
||||||
|
const {
|
||||||
|
emailDomains,
|
||||||
|
countingChannelId,
|
||||||
|
generalChannelId,
|
||||||
|
logChannelId,
|
||||||
|
verificationChannelId,
|
||||||
|
verifiedRoleName,
|
||||||
|
actionItemsChannelId,
|
||||||
|
actionItemsTargetChannelId,
|
||||||
|
} = guildData.settings;
|
||||||
|
|
||||||
|
const countingChannel =
|
||||||
|
guildData.channels?.find((c) => c.id === countingChannelId)?.name ||
|
||||||
|
countingChannelId;
|
||||||
|
const generalChannel =
|
||||||
|
guildData.channels?.find((c) => c.id === generalChannelId)?.name ||
|
||||||
|
generalChannelId;
|
||||||
|
const logChannel =
|
||||||
|
guildData.channels?.find((c) => c.id === logChannelId)?.name ||
|
||||||
|
logChannelId;
|
||||||
|
const verificationChannel =
|
||||||
|
guildData.channels?.find((c) => c.id === verificationChannelId)?.name ||
|
||||||
|
verificationChannelId;
|
||||||
|
const actionItemsChannel =
|
||||||
|
guildData.channels?.find((c) => c.id === actionItemsChannelId)?.name ||
|
||||||
|
actionItemsChannelId;
|
||||||
|
const actionItemsTargetChannel =
|
||||||
|
guildData.channels?.find((c) => c.id === actionItemsTargetChannelId)
|
||||||
|
?.name || actionItemsTargetChannelId;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h2 className="text-2xl font-bold">Current Server Settings</h2>
|
||||||
|
<div>
|
||||||
|
<strong>Guild ID:</strong> {guildData.guildId}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Email Domains:</strong> {emailDomains.join(", ") || "None"}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Counting Channel:</strong> {countingChannel}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>General Channel:</strong> {generalChannel}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Log Channel:</strong> {logChannel}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Verification Channel:</strong> {verificationChannel}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Verified Role Name:</strong> {verifiedRoleName}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Action Items Channel:</strong> {actionItemsChannel}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Action Items Target Channel:</strong>{" "}
|
||||||
|
{actionItemsTargetChannel}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderContent = () => {
|
||||||
|
switch (activeTab) {
|
||||||
|
case "settings":
|
||||||
|
return renderSettingsContent();
|
||||||
|
case "roles":
|
||||||
|
return <div>Roles management for {guildData?.guildName}</div>;
|
||||||
|
case "channels":
|
||||||
|
return <div>Channels management for {guildData?.guildName}</div>;
|
||||||
|
case "logs":
|
||||||
|
return <div>Logs for {guildData?.guildName}</div>;
|
||||||
|
default:
|
||||||
|
return <div>Select a tab to manage.</div>;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex min-h-screen bg-gray-900 text-gray-200">
|
||||||
|
<aside className="w-64 bg-gray-800 p-4">
|
||||||
|
<h2 className="text-lg font-bold mb-4">Admin Panel</h2>
|
||||||
|
<ul className="space-y-2">
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
className={`w-full text-left p-2 rounded hover:bg-gray-700 ${
|
||||||
|
activeTab === "settings" ? "bg-gray-700" : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => setActiveTab("settings")}
|
||||||
|
>
|
||||||
|
Server Settings
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
className={`w-full text-left p-2 rounded hover:bg-gray-700 ${
|
||||||
|
activeTab === "roles" ? "bg-gray-700" : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => setActiveTab("roles")}
|
||||||
|
>
|
||||||
|
Manage Roles
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
className={`w-full text-left p-2 rounded hover:bg-gray-700 ${
|
||||||
|
activeTab === "channels" ? "bg-gray-700" : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => setActiveTab("channels")}
|
||||||
|
>
|
||||||
|
Manage Channels
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
className={`w-full text-left p-2 rounded hover:bg-gray-700 ${
|
||||||
|
activeTab === "logs" ? "bg-gray-700" : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => setActiveTab("logs")}
|
||||||
|
>
|
||||||
|
View Logs
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<main className="flex-1 p-8">
|
||||||
|
<h1 className="text-4xl font-bold mb-4">
|
||||||
|
Manage Server: {guildData ? guildData.guildName : "Loading..."}
|
||||||
|
</h1>
|
||||||
|
{guildData?.guildIcon && (
|
||||||
|
<img
|
||||||
|
src={`https://cdn.discordapp.com/icons/${guildData.guildId}/${guildData.guildIcon}.png`}
|
||||||
|
alt={`${guildData.guildName} icon`}
|
||||||
|
className="w-24 h-24 mb-4"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{renderContent()}
|
||||||
|
<button
|
||||||
|
onClick={() => router.back()}
|
||||||
|
className="mt-4 px-4 py-2 bg-blue-600 rounded hover:bg-blue-500 transition duration-200"
|
||||||
|
>
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
90
dashboard/app/admin/page.tsx
Normal file
90
dashboard/app/admin/page.tsx
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useSession, signOut } from "next-auth/react";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
|
interface ServerSetting {
|
||||||
|
_id: string;
|
||||||
|
guildId: string;
|
||||||
|
guildName: string;
|
||||||
|
guildIcon: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AdminPage() {
|
||||||
|
const { data: session } = useSession();
|
||||||
|
const [serverSettings, setServerSettings] = useState<ServerSetting[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!session) return;
|
||||||
|
|
||||||
|
const fetchServerSettings = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/discord/guilds");
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Failed to fetch server settings");
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
console.log("Fetched Server Settings:", data);
|
||||||
|
setServerSettings(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching server settings:", error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchServerSettings();
|
||||||
|
}, [session]);
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-900 text-gray-200">
|
||||||
|
<h1 className="text-2xl font-bold">Access Denied</h1>
|
||||||
|
<p>Please sign in to access this page.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleServerClick = (guildId: string) => {
|
||||||
|
router.push(`/admin/manage/${guildId}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-900 text-gray-200">
|
||||||
|
<h1 className="text-4xl font-bold mb-4">Welcome to the Admin Page!</h1>
|
||||||
|
<p className="mb-6">You are signed in as: {session.user?.name}</p>
|
||||||
|
<button
|
||||||
|
onClick={() => signOut({ callbackUrl: "/" })}
|
||||||
|
className="px-6 py-2 bg-red-600 rounded hover:bg-red-500 transition duration-200 mb-4"
|
||||||
|
>
|
||||||
|
Sign Out
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{loading ? (
|
||||||
|
<p className="mt-4">Loading server settings...</p>
|
||||||
|
) : (
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 mt-4">
|
||||||
|
{serverSettings.map((setting) => (
|
||||||
|
<div
|
||||||
|
key={setting._id}
|
||||||
|
onClick={() => handleServerClick(setting.guildId)}
|
||||||
|
className="bg-gray-800 p-4 rounded shadow-md cursor-pointer hover:bg-gray-700 transition duration-200 flex flex-col items-center"
|
||||||
|
>
|
||||||
|
{setting.guildIcon && (
|
||||||
|
<img
|
||||||
|
src={`https://cdn.discordapp.com/icons/${setting.guildId}/${setting.guildIcon}.png`}
|
||||||
|
alt={`${setting.guildName} icon`}
|
||||||
|
className="w-16 h-16 rounded-full mb-2"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<h2 className="text-lg font-bold">{setting.guildName}</h2>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
21
dashboard/app/api/auth/[...nextauth]/route.ts
Normal file
21
dashboard/app/api/auth/[...nextauth]/route.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import NextAuth from "next-auth";
|
||||||
|
import DiscordProvider from "next-auth/providers/discord";
|
||||||
|
|
||||||
|
export const authOptions = {
|
||||||
|
providers: [
|
||||||
|
DiscordProvider({
|
||||||
|
clientId: process.env.DISCORD_CLIENT_ID!,
|
||||||
|
clientSecret: process.env.DISCORD_CLIENT_SECRET!,
|
||||||
|
authorization: {
|
||||||
|
params: {
|
||||||
|
scope: "identify guilds",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
secret: process.env.NEXTAUTH_SECRET,
|
||||||
|
};
|
||||||
|
|
||||||
|
const handler = NextAuth(authOptions);
|
||||||
|
|
||||||
|
export { handler as GET, handler as POST };
|
44
dashboard/app/api/discord/channels/route.ts
Normal file
44
dashboard/app/api/discord/channels/route.ts
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import axios from "axios";
|
||||||
|
import { getServerSession } from "next-auth/next";
|
||||||
|
import { authOptions } from "../../auth/[...nextauth]/route";
|
||||||
|
|
||||||
|
const DISCORD_API_BASE = "https://discord.com/api/v10";
|
||||||
|
const BOT_TOKEN = process.env.DISCORD_BOT_TOKEN;
|
||||||
|
|
||||||
|
export async function GET(request: Request) {
|
||||||
|
const session = await getServerSession(authOptions);
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Unauthorized access." },
|
||||||
|
{ status: 401 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = new URL(request.url);
|
||||||
|
const guildId = url.searchParams.get("guildId");
|
||||||
|
|
||||||
|
if (!guildId || !BOT_TOKEN) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Guild ID or Discord bot token not set." },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(
|
||||||
|
`${DISCORD_API_BASE}/guilds/${guildId}/channels`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bot ${BOT_TOKEN}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return NextResponse.json(response.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching channels from Discord API:", error);
|
||||||
|
return NextResponse.error();
|
||||||
|
}
|
||||||
|
}
|
74
dashboard/app/api/discord/guilds/route.ts
Normal file
74
dashboard/app/api/discord/guilds/route.ts
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import clientPromise from "@/lib/mongodb";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
interface ServerSetting {
|
||||||
|
_id: string;
|
||||||
|
guildId: string;
|
||||||
|
emailDomains: string[];
|
||||||
|
countingChannelId: string;
|
||||||
|
generalChannelId: string;
|
||||||
|
logChannelId: string;
|
||||||
|
verificationChannelId: string;
|
||||||
|
verifiedRoleName: string;
|
||||||
|
actionItemsChannelId: string;
|
||||||
|
actionItemsTargetChannelId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DISCORD_API_BASE = "https://discord.com/api/v10";
|
||||||
|
const BOT_TOKEN = process.env.DISCORD_BOT_TOKEN;
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
if (!BOT_TOKEN) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Discord bot token not set." },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const client = await clientPromise;
|
||||||
|
const database = client.db("test");
|
||||||
|
const collection = database.collection<ServerSetting>("serversettings");
|
||||||
|
|
||||||
|
const serverSettings = await collection.find({}).toArray();
|
||||||
|
|
||||||
|
const enrichedServerSettings = await Promise.all(
|
||||||
|
serverSettings.map(async (setting) => {
|
||||||
|
try {
|
||||||
|
const guildResponse = await axios.get(
|
||||||
|
`${DISCORD_API_BASE}/guilds/${setting.guildId}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bot ${BOT_TOKEN}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const guildData = guildResponse.data;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...setting,
|
||||||
|
guildName: guildData.name,
|
||||||
|
guildIcon: guildData.icon,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
`Error fetching guild details for ${setting.guildId}:`,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
...setting,
|
||||||
|
guildName: "Unknown Guild",
|
||||||
|
guildIcon: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return NextResponse.json(enrichedServerSettings);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching server settings:", error);
|
||||||
|
return NextResponse.error();
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ import localFont from "next/font/local";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import Navbar from "@/components/Navbar";
|
import Navbar from "@/components/Navbar";
|
||||||
import Footer from "@/components/Footer";
|
import Footer from "@/components/Footer";
|
||||||
|
import ClientProvider from "@/components/ClientProvider";
|
||||||
|
|
||||||
const geistSans = localFont({
|
const geistSans = localFont({
|
||||||
src: "./fonts/GeistVF.woff",
|
src: "./fonts/GeistVF.woff",
|
||||||
|
@ -26,6 +27,7 @@ export default function RootLayout({
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
|
<ClientProvider>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<body
|
<body
|
||||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||||
|
@ -35,5 +37,6 @@ export default function RootLayout({
|
||||||
<Footer />
|
<Footer />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
</ClientProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
24
dashboard/app/signin/page.tsx
Normal file
24
dashboard/app/signin/page.tsx
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { signIn } from "next-auth/react";
|
||||||
|
|
||||||
|
export default function SignIn() {
|
||||||
|
const handleSignIn = () => {
|
||||||
|
signIn("discord", { callbackUrl: "/admin" });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-900 text-gray-200">
|
||||||
|
<h1 className="text-4xl font-bold mb-4">Sign In</h1>
|
||||||
|
<p className="mb-6">
|
||||||
|
Please sign in to access the features of our Discord bot.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
onClick={handleSignIn}
|
||||||
|
className="px-6 py-2 bg-blue-600 rounded hover:bg-blue-500 transition duration-200"
|
||||||
|
>
|
||||||
|
Sign in with Discord
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
11
dashboard/components/ClientProvider.tsx
Normal file
11
dashboard/components/ClientProvider.tsx
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { SessionProvider } from "next-auth/react";
|
||||||
|
|
||||||
|
interface ClientProviderProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ClientProvider({ children }: ClientProviderProps) {
|
||||||
|
return <SessionProvider>{children}</SessionProvider>;
|
||||||
|
}
|
10
dashboard/lib/mongodb.ts
Normal file
10
dashboard/lib/mongodb.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { MongoClient } from "mongodb";
|
||||||
|
|
||||||
|
if (!process.env.MONGODB_URI) {
|
||||||
|
throw new Error("MongoDB URI not found in .env.local");
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = new MongoClient(process.env.MONGODB_URI);
|
||||||
|
const clientPromise = client.connect();
|
||||||
|
|
||||||
|
export default clientPromise;
|
|
@ -9,18 +9,22 @@
|
||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/next-auth": "^3.13.0",
|
||||||
|
"axios": "^1.7.7",
|
||||||
|
"mongodb": "^6.10.0",
|
||||||
|
"next": "15.0.1",
|
||||||
|
"next-auth": "^4.24.10",
|
||||||
"react": "19.0.0-rc-69d4b800-20241021",
|
"react": "19.0.0-rc-69d4b800-20241021",
|
||||||
"react-dom": "19.0.0-rc-69d4b800-20241021",
|
"react-dom": "19.0.0-rc-69d4b800-20241021"
|
||||||
"next": "15.0.1"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5",
|
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^18",
|
"@types/react": "^18",
|
||||||
"@types/react-dom": "^18",
|
"@types/react-dom": "^18",
|
||||||
|
"eslint": "^8",
|
||||||
|
"eslint-config-next": "15.0.1",
|
||||||
"postcss": "^8",
|
"postcss": "^8",
|
||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.1",
|
||||||
"eslint": "^8",
|
"typescript": "^5"
|
||||||
"eslint-config-next": "15.0.1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue