Prettier format pending files
This commit is contained in:
parent
ba233408ed
commit
e5aacf6773
25 changed files with 817 additions and 551 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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" });
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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" });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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" });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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" });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
455
client/App.tsx
455
client/App.tsx
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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] });
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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] });
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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] });
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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] });
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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] });
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue