diff --git a/README.md b/README.md index 9255cf18..d9974f2d 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,54 @@ -# AeThex Forge - Local Development Setup +# AeThex Forge – Local Development Setup ## Quick Start Guide -This guide will help you set up and run the AeThex platform locally on your machine. +This guide will help you set up and run the AeThex Developer Platform locally. ## Prerequisites 1. **Node.js** (v18 or higher) - - Download from: https://nodejs.org/ - - This will also install npm (Node Package Manager) + - [Download Node.js](https://nodejs.org/) + - Includes npm (Node Package Manager) -2. **Git** (optional, if you want to clone updates) - - Download from: https://git-scm.com/ +2. **Git** (recommended for updates) + - [Download Git](https://git-scm.com/) ## Installation Steps ### 1. Install Node.js -- Visit https://nodejs.org/ and download the LTS version -- Run the installer and follow the setup wizard -- Restart your terminal/PowerShell after installation + +- Download and install the LTS version from [nodejs.org](https://nodejs.org/) +- Follow the setup wizard +- Restart your terminal after installation ### 2. Verify Installation -Open PowerShell or Command Prompt and run: -```powershell + +Open your terminal and run: + +```bash node --version npm --version ``` + You should see version numbers (e.g., v20.x.x and 10.x.x) ### 3. Install Project Dependencies + Navigate to the project folder and install dependencies: -```powershell -cd C:\Users\PCOEM\Downloads\aethex-forge\aethex-forge + +```bash +cd /path/to/aethex-forge npm install ``` + This may take a few minutes as it downloads all required packages. ### 4. Set Up Environment Variables -Create a `.env` file in the root directory (`aethex-forge` folder) with the following variables: + +Create a `.env` file in the root directory (`aethex-forge/`) with the following variables: **Minimum Required (to run the app):** + ```env # Supabase Configuration (Required) VITE_SUPABASE_URL=your_supabase_url_here @@ -52,6 +61,7 @@ VITE_API_BASE=http://localhost:5000 ``` **Optional (for full functionality):** + ```env # Discord Integration DISCORD_CLIENT_ID=your_discord_client_id @@ -76,76 +86,84 @@ VITE_GHOST_API_URL=your_ghost_api_url GHOST_ADMIN_API_KEY=your_ghost_admin_key ``` -**Note:** You can start with just the Supabase variables to get the app running. Other features will work once you add their respective credentials. +**Note:** You can start with just the Supabase variables to get the app running. Add other credentials as needed for full functionality. ### 5. Run the Development Server -```powershell + +```bash npm run dev ``` -The application will start on **http://localhost:5000** +The application will start on **[http://localhost:5000](http://localhost:5000)** (or the port set in your config). Open your browser and navigate to that URL to view the application. ## Available Commands -- `npm run dev` - Start development server (port 5000) -- `npm run build` - Build for production -- `npm start` - Start production server -- `npm run typecheck` - Check TypeScript types -- `npm test` - Run tests +- `npm run dev` – Start development server (default: port 5000) +- `npm run build` – Build for production +- `npm start` – Start production server +- `npm run typecheck` – Check TypeScript types +- `npm test` – Run tests +aethex-forge/ +aethex-forge/ ## Project Structure -``` +```text aethex-forge/ -├── client/ # React frontend (pages, components) -├── server/ # Express backend API -├── api/ # API route handlers -├── shared/ # Shared types between client/server -├── discord-bot/ # Discord bot integration -└── supabase/ # Database migrations +├── client/ # React SPA frontend (pages, components, UI) +├── server/ # Express backend API +├── api/ # API route handlers (modular) +├── shared/ # Shared types/interfaces (client/server) +├── supabase/ # Database migrations & SQL +├── docs/ # Project documentation +└── ... # Other supporting folders ``` ## Getting Supabase Credentials If you don't have Supabase credentials yet: -1. Go to https://supabase.com/ -2. Create a free account -3. Create a new project -4. Go to Project Settings → API -5. Copy: +1. Go to [supabase.com](https://supabase.com/) +2. Create a free account and project +3. In your project, go to **Settings → API** +4. Copy: - Project URL → `VITE_SUPABASE_URL` and `SUPABASE_URL` - - `anon` `public` key → `VITE_SUPABASE_ANON_KEY` - - `service_role` `secret` key → `SUPABASE_SERVICE_ROLE` + - `anon` public key → `VITE_SUPABASE_ANON_KEY` + - `service_role` secret key → `SUPABASE_SERVICE_ROLE` ## Troubleshooting ### Port Already in Use + If port 5000 is already in use, you can change it in `vite.config.ts`: + ```typescript server: { - port: 5001, // Change to any available port + port: 5001, // Change to any available port } ``` ### Module Not Found Errors + Try deleting `node_modules` and `package-lock.json`, then run `npm install` again: -```powershell -Remove-Item -Recurse -Force node_modules -Remove-Item package-lock.json + +```bash +rm -rf node_modules package-lock.json npm install ``` ### Environment Variables Not Loading -- Make sure `.env` file is in the root `aethex-forge` directory + +- Ensure `.env` is in the root `aethex-forge` directory - Restart the dev server after adding new environment variables - Variables starting with `VITE_` are exposed to the client ## Need Help? -- Check the `docs/` folder for detailed documentation -- Review `AGENTS.md` for architecture details -- See `replit.md` for deployment information +- See the `docs/` folder for detailed documentation +- Review `AGENTS.md` for architecture and tech stack +- See `replit.md` for cloud deployment info +- For advanced troubleshooting, check `DEPLOYMENT_CHECKLIST.md` and `PHASE1_IMPLEMENTATION_SUMMARY.md` diff --git a/aethex-logos.zip b/aethex-logos.zip new file mode 100644 index 00000000..511d3c9a Binary files /dev/null and b/aethex-logos.zip differ diff --git a/aethex-logos/arms/corp-512.png b/aethex-logos/arms/corp-512.png new file mode 100644 index 00000000..24c203ce Binary files /dev/null and b/aethex-logos/arms/corp-512.png differ diff --git a/aethex-logos/arms/devlink-512.png b/aethex-logos/arms/devlink-512.png new file mode 100644 index 00000000..4a02eb5f Binary files /dev/null and b/aethex-logos/arms/devlink-512.png differ diff --git a/aethex-logos/arms/foundation-512.png b/aethex-logos/arms/foundation-512.png new file mode 100644 index 00000000..d111c853 Binary files /dev/null and b/aethex-logos/arms/foundation-512.png differ diff --git a/aethex-logos/arms/gameforge-512.png b/aethex-logos/arms/gameforge-512.png new file mode 100644 index 00000000..68f059cf Binary files /dev/null and b/aethex-logos/arms/gameforge-512.png differ diff --git a/aethex-logos/arms/labs-512.png b/aethex-logos/arms/labs-512.png new file mode 100644 index 00000000..8a317bb4 Binary files /dev/null and b/aethex-logos/arms/labs-512.png differ diff --git a/aethex-logos/arms/nexus-512.png b/aethex-logos/arms/nexus-512.png new file mode 100644 index 00000000..f00ad596 Binary files /dev/null and b/aethex-logos/arms/nexus-512.png differ diff --git a/aethex-logos/arms/staff-512.png b/aethex-logos/arms/staff-512.png new file mode 100644 index 00000000..1bb52bab Binary files /dev/null and b/aethex-logos/arms/staff-512.png differ diff --git a/aethex-logos/icons/1024x1024.png b/aethex-logos/icons/1024x1024.png new file mode 100644 index 00000000..b6d26b83 Binary files /dev/null and b/aethex-logos/icons/1024x1024.png differ diff --git a/aethex-logos/icons/128x128.png b/aethex-logos/icons/128x128.png new file mode 100644 index 00000000..35284e30 Binary files /dev/null and b/aethex-logos/icons/128x128.png differ diff --git a/aethex-logos/icons/16x16.png b/aethex-logos/icons/16x16.png new file mode 100644 index 00000000..ab6bdbc0 Binary files /dev/null and b/aethex-logos/icons/16x16.png differ diff --git a/aethex-logos/icons/256x256.png b/aethex-logos/icons/256x256.png new file mode 100644 index 00000000..6904074e Binary files /dev/null and b/aethex-logos/icons/256x256.png differ diff --git a/aethex-logos/icons/32x32.png b/aethex-logos/icons/32x32.png new file mode 100644 index 00000000..026bf1ae Binary files /dev/null and b/aethex-logos/icons/32x32.png differ diff --git a/aethex-logos/icons/48x48.png b/aethex-logos/icons/48x48.png new file mode 100644 index 00000000..35bfcd31 Binary files /dev/null and b/aethex-logos/icons/48x48.png differ diff --git a/aethex-logos/icons/512x512.png b/aethex-logos/icons/512x512.png new file mode 100644 index 00000000..d47dc17e Binary files /dev/null and b/aethex-logos/icons/512x512.png differ diff --git a/aethex-logos/icons/64x64.png b/aethex-logos/icons/64x64.png new file mode 100644 index 00000000..8adb463b Binary files /dev/null and b/aethex-logos/icons/64x64.png differ diff --git a/aethex-logos/icons/favicon.ico b/aethex-logos/icons/favicon.ico new file mode 100644 index 00000000..ef09b153 Binary files /dev/null and b/aethex-logos/icons/favicon.ico differ diff --git a/aethex-logos/icons/icon.ico b/aethex-logos/icons/icon.ico new file mode 100644 index 00000000..e880ab31 Binary files /dev/null and b/aethex-logos/icons/icon.ico differ diff --git a/aethex-logos/icons/icon.svg b/aethex-logos/icons/icon.svg new file mode 100644 index 00000000..8683e834 --- /dev/null +++ b/aethex-logos/icons/icon.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/aethex-logos/main/aethex-logo-1200.png b/aethex-logos/main/aethex-logo-1200.png new file mode 100644 index 00000000..2ade6ba2 Binary files /dev/null and b/aethex-logos/main/aethex-logo-1200.png differ diff --git a/aethex-logos/main/aethex-logo-512.png b/aethex-logos/main/aethex-logo-512.png new file mode 100644 index 00000000..70c6bcd2 Binary files /dev/null and b/aethex-logos/main/aethex-logo-512.png differ diff --git a/client/components/EthosStorefrontWidget.tsx b/client/components/EthosStorefrontWidget.tsx index 0675ee74..3cdac2da 100644 --- a/client/components/EthosStorefrontWidget.tsx +++ b/client/components/EthosStorefrontWidget.tsx @@ -10,7 +10,6 @@ import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Music, - Toggle, ToggleLeft, ToggleRight, ExternalLink, diff --git a/client/components/admin/AdminBlogEditor.tsx b/client/components/admin/AdminBlogEditor.tsx index 21d46c79..faf609b9 100644 --- a/client/components/admin/AdminBlogEditor.tsx +++ b/client/components/admin/AdminBlogEditor.tsx @@ -67,7 +67,7 @@ const BlogEditor = ({ onPublish, initialData }: BlogEditorProps) => { const handlePublish = async () => { if (!title.trim() || !html.trim()) { - toast.error("Title and body are required"); + toast.error({ title: "Title and body are required" }); return; } @@ -95,7 +95,7 @@ const BlogEditor = ({ onPublish, initialData }: BlogEditorProps) => { } const data = await response.json(); - toast.success(`Post published: ${data.url}`); + toast.success({ title: `Post published: ${data.url}` }); onPublish?.(true); // Reset form @@ -108,7 +108,7 @@ const BlogEditor = ({ onPublish, initialData }: BlogEditorProps) => { setMetaTitle(""); setMetaDescription(""); } catch (error: any) { - toast.error(error.message || "Failed to publish post"); + toast.error({ title: error.message || "Failed to publish post" }); onPublish?.(false); } finally { setIsLoading(false); diff --git a/client/components/admin/AdminFoundationManager.tsx b/client/components/admin/AdminFoundationManager.tsx index a68d7a39..1231e9bd 100644 --- a/client/components/admin/AdminFoundationManager.tsx +++ b/client/components/admin/AdminFoundationManager.tsx @@ -101,7 +101,7 @@ export default function AdminFoundationManager() { const data = await response.json(); setMentors(data || []); } catch (error) { - aethexToast.error("Failed to load mentors"); + aethexToast.error({ title: "Failed to load mentors" }); console.error(error); } finally { setLoadingMentors(false); @@ -116,7 +116,7 @@ export default function AdminFoundationManager() { const data = await response.json(); setCourses(data || []); } catch (error) { - aethexToast.error("Failed to load courses"); + aethexToast.error({ title: "Failed to load courses" }); console.error(error); } finally { setLoadingCourses(false); @@ -133,7 +133,7 @@ export default function AdminFoundationManager() { const data = await response.json(); setAchievements(data || []); } catch (error) { - aethexToast.error("Failed to load achievements"); + aethexToast.error({ title: "Failed to load achievements" }); console.error(error); } finally { setLoadingAchievements(false); @@ -154,14 +154,14 @@ export default function AdminFoundationManager() { ); if (!response.ok) throw new Error("Failed to update mentor"); - aethexToast.success( - `Mentor ${approvalAction === "approve" ? "approved" : "rejected"}`, - ); + aethexToast.success({ + title: `Mentor ${approvalAction === "approve" ? "approved" : "rejected"}`, + }); setApprovalDialogOpen(false); setSelectedMentor(null); fetchMentors(); } catch (error) { - aethexToast.error("Failed to update mentor"); + aethexToast.error({ title: "Failed to update mentor" }); console.error(error); } }; @@ -178,10 +178,10 @@ export default function AdminFoundationManager() { ); if (!response.ok) throw new Error("Failed to update course"); - aethexToast.success(`Course ${publish ? "published" : "unpublished"}`); + aethexToast.success({ title: `Course ${publish ? "published" : "unpublished"}` }); fetchCourses(); } catch (error) { - aethexToast.error("Failed to update course"); + aethexToast.error({ title: "Failed to update course" }); console.error(error); } }; @@ -196,10 +196,10 @@ export default function AdminFoundationManager() { ); if (!response.ok) throw new Error("Failed to delete course"); - aethexToast.success("Course deleted"); + aethexToast.success({ title: "Course deleted" }); fetchCourses(); } catch (error) { - aethexToast.error("Failed to delete course"); + aethexToast.error({ title: "Failed to delete course" }); console.error(error); } }; diff --git a/client/components/admin/AdminNexusManager.tsx b/client/components/admin/AdminNexusManager.tsx index fb09462b..ffe5f35d 100644 --- a/client/components/admin/AdminNexusManager.tsx +++ b/client/components/admin/AdminNexusManager.tsx @@ -105,7 +105,7 @@ export default function AdminNexusManager() { const data = await response.json(); setOpportunities(data || []); } catch (error) { - aethexToast.error("Failed to load opportunities"); + aethexToast.error({ title: "Failed to load opportunities" }); console.error(error); } finally { setLoadingOpp(false); @@ -120,7 +120,7 @@ export default function AdminNexusManager() { const data = await response.json(); setDisputes(data || []); } catch (error) { - aethexToast.error("Failed to load disputes"); + aethexToast.error({ title: "Failed to load disputes" }); console.error(error); } finally { setLoadingDisputes(false); @@ -135,7 +135,7 @@ export default function AdminNexusManager() { const data = await response.json(); setCommissions(data || []); } catch (error) { - aethexToast.error("Failed to load commissions"); + aethexToast.error({ title: "Failed to load commissions" }); console.error(error); } finally { setLoadingCommissions(false); @@ -157,10 +157,10 @@ export default function AdminNexusManager() { ); if (!response.ok) throw new Error("Failed to update opportunity"); - aethexToast.success(`Opportunity marked as ${status}`); + aethexToast.success({ title: `Opportunity marked as ${status}` }); fetchOpportunities(); } catch (error) { - aethexToast.error("Failed to update opportunity"); + aethexToast.error({ title: "Failed to update opportunity" }); console.error(error); } }; @@ -180,12 +180,12 @@ export default function AdminNexusManager() { ); if (!response.ok) throw new Error("Failed to update opportunity"); - aethexToast.success( - `Opportunity ${featured ? "featured" : "unfeatured"}`, - ); + aethexToast.success({ + title: `Opportunity ${featured ? "featured" : "unfeatured"}`, + }); fetchOpportunities(); } catch (error) { - aethexToast.error("Failed to update opportunity"); + aethexToast.error({ title: "Failed to update opportunity" }); console.error(error); } }; @@ -207,15 +207,15 @@ export default function AdminNexusManager() { ); if (!response.ok) throw new Error("Failed to update dispute"); - aethexToast.success( - `Dispute ${disputeAction === "resolve" ? "resolved" : "escalated"}`, - ); + aethexToast.success({ + title: `Dispute ${disputeAction === "resolve" ? "resolved" : "escalated"}`, + }); setDisputeDialogOpen(false); setSelectedDispute(null); setDisputeResolution(""); fetchDisputes(); } catch (error) { - aethexToast.error("Failed to update dispute"); + aethexToast.error({ title: "Failed to update dispute" }); console.error(error); } }; diff --git a/client/components/admin/MaintenanceToggle.tsx b/client/components/admin/MaintenanceToggle.tsx index ba5b0bee..9f877f15 100644 --- a/client/components/admin/MaintenanceToggle.tsx +++ b/client/components/admin/MaintenanceToggle.tsx @@ -12,20 +12,20 @@ export default function MaintenanceToggle() { const handleToggle = async () => { if (!canBypass) { - aethexToast.error("Only admins can toggle maintenance mode"); + aethexToast.error({ title: "Only admins can toggle maintenance mode" }); return; } setToggling(true); try { await toggleMaintenanceMode(); - aethexToast.success( - isMaintenanceMode + aethexToast.success({ + title: isMaintenanceMode ? "Maintenance mode disabled - site is now live" : "Maintenance mode enabled - visitors will see maintenance page" - ); + }); } catch (error: any) { - aethexToast.error(error?.message || "Failed to toggle maintenance mode"); + aethexToast.error({ title: error?.message || "Failed to toggle maintenance mode" }); } finally { setToggling(false); } diff --git a/client/components/creator-network/DevConnectLinkModal.tsx b/client/components/creator-network/DevConnectLinkModal.tsx index 8f74923a..cfc11417 100644 --- a/client/components/creator-network/DevConnectLinkModal.tsx +++ b/client/components/creator-network/DevConnectLinkModal.tsx @@ -27,11 +27,11 @@ export function DevConnectLinkModal({ }: DevConnectLinkModalProps) { const [username, setUsername] = useState(""); const [isLoading, setIsLoading] = useState(false); - const { toast } = useAethexToast(); + const toast = useAethexToast(); const handleLink = async () => { if (!username.trim()) { - toast("Please enter your DevConnect username", "error"); + toast.error({ title: "Please enter your DevConnect username" }); return; } @@ -41,17 +41,16 @@ export function DevConnectLinkModal({ devconnect_username: username.trim(), devconnect_profile_url: `https://devconnect.sbs/${username.trim()}`, }); - toast("DevConnect account linked successfully!", "success"); + toast.success({ title: "DevConnect account linked successfully!" }); setUsername(""); onOpenChange(false); onSuccess?.(); } catch (error) { - toast( - error instanceof Error + toast.error({ + title: error instanceof Error ? error.message : "Failed to link DevConnect account", - "error", - ); + }); } finally { setIsLoading(false); } diff --git a/client/components/dev-platform/index.ts b/client/components/dev-platform/index.ts index f1dcc28b..3cd3a622 100644 --- a/client/components/dev-platform/index.ts +++ b/client/components/dev-platform/index.ts @@ -1,21 +1,21 @@ // Export layout components -export { default as DevPlatformLayout } from './layouts/DevPlatformLayout'; -export { default as ThreeColumnLayout } from './layouts/ThreeColumnLayout'; +export { DevPlatformLayout } from './layouts/DevPlatformLayout'; +export { ThreeColumnLayout } from './layouts/ThreeColumnLayout'; // Export UI components -export { default as CodeBlock } from './ui/CodeBlock'; -export { default as Callout } from './ui/Callout'; -export { default as StatCard } from './ui/StatCard'; -export { default as ApiEndpointCard } from './ui/ApiEndpointCard'; +export { CodeBlock } from './ui/CodeBlock'; +export { Callout } from './ui/Callout'; +export { StatCard } from './ui/StatCard'; +export { ApiEndpointCard } from './ui/ApiEndpointCard'; // Export feature components -export { default as DevPlatformNav } from './DevPlatformNav'; -export { default as DevPlatformFooter } from './DevPlatformFooter'; -export { default as Breadcrumbs } from './Breadcrumbs'; -export { default as CodeTabs } from './CodeTabs'; -export { default as TemplateCard } from './TemplateCard'; -export { default as MarketplaceCard } from './MarketplaceCard'; -export { default as ExampleCard } from './ExampleCard'; -export { default as ApiKeyCard } from './ApiKeyCard'; -export { default as CreateApiKeyDialog } from './CreateApiKeyDialog'; -export { default as UsageChart } from './UsageChart'; +export { DevPlatformNav } from './DevPlatformNav'; +export { DevPlatformFooter } from './DevPlatformFooter'; +export { Breadcrumbs } from './Breadcrumbs'; +export { CodeTabs } from './CodeTabs'; +export { TemplateCard } from './TemplateCard'; +export { MarketplaceCard } from './MarketplaceCard'; +export { ExampleCard } from './ExampleCard'; +export { ApiKeyCard } from './ApiKeyCard'; +export { CreateApiKeyDialog } from './CreateApiKeyDialog'; +export { UsageChart } from './UsageChart'; diff --git a/client/components/settings/WalletVerification.tsx b/client/components/settings/WalletVerification.tsx index 0a0adb8d..55e9ade0 100644 --- a/client/components/settings/WalletVerification.tsx +++ b/client/components/settings/WalletVerification.tsx @@ -47,20 +47,20 @@ export const WalletVerification = ({ const handleConnect = async () => { if (!walletInput.trim()) { - aethexToast.warning("Please enter a wallet address"); + aethexToast.warning({ title: "Please enter a wallet address" }); return; } const normalized = walletInput.trim().toLowerCase(); if (!isValidEthereumAddress(normalized)) { - aethexToast.warning( - "Invalid Ethereum address. Must be 0x followed by 40 hexadecimal characters.", - ); + aethexToast.warning({ + title: "Invalid Ethereum address. Must be 0x followed by 40 hexadecimal characters.", + }); return; } if (!user?.id) { - aethexToast.error("User not authenticated"); + aethexToast.error({ title: "User not authenticated" }); return; } @@ -86,16 +86,16 @@ export const WalletVerification = ({ const data = await response.json(); setConnectedWallet(normalized); setWalletInput(""); - aethexToast.success("✅ Wallet connected successfully!"); + aethexToast.success({ title: "✅ Wallet connected successfully!" }); if (onWalletUpdated) { onWalletUpdated(normalized); } } catch (error: any) { console.error("[Wallet Verification] Error:", error?.message); - aethexToast.error( - error?.message || "Failed to connect wallet. Please try again.", - ); + aethexToast.error({ + title: error?.message || "Failed to connect wallet. Please try again.", + }); } finally { setIsLoading(false); } @@ -123,16 +123,16 @@ export const WalletVerification = ({ } setConnectedWallet(null); - aethexToast.success("Wallet disconnected"); + aethexToast.success({ title: "Wallet disconnected" }); if (onWalletUpdated) { onWalletUpdated(null); } } catch (error: any) { console.error("[Wallet Verification] Error:", error?.message); - aethexToast.error( - error?.message || "Failed to disconnect wallet. Please try again.", - ); + aethexToast.error({ + title: error?.message || "Failed to disconnect wallet. Please try again.", + }); } finally { setIsLoading(false); } diff --git a/client/pages/Explore.tsx b/client/pages/Explore.tsx index 1e0eda0b..374b03fe 100644 --- a/client/pages/Explore.tsx +++ b/client/pages/Explore.tsx @@ -26,7 +26,7 @@ import { TrendingUp, Heart, MessageSquare, - Avatar, + User, } from "lucide-react"; import { useEffect, useMemo, useState } from "react"; import { diff --git a/client/pages/Index.tsx b/client/pages/Index.tsx index 7631ff4e..89cf8c44 100644 --- a/client/pages/Index.tsx +++ b/client/pages/Index.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from "react"; +import { useState } from "react"; import SEO from "@/components/SEO"; import Layout from "@/components/Layout"; import { Button } from "@/components/ui/button"; @@ -115,16 +115,7 @@ const features = [ ]; export default function Index() { - const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 }); const [hoveredCard, setHoveredCard] = useState(null); - - useEffect(() => { - const handleMouseMove = (e: MouseEvent) => { - setMousePosition({ x: e.clientX, y: e.clientY }); - }; - window.addEventListener("mousemove", handleMouseMove); - return () => window.removeEventListener("mousemove", handleMouseMove); - }, []); return ( @@ -141,10 +132,10 @@ export default function Index() { {/* Animated Background */}
{ + navigator.clipboard.writeText(codeExample); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( + + + + {/* Subtle background */} +
+
+
+
+
+ +
+ {/* Hero */} +
+
+
+ {/* Left: Value Prop */} +
+ + v2.4.0 — TypeScript SDK + + +

+ One API for +
+ cross-platform games +

+ +

+ Connect players across Roblox, Minecraft, Fortnite, and more. + Sync identity, achievements, and inventory with a single SDK. +

+ +
+ + + + + + +
+ + {/* Install command */} +
+ + npm install @aethex/sdk + + +
+ + {/* Stats row */} +
+ {stats.map((stat) => ( +
+

{stat.value}

+

{stat.label}

+
+ ))} +
+
+ + {/* Right: Code Example */} +
+ +
+
+
+
+
+
+ app.ts + +
+
+                    
+                      import
+                       {"{"} 
+                      AeThex
+                       {"}"} 
+                      from
+                       '@aethex/sdk'
+                      ;
+                      {"\n\n"}
+                      const
+                       client
+                       = 
+                      new
+                       AeThex
+                      {"({ "}
+                      apiKey
+                      : 
+                      process.env.
+                      AETHEX_KEY {"});"}
+                      {"\n\n"}
+                      // Authenticate user across platforms
+                      {"\n"}
+                      const
+                       user
+                       = 
+                      await
+                       client
+                      .passport.
+                      authenticate
+                      {"({"}
+                      {"\n"}
+                      {"  "}
+                      platform
+                      : 
+                      'roblox'
+                      ,
+                      {"\n"}
+                      {"  "}
+                      userId
+                      : 
+                      '123456789'
+                      {"\n"}
+                      {"});"}
+                      {"\n\n"}
+                      // Sync achievements, inventory, progress
+                      {"\n"}
+                      await
+                       client
+                      .
+                      sync
+                      {"({"}
+                      {"\n"}
+                      {"  "}
+                      achievements
+                      : 
+                      user
+                      .achievements,
+                      {"\n"}
+                      {"  "}
+                      inventory
+                      : 
+                      user
+                      .inventory,
+                      {"\n"}
+                      {"  "}
+                      progress
+                      : 
+                      user
+                      .gameProgress
+                      {"\n"}
+                      {"});"}
+                    
+                  
+ +
+
+
+
+ + {/* Platforms */} +
+
+
+ Works with: + {platforms.map((platform) => ( + + {platform} + + ))} +
+
+
+ + {/* Ecosystem Pillars */} +
+
+
+

The AeThex Ecosystem

+

+ Six interconnected realms with specialized APIs for every use case +

+
+ +
+ {ecosystemPillars.map((pillar) => ( + + +
+
+ +
+
+

+ {pillar.title} +

+

+ {pillar.description} +

+
+
+ Explore +
+
+
+ + ))} +
+
+
+ + {/* Features */} +
+
+
+

Built for game developers

+

+ Everything you need to connect players across platforms +

+
+ +
+ {features.map((feature) => ( + +
+ +
+

{feature.title}

+

{feature.description}

+
+ ))} +
+
+
+ + {/* CTA */} +
+
+ + +

Start building today

+

+ Get your API key and integrate in minutes. Free tier includes 10K API calls/month. +

+
+ + + + + + +
+
+
+
+ + {/* Footer spacer */} +
+
+ + ); +} diff --git a/client/pages/Labs.tsx b/client/pages/Labs.tsx index bcb8c62c..ccb790bb 100644 --- a/client/pages/Labs.tsx +++ b/client/pages/Labs.tsx @@ -154,9 +154,9 @@ export default function Labs() { {/* Cyberpunk Background Effects */}
-
-
-
+
+
+
{/* Hero Section */} diff --git a/client/pages/admin/AdminEthosVerification.tsx b/client/pages/admin/AdminEthosVerification.tsx index 89096e7d..e724de30 100644 --- a/client/pages/admin/AdminEthosVerification.tsx +++ b/client/pages/admin/AdminEthosVerification.tsx @@ -86,7 +86,7 @@ export default function AdminEthosVerification() { setRequests(data); } catch (error) { console.error(error); - toast.error("Failed to load verification requests"); + toast.error({ title: "Failed to load verification requests" }); } finally { setLoading(false); } @@ -124,11 +124,11 @@ export default function AdminEthosVerification() { if (!response.ok) throw new Error(`Failed to ${confirmAction} artist`); - toast.success( - confirmAction === "approve" + toast.success({ + title: confirmAction === "approve" ? "Artist verified successfully! Email sent." : "Artist application rejected. Email sent.", - ); + }); setIsConfirming(false); setSelectedRequest(null); @@ -136,7 +136,7 @@ export default function AdminEthosVerification() { fetchRequests(); } catch (error) { console.error(error); - toast.error(`Failed to ${confirmAction} artist`); + toast.error({ title: `Failed to ${confirmAction} artist` }); } }; diff --git a/server/index.ts b/server/index.ts index 078fa527..624422cb 100644 --- a/server/index.ts +++ b/server/index.ts @@ -1038,6 +1038,211 @@ export function createServer() { } }); + // ===================================================== + // Foundation OAuth Callback Routes + // ===================================================== + const FOUNDATION_URL = process.env.VITE_FOUNDATION_URL || "https://aethex.foundation"; + const FOUNDATION_CLIENT_ID = process.env.FOUNDATION_OAUTH_CLIENT_ID || "aethex_corp"; + const FOUNDATION_CLIENT_SECRET = process.env.FOUNDATION_OAUTH_CLIENT_SECRET; + const API_BASE = process.env.VITE_API_BASE || "https://aethex.dev"; + + // GET /auth/callback - Receives authorization code from Foundation + app.get("/auth/callback", async (req, res) => { + const { code, state, error, error_description } = req.query; + + // Handle Foundation errors + if (error) { + const message = error_description + ? decodeURIComponent(String(error_description)) + : String(error); + return res.redirect(`/login?error=${error}&message=${encodeURIComponent(message)}`); + } + + if (!code) { + return res.redirect(`/login?error=no_code&message=${encodeURIComponent("No authorization code received")}`); + } + + try { + console.log("[Foundation OAuth] Received authorization code, initiating token exchange"); + + // Exchange code for token + if (!FOUNDATION_CLIENT_SECRET) { + throw new Error("FOUNDATION_OAUTH_CLIENT_SECRET not configured"); + } + + const tokenEndpoint = `${FOUNDATION_URL}/api/oauth/token`; + const params = new URLSearchParams(); + params.append("grant_type", "authorization_code"); + params.append("code", String(code)); + params.append("client_id", FOUNDATION_CLIENT_ID); + params.append("client_secret", FOUNDATION_CLIENT_SECRET); + params.append("redirect_uri", `${API_BASE}/auth/callback`); + + const tokenResponse = await fetch(tokenEndpoint, { + method: "POST", + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + body: params.toString(), + }); + + if (!tokenResponse.ok) { + const errorData = await tokenResponse.json().catch(() => ({})); + console.error("[Foundation OAuth] Token exchange failed:", errorData); + throw new Error(`Token exchange failed: ${tokenResponse.status}`); + } + + const tokenData = await tokenResponse.json(); + if (!tokenData.access_token) { + throw new Error("No access token in Foundation response"); + } + + // Fetch user info from Foundation + const userInfoResponse = await fetch(`${FOUNDATION_URL}/api/oauth/userinfo`, { + method: "GET", + headers: { + Authorization: `Bearer ${tokenData.access_token}`, + Accept: "application/json", + }, + }); + + if (!userInfoResponse.ok) { + throw new Error(`Failed to fetch user info: ${userInfoResponse.status}`); + } + + const userInfo = await userInfoResponse.json(); + if (!userInfo.id || !userInfo.email) { + throw new Error("Invalid user info from Foundation"); + } + + console.log("[Foundation OAuth] User authenticated:", userInfo.id); + + // Sync user to local database + const now = new Date().toISOString(); + const cacheValidUntil = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(); + + await adminSupabase.from("user_profiles").upsert({ + id: userInfo.id, + email: userInfo.email, + username: userInfo.username || null, + full_name: userInfo.full_name || null, + avatar_url: userInfo.avatar_url || null, + profile_completed: userInfo.profile_complete || false, + foundation_synced_at: now, + cache_valid_until: cacheValidUntil, + updated_at: now, + }); + + // Set session cookies + res.cookie("foundation_access_token", tokenData.access_token, { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "strict", + maxAge: (tokenData.expires_in || 3600) * 1000, + }); + res.cookie("auth_user_id", userInfo.id, { + secure: process.env.NODE_ENV === "production", + sameSite: "strict", + maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days + }); + + // Redirect to dashboard or stored destination + const redirectTo = (req.query.redirect_to as string) || "/dashboard"; + return res.redirect(redirectTo); + } catch (err: any) { + console.error("[Foundation OAuth] Callback error:", err); + const message = err?.message || "Authentication failed"; + return res.redirect(`/login?error=auth_failed&message=${encodeURIComponent(message)}`); + } + }); + + // POST /auth/callback/exchange - Exchange code for token (called from frontend) + app.post("/auth/callback/exchange", async (req, res) => { + const { code } = req.body || {}; + + if (!code) { + return res.status(400).json({ error: "Authorization code is required" }); + } + + try { + if (!FOUNDATION_CLIENT_SECRET) { + throw new Error("FOUNDATION_OAUTH_CLIENT_SECRET not configured"); + } + + const tokenEndpoint = `${FOUNDATION_URL}/api/oauth/token`; + const params = new URLSearchParams(); + params.append("grant_type", "authorization_code"); + params.append("code", code); + params.append("client_id", FOUNDATION_CLIENT_ID); + params.append("client_secret", FOUNDATION_CLIENT_SECRET); + params.append("redirect_uri", `${API_BASE}/auth/callback`); + + const tokenResponse = await fetch(tokenEndpoint, { + method: "POST", + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + body: params.toString(), + }); + + if (!tokenResponse.ok) { + const errorData = await tokenResponse.json().catch(() => ({})); + throw new Error(`Token exchange failed: ${tokenResponse.status}`); + } + + const tokenData = await tokenResponse.json(); + if (!tokenData.access_token) { + throw new Error("No access token in Foundation response"); + } + + // Fetch user info from Foundation + const userInfoResponse = await fetch(`${FOUNDATION_URL}/api/oauth/userinfo`, { + method: "GET", + headers: { + Authorization: `Bearer ${tokenData.access_token}`, + Accept: "application/json", + }, + }); + + if (!userInfoResponse.ok) { + throw new Error(`Failed to fetch user info: ${userInfoResponse.status}`); + } + + const userInfo = await userInfoResponse.json(); + + // Sync user to local database + const now = new Date().toISOString(); + const cacheValidUntil = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(); + + await adminSupabase.from("user_profiles").upsert({ + id: userInfo.id, + email: userInfo.email, + username: userInfo.username || null, + full_name: userInfo.full_name || null, + avatar_url: userInfo.avatar_url || null, + profile_completed: userInfo.profile_complete || false, + foundation_synced_at: now, + cache_valid_until: cacheValidUntil, + updated_at: now, + }); + + // Set session cookies + res.cookie("foundation_access_token", tokenData.access_token, { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "strict", + maxAge: (tokenData.expires_in || 3600) * 1000, + }); + res.cookie("auth_user_id", userInfo.id, { + secure: process.env.NODE_ENV === "production", + sameSite: "strict", + maxAge: 30 * 24 * 60 * 60 * 1000, + }); + + console.log("[Foundation OAuth] Token exchange successful for:", userInfo.id); + return res.json({ accessToken: tokenData.access_token, user: userInfo }); + } catch (err: any) { + console.error("[Foundation OAuth] Token exchange error:", err); + return res.status(400).json({ error: err?.message || "Token exchange failed" }); + } + }); + // Admin-backed API (service role) try { const ownerEmail = ( diff --git a/supabase/.temp/cli-latest b/supabase/.temp/cli-latest index 9b07af9e..1dd61787 100644 --- a/supabase/.temp/cli-latest +++ b/supabase/.temp/cli-latest @@ -1 +1 @@ -v2.72.7 \ No newline at end of file +v2.75.0 \ No newline at end of file diff --git a/supabase/migrations/20260205_update_oauth_redirect_uris.sql b/supabase/migrations/20260205_update_oauth_redirect_uris.sql new file mode 100644 index 00000000..f7204bea --- /dev/null +++ b/supabase/migrations/20260205_update_oauth_redirect_uris.sql @@ -0,0 +1,7 @@ +-- Update OAuth client redirect URIs to include localhost:8080 for local dev +-- This enables Foundation login to work in both production and development + +UPDATE public.oauth_clients +SET redirect_uris = '["https://aethex.dev/auth/callback", "http://localhost:8080/auth/callback", "http://localhost:3000/auth/callback", "http://localhost:5173/auth/callback"]'::jsonb, + updated_at = NOW() +WHERE client_id = 'aethex_corp';