new file: aethex-logos/icons/1024x1024.png

This commit is contained in:
MrPiglr 2026-02-05 10:04:49 +00:00 committed by GitHub
parent 4216430546
commit dff8b2ecb1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
38 changed files with 789 additions and 136 deletions

106
README.md
View file

@ -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`

BIN
aethex-logos.zip Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 491 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,021 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

BIN
aethex-logos/icons/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

View file

@ -0,0 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
<defs>
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#1e3a5f;stop-opacity:1" />
<stop offset="100%" style="stop-color:#0f172a;stop-opacity:1" />
</linearGradient>
<linearGradient id="accent" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#3b82f6;stop-opacity:1" />
<stop offset="100%" style="stop-color:#8b5cf6;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="512" height="512" rx="80" fill="url(#bg)"/>
<path d="M256 100 L380 380 L256 320 L132 380 Z" fill="url(#accent)" opacity="0.9"/>
<path d="M256 140 L350 340 L256 295 L162 340 Z" fill="#0f172a" opacity="0.3"/>
<circle cx="256" cy="220" r="30" fill="#fff" opacity="0.9"/>
</svg>

After

Width:  |  Height:  |  Size: 861 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 887 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

View file

@ -10,7 +10,6 @@ import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import {
Music,
Toggle,
ToggleLeft,
ToggleRight,
ExternalLink,

View file

@ -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);

View file

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

View file

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

View file

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

View file

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

View file

@ -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';

View file

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

View file

@ -26,7 +26,7 @@ import {
TrendingUp,
Heart,
MessageSquare,
Avatar,
User,
} from "lucide-react";
import { useEffect, useMemo, useState } from "react";
import {

View file

@ -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<number | null>(null);
useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
setMousePosition({ x: e.clientX, y: e.clientY });
};
window.addEventListener("mousemove", handleMouseMove);
return () => window.removeEventListener("mousemove", handleMouseMove);
}, []);
return (
<Layout hideFooter>
@ -141,10 +132,10 @@ export default function Index() {
{/* Animated Background */}
<div className="fixed inset-0 pointer-events-none overflow-hidden">
<motion.div
className="absolute w-[800px] h-[800px] rounded-full blur-[128px] opacity-20 bg-primary/30"
className="absolute w-[600px] h-[600px] rounded-full blur-[100px] opacity-15 bg-primary/30"
style={{
left: mousePosition.x - 400,
top: mousePosition.y - 400,
left: '10%',
top: '20%',
}}
animate={{
x: [0, 50, 0],

View file

@ -0,0 +1,418 @@
import { useState } from "react";
import SEO from "@/components/SEO";
import Layout from "@/components/Layout";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Link } from "react-router-dom";
import {
ArrowRight,
Terminal,
Copy,
Check,
BookOpen,
Zap,
Shield,
Globe,
Code2,
Database,
Users,
Boxes,
Layers,
Trophy,
Gamepad2,
} from "lucide-react";
const codeExample = `import { AeThex } from '@aethex/sdk';
const client = new AeThex({ apiKey: process.env.AETHEX_KEY });
// Authenticate user across platforms
const user = await client.passport.authenticate({
platform: 'roblox',
userId: '123456789'
});
// Sync achievements, inventory, progress
await client.sync({
achievements: user.achievements,
inventory: user.inventory,
progress: user.gameProgress
});`;
const ecosystemPillars = [
{
icon: Boxes,
title: "Six Realms",
description: "Specialized APIs for every use case",
href: "/realms",
gradient: "from-purple-500 to-indigo-600",
},
{
icon: Database,
title: "Developer APIs",
description: "REST APIs for all platforms",
href: "/dev-platform/api-reference",
gradient: "from-blue-500 to-cyan-600",
},
{
icon: Terminal,
title: "SDK & Tools",
description: "Ship faster with TypeScript SDK",
href: "/dev-platform/quick-start",
gradient: "from-cyan-500 to-emerald-600",
},
{
icon: Layers,
title: "Marketplace",
description: "Premium plugins & integrations",
href: "/dev-platform/marketplace",
gradient: "from-emerald-500 to-lime-600",
},
{
icon: Users,
title: "Community",
description: "12K+ developers building together",
href: "/community",
gradient: "from-amber-500 to-red-600",
},
{
icon: Trophy,
title: "Opportunities",
description: "Get paid to build",
href: "/opportunities",
gradient: "from-pink-500 to-red-600",
},
];
const stats = [
{ value: "12K+", label: "Developers" },
{ value: "2.5M+", label: "API Calls/Day" },
{ value: "150+", label: "Examples" },
{ value: "6", label: "Realms" },
];
const features = [
{
icon: Globe,
title: "Cross-Platform Identity",
description: "One passport across Roblox, Minecraft, Fortnite, and more.",
},
{
icon: Database,
title: "Universal Data Sync",
description: "Sync achievements, inventory, and progress with a single API.",
},
{
icon: Shield,
title: "Enterprise Auth",
description: "OAuth 2.0, PKCE, JWT. Production-ready out of the box.",
},
{
icon: Gamepad2,
title: "Game Integration",
description: "Drop-in SDKs for Roblox, Unity, Unreal, and more.",
},
];
const platforms = ["Roblox", "Minecraft", "Fortnite", "Meta Horizon", "Zepeto", "Unity", "Unreal"];
export default function Index() {
const [copied, setCopied] = useState(false);
const copyCode = () => {
navigator.clipboard.writeText(codeExample);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
return (
<Layout hideFooter>
<SEO
pageTitle="AeThex | Developer Platform"
description="Build cross-platform experiences with the AeThex SDK. One API for identity, data sync, and authentication across metaverse platforms."
canonical={typeof window !== "undefined" ? window.location.href : undefined}
/>
{/* Subtle background */}
<div className="fixed inset-0 pointer-events-none overflow-hidden -z-10">
<div className="absolute top-0 right-0 w-[600px] h-[600px] rounded-full bg-primary/5 blur-3xl" />
<div className="absolute bottom-0 left-0 w-[500px] h-[500px] rounded-full bg-primary/5 blur-3xl" />
<div
className="absolute inset-0 opacity-[0.02]"
style={{
backgroundImage: `linear-gradient(to right, hsl(var(--primary)) 1px, transparent 1px),
linear-gradient(to bottom, hsl(var(--primary)) 1px, transparent 1px)`,
backgroundSize: "60px 60px",
}}
/>
</div>
<div className="relative min-h-screen">
{/* Hero */}
<section className="relative pt-20 pb-16 px-4">
<div className="max-w-6xl mx-auto">
<div className="grid lg:grid-cols-2 gap-12 items-center">
{/* Left: Value Prop */}
<div className="space-y-6">
<Badge variant="outline" className="text-xs font-mono border-primary/30">
v2.4.0 — TypeScript SDK
</Badge>
<h1 className="text-4xl md:text-5xl lg:text-6xl font-bold tracking-tight">
One API for
<br />
<span className="text-primary">cross-platform games</span>
</h1>
<p className="text-lg text-muted-foreground max-w-lg">
Connect players across Roblox, Minecraft, Fortnite, and more.
Sync identity, achievements, and inventory with a single SDK.
</p>
<div className="flex flex-wrap gap-3 pt-2">
<Link to="/dev-platform/quick-start">
<Button size="lg" className="font-medium shadow-lg shadow-primary/25">
Get Started
<ArrowRight className="w-4 h-4 ml-2" />
</Button>
</Link>
<Link to="/dev-platform/api-reference">
<Button size="lg" variant="outline" className="font-medium">
<BookOpen className="w-4 h-4 mr-2" />
Read Docs
</Button>
</Link>
</div>
{/* Install command */}
<div className="flex items-center gap-2 pt-4">
<code className="flex-1 bg-muted px-4 py-2.5 rounded-lg font-mono text-sm border">
npm install @aethex/sdk
</code>
<Button
size="sm"
variant="ghost"
onClick={() => navigator.clipboard.writeText("npm install @aethex/sdk")}
>
<Copy className="w-4 h-4" />
</Button>
</div>
{/* Stats row */}
<div className="grid grid-cols-4 gap-4 pt-6">
{stats.map((stat) => (
<div key={stat.label} className="text-center">
<p className="text-2xl md:text-3xl font-bold text-primary">{stat.value}</p>
<p className="text-xs text-muted-foreground">{stat.label}</p>
</div>
))}
</div>
</div>
{/* Right: Code Example */}
<div className="relative">
<Card className="bg-zinc-950 border-zinc-800 overflow-hidden shadow-2xl">
<div className="flex items-center justify-between px-4 py-2 border-b border-zinc-800">
<div className="flex items-center gap-2">
<div className="w-3 h-3 rounded-full bg-red-500/80" />
<div className="w-3 h-3 rounded-full bg-yellow-500/80" />
<div className="w-3 h-3 rounded-full bg-green-500/80" />
</div>
<span className="text-xs text-zinc-500 font-mono">app.ts</span>
<Button
size="sm"
variant="ghost"
className="h-6 text-zinc-400 hover:text-white"
onClick={copyCode}
>
{copied ? <Check className="w-3 h-3" /> : <Copy className="w-3 h-3" />}
</Button>
</div>
<pre className="p-4 text-sm overflow-x-auto">
<code className="text-zinc-300 font-mono whitespace-pre-wrap">
<span className="text-purple-400">import</span>
<span className="text-zinc-300"> {"{"} </span>
<span className="text-yellow-300">AeThex</span>
<span className="text-zinc-300"> {"}"} </span>
<span className="text-purple-400">from</span>
<span className="text-green-400"> '@aethex/sdk'</span>
<span className="text-zinc-300">;</span>
{"\n\n"}
<span className="text-purple-400">const</span>
<span className="text-blue-300"> client</span>
<span className="text-zinc-300"> = </span>
<span className="text-purple-400">new</span>
<span className="text-yellow-300"> AeThex</span>
<span className="text-zinc-300">{"({ "}</span>
<span className="text-blue-300">apiKey</span>
<span className="text-zinc-300">: </span>
<span className="text-blue-300">process.env.</span>
<span className="text-zinc-300">AETHEX_KEY {"});"}</span>
{"\n\n"}
<span className="text-zinc-600">// Authenticate user across platforms</span>
{"\n"}
<span className="text-purple-400">const</span>
<span className="text-blue-300"> user</span>
<span className="text-zinc-300"> = </span>
<span className="text-purple-400">await</span>
<span className="text-blue-300"> client</span>
<span className="text-zinc-300">.passport.</span>
<span className="text-yellow-300">authenticate</span>
<span className="text-zinc-300">{"({"}</span>
{"\n"}
<span className="text-zinc-300">{" "}</span>
<span className="text-blue-300">platform</span>
<span className="text-zinc-300">: </span>
<span className="text-green-400">'roblox'</span>
<span className="text-zinc-300">,</span>
{"\n"}
<span className="text-zinc-300">{" "}</span>
<span className="text-blue-300">userId</span>
<span className="text-zinc-300">: </span>
<span className="text-green-400">'123456789'</span>
{"\n"}
<span className="text-zinc-300">{"});"}</span>
{"\n\n"}
<span className="text-zinc-600">// Sync achievements, inventory, progress</span>
{"\n"}
<span className="text-purple-400">await</span>
<span className="text-blue-300"> client</span>
<span className="text-zinc-300">.</span>
<span className="text-yellow-300">sync</span>
<span className="text-zinc-300">{"({"}</span>
{"\n"}
<span className="text-zinc-300">{" "}</span>
<span className="text-blue-300">achievements</span>
<span className="text-zinc-300">: </span>
<span className="text-blue-300">user</span>
<span className="text-zinc-300">.achievements,</span>
{"\n"}
<span className="text-zinc-300">{" "}</span>
<span className="text-blue-300">inventory</span>
<span className="text-zinc-300">: </span>
<span className="text-blue-300">user</span>
<span className="text-zinc-300">.inventory,</span>
{"\n"}
<span className="text-zinc-300">{" "}</span>
<span className="text-blue-300">progress</span>
<span className="text-zinc-300">: </span>
<span className="text-blue-300">user</span>
<span className="text-zinc-300">.gameProgress</span>
{"\n"}
<span className="text-zinc-300">{"});"}</span>
</code>
</pre>
</Card>
</div>
</div>
</div>
</section>
{/* Platforms */}
<section className="py-6 px-4 border-y border-border/30">
<div className="max-w-6xl mx-auto">
<div className="flex flex-wrap items-center justify-center gap-6 md:gap-10 text-muted-foreground">
<span className="text-sm font-medium">Works with:</span>
{platforms.map((platform) => (
<span key={platform} className="text-sm font-medium hover:text-foreground transition-colors cursor-default">
{platform}
</span>
))}
</div>
</div>
</section>
{/* Ecosystem Pillars */}
<section className="py-20 px-4">
<div className="max-w-6xl mx-auto space-y-12">
<div className="text-center space-y-4">
<h2 className="text-3xl md:text-4xl font-bold">The AeThex Ecosystem</h2>
<p className="text-muted-foreground max-w-2xl mx-auto">
Six interconnected realms with specialized APIs for every use case
</p>
</div>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-5">
{ecosystemPillars.map((pillar) => (
<Link key={pillar.title} to={pillar.href}>
<Card className="group p-6 h-full hover:border-primary/40 transition-all duration-200 hover:shadow-lg hover:shadow-primary/5">
<div className="space-y-4">
<div className={`w-14 h-14 rounded-xl bg-gradient-to-br ${pillar.gradient} flex items-center justify-center shadow-lg group-hover:scale-105 transition-transform`}>
<pillar.icon className="w-7 h-7 text-white" />
</div>
<div className="space-y-2">
<h3 className="text-xl font-semibold group-hover:text-primary transition-colors">
{pillar.title}
</h3>
<p className="text-muted-foreground text-sm">
{pillar.description}
</p>
</div>
<div className="flex items-center text-primary text-sm font-medium group-hover:translate-x-1 transition-transform">
Explore <ArrowRight className="w-4 h-4 ml-1" />
</div>
</div>
</Card>
</Link>
))}
</div>
</div>
</section>
{/* Features */}
<section className="py-20 px-4 bg-muted/30">
<div className="max-w-6xl mx-auto space-y-12">
<div className="text-center space-y-4">
<h2 className="text-3xl md:text-4xl font-bold">Built for game developers</h2>
<p className="text-muted-foreground max-w-2xl mx-auto">
Everything you need to connect players across platforms
</p>
</div>
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-5">
{features.map((feature) => (
<Card key={feature.title} className="p-5 space-y-3">
<div className="w-11 h-11 rounded-lg bg-primary/10 flex items-center justify-center">
<feature.icon className="w-5 h-5 text-primary" />
</div>
<h3 className="font-semibold">{feature.title}</h3>
<p className="text-sm text-muted-foreground">{feature.description}</p>
</Card>
))}
</div>
</div>
</section>
{/* CTA */}
<section className="py-20 px-4">
<div className="max-w-4xl mx-auto">
<Card className="p-8 md:p-12 text-center space-y-6 bg-gradient-to-br from-primary/10 via-primary/5 to-background border-primary/20">
<Zap className="w-12 h-12 text-primary mx-auto" />
<h2 className="text-3xl md:text-4xl font-bold">Start building today</h2>
<p className="text-muted-foreground max-w-lg mx-auto">
Get your API key and integrate in minutes. Free tier includes 10K API calls/month.
</p>
<div className="flex flex-wrap gap-4 justify-center pt-2">
<Link to="/dev-platform/dashboard">
<Button size="lg" className="shadow-lg shadow-primary/25">
Get API Key
<ArrowRight className="w-4 h-4 ml-2" />
</Button>
</Link>
<Link to="/dev-platform/quick-start">
<Button size="lg" variant="outline">
View Quick Start
</Button>
</Link>
</div>
</Card>
</div>
</section>
{/* Footer spacer */}
<div className="pb-16" />
</div>
</Layout>
);
}

View file

@ -154,9 +154,9 @@ export default function Labs() {
{/* Cyberpunk Background Effects */}
<div className="pointer-events-none absolute inset-0 opacity-[0.12] [background-image:radial-gradient(circle_at_top,#facc15_0,rgba(0,0,0,0.45)_55%,rgba(0,0,0,0.9)_100%)]" />
<div className="pointer-events-none absolute inset-0 bg-[linear-gradient(transparent_0,transparent_calc(100%-1px),rgba(250,204,21,0.05)_calc(100%-1px))] bg-[length:100%_32px]" />
<div className="pointer-events-none absolute inset-0 opacity-[0.08] [background-image:linear-gradient(90deg,rgba(251,191,36,0.1)_1px,transparent_1px),linear-gradient(0deg,rgba(251,191,36,0.1)_1px,transparent_1px)] [background-size:50px_50px] animate-pulse" />
<div className="pointer-events-none absolute top-20 left-10 w-96 h-96 bg-yellow-500/20 rounded-full mix-blend-multiply filter blur-3xl animate-pulse" />
<div className="pointer-events-none absolute bottom-20 right-10 w-96 h-96 bg-yellow-600/10 rounded-full mix-blend-multiply filter blur-3xl animate-pulse" />
<div className="pointer-events-none absolute inset-0 opacity-[0.08] [background-image:linear-gradient(90deg,rgba(251,191,36,0.1)_1px,transparent_1px),linear-gradient(0deg,rgba(251,191,36,0.1)_1px,transparent_1px)] [background-size:50px_50px]" />
<div className="pointer-events-none absolute top-20 left-10 w-96 h-96 bg-yellow-500/20 rounded-full mix-blend-multiply filter blur-3xl" />
<div className="pointer-events-none absolute bottom-20 right-10 w-96 h-96 bg-yellow-600/10 rounded-full mix-blend-multiply filter blur-3xl" />
<main className="relative z-10">
{/* Hero Section */}

View file

@ -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` });
}
};

View file

@ -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 = (

View file

@ -1 +1 @@
v2.72.7
v2.75.0

View file

@ -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';