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() {
- View profile + Dashboard - Profile + Dashboard
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 ( +
+
+
+

Loading...

+
+
+ ); + } + + return ( +
+ {/* Top Bar */} +
+
+
+ + AlphaGameBot + + / + Dashboard +
+ + {user && ( +
+ {`${user.username} +
+
{user.username}
+
+ #{user.discriminator} +
+
+
+ )} +
+
+ +
+ {/* 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} + ) : ( +
+ {guild.name.charAt(0).toUpperCase()} +
+ )} +
+

{guild.name}

+

+ ID: {guild.id} +

+
+
+ +
+ + +
+
+ ))} +
+
+ ); +} 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} +
+