Prettier format pending files

This commit is contained in:
Builder.io 2025-08-06 00:35:27 +00:00
parent ad88034b7a
commit 7097f8408b
10 changed files with 877 additions and 718 deletions

View file

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

View file

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

View file

@ -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")}
>
<ExternalLink className="h-3 w-3 mr-1" />
Try Login

View file

@ -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<void>;
signUp: (email: string, password: string, userData?: Partial<AethexUserProfile>) => Promise<void>;
signInWithOAuth: (provider: 'github' | 'google') => Promise<void>;
signUp: (
email: string,
password: string,
userData?: Partial<AethexUserProfile>,
) => Promise<void>;
signInWithOAuth: (provider: "github" | "google") => Promise<void>;
signOut: () => Promise<void>;
updateProfile: (updates: Partial<AethexUserProfile>) => Promise<void>;
}
@ -23,12 +31,14 @@ const AuthContext = createContext<AuthContextType | undefined>(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<User | null>(null);
const [profile, setProfile] = useState<AethexUserProfile | null>(null);
const [session, setSession] = useState<Session | null>(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<AethexUserProfile | null> => {
const fetchUserProfile = async (
userId: string,
): Promise<AethexUserProfile | null> => {
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<AethexUserProfile>) => {
const signUp = async (
email: string,
password: string,
userData?: Partial<AethexUserProfile>,
) => {
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<AethexUserProfile>) => {
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 (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};

View file

@ -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<AethexUserProfile | null> {
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<AethexUserProfile>): Promise<AethexUserProfile | null> {
async updateProfile(
userId: string,
updates: Partial<AethexUserProfile>,
): Promise<AethexUserProfile | null> {
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<AethexUserProfile>): Promise<AethexUserProfile | null> {
async createInitialProfile(
userId: string,
profileData: Partial<AethexUserProfile>,
): Promise<AethexUserProfile | null> {
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<void> {
// 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<string[]> {
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<AethexProject[]> {
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<AethexProject, 'id' | 'created_at' | 'updated_at'>): Promise<AethexProject | null> {
async createProject(
project: Omit<AethexProject, "id" | "created_at" | "updated_at">,
): Promise<AethexProject | null> {
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<AethexProject>): Promise<AethexProject | null> {
async updateProject(
projectId: string,
updates: Partial<AethexProject>,
): Promise<AethexProject | null> {
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<boolean> {
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<AethexProject[]> {
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<AethexAchievement[]> {
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<AethexAchievement[]> {
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<void> {
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<void> {
// 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,21 +346,21 @@ 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) {
@ -357,9 +372,9 @@ export const aethexAchievementService = {
async checkAndAwardOnboardingAchievement(userId: string): Promise<void> {
const { data: achievement } = await supabase
.from('achievements')
.select('id')
.eq('name', 'AeThex Explorer')
.from("achievements")
.select("id")
.eq("name", "AeThex Explorer")
.single();
if (achievement) {
@ -373,9 +388,9 @@ export const aethexAchievementService = {
// 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<any[]> {
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<void> {
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<void> {
await supabase
.from('notifications')
.insert({
user_id: userId,
type,
data,
});
async createNotification(
userId: string,
type: string,
data: any,
): Promise<void> {
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();
},

View file

@ -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"];

View file

@ -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<UserProfile> = {
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>): 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, 'id' | 'created_at' | 'updated_at'>): Project {
static createProject(
project: Omit<Project, "id" | "created_at" | "updated_at">,
): 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>): Project | null {
static updateProject(
projectId: string,
updates: Partial<Project>,
): 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));
localStorage.setItem(
STORAGE_KEYS.ACHIEVEMENTS,
JSON.stringify(earnedIds),
);
const achievement = DEMO_ACHIEVEMENTS.find(a => a.id === achievementId);
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);
});
}

View file

@ -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<UserProfile | null> {
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<UserProfile>): Promise<UserProfile> {
async updateProfile(
userId: string,
updates: Partial<UserProfile>,
): Promise<UserProfile> {
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<UserProfile, 'created_at' | 'updated_at'>): Promise<UserProfile> {
async createProfile(
profile: Omit<UserProfile, "created_at" | "updated_at">,
): Promise<UserProfile> {
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<void> {
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<string[]> {
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<Project[]> {
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<Project, 'id' | 'created_at' | 'updated_at'>): Promise<Project> {
async createProject(
project: Omit<Project, "id" | "created_at" | "updated_at">,
): Promise<Project> {
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<Project>): Promise<Project> {
async updateProject(
projectId: string,
updates: Partial<Project>,
): Promise<Project> {
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<void> {
const { error } = await supabase
.from('projects')
.from("projects")
.delete()
.eq('id', projectId);
.eq("id", projectId);
if (error) throw error;
},
async getAllProjects(limit = 10): Promise<Project[]> {
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<Achievement[]> {
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<Achievement[]> {
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<void> {
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;
}
},
@ -179,7 +200,9 @@ export const achievementService = {
// 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<CommunityPost[]> {
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<CommunityPost, 'id' | 'created_at' | 'updated_at' | 'likes_count' | 'comments_count'>): Promise<CommunityPost> {
async createPost(
post: Omit<
CommunityPost,
"id" | "created_at" | "updated_at" | "likes_count" | "comments_count"
>,
): Promise<CommunityPost> {
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<CommunityPost[]> {
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<any[]> {
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<void> {
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<void> {
const { error } = await supabase
.from('notifications')
.insert({
user_id: userId,
title,
message,
type,
});
async createNotification(
userId: string,
title: string,
message?: string,
type = "info",
): Promise<void> {
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();
},

View file

@ -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<Database>(
supabaseUrl || fallbackUrl,
@ -20,9 +23,9 @@ export const supabase = createClient<Database>(
auth: {
autoRefreshToken: isSupabaseConfigured,
persistSession: isSupabaseConfigured,
detectSessionInUrl: isSupabaseConfigured
}
}
detectSessionInUrl: isSupabaseConfigured,
},
},
);
// Auth helpers

View file

@ -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() {
<CardDescription>
{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"}
</CardDescription>
</div>
<Badge
@ -221,7 +221,9 @@ export default function Login() {
type="password"
value={password}
onChange={(e) => 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() {
<Button
type="submit"
className="w-full bg-gradient-to-r from-aethex-500 to-neon-blue hover:from-aethex-600 hover:to-neon-blue/90 hover-lift interactive-scale glow-blue"
disabled={!email || !password || (isSignUp && !fullName) || isLoading}
disabled={
!email || !password || (isSignUp && !fullName) || isLoading
}
>
<LogIn className="h-4 w-4 mr-2" />
{isSignUp ? "Create Account" : "Sign In to Dashboard"}
@ -265,7 +269,9 @@ export default function Login() {
<div className="text-center pt-4">
<p className="text-sm text-muted-foreground">
{isSignUp ? "Already have an account?" : "Don't have an account?"}{" "}
{isSignUp
? "Already have an account?"
: "Don't have an account?"}{" "}
<button
onClick={() => setIsSignUp(!isSignUp)}
className="text-aethex-400 hover:underline font-medium"