diff --git a/api/discord/activity-auth.ts b/api/discord/activity-auth.ts index 1e85a6df..73749faa 100644 --- a/api/discord/activity-auth.ts +++ b/api/discord/activity-auth.ts @@ -3,7 +3,7 @@ import { createClient } from "@supabase/supabase-js"; const supabase = createClient( process.env.SUPABASE_URL || "", - process.env.SUPABASE_SERVICE_ROLE || "" + process.env.SUPABASE_SERVICE_ROLE || "", ); interface ActivityAuthRequest { @@ -21,10 +21,7 @@ interface UserData { primary_arm: string | null; } -export default async function handler( - req: VercelRequest, - res: VercelResponse -) { +export default async function handler(req: VercelRequest, res: VercelResponse) { if (req.method !== "POST") { res.setHeader("Allow", "POST"); return res.status(405).json({ error: "Method not allowed" }); @@ -38,15 +35,20 @@ export default async function handler( } // Verify the access token with Discord API - const discordResponse = await fetch("https://discord.com/api/v10/users/@me", { - headers: { - Authorization: `Bearer ${access_token}`, + const discordResponse = await fetch( + "https://discord.com/api/v10/users/@me", + { + headers: { + Authorization: `Bearer ${access_token}`, + }, }, - }); + ); if (!discordResponse.ok) { if (discordResponse.status === 401) { - return res.status(401).json({ error: "Invalid or expired access token" }); + return res + .status(401) + .json({ error: "Invalid or expired access token" }); } throw new Error(`Discord API error: ${discordResponse.statusText}`); } diff --git a/api/discord/admin-register-commands.ts b/api/discord/admin-register-commands.ts index c6ed17c1..842bfb84 100644 --- a/api/discord/admin-register-commands.ts +++ b/api/discord/admin-register-commands.ts @@ -46,10 +46,7 @@ const COMMANDS: CommandData[] = [ }, ]; -export default async function handler( - req: VercelRequest, - res: VercelResponse -) { +export default async function handler(req: VercelRequest, res: VercelResponse) { // Verify this is a POST request if (req.method !== "POST") { res.setHeader("Allow", "POST"); @@ -77,18 +74,16 @@ export default async function handler( try { const rest = new REST({ version: "10" }).setToken( - process.env.DISCORD_BOT_TOKEN! + process.env.DISCORD_BOT_TOKEN!, ); - console.log( - `šŸ“ Registering ${COMMANDS.length} Discord slash commands...` - ); + console.log(`šŸ“ Registering ${COMMANDS.length} Discord slash commands...`); try { // Try bulk update first const data = await rest.put( Routes.applicationCommands(process.env.DISCORD_CLIENT_ID!), - { body: COMMANDS } + { body: COMMANDS }, ); console.log(`āœ… Successfully registered ${data.length} slash commands`); @@ -102,7 +97,7 @@ export default async function handler( // Handle Error 50240 (Entry Point conflict) if (bulkError.code === 50240) { console.warn( - "āš ļø Error 50240: Entry Point detected. Registering individually..." + "āš ļø Error 50240: Entry Point detected. Registering individually...", ); const results = []; @@ -114,7 +109,7 @@ export default async function handler( // Try to post individual command const posted = await rest.post( Routes.applicationCommands(process.env.DISCORD_CLIENT_ID!), - { body: command } + { body: command }, ); results.push({ name: command.name, @@ -141,7 +136,7 @@ export default async function handler( } console.log( - `āœ… Registration complete: ${successCount} new, ${skipCount} already existed` + `āœ… Registration complete: ${successCount} new, ${skipCount} already existed`, ); return res.status(200).json({ diff --git a/client/App.tsx b/client/App.tsx index 97df9233..e95e30bd 100644 --- a/client/App.tsx +++ b/client/App.tsx @@ -122,247 +122,265 @@ const App = () => ( - - - - - - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } - /> - } /> - } /> - } - /> - - } /> - } - /> - } - /> - } - /> - } - /> - } - /> - - } - /> - } /> - } - /> - } /> - } /> - } /> - } /> - } /> - } /> - - {/* Creator Network routes */} - } /> - } - /> - } /> - } - /> - - {/* Service routes */} - } - /> - } - /> - } /> - } /> - } - /> - } /> - - {/* New Arm Landing Pages */} - } /> - } - /> - } /> - } - /> - - } /> - } - /> - } - /> - } - /> - - } /> - } - /> - } - /> - } /> - - } /> - } - /> - } - /> - } - /> - - {/* Dev-Link routes */} - } /> - } - /> - - {/* Nexus routes */} - } /> - - {/* Resource routes */} - }> - } /> - } /> - } /> + + + + + + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> } + path="/projects/:projectId/board" + element={} + /> + } /> + } /> + } /> - } /> - } /> - } /> - } /> - } /> - - } /> - } /> - } /> - } /> - } - /> - } - /> - } - /> - } - /> - } - /> - } /> - } /> - } /> - } /> - } /> - {/* Informational routes */} - } /> - } - /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> + } + /> + } + /> + } + /> + } + /> + } + /> + } + /> - {/* Legal routes */} - } /> - } /> + } + /> + } /> + } + /> + } /> + } /> + } /> + } + /> + } /> + } /> - {/* Discord routes */} - } /> - } /> - } - /> + {/* Creator Network routes */} + } /> + } + /> + } + /> + } + /> - {/* Explicit 404 route for static hosting fallbacks */} - } /> - {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} - } /> - - - + {/* Service routes */} + } + /> + } + /> + } + /> + } /> + } + /> + } /> + + {/* New Arm Landing Pages */} + } /> + } + /> + } /> + } + /> + + } /> + } + /> + } + /> + } + /> + + } /> + } + /> + } + /> + } + /> + + } /> + } + /> + } + /> + } + /> + + {/* Dev-Link routes */} + } /> + } + /> + + {/* Nexus routes */} + } /> + + {/* Resource routes */} + }> + } /> + } /> + } /> + } + /> + } /> + } /> + } /> + } /> + } + /> + + } /> + } /> + } /> + } /> + } + /> + } + /> + } + /> + } + /> + } + /> + } /> + } /> + } /> + } /> + } /> + + {/* Informational routes */} + } /> + } + /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + {/* Legal routes */} + } /> + } /> + + {/* Discord routes */} + } /> + } /> + } + /> + + {/* Explicit 404 route for static hosting fallbacks */} + } /> + {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} + } /> + + + diff --git a/client/components/admin/AdminDiscordManagement.tsx b/client/components/admin/AdminDiscordManagement.tsx index b29e5080..363232e7 100644 --- a/client/components/admin/AdminDiscordManagement.tsx +++ b/client/components/admin/AdminDiscordManagement.tsx @@ -127,7 +127,7 @@ export function AdminDiscordManagement() { setRegisterSuccess(null); const adminToken = prompt( - "Enter admin registration token (from environment variables):" + "Enter admin registration token (from environment variables):", ); if (!adminToken) { setRegisterError("Registration cancelled"); @@ -149,7 +149,7 @@ export function AdminDiscordManagement() { const data = await response.json(); setRegisterSuccess( - data.message || "Discord commands registered successfully!" + data.message || "Discord commands registered successfully!", ); setTimeout(() => setRegisterSuccess(null), 5000); } catch (err) { diff --git a/client/contexts/DiscordActivityContext.tsx b/client/contexts/DiscordActivityContext.tsx index 575c7808..06920623 100644 --- a/client/contexts/DiscordActivityContext.tsx +++ b/client/contexts/DiscordActivityContext.tsx @@ -31,7 +31,7 @@ export const useDiscordActivity = () => { const context = useContext(DiscordActivityContext); if (!context) { throw new Error( - "useDiscordActivity must be used within DiscordActivityProvider" + "useDiscordActivity must be used within DiscordActivityProvider", ); } return context; @@ -41,9 +41,9 @@ interface DiscordActivityProviderProps { children: React.ReactNode; } -export const DiscordActivityProvider: React.FC = ({ - children, -}) => { +export const DiscordActivityProvider: React.FC< + DiscordActivityProviderProps +> = ({ children }) => { const [isActivity, setIsActivity] = useState(false); const [isLoading, setIsLoading] = useState(false); const [user, setUser] = useState(null); @@ -64,11 +64,10 @@ export const DiscordActivityProvider: React.FC = ( setIsLoading(true); // Import the Discord SDK dynamically - const { DiscordSDK } = await import( - "@discord/embedded-app-sdk" - ); + const { DiscordSDK } = await import("@discord/embedded-app-sdk"); - const clientId = import.meta.env.VITE_DISCORD_CLIENT_ID || "578971245454950421"; + const clientId = + import.meta.env.VITE_DISCORD_CLIENT_ID || "578971245454950421"; const sdk = new DiscordSDK({ clientId, @@ -119,7 +118,7 @@ export const DiscordActivityProvider: React.FC = ( "Content-Type": "application/json", }, body: JSON.stringify({ - access_token: currentUser.access_token || "" + access_token: currentUser.access_token || "", }), }); diff --git a/client/pages/Activity.tsx b/client/pages/Activity.tsx index 1163522f..14cf00c0 100644 --- a/client/pages/Activity.tsx +++ b/client/pages/Activity.tsx @@ -132,7 +132,9 @@ export default function Activity() { /profile,{" "} /set-realm, and{" "} - /verify-role{" "} + + /verify-role + {" "} to manage your account within Discord.

diff --git a/discord-bot/bot.js b/discord-bot/bot.js index 67a14f69..d9742d97 100644 --- a/discord-bot/bot.js +++ b/discord-bot/bot.js @@ -114,9 +114,7 @@ client.login(process.env.DISCORD_BOT_TOKEN); client.once("ready", () => { console.log(`āœ… Bot logged in as ${client.user.tag}`); console.log(`šŸ“” Listening in ${client.guilds.cache.size} server(s)`); - console.log( - "ā„¹ļø Commands are registered via: npm run register-commands" - ); + console.log("ā„¹ļø Commands are registered via: npm run register-commands"); // Set bot status client.user.setActivity("/verify to link your AeThex account", { diff --git a/discord-bot/scripts/register-commands.js b/discord-bot/scripts/register-commands.js index 9d92dc21..27ffb8d9 100644 --- a/discord-bot/scripts/register-commands.js +++ b/discord-bot/scripts/register-commands.js @@ -4,16 +4,13 @@ const path = require("path"); require("dotenv").config(); // Validate environment variables -const requiredEnvVars = [ - "DISCORD_BOT_TOKEN", - "DISCORD_CLIENT_ID", -]; +const requiredEnvVars = ["DISCORD_BOT_TOKEN", "DISCORD_CLIENT_ID"]; const missingVars = requiredEnvVars.filter((envVar) => !process.env[envVar]); if (missingVars.length > 0) { console.error( "āŒ FATAL ERROR: Missing required environment variables:", - missingVars.join(", ") + missingVars.join(", "), ); console.error("\nPlease set these before running command registration:"); missingVars.forEach((envVar) => { @@ -43,55 +40,68 @@ for (const file of commandFiles) { async function registerCommands() { try { const rest = new REST({ version: "10" }).setToken( - process.env.DISCORD_BOT_TOKEN + process.env.DISCORD_BOT_TOKEN, ); console.log(`\nšŸ“ Registering ${commands.length} slash commands...`); - console.log("āš ļø This will co-exist with Discord's auto-generated Entry Point command.\n"); + console.log( + "āš ļø This will co-exist with Discord's auto-generated Entry Point command.\n", + ); try { const data = await rest.put( Routes.applicationCommands(process.env.DISCORD_CLIENT_ID), - { body: commands } + { body: commands }, ); console.log(`āœ… Successfully registered ${data.length} slash commands.`); console.log("\nšŸŽ‰ Command registration complete!"); console.log("ā„¹ļø Your commands are now live in Discord."); - console.log("ā„¹ļø The Entry Point command (for Activities) will be managed by Discord.\n"); + console.log( + "ā„¹ļø The Entry Point command (for Activities) will be managed by Discord.\n", + ); } catch (error) { // Handle Entry Point command conflict if (error.code === 50240) { console.warn( - "āš ļø Error 50240: Entry Point command detected (Discord Activity enabled)." + "āš ļø Error 50240: Entry Point command detected (Discord Activity enabled).", ); console.warn("Registering commands individually...\n"); - + let successCount = 0; for (const command of commands) { try { await rest.post( Routes.applicationCommands(process.env.DISCORD_CLIENT_ID), - { body: command } + { body: command }, ); successCount++; } catch (postError) { if (postError.code === 50045) { - console.warn(` āš ļø ${command.name}: Already registered (skipping)`); + console.warn( + ` āš ļø ${command.name}: Already registered (skipping)`, + ); } else { console.error(` āŒ ${command.name}: ${postError.message}`); } } } - - console.log(`\nāœ… Registered ${successCount} slash commands (individual mode).`); + + console.log( + `\nāœ… Registered ${successCount} slash commands (individual mode).`, + ); console.log("šŸŽ‰ Command registration complete!"); - console.log("ā„¹ļø The Entry Point command will be managed by Discord.\n"); + console.log( + "ā„¹ļø The Entry Point command will be managed by Discord.\n", + ); } else { throw error; } } } catch (error) { - console.error("āŒ Fatal error registering commands:", error.message || error); + console.error( + "āŒ Fatal error registering commands:", + error.message || error, + ); process.exit(1); } } diff --git a/docs/DISCORD-ACTIVITY-DEPLOYMENT.md b/docs/DISCORD-ACTIVITY-DEPLOYMENT.md index 8723f6ed..4e1eeda8 100644 --- a/docs/DISCORD-ACTIVITY-DEPLOYMENT.md +++ b/docs/DISCORD-ACTIVITY-DEPLOYMENT.md @@ -11,11 +11,13 @@ If you can't run `npm` in your development environment, follow this guide to dep Add these to your deployment platform (Vercel, PebbleHost): ### Vercel (Frontend) + ``` VITE_DISCORD_CLIENT_ID=578971245454950421 ``` ### PebbleHost (Discord Bot) + ``` DISCORD_BOT_TOKEN= DISCORD_CLIENT_ID=578971245454950421 @@ -26,6 +28,7 @@ BOT_PORT=3000 ``` ### Vercel (Backend - for command registration) + ``` DISCORD_BOT_TOKEN= DISCORD_CLIENT_ID=578971245454950421 @@ -68,15 +71,15 @@ You can add a button to `/admin` panel that triggers this endpoint: ```typescript async function registerCommands() { - const response = await fetch('/api/discord/admin-register-commands', { - method: 'POST', + const response = await fetch("/api/discord/admin-register-commands", { + method: "POST", headers: { - 'Authorization': `Bearer ${process.env.DISCORD_ADMIN_REGISTER_TOKEN}`, - 'Content-Type': 'application/json', + Authorization: `Bearer ${process.env.DISCORD_ADMIN_REGISTER_TOKEN}`, + "Content-Type": "application/json", }, }); const data = await response.json(); - console.log('Registration result:', data); + console.log("Registration result:", data); } ``` @@ -131,10 +134,12 @@ Command registration happens via the API endpoint (Step 3), not on bot startup. ### Error 50240: "Cannot remove Entry Point command" **This happens if:** + - You enable Activities, then the bot tries to register commands via bulk update - The bot is trying to overwrite the auto-generated Entry Point command **Solution:** + - āœ… Your bot code has been fixed (bot no longer registers on startup) - Just call the `/api/discord/admin-register-commands` endpoint (Step 3) - The endpoint handles Error 50240 gracefully @@ -142,6 +147,7 @@ Command registration happens via the API endpoint (Step 3), not on bot startup. ### Activity not loading in Discord **Check:** + 1. Activities enabled in Discord Developer Portal āœ… 2. Activity URL is set to `https://aethex.dev/activity` (not an IP) āœ… 3. Frontend is deployed to Vercel āœ… @@ -151,6 +157,7 @@ Command registration happens via the API endpoint (Step 3), not on bot startup. ### "Unauthorized" error when calling register endpoint **Check:** + 1. `DISCORD_ADMIN_REGISTER_TOKEN` is set in Vercel environment variables 2. You're passing the correct token in the `Authorization: Bearer` header 3. The token matches exactly (no extra spaces) @@ -158,6 +165,7 @@ Command registration happens via the API endpoint (Step 3), not on bot startup. ### Bot not responding to commands **Check:** + 1. Bot is online on PebbleHost (check logs) 2. Commands are registered (call `/api/discord/admin-register-commands` and check response) 3. Response shows commands registered successfully @@ -167,13 +175,13 @@ Command registration happens via the API endpoint (Step 3), not on bot startup. ## Quick Reference -| What | How | -|------|-----| -| Enable Activities | Discord Developer Portal → Settings → Activities → Enable | +| What | How | +| ----------------- | -------------------------------------------------------------- | +| Enable Activities | Discord Developer Portal → Settings → Activities → Enable | | Register Commands | POST to `/api/discord/admin-register-commands` with auth token | -| Deploy Bot | Push to PebbleHost, bot starts with `npm start` | -| Deploy Frontend | Push to GitHub, Vercel auto-deploys | -| Test Activity | Open Discord, click Activity button, should load | +| Deploy Bot | Push to PebbleHost, bot starts with `npm start` | +| Deploy Frontend | Push to GitHub, Vercel auto-deploys | +| Test Activity | Open Discord, click Activity button, should load | --- @@ -209,6 +217,6 @@ Plus the auto-generated **Entry Point** command (managed by Discord for Activiti āœ… Everything can be deployed via web interfaces āœ… Commands registered via API endpoint āœ… Error 50240 handled automatically -āœ… Activity loads instantly in Discord +āœ… Activity loads instantly in Discord **You're all set!** šŸš€ diff --git a/docs/DISCORD-ADMIN-COMMANDS-REGISTRATION.md b/docs/DISCORD-ADMIN-COMMANDS-REGISTRATION.md index 26a2f88a..3c21da0a 100644 --- a/docs/DISCORD-ADMIN-COMMANDS-REGISTRATION.md +++ b/docs/DISCORD-ADMIN-COMMANDS-REGISTRATION.md @@ -24,10 +24,12 @@ Click this button. A popup will appear asking for your **admin registration token**. This token is: + - Set in your Vercel environment variables as `DISCORD_ADMIN_REGISTER_TOKEN` - A random secure string (e.g., `sk-admin-aethex-discord-2024`) **Example popup:** + ``` Enter admin registration token (from environment variables): [_________________________] [OK] [Cancel] @@ -40,16 +42,19 @@ The button will show **"Registering..."** with a spinning loader. Once complete, you'll see one of: **āœ… Success:** + ``` āœ… Registered 5 new commands (Entry Point already exists) ``` **āš ļø Partial Success (Error 50240):** + ``` āœ… Registered 5 new commands (Entry Point managed by Discord) ``` **āŒ Error:** + ``` āŒ Invalid or expired access token ``` @@ -77,6 +82,7 @@ Plus Discord's auto-generated **"Entry Point"** command (for Discord Activities) **Issue:** Getting this error when registering. **Solution:** + 1. Check your `DISCORD_ADMIN_REGISTER_TOKEN` environment variable in Vercel 2. Make sure you entered the token exactly as it's set (case-sensitive) 3. Verify token doesn't have extra spaces @@ -86,6 +92,7 @@ Plus Discord's auto-generated **"Entry Point"** command (for Discord Activities) **Issue:** Generic error when clicking the button. **Check:** + 1. Is your bot deployed on PebbleHost? 2. Are your `DISCORD_BOT_TOKEN` and `DISCORD_CLIENT_ID` env vars set on Vercel (backend)? 3. Open browser console (F12) → Network tab → Check the POST request to `/api/discord/admin-register-commands` @@ -93,6 +100,7 @@ Plus Discord's auto-generated **"Entry Point"** command (for Discord Activities) ### "Entry Point command already exists" (Not an Error) **This is expected!** When you enable Discord Activities: + 1. Discord auto-creates an "Entry Point" command 2. Our script recognizes this and doesn't try to overwrite it 3. Your bot's 5 commands live alongside it peacefully @@ -102,6 +110,7 @@ Plus Discord's auto-generated **"Entry Point"** command (for Discord Activities) ## Environment Variables Required **On Vercel (Backend):** + ``` DISCORD_BOT_TOKEN= DISCORD_CLIENT_ID=578971245454950421 @@ -109,6 +118,7 @@ DISCORD_ADMIN_REGISTER_TOKEN=sk-admin-aethex-discord-2024 ``` **On PebbleHost (Bot):** + ``` DISCORD_BOT_TOKEN= DISCORD_CLIENT_ID=578971245454950421 @@ -124,6 +134,7 @@ SUPABASE_SERVICE_ROLE= If the admin button doesn't work, you can also register commands using: ### curl (if you have PebbleHost console access) + ```bash curl -X POST https://aethex.dev/api/discord/admin-register-commands \ -H "Authorization: Bearer YOUR_DISCORD_ADMIN_REGISTER_TOKEN" \ @@ -131,6 +142,7 @@ curl -X POST https://aethex.dev/api/discord/admin-register-commands \ ``` ### Postman + 1. Create POST request to `https://aethex.dev/api/discord/admin-register-commands` 2. Add header: `Authorization: Bearer YOUR_DISCORD_ADMIN_REGISTER_TOKEN` 3. Click Send @@ -142,6 +154,6 @@ curl -X POST https://aethex.dev/api/discord/admin-register-commands \ āœ… Click "Register Commands" in Admin → Discord tab āœ… Enter your admin token āœ… Wait for success confirmation -āœ… Commands are now live in Discord +āœ… Commands are now live in Discord Done! šŸŽ‰