From 1343a0775644afb05cdd1c476d932ae65024b623 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 14 Dec 2025 00:11:45 +0000
Subject: [PATCH 1/6] Initial plan
From f7aa4eefeabb1a4652f9738705525db7c5b7f8b0 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 14 Dec 2025 00:18:33 +0000
Subject: [PATCH 2/6] Add webapp with guild listing and authentication
Co-authored-by: AlphaGameDeveloper <77273893+AlphaGameDeveloper@users.noreply.github.com>
---
.../migration.sql | 2 +
prisma/schema.prisma | 13 +-
web/app/api/auth/callback/route.ts | 1 +
web/app/api/auth/login/route.ts | 2 +-
web/app/api/guilds/route.ts | 94 ++++++++++
web/app/components/InlineLoginStatus.tsx | 4 +-
web/app/components/UserAvatar.tsx | 4 +-
web/app/webapp/layout.tsx | 153 ++++++++++++++++
web/app/webapp/page.tsx | 172 ++++++++++++++++++
9 files changed, 434 insertions(+), 11 deletions(-)
create mode 100644 prisma/migrations/20251214001610_add_access_token_to_session/migration.sql
create mode 100644 web/app/api/guilds/route.ts
create mode 100644 web/app/webapp/layout.tsx
create mode 100644 web/app/webapp/page.tsx
diff --git a/prisma/migrations/20251214001610_add_access_token_to_session/migration.sql b/prisma/migrations/20251214001610_add_access_token_to_session/migration.sql
new file mode 100644
index 0000000..0332b61
--- /dev/null
+++ b/prisma/migrations/20251214001610_add_access_token_to_session/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE "Session" ADD COLUMN "access_token" TEXT;
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 9338a55..c893aef 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -58,12 +58,13 @@ model ErrorReport {
/// Sessions for web authentication. Stores a hash of the session token
/// (so the raw token is only sent to the user's cookie) and points to a User.
model Session {
- id String @id @default(cuid())
- hashedId String @unique
- user_id String @db.VarChar(20)
- user_json Json
- created_at DateTime @default(now())
- expires_at DateTime
+ id String @id @default(cuid())
+ hashedId String @unique
+ user_id String @db.VarChar(20)
+ user_json Json
+ access_token String? @db.Text
+ created_at DateTime @default(now())
+ expires_at DateTime
user User @relation(fields: [user_id], references: [id])
}
diff --git a/web/app/api/auth/callback/route.ts b/web/app/api/auth/callback/route.ts
index 869d62e..b68215c 100644
--- a/web/app/api/auth/callback/route.ts
+++ b/web/app/api/auth/callback/route.ts
@@ -56,6 +56,7 @@ export async function GET(req: NextRequest) {
hashedId: hashed,
user_id: user.id,
user_json: user,
+ access_token: token.access_token,
expires_at: expires
}
});
diff --git a/web/app/api/auth/login/route.ts b/web/app/api/auth/login/route.ts
index 7173f9f..5304071 100644
--- a/web/app/api/auth/login/route.ts
+++ b/web/app/api/auth/login/route.ts
@@ -6,7 +6,7 @@ export async function GET() {
const clientId = process.env.DISCORD_CLIENT_ID;
const base = process.env.NEXT_PUBLIC_BASE_URL;
const redirectUri = (base || '') + '/api/auth/callback';
- const scope = encodeURIComponent('identify');
+ const scope = encodeURIComponent('identify guilds');
if (!clientId || !process.env.NEXT_PUBLIC_BASE_URL) {
return NextResponse.json({ error: 'OAuth not configured' }, { status: 500 });
diff --git a/web/app/api/guilds/route.ts b/web/app/api/guilds/route.ts
new file mode 100644
index 0000000..a50bbb6
--- /dev/null
+++ b/web/app/api/guilds/route.ts
@@ -0,0 +1,94 @@
+// This file is a part of AlphaGameBot.
+//
+// AlphaGameBot - A Discord bot that's free and (hopefully) doesn't suck.
+// Copyright (C) 2025 Damien Boisvert (AlphaGameDeveloper)
+//
+// AlphaGameBot is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// AlphaGameBot is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with AlphaGameBot. If not, see .
+
+import { hashToken } from '@/app/lib/session';
+import { NextRequest, NextResponse } from 'next/server';
+import db from '../../../lib/database';
+
+interface DiscordGuild {
+ id: string;
+ name: string;
+ icon: string | null;
+ owner: boolean;
+ permissions: string;
+}
+
+export async function GET(req: NextRequest) {
+ const cookie = req.headers.get('cookie') || '';
+ const match = cookie.split(';').map(s => s.trim()).find(s => s.startsWith('agb_session='));
+ if (!match) return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
+
+ try {
+ const raw = match.replace('agb_session=', '');
+ const hashed = hashToken(raw);
+ const session = await db.session.findFirst({ where: { hashedId: hashed } });
+
+ if (!session || !session.access_token) {
+ return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
+ }
+
+ if (new Date(session.expires_at) < new Date()) {
+ await db.session.deleteMany({ where: { hashedId: hashed } });
+ return NextResponse.json({ error: 'Session expired' }, { status: 401 });
+ }
+
+ // Fetch guilds from Discord API
+ const res = await fetch('https://discord.com/api/users/@me/guilds', {
+ headers: { Authorization: `Bearer ${session.access_token}` }
+ });
+
+ if (!res.ok) {
+ return NextResponse.json({ error: 'Failed to fetch guilds' }, { status: res.status });
+ }
+
+ const guilds = await res.json() as DiscordGuild[];
+
+ // Filter guilds where user has administrator permission
+ // Discord permission bit for ADMINISTRATOR is 0x8
+ const adminGuilds = guilds.filter(guild => {
+ const permissions = BigInt(guild.permissions);
+ return (permissions & BigInt(0x8)) === BigInt(0x8) || guild.owner;
+ });
+
+ // Check which guilds have AlphaGameBot
+ const guildIds = adminGuilds.map(g => g.id);
+ const botGuilds = await db.guild.findMany({
+ where: { id: { in: guildIds } },
+ select: { id: true, name: true, updated_at: true }
+ });
+
+ const botGuildIds = new Set(botGuilds.map(g => g.id));
+
+ // Return guilds with bot status
+ const guildsWithBotStatus = adminGuilds.map(guild => ({
+ id: guild.id,
+ name: guild.name,
+ icon: guild.icon,
+ hasBot: botGuildIds.has(guild.id),
+ dbInfo: botGuilds.find(bg => bg.id === guild.id)
+ }));
+
+ // Only return guilds that have the bot
+ const mutualGuilds = guildsWithBotStatus.filter(g => g.hasBot);
+
+ return NextResponse.json({ guilds: mutualGuilds });
+ } catch (error) {
+ console.error('Error fetching guilds:', error);
+ return NextResponse.json({ error: 'Internal error' }, { status: 500 });
+ }
+}
diff --git a/web/app/components/InlineLoginStatus.tsx b/web/app/components/InlineLoginStatus.tsx
index 600fbdf..bf37744 100644
--- a/web/app/components/InlineLoginStatus.tsx
+++ b/web/app/components/InlineLoginStatus.tsx
@@ -75,10 +75,10 @@ export default function InlineLoginStatus() {
diff --git a/web/app/webapp/layout.tsx b/web/app/webapp/layout.tsx
new file mode 100644
index 0000000..0541fd4
--- /dev/null
+++ b/web/app/webapp/layout.tsx
@@ -0,0 +1,153 @@
+// This file is a part of AlphaGameBot.
+//
+// AlphaGameBot - A Discord bot that's free and (hopefully) doesn't suck.
+// Copyright (C) 2025 Damien Boisvert (AlphaGameDeveloper)
+//
+// AlphaGameBot is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// AlphaGameBot is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with AlphaGameBot. If not, see .
+
+"use client";
+
+import Link from "next/link";
+import { usePathname } from "next/navigation";
+import { useEffect, useState } from "react";
+import type { User } from "discord.js";
+
+export default function WebAppLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ const [user, setUser] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const pathname = usePathname();
+
+ useEffect(() => {
+ fetch('/api/auth/session')
+ .then((res) => res.json())
+ .then((data) => {
+ if (!data.user) {
+ window.location.href = '/api/auth/login';
+ } else {
+ setUser(data.user);
+ setLoading(false);
+ }
+ })
+ .catch(() => {
+ window.location.href = '/api/auth/login';
+ });
+ }, []);
+
+ if (loading) {
+ return (
+
+ );
+ }
+
+ return (
+
+ {/* Top Bar */}
+
+
+
+ {/* Right Side Navigation */}
+
+
+
+
+ {/* Main Content */}
+
+ {children}
+
+
+
+ );
+}
diff --git a/web/app/webapp/page.tsx b/web/app/webapp/page.tsx
new file mode 100644
index 0000000..d415c09
--- /dev/null
+++ b/web/app/webapp/page.tsx
@@ -0,0 +1,172 @@
+// This file is a part of AlphaGameBot.
+//
+// AlphaGameBot - A Discord bot that's free and (hopefully) doesn't suck.
+// Copyright (C) 2025 Damien Boisvert (AlphaGameDeveloper)
+//
+// AlphaGameBot is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// AlphaGameBot is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with AlphaGameBot. If not, see .
+
+"use client";
+
+import { useEffect, useState } from "react";
+
+interface Guild {
+ id: string;
+ name: string;
+ icon: string | null;
+ hasBot: boolean;
+ dbInfo?: {
+ id: string;
+ name: string;
+ updated_at: Date;
+ };
+}
+
+export default function WebAppPage() {
+ const [guilds, setGuilds] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ fetch('/api/guilds')
+ .then((res) => {
+ if (!res.ok) throw new Error('Failed to fetch guilds');
+ return res.json();
+ })
+ .then((data) => {
+ setGuilds(data.guilds || []);
+ setLoading(false);
+ })
+ .catch((err) => {
+ setError(err.message);
+ setLoading(false);
+ });
+ }, []);
+
+ if (loading) {
+ return (
+
+
+
Loading your servers...
+
+ );
+ }
+
+ if (error) {
+ return (
+
+
+
+
+
+
Error loading servers
+
{error}
+
+
+ );
+ }
+
+ if (guilds.length === 0) {
+ return (
+
+
+
+
+
+
No servers found
+
You don't have any servers where you're an administrator and AlphaGameBot is present.
+
+
+
+ );
+ }
+
+ return (
+
+
+
Your Servers
+
+ Manage AlphaGameBot in servers where you have administrator permissions
+
+
+
+
+ {guilds.map((guild) => (
+
+
+ {guild.icon ? (
+
+ ) : (
+
+ {guild.name.charAt(0).toUpperCase()}
+
+ )}
+
+
{guild.name}
+
+ ID: {guild.id}
+
+
+
+
+
+
+ Configure
+
+
+ Stats
+
+
+
+ ))}
+
+
+ );
+}
From b011a1f21695d63730af0d78dd4d3a393537e7f8 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 14 Dec 2025 00:21:42 +0000
Subject: [PATCH 3/6] Fix linting issues in webapp files
Co-authored-by: AlphaGameDeveloper <77273893+AlphaGameDeveloper@users.noreply.github.com>
---
package-lock.json | 18 ------------------
web/app/api/guilds/route.ts | 3 ++-
web/app/webapp/layout.tsx | 2 +-
web/app/webapp/page.tsx | 2 +-
4 files changed, 4 insertions(+), 21 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 0a05022..f40d378 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -95,7 +95,6 @@
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5",
@@ -2589,7 +2588,6 @@
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-7.1.0.tgz",
"integrity": "sha512-fNxRUk1KhjSbnbuBxlWSnBLKLBNun52ZBTcs22H/xEEzM6Ap81ZFTQ4bZBxVQGQgVY0xugKGoRcCbaKjLQ3XZA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@fortawesome/fontawesome-common-types": "7.1.0"
},
@@ -4265,7 +4263,6 @@
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.6.tgz",
"integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@octokit/auth-token": "^6.0.0",
"@octokit/graphql": "^9.0.3",
@@ -5139,7 +5136,6 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
"integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"undici-types": "~7.16.0"
}
@@ -5156,7 +5152,6 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.6.tgz",
"integrity": "sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w==",
"license": "MIT",
- "peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -5263,7 +5258,6 @@
"integrity": "sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.47.0",
"@typescript-eslint/types": "8.47.0",
@@ -5773,7 +5767,6 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -6414,7 +6407,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.8.25",
"caniuse-lite": "^1.0.30001754",
@@ -6719,7 +6711,6 @@
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
"devOptional": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
@@ -7675,7 +7666,6 @@
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -7861,7 +7851,6 @@
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9",
@@ -10152,7 +10141,6 @@
"integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@jest/core": "30.2.0",
"@jest/types": "30.2.0",
@@ -13526,7 +13514,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -13536,7 +13523,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -14955,7 +14941,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12"
},
@@ -15116,7 +15101,6 @@
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
@@ -15349,7 +15333,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true,
"license": "Apache-2.0",
- "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -16474,7 +16457,6 @@
"integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
diff --git a/web/app/api/guilds/route.ts b/web/app/api/guilds/route.ts
index a50bbb6..06d5c44 100644
--- a/web/app/api/guilds/route.ts
+++ b/web/app/api/guilds/route.ts
@@ -18,7 +18,7 @@
import { hashToken } from '@/app/lib/session';
import { NextRequest, NextResponse } from 'next/server';
-import db from '../../../lib/database';
+import db from '@/app/lib/database';
interface DiscordGuild {
id: string;
@@ -88,6 +88,7 @@ export async function GET(req: NextRequest) {
return NextResponse.json({ guilds: mutualGuilds });
} catch (error) {
+ // eslint-disable-next-line no-console
console.error('Error fetching guilds:', error);
return NextResponse.json({ error: 'Internal error' }, { status: 500 });
}
diff --git a/web/app/webapp/layout.tsx b/web/app/webapp/layout.tsx
index 0541fd4..7e06871 100644
--- a/web/app/webapp/layout.tsx
+++ b/web/app/webapp/layout.tsx
@@ -53,7 +53,7 @@ export default function WebAppLayout({
+ style={{ borderColor: 'var(--primary-500)', borderTopColor: 'transparent' }}>
Loading...
diff --git a/web/app/webapp/page.tsx b/web/app/webapp/page.tsx
index d415c09..2ab5065 100644
--- a/web/app/webapp/page.tsx
+++ b/web/app/webapp/page.tsx
@@ -57,7 +57,7 @@ export default function WebAppPage() {
return (
+ style={{ borderColor: 'var(--primary-500)', borderTopColor: 'transparent' }}>
Loading your servers...
);
From 5ef87f9d8d420a87156e6a55a8265b678e0776e8 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 14 Dec 2025 00:25:43 +0000
Subject: [PATCH 4/6] Fix hardcoded Discord client ID and expose via env var
Co-authored-by: AlphaGameDeveloper <77273893+AlphaGameDeveloper@users.noreply.github.com>
---
web/app/webapp/page.tsx | 2 +-
web/next.config.ts | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/web/app/webapp/page.tsx b/web/app/webapp/page.tsx
index 2ab5065..cab7007 100644
--- a/web/app/webapp/page.tsx
+++ b/web/app/webapp/page.tsx
@@ -88,7 +88,7 @@ export default function WebAppPage() {
You don't have any servers where you're an administrator and AlphaGameBot is present.
Date: Sun, 14 Dec 2025 04:57:24 +0000
Subject: [PATCH 5/6] Separate website and webapp layouts using Next.js route
groups
Co-authored-by: AlphaGameDeveloper <77273893+AlphaGameDeveloper@users.noreply.github.com>
---
web/app/{ => (webapp)}/webapp/layout.tsx | 0
web/app/{ => (webapp)}/webapp/page.tsx | 0
web/app/{ => (website)}/about/AboutPage.tsx | 0
web/app/{ => (website)}/about/page.tsx | 0
.../blog/[year]/[month]/[slug]/page.tsx | 0
.../blog/components/BlogControls.tsx | 0
.../blog/components/BlogPostsList.tsx | 0
.../blog/components/PostTypeBadge.tsx | 0
web/app/{ => (website)}/blog/page.tsx | 0
web/app/{ => (website)}/contact/page.tsx | 0
web/app/{ => (website)}/contact/submitForm.ts | 0
web/app/(website)/layout.tsx | 81 +++++++++++++++++++
web/app/{ => (website)}/page.tsx | 0
web/app/layout.tsx | 78 +-----------------
14 files changed, 82 insertions(+), 77 deletions(-)
rename web/app/{ => (webapp)}/webapp/layout.tsx (100%)
rename web/app/{ => (webapp)}/webapp/page.tsx (100%)
rename web/app/{ => (website)}/about/AboutPage.tsx (100%)
rename web/app/{ => (website)}/about/page.tsx (100%)
rename web/app/{ => (website)}/blog/[year]/[month]/[slug]/page.tsx (100%)
rename web/app/{ => (website)}/blog/components/BlogControls.tsx (100%)
rename web/app/{ => (website)}/blog/components/BlogPostsList.tsx (100%)
rename web/app/{ => (website)}/blog/components/PostTypeBadge.tsx (100%)
rename web/app/{ => (website)}/blog/page.tsx (100%)
rename web/app/{ => (website)}/contact/page.tsx (100%)
rename web/app/{ => (website)}/contact/submitForm.ts (100%)
create mode 100644 web/app/(website)/layout.tsx
rename web/app/{ => (website)}/page.tsx (100%)
diff --git a/web/app/webapp/layout.tsx b/web/app/(webapp)/webapp/layout.tsx
similarity index 100%
rename from web/app/webapp/layout.tsx
rename to web/app/(webapp)/webapp/layout.tsx
diff --git a/web/app/webapp/page.tsx b/web/app/(webapp)/webapp/page.tsx
similarity index 100%
rename from web/app/webapp/page.tsx
rename to web/app/(webapp)/webapp/page.tsx
diff --git a/web/app/about/AboutPage.tsx b/web/app/(website)/about/AboutPage.tsx
similarity index 100%
rename from web/app/about/AboutPage.tsx
rename to web/app/(website)/about/AboutPage.tsx
diff --git a/web/app/about/page.tsx b/web/app/(website)/about/page.tsx
similarity index 100%
rename from web/app/about/page.tsx
rename to web/app/(website)/about/page.tsx
diff --git a/web/app/blog/[year]/[month]/[slug]/page.tsx b/web/app/(website)/blog/[year]/[month]/[slug]/page.tsx
similarity index 100%
rename from web/app/blog/[year]/[month]/[slug]/page.tsx
rename to web/app/(website)/blog/[year]/[month]/[slug]/page.tsx
diff --git a/web/app/blog/components/BlogControls.tsx b/web/app/(website)/blog/components/BlogControls.tsx
similarity index 100%
rename from web/app/blog/components/BlogControls.tsx
rename to web/app/(website)/blog/components/BlogControls.tsx
diff --git a/web/app/blog/components/BlogPostsList.tsx b/web/app/(website)/blog/components/BlogPostsList.tsx
similarity index 100%
rename from web/app/blog/components/BlogPostsList.tsx
rename to web/app/(website)/blog/components/BlogPostsList.tsx
diff --git a/web/app/blog/components/PostTypeBadge.tsx b/web/app/(website)/blog/components/PostTypeBadge.tsx
similarity index 100%
rename from web/app/blog/components/PostTypeBadge.tsx
rename to web/app/(website)/blog/components/PostTypeBadge.tsx
diff --git a/web/app/blog/page.tsx b/web/app/(website)/blog/page.tsx
similarity index 100%
rename from web/app/blog/page.tsx
rename to web/app/(website)/blog/page.tsx
diff --git a/web/app/contact/page.tsx b/web/app/(website)/contact/page.tsx
similarity index 100%
rename from web/app/contact/page.tsx
rename to web/app/(website)/contact/page.tsx
diff --git a/web/app/contact/submitForm.ts b/web/app/(website)/contact/submitForm.ts
similarity index 100%
rename from web/app/contact/submitForm.ts
rename to web/app/(website)/contact/submitForm.ts
diff --git a/web/app/(website)/layout.tsx b/web/app/(website)/layout.tsx
new file mode 100644
index 0000000..fc97850
--- /dev/null
+++ b/web/app/(website)/layout.tsx
@@ -0,0 +1,81 @@
+import type { Metadata } from "next";
+import Footer from "../components/Footer";
+import Header from "../components/Header";
+
+export const metadata: Metadata = {
+ metadataBase: new URL("https://alphagamebot.com"),
+ title: {
+ default: "AlphaGameBot - Free Discord Leveling Bot | Boost Server Engagement",
+ template: "%s • AlphaGameBot",
+ },
+ description:
+ "Free open-source Discord bot with user leveling, XP system, global leaderboards, and engagement tracking. Boost your Discord community with 1,200+ active servers.",
+ keywords: [
+ "discord bot",
+ "leveling bot",
+ "discord xp",
+ "server engagement",
+ "discord leaderboard",
+ "free discord bot",
+ "open source discord bot",
+ "discord community",
+ "discord gamification",
+ ],
+ authors: [{ name: "Damien Boisvert" }],
+ creator: "Damien Boisvert",
+ publisher: "AlphaGameBot",
+ robots: {
+ index: true,
+ follow: true,
+ googleBot: {
+ index: true,
+ follow: true,
+ "max-video-preview": -1,
+ "max-image-preview": "large",
+ "max-snippet": -1,
+ },
+ },
+ openGraph: {
+ type: "website",
+ locale: "en_US",
+ url: "https://alphagamebot.com",
+ title: "AlphaGameBot - Free Discord Engagement Bot",
+ description:
+ "Boost your Discord server engagement with our free open-source leveling bot. XP system, global leaderboards, and more!",
+ siteName: "AlphaGameBot",
+ images: [
+ {
+ url: "/og-image.png",
+ width: 1200,
+ height: 630,
+ alt: "AlphaGameBot Discord Bot",
+ },
+ ],
+ },
+ twitter: {
+ card: "summary_large_image",
+ title: "AlphaGameBot - Free Discord Engagement Bot",
+ description:
+ "Boost your Discord server engagement with our free open-source leveling bot. XP system, global leaderboards, and more!",
+ images: ["/og-image.png"],
+ },
+ alternates: {
+ canonical: "https://alphagamebot.com",
+ },
+};
+
+export default function WebsiteLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ return (
+ <>
+
+
+ {children}
+
+
+ >
+ );
+}
diff --git a/web/app/page.tsx b/web/app/(website)/page.tsx
similarity index 100%
rename from web/app/page.tsx
rename to web/app/(website)/page.tsx
diff --git a/web/app/layout.tsx b/web/app/layout.tsx
index 8bbc2e8..1c1a9b8 100644
--- a/web/app/layout.tsx
+++ b/web/app/layout.tsx
@@ -1,78 +1,6 @@
-import type { Metadata } from "next";
-import { Inter } from "next/font/google";
-import Footer from "./components/Footer";
-import Header from "./components/Header";
import { UmamiAnalytics } from "./components/Umami";
import "./globals.css";
-const inter = Inter({
- subsets: ["latin"],
- display: "swap",
- variable: "--font-inter",
-});
-
-export const metadata: Metadata = {
- metadataBase: new URL("https://alphagamebot.com"),
- title: {
- default: "AlphaGameBot - Free Discord Leveling Bot | Boost Server Engagement",
- template: "%s • AlphaGameBot",
- },
- description:
- "Free open-source Discord bot with user leveling, XP system, global leaderboards, and engagement tracking. Boost your Discord community with 1,200+ active servers.",
- keywords: [
- "discord bot",
- "leveling bot",
- "discord xp",
- "server engagement",
- "discord leaderboard",
- "free discord bot",
- "open source discord bot",
- "discord community",
- "discord gamification",
- ],
- authors: [{ name: "Damien Boisvert" }],
- creator: "Damien Boisvert",
- publisher: "AlphaGameBot",
- robots: {
- index: true,
- follow: true,
- googleBot: {
- index: true,
- follow: true,
- "max-video-preview": -1,
- "max-image-preview": "large",
- "max-snippet": -1,
- },
- },
- openGraph: {
- type: "website",
- locale: "en_US",
- url: "https://alphagamebot.com",
- title: "AlphaGameBot - Free Discord Engagement Bot",
- description:
- "Boost your Discord server engagement with our free open-source leveling bot. XP system, global leaderboards, and more!",
- siteName: "AlphaGameBot",
- images: [
- {
- url: "/og-image.png",
- width: 1200,
- height: 630,
- alt: "AlphaGameBot Discord Bot",
- },
- ],
- },
- twitter: {
- card: "summary_large_image",
- title: "AlphaGameBot - Free Discord Engagement Bot",
- description:
- "Boost your Discord server engagement with our free open-source leveling bot. XP system, global leaderboards, and more!",
- images: ["/og-image.png"],
- },
- alternates: {
- canonical: "https://alphagamebot.com",
- },
-};
-
export default function RootLayout({
children,
}: Readonly<{
@@ -85,11 +13,7 @@ export default function RootLayout({
-
-
- {children}
-
-
+ {children}