diff --git a/astro-site/.astro/settings.json b/astro-site/.astro/settings.json index ce198b8..6d76a53 100644 --- a/astro-site/.astro/settings.json +++ b/astro-site/.astro/settings.json @@ -1,5 +1,5 @@ { "_variables": { - "lastUpdateCheck": 1768189288502 + "lastUpdateCheck": 1770417750117 } } \ No newline at end of file diff --git a/astro-site/astro.config.mjs b/astro-site/astro.config.mjs index 7ce9cc3..4c89e4d 100644 --- a/astro-site/astro.config.mjs +++ b/astro-site/astro.config.mjs @@ -5,4 +5,8 @@ import react from '@astrojs/react'; export default defineConfig({ integrations: [tailwind(), react()], site: 'https://aethex-connect.com', + server: { + port: 4321, + host: 'localhost' + } }); diff --git a/astro-site/src/components/aethex/AeThexProvider.jsx b/astro-site/src/components/aethex/AeThexProvider.jsx new file mode 100644 index 0000000..0971c64 --- /dev/null +++ b/astro-site/src/components/aethex/AeThexProvider.jsx @@ -0,0 +1,275 @@ +/** + * AeThex Provider + * Main context provider for AeThex Connect - handles auth, chat, and real-time features + */ + +import React, { createContext, useContext, useState, useEffect, useCallback } from 'react'; +import { io } from 'socket.io-client'; + +const API_URL = import.meta.env?.VITE_API_URL || 'http://localhost:3000'; +const API_BASE = `${API_URL}/api`; + +const AeThexContext = createContext(null); + +export function useAeThex() { + return useContext(AeThexContext); +} + +export function AeThexProvider({ children }) { + // Auth state + const [user, setUser] = useState(null); + const [token, setTokenState] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + // Socket state + const [socket, setSocket] = useState(null); + const [connected, setConnected] = useState(false); + + // Chat state + const [servers, setServers] = useState([]); + const [channels, setChannels] = useState([]); + const [messages, setMessages] = useState([]); + const [currentServer, setCurrentServer] = useState(null); + const [currentChannel, setCurrentChannel] = useState(null); + const [onlineUsers, setOnlineUsers] = useState([]); + + // Token management + const setToken = useCallback((newToken) => { + setTokenState(newToken); + if (newToken) { + localStorage.setItem('aethex_token', newToken); + } else { + localStorage.removeItem('aethex_token'); + } + }, []); + + // API request helper + const apiRequest = useCallback(async (endpoint, options = {}) => { + const headers = { + 'Content-Type': 'application/json', + ...options.headers, + }; + + const currentToken = token || localStorage.getItem('aethex_token'); + if (currentToken) { + headers['Authorization'] = `Bearer ${currentToken}`; + } + + const response = await fetch(`${API_BASE}${endpoint}`, { + ...options, + headers, + }); + + const data = await response.json(); + if (!response.ok) { + throw new Error(data.error || 'Request failed'); + } + return data; + }, [token]); + + // Auth functions + const login = useCallback(async (email, password) => { + setError(null); + setLoading(true); + try { + const response = await apiRequest('/auth/login', { + method: 'POST', + body: JSON.stringify({ email, password }), + }); + if (response.success) { + setToken(response.data.token); + setUser(response.data.user); + return { success: true }; + } + throw new Error(response.error || 'Login failed'); + } catch (err) { + setError(err.message); + return { success: false, error: err.message }; + } finally { + setLoading(false); + } + }, [apiRequest, setToken]); + + const register = useCallback(async (email, password, username, displayName) => { + setError(null); + setLoading(true); + try { + const response = await apiRequest('/auth/register', { + method: 'POST', + body: JSON.stringify({ email, password, username, displayName }), + }); + if (response.success) { + setToken(response.data.token); + setUser(response.data.user); + return { success: true }; + } + throw new Error(response.error || 'Registration failed'); + } catch (err) { + setError(err.message); + return { success: false, error: err.message }; + } finally { + setLoading(false); + } + }, [apiRequest, setToken]); + + const demoLogin = useCallback(async () => { + setError(null); + setLoading(true); + try { + const response = await apiRequest('/auth/demo', { + method: 'POST', + }); + if (response.success) { + setToken(response.data.token); + setUser(response.data.user); + return { success: true }; + } + throw new Error(response.error || 'Demo login failed'); + } catch (err) { + setError(err.message); + return { success: false, error: err.message }; + } finally { + setLoading(false); + } + }, [apiRequest, setToken]); + + const logout = useCallback(() => { + setToken(null); + setUser(null); + if (socket) { + socket.disconnect(); + setSocket(null); + } + setConnected(false); + }, [socket, setToken]); + + // Socket connection + const connectSocket = useCallback((authToken) => { + if (socket) { + socket.disconnect(); + } + + const newSocket = io(API_URL, { + auth: { token: authToken }, + reconnection: true, + reconnectionDelay: 1000, + reconnectionAttempts: 10, + transports: ['websocket', 'polling'] + }); + + newSocket.on('connect', () => { + console.log('✓ Connected to AeThex Connect'); + setConnected(true); + }); + + newSocket.on('disconnect', () => { + console.log('✗ Disconnected from AeThex Connect'); + setConnected(false); + }); + + newSocket.on('message:new', (message) => { + setMessages(prev => [...prev, message]); + }); + + newSocket.on('presence:online', (users) => { + setOnlineUsers(users); + }); + + setSocket(newSocket); + return newSocket; + }, [socket]); + + // Chat functions + const sendMessage = useCallback((content) => { + if (!socket || !connected || !currentChannel) return; + socket.emit('channel:message', { + channelId: currentChannel.id, + content + }); + }, [socket, connected, currentChannel]); + + const joinChannel = useCallback((channelId) => { + if (!socket || !connected) return; + socket.emit('channel:join', { channelId }); + }, [socket, connected]); + + // Check auth on mount + useEffect(() => { + const checkAuth = async () => { + const storedToken = localStorage.getItem('aethex_token'); + if (storedToken) { + setTokenState(storedToken); + try { + const response = await fetch(`${API_BASE}/auth/me`, { + headers: { 'Authorization': `Bearer ${storedToken}` } + }); + const data = await response.json(); + if (data.success) { + setUser(data.data); + connectSocket(storedToken); + } else { + localStorage.removeItem('aethex_token'); + } + } catch (err) { + console.error('Auth check failed:', err); + localStorage.removeItem('aethex_token'); + } + } + setLoading(false); + }; + + checkAuth(); + + return () => { + if (socket) { + socket.disconnect(); + } + }; + }, []); + + // Connect socket when user logs in + useEffect(() => { + if (user && token && !socket) { + connectSocket(token); + } + }, [user, token]); + + const value = { + // Auth + user, + loading, + error, + isAuthenticated: !!user, + login, + register, + demoLogin, + logout, + + // Socket + socket, + connected, + + // Chat + servers, + channels, + messages, + currentServer, + currentChannel, + onlineUsers, + setCurrentServer, + setCurrentChannel, + setMessages, + sendMessage, + joinChannel, + + // API helper + apiRequest + }; + + return ( + + {children} + + ); +} diff --git a/astro-site/src/components/auth/AccountLinker.jsx b/astro-site/src/components/auth/AccountLinker.jsx index 282466c..dd35fa3 100644 --- a/astro-site/src/components/auth/AccountLinker.jsx +++ b/astro-site/src/components/auth/AccountLinker.jsx @@ -1,18 +1,13 @@ - - import React, { useState } from "react"; -import { useMatrix } from "../matrix/MatrixProvider.jsx"; - +import { useAeThex } from "../aethex/AeThexProvider.jsx"; /** - * UI for linking AeThex and Matrix accounts. - * 1. User logs in with AeThex credentials (simulate for now) - * 2. User logs in with Matrix credentials - * 3. Store mapping in localStorage (or call backend in real app) + * UI for AeThex account login/registration */ export default function AccountLinker({ onLinked }) { - const matrixCtx = useMatrix(); - if (!matrixCtx) { + const aethex = useAeThex(); + + if (!aethex) { return (
@@ -24,7 +19,7 @@ export default function AccountLinker({ onLinked }) {
Hang tight!
- We’re prepping your login experience… + We're prepping your login experience…
+ + ); +} diff --git a/astro-site/src/components/auth/LoginIsland.jsx b/astro-site/src/components/auth/LoginIsland.jsx index 189de02..4b9ce14 100644 --- a/astro-site/src/components/auth/LoginIsland.jsx +++ b/astro-site/src/components/auth/LoginIsland.jsx @@ -1,11 +1,11 @@ import React from "react"; -import { MatrixProvider } from "../matrix/MatrixProvider.jsx"; -import AccountLinker from "./AccountLinker.jsx"; +import { AeThexProvider } from "../aethex/AeThexProvider.jsx"; +import LoginForm from "./LoginForm.jsx"; export default function LoginIsland() { return ( - - {}} /> - + + + ); } diff --git a/astro-site/src/components/auth/SupabaseLogin.jsx b/astro-site/src/components/auth/SupabaseLogin.jsx deleted file mode 100644 index 2300916..0000000 --- a/astro-site/src/components/auth/SupabaseLogin.jsx +++ /dev/null @@ -1,70 +0,0 @@ -import React, { useState } from "react"; -import { createClient } from "@supabase/supabase-js"; - -const supabase = createClient( - import.meta.env.PUBLIC_SUPABASE_URL, - import.meta.env.PUBLIC_SUPABASE_ANON_KEY -); - -export default function SupabaseLogin() { - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [error, setError] = useState(null); - const [loading, setLoading] = useState(false); - const [success, setSuccess] = useState(false); - - const handleLogin = async (e) => { - e.preventDefault(); - setLoading(true); - setError(null); - setSuccess(false); - const { error } = await supabase.auth.signInWithPassword({ email, password }); - setLoading(false); - if (error) { - setError(error.message); - } else { - setSuccess(true); - // Optionally redirect or reload - window.location.href = "/app"; - } - }; - - return ( -
-
- AeThex Logo -

AeThex Connect

-

Sign in to your account

- {error &&
{error}
} - {success &&
Login successful!
} -
- setEmail(e.target.value)} - autoFocus - /> - setPassword(e.target.value)} - /> - -
-
- By continuing, you agree to the Terms of Service and Privacy Policy. -
-
- -
- ); -} diff --git a/astro-site/src/components/mockup/ChatArea.jsx b/astro-site/src/components/mockup/ChatArea.jsx index fed4d27..6cf9928 100644 --- a/astro-site/src/components/mockup/ChatArea.jsx +++ b/astro-site/src/components/mockup/ChatArea.jsx @@ -1,30 +1,28 @@ import React, { useEffect } from "react"; import Message from "./Message"; import MessageInput from "./MessageInput"; -import { useMatrix } from "../matrix/MatrixProvider.jsx"; +import { useAeThex } from "../aethex/AeThexProvider.jsx"; import DemoLoginButton from "./DemoLoginButton.jsx"; -// Default room to join (replace with your Matrix room ID) -const DEFAULT_ROOM_ID = "!foundation:matrix.org"; +// Default channel to join +const DEFAULT_CHANNEL_ID = "general"; export default function ChatArea() { - const { messages, joinRoom, currentRoomId, user, login, loading } = useMatrix(); + const { messages, joinChannel, currentChannelId, user, demoLogin, loading, isAuthenticated } = useAeThex(); - // Join the default room on login + // Join the default channel on login useEffect(() => { - if (user && !currentRoomId) { - joinRoom(DEFAULT_ROOM_ID); + if (isAuthenticated && user && !currentChannelId) { + joinChannel(DEFAULT_CHANNEL_ID); } - }, [user, currentRoomId, joinRoom]); + }, [isAuthenticated, user, currentChannelId, joinChannel]); // Demo login handler - const handleDemoLogin = () => { - // Use a public Matrix test account or a known demo account - // You can change these credentials as needed - login("@mrpiglr:matrix.org", "Max!FTW2023!", "https://matrix.org"); + const handleDemoLogin = async () => { + await demoLogin(); }; - if (!user) { + if (!isAuthenticated || !user) { return (
@@ -49,7 +47,7 @@ export default function ChatArea() {
{messages && messages.length > 0 ? ( messages.map((msg, i) => ( - + )) ) : (
No messages yet.
@@ -65,19 +63,15 @@ export default function ChatArea() { } -// Helper to convert Matrix event to Message props -function matrixToMessageProps(event) { - if (!event) return {}; - if (event.type === "m.room.message") { - return { - type: "user", - author: event.sender?.split(":")[0]?.replace("@", "") || "User", - text: event.content?.body || "", - time: new Date(event.origin_server_ts).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }), - avatar: event.sender?.charAt(1)?.toUpperCase() || "U", - avatarBg: "from-blue-600 to-blue-900", - }; - } - // Add system message mapping if needed - return { type: "system", label: "MATRIX", text: JSON.stringify(event), className: "foundation" }; +// Helper to convert AeThex message to Message props +function messageToProps(msg) { + if (!msg) return {}; + return { + type: "user", + author: msg.sender?.display_name || msg.sender?.username || "User", + text: msg.content || "", + time: new Date(msg.created_at).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }), + avatar: (msg.sender?.display_name || msg.sender?.username || "U").charAt(0).toUpperCase(), + avatarBg: "from-blue-600 to-blue-900", + }; } diff --git a/astro-site/src/components/mockup/MessageInput.jsx b/astro-site/src/components/mockup/MessageInput.jsx index 271eb9c..3328f48 100644 --- a/astro-site/src/components/mockup/MessageInput.jsx +++ b/astro-site/src/components/mockup/MessageInput.jsx @@ -1,16 +1,16 @@ import React, { useState } from "react"; -import { useMatrix } from "../matrix/MatrixProvider.jsx"; +import { useAeThex } from "../aethex/AeThexProvider.jsx"; -const DEFAULT_ROOM_ID = "!foundation:matrix.org"; +const DEFAULT_CHANNEL_ID = "general"; export default function MessageInput() { const [text, setText] = useState(""); - const { sendMessage, user, currentRoomId } = useMatrix(); + const { sendMessage, user, currentChannelId, isAuthenticated } = useAeThex(); const handleSend = async (e) => { e.preventDefault(); - if (!text.trim() || !user) return; - await sendMessage(currentRoomId || DEFAULT_ROOM_ID, text); + if (!text.trim() || !user || !isAuthenticated) return; + await sendMessage(currentChannelId || DEFAULT_CHANNEL_ID, text); setText(""); }; @@ -24,7 +24,7 @@ export default function MessageInput() { maxLength={2000} value={text} onChange={e => setText(e.target.value)} - disabled={!user} + disabled={!user || !isAuthenticated} /> diff --git a/astro-site/src/components/webrtc/WebRTCProvider.jsx b/astro-site/src/components/webrtc/WebRTCProvider.jsx index e60d6a8..4313b84 100644 --- a/astro-site/src/components/webrtc/WebRTCProvider.jsx +++ b/astro-site/src/components/webrtc/WebRTCProvider.jsx @@ -1,7 +1,7 @@ import React, { createContext, useContext, useRef, useState, useEffect, useCallback } from "react"; import Peer from "simple-peer"; -import { useMatrix } from "../matrix/MatrixProvider.jsx"; +import { useAeThex } from "../aethex/AeThexProvider.jsx"; const WebRTCContext = createContext(null); @@ -13,31 +13,32 @@ export function WebRTCProvider({ children }) { const [peers, setPeers] = useState([]); // [{ peerId, peer, stream }] const [localStream, setLocalStream] = useState(null); const [joined, setJoined] = useState(false); + const [currentVoiceChannel, setCurrentVoiceChannel] = useState(null); const peersRef = useRef({}); - const matrix = useMatrix(); - if (!matrix) { - // Optionally render a fallback or nothing if Matrix context is not available + const aethex = useAeThex(); + + if (!aethex) { return null; } - const { client, user, currentRoomId } = matrix; - const SIGNAL_EVENT = "org.aethex.voice.signal"; - const VOICE_ROOM = currentRoomId || "!foundation:matrix.org"; + + const { socket, user, isAuthenticated } = aethex; - // Helper: Send signal via Matrix event + // Helper: Send signal via Socket.io const sendSignal = useCallback((to, data) => { - if (!client || !VOICE_ROOM) return; - client.sendEvent(VOICE_ROOM, SIGNAL_EVENT, { to, from: user?.userId, data }, ""); - }, [client, VOICE_ROOM, user]); + if (!socket || !currentVoiceChannel) return; + socket.emit('voice:signal', { to, channelId: currentVoiceChannel, data }); + }, [socket, currentVoiceChannel]); // Join a voice channel (start local audio, announce self) - const joinVoice = async () => { - if (localStream || !client || !user) return; + const joinVoice = async (channelId) => { + if (localStream || !socket || !user || !isAuthenticated) return; try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); setLocalStream(stream); + setCurrentVoiceChannel(channelId); setJoined(true); // Announce self to others - client.sendEvent(VOICE_ROOM, SIGNAL_EVENT, { join: true, from: user.userId }, ""); + socket.emit('voice:join', { channelId }); } catch (err) { alert("Could not access microphone: " + err.message); } @@ -53,57 +54,78 @@ export function WebRTCProvider({ children }) { peersRef.current = {}; setPeers([]); setJoined(false); - if (client && user) { - client.sendEvent(VOICE_ROOM, SIGNAL_EVENT, { leave: true, from: user.userId }, ""); + if (socket && currentVoiceChannel) { + socket.emit('voice:leave', { channelId: currentVoiceChannel }); } + setCurrentVoiceChannel(null); }; - // Handle incoming Matrix signal events + // Handle incoming Socket.io signal events useEffect(() => { - if (!client || !user || !VOICE_ROOM) return; - const handler = (event) => { - if (event.getType() !== SIGNAL_EVENT) return; - const { from, to, data, join, leave } = event.getContent(); - if (from === user.userId) return; - // Handle join: create peer if not exists - if (join) { - if (!peersRef.current[from]) { - const initiator = user.userId > from; // simple deterministic initiator - const peer = new Peer({ initiator, trickle: false, stream: localStream }); - peer.on("signal", signal => sendSignal(from, signal)); - peer.on("stream", remoteStream => { - setPeers(p => [...p, { peerId: from, peer, stream: remoteStream }]); - }); - peer.on("close", () => { - setPeers(p => p.filter(x => x.peerId !== from)); - delete peersRef.current[from]; - }); - peersRef.current[from] = { peer }; - } - } - // Handle leave: remove peer - if (leave && peersRef.current[from]) { - peersRef.current[from].peer.destroy(); - delete peersRef.current[from]; - setPeers(p => p.filter(x => x.peerId !== from)); - } - // Handle signal: pass to peer - if (data && peersRef.current[from]) { - peersRef.current[from].peer.signal(data); + if (!socket || !user || !isAuthenticated) return; + + const handleUserJoined = ({ userId, channelId }) => { + if (userId === user.id || channelId !== currentVoiceChannel || !localStream) return; + + if (!peersRef.current[userId]) { + const initiator = user.id > userId; // deterministic initiator + const peer = new Peer({ initiator, trickle: false, stream: localStream }); + + peer.on("signal", signal => sendSignal(userId, signal)); + peer.on("stream", remoteStream => { + setPeers(p => [...p, { peerId: userId, peer, stream: remoteStream }]); + }); + peer.on("close", () => { + setPeers(p => p.filter(x => x.peerId !== userId)); + delete peersRef.current[userId]; + }); + + peersRef.current[userId] = { peer }; } }; - client.on("event", handler); - return () => client.removeListener("event", handler); - }, [client, user, VOICE_ROOM, localStream, sendSignal]); - - // Announce self to new joiners - useEffect(() => { - if (!joined || !client || !user) return; - client.sendEvent(VOICE_ROOM, SIGNAL_EVENT, { join: true, from: user.userId }, ""); - }, [joined, client, user, VOICE_ROOM]); + + const handleUserLeft = ({ userId }) => { + if (peersRef.current[userId]) { + peersRef.current[userId].peer.destroy(); + delete peersRef.current[userId]; + setPeers(p => p.filter(x => x.peerId !== userId)); + } + }; + + const handleSignal = ({ from, data }) => { + if (peersRef.current[from]) { + peersRef.current[from].peer.signal(data); + } else if (localStream) { + // Create peer for late joiners + const peer = new Peer({ initiator: false, trickle: false, stream: localStream }); + + peer.on("signal", signal => sendSignal(from, signal)); + peer.on("stream", remoteStream => { + setPeers(p => [...p, { peerId: from, peer, stream: remoteStream }]); + }); + peer.on("close", () => { + setPeers(p => p.filter(x => x.peerId !== from)); + delete peersRef.current[from]; + }); + + peer.signal(data); + peersRef.current[from] = { peer }; + } + }; + + socket.on('voice:user_joined', handleUserJoined); + socket.on('voice:user_left', handleUserLeft); + socket.on('voice:signal', handleSignal); + + return () => { + socket.off('voice:user_joined', handleUserJoined); + socket.off('voice:user_left', handleUserLeft); + socket.off('voice:signal', handleSignal); + }; + }, [socket, user, isAuthenticated, currentVoiceChannel, localStream, sendSignal]); return ( - + {children} ); diff --git a/astro-site/src/pages/_mockup.jsx b/astro-site/src/pages/_mockup.jsx index 84ec9d5..31a45bf 100644 --- a/astro-site/src/pages/_mockup.jsx +++ b/astro-site/src/pages/_mockup.jsx @@ -1,2 +1,2 @@ -// Removed: This page is deprecated. Use /app for the full platform UI. \ No newline at end of file +// Removed: This page is deprecated. Use /app for the full platform UI. diff --git a/astro-site/src/pages/images.astro b/astro-site/src/pages/images.astro new file mode 100644 index 0000000..71c557d --- /dev/null +++ b/astro-site/src/pages/images.astro @@ -0,0 +1,154 @@ +--- +/** + * Image Gallery Demo - Shows available Unsplash assets + */ +--- + + + + + + AeThex Image Assets + + + + ← Back to Home +

🎨 AeThex Image Assets

+

+ Free stock images from Unsplash + DiceBear avatars +

+ +

📸 Preset Background Images

+
+
+ Hero +
+
Hero Background
+
Gaming setup - perfect for landing pages
+
+
+
+ Login +
+
Login Background
+
Retro gaming aesthetic
+
+
+
+ Chat +
+
Chat Background
+
Dark abstract pattern
+
+
+
+ Server +
+
Server Banner
+
Esports/competitive gaming
+
+
+
+ Profile +
+
Profile Banner
+
Gaming aesthetic
+
+
+
+ Voice +
+
Voice Channel
+
Audio waves visualization
+
+
+
+ Premium +
+
Premium Banner
+
Purple gradient - perfect for upgrades
+
+
+
+ +

👤 DiceBear Avatars

+

Auto-generated based on username seed

+
+ Avatar 1 + Avatar 2 + Avatar 3 + Avatar 4 + Avatar 5 + Avatar 6 + Avatar 7 +
+ +

🎮 Dynamic Gaming Images

+

Random images from Unsplash (refreshes on reload)

+
+
+ Random Gaming +
+
Gaming + Neon
+
source.unsplash.com/600x400/?gaming,neon
+
+
+
+ Random Tech +
+
Technology + Dark
+
source.unsplash.com/600x400/?technology,dark
+
+
+
+ Random Abstract +
+
Abstract + Gradient
+
source.unsplash.com/600x400/?abstract,gradient
+
+
+
+ +
+

All images from Unsplash (free for commercial use)

+

Avatars from DiceBear (free for commercial use)

+
+ + diff --git a/astro-site/src/pages/login.astro b/astro-site/src/pages/login.astro index 475475e..81bd75c 100644 --- a/astro-site/src/pages/login.astro +++ b/astro-site/src/pages/login.astro @@ -1,9 +1,9 @@ --- import Layout from '../layouts/Layout.astro'; -import SupabaseLogin from '../components/auth/SupabaseLogin.jsx'; +import LoginIsland from '../components/auth/LoginIsland.jsx'; --- - + diff --git a/astro-site/src/pages/mockup.jsx b/astro-site/src/pages/mockup.jsx deleted file mode 100644 index 31a45bf..0000000 --- a/astro-site/src/pages/mockup.jsx +++ /dev/null @@ -1,2 +0,0 @@ - -// Removed: This page is deprecated. Use /app for the full platform UI. diff --git a/astro-site/src/react-app/Demo.jsx b/astro-site/src/react-app/Demo.jsx index 0f70b43..ea1289f 100644 --- a/astro-site/src/react-app/Demo.jsx +++ b/astro-site/src/react-app/Demo.jsx @@ -15,10 +15,47 @@ import './App.css'; */ function DemoContent() { const [activeTab, setActiveTab] = useState('overview'); - const { user, loading } = useAuth(); + const { user, loading, isAuthenticated, demoLogin, error } = useAuth(); + const [loginLoading, setLoginLoading] = useState(false); + + // Handle demo login + const handleDemoLogin = async () => { + setLoginLoading(true); + await demoLogin(); + setLoginLoading(false); + }; + + // Show login screen when not authenticated + if (!loading && !isAuthenticated) { + return ( +
+
{String.fromCodePoint(0x1F680)}
+

AeThex Connect Demo

+

Sign in to explore all features

+ {error &&

{error}

} + +
+ ); + } // Show loading state while auth initializes - if (loading || !user) { + if (loading) { return (
{String.fromCodePoint(0x1F680)}
@@ -47,7 +84,7 @@ function DemoContent() {
- {user.name} + {user.displayName || user.username} {user.email}
{user.verifiedDomain && ( diff --git a/astro-site/src/react-app/contexts/AuthContext.jsx b/astro-site/src/react-app/contexts/AuthContext.jsx index 8757c84..9d57352 100644 --- a/astro-site/src/react-app/contexts/AuthContext.jsx +++ b/astro-site/src/react-app/contexts/AuthContext.jsx @@ -1,11 +1,8 @@ -import React, { createContext, useContext, useState, useEffect } from 'react'; -import { createClient } from '@supabase/supabase-js'; +import React, { createContext, useContext, useState, useEffect, useCallback } from 'react'; -const supabase = createClient( - import.meta.env.PUBLIC_SUPABASE_URL, - import.meta.env.PUBLIC_SUPABASE_ANON_KEY -); +const API_URL = import.meta.env?.VITE_API_URL || 'http://localhost:3000'; +const API_BASE = `${API_URL}/api`; const AuthContext = createContext(); @@ -19,29 +16,164 @@ export function useAuth() { export function AuthProvider({ children }) { const [user, setUser] = useState(null); + const [token, setTokenState] = useState(null); const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + // Token management + const setToken = useCallback((newToken) => { + setTokenState(newToken); + if (newToken) { + localStorage.setItem('aethex_token', newToken); + } else { + localStorage.removeItem('aethex_token'); + } + }, []); + + // API helper + const apiRequest = useCallback(async (endpoint, options = {}) => { + const headers = { + 'Content-Type': 'application/json', + ...options.headers, + }; + const currentToken = token || localStorage.getItem('aethex_token'); + if (currentToken) { + headers['Authorization'] = `Bearer ${currentToken}`; + } + const response = await fetch(`${API_BASE}${endpoint}`, { + ...options, + headers, + }); + const data = await response.json(); + if (!response.ok) { + throw new Error(data.error || 'Request failed'); + } + return data; + }, [token]); + + // Check auth on mount useEffect(() => { - const getSession = async () => { + const checkAuth = async () => { setLoading(true); - const { data: { session } } = await supabase.auth.getSession(); - if (session?.user) { - setUser(session.user); - } else { - setUser(null); + const storedToken = localStorage.getItem('aethex_token'); + + if (storedToken) { + setTokenState(storedToken); + try { + const response = await fetch(`${API_BASE}/auth/me`, { + headers: { + 'Authorization': `Bearer ${storedToken}`, + }, + }); + const data = await response.json(); + if (response.ok && data.success) { + setUser(data.user); + } else { + // Token invalid, clear it + localStorage.removeItem('aethex_token'); + setTokenState(null); + setUser(null); + } + } catch (err) { + console.error('Auth check failed:', err); + setUser(null); + } } setLoading(false); }; - getSession(); - const { data: listener } = supabase.auth.onAuthStateChange((_event, session) => { - setUser(session?.user || null); - }); - return () => { - listener?.subscription.unsubscribe(); - }; + checkAuth(); }, []); - const value = { user, loading, supabase }; + // Login + const login = useCallback(async (email, password) => { + setError(null); + setLoading(true); + try { + const response = await apiRequest('/auth/login', { + method: 'POST', + body: JSON.stringify({ email, password }), + }); + if (response.success && response.data) { + setToken(response.data.token); + setUser(response.data.user); + return { success: true, user: response.data.user }; + } + return { success: false, error: response.error }; + } catch (err) { + setError(err.message); + return { success: false, error: err.message }; + } finally { + setLoading(false); + } + }, [apiRequest, setToken]); + + // Register + const register = useCallback(async (email, password, username, displayName) => { + setError(null); + setLoading(true); + try { + const response = await apiRequest('/auth/register', { + method: 'POST', + body: JSON.stringify({ email, password, username, displayName }), + }); + if (response.success && response.data) { + setToken(response.data.token); + setUser(response.data.user); + return { success: true, user: response.data.user }; + } + return { success: false, error: response.error }; + } catch (err) { + setError(err.message); + return { success: false, error: err.message }; + } finally { + setLoading(false); + } + }, [apiRequest, setToken]); + + // Demo login + const demoLogin = useCallback(async () => { + setError(null); + setLoading(true); + try { + const response = await apiRequest('/auth/demo', { + method: 'POST', + }); + if (response.success && response.data) { + setToken(response.data.token); + setUser(response.data.user); + return { success: true, user: response.data.user }; + } + return { success: false, error: response.error }; + } catch (err) { + setError(err.message); + return { success: false, error: err.message }; + } finally { + setLoading(false); + } + }, [apiRequest, setToken]); + + // Logout + const logout = useCallback(async () => { + try { + await apiRequest('/auth/logout', { method: 'POST' }); + } catch (err) { + console.error('Logout API call failed:', err); + } + setToken(null); + setUser(null); + }, [apiRequest, setToken]); + + const value = { + user, + token, + loading, + error, + isAuthenticated: !!user, + login, + register, + demoLogin, + logout, + }; return ( diff --git a/astro-site/src/react-app/contexts/SocketContext.jsx b/astro-site/src/react-app/contexts/SocketContext.jsx index 28c4563..782dcb5 100644 --- a/astro-site/src/react-app/contexts/SocketContext.jsx +++ b/astro-site/src/react-app/contexts/SocketContext.jsx @@ -3,27 +3,31 @@ * Provides Socket.io connection to all components */ -import React, { createContext, useContext, useEffect, useState } from 'react'; +import React, { createContext, useContext, useEffect, useState, useCallback } from 'react'; import { io } from 'socket.io-client'; +import { useAuth } from './AuthContext'; const SocketContext = createContext(null); export function SocketProvider({ children }) { const [socket, setSocket] = useState(null); const [connected, setConnected] = useState(false); + const [messages, setMessages] = useState([]); + const [typingUsers, setTypingUsers] = useState({}); + const { token, isAuthenticated } = useAuth(); useEffect(() => { - const token = localStorage.getItem('token'); + const authToken = token || localStorage.getItem('aethex_token'); - if (!token) { - console.log('No auth token, skipping socket connection'); + if (!authToken || !isAuthenticated) { + console.log('No auth, skipping socket connection'); return; } // Connect to Socket.io server const socketInstance = io(import.meta.env.VITE_API_URL || 'http://localhost:3000', { auth: { - token: token + token: authToken }, reconnection: true, reconnectionDelay: 1000, @@ -48,6 +52,28 @@ export function SocketProvider({ children }) { console.error('Socket error:', error); }); + // Message events + socketInstance.on('message', (msg) => { + setMessages(prev => [...prev, msg]); + }); + + socketInstance.on('user_typing', ({ conversationId, userId, username }) => { + setTypingUsers(prev => ({ + ...prev, + [conversationId]: { ...prev[conversationId], [userId]: username } + })); + }); + + socketInstance.on('user_stopped_typing', ({ conversationId, userId }) => { + setTypingUsers(prev => { + const updated = { ...prev }; + if (updated[conversationId]) { + delete updated[conversationId][userId]; + } + return updated; + }); + }); + setSocket(socketInstance); // Cleanup on unmount @@ -56,10 +82,39 @@ export function SocketProvider({ children }) { socketInstance.disconnect(); } }; - }, []); + }, [token, isAuthenticated]); + + // Send message + const sendMessage = useCallback((channelId, content) => { + if (socket && connected) { + socket.emit('message', { channelId, content }); + } + }, [socket, connected]); + + // Join channel + const joinChannel = useCallback((channelId) => { + if (socket && connected) { + socket.emit('join_conversation', { conversationId: channelId }); + } + }, [socket, connected]); + + // Send typing indicator + const sendTyping = useCallback((channelId, isTyping) => { + if (socket && connected) { + socket.emit(isTyping ? 'typing_start' : 'typing_stop', { conversationId: channelId }); + } + }, [socket, connected]); return ( - + {children} ); diff --git a/astro-site/src/react-app/utils/socket.js b/astro-site/src/react-app/utils/socket.js index 188de1e..9fbf34e 100644 --- a/astro-site/src/react-app/utils/socket.js +++ b/astro-site/src/react-app/utils/socket.js @@ -1,23 +1,18 @@ // socket.js -// Example Socket.io client for AeThex Connect (Supabase JWT auth) +// Socket.io client for AeThex Connect import { io } from 'socket.io-client'; -import { createClient } from '@supabase/supabase-js'; -// Initialize Supabase client -const supabase = createClient( - import.meta.env.PUBLIC_SUPABASE_URL, - import.meta.env.PUBLIC_SUPABASE_ANON_KEY -); +const API_URL = import.meta.env?.VITE_API_URL || 'http://localhost:3001'; export async function connectSocket() { - // Get current session (JWT) - const { data: { session } } = await supabase.auth.getSession(); - if (!session) throw new Error('Not authenticated'); + // Get AeThex token from localStorage + const token = localStorage.getItem('aethex_token'); + if (!token) throw new Error('Not authenticated'); // Connect to signaling server - const socket = io(import.meta.env.PUBLIC_SIGNALING_SERVER_URL, { - auth: { token: session.access_token } + const socket = io(API_URL, { + auth: { token } }); // Example: handle connection diff --git a/astro-site/src/utils/unsplash.js b/astro-site/src/utils/unsplash.js new file mode 100644 index 0000000..70c88fd --- /dev/null +++ b/astro-site/src/utils/unsplash.js @@ -0,0 +1,106 @@ +/** + * Unsplash Image Utility + * Provides free stock images for AeThex Connect + * Uses Unsplash Source API (no key required for basic usage) + */ + +// Curated gaming/tech collection IDs from Unsplash +const COLLECTIONS = { + gaming: '1424240', // Gaming collection + tech: '3582603', // Technology collection + neon: '1459961', // Neon/cyberpunk aesthetic + dark: '827743', // Dark moody shots + abstract: '1065976', // Abstract patterns +}; + +/** + * Get a random image URL from Unsplash + * @param {Object} options + * @param {number} options.width - Image width + * @param {number} options.height - Image height + * @param {string} options.query - Search query + * @param {string} options.collection - Collection ID + * @returns {string} Image URL + */ +export function getUnsplashImage({ width = 1920, height = 1080, query, collection } = {}) { + const base = 'https://source.unsplash.com'; + + if (collection && COLLECTIONS[collection]) { + return `${base}/collection/${COLLECTIONS[collection]}/${width}x${height}`; + } + + if (query) { + return `${base}/${width}x${height}/?${encodeURIComponent(query)}`; + } + + return `${base}/random/${width}x${height}`; +} + +/** + * Get a gaming-themed background + */ +export function getGamingBackground(width = 1920, height = 1080) { + return getUnsplashImage({ width, height, query: 'gaming,neon,dark' }); +} + +/** + * Get a tech-themed background + */ +export function getTechBackground(width = 1920, height = 1080) { + return getUnsplashImage({ width, height, query: 'technology,dark,abstract' }); +} + +/** + * Get a random avatar placeholder + * Uses specific seeds for consistency + */ +export function getAvatarImage(seed, size = 200) { + // Use DiceBear for avatars (more reliable than Unsplash for small images) + const styles = ['adventurer', 'avataaars', 'bottts', 'fun-emoji', 'lorelei', 'notionists', 'personas']; + const style = styles[Math.abs(hashCode(seed)) % styles.length]; + return `https://api.dicebear.com/7.x/${style}/svg?seed=${encodeURIComponent(seed)}&size=${size}`; +} + +/** + * Get abstract pattern for cards/backgrounds + */ +export function getAbstractPattern(width = 800, height = 600) { + return getUnsplashImage({ width, height, query: 'abstract,gradient,dark' }); +} + +/** + * Prebuilt image URLs for specific use cases + */ +export const PRESET_IMAGES = { + heroBackground: 'https://images.unsplash.com/photo-1538481199705-c710c4e965fc?w=1920&q=80', // Gaming setup + loginBackground: 'https://images.unsplash.com/photo-1550745165-9bc0b252726f?w=1920&q=80', // Retro gaming + chatBackground: 'https://images.unsplash.com/photo-1614850523459-c2f4c699c52e?w=1920&q=80', // Dark abstract + serverBanner: 'https://images.unsplash.com/photo-1542751371-adc38448a05e?w=1200&q=80', // Esports + profileBanner: 'https://images.unsplash.com/photo-1511512578047-dfb367046420?w=1200&q=80', // Gaming aesthetic + defaultAvatar: 'https://images.unsplash.com/photo-1566577134770-3d85bb3a9cc4?w=400&q=80', // Abstract + voiceChannel: 'https://images.unsplash.com/photo-1598488035139-bdbb2231ce04?w=800&q=80', // Audio waves + premiumBanner: 'https://images.unsplash.com/photo-1557682250-33bd709cbe85?w=1200&q=80', // Purple gradient +}; + +/** + * Simple hash function for consistent results + */ +function hashCode(str) { + let hash = 0; + for (let i = 0; i < str.length; i++) { + const char = str.charCodeAt(i); + hash = ((hash << 5) - hash) + char; + hash = hash & hash; + } + return hash; +} + +export default { + getUnsplashImage, + getGamingBackground, + getTechBackground, + getAvatarImage, + getAbstractPattern, + PRESET_IMAGES, + COLLECTIONS, +}; diff --git a/package-lock.json b/package-lock.json index 766c6ce..be4125c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "dependencies": { "@supabase/supabase-js": "^2.90.1", "bcrypt": "^5.1.1", + "bcryptjs": "^3.0.3", "cors": "^2.8.5", "dotenv": "^16.3.1", "ethers": "^6.10.0", @@ -5044,6 +5045,15 @@ "node": ">= 10.0.0" } }, + "node_modules/bcryptjs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz", + "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==", + "license": "BSD-3-Clause", + "bin": { + "bcrypt": "bin/bcrypt" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", diff --git a/package.json b/package.json index c7a7072..47acbcb 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "dependencies": { "@supabase/supabase-js": "^2.90.1", "bcrypt": "^5.1.1", + "bcryptjs": "^3.0.3", "cors": "^2.8.5", "dotenv": "^16.3.1", "ethers": "^6.10.0", diff --git a/packages/web/README.md b/packages/web/README.md deleted file mode 100644 index f60cd73..0000000 --- a/packages/web/README.md +++ /dev/null @@ -1,153 +0,0 @@ -# AeThex Connect - Web PWA - -Progressive Web App for AeThex Connect built with React, TypeScript, and Vite. - -## Features - -- **Real-time Messaging** - End-to-end encrypted conversations -- **Voice & Video Calls** - WebRTC-powered calls with crystal-clear quality -- **Offline Support** - Service worker enables offline messaging and call history -- **Dark Gaming Theme** - Modern, dark UI optimized for long sessions -- **Progressive Enhancement** - Works on desktop and mobile with native app-like experience -- **Responsive Design** - Tailwind CSS for mobile-first design - -## Project Structure - -``` -packages/web/src/ -├── index.tsx # App entry point with PWA registration -├── App.tsx # Main router and layout -├── pages/ -│ ├── LoginPage.tsx # Authentication -│ ├── HomePage.tsx # Dashboard -│ ├── ChatPage.tsx # Messaging interface -│ ├── CallsPage.tsx # Voice/video calls -│ └── SettingsPage.tsx # User preferences -├── layouts/ -│ └── MainLayout.tsx # Sidebar + header layout -├── components/ # Reusable UI components -├── hooks/ # Custom React hooks -├── utils/ -│ ├── serviceWorker.ts # PWA registration -│ └── webrtc.ts # WebRTC signaling -└── styles/ - ├── global.css # Tailwind + custom styles - └── app.css # Component styles -``` - -## Installation - -```bash -npm install --workspace=@aethex/web -``` - -## Development - -```bash -npm run dev -w @aethex/web -``` - -Open http://localhost:5173 in your browser. - -## Building - -```bash -npm run build -w @aethex/web -npm run preview -w @aethex/web -``` - -## PWA Features - -### Service Worker -- **Cache First**: Static assets cached for instant loading -- **Network First**: API requests fetch fresh data with cache fallback -- **Background Sync**: Offline messages synced when connection restored - -### Manifest -- **Installable**: Add to homescreen on mobile devices -- **Standalone**: Runs as full-screen app without browser UI -- **Icons**: Adaptive icons for modern devices - -### Offline Support -- Browse offline messages and call history -- Compose messages while offline (synced automatically) -- Works without internet connection - -## Redux State Management - -- **Auth Slice**: User authentication, tokens, profile -- **Messaging Slice**: Conversations, messages, read receipts -- **Calls Slice**: Active calls, call history, voice state - -## WebRTC Integration - -- **Peer Connections**: Manage multiple simultaneous calls -- **Signaling**: Socket.IO-based call negotiation -- **Media Streams**: Audio/video track control -- **ICE Candidates**: Automatic NAT traversal - -## Styling - -Uses **Tailwind CSS** with custom configuration: -- Dark gaming theme (purple/pink accents) -- Responsive breakpoints -- Smooth animations and transitions -- Custom components (@layer directives) - -## Environment Variables - -Create `.env.local`: - -``` -VITE_API_URL=http://localhost:3000 -VITE_SOCKET_URL=ws://localhost:3000 -VITE_SUPABASE_URL=your_supabase_url -VITE_SUPABASE_KEY=your_supabase_key -``` - -## Browser Support - -- Chrome/Edge 90+ -- Firefox 88+ -- Safari 14+ -- Mobile browsers with Service Worker support - -## Performance Optimizations - -- Code splitting with React Router -- Lazy component loading -- Image optimization -- CSS purging with Tailwind -- Service worker caching strategies - -## Testing - -```bash -npm run test -w @aethex/web -``` - -## Deployment - -### Vercel -```bash -vercel deploy -``` - -### Netlify -```bash -netlify deploy --prod --dir dist -``` - -### Docker -```bash -docker build -t aethex-web . -docker run -p 3000:80 aethex-web -``` - -## Contributing - -See main [CONTRIBUTING.md](../../CONTRIBUTING.md) - -## License - -MIT diff --git a/packages/web/package.json b/packages/web/package.json deleted file mode 100644 index 35b923a..0000000 --- a/packages/web/package.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "@aethex/web", - "version": "1.0.0", - "private": true, - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview", - "test": "vitest", - "lint": "eslint src --ext ts,tsx", - "clean": "rm -rf dist" - }, - "dependencies": { - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-router-dom": "^6.21.0", - "@reduxjs/toolkit": "^2.0.1", - "react-redux": "^9.0.4", - "socket.io-client": "^4.6.0", - "workbox-precaching": "^7.0.0", - "workbox-routing": "^7.0.0", - "workbox-strategies": "^7.0.0", - "workbox-background-sync": "^7.0.0", - "workbox-expiration": "^7.0.0" - }, - "devDependencies": { - "@types/react": "^18.2.45", - "@types/react-dom": "^18.2.18", - "@vitejs/plugin-react": "^4.2.1", - "vite": "^5.0.8", - "vite-plugin-pwa": "^0.17.4", - "vitest": "^1.1.0", - "typescript": "^5.3.3", - "tailwindcss": "^3.3.7", - "postcss": "^8.4.33", - "autoprefixer": "^10.4.17", - "eslint": "^8.55.0", - "@typescript-eslint/eslint-plugin": "^6.15.0", - "@typescript-eslint/parser": "^6.15.0" - } -} diff --git a/packages/web/postcss.config.js b/packages/web/postcss.config.js deleted file mode 100644 index 2ef30fc..0000000 --- a/packages/web/postcss.config.js +++ /dev/null @@ -1,9 +0,0 @@ -/** @type {import('postcss-load-config').Config} */ -const config = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; - -export default config; diff --git a/packages/web/public/index.html b/packages/web/public/index.html deleted file mode 100644 index b729237..0000000 --- a/packages/web/public/index.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - AeThex Connect - - - - - - - - - - - - - - - -
- - - diff --git a/packages/web/public/manifest.json b/packages/web/public/manifest.json deleted file mode 100644 index 58f6d4f..0000000 --- a/packages/web/public/manifest.json +++ /dev/null @@ -1,124 +0,0 @@ -{ - "name": "AeThex Connect", - "short_name": "AeThex", - "description": "Next-generation communication platform with blockchain identity verification, real-time messaging, voice/video calls, and premium features", - "start_url": "/", - "scope": "/", - "display": "standalone", - "orientation": "portrait-primary", - "background_color": "#0a0a0f", - "theme_color": "#a855f7", - "categories": ["communication", "productivity", "social"], - "icons": [ - { - "src": "/icon-72.png", - "sizes": "72x72", - "type": "image/png" - }, - { - "src": "/icon-96.png", - "sizes": "96x96", - "type": "image/png" - }, - { - "src": "/icon-128.png", - "sizes": "128x128", - "type": "image/png" - }, - { - "src": "/icon-144.png", - "sizes": "144x144", - "type": "image/png" - }, - { - "src": "/icon-152.png", - "sizes": "152x152", - "type": "image/png" - }, - { - "src": "/icon-192.png", - "sizes": "192x192", - "type": "image/png", - "purpose": "any maskable" - }, - { - "src": "/icon-384.png", - "sizes": "384x384", - "type": "image/png" - }, - { - "src": "/icon-512.png", - "sizes": "512x512", - "type": "image/png" - } - ], - "screenshots": [ - { - "src": "/screenshot-1.png", - "sizes": "1280x720", - "type": "image/png", - "label": "Main chat interface" - }, - { - "src": "/screenshot-2.png", - "sizes": "1280x720", - "type": "image/png", - "label": "Voice channel" - } - ], - "share_target": { - "action": "/share", - "method": "POST", - "enctype": "multipart/form-data", - "params": { - "title": "title", - "text": "text", - "url": "url", - "files": [ - { - "name": "media", - "accept": ["image/*", "video/*", "audio/*"] - } - ] - } - }, - "shortcuts": [ - { - "name": "New Message", - "short_name": "Message", - "description": "Start a new conversation", - "url": "/new-message", - "icons": [{ "src": "/icon-message.png", "sizes": "96x96" }] - }, - { - "name": "Voice Channel", - "short_name": "Voice", - "description": "Join voice channel", - "url": "/voice", - "icons": [{ "src": "/icon-voice.png", "sizes": "96x96" }] - }, - { - "name": "Friends", - "short_name": "Friends", - "description": "View friends list", - "url": "/friends", - "icons": [{ "src": "/icon-friends.png", "sizes": "96x96" }] - } - ], - "protocol_handlers": [ - { - "protocol": "web+aethex", - "url": "/handle?url=%s" - } - ], - "file_handlers": [ - { - "action": "/share", - "accept": { - "image/*": [".png", ".jpg", ".jpeg", ".gif", ".webp"], - "video/*": [".mp4", ".webm"], - "audio/*": [".mp3", ".wav", ".ogg"] - } - } - ] -} diff --git a/packages/web/public/service-worker.ts b/packages/web/public/service-worker.ts deleted file mode 100644 index 1fc6beb..0000000 --- a/packages/web/public/service-worker.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { precacheAndRoute } from 'workbox-precaching'; -import { registerRoute } from 'workbox-routing'; -import { NetworkFirst, CacheFirst, StaleWhileRevalidate } from 'workbox-strategies'; -import { BackgroundSyncPlugin } from 'workbox-background-sync'; -import { ExpirationPlugin } from 'workbox-expiration'; - -declare const self: ServiceWorkerGlobalScope; - -// Precache all build assets -precacheAndRoute(self.__WB_MANIFEST); - -// API requests - Network first, cache fallback -registerRoute( - ({ url }) => url.pathname.startsWith('/api/'), - new NetworkFirst({ - cacheName: 'api-cache', - plugins: [ - new ExpirationPlugin({ - maxEntries: 50, - maxAgeSeconds: 5 * 60, // 5 minutes - }), - ], - }) -); - -// Images - Cache first -registerRoute( - ({ request }) => request.destination === 'image', - new CacheFirst({ - cacheName: 'images', - plugins: [ - new ExpirationPlugin({ - maxEntries: 100, - maxAgeSeconds: 30 * 24 * 60 * 60, // 30 days - }), - ], - }) -); - -// Fonts - Cache first -registerRoute( - ({ request }) => request.destination === 'font', - new CacheFirst({ - cacheName: 'fonts', - plugins: [ - new ExpirationPlugin({ - maxEntries: 20, - maxAgeSeconds: 365 * 24 * 60 * 60, // 1 year - }), - ], - }) -); - -// Background sync for failed POST requests -const bgSyncPlugin = new BackgroundSyncPlugin('message-queue', { - maxRetentionTime: 24 * 60, // Retry for 24 hours -}); - -registerRoute( - ({ url }) => url.pathname.startsWith('/api/messages'), - new NetworkFirst({ - plugins: [bgSyncPlugin], - }), - 'POST' -); - -// Push notifications -self.addEventListener('push', (event) => { - const data = event.data?.json() || {}; - - const options: NotificationOptions = { - body: data.body || 'You have a new message', - icon: data.icon || '/icon-192.png', - badge: '/badge-96.png', - tag: data.tag || 'notification', - data: data.data || {}, - actions: data.actions || [ - { action: 'open', title: 'Open' }, - { action: 'dismiss', title: 'Dismiss' }, - ], - vibrate: [200, 100, 200], - requireInteraction: data.requireInteraction || false, - }; - - event.waitUntil( - self.registration.showNotification(data.title || 'AeThex Connect', options) - ); -}); - -// Notification click handler -self.addEventListener('notificationclick', (event) => { - event.notification.close(); - - if (event.action === 'open' || !event.action) { - const urlToOpen = event.notification.data?.url || '/'; - - event.waitUntil( - self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => { - // Check if there's already a window open - for (const client of clientList) { - if (client.url === urlToOpen && 'focus' in client) { - return client.focus(); - } - } - // Open new window if none exists - if (self.clients.openWindow) { - return self.clients.openWindow(urlToOpen); - } - }) - ); - } -}); - -// Handle notification actions (reply, etc.) -self.addEventListener('notificationclick', (event) => { - if (event.action === 'reply' && event.reply) { - // Send reply via background sync - event.waitUntil( - fetch('/api/messages', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - conversationId: event.notification.data.conversationId, - content: event.reply, - contentType: 'text', - }), - }) - ); - } -}); - -// Periodic background sync (for checking new messages when offline) -self.addEventListener('periodicsync', (event: any) => { - if (event.tag === 'check-messages') { - event.waitUntil(checkForNewMessages()); - } -}); - -async function checkForNewMessages() { - try { - const response = await fetch('/api/messages/unread'); - const data = await response.json(); - - if (data.count > 0) { - self.registration.showNotification('New Messages', { - body: `You have ${data.count} unread messages`, - icon: '/icon-192.png', - badge: '/badge-96.png', - tag: 'unread-messages', - }); - } - } catch (error) { - console.error('Error checking messages:', error); - } -} - -// Skip waiting and claim clients immediately -self.addEventListener('message', (event) => { - if (event.data && event.data.type === 'SKIP_WAITING') { - self.skipWaiting(); - } -}); - -self.addEventListener('activate', (event) => { - event.waitUntil(self.clients.claim()); -}); - -// Log service worker version -console.log('AeThex Connect Service Worker v1.0.0'); diff --git a/packages/web/public/sw.js b/packages/web/public/sw.js deleted file mode 100644 index 7b4df5b..0000000 --- a/packages/web/public/sw.js +++ /dev/null @@ -1,148 +0,0 @@ -const CACHE_NAME = 'aethex-connect-v1'; -const ASSETS_TO_CACHE = [ - '/', - '/index.html', - '/manifest.json', -]; - -// Install event -self.addEventListener('install', (event) => { - event.waitUntil( - caches.open(CACHE_NAME).then((cache) => { - console.log('Caching app shell'); - return cache.addAll(ASSETS_TO_CACHE).catch(err => { - console.log('Cache addAll error:', err); - // Don't fail installation if some assets can't be cached - }); - }) - ); - self.skipWaiting(); -}); - -// Activate event -self.addEventListener('activate', (event) => { - event.waitUntil( - caches.keys().then((cacheNames) => { - return Promise.all( - cacheNames.map((cacheName) => { - if (cacheName !== CACHE_NAME) { - console.log('Deleting old cache:', cacheName); - return caches.delete(cacheName); - } - }) - ); - }) - ); - self.clients.claim(); -}); - -// Fetch event - Network first, fallback to cache -self.addEventListener('fetch', (event) => { - const { request } = event; - - // Skip non-GET requests - if (request.method !== 'GET') { - return; - } - - // API requests - network first - if (request.url.includes('/api/')) { - event.respondWith( - fetch(request) - .then((response) => { - if (!response || response.status !== 200) { - return response; - } - - const responseClone = response.clone(); - caches.open(CACHE_NAME).then((cache) => { - cache.put(request, responseClone); - }); - - return response; - }) - .catch(() => { - return caches.match(request); - }) - ); - } else { - // Static assets - cache first - event.respondWith( - caches.match(request).then((response) => { - if (response) { - return response; - } - - return fetch(request).then((response) => { - if (!response || response.status !== 200 || response.type === 'error') { - return response; - } - - const responseClone = response.clone(); - caches.open(CACHE_NAME).then((cache) => { - cache.put(request, responseClone); - }); - - return response; - }); - }) - ); - } -}); - -// Background sync for offline messages -self.addEventListener('sync', (event) => { - if (event.tag === 'sync-messages') { - event.waitUntil(syncMessages()); - } -}); - -async function syncMessages() { - try { - const db = await openIndexedDB(); - const messages = await getPendingMessages(db); - - for (const message of messages) { - try { - await fetch('/api/messages', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(message), - }); - await deletePendingMessage(db, message.id); - } catch (error) { - console.error('Failed to sync message:', error); - } - } - } catch (error) { - console.error('Sync error:', error); - } -} - -function openIndexedDB() { - return new Promise((resolve, reject) => { - const request = indexedDB.open('aethex-connect', 1); - request.onerror = () => reject(request.error); - request.onsuccess = () => resolve(request.result); - }); -} - -function getPendingMessages(db) { - return new Promise((resolve, reject) => { - const transaction = db.transaction(['pendingMessages'], 'readonly'); - const store = transaction.objectStore('pendingMessages'); - const request = store.getAll(); - request.onerror = () => reject(request.error); - request.onsuccess = () => resolve(request.result); - }); -} - -function deletePendingMessage(db, id) { - return new Promise((resolve, reject) => { - const transaction = db.transaction(['pendingMessages'], 'readwrite'); - const store = transaction.objectStore('pendingMessages'); - const request = store.delete(id); - request.onerror = () => reject(request.error); - request.onsuccess = () => resolve(); - }); -} diff --git a/packages/web/src/App.tsx b/packages/web/src/App.tsx deleted file mode 100644 index d68220c..0000000 --- a/packages/web/src/App.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React, { useEffect } from 'react'; -import { Routes, Route, Navigate } from 'react-router-dom'; -import { useAppSelector } from './store'; -import MainLayout from './layouts/MainLayout'; -import HomePage from './pages/HomePage'; -import ChatPage from './pages/ChatPage'; -import CallsPage from './pages/CallsPage'; -import SettingsPage from './pages/SettingsPage'; -import LoginPage from './pages/LoginPage'; -import './styles/app.css'; - -export default function App() { - const { user, loading } = useAppSelector(state => state.auth); - - useEffect(() => { - // Restore auth state on app load - const token = localStorage.getItem('authToken'); - if (token && !user) { - // Token exists but user not loaded - this would be handled by Redux persist - console.log('Auth token found, waiting for hydration...'); - } - }, []); - - if (loading) { - return ( -
-
-
- ); - } - - return ( - - } /> - - - } /> - } /> - } /> - } /> - } /> - - - ) : ( - - ) - } - /> - - ); -} diff --git a/packages/web/src/index.tsx b/packages/web/src/index.tsx deleted file mode 100644 index f514777..0000000 --- a/packages/web/src/index.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import { Provider } from 'react-redux'; -import { BrowserRouter } from 'react-router-dom'; -import App from './App'; -import { store } from './store'; -import './styles/global.css'; -import { registerServiceWorker } from './utils/serviceWorker'; - -// Register service worker for PWA capabilities -registerServiceWorker(); - -ReactDOM.createRoot(document.getElementById('root')!).render( - - - - - - - -); diff --git a/packages/web/src/layouts/MainLayout.tsx b/packages/web/src/layouts/MainLayout.tsx deleted file mode 100644 index 26317cd..0000000 --- a/packages/web/src/layouts/MainLayout.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import React from 'react'; - -interface MainLayoutProps { - children: React.ReactNode; -} - -export default function MainLayout({ children }: MainLayoutProps) { - return ( -
- {/* Sidebar */} - - - {/* Main Content */} -
- {/* Header */} -
-
-

AeThex Connect

-
- - -
-
-
- - {/* Content Area */} -
- {children} -
-
-
- ); -} diff --git a/packages/web/src/pages/CallsPage.tsx b/packages/web/src/pages/CallsPage.tsx deleted file mode 100644 index d3277b1..0000000 --- a/packages/web/src/pages/CallsPage.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import React, { useState } from 'react'; -import { useAppSelector } from '../store'; - -export default function CallsPage() { - const { activeCall, callHistory } = useAppSelector(state => state.calls); - const [selectedCallHistory, setSelectedCallHistory] = useState(0); - - return ( -
- {activeCall ? ( - // Active Call View -
-
-
- 👤 -
-

{activeCall.participantName}

-

Call in progress

-

{activeCall.duration}

-
- -
- - - - - -
-
- ) : ( - // Call History -
-

Calls

- - {callHistory.length === 0 ? ( -
-

No call history yet. Start a call to get begun!

-
- ) : ( -
- {callHistory.map((call, index) => ( -
-
-
- 👤 -
-
-

{call.participantName}

-

- {call.type === 'voice' ? '📞 Voice Call' : '📹 Video Call'} • {call.duration} -

-
-
-
-

- {new Date(call.timestamp).toLocaleDateString()} -

- -
-
- ))} -
- )} -
- )} -
- ); -} diff --git a/packages/web/src/pages/ChatPage.tsx b/packages/web/src/pages/ChatPage.tsx deleted file mode 100644 index 24d26b4..0000000 --- a/packages/web/src/pages/ChatPage.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { useAppSelector } from '../store'; - -export default function ChatPage() { - const { conversations, messages } = useAppSelector(state => state.messaging); - const [selectedConversation, setSelectedConversation] = useState(conversations[0]?.id); - const [inputValue, setInputValue] = useState(''); - - const currentMessages = messages[selectedConversation] || []; - - const handleSendMessage = (e: React.FormEvent) => { - e.preventDefault(); - if (!inputValue.trim()) return; - - console.log('Sending message:', inputValue); - setInputValue(''); - }; - - return ( -
- {/* Conversations List */} -
-
-

Messages

- -
- -
- {conversations.map((conversation) => ( - - ))} -
-
- - {/* Chat Area */} -
- {selectedConversation ? ( - <> - {/* Chat Header */} -
-

- {conversations.find(c => c.id === selectedConversation)?.participantName} -

-
- - -
-
- - {/* Messages */} -
- {currentMessages.length === 0 ? ( -
-

No messages yet. Start the conversation!

-
- ) : ( - currentMessages.map((msg) => ( -
-
-

{msg.content}

-

{new Date(msg.createdAt).toLocaleTimeString()}

-
-
- )) - )} -
- - {/* Input */} -
-
- - setInputValue(e.target.value)} - placeholder="Type a message..." - className="flex-1 px-4 py-2 bg-gray-700 border border-gray-600 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:border-purple-500 focus:ring-1 focus:ring-purple-500" - /> - -
-
- - ) : ( -
-

Select a conversation to start messaging

-
- )} -
-
- ); -} diff --git a/packages/web/src/pages/HomePage.tsx b/packages/web/src/pages/HomePage.tsx deleted file mode 100644 index 8942dfc..0000000 --- a/packages/web/src/pages/HomePage.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import React from 'react'; - -export default function HomePage() { - return ( -
-
-

Welcome to AeThex Connect

-

- Your next-generation communication platform with blockchain identity verification, - real-time messaging, voice/video calls, and premium features. -

- -
- {/* Feature Cards */} -
-
💬
-

Instant Messaging

-

- End-to-end encrypted messages with real-time synchronization across all devices. -

-
- -
-
📞
-

Voice & Video

-

- Crystal-clear voice calls and HD video conferencing with WebRTC technology. -

-
- -
-
🎮
-

GameForge Integration

-

- Connect with game communities and manage channels with GameForge. -

-
- -
-
🔐
-

Verified Identity

-

- Blockchain-backed domain verification for authentic user identification. -

-
-
- -
-

Get Started Now

-

- Explore your messages, start a call, or customize your settings to unlock the full potential of AeThex Connect. -

-
- - -
-
-
-
- ); -} diff --git a/packages/web/src/pages/LoginPage.tsx b/packages/web/src/pages/LoginPage.tsx deleted file mode 100644 index 6a5abe5..0000000 --- a/packages/web/src/pages/LoginPage.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import React, { useState } from 'react'; -import { useAppDispatch, useAppSelector, loginAsync } from '../store'; - -export default function LoginPage() { - const dispatch = useAppDispatch(); - const { loading, error } = useAppSelector(state => state.auth); - const [email, setEmail] = useState(''); - const [password, setPassword] = useState(''); - const [isSignUp, setIsSignUp] = useState(false); - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - if (isSignUp) { - // Sign up logic would go here - console.log('Sign up:', email, password); - } else { - // Login - dispatch(loginAsync({ email, password })); - } - }; - - return ( -
-
- {/* Logo/Header */} -
-

- AeThex Connect -

-

Next-generation communication platform

-
- - {/* Form Card */} -
-

- {isSignUp ? 'Create Account' : 'Welcome Back'} -

- - {error && ( -
- {error} -
- )} - -
-
- - setEmail(e.target.value)} - className="w-full px-4 py-2 bg-gray-700 border border-gray-600 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:border-purple-500 focus:ring-1 focus:ring-purple-500 transition" - placeholder="you@example.com" - required - /> -
- -
- - setPassword(e.target.value)} - className="w-full px-4 py-2 bg-gray-700 border border-gray-600 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:border-purple-500 focus:ring-1 focus:ring-purple-500 transition" - placeholder="••••••••" - required - /> -
- - -
- -
- -
- -
-

Demo Credentials:

-

Email: demo@aethex.dev

-

Password: demo123

-
-
- - {/* Footer */} -

- © 2026 AeThex Corporation. All rights reserved. -

-
-
- ); -} diff --git a/packages/web/src/pages/SettingsPage.tsx b/packages/web/src/pages/SettingsPage.tsx deleted file mode 100644 index 0523b51..0000000 --- a/packages/web/src/pages/SettingsPage.tsx +++ /dev/null @@ -1,197 +0,0 @@ -import React, { useState } from 'react'; -import { useAppDispatch, useAppSelector, logoutAsync } from '../store'; - -export default function SettingsPage() { - const dispatch = useAppDispatch(); - const { user } = useAppSelector(state => state.auth); - const [activeTab, setActiveTab] = useState('profile'); - - const handleLogout = () => { - dispatch(logoutAsync()); - }; - - const tabs = [ - { id: 'profile', label: 'Profile', icon: '👤' }, - { id: 'privacy', label: 'Privacy & Security', icon: '🔒' }, - { id: 'notifications', label: 'Notifications', icon: '🔔' }, - { id: 'appearance', label: 'Appearance', icon: '🎨' }, - { id: 'about', label: 'About', icon: 'ℹ️' }, - ]; - - return ( -
-

Settings

- -
- {/* Sidebar Navigation */} -
- -
- - {/* Content Area */} -
- {activeTab === 'profile' && ( -
-
-

Profile Settings

-
-
- - -
-
- - -
-
- -