From 7097f8408b04fe1f35fcb736248affbb694f88d3 Mon Sep 17 00:00:00 2001 From: "Builder.io" Date: Wed, 6 Aug 2025 00:35:27 +0000 Subject: [PATCH] Prettier format pending files --- OAUTH_SETUP.md | 7 + SUPABASE_SETUP.md | 6 + client/components/SupabaseStatus.tsx | 18 +- client/contexts/AuthContext.tsx | 187 ++++---- client/lib/aethex-database-adapter.ts | 268 ++++++----- client/lib/database.types.ts | 646 +++++++++++++------------- client/lib/demo-storage.ts | 194 ++++---- client/lib/supabase-service.ts | 226 +++++---- client/lib/supabase.ts | 23 +- client/pages/Login.tsx | 20 +- 10 files changed, 877 insertions(+), 718 deletions(-) diff --git a/OAUTH_SETUP.md b/OAUTH_SETUP.md index 89d79bb3..2e20d03f 100644 --- a/OAUTH_SETUP.md +++ b/OAUTH_SETUP.md @@ -5,11 +5,13 @@ This guide will help you configure GitHub and Google OAuth login for your AeThex ## 🔧 Supabase OAuth Configuration ### 1. Access Your Supabase Dashboard + 1. Go to [app.supabase.com](https://app.supabase.com) 2. Select your project: `kmdeisowhtsalsekkzqd` 3. Navigate to **Authentication** > **Providers** ### 2. Configure Site URL + 1. Go to **Authentication** > **Settings** 2. Set your Site URL to: `https://e7c3806a9bfe4bdf9bb8a72a7f0d31cd-324f24a826ec4eb198c1a0eef.fly.dev` 3. Add Redirect URLs: @@ -19,6 +21,7 @@ This guide will help you configure GitHub and Google OAuth login for your AeThex ## 🐙 GitHub OAuth Setup ### 1. Create GitHub OAuth App + 1. Go to [GitHub Developer Settings](https://github.com/settings/developers) 2. Click **New OAuth App** 3. Fill in the details: @@ -29,6 +32,7 @@ This guide will help you configure GitHub and Google OAuth login for your AeThex 5. Copy the **Client ID** and **Client Secret** ### 2. Configure in Supabase + 1. In Supabase dashboard, go to **Authentication** > **Providers** 2. Find **GitHub** and click to configure 3. Enable GitHub provider @@ -38,6 +42,7 @@ This guide will help you configure GitHub and Google OAuth login for your AeThex ## 🌐 Google OAuth Setup ### 1. Create Google OAuth Credentials + 1. Go to [Google Cloud Console](https://console.cloud.google.com/) 2. Create a new project or select existing one 3. Enable **Google+ API** and **Google Identity API** @@ -49,6 +54,7 @@ This guide will help you configure GitHub and Google OAuth login for your AeThex 8. Copy the **Client ID** and **Client Secret** ### 2. Configure in Supabase + 1. In Supabase dashboard, go to **Authentication** > **Providers** 2. Find **Google** and click to configure 3. Enable Google provider @@ -74,6 +80,7 @@ Once configured: ## 🚀 Features Enabled With OAuth configured, users can: + - **One-click login** with GitHub or Google - **Automatic profile setup** with avatar and name from OAuth provider - **Seamless integration** with existing AeThex community platform diff --git a/SUPABASE_SETUP.md b/SUPABASE_SETUP.md index 30a4993e..ce506c10 100644 --- a/SUPABASE_SETUP.md +++ b/SUPABASE_SETUP.md @@ -218,11 +218,13 @@ CREATE TRIGGER update_community_posts_updated_at BEFORE UPDATE ON community_post Once set up, your AeThex app will have: ### 🔐 Authentication + - Email/password sign up and sign in - User session management - Profile creation and updates ### 🗄️ Database Features + - User profiles with extended information - Project portfolio management - Achievement system with XP and levels @@ -230,16 +232,19 @@ Once set up, your AeThex app will have: - Real-time notifications ### 🎮 Gamification + - User levels and XP tracking - Achievement system - Progress tracking ### 💬 Community + - User-generated content - Real-time updates - Comment system ### 📊 Dashboard + - Personalized user dashboard - Project tracking - Achievement display @@ -255,6 +260,7 @@ Once set up, your AeThex app will have: ## 7. Production Deployment For production: + 1. Update your site URL in Supabase Authentication settings 2. Add your production domain to redirect URLs 3. Update your environment variables in your hosting platform diff --git a/client/components/SupabaseStatus.tsx b/client/components/SupabaseStatus.tsx index 353e99a9..59ceb765 100644 --- a/client/components/SupabaseStatus.tsx +++ b/client/components/SupabaseStatus.tsx @@ -1,8 +1,8 @@ -import React from 'react'; -import { isSupabaseConfigured } from '@/lib/supabase'; -import { Alert, AlertDescription } from '@/components/ui/alert'; -import { Button } from '@/components/ui/button'; -import { Database, ExternalLink, Info } from 'lucide-react'; +import React from "react"; +import { isSupabaseConfigured } from "@/lib/supabase"; +import { Alert, AlertDescription } from "@/components/ui/alert"; +import { Button } from "@/components/ui/button"; +import { Database, ExternalLink, Info } from "lucide-react"; export default function SupabaseStatus() { // Only show if Supabase is not configured (demo mode) @@ -26,9 +26,11 @@ export default function SupabaseStatus() { className="border-blue-500/50 text-blue-300 hover:bg-blue-500/20" onClick={() => { // Hide the status notification - const notification = document.querySelector('[data-supabase-status]'); + const notification = document.querySelector( + "[data-supabase-status]", + ); if (notification) { - notification.style.display = 'none'; + notification.style.display = "none"; } }} > @@ -39,7 +41,7 @@ export default function SupabaseStatus() { size="sm" variant="outline" className="border-blue-500/50 text-blue-300 hover:bg-blue-500/20" - onClick={() => window.open('/login', '_self')} + onClick={() => window.open("/login", "_self")} > Try Login diff --git a/client/contexts/AuthContext.tsx b/client/contexts/AuthContext.tsx index 451e62d9..edef5f53 100644 --- a/client/contexts/AuthContext.tsx +++ b/client/contexts/AuthContext.tsx @@ -1,10 +1,14 @@ -import React, { createContext, useContext, useEffect, useState } from 'react'; -import { User, Session } from '@supabase/supabase-js'; -import { supabase, isSupabaseConfigured } from '@/lib/supabase'; -import { UserProfile } from '@/lib/database.types'; -import { aethexToast } from '@/lib/aethex-toast'; -import { DemoStorageService } from '@/lib/demo-storage'; -import { aethexUserService, aethexAchievementService, type AethexUserProfile } from '@/lib/aethex-database-adapter'; +import React, { createContext, useContext, useEffect, useState } from "react"; +import { User, Session } from "@supabase/supabase-js"; +import { supabase, isSupabaseConfigured } from "@/lib/supabase"; +import { UserProfile } from "@/lib/database.types"; +import { aethexToast } from "@/lib/aethex-toast"; +import { DemoStorageService } from "@/lib/demo-storage"; +import { + aethexUserService, + aethexAchievementService, + type AethexUserProfile, +} from "@/lib/aethex-database-adapter"; interface AuthContextType { user: User | null; @@ -12,8 +16,12 @@ interface AuthContextType { session: Session | null; loading: boolean; signIn: (email: string, password: string) => Promise; - signUp: (email: string, password: string, userData?: Partial) => Promise; - signInWithOAuth: (provider: 'github' | 'google') => Promise; + signUp: ( + email: string, + password: string, + userData?: Partial, + ) => Promise; + signInWithOAuth: (provider: "github" | "google") => Promise; signOut: () => Promise; updateProfile: (updates: Partial) => Promise; } @@ -23,12 +31,14 @@ const AuthContext = createContext(undefined); export const useAuth = () => { const context = useContext(AuthContext); if (context === undefined) { - throw new Error('useAuth must be used within an AuthProvider'); + throw new Error("useAuth must be used within an AuthProvider"); } return context; }; -export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { +export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ + children, +}) => { const [user, setUser] = useState(null); const [profile, setProfile] = useState(null); const [session, setSession] = useState(null); @@ -37,23 +47,28 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children useEffect(() => { // Check if Supabase is configured if (!isSupabaseConfigured) { - console.warn('Supabase is not configured. Please set up your environment variables.'); + console.warn( + "Supabase is not configured. Please set up your environment variables.", + ); setLoading(false); return; } // Get initial session - supabase.auth.getSession().then(({ data: { session } }) => { - setSession(session); - setUser(session?.user ?? null); - if (session?.user) { - fetchUserProfile(session.user.id); - } - setLoading(false); - }).catch((error) => { - console.error('Error getting session:', error); - setLoading(false); - }); + supabase.auth + .getSession() + .then(({ data: { session } }) => { + setSession(session); + setUser(session?.user ?? null); + if (session?.user) { + fetchUserProfile(session.user.id); + } + setLoading(false); + }) + .catch((error) => { + console.error("Error getting session:", error); + setLoading(false); + }); // Listen for auth changes const { @@ -69,26 +84,30 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children if (!profile && session.user.app_metadata?.provider) { try { await aethexUserService.createInitialProfile(session.user.id, { - username: session.user.user_metadata?.user_name || - session.user.user_metadata?.preferred_username || - session.user.email?.split('@')[0] || - `user_${Date.now()}`, - full_name: session.user.user_metadata?.full_name || - session.user.user_metadata?.name || - session.user.email?.split('@')[0], + username: + session.user.user_metadata?.user_name || + session.user.user_metadata?.preferred_username || + session.user.email?.split("@")[0] || + `user_${Date.now()}`, + full_name: + session.user.user_metadata?.full_name || + session.user.user_metadata?.name || + session.user.email?.split("@")[0], email: session.user.email, avatar_url: session.user.user_metadata?.avatar_url, - user_type: 'community_member', // Default for OAuth users - experience_level: 'beginner', + user_type: "community_member", // Default for OAuth users + experience_level: "beginner", }); // Fetch the newly created profile await fetchUserProfile(session.user.id); // Award onboarding achievement for OAuth users - await aethexAchievementService.checkAndAwardOnboardingAchievement(session.user.id); + await aethexAchievementService.checkAndAwardOnboardingAchievement( + session.user.id, + ); } catch (error) { - console.error('Error creating OAuth user profile:', error); + console.error("Error creating OAuth user profile:", error); } } } else { @@ -97,15 +116,15 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children setLoading(false); // Show toast notifications for auth events - if (event === 'SIGNED_IN') { + if (event === "SIGNED_IN") { aethexToast.success({ - title: 'Welcome back!', - description: 'Successfully signed in to AeThex OS' + title: "Welcome back!", + description: "Successfully signed in to AeThex OS", }); - } else if (event === 'SIGNED_OUT') { + } else if (event === "SIGNED_OUT") { aethexToast.info({ - title: 'Signed out', - description: 'Come back soon!' + title: "Signed out", + description: "Come back soon!", }); } }); @@ -113,7 +132,9 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children return () => subscription.unsubscribe(); }, []); - const fetchUserProfile = async (userId: string): Promise => { + const fetchUserProfile = async ( + userId: string, + ): Promise => { if (!isSupabaseConfigured) { // Initialize demo data and get profile DemoStorageService.initializeDemoData(); @@ -127,7 +148,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children setProfile(userProfile); return userProfile; } catch (error) { - console.error('Error fetching user profile:', error); + console.error("Error fetching user profile:", error); return null; } }; @@ -135,13 +156,14 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children const signIn = async (email: string, password: string) => { if (!isSupabaseConfigured) { aethexToast.warning({ - title: 'Demo Mode', - description: 'Supabase not configured. This is a demo - please set up your Supabase project.' + title: "Demo Mode", + description: + "Supabase not configured. This is a demo - please set up your Supabase project.", }); // Simulate successful login for demo setTimeout(() => { - setUser({ id: 'demo-user', email } as User); - setSession({ user: { id: 'demo-user', email } } as Session); + setUser({ id: "demo-user", email } as User); + setSession({ user: { id: "demo-user", email } } as Session); }, 500); return; } @@ -155,18 +177,23 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children if (error) throw error; } catch (error: any) { aethexToast.error({ - title: 'Sign in failed', - description: error.message + title: "Sign in failed", + description: error.message, }); throw error; } }; - const signUp = async (email: string, password: string, userData?: Partial) => { + const signUp = async ( + email: string, + password: string, + userData?: Partial, + ) => { if (!isSupabaseConfigured) { aethexToast.warning({ - title: 'Demo Mode', - description: 'Supabase not configured. This is a demo - please set up your Supabase project.' + title: "Demo Mode", + description: + "Supabase not configured. This is a demo - please set up your Supabase project.", }); return; } @@ -187,24 +214,25 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }); aethexToast.success({ - title: 'Account created!', - description: 'Please check your email to verify your account' + title: "Account created!", + description: "Please check your email to verify your account", }); } } catch (error: any) { aethexToast.error({ - title: 'Sign up failed', - description: error.message + title: "Sign up failed", + description: error.message, }); throw error; } }; - const signInWithOAuth = async (provider: 'github' | 'google') => { + const signInWithOAuth = async (provider: "github" | "google") => { if (!isSupabaseConfigured) { aethexToast.warning({ - title: 'Demo Mode', - description: 'OAuth login requires Supabase to be configured. Currently in demo mode.' + title: "Demo Mode", + description: + "OAuth login requires Supabase to be configured. Currently in demo mode.", }); return; } @@ -220,13 +248,13 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children if (error) throw error; aethexToast.success({ - title: 'Redirecting...', - description: `Signing in with ${provider}` + title: "Redirecting...", + description: `Signing in with ${provider}`, }); } catch (error: any) { aethexToast.error({ title: `${provider} sign in failed`, - description: error.message + description: error.message, }); throw error; } @@ -238,8 +266,8 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children setSession(null); setProfile(null); aethexToast.info({ - title: 'Signed out', - description: 'Demo session ended' + title: "Signed out", + description: "Demo session ended", }); return; } @@ -249,38 +277,43 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children if (error) throw error; } catch (error: any) { aethexToast.error({ - title: 'Sign out failed', - description: error.message + title: "Sign out failed", + description: error.message, }); throw error; } }; const updateProfile = async (updates: Partial) => { - if (!user) throw new Error('No user logged in'); + if (!user) throw new Error("No user logged in"); if (!isSupabaseConfigured) { // Use demo storage - const updatedProfile = DemoStorageService.updateUserProfile(updates as any); + const updatedProfile = DemoStorageService.updateUserProfile( + updates as any, + ); setProfile(updatedProfile as AethexUserProfile); aethexToast.success({ - title: 'Profile updated', - description: 'Your profile has been updated successfully' + title: "Profile updated", + description: "Your profile has been updated successfully", }); return; } try { - const updatedProfile = await aethexUserService.updateProfile(user.id, updates); + const updatedProfile = await aethexUserService.updateProfile( + user.id, + updates, + ); setProfile(updatedProfile); aethexToast.success({ - title: 'Profile updated', - description: 'Your profile has been updated successfully' + title: "Profile updated", + description: "Your profile has been updated successfully", }); } catch (error: any) { aethexToast.error({ - title: 'Update failed', - description: error.message + title: "Update failed", + description: error.message, }); throw error; } @@ -298,9 +331,5 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children updateProfile, }; - return ( - - {children} - - ); + return {children}; }; diff --git a/client/lib/aethex-database-adapter.ts b/client/lib/aethex-database-adapter.ts index 86b95d80..0462581e 100644 --- a/client/lib/aethex-database-adapter.ts +++ b/client/lib/aethex-database-adapter.ts @@ -1,9 +1,9 @@ // Database adapter for existing AeThex community platform // Maps existing schema to our application needs -import { supabase } from './supabase'; -import type { Database } from './database.types'; -import { aethexToast } from './aethex-toast'; +import { supabase } from "./supabase"; +import type { Database } from "./database.types"; +import { aethexToast } from "./aethex-toast"; // Extended user profile type that matches existing + new schema export interface AethexUserProfile { @@ -21,8 +21,8 @@ export interface AethexUserProfile { created_at: string; updated_at: string; // New AeThex app fields - user_type?: 'game_developer' | 'client' | 'community_member' | 'customer'; - experience_level?: 'beginner' | 'intermediate' | 'advanced' | 'expert'; + user_type?: "game_developer" | "client" | "community_member" | "customer"; + experience_level?: "beginner" | "intermediate" | "advanced" | "expert"; full_name?: string; location?: string; website_url?: string; @@ -38,7 +38,7 @@ export interface AethexProject { user_id: string; title: string; description?: string; - status: 'planning' | 'in_progress' | 'completed' | 'on_hold'; + status: "planning" | "in_progress" | "completed" | "on_hold"; technologies?: string[]; github_url?: string; demo_url?: string; @@ -69,56 +69,64 @@ export interface AethexUserAchievement { // User Profile Services export const aethexUserService = { async getCurrentUser(): Promise { - const { data: { user } } = await supabase.auth.getUser(); + const { + data: { user }, + } = await supabase.auth.getUser(); if (!user) return null; const { data, error } = await supabase - .from('profiles') - .select('*') - .eq('id', user.id) + .from("profiles") + .select("*") + .eq("id", user.id) .single(); if (error) { - console.error('Error fetching user profile:', error); + console.error("Error fetching user profile:", error); return null; } return data as AethexUserProfile; }, - async updateProfile(userId: string, updates: Partial): Promise { + async updateProfile( + userId: string, + updates: Partial, + ): Promise { const { data, error } = await supabase - .from('profiles') + .from("profiles") .update(updates) - .eq('id', userId) + .eq("id", userId) .select() .single(); if (error) { - console.error('Error updating profile:', error); + console.error("Error updating profile:", error); throw error; } return data as AethexUserProfile; }, - async createInitialProfile(userId: string, profileData: Partial): Promise { + async createInitialProfile( + userId: string, + profileData: Partial, + ): Promise { const { data, error } = await supabase - .from('profiles') + .from("profiles") .insert({ id: userId, username: profileData.username || `user_${Date.now()}`, - user_type: profileData.user_type || 'community_member', - experience_level: profileData.experience_level || 'beginner', + user_type: profileData.user_type || "community_member", + experience_level: profileData.experience_level || "beginner", full_name: profileData.full_name, email: profileData.email, - ...profileData + ...profileData, }) .select() .single(); if (error) { - console.error('Error creating profile:', error); + console.error("Error creating profile:", error); throw error; } @@ -127,39 +135,36 @@ export const aethexUserService = { async addUserInterests(userId: string, interests: string[]): Promise { // First, delete existing interests - await supabase - .from('user_interests') - .delete() - .eq('user_id', userId); + await supabase.from("user_interests").delete().eq("user_id", userId); // Insert new interests - const interestRows = interests.map(interest => ({ + const interestRows = interests.map((interest) => ({ user_id: userId, interest, })); const { error } = await supabase - .from('user_interests') + .from("user_interests") .insert(interestRows); if (error) { - console.error('Error adding interests:', error); + console.error("Error adding interests:", error); throw error; } }, async getUserInterests(userId: string): Promise { const { data, error } = await supabase - .from('user_interests') - .select('interest') - .eq('user_id', userId); + .from("user_interests") + .select("interest") + .eq("user_id", userId); if (error) { - console.error('Error fetching interests:', error); + console.error("Error fetching interests:", error); return []; } - return data.map(item => item.interest); + return data.map((item) => item.interest); }, }; @@ -167,44 +172,49 @@ export const aethexUserService = { export const aethexProjectService = { async getUserProjects(userId: string): Promise { const { data, error } = await supabase - .from('projects') - .select('*') - .eq('user_id', userId) - .order('created_at', { ascending: false }); + .from("projects") + .select("*") + .eq("user_id", userId) + .order("created_at", { ascending: false }); if (error) { - console.error('Error fetching projects:', error); + console.error("Error fetching projects:", error); return []; } return data as AethexProject[]; }, - async createProject(project: Omit): Promise { + async createProject( + project: Omit, + ): Promise { const { data, error } = await supabase - .from('projects') + .from("projects") .insert(project) .select() .single(); if (error) { - console.error('Error creating project:', error); + console.error("Error creating project:", error); throw error; } return data as AethexProject; }, - async updateProject(projectId: string, updates: Partial): Promise { + async updateProject( + projectId: string, + updates: Partial, + ): Promise { const { data, error } = await supabase - .from('projects') + .from("projects") .update(updates) - .eq('id', projectId) + .eq("id", projectId) .select() .single(); if (error) { - console.error('Error updating project:', error); + console.error("Error updating project:", error); throw error; } @@ -213,12 +223,12 @@ export const aethexProjectService = { async deleteProject(projectId: string): Promise { const { error } = await supabase - .from('projects') + .from("projects") .delete() - .eq('id', projectId); + .eq("id", projectId); if (error) { - console.error('Error deleting project:', error); + console.error("Error deleting project:", error); return false; } @@ -227,21 +237,23 @@ export const aethexProjectService = { async getAllProjects(limit = 10): Promise { const { data, error } = await supabase - .from('projects') - .select(` + .from("projects") + .select( + ` *, profiles!projects_user_id_fkey ( username, full_name, avatar_url ) - `) - .eq('status', 'completed') - .order('created_at', { ascending: false }) + `, + ) + .eq("status", "completed") + .order("created_at", { ascending: false }) .limit(limit); if (error) { - console.error('Error fetching all projects:', error); + console.error("Error fetching all projects:", error); return []; } @@ -253,12 +265,12 @@ export const aethexProjectService = { export const aethexAchievementService = { async getAllAchievements(): Promise { const { data, error } = await supabase - .from('achievements') - .select('*') - .order('points_reward', { ascending: false }); + .from("achievements") + .select("*") + .order("points_reward", { ascending: false }); if (error) { - console.error('Error fetching achievements:', error); + console.error("Error fetching achievements:", error); return []; } @@ -267,45 +279,48 @@ export const aethexAchievementService = { async getUserAchievements(userId: string): Promise { const { data, error } = await supabase - .from('user_achievements') - .select(` + .from("user_achievements") + .select( + ` unlocked_at, achievements (*) - `) - .eq('user_id', userId) - .order('unlocked_at', { ascending: false }); + `, + ) + .eq("user_id", userId) + .order("unlocked_at", { ascending: false }); if (error) { - console.error('Error fetching user achievements:', error); + console.error("Error fetching user achievements:", error); return []; } - return data.map(item => item.achievements).filter(Boolean) as AethexAchievement[]; + return data + .map((item) => item.achievements) + .filter(Boolean) as AethexAchievement[]; }, async awardAchievement(userId: string, achievementId: string): Promise { - const { error } = await supabase - .from('user_achievements') - .insert({ - user_id: userId, - achievement_id: achievementId, - }); + const { error } = await supabase.from("user_achievements").insert({ + user_id: userId, + achievement_id: achievementId, + }); - if (error && error.code !== '23505') { // Ignore duplicate key error - console.error('Error awarding achievement:', error); + if (error && error.code !== "23505") { + // Ignore duplicate key error + console.error("Error awarding achievement:", error); throw error; } // Get achievement details for toast const { data: achievement } = await supabase - .from('achievements') - .select('*') - .eq('id', achievementId) + .from("achievements") + .select("*") + .eq("id", achievementId) .single(); if (achievement) { aethexToast.aethex({ - title: 'Achievement Unlocked! 🎉', + title: "Achievement Unlocked! 🎉", description: `${achievement.icon} ${achievement.name} - ${achievement.description}`, duration: 8000, }); @@ -318,9 +333,9 @@ export const aethexAchievementService = { async updateUserXPAndLevel(userId: string, xpGained: number): Promise { // Get current user data const { data: profile } = await supabase - .from('profiles') - .select('total_xp, level, loyalty_points') - .eq('id', userId) + .from("profiles") + .select("total_xp, level, loyalty_points") + .eq("id", userId) .single(); if (!profile) return; @@ -331,23 +346,23 @@ export const aethexAchievementService = { // Update profile await supabase - .from('profiles') + .from("profiles") .update({ total_xp: newTotalXP, level: newLevel, loyalty_points: newLoyaltyPoints, }) - .eq('id', userId); + .eq("id", userId); // Check for level-up achievements if (newLevel > (profile.level || 1)) { if (newLevel >= 5) { const levelUpAchievement = await supabase - .from('achievements') - .select('id') - .eq('name', 'Level Master') + .from("achievements") + .select("id") + .eq("name", "Level Master") .single(); - + if (levelUpAchievement.data) { await this.awardAchievement(userId, levelUpAchievement.data.id); } @@ -357,9 +372,9 @@ export const aethexAchievementService = { async checkAndAwardOnboardingAchievement(userId: string): Promise { const { data: achievement } = await supabase - .from('achievements') - .select('id') - .eq('name', 'AeThex Explorer') + .from("achievements") + .select("id") + .eq("name", "AeThex Explorer") .single(); if (achievement) { @@ -369,13 +384,13 @@ export const aethexAchievementService = { async checkAndAwardProjectAchievements(userId: string): Promise { const projects = await aethexProjectService.getUserProjects(userId); - + // First project achievement if (projects.length >= 1) { const { data: achievement } = await supabase - .from('achievements') - .select('id') - .eq('name', 'Portfolio Creator') + .from("achievements") + .select("id") + .eq("name", "Portfolio Creator") .single(); if (achievement) { @@ -384,12 +399,12 @@ export const aethexAchievementService = { } // Project master achievement - const completedProjects = projects.filter(p => p.status === 'completed'); + const completedProjects = projects.filter((p) => p.status === "completed"); if (completedProjects.length >= 10) { const { data: achievement } = await supabase - .from('achievements') - .select('id') - .eq('name', 'Project Master') + .from("achievements") + .select("id") + .eq("name", "Project Master") .single(); if (achievement) { @@ -403,14 +418,14 @@ export const aethexAchievementService = { export const aethexNotificationService = { async getUserNotifications(userId: string): Promise { const { data, error } = await supabase - .from('notifications') - .select('*') - .eq('user_id', userId) - .order('created_at', { ascending: false }) + .from("notifications") + .select("*") + .eq("user_id", userId) + .order("created_at", { ascending: false }) .limit(10); if (error) { - console.error('Error fetching notifications:', error); + console.error("Error fetching notifications:", error); return []; } @@ -419,51 +434,56 @@ export const aethexNotificationService = { async markAsRead(notificationId: string): Promise { await supabase - .from('notifications') + .from("notifications") .update({ is_read: true }) - .eq('id', notificationId); + .eq("id", notificationId); }, - async createNotification(userId: string, type: string, data: any): Promise { - await supabase - .from('notifications') - .insert({ - user_id: userId, - type, - data, - }); + async createNotification( + userId: string, + type: string, + data: any, + ): Promise { + await supabase.from("notifications").insert({ + user_id: userId, + type, + data, + }); }, }; // Real-time subscriptions export const aethexRealtimeService = { - subscribeToUserNotifications(userId: string, callback: (notification: any) => void) { + subscribeToUserNotifications( + userId: string, + callback: (notification: any) => void, + ) { return supabase .channel(`notifications:${userId}`) .on( - 'postgres_changes', + "postgres_changes", { - event: 'INSERT', - schema: 'public', - table: 'notifications', + event: "INSERT", + schema: "public", + table: "notifications", filter: `user_id=eq.${userId}`, }, - callback + callback, ) .subscribe(); }, subscribeToProjects(callback: (project: any) => void) { return supabase - .channel('projects') + .channel("projects") .on( - 'postgres_changes', + "postgres_changes", { - event: 'INSERT', - schema: 'public', - table: 'projects', + event: "INSERT", + schema: "public", + table: "projects", }, - callback + callback, ) .subscribe(); }, diff --git a/client/lib/database.types.ts b/client/lib/database.types.ts index d4b9a159..c396b352 100644 --- a/client/lib/database.types.ts +++ b/client/lib/database.types.ts @@ -4,378 +4,394 @@ export type Json = | boolean | null | { [key: string]: Json | undefined } - | Json[] + | Json[]; export type Database = { public: { Tables: { achievements: { Row: { - badge_color: string | null - created_at: string - description: string | null - icon: string | null - id: string - name: string - xp_reward: number | null - } + badge_color: string | null; + created_at: string; + description: string | null; + icon: string | null; + id: string; + name: string; + xp_reward: number | null; + }; Insert: { - badge_color?: string | null - created_at?: string - description?: string | null - icon?: string | null - id?: string - name: string - xp_reward?: number | null - } + badge_color?: string | null; + created_at?: string; + description?: string | null; + icon?: string | null; + id?: string; + name: string; + xp_reward?: number | null; + }; Update: { - badge_color?: string | null - created_at?: string - description?: string | null - icon?: string | null - id?: string - name?: string - xp_reward?: number | null - } - Relationships: [] - } + badge_color?: string | null; + created_at?: string; + description?: string | null; + icon?: string | null; + id?: string; + name?: string; + xp_reward?: number | null; + }; + Relationships: []; + }; comments: { Row: { - author_id: string - content: string - created_at: string - id: string - post_id: string - } + author_id: string; + content: string; + created_at: string; + id: string; + post_id: string; + }; Insert: { - author_id: string - content: string - created_at?: string - id?: string - post_id: string - } + author_id: string; + content: string; + created_at?: string; + id?: string; + post_id: string; + }; Update: { - author_id?: string - content?: string - created_at?: string - id?: string - post_id?: string - } + author_id?: string; + content?: string; + created_at?: string; + id?: string; + post_id?: string; + }; Relationships: [ { - foreignKeyName: "comments_author_id_fkey" - columns: ["author_id"] - isOneToOne: false - referencedRelation: "user_profiles" - referencedColumns: ["id"] + foreignKeyName: "comments_author_id_fkey"; + columns: ["author_id"]; + isOneToOne: false; + referencedRelation: "user_profiles"; + referencedColumns: ["id"]; }, { - foreignKeyName: "comments_post_id_fkey" - columns: ["post_id"] - isOneToOne: false - referencedRelation: "community_posts" - referencedColumns: ["id"] - } - ] - } + foreignKeyName: "comments_post_id_fkey"; + columns: ["post_id"]; + isOneToOne: false; + referencedRelation: "community_posts"; + referencedColumns: ["id"]; + }, + ]; + }; community_posts: { Row: { - author_id: string - category: string | null - comments_count: number | null - content: string - created_at: string - id: string - is_published: boolean | null - likes_count: number | null - tags: string[] | null - title: string - updated_at: string - } + author_id: string; + category: string | null; + comments_count: number | null; + content: string; + created_at: string; + id: string; + is_published: boolean | null; + likes_count: number | null; + tags: string[] | null; + title: string; + updated_at: string; + }; Insert: { - author_id: string - category?: string | null - comments_count?: number | null - content: string - created_at?: string - id?: string - is_published?: boolean | null - likes_count?: number | null - tags?: string[] | null - title: string - updated_at?: string - } + author_id: string; + category?: string | null; + comments_count?: number | null; + content: string; + created_at?: string; + id?: string; + is_published?: boolean | null; + likes_count?: number | null; + tags?: string[] | null; + title: string; + updated_at?: string; + }; Update: { - author_id?: string - category?: string | null - comments_count?: number | null - content?: string - created_at?: string - id?: string - is_published?: boolean | null - likes_count?: number | null - tags?: string[] | null - title?: string - updated_at?: string - } + author_id?: string; + category?: string | null; + comments_count?: number | null; + content?: string; + created_at?: string; + id?: string; + is_published?: boolean | null; + likes_count?: number | null; + tags?: string[] | null; + title?: string; + updated_at?: string; + }; Relationships: [ { - foreignKeyName: "community_posts_author_id_fkey" - columns: ["author_id"] - isOneToOne: false - referencedRelation: "user_profiles" - referencedColumns: ["id"] - } - ] - } + foreignKeyName: "community_posts_author_id_fkey"; + columns: ["author_id"]; + isOneToOne: false; + referencedRelation: "user_profiles"; + referencedColumns: ["id"]; + }, + ]; + }; notifications: { Row: { - created_at: string - id: string - message: string | null - read: boolean | null - title: string - type: string | null - user_id: string - } + created_at: string; + id: string; + message: string | null; + read: boolean | null; + title: string; + type: string | null; + user_id: string; + }; Insert: { - created_at?: string - id?: string - message?: string | null - read?: boolean | null - title: string - type?: string | null - user_id: string - } + created_at?: string; + id?: string; + message?: string | null; + read?: boolean | null; + title: string; + type?: string | null; + user_id: string; + }; Update: { - created_at?: string - id?: string - message?: string | null - read?: boolean | null - title?: string - type?: string | null - user_id?: string - } + created_at?: string; + id?: string; + message?: string | null; + read?: boolean | null; + title?: string; + type?: string | null; + user_id?: string; + }; Relationships: [ { - foreignKeyName: "notifications_user_id_fkey" - columns: ["user_id"] - isOneToOne: false - referencedRelation: "user_profiles" - referencedColumns: ["id"] - } - ] - } + foreignKeyName: "notifications_user_id_fkey"; + columns: ["user_id"]; + isOneToOne: false; + referencedRelation: "user_profiles"; + referencedColumns: ["id"]; + }, + ]; + }; projects: { Row: { - created_at: string - demo_url: string | null - description: string | null - end_date: string | null - github_url: string | null - id: string - image_url: string | null - start_date: string | null - status: Database["public"]["Enums"]["project_status_enum"] | null - technologies: string[] | null - title: string - updated_at: string - user_id: string - } + created_at: string; + demo_url: string | null; + description: string | null; + end_date: string | null; + github_url: string | null; + id: string; + image_url: string | null; + start_date: string | null; + status: Database["public"]["Enums"]["project_status_enum"] | null; + technologies: string[] | null; + title: string; + updated_at: string; + user_id: string; + }; Insert: { - created_at?: string - demo_url?: string | null - description?: string | null - end_date?: string | null - github_url?: string | null - id?: string - image_url?: string | null - start_date?: string | null - status?: Database["public"]["Enums"]["project_status_enum"] | null - technologies?: string[] | null - title: string - updated_at?: string - user_id: string - } + created_at?: string; + demo_url?: string | null; + description?: string | null; + end_date?: string | null; + github_url?: string | null; + id?: string; + image_url?: string | null; + start_date?: string | null; + status?: Database["public"]["Enums"]["project_status_enum"] | null; + technologies?: string[] | null; + title: string; + updated_at?: string; + user_id: string; + }; Update: { - created_at?: string - demo_url?: string | null - description?: string | null - end_date?: string | null - github_url?: string | null - id?: string - image_url?: string | null - start_date?: string | null - status?: Database["public"]["Enums"]["project_status_enum"] | null - technologies?: string[] | null - title?: string - updated_at?: string - user_id?: string - } + created_at?: string; + demo_url?: string | null; + description?: string | null; + end_date?: string | null; + github_url?: string | null; + id?: string; + image_url?: string | null; + start_date?: string | null; + status?: Database["public"]["Enums"]["project_status_enum"] | null; + technologies?: string[] | null; + title?: string; + updated_at?: string; + user_id?: string; + }; Relationships: [ { - foreignKeyName: "projects_user_id_fkey" - columns: ["user_id"] - isOneToOne: false - referencedRelation: "user_profiles" - referencedColumns: ["id"] - } - ] - } + foreignKeyName: "projects_user_id_fkey"; + columns: ["user_id"]; + isOneToOne: false; + referencedRelation: "user_profiles"; + referencedColumns: ["id"]; + }, + ]; + }; user_achievements: { Row: { - achievement_id: string - earned_at: string - id: string - user_id: string - } + achievement_id: string; + earned_at: string; + id: string; + user_id: string; + }; Insert: { - achievement_id: string - earned_at?: string - id?: string - user_id: string - } + achievement_id: string; + earned_at?: string; + id?: string; + user_id: string; + }; Update: { - achievement_id?: string - earned_at?: string - id?: string - user_id?: string - } + achievement_id?: string; + earned_at?: string; + id?: string; + user_id?: string; + }; Relationships: [ { - foreignKeyName: "user_achievements_achievement_id_fkey" - columns: ["achievement_id"] - isOneToOne: false - referencedRelation: "achievements" - referencedColumns: ["id"] + foreignKeyName: "user_achievements_achievement_id_fkey"; + columns: ["achievement_id"]; + isOneToOne: false; + referencedRelation: "achievements"; + referencedColumns: ["id"]; }, { - foreignKeyName: "user_achievements_user_id_fkey" - columns: ["user_id"] - isOneToOne: false - referencedRelation: "user_profiles" - referencedColumns: ["id"] - } - ] - } + foreignKeyName: "user_achievements_user_id_fkey"; + columns: ["user_id"]; + isOneToOne: false; + referencedRelation: "user_profiles"; + referencedColumns: ["id"]; + }, + ]; + }; user_interests: { Row: { - created_at: string - id: string - interest: string - user_id: string - } + created_at: string; + id: string; + interest: string; + user_id: string; + }; Insert: { - created_at?: string - id?: string - interest: string - user_id: string - } + created_at?: string; + id?: string; + interest: string; + user_id: string; + }; Update: { - created_at?: string - id?: string - interest?: string - user_id?: string - } + created_at?: string; + id?: string; + interest?: string; + user_id?: string; + }; Relationships: [ { - foreignKeyName: "user_interests_user_id_fkey" - columns: ["user_id"] - isOneToOne: false - referencedRelation: "user_profiles" - referencedColumns: ["id"] - } - ] - } + foreignKeyName: "user_interests_user_id_fkey"; + columns: ["user_id"]; + isOneToOne: false; + referencedRelation: "user_profiles"; + referencedColumns: ["id"]; + }, + ]; + }; user_profiles: { Row: { - avatar_url: string | null - bio: string | null - created_at: string - experience_level: Database["public"]["Enums"]["experience_level_enum"] | null - full_name: string | null - github_url: string | null - id: string - level: number | null - linkedin_url: string | null - location: string | null - total_xp: number | null - twitter_url: string | null - updated_at: string - user_type: Database["public"]["Enums"]["user_type_enum"] - username: string | null - website_url: string | null - } + avatar_url: string | null; + bio: string | null; + created_at: string; + experience_level: + | Database["public"]["Enums"]["experience_level_enum"] + | null; + full_name: string | null; + github_url: string | null; + id: string; + level: number | null; + linkedin_url: string | null; + location: string | null; + total_xp: number | null; + twitter_url: string | null; + updated_at: string; + user_type: Database["public"]["Enums"]["user_type_enum"]; + username: string | null; + website_url: string | null; + }; Insert: { - avatar_url?: string | null - bio?: string | null - created_at?: string - experience_level?: Database["public"]["Enums"]["experience_level_enum"] | null - full_name?: string | null - github_url?: string | null - id: string - level?: number | null - linkedin_url?: string | null - location?: string | null - total_xp?: number | null - twitter_url?: string | null - updated_at?: string - user_type: Database["public"]["Enums"]["user_type_enum"] - username?: string | null - website_url?: string | null - } + avatar_url?: string | null; + bio?: string | null; + created_at?: string; + experience_level?: + | Database["public"]["Enums"]["experience_level_enum"] + | null; + full_name?: string | null; + github_url?: string | null; + id: string; + level?: number | null; + linkedin_url?: string | null; + location?: string | null; + total_xp?: number | null; + twitter_url?: string | null; + updated_at?: string; + user_type: Database["public"]["Enums"]["user_type_enum"]; + username?: string | null; + website_url?: string | null; + }; Update: { - avatar_url?: string | null - bio?: string | null - created_at?: string - experience_level?: Database["public"]["Enums"]["experience_level_enum"] | null - full_name?: string | null - github_url?: string | null - id?: string - level?: number | null - linkedin_url?: string | null - location?: string | null - total_xp?: number | null - twitter_url?: string | null - updated_at?: string - user_type?: Database["public"]["Enums"]["user_type_enum"] - username?: string | null - website_url?: string | null - } + avatar_url?: string | null; + bio?: string | null; + created_at?: string; + experience_level?: + | Database["public"]["Enums"]["experience_level_enum"] + | null; + full_name?: string | null; + github_url?: string | null; + id?: string; + level?: number | null; + linkedin_url?: string | null; + location?: string | null; + total_xp?: number | null; + twitter_url?: string | null; + updated_at?: string; + user_type?: Database["public"]["Enums"]["user_type_enum"]; + username?: string | null; + website_url?: string | null; + }; Relationships: [ { - foreignKeyName: "user_profiles_id_fkey" - columns: ["id"] - isOneToOne: true - referencedRelation: "users" - referencedColumns: ["id"] - } - ] - } - } + foreignKeyName: "user_profiles_id_fkey"; + columns: ["id"]; + isOneToOne: true; + referencedRelation: "users"; + referencedColumns: ["id"]; + }, + ]; + }; + }; Views: { - [_ in never]: never - } + [_ in never]: never; + }; Functions: { - [_ in never]: never - } + [_ in never]: never; + }; Enums: { - experience_level_enum: "beginner" | "intermediate" | "advanced" | "expert" - project_status_enum: "planning" | "in_progress" | "completed" | "on_hold" - user_type_enum: "game_developer" | "client" | "community_member" | "customer" - } + experience_level_enum: + | "beginner" + | "intermediate" + | "advanced" + | "expert"; + project_status_enum: "planning" | "in_progress" | "completed" | "on_hold"; + user_type_enum: + | "game_developer" + | "client" + | "community_member" + | "customer"; + }; CompositeTypes: { - [_ in never]: never - } - } -} + [_ in never]: never; + }; + }; +}; -export type UserProfile = Database['public']['Tables']['user_profiles']['Row']; -export type Project = Database['public']['Tables']['projects']['Row']; -export type Achievement = Database['public']['Tables']['achievements']['Row']; -export type CommunityPost = Database['public']['Tables']['community_posts']['Row']; -export type Notification = Database['public']['Tables']['notifications']['Row']; +export type UserProfile = Database["public"]["Tables"]["user_profiles"]["Row"]; +export type Project = Database["public"]["Tables"]["projects"]["Row"]; +export type Achievement = Database["public"]["Tables"]["achievements"]["Row"]; +export type CommunityPost = + Database["public"]["Tables"]["community_posts"]["Row"]; +export type Notification = Database["public"]["Tables"]["notifications"]["Row"]; -export type UserType = Database['public']['Enums']['user_type_enum']; -export type ExperienceLevel = Database['public']['Enums']['experience_level_enum']; -export type ProjectStatus = Database['public']['Enums']['project_status_enum']; +export type UserType = Database["public"]["Enums"]["user_type_enum"]; +export type ExperienceLevel = + Database["public"]["Enums"]["experience_level_enum"]; +export type ProjectStatus = Database["public"]["Enums"]["project_status_enum"]; diff --git a/client/lib/demo-storage.ts b/client/lib/demo-storage.ts index 79d46141..a491a1c6 100644 --- a/client/lib/demo-storage.ts +++ b/client/lib/demo-storage.ts @@ -1,74 +1,74 @@ // Demo storage service - simulates backend functionality using localStorage -import type { UserProfile, Project, Achievement } from './database.types'; -import { aethexToast } from './aethex-toast'; +import type { UserProfile, Project, Achievement } from "./database.types"; +import { aethexToast } from "./aethex-toast"; const STORAGE_KEYS = { - USER_PROFILE: 'aethex_demo_user_profile', - PROJECTS: 'aethex_demo_projects', - ACHIEVEMENTS: 'aethex_demo_achievements', - NOTIFICATIONS: 'aethex_demo_notifications', - INTERESTS: 'aethex_demo_interests', + USER_PROFILE: "aethex_demo_user_profile", + PROJECTS: "aethex_demo_projects", + ACHIEVEMENTS: "aethex_demo_achievements", + NOTIFICATIONS: "aethex_demo_notifications", + INTERESTS: "aethex_demo_interests", }; // Demo user data const DEMO_USER_PROFILE: Partial = { - id: 'demo-user-123', - username: 'demo_developer', - full_name: 'Demo Developer', - user_type: 'game_developer', - experience_level: 'intermediate', + id: "demo-user-123", + username: "demo_developer", + full_name: "Demo Developer", + user_type: "game_developer", + experience_level: "intermediate", total_xp: 1250, level: 5, - bio: 'Passionate game developer exploring the AeThex ecosystem', - location: 'Digital Realm', - github_url: 'https://github.com/demo-developer', - twitter_url: 'https://twitter.com/demo_dev', + bio: "Passionate game developer exploring the AeThex ecosystem", + location: "Digital Realm", + github_url: "https://github.com/demo-developer", + twitter_url: "https://twitter.com/demo_dev", created_at: new Date().toISOString(), updated_at: new Date().toISOString(), }; const DEMO_PROJECTS: Project[] = [ { - id: 'proj-1', - user_id: 'demo-user-123', - title: 'Quantum Quest', - description: 'A sci-fi adventure game built with AeThex Engine', - status: 'in_progress', - technologies: ['AeThex Engine', 'TypeScript', 'WebGL'], - github_url: 'https://github.com/demo/quantum-quest', - demo_url: 'https://quantum-quest-demo.com', + id: "proj-1", + user_id: "demo-user-123", + title: "Quantum Quest", + description: "A sci-fi adventure game built with AeThex Engine", + status: "in_progress", + technologies: ["AeThex Engine", "TypeScript", "WebGL"], + github_url: "https://github.com/demo/quantum-quest", + demo_url: "https://quantum-quest-demo.com", image_url: null, - start_date: '2024-01-15', + start_date: "2024-01-15", end_date: null, created_at: new Date().toISOString(), updated_at: new Date().toISOString(), }, { - id: 'proj-2', - user_id: 'demo-user-123', - title: 'Neon Runner', - description: 'Fast-paced endless runner with cyberpunk aesthetics', - status: 'completed', - technologies: ['AeThex Engine', 'JavaScript', 'CSS3'], - github_url: 'https://github.com/demo/neon-runner', - demo_url: 'https://neon-runner-demo.com', + id: "proj-2", + user_id: "demo-user-123", + title: "Neon Runner", + description: "Fast-paced endless runner with cyberpunk aesthetics", + status: "completed", + technologies: ["AeThex Engine", "JavaScript", "CSS3"], + github_url: "https://github.com/demo/neon-runner", + demo_url: "https://neon-runner-demo.com", image_url: null, - start_date: '2023-08-01', - end_date: '2023-12-15', + start_date: "2023-08-01", + end_date: "2023-12-15", created_at: new Date().toISOString(), updated_at: new Date().toISOString(), }, { - id: 'proj-3', - user_id: 'demo-user-123', - title: 'Pixel Physics', - description: 'Educational physics simulation game', - status: 'planning', - technologies: ['AeThex Engine', 'React', 'Physics Engine'], + id: "proj-3", + user_id: "demo-user-123", + title: "Pixel Physics", + description: "Educational physics simulation game", + status: "planning", + technologies: ["AeThex Engine", "React", "Physics Engine"], github_url: null, demo_url: null, image_url: null, - start_date: '2024-03-01', + start_date: "2024-03-01", end_date: null, created_at: new Date().toISOString(), updated_at: new Date().toISOString(), @@ -77,39 +77,39 @@ const DEMO_PROJECTS: Project[] = [ const DEMO_ACHIEVEMENTS: Achievement[] = [ { - id: 'ach-1', - name: 'Welcome to AeThex', - description: 'Complete your profile setup', - icon: '🎉', + id: "ach-1", + name: "Welcome to AeThex", + description: "Complete your profile setup", + icon: "🎉", xp_reward: 100, - badge_color: '#10B981', + badge_color: "#10B981", created_at: new Date().toISOString(), }, { - id: 'ach-2', - name: 'First Project', - description: 'Create your first project', - icon: '🚀', + id: "ach-2", + name: "First Project", + description: "Create your first project", + icon: "🚀", xp_reward: 150, - badge_color: '#3B82F6', + badge_color: "#3B82F6", created_at: new Date().toISOString(), }, { - id: 'ach-3', - name: 'Community Contributor', - description: 'Make your first community post', - icon: '💬', + id: "ach-3", + name: "Community Contributor", + description: "Make your first community post", + icon: "💬", xp_reward: 75, - badge_color: '#8B5CF6', + badge_color: "#8B5CF6", created_at: new Date().toISOString(), }, { - id: 'ach-4', - name: 'Experienced Developer', - description: 'Complete 5 projects', - icon: '👨‍💻', + id: "ach-4", + name: "Experienced Developer", + description: "Complete 5 projects", + icon: "👨‍💻", xp_reward: 300, - badge_color: '#EF4444', + badge_color: "#EF4444", created_at: new Date().toISOString(), }, ]; @@ -123,7 +123,11 @@ export class DemoStorageService { static updateUserProfile(updates: Partial): UserProfile { const current = this.getUserProfile() || DEMO_USER_PROFILE; - const updated = { ...current, ...updates, updated_at: new Date().toISOString() }; + const updated = { + ...current, + ...updates, + updated_at: new Date().toISOString(), + }; localStorage.setItem(STORAGE_KEYS.USER_PROFILE, JSON.stringify(updated)); return updated as UserProfile; } @@ -134,7 +138,9 @@ export class DemoStorageService { return stored ? JSON.parse(stored) : DEMO_PROJECTS; } - static createProject(project: Omit): Project { + static createProject( + project: Omit, + ): Project { const projects = this.getUserProjects(); const newProject: Project = { ...project, @@ -147,19 +153,26 @@ export class DemoStorageService { return newProject; } - static updateProject(projectId: string, updates: Partial): Project | null { + static updateProject( + projectId: string, + updates: Partial, + ): Project | null { const projects = this.getUserProjects(); - const index = projects.findIndex(p => p.id === projectId); + const index = projects.findIndex((p) => p.id === projectId); if (index === -1) return null; - projects[index] = { ...projects[index], ...updates, updated_at: new Date().toISOString() }; + projects[index] = { + ...projects[index], + ...updates, + updated_at: new Date().toISOString(), + }; localStorage.setItem(STORAGE_KEYS.PROJECTS, JSON.stringify(projects)); return projects[index]; } static deleteProject(projectId: string): boolean { const projects = this.getUserProjects(); - const filtered = projects.filter(p => p.id !== projectId); + const filtered = projects.filter((p) => p.id !== projectId); localStorage.setItem(STORAGE_KEYS.PROJECTS, JSON.stringify(filtered)); return filtered.length < projects.length; } @@ -171,22 +184,25 @@ export class DemoStorageService { static getUserAchievements(): Achievement[] { const stored = localStorage.getItem(STORAGE_KEYS.ACHIEVEMENTS); - const earnedIds = stored ? JSON.parse(stored) : ['ach-1', 'ach-2', 'ach-3']; - return DEMO_ACHIEVEMENTS.filter(ach => earnedIds.includes(ach.id)); + const earnedIds = stored ? JSON.parse(stored) : ["ach-1", "ach-2", "ach-3"]; + return DEMO_ACHIEVEMENTS.filter((ach) => earnedIds.includes(ach.id)); } static awardAchievement(achievementId: string): void { const stored = localStorage.getItem(STORAGE_KEYS.ACHIEVEMENTS); - const earnedIds = stored ? JSON.parse(stored) : ['ach-1', 'ach-2', 'ach-3']; - + const earnedIds = stored ? JSON.parse(stored) : ["ach-1", "ach-2", "ach-3"]; + if (!earnedIds.includes(achievementId)) { earnedIds.push(achievementId); - localStorage.setItem(STORAGE_KEYS.ACHIEVEMENTS, JSON.stringify(earnedIds)); - - const achievement = DEMO_ACHIEVEMENTS.find(a => a.id === achievementId); + localStorage.setItem( + STORAGE_KEYS.ACHIEVEMENTS, + JSON.stringify(earnedIds), + ); + + const achievement = DEMO_ACHIEVEMENTS.find((a) => a.id === achievementId); if (achievement) { aethexToast.aethex({ - title: 'Achievement Unlocked!', + title: "Achievement Unlocked!", description: `${achievement.icon} ${achievement.name} - ${achievement.description}`, duration: 8000, }); @@ -197,7 +213,9 @@ export class DemoStorageService { // Interests Management static getUserInterests(): string[] { const stored = localStorage.getItem(STORAGE_KEYS.INTERESTS); - return stored ? JSON.parse(stored) : ['Game Development', 'AI/ML', 'Web3', 'Mobile Apps']; + return stored + ? JSON.parse(stored) + : ["Game Development", "AI/ML", "Web3", "Mobile Apps"]; } static updateUserInterests(interests: string[]): void { @@ -208,22 +226,34 @@ export class DemoStorageService { static initializeDemoData(): void { // Only initialize if no data exists if (!localStorage.getItem(STORAGE_KEYS.USER_PROFILE)) { - localStorage.setItem(STORAGE_KEYS.USER_PROFILE, JSON.stringify(DEMO_USER_PROFILE)); + localStorage.setItem( + STORAGE_KEYS.USER_PROFILE, + JSON.stringify(DEMO_USER_PROFILE), + ); } if (!localStorage.getItem(STORAGE_KEYS.PROJECTS)) { - localStorage.setItem(STORAGE_KEYS.PROJECTS, JSON.stringify(DEMO_PROJECTS)); + localStorage.setItem( + STORAGE_KEYS.PROJECTS, + JSON.stringify(DEMO_PROJECTS), + ); } if (!localStorage.getItem(STORAGE_KEYS.ACHIEVEMENTS)) { - localStorage.setItem(STORAGE_KEYS.ACHIEVEMENTS, JSON.stringify(['ach-1', 'ach-2', 'ach-3'])); + localStorage.setItem( + STORAGE_KEYS.ACHIEVEMENTS, + JSON.stringify(["ach-1", "ach-2", "ach-3"]), + ); } if (!localStorage.getItem(STORAGE_KEYS.INTERESTS)) { - localStorage.setItem(STORAGE_KEYS.INTERESTS, JSON.stringify(['Game Development', 'AI/ML', 'Web3', 'Mobile Apps'])); + localStorage.setItem( + STORAGE_KEYS.INTERESTS, + JSON.stringify(["Game Development", "AI/ML", "Web3", "Mobile Apps"]), + ); } } // Clear all demo data static clearDemoData(): void { - Object.values(STORAGE_KEYS).forEach(key => { + Object.values(STORAGE_KEYS).forEach((key) => { localStorage.removeItem(key); }); } diff --git a/client/lib/supabase-service.ts b/client/lib/supabase-service.ts index 98a56ff0..5712617c 100644 --- a/client/lib/supabase-service.ts +++ b/client/lib/supabase-service.ts @@ -1,27 +1,36 @@ -import { supabase } from './supabase'; -import type { Database, UserProfile, Project, Achievement, CommunityPost } from './database.types'; +import { supabase } from "./supabase"; +import type { + Database, + UserProfile, + Project, + Achievement, + CommunityPost, +} from "./database.types"; // User Profile Services export const userProfileService = { async getProfile(userId: string): Promise { const { data, error } = await supabase - .from('user_profiles') - .select('*') - .eq('id', userId) + .from("user_profiles") + .select("*") + .eq("id", userId) .single(); - if (error && error.code !== 'PGRST116') { + if (error && error.code !== "PGRST116") { throw error; } return data; }, - async updateProfile(userId: string, updates: Partial): Promise { + async updateProfile( + userId: string, + updates: Partial, + ): Promise { const { data, error } = await supabase - .from('user_profiles') + .from("user_profiles") .update(updates) - .eq('id', userId) + .eq("id", userId) .select() .single(); @@ -29,9 +38,11 @@ export const userProfileService = { return data; }, - async createProfile(profile: Omit): Promise { + async createProfile( + profile: Omit, + ): Promise { const { data, error } = await supabase - .from('user_profiles') + .from("user_profiles") .insert(profile) .select() .single(); @@ -41,13 +52,13 @@ export const userProfileService = { }, async addInterests(userId: string, interests: string[]): Promise { - const interestRows = interests.map(interest => ({ + const interestRows = interests.map((interest) => ({ user_id: userId, interest, })); const { error } = await supabase - .from('user_interests') + .from("user_interests") .insert(interestRows); if (error) throw error; @@ -55,12 +66,12 @@ export const userProfileService = { async getUserInterests(userId: string): Promise { const { data, error } = await supabase - .from('user_interests') - .select('interest') - .eq('user_id', userId); + .from("user_interests") + .select("interest") + .eq("user_id", userId); if (error) throw error; - return data.map(item => item.interest); + return data.map((item) => item.interest); }, }; @@ -68,18 +79,20 @@ export const userProfileService = { export const projectService = { async getUserProjects(userId: string): Promise { const { data, error } = await supabase - .from('projects') - .select('*') - .eq('user_id', userId) - .order('created_at', { ascending: false }); + .from("projects") + .select("*") + .eq("user_id", userId) + .order("created_at", { ascending: false }); if (error) throw error; return data; }, - async createProject(project: Omit): Promise { + async createProject( + project: Omit, + ): Promise { const { data, error } = await supabase - .from('projects') + .from("projects") .insert(project) .select() .single(); @@ -88,11 +101,14 @@ export const projectService = { return data; }, - async updateProject(projectId: string, updates: Partial): Promise { + async updateProject( + projectId: string, + updates: Partial, + ): Promise { const { data, error } = await supabase - .from('projects') + .from("projects") .update(updates) - .eq('id', projectId) + .eq("id", projectId) .select() .single(); @@ -102,26 +118,28 @@ export const projectService = { async deleteProject(projectId: string): Promise { const { error } = await supabase - .from('projects') + .from("projects") .delete() - .eq('id', projectId); + .eq("id", projectId); if (error) throw error; }, async getAllProjects(limit = 10): Promise { const { data, error } = await supabase - .from('projects') - .select(` + .from("projects") + .select( + ` *, user_profiles ( username, full_name, avatar_url ) - `) - .eq('status', 'completed') - .order('created_at', { ascending: false }) + `, + ) + .eq("status", "completed") + .order("created_at", { ascending: false }) .limit(limit); if (error) throw error; @@ -133,9 +151,9 @@ export const projectService = { export const achievementService = { async getAllAchievements(): Promise { const { data, error } = await supabase - .from('achievements') - .select('*') - .order('xp_reward', { ascending: false }); + .from("achievements") + .select("*") + .order("xp_reward", { ascending: false }); if (error) throw error; return data; @@ -143,27 +161,30 @@ export const achievementService = { async getUserAchievements(userId: string): Promise { const { data, error } = await supabase - .from('user_achievements') - .select(` + .from("user_achievements") + .select( + ` earned_at, achievements (*) - `) - .eq('user_id', userId) - .order('earned_at', { ascending: false }); + `, + ) + .eq("user_id", userId) + .order("earned_at", { ascending: false }); if (error) throw error; - return data.map(item => item.achievements).filter(Boolean) as Achievement[]; + return data + .map((item) => item.achievements) + .filter(Boolean) as Achievement[]; }, async awardAchievement(userId: string, achievementId: string): Promise { - const { error } = await supabase - .from('user_achievements') - .insert({ - user_id: userId, - achievement_id: achievementId, - }); + const { error } = await supabase.from("user_achievements").insert({ + user_id: userId, + achievement_id: achievementId, + }); - if (error && error.code !== '23505') { // Ignore duplicate key error + if (error && error.code !== "23505") { + // Ignore duplicate key error throw error; } }, @@ -172,14 +193,16 @@ export const achievementService = { // Check for various achievement conditions const profile = await userProfileService.getProfile(userId); const projects = await projectService.getUserProjects(userId); - + if (!profile) return; const achievements = await this.getAllAchievements(); // Welcome achievement if (profile.full_name && profile.user_type) { - const welcomeAchievement = achievements.find(a => a.name === 'Welcome to AeThex'); + const welcomeAchievement = achievements.find( + (a) => a.name === "Welcome to AeThex", + ); if (welcomeAchievement) { await this.awardAchievement(userId, welcomeAchievement.id); } @@ -187,16 +210,20 @@ export const achievementService = { // First project achievement if (projects.length >= 1) { - const firstProjectAchievement = achievements.find(a => a.name === 'First Project'); + const firstProjectAchievement = achievements.find( + (a) => a.name === "First Project", + ); if (firstProjectAchievement) { await this.awardAchievement(userId, firstProjectAchievement.id); } } // Experienced developer achievement - const completedProjects = projects.filter(p => p.status === 'completed'); + const completedProjects = projects.filter((p) => p.status === "completed"); if (completedProjects.length >= 5) { - const experiencedAchievement = achievements.find(a => a.name === 'Experienced Developer'); + const experiencedAchievement = achievements.find( + (a) => a.name === "Experienced Developer", + ); if (experiencedAchievement) { await this.awardAchievement(userId, experiencedAchievement.id); } @@ -208,26 +235,33 @@ export const achievementService = { export const communityService = { async getPosts(limit = 10): Promise { const { data, error } = await supabase - .from('community_posts') - .select(` + .from("community_posts") + .select( + ` *, user_profiles ( username, full_name, avatar_url ) - `) - .eq('is_published', true) - .order('created_at', { ascending: false }) + `, + ) + .eq("is_published", true) + .order("created_at", { ascending: false }) .limit(limit); if (error) throw error; return data; }, - async createPost(post: Omit): Promise { + async createPost( + post: Omit< + CommunityPost, + "id" | "created_at" | "updated_at" | "likes_count" | "comments_count" + >, + ): Promise { const { data, error } = await supabase - .from('community_posts') + .from("community_posts") .insert(post) .select() .single(); @@ -238,10 +272,10 @@ export const communityService = { async getUserPosts(userId: string): Promise { const { data, error } = await supabase - .from('community_posts') - .select('*') - .eq('author_id', userId) - .order('created_at', { ascending: false }); + .from("community_posts") + .select("*") + .eq("author_id", userId) + .order("created_at", { ascending: false }); if (error) throw error; return data; @@ -252,10 +286,10 @@ export const communityService = { export const notificationService = { async getUserNotifications(userId: string): Promise { const { data, error } = await supabase - .from('notifications') - .select('*') - .eq('user_id', userId) - .order('created_at', { ascending: false }) + .from("notifications") + .select("*") + .eq("user_id", userId) + .order("created_at", { ascending: false }) .limit(10); if (error) throw error; @@ -264,22 +298,25 @@ export const notificationService = { async markAsRead(notificationId: string): Promise { const { error } = await supabase - .from('notifications') + .from("notifications") .update({ read: true }) - .eq('id', notificationId); + .eq("id", notificationId); if (error) throw error; }, - async createNotification(userId: string, title: string, message?: string, type = 'info'): Promise { - const { error } = await supabase - .from('notifications') - .insert({ - user_id: userId, - title, - message, - type, - }); + async createNotification( + userId: string, + title: string, + message?: string, + type = "info", + ): Promise { + const { error } = await supabase.from("notifications").insert({ + user_id: userId, + title, + message, + type, + }); if (error) throw error; }, @@ -287,34 +324,37 @@ export const notificationService = { // Real-time subscriptions export const realtimeService = { - subscribeToUserNotifications(userId: string, callback: (notification: any) => void) { + subscribeToUserNotifications( + userId: string, + callback: (notification: any) => void, + ) { return supabase .channel(`notifications:${userId}`) .on( - 'postgres_changes', + "postgres_changes", { - event: 'INSERT', - schema: 'public', - table: 'notifications', + event: "INSERT", + schema: "public", + table: "notifications", filter: `user_id=eq.${userId}`, }, - callback + callback, ) .subscribe(); }, subscribeToCommunityPosts(callback: (post: any) => void) { return supabase - .channel('community_posts') + .channel("community_posts") .on( - 'postgres_changes', + "postgres_changes", { - event: 'INSERT', - schema: 'public', - table: 'community_posts', - filter: 'is_published=eq.true', + event: "INSERT", + schema: "public", + table: "community_posts", + filter: "is_published=eq.true", }, - callback + callback, ) .subscribe(); }, diff --git a/client/lib/supabase.ts b/client/lib/supabase.ts index fa2207b0..f960a6e0 100644 --- a/client/lib/supabase.ts +++ b/client/lib/supabase.ts @@ -1,17 +1,20 @@ -import { createClient } from '@supabase/supabase-js'; -import type { Database } from './database.types'; +import { createClient } from "@supabase/supabase-js"; +import type { Database } from "./database.types"; const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY; // Check if Supabase is configured -export const isSupabaseConfigured = !!(supabaseUrl && supabaseAnonKey && - supabaseUrl !== 'https://your-project-ref.supabase.co' && - supabaseAnonKey !== 'your-anon-key-here'); +export const isSupabaseConfigured = !!( + supabaseUrl && + supabaseAnonKey && + supabaseUrl !== "https://your-project-ref.supabase.co" && + supabaseAnonKey !== "your-anon-key-here" +); // Use fallback values for development if not configured -const fallbackUrl = 'https://demo.supabase.co'; -const fallbackKey = 'demo-key'; +const fallbackUrl = "https://demo.supabase.co"; +const fallbackKey = "demo-key"; export const supabase = createClient( supabaseUrl || fallbackUrl, @@ -20,9 +23,9 @@ export const supabase = createClient( auth: { autoRefreshToken: isSupabaseConfigured, persistSession: isSupabaseConfigured, - detectSessionInUrl: isSupabaseConfigured - } - } + detectSessionInUrl: isSupabaseConfigured, + }, + }, ); // Auth helpers diff --git a/client/pages/Login.tsx b/client/pages/Login.tsx index b4d7c07e..c8b925a0 100644 --- a/client/pages/Login.tsx +++ b/client/pages/Login.tsx @@ -56,7 +56,8 @@ export default function Login() { }); aethexToast.success({ title: "Account created!", - description: "Please check your email to verify your account, then sign in." + description: + "Please check your email to verify your account, then sign in.", }); setIsSignUp(false); } else { @@ -70,7 +71,7 @@ export default function Login() { } }; - const handleSocialLogin = async (provider: 'github' | 'google') => { + const handleSocialLogin = async (provider: "github" | "google") => { setIsLoading(true); try { await signInWithOAuth(provider); @@ -125,8 +126,7 @@ export default function Login() { {isSignUp ? "Create your AeThex account to get started" - : "Sign in to your AeThex account to access the dashboard" - } + : "Sign in to your AeThex account to access the dashboard"} setPassword(e.target.value)} - placeholder={isSignUp ? "Create a password" : "Enter your password"} + placeholder={ + isSignUp ? "Create a password" : "Enter your password" + } className="pl-10 bg-background/50 border-border/50 focus:border-aethex-400" required minLength={isSignUp ? 6 : undefined} @@ -255,7 +257,9 @@ export default function Login() {