Prettier format pending files

This commit is contained in:
Builder.io 2025-11-08 11:03:25 +00:00
parent ba233408ed
commit e5aacf6773
25 changed files with 817 additions and 551 deletions

View file

@ -64,18 +64,17 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
if (existingLink) {
return res.status(409).json({
error: "This Discord account is already linked to another AeThex account",
error:
"This Discord account is already linked to another AeThex account",
});
}
// Create the link
const { error: linkError } = await supabase
.from("discord_links")
.insert({
discord_id: verification.discord_id,
user_id,
primary_arm: "labs", // Default to labs
});
const { error: linkError } = await supabase.from("discord_links").insert({
discord_id: verification.discord_id,
user_id,
primary_arm: "labs", // Default to labs
});
if (linkError) {
console.error("Failed to create discord link:", linkError);

View file

@ -24,7 +24,10 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
// Verify request is from Discord bot (simple verification)
const authorization = req.headers.authorization;
if (!authorization || authorization !== `Bearer ${process.env.DISCORD_BOT_TOKEN}`) {
if (
!authorization ||
authorization !== `Bearer ${process.env.DISCORD_BOT_TOKEN}`
) {
return res.status(401).json({ error: "Unauthorized" });
}

View file

@ -31,7 +31,11 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
const secret = process.env.ROBLOX_SHARED_SECRET || "";
// Verify signature for security
if (signature && secret && !verifyRobloxSignature(payload, signature, secret)) {
if (
signature &&
secret &&
!verifyRobloxSignature(payload, signature, secret)
) {
console.warn("Invalid Roblox signature");
return res.status(401).json({ error: "Invalid signature" });
}

View file

@ -13,7 +13,8 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
}
try {
const { session_token, game } = req.method === "POST" ? req.body : req.query;
const { session_token, game } =
req.method === "POST" ? req.body : req.query;
if (!session_token) {
return res.status(400).json({ error: "session_token is required" });
@ -22,7 +23,9 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
// Find the session
const { data: sessionData, error: sessionError } = await supabase
.from("game_sessions")
.select("*, user_profiles!inner(id, username, email, full_name, metadata)")
.select(
"*, user_profiles!inner(id, username, email, full_name, metadata)",
)
.eq("session_token", String(session_token))
.single();
@ -38,7 +41,9 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
// Optional: Verify game matches if provided
if (game && sessionData.game !== String(game).toLowerCase()) {
return res.status(403).json({ error: "Token is not valid for this game" });
return res
.status(403)
.json({ error: "Token is not valid for this game" });
}
// Update last activity

View file

@ -31,7 +31,8 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
const clientId = process.env.ROBLOX_OAUTH_CLIENT_ID;
const clientSecret = process.env.ROBLOX_OAUTH_CLIENT_SECRET;
const redirectUri =
process.env.ROBLOX_OAUTH_REDIRECT_URI || "https://aethex.dev/roblox-callback";
process.env.ROBLOX_OAUTH_REDIRECT_URI ||
"https://aethex.dev/roblox-callback";
if (!clientId || !clientSecret) {
return res.status(500).json({ error: "Roblox OAuth not configured" });
@ -59,9 +60,12 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
const tokenData: RobloxTokenResponse = await tokenResponse.json();
// Get user info with access token
const userResponse = await fetch("https://apis.roblox.com/oauth/v1/userinfo", {
headers: { Authorization: `Bearer ${tokenData.access_token}` },
});
const userResponse = await fetch(
"https://apis.roblox.com/oauth/v1/userinfo",
{
headers: { Authorization: `Bearer ${tokenData.access_token}` },
},
);
if (!userResponse.ok) {
console.error("Failed to fetch Roblox user info");

View file

@ -21,7 +21,8 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
const token = authHeader.substring(7);
// Verify token with Supabase
const { data: userData, error: authError } = await supabase.auth.getUser(token);
const { data: userData, error: authError } =
await supabase.auth.getUser(token);
if (authError || !userData.user) {
return res.status(401).json({ error: "Invalid token" });
}

View file

@ -22,7 +22,8 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
const token = authHeader.substring(7);
// Verify token with Supabase
const { data: userData, error: authError } = await supabase.auth.getUser(token);
const { data: userData, error: authError } =
await supabase.auth.getUser(token);
if (authError || !userData.user) {
return res.status(401).json({ error: "Invalid token" });
}
@ -66,15 +67,16 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
.eq("nonce", nonce);
// Link wallet to existing user
const { error: linkError } = await supabase
.from("web3_wallets")
.insert({
user_id: userData.user.id,
wallet_address: normalizedAddress,
chain_id: 1, // Ethereum mainnet
});
const { error: linkError } = await supabase.from("web3_wallets").insert({
user_id: userData.user.id,
wallet_address: normalizedAddress,
chain_id: 1, // Ethereum mainnet
});
if (linkError && !linkError.message.includes("violates unique constraint")) {
if (
linkError &&
!linkError.message.includes("violates unique constraint")
) {
console.error("Failed to link wallet:", linkError);
return res.status(500).json({ error: "Failed to link wallet" });
}

View file

@ -80,15 +80,16 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
const username = normalizedAddress.substring(2, 10);
// Create Supabase auth user
const { data: authData, error: authError } = await supabase.auth.admin.createUser({
email,
password: require("crypto").randomBytes(32).toString("hex"),
email_confirm: true,
user_metadata: {
wallet_address: normalizedAddress,
auth_method: "web3",
},
});
const { data: authData, error: authError } =
await supabase.auth.admin.createUser({
email,
password: require("crypto").randomBytes(32).toString("hex"),
email_confirm: true,
user_metadata: {
wallet_address: normalizedAddress,
auth_method: "web3",
},
});
if (authError || !authData.user) {
console.error("Failed to create auth user:", authError);

View file

@ -119,232 +119,247 @@ const App = () => (
<Web3Provider>
<DiscordProvider>
<TooltipProvider>
<Toaster />
<Analytics />
<BrowserRouter>
<SkipAgentController />
<PageTransition>
<Routes>
<Route path="/" element={<Index />} />
<Route path="/onboarding" element={<Onboarding />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/realms" element={<Realms />} />
<Route path="/investors" element={<Investors />} />
<Route path="/roadmap" element={<Roadmap />} />
<Route path="/trust" element={<Trust />} />
<Route path="/press" element={<PressKit />} />
<Route path="/projects" element={<Projects />} />
<Route path="/projects/admin" element={<ProjectsAdmin />} />
<Route path="/directory" element={<Directory />} />
<Route path="/admin" element={<Admin />} />
<Route path="/admin/docs-sync" element={<DocsSync />} />
<Route path="/feed" element={<Feed />} />
<Route path="/teams" element={<Teams />} />
<Route path="/squads" element={<Squads />} />
<Route path="/mentee-hub" element={<MenteeHub />} />
<Route path="/projects/new" element={<ProjectsNew />} />
<Route
path="/projects/:projectId/board"
element={<ProjectBoard />}
/>
<Route path="/profile" element={<Profile />} />
<Route path="/profile/me" element={<Profile />} />
<Route
path="/profile/applications"
element={<MyApplications />}
/>
<Route path="/developers" element={<DevelopersDirectory />} />
<Route
path="/developers/me"
element={<LegacyPassportRedirect />}
/>
<Route
path="/developers/:id"
element={<LegacyPassportRedirect />}
/>
<Route
path="/profiles"
element={<Navigate to="/developers" replace />}
/>
<Route
path="/profiles/me"
element={<LegacyPassportRedirect />}
/>
<Route
path="/profiles/:id"
element={<LegacyPassportRedirect />}
/>
<Route
path="/passport"
element={<Navigate to="/passport/me" replace />}
/>
<Route path="/passport/me" element={<ProfilePassport />} />
<Route
path="/passport/:username"
element={<ProfilePassport />}
/>
<Route path="/login" element={<Login />} />
<Route path="/signup" element={<SignupRedirect />} />
<Route path="/reset-password" element={<ResetPassword />} />
<Route path="/roblox-callback" element={<RobloxCallback />} />
<Route path="/web3-callback" element={<Web3Callback />} />
<Route path="/discord-verify" element={<DiscordVerify />} />
{/* Creator Network routes */}
<Route path="/creators" element={<CreatorDirectory />} />
<Route
path="/creators/:username"
element={<CreatorProfile />}
/>
<Route path="/opportunities" element={<OpportunitiesHub />} />
<Route
path="/opportunities/:id"
element={<OpportunityDetail />}
/>
{/* Service routes */}
<Route path="/game-development" element={<GameDevelopment />} />
<Route path="/consulting" element={<DevelopmentConsulting />} />
<Route path="/mentorship" element={<MentorshipPrograms />} />
<Route path="/engage" element={<Engage />} />
<Route
path="/pricing"
element={<Navigate to="/engage" replace />}
/>
<Route path="/research" element={<ResearchLabs />} />
{/* New Arm Landing Pages */}
<Route path="/labs" element={<Labs />} />
<Route
path="/labs/explore-research"
element={<LabsExploreResearch />}
/>
<Route path="/labs/join-team" element={<LabsJoinTeam />} />
<Route
path="/labs/get-involved"
element={<LabsGetInvolved />}
/>
<Route path="/gameforge" element={<GameForge />} />
<Route
path="/gameforge/start-building"
element={<GameForgeStartBuilding />}
/>
<Route
path="/gameforge/view-portfolio"
element={<GameForgeViewPortfolio />}
/>
<Route
path="/gameforge/join-gameforge"
element={<GameForgeJoinGameForge />}
/>
<Route path="/corp" element={<Corp />} />
<Route
path="/corp/schedule-consultation"
element={<CorpScheduleConsultation />}
/>
<Route
path="/corp/view-case-studies"
element={<CorpViewCaseStudies />}
/>
<Route path="/corp/contact-us" element={<CorpContactUs />} />
<Route path="/foundation" element={<Foundation />} />
<Route
path="/foundation/contribute"
element={<FoundationContribute />}
/>
<Route
path="/foundation/learn-more"
element={<FoundationLearnMore />}
/>
<Route
path="/foundation/get-involved"
element={<FoundationGetInvolved />}
/>
{/* Dev-Link routes */}
<Route path="/dev-link" element={<DevLink />} />
<Route
path="/dev-link/waitlist"
element={<DevLinkProfiles />}
/>
{/* Nexus routes */}
<Route path="/nexus" element={<Nexus />} />
{/* Resource routes */}
<Route path="/docs" element={<DocsLayout />}>
<Route index element={<DocsOverview />} />
<Route path="tutorials" element={<DocsTutorials />} />
<Route path="curriculum" element={<DocsCurriculum />} />
<Toaster />
<Analytics />
<BrowserRouter>
<SkipAgentController />
<PageTransition>
<Routes>
<Route path="/" element={<Index />} />
<Route path="/onboarding" element={<Onboarding />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/realms" element={<Realms />} />
<Route path="/investors" element={<Investors />} />
<Route path="/roadmap" element={<Roadmap />} />
<Route path="/trust" element={<Trust />} />
<Route path="/press" element={<PressKit />} />
<Route path="/projects" element={<Projects />} />
<Route path="/projects/admin" element={<ProjectsAdmin />} />
<Route path="/directory" element={<Directory />} />
<Route path="/admin" element={<Admin />} />
<Route path="/admin/docs-sync" element={<DocsSync />} />
<Route path="/feed" element={<Feed />} />
<Route path="/teams" element={<Teams />} />
<Route path="/squads" element={<Squads />} />
<Route path="/mentee-hub" element={<MenteeHub />} />
<Route path="/projects/new" element={<ProjectsNew />} />
<Route
path="getting-started"
element={<DocsGettingStarted />}
path="/projects/:projectId/board"
element={<ProjectBoard />}
/>
<Route path="/profile" element={<Profile />} />
<Route path="/profile/me" element={<Profile />} />
<Route
path="/profile/applications"
element={<MyApplications />}
/>
<Route path="platform" element={<DocsPlatform />} />
<Route path="api" element={<DocsApiReference />} />
<Route path="cli" element={<DocsCli />} />
<Route path="examples" element={<DocsExamples />} />
<Route path="integrations" element={<DocsIntegrations />} />
</Route>
<Route path="/tutorials" element={<Tutorials />} />
<Route path="/blog" element={<Blog />} />
<Route path="/blog/:slug" element={<BlogPost />} />
<Route path="/community" element={<Community />} />
<Route path="/community/teams" element={<FoundationTeams />} />
<Route path="/community/about" element={<FoundationAbout />} />
<Route
path="/community/mentorship"
element={<MentorshipRequest />}
/>
<Route
path="/community/mentorship/apply"
element={<MentorApply />}
/>
<Route
path="/community/mentor/:username"
element={<MentorProfile />}
/>
<Route path="/community/:tabId" element={<Community />} />
<Route path="/staff" element={<Staff />} />
<Route path="/support" element={<Support />} />
<Route path="/status" element={<Status />} />
<Route path="/changelog" element={<Changelog />} />
{/* Informational routes */}
<Route path="/wix" element={<Wix />} />
<Route path="/wix/case-studies" element={<WixCaseStudies />} />
<Route path="/wix/faq" element={<WixFaq />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
<Route path="/get-started" element={<GetStarted />} />
<Route path="/explore" element={<Explore />} />
<Route path="/services" element={<Services />} />
<Route path="/careers" element={<Careers />} />
<Route path="/developers" element={<DevelopersDirectory />} />
<Route
path="/developers/me"
element={<LegacyPassportRedirect />}
/>
<Route
path="/developers/:id"
element={<LegacyPassportRedirect />}
/>
<Route
path="/profiles"
element={<Navigate to="/developers" replace />}
/>
<Route
path="/profiles/me"
element={<LegacyPassportRedirect />}
/>
<Route
path="/profiles/:id"
element={<LegacyPassportRedirect />}
/>
{/* Legal routes */}
<Route path="/privacy" element={<Privacy />} />
<Route path="/terms" element={<Terms />} />
<Route
path="/passport"
element={<Navigate to="/passport/me" replace />}
/>
<Route path="/passport/me" element={<ProfilePassport />} />
<Route
path="/passport/:username"
element={<ProfilePassport />}
/>
<Route path="/login" element={<Login />} />
<Route path="/signup" element={<SignupRedirect />} />
<Route path="/reset-password" element={<ResetPassword />} />
<Route path="/roblox-callback" element={<RobloxCallback />} />
<Route path="/web3-callback" element={<Web3Callback />} />
<Route path="/discord-verify" element={<DiscordVerify />} />
{/* Discord routes */}
<Route path="/discord" element={<DiscordActivity />} />
<Route
path="/discord/callback"
element={<DiscordOAuthCallback />}
/>
{/* Creator Network routes */}
<Route path="/creators" element={<CreatorDirectory />} />
<Route
path="/creators/:username"
element={<CreatorProfile />}
/>
<Route path="/opportunities" element={<OpportunitiesHub />} />
<Route
path="/opportunities/:id"
element={<OpportunityDetail />}
/>
{/* Explicit 404 route for static hosting fallbacks */}
<Route path="/404" element={<FourOhFourPage />} />
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
<Route path="*" element={<FourOhFourPage />} />
</Routes>
</PageTransition>
</BrowserRouter>
</TooltipProvider>
{/* Service routes */}
<Route
path="/game-development"
element={<GameDevelopment />}
/>
<Route
path="/consulting"
element={<DevelopmentConsulting />}
/>
<Route path="/mentorship" element={<MentorshipPrograms />} />
<Route path="/engage" element={<Engage />} />
<Route
path="/pricing"
element={<Navigate to="/engage" replace />}
/>
<Route path="/research" element={<ResearchLabs />} />
{/* New Arm Landing Pages */}
<Route path="/labs" element={<Labs />} />
<Route
path="/labs/explore-research"
element={<LabsExploreResearch />}
/>
<Route path="/labs/join-team" element={<LabsJoinTeam />} />
<Route
path="/labs/get-involved"
element={<LabsGetInvolved />}
/>
<Route path="/gameforge" element={<GameForge />} />
<Route
path="/gameforge/start-building"
element={<GameForgeStartBuilding />}
/>
<Route
path="/gameforge/view-portfolio"
element={<GameForgeViewPortfolio />}
/>
<Route
path="/gameforge/join-gameforge"
element={<GameForgeJoinGameForge />}
/>
<Route path="/corp" element={<Corp />} />
<Route
path="/corp/schedule-consultation"
element={<CorpScheduleConsultation />}
/>
<Route
path="/corp/view-case-studies"
element={<CorpViewCaseStudies />}
/>
<Route path="/corp/contact-us" element={<CorpContactUs />} />
<Route path="/foundation" element={<Foundation />} />
<Route
path="/foundation/contribute"
element={<FoundationContribute />}
/>
<Route
path="/foundation/learn-more"
element={<FoundationLearnMore />}
/>
<Route
path="/foundation/get-involved"
element={<FoundationGetInvolved />}
/>
{/* Dev-Link routes */}
<Route path="/dev-link" element={<DevLink />} />
<Route
path="/dev-link/waitlist"
element={<DevLinkProfiles />}
/>
{/* Nexus routes */}
<Route path="/nexus" element={<Nexus />} />
{/* Resource routes */}
<Route path="/docs" element={<DocsLayout />}>
<Route index element={<DocsOverview />} />
<Route path="tutorials" element={<DocsTutorials />} />
<Route path="curriculum" element={<DocsCurriculum />} />
<Route
path="getting-started"
element={<DocsGettingStarted />}
/>
<Route path="platform" element={<DocsPlatform />} />
<Route path="api" element={<DocsApiReference />} />
<Route path="cli" element={<DocsCli />} />
<Route path="examples" element={<DocsExamples />} />
<Route path="integrations" element={<DocsIntegrations />} />
</Route>
<Route path="/tutorials" element={<Tutorials />} />
<Route path="/blog" element={<Blog />} />
<Route path="/blog/:slug" element={<BlogPost />} />
<Route path="/community" element={<Community />} />
<Route
path="/community/teams"
element={<FoundationTeams />}
/>
<Route
path="/community/about"
element={<FoundationAbout />}
/>
<Route
path="/community/mentorship"
element={<MentorshipRequest />}
/>
<Route
path="/community/mentorship/apply"
element={<MentorApply />}
/>
<Route
path="/community/mentor/:username"
element={<MentorProfile />}
/>
<Route path="/community/:tabId" element={<Community />} />
<Route path="/staff" element={<Staff />} />
<Route path="/support" element={<Support />} />
<Route path="/status" element={<Status />} />
<Route path="/changelog" element={<Changelog />} />
{/* Informational routes */}
<Route path="/wix" element={<Wix />} />
<Route
path="/wix/case-studies"
element={<WixCaseStudies />}
/>
<Route path="/wix/faq" element={<WixFaq />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
<Route path="/get-started" element={<GetStarted />} />
<Route path="/explore" element={<Explore />} />
<Route path="/services" element={<Services />} />
<Route path="/careers" element={<Careers />} />
{/* Legal routes */}
<Route path="/privacy" element={<Privacy />} />
<Route path="/terms" element={<Terms />} />
{/* Discord routes */}
<Route path="/discord" element={<DiscordActivity />} />
<Route
path="/discord/callback"
element={<DiscordOAuthCallback />}
/>
{/* Explicit 404 route for static hosting fallbacks */}
<Route path="/404" element={<FourOhFourPage />} />
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
<Route path="*" element={<FourOhFourPage />} />
</Routes>
</PageTransition>
</BrowserRouter>
</TooltipProvider>
</DiscordProvider>
</Web3Provider>
</AuthProvider>

View file

@ -1,4 +1,10 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { CheckCircle2 } from "lucide-react";
@ -12,7 +18,8 @@ const REALMS = [
{
id: "labs",
title: "🧪 Labs",
description: "Research & Development - Cutting-edge innovation and experimentation",
description:
"Research & Development - Cutting-edge innovation and experimentation",
color: "from-yellow-500/20 to-yellow-600/20",
borderColor: "border-yellow-400",
},
@ -46,18 +53,25 @@ const REALMS = [
},
];
export default function RealmSelection({ selectedRealm, onSelect, onNext }: RealmSelectionProps) {
export default function RealmSelection({
selectedRealm,
onSelect,
onNext,
}: RealmSelectionProps) {
return (
<div className="space-y-6">
<div>
<h2 className="text-2xl font-bold text-white mb-2">Choose Your Primary Realm</h2>
<h2 className="text-2xl font-bold text-white mb-2">
Choose Your Primary Realm
</h2>
<p className="text-gray-400">
Select the AeThex realm that best matches your primary focus. You can always change this later.
Select the AeThex realm that best matches your primary focus. You can
always change this later.
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{REALMS.map(realm => (
{REALMS.map((realm) => (
<div
key={realm.id}
onClick={() => onSelect(realm.id)}
@ -67,7 +81,9 @@ export default function RealmSelection({ selectedRealm, onSelect, onNext }: Real
>
<Card
className={`h-full border-2 ${realm.borderColor} ${
selectedRealm === realm.id ? "ring-2 ring-offset-2 ring-white" : ""
selectedRealm === realm.id
? "ring-2 ring-offset-2 ring-white"
: ""
} hover:shadow-lg transition-shadow`}
>
<CardHeader>
@ -81,7 +97,9 @@ export default function RealmSelection({ selectedRealm, onSelect, onNext }: Real
</div>
</CardHeader>
<CardContent>
<CardDescription className="text-sm">{realm.description}</CardDescription>
<CardDescription className="text-sm">
{realm.description}
</CardDescription>
</CardContent>
</Card>
</div>

View file

@ -836,8 +836,14 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
window.localStorage.removeItem("onboarding_complete");
window.localStorage.removeItem("aethex_onboarding_progress_v1");
Object.keys(window.localStorage)
.filter(key => key.startsWith("sb-") || key.includes("supabase") || key.startsWith("mock_") || key.startsWith("demo_"))
.forEach(key => window.localStorage.removeItem(key));
.filter(
(key) =>
key.startsWith("sb-") ||
key.includes("supabase") ||
key.startsWith("mock_") ||
key.startsWith("demo_"),
)
.forEach((key) => window.localStorage.removeItem(key));
console.log("localStorage cleared");
} catch (e) {
console.warn("localStorage clear failed:", e);

View file

@ -1,4 +1,10 @@
import React, { createContext, useContext, useState, useCallback, useEffect } from "react";
import React, {
createContext,
useContext,
useState,
useCallback,
useEffect,
} from "react";
import { aethexToast } from "@/lib/aethex-toast";
export interface Web3ContextType {
@ -13,7 +19,9 @@ export interface Web3ContextType {
const Web3Context = createContext<Web3ContextType | undefined>(undefined);
export const Web3Provider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
export const Web3Provider: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const [account, setAccount] = useState<string | null>(null);
const [isConnected, setIsConnected] = useState(false);
const [isConnecting, setIsConnecting] = useState(false);
@ -100,7 +108,11 @@ export const Web3Provider: React.FC<{ children: React.ReactNode }> = ({ children
const signMessage = useCallback(
async (message: string): Promise<string> => {
if (!account || typeof window === "undefined" || !(window as any).ethereum) {
if (
!account ||
typeof window === "undefined" ||
!(window as any).ethereum
) {
throw new Error("Wallet not connected");
}
@ -144,8 +156,14 @@ export const Web3Provider: React.FC<{ children: React.ReactNode }> = ({ children
(window as any).ethereum.on("chainChanged", handleChainChanged);
return () => {
(window as any).ethereum?.removeListener("accountsChanged", handleAccountsChanged);
(window as any).ethereum?.removeListener("chainChanged", handleChainChanged);
(window as any).ethereum?.removeListener(
"accountsChanged",
handleAccountsChanged,
);
(window as any).ethereum?.removeListener(
"chainChanged",
handleChainChanged,
);
};
}, []);

View file

@ -1513,27 +1513,59 @@ export default function Admin() {
<CardContent className="space-y-6">
<div className="grid gap-4">
<div className="bg-background/40 rounded-lg p-4 border border-border/40">
<h3 className="font-semibold mb-4">Realm to Discord Role Mappings</h3>
<h3 className="font-semibold mb-4">
Realm to Discord Role Mappings
</h3>
<div className="space-y-2">
<p className="text-sm text-gray-400">
Configure which Discord roles are assigned for each AeThex realm and user type.
Configure which Discord roles are assigned for each
AeThex realm and user type.
</p>
<div className="mt-4 space-y-3">
{[
{ realm: "Labs", role: "Labs Creator", members: 234 },
{ realm: "GameForge", role: "GameForge Creator", members: 456 },
{ realm: "Corp", role: "Corp Member", members: 89 },
{ realm: "Foundation", role: "Foundation Member", members: 145 },
{ realm: "Dev-Link", role: "Dev-Link Member", members: 78 },
{
realm: "Labs",
role: "Labs Creator",
members: 234,
},
{
realm: "GameForge",
role: "GameForge Creator",
members: 456,
},
{
realm: "Corp",
role: "Corp Member",
members: 89,
},
{
realm: "Foundation",
role: "Foundation Member",
members: 145,
},
{
realm: "Dev-Link",
role: "Dev-Link Member",
members: 78,
},
].map((mapping, idx) => (
<div key={idx} className="flex items-center justify-between p-3 bg-background/30 rounded border border-border/20">
<div
key={idx}
className="flex items-center justify-between p-3 bg-background/30 rounded border border-border/20"
>
<div>
<p className="font-medium">{mapping.realm}</p>
<p className="text-sm text-gray-400">{mapping.role}</p>
<p className="text-sm text-gray-400">
{mapping.role}
</p>
</div>
<div className="text-right">
<p className="text-lg font-bold text-purple-400">{mapping.members}</p>
<p className="text-xs text-gray-500">assigned members</p>
<p className="text-lg font-bold text-purple-400">
{mapping.members}
</p>
<p className="text-xs text-gray-500">
assigned members
</p>
</div>
</div>
))}
@ -1542,19 +1574,31 @@ export default function Admin() {
</div>
<div className="bg-background/40 rounded-lg p-4 border border-border/40">
<h3 className="font-semibold mb-3">Bot Configuration</h3>
<h3 className="font-semibold mb-3">
Bot Configuration
</h3>
<div className="space-y-2 text-sm">
<div className="flex items-center justify-between">
<span className="text-gray-400">Bot Status</span>
<Badge variant="default" className="bg-green-500">Online</Badge>
<Badge variant="default" className="bg-green-500">
Online
</Badge>
</div>
<div className="flex items-center justify-between">
<span className="text-gray-400">Servers Connected</span>
<span className="font-mono text-purple-400">12 servers</span>
<span className="text-gray-400">
Servers Connected
</span>
<span className="font-mono text-purple-400">
12 servers
</span>
</div>
<div className="flex items-center justify-between">
<span className="text-gray-400">Linked Accounts</span>
<span className="font-mono text-purple-400">1,234 users</span>
<span className="text-gray-400">
Linked Accounts
</span>
<span className="font-mono text-purple-400">
1,234 users
</span>
</div>
</div>
</div>

View file

@ -55,7 +55,8 @@ export default function DiscordVerify() {
toastSuccess({
title: "Discord Linked!",
description: "Your Discord account has been successfully linked to AeThex",
description:
"Your Discord account has been successfully linked to AeThex",
});
// Redirect to profile settings

View file

@ -514,7 +514,10 @@ export default function Onboarding() {
selectedRealm={data.creatorProfile.primaryArm || ""}
onSelect={(realm) =>
updateData({
creatorProfile: { ...data.creatorProfile, primaryArm: realm },
creatorProfile: {
...data.creatorProfile,
primaryArm: realm,
},
})
}
onNext={nextStep}

View file

@ -47,7 +47,8 @@ export default function RobloxCallback() {
const errorData = await response.json();
toastError({
title: "Authentication failed",
description: errorData.error || "Could not authenticate with Roblox",
description:
errorData.error || "Could not authenticate with Roblox",
});
navigate("/login");
return;

View file

@ -60,7 +60,8 @@ export default function Web3Callback() {
if (user && data.success) {
toastSuccess({
title: "Wallet linked",
description: "Your Ethereum wallet is now connected to your account",
description:
"Your Ethereum wallet is now connected to your account",
});
navigate("/dashboard?tab=connections");
return;
@ -93,11 +94,21 @@ export default function Web3Callback() {
if (account && !authLoading) {
handleWeb3Auth();
}
}, [account, authLoading, signMessage, user, navigate, toastError, toastSuccess]);
}, [
account,
authLoading,
signMessage,
user,
navigate,
toastError,
toastSuccess,
]);
return (
<LoadingScreen
message={isProcessing ? "Verifying your wallet..." : "Connecting wallet..."}
message={
isProcessing ? "Verifying your wallet..." : "Connecting wallet..."
}
showProgress={true}
duration={5000}
/>

View file

@ -12,6 +12,7 @@
### Step 1: Prepare the Bot Directory
Ensure all bot files are committed:
```
code/discord-bot/
├── bot.js
@ -53,16 +54,19 @@ NODE_ENV=production
In Spaceship Application Settings:
**Build Command:**
```bash
npm install
```
**Start Command:**
```bash
npm start
```
**Root Directory:**
```
code/discord-bot
```
@ -80,6 +84,7 @@ code/discord-bot
### Step 6: Verify Bot is Online
Once deployed:
1. Go to your Discord server
2. Type `/verify` - the command autocomplete should appear
3. Bot should be online with status "Listening to /verify to link your AeThex account"
@ -87,6 +92,7 @@ Once deployed:
## 📡 Discord Bot Endpoints
The bot will be accessible at:
```
https://<your-spaceship-domain>/
```
@ -96,11 +102,13 @@ The bot uses Discord's WebSocket connection (not HTTP), so it doesn't need to ex
## 🔌 API Integration
Frontend calls to link Discord accounts:
- **Endpoint:** `POST /api/discord/link`
- **Body:** `{ verification_code, user_id }`
- **Response:** `{ success: true, message: "..." }`
Discord Verify page (`/discord-verify?code=XXX`) will automatically:
1. Call `/api/discord/link` with the verification code
2. Link the Discord ID to the AeThex user account
3. Redirect to dashboard on success
@ -108,22 +116,26 @@ Discord Verify page (`/discord-verify?code=XXX`) will automatically:
## 🛠️ Debugging
### Check bot logs on Spaceship:
- Application → Logs
- Filter for "bot.js" or "error"
### Common issues:
**"Discord bot not responding to commands"**
- Check: `DISCORD_BOT_TOKEN` is correct
- Check: Bot is added to the Discord server with "applications.commands" scope
- Check: Spaceship logs show "✅ Logged in"
**"Supabase verification fails"**
- Check: `SUPABASE_URL` and `SUPABASE_SERVICE_ROLE` are correct
- Check: `discord_links` and `discord_verifications` tables exist
- Run migration: `code/supabase/migrations/20250107_add_discord_integration.sql`
**"Slash commands not appearing in Discord"**
- Check: Logs show "✅ Successfully registered X slash commands"
- Discord may need 1-2 minutes to sync commands
- Try typing `/` in Discord to force refresh
@ -132,12 +144,14 @@ Discord Verify page (`/discord-verify?code=XXX`) will automatically:
## 📊 Monitoring
### Key metrics to monitor:
- Bot uptime (should be 24/7)
- Command usage (in Supabase)
- Verification code usage (in Supabase)
- Discord role sync success rate
### View in Admin Dashboard:
- AeThex Admin Panel → Discord Management tab
- Shows:
- Bot status
@ -156,6 +170,7 @@ Discord Verify page (`/discord-verify?code=XXX`) will automatically:
## 🆘 Support
For issues:
1. Check Spaceship logs
2. Review `/api/discord/link` endpoint response
3. Verify all environment variables are set correctly
@ -177,6 +192,7 @@ Run this migration on your AeThex Supabase:
## 🎉 You're All Set!
Once deployed, users can:
1. Click "Link Discord" in their profile settings
2. Type `/verify` in Discord
3. Click the verification link

View file

@ -1,11 +1,20 @@
const { Client, GatewayIntentBits, REST, Routes, Collection, EmbedBuilder } = require('discord.js');
const { createClient } = require('@supabase/supabase-js');
const fs = require('fs');
const path = require('path');
require('dotenv').config();
const {
Client,
GatewayIntentBits,
REST,
Routes,
Collection,
EmbedBuilder,
} = require("discord.js");
const { createClient } = require("@supabase/supabase-js");
const fs = require("fs");
const path = require("path");
require("dotenv").config();
// Initialize Discord client
const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.DirectMessages] });
const client = new Client({
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.DirectMessages],
});
// Initialize Supabase
const supabase = createClient(
@ -17,35 +26,41 @@ const supabase = createClient(
client.commands = new Collection();
// Load commands from commands directory
const commandsPath = path.join(__dirname, 'commands');
const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));
const commandsPath = path.join(__dirname, "commands");
const commandFiles = fs
.readdirSync(commandsPath)
.filter((file) => file.endsWith(".js"));
for (const file of commandFiles) {
const filePath = path.join(commandsPath, file);
const command = require(filePath);
if ('data' in command && 'execute' in command) {
if ("data" in command && "execute" in command) {
client.commands.set(command.data.name, command);
console.log(`✅ Loaded command: ${command.data.name}`);
}
}
// Bot ready event
client.once('ready', () => {
client.once("ready", () => {
console.log(`✅ Bot logged in as ${client.user.tag}`);
console.log(`📡 Listening in ${client.guilds.cache.size} server(s)`);
// Set bot status
client.user.setActivity('/verify to link your AeThex account', { type: 'LISTENING' });
client.user.setActivity("/verify to link your AeThex account", {
type: "LISTENING",
});
});
// Slash command interaction handler
client.on('interactionCreate', async interaction => {
client.on("interactionCreate", async (interaction) => {
if (!interaction.isChatInputCommand()) return;
const command = client.commands.get(interaction.commandName);
if (!command) {
console.warn(`⚠️ No command matching ${interaction.commandName} was found.`);
console.warn(
`⚠️ No command matching ${interaction.commandName} was found.`,
);
return;
}
@ -53,12 +68,12 @@ client.on('interactionCreate', async interaction => {
await command.execute(interaction, supabase, client);
} catch (error) {
console.error(`❌ Error executing ${interaction.commandName}:`, error);
const errorEmbed = new EmbedBuilder()
.setColor(0xFF0000)
.setTitle('❌ Command Error')
.setDescription('There was an error while executing this command.')
.setFooter({ text: 'Contact support if this persists' });
.setColor(0xff0000)
.setTitle("❌ Command Error")
.setDescription("There was an error while executing this command.")
.setFooter({ text: "Contact support if this persists" });
if (interaction.replied || interaction.deferred) {
await interaction.followUp({ embeds: [errorEmbed], ephemeral: true });
@ -76,7 +91,9 @@ async function registerCommands() {
commands.push(command.data.toJSON());
}
const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_BOT_TOKEN);
const rest = new REST({ version: "10" }).setToken(
process.env.DISCORD_BOT_TOKEN,
);
console.log(`📝 Registering ${commands.length} slash commands...`);
@ -87,24 +104,24 @@ async function registerCommands() {
console.log(`✅ Successfully registered ${data.length} slash commands.`);
} catch (error) {
console.error('❌ Error registering commands:', error);
console.error("❌ Error registering commands:", error);
}
}
// Login and register commands
client.login(process.env.DISCORD_BOT_TOKEN);
client.once('ready', async () => {
client.once("ready", async () => {
await registerCommands();
});
// Error handling
process.on('unhandledRejection', error => {
console.error('❌ Unhandled Promise Rejection:', error);
process.on("unhandledRejection", (error) => {
console.error("❌ Unhandled Promise Rejection:", error);
});
process.on('uncaughtException', error => {
console.error('❌ Uncaught Exception:', error);
process.on("uncaughtException", (error) => {
console.error("❌ Uncaught Exception:", error);
process.exit(1);
});

View file

@ -1,75 +1,92 @@
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
module.exports = {
data: new SlashCommandBuilder()
.setName('profile')
.setDescription('View your AeThex profile in Discord'),
.setName("profile")
.setDescription("View your AeThex profile in Discord"),
async execute(interaction, supabase) {
await interaction.deferReply({ ephemeral: true });
try {
const { data: link } = await supabase
.from('discord_links')
.select('user_id, primary_arm')
.eq('discord_id', interaction.user.id)
.from("discord_links")
.select("user_id, primary_arm")
.eq("discord_id", interaction.user.id)
.single();
if (!link) {
const embed = new EmbedBuilder()
.setColor(0xFF6B6B)
.setTitle('❌ Not Linked')
.setDescription('You must link your Discord account to AeThex first.\nUse `/verify` to get started.');
.setColor(0xff6b6b)
.setTitle("❌ Not Linked")
.setDescription(
"You must link your Discord account to AeThex first.\nUse `/verify` to get started.",
);
return await interaction.editReply({ embeds: [embed] });
}
const { data: profile } = await supabase
.from('user_profiles')
.select('*')
.eq('id', link.user_id)
.from("user_profiles")
.select("*")
.eq("id", link.user_id)
.single();
if (!profile) {
const embed = new EmbedBuilder()
.setColor(0xFF6B6B)
.setTitle('❌ Profile Not Found')
.setDescription('Your AeThex profile could not be found.');
.setColor(0xff6b6b)
.setTitle("❌ Profile Not Found")
.setDescription("Your AeThex profile could not be found.");
return await interaction.editReply({ embeds: [embed] });
}
const armEmojis = {
labs: '🧪',
gameforge: '🎮',
corp: '💼',
foundation: '🤝',
devlink: '💻',
labs: "🧪",
gameforge: "🎮",
corp: "💼",
foundation: "🤝",
devlink: "💻",
};
const embed = new EmbedBuilder()
.setColor(0x7289DA)
.setTitle(`${profile.full_name || 'AeThex User'}'s Profile`)
.setThumbnail(profile.avatar_url || 'https://aethex.dev/placeholder.svg')
.addFields(
{ name: '👤 Username', value: profile.username || 'N/A', inline: true },
{ name: `${armEmojis[link.primary_arm] || '⚔️'} Primary Realm`, value: link.primary_arm || 'Not set', inline: true },
{ name: '📊 Role', value: profile.user_type || 'community_member', inline: true },
{ name: '📝 Bio', value: profile.bio || 'No bio set', inline: false },
.setColor(0x7289da)
.setTitle(`${profile.full_name || "AeThex User"}'s Profile`)
.setThumbnail(
profile.avatar_url || "https://aethex.dev/placeholder.svg",
)
.addFields(
{ name: '🔗 Links', value: `[Visit Full Profile](https://aethex.dev/creators/${profile.username})` },
{
name: "👤 Username",
value: profile.username || "N/A",
inline: true,
},
{
name: `${armEmojis[link.primary_arm] || "⚔️"} Primary Realm`,
value: link.primary_arm || "Not set",
inline: true,
},
{
name: "📊 Role",
value: profile.user_type || "community_member",
inline: true,
},
{ name: "📝 Bio", value: profile.bio || "No bio set", inline: false },
)
.setFooter({ text: 'AeThex | Your Web3 Creator Hub' });
.addFields({
name: "🔗 Links",
value: `[Visit Full Profile](https://aethex.dev/creators/${profile.username})`,
})
.setFooter({ text: "AeThex | Your Web3 Creator Hub" });
await interaction.editReply({ embeds: [embed] });
} catch (error) {
console.error('Profile command error:', error);
console.error("Profile command error:", error);
const embed = new EmbedBuilder()
.setColor(0xFF0000)
.setTitle('❌ Error')
.setDescription('Failed to fetch profile. Please try again.');
.setColor(0xff0000)
.setTitle("❌ Error")
.setDescription("Failed to fetch profile. Please try again.");
await interaction.editReply({ embeds: [embed] });
}
},

View file

@ -1,42 +1,61 @@
const { SlashCommandBuilder, EmbedBuilder, StringSelectMenuBuilder, ActionRowBuilder } = require('discord.js');
const {
SlashCommandBuilder,
EmbedBuilder,
StringSelectMenuBuilder,
ActionRowBuilder,
} = require("discord.js");
const REALMS = [
{ value: 'labs', label: '🧪 Labs', description: 'Research & Development' },
{ value: 'gameforge', label: '🎮 GameForge', description: 'Game Development' },
{ value: 'corp', label: '💼 Corp', description: 'Enterprise Solutions' },
{ value: 'foundation', label: '🤝 Foundation', description: 'Community & Education' },
{ value: 'devlink', label: '💻 Dev-Link', description: 'Professional Networking' },
{ value: "labs", label: "🧪 Labs", description: "Research & Development" },
{
value: "gameforge",
label: "🎮 GameForge",
description: "Game Development",
},
{ value: "corp", label: "💼 Corp", description: "Enterprise Solutions" },
{
value: "foundation",
label: "🤝 Foundation",
description: "Community & Education",
},
{
value: "devlink",
label: "💻 Dev-Link",
description: "Professional Networking",
},
];
module.exports = {
data: new SlashCommandBuilder()
.setName('set-realm')
.setDescription('Set your primary AeThex realm/arm'),
.setName("set-realm")
.setDescription("Set your primary AeThex realm/arm"),
async execute(interaction, supabase) {
await interaction.deferReply({ ephemeral: true });
try {
const { data: link } = await supabase
.from('discord_links')
.select('user_id, primary_arm')
.eq('discord_id', interaction.user.id)
.from("discord_links")
.select("user_id, primary_arm")
.eq("discord_id", interaction.user.id)
.single();
if (!link) {
const embed = new EmbedBuilder()
.setColor(0xFF6B6B)
.setTitle('❌ Not Linked')
.setDescription('You must link your Discord account to AeThex first.\nUse `/verify` to get started.');
.setColor(0xff6b6b)
.setTitle("❌ Not Linked")
.setDescription(
"You must link your Discord account to AeThex first.\nUse `/verify` to get started.",
);
return await interaction.editReply({ embeds: [embed] });
}
const select = new StringSelectMenuBuilder()
.setCustomId('select_realm')
.setPlaceholder('Choose your primary realm')
.setCustomId("select_realm")
.setPlaceholder("Choose your primary realm")
.addOptions(
REALMS.map(realm => ({
REALMS.map((realm) => ({
label: realm.label,
description: realm.description,
value: realm.value,
@ -47,48 +66,60 @@ module.exports = {
const row = new ActionRowBuilder().addComponents(select);
const embed = new EmbedBuilder()
.setColor(0x7289DA)
.setTitle('⚔️ Choose Your Realm')
.setDescription('Select your primary AeThex realm. This determines your main Discord role.')
.addFields(
{ name: 'Current Realm', value: link.primary_arm || 'Not set' },
);
.setColor(0x7289da)
.setTitle("⚔️ Choose Your Realm")
.setDescription(
"Select your primary AeThex realm. This determines your main Discord role.",
)
.addFields({
name: "Current Realm",
value: link.primary_arm || "Not set",
});
await interaction.editReply({ embeds: [embed], components: [row] });
const filter = i => i.user.id === interaction.user.id && i.customId === 'select_realm';
const collector = interaction.channel.createMessageComponentCollector({ filter, time: 60000 });
const filter = (i) =>
i.user.id === interaction.user.id && i.customId === "select_realm";
const collector = interaction.channel.createMessageComponentCollector({
filter,
time: 60000,
});
collector.on('collect', async i => {
collector.on("collect", async (i) => {
const selectedRealm = i.values[0];
await supabase
.from('discord_links')
.from("discord_links")
.update({ primary_arm: selectedRealm })
.eq('discord_id', interaction.user.id);
.eq("discord_id", interaction.user.id);
const realm = REALMS.find(r => r.value === selectedRealm);
const realm = REALMS.find((r) => r.value === selectedRealm);
const confirmEmbed = new EmbedBuilder()
.setColor(0x00FF00)
.setTitle('✅ Realm Set')
.setDescription(`Your primary realm is now **${realm.label}**\n\nYou'll be assigned the corresponding Discord role.`);
.setColor(0x00ff00)
.setTitle("✅ Realm Set")
.setDescription(
`Your primary realm is now **${realm.label}**\n\nYou'll be assigned the corresponding Discord role.`,
);
await i.update({ embeds: [confirmEmbed], components: [] });
});
collector.on('end', collected => {
collector.on("end", (collected) => {
if (collected.size === 0) {
interaction.editReply({ content: 'Realm selection timed out.', components: [] });
interaction.editReply({
content: "Realm selection timed out.",
components: [],
});
}
});
} catch (error) {
console.error('Set-realm command error:', error);
console.error("Set-realm command error:", error);
const embed = new EmbedBuilder()
.setColor(0xFF0000)
.setTitle('❌ Error')
.setDescription('Failed to update realm. Please try again.');
.setColor(0xff0000)
.setTitle("❌ Error")
.setDescription("Failed to update realm. Please try again.");
await interaction.editReply({ embeds: [embed] });
}
},

View file

@ -1,48 +1,49 @@
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
module.exports = {
data: new SlashCommandBuilder()
.setName('unlink')
.setDescription('Unlink your Discord account from AeThex'),
.setName("unlink")
.setDescription("Unlink your Discord account from AeThex"),
async execute(interaction, supabase) {
await interaction.deferReply({ ephemeral: true });
try {
const { data: link } = await supabase
.from('discord_links')
.select('*')
.eq('discord_id', interaction.user.id)
.from("discord_links")
.select("*")
.eq("discord_id", interaction.user.id)
.single();
if (!link) {
const embed = new EmbedBuilder()
.setColor(0xFF6B6B)
.setTitle(' Not Linked')
.setDescription('Your Discord account is not linked to AeThex.');
.setColor(0xff6b6b)
.setTitle(" Not Linked")
.setDescription("Your Discord account is not linked to AeThex.");
return await interaction.editReply({ embeds: [embed] });
}
// Delete the link
await supabase
.from('discord_links')
.from("discord_links")
.delete()
.eq('discord_id', interaction.user.id);
.eq("discord_id", interaction.user.id);
// Remove Discord roles from user
const guild = interaction.guild;
const member = await guild.members.fetch(interaction.user.id);
// Find and remove all AeThex-related roles
const rolesToRemove = member.roles.cache.filter(role =>
role.name.includes('Labs') ||
role.name.includes('GameForge') ||
role.name.includes('Corp') ||
role.name.includes('Foundation') ||
role.name.includes('Dev-Link') ||
role.name.includes('Premium') ||
role.name.includes('Creator')
const rolesToRemove = member.roles.cache.filter(
(role) =>
role.name.includes("Labs") ||
role.name.includes("GameForge") ||
role.name.includes("Corp") ||
role.name.includes("Foundation") ||
role.name.includes("Dev-Link") ||
role.name.includes("Premium") ||
role.name.includes("Creator"),
);
for (const [, role] of rolesToRemove) {
@ -54,18 +55,20 @@ module.exports = {
}
const embed = new EmbedBuilder()
.setColor(0x00FF00)
.setTitle('✅ Account Unlinked')
.setDescription('Your Discord account has been unlinked from AeThex.\nAll associated roles have been removed.');
.setColor(0x00ff00)
.setTitle("✅ Account Unlinked")
.setDescription(
"Your Discord account has been unlinked from AeThex.\nAll associated roles have been removed.",
);
await interaction.editReply({ embeds: [embed] });
} catch (error) {
console.error('Unlink command error:', error);
console.error("Unlink command error:", error);
const embed = new EmbedBuilder()
.setColor(0xFF0000)
.setTitle('❌ Error')
.setDescription('Failed to unlink account. Please try again.');
.setColor(0xff0000)
.setTitle("❌ Error")
.setDescription("Failed to unlink account. Please try again.");
await interaction.editReply({ embeds: [embed] });
}
},

View file

@ -1,71 +1,96 @@
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
module.exports = {
data: new SlashCommandBuilder()
.setName('verify-role')
.setDescription('Check your AeThex-assigned Discord roles'),
.setName("verify-role")
.setDescription("Check your AeThex-assigned Discord roles"),
async execute(interaction, supabase) {
await interaction.deferReply({ ephemeral: true });
try {
const { data: link } = await supabase
.from('discord_links')
.select('user_id, primary_arm')
.eq('discord_id', interaction.user.id)
.from("discord_links")
.select("user_id, primary_arm")
.eq("discord_id", interaction.user.id)
.single();
if (!link) {
const embed = new EmbedBuilder()
.setColor(0xFF6B6B)
.setTitle('❌ Not Linked')
.setDescription('You must link your Discord account to AeThex first.\nUse `/verify` to get started.');
.setColor(0xff6b6b)
.setTitle("❌ Not Linked")
.setDescription(
"You must link your Discord account to AeThex first.\nUse `/verify` to get started.",
);
return await interaction.editReply({ embeds: [embed] });
}
const { data: profile } = await supabase
.from('user_profiles')
.select('user_type')
.eq('id', link.user_id)
.from("user_profiles")
.select("user_type")
.eq("id", link.user_id)
.single();
const { data: mappings } = await supabase
.from('discord_role_mappings')
.select('discord_role')
.eq('arm', link.primary_arm)
.eq('user_type', profile?.user_type || 'community_member');
.from("discord_role_mappings")
.select("discord_role")
.eq("arm", link.primary_arm)
.eq("user_type", profile?.user_type || "community_member");
const member = await interaction.guild.members.fetch(interaction.user.id);
const aethexRoles = member.roles.cache.filter(role =>
role.name.includes('Labs') ||
role.name.includes('GameForge') ||
role.name.includes('Corp') ||
role.name.includes('Foundation') ||
role.name.includes('Dev-Link') ||
role.name.includes('Premium') ||
role.name.includes('Creator')
const aethexRoles = member.roles.cache.filter(
(role) =>
role.name.includes("Labs") ||
role.name.includes("GameForge") ||
role.name.includes("Corp") ||
role.name.includes("Foundation") ||
role.name.includes("Dev-Link") ||
role.name.includes("Premium") ||
role.name.includes("Creator"),
);
const embed = new EmbedBuilder()
.setColor(0x7289DA)
.setTitle('🔐 Your AeThex Roles')
.setColor(0x7289da)
.setTitle("🔐 Your AeThex Roles")
.addFields(
{ name: '⚔️ Primary Realm', value: link.primary_arm || 'Not set', inline: true },
{ name: '👤 User Type', value: profile?.user_type || 'community_member', inline: true },
{ name: '🎭 Discord Roles', value: aethexRoles.size > 0 ? aethexRoles.map(r => r.name).join(', ') : 'None assigned yet' },
{ name: '📋 Expected Roles', value: mappings?.length > 0 ? mappings.map(m => m.discord_role).join(', ') : 'No mappings found' },
{
name: "⚔️ Primary Realm",
value: link.primary_arm || "Not set",
inline: true,
},
{
name: "👤 User Type",
value: profile?.user_type || "community_member",
inline: true,
},
{
name: "🎭 Discord Roles",
value:
aethexRoles.size > 0
? aethexRoles.map((r) => r.name).join(", ")
: "None assigned yet",
},
{
name: "📋 Expected Roles",
value:
mappings?.length > 0
? mappings.map((m) => m.discord_role).join(", ")
: "No mappings found",
},
)
.setFooter({ text: 'Roles are assigned automatically based on your AeThex profile' });
.setFooter({
text: "Roles are assigned automatically based on your AeThex profile",
});
await interaction.editReply({ embeds: [embed] });
} catch (error) {
console.error('Verify-role command error:', error);
console.error("Verify-role command error:", error);
const embed = new EmbedBuilder()
.setColor(0xFF0000)
.setTitle('❌ Error')
.setDescription('Failed to verify roles. Please try again.');
.setColor(0xff0000)
.setTitle("❌ Error")
.setDescription("Failed to verify roles. Please try again.");
await interaction.editReply({ embeds: [embed] });
}
},

View file

@ -1,35 +1,46 @@
const { SlashCommandBuilder, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js');
const {
SlashCommandBuilder,
EmbedBuilder,
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
} = require("discord.js");
module.exports = {
data: new SlashCommandBuilder()
.setName('verify')
.setDescription('Link your Discord account to your AeThex account'),
.setName("verify")
.setDescription("Link your Discord account to your AeThex account"),
async execute(interaction, supabase) {
await interaction.deferReply({ ephemeral: true });
try {
const { data: existingLink } = await supabase
.from('discord_links')
.select('*')
.eq('discord_id', interaction.user.id)
.from("discord_links")
.select("*")
.eq("discord_id", interaction.user.id)
.single();
if (existingLink) {
const embed = new EmbedBuilder()
.setColor(0x00FF00)
.setTitle('✅ Already Linked')
.setDescription(`Your Discord account is already linked to AeThex (User ID: ${existingLink.user_id})`);
.setColor(0x00ff00)
.setTitle("✅ Already Linked")
.setDescription(
`Your Discord account is already linked to AeThex (User ID: ${existingLink.user_id})`,
);
return await interaction.editReply({ embeds: [embed] });
}
// Generate verification code
const verificationCode = Math.random().toString(36).substring(2, 8).toUpperCase();
const verificationCode = Math.random()
.toString(36)
.substring(2, 8)
.toUpperCase();
const expiresAt = new Date(Date.now() + 15 * 60 * 1000); // 15 minutes
// Store verification code
await supabase.from('discord_verifications').insert({
await supabase.from("discord_verifications").insert({
discord_id: interaction.user.id,
verification_code: verificationCode,
expires_at: expiresAt.toISOString(),
@ -38,30 +49,34 @@ module.exports = {
const verifyUrl = `https://aethex.dev/discord-verify?code=${verificationCode}`;
const embed = new EmbedBuilder()
.setColor(0x7289DA)
.setTitle('🔗 Link Your AeThex Account')
.setDescription('Click the button below to link your Discord account to AeThex.')
.addFields(
{ name: '⏱️ Expires In', value: '15 minutes' },
{ name: '📝 Verification Code', value: `\`${verificationCode}\`` },
.setColor(0x7289da)
.setTitle("🔗 Link Your AeThex Account")
.setDescription(
"Click the button below to link your Discord account to AeThex.",
)
.setFooter({ text: 'Your security code will expire in 15 minutes' });
.addFields(
{ name: "⏱️ Expires In", value: "15 minutes" },
{ name: "📝 Verification Code", value: `\`${verificationCode}\`` },
)
.setFooter({ text: "Your security code will expire in 15 minutes" });
const row = new ActionRowBuilder().addComponents(
new ButtonBuilder()
.setLabel('Link Account')
.setLabel("Link Account")
.setStyle(ButtonStyle.Link)
.setURL(verifyUrl),
);
await interaction.editReply({ embeds: [embed], components: [row] });
} catch (error) {
console.error('Verify command error:', error);
console.error("Verify command error:", error);
const embed = new EmbedBuilder()
.setColor(0xFF0000)
.setTitle('❌ Error')
.setDescription('Failed to generate verification code. Please try again.');
.setColor(0xff0000)
.setTitle("❌ Error")
.setDescription(
"Failed to generate verification code. Please try again.",
);
await interaction.editReply({ embeds: [embed] });
}
},

View file

@ -16,6 +16,7 @@ This guide covers how to integrate AeThex authentication with game engines: Robl
## Overview
AeThex provides a unified authentication system for games to:
- Authenticate players across different platforms
- Link player accounts to Roblox, Ethereum wallets, and other providers
- Store player profiles and achievements
@ -53,13 +54,13 @@ function AethexAuth:authenticate(playerId, playerName)
player_name = playerName,
platform = "PC"
})
local response = game:GetService("HttpService"):PostAsync(
API_BASE .. "/games/game-auth",
body,
Enum.HttpContentType.ApplicationJson
)
local data = game:GetService("HttpService"):JSONDecode(response)
return data
end
@ -73,7 +74,7 @@ function AethexAuth:verifyToken(sessionToken)
}),
Enum.HttpContentType.ApplicationJson
)
return game:GetService("HttpService"):JSONDecode(response)
end
@ -88,7 +89,7 @@ local AethexAuth = require(game.ServerScriptService:WaitForChild("AethexAuth"))
Players.PlayerAdded:Connect(function(player)
local authResult = AethexAuth:authenticate(player.UserId, player.Name)
if authResult.success then
player:SetAttribute("AethexSessionToken", authResult.session_token)
player:SetAttribute("AethexUserId", authResult.user_id)
@ -118,7 +119,7 @@ using System.Collections;
public class AethexAuth : MonoBehaviour
{
private const string API_BASE = "https://aethex.dev/api";
[System.Serializable]
public class AuthResponse
{
@ -129,7 +130,7 @@ public class AethexAuth : MonoBehaviour
public int expires_in;
public string error;
}
public static IEnumerator AuthenticatePlayer(
string playerId,
string playerName,
@ -139,7 +140,7 @@ public class AethexAuth : MonoBehaviour
$"{API_BASE}/games/game-auth",
"POST"
);
var requestBody = new AuthRequest
{
game = "unity",
@ -147,14 +148,14 @@ public class AethexAuth : MonoBehaviour
player_name = playerName,
platform = "PC"
};
string jsonBody = JsonUtility.ToJson(requestBody);
request.uploadHandler = new UploadHandlerRaw(System.Text.Encoding.UTF8.GetBytes(jsonBody));
request.downloadHandler = new DownloadHandlerBuffer();
request.SetRequestHeader("Content-Type", "application/json");
yield return request.SendWebRequest();
if (request.result == UnityWebRequest.Result.Success)
{
var response = JsonUtility.FromJson<AuthResponse>(request.downloadHandler.text);
@ -165,7 +166,7 @@ public class AethexAuth : MonoBehaviour
callback(new AuthResponse { error = request.error });
}
}
[System.Serializable]
private class AuthRequest
{
@ -186,10 +187,10 @@ public class GameManager : MonoBehaviour
{
string playerId = SystemInfo.deviceUniqueIdentifier;
string playerName = "UnityPlayer_" + Random.Range(1000, 9999);
StartCoroutine(AethexAuth.AuthenticatePlayer(playerId, playerName, OnAuthComplete));
}
void OnAuthComplete(AethexAuth.AuthResponse response)
{
if (response.success)
@ -231,12 +232,12 @@ public:
int32 ExpiresIn;
FString Error;
};
static void AuthenticatePlayer(
const FString& PlayerId,
const FString& PlayerName,
TFunction<void(const FAuthResponse&)> OnComplete);
private:
static void OnAuthResponse(
FHttpRequestPtr Request,
@ -259,30 +260,30 @@ void FAethexAuth::AuthenticatePlayer(
TFunction<void(const FAuthResponse&)> OnComplete)
{
FHttpModule& HttpModule = FHttpModule::Get();
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request =
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request =
HttpModule.CreateRequest();
Request->SetURL(TEXT("https://aethex.dev/api/games/game-auth"));
Request->SetVerb(TEXT("POST"));
Request->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
// Create JSON body
TSharedPtr<FJsonObject> JsonObject = MakeShareable(new FJsonObject());
JsonObject->SetStringField(TEXT("game"), TEXT("unreal"));
JsonObject->SetStringField(TEXT("player_id"), PlayerId);
JsonObject->SetStringField(TEXT("player_name"), PlayerName);
JsonObject->SetStringField(TEXT("platform"), TEXT("PC"));
FString OutputString;
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutputString);
FJsonSerializer::Serialize(JsonObject.ToSharedRef(), Writer);
Request->SetContentAsString(OutputString);
Request->OnProcessRequestComplete().BindStatic(
&FAethexAuth::OnAuthResponse,
OnComplete);
Request->ProcessRequest();
}
@ -293,12 +294,12 @@ void FAethexAuth::OnAuthResponse(
TFunction<void(const FAuthResponse&)> OnComplete)
{
FAuthResponse AuthResponse;
if (bWasSuccessful && Response.IsValid())
{
TSharedPtr<FJsonObject> JsonObject;
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString());
if (FJsonSerializer::Deserialize(Reader, JsonObject) && JsonObject.IsValid())
{
AuthResponse.bSuccess = JsonObject->GetBoolField(TEXT("success"));
@ -312,7 +313,7 @@ void FAethexAuth::OnAuthResponse(
{
AuthResponse.Error = TEXT("Authentication request failed");
}
OnComplete(AuthResponse);
}
```
@ -331,68 +332,68 @@ const API_BASE = "https://aethex.dev/api"
func authenticate_player(player_id: String, player_name: String) -> Dictionary:
var http_request = HTTPRequest.new()
add_child(http_request)
var url = API_BASE + "/games/game-auth"
var headers = ["Content-Type: application/json"]
var body = {
"game": "godot",
"player_id": player_id,
"player_name": player_name,
"platform": OS.get_name()
}
var response = http_request.request(
url,
headers,
HTTPClient.METHOD_POST,
JSON.stringify(body)
)
if response != OK:
return {"error": "Request failed"}
var result = await http_request.request_completed
if result[1] != 200:
return {"error": "Authentication failed"}
var response_data = JSON.parse_string(result[3].get_string_from_utf8())
http_request.queue_free()
return response_data
func verify_token(session_token: String) -> Dictionary:
var http_request = HTTPRequest.new()
add_child(http_request)
var url = API_BASE + "/games/verify-token"
var headers = ["Content-Type: application/json"]
var body = {
"session_token": session_token,
"game": "godot"
}
var response = http_request.request(
url,
headers,
HTTPClient.METHOD_POST,
JSON.stringify(body)
)
var result = await http_request.request_completed
var response_data = JSON.parse_string(result[3].get_string_from_utf8())
http_request.queue_free()
return response_data
func _ready():
var player_id = OS.get_unique_id()
var player_name = "GodotPlayer_" + str(randi_range(1000, 9999))
var auth_result = await authenticate_player(player_id, player_name)
if auth_result.has("success") and auth_result["success"]:
print("Authenticated as: ", auth_result["username"])
# Store token
@ -413,27 +414,29 @@ func _ready():
Authenticate a game player and create a session.
**Request:**
```json
{
"game": "unity|unreal|godot|roblox|custom",
"player_id": "unique-player-id",
"player_name": "player-display-name",
"device_id": "optional-device-id",
"platform": "PC|Mobile|Console"
"game": "unity|unreal|godot|roblox|custom",
"player_id": "unique-player-id",
"player_name": "player-display-name",
"device_id": "optional-device-id",
"platform": "PC|Mobile|Console"
}
```
**Response:**
```json
{
"success": true,
"session_token": "token-string",
"user_id": "uuid",
"username": "username",
"game": "unity",
"expires_in": 604800,
"api_base_url": "https://aethex.dev/api",
"docs_url": "https://docs.aethex.dev/game-integration"
"success": true,
"session_token": "token-string",
"user_id": "uuid",
"username": "username",
"game": "unity",
"expires_in": 604800,
"api_base_url": "https://aethex.dev/api",
"docs_url": "https://docs.aethex.dev/game-integration"
}
```
@ -442,24 +445,26 @@ Authenticate a game player and create a session.
Verify a game session token and get player data.
**Request:**
```json
{
"session_token": "token-string",
"game": "unity"
"session_token": "token-string",
"game": "unity"
}
```
**Response:**
```json
{
"valid": true,
"user_id": "uuid",
"username": "username",
"email": "user@example.com",
"full_name": "Player Name",
"game": "unity",
"platform": "PC",
"expires_at": "2025-01-15T10:30:00Z"
"valid": true,
"user_id": "uuid",
"username": "username",
"email": "user@example.com",
"full_name": "Player Name",
"game": "unity",
"platform": "PC",
"expires_at": "2025-01-15T10:30:00Z"
}
```
@ -475,6 +480,7 @@ Verify a game session token and get player data.
## Support
For issues or questions, visit:
- Docs: https://docs.aethex.dev
- Discord: https://discord.gg/aethex
- Email: support@aethex.tech